From f67b8df3ebdba2d398b9cce686b7c644adffff08 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Sat, 7 May 2011 00:02:01 +0200 Subject: library split --- src/widgets/platforms/mac/qapplication_mac.mm | 3134 ++++++++++ src/widgets/platforms/mac/qclipboard_mac.cpp | 634 ++ src/widgets/platforms/mac/qcocoaapplication_mac.mm | 222 + .../platforms/mac/qcocoaapplication_mac_p.h | 117 + .../platforms/mac/qcocoaapplicationdelegate_mac.mm | 354 ++ .../mac/qcocoaapplicationdelegate_mac_p.h | 128 + .../platforms/mac/qcocoaintrospection_mac.mm | 125 + src/widgets/platforms/mac/qcocoaintrospection_p.h | 84 + src/widgets/platforms/mac/qcocoamenuloader_mac.mm | 264 + src/widgets/platforms/mac/qcocoamenuloader_mac_p.h | 95 + src/widgets/platforms/mac/qcocoapanel_mac.mm | 70 + src/widgets/platforms/mac/qcocoapanel_mac_p.h | 83 + .../mac/qcocoasharedwindowmethods_mac_p.h | 610 ++ src/widgets/platforms/mac/qcocoaview_mac.mm | 1388 +++++ src/widgets/platforms/mac/qcocoaview_mac_p.h | 87 + src/widgets/platforms/mac/qcocoawindow_mac.mm | 90 + src/widgets/platforms/mac/qcocoawindow_mac_p.h | 97 + .../mac/qcocoawindowcustomthemeframe_mac.mm | 62 + .../mac/qcocoawindowcustomthemeframe_mac_p.h | 61 + .../platforms/mac/qcocoawindowdelegate_mac.mm | 439 ++ .../platforms/mac/qcocoawindowdelegate_mac_p.h | 110 + src/widgets/platforms/mac/qcolormap_mac.cpp | 111 + src/widgets/platforms/mac/qcursor_mac.mm | 689 +++ src/widgets/platforms/mac/qdesktopwidget_mac.mm | 257 + src/widgets/platforms/mac/qdesktopwidget_mac_p.h | 75 + src/widgets/platforms/mac/qdnd_mac.mm | 753 +++ src/widgets/platforms/mac/qeventdispatcher_mac.mm | 1200 ++++ src/widgets/platforms/mac/qeventdispatcher_mac_p.h | 224 + src/widgets/platforms/mac/qfont_mac.cpp | 165 + src/widgets/platforms/mac/qfontdatabase_mac.cpp | 466 ++ src/widgets/platforms/mac/qfontengine_coretext.mm | 880 +++ src/widgets/platforms/mac/qfontengine_coretext_p.h | 144 + src/widgets/platforms/mac/qfontengine_mac.mm | 1236 ++++ src/widgets/platforms/mac/qfontengine_mac_p.h | 165 + src/widgets/platforms/mac/qkeymapper_mac.cpp | 1023 ++++ src/widgets/platforms/mac/qmacdefines_mac.h | 180 + .../platforms/mac/qmacgesturerecognizer_mac.mm | 272 + .../platforms/mac/qmacgesturerecognizer_mac_p.h | 106 + src/widgets/platforms/mac/qmime_mac.cpp | 1310 ++++ src/widgets/platforms/mac/qmultitouch_mac.mm | 218 + src/widgets/platforms/mac/qmultitouch_mac_p.h | 102 + src/widgets/platforms/mac/qnsframeview_mac_p.h | 154 + src/widgets/platforms/mac/qnsthemeframe_mac_p.h | 246 + src/widgets/platforms/mac/qnstitledframe_mac_p.h | 205 + src/widgets/platforms/mac/qpaintdevice_mac.cpp | 152 + src/widgets/platforms/mac/qpaintengine_mac.cpp | 1751 ++++++ src/widgets/platforms/mac/qpaintengine_mac_p.h | 254 + src/widgets/platforms/mac/qpixmap_mac.cpp | 1195 ++++ src/widgets/platforms/mac/qpixmap_mac_p.h | 134 + src/widgets/platforms/mac/qprintengine_mac.mm | 911 +++ src/widgets/platforms/mac/qprintengine_mac_p.h | 166 + src/widgets/platforms/mac/qprinterinfo_mac.cpp | 120 + src/widgets/platforms/mac/qrawfont_mac.cpp | 83 + src/widgets/platforms/mac/qregion_mac.cpp | 286 + src/widgets/platforms/mac/qsound_mac.mm | 190 + src/widgets/platforms/mac/qt_cocoa_helpers_mac.mm | 1824 ++++++ src/widgets/platforms/mac/qt_cocoa_helpers_mac_p.h | 340 ++ src/widgets/platforms/mac/qt_mac.cpp | 174 + src/widgets/platforms/mac/qt_mac_p.h | 286 + src/widgets/platforms/mac/qtextengine_mac.cpp | 656 ++ src/widgets/platforms/mac/qwidget_mac.mm | 5420 +++++++++++++++++ src/widgets/platforms/s60/qapplication_s60.cpp | 2712 +++++++++ src/widgets/platforms/s60/qclipboard_s60.cpp | 331 ++ src/widgets/platforms/s60/qcolormap_s60.cpp | 107 + src/widgets/platforms/s60/qcursor_s60.cpp | 533 ++ src/widgets/platforms/s60/qdesktopwidget_s60.cpp | 316 + src/widgets/platforms/s60/qdnd_s60.cpp | 359 ++ src/widgets/platforms/s60/qeventdispatcher_s60.cpp | 196 + src/widgets/platforms/s60/qeventdispatcher_s60_p.h | 127 + src/widgets/platforms/s60/qfont_s60.cpp | 136 + src/widgets/platforms/s60/qfontdatabase_s60.cpp | 1091 ++++ src/widgets/platforms/s60/qfontengine_s60.cpp | 569 ++ src/widgets/platforms/s60/qfontengine_s60_p.h | 167 + src/widgets/platforms/s60/qkeymapper_s60.cpp | 258 + src/widgets/platforms/s60/qpaintengine_s60.cpp | 145 + src/widgets/platforms/s60/qpaintengine_s60_p.h | 84 + src/widgets/platforms/s60/qpixmap_s60.cpp | 1040 ++++ src/widgets/platforms/s60/qpixmap_s60_p.h | 141 + src/widgets/platforms/s60/qregion_s60.cpp | 52 + src/widgets/platforms/s60/qsoftkeymanager_s60.cpp | 440 ++ src/widgets/platforms/s60/qsoftkeymanager_s60_p.h | 113 + src/widgets/platforms/s60/qsound_s60.cpp | 224 + src/widgets/platforms/s60/qt_s60_p.h | 625 ++ src/widgets/platforms/s60/qwidget_s60.cpp | 1450 +++++ src/widgets/platforms/win/qapplication_win.cpp | 4243 +++++++++++++ src/widgets/platforms/win/qclipboard_win.cpp | 398 ++ src/widgets/platforms/win/qcolormap_win.cpp | 201 + src/widgets/platforms/win/qcursor_win.cpp | 492 ++ src/widgets/platforms/win/qdesktopwidget_win.cpp | 387 ++ src/widgets/platforms/win/qdnd_win.cpp | 1027 ++++ src/widgets/platforms/win/qfont_win.cpp | 166 + src/widgets/platforms/win/qfontdatabase_win.cpp | 1348 +++++ src/widgets/platforms/win/qfontengine_win.cpp | 1339 +++++ src/widgets/platforms/win/qfontengine_win_p.h | 164 + src/widgets/platforms/win/qguifunctions_wince.cpp | 408 ++ src/widgets/platforms/win/qguifunctions_wince.h | 151 + src/widgets/platforms/win/qkeymapper_win.cpp | 1207 ++++ src/widgets/platforms/win/qmime_win.cpp | 1556 +++++ src/widgets/platforms/win/qole_win.cpp | 255 + src/widgets/platforms/win/qpaintdevice_win.cpp | 62 + src/widgets/platforms/win/qpixmap_win.cpp | 477 ++ src/widgets/platforms/win/qprintengine_win.cpp | 1776 ++++++ src/widgets/platforms/win/qprintengine_win_p.h | 261 + src/widgets/platforms/win/qprinterinfo_win.cpp | 122 + src/widgets/platforms/win/qrawfont_win.cpp | 707 +++ src/widgets/platforms/win/qregion_win.cpp | 149 + src/widgets/platforms/win/qsound_win.cpp | 205 + src/widgets/platforms/win/qwidget_win.cpp | 2139 +++++++ src/widgets/platforms/win/qwidget_wince.cpp | 675 +++ src/widgets/platforms/win/qwindowdefs_win.h | 132 + .../win/qwinnativepangesturerecognizer_win.cpp | 133 + .../win/qwinnativepangesturerecognizer_win_p.h | 80 + src/widgets/platforms/x11/qapplication_x11.cpp | 6239 ++++++++++++++++++++ src/widgets/platforms/x11/qclipboard_x11.cpp | 1539 +++++ src/widgets/platforms/x11/qcolormap_x11.cpp | 670 +++ src/widgets/platforms/x11/qcursor_x11.cpp | 637 ++ src/widgets/platforms/x11/qdesktopwidget_x11.cpp | 406 ++ src/widgets/platforms/x11/qdnd_x11.cpp | 2072 +++++++ src/widgets/platforms/x11/qeventdispatcher_x11.cpp | 191 + src/widgets/platforms/x11/qeventdispatcher_x11_p.h | 86 + src/widgets/platforms/x11/qfont_x11.cpp | 368 ++ src/widgets/platforms/x11/qfontdatabase_x11.cpp | 2146 +++++++ src/widgets/platforms/x11/qfontengine_x11.cpp | 1215 ++++ src/widgets/platforms/x11/qfontengine_x11_p.h | 180 + src/widgets/platforms/x11/qkde.cpp | 176 + src/widgets/platforms/x11/qkde_p.h | 81 + src/widgets/platforms/x11/qkeymapper_x11.cpp | 1869 ++++++ src/widgets/platforms/x11/qkeymapper_x11_p.cpp | 489 ++ src/widgets/platforms/x11/qmotifdnd_x11.cpp | 1031 ++++ src/widgets/platforms/x11/qpaintdevice_x11.cpp | 84 + src/widgets/platforms/x11/qpaintengine_x11.cpp | 2507 ++++++++ src/widgets/platforms/x11/qpaintengine_x11_p.h | 246 + src/widgets/platforms/x11/qpixmap_x11.cpp | 2419 ++++++++ src/widgets/platforms/x11/qpixmap_x11_p.h | 156 + src/widgets/platforms/x11/qregion_x11.cpp | 92 + src/widgets/platforms/x11/qsound_x11.cpp | 296 + src/widgets/platforms/x11/qt_x11_p.h | 757 +++ src/widgets/platforms/x11/qwidget_x11.cpp | 3146 ++++++++++ src/widgets/platforms/x11/qwidgetcreate_x11.cpp | 79 + src/widgets/platforms/x11/qx11embed_x11.cpp | 1808 ++++++ src/widgets/platforms/x11/qx11embed_x11.h | 132 + src/widgets/platforms/x11/qx11info_x11.cpp | 543 ++ src/widgets/platforms/x11/qx11info_x11.h | 123 + 143 files changed, 95636 insertions(+) create mode 100644 src/widgets/platforms/mac/qapplication_mac.mm create mode 100644 src/widgets/platforms/mac/qclipboard_mac.cpp create mode 100644 src/widgets/platforms/mac/qcocoaapplication_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoaapplication_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoaapplicationdelegate_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoaapplicationdelegate_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoaintrospection_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoaintrospection_p.h create mode 100644 src/widgets/platforms/mac/qcocoamenuloader_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoamenuloader_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoapanel_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoapanel_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoasharedwindowmethods_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoaview_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoaview_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoawindow_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoawindow_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac_p.h create mode 100644 src/widgets/platforms/mac/qcocoawindowdelegate_mac.mm create mode 100644 src/widgets/platforms/mac/qcocoawindowdelegate_mac_p.h create mode 100644 src/widgets/platforms/mac/qcolormap_mac.cpp create mode 100644 src/widgets/platforms/mac/qcursor_mac.mm create mode 100644 src/widgets/platforms/mac/qdesktopwidget_mac.mm create mode 100644 src/widgets/platforms/mac/qdesktopwidget_mac_p.h create mode 100644 src/widgets/platforms/mac/qdnd_mac.mm create mode 100644 src/widgets/platforms/mac/qeventdispatcher_mac.mm create mode 100644 src/widgets/platforms/mac/qeventdispatcher_mac_p.h create mode 100644 src/widgets/platforms/mac/qfont_mac.cpp create mode 100644 src/widgets/platforms/mac/qfontdatabase_mac.cpp create mode 100644 src/widgets/platforms/mac/qfontengine_coretext.mm create mode 100644 src/widgets/platforms/mac/qfontengine_coretext_p.h create mode 100644 src/widgets/platforms/mac/qfontengine_mac.mm create mode 100644 src/widgets/platforms/mac/qfontengine_mac_p.h create mode 100644 src/widgets/platforms/mac/qkeymapper_mac.cpp create mode 100644 src/widgets/platforms/mac/qmacdefines_mac.h create mode 100644 src/widgets/platforms/mac/qmacgesturerecognizer_mac.mm create mode 100644 src/widgets/platforms/mac/qmacgesturerecognizer_mac_p.h create mode 100644 src/widgets/platforms/mac/qmime_mac.cpp create mode 100644 src/widgets/platforms/mac/qmultitouch_mac.mm create mode 100644 src/widgets/platforms/mac/qmultitouch_mac_p.h create mode 100644 src/widgets/platforms/mac/qnsframeview_mac_p.h create mode 100644 src/widgets/platforms/mac/qnsthemeframe_mac_p.h create mode 100644 src/widgets/platforms/mac/qnstitledframe_mac_p.h create mode 100644 src/widgets/platforms/mac/qpaintdevice_mac.cpp create mode 100644 src/widgets/platforms/mac/qpaintengine_mac.cpp create mode 100644 src/widgets/platforms/mac/qpaintengine_mac_p.h create mode 100644 src/widgets/platforms/mac/qpixmap_mac.cpp create mode 100644 src/widgets/platforms/mac/qpixmap_mac_p.h create mode 100644 src/widgets/platforms/mac/qprintengine_mac.mm create mode 100644 src/widgets/platforms/mac/qprintengine_mac_p.h create mode 100644 src/widgets/platforms/mac/qprinterinfo_mac.cpp create mode 100644 src/widgets/platforms/mac/qrawfont_mac.cpp create mode 100644 src/widgets/platforms/mac/qregion_mac.cpp create mode 100644 src/widgets/platforms/mac/qsound_mac.mm create mode 100644 src/widgets/platforms/mac/qt_cocoa_helpers_mac.mm create mode 100644 src/widgets/platforms/mac/qt_cocoa_helpers_mac_p.h create mode 100644 src/widgets/platforms/mac/qt_mac.cpp create mode 100644 src/widgets/platforms/mac/qt_mac_p.h create mode 100644 src/widgets/platforms/mac/qtextengine_mac.cpp create mode 100644 src/widgets/platforms/mac/qwidget_mac.mm create mode 100644 src/widgets/platforms/s60/qapplication_s60.cpp create mode 100644 src/widgets/platforms/s60/qclipboard_s60.cpp create mode 100644 src/widgets/platforms/s60/qcolormap_s60.cpp create mode 100644 src/widgets/platforms/s60/qcursor_s60.cpp create mode 100644 src/widgets/platforms/s60/qdesktopwidget_s60.cpp create mode 100644 src/widgets/platforms/s60/qdnd_s60.cpp create mode 100644 src/widgets/platforms/s60/qeventdispatcher_s60.cpp create mode 100644 src/widgets/platforms/s60/qeventdispatcher_s60_p.h create mode 100644 src/widgets/platforms/s60/qfont_s60.cpp create mode 100644 src/widgets/platforms/s60/qfontdatabase_s60.cpp create mode 100644 src/widgets/platforms/s60/qfontengine_s60.cpp create mode 100644 src/widgets/platforms/s60/qfontengine_s60_p.h create mode 100644 src/widgets/platforms/s60/qkeymapper_s60.cpp create mode 100644 src/widgets/platforms/s60/qpaintengine_s60.cpp create mode 100644 src/widgets/platforms/s60/qpaintengine_s60_p.h create mode 100644 src/widgets/platforms/s60/qpixmap_s60.cpp create mode 100644 src/widgets/platforms/s60/qpixmap_s60_p.h create mode 100644 src/widgets/platforms/s60/qregion_s60.cpp create mode 100644 src/widgets/platforms/s60/qsoftkeymanager_s60.cpp create mode 100644 src/widgets/platforms/s60/qsoftkeymanager_s60_p.h create mode 100644 src/widgets/platforms/s60/qsound_s60.cpp create mode 100644 src/widgets/platforms/s60/qt_s60_p.h create mode 100644 src/widgets/platforms/s60/qwidget_s60.cpp create mode 100644 src/widgets/platforms/win/qapplication_win.cpp create mode 100644 src/widgets/platforms/win/qclipboard_win.cpp create mode 100644 src/widgets/platforms/win/qcolormap_win.cpp create mode 100644 src/widgets/platforms/win/qcursor_win.cpp create mode 100644 src/widgets/platforms/win/qdesktopwidget_win.cpp create mode 100644 src/widgets/platforms/win/qdnd_win.cpp create mode 100644 src/widgets/platforms/win/qfont_win.cpp create mode 100644 src/widgets/platforms/win/qfontdatabase_win.cpp create mode 100644 src/widgets/platforms/win/qfontengine_win.cpp create mode 100644 src/widgets/platforms/win/qfontengine_win_p.h create mode 100644 src/widgets/platforms/win/qguifunctions_wince.cpp create mode 100644 src/widgets/platforms/win/qguifunctions_wince.h create mode 100644 src/widgets/platforms/win/qkeymapper_win.cpp create mode 100644 src/widgets/platforms/win/qmime_win.cpp create mode 100644 src/widgets/platforms/win/qole_win.cpp create mode 100644 src/widgets/platforms/win/qpaintdevice_win.cpp create mode 100644 src/widgets/platforms/win/qpixmap_win.cpp create mode 100644 src/widgets/platforms/win/qprintengine_win.cpp create mode 100644 src/widgets/platforms/win/qprintengine_win_p.h create mode 100644 src/widgets/platforms/win/qprinterinfo_win.cpp create mode 100644 src/widgets/platforms/win/qrawfont_win.cpp create mode 100644 src/widgets/platforms/win/qregion_win.cpp create mode 100644 src/widgets/platforms/win/qsound_win.cpp create mode 100644 src/widgets/platforms/win/qwidget_win.cpp create mode 100644 src/widgets/platforms/win/qwidget_wince.cpp create mode 100644 src/widgets/platforms/win/qwindowdefs_win.h create mode 100644 src/widgets/platforms/win/qwinnativepangesturerecognizer_win.cpp create mode 100644 src/widgets/platforms/win/qwinnativepangesturerecognizer_win_p.h create mode 100644 src/widgets/platforms/x11/qapplication_x11.cpp create mode 100644 src/widgets/platforms/x11/qclipboard_x11.cpp create mode 100644 src/widgets/platforms/x11/qcolormap_x11.cpp create mode 100644 src/widgets/platforms/x11/qcursor_x11.cpp create mode 100644 src/widgets/platforms/x11/qdesktopwidget_x11.cpp create mode 100644 src/widgets/platforms/x11/qdnd_x11.cpp create mode 100644 src/widgets/platforms/x11/qeventdispatcher_x11.cpp create mode 100644 src/widgets/platforms/x11/qeventdispatcher_x11_p.h create mode 100644 src/widgets/platforms/x11/qfont_x11.cpp create mode 100644 src/widgets/platforms/x11/qfontdatabase_x11.cpp create mode 100644 src/widgets/platforms/x11/qfontengine_x11.cpp create mode 100644 src/widgets/platforms/x11/qfontengine_x11_p.h create mode 100644 src/widgets/platforms/x11/qkde.cpp create mode 100644 src/widgets/platforms/x11/qkde_p.h create mode 100644 src/widgets/platforms/x11/qkeymapper_x11.cpp create mode 100644 src/widgets/platforms/x11/qkeymapper_x11_p.cpp create mode 100644 src/widgets/platforms/x11/qmotifdnd_x11.cpp create mode 100644 src/widgets/platforms/x11/qpaintdevice_x11.cpp create mode 100644 src/widgets/platforms/x11/qpaintengine_x11.cpp create mode 100644 src/widgets/platforms/x11/qpaintengine_x11_p.h create mode 100644 src/widgets/platforms/x11/qpixmap_x11.cpp create mode 100644 src/widgets/platforms/x11/qpixmap_x11_p.h create mode 100644 src/widgets/platforms/x11/qregion_x11.cpp create mode 100644 src/widgets/platforms/x11/qsound_x11.cpp create mode 100644 src/widgets/platforms/x11/qt_x11_p.h create mode 100644 src/widgets/platforms/x11/qwidget_x11.cpp create mode 100644 src/widgets/platforms/x11/qwidgetcreate_x11.cpp create mode 100644 src/widgets/platforms/x11/qx11embed_x11.cpp create mode 100644 src/widgets/platforms/x11/qx11embed_x11.h create mode 100644 src/widgets/platforms/x11/qx11info_x11.cpp create mode 100644 src/widgets/platforms/x11/qx11info_x11.h (limited to 'src/widgets/platforms') diff --git a/src/widgets/platforms/mac/qapplication_mac.mm b/src/widgets/platforms/mac/qapplication_mac.mm new file mode 100644 index 0000000000..f607a72e92 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qclipboard_mac.cpp b/src/widgets/platforms/mac/qclipboard_mac.cpp new file mode 100644 index 0000000000..4a8bc56e41 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaapplication_mac.mm b/src/widgets/platforms/mac/qcocoaapplication_mac.mm new file mode 100644 index 0000000000..872f31dec7 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaapplication_mac_p.h b/src/widgets/platforms/mac/qcocoaapplication_mac_p.h new file mode 100644 index 0000000000..0c3f5e442d --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaapplicationdelegate_mac.mm b/src/widgets/platforms/mac/qcocoaapplicationdelegate_mac.mm new file mode 100644 index 0000000000..77cd8902c3 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaapplicationdelegate_mac_p.h b/src/widgets/platforms/mac/qcocoaapplicationdelegate_mac_p.h new file mode 100644 index 0000000000..714c046f48 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaintrospection_mac.mm b/src/widgets/platforms/mac/qcocoaintrospection_mac.mm new file mode 100644 index 0000000000..70c893aeec --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaintrospection_p.h b/src/widgets/platforms/mac/qcocoaintrospection_p.h new file mode 100644 index 0000000000..1c7d6ac13c --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoamenuloader_mac.mm b/src/widgets/platforms/mac/qcocoamenuloader_mac.mm new file mode 100644 index 0000000000..71ff011069 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoamenuloader_mac_p.h b/src/widgets/platforms/mac/qcocoamenuloader_mac_p.h new file mode 100644 index 0000000000..cfcc7e00c6 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoapanel_mac.mm b/src/widgets/platforms/mac/qcocoapanel_mac.mm new file mode 100644 index 0000000000..67a12e25f8 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoapanel_mac_p.h b/src/widgets/platforms/mac/qcocoapanel_mac_p.h new file mode 100644 index 0000000000..542615903e --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoasharedwindowmethods_mac_p.h b/src/widgets/platforms/mac/qcocoasharedwindowmethods_mac_p.h new file mode 100644 index 0000000000..ee1115bd4e --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoaview_mac.mm b/src/widgets/platforms/mac/qcocoaview_mac.mm new file mode 100644 index 0000000000..e885d1552c --- /dev/null +++ b/src/widgets/platforms/mac/qcocoaview_mac.mm @@ -0,0 +1,1388 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt 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()) { + 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/widgets/platforms/mac/qcocoaview_mac_p.h b/src/widgets/platforms/mac/qcocoaview_mac_p.h new file mode 100644 index 0000000000..cc79b6705b --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindow_mac.mm b/src/widgets/platforms/mac/qcocoawindow_mac.mm new file mode 100644 index 0000000000..6e5023aaca --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindow_mac_p.h b/src/widgets/platforms/mac/qcocoawindow_mac_p.h new file mode 100644 index 0000000000..d567cab244 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac.mm b/src/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac.mm new file mode 100644 index 0000000000..b761934c01 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac_p.h b/src/widgets/platforms/mac/qcocoawindowcustomthemeframe_mac_p.h new file mode 100644 index 0000000000..09b40875f6 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindowdelegate_mac.mm b/src/widgets/platforms/mac/qcocoawindowdelegate_mac.mm new file mode 100644 index 0000000000..1faf068a12 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcocoawindowdelegate_mac_p.h b/src/widgets/platforms/mac/qcocoawindowdelegate_mac_p.h new file mode 100644 index 0000000000..638ce2df9a --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qcolormap_mac.cpp b/src/widgets/platforms/mac/qcolormap_mac.cpp new file mode 100644 index 0000000000..28589f41b8 --- /dev/null +++ b/src/widgets/platforms/mac/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 QColormap::colormap() const +{ return QVector(); } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/mac/qcursor_mac.mm b/src/widgets/platforms/mac/qcursor_mac.mm new file mode 100644 index 0000000000..0afa3ee4f0 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qdesktopwidget_mac.mm b/src/widgets/platforms/mac/qdesktopwidget_mac.mm new file mode 100644 index 0000000000..0b529c9843 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qdesktopwidget_mac_p.h b/src/widgets/platforms/mac/qdesktopwidget_mac_p.h new file mode 100644 index 0000000000..ac638ea7d8 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qdnd_mac.mm b/src/widgets/platforms/mac/qdnd_mac.mm new file mode 100644 index 0000000000..3af2ba007c --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qeventdispatcher_mac.mm b/src/widgets/platforms/mac/qeventdispatcher_mac.mm new file mode 100644 index 0000000000..677a7368b4 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#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(const_cast(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::registeredTimers(QObject *object) const +{ + if (!object) { + qWarning("QEventDispatcherMac:registeredTimers: invalid argument"); + return QList(); + } + + QList 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(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(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(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 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; itestAttribute(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 dialogs = widget->findChildren(); + for (int i=0; i(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(info)->aboutToBlock(); + else + emit static_cast(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(info), blockSendPostedEvents); +} + +void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info) +{ + processPostedEvents(static_cast(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/widgets/platforms/mac/qeventdispatcher_mac_p.h b/src/widgets/platforms/mac/qeventdispatcher_mac_p.h new file mode 100644 index 0000000000..12fcafbb01 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#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 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 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 MacTimerHash; + +struct MacSocketInfo { + MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {} + CFSocketRef socket; + CFRunLoopSourceRef runloop; + QObject *readNotifier; + QObject *writeNotifier; +}; +typedef QHash 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 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 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/widgets/platforms/mac/qfont_mac.cpp b/src/widgets/platforms/mac/qfont_mac.cpp new file mode 100644 index 0000000000..daf68c03ea --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include +#include "qfontdatabase.h" +#include +#include "qtextengine_p.h" +#include + +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(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(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/widgets/platforms/mac/qfontdatabase_mac.cpp b/src/widgets/platforms/mac/qfontdatabase_mac.cpp new file mode 100644 index 0000000000..5ba236b5f7 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include "qfontengine_p.h" +#include +#include +#include +#include +#include +#include + +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 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(os2Table.data() + 42), + qFromBigEndian(os2Table.data() + 46), + qFromBigEndian(os2Table.data() + 50), + qFromBigEndian(os2Table.data() + 54) + }; + quint32 codePageRange[2] = { qFromBigEndian(os2Table.data() + 78), qFromBigEndian(os2Table.data() + 82) }; + QList 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 collection = CTFontCollectionCreateFromAvailableFonts(0); + if(!collection) + return; + QCFType 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 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 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(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 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 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 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/widgets/platforms/mac/qfontengine_coretext.mm b/src/widgets/platforms/mac/qfontengine_coretext.mm new file mode 100644 index 0000000000..d4df2183ed --- /dev/null +++ b/src/widgets/platforms/mac/qfontengine_coretext.mm @@ -0,0 +1,880 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in 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 +#include + +#include + +#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 &cgGlyphs, + QGlyphLayout *glyphs, int len, + QTextEngine::ShaperFlags flags, + const QFontDef &fontDef) +{ + Q_UNUSED(flags); + QVarLengthArray 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 descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize); + QCFType 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 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(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 *, + QScriptItem *si) const +{ + QCFType cfstring = CFStringCreateWithCharactersNoCopy(0, + reinterpret_cast(str), + len, kCFAllocatorNull); + QCFType attributedString = CFAttributedStringCreate(0, cfstring, attributeDict); + QCFType 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 options = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, 1, + &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + typeSetter = CTTypesetterCreateWithAttributedStringAndOptions(attributedString, options); + } else +#else + Q_UNUSED(flags); +#endif + typeSetter = CTTypesetterCreateWithAttributedString(attributedString); + + CFRange range = {0, 0}; + QCFType 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(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft); + + bool outOBounds = false; + for (uint i = 0; i < arraySize; ++i) { + CTRunRef run = static_cast(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); + int prepend = 0; +#if MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_5 + UniChar beginGlyph = CFStringGetCharacterAtIndex(cfstring, stringRange.location); + QChar dir = QChar::direction(beginGlyph); + bool beginWithOverride = dir == QChar::DirLRO || dir == QChar::DirRLO || dir == QChar::DirLRE || dir == QChar::DirRLE; + if (beginWithOverride) { + logClusters[stringRange.location] = 0; + outGlyphs[0] = 0xFFFF; + outAdvances_x[0] = 0; + outAdvances_y[0] = 0; + outAttributes[0].clusterStart = true; + outAttributes[0].dontPrint = true; + outGlyphs++; + outAdvances_x++; + outAdvances_y++; + outAttributes++; + prepend = 1; + } +#endif + 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(CFDictionaryGetValue(runAttribs, NSFontAttributeName)); + uint fontIndex = fontIndexForFont(runFont); + const QFontEngine *engine = engineAt(fontIndex); + fontIndex <<= 24; + si->ascent = qMax(engine->ascent(), si->ascent); + si->descent = qMax(engine->descent(), si->descent); + si->leading = qMax(engine->leading(), si->leading); + //NSLog(@"Run Font Name = %@", CTFontCopyFamilyName(runFont)); + if (endWithPDF) + glyphCount--; + + QVarLengthArray cgglyphs(0); + const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run); + if (!tmpGlyphs) { + cgglyphs.resize(glyphCount); + CTRunGetGlyphs(run, range, cgglyphs.data()); + tmpGlyphs = cgglyphs.constData(); + } + QVarLengthArray 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 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 + prepend; + (i < stringRange.location + stringRange.length) && (k < glyphCount); ++i) { + if (tmpIndices[k * rtlSign + rtlOffset] == i || i == stringRange.location + prepend) { + 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 + prepend; + 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 cfstring; + + QVarLengthArray 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(str), len, kCFAllocatorNull); + QCFType 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(reinterpret_cast(os2Table.constData() + 8)); + // qAbs is a workaround for weird fonts like Lucida Grande + qint16 width = qAbs(qFromBigEndian(reinterpret_cast(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(reinterpret_cast(hheaTable.constData() + 10)); + ctMaxCharWidth = width * fontDef.pixelSize / emSize; + qint16 bearing = qFromBigEndian(reinterpret_cast(hheaTable.constData() + 12)); + ctMinLeftBearing = bearing * fontDef.pixelSize / emSize; + bearing = qFromBigEndian(reinterpret_cast(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 cfstring; + + QVarLengthArray 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 positions; + QVarLengthArray 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 advances(glyphs.size()); + QVarLengthArray 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(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 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 colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y= 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 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 cgGlyphs(len); + return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len); +} + +bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + QCFType 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))); +} + +QFontEngine *QCoreTextFontEngine::cloneWithSize(qreal pixelSize) const +{ + QFontDef newFontDef = fontDef; + newFontDef.pixelSize = pixelSize; + newFontDef.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + + return new QCoreTextFontEngine(cgFont, fontDef); +} + +QT_END_NAMESPACE + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + diff --git a/src/widgets/platforms/mac/qfontengine_coretext_p.h b/src/widgets/platforms/mac/qfontengine_coretext_p.h new file mode 100644 index 0000000000..bb80a9b2f3 --- /dev/null +++ b/src/widgets/platforms/mac/qfontengine_coretext_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 QFONTENGINE_CORETEXT_P_H +#define QFONTENGINE_CORETEXT_P_H + +#include + +#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; + + virtual QFontEngine *cloneWithSize(qreal pixelSize) 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, + QScriptItem *si) 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(engines.at(i)); } + + uint fontIndexForFont(CTFontRef font) const; + CTFontRef ctfont; + mutable QCFType 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/widgets/platforms/mac/qfontengine_mac.mm b/src/widgets/platforms/mac/qfontengine_mac.mm new file mode 100644 index 0000000000..9f094ad7d1 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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(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(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(data); + p->path->moveTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::closePath(void *data) +{ + static_cast(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, + 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(static_cast(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(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(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, QScriptItem *) 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 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(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(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(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(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 atsuGlyphs(glyphs->numGlyphs); + for (int i = 0; i < glyphs->numGlyphs; ++i) + atsuGlyphs[i] = glyphs->glyphs[i]; + + QVarLengthArray 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 colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y= 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 positions; + QVarLengthArray 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 advances(glyphs.size()); + QVarLengthArray 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(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(bbox.xMin); + bbox.yMin = qFromBigEndian(bbox.yMin); + bbox.xMax = qFromBigEndian(bbox.xMax); + bbox.yMax = qFromBigEndian(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(metrics.ascender); + metrics.descender = qFromBigEndian(metrics.descender); + metrics.linegap = qFromBigEndian(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(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/widgets/platforms/mac/qfontengine_mac_p.h b/src/widgets/platforms/mac/qfontengine_mac_p.h new file mode 100644 index 0000000000..292ea98d9a --- /dev/null +++ b/src/widgets/platforms/mac/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 + +#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 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, QScriptItem *) 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(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/widgets/platforms/mac/qkeymapper_mac.cpp b/src/widgets/platforms/mac/qkeymapper_mac.cpp new file mode 100644 index 0000000000..d3bbf89711 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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_, 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(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType inputSource = TISCopyCurrentKeyboardInputSource(); + Q_ASSERT(inputSource != 0); + CFDataRef data = static_cast(TISGetInputSourceProperty(inputSource, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast(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(&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(reinterpret_cast(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get unicode keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType source = TISCopyCurrentKeyboardInputSource(); + if (keyboard_mode != NullMode && source == currentInputSource) { + return false; + } + Q_ASSERT(source != 0); + CFDataRef data = static_cast(TISGetInputSourceProperty(source, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast(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(reinterpret_cast(&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(&iso639Code)); +#else + CFArrayRef array = static_cast(TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceLanguages)); + iso639Code = static_cast(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 +QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList 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(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(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/widgets/platforms/mac/qmacdefines_mac.h b/src/widgets/platforms/mac/qmacdefines_mac.h new file mode 100644 index 0000000000..d6ccb93593 --- /dev/null +++ b/src/widgets/platforms/mac/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 + +#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 +# 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/widgets/platforms/mac/qmacgesturerecognizer_mac.mm b/src/widgets/platforms/mac/qmacgesturerecognizer_mac.mm new file mode 100644 index 0000000000..6a4f0bb445 --- /dev/null +++ b/src/widgets/platforms/mac/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(event); + switch (ev->gestureType) { + case QNativeGestureEvent::Swipe: { + QSwipeGesture *g = static_cast(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(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(gesture); + QNativeGestureEvent *ev = static_cast(event); + switch(ev->gestureType) { + case QNativeGestureEvent::GestureBegin: + reset(gesture); + g->setStartCenterPoint(static_cast(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(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(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(gesture); + + switch (event->type()) { + case QEvent::TouchBegin: { + const QTouchEvent *ev = static_cast(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(event); + if (ev->touchPoints().size() == 1) + return QGestureRecognizer::FinishGesture; + break;} + case QEvent::TouchUpdate: { + if (_panCanceled) + break; + + const QTouchEvent *ev = static_cast(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(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(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/widgets/platforms/mac/qmacgesturerecognizer_mac_p.h b/src/widgets/platforms/mac/qmacgesturerecognizer_mac_p.h new file mode 100644 index 0000000000..465f6a2ac8 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qmime_mac.cpp b/src/widgets/platforms/mac/qmime_mac.cpp new file mode 100644 index 0000000000..d6f6222c23 --- /dev/null +++ b/src/widgets/platforms/mac/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 +# include +# include +# include +#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 + + +#ifdef Q_WS_MAC32 +#include +#include +#endif + +QT_BEGIN_NAMESPACE + +extern CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0); // qpaintengine_mac.cpp + +typedef QList 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 data, QString flav); + QList 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 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 QMacPasteboardMimeAny::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList 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 data, QString flav); + QList 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, QString) +{ + QVariant ret; + return ret; +} + +QList QMacPasteboardMimeTypeName::convertFromMime(const QString &, QVariant, QString) +{ + QList 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 data, QString flav); + QList 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 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(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList QMacPasteboardMimePlainText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList 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 data, QString flav); + QList 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 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(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else if (flavor == QLatin1String("public.utf16-plain-text")) { + ret = QString(reinterpret_cast(firstData.constData()), + firstData.size() / sizeof(QChar)); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList QMacPasteboardMimeUnicodeText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList 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 data, QString flav); + QList 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 data, QString flavor) +{ + if (!canConvert(mimeType, flavor)) + return QVariant(); + if (data.count() > 1) + qWarning("QMacPasteboardMimeHTMLText: Cannot handle multiple member data"); + return data.first(); +} + +QList QMacPasteboardMimeHTMLText::convertFromMime(const QString &mime, QVariant data, QString flavor) +{ + QList 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(library.resolve("GraphicsImportSetDataHandle")); + ptrGraphicsImportCreateCGImage = reinterpret_cast(library.resolve("GraphicsImportCreateCGImage")); + ptrGraphicsExportSetInputCGImage = reinterpret_cast(library.resolve("GraphicsExportSetInputCGImage")); + ptrGraphicsExportSetOutputHandle = reinterpret_cast(library.resolve("GraphicsExportSetOutputHandle")); + ptrGraphicsExportDoExport = reinterpret_cast(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 data, QString flav); + QList 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 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 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 QMacPasteboardMimePict::convertFromMime(const QString &mime, QVariant variant, + QString flav) +{ + QList ret; + if (!resolveMimeQuickTimeSymbols()) + return ret; + + if (!canConvert(mime, flav)) + return ret; + QCFType cgimage = qt_mac_createCGImageFromQImage(qvariant_cast(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(*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 data, QString flav); + QList 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 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 image; + QCFType tiffData = CFDataCreateWithBytesNoCopy(0, + reinterpret_cast(a.constData()), + a.size(), kCFAllocatorNull); + QCFType imageSource = CGImageSourceCreateWithData(tiffData, 0); + image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + + if (image != 0) + ret = QVariant(QPixmap::fromMacCGImageRef(image).toImage()); + return ret; +} + +QList QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) +{ + QList ret; + if (!canConvert(mime, flav)) + return ret; + + QImage img = qvariant_cast(variant); + QCFType 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 data = CFDataCreateMutable(0, 0); + QCFType imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); + if (imageDestination != 0) { + CFTypeRef keys[2]; + QCFType values[2]; + QCFType 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(keys), + reinterpret_cast(values), 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CGImageDestinationAddImage(imageDestination, cgimage, options); + CGImageDestinationFinalize(imageDestination); + } + QByteArray ar(CFDataGetLength(data), 0); + CFDataGetBytes(data, + CFRangeMake(0, ar.size()), + reinterpret_cast(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(*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 data, QString flav); + QList 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 data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + QList 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 QMacPasteboardMimeFileUri::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList ret; + if (!canConvert(mime, flav)) + return ret; + QList 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 data, QString flav); + QList 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 data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + + QList ret; + for (int i=0; i QMacPasteboardMimeUrl::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList ret; + if (!canConvert(mime, flav)) + return ret; + + QList urls = data.toList(); + for(int i=0; i data, QString flav); + QList 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 data, QString) +{ + QByteArray cards; + if (mime == QLatin1String("text/plain")) { + for (int i=0; i QMacPasteboardMimeVCard::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList 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 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 data, QString flav); + QList 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 ®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 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::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, QString) +{ + qWarning("QMacPasteboardMimeAnyQt3Mime: Cannot write anything!"); + return QVariant(); +} + +QList QMacPasteboardMimeQt3Any::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList 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::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 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 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/widgets/platforms/mac/qmultitouch_mac.mm b/src/widgets/platforms/mac/qmultitouch_mac.mm new file mode 100644 index 0000000000..d9e845a01c --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +QHash 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 +QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) +{ + QMap 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; iupdateTouchData(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; iupdateTouchData(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/widgets/platforms/mac/qmultitouch_mac_p.h b/src/widgets/platforms/mac/qmultitouch_mac_p.h new file mode 100644 index 0000000000..16be930d0a --- /dev/null +++ b/src/widgets/platforms/mac/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 +#endif + +#include +#include +#include + +#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 getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch); + static void setMouseInDraggingState(bool inDraggingState); + + private: + static QHash _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/widgets/platforms/mac/qnsframeview_mac_p.h b/src/widgets/platforms/mac/qnsframeview_mac_p.h new file mode 100644 index 0000000000..6ec3f64efa --- /dev/null +++ b/src/widgets/platforms/mac/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 + +@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/widgets/platforms/mac/qnsthemeframe_mac_p.h b/src/widgets/platforms/mac/qnsthemeframe_mac_p.h new file mode 100644 index 0000000000..2cb4916c06 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#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/widgets/platforms/mac/qnstitledframe_mac_p.h b/src/widgets/platforms/mac/qnstitledframe_mac_p.h new file mode 100644 index 0000000000..4eb5332194 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#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/widgets/platforms/mac/qpaintdevice_mac.cpp b/src/widgets/platforms/mac/qpaintdevice_mac.cpp new file mode 100644 index 0000000000..245408a0b0 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include +#include + +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(static_cast(device)->macQDHandle()); + } else if(device->devType() == QInternal::Widget) { + return static_cast(static_cast(device)->macQDHandle()); + } else if(device->devType() == QInternal::Printer) { + QPaintEngine *engine = static_cast(device)->paintEngine(); + return static_cast(static_cast(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(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(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(static_cast(pdev)->macCGHandle()); + CGContextRetain(ret); + return ret; + } else if (pdev->devType() == QInternal::MacQuartz) { + return static_cast(pdev)->cgContext(); + } + return 0; +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/mac/qpaintengine_mac.cpp b/src/widgets/platforms/mac/qpaintengine_mac.cpp new file mode 100644 index 0000000000..c6d061dea8 --- /dev/null +++ b/src/widgets/platforms/mac/qpaintengine_mac.cpp @@ -0,0 +1,1751 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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(pe)->paintEngine(); + pe->syncState(); + context = 0; + if(pe->type() == QPaintEngine::CoreGraphics) + context = static_cast(pe)->handle(); + + int devType = p->device()->devType(); + if (pe->type() == QPaintEngine::Raster + && (devType == QInternal::Widget || + devType == QInternal::Pixmap || + devType == QInternal::Image)) { + + 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(paintDevice) + : 0); +} + +inline static QCFType 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(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 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 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::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 shape = rgn.toHIMutableShape(); + Q_ASSERT(!HIShapeIsEmpty(shape)); + HIShapeReplacePathInCGContext(shape, hd); + } else +#endif + { + QVector 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(d->pdev)->windowType() == Qt::Desktop) { +#ifndef QT_MAC_USE_COCOA + HideWindow(qt_mac_window_for(static_cast(d->pdev))); +#else +// // ### need to do [qt_mac_window_for(static_cast(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; idrawPath(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 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 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(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 dataProvider = CGDataProviderCreateWithData(image, + static_cast(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 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(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(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); +#else + static_cast(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 linedashes; + if(pen.style() == Qt::CustomDashLine) { + QVector 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(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(¤t.brush), + 1, domain, 4, 0, &callbacks); + + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + if (bs == Qt::LinearGradientPattern) { + const QLinearGradient *linearGrad = static_cast(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(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/widgets/platforms/mac/qpaintengine_mac_p.h b/src/widgets/platforms/mac/qpaintengine_mac_p.h new file mode 100644 index 0000000000..3e71d6ca6b --- /dev/null +++ b/src/widgets/platforms/mac/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 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/widgets/platforms/mac/qpixmap_mac.cpp b/src/widgets/platforms/mac/qpixmap_mac.cpp new file mode 100644 index 0000000000..72e2aa6e04 --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qpixmap_mac_p.h b/src/widgets/platforms/mac/qpixmap_mac_p.h new file mode 100644 index 0000000000..307e38aceb --- /dev/null +++ b/src/widgets/platforms/mac/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/widgets/platforms/mac/qprintengine_mac.mm b/src/widgets/platforms/mac/qprintengine_mac.mm new file mode 100644 index 0000000000..1dce30301f --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include + +#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(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 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(d->paintEngine)->d_func()->stackCount = 0; + static_cast(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(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 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( + const_cast(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 QMacPrintEnginePrivate::supportedResolutions() const +{ + Q_ASSERT_X(session, "QMacPrinterEngine::supportedResolutions", + "must have a valid printer session"); + UInt32 resCount; + QList 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 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 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([printInfo PMPrintSession]); +#endif + + PMPrinter printer; + if (session && PMSessionGetCurrentPrinter(session, &printer) == noErr) { + QList 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([printInfo PMPrintSettings]); + format = static_cast([printInfo PMPageFormat]); +#endif + +#ifndef QT_MAC_USE_COCOA + if (!settingsOK || !formatOK) { + qWarning("QMacPrintEngine::initialize: Unable to initialize QPainter"); + state = QPrinter::Error; + } +#endif + + QHash::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(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 printerList; + status = PMServerCreatePrinterList(kPMServerLocal, &printerList); + if (status == noErr) { + CFIndex count = CFArrayGetCount(printerList); + for (CFIndex i=0; i(const_cast(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 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 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 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/widgets/platforms/mac/qprintengine_mac_p.h b/src/widgets/platforms/mac/qprintengine_mac_p.h new file mode 100644 index 0000000000..511705d26f --- /dev/null +++ b/src/widgets/platforms/mac/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 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 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/widgets/platforms/mac/qprinterinfo_mac.cpp b/src/widgets/platforms/mac/qprinterinfo_mac.cpp new file mode 100644 index 0000000000..b24ab70267 --- /dev/null +++ b/src/widgets/platforms/mac/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::availablePrinters() +{ + QList printers; + + QCFType array; + if (PMServerCreatePrinterList(kPMServerLocal, &array) == noErr) { + CFIndex count = CFArrayGetCount(array); + for (int i = 0; i < count; ++i) { + PMPrinter printer = static_cast(const_cast(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 printers = availablePrinters(); + foreach (const QPrinterInfo &printerInfo, printers) { + if (printerInfo.isDefault()) + return printerInfo; + } + + return printers.value(0); +} + +QList QPrinterInfo::supportedPaperSizes() const +{ + const Q_D(QPrinterInfo); + + QList 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(const_cast(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/widgets/platforms/mac/qrawfont_mac.cpp b/src/widgets/platforms/mac/qrawfont_mac.cpp new file mode 100644 index 0000000000..1ed4185a5d --- /dev/null +++ b/src/widgets/platforms/mac/qrawfont_mac.cpp @@ -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 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 + +#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 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(); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/widgets/platforms/mac/qregion_mac.cpp b/src/widgets/platforms/mac/qregion_mac.cpp new file mode 100644 index 0000000000..50fd783df4 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include "qcoreapplication.h" +#include + +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(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 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(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/widgets/platforms/mac/qsound_mac.mm b/src/widgets/platforms/mac/qsound_mac.mm new file mode 100644 index 0000000000..5a9af135b0 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include "qsound.h" +#include "qsound_p.h" +#include +#include +#include +#import + +#include + +QT_BEGIN_NAMESPACE + +void qt_mac_beep() +{ + NSBeep(); +} + +QT_END_NAMESPACE + +#ifndef QT_NO_SOUND + +QT_BEGIN_NAMESPACE + +typedef QHash 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 +-(void)sound:(NSSound *)sound didFinishPlaying:(BOOL)aBool; +@end +#endif + +QT_USE_NAMESPACE + +@interface QT_MANGLE_NAMESPACE(QMacSoundDelegate) : NSObject { + 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(reinterpret_cast(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/widgets/platforms/mac/qt_cocoa_helpers_mac.mm b/src/widgets/platforms/mac/qt_cocoa_helpers_mac.mm new file mode 100644 index 0000000000..32123ee682 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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(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(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(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(widget))->updateFrameStrut(); + } + } +#else + qt_widget_private(const_cast(widget))->updateFrameStrut(); + ShowHideWindowToolbar(wnd, show, false); +#endif +} + + +void macWindowToolbarSet( void * /*OSWindowRef*/ window, void *toolbarRef ) +{ + OSWindowRef wnd = static_cast(window); +#if QT_MAC_USE_COCOA + [wnd setToolbar:static_cast(toolbarRef)]; +#else + SetWindowToolbar(wnd, static_cast(toolbarRef)); +#endif +} + +bool macWindowToolbarIsVisible( void * /*OSWindowRef*/ window ) +{ + OSWindowRef wnd = static_cast(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(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(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 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(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_mac_nativeview_for(widget)); + if (cocoaView && [cocoaView respondsToSelector:@selector(qt_qwidget)]) { + [cocoaView qt_clearQWidget]; + } +} + +void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent) +{ + NSEvent *proximityEvent = static_cast(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 rev_entries(NumEntries); + static bool mustInit = true; + if (mustInit){ + mustInit = false; + for (int i=0; i::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 characters(len); + bool changed = false; + for (int i = 0; i source = TISCopyCurrentKeyboardInputSource(); + return TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) == 0; +} + +bool qt_dispatchKeyEventWithCocoa(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent) +{ + NSEvent *event = static_cast(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(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(keyEvent); + EventRef key_event = static_cast(const_cast([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(flagsChangedEvent); + EventRef key_event = static_cast(const_cast([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(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 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(const_cast([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(view); + NSView *theNSView = static_cast(view); + NSEvent *theTabletEvent = static_cast(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(const_cast([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(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(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(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(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); + 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(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(reinterpret_cast(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(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([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([itemArray objectAtIndex:i]); + if (QAction *action = reinterpret_cast([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(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(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 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/widgets/platforms/mac/qt_cocoa_helpers_mac_p.h b/src/widgets/platforms/mac/qt_cocoa_helpers_mac_p.h new file mode 100644 index 0000000000..a49753ae2f --- /dev/null +++ b/src/widgets/platforms/mac/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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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(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(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } + +inline QString qt_mac_NSStringToQString(const NSString *nsstr) +{ return QCFString::toQString(reinterpret_cast(nsstr)); } + +inline NSString *qt_mac_QStringToNSString(const QString &qstr) +{ return [reinterpret_cast(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/widgets/platforms/mac/qt_mac.cpp b/src/widgets/platforms/mac/qt_mac.cpp new file mode 100644 index 0000000000..046bcf6a54 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include +#include +#include + +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 ctfont = CopyCTThemeFont(themeID); + QString familyName = QCFString(CTFontCopyFamilyName(ctfont)); + QCFType dict = CTFontCopyTraits(ctfont); + CFNumberRef num = static_cast(CFDictionaryGetValue(dict, kCTFontWeightTrait)); + float fW; + CFNumberGetValue(num, kCFNumberFloat32Type, &fW); + QFont::Weight wght = fW > 0. ? QFont::Bold : QFont::Normal; + num = static_cast(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 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/widgets/platforms/mac/qt_mac_p.h b/src/widgets/platforms/mac/qt_mac_p.h new file mode 100644 index 0000000000..b2bb804ff0 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#ifdef QT_MAC_USE_COCOA +#include +#endif // QT_MAC_USE_COCOA +#endif + +#include + +#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 + +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 *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 promises; + + OSPasteboardRef paste; + uchar mime_type; + mutable QPointer 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/widgets/platforms/mac/qtextengine_mac.cpp b/src/widgets/platforms/mac/qtextengine_mac.cpp new file mode 100644 index 0000000000..2c6e579b45 --- /dev/null +++ b/src/widgets/platforms/mac/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(), &si)) { + ensureSpace(num_glyphs); + g = availableGlyphs(&si); + stringToCMapFailed = !fe->stringToCMap(str, len, &g, &num_glyphs, flags, log_clusters, + attributes(), &si); + } + + 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/widgets/platforms/mac/qwidget_mac.mm b/src/widgets/platforms/mac/qwidget_mac.mm new file mode 100644 index 0000000000..354f05ba10 --- /dev/null +++ b/src/widgets/platforms/mac/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 +#include + +#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 +#include +#include +#include "qpainter.h" +#include "qstyle.h" +#include "qtimer.h" +#include "qfocusframe.h" +#include "qdebug.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qwidget_p.h" +#include "qevent_p.h" +#include "qdnd_p.h" +#include +#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 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 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 qt_last_mouse_receiver; //qapplication_mac.mm +extern QPointer 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 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 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(w, qRound(r.origin.x + r.size.width)); + h = qMax(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(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(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(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(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 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 rects = region.rects(); + for (int i = 0; inativeParentWidget()) { + // 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 rects = region.rects(); + for (int i = 0; imapTo(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 is 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(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 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 &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(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(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(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(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(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(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(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(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.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::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(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(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(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((CFStringRef)string); + [qt_mac_window_for(q) setRepresentedURL:[NSURL fileURLWithPath:const_cast(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(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(&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((uint)avail.x()+avail.width(), bounds.right+fs.left()); + if(bounds.bottom < avail.y()+avail.height()) + bounds.bottom = qMin((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(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(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(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(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 &viewOrder = *reinterpret_cast *>(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 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(&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(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(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 &dirtyRectsToScroll = dirtyOnWidget.rects(); + for (int i=0; ipos(), 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(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(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(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(win) registerDragTypes]; + else if ([win isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaPanel) class]]) + [static_cast(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(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 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(ref) worksWhenModal] : false; + if (worksWhenModal) + [static_cast(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(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(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(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(view) setEnabled:enable]; +#else + Q_UNUSED(enable); +#endif +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/s60/qapplication_s60.cpp b/src/widgets/platforms/s60/qapplication_s60.cpp new file mode 100644 index 0000000000..408c3b5883 --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qclipboard_s60.cpp b/src/widgets/platforms/s60/qclipboard_s60.cpp new file mode 100644 index 0000000000..0dafae0996 --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qcolormap_s60.cpp b/src/widgets/platforms/s60/qcolormap_s60.cpp new file mode 100644 index 0000000000..2c634db8a5 --- /dev/null +++ b/src/widgets/platforms/s60/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 QColormap::colormap() const +{ return QVector(); } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/s60/qcursor_s60.cpp b/src/widgets/platforms/s60/qcursor_s60.cpp new file mode 100644 index 0000000000..8dfe87ef81 --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qdesktopwidget_s60.cpp b/src/widgets/platforms/s60/qdesktopwidget_s60.cpp new file mode 100644 index 0000000000..62a4d40eba --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qdnd_s60.cpp b/src/widgets/platforms/s60/qdnd_s60.cpp new file mode 100644 index 0000000000..a9847a98f8 --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qeventdispatcher_s60.cpp b/src/widgets/platforms/s60/qeventdispatcher_s60.cpp new file mode 100644 index 0000000000..2d92c89c07 --- /dev/null +++ b/src/widgets/platforms/s60/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 + +#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(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(CCoeEnv::Static())->complete(); + } + + QEventDispatcherSymbian::reactivateDeferredActiveObjects(); +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/s60/qeventdispatcher_s60_p.h b/src/widgets/platforms/s60/qeventdispatcher_s60_p.h new file mode 100644 index 0000000000..7c5a8d03d4 --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include "qt_s60_p.h" + +#include + +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 m_deferredInputEvents; +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_S60_P_H diff --git a/src/widgets/platforms/s60/qfont_s60.cpp b/src/widgets/platforms/s60/qfont_s60.cpp new file mode 100644 index 0000000000..114191d765 --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include +#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/widgets/platforms/s60/qfontdatabase_s60.cpp b/src/widgets/platforms/s60/qfontdatabase_s60.cpp new file mode 100644 index 0000000000..1db4a7d359 --- /dev/null +++ b/src/widgets/platforms/s60/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 +#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 +#include +#include "qendian.h" +#include +#ifdef QT_NO_FREETYPE +#include +#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS +#include // 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' + + struct CFontFromFontStoreReleaser { + static inline void cleanup(CFont *font) + { + if (!font) + return; + const QSymbianFontDatabaseExtrasImplementation *dbExtras = + static_cast(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 m_extras; + + mutable QHash m_extrasHash; + mutable QSet 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(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(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(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(db->symbianExtras); + if (!dbExtras) + return; // initializeDb() has never been called + if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) { + qDeleteAll(dbExtras->m_extrasHash); + } else { + typedef QList::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 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(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 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 sFont(font); + if (font->TypeUid() != KCFbsFontUid) + return false; + TOpenFontFaceAttrib faceAttrib; + const CFbsFont *cfbsFont = static_cast(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(os2Table.constData()); + const unsigned char* ulUnicodeRange = data + 42; + quint32 unicodeRange[4] = { + qFromBigEndian(ulUnicodeRange), + qFromBigEndian(ulUnicodeRange + 4), + qFromBigEndian(ulUnicodeRange + 8), + qFromBigEndian(ulUnicodeRange + 12) + }; + const unsigned char* ulCodePageRange = data + 78; + quint32 codePageRange[2] = { + qFromBigEndian(ulCodePageRange), + qFromBigEndian(ulCodePageRange + 4) + }; + const QList 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(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(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(table.size()); + + if (tableLength > 50000 // hard limit + || tableLength < sizeof(NameTableHead)) // corrupt name table + return false; + + const NameTableHead *head = reinterpret_cast(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(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(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(markedStrings.length())); + nameRecord->length = qToBigEndian(static_cast(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(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(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 Range; + QList memoryRanges; + memoryRanges.reserve(numTables); + for (int i = 0; i < numTables; ++i) { + TableRecord *tableRecord = + reinterpret_cast(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('head'))) { + if (length < ttfCheckSumAdjustmentOffset + sizeof(quint32)) + return false; // Invalid 'head' table + const quint32 *checkSumAdjustmentTag = + reinterpret_cast(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('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(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(markedTtf.data() + sizeof(OffsetTable) + indexOfHeadTable * sizeof(TableRecord)); + quint32 *checkSumAdjustmentTag = + reinterpret_cast(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(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(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 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(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/widgets/platforms/s60/qfontengine_s60.cpp b/src/widgets/platforms/s60/qfontengine_s60.cpp new file mode 100644 index 0000000000..e9b54e350f --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include "qimage.h" +#include +#include + +#include +#include +#include +#include +#if defined(Q_SYMBIAN_HAS_GLYPHOUTLINE_API) +#include +#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(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(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(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 + (cmapTable.constData()), cmapTable.size(), &m_symbolCMap, &size); + m_cmapTable = QByteArray(reinterpret_cast(cmap), size); + } + return reinterpret_cast(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(head.constData()); + const uchar* unitsPerEm = tableData + unitsPerEmOffset; + m_unitsPerEm = qFromBigEndian(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(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/widgets/platforms/s60/qfontengine_s60_p.h b/src/widgets/platforms/s60/qfontengine_s60_p.h new file mode 100644 index 0000000000..c0eeef5264 --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include "qsize.h" +#include + +// 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/widgets/platforms/s60/qkeymapper_s60.cpp b/src/widgets/platforms/s60/qkeymapper_s60.cpp new file mode 100644 index 0000000000..08cfae0d2d --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QKeyMapperPrivate::QKeyMapperPrivate() +{ +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ +} + +QList QKeyMapperPrivate::possibleKeys(QKeyEvent * /* e */) +{ + QList 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 +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/widgets/platforms/s60/qpaintengine_s60.cpp b/src/widgets/platforms/s60/qpaintengine_s60.cpp new file mode 100644 index 0000000000..ca303be03d --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include +#include +#include + +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(pm.pixmapData()); + srcData->beginDataAccess(); + QRasterPaintEngine::drawPixmap(p, pm); + srcData->endDataAccess(); + } else { + 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); + } + } +} + +void QS60PaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + if (pm.pixmapData()->classId() == QPixmapData::RasterClass) { + QS60PixmapData *srcData = static_cast(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(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(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/widgets/platforms/s60/qpaintengine_s60_p.h b/src/widgets/platforms/s60/qpaintengine_s60_p.h new file mode 100644 index 0000000000..a62bdac97c --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qpixmap_s60.cpp b/src/widgets/platforms/s60/qpixmap_s60.cpp new file mode 100644 index 0000000000..c8aa003ffa --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qpixmap_s60_p.h b/src/widgets/platforms/s60/qpixmap_s60_p.h new file mode 100644 index 0000000000..c440bbc33a --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qregion_s60.cpp b/src/widgets/platforms/s60/qregion_s60.cpp new file mode 100644 index 0000000000..eafff1b965 --- /dev/null +++ b/src/widgets/platforms/s60/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/widgets/platforms/s60/qsoftkeymanager_s60.cpp b/src/widgets/platforms/s60/qsoftkeymanager_s60.cpp new file mode 100644 index 0000000000..79ed91af5b --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include + +#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 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(); + 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/widgets/platforms/s60/qsoftkeymanager_s60_p.h b/src/widgets/platforms/s60/qsoftkeymanager_s60_p.h new file mode 100644 index 0000000000..9cb3787cb8 --- /dev/null +++ b/src/widgets/platforms/s60/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 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/widgets/platforms/s60/qsound_s60.cpp b/src/widgets/platforms/s60/qsound_s60.cpp new file mode 100644 index 0000000000..acc5c2a56f --- /dev/null +++ b/src/widgets/platforms/s60/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 + +#include +#include + +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 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/widgets/platforms/s60/qt_s60_p.h b/src/widgets/platforms/s60/qt_s60_p.h new file mode 100644 index 0000000000..8aba53a168 --- /dev/null +++ b/src/widgets/platforms/s60/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 +#include +#include +#include + +#ifdef Q_WS_S60 +#include // AknLayoutUtils +#include // EEikStatusPaneUidTitle +#include // CAknTitlePane +#include // CAknContextPane +#include // CEikStatusPane +#include // MAknFadedComponent and TAknPopupFader +#include // BeginFullScreen +#ifdef QT_SYMBIAN_HAVE_AKNTRANSEFFECT_H +#include // 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 tls; + TUid uid; + int screenDepth; + QPoint lastCursorPos; + QPoint lastPointerEventPos; + QPointer lastPointerEventTarget; + QPointer 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 splitViewLastWidget; + + static CEikButtonGroupContainer *cba; + + enum ScanCodeState { + Unpressed, + KeyDown, + KeyDownAndKey + }; + QHash 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(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(S60->statusPaneSubPane(EEikStatusPaneUidTitle)); +} + +// Returns the application's title pane, if not present returns NULL. +inline CAknContextPane* QS60Data::contextPane() +{ + return static_cast(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/widgets/platforms/s60/qwidget_s60.cpp b/src/widgets/platforms/s60/qwidget_s60.cpp new file mode 100644 index 0000000000..e28a75a6ab --- /dev/null +++ b/src/widgets/platforms/s60/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 + +#ifdef Q_WS_S60 +#include +#include +#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& a, const QList& b) +{ + if ( a.count() != b.count()) + return false; + int index=0; + while (indexsoftKeyRole() != 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(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(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(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(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 control( new QSymbianControl(q) ); + Q_CHECK_PTR(control); + + QT_TRAP_THROWING(control->ConstructL(true, desktop)); + control->SetMopParent(static_cast(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 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(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(S60->appUi()); + MEikAppUiFactory *factory = CEikonEnv::Static()->AppUiFactory(); + + QT_TRAP_THROWING( + factory->CreateResourceIndependentFurnitureL(ui); + + TRect boundingRect = static_cast(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(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(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(S60->appUi())->ClientRect(); + id->SetExtent(r.iTl, r.Size()); + } else if (!q->testAttribute(Qt::WA_Moved) && q->windowType() != Qt::Dialog) { + id->SetPosition(static_cast(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(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(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(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(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(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(q->effectiveWinId()->DrawableWindow()); + QSymbianControl *window = static_cast(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(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(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(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(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(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(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(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(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/widgets/platforms/win/qapplication_win.cpp b/src/widgets/platforms/win/qapplication_win.cpp new file mode 100644 index 0000000000..72a05afbd0 --- /dev/null +++ b/src/widgets/platforms/win/qapplication_win.cpp @@ -0,0 +1,4243 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt 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 qreal qt_fontsmoothing_gamma; +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 + + int winSmooth; + if (SystemParametersInfo(0x200C /* SPI_GETFONTSMOOTHINGCONTRAST */, 0, &winSmooth, 0)) { + qt_fontsmoothing_gamma = winSmooth / qreal(1000.0); + } else { + qt_fontsmoothing_gamma = 1.0; + } + + // Safeguard ourselves against corrupt registry values... + if (qt_fontsmoothing_gamma > 5 || qt_fontsmoothing_gamma < 1) + qt_fontsmoothing_gamma = qreal(1.4); +} + +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/widgets/platforms/win/qclipboard_win.cpp b/src/widgets/platforms/win/qclipboard_win.cpp new file mode 100644 index 0000000000..ea41165b9c --- /dev/null +++ b/src/widgets/platforms/win/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/widgets/platforms/win/qcolormap_win.cpp b/src/widgets/platforms/win/qcolormap_win.cpp new file mode 100644 index 0000000000..1773f717c0 --- /dev/null +++ b/src/widgets/platforms/win/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 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 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/widgets/platforms/win/qcursor_win.cpp b/src/widgets/platforms/win/qcursor_win.cpp new file mode 100644 index 0000000000..8a9362ebfc --- /dev/null +++ b/src/widgets/platforms/win/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/widgets/platforms/win/qdesktopwidget_win.cpp b/src/widgets/platforms/win/qdesktopwidget_win.cpp new file mode 100644 index 0000000000..d57b355ef4 --- /dev/null +++ b/src/widgets/platforms/win/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/widgets/platforms/win/qdnd_win.cpp b/src/widgets/platforms/win/qdnd_win.cpp new file mode 100644 index 0000000000..176e3cef7f --- /dev/null +++ b/src/widgets/platforms/win/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/widgets/platforms/win/qfont_win.cpp b/src/widgets/platforms/win/qfont_win.cpp new file mode 100644 index 0000000000..3ef761bfa5 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include "qt_windows.h" +#include +#include "qapplication.h" +#include +#include + +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(engine)->engine(0); + if (engine->type() == QFontEngine::Win) + return static_cast(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/widgets/platforms/win/qfontdatabase_win.cpp b/src/widgets/platforms/win/qfontdatabase_win.cpp new file mode 100644 index 0000000000..05b7509bf6 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include +#include "qfont_p.h" +#include "qfontengine_p.h" +#include "qpaintdevice.h" +#include +#include "qabstractfileengine.h" +#include "qendian.h" + +#if !defined(QT_NO_DIRECTWRITE) +# include "qsettings.h" +# include "qfontenginedirectwrite_p.h" +#endif + +#ifdef Q_OS_WINCE +# include +#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 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(&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 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 getTrueTypeFontOffsets(const uchar *fontData) +{ + QList offsets; + const quint32 headerTag = *reinterpret_cast(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(fontData + 8); + for (uint i = 0; i < numFonts; ++i) { + offsets << qFromBigEndian(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(data + 4); + for (uint i = 0; i < numTables; ++i) { + const quint32 offset = 12 + 16 * i; + if (*reinterpret_cast(data + offset) == tag) { + *table = fileBegin + qFromBigEndian(data + offset + 8); + *length = qFromBigEndian(data + offset + 12); + return; + } + } + *table = 0; + *length = 0; + return; +} + +static void getFamiliesAndSignatures(const QByteArray &fontData, QFontDatabasePrivate::ApplicationFont *appFont) +{ + const uchar *data = reinterpret_cast(fontData.constData()); + + QList 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(table + 42); + signature.fsUsb[1] = qFromBigEndian(table + 46); + signature.fsUsb[2] = qFromBigEndian(table + 50); + signature.fsUsb[3] = qFromBigEndian(table + 54); + + signature.fsCsb[0] = qFromBigEndian(table + 78); + signature.fsCsb[1] = qFromBigEndian(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/widgets/platforms/win/qfontengine_win.cpp b/src/widgets/platforms/win/qfontengine_win.cpp new file mode 100644 index 0000000000..54d7ec2980 --- /dev/null +++ b/src/widgets/platforms/win/qfontengine_win.cpp @@ -0,0 +1,1339 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt 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 +#include "qt_windows.h" +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include "qpaintengine.h" +#include "qvarlengtharray.h" +#include +#include + +#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, 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(MAKE_TAG('c', 'm', 'a', 'p'))); + int size = 0; + cmap = QFontEngine::getCMap(reinterpret_cast(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 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 (offsetcb)) { + curve = (TTPOLYCURVE*)((char*)(dataBuffer) + offset); + switch (curve->wType) { + case TT_PRIM_LINE: { + for (int i=0; icpfx; ++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; icpfx - 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; icpfx; ) { + 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 +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(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 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; yheight(); ++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; xwidth(); ++x) + dest[x] = 255 - qGray(src[x]); + } else { + const uint *src = (uint *) ((const QImage &) mask->image).scanLine(y); + for (int x=0; xwidth(); ++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; yheight(); ++y) { + uint *dest = (uint *) rgbMask.scanLine(y); + const uint *src = (uint *) source.scanLine(y); + for (int x=0; xwidth(); ++x) { + dest[x] = 0xffffffff - (0x00ffffff & src[x]); + } + } + + delete mask; + + return rgbMask; +} + +// From qfontdatabase_win.cpp +extern QFontEngine *qt_load_font_engine_win(const QFontDef &request); +QFontEngine *QFontEngineWin::cloneWithSize(qreal pixelSize) const +{ + QFontDef request = fontDef; + QString actualFontName = request.family; + if (!uniqueFamilyName.isEmpty()) + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + + QFontEngine *fontEngine = qt_load_font_engine_win(request); + if (fontEngine != NULL) + fontEngine->fontDef.family = actualFontName; + + return fontEngine; +} + +// -------------------------------------- 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(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/widgets/platforms/win/qfontengine_win_p.h b/src/widgets/platforms/win/qfontengine_win_p.h new file mode 100644 index 0000000000..114149d61f --- /dev/null +++ b/src/widgets/platforms/win/qfontengine_win_p.h @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in 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 + +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); + + virtual QFontEngine *cloneWithSize(qreal pixelSize) const; + +#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; + QString uniqueFamilyName; + 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/widgets/platforms/win/qguifunctions_wince.cpp b/src/widgets/platforms/win/qguifunctions_wince.cpp new file mode 100644 index 0000000000..bb4ed11589 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include + +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 (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/widgets/platforms/win/qguifunctions_wince.h b/src/widgets/platforms/win/qguifunctions_wince.h new file mode 100644 index 0000000000..2e14de0693 --- /dev/null +++ b/src/widgets/platforms/win/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 +#define UNDER_NT +#include + +#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/widgets/platforms/win/qkeymapper_win.cpp b/src/widgets/platforms/win/qkeymapper_win.cpp new file mode 100644 index 0000000000..92fa582617 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include +#include +#include +#include +#include +#include +#include + +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(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_, 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(&buffer), 0); + ::ToAscii(vk_key, scancode, kbdBuffer, reinterpret_cast(&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<deadkeys & 1< QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList 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+ 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/widgets/platforms/win/qmime_win.cpp b/src/widgets/platforms/win/qmime_win.cpp new file mode 100644 index 0000000000..feb8b78eca --- /dev/null +++ b/src/widgets/platforms/win/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 +#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 windowsMimes(); + +private: + void init(); + bool initialized; + QList 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 (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 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 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 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 QWindowsMime::allFormatsForMime(const QMimeData *mimeData) +{ + QList mimes = theMimeList()->windowsMimes(); + QVector formatics; + formatics.reserve(20); +#ifndef QT_NO_DRAGANDDROP + QStringList formats = QInternalMimeData::formatsHelper(mimeData); + for (int f=0; f=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 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 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= 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 QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector 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 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 urls = mimeData->urls(); + for (int i=0; ihasFormat(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 urls = mimeData->urls(); + QStringList fileNames; + int size = sizeof(DROPFILES)+2; + for (int i=0; ipFiles = 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 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 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 QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector 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 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 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 QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector 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 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 = "" + html.mid(start, end - start); + html += ""; + 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("") == -1) + result += ""; + result += data; + if (data.indexOf("") == -1) + result += ""; + + // 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("") + 20).toLatin1(); + memcpy((char *)(result.data() + 79 - pos.length()), pos.constData(), pos.length()); + pos = QString::number(result.indexOf("")).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 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 QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector formatetcs; + if (mimeData->hasImage() && mimeType == QLatin1String("application/x-qt-image")) { + //add DIBV5 if image has alpha channel + QImage image = qvariant_cast(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(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(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 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 outFormats; + QMap 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 QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const +{ + QVector 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 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 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 QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const +{ + QVector 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(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 (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 (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 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(&bi), bi.bV5Size); + if (s.status() != QDataStream::Ok) + return false; + + DWORD colorSpace[3] = {0x00ff0000,0x0000ff00,0x000000ff}; + d->write(reinterpret_cast(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/widgets/platforms/win/qole_win.cpp b/src/widgets/platforms/win/qole_win.cpp new file mode 100644 index 0000000000..24e2d5b292 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include "qguifunctions_wince.h" +#endif + +QT_BEGIN_NAMESPACE + +QOleEnumFmtEtc::QOleEnumFmtEtc(const QVector &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 &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/widgets/platforms/win/qpaintdevice_win.cpp b/src/widgets/platforms/win/qpaintdevice_win.cpp new file mode 100644 index 0000000000..3dbe97492a --- /dev/null +++ b/src/widgets/platforms/win/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 +#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/widgets/platforms/win/qpixmap_win.cpp b/src/widgets/platforms/win/qpixmap_win.cpp new file mode 100644 index 0000000000..9c14ac7726 --- /dev/null +++ b/src/widgets/platforms/win/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/widgets/platforms/win/qprintengine_win.cpp b/src/widgets/platforms/win/qprintengine_win.cpp new file mode 100644 index 0000000000..07d66f5bd0 --- /dev/null +++ b/src/widgets/platforms/win/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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +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(d->docName.utf16()); + if (d->printToFile && !d->fileName.isEmpty()) + di.lpszOutput = reinterpret_cast(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(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(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= 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; ihas_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(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(program.utf16()), + reinterpret_cast(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 QWin32PrintEnginePrivate::queryResolutions() const +{ + // Read the supported resolutions of the printer. + QList list; + + DWORD numRes = DeviceCapabilities(reinterpret_cast(name.utf16()), + reinterpret_cast(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(name.utf16()), + reinterpret_cast(port.utf16()), + DC_ENUMRESOLUTIONS, (LPWSTR)enumRes, 0); + + if (errRes == (DWORD)-1) { + qErrnoWarning("QWin32PrintEngine::queryResolutions: DeviceCapabilities failed"); + return list; + } + + for (uint i=0; idevMode) + 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 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(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 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 out; + for (int i=0; ipaper_size; + break; + + case PPK_PageMargins: + { + QList 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(program.utf16()), + reinterpret_cast(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(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 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 positions; + QVarLengthArray _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 glyphDistances(_glyphs.size() * 2); + QVarLengthArray 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/widgets/platforms/win/qprintengine_win_p.h b/src/widgets/platforms/win/qprintengine_win_p.h new file mode 100644 index 0000000000..b4d0670e7b --- /dev/null +++ b/src/widgets/platforms/win/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 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/widgets/platforms/win/qprinterinfo_win.cpp b/src/widgets/platforms/win/qprinterinfo_win.cpp new file mode 100644 index 0000000000..2c4014d8dc --- /dev/null +++ b/src/widgets/platforms/win/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 + +#include + +QT_BEGIN_NAMESPACE + +#ifndef QT_NO_PRINTER + +extern QPrinter::PaperSize mapDevmodePaperSize(int s); + +QList QPrinterInfo::availablePrinters() +{ + QList 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(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 QPrinterInfo::supportedPaperSizes() const +{ + const Q_D(QPrinterInfo); + + QList paperSizes; + if (isNull()) + return paperSizes; + + DWORD size = DeviceCapabilities(reinterpret_cast(d->name.utf16()), + NULL, DC_PAPERS, NULL, NULL); + if ((int)size != -1) { + wchar_t *papers = new wchar_t[size]; + size = DeviceCapabilities(reinterpret_cast(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/widgets/platforms/win/qrawfont_win.cpp b/src/widgets/platforms/win/qrawfont_win.cpp new file mode 100644 index 0000000000..d8acf57431 --- /dev/null +++ b/src/widgets/platforms/win/qrawfont_win.cpp @@ -0,0 +1,707 @@ +/**************************************************************************** +** +** Copyright (C) 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 + +#if !defined(QT_NO_DIRECTWRITE) +# include "qfontenginedirectwrite_p.h" +# include +#endif + +#if !defined(QT_NO_RAWFONT) + +QT_BEGIN_NAMESPACE + +namespace { + + template + struct BigEndian + { + quint8 data[sizeof(T)]; + + operator T() const + { + T littleEndian = 0; + for (int i=0; i &operator=(const T &t) + { + for (int i=0; i> (sizeof(T) - i - 1) * 8) & 0xff); + } + + return *this; + } + }; + +# pragma pack(1) + + // Common structure for all formats of the "name" table + struct NameTable + { + BigEndian format; + BigEndian count; + BigEndian stringOffset; + }; + + struct NameRecord + { + BigEndian platformID; + BigEndian encodingID; + BigEndian languageID; + BigEndian nameID; + BigEndian length; + BigEndian offset; + }; + + struct OffsetSubTable + { + BigEndian scalerType; + BigEndian numTables; + BigEndian searchRange; + BigEndian entrySelector; + BigEndian rangeShift; + }; + + struct TableDirectory + { + BigEndian identifier; + BigEndian checkSum; + BigEndian offset; + BigEndian length; + }; + + struct OS2Table + { + BigEndian version; + BigEndian avgCharWidth; + BigEndian weightClass; + BigEndian widthClass; + BigEndian type; + BigEndian subscriptXSize; + BigEndian subscriptYSize; + BigEndian subscriptXOffset; + BigEndian subscriptYOffset; + BigEndian superscriptXSize; + BigEndian superscriptYSize; + BigEndian superscriptXOffset; + BigEndian superscriptYOffset; + BigEndian strikeOutSize; + BigEndian strikeOutPosition; + BigEndian familyClass; + quint8 panose[10]; + BigEndian unicodeRanges[4]; + quint8 vendorID[4]; + BigEndian selection; + BigEndian firstCharIndex; + BigEndian lastCharIndex; + BigEndian typoAscender; + BigEndian typoDescender; + BigEndian typoLineGap; + BigEndian winAscent; + BigEndian winDescent; + BigEndian codepageRanges[2]; + BigEndian height; + BigEndian capHeight; + BigEndian defaultChar; + BigEndian breakChar; + BigEndian 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 *tagIdPtr = + reinterpret_cast *>(tagName.constData()); + quint32 tagId = *tagIdPtr; + + OffsetSubTable *offsetSubTable = reinterpret_cast(m_fontData.data()); + TableDirectory *tableDirectory = reinterpret_cast(offsetSubTable + 1); + + TableDirectory *nameTableDirectoryEntry = 0; + for (int i=0; inumTables; ++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(m_fontData.data() + + nameTableDirectoryEntry->offset); + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + for (int i=0; icount; ++i, ++nameRecord) { + if (nameRecord->nameID == 1 + && nameRecord->platformID == 3 // Windows + && nameRecord->languageID == 0x0409) { // US English + const void *ptr = reinterpret_cast(nameTable) + + nameTable->stringOffset + + nameRecord->offset; + + const BigEndian *s = reinterpret_cast *>(ptr); + for (int j=0; jlength / 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(newNameTable.data()); + nameTable->count = requiredRecordCount; + nameTable->stringOffset = sizeOfHeader; + + NameRecord *nameRecord = reinterpret_cast(nameTable + 1); + for (int i=0; inameID = 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 *stringStorage = reinterpret_cast *>(nameRecord); + const quint16 *sourceString = newFamilyName.utf16(); + for (int i=0; i(newNameTable.data()); + quint32 *tableEnd = reinterpret_cast(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 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(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(&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(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); + + QString 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(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(func); + + func = QSystemLibrary::resolve(QLatin1String("gdi32"), "AddFontMemResourceEx"); + ptrAddFontMemResourceEx = + reinterpret_cast(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 + static_cast(fontEngine)->uniqueFamilyName = uniqueFamilyName; + 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(&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(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); + } + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/widgets/platforms/win/qregion_win.cpp b/src/widgets/platforms/win/qregion_win.cpp new file mode 100644 index 0000000000..3466b62cbd --- /dev/null +++ b/src/widgets/platforms/win/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(buf); + if (GetRegionData(rgn, numBytes, rd) == 0) { + delete [] buf; + return QRegion(); + } + + QRegion region; + RECT *r = reinterpret_cast(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/widgets/platforms/win/qsound_win.cpp b/src/widgets/platforms/win/qsound_win.cpp new file mode 100644 index 0000000000..c11482d608 --- /dev/null +++ b/src/widgets/platforms/win/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 +#include "qpointer.h" +#include "qsound_p.h" + +#include + +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 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/widgets/platforms/win/qwidget_win.cpp b/src/widgets/platforms/win/qwidget_win.cpp new file mode 100644 index 0000000000..a02c5ba008 --- /dev/null +++ b/src/widgets/platforms/win/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 + +#include + +#include +#include +#include +#include + +#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 +#include +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 +#include + +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(windowClassName.utf16()), + reinterpret_cast(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(windowClassName.utf16()), + reinterpret_cast(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(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(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(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(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(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(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(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/widgets/platforms/win/qwidget_wince.cpp b/src/widgets/platforms/win/qwidget_wince.cpp new file mode 100644 index 0000000000..7676182ef0 --- /dev/null +++ b/src/widgets/platforms/win/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(windowClassName.utf16()), + reinterpret_cast(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(windowClassName.utf16()), + reinterpret_cast(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 list = findChildren(); + 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/widgets/platforms/win/qwindowdefs_win.h b/src/widgets/platforms/win/qwindowdefs_win.h new file mode 100644 index 0000000000..a4dd38410c --- /dev/null +++ b/src/widgets/platforms/win/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 + +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/widgets/platforms/win/qwinnativepangesturerecognizer_win.cpp b/src/widgets/platforms/win/qwinnativepangesturerecognizer_win.cpp new file mode 100644 index 0000000000..0d13bafc0c --- /dev/null +++ b/src/widgets/platforms/win/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(target)) + return 0; + + QWidget *q = static_cast(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(state); + QPanGesturePrivate *d = q->d_func(); + + QGestureRecognizer::Result result = QGestureRecognizer::Ignore; + if (event->type() == QEvent::NativeGesture) { + QNativeGestureEvent *ev = static_cast(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(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/widgets/platforms/win/qwinnativepangesturerecognizer_win_p.h b/src/widgets/platforms/win/qwinnativepangesturerecognizer_win_p.h new file mode 100644 index 0000000000..6d23e41ce3 --- /dev/null +++ b/src/widgets/platforms/win/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 + +#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/widgets/platforms/x11/qapplication_x11.cpp b/src/widgets/platforms/x11/qapplication_x11.cpp new file mode 100644 index 0000000000..20542ea328 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qclipboard_x11.cpp b/src/widgets/platforms/x11/qclipboard_x11.cpp new file mode 100644 index 0000000000..d566c86e04 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qcolormap_x11.cpp b/src/widgets/platforms/x11/qcolormap_x11.cpp new file mode 100644 index 0000000000..05eefa455b --- /dev/null +++ b/src/widgets/platforms/x11/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 +#include + +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 colors; + QVector 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 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 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/widgets/platforms/x11/qcursor_x11.cpp b/src/widgets/platforms/x11/qcursor_x11.cpp new file mode 100644 index 0000000000..d0ed98e1fe --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qdesktopwidget_x11.cpp b/src/widgets/platforms/x11/qdesktopwidget_x11.cpp new file mode 100644 index 0000000000..b0f12903a1 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qdnd_x11.cpp b/src/widgets/platforms/x11/qdnd_x11.cpp new file mode 100644 index 0000000000..9ff1543e51 --- /dev/null +++ b/src/widgets/platforms/x11/qdnd_x11.cpp @@ -0,0 +1,2072 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in 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: + 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/widgets/platforms/x11/qeventdispatcher_x11.cpp b/src/widgets/platforms/x11/qeventdispatcher_x11.cpp new file mode 100644 index 0000000000..110786a378 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qeventdispatcher_x11_p.h b/src/widgets/platforms/x11/qeventdispatcher_x11_p.h new file mode 100644 index 0000000000..cfdd2a5fa6 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qfont_x11.cpp b/src/widgets/platforms/x11/qfont_x11.cpp new file mode 100644 index 0000000000..c72a5fade5 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qfontdatabase_x11.cpp b/src/widgets/platforms/x11/qfontdatabase_x11.cpp new file mode 100644 index 0000000000..0c0c4c8343 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qfontengine_x11.cpp b/src/widgets/platforms/x11/qfontengine_x11.cpp new file mode 100644 index 0000000000..4260b85b11 --- /dev/null +++ b/src/widgets/platforms/x11/qfontengine_x11.cpp @@ -0,0 +1,1215 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in 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 +} + +QFontEngine *QFontEngineX11FT::cloneWithSize(qreal pixelSize) const +{ + QFontDef fontDef; + fontDef.pixelSize = pixelSize; + QFontEngineX11FT *fe = new QFontEngineX11FT(fontDef); + if (!fe->initFromFontEngine(this)) { + delete fe; + return 0; + } else { + fe->xglyph_format = xglyph_format; + return fe; + } +} + +#endif // QT_NO_FONTCONFIG + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/x11/qfontengine_x11_p.h b/src/widgets/platforms/x11/qfontengine_x11_p.h new file mode 100644 index 0000000000..d7eb39daaa --- /dev/null +++ b/src/widgets/platforms/x11/qfontengine_x11_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 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(); + + QFontEngine *cloneWithSize(qreal pixelSize) const; + +#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/widgets/platforms/x11/qkde.cpp b/src/widgets/platforms/x11/qkde.cpp new file mode 100644 index 0000000000..7d333feb9a --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qkde_p.h b/src/widgets/platforms/x11/qkde_p.h new file mode 100644 index 0000000000..4e108f6e9e --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qkeymapper_x11.cpp b/src/widgets/platforms/x11/qkeymapper_x11.cpp new file mode 100644 index 0000000000..5383bfd456 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qkeymapper_x11_p.cpp b/src/widgets/platforms/x11/qkeymapper_x11_p.cpp new file mode 100644 index 0000000000..2dbe1e77a4 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qmotifdnd_x11.cpp b/src/widgets/platforms/x11/qmotifdnd_x11.cpp new file mode 100644 index 0000000000..eef4cc470b --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qpaintdevice_x11.cpp b/src/widgets/platforms/x11/qpaintdevice_x11.cpp new file mode 100644 index 0000000000..690dea99d7 --- /dev/null +++ b/src/widgets/platforms/x11/qpaintdevice_x11.cpp @@ -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$ +** +****************************************************************************/ + +#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; +} + +QT_END_NAMESPACE diff --git a/src/widgets/platforms/x11/qpaintengine_x11.cpp b/src/widgets/platforms/x11/qpaintengine_x11.cpp new file mode 100644 index 0000000000..1256996491 --- /dev/null +++ b/src/widgets/platforms/x11/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/qhexstring_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/widgets/platforms/x11/qpaintengine_x11_p.h b/src/widgets/platforms/x11/qpaintengine_x11_p.h new file mode 100644 index 0000000000..897c69f122 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qpixmap_x11.cpp b/src/widgets/platforms/x11/qpixmap_x11.cpp new file mode 100644 index 0000000000..bc468cb7ec --- /dev/null +++ b/src/widgets/platforms/x11/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 <private/qpaintengine_x11_p.h> +#include <private/qt_x11_p.h> +#include "qx11info_x11.h" +#include <private/qdrawhelper_p.h> +#include <private/qimage_p.h> +#include <private/qimagepixmapcleanuphooks_p.h> + +#include <stdlib.h> + +#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<QImage *>(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<QRgb> 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<QRgb> 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; i<n; i++) { + for (j=0; j<n; j++) { + D[i][j]*=4; + D[i+n][j]=D[i][j]+2; + D[i][j+n]=D[i][j]+3; + D[i+n][j+n]=D[i][j]+1; + } + } + } + init=true; + } + + enum { BPP8, + BPP16_565, BPP16_555, + BPP16_MSB, BPP16_LSB, + BPP24_888, + BPP24_MSB, BPP24_LSB, + BPP32_8888, + BPP32_MSB, BPP32_LSB + } mode = BPP8; + + bool same_msb_lsb = (xi->byte_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<<rbits) & 255) \ + > thres) \ + r += (1<<(8-rbits)); \ + if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ + > thres) \ + g += (1<<(8-gbits)); \ + if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ + > 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<<rbits) & 255) \ + > thres) \ + r += (1<<(8-rbits)); \ + int g = qGreen(*p); \ + if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ + > thres) \ + g += (1<<(8-gbits)); \ + int b = qBlue (*p++); \ + if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ + > 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; y<h; y++) { \ + const uchar* src = cimage.scanLine(y); \ + uchar* dst = newbits + xi->bytes_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<w; x++) { + GET_PIXEL_DITHER_TC_OPT(<<8,<<3,>>3,0xf800,0x7e0,0x1f,5,6,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC_OPT(<<7,<<2,>>3,0x7c00,0x3e0,0x1f,5,5,5) + *dst16++ = pixel; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC + *dst++ = (pixel >> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL_DITHER_TC + *dst++ = pixel; + *dst++ = pixel >> 8; + } + ) + break; + default: + qFatal("Logic error"); + } + } else { + switch (mode) { + case BPP8: // 8 bit + CYCLE( + Q_UNUSED(p); + for (int x=0; x<w; x++) + *dst++ = pix[*src++]; + ) + break; + case BPP16_565: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x = 0; x < w; x++) { + *dst16++ = ((*p >> 8) & 0xf800) + | ((*p >> 5) & 0x7e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_555: + CYCLE( + quint16* dst16 = (quint16*)dst; + for (int x=0; x<w; x++) { + *dst16++ = ((*p >> 9) & 0x7c00) + | ((*p >> 6) & 0x3e0) + | ((*p >> 3) & 0x1f); + ++p; + } + ) + break; + case BPP16_MSB: // 16 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = (pixel >> 8); + *dst++ = pixel; + } + ) + break; + case BPP16_LSB: // 16 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 8; + } + ) + break; + case BPP24_888: // 24 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + *dst++ = qRed (*p); + *dst++ = qGreen(*p); + *dst++ = qBlue (*p++); + } + ) + break; + case BPP24_MSB: // 24 bit MSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP24_LSB: // 24 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 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<w; x++) { + GET_PIXEL + *dst++ = pixel >> 24; + *dst++ = pixel >> 16; + *dst++ = pixel >> 8; + *dst++ = pixel; + } + ) + break; + case BPP32_LSB: // 32 bit LSB + CYCLE( + for (int x=0; x<w; x++) { + GET_PIXEL + *dst++ = pixel; + *dst++ = pixel >> 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<QRgb> 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; j<ncols; j++) { + px = &pixarr[j]; + if (px->use) { + 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; j<ncols; j++) { + px = &pixarr[j]; + if (px->use) { + 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<QX11PixmapData*>(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; y<xi->height; 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<QColor> colors = QColormap::instance(xinfo.screen()).colormap(); + int j = 0; + for (int i=0; i<colors.size(); i++) { // translate pixels + if (use[i]) + image.setColor(j++, 0xff000000 | colors.at(i).rgb()); + } + } + + return image; +} + +/*! + Returns a copy of the pixmap that is transformed using the given + transformation \a matrix and transformation \a mode. The original + pixmap is not changed. + + The transformation \a matrix 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 QX11PixmapData::transformed(const QTransform &transform, + Qt::TransformationMode mode ) const +{ + if (mode == Qt::SmoothTransformation || transform.type() >= 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; y<h; y++) + memset(dptr + y*xshmimg->bytes_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<QX11PixmapData*>(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<QX11PixmapData*>(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<QX11PixmapData*>(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<const QX11PixmapData*>(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<const QX11PixmapData*>(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/widgets/platforms/x11/qpixmap_x11_p.h b/src/widgets/platforms/x11/qpixmap_x11_p.h new file mode 100644 index 0000000000..eb8e5819ad --- /dev/null +++ b/src/widgets/platforms/x11/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 <QtGui/private/qpixmapdata_p.h> +#include <QtGui/private/qpixmapdatafactory_p.h> + +#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/widgets/platforms/x11/qregion_x11.cpp b/src/widgets/platforms/x11/qregion_x11.cpp new file mode 100644 index 0000000000..ef4e844bfa --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qsound_x11.cpp b/src/widgets/platforms/x11/qsound_x11.cpp new file mode 100644 index 0000000000..12c06f0aa1 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qt_x11_p.h b/src/widgets/platforms/x11/qt_x11_p.h new file mode 100644 index 0000000000..69079cfaad --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qwidget_x11.cpp b/src/widgets/platforms/x11/qwidget_x11.cpp new file mode 100644 index 0000000000..5ece7d65c6 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qwidgetcreate_x11.cpp b/src/widgets/platforms/x11/qwidgetcreate_x11.cpp new file mode 100644 index 0000000000..16bd6abf9a --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qx11embed_x11.cpp b/src/widgets/platforms/x11/qx11embed_x11.cpp new file mode 100644 index 0000000000..49a819469e --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qx11embed_x11.h b/src/widgets/platforms/x11/qx11embed_x11.h new file mode 100644 index 0000000000..30929f7ba9 --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qx11info_x11.cpp b/src/widgets/platforms/x11/qx11info_x11.cpp new file mode 100644 index 0000000000..f52443befc --- /dev/null +++ b/src/widgets/platforms/x11/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/widgets/platforms/x11/qx11info_x11.h b/src/widgets/platforms/x11/qx11info_x11.h new file mode 100644 index 0000000000..ece85740d2 --- /dev/null +++ b/src/widgets/platforms/x11/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 -- cgit v1.2.3