diff options
Diffstat (limited to 'src/gui/platforms/mac')
61 files changed, 32377 insertions, 0 deletions
diff --git a/src/gui/platforms/mac/qapplication_mac.mm b/src/gui/platforms/mac/qapplication_mac.mm new file mode 100644 index 0000000000..f607a72e92 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +#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 <private/qcocoamenuloader_mac_p.h> +#include <private/qcocoaapplication_mac_p.h> +#include <private/qcocoaapplicationdelegate_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qcocoawindow_mac_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qdesktopwidget_mac_p.h> +#include <private/qeventdispatcher_mac_p.h> +#include <qvarlengtharray.h> + +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#ifndef QT_NO_THREAD +# include "qmutex.h" +#endif + +#include <unistd.h> +#include <string.h> +#include <sys/time.h> +#include <sys/select.h> + +/***************************************************************************** + 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*> *QMacWindowChangeEvent::change_events = 0; +QPointer<QWidget> topLevelAt_cache = 0; + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ +static struct { + bool use_qt_time_limit; + QPointer<QWidget> 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<QWidget> 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<w; ++y) { + for (int x=0; x<h; ++x) { + int r = qRed(bits[x]); + int g = qGreen(bits[x]); + int b = qBlue(bits[x]); + if (r != g || r != b) { + qt_applefontsmoothing_enabled = true; + return; + } + } + bits += bpl; + } + qt_applefontsmoothing_enabled = false; +} + +Q_GUI_EXPORT bool qt_mac_execute_apple_script(const char *script, long script_len, AEDesc *ret) { + OSStatus err; + AEDesc scriptTextDesc; + ComponentInstance theComponent = 0; + OSAID scriptID = kOSANullScript, resultID = kOSANullScript; + + // set up locals to a known state + AECreateDesc(typeNull, 0, 0, &scriptTextDesc); + scriptID = kOSANullScript; + resultID = kOSANullScript; + + // open the scripting component + theComponent = OpenDefaultComponent(kOSAComponentType, typeAppleScript); + if (!theComponent) { + err = paramErr; + goto bail; + } + + // put the script text into an aedesc + err = AECreateDesc(typeUTF8Text, script, script_len, &scriptTextDesc); + if (err != noErr) + goto bail; + + // compile the script + err = OSACompile(theComponent, &scriptTextDesc, kOSAModeNull, &scriptID); + if (err != noErr) + goto bail; + + // run the script + err = OSAExecute(theComponent, scriptID, kOSANullScript, kOSAModeNull, &resultID); + + // collect the results - if any + if (ret) { + AECreateDesc(typeNull, 0, 0, ret); + if (err == errOSAScriptError) + OSAScriptError(theComponent, kOSAErrorMessage, typeChar, ret); + else if (err == noErr && resultID != kOSANullScript) + OSADisplay(theComponent, resultID, typeChar, kOSAModeNull, ret); + } +bail: + AEDisposeDesc(&scriptTextDesc); + if (scriptID != kOSANullScript) + OSADispose(theComponent, scriptID); + if (resultID != kOSANullScript) + OSADispose(theComponent, resultID); + if (theComponent) + CloseComponent(theComponent); + return err == noErr; +} + +Q_GUI_EXPORT bool qt_mac_execute_apple_script(const char *script, AEDesc *ret) +{ + return qt_mac_execute_apple_script(script, qstrlen(script), ret); +} + +Q_GUI_EXPORT bool qt_mac_execute_apple_script(const QString &script, AEDesc *ret) +{ + const QByteArray l = script.toUtf8(); return qt_mac_execute_apple_script(l.constData(), l.size(), ret); +} + +/* Resolution change magic */ +void qt_mac_display_change_callbk(CGDirectDisplayID, CGDisplayChangeSummaryFlags flags, void *) +{ +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 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<NSImage *>(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<QWidgetPrivate::GlWidgetInfo> &glWidgets = qt_widget_private(widget)->glWidgets; + QList<QWidgetPrivate::GlWidgetInfo>::iterator end = glWidgets.end(); + QList<QWidgetPrivate::GlWidgetInfo>::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<QWidgetPrivate::GlWidgetInfo> &glWidgets = qt_widget_private(widget)->glWidgets; + QList<QWidgetPrivate::GlWidgetInfo>::iterator end = glWidgets.end(); + QList<QWidgetPrivate::GlWidgetInfo>::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<QWidgetPrivate::GlWidgetInfo> &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<QWidgetPrivate::GlWidgetInfo>::iterator end = glWidgets.end(); + QList<QWidgetPrivate::GlWidgetInfo>::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<QWidget> 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<CFStringRef>(value)).toInt()); + else if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(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<CFBooleanRef>(value)); + else if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast<CFStringRef>(value)).toInt()); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(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<CFURLRef> 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<NSInteger> 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<QWidget> 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<QDockWidget *>(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<QWidget> 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<QWidget*>(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<NSEvent *>(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<QTimer *>(q_func()->sender())) { + QHash<QWidget *, QTimer *>::iterator it = alertTimerHash.begin(); + while (it != alertTimerHash.end()) { + if (it.value() == timer) { + alertTimerHash.erase(it); + timer->deleteLater(); + break; + } + ++it; + } + if (alertTimerHash.isEmpty()) { + qt_mac_cancel_notification(); + } + } +} + +void QApplication::setCursorFlashTime(int msecs) +{ + QApplicationPrivate::cursor_flash_time = msecs; +} + +int QApplication::cursorFlashTime() +{ + return QApplicationPrivate::cursor_flash_time; +} + +void QApplication::setDoubleClickInterval(int ms) +{ + qt_mac_dblclick.use_qt_time_limit = true; + QApplicationPrivate::mouse_double_click_time = ms; +} + +int QApplication::doubleClickInterval() +{ + if (!qt_mac_dblclick.use_qt_time_limit) { //get it from the system + QSettings appleSettings(QLatin1String("apple.com")); + /* First worked as of 10.3.3 */ + double dci = appleSettings.value(QLatin1String("com/apple/mouse/doubleClickThreshold"), 0.5).toDouble(); + return int(dci * 1000); + } + return QApplicationPrivate::mouse_double_click_time; +} + +void QApplication::setKeyboardInputInterval(int ms) +{ + QApplicationPrivate::keyboard_input_time = ms; +} + +int QApplication::keyboardInputInterval() +{ + // FIXME: get from the system + return QApplicationPrivate::keyboard_input_time; +} + +#ifndef QT_NO_WHEELEVENT +void QApplication::setWheelScrollLines(int n) +{ + QApplicationPrivate::wheel_scroll_lines = n; +} + +int QApplication::wheelScrollLines() +{ + return QApplicationPrivate::wheel_scroll_lines; +} +#endif + +void QApplication::setEffectEnabled(Qt::UIEffect effect, bool enable) +{ + switch (effect) { + case Qt::UI_FadeMenu: + QApplicationPrivate::fade_menu = enable; + break; + case Qt::UI_AnimateMenu: + QApplicationPrivate::animate_menu = enable; + break; + case Qt::UI_FadeTooltip: + QApplicationPrivate::fade_tooltip = enable; + break; + case Qt::UI_AnimateTooltip: + QApplicationPrivate::animate_tooltip = enable; + break; + case Qt::UI_AnimateCombo: + QApplicationPrivate::animate_combo = enable; + break; + case Qt::UI_AnimateToolBox: + QApplicationPrivate::animate_toolbox = enable; + break; + case Qt::UI_General: + QApplicationPrivate::fade_tooltip = true; + break; + default: + QApplicationPrivate::animate_ui = enable; + break; + } + + if (enable) + QApplicationPrivate::animate_ui = true; +} + +bool QApplication::isEffectEnabled(Qt::UIEffect effect) +{ + if (QColormap::instance().depth() < 16 || !QApplicationPrivate::animate_ui) + return false; + + switch(effect) { + case Qt::UI_AnimateMenu: + return QApplicationPrivate::animate_menu; + case Qt::UI_FadeMenu: + return QApplicationPrivate::fade_menu; + case Qt::UI_AnimateCombo: + return QApplicationPrivate::animate_combo; + case Qt::UI_AnimateTooltip: + return QApplicationPrivate::animate_tooltip; + case Qt::UI_FadeTooltip: + return QApplicationPrivate::fade_tooltip; + case Qt::UI_AnimateToolBox: + return QApplicationPrivate::animate_toolbox; + default: + break; + } + return QApplicationPrivate::animate_ui; +} + +/*! + \internal +*/ +bool QApplicationPrivate::qt_mac_apply_settings() +{ + QSettings settings(QSettings::UserScope, QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + /* + Qt settings. This is how they are written into the datastream. + Palette/ * - QPalette + font - QFont + libraryPath - QStringList + style - QString + doubleClickInterval - int + cursorFlashTime - int + wheelScrollLines - int + colorSpec - QString + defaultCodec - QString + globalStrut/width - int + globalStrut/height - int + GUIEffects - QStringList + Font Substitutions/ * - QStringList + Font Substitutions/... - QStringList + */ + + // read library (ie. plugin) path list + QString libpathkey = + QString::fromLatin1("%1.%2/libraryPath") + .arg(QT_VERSION >> 16) + .arg((QT_VERSION & 0xff00) >> 8); + QStringList pathlist = settings.value(libpathkey).toString().split(QLatin1Char(':')); + if (!pathlist.isEmpty()) { + QStringList::ConstIterator it = pathlist.begin(); + while(it != pathlist.end()) + QApplication::addLibraryPath(*it++); + } + + QString defaultcodec = settings.value(QLatin1String("defaultCodec"), QVariant(QLatin1String("none"))).toString(); + if (defaultcodec != QLatin1String("none")) { + QTextCodec *codec = QTextCodec::codecForName(defaultcodec.toLatin1().constData()); + if (codec) + QTextCodec::setCodecForTr(codec); + } + + if (qt_is_gui_used) { + QString str; + QStringList strlist; + int num; + + // read new palette + int i; + QPalette pal(QApplication::palette()); + strlist = settings.value(QLatin1String("Palette/active")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Active, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/inactive")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Inactive, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + strlist = settings.value(QLatin1String("Palette/disabled")).toStringList(); + if (strlist.count() == QPalette::NColorRoles) { + for (i = 0; i < QPalette::NColorRoles; i++) + pal.setColor(QPalette::Disabled, (QPalette::ColorRole) i, + QColor(strlist[i])); + } + + if (pal != QApplication::palette()) + QApplication::setPalette(pal); + + // read new font + QFont font(QApplication::font()); + str = settings.value(QLatin1String("font")).toString(); + if (!str.isEmpty()) { + font.fromString(str); + if (font != QApplication::font()) + QApplication::setFont(font); + } + + // read new QStyle + QString stylename = settings.value(QLatin1String("style")).toString(); + if (! stylename.isNull() && ! stylename.isEmpty()) { + QStyle *style = QStyleFactory::create(stylename); + if (style) + QApplication::setStyle(style); + else + stylename = QLatin1String("default"); + } else { + stylename = QLatin1String("default"); + } + + num = settings.value(QLatin1String("doubleClickInterval"), + QApplication::doubleClickInterval()).toInt(); + QApplication::setDoubleClickInterval(num); + + num = settings.value(QLatin1String("cursorFlashTime"), + QApplication::cursorFlashTime()).toInt(); + QApplication::setCursorFlashTime(num); + +#ifndef QT_NO_WHEELEVENT + num = settings.value(QLatin1String("wheelScrollLines"), + QApplication::wheelScrollLines()).toInt(); + QApplication::setWheelScrollLines(num); +#endif + + QString colorspec = settings.value(QLatin1String("colorSpec"), + QVariant(QLatin1String("default"))).toString(); + if (colorspec == QLatin1String("normal")) + QApplication::setColorSpec(QApplication::NormalColor); + else if (colorspec == QLatin1String("custom")) + QApplication::setColorSpec(QApplication::CustomColor); + else if (colorspec == QLatin1String("many")) + QApplication::setColorSpec(QApplication::ManyColor); + else if (colorspec != QLatin1String("default")) + colorspec = QLatin1String("default"); + + int w = settings.value(QLatin1String("globalStrut/width")).toInt(); + int h = settings.value(QLatin1String("globalStrut/height")).toInt(); + QSize strut(w, h); + if (strut.isValid()) + QApplication::setGlobalStrut(strut); + + QStringList effects = settings.value(QLatin1String("GUIEffects")).toStringList(); + if (!effects.isEmpty()) { + if (effects.contains(QLatin1String("none"))) + QApplication::setEffectEnabled(Qt::UI_General, false); + if (effects.contains(QLatin1String("general"))) + QApplication::setEffectEnabled(Qt::UI_General, true); + if (effects.contains(QLatin1String("animatemenu"))) + QApplication::setEffectEnabled(Qt::UI_AnimateMenu, true); + if (effects.contains(QLatin1String("fademenu"))) + QApplication::setEffectEnabled(Qt::UI_FadeMenu, true); + if (effects.contains(QLatin1String("animatecombo"))) + QApplication::setEffectEnabled(Qt::UI_AnimateCombo, true); + if (effects.contains(QLatin1String("animatetooltip"))) + QApplication::setEffectEnabled(Qt::UI_AnimateTooltip, true); + if (effects.contains(QLatin1String("fadetooltip"))) + QApplication::setEffectEnabled(Qt::UI_FadeTooltip, true); + if (effects.contains(QLatin1String("animatetoolbox"))) + QApplication::setEffectEnabled(Qt::UI_AnimateToolBox, true); + } else { + QApplication::setEffectEnabled(Qt::UI_General, true); + } + + settings.beginGroup(QLatin1String("Font Substitutions")); + QStringList fontsubs = settings.childKeys(); + if (!fontsubs.isEmpty()) { + QStringList::Iterator it = fontsubs.begin(); + for (; it != fontsubs.end(); ++it) { + QString fam = QString::fromLatin1((*it).toLatin1().constData()); + QStringList subs = settings.value(fam).toStringList(); + QFont::insertSubstitutions(fam, subs); + } + } + settings.endGroup(); + } + + settings.endGroup(); + return true; +} + +// DRSWAT + +bool QApplicationPrivate::canQuit() +{ +#ifndef QT_MAC_USE_COCOA + return true; +#else + Q_Q(QApplication); +#ifdef QT_MAC_USE_COCOA + [[NSApp mainMenu] cancelTracking]; +#else + HiliteMenu(0); +#endif + + bool handle_quit = true; + if (QApplicationPrivate::modalState() && [[[[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] + menuLoader] quitMenuItem] isEnabled]) { + int visible = 0; + const QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); ++i) { + if (tlws.at(i)->isVisible()) + ++visible; + } + handle_quit = (visible <= 1); + } + if (handle_quit) { + QCloseEvent ev; + QApplication::sendSpontaneousEvent(q, &ev); + if (ev.isAccepted()) { + return true; + } + } + return false; +#endif +} + +void onApplicationWindowChangedActivation(QWidget *widget, bool activated) +{ +#if QT_MAC_USE_COCOA + if (!widget) + return; + + if (activated) { + if (QApplicationPrivate::app_style) { + QEvent ev(QEvent::Style); + qt_sendSpontaneousEvent(QApplicationPrivate::app_style, &ev); + } + qApp->setActiveWindow(widget); + } else { // deactivated + if (QApplicationPrivate::active_window == widget) + qApp->setActiveWindow(0); + } + + QMenuBar::macUpdateMenuBar(); + qt_mac_update_cursor(); +#else + Q_UNUSED(widget); + Q_UNUSED(activated); +#endif +} + + +void onApplicationChangedActivation( bool activated ) +{ +#if QT_MAC_USE_COCOA + QApplication *app = qApp; + +//NSLog(@"App Changed Activation\n"); + + if ( activated ) { + if (QApplication::desktopSettingsAware()) + qt_mac_update_os_settings(); + + if (qt_clipboard) { //manufacture an event so the clipboard can see if it has changed + QEvent ev(QEvent::Clipboard); + qt_sendSpontaneousEvent(qt_clipboard, &ev); + } + + if (app) { + QEvent ev(QEvent::ApplicationActivate); + qt_sendSpontaneousEvent(app, &ev); + } + + if (!app->activeWindow()) { + OSWindowRef wp = [NSApp keyWindow]; + if (QWidget *tmp_w = qt_mac_find_window(wp)) + app->setActiveWindow(tmp_w); + } + QMenuBar::macUpdateMenuBar(); + qt_mac_update_cursor(); + } else { // de-activated + QApplicationPrivate *priv = [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] qAppPrivate]; + while (priv->inPopupMode()) + app->activePopupWidget()->close(); + if (app) { + QEvent ev(QEvent::ApplicationDeactivate); + qt_sendSpontaneousEvent(app, &ev); + } + app->setActiveWindow(0); + } +#else + Q_UNUSED(activated); +#endif +} + +void QApplicationPrivate::initializeMultitouch_sys() +{ } +void QApplicationPrivate::cleanupMultitouch_sys() +{ } + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qclipboard_mac.cpp b/src/gui/platforms/mac/qclipboard_mac.cpp new file mode 100644 index 0000000000..4a8bc56e41 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include "qevent.h" +#include "qurl.h" +#include <stdlib.h> +#include <string.h> +#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<CFDataRef> 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<QByteArray> md = promise.convertor->convertFromMime(promise.mime, promise.data, flavorAsQString); + if (md.size() <= promise.offset) + return cantGetFlavorErr; + const QByteArray &ba = md[promise.offset]; + QCFType<CFDataRef> 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<CFArrayRef> types; + if(PasteboardCopyItemFlavors(paste, id, &types ) != noErr) + return false; + + const int type_count = CFArrayGetCount(types); + for(int i = 0; i < type_count; ++i) { + CFStringRef flavor = (CFStringRef)CFArrayGetValueAtIndex(types, i); + 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<QMacPasteboardMime*> 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<QMacPasteboardMime *>::Iterator it = availableConverters.begin(); it != availableConverters.end(); ++it) { + QMacPasteboardMime *c = (*it); + QString flavor(c->flavorFor(mimeType)); + if(!flavor.isEmpty()) { + QVariant mimeData = static_cast<QMacMimeData*>(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<CFArrayRef> 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<CFArrayRef> 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<QMacPasteboardMime *> 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<QByteArray> retList; + for(uint index = 1; index <= cnt; ++index) { + PasteboardItemID id; + if(PasteboardGetItemIdentifier(paste, index, &id) != noErr) + continue; + + QCFType<CFArrayRef> 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<CFStringRef>(CFArrayGetValueAtIndex(types, i)); + if(c_flavor == QCFString::toQString(flavor)) { + QCFType<CFDataRef> 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<QMacPasteboard *>(this)->setMimeData(0); + +#ifdef DEBUG_PASTEBOARD + if(fromGlobal) + qDebug("Pasteboard: Synchronize!"); +#endif + return fromGlobal; +} + + + + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qcocoaapplication_mac.mm b/src/gui/platforms/mac/qcocoaapplication_mac.mm new file mode 100644 index 0000000000..872f31dec7 --- /dev/null +++ b/src/gui/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 <qglobal.h> +#ifdef QT_MAC_USE_COCOA +#include <private/qcocoaapplication_mac_p.h> +#include <private/qcocoaapplicationdelegate_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qcocoaintrospection_p.h> + +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<QCocoaPostMessageArgs *>(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<EventRef>(event))) + return true; + + if ([event type] == NSApplicationDefined) { + switch ([event subtype]) { + case QtCocoaEventSubTypePostMessage: + [NSApp qt_sendPostedMessage:event]; + return true; + default: + break; + } + } + return false; +} + +@end + +@implementation QNSApplication + +- (void)qt_sendEvent_original:(NSEvent *)event +{ + Q_UNUSED(event); + // This method will only be used as a signature + // template for the method we add into NSApplication + // containing the original [NSApplication sendEvent:] implementation +} + +- (void)qt_sendEvent_replacement:(NSEvent *)event +{ + // This method (or its implementation to be precise) will + // be called instead of sendEvent if redirection occurs. + // 'self' will then be an instance of NSApplication + // (and not QNSApplication) + if (![NSApp qt_filterEvent:event]) + [self qt_sendEvent_original:event]; +} + +- (void)sendEvent:(NSEvent *)event +{ + // This method will be called if + // no redirection occurs + if (![NSApp qt_filterEvent:event]) + [super sendEvent:event]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + // Forward actions sendt from the menu bar (e.g. quit) to the menu loader. + // Having this method here means that we are the last stop in the responder + // chain, and that we are able to handle menu actions even when no window is + // visible on screen. Note: If Qt is used as a plugin, Qt will not use a + // native menu bar. Hence, we will also not need to do any redirection etc. as + // we do with sendEvent. + [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent() +{ + if ([NSApp isMemberOfClass:[QNSApplication class]]) { + // No need to change implementation since Qt + // already controls a subclass of NSApplication + return; + } + + // Change the implementation of [NSApplication sendEvent] to the + // implementation of qt_sendEvent_replacement found in QNSApplication. + // And keep the old implementation that gets overwritten inside a new + // method 'qt_sendEvent_original' that we add to NSApplication + qt_cocoa_change_implementation( + [NSApplication class], + @selector(sendEvent:), + [QNSApplication class], + @selector(qt_sendEvent_replacement:), + @selector(qt_sendEvent_original:)); + } + +QT_END_NAMESPACE +#endif diff --git a/src/gui/platforms/mac/qcocoaapplication_mac_p.h b/src/gui/platforms/mac/qcocoaapplication_mac_p.h new file mode 100644 index 0000000000..0c3f5e442d --- /dev/null +++ b/src/gui/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 <AppKit/AppKit.h> +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) +@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); + +@interface NSApplication (QT_MANGLE_NAMESPACE(QApplicationIntegration)) +- (void)QT_MANGLE_NAMESPACE(qt_setDockMenu):(NSMenu *)newMenu; +- (QApplicationPrivate *)QT_MANGLE_NAMESPACE(qt_qappPrivate); +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader); +- (int)QT_MANGLE_NAMESPACE(qt_validModesForFontPanel):(NSFontPanel *)fontPanel; + +- (void)qt_sendPostedMessage:(NSEvent *)event; +- (BOOL)qt_filterEvent:(NSEvent *)event; +@end + +@interface QNSApplication : NSApplication { +} +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent(); + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/platforms/mac/qcocoaapplicationdelegate_mac.mm b/src/gui/platforms/mac/qcocoaapplicationdelegate_mac.mm new file mode 100644 index 0000000000..77cd8902c3 --- /dev/null +++ b/src/gui/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 <private/qcocoaapplicationdelegate_mac_p.h> +#import <private/qcocoamenuloader_mac_p.h> +#import <private/qcocoaapplication_mac_p.h> +#include <private/qapplication_p.h> +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qdesktopwidget_mac_p.h> +#include <qevent.h> +#include <qurl.h> +#include <qapplication.h> + +QT_BEGIN_NAMESPACE +extern void onApplicationChangedActivation(bool); // qapplication_mac.mm +extern void qt_release_apple_event_handler(); //qapplication_mac.mm +extern QPointer<QWidget> qt_last_mouse_receiver; // qapplication_mac.cpp +extern QPointer<QWidget> qt_last_native_mouse_receiver; // qt_cocoa_helpers_mac.mm +extern QPointer<QWidget> 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 <NSApplicationDelegate> *)oldDelegate +{ + [oldDelegate retain]; + [reflectionDelegate release]; + reflectionDelegate = oldDelegate; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + NSMethodSignature *result = [super methodSignatureForSelector:aSelector]; + if (!result && reflectionDelegate) { + result = [reflectionDelegate methodSignatureForSelector:aSelector]; + } + return result; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + BOOL result = [super respondsToSelector:aSelector]; + if (!result && reflectionDelegate) + result = [reflectionDelegate respondsToSelector:aSelector]; + return result; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + SEL invocationSelector = [invocation selector]; + if (reflectionDelegate && [reflectionDelegate respondsToSelector:invocationSelector]) + [invocation invokeWithTarget:reflectionDelegate]; + else + [self doesNotRecognizeSelector:invocationSelector]; +} + +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(replyEvent); + + NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + QUrl url(qt_mac_NSStringToQString(urlString)); + QFileOpenEvent qtEvent(url); + qt_sendSpontaneousEvent(qAppInstance(), &qtEvent); +} + +- (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(event); + Q_UNUSED(replyEvent); + [NSApp terminate:self]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end +#endif diff --git a/src/gui/platforms/mac/qcocoaapplicationdelegate_mac_p.h b/src/gui/platforms/mac/qcocoaapplicationdelegate_mac_p.h new file mode 100644 index 0000000000..714c046f48 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +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 <NSObject> +- (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 <NSApplicationDelegate> { + bool startedQuit; + QApplicationPrivate *qtPrivate; + NSMenu *dockMenu; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader; + NSObject <NSApplicationDelegate> *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 <NSApplicationDelegate> *)oldDelegate; +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end +#endif diff --git a/src/gui/platforms/mac/qcocoaintrospection_mac.mm b/src/gui/platforms/mac/qcocoaintrospection_mac.mm new file mode 100644 index 0000000000..70c893aeec --- /dev/null +++ b/src/gui/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 <private/qcocoaintrospection_p.h> + +QT_BEGIN_NAMESPACE + +void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel, SEL backupSel) +{ +#ifndef QT_MAC_USE_COCOA + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) +#endif + { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + // The following code replaces the _implementation_ for the selector we want to hack + // (originalSel) with the implementation found in proxyClass. Then it creates + // a new 'backup' method inside baseClass containing the old, original, + // implementation (fakeSel). You can let the proxy implementation of originalSel + // call fakeSel if needed (similar approach to calling a super class implementation). + // fakeSel must also be implemented in proxyClass, as the signature is used + // as template for the method one we add into baseClass. + // NB: You will typically never create any instances of proxyClass; we use it + // only for stealing its contents and put it into baseClass. + if (!replacementSel) + replacementSel = originalSel; + + Method originalMethod = class_getInstanceMethod(baseClass, originalSel); + Method replacementMethod = class_getInstanceMethod(proxyClass, replacementSel); + IMP originalImp = method_setImplementation(originalMethod, method_getImplementation(replacementMethod)); + + if (backupSel) { + Method backupMethod = class_getInstanceMethod(proxyClass, backupSel); + class_addMethod(baseClass, backupSel, originalImp, method_getTypeEncoding(backupMethod)); + } +#endif + } +} + +void qt_cocoa_change_back_implementation(Class baseClass, SEL originalSel, SEL backupSel) +{ +#ifndef QT_MAC_USE_COCOA + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) +#endif + { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + Method originalMethod = class_getInstanceMethod(baseClass, originalSel); + Method backupMethodInBaseClass = class_getInstanceMethod(baseClass, backupSel); + method_setImplementation(originalMethod, method_getImplementation(backupMethodInBaseClass)); +#endif + } +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qcocoaintrospection_p.h b/src/gui/platforms/mac/qcocoaintrospection_p.h new file mode 100644 index 0000000000..1c7d6ac13c --- /dev/null +++ b/src/gui/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 <qglobal.h> +#import <objc/objc-class.h> + +QT_BEGIN_NAMESPACE + +void qt_cocoa_change_implementation(Class baseClass, SEL originalSel, Class proxyClass, SEL replacementSel = 0, SEL backupSel = 0); +void qt_cocoa_change_back_implementation(Class baseClass, SEL originalSel, SEL backupSel); + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qcocoamenuloader_mac.mm b/src/gui/platforms/mac/qcocoamenuloader_mac.mm new file mode 100644 index 0000000000..71ff011069 --- /dev/null +++ b/src/gui/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 <qaction.h> +#include <qcoreapplication.h> +#include <private/qcocoamenuloader_mac_p.h> +#include <private/qapplication_p.h> +#include <private/qt_mac_p.h> +#include <private/qmenubar_p.h> +#include <qmenubar.h> +#include <private/qt_cocoa_helpers_mac_p.h> + +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<const NSString*>(QCFString::toCFStringRef(qAppName())); + [quitItem setTitle:[[quitItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [hideItem setTitle:[[hideItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [aboutItem setTitle:[[aboutItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(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<NSMenuItem *>(sender); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) { + action->trigger(); + } else if (item == quitItem) { + // We got here because someone was once the quitItem, but it has been + // abandoned (e.g., the menubar was deleted). In the meantime, just do + // normal QApplication::quit(). + qApp->quit(); + } +} + + - (void)orderFrontCharacterPalette:(id)sender + { + [NSApp orderFrontCharacterPalette:sender]; + } +@end +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/platforms/mac/qcocoamenuloader_mac_p.h b/src/gui/platforms/mac/qcocoamenuloader_mac_p.h new file mode 100644 index 0000000000..cfcc7e00c6 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSResponder +{ + IBOutlet NSMenu *theMenu; + IBOutlet NSMenu *appMenu; + IBOutlet NSMenuItem *quitItem; + IBOutlet NSMenuItem *preferencesItem; + IBOutlet NSMenuItem *aboutItem; + IBOutlet NSMenuItem *aboutQtItem; + IBOutlet NSMenuItem *hideItem; + NSMenuItem *lastAppSpecificItem; + NSMenuItem *servicesItem; + NSMenuItem *hideAllOthersItem; + NSMenuItem *showAllItem; +} +- (void)ensureAppMenuInMenu:(NSMenu *)menu; +- (void)removeActionsFromAppMenu; +- (NSMenu *)applicationMenu; +- (NSMenu *)menu; +- (NSMenuItem *)quitMenuItem; +- (NSMenuItem *)preferencesMenuItem; +- (NSMenuItem *)aboutMenuItem; +- (NSMenuItem *)aboutQtMenuItem; +- (NSMenuItem *)hideMenuItem; +- (NSMenuItem *)appSpecificMenuItem; +- (IBAction)terminate:(id)sender; +- (IBAction)orderFrontStandardAboutPanel:(id)sender; +- (IBAction)hideOtherApplications:(id)sender; +- (IBAction)unhideAllApplications:(id)sender; +- (IBAction)hide:(id)sender; +- (IBAction)qtDispatcherToQAction:(id)sender; +- (void)qtUpdateMenubar; +- (void)orderFrontCharacterPalette:(id)sender; +@end + +#endif // QT_MAC_USE_COCOA +#endif // QCOCOAMENULOADER_P_H diff --git a/src/gui/platforms/mac/qcocoapanel_mac.mm b/src/gui/platforms/mac/qcocoapanel_mac.mm new file mode 100644 index 0000000000..67a12e25f8 --- /dev/null +++ b/src/gui/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 <private/qcocoapanel_mac_p.h> +#ifdef QT_MAC_USE_COCOA +#import <private/qt_cocoa_helpers_mac_p.h> +#import <private/qcocoawindow_mac_p.h> +#import <private/qcocoawindowdelegate_mac_p.h> +#import <private/qcocoaview_mac_p.h> +#import <private/qcocoawindowcustomthemeframe_mac_p.h> +#import <private/qcocoaapplication_mac_p.h> +#import <private/qmultitouch_mac_p.h> +#import <private/qapplication_p.h> +#import <private/qbackingstore_p.h> +#import <private/qdnd_p.h> + +#include <QtGui/QWidget> + +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_USE_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QCocoaPanel) + +/*********************************************************************** + Copy and Paste between QCocoaWindow and QCocoaPanel + This is a bit unfortunate, but thanks to the dynamic dispatch we + have to duplicate this code or resort to really silly forwarding methods +**************************************************************************/ +#include "qcocoasharedwindowmethods_mac_p.h" + +@end +#endif diff --git a/src/gui/platforms/mac/qcocoapanel_mac_p.h b/src/gui/platforms/mac/qcocoapanel_mac_p.h new file mode 100644 index 0000000000..542615903e --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +QT_FORWARD_DECLARE_CLASS(QStringList); +QT_FORWARD_DECLARE_CLASS(QCocoaDropData); + +@interface NSPanel (QtIntegration) +- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender; +- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender; +- (void)draggingExited:(id <NSDraggingInfo>)sender; +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender; +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaPanel) : NSPanel { + QStringList *currentCustomDragTypes; + QCocoaDropData *dropData; + NSInteger dragEnterSequence; +} + ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask; +- (void)registerDragTypes; +- (void)drawRectOriginal:(NSRect)rect; + +@end +#endif + +#endif diff --git a/src/gui/platforms/mac/qcocoasharedwindowmethods_mac_p.h b/src/gui/platforms/mac/qcocoasharedwindowmethods_mac_p.h new file mode 100644 index 0000000000..ee1115bd4e --- /dev/null +++ b/src/gui/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<QWidget> 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<QWidget>, 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 <NSDraggingInfo>)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 <NSDraggingInfo>)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 <NSDraggingInfo>)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 <NSDraggingInfo>)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 <NSDraggingInfo>)sender +{ + QWidget *qwidget = *currentDragTarget(); + if (!qwidget) + return NO; + + *currentDragTarget() = 0; + NSPoint nswindowPoint = [sender draggingLocation]; + NSPoint nsglobalPoint = [[sender draggingDestinationWindow] convertBaseToScreen:nswindowPoint]; + QPoint globalPoint = flipPoint(nsglobalPoint).toPoint(); + + [self addDropData:sender]; + + NSDragOperation nsActions = [sender draggingSourceOperationMask]; + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations(nsActions); + QMimeData *mimeData = dropData; + + if (QDragManager::self()->source()) + mimeData = QDragManager::self()->dragPrivate()->data; + if (QDragManager::self()->object) + QDragManager::self()->dragPrivate()->target = qwidget; + + QPoint localPoint(qwidget->mapFromGlobal(globalPoint)); + QDropEvent de(localPoint, qtAllowed, mimeData, + QApplication::mouseButtons(), QApplication::keyboardModifiers()); + QApplication::sendEvent(qwidget, &de); + + if (QDragManager::self()->object) + QDragManager::self()->dragPrivate()->executed_action = de.dropAction(); + + return de.isAccepted(); +} + +// This is a hack and it should be removed once we find the real cause for +// the painting problems. +// We have a static variable that signals if we have been called before or not. +static bool firstDrawingInvocation = true; + +// The method below exists only as a workaround to draw/not draw the baseline +// in the title bar. This is to support unifiedToolbar look. + +// This method is very special. To begin with, it is a +// method that will get called only if we enable documentMode. +// Furthermore, it won't get called as a normal method, we swap +// this method with the normal implementation of drawRect in +// _NSThemeFrame. When this method is active, its mission is to +// first call the original drawRect implementation so the widget +// gets proper painting. After that, it needs to detect if there +// is a toolbar or not, in order to decide how to handle the unified +// look. The distinction is important since the presence and +// visibility of a toolbar change the way we enter into unified mode. +// When there is a toolbar and that toolbar is visible, the problem +// is as simple as to tell the toolbar not to draw its baseline. +// However when there is not toolbar or the toolbar is not visible, +// we need to draw a line on top of the baseline, because the baseline +// in that case will belong to the title. For this case we need to draw +// a line on top of the baseline. +// As usual, there is a special case. When we first are called, we might +// need to repaint ourselves one more time. We only need that if we +// didn't get the activation, i.e. when we are launched via the command +// line. And this only if the toolbar is visible from the beginning, +// so we have a special flag that signals if we need to repaint or not. +- (void)drawRectSpecial:(NSRect)rect +{ + // Call the original drawing method. + [id(self) drawRectOriginal:rect]; + NSWindow *window = [id(self) window]; + NSToolbar *toolbar = [window toolbar]; + if(!toolbar) { + // There is no toolbar, we have to draw a line on top of the line drawn by Cocoa. + macDrawRectOnTop((void *)window); + } else { + if([toolbar isVisible]) { + // We tell Cocoa to avoid drawing the line at the end. + if(firstDrawingInvocation) { + firstDrawingInvocation = false; + macSyncDrawingOnFirstInvocation((void *)window); + } else + [toolbar setShowsBaselineSeparator:NO]; + } else { + // There is a toolbar but it is not visible so + // we have to draw a line on top of the line drawn by Cocoa. + macDrawRectOnTop((void *)window); + } + } +} + +- (void)drawRectOriginal:(NSRect)rect +{ + Q_UNUSED(rect) + // This method implementation is here to silenct the compiler. + // See drawRectSpecial for information. +} + diff --git a/src/gui/platforms/mac/qcocoaview_mac.mm b/src/gui/platforms/mac/qcocoaview_mac.mm new file mode 100644 index 0000000000..e885d1552c --- /dev/null +++ b/src/gui/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 <private/qcocoaview_mac_p.h> +#ifdef QT_MAC_USE_COCOA + +#include <private/qwidget_p.h> +#include <private/qt_mac_p.h> +#include <private/qapplication_p.h> +#include <private/qabstractscrollarea_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qdnd_p.h> +#include <private/qmacinputcontext_p.h> +#include <private/qevent_p.h> +#include <private/qbackingstore_p.h> +#include <private/qwindowsurface_raster_p.h> +#include <private/qunifiedtoolbarsurface_mac_p.h> + +#include <qscrollarea.h> +#include <qhash.h> +#include <qtextformat.h> +#include <qpaintengine.h> +#include <QUrl> +#include <QAccessible> +#include <QFileInfo> +#include <QFile> + +#include <qdebug.h> + +@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<QWidget> qt_last_mouse_receiver; // qapplication_mac.cpp +extern QPointer<QWidget> 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<QWidget> 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<QRasterWindowSurface *>(qwidget->windowSurface()); + if (!winSurface || !winSurface->needsFlush) { + qt_mac_release_graphics_context(context); + return; + } + + // Clip to region. + const QVector<QRect> &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<QAbstractScrollArea *>(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<CFStringRef>([aString string])); + } else { + commitText = QCFString::toQString(reinterpret_cast<CFStringRef>(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<QInputMethodEvent::Attribute> attrs; + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, selRange.location + selRange.length, 1, QVariant()); + if ([aString isKindOfClass:[NSAttributedString class]]) { + qtText = QCFString::toQString(reinterpret_cast<CFStringRef>([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<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + effectiveRange.location, + effectiveRange.length, + format); + } + index = effectiveRange.location + effectiveRange.length; + } + } else { + // No attributes specified, take only the preedit text. + qtText = QCFString::toQString(reinterpret_cast<CFStringRef>(aString)); + composingLength = qtText.length(); + } + // Make sure that we have at least one text format. + if (attrs.size() <= 1) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<<QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, + 0, composingLength, format); + } + *composingText = qtText; + + QInputMethodEvent e(qtText, attrs); + if (QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget)) + qt_sendSpontaneousEvent(widgetToGetKey, &e); + + if (!composingLength) + composing = false; +} + +- (void) unmarkText +{ + if (composing) { + QInputMethodEvent e; + e.setCommitString(*composingText); + if (QWidget *widgetToGetKey = qt_mac_getTargetForKeyEvent(qwidget)) + qt_sendSpontaneousEvent(widgetToGetKey, &e); + } + composingText->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<const NSString *>((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast<NSString *>(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<QT_MANGLE_NAMESPACE(QCocoaView) *>(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<QT_MANGLE_NAMESPACE(QCocoaView) *>(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<QT_MANGLE_NAMESPACE(QCocoaView) *>(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<CFURLRef> pasteLocation = 0; + PasteboardCopyPasteLocation(dragBoard.pasteBoard(), &pasteLocation); + if (pasteLocation) { + QList<QUrl> urls = o->mimeData()->urls(); + for (int i = 0; i < urls.size(); ++i) { + QUrl fromUrl = urls.at(i); + QString filename = QFileInfo(fromUrl.path()).fileName(); + QUrl toUrl(QCFString::toQString(CFURLGetString(pasteLocation)) + filename); + if (performedAction == Qt::MoveAction) + QFile::rename(fromUrl.path(), toUrl.path()); + else if (performedAction == Qt::CopyAction) + QFile::copy(fromUrl.path(), toUrl.path()); + } + } + } + + // Clean-up: + o->setMimeData(0); + o->deleteLater(); + return performedAction; +} + +QT_END_NAMESPACE + +#endif // QT_MAC_USE_COCOA diff --git a/src/gui/platforms/mac/qcocoaview_mac_p.h b/src/gui/platforms/mac/qcocoaview_mac_p.h new file mode 100644 index 0000000000..cc79b6705b --- /dev/null +++ b/src/gui/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 <qevent.h> +#ifdef QT_MAC_USE_COCOA +#import <Cocoa/Cocoa.h> + +@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 <NSTextInput> { + QWidget *qwidget; + QWidgetPrivate *qwidgetprivate; + NSDragOperation supportedActions; + bool composing; + int composingLength; + bool sendKeyEvents; + bool fromKeyDownEvent; + QString *composingText; + @public int alienTouchCount; +} +- (id)initWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate; +- (void) finishInitWithQWidget:(QWidget *)widget widgetPrivate:(QWidgetPrivate *)widgetprivate; +- (void)frameDidChange:(NSNotification *)note; +- (void)setSupportedActions:(NSDragOperation)actions; +- (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal; +- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; +- (BOOL)isComposing; +- (QWidget *)qt_qwidget; +- (void) qt_clearQWidget; + +@end +#endif diff --git a/src/gui/platforms/mac/qcocoawindow_mac.mm b/src/gui/platforms/mac/qcocoawindow_mac.mm new file mode 100644 index 0000000000..6e5023aaca --- /dev/null +++ b/src/gui/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 <private/qcocoawindow_mac_p.h> +#import <private/qcocoawindowdelegate_mac_p.h> +#import <private/qcocoaview_mac_p.h> +#import <private/qt_cocoa_helpers_mac_p.h> +#import <private/qcocoawindowcustomthemeframe_mac_p.h> +#import <private/qcocoaapplication_mac_p.h> +#import <private/qdnd_p.h> +#import <private/qmultitouch_mac_p.h> + +#include <QtGui/QWidget> + +QT_FORWARD_DECLARE_CLASS(QWidget); +QT_USE_NAMESPACE + +@implementation NSWindow (QT_MANGLE_NAMESPACE(QWidgetIntegration)) + +- (id)QT_MANGLE_NAMESPACE(qt_initWithQWidget):(QWidget*)widget contentRect:(NSRect)rect styleMask:(NSUInteger)mask +{ + self = [self initWithContentRect:rect styleMask:mask backing:NSBackingStoreBuffered defer:YES]; + if (self) { + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] becomeDelegteForWindow:self widget:widget]; + [self setReleasedWhenClosed:NO]; + } + return self; +} + +- (QWidget *)QT_MANGLE_NAMESPACE(qt_qwidget) +{ + QWidget *widget = 0; + if ([self delegate] == [QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate]) + widget = [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] qt_qwidgetForWindow:self]; + return widget; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QCocoaWindow) + +/*********************************************************************** + Copy and Paste between QCocoaWindow and QCocoaPanel + This is a bit unfortunate, but thanks to the dynamic dispatch we + have to duplicate this code or resort to really silly forwarding methods +**************************************************************************/ +#include "qcocoasharedwindowmethods_mac_p.h" + +@end +#endif diff --git a/src/gui/platforms/mac/qcocoawindow_mac_p.h b/src/gui/platforms/mac/qcocoawindow_mac_p.h new file mode 100644 index 0000000000..d567cab244 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#include <private/qapplication_p.h> +#include <private/qbackingstore_p.h> + +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 <NSDraggingInfo>)sender; +- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender; +- (void)draggingExited:(id <NSDraggingInfo>)sender; +- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender; +@end + +@interface QT_MANGLE_NAMESPACE(QCocoaWindow) : NSWindow { + QStringList *currentCustomDragTypes; + QCocoaDropData *dropData; + NSInteger dragEnterSequence; +} + ++ (Class)frameViewClassForStyleMask:(NSUInteger)styleMask; +- (void)registerDragTypes; +- (void)drawRectOriginal:(NSRect)rect; + +@end +#endif + +#endif diff --git a/src/gui/platforms/mac/qcocoawindowcustomthemeframe_mac.mm b/src/gui/platforms/mac/qcocoawindowcustomthemeframe_mac.mm new file mode 100644 index 0000000000..b761934c01 --- /dev/null +++ b/src/gui/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/gui/platforms/mac/qcocoawindowcustomthemeframe_mac_p.h b/src/gui/platforms/mac/qcocoawindowcustomthemeframe_mac_p.h new file mode 100644 index 0000000000..09b40875f6 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#include "qmacdefines_mac.h" +#import "qnsthemeframe_mac_p.h" + +@interface QT_MANGLE_NAMESPACE(QCocoaWindowCustomThemeFrame) : NSThemeFrame +{ +} + +@end diff --git a/src/gui/platforms/mac/qcocoawindowdelegate_mac.mm b/src/gui/platforms/mac/qcocoawindowdelegate_mac.mm new file mode 100644 index 0000000000..1faf068a12 --- /dev/null +++ b/src/gui/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 <private/qwidget_p.h> +#include <private/qapplication_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <qevent.h> +#include <qlayout.h> +#include <qcoreapplication.h> +#include <qmenubar.h> +#include <QMainWindow> +#include <QToolBar> +#include <private/qmainwindowlayout_p.h> + +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<NSWindow *, QWidget *>(); + m_drawerHash = new QHash<NSDrawer *, QWidget *>(); + } + return self; +} + +- (void)dealloc +{ + sharedCocoaWindowDelegate = nil; + QHash<NSWindow *, QWidget *>::const_iterator windowIt = m_windowHash->constBegin(); + while (windowIt != m_windowHash->constEnd()) { + [windowIt.key() setDelegate:nil]; + ++windowIt; + } + delete m_windowHash; + QHash<NSDrawer *, QWidget *>::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<QMainWindow*>(qwidget->window()); + if (mWindow) { + QMainWindowLayout *mLayout = qobject_cast<QMainWindowLayout*>(mWindow->layout()); + QList<QToolBar *> toolbarList = mLayout->qtoolbarsInUnifiedToolbarList; + + for (int i = 0; i < toolbarList.size(); ++i) { + QToolBar* toolbar = toolbarList.at(i); + 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<CGFloat>(frameToReturn.size.width, + size.width()+(windowFrameRect.size.width - viewFrameRect.size.width)); + frameToReturn.size.height = qMin<CGFloat>(frameToReturn.size.height, + size.height()+(windowFrameRect.size.height - viewFrameRect.size.height)); + return frameToReturn; +} + +- (void)becomeDelegteForWindow:(NSWindow *)window widget:(QWidget *)widget +{ + m_windowHash->insert(window, widget); + [window setDelegate:self]; +} + +- (void)resignDelegateForWindow:(NSWindow *)window +{ + [window setDelegate:nil]; + m_windowHash->remove(window); +} + +- (void)becomeDelegateForDrawer:(NSDrawer *)drawer widget:(QWidget *)widget +{ + m_drawerHash->insert(drawer, widget); + [drawer setDelegate:self]; + NSWindow *window = [[drawer contentView] window]; + [self becomeDelegteForWindow:window widget:widget]; +} + +- (void)resignDelegateForDrawer:(NSDrawer *)drawer +{ + QWidget *widget = m_drawerHash->value(drawer); + [drawer setDelegate:nil]; + if (widget) + [self resignDelegateForWindow:[[drawer contentView] window]]; + m_drawerHash->remove(drawer); +} + +- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu +{ + Q_UNUSED(menu); + QWidget *qwidget = m_windowHash->value(window); + if (qwidget && !qwidget->windowFilePath().isEmpty()) { + return YES; + } + return NO; +} + +- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event + from:(NSPoint)dragImageLocation + withPasteboard:(NSPasteboard *)pasteboard +{ + Q_UNUSED(event); + Q_UNUSED(dragImageLocation); + Q_UNUSED(pasteboard); + QWidget *qwidget = m_windowHash->value(window); + if (qwidget && !qwidget->windowFilePath().isEmpty()) { + return YES; + } + return NO; +} + +- (void)syncContentViewFrame: (NSNotification *)notification +{ + NSView *cView = [notification object]; + if (cView) { + NSWindow *window = [cView window]; + QWidget *qwidget = m_windowHash->value(window); + if (qwidget) { + QWidgetData *widgetData = qt_qwidget_data(qwidget); + NSRect rect = [cView frame]; + const QSize newSize(rect.size.width, rect.size.height); + const QSize &oldSize = widgetData->crect.size(); + if (newSize != oldSize) { + [self syncSizeForWidget:qwidget toSize:newSize fromSize:oldSize]; + } + } + + } +} + +@end +#endif// QT_MAC_USE_COCOA diff --git a/src/gui/platforms/mac/qcocoawindowdelegate_mac_p.h b/src/gui/platforms/mac/qcocoawindowdelegate_mac_p.h new file mode 100644 index 0000000000..638ce2df9a --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +QT_BEGIN_NAMESPACE +template <class Key, class T> 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 <NSObject> +- (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 <NSObject> +- (NSSize)drawerWillResizeContents:(NSDrawer *)sender toSize:(NSSize)contentSize; +@end + +#endif + + + +@interface QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) : NSObject<NSWindowDelegate, NSDrawerDelegate> { + QHash<NSWindow *, QWidget *> *m_windowHash; + QHash<NSDrawer *, QWidget *> *m_drawerHash; +} ++ (QT_MANGLE_NAMESPACE(QCocoaWindowDelegate)*)sharedDelegate; +- (void)becomeDelegteForWindow:(NSWindow *)window widget:(QWidget *)widget; +- (void)resignDelegateForWindow:(NSWindow *)window; +- (void)becomeDelegateForDrawer:(NSDrawer *)drawer widget:(QWidget *)widget; +- (void)resignDelegateForDrawer:(NSDrawer *)drawer; +- (void)dumpMaximizedStateforWidget:(QWidget*)qwidget window:(NSWindow *)window; +- (void)syncSizeForWidget:(QWidget *)qwidget + toSize:(const QSize &)newSize + fromSize:(const QSize &)oldSize; +- (NSSize)closestAcceptableSizeForWidget:(QWidget *)qwidget + window:(NSWindow *)window withNewSize:(NSSize)proposedSize; +- (QWidget *)qt_qwidgetForWindow:(NSWindow *)window; +- (void)syncContentViewFrame: (NSNotification *)notification; +@end +#endif diff --git a/src/gui/platforms/mac/qcolormap_mac.cpp b/src/gui/platforms/mac/qcolormap_mac.cpp new file mode 100644 index 0000000000..28589f41b8 --- /dev/null +++ b/src/gui/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<QColor> QColormap::colormap() const +{ return QVector<QColor>(); } + +QColormap &QColormap::operator=(const QColormap &colormap) +{ qAtomicAssign(d, colormap.d); return *this; } + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qcursor_mac.mm b/src/gui/platforms/mac/qcursor_mac.mm new file mode 100644 index 0000000000..0afa3ee4f0 --- /dev/null +++ b/src/gui/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 <private/qcursor_p.h> +#include <private/qpixmap_mac_p.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qcursor.h> +#include <qevent.h> +#include <string.h> +#include <unistd.h> +#include <AppKit/NSCursor.h> +#include <qpainter.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qapplication_p.h> + +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<QWidget> 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<NSCursor *>(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<NSCursor *>(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<NSCursor *>(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<QWidget> lastWidgetUnderMouse = 0; +static QPointer<QWidget> 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<NSCursor *>(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<NSCursor *>(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<QRgb *>(bmi.scanLine(row)); + QRgb *bmmData = reinterpret_cast<QRgb *>(bmmi.scanLine(row)); + QRgb *finalData = reinterpret_cast<QRgb *>(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<NSImage*>(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<NSImage *>(qt_mac_create_nsimage(pixmap)); + curs.cp.nscursor = [[NSCursor alloc] initWithImage:nsimage hotSpot: hotSpot]; + [nsimage release]; +} + +void QCursorData::update() +{ + if(!QCursorData::initialized) + QCursorData::initialize(); + if(type != QCursorData::TYPE_None) + return; + + /* Note to self... *** + * mask x data + * 0xFF x 0x00 == fully opaque white + * 0x00 x 0xFF == xor'd black + * 0xFF x 0xFF == fully opaque black + * 0x00 x 0x00 == fully transparent + */ + + if (hx < 0) + hx = 0; + if (hy < 0) + hy = 0; + +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + static const uchar cur_ver_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0, + 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0f, 0xf0, + 0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00 }; + static const uchar mcur_ver_bits[] = { + 0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x0f, 0xe0, 0x1f, 0xf0, 0x3f, 0xf8, + 0x7f, 0xfc, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x7f, 0xfc, 0x3f, 0xf8, + 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80 }; + + static const uchar cur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x18, 0x30, + 0x38, 0x38, 0x7f, 0xfc, 0x7f, 0xfc, 0x38, 0x38, 0x18, 0x30, 0x08, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_hor_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x0c, 0x60, 0x1c, 0x70, 0x3c, 0x78, + 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3c, 0x78, + 0x1c, 0x70, 0x0c, 0x60, 0x04, 0x40, 0x00, 0x00 }; + + static const uchar cur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0x78, + 0x00, 0xf8, 0x01, 0xd8, 0x23, 0x88, 0x37, 0x00, 0x3e, 0x00, 0x3c, 0x00, + 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_fdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, 0xfc, + 0x41, 0xfc, 0x63, 0xfc, 0x77, 0xdc, 0x7f, 0x8c, 0x7f, 0x04, 0x7e, 0x00, + 0x7f, 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x00, 0x00 }; + + static const uchar cur_bdiag_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, 0x00, + 0x37, 0x00, 0x23, 0x88, 0x01, 0xd8, 0x00, 0xf8, 0x00, 0x78, 0x00, 0xf8, + 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + static const uchar mcur_bdiag_bits[] = { + 0x00, 0x00, 0x7f, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7e, 0x00, 0x7f, 0x04, + 0x7f, 0x8c, 0x77, 0xdc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, 0xfc, + 0x03, 0xfc, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x00 }; + + static const unsigned char cur_up_arrow_bits[] = { + 0x00, 0x80, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20, 0x04, 0x10, + 0x04, 0x10, 0x08, 0x08, 0x0f, 0x78, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, + 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0 }; + static const unsigned char mcur_up_arrow_bits[] = { + 0x00, 0x80, 0x01, 0xc0, 0x01, 0xc0, 0x03, 0xe0, 0x03, 0xe0, 0x07, 0xf0, + 0x07, 0xf0, 0x0f, 0xf8, 0x0f, 0xf8, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, + 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0 }; +#endif + const uchar *cursorData = 0; + const uchar *cursorMaskData = 0; +#ifdef QT_MAC_USE_COCOA + switch (cshape) { // map Q cursor to MAC cursor + case Qt::BitmapCursor: { + if (pixmap.isNull()) + initCursorFromBitmap(); + else + initCursorFromPixmap(); + break; } + case Qt::BlankCursor: { + pixmap = QPixmap(16, 16); + pixmap.fill(Qt::transparent); + initCursorFromPixmap(); + break; } + case Qt::ArrowCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor arrowCursor]; + break; } + case Qt::CrossCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor crosshairCursor]; + break; } + case Qt::WaitCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/spincursor.png")); + initCursorFromPixmap(); + break; } + case Qt::IBeamCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor IBeamCursor]; + break; } + case Qt::SizeAllCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/pluscursor.png")); + initCursorFromPixmap(); + break; } + case Qt::WhatsThisCursor: { //for now just use the pointing hand + case Qt::PointingHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor pointingHandCursor]; + break; } + case Qt::BusyCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/waitcursor.png")); + initCursorFromPixmap(); + break; } + case Qt::SplitVCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor resizeUpDownCursor]; + break; } + case Qt::SplitHCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor resizeLeftRightCursor]; + break; } + case Qt::ForbiddenCursor: { + pixmap = QPixmap(QLatin1String(":/trolltech/mac/cursors/images/forbiddencursor.png")); + initCursorFromPixmap(); + break; } + case Qt::OpenHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor openHandCursor]; + break; + case Qt::ClosedHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor closedHandCursor]; + break; + case Qt::DragCopyCursor: + type = QCursorData::TYPE_ThemeCursor; + if ([NSCursor respondsToSelector:@selector(dragCopyCursor)]) + curs.cp.nscursor = [NSCursor performSelector:@selector(dragCopyCursor)]; + break; + case Qt::DragMoveCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.cp.nscursor = [NSCursor arrowCursor]; + break; + case Qt::DragLinkCursor: + type = QCursorData::TYPE_ThemeCursor; + if ([NSCursor respondsToSelector:@selector(dragLinkCursor)]) + curs.cp.nscursor = [NSCursor performSelector:@selector(dragLinkCursor)]; + break; +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + case Qt::SizeVerCursor: + cursorData = cur_ver_bits; + cursorMaskData = mcur_ver_bits; + hx = hy = 8; + break; + case Qt::SizeHorCursor: + cursorData = cur_hor_bits; + cursorMaskData = mcur_hor_bits; + hx = hy = 8; + break; + case Qt::SizeBDiagCursor: + cursorData = cur_fdiag_bits; + cursorMaskData = mcur_fdiag_bits; + hx = hy = 8; + break; + case Qt::SizeFDiagCursor: + cursorData = cur_bdiag_bits; + cursorMaskData = mcur_bdiag_bits; + hx = hy = 8; + break; + case Qt::UpArrowCursor: + cursorData = cur_up_arrow_bits; + cursorMaskData = mcur_up_arrow_bits; + hx = 8; + break; +#endif + default: + qWarning("Qt: QCursor::update: Invalid cursor shape %d", cshape); + return; + } +#else + // Carbon + switch (cshape) { // map Q cursor to MAC cursor + case Qt::BitmapCursor: { + if (pixmap.isNull()) + initCursorFromBitmap(); + else + initCursorFromPixmap(); + break; } + case Qt::BlankCursor: { + pixmap = QPixmap(16, 16); + pixmap.fill(Qt::transparent); + initCursorFromPixmap(); + break; } + case Qt::ArrowCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeArrowCursor; + break; } + case Qt::CrossCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeCrossCursor; + break; } + case Qt::WaitCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeWatchCursor; + break; } + case Qt::IBeamCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeIBeamCursor; + break; } + case Qt::SizeAllCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemePlusCursor; + break; } + case Qt::WhatsThisCursor: { //for now just use the pointing hand + case Qt::PointingHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemePointingHandCursor; + break; } + case Qt::BusyCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeSpinningCursor; + break; } + case Qt::SplitVCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeResizeUpDownCursor; + break; } + case Qt::SplitHCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeResizeLeftRightCursor; + break; } + case Qt::ForbiddenCursor: { + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeNotAllowedCursor; + break; } + case Qt::OpenHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeOpenHandCursor; + break; + case Qt::ClosedHandCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeClosedHandCursor; + break; + case Qt::DragMoveCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeArrowCursor; + break; + case Qt::DragCopyCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeCopyArrowCursor; + break; + case Qt::DragLinkCursor: + type = QCursorData::TYPE_ThemeCursor; + curs.tc.curs = kThemeAliasArrowCursor; + break; +#define QT_USE_APPROXIMATE_CURSORS +#ifdef QT_USE_APPROXIMATE_CURSORS + case Qt::SizeVerCursor: + cursorData = cur_ver_bits; + cursorMaskData = mcur_ver_bits; + hx = hy = 8; + break; + case Qt::SizeHorCursor: + cursorData = cur_hor_bits; + cursorMaskData = mcur_hor_bits; + hx = hy = 8; + break; + case Qt::SizeBDiagCursor: + cursorData = cur_fdiag_bits; + cursorMaskData = mcur_fdiag_bits; + hx = hy = 8; + break; + case Qt::SizeFDiagCursor: + cursorData = cur_bdiag_bits; + cursorMaskData = mcur_bdiag_bits; + hx = hy = 8; + break; + case Qt::UpArrowCursor: + cursorData = cur_up_arrow_bits; + cursorMaskData = mcur_up_arrow_bits; + hx = 8; + break; +#endif + default: + qWarning("Qt: QCursor::update: Invalid cursor shape %d", cshape); + return; + } +#endif + + if (cursorData) { + bm = new QBitmap(QBitmap::fromData(QSize(16, 16), cursorData, + QImage::Format_Mono)); + bmm = new QBitmap(QBitmap::fromData(QSize(16, 16), cursorMaskData, + QImage::Format_Mono)); + initCursorFromBitmap(); + } + +#if 0 + if(type == QCursorData::TYPE_CursPtr && curs.cp.hcurs && curs.cp.my_cursor) { + curs.cp.hcurs->hotSpot.h = hx >= 0 ? hx : 8; + curs.cp.hcurs->hotSpot.v = hy >= 0 ? hy : 8; + } +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qdesktopwidget_mac.mm b/src/gui/platforms/mac/qdesktopwidget_mac.mm new file mode 100644 index 0000000000..0b529c9843 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +#include "qapplication.h" +#include "qdesktopwidget.h" +#include <private/qt_mac_p.h> +#include "qwidget_p.h" +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qdesktopwidget_mac_p.h> + +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; i<screenCount; i++) { + NSRect r = [[displays objectAtIndex:i] frame]; + int flippedY = - r.origin.y + // account for position offset and + primaryRect.size.height - r.size.height; // height difference. + screenRects.append(QRectF(r.origin.x, flippedY, + r.size.width, r.size.height)); + + r = [[displays objectAtIndex:i] visibleFrame]; + flippedY = - r.origin.y + // account for position offset and + primaryRect.size.height - r.size.height; // height difference. + availableRects.append(QRectF(r.origin.x, flippedY, + r.size.width, r.size.height)); + } +} + + + +QDesktopWidget::QDesktopWidget() + : QWidget(0, Qt::Desktop) +{ + setObjectName(QLatin1String("desktop")); + setAttribute(Qt::WA_WState_Visible); +} + +QDesktopWidget::~QDesktopWidget() +{ +} + +bool QDesktopWidget::isVirtualDesktop() const +{ + return true; +} + +int QDesktopWidget::primaryScreen() const +{ + return qdesktopWidgetImplementation()->appScreen; +} + +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<QRectF> oldRects(d->screenRects); + const QVector<QRectF> oldWorks(d->availableRects); + + d->onResize(); + + for (int i = 0; i < qMin(oldScreenCount, d->screenCount); ++i) { + if (oldRects.at(i) != d->screenRects.at(i)) + emit resized(i); + } + for (int i = 0; i < qMin(oldScreenCount, d->screenCount); ++i) { + if (oldWorks.at(i) != d->availableRects.at(i)) + emit workAreaResized(i); + } + + if (oldScreenCount != d->screenCount) + emit screenCountChanged(d->screenCount); +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qdesktopwidget_mac_p.h b/src/gui/platforms/mac/qdesktopwidget_mac_p.h new file mode 100644 index 0000000000..ac638ea7d8 --- /dev/null +++ b/src/gui/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 <qrect.h> + +QT_BEGIN_NAMESPACE + +class QDesktopWidgetImplementation +{ +public: + QDesktopWidgetImplementation(); + ~QDesktopWidgetImplementation(); + static QDesktopWidgetImplementation *instance(); + + int appScreen; + int screenCount; + + QVector<QRectF> availableRects; + QVector<QRectF> screenRects; + + QRect availableRect(int screenIndex) const; + QRect screenRect(int screenIndex) const; + void onResize(); +}; + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qdnd_mac.mm b/src/gui/platforms/mac/qdnd_mac.mm new file mode 100644 index 0000000000..3af2ba007c --- /dev/null +++ b/src/gui/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 <stdlib.h> +#include <string.h> +#ifndef QT_NO_ACCESSIBILITY +# include "qaccessible.h" +#endif + +#include <private/qapplication_p.h> +#include <private/qwidget_p.h> +#include <private/qdnd_p.h> +#include <private/qt_mac_p.h> + +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<CFURLRef> 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<QUrl> urls = o->mimeData()->urls(); + for (int i = 0; i < urls.size(); ++i){ + QUrl fromUrl = urls.at(i); + QString filename = QFileInfo(fromUrl.path()).fileName(); + QUrl toUrl(QCFString::toQString(CFURLGetString(pasteLocation)) + filename); + if (action == Qt::MoveAction){ + if (QFile::rename(fromUrl.path(), toUrl.path())) + atleastOne = true; + } else if (action == Qt::CopyAction){ + if (QFile::copy(fromUrl.path(), toUrl.path())) + atleastOne = true; + } + } + if (atleastOne){ + DisposeDrag(dragRef); + o->setMimeData(0); + o->deleteLater(); + return action; + } + } + + DragActions ret = kDragActionNothing; + GetDragDropAction(dragRef, &ret); + DisposeDrag(dragRef); //cleanup + o->setMimeData(0); + o->deleteLater(); + return qt_mac_dnd_map_mac_default_action(ret); + } + DisposeDrag(dragRef); //cleanup + return Qt::IgnoreAction; +} +#endif + +void QDragManager::updatePixmap() +{ +} + +QCocoaDropData::QCocoaDropData(CFStringRef pasteboard) + : QInternalMimeData() +{ + NSString* pasteboardName = (NSString*)pasteboard; + [pasteboardName retain]; + dropPasteboard = pasteboard; +} + +QCocoaDropData::~QCocoaDropData() +{ + NSString* pasteboardName = (NSString*)dropPasteboard; + [pasteboardName release]; +} + +QStringList QCocoaDropData::formats_sys() const +{ + QStringList formats; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return formats; + } + formats = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).formats(); + return formats; +} + +QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QVariant::Type type) const +{ + QVariant data; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return data; + } + data = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).retrieveData(mimeType, type); + CFRelease(board); + return data; +} + +bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const +{ + bool has = false; + OSPasteboardRef board; + if (PasteboardCreate(dropPasteboard, &board) != noErr) { + qDebug("DnD: Cannot get PasteBoard!"); + return has; + } + has = QMacPasteboard(board, QMacPasteboardMime::MIME_DND).hasFormat(mimeType); + CFRelease(board); + return has; +} + +#endif // QT_NO_DRAGANDDROP +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qeventdispatcher_mac.mm b/src/gui/platforms/mac/qeventdispatcher_mac.mm new file mode 100644 index 0000000000..677a7368b4 --- /dev/null +++ b/src/gui/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 <private/qcocoaapplication_mac_p.h> +#include "private/qt_cocoa_helpers_mac_p.h" + +#ifndef QT_NO_THREAD +# include "qmutex.h" +#endif + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + Externals + *****************************************************************************/ +extern void qt_event_request_timer(MacTimerInfo *); //qapplication_mac.cpp +extern MacTimerInfo *qt_event_get_timer(EventRef); //qapplication_mac.cpp +extern void qt_event_request_select(QEventDispatcherMac *); //qapplication_mac.cpp +extern void qt_event_request_updates(); //qapplication_mac.cpp +extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp +extern bool qt_is_gui_used; //qapplication.cpp +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); // qapplication.cpp +extern bool qt_mac_is_macsheet(const QWidget *); //qwidget_mac.cpp + +static inline CFRunLoopRef mainRunLoop() +{ +#ifndef QT_MAC_USE_COCOA + return reinterpret_cast<CFRunLoopRef>(const_cast<void *>(GetCFRunLoopFromEventLoop(GetMainEventLoop()))); +#else + return CFRunLoopGetMain(); +#endif +} + +/***************************************************************************** + Timers stuff + *****************************************************************************/ + +/* timer call back */ +void QEventDispatcherMacPrivate::activateTimer(CFRunLoopTimerRef, void *info) +{ + int timerID = +#ifdef Q_OS_MAC64 + qint64(info); +#else + int(info); +#endif + + MacTimerInfo *tmr; + tmr = macTimerHash.value(timerID); + if (tmr == 0 || tmr->pending == true) + return; // Can't send another timer event if it's pending. + + + if (blockSendPostedEvents) { + QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id)); + } else { + tmr->pending = true; + QTimerEvent e(tmr->id); + qt_sendSpontaneousEvent(tmr->obj, &e); + // Get the value again in case the timer gets unregistered during the sendEvent. + tmr = macTimerHash.value(timerID); + if (tmr != 0) + tmr->pending = false; + } + +} + +void QEventDispatcherMac::registerTimer(int timerId, int interval, QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (timerId < 1 || interval < 0 || !obj) { + qWarning("QEventDispatcherMac::registerTimer: invalid arguments"); + return; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::startTimer: timers cannot be started from another thread"); + return; + } +#endif + + MacTimerInfo *t = new MacTimerInfo(); + t->id = timerId; + t->interval = interval; + t->obj = obj; + t->runLoopTimer = 0; + t->pending = false; + + CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent(); + CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001); + fireDate += cfinterval; + QEventDispatcherMacPrivate::macTimerHash.insert(timerId, t); + CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 }; + t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0, + QEventDispatcherMacPrivate::activateTimer, &info); + if (t->runLoopTimer == 0) { + qFatal("QEventDispatcherMac::registerTimer: Cannot create timer"); + } + CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes); +} + +bool QEventDispatcherMac::unregisterTimer(int identifier) +{ +#ifndef QT_NO_DEBUG + if (identifier < 1) { + qWarning("QEventDispatcherMac::unregisterTimer: invalid argument"); + return false; + } else if (thread() != QThread::currentThread()) { + qWarning("QObject::killTimer: timers cannot be stopped from another thread"); + return false; + } +#endif + if (identifier <= 0) + return false; // not init'd or invalid timer + + MacTimerInfo *timerInfo = QEventDispatcherMacPrivate::macTimerHash.take(identifier); + if (timerInfo == 0) + return false; + + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(identifier); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + + return true; +} + +bool QEventDispatcherMac::unregisterTimers(QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (!obj) { + qWarning("QEventDispatcherMac::unregisterTimers: invalid argument"); + return false; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::killTimers: timers cannot be stopped from another thread"); + return false; + } +#endif + + MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.end()) { + MacTimerInfo *timerInfo = it.value(); + if (timerInfo->obj != obj) { + ++it; + } else { + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + it = QEventDispatcherMacPrivate::macTimerHash.erase(it); + } + } + return true; +} + +QList<QEventDispatcherMac::TimerInfo> +QEventDispatcherMac::registeredTimers(QObject *object) const +{ + if (!object) { + qWarning("QEventDispatcherMac:registeredTimers: invalid argument"); + return QList<TimerInfo>(); + } + + QList<TimerInfo> list; + + MacTimerHash::const_iterator it = QEventDispatcherMacPrivate::macTimerHash.constBegin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.constEnd()) { + MacTimerInfo *t = it.value(); + if (t->obj == object) + list << TimerInfo(t->id, t->interval); + ++it; + } + return list; +} + +/************************************************************************** + Socket Notifiers + *************************************************************************/ +void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef, + const void *, void *info) { + QEventDispatcherMacPrivate *const eventDispatcher + = static_cast<QEventDispatcherMacPrivate *>(info); + int nativeSocket = CFSocketGetNative(s); + MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket); + QEvent notifierEvent(QEvent::SockAct); + + // There is a race condition that happen where we disable the notifier and + // the kernel still has a notification to pass on. We then get this + // notification after we've successfully disabled the CFSocket, but our Qt + // notifier is now gone. The upshot is we have to check the notifier + // everytime. + if (callbackType == kCFSocketReadCallBack) { + if (socketInfo->readNotifier) + QApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent); + } else if (callbackType == kCFSocketWriteCallBack) { + if (socketInfo->writeNotifier) + QApplication::sendEvent(socketInfo->writeNotifier, ¬ifierEvent); + } +} + +/* + Adds a loop source for the given socket to the current run loop. +*/ +CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket) +{ + CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); + if (!loopSource) + return 0; + + CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes); + return loopSource; +} + +/* + Removes the loop source for the given socket from the current run loop. +*/ +void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop) +{ + Q_ASSERT(runloop); + CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes); + CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); + CFRunLoopSourceInvalidate(runloop); +} + +/* + Register a QSocketNotifier with the mac event system by creating a CFSocket with + with a read/write callback. + + Qt has separate socket notifiers for reading and writing, but on the mac there is + a limitation of one CFSocket object for each native socket. +*/ +void QEventDispatcherMac::registerSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() + || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); + return; + } +#endif + + Q_D(QEventDispatcherMac); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + + // Check if we have a CFSocket for the native socket, create one if not. + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + socketInfo = new MacSocketInfo(); + + // Create CFSocket, specify that we want both read and write callbacks (the callbacks + // are enabled/disabled later on). + const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack; + CFSocketContext context = {0, d, 0, 0, 0}; + socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context); + if (CFSocketIsValid(socketInfo->socket) == false) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket"); + return; + } + + CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket); + flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write + flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation + CFSocketSetSocketFlags(socketInfo->socket, flags); + + // Add CFSocket to runloop. + if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop"); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + return; + } + + // Disable both callback types by default. This must be done after + // we add the CFSocket to the runloop, or else these calls will have + // no effect. + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + + d->macSockets.insert(nativeSocket, socketInfo); + } + + // Increment read/write counters and select enable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(socketInfo->readNotifier == 0); + socketInfo->readNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(socketInfo->writeNotifier == 0); + socketInfo->writeNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } +} + +/* + Unregister QSocketNotifer. The CFSocket correspoding to this notifier is + removed from the runloop of this is the last notifier that users + that CFSocket. +*/ +void QEventDispatcherMac::unregisterSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); + return; + } +#endif + + Q_D(QEventDispatcherMac); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier"); + return; + } + + // Decrement read/write counters and disable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(notifier == socketInfo->readNotifier); + socketInfo->readNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(notifier == socketInfo->writeNotifier); + socketInfo->writeNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } + + // Remove CFSocket from runloop if this was the last QSocketNotifier. + if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) { + if (CFSocketIsValid(socketInfo->socket)) + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + delete socketInfo; + d->macSockets.remove(nativeSocket); + } +} + +bool QEventDispatcherMac::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); + return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); +} + + +static bool qt_mac_send_event(QEventLoop::ProcessEventsFlags, OSEventRef event, OSWindowRef pt) +{ +#ifndef QT_MAC_USE_COCOA + if(pt && SendEventToWindow(event, pt) != eventNotHandledErr) + return true; + return !SendEventToEventTarget(event, GetEventDispatcherTarget()); +#else // QT_MAC_USE_COCOA + if (pt) + [pt sendEvent:event]; + else + [NSApp sendEvent:event]; + return true; +#endif +} + +#ifdef QT_MAC_USE_COCOA +static bool IsMouseOrKeyEvent( NSEvent* event ) +{ + bool result = false; + + switch( [event type] ) + { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: // ?? + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSMouseEntered: + case NSMouseExited: + case NSKeyDown: + case NSKeyUp: + case NSFlagsChanged: // key modifiers changed? + case NSCursorUpdate: // ?? + case NSScrollWheel: + case NSTabletPoint: + case NSTabletProximity: + case NSOtherMouseDown: + case NSOtherMouseUp: + case NSOtherMouseDragged: +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + case NSEventTypeGesture: // touch events + case NSEventTypeMagnify: + case NSEventTypeSwipe: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: +#endif +#endif // QT_NO_GESTURES + result = true; + break; + + default: + break; + } + return result; +} +#endif + +static inline void qt_mac_waitForMoreEvents() +{ +#ifndef QT_MAC_USE_COCOA + while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true) == kCFRunLoopRunTimedOut) ; +#else + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +#endif +} + +#ifdef QT_MAC_USE_COCOA +static inline void qt_mac_waitForMoreModalSessionEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSModalPanelRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} +#endif + +bool QEventDispatcherMac::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QEventDispatcherMac); + d->interrupt = false; + +#ifdef QT_MAC_USE_COCOA + bool interruptLater = false; + QtMacInterruptDispatcherHelp::cancelInterruptLater(); +#endif + + // In case we end up recursing while we now process events, make sure + // that we send remaining posted Qt events before this call returns: + wakeUp(); + emit awake(); + + bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents; + bool retVal = false; + forever { + if (d->interrupt) + break; + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSEvent* event = 0; + + // First, send all previously excluded input events, if any: + if (!excludeUserEvents) { + while (!d->queuedUserInputEvents.isEmpty()) { + event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); + if (!filterEvent(event)) { + qt_mac_send_event(flags, event, 0); + retVal = true; + } + [event release]; + } + } + + // If Qt is used as a plugin, or as an extension in a native cocoa + // application, we should not run or stop NSApplication; This will be + // done from the application itself. And if processEvents is called + // manually (rather than from a QEventLoop), we cannot enter a tight + // loop and block this call, but instead we need to return after one flush. + // Finally, if we are to exclude user input events, we cannot call [NSApp run] + // as we then loose control over which events gets dispatched: + const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning]; + const bool canExec_Qt = !excludeUserEvents && + (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ; + + if (canExec_Qt && canExec_3rdParty) { + // We can use exec-mode, meaning that we can stay in a tight loop until + // interrupted. This is mostly an optimization, but it allow us to use + // [NSApp run], which is the normal code path for cocoa applications. + if (NSModalSession session = d->currentModalSession()) { + QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); + while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + qt_mac_waitForMoreModalSessionEvents(); + + if (!d->interrupt && session == d->currentModalSessionCached) { + // Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + } else { + d->nsAppRunCalledByQt = true; + QBoolBlocker execGuard(d->currentExecIsNSAppRun, true); + [NSApp run]; + } + retVal = true; + } else { + // We cannot block the thread (and run in a tight loop). + // Instead we will process all current pending events and return. + d->ensureNSAppInitialized(); + if (NSModalSession session = d->currentModalSession()) { + // INVARIANT: a modal window is executing. + if (!excludeUserEvents) { + // Since we can dispatch all kinds of events, we choose + // to use cocoa's native way of running modal sessions: + if (flags & QEventLoop::WaitForMoreEvents) + qt_mac_waitForMoreModalSessionEvents(); + NSInteger status = [NSApp runModalSession:session]; + if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) { + // INVARIANT: Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + retVal = true; + } else do { + // Dispatch all non-user events (but que non-user events up for later). In + // this case, we need more control over which events gets dispatched, and + // cannot use [NSApp runModalSession:session]: + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSModalPanelRunLoopMode + dequeue: YES]; + + if (event) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + if (!filterEvent(event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + } + } while (!d->interrupt && event != nil); + } else do { + // INVARIANT: No modal window is executing. + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + if (event) { + if (flags & QEventLoop::ExcludeUserInputEvents) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + } + if (!filterEvent(event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + } + } while (!d->interrupt && event != nil); + + // Be sure to flush the Qt posted events when not using exec mode + // (exec mode will always do this call from the event loop source): + if (!d->interrupt) + QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + + // Since the window that holds modality might have changed while processing + // events, we we need to interrupt when we return back the previous process + // event recursion to ensure that we spin the correct modal session. + // We do the interruptLater at the end of the function to ensure that we don't + // disturb the 'wait for more events' below (as deleteLater will post an event): + interruptLater = true; + } +#else + do { + EventRef event; + if (!(flags & QEventLoop::ExcludeUserInputEvents) + && !d->queuedUserInputEvents.isEmpty()) { + // process a pending user input event + event = static_cast<EventRef>(d->queuedUserInputEvents.takeFirst()); + } else { + OSStatus err = ReceiveNextEvent(0,0, kEventDurationNoWait, true, &event); + if(err != noErr) + continue; + // else + if (flags & QEventLoop::ExcludeUserInputEvents) { + UInt32 ekind = GetEventKind(event), + eclass = GetEventClass(event); + switch(eclass) { + case kEventClassQt: + if(ekind != kEventQtRequestContext) + break; + // fall through + case kEventClassMouse: + case kEventClassKeyboard: + d->queuedUserInputEvents.append(event); + continue; + } + } + } + + if (!filterEvent(&event) && qt_mac_send_event(flags, event, 0)) + retVal = true; + ReleaseEvent(event); + } while(!d->interrupt && GetNumEventsInQueue(GetMainEventQueue()) > 0); + +#endif + + bool canWait = (d->threadData->canWait + && !retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + // INVARIANT: We haven't processed any events yet. And we're told + // to stay inside this function until at least one event is processed. + qt_mac_waitForMoreEvents(); + flags &= ~QEventLoop::WaitForMoreEvents; + } else { + // Done with event processing for now. + // Leave the function: + break; + } + } + + // If we're interrupted, we need to interrupt the _current_ + // recursion as well to check if it is still supposed to be + // executing. This way we wind down the stack until we land + // on a recursion that again calls processEvents (typically + // from QEventLoop), and set interrupt to false: + if (d->interrupt) + interrupt(); + +#ifdef QT_MAC_USE_COCOA + if (interruptLater) + QtMacInterruptDispatcherHelp::interruptLater(); +#endif + + return retVal; +} + +void QEventDispatcherMac::wakeUp() +{ + Q_D(QEventDispatcherMac); + d->serialNumber.ref(); + CFRunLoopSourceSignal(d->postedEventsSource); + CFRunLoopWakeUp(mainRunLoop()); +} + +void QEventDispatcherMac::flush() +{ + if(qApp) { + QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); i++) { + QWidget *tlw = tlws.at(i); + if(tlw->isVisible()) + macWindowFlush(qt_mac_window_for(tlw)); + } + } +} + +/***************************************************************************** + QEventDispatcherMac Implementation + *****************************************************************************/ +MacTimerHash QEventDispatcherMacPrivate::macTimerHash; +bool QEventDispatcherMacPrivate::blockSendPostedEvents = false; +bool QEventDispatcherMacPrivate::interrupt = false; + +#ifdef QT_MAC_USE_COCOA +QStack<QCocoaModalSessionInfo> QEventDispatcherMacPrivate::cocoaModalSessionStack; +bool QEventDispatcherMacPrivate::currentExecIsNSAppRun = false; +bool QEventDispatcherMacPrivate::nsAppRunCalledByQt = false; +bool QEventDispatcherMacPrivate::cleanupModalSessionsNeeded = false; +NSModalSession QEventDispatcherMacPrivate::currentModalSessionCached = 0; + +void QEventDispatcherMacPrivate::ensureNSAppInitialized() +{ + // Some elements in Cocoa require NSApplication to be running before + // they get fully initialized, in particular the menu bar. This + // function is intended for cases where a dialog is told to execute before + // QApplication::exec is called, or the application spins the events loop + // manually rather than calling QApplication:exec. + // The function makes sure that NSApplication starts running, but stops + // it again as soon as the send posted events callback is called. That way + // we let Cocoa finish the initialization it seems to need. We'll only + // apply this trick at most once for any application, and we avoid doing it + // for the common case where main just starts QApplication::exec. + if (nsAppRunCalledByQt || [NSApp isRunning]) + return; + nsAppRunCalledByQt = true; + QBoolBlocker block1(interrupt, true); + QBoolBlocker block2(currentExecIsNSAppRun, true); + [NSApp run]; +} + +void QEventDispatcherMacPrivate::temporarilyStopAllModalSessions() +{ + // Flush, and Stop, all created modal session, and as + // such, make them pending again. The next call to + // currentModalSession will recreate them again. The + // reason to stop all session like this is that otherwise + // a call [NSApp stop] would not stop NSApp, but rather + // the current modal session. So if we need to stop NSApp + // we need to stop all the modal session first. To avoid changing + // the stacking order of the windows while doing so, we put + // up a block that is used in QCocoaWindow and QCocoaPanel: + int stackSize = cocoaModalSessionStack.size(); + for (int i=0; i<stackSize; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.session) { + [NSApp endModalSession:info.session]; + info.session = 0; + } + } + currentModalSessionCached = 0; +} + +NSModalSession QEventDispatcherMacPrivate::currentModalSession() +{ + // If we have one or more modal windows, this function will create + // a session for each of those, and return the one for the top. + if (currentModalSessionCached) + return currentModalSessionCached; + + if (cocoaModalSessionStack.isEmpty()) + return 0; + + int sessionCount = cocoaModalSessionStack.size(); + for (int i=0; i<sessionCount; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (!info.widget) + continue; + if (info.widget->testAttribute(Qt::WA_DontShowOnScreen)) + continue; + if (!info.session) { + QMacCocoaAutoReleasePool pool; + NSWindow *window = qt_mac_window_for(info.widget); + if (!window) + continue; + + ensureNSAppInitialized(); + QBoolBlocker block1(blockSendPostedEvents, true); + info.nswindow = window; + [(NSWindow*) info.nswindow retain]; + int levelBeforeEnterModal = [window level]; + info.session = [NSApp beginModalSessionForWindow:window]; + // Make sure we don't stack the window lower that it was before + // entering modal, in case it e.g. had the stays-on-top flag set: + if (levelBeforeEnterModal > [window level]) + [window setLevel:levelBeforeEnterModal]; + } + currentModalSessionCached = info.session; + cleanupModalSessionsNeeded = false; + } + return currentModalSessionCached; +} + +static void setChildrenWorksWhenModal(QWidget *widget, bool worksWhenModal) +{ + // For NSPanels (but not NSWindows, sadly), we can set the flag + // worksWhenModal, so that they are active even when they are not modal. + QList<QDialog *> dialogs = widget->findChildren<QDialog *>(); + for (int i=0; i<dialogs.size(); ++i){ + NSWindow *window = qt_mac_window_for(dialogs[i]); + if (window && [window isKindOfClass:[NSPanel class]]) { + [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal]; + if (worksWhenModal && [window isVisible]){ + [window orderFront:window]; + } + } + } +} + +void QEventDispatcherMacPrivate::updateChildrenWorksWhenModal() +{ + // Make the dialog children of the widget + // active. And make the dialog children of + // the previous modal dialog unactive again: + QMacCocoaAutoReleasePool pool; + int size = cocoaModalSessionStack.size(); + if (size > 0){ + if (QWidget *prevModal = cocoaModalSessionStack[size-1].widget) + setChildrenWorksWhenModal(prevModal, true); + if (size > 1){ + if (QWidget *prevModal = cocoaModalSessionStack[size-2].widget) + setChildrenWorksWhenModal(prevModal, false); + } + } +} + +void QEventDispatcherMacPrivate::cleanupModalSessions() +{ + // Go through the list of modal sessions, and end those + // that no longer has a widget assosiated; no widget means + // the the session has logically ended. The reason we wait like + // this to actually end the sessions for real (rather than at the + // point they were marked as stopped), is that ending a session + // when no other session runs below it on the stack will make cocoa + // drop some events on the floor. + QMacCocoaAutoReleasePool pool; + int stackSize = cocoaModalSessionStack.size(); + + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.widget) { + // This session has a widget, and is therefore not marked + // as stopped. So just make it current. There might still be other + // stopped sessions on the stack, but those will be stopped on + // a later "cleanup" call. + currentModalSessionCached = info.session; + break; + } + cocoaModalSessionStack.remove(i); + currentModalSessionCached = 0; + if (info.session) { + [NSApp endModalSession:info.session]; + [(NSWindow *)info.nswindow release]; + } + } + + updateChildrenWorksWhenModal(); + cleanupModalSessionsNeeded = false; +} + +void QEventDispatcherMacPrivate::beginModalSession(QWidget *widget) +{ + // Add a new, empty (null), NSModalSession to the stack. + // It will become active the next time QEventDispatcher::processEvents is called. + // A QCocoaModalSessionInfo is considered pending to become active if the widget pointer + // is non-zero, and the session pointer is zero (it will become active upon a call to + // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if + // the widget pointer is zero, and the session pointer is non-zero (it will be fully + // stopped in cleanupModalSessions()). + QCocoaModalSessionInfo info = {widget, 0, 0}; + cocoaModalSessionStack.push(info); + updateChildrenWorksWhenModal(); + currentModalSessionCached = 0; +} + +void QEventDispatcherMacPrivate::endModalSession(QWidget *widget) +{ + // Mark all sessions attached to widget as pending to be stopped. We do this + // by setting the widget pointer to zero, but leave the session pointer. + // We don't tell cocoa to stop any sessions just yet, because cocoa only understands + // when we stop the _current_ modal session (which is the session on top of + // the stack, and might not belong to 'widget'). + int stackSize = cocoaModalSessionStack.size(); + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.widget == widget) { + info.widget = 0; + if (i == stackSize-1) { + // The top sessions ended. Interrupt the event dispatcher + // to start spinning the correct session immidiatly: + currentModalSessionCached = 0; + cleanupModalSessionsNeeded = true; + QEventDispatcherMac::instance()->interrupt(); + } + } + } +} + +#endif + +QEventDispatcherMacPrivate::QEventDispatcherMacPrivate() +{ +} + +QEventDispatcherMac::QEventDispatcherMac(QObject *parent) + : QAbstractEventDispatcher(*new QEventDispatcherMacPrivate, parent) +{ + Q_D(QEventDispatcherMac); + CFRunLoopSourceContext context; + bzero(&context, sizeof(CFRunLoopSourceContext)); + context.info = d; + context.equal = QEventDispatcherMacPrivate::postedEventSourceEqualCallback; + context.perform = QEventDispatcherMacPrivate::postedEventsSourcePerformCallback; + d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context); + Q_ASSERT(d->postedEventsSource); + CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + + CFRunLoopObserverContext observerContext; + bzero(&observerContext, sizeof(CFRunLoopObserverContext)); + observerContext.info = this; + d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, + true, 0, + QEventDispatcherMacPrivate::waitingObserverCallback, + &observerContext); + CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes); + + /* The first cycle in the loop adds the source and the events of the source + are not processed. + We use an observer to process the posted events for the first + execution of the loop. */ + CFRunLoopObserverContext firstTimeObserverContext; + bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext)); + firstTimeObserverContext.info = d; + d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopEntry, + /* repeats = */ false, + 0, + QEventDispatcherMacPrivate::firstLoopEntry, + &firstTimeObserverContext); + CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes); +} + +void QEventDispatcherMacPrivate::waitingObserverCallback(CFRunLoopObserverRef, + CFRunLoopActivity activity, void *info) +{ + if (activity == kCFRunLoopBeforeWaiting) + emit static_cast<QEventDispatcherMac*>(info)->aboutToBlock(); + else + emit static_cast<QEventDispatcherMac*>(info)->awake(); +} + +Boolean QEventDispatcherMacPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2) +{ + return info1 == info2; +} + +inline static void processPostedEvents(QEventDispatcherMacPrivate *const d, const bool blockSendPostedEvents) +{ + if (blockSendPostedEvents) { + // We're told to not send posted events (because the event dispatcher + // is currently working on setting up the correct session to run). But + // we still need to make sure that we don't fall asleep until pending events + // are sendt, so we just signal this need, and return: + CFRunLoopSourceSignal(d->postedEventsSource); + return; + } + +#ifdef QT_MAC_USE_COCOA + if (d->cleanupModalSessionsNeeded) + d->cleanupModalSessions(); +#endif + + if (d->interrupt) { +#ifdef QT_MAC_USE_COCOA + if (d->currentExecIsNSAppRun) { + // The event dispatcher has been interrupted. But since + // [NSApplication run] is running the event loop, we + // delayed stopping it until now (to let cocoa process + // pending cocoa events first). + if (d->currentModalSessionCached) + d->temporarilyStopAllModalSessions(); + [NSApp stop:NSApp]; + d->cancelWaitForMoreEvents(); + } +#endif + return; + } + + if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) { + d->lastSerial = d->serialNumber; + QApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + } +} + +void QEventDispatcherMacPrivate::firstLoopEntry(CFRunLoopObserverRef ref, + CFRunLoopActivity activity, + void *info) +{ + Q_UNUSED(ref); + Q_UNUSED(activity); +#ifdef QT_MAC_USE_COCOA + QApplicationPrivate::qt_initAfterNSAppStarted(); +#endif + processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); +} + +void QEventDispatcherMacPrivate::postedEventsSourcePerformCallback(void *info) +{ + processPostedEvents(static_cast<QEventDispatcherMacPrivate *>(info), blockSendPostedEvents); +} + +#ifdef QT_MAC_USE_COCOA +void QEventDispatcherMacPrivate::cancelWaitForMoreEvents() +{ + // In case the event dispatcher is waiting for more + // events somewhere, we post a dummy event to wake it up: + QMacCocoaAutoReleasePool pool; + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint + modifierFlags:0 timestamp:0. windowNumber:0 context:0 + subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; +} +#endif + +void QEventDispatcherMac::interrupt() +{ + Q_D(QEventDispatcherMac); + d->interrupt = true; + wakeUp(); + +#ifndef QT_MAC_USE_COCOA + CFRunLoopStop(mainRunLoop()); +#else + // We do nothing more here than setting d->interrupt = true, and + // poke the event loop if it is sleeping. Actually stopping + // NSApp, or the current modal session, is done inside the send + // posted events callback. We do this to ensure that all current pending + // cocoa events gets delivered before we stop. Otherwise, if we now stop + // the last event loop recursion, cocoa will just drop pending posted + // events on the floor before we get a chance to reestablish a new session. + d->cancelWaitForMoreEvents(); +#endif +} + +QEventDispatcherMac::~QEventDispatcherMac() +{ + Q_D(QEventDispatcherMac); + //timer cleanup + MacTimerHash::iterator it = QEventDispatcherMacPrivate::macTimerHash.begin(); + while (it != QEventDispatcherMacPrivate::macTimerHash.end()) { + MacTimerInfo *t = it.value(); + if (t->runLoopTimer) { + CFRunLoopTimerInvalidate(t->runLoopTimer); + CFRelease(t->runLoopTimer); + } + delete t; + ++it; + } + QEventDispatcherMacPrivate::macTimerHash.clear(); + + // Remove CFSockets from the runloop. + for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) { + MacSocketInfo *socketInfo = (*it); + if (CFSocketIsValid(socketInfo->socket)) { + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + } + } + CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + CFRelease(d->postedEventsSource); + + CFRunLoopObserverInvalidate(d->waitingObserver); + CFRelease(d->waitingObserver); + + CFRunLoopObserverInvalidate(d->firstTimeObserver); + CFRelease(d->firstTimeObserver); +} + +#ifdef QT_MAC_USE_COCOA + +QtMacInterruptDispatcherHelp* QtMacInterruptDispatcherHelp::instance = 0; + +QtMacInterruptDispatcherHelp::QtMacInterruptDispatcherHelp() : cancelled(false) +{ + // The whole point of this class is that we enable a way to interrupt + // the event dispatcher when returning back to a lower recursion level + // than where interruptLater was called. This is needed to detect if + // [NSApp run] should still be running at the recursion level it is at. + // Since the interrupt is canceled if processEvents is called before + // this object gets deleted, we also avoid interrupting unnecessary. + deleteLater(); +} + +QtMacInterruptDispatcherHelp::~QtMacInterruptDispatcherHelp() +{ + if (cancelled) + return; + instance = 0; + QEventDispatcherMac::instance()->interrupt(); +} + +void QtMacInterruptDispatcherHelp::cancelInterruptLater() +{ + if (!instance) + return; + instance->cancelled = true; + delete instance; + instance = 0; +} + +void QtMacInterruptDispatcherHelp::interruptLater() +{ + cancelInterruptLater(); + instance = new QtMacInterruptDispatcherHelp; +} + +#endif + +QT_END_NAMESPACE + diff --git a/src/gui/platforms/mac/qeventdispatcher_mac_p.h b/src/gui/platforms/mac/qeventdispatcher_mac_p.h new file mode 100644 index 0000000000..12fcafbb01 --- /dev/null +++ b/src/gui/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 <QtGui/qwindowdefs.h> +#include <QtCore/qhash.h> +#include <QtCore/qstack.h> +#include "private/qabstracteventdispatcher_p.h" +#include "private/qt_mac_p.h" + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA +typedef struct _NSModalSession *NSModalSession; +typedef struct _QCocoaModalSessionInfo { + QPointer<QWidget> widget; + NSModalSession session; + void *nswindow; +} QCocoaModalSessionInfo; +#endif + +class QEventDispatcherMacPrivate; + +class QEventDispatcherMac : public QAbstractEventDispatcher +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QEventDispatcherMac) + +public: + explicit QEventDispatcherMac(QObject *parent = 0); + ~QEventDispatcherMac(); + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void registerSocketNotifier(QSocketNotifier *notifier); + void unregisterSocketNotifier(QSocketNotifier *notifier); + + void registerTimer(int timerId, int interval, QObject *object); + bool unregisterTimer(int timerId); + bool unregisterTimers(QObject *object); + QList<TimerInfo> registeredTimers(QObject *object) const; + + void wakeUp(); + void flush(); + void interrupt(); + +private: + friend void qt_mac_select_timer_callbk(__EventLoopTimer*, void*); + friend class QApplicationPrivate; +}; + +struct MacTimerInfo { + int id; + int interval; + QObject *obj; + bool pending; + CFRunLoopTimerRef runLoopTimer; + bool operator==(const MacTimerInfo &other) + { + return (id == other.id); + } +}; +typedef QHash<int, MacTimerInfo *> MacTimerHash; + +struct MacSocketInfo { + MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {} + CFSocketRef socket; + CFRunLoopSourceRef runloop; + QObject *readNotifier; + QObject *writeNotifier; +}; +typedef QHash<int, MacSocketInfo *> MacSocketHash; + +class QEventDispatcherMacPrivate : public QAbstractEventDispatcherPrivate +{ + Q_DECLARE_PUBLIC(QEventDispatcherMac) + +public: + QEventDispatcherMacPrivate(); + + static MacTimerHash macTimerHash; + // Set 'blockSendPostedEvents' to true if you _really_ need + // to make sure that qt events are not posted while calling + // low-level cocoa functions (like beginModalForWindow). And + // use a QBoolBlocker to be safe: + static bool blockSendPostedEvents; +#ifdef QT_MAC_USE_COCOA + // The following variables help organizing modal sessions: + static QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; + static bool currentExecIsNSAppRun; + static bool nsAppRunCalledByQt; + static bool cleanupModalSessionsNeeded; + static NSModalSession currentModalSessionCached; + static NSModalSession currentModalSession(); + static void updateChildrenWorksWhenModal(); + static void temporarilyStopAllModalSessions(); + static void beginModalSession(QWidget *widget); + static void endModalSession(QWidget *widget); + static void cancelWaitForMoreEvents(); + static void cleanupModalSessions(); + static void ensureNSAppInitialized(); +#endif + + MacSocketHash macSockets; + QList<void *> queuedUserInputEvents; // List of EventRef in Carbon, and NSEvent * in Cocoa + CFRunLoopSourceRef postedEventsSource; + CFRunLoopObserverRef waitingObserver; + CFRunLoopObserverRef firstTimeObserver; + QAtomicInt serialNumber; + int lastSerial; + static bool interrupt; +private: + static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2); + static void postedEventsSourcePerformCallback(void *info); + static void activateTimer(CFRunLoopTimerRef, void *info); + static void waitingObserverCallback(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); + static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); +}; + +#ifdef QT_MAC_USE_COCOA +class QtMacInterruptDispatcherHelp : public QObject +{ + static QtMacInterruptDispatcherHelp *instance; + bool cancelled; + + QtMacInterruptDispatcherHelp(); + ~QtMacInterruptDispatcherHelp(); + + public: + static void interruptLater(); + static void cancelInterruptLater(); +}; +#endif + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_MAC_P_H diff --git a/src/gui/platforms/mac/qfont_mac.cpp b/src/gui/platforms/mac/qfont_mac.cpp new file mode 100644 index 0000000000..daf68c03ea --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include <private/qtextengine_p.h> +#include <private/qunicodetables_p.h> +#include <qapplication.h> +#include "qfontdatabase.h" +#include <qpainter.h> +#include "qtextengine_p.h" +#include <stdlib.h> + +QT_BEGIN_NAMESPACE + +extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + +int qt_mac_pixelsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pixelSize == -1) + ret = def.pointSize * dpi / qt_mac_defaultDpi_x(); + else + ret = def.pixelSize; + return qRound(ret); +} +int qt_mac_pointsize(const QFontDef &def, int dpi) +{ + float ret; + if(def.pointSize < 0) + ret = def.pixelSize * qt_mac_defaultDpi_x() / float(dpi); + else + ret = def.pointSize; + return qRound(ret); +} + +QString QFont::rawName() const +{ + return family(); +} + +void QFont::setRawName(const QString &name) +{ + setFamily(name); +} + +void QFont::cleanup() +{ + QFontCache::cleanup(); +} + +/*! + Returns an ATSUFontID +*/ +quint32 QFont::macFontID() const // ### need 64-bit version +{ +#ifdef QT_MAC_USE_COCOA + return 0; +#elif 1 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Multi) + return static_cast<QFontEngineMacMulti*>(fe)->macFontID(); +#else + Str255 name; + if(FMGetFontFamilyName((FMFontFamily)((UInt32)handle()), name) == noErr) { + short fnum; + GetFNum(name, &fnum); + return fnum; + } +#endif + return 0; +} + +// Returns an ATSUFonFamilyRef +Qt::HANDLE QFont::handle() const +{ +#if 0 + QFontEngine *fe = d->engineForScript(QUnicodeTables::Common); + if (fe && fe->type() == QFontEngine::Mac) + return (Qt::HANDLE)static_cast<QFontEngineMacMulti*>(fe)->fontFamilyRef(); +#endif + return 0; +} + +void QFont::initialize() +{ } + +QString QFont::defaultFamily() const +{ + switch(d->request.styleHint) { + case QFont::Times: + return QString::fromLatin1("Times New Roman"); + case QFont::Courier: + return QString::fromLatin1("Courier New"); + case QFont::Monospace: + return QString::fromLatin1("Courier"); + case QFont::Decorative: + return QString::fromLatin1("Bookman Old Style"); + case QFont::Cursive: + return QString::fromLatin1("Apple Chancery"); + case QFont::Fantasy: + return QString::fromLatin1("Papyrus"); + case QFont::Helvetica: + case QFont::System: + default: + return QString::fromLatin1("Helvetica"); + } +} + +QString QFont::lastResortFamily() const +{ + return QString::fromLatin1("Helvetica"); +} + +QString QFont::lastResortFont() const +{ + return QString::fromLatin1("Geneva"); +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qfontdatabase_mac.cpp b/src/gui/platforms/mac/qfontdatabase_mac.cpp new file mode 100644 index 0000000000..5ba236b5f7 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include "qfontengine_p.h" +#include <qfile.h> +#include <qabstractfileengine.h> +#include <stdlib.h> +#include <qendian.h> +#include <private/qfontengine_coretext_p.h> +#include <private/qfontengine_mac_p.h> + +QT_BEGIN_NAMESPACE + +int qt_mac_pixelsize(const QFontDef &def, int dpi); //qfont_mac.cpp +int qt_mac_pointsize(const QFontDef &def, int dpi); //qfont_mac.cpp + +#ifndef QT_MAC_USE_COCOA +static void initWritingSystems(QtFontFamily *family, ATSFontRef atsFont) +{ + ByteCount length = 0; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, 0, 0, &length) != noErr) + return; + QVarLengthArray<uchar> os2Table(length); + if (length < 86 + || ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 0, length, os2Table.data(), &length) != noErr) + return; + + // See also qfontdatabase_win.cpp, offsets taken from OS/2 table in the TrueType spec + quint32 unicodeRange[4] = { + qFromBigEndian<quint32>(os2Table.data() + 42), + qFromBigEndian<quint32>(os2Table.data() + 46), + qFromBigEndian<quint32>(os2Table.data() + 50), + qFromBigEndian<quint32>(os2Table.data() + 54) + }; + quint32 codePageRange[2] = { qFromBigEndian<quint32>(os2Table.data() + 78), qFromBigEndian<quint32>(os2Table.data() + 82) }; + QList<QFontDatabase::WritingSystem> systems = qt_determine_writing_systems_from_truetype_bits(unicodeRange, codePageRange); +#if 0 + QCFString name; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &name); + qDebug() << systems.count() << "writing systems for" << QString(name); +qDebug() << "first char" << hex << unicodeRange[0]; + for (int i = 0; i < systems.count(); ++i) + qDebug() << QFontDatabase::writingSystemName(systems.at(i)); +#endif + for (int i = 0; i < systems.count(); ++i) + family->writingSystems[systems.at(i)] = QtFontFamily::Supported; +} +#endif + +static void initializeDb() +{ + QFontDatabasePrivate *db = privateDb(); + if(!db || db->count) + return; + +#if defined(QT_MAC_USE_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + QCFType<CTFontCollectionRef> collection = CTFontCollectionCreateFromAvailableFonts(0); + if(!collection) + return; + QCFType<CFArrayRef> fonts = CTFontCollectionCreateMatchingFontDescriptors(collection); + if(!fonts) + return; + QString foundry_name = "CoreText"; + const int numFonts = CFArrayGetCount(fonts); + for(int i = 0; i < numFonts; ++i) { + CTFontDescriptorRef font = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fonts, i); + + QCFString family_name = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + QtFontFamily *family = db->family(family_name, true); + for(int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) + family->writingSystems[ws] = QtFontFamily::Supported; + QtFontFoundry *foundry = family->foundry(foundry_name, true); + + QtFontStyle::Key styleKey; + if(QCFType<CFDictionaryRef> styles = (CFDictionaryRef)CTFontDescriptorCopyAttribute(font, kCTFontTraitsAttribute)) { + if(CFNumberRef weight = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontWeightTrait)) { + Q_ASSERT(CFNumberIsFloatType(weight)); + double d; + if(CFNumberGetValue(weight, kCFNumberDoubleType, &d)) { + //qDebug() << "BOLD" << (QString)family_name << d; + styleKey.weight = (d > 0.0) ? QFont::Bold : QFont::Normal; + } + } + if(CFNumberRef italic = (CFNumberRef)CFDictionaryGetValue(styles, kCTFontSlantTrait)) { + Q_ASSERT(CFNumberIsFloatType(italic)); + double d; + if(CFNumberGetValue(italic, kCFNumberDoubleType, &d)) { + //qDebug() << "ITALIC" << (QString)family_name << d; + if (d > 0.0) + styleKey.style = QFont::StyleItalic; + } + } + } + + QtFontStyle *style = foundry->style(styleKey, true); + style->smoothScalable = true; + if(QCFType<CFNumberRef> size = (CFNumberRef)CTFontDescriptorCopyAttribute(font, kCTFontSizeAttribute)) { + //qDebug() << "WHEE"; + int pixel_size=0; + if(CFNumberIsFloatType(size)) { + double d; + CFNumberGetValue(size, kCFNumberDoubleType, &d); + pixel_size = d; + } else { + CFNumberGetValue(size, kCFNumberIntType, &pixel_size); + } + //qDebug() << "SIZE" << (QString)family_name << pixel_size; + if(pixel_size) + style->pixelSize(pixel_size, true); + } else { + //qDebug() << "WTF?"; + } + } +} else +#endif + { +#ifndef QT_MAC_USE_COCOA + FMFontIterator it; + if (!FMCreateFontIterator(0, 0, kFMUseGlobalScopeOption, &it)) { + while (true) { + FMFont fmFont; + if (FMGetNextFont(&it, &fmFont) != noErr) + break; + + FMFontFamily fmFamily; + FMFontStyle fmStyle; + QString familyName; + + QtFontStyle::Key styleKey; + + ATSFontRef atsFont = FMGetATSFontRefFromFont(fmFont); + + if (!FMGetFontFamilyInstanceFromFont(fmFont, &fmFamily, &fmStyle)) { + { //sanity check the font, and see if we can use it at all! --Sam + ATSUFontID fontID; + if(ATSUFONDtoFontID(fmFamily, 0, &fontID) != noErr) + continue; + } + + if (fmStyle & ::italic) + styleKey.style = QFont::StyleItalic; + if (fmStyle & ::bold) + styleKey.weight = QFont::Bold; + + ATSFontFamilyRef familyRef = FMGetATSFontFamilyRefFromFontFamily(fmFamily); + QCFString cfFamilyName;; + ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &cfFamilyName); + familyName = cfFamilyName; + } else { + QCFString cfFontName; + ATSFontGetName(atsFont, kATSOptionFlagsDefault, &cfFontName); + familyName = cfFontName; + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian<quint16>(data); + } + if (macStyle & 1) + styleKey.weight = QFont::Bold; + if (macStyle & 2) + styleKey.style = QFont::StyleItalic; + } + + QtFontFamily *family = db->family(familyName, true); + QtFontFoundry *foundry = family->foundry(QString(), true); + QtFontStyle *style = foundry->style(styleKey, true); + style->pixelSize(0, true); + style->smoothScalable = true; + + initWritingSystems(family, atsFont); + } + FMDisposeFontIterator(&it); + } +#endif + } + +} + +static inline void load(const QString & = QString(), int = -1) +{ + initializeDb(); +} + +static const char *styleHint(const QFontDef &request) +{ + const char *stylehint = 0; + switch (request.styleHint) { + case QFont::SansSerif: + stylehint = "Arial"; + break; + case QFont::Serif: + stylehint = "Times New Roman"; + break; + case QFont::TypeWriter: + stylehint = "Courier New"; + break; + default: + if (request.fixedPitch) + stylehint = "Courier New"; + break; + } + return stylehint; +} + +static inline float weightToFloat(unsigned int weight) +{ + return (weight - 50) / 100.0; +} + +void QFontDatabase::load(const QFontPrivate *d, int script) +{ + // sanity checks + if(!qApp) + qWarning("QFont: Must construct a QApplication before a QFont"); + + Q_ASSERT(script >= 0 && script < QUnicodeTables::ScriptCount); + Q_UNUSED(script); + + QFontDef req = d->request; + req.pixelSize = qt_mac_pixelsize(req, d->dpi); + + // set the point size to 0 to get better caching + req.pointSize = 0; + QFontCache::Key key = QFontCache::Key(req, QUnicodeTables::Common, d->screen); + + if(!(d->engineData = QFontCache::instance()->findEngineData(key))) { + d->engineData = new QFontEngineData; + QFontCache::instance()->insertEngineData(key, d->engineData); + } else { + d->engineData->ref.ref(); + } + if(d->engineData->engine) // already loaded + return; + + // set it to the actual pointsize, so QFontInfo will do the right thing + req.pointSize = qRound(qt_mac_pointsize(d->request, d->dpi)); + + QFontEngine *e = QFontCache::instance()->findEngine(key); + if(!e && qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) { + e = new QTestFontEngine(req.pixelSize); + e->fontDef = req; + } + + if(e) { + e->ref.ref(); + d->engineData->engine = e; + return; // the font info and fontdef should already be filled + } + + //find the font + QStringList family_list = familyList(req); + + const char *stylehint = styleHint(req); + if (stylehint) + family_list << QLatin1String(stylehint); + + // add QFont::defaultFamily() to the list, for compatibility with + // previous versions + family_list << QApplication::font().defaultFamily(); + +#if defined(QT_MAC_USE_COCOA) + QCFString fontName = NULL, familyName = NULL; +#else + ATSFontFamilyRef familyRef = 0; + ATSFontRef fontRef = 0; +#endif + + QMutexLocker locker(fontDatabaseMutex()); + QFontDatabasePrivate *db = privateDb(); + if (!db->count) + initializeDb(); + for(int i = 0; i < family_list.size(); ++i) { + for (int k = 0; k < db->count; ++k) { + if (db->families[k]->name.compare(family_list.at(i), Qt::CaseInsensitive) == 0) { + QByteArray family_name = db->families[k]->name.toUtf8(); +#if defined(QT_MAC_USE_COCOA) + QCFType<CTFontRef> ctFont = CTFontCreateWithName(QCFString(db->families[k]->name), 12, NULL); + if (ctFont) { + fontName = CTFontCopyFullName(ctFont); + familyName = CTFontCopyFamilyName(ctFont); + goto FamilyFound; + } +#else + familyRef = ATSFontFamilyFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + if (familyRef) { + fontRef = ATSFontFindFromName(QCFString(db->families[k]->name), kATSOptionFlagsDefault); + goto FamilyFound; + } +#endif + } + } + } +FamilyFound: + //fill in the engine's font definition + QFontDef fontDef = d->request; //copy.. + if(fontDef.pointSize < 0) + fontDef.pointSize = qt_mac_pointsize(fontDef, d->dpi); + else + fontDef.pixelSize = qt_mac_pixelsize(fontDef, d->dpi); + +#ifdef QT_MAC_USE_COCOA + fontDef.family = familyName; + QFontEngine *engine = new QCoreTextFontEngineMulti(fontName, fontDef, d->kerning); +#else + QCFString actualName; + if (ATSFontFamilyGetName(familyRef, kATSOptionFlagsDefault, &actualName) == noErr) + fontDef.family = actualName; + QFontEngine *engine = new QFontEngineMacMulti(familyRef, fontRef, fontDef, d->kerning); +#endif + d->engineData->engine = engine; + engine->ref.ref(); //a ref for the engineData->engine + QFontCache::instance()->insertEngine(key, engine); +} + +static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt) +{ + ATSFontContainerRef handle; + OSStatus e = noErr; + + if(fnt->data.isEmpty()) { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp + FSRef ref; + if(qt_mac_create_fsref(fnt->fileName, &ref) != noErr) + return; + + ATSFontActivateFromFileReference(&ref, kATSFontContextLocal, kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + } else +#endif + { +#ifndef Q_WS_MAC64 + extern Q_CORE_EXPORT OSErr qt_mac_create_fsspec(const QString &, FSSpec *); // global.cpp + FSSpec spec; + if(qt_mac_create_fsspec(fnt->fileName, &spec) != noErr) + return; + + e = ATSFontActivateFromFileSpecification(&spec, kATSFontContextLocal, kATSFontFormatUnspecified, + 0, kATSOptionFlagsDefault, &handle); +#endif + } + } else { + e = ATSFontActivateFromMemory((void *)fnt->data.constData(), fnt->data.size(), kATSFontContextLocal, + kATSFontFormatUnspecified, 0, kATSOptionFlagsDefault, &handle); + + fnt->data = QByteArray(); + } + + if(e != noErr) + return; + + ItemCount fontCount = 0; + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, 0, 0, &fontCount); + if(e != noErr) + return; + + QVarLengthArray<ATSFontRef> containedFonts(fontCount); + e = ATSFontFindFromContainer(handle, kATSOptionFlagsDefault, fontCount, containedFonts.data(), &fontCount); + if(e != noErr) + return; + + fnt->families.clear(); +#if defined(QT_MAC_USE_COCOA) + // Make sure that the family name set on the font matches what + // kCTFontFamilyNameAttribute returns in initializeDb(). + // So far the best solution seems find the installed font + // using CoreText and get the family name from it. + // (ATSFontFamilyGetName appears to be the correct API, but also + // returns the font display name.) + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString fontPostScriptName; + ATSFontGetPostScriptName(containedFonts[i], kATSOptionFlagsDefault, &fontPostScriptName); + QCFType<CTFontDescriptorRef> font = CTFontDescriptorCreateWithNameAndSize(fontPostScriptName, 14); + QCFString familyName = (CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute); + fnt->families.append(familyName); + } +#else + for(int i = 0; i < containedFonts.size(); ++i) { + QCFString family; + ATSFontGetName(containedFonts[i], kATSOptionFlagsDefault, &family); + fnt->families.append(family); + } +#endif + + fnt->handle = handle; +} + +bool QFontDatabase::removeApplicationFont(int handle) +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + if(handle < 0 || handle >= db->applicationFonts.count()) + return false; + + OSStatus e = ATSFontDeactivate(db->applicationFonts.at(handle).handle, + /*iRefCon=*/0, kATSOptionFlagsDefault); + if(e != noErr) + return false; + + db->applicationFonts[handle] = QFontDatabasePrivate::ApplicationFont(); + + db->invalidate(); + return true; +} + +bool QFontDatabase::removeAllApplicationFonts() +{ + QMutexLocker locker(fontDatabaseMutex()); + + QFontDatabasePrivate *db = privateDb(); + for(int i = 0; i < db->applicationFonts.count(); ++i) { + if(!removeApplicationFont(i)) + return false; + } + return true; +} + +bool QFontDatabase::supportsThreadedFontRendering() +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qfontengine_coretext.mm b/src/gui/platforms/mac/qfontengine_coretext.mm new file mode 100644 index 0000000000..d4df2183ed --- /dev/null +++ b/src/gui/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 <QtCore/qendian.h> +#include <QtCore/qsettings.h> + +#include <private/qimage_p.h> + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +QT_BEGIN_NAMESPACE + +static float SYNTHETIC_ITALIC_SKEW = tanf(14 * acosf(0) / 90); + +static void loadAdvancesForGlyphs(CTFontRef ctfont, + QVarLengthArray<CGGlyph> &cgGlyphs, + QGlyphLayout *glyphs, int len, + QTextEngine::ShaperFlags flags, + const QFontDef &fontDef) +{ + Q_UNUSED(flags); + QVarLengthArray<CGSize> advances(len); + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, cgGlyphs.data(), advances.data(), len); + + for (int i = 0; i < len; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + continue; + glyphs->advances_x[i] = QFixed::fromReal(advances[i].width); + glyphs->advances_y[i] = QFixed::fromReal(advances[i].height); + } + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + for (int i = 0; i < len; ++i) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(const QCFString &name, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + CTFontSymbolicTraits symbolicTraits = 0; + if (fontDef.weight >= QFont::Bold) + symbolicTraits |= kCTFontBoldTrait; + switch (fontDef.style) { + case QFont::StyleNormal: + break; + case QFont::StyleItalic: + case QFont::StyleOblique: + symbolicTraits |= kCTFontItalicTrait; + break; + } + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + QCFType<CTFontDescriptorRef> descriptor = CTFontDescriptorCreateWithNameAndSize(name, fontDef.pixelSize); + QCFType<CTFontRef> baseFont = CTFontCreateWithFontDescriptor(descriptor, fontDef.pixelSize, &transform); + ctfont = CTFontCreateCopyWithSymbolicTraits(baseFont, fontDef.pixelSize, &transform, symbolicTraits, symbolicTraits); + + // CTFontCreateCopyWithSymbolicTraits returns NULL if we ask for a trait that does + // not exist for the given font. (for example italic) + if (ctfont == 0) { + ctfont = baseFont; + CFRetain(ctfont); + } + init(kerning); +} + +QCoreTextFontEngineMulti::QCoreTextFontEngineMulti(CGFontRef cgFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + } + + ctfont = CTFontCreateWithGraphicsFont(cgFontRef, fontDef.pixelSize, &transform, NULL); + init(kerning); +} + +QCoreTextFontEngineMulti::~QCoreTextFontEngineMulti() +{ + CFRelease(ctfont); +} + +void QCoreTextFontEngineMulti::init(bool kerning) +{ + Q_ASSERT(ctfont != NULL); + attributeDict = CFDictionaryCreateMutable(0, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionaryAddValue(attributeDict, NSFontAttributeName, ctfont); + if (!kerning) { + float zero = 0.0; + QCFType<CFNumberRef> noKern = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloatType, &zero); + CFDictionaryAddValue(attributeDict, kCTKernAttributeName, noKern); + } + + QCoreTextFontEngine *fe = new QCoreTextFontEngine(ctfont, fontDef); + fe->ref.ref(); + engines.append(fe); +} + +uint QCoreTextFontEngineMulti::fontIndexForFont(CTFontRef font) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (CFEqual(engineAt(i)->ctfont, font)) + return i; + } + + QCoreTextFontEngineMulti *that = const_cast<QCoreTextFontEngineMulti *>(this); + QCoreTextFontEngine *fe = new QCoreTextFontEngine(font, fontDef); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QCoreTextFontEngineMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *, + QScriptItem *si) const +{ + QCFType<CFStringRef> cfstring = CFStringCreateWithCharactersNoCopy(0, + reinterpret_cast<const UniChar *>(str), + len, kCFAllocatorNull); + QCFType<CFAttributedStringRef> attributedString = CFAttributedStringCreate(0, cfstring, attributeDict); + QCFType<CTTypesetterRef> typeSetter; + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (flags & QTextEngine::RightToLeft) { + const void *optionKeys[] = { kCTTypesetterOptionForcedEmbeddingLevel }; + const short rtlForcedEmbeddingLevelValue = 1; + const void *rtlOptionValues[] = { CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &rtlForcedEmbeddingLevelValue) }; + QCFType<CFDictionaryRef> options = CFDictionaryCreate(kCFAllocatorDefault, optionKeys, rtlOptionValues, 1, + &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + typeSetter = CTTypesetterCreateWithAttributedStringAndOptions(attributedString, options); + } else +#else + Q_UNUSED(flags); +#endif + typeSetter = CTTypesetterCreateWithAttributedString(attributedString); + + CFRange range = {0, 0}; + QCFType<CTLineRef> line = CTTypesetterCreateLine(typeSetter, range); + CFArrayRef array = CTLineGetGlyphRuns(line); + uint arraySize = CFArrayGetCount(array); + glyph_t *outGlyphs = glyphs->glyphs; + HB_GlyphAttributes *outAttributes = glyphs->attributes; + QFixed *outAdvances_x = glyphs->advances_x; + QFixed *outAdvances_y = glyphs->advances_y; + glyph_t *initialGlyph = outGlyphs; + + if (arraySize == 0) { + // CoreText failed to shape the text we gave it, so we assume one glyph + // per character and build a list of invalid glyphs with zero advance + *nglyphs = len; + for (int i = 0; i < len; ++i) { + outGlyphs[i] = 0; + if (logClusters) + logClusters[i] = i; + outAdvances_x[i] = QFixed(); + outAdvances_y[i] = QFixed(); + outAttributes[i].clusterStart = true; + } + return true; + } + + const bool rtl = (CTRunGetStatus(static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, 0))) & kCTRunStatusRightToLeft); + + bool outOBounds = false; + for (uint i = 0; i < arraySize; ++i) { + CTRunRef run = static_cast<CTRunRef>(CFArrayGetValueAtIndex(array, rtl ? (arraySize - 1 - i) : i)); + CFIndex glyphCount = CTRunGetGlyphCount(run); + if (glyphCount == 0) + continue; + + Q_ASSERT((CTRunGetStatus(run) & kCTRunStatusRightToLeft) == rtl); + CFRange stringRange = CTRunGetStringRange(run); + 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<CTFontRef>(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<CGGlyph, 512> cgglyphs(0); + const CGGlyph *tmpGlyphs = CTRunGetGlyphsPtr(run); + if (!tmpGlyphs) { + cgglyphs.resize(glyphCount); + CTRunGetGlyphs(run, range, cgglyphs.data()); + tmpGlyphs = cgglyphs.constData(); + } + QVarLengthArray<CGPoint, 512> cgpoints(0); + const CGPoint *tmpPoints = CTRunGetPositionsPtr(run); + if (!tmpPoints) { + cgpoints.resize(glyphCount); + CTRunGetPositions(run, range, cgpoints.data()); + tmpPoints = cgpoints.constData(); + } + + const int rtlOffset = rtl ? (glyphCount - 1) : 0; + const int rtlSign = rtl ? -1 : 1; + + if (logClusters) { + CFRange stringRange = CTRunGetStringRange(run); + QVarLengthArray<CFIndex, 512> stringIndices(0); + const CFIndex *tmpIndices = CTRunGetStringIndicesPtr(run); + if (!tmpIndices) { + stringIndices.resize(glyphCount); + CTRunGetStringIndices(run, range, stringIndices.data()); + tmpIndices = stringIndices.constData(); + } + + const int firstGlyphIndex = outGlyphs - initialGlyph; + outAttributes[0].clusterStart = true; + + CFIndex k = 0; + CFIndex i = 0; + for (i = stringRange.location + 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<CFStringRef> cfstring; + + QVarLengthArray<CGGlyph> cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) { + if (cgGlyphs[i]) { + glyphs->glyphs[i] = cgGlyphs[i]; + } else { + if (!cfstring) + cfstring = CFStringCreateWithCharactersNoCopy(0, reinterpret_cast<const UniChar *>(str), len, kCFAllocatorNull); + QCFType<CTFontRef> substituteFont = CTFontCreateForString(ctfont, cfstring, CFRangeMake(i, 1)); + CGGlyph substituteGlyph = 0; + CTFontGetGlyphsForCharacters(substituteFont, (const UniChar*)str + i, &substituteGlyph, 1); + if (substituteGlyph) { + const uint fontIndex = (fontIndexForFont(substituteFont) << 24); + glyphs->glyphs[i] = substituteGlyph | fontIndex; + if (!(flags & QTextEngine::GlyphIndicesOnly)) { + CGSize advance; + CTFontGetAdvancesForGlyphs(substituteFont, kCTFontHorizontalOrientation, &substituteGlyph, &advance, 1); + glyphs->advances_x[i] = QFixed::fromReal(advance.width); + glyphs->advances_y[i] = QFixed::fromReal(advance.height); + } + } + } + } + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +void QCoreTextFontEngineMulti::loadEngine(int) +{ + // Do nothing + Q_ASSERT(false); +} + +extern int qt_antialiasing_threshold; // from qapplication.cpp + +static inline CGAffineTransform transformFromFontDef(const QFontDef &fontDef) +{ + CGAffineTransform transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + return transform; +} + +QCoreTextFontEngine::QCoreTextFontEngine(CTFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + ctfont = font; + CFRetain(ctfont); + cgFont = CTFontCopyGraphicsFont(font, NULL); + init(); +} + +QCoreTextFontEngine::QCoreTextFontEngine(CGFontRef font, const QFontDef &def) +{ + fontDef = def; + transform = transformFromFontDef(fontDef); + cgFont = font; + // Keep reference count balanced + CFRetain(cgFont); + ctfont = CTFontCreateWithGraphicsFont(font, fontDef.pixelSize, &transform, NULL); + init(); +} + +QCoreTextFontEngine::~QCoreTextFontEngine() +{ + CFRelease(cgFont); + CFRelease(ctfont); +} + +extern QFont::Weight weightFromInteger(int weight); // qfontdatabase.cpp + +int getTraitValue(CFDictionaryRef allTraits, CFStringRef trait) +{ + if (CFDictionaryContainsKey(allTraits, trait)) { + CFNumberRef traitNum = (CFNumberRef) CFDictionaryGetValue(allTraits, trait); + float v = 0; + CFNumberGetValue(traitNum, kCFNumberFloatType, &v); + // the value we get from CFNumberRef is from -1.0 to 1.0 + int value = v * 500 + 500; + return value; + } + + return 0; +} + +void QCoreTextFontEngine::init() +{ + Q_ASSERT(ctfont != NULL); + Q_ASSERT(cgFont != NULL); + + QCFString family = CTFontCopyFamilyName(ctfont); + fontDef.family = family; + + synthesisFlags = 0; + CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(ctfont); + if (traits & kCTFontItalicTrait) + fontDef.style = QFont::StyleItalic; + + CFDictionaryRef allTraits = CTFontCopyTraits(ctfont); + fontDef.weight = weightFromInteger(getTraitValue(allTraits, kCTFontWeightTrait)); + int slant = getTraitValue(allTraits, kCTFontSlantTrait); + if (slant > 500 && !(traits & kCTFontItalicTrait)) + fontDef.style = QFont::StyleOblique; + CFRelease(allTraits); + + if (fontDef.weight >= QFont::Bold && !(traits & kCTFontBoldTrait)) + synthesisFlags |= SynthesizedBold; + // XXX: we probably don't need to synthesis italic for oblique font + if (fontDef.style != QFont::StyleNormal && !(traits & kCTFontItalicTrait)) + synthesisFlags |= SynthesizedItalic; + + avgCharWidth = 0; + QByteArray os2Table = getSfntTable(MAKE_TAG('O', 'S', '/', '2')); + unsigned emSize = CTFontGetUnitsPerEm(ctfont); + if (os2Table.size() >= 10) { + fsType = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(os2Table.constData() + 8)); + // qAbs is a workaround for weird fonts like Lucida Grande + qint16 width = qAbs(qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(os2Table.constData() + 2))); + avgCharWidth = QFixed::fromReal(width * fontDef.pixelSize / emSize); + } else + avgCharWidth = QFontEngine::averageCharWidth(); + + ctMaxCharWidth = ctMinLeftBearing = ctMinRightBearing = 0; + QByteArray hheaTable = getSfntTable(MAKE_TAG('h', 'h', 'e', 'a')); + if (hheaTable.size() >= 16) { + quint16 width = qFromBigEndian<quint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 10)); + ctMaxCharWidth = width * fontDef.pixelSize / emSize; + qint16 bearing = qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 12)); + ctMinLeftBearing = bearing * fontDef.pixelSize / emSize; + bearing = qFromBigEndian<qint16>(reinterpret_cast<const uchar *>(hheaTable.constData() + 14)); + ctMinRightBearing = bearing * fontDef.pixelSize / emSize; + } +} + +bool QCoreTextFontEngine::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, + int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + *nglyphs = len; + QCFType<CFStringRef> cfstring; + + QVarLengthArray<CGGlyph> cgGlyphs(len); + CTFontGetGlyphsForCharacters(ctfont, (const UniChar*)str, cgGlyphs.data(), len); + + for (int i = 0; i < len; ++i) + if (cgGlyphs[i]) + glyphs->glyphs[i] = cgGlyphs[i]; + + if (flags & QTextEngine::GlyphIndicesOnly) + return true; + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, len, flags, fontDef); + return true; +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QCoreTextFontEngine::boundingBox(glyph_t glyph) +{ + glyph_metrics_t ret; + CGGlyph g = glyph; + CGRect rect = CTFontGetBoundingRectsForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, 0, 1); + if (synthesisFlags & QFontEngine::SynthesizedItalic) { + rect.size.width += rect.size.height * SYNTHETIC_ITALIC_SKEW; + } + ret.width = QFixed::fromReal(rect.size.width); + ret.height = QFixed::fromReal(rect.size.height); + ret.x = QFixed::fromReal(rect.origin.x); + ret.y = -QFixed::fromReal(rect.origin.y) - ret.height; + CGSize advances[1]; + CTFontGetAdvancesForGlyphs(ctfont, kCTFontHorizontalOrientation, &g, advances, 1); + ret.xoff = QFixed::fromReal(advances[0].width); + ret.yoff = QFixed::fromReal(advances[0].height); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + ret.xoff = ret.xoff.round(); + ret.yoff = ret.yoff.round(); + } + + return ret; +} + +QFixed QCoreTextFontEngine::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetAscent(ctfont)).round() + : QFixed::fromReal(CTFontGetAscent(ctfont)); +} +QFixed QCoreTextFontEngine::descent() const +{ + QFixed d = QFixed::fromReal(CTFontGetDescent(ctfont)); + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + d = d.round(); + + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return d - 1; +} +QFixed QCoreTextFontEngine::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetLeading(ctfont)).round() + : QFixed::fromReal(CTFontGetLeading(ctfont)); +} +QFixed QCoreTextFontEngine::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? QFixed::fromReal(CTFontGetXHeight(ctfont)).round() + : QFixed::fromReal(CTFontGetXHeight(ctfont)); +} + +QFixed QCoreTextFontEngine::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? avgCharWidth.round() : avgCharWidth; +} + +qreal QCoreTextFontEngine::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMaxCharWidth) : ctMaxCharWidth; +} + +qreal QCoreTextFontEngine::minLeftBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinLeftBearing) : ctMinLeftBearing; +} + +qreal QCoreTextFontEngine::minRightBearing() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(ctMinRightBearing) : ctMinLeftBearing; +} + +void QCoreTextFontEngine::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray<CGSize> advances(glyphs.size()); + QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + //NSLog(@"Font inDraw %@ ctfont %@", CGFontCopyFullName(cgFont), CTFontCopyFamilyName(ctfont)); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +struct ConvertPathInfo +{ + ConvertPathInfo(QPainterPath *newPath, const QPointF &newPos) : path(newPath), pos(newPos) {} + QPainterPath *path; + QPointF pos; +}; + +static void convertCGPathToQPainterPath(void *info, const CGPathElement *element) +{ + ConvertPathInfo *myInfo = static_cast<ConvertPathInfo *>(info); + switch(element->type) { + case kCGPathElementMoveToPoint: + myInfo->path->moveTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddLineToPoint: + myInfo->path->lineTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y()); + break; + case kCGPathElementAddQuadCurveToPoint: + myInfo->path->quadTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y()); + break; + case kCGPathElementAddCurveToPoint: + myInfo->path->cubicTo(element->points[0].x + myInfo->pos.x(), + element->points[0].y + myInfo->pos.y(), + element->points[1].x + myInfo->pos.x(), + element->points[1].y + myInfo->pos.y(), + element->points[2].x + myInfo->pos.x(), + element->points[2].y + myInfo->pos.y()); + break; + case kCGPathElementCloseSubpath: + myInfo->path->closeSubpath(); + break; + default: + qDebug() << "Unhandled path transform type: " << element->type; + } + +} + +void QCoreTextFontEngine::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int nGlyphs, + QPainterPath *path, QTextItem::RenderFlags) +{ + CGAffineTransform cgMatrix = CGAffineTransformIdentity; + cgMatrix = CGAffineTransformScale(cgMatrix, 1, -1); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + for (int i = 0; i < nGlyphs; ++i) { + QCFType<CGPathRef> cgpath = CTFontCreatePathForGlyph(ctfont, glyphs[i], &cgMatrix); + ConvertPathInfo info(path, positions[i].toPointF()); + CGPathApply(cgpath, &info, convertCGPathToQPainterPath); + } +} + +QImage QCoreTextFontEngine::imageForGlyph(glyph_t glyph, QFixed subPixelPosition, int /*margin*/, bool aa) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+2, QImage::Format_RGB32); + im.fill(0); + + CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, aa || + (fontDef.pointSize > qt_antialiasing_threshold + && !(fontDef.styleStrategy & QFont::NoAntialias))); + CGContextSetShouldSmoothFonts(ctx, aa); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, SYNTHETIC_ITALIC_SKEW, 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + subPixelPosition.toReal(); + qreal pos_y = im.height() + br.y.toReal() - 1; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QCoreTextFontEngine::alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition) +{ + QImage im = imageForGlyph(glyph, subPixelPosition, 0, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uint *src = (uint*) im.scanLine(y); + uchar *dst = indexed.scanLine(y); + for (int x=0; x<im.width(); ++x) { + *dst = qGray(*src); + ++dst; + ++src; + } + } + + return indexed; +} + +QImage QCoreTextFontEngine::alphaRGBMapForGlyph(glyph_t glyph, QFixed subPixelPosition, int margin, const QTransform &x) +{ + if (x.type() >= QTransform::TxScale) + return QFontEngine::alphaRGBMapForGlyph(glyph, subPixelPosition, margin, x); + + QImage im = imageForGlyph(glyph, subPixelPosition, margin, true); + qGamma_correct_back_to_linear_cs(&im); + return im; +} + +void QCoreTextFontEngine::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + int i, numGlyphs = glyphs->numGlyphs; + QVarLengthArray<CGGlyph> cgGlyphs(numGlyphs); + + for (i = 0; i < numGlyphs; ++i) { + if (glyphs->glyphs[i] & 0xff000000) + cgGlyphs[i] = 0; + else + cgGlyphs[i] = glyphs->glyphs[i]; + } + + loadAdvancesForGlyphs(ctfont, cgGlyphs, glyphs, numGlyphs, flags, fontDef); +} + +QFontEngine::FaceId QCoreTextFontEngine::faceId() const +{ + return QFontEngine::FaceId(); +} + +bool QCoreTextFontEngine::canRender(const QChar *string, int len) +{ + QVarLengthArray<CGGlyph> cgGlyphs(len); + return CTFontGetGlyphsForCharacters(ctfont, (const UniChar *) string, cgGlyphs.data(), len); +} + +bool QCoreTextFontEngine::getSfntTableData(uint tag, uchar *buffer, uint *length) const +{ + QCFType<CFDataRef> table = CTFontCopyTable(ctfont, tag, 0); + if (!table || !length) + return false; + CFIndex tableLength = CFDataGetLength(table); + int availableLength = *length; + *length = tableLength; + if (buffer) { + if (tableLength > availableLength) + return false; + CFDataGetBytes(table, CFRangeMake(0, tableLength), buffer); + } + return true; +} + +void QCoreTextFontEngine::getUnscaledGlyph(glyph_t, QPainterPath *, glyph_metrics_t *) +{ + // ### +} + +QFixed QCoreTextFontEngine::emSquareSize() const +{ + return QFixed::QFixed(int(CTFontGetUnitsPerEm(ctfont))); +} + +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/gui/platforms/mac/qfontengine_coretext_p.h b/src/gui/platforms/mac/qfontengine_coretext_p.h new file mode 100644 index 0000000000..bb80a9b2f3 --- /dev/null +++ b/src/gui/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 <private/qfontengine_p.h> + +#if !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +class QRawFontPrivate; +class QCoreTextFontEngineMulti; +class QCoreTextFontEngine : public QFontEngine +{ +public: + QCoreTextFontEngine(CTFontRef font, const QFontDef &def); + QCoreTextFontEngine(CGFontRef font, const QFontDef &def); + ~QCoreTextFontEngine(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QCoreTextFontEngine"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + virtual bool supportsSubPixelPositions() const { return true; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual bool getSfntTableData(uint /*tag*/, uchar * /*buffer*/, uint * /*length*/) const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t, QFixed subPixelPosition); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + virtual qreal minRightBearing() const; + virtual qreal minLeftBearing() const; + virtual QFixed emSquareSize() const; + + 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<const QCoreTextFontEngine *>(engines.at(i)); } + + uint fontIndexForFont(CTFontRef font) const; + CTFontRef ctfont; + mutable QCFType<CFMutableDictionaryRef> attributeDict; + CGAffineTransform transform; + friend class QFontDialogPrivate; +}; + +#endif// !defined(Q_WS_MAC) || (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + +#endif // QFONTENGINE_CORETEXT_P_H diff --git a/src/gui/platforms/mac/qfontengine_mac.mm b/src/gui/platforms/mac/qfontengine_mac.mm new file mode 100644 index 0000000000..9f094ad7d1 --- /dev/null +++ b/src/gui/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 <private/qapplication_p.h> +#include <private/qfontengine_p.h> +#include <private/qpainter_p.h> +#include <private/qtextengine_p.h> +#include <qbitmap.h> +#include <private/qpaintengine_mac_p.h> +#include <private/qprintengine_mac_p.h> +#include <qglobal.h> +#include <qpixmap.h> +#include <qpixmapcache.h> +#include <qvarlengtharray.h> +#include <qdebug.h> +#include <qendian.h> +#include <qmath.h> +#include <private/qimage_p.h> + +#include <ApplicationServices/ApplicationServices.h> +#include <AppKit/AppKit.h> + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + QFontEngine debug facilities + *****************************************************************************/ +//#define DEBUG_ADVANCES + +extern int qt_antialiasing_threshold; // QApplication.cpp + +#ifndef FixedToQFixed +#define FixedToQFixed(a) QFixed::fromFixed((a) >> 10) +#define QFixedToFixed(x) ((x).value() << 10) +#endif + +class QMacFontPath +{ + float x, y; + QPainterPath *path; +public: + inline QMacFontPath(float _x, float _y, QPainterPath *_path) : x(_x), y(_y), path(_path) { } + inline void setPosition(float _x, float _y) { x = _x; y = _y; } + inline void advance(float _x) { x += _x; } + static OSStatus lineTo(const Float32Point *, void *); + static OSStatus cubicTo(const Float32Point *, const Float32Point *, + const Float32Point *, void *); + static OSStatus moveTo(const Float32Point *, void *); + static OSStatus closePath(void *); +}; + +OSStatus QMacFontPath::lineTo(const Float32Point *pt, void *data) + +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->lineTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::cubicTo(const Float32Point *cp1, const Float32Point *cp2, + const Float32Point *ep, void *data) + +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->cubicTo(p->x + cp1->x, p->y + cp1->y, + p->x + cp2->x, p->y + cp2->y, + p->x + ep->x, p->y + ep->y); + return noErr; +} + +OSStatus QMacFontPath::moveTo(const Float32Point *pt, void *data) +{ + QMacFontPath *p = static_cast<QMacFontPath*>(data); + p->path->moveTo(p->x + pt->x, p->y + pt->y); + return noErr; +} + +OSStatus QMacFontPath::closePath(void *data) +{ + static_cast<QMacFontPath*>(data)->path->closeSubpath(); + return noErr; +} + + +#ifndef QT_MAC_USE_COCOA +QFontEngineMacMulti::QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning) + : QFontEngineMulti(0) +{ + this->fontDef = fontDef; + this->kerning = kerning; + + // hopefully (CTFontCreateWithName or CTFontCreateWithFontDescriptor) + CTFontCreateCopyWithSymbolicTraits + // (or CTFontCreateWithQuickdrawInstance) + FMFontFamily fmFamily; + FMFontStyle fntStyle = 0; + fmFamily = FMGetFontFamilyFromATSFontFamilyRef(atsFamily); + if (fmFamily == kInvalidFontFamily) { + // Use the ATSFont then... + fontID = FMGetFontFromATSFontRef(atsFontRef); + } else { + if (fontDef.weight >= QFont::Bold) + fntStyle |= ::bold; + if (fontDef.style != QFont::StyleNormal) + fntStyle |= ::italic; + + FMFontStyle intrinsicStyle; + FMFont fnt = 0; + if (FMGetFontFromFontFamilyInstance(fmFamily, fntStyle, &fnt, &intrinsicStyle) == noErr) + fontID = FMGetATSFontRefFromFont(fnt); + } + + // CFDictionaryRef, <CTStringAttributes.h> + OSStatus status; + + status = ATSUCreateTextLayout(&textLayout); + Q_ASSERT(status == noErr); + + const int maxAttributeCount = 5; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(fontDef.pixelSize, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + tags[attributeCount] = kATSUFontTag; + sizes[attributeCount] = sizeof(fontID); + values[attributeCount] = &this->fontID; + ++attributeCount; + + transform = CGAffineTransformIdentity; + if (fontDef.stretch != 100) { + transform = CGAffineTransformMakeScale(float(fontDef.stretch) / float(100), 1); + tags[attributeCount] = kATSUFontMatrixTag; + sizes[attributeCount] = sizeof(transform); + values[attributeCount] = &transform; + ++attributeCount; + } + + status = ATSUCreateStyle(&style); + Q_ASSERT(status == noErr); + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + status = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(status == noErr); + + QFontEngineMac *fe = new QFontEngineMac(style, fontID, fontDef, this); + fe->ref.ref(); + engines.append(fe); +} + +QFontEngineMacMulti::~QFontEngineMacMulti() +{ + ATSUDisposeTextLayout(textLayout); + ATSUDisposeStyle(style); + + for (int i = 0; i < engines.count(); ++i) { + QFontEngineMac *fe = const_cast<QFontEngineMac *>(static_cast<const QFontEngineMac *>(engines.at(i))); + fe->multiEngine = 0; + if (!fe->ref.deref()) + delete fe; + } + engines.clear(); +} + +struct QGlyphLayoutInfo +{ + QGlyphLayout *glyphs; + int *numGlyphs; + bool callbackCalled; + int *mappedFonts; + QTextEngine::ShaperFlags flags; + QFontEngineMacMulti::ShaperItem *shaperItem; + unsigned int styleStrategy; +}; + +static OSStatus atsuPostLayoutCallback(ATSULayoutOperationSelector selector, ATSULineRef lineRef, URefCon refCon, + void *operationExtraParameter, ATSULayoutOperationCallbackStatus *callbackStatus) +{ + Q_UNUSED(selector); + Q_UNUSED(operationExtraParameter); + + QGlyphLayoutInfo *nfo = reinterpret_cast<QGlyphLayoutInfo *>(refCon); + nfo->callbackCalled = true; + + ATSLayoutRecord *layoutData = 0; + ItemCount itemCount = 0; + + OSStatus e = noErr; + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + /*iCreate =*/ false, + (void **) &layoutData, + &itemCount); + if (e != noErr) + return e; + + *nfo->numGlyphs = itemCount - 1; + + Fixed *baselineDeltas = 0; + + e = ATSUDirectGetLayoutDataArrayPtrFromLineRef(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + /*iCreate =*/ true, + (void **) &baselineDeltas, + &itemCount); + if (e != noErr) + return e; + + int nextCharStop = -1; + int currentClusterGlyph = -1; // first glyph in log cluster + QFontEngineMacMulti::ShaperItem *item = nfo->shaperItem; + if (item->charAttributes) { + item = nfo->shaperItem; +#if !defined(QT_NO_DEBUG) + int surrogates = 0; + const QChar *str = item->string; + for (int i = item->from; i < item->from + item->length - 1; ++i) { + surrogates += (str[i].unicode() >= 0xd800 && str[i].unicode() < 0xdc00 + && str[i+1].unicode() >= 0xdc00 && str[i+1].unicode() < 0xe000); + } +#endif + for (nextCharStop = item->from; nextCharStop < item->from + item->length; ++nextCharStop) + if (item->charAttributes[nextCharStop].charStop) + break; + nextCharStop -= item->from; + } + + nfo->glyphs->attributes[0].clusterStart = true; + int glyphIdx = 0; + int glyphIncrement = 1; + if (nfo->flags & QTextEngine::RightToLeft) { + glyphIdx = itemCount - 2; + glyphIncrement = -1; + } + for (int i = 0; i < *nfo->numGlyphs; ++i, glyphIdx += glyphIncrement) { + + int charOffset = layoutData[glyphIdx].originalOffset / sizeof(UniChar); + const int fontIdx = nfo->mappedFonts[charOffset]; + + ATSGlyphRef glyphId = layoutData[glyphIdx].glyphID; + + QFixed yAdvance = FixedToQFixed(baselineDeltas[glyphIdx]); + QFixed xAdvance = FixedToQFixed(layoutData[glyphIdx + 1].realPos - layoutData[glyphIdx].realPos); + + if (nfo->styleStrategy & QFont::ForceIntegerMetrics) { + yAdvance = yAdvance.round(); + xAdvance = xAdvance.round(); + } + + if (glyphId != 0xffff || i == 0) { + if (i < nfo->glyphs->numGlyphs) + { + nfo->glyphs->glyphs[i] = (glyphId & 0x00ffffff) | (fontIdx << 24); + + nfo->glyphs->advances_y[i] = yAdvance; + nfo->glyphs->advances_x[i] = xAdvance; + } + } else { + // ATSUI gives us 0xffff as glyph id at the index in the glyph array for + // a character position that maps to a ligtature. Such a glyph id does not + // result in any visual glyph, but it may have an advance, which is why we + // sum up the glyph advances. + --i; + nfo->glyphs->advances_y[i] += yAdvance; + nfo->glyphs->advances_x[i] += xAdvance; + *nfo->numGlyphs -= 1; + } + + if (item->log_clusters) { + if (charOffset >= nextCharStop) { + nfo->glyphs->attributes[i].clusterStart = true; + currentClusterGlyph = i; + + ++nextCharStop; + for (; nextCharStop < item->length; ++nextCharStop) + if (item->charAttributes[item->from + nextCharStop].charStop) + break; + } else { + if (currentClusterGlyph == -1) + currentClusterGlyph = i; + } + item->log_clusters[charOffset] = currentClusterGlyph; + + // surrogate handling + if (charOffset < item->length - 1) { + QChar current = item->string[item->from + charOffset]; + QChar next = item->string[item->from + charOffset + 1]; + if (current.unicode() >= 0xd800 && current.unicode() < 0xdc00 + && next.unicode() >= 0xdc00 && next.unicode() < 0xe000) { + item->log_clusters[charOffset + 1] = currentClusterGlyph; + } + } + } + } + + /* + if (item) { + qDebug() << "resulting logclusters:"; + for (int i = 0; i < item->length; ++i) + qDebug() << "logClusters[" << i << "] =" << item->log_clusters[i]; + qDebug() << "clusterstarts:"; + for (int i = 0; i < *nfo->numGlyphs; ++i) + qDebug() << "clusterStart[" << i << "] =" << nfo->glyphs[i].attributes.clusterStart; + } + */ + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataBaselineDeltaFixedArray, + (void **) &baselineDeltas); + + ATSUDirectReleaseLayoutDataArrayPtr(lineRef, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, + (void **) &layoutData); + + *callbackStatus = kATSULayoutOperationCallbackStatusHandled; + return noErr; +} + +int QFontEngineMacMulti::fontIndexForFontID(ATSUFontID id) const +{ + for (int i = 0; i < engines.count(); ++i) { + if (engineAt(i)->fontID == id) + return i; + } + + QFontEngineMacMulti *that = const_cast<QFontEngineMacMulti *>(this); + QFontEngineMac *fe = new QFontEngineMac(style, id, fontDef, that); + fe->ref.ref(); + that->engines.append(fe); + return engines.count() - 1; +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + return stringToCMap(str, len, glyphs, nglyphs, flags, /*logClusters=*/0, /*charAttributes=*/0); +} + +bool QFontEngineMacMulti::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes, 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<int> mappedFonts(len); + for (int i = 0; i < len; ++i) + mappedFonts[i] = 0; + nfo.mappedFonts = mappedFonts.data(); + + Q_ASSERT(sizeof(void *) <= sizeof(URefCon)); + e = ATSUSetTextLayoutRefCon(textLayout, (URefCon)&nfo); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetTextLayoutRefCon %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + { + const int maxAttributeCount = 3; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + tags[attributeCount] = kATSULineLayoutOptionsTag; + ATSLineLayoutOptions layopts = kATSLineHasNoOpticalAlignment + | kATSLineIgnoreFontLeading + | kATSLineNoSpecialJustification // we do kashidas ourselves + | kATSLineDisableAllJustification + ; + + if (fontDef.styleStrategy & QFont::NoAntialias) + layopts |= kATSLineNoAntiAliasing; + + if (!kerning) + layopts |= kATSLineDisableAllKerningAdjustments; + + values[attributeCount] = &layopts; + sizes[attributeCount] = sizeof(layopts); + ++attributeCount; + + tags[attributeCount] = kATSULayoutOperationOverrideTag; + ATSULayoutOperationOverrideSpecifier spec; + spec.operationSelector = kATSULayoutOperationPostLayoutAdjustment; + spec.overrideUPP = atsuPostLayoutCallback; + values[attributeCount] = &spec; + sizes[attributeCount] = sizeof(spec); + ++attributeCount; + + // CTWritingDirection + Boolean direction; + if (flags & QTextEngine::RightToLeft) + direction = kATSURightToLeftBaseDirection; + else + direction = kATSULeftToRightBaseDirection; + tags[attributeCount] = kATSULineDirectionTag; + values[attributeCount] = &direction; + sizes[attributeCount] = sizeof(direction); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + e = ATSUSetLayoutControls(textLayout, attributeCount, tags, sizes, values); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetLayoutControls %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + } + + e = ATSUSetRunStyle(textLayout, style, 0, len); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUSetRunStyle %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + if (!(fontDef.styleStrategy & QFont::NoFontMerging)) { + int pos = 0; + do { + ATSUFontID substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + int fontIdx = fontIndexForFontID(substFont); + for (uint i = 0; i < changeCount; ++i) + mappedFonts[changedOffset + i] = fontIdx; + pos = changedOffset + changeCount; + ATSUSetRunStyle(textLayout, engineAt(fontIdx)->style, changedOffset, changeCount); + } else if (e == kATSUFontsNotMatched) { + pos = changedOffset + changeCount; + } + } while (pos < len && e != noErr); + } + { // trigger the a layout + // CFAttributedStringCreate, CTFramesetterCreateWithAttributedString (or perhaps Typesetter) + Rect rect; + e = ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning, kATSUToTextEnd, + /*iLocationX =*/ 0, /*iLocationY =*/ 0, + &rect); + if (e != noErr) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage %s: %d", long(e), __FILE__, __LINE__); + return false; + } + } + + if (!nfo.callbackCalled) { + qWarning("Qt: internal: %ld: Error ATSUMeasureTextImage did not trigger callback %s: %d", long(e), __FILE__, __LINE__); + return false; + } + + ATSUClearLayoutCache(textLayout, kATSUFromTextBeginning); + if (prevNumGlyphs < *nfo.numGlyphs) + return false; + return true; +} + +void QFontEngineMacMulti::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_ASSERT(false); + Q_UNUSED(glyphs); + Q_UNUSED(flags); +} + +void QFontEngineMacMulti::doKerning(QGlyphLayout *, QTextEngine::ShaperFlags) const +{ + //Q_ASSERT(false); +} + +void QFontEngineMacMulti::loadEngine(int /*at*/) +{ + // should never be called! + Q_ASSERT(false); +} + +bool QFontEngineMacMulti::canRender(const QChar *string, int len) +{ + ATSUSetTextPointerLocation(textLayout, reinterpret_cast<const UniChar *>(string), 0, len, len); + ATSUSetRunStyle(textLayout, style, 0, len); + + OSStatus e = noErr; + int pos = 0; + do { + FMFont substFont = 0; + UniCharArrayOffset changedOffset = 0; + UniCharCount changeCount = 0; + + // CTFontCreateForString + e = ATSUMatchFontsToText(textLayout, pos, len - pos, + &substFont, &changedOffset, + &changeCount); + if (e == kATSUFontsMatched) { + pos = changedOffset + changeCount; + } else if (e == kATSUFontsNotMatched) { + break; + } + } while (pos < len && e != noErr); + + return e == noErr || e == kATSUFontsMatched; +} + +QFontEngineMac::QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine) + : fontID(fontID), multiEngine(multiEngine), cmap(0), symbolCMap(false) +{ + fontDef = def; + ATSUCreateAndCopyStyle(baseStyle, &style); + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + cgFont = CGFontCreateWithPlatformFont(&atsFont); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + synthesisFlags = 0; + + // synthesizing using CG is not recommended + quint16 macStyle = 0; + { + uchar data[4]; + ByteCount len = 4; + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 44, 4, &data, &len) == noErr) + macStyle = qFromBigEndian<quint16>(data); + } + + Boolean atsuBold = false; + Boolean atsuItalic = false; + if (fontDef.weight >= QFont::Bold) { + if (!(macStyle & 1)) { + synthesisFlags |= SynthesizedBold; + atsuBold = true; + tags[attributeCount] = kATSUQDBoldfaceTag; + sizes[attributeCount] = sizeof(atsuBold); + values[attributeCount] = &atsuBold; + ++attributeCount; + } + } + if (fontDef.style != QFont::StyleNormal) { + if (!(macStyle & 2)) { + synthesisFlags |= SynthesizedItalic; + atsuItalic = true; + tags[attributeCount] = kATSUQDItalicTag; + sizes[attributeCount] = sizeof(atsuItalic); + values[attributeCount] = &atsuItalic; + ++attributeCount; + } + } + + tags[attributeCount] = kATSUFontTag; + values[attributeCount] = &fontID; + sizes[attributeCount] = sizeof(fontID); + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(style, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // CTFontCopyTable + quint16 tmpFsType; + if (ATSFontGetTable(atsFont, MAKE_TAG('O', 'S', '/', '2'), 8, 2, &tmpFsType, 0) == noErr) + fsType = qFromBigEndian<quint16>(tmpFsType); + else + fsType = 0; + + if (multiEngine) + transform = multiEngine->transform; + else + transform = CGAffineTransformIdentity; + + ATSUTextMeasurement metric; + + ATSUGetAttribute(style, kATSUAscentTag, sizeof(metric), &metric, 0); + m_ascent = FixRound(metric); + + ATSUGetAttribute(style, kATSUDescentTag, sizeof(metric), &metric, 0); + m_descent = FixRound(metric); + + ATSUGetAttribute(style, kATSULeadingTag, sizeof(metric), &metric, 0); + m_leading = FixRound(metric); + + ATSFontMetrics metrics; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_maxCharWidth = metrics.maxAdvanceWidth * fontDef.pointSize; + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_xHeight = QFixed::fromReal(metrics.xHeight * fontDef.pointSize); + + ATSFontGetHorizontalMetrics(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &metrics); + m_averageCharWidth = QFixed::fromReal(metrics.avgAdvanceWidth * fontDef.pointSize); + + // Use width of 'X' if ATSFontGetHorizontalMetrics returns 0 for avgAdvanceWidth. + if (m_averageCharWidth == QFixed(0)) { + QChar c('X'); + QGlyphLayoutArray<1> glyphs; + int nglyphs = 1; + stringToCMap(&c, 1, &glyphs, &nglyphs, 0); + glyph_metrics_t metrics = boundingBox(glyphs); + m_averageCharWidth = metrics.width; + } +} + +QFontEngineMac::~QFontEngineMac() +{ + ATSUDisposeStyle(style); +} + +static inline unsigned int getChar(const QChar *str, int &i, const int len) +{ + unsigned int uc = str[i].unicode(); + if (uc >= 0xd800 && uc < 0xdc00 && i < len-1) { + uint low = str[i+1].unicode(); + if (low >= 0xdc00 && low < 0xe000) { + uc = (uc - 0xd800)*0x400 + (low - 0xdc00) + 0x10000; + ++i; + } + } + return uc; +} + +// Not used directly for shaping, only used to calculate m_averageCharWidth +bool QFontEngineMac::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const +{ + if (!cmap) { + cmapTable = getSfntTable(MAKE_TAG('c', 'm', 'a', 'p')); + int size = 0; + cmap = getCMap(reinterpret_cast<const uchar *>(cmapTable.constData()), cmapTable.size(), &symbolCMap, &size); + if (!cmap) + return false; + } + if (symbolCMap) { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + if(!glyphs->glyphs[i] && uc < 0x100) + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc + 0xf000); + } + } else { + for (int i = 0; i < len; ++i) { + unsigned int uc = getChar(str, i, len); + glyphs->glyphs[i] = getTrueTypeGlyphIndex(cmap, uc); + } + } + + *nglyphs = len; + glyphs->numGlyphs = *nglyphs; + + if (!(flags & QTextEngine::GlyphIndicesOnly)) + recalcAdvances(glyphs, flags); + + return true; +} + +void QFontEngineMac::recalcAdvances(QGlyphLayout *glyphs, QTextEngine::ShaperFlags flags) const +{ + Q_UNUSED(flags) + + QVarLengthArray<GlyphID> atsuGlyphs(glyphs->numGlyphs); + for (int i = 0; i < glyphs->numGlyphs; ++i) + atsuGlyphs[i] = glyphs->glyphs[i]; + + QVarLengthArray<ATSGlyphScreenMetrics> metrics(glyphs->numGlyphs); + + ATSUGlyphGetScreenMetrics(style, glyphs->numGlyphs, atsuGlyphs.data(), sizeof(GlyphID), + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + metrics.data()); + + for (int i = 0; i < glyphs->numGlyphs; ++i) { + glyphs->advances_x[i] = QFixed::fromReal(metrics[i].deviceAdvance.x); + glyphs->advances_y[i] = QFixed::fromReal(metrics[i].deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + glyphs->advances_x[i] = glyphs->advances_x[i].round(); + glyphs->advances_y[i] = glyphs->advances_y[i].round(); + } + } +} + +glyph_metrics_t QFontEngineMac::boundingBox(const QGlyphLayout &glyphs) +{ + QFixed w; + bool round = fontDef.styleStrategy & QFont::ForceIntegerMetrics; + for (int i = 0; i < glyphs.numGlyphs; ++i) { + w += round ? glyphs.effectiveAdvance(i).round() + : glyphs.effectiveAdvance(i); + } + return glyph_metrics_t(0, -(ascent()), w - lastRightBearing(glyphs, round), ascent()+descent(), w, 0); +} + +glyph_metrics_t QFontEngineMac::boundingBox(glyph_t glyph) +{ + GlyphID atsuGlyph = glyph; + + ATSGlyphScreenMetrics metrics; + + ATSUGlyphGetScreenMetrics(style, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &metrics); + + // ### check again + + glyph_metrics_t gm; + gm.width = int(metrics.width); + gm.height = int(metrics.height); + gm.x = QFixed::fromReal(metrics.topLeft.x); + gm.y = -QFixed::fromReal(metrics.topLeft.y); + gm.xoff = QFixed::fromReal(metrics.deviceAdvance.x); + gm.yoff = QFixed::fromReal(metrics.deviceAdvance.y); + + if (fontDef.styleStrategy & QFont::ForceIntegerMetrics) { + gm.x = gm.x.floor(); + gm.y = gm.y.floor(); + gm.xoff = gm.xoff.round(); + gm.yoff = gm.yoff.round(); + } + + return gm; +} + +QFixed QFontEngineMac::ascent() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_ascent.round() + : m_ascent; +} + +QFixed QFontEngineMac::descent() const +{ + // subtract a pixel to even out the historical +1 in QFontMetrics::height(). + // Fix in Qt 5. + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_descent.round() - 1 + : m_descent; +} + +QFixed QFontEngineMac::leading() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_leading.round() + : m_leading; +} + +qreal QFontEngineMac::maxCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? qRound(m_maxCharWidth) + : m_maxCharWidth; +} + +QFixed QFontEngineMac::xHeight() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_xHeight.round() + : m_xHeight; +} + +QFixed QFontEngineMac::averageCharWidth() const +{ + return (fontDef.styleStrategy & QFont::ForceIntegerMetrics) + ? m_averageCharWidth.round() + : m_averageCharWidth; +} + +static void addGlyphsToPathHelper(ATSUStyle style, glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path) +{ + if (!numGlyphs) + return; + + OSStatus e; + + QMacFontPath fontpath(0, 0, path); + ATSCubicMoveToUPP moveTo = NewATSCubicMoveToUPP(QMacFontPath::moveTo); + ATSCubicLineToUPP lineTo = NewATSCubicLineToUPP(QMacFontPath::lineTo); + ATSCubicCurveToUPP cubicTo = NewATSCubicCurveToUPP(QMacFontPath::cubicTo); + ATSCubicClosePathUPP closePath = NewATSCubicClosePathUPP(QMacFontPath::closePath); + + // CTFontCreatePathForGlyph + for (int i = 0; i < numGlyphs; ++i) { + GlyphID glyph = glyphs[i]; + + fontpath.setPosition(positions[i].x.toReal(), positions[i].y.toReal()); + ATSUGlyphGetCubicPaths(style, glyph, moveTo, lineTo, + cubicTo, closePath, &fontpath, &e); + } + + DisposeATSCubicMoveToUPP(moveTo); + DisposeATSCubicLineToUPP(lineTo); + DisposeATSCubicCurveToUPP(cubicTo); + DisposeATSCubicClosePathUPP(closePath); +} + +void QFontEngineMac::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, QPainterPath *path, + QTextItem::RenderFlags) +{ + addGlyphsToPathHelper(style, glyphs, positions, numGlyphs, path); +} + + +/*! + Helper function for alphaMapForGlyph and alphaRGBMapForGlyph. The two are identical, except for + the subpixel antialiasing... +*/ +QImage QFontEngineMac::imageForGlyph(glyph_t glyph, int margin, bool colorful) +{ + const glyph_metrics_t br = boundingBox(glyph); + QImage im(qRound(br.width)+2, qRound(br.height)+4, QImage::Format_RGB32); + im.fill(0xff000000); + + CGColorSpaceRef colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + uint cgflags = kCGImageAlphaNoneSkipFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ctx = CGBitmapContextCreate(im.bits(), im.width(), im.height(), + 8, im.bytesPerLine(), colorspace, + cgflags); + CGContextSetFontSize(ctx, fontDef.pixelSize); + CGContextSetShouldAntialias(ctx, fontDef.pointSize > qt_antialiasing_threshold && !(fontDef.styleStrategy & QFont::NoAntialias)); + // turn off sub-pixel hinting - no support for that in OpenGL + CGContextSetShouldSmoothFonts(ctx, colorful); + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, 1, 0, 0); + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); + CGContextSetTextDrawingMode(ctx, kCGTextFill); + CGContextSetFont(ctx, cgFont); + + qreal pos_x = -br.x.toReal() + 1; + qreal pos_y = im.height() + br.y.toReal() - 2; + CGContextSetTextPosition(ctx, pos_x, pos_y); + + CGSize advance; + advance.width = 0; + advance.height = 0; + CGGlyph cgGlyph = glyph; + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, pos_x + 0.5 * lineThickness().toReal(), pos_y); + CGContextShowGlyphsWithAdvances(ctx, &cgGlyph, &advance, 1); + } + + CGContextRelease(ctx); + + return im; +} + +QImage QFontEngineMac::alphaMapForGlyph(glyph_t glyph) +{ + QImage im = imageForGlyph(glyph, 2, false); + + QImage indexed(im.width(), im.height(), QImage::Format_Indexed8); + QVector<QRgb> colors(256); + for (int i=0; i<256; ++i) + colors[i] = qRgba(0, 0, 0, i); + indexed.setColorTable(colors); + + for (int y=0; y<im.height(); ++y) { + uint *src = (uint*) im.scanLine(y); + uchar *dst = indexed.scanLine(y); + for (int x=0; x<im.width(); ++x) { + *dst = qGray(*src); + ++dst; + ++src; + } + } + + return indexed; +} + +QImage QFontEngineMac::alphaRGBMapForGlyph(glyph_t glyph, QFixed, int margin, const QTransform &t) +{ + QImage im = imageForGlyph(glyph, margin, true); + + if (t.type() >= QTransform::TxScale) { + im = im.transformed(t); + } + + qGamma_correct_back_to_linear_cs(&im); + + return im; +} + + +bool QFontEngineMac::canRender(const QChar *string, int len) +{ + Q_ASSERT(false); + Q_UNUSED(string); + Q_UNUSED(len); + return false; +} + +void QFontEngineMac::draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight) +{ + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix; + matrix.translate(x, y); + getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + CGContextSetFontSize(ctx, fontDef.pixelSize); + + CGAffineTransform oldTextMatrix = CGContextGetTextMatrix(ctx); + + CGAffineTransform cgMatrix = CGAffineTransformMake(1, 0, 0, -1, 0, -paintDeviceHeight); + + CGAffineTransformConcat(cgMatrix, oldTextMatrix); + + if (synthesisFlags & QFontEngine::SynthesizedItalic) + cgMatrix = CGAffineTransformConcat(cgMatrix, CGAffineTransformMake(1, 0, -tanf(14 * acosf(0) / 90), 1, 0, 0)); + + cgMatrix = CGAffineTransformConcat(cgMatrix, transform); + + CGContextSetTextMatrix(ctx, cgMatrix); + + CGContextSetTextDrawingMode(ctx, kCGTextFill); + + + QVarLengthArray<CGSize> advances(glyphs.size()); + QVarLengthArray<CGGlyph> cgGlyphs(glyphs.size()); + + for (int i = 0; i < glyphs.size() - 1; ++i) { + advances[i].width = (positions[i + 1].x - positions[i].x).toReal(); + advances[i].height = (positions[i + 1].y - positions[i].y).toReal(); + cgGlyphs[i] = glyphs[i]; + } + advances[glyphs.size() - 1].width = 0; + advances[glyphs.size() - 1].height = 0; + cgGlyphs[glyphs.size() - 1] = glyphs[glyphs.size() - 1]; + + CGContextSetFont(ctx, cgFont); + + CGContextSetTextPosition(ctx, positions[0].x.toReal(), positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + + if (synthesisFlags & QFontEngine::SynthesizedBold) { + CGContextSetTextPosition(ctx, positions[0].x.toReal() + 0.5 * lineThickness().toReal(), + positions[0].y.toReal()); + + CGContextShowGlyphsWithAdvances(ctx, cgGlyphs.data(), advances.data(), glyphs.size()); + } + + CGContextSetTextMatrix(ctx, oldTextMatrix); +} + +QFontEngine::FaceId QFontEngineMac::faceId() const +{ + FaceId ret; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + // CTFontGetPlatformFont + FSRef ref; + if (ATSFontGetFileReference(FMGetATSFontRefFromFont(fontID), &ref) != noErr) + return ret; + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +}else +#endif +{ + FSSpec spec; + if (ATSFontGetFileSpecification(FMGetATSFontRefFromFont(fontID), &spec) != noErr) + return ret; + + FSRef ref; + FSpMakeFSRef(&spec, &ref); + ret.filename = QByteArray(128, 0); + ret.index = fontID; + FSRefMakePath(&ref, (UInt8 *)ret.filename.data(), ret.filename.size()); +} + return ret; +} + +QByteArray QFontEngineMac::getSfntTable(uint tag) const +{ + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + + ByteCount length; + OSStatus status = ATSFontGetTable(atsFont, tag, 0, 0, 0, &length); + if (status != noErr) + return QByteArray(); + QByteArray table(length, 0); + // CTFontCopyTable + status = ATSFontGetTable(atsFont, tag, 0, table.length(), table.data(), &length); + if (status != noErr) + return QByteArray(); + return table; +} + +QFontEngine::Properties QFontEngineMac::properties() const +{ + QFontEngine::Properties props; + ATSFontRef atsFont = FMGetATSFontRefFromFont(fontID); + quint16 tmp; + // CTFontGetUnitsPerEm + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 18, 2, &tmp, 0) == noErr) + props.emSquare = qFromBigEndian<quint16>(tmp); + struct { + qint16 xMin; + qint16 yMin; + qint16 xMax; + qint16 yMax; + } bbox; + bbox.xMin = bbox.xMax = bbox.yMin = bbox.yMax = 0; + // CTFontGetBoundingBox + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'e', 'a', 'd'), 36, 8, &bbox, 0) == noErr) { + bbox.xMin = qFromBigEndian<quint16>(bbox.xMin); + bbox.yMin = qFromBigEndian<quint16>(bbox.yMin); + bbox.xMax = qFromBigEndian<quint16>(bbox.xMax); + bbox.yMax = qFromBigEndian<quint16>(bbox.yMax); + } + struct { + qint16 ascender; + qint16 descender; + qint16 linegap; + } metrics; + metrics.ascender = metrics.descender = metrics.linegap = 0; + // CTFontGetAscent, etc. + if (ATSFontGetTable(atsFont, MAKE_TAG('h', 'h', 'e', 'a'), 4, 6, &metrics, 0) == noErr) { + metrics.ascender = qFromBigEndian<quint16>(metrics.ascender); + metrics.descender = qFromBigEndian<quint16>(metrics.descender); + metrics.linegap = qFromBigEndian<quint16>(metrics.linegap); + } + props.ascent = metrics.ascender; + props.descent = -metrics.descender; + props.leading = metrics.linegap; + props.boundingBox = QRectF(bbox.xMin, -bbox.yMax, + bbox.xMax - bbox.xMin, + bbox.yMax - bbox.yMin); + props.italicAngle = 0; + props.capHeight = props.ascent; + + qint16 lw = 0; + // fonts lie + if (ATSFontGetTable(atsFont, MAKE_TAG('p', 'o', 's', 't'), 10, 2, &lw, 0) == noErr) + lw = qFromBigEndian<quint16>(lw); + props.lineWidth = lw; + + // CTFontCopyPostScriptName + QCFString psName; + if (ATSFontGetPostScriptName(FMGetATSFontRefFromFont(fontID), kATSOptionFlagsDefault, &psName) == noErr) + props.postscriptName = QString(psName).toUtf8(); + props.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(props.postscriptName); + return props; +} + +void QFontEngineMac::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) +{ + ATSUStyle unscaledStyle; + ATSUCreateAndCopyStyle(style, &unscaledStyle); + + int emSquare = properties().emSquare.toInt(); + + const int maxAttributeCount = 4; + ATSUAttributeTag tags[maxAttributeCount + 1]; + ByteCount sizes[maxAttributeCount + 1]; + ATSUAttributeValuePtr values[maxAttributeCount + 1]; + int attributeCount = 0; + + Fixed size = FixRatio(emSquare, 1); + tags[attributeCount] = kATSUSizeTag; + sizes[attributeCount] = sizeof(size); + values[attributeCount] = &size; + ++attributeCount; + + Q_ASSERT(attributeCount < maxAttributeCount + 1); + OSStatus err = ATSUSetAttributes(unscaledStyle, attributeCount, tags, sizes, values); + Q_ASSERT(err == noErr); + Q_UNUSED(err); + + // various CTFont metrics functions: CTFontGetBoundingRectsForGlyphs, CTFontGetAdvancesForGlyphs + GlyphID atsuGlyph = glyph; + ATSGlyphScreenMetrics atsuMetrics; + ATSUGlyphGetScreenMetrics(unscaledStyle, 1, &atsuGlyph, 0, + /* iForcingAntiAlias =*/ false, + /* iAntiAliasSwitch =*/true, + &atsuMetrics); + + metrics->width = int(atsuMetrics.width); + metrics->height = int(atsuMetrics.height); + metrics->x = QFixed::fromReal(atsuMetrics.topLeft.x); + metrics->y = -QFixed::fromReal(atsuMetrics.topLeft.y); + metrics->xoff = QFixed::fromReal(atsuMetrics.deviceAdvance.x); + metrics->yoff = QFixed::fromReal(atsuMetrics.deviceAdvance.y); + + QFixedPoint p; + addGlyphsToPathHelper(unscaledStyle, &glyph, &p, 1, path); + + ATSUDisposeStyle(unscaledStyle); +} +#endif // !QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qfontengine_mac_p.h b/src/gui/platforms/mac/qfontengine_mac_p.h new file mode 100644 index 0000000000..292ea98d9a --- /dev/null +++ b/src/gui/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 <private/qfontengine_p.h> + +#ifndef QT_MAC_USE_COCOA +class QFontEngineMacMulti; +class QFontEngineMac : public QFontEngine +{ + friend class QFontEngineMacMulti; +public: + QFontEngineMac(ATSUStyle baseStyle, ATSUFontID fontID, const QFontDef &def, QFontEngineMacMulti *multiEngine = 0); + virtual ~QFontEngineMac(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *numGlyphs, QTextEngine::ShaperFlags flags) const; + virtual void recalcAdvances(QGlyphLayout *, QTextEngine::ShaperFlags) const; + + virtual glyph_metrics_t boundingBox(const QGlyphLayout &glyphs); + virtual glyph_metrics_t boundingBox(glyph_t glyph); + + virtual QFixed ascent() const; + virtual QFixed descent() const; + virtual QFixed leading() const; + virtual QFixed xHeight() const; + virtual qreal maxCharWidth() const; + virtual QFixed averageCharWidth() const; + + virtual void addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs, + QPainterPath *path, QTextItem::RenderFlags); + + virtual const char *name() const { return "QFontEngineMac"; } + + virtual bool canRender(const QChar *string, int len); + + virtual int synthesized() const { return synthesisFlags; } + + virtual Type type() const { return QFontEngine::Mac; } + + void draw(CGContextRef ctx, qreal x, qreal y, const QTextItemInt &ti, int paintDeviceHeight); + + virtual FaceId faceId() const; + virtual QByteArray getSfntTable(uint tag) const; + virtual Properties properties() const; + virtual void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics); + virtual QImage alphaMapForGlyph(glyph_t); + virtual QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, int margin, const QTransform &t); + +private: + QImage imageForGlyph(glyph_t glyph, int margin, bool colorful); + + ATSUFontID fontID; + QCFType<CGFontRef> cgFont; + ATSUStyle style; + int synthesisFlags; + mutable QGlyphLayout kashidaGlyph; + QFontEngineMacMulti *multiEngine; + mutable const unsigned char *cmap; + mutable bool symbolCMap; + mutable QByteArray cmapTable; + CGAffineTransform transform; + QFixed m_ascent; + QFixed m_descent; + QFixed m_leading; + qreal m_maxCharWidth; + QFixed m_xHeight; + QFixed m_averageCharWidth; +}; + +class QFontEngineMacMulti : public QFontEngineMulti +{ + friend class QFontEngineMac; +public: + // internal + struct ShaperItem + { + inline ShaperItem() : string(0), from(0), length(0), + log_clusters(0), charAttributes(0) {} + + const QChar *string; + int from; + int length; + QGlyphLayout glyphs; + unsigned short *log_clusters; + const HB_CharAttributes *charAttributes; + QTextEngine::ShaperFlags flags; + }; + + QFontEngineMacMulti(const ATSFontFamilyRef &atsFamily, const ATSFontRef &atsFontRef, const QFontDef &fontDef, bool kerning); + virtual ~QFontEngineMacMulti(); + + virtual bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags) const; + bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, + unsigned short *logClusters, const HB_CharAttributes *charAttributes, 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<const QFontEngineMac *>(engines.at(i)); } + + bool stringToCMapInternal(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QTextEngine::ShaperFlags flags, ShaperItem *item) const; + + int fontIndexForFontID(ATSUFontID id) const; + + ATSUFontID fontID; + uint kerning : 1; + + mutable ATSUTextLayout textLayout; + mutable ATSUStyle style; + CGAffineTransform transform; +}; +#endif //!QT_MAC_USE_COCOA + +#endif // QFONTENGINE_MAC_P_H diff --git a/src/gui/platforms/mac/qkeymapper_mac.cpp b/src/gui/platforms/mac/qkeymapper_mac.cpp new file mode 100644 index 0000000000..d3bbf89711 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include <qdebug.h> +#include <qevent.h> +#include <private/qevent_p.h> +#include <qtextcodec.h> +#include <qapplication.h> +#include <qinputcontext.h> +#include <private/qkeymapper_p.h> +#include <private/qapplication_p.h> +#include <private/qmacinputcontext_p.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +/***************************************************************************** + QKeyMapper debug facilities + *****************************************************************************/ +//#define DEBUG_KEY_BINDINGS +//#define DEBUG_KEY_BINDINGS_MODIFIERS +//#define DEBUG_KEY_MAPS + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ +bool qt_mac_eat_unicode_key = false; +extern bool qt_sendSpontaneousEvent(QObject *obj, QEvent *event); //qapplication_mac.cpp + +Q_GUI_EXPORT void qt_mac_secure_keyboard(bool b) +{ + static bool secure = false; + if (b != secure){ + b ? EnableSecureEventInput() : DisableSecureEventInput(); + secure = b; + } +} + +/* + \internal + A Mac KeyboardLayoutItem has 8 possible states: + 1. Unmodified + 2. Shift + 3. Control + 4. Control + Shift + 5. Alt + 6. Alt + Shift + 7. Alt + Control + 8. Alt + Control + Shift + 9. Meta + 10. Meta + Shift + 11. Meta + Control + 12. Meta + Control + Shift + 13. Meta + Alt + 14. Meta + Alt + Shift + 15. Meta + Alt + Control + 16. Meta + Alt + Control + Shift +*/ +struct KeyboardLayoutItem { + bool dirty; + quint32 qtKey[16]; // Can by any Qt::Key_<foo>, or unicode character +}; + +// Possible modifier states. +// NOTE: The order of these states match the order in QKeyMapperPrivate::updatePossibleKeyCodes()! +static const Qt::KeyboardModifiers ModsTbl[] = { + Qt::NoModifier, // 0 + Qt::ShiftModifier, // 1 + Qt::ControlModifier, // 2 + Qt::ControlModifier | Qt::ShiftModifier, // 3 + Qt::AltModifier, // 4 + Qt::AltModifier | Qt::ShiftModifier, // 5 + Qt::AltModifier | Qt::ControlModifier, // 6 + Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 7 + Qt::MetaModifier, // 8 + Qt::MetaModifier | Qt::ShiftModifier, // 9 + Qt::MetaModifier | Qt::ControlModifier, // 10 + Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier,// 11 + Qt::MetaModifier | Qt::AltModifier, // 12 + Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier, // 13 + Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier, // 14 + Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 15 +}; + +/* key maps */ +struct qt_mac_enum_mapper +{ + int mac_code; + int qt_code; +#if defined(DEBUG_KEY_BINDINGS) +# define QT_MAC_MAP_ENUM(x) x, #x + const char *desc; +#else +# define QT_MAC_MAP_ENUM(x) x +#endif +}; + +//modifiers +static qt_mac_enum_mapper qt_mac_modifier_symbols[] = { + { shiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, + { rightShiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, + { controlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, + { rightControlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, + { cmdKey, QT_MAC_MAP_ENUM(Qt::ControlModifier) }, + { optionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, + { rightOptionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, + { kEventKeyModifierNumLockMask, QT_MAC_MAP_ENUM(Qt::KeypadModifier) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; +Qt::KeyboardModifiers qt_mac_get_modifiers(int keys) +{ +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", keys, keys); +#endif + Qt::KeyboardModifiers ret = Qt::NoModifier; + for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { + if (keys & qt_mac_modifier_symbols[i].mac_code) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); +#endif + ret |= Qt::KeyboardModifier(qt_mac_modifier_symbols[i].qt_code); + } + } + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + Qt::KeyboardModifiers oldModifiers = ret; + ret &= ~(Qt::MetaModifier | Qt::ControlModifier); + if (oldModifiers & Qt::ControlModifier) + ret |= Qt::MetaModifier; + if (oldModifiers & Qt::MetaModifier) + ret |= Qt::ControlModifier; + } + return ret; +} +static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys) +{ +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", (int)keys, (int)keys); +#endif + int ret = 0; + for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { + if (keys & qt_mac_modifier_symbols[i].qt_code) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); +#endif + ret |= qt_mac_modifier_symbols[i].mac_code; + } + } + + if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + int oldModifiers = ret; + ret &= ~(controlKeyBit | cmdKeyBit); + if (oldModifiers & controlKeyBit) + ret |= cmdKeyBit; + if (oldModifiers & cmdKeyBit) + ret |= controlKeyBit; + } + return ret; +} +void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object) +{ + static quint32 cachedModifiers = 0; + quint32 lastModifiers = cachedModifiers, + changedModifiers = lastModifiers ^ modifiers; + cachedModifiers = modifiers; + + //check the bits + static qt_mac_enum_mapper modifier_key_symbols[] = { + { shiftKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Shift) }, + { rightShiftKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Shift) }, //??? + { controlKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Meta) }, + { rightControlKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Meta) }, //??? + { cmdKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Control) }, + { optionKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Alt) }, + { rightOptionKeyBit, QT_MAC_MAP_ENUM(Qt::Key_Alt) }, //??? + { alphaLockBit, QT_MAC_MAP_ENUM(Qt::Key_CapsLock) }, + { kEventKeyModifierNumLockBit, QT_MAC_MAP_ENUM(Qt::Key_NumLock) }, + { 0, QT_MAC_MAP_ENUM(0) } }; + for (int i = 0; i <= 32; i++) { //just check each bit + if (!(changedModifiers & (1 << i))) + continue; + QEvent::Type etype = QEvent::KeyPress; + if (lastModifiers & (1 << i)) + etype = QEvent::KeyRelease; + int key = 0; + for (uint x = 0; modifier_key_symbols[x].mac_code; x++) { + if (modifier_key_symbols[x].mac_code == i) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("got modifier changed: %s", modifier_key_symbols[x].desc); +#endif + key = modifier_key_symbols[x].qt_code; + break; + } + } + if (!key) { +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("could not get modifier changed: %d", i); +#endif + continue; + } +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("KeyEvent (modif): Sending %s to %s::%s: %d - 0x%08x", + etype == QEvent::KeyRelease ? "KeyRelease" : "KeyPress", + object ? object->metaObject()->className() : "none", + object ? object->objectName().toLatin1().constData() : "", + key, (int)modifiers); +#endif + QKeyEvent ke(etype, key, qt_mac_get_modifiers(modifiers ^ (1 << i)), QLatin1String("")); + qt_sendSpontaneousEvent(object, &ke); + } +} + +//keyboard keys (non-modifiers) +static qt_mac_enum_mapper qt_mac_keyboard_symbols[] = { + { kHomeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Home) }, + { kEnterCharCode, QT_MAC_MAP_ENUM(Qt::Key_Enter) }, + { kEndCharCode, QT_MAC_MAP_ENUM(Qt::Key_End) }, + { kBackspaceCharCode, QT_MAC_MAP_ENUM(Qt::Key_Backspace) }, + { kTabCharCode, QT_MAC_MAP_ENUM(Qt::Key_Tab) }, + { kPageUpCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, + { kPageDownCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, + { kReturnCharCode, QT_MAC_MAP_ENUM(Qt::Key_Return) }, + { kEscapeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Escape) }, + { kLeftArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Left) }, + { kRightArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Right) }, + { kUpArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Up) }, + { kDownArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Down) }, + { kHelpCharCode, QT_MAC_MAP_ENUM(Qt::Key_Help) }, + { kDeleteCharCode, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, +//ascii maps, for debug + { ':', QT_MAC_MAP_ENUM(Qt::Key_Colon) }, + { ';', QT_MAC_MAP_ENUM(Qt::Key_Semicolon) }, + { '<', QT_MAC_MAP_ENUM(Qt::Key_Less) }, + { '=', QT_MAC_MAP_ENUM(Qt::Key_Equal) }, + { '>', QT_MAC_MAP_ENUM(Qt::Key_Greater) }, + { '?', QT_MAC_MAP_ENUM(Qt::Key_Question) }, + { '@', QT_MAC_MAP_ENUM(Qt::Key_At) }, + { ' ', QT_MAC_MAP_ENUM(Qt::Key_Space) }, + { '!', QT_MAC_MAP_ENUM(Qt::Key_Exclam) }, + { '"', QT_MAC_MAP_ENUM(Qt::Key_QuoteDbl) }, + { '#', QT_MAC_MAP_ENUM(Qt::Key_NumberSign) }, + { '$', QT_MAC_MAP_ENUM(Qt::Key_Dollar) }, + { '%', QT_MAC_MAP_ENUM(Qt::Key_Percent) }, + { '&', QT_MAC_MAP_ENUM(Qt::Key_Ampersand) }, + { '\'', QT_MAC_MAP_ENUM(Qt::Key_Apostrophe) }, + { '(', QT_MAC_MAP_ENUM(Qt::Key_ParenLeft) }, + { ')', QT_MAC_MAP_ENUM(Qt::Key_ParenRight) }, + { '*', QT_MAC_MAP_ENUM(Qt::Key_Asterisk) }, + { '+', QT_MAC_MAP_ENUM(Qt::Key_Plus) }, + { ',', QT_MAC_MAP_ENUM(Qt::Key_Comma) }, + { '-', QT_MAC_MAP_ENUM(Qt::Key_Minus) }, + { '.', QT_MAC_MAP_ENUM(Qt::Key_Period) }, + { '/', QT_MAC_MAP_ENUM(Qt::Key_Slash) }, + { '[', QT_MAC_MAP_ENUM(Qt::Key_BracketLeft) }, + { ']', QT_MAC_MAP_ENUM(Qt::Key_BracketRight) }, + { '\\', QT_MAC_MAP_ENUM(Qt::Key_Backslash) }, + { '_', QT_MAC_MAP_ENUM(Qt::Key_Underscore) }, + { '`', QT_MAC_MAP_ENUM(Qt::Key_QuoteLeft) }, + { '{', QT_MAC_MAP_ENUM(Qt::Key_BraceLeft) }, + { '}', QT_MAC_MAP_ENUM(Qt::Key_BraceRight) }, + { '|', QT_MAC_MAP_ENUM(Qt::Key_Bar) }, + { '~', QT_MAC_MAP_ENUM(Qt::Key_AsciiTilde) }, + { '^', QT_MAC_MAP_ENUM(Qt::Key_AsciiCircum) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static qt_mac_enum_mapper qt_mac_keyvkey_symbols[] = { //real scan codes + { 122, QT_MAC_MAP_ENUM(Qt::Key_F1) }, + { 120, QT_MAC_MAP_ENUM(Qt::Key_F2) }, + { 99, QT_MAC_MAP_ENUM(Qt::Key_F3) }, + { 118, QT_MAC_MAP_ENUM(Qt::Key_F4) }, + { 96, QT_MAC_MAP_ENUM(Qt::Key_F5) }, + { 97, QT_MAC_MAP_ENUM(Qt::Key_F6) }, + { 98, QT_MAC_MAP_ENUM(Qt::Key_F7) }, + { 100, QT_MAC_MAP_ENUM(Qt::Key_F8) }, + { 101, QT_MAC_MAP_ENUM(Qt::Key_F9) }, + { 109, QT_MAC_MAP_ENUM(Qt::Key_F10) }, + { 103, QT_MAC_MAP_ENUM(Qt::Key_F11) }, + { 111, QT_MAC_MAP_ENUM(Qt::Key_F12) }, + { 105, QT_MAC_MAP_ENUM(Qt::Key_F13) }, + { 107, QT_MAC_MAP_ENUM(Qt::Key_F14) }, + { 113, QT_MAC_MAP_ENUM(Qt::Key_F15) }, + { 106, QT_MAC_MAP_ENUM(Qt::Key_F16) }, + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static qt_mac_enum_mapper qt_mac_private_unicode[] = { + { 0xF700, QT_MAC_MAP_ENUM(Qt::Key_Up) }, //NSUpArrowFunctionKey + { 0xF701, QT_MAC_MAP_ENUM(Qt::Key_Down) }, //NSDownArrowFunctionKey + { 0xF702, QT_MAC_MAP_ENUM(Qt::Key_Left) }, //NSLeftArrowFunctionKey + { 0xF703, QT_MAC_MAP_ENUM(Qt::Key_Right) }, //NSRightArrowFunctionKey + { 0xF727, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertFunctionKey + { 0xF728, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteFunctionKey + { 0xF729, QT_MAC_MAP_ENUM(Qt::Key_Home) }, //NSHomeFunctionKey + { 0xF72B, QT_MAC_MAP_ENUM(Qt::Key_End) }, //NSEndFunctionKey + { 0xF72C, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, //NSPageUpFunctionKey + { 0xF72D, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, //NSPageDownFunctionKey + { 0xF72F, QT_MAC_MAP_ENUM(Qt::Key_ScrollLock) }, //NSScrollLockFunctionKey + { 0xF730, QT_MAC_MAP_ENUM(Qt::Key_Pause) }, //NSPauseFunctionKey + { 0xF731, QT_MAC_MAP_ENUM(Qt::Key_SysReq) }, //NSSysReqFunctionKey + { 0xF735, QT_MAC_MAP_ENUM(Qt::Key_Menu) }, //NSMenuFunctionKey + { 0xF738, QT_MAC_MAP_ENUM(Qt::Key_Print) }, //NSPrintFunctionKey + { 0xF73A, QT_MAC_MAP_ENUM(Qt::Key_Clear) }, //NSClearDisplayFunctionKey + { 0xF73D, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertCharFunctionKey + { 0xF73E, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteCharFunctionKey + { 0xF741, QT_MAC_MAP_ENUM(Qt::Key_Select) }, //NSSelectFunctionKey + { 0xF742, QT_MAC_MAP_ENUM(Qt::Key_Execute) }, //NSExecuteFunctionKey + { 0xF746, QT_MAC_MAP_ENUM(Qt::Key_Help) }, //NSHelpFunctionKey + { 0xF747, QT_MAC_MAP_ENUM(Qt::Key_Mode_switch) }, //NSModeSwitchFunctionKey + { 0, QT_MAC_MAP_ENUM(0) } +}; + +static int qt_mac_get_key(int modif, const QChar &key, int virtualKey) +{ +#ifdef DEBUG_KEY_BINDINGS + qDebug("**Mapping key: %d (0x%04x) - %d (0x%04x)", key.unicode(), key.unicode(), virtualKey, virtualKey); +#endif + + if (key == kClearCharCode && virtualKey == 0x47) + return Qt::Key_Clear; + + if (key.isDigit()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, key.digitValue()); +#endif + return key.digitValue() + Qt::Key_0; + } + + if (key.isLetter()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, (key.toUpper().unicode() - 'A')); +#endif + return (key.toUpper().unicode() - 'A') + Qt::Key_A; + } + if (key.isSymbol()) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %d", __LINE__, (key.unicode())); +#endif + return key.unicode(); + } + + for (int i = 0; qt_mac_keyboard_symbols[i].qt_code; i++) { + if (qt_mac_keyboard_symbols[i].mac_code == key) { + /* To work like Qt for X11 we issue Backtab when Shift + Tab are pressed */ + if (qt_mac_keyboard_symbols[i].qt_code == Qt::Key_Tab && (modif & Qt::ShiftModifier)) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: Qt::Key_Backtab", __LINE__); +#endif + return Qt::Key_Backtab; + } + +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %s", __LINE__, qt_mac_keyboard_symbols[i].desc); +#endif + return qt_mac_keyboard_symbols[i].qt_code; + } + } + + //last ditch try to match the scan code + for (int i = 0; qt_mac_keyvkey_symbols[i].qt_code; i++) { + if (qt_mac_keyvkey_symbols[i].mac_code == virtualKey) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("%d: got key: %s", __LINE__, qt_mac_keyvkey_symbols[i].desc); +#endif + return qt_mac_keyvkey_symbols[i].qt_code; + } + } + + // check if they belong to key codes in private unicode range + if (key >= 0xf700 && key <= 0xf747) { + if (key >= 0xf704 && key <= 0xf726) { + return Qt::Key_F1 + (key.unicode() - 0xf704) ; + } + for (int i = 0; qt_mac_private_unicode[i].qt_code; i++) { + if (qt_mac_private_unicode[i].mac_code == key) { + return qt_mac_private_unicode[i].qt_code; + } + } + + } + + //oh well +#ifdef DEBUG_KEY_BINDINGS + qDebug("Unknown case.. %s:%d %d[%d] %d", __FILE__, __LINE__, key.unicode(), key.toLatin1(), virtualKey); +#endif + return Qt::Key_unknown; +} + +static Boolean qt_KeyEventComparatorProc(EventRef inEvent, void *data) +{ + UInt32 ekind = GetEventKind(inEvent), + eclass = GetEventClass(inEvent); + return (eclass == kEventClassKeyboard && (void *)ekind == data); +} + +static bool translateKeyEventInternal(EventHandlerCallRef er, EventRef keyEvent, int *qtKey, + QChar *outChar, Qt::KeyboardModifiers *outModifiers, bool *outHandled) +{ +#if !defined(QT_MAC_USE_COCOA) || defined(Q_OS_MAC64) + Q_UNUSED(er); + Q_UNUSED(outHandled); +#endif + const UInt32 ekind = GetEventKind(keyEvent); + { + UInt32 mac_modifiers = 0; + GetEventParameter(keyEvent, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(mac_modifiers), 0, &mac_modifiers); +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("************ Mapping modifiers and key ***********"); +#endif + *outModifiers = qt_mac_get_modifiers(mac_modifiers); +#ifdef DEBUG_KEY_BINDINGS_MODIFIERS + qDebug("------------ Mapping modifiers and key -----------"); +#endif + } + + //get keycode + UInt32 keyCode = 0; + GetEventParameter(keyEvent, kEventParamKeyCode, typeUInt32, 0, sizeof(keyCode), 0, &keyCode); + + //get mac mapping + static UInt32 tmp_unused_state = 0L; + const UCKeyboardLayout *uchrData = 0; +#if defined(Q_OS_MAC32) + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + OSStatus err; + if (keyLayoutRef != 0) { + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLuchrData, + (reinterpret_cast<const void **>(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType<TISInputSourceRef> inputSource = TISCopyCurrentKeyboardInputSource(); + Q_ASSERT(inputSource != 0); + CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(inputSource, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : 0; +#endif + *qtKey = Qt::Key_unknown; + if (uchrData) { + // The easy stuff; use the unicode stuff! + UniChar string[4]; + UniCharCount actualLength; + UInt32 currentModifiers = GetCurrentEventKeyModifiers(); + UInt32 currentModifiersWOAltOrControl = currentModifiers & ~(controlKey | optionKey); + int keyAction; + switch (ekind) { + default: + case kEventRawKeyDown: + keyAction = kUCKeyActionDown; + break; + case kEventRawKeyUp: + keyAction = kUCKeyActionUp; + break; + case kEventRawKeyRepeat: + keyAction = kUCKeyActionAutoKey; + break; + } + OSStatus err = UCKeyTranslate(uchrData, keyCode, keyAction, + ((currentModifiersWOAltOrControl >> 8) & 0xff), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysMask, &tmp_unused_state, 4, &actualLength, + string); + if (err == noErr) { + *outChar = QChar(string[0]); + *qtKey = qt_mac_get_key(*outModifiers, *outChar, keyCode); + if (currentModifiersWOAltOrControl != currentModifiers) { + // Now get the real char. + err = UCKeyTranslate(uchrData, keyCode, keyAction, + ((currentModifiers >> 8) & 0xff), LMGetKbdType(), + kUCKeyTranslateNoDeadKeysMask, &tmp_unused_state, 4, &actualLength, + string); + if (err == noErr) + *outChar = QChar(string[0]); + } + } else { + qWarning("Qt::internal::UCKeyTranslate is returnining %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#ifdef Q_OS_MAC32 + else { + // The road less travelled; use KeyTranslate + const void *keyboard_layout; + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLKCHRData, + reinterpret_cast<const void **>(&keyboard_layout)); + + int translatedChar = KeyTranslate(keyboard_layout, (GetCurrentEventKeyModifiers() & + (kEventKeyModifierNumLockMask|shiftKey|cmdKey| + rightShiftKey|alphaLock)) | keyCode, + &tmp_unused_state); + if (!translatedChar) { +#ifdef QT_MAC_USE_COCOA + if (outHandled) { + qt_mac_eat_unicode_key = false; + if (er) + CallNextEventHandler(er, keyEvent); + *outHandled = qt_mac_eat_unicode_key; + } +#endif + return false; + } + + //map it into qt keys + *qtKey = qt_mac_get_key(*outModifiers, QChar(translatedChar), keyCode); + if (*outModifiers & (Qt::AltModifier | Qt::ControlModifier)) { + if (translatedChar & (1 << 7)) //high ascii + translatedChar = 0; + } else { //now get the real ascii value + UInt32 tmp_mod = 0L; + static UInt32 tmp_state = 0L; + if (*outModifiers & Qt::ShiftModifier) + tmp_mod |= shiftKey; + if (*outModifiers & Qt::MetaModifier) + tmp_mod |= controlKey; + if (*outModifiers & Qt::ControlModifier) + tmp_mod |= cmdKey; + if (GetCurrentEventKeyModifiers() & alphaLock) //no Qt mapper + tmp_mod |= alphaLock; + if (*outModifiers & Qt::AltModifier) + tmp_mod |= optionKey; + if (*outModifiers & Qt::KeypadModifier) + tmp_mod |= kEventKeyModifierNumLockMask; + translatedChar = KeyTranslate(keyboard_layout, tmp_mod | keyCode, &tmp_state); + } + { + ByteCount unilen = 0; + if (GetEventParameter(keyEvent, kEventParamKeyUnicodes, typeUnicodeText, 0, 0, &unilen, 0) + == noErr && unilen == 2) { + GetEventParameter(keyEvent, kEventParamKeyUnicodes, typeUnicodeText, 0, unilen, 0, outChar); + } else if (translatedChar) { + static QTextCodec *c = 0; + if (!c) + c = QTextCodec::codecForName("Apple Roman"); + char tmpChar = (char)translatedChar; // **sigh** + *outChar = c->toUnicode(&tmpChar, 1).at(0); + } else { + *qtKey = qt_mac_get_key(*outModifiers, QChar(translatedChar), keyCode); + } + } + } +#endif + if (*qtKey == Qt::Key_unknown) + *qtKey = qt_mac_get_key(*outModifiers, *outChar, keyCode); + return true; +} + +QKeyMapperPrivate::QKeyMapperPrivate() +{ + memset(keyLayout, 0, sizeof(keyLayout)); + keyboard_layout_format.unicode = 0; +#ifdef Q_OS_MAC32 + keyboard_mode = NullMode; +#else + currentInputSource = 0; +#endif +} + +QKeyMapperPrivate::~QKeyMapperPrivate() +{ + deleteLayouts(); +} + +bool +QKeyMapperPrivate::updateKeyboard() +{ + const UCKeyboardLayout *uchrData = 0; +#ifdef Q_OS_MAC32 + KeyboardLayoutRef keyLayoutRef = 0; + KLGetCurrentKeyboardLayout(&keyLayoutRef); + + if (keyboard_mode != NullMode && currentKeyboardLayout == keyLayoutRef) + return false; + + OSStatus err; + if (keyLayoutRef != 0) { + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLuchrData, + const_cast<const void **>(reinterpret_cast<const void **>(&uchrData))); + if (err != noErr) { + qWarning("Qt::internal::unable to get unicode keyboardlayout %ld %s:%d", + long(err), __FILE__, __LINE__); + } + } +#else + QCFType<TISInputSourceRef> source = TISCopyCurrentKeyboardInputSource(); + if (keyboard_mode != NullMode && source == currentInputSource) { + return false; + } + Q_ASSERT(source != 0); + CFDataRef data = static_cast<CFDataRef>(TISGetInputSourceProperty(source, + kTISPropertyUnicodeKeyLayoutData)); + uchrData = data ? reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data)) : 0; +#endif + + keyboard_kind = LMGetKbdType(); + if (uchrData) { + keyboard_layout_format.unicode = uchrData; + keyboard_mode = UnicodeMode; + } +#ifdef Q_OS_MAC32 + else { + void *happy; + err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLKCHRData, + const_cast<const void **>(reinterpret_cast<void **>(&happy))); + if (err != noErr) { + qFatal("Qt::internal::unable to get non-unicode layout, cannot procede %ld %s:%d", + long(err), __FILE__, __LINE__); + } + keyboard_layout_format.other = happy; + keyboard_mode = OtherMode; + } + + currentKeyboardLayout = keyLayoutRef; +#else + currentInputSource = source; +#endif + keyboard_dead = 0; + CFStringRef iso639Code; +#ifdef Q_OS_MAC32 +# ifndef kKLLanguageCode +# define kKLLanguageCode 9 +# endif + KLGetKeyboardLayoutProperty(currentKeyboardLayout, kKLLanguageCode, + reinterpret_cast<const void **>(&iso639Code)); +#else + CFArrayRef array = static_cast<CFArrayRef>(TISGetInputSourceProperty(currentInputSource, kTISPropertyInputSourceLanguages)); + iso639Code = static_cast<CFStringRef>(CFArrayGetValueAtIndex(array, 0)); // Actually a RFC3066bis, but it's close enough +#endif + if (iso639Code) { + keyboardInputLocale = QLocale(QCFString::toQString(iso639Code)); + keyboardInputDirection = keyboardInputLocale.textDirection(); + } else { + keyboardInputLocale = QLocale::c(); + keyboardInputDirection = Qt::LeftToRight; + } + return true; +} + +void +QKeyMapperPrivate::deleteLayouts() +{ + keyboard_mode = NullMode; + for (int i = 0; i < 255; ++i) { + if (keyLayout[i]) { + delete keyLayout[i]; + keyLayout[i] = 0; + } + } +} + +void +QKeyMapperPrivate::clearMappings() +{ + deleteLayouts(); + updateKeyboard(); +} + +QList<int> +QKeyMapperPrivate::possibleKeys(QKeyEvent *e) +{ + QList<int> ret; + + KeyboardLayoutItem *kbItem = keyLayout[e->nativeVirtualKey()]; + if (!kbItem) // Key is not in any keyboard layout (e.g. eisu-key on Japanese keyboard) + return ret; + + int baseKey = kbItem->qtKey[0]; + Qt::KeyboardModifiers keyMods = e->modifiers(); + ret << int(baseKey + keyMods); // The base key is _always_ valid, of course + + for (int i = 1; i < 8; ++i) { + Qt::KeyboardModifiers neededMods = ModsTbl[i]; + int key = kbItem->qtKey[i]; + if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) + ret << int(key + (keyMods & ~neededMods)); + } + + return ret; +} + +bool QKeyMapperPrivate::translateKeyEvent(QWidget *widget, EventHandlerCallRef er, EventRef event, + void *info, bool grab) +{ + Q_ASSERT(GetEventClass(event) == kEventClassKeyboard); + bool handled_event=true; + UInt32 ekind = GetEventKind(event); + + // unfortunately modifiers changed event looks quite different, so I have a separate + // code path + if (ekind == kEventRawKeyModifiersChanged) { + //figure out changed modifiers, wish Apple would just send a delta + UInt32 modifiers = 0; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(modifiers), 0, &modifiers); + qt_mac_send_modifiers_changed(modifiers, widget); + return true; + } + + QInputContext *currentContext = qApp->inputContext(); + if (currentContext && currentContext->isComposing()) { + if (ekind == kEventRawKeyDown) { + QMacInputContext *context = qobject_cast<QMacInputContext*>(currentContext); + if (context) + context->setLastKeydownEvent(event); + } + return false; + } + // Once we process the key down , we don't need to send the saved event again from + // kEventTextInputUnicodeForKeyEvent, so clear it. + if (currentContext && ekind == kEventRawKeyDown) { + QMacInputContext *context = qobject_cast<QMacInputContext*>(currentContext); + if (context) + context->setLastKeydownEvent(0); + } + + //get modifiers + Qt::KeyboardModifiers modifiers; + int qtKey; + QChar ourChar; + if (translateKeyEventInternal(er, event, &qtKey, &ourChar, &modifiers, + &handled_event) == false) + return handled_event; + QString text(ourChar); + /* This is actually wrong - but unfortunately it is the best that can be + done for now because of the Control/Meta mapping problems */ + if (modifiers & (Qt::ControlModifier | Qt::MetaModifier) + && !qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { + text = QString(); + } + + + if (widget) { +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(info); + // Try not to call "other" event handlers if we have a popup, + // However, if the key has text + // then we should pass it along because otherwise then people + // can use input method stuff. + if (!qApp->activePopupWidget() + || (qApp->activePopupWidget() && !text.isEmpty())) { + //Find out if someone else wants the event, namely + //is it of use to text services? If so we won't bother + //with a QKeyEvent. + qt_mac_eat_unicode_key = false; + if (er) + CallNextEventHandler(er, event); + extern bool qt_mac_menubar_is_open(); + if (qt_mac_eat_unicode_key || qt_mac_menubar_is_open()) { + return true; + } + } +#endif + // Try to compress key events. + if (!text.isEmpty() && widget->testAttribute(Qt::WA_KeyCompression)) { + EventTime lastTime = GetEventTime(event); + for (;;) { + EventRef releaseEvent = FindSpecificEventInQueue(GetMainEventQueue(), + qt_KeyEventComparatorProc, + (void*)kEventRawKeyUp); + if (!releaseEvent) + break; + const EventTime releaseTime = GetEventTime(releaseEvent); + if (releaseTime < lastTime) + break; + lastTime = releaseTime; + + EventRef pressEvent = FindSpecificEventInQueue(GetMainEventQueue(), + qt_KeyEventComparatorProc, + (void*)kEventRawKeyDown); + if (!pressEvent) + break; + const EventTime pressTime = GetEventTime(pressEvent); + if (pressTime < lastTime) + break; + lastTime = pressTime; + + Qt::KeyboardModifiers compressMod; + int compressQtKey = 0; + QChar compressChar; + if (translateKeyEventInternal(er, pressEvent, + &compressQtKey, &compressChar, &compressMod, 0) + == false) { + break; + } + // Copied from qapplication_x11.cpp (change both). + + bool stopCompression = + // 1) misc keys + (compressQtKey >= Qt::Key_Escape && compressQtKey <= Qt::Key_SysReq) + // 2) cursor movement + || (compressQtKey >= Qt::Key_Home && compressQtKey <= Qt::Key_PageDown) + // 3) extra keys + || (compressQtKey >= Qt::Key_Super_L && compressQtKey <= Qt::Key_Direction_R) + // 4) something that a) doesn't translate to text or b) translates + // to newline text + || (compressQtKey == 0) + || (compressChar == QLatin1Char('\n')) + || (compressQtKey == Qt::Key_unknown); + + if (compressMod == modifiers && !compressChar.isNull() && !stopCompression) { +#ifdef DEBUG_KEY_BINDINGS + qDebug("compressing away %c", compressChar.toLatin1()); +#endif + text += compressChar; + // Clean up + RemoveEventFromQueue(GetMainEventQueue(), releaseEvent); + RemoveEventFromQueue(GetMainEventQueue(), pressEvent); + } else { +#ifdef DEBUG_KEY_BINDINGS + qDebug("stoping compression.."); +#endif + break; + } + } + } + + // There is no way to get the scan code from carbon. But we cannot use the value 0, since + // it indicates that the event originates from somewhere else than the keyboard + UInt32 macScanCode = 1; + UInt32 macVirtualKey = 0; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, 0, sizeof(macVirtualKey), 0, &macVirtualKey); + UInt32 macModifiers = 0; + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(macModifiers), 0, &macModifiers); +#ifdef QT_MAC_USE_COCOA + // The unicode characters in the range 0xF700-0xF747 are reserved + // by Mac OS X for transient use as keyboard function keys. We + // wont send 'text' for such key events. This is done to match + // behavior on other platforms. + unsigned int *unicodeKey = (unsigned int*)info; + if (*unicodeKey >= 0xf700 && *unicodeKey <= 0xf747) + text = QString(); + bool isAccepted; +#endif + handled_event = QKeyMapper::sendKeyEvent(widget, grab, + (ekind == kEventRawKeyUp) ? QEvent::KeyRelease : QEvent::KeyPress, + qtKey, modifiers, text, ekind == kEventRawKeyRepeat, 0, + macScanCode, macVirtualKey, macModifiers +#ifdef QT_MAC_USE_COCOA + ,&isAccepted +#endif + ); +#ifdef QT_MAC_USE_COCOA + *unicodeKey = (unsigned int)isAccepted; +#endif + } + return handled_event; +} + +void +QKeyMapperPrivate::updateKeyMap(EventHandlerCallRef, EventRef event, void * +#if defined(QT_MAC_USE_COCOA) + unicodeKey // unicode character from NSEvent (modifiers applied) +#endif + ) +{ + UInt32 macVirtualKey = 0; + GetEventParameter(event, kEventParamKeyCode, typeUInt32, 0, sizeof(macVirtualKey), 0, &macVirtualKey); + if (updateKeyboard()) + QKeyMapper::changeKeyboard(); + else if (keyLayout[macVirtualKey]) + return; + + UniCharCount buffer_size = 10; + UniChar buffer[buffer_size]; + keyLayout[macVirtualKey] = new KeyboardLayoutItem; + for (int i = 0; i < 16; ++i) { + UniCharCount out_buffer_size = 0; + keyLayout[macVirtualKey]->qtKey[i] = 0; +#ifdef Q_WS_MAC32 + if (keyboard_mode == UnicodeMode) { +#endif + const UInt32 keyModifier = ((qt_mac_get_mac_modifiers(ModsTbl[i]) >> 8) & 0xFF); + OSStatus err = UCKeyTranslate(keyboard_layout_format.unicode, macVirtualKey, kUCKeyActionDown, keyModifier, + keyboard_kind, 0, &keyboard_dead, buffer_size, &out_buffer_size, buffer); + if (err == noErr && out_buffer_size) { + const QChar unicode(buffer[0]); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } +#ifndef Q_WS_MAC32 + else { + const QChar unicode(*((UniChar *)unicodeKey)); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } +#endif +#ifdef Q_WS_MAC32 + } else { + const UInt32 keyModifier = (qt_mac_get_mac_modifiers(ModsTbl[i])); + + uchar translatedChar = KeyTranslate(keyboard_layout_format.other, keyModifier | macVirtualKey, &keyboard_dead); + if (translatedChar) { + static QTextCodec *c = 0; + if (!c) + c = QTextCodec::codecForName("Apple Roman"); + const QChar unicode(c->toUnicode((const char *)&translatedChar, 1).at(0)); + int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); + if (qtkey == Qt::Key_unknown) + qtkey = unicode.unicode(); + keyLayout[macVirtualKey]->qtKey[i] = qtkey; + } + } +#endif + } +#ifdef DEBUG_KEY_MAPS + qDebug("updateKeyMap for virtual key = 0x%02x!", (uint)macVirtualKey); + for (int i = 0; i < 16; ++i) { + qDebug(" [%d] (%d,0x%02x,'%c')", i, + keyLayout[macVirtualKey]->qtKey[i], + keyLayout[macVirtualKey]->qtKey[i], + keyLayout[macVirtualKey]->qtKey[i]); + } +#endif +} + +bool +QKeyMapper::sendKeyEvent(QWidget *widget, bool grab, + QEvent::Type type, int code, Qt::KeyboardModifiers modifiers, + const QString &text, bool autorepeat, int count, + quint32 nativeScanCode, quint32 nativeVirtualKey, + quint32 nativeModifiers, bool *isAccepted) +{ + Q_UNUSED(count); + if (widget && widget->isEnabled()) { + bool key_event = true; +#if defined(QT3_SUPPORT) && !defined(QT_NO_SHORTCUT) + if (type == QEvent::KeyPress && !grab + && QApplicationPrivate::instance()->use_compat()) { + QKeyEventEx accel_ev(type, code, modifiers, + text, autorepeat, qMax(1, int(text.length())), + nativeScanCode, nativeVirtualKey, nativeModifiers); + if (QApplicationPrivate::instance()->qt_tryAccelEvent(widget, &accel_ev)) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: %s::%s consumed Accel: %s", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData()); +#endif + key_event = false; + } else { + if (accel_ev.isAccepted()) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: %s::%s overrode Accel: %s", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData()); +#endif + } + } + } +#else +Q_UNUSED(grab); +#endif // QT3_SUPPORT && !QT_NO_SHORTCUT + if (key_event) { +#if defined(DEBUG_KEY_BINDINGS) || defined(DEBUG_KEY_BINDINGS_MODIFIERS) + qDebug("KeyEvent: Sending %s to %s::%s: %s 0x%08x%s", + type == QEvent::KeyRelease ? "KeyRelease" : "KeyPress", + widget ? widget->metaObject()->className() : "none", + widget ? widget->objectName().toLatin1().constData() : "", + text.toLatin1().constData(), int(modifiers), + autorepeat ? " Repeat" : ""); +#endif + QKeyEventEx ke(type, code, modifiers, text, autorepeat, qMax(1, text.length()), + nativeScanCode, nativeVirtualKey, nativeModifiers); + bool retMe = qt_sendSpontaneousEvent(widget,&ke); + if (isAccepted) + *isAccepted = ke.isAccepted(); + return retMe; + } + } + return false; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qmacdefines_mac.h b/src/gui/platforms/mac/qmacdefines_mac.h new file mode 100644 index 0000000000..d6ccb93593 --- /dev/null +++ b/src/gui/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 <QtCore/qglobal.h> + +#ifdef qDebug +# define old_qDebug qDebug +# undef qDebug +#endif + +#ifdef __LP64__ +typedef signed int OSStatus; +#else +typedef signed long OSStatus; +#endif + +#ifdef __OBJC__ +# ifdef slots +# define old_slots slots +# undef slots +# endif +#include <Cocoa/Cocoa.h> +# ifdef old_slots +# undef slots +# define slots +# undef old_slots +# endif +#endif +#ifdef QT_MAC_USE_COCOA + typedef struct OpaqueEventHandlerCallRef * EventHandlerCallRef; + typedef struct OpaqueEventRef * EventRef; + typedef struct OpaqueMenuRef * MenuRef; + typedef struct OpaquePasteboardRef* PasteboardRef; + typedef struct OpaqueRgnHandle * RgnHandle; + typedef const struct __HIShape *HIShapeRef; + typedef struct __HIShape *HIMutableShapeRef; + typedef struct CGRect CGRect; + typedef struct CGImage *CGImageRef; + typedef struct CGContext *CGContextRef; + typedef struct GDevice * GDPtr; + typedef GDPtr * GDHandle; + typedef struct OpaqueIconRef * IconRef; +# ifdef __OBJC__ + typedef NSWindow* OSWindowRef; + typedef NSView *OSViewRef; + typedef NSMenu *OSMenuRef; + typedef NSEvent *OSEventRef; +# else + typedef void *OSWindowRef; + typedef void *OSViewRef; + typedef void *OSMenuRef; + typedef void *OSEventRef; +# endif +#else // Carbon + typedef struct OpaqueEventHandlerCallRef * EventHandlerCallRef; + typedef struct OpaqueEventRef * EventRef; + typedef struct OpaqueMenuRef * MenuRef; + typedef struct OpaquePasteboardRef* PasteboardRef; + typedef struct OpaqueRgnHandle * RgnHandle; + typedef const struct __HIShape *HIShapeRef; + typedef struct __HIShape *HIMutableShapeRef; + typedef struct CGRect CGRect; + typedef struct CGImage *CGImageRef; + typedef struct CGContext *CGContextRef; + typedef struct GDevice * GDPtr; + typedef GDPtr * GDHandle; + typedef struct OpaqueIconRef * IconRef; + typedef struct OpaqueWindowPtr * WindowRef; + typedef struct OpaqueControlRef * HIViewRef; + typedef WindowRef OSWindowRef; + typedef HIViewRef OSViewRef; + typedef MenuRef OSMenuRef; + typedef EventRef OSEventRef; +#endif // QT_MAC_USE_COCOA + +typedef PasteboardRef OSPasteboardRef; +typedef struct AEDesc AEDescList; +typedef AEDescList AERecord; +typedef AERecord AppleEvent; + +#ifdef check +#undef check +#endif + +#ifdef old_qDebug +# undef qDebug +# define qDebug QT_NO_QDEBUG_MACRO +# undef old_qDebug +#endif diff --git a/src/gui/platforms/mac/qmacgesturerecognizer_mac.mm b/src/gui/platforms/mac/qmacgesturerecognizer_mac.mm new file mode 100644 index 0000000000..6a4f0bb445 --- /dev/null +++ b/src/gui/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<QNativeGestureEvent*>(event); + switch (ev->gestureType) { + case QNativeGestureEvent::Swipe: { + QSwipeGesture *g = static_cast<QSwipeGesture *>(gesture); + g->setSwipeAngle(ev->angle); + g->setHotSpot(ev->position); + return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint; + break; } + default: + break; + } + } + + return QGestureRecognizer::Ignore; +} + +void QMacSwipeGestureRecognizer::reset(QGesture *gesture) +{ + QSwipeGesture *g = static_cast<QSwipeGesture *>(gesture); + g->setSwipeAngle(0); + QGestureRecognizer::reset(gesture); +} + +//////////////////////////////////////////////////////////////////////// + +QMacPinchGestureRecognizer::QMacPinchGestureRecognizer() +{ +} + +QGesture *QMacPinchGestureRecognizer::create(QObject * /*target*/) +{ + return new QPinchGesture; +} + +QGestureRecognizer::Result +QMacPinchGestureRecognizer::recognize(QGesture *gesture, QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::NativeGesture && obj->isWidgetType()) { + QPinchGesture *g = static_cast<QPinchGesture *>(gesture); + QNativeGestureEvent *ev = static_cast<QNativeGestureEvent*>(event); + switch(ev->gestureType) { + case QNativeGestureEvent::GestureBegin: + reset(gesture); + g->setStartCenterPoint(static_cast<QWidget*>(obj)->mapFromGlobal(ev->position)); + g->setCenterPoint(g->startCenterPoint()); + g->setChangeFlags(QPinchGesture::CenterPointChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::MayBeGesture | QGestureRecognizer::ConsumeEventHint; + case QNativeGestureEvent::Rotate: { + g->setLastScaleFactor(g->scaleFactor()); + g->setLastRotationAngle(g->rotationAngle()); + g->setRotationAngle(g->rotationAngle() + ev->percentage); + g->setChangeFlags(QPinchGesture::RotationAngleChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + } + case QNativeGestureEvent::Zoom: + g->setLastScaleFactor(g->scaleFactor()); + g->setLastRotationAngle(g->rotationAngle()); + g->setScaleFactor(g->scaleFactor() * (1 + ev->percentage)); + g->setChangeFlags(QPinchGesture::ScaleFactorChanged); + g->setTotalChangeFlags(g->totalChangeFlags() | g->changeFlags()); + g->setHotSpot(ev->position); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + case QNativeGestureEvent::GestureEnd: + return QGestureRecognizer::FinishGesture | QGestureRecognizer::ConsumeEventHint; + default: + break; + } + } + + return QGestureRecognizer::Ignore; +} + +void QMacPinchGestureRecognizer::reset(QGesture *gesture) +{ + QPinchGesture *g = static_cast<QPinchGesture *>(gesture); + g->setChangeFlags(0); + g->setTotalChangeFlags(0); + g->setScaleFactor(1.0f); + g->setTotalScaleFactor(1.0f); + g->setLastScaleFactor(1.0f); + g->setRotationAngle(0.0f); + g->setTotalRotationAngle(0.0f); + g->setLastRotationAngle(0.0f); + g->setCenterPoint(QPointF()); + g->setStartCenterPoint(QPointF()); + g->setLastCenterPoint(QPointF()); + QGestureRecognizer::reset(gesture); +} + +//////////////////////////////////////////////////////////////////////// + +#if defined(QT_MAC_USE_COCOA) + +QMacPanGestureRecognizer::QMacPanGestureRecognizer() : _panCanceled(true) +{ +} + +QGesture *QMacPanGestureRecognizer::create(QObject *target) +{ + if (!target) + return new QPanGesture; + + if (QWidget *w = qobject_cast<QWidget *>(target)) { + w->setAttribute(Qt::WA_AcceptTouchEvents); + w->setAttribute(Qt::WA_TouchPadAcceptSingleTouchEvents); + return new QPanGesture; + } + return 0; +} + +QGestureRecognizer::Result +QMacPanGestureRecognizer::recognize(QGesture *gesture, QObject *target, QEvent *event) +{ + const int panBeginDelay = 300; + const int panBeginRadius = 3; + + QPanGesture *g = static_cast<QPanGesture *>(gesture); + + switch (event->type()) { + case QEvent::TouchBegin: { + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) { + reset(gesture); + _startPos = QCursor::pos(); + _panTimer.start(panBeginDelay, target); + _panCanceled = false; + return QGestureRecognizer::MayBeGesture; + } + break;} + case QEvent::TouchEnd: { + if (_panCanceled) + break; + + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) + return QGestureRecognizer::FinishGesture; + break;} + case QEvent::TouchUpdate: { + if (_panCanceled) + break; + + const QTouchEvent *ev = static_cast<const QTouchEvent*>(event); + if (ev->touchPoints().size() == 1) { + if (_panTimer.isActive()) { + // INVARIANT: Still in maybeGesture. Check if the user + // moved his finger so much that it makes sense to cancel the pan: + const QPointF p = QCursor::pos(); + if ((p - _startPos).manhattanLength() > panBeginRadius) { + _panCanceled = true; + _panTimer.stop(); + return QGestureRecognizer::CancelGesture; + } + } else { + const QPointF p = QCursor::pos(); + const QPointF posOffset = p - _startPos; + g->setLastOffset(g->offset()); + g->setOffset(QPointF(posOffset.x(), posOffset.y())); + g->setHotSpot(_startPos); + return QGestureRecognizer::TriggerGesture; + } + } else if (_panTimer.isActive()) { + // I only want to cancel the pan if the user is pressing + // more than one finger, and the pan hasn't started yet: + _panCanceled = true; + _panTimer.stop(); + return QGestureRecognizer::CancelGesture; + } + break;} + case QEvent::Timer: { + QTimerEvent *ev = static_cast<QTimerEvent *>(event); + if (ev->timerId() == _panTimer.timerId()) { + _panTimer.stop(); + if (_panCanceled) + break; + // Begin new pan session! + _startPos = QCursor::pos(); + g->setHotSpot(_startPos); + return QGestureRecognizer::TriggerGesture | QGestureRecognizer::ConsumeEventHint; + } + break; } + default: + break; + } + + return QGestureRecognizer::Ignore; +} + +void QMacPanGestureRecognizer::reset(QGesture *gesture) +{ + QPanGesture *g = static_cast<QPanGesture *>(gesture); + _startPos = QPointF(); + _panCanceled = true; + g->setOffset(QPointF(0, 0)); + g->setLastOffset(QPointF(0, 0)); + g->setAcceleration(qreal(1)); + QGestureRecognizer::reset(gesture); +} +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE + +#endif // QT_NO_GESTURES diff --git a/src/gui/platforms/mac/qmacgesturerecognizer_mac_p.h b/src/gui/platforms/mac/qmacgesturerecognizer_mac_p.h new file mode 100644 index 0000000000..465f6a2ac8 --- /dev/null +++ b/src/gui/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/gui/platforms/mac/qmime_mac.cpp b/src/gui/platforms/mac/qmime_mac.cpp new file mode 100644 index 0000000000..d6f6222c23 --- /dev/null +++ b/src/gui/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 <unistd.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/fcntl.h> +#endif + +#include "qdebug.h" +#include "qpixmap.h" +#include "qimagewriter.h" +#include "qimagereader.h" +#include "qdatastream.h" +#include "qbuffer.h" +#include "qdatetime.h" +#include "qapplication_p.h" +#include "qtextcodec.h" +#include "qregexp.h" +#include "qurl.h" +#include "qmap.h" +#include <private/qt_mac_p.h> + + +#ifdef Q_WS_MAC32 +#include <QuickTime/QuickTime.h> +#include <qlibrary.h> +#endif + +QT_BEGIN_NAMESPACE + +extern CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0); // qpaintengine_mac.cpp + +typedef QList<QMacPasteboardMime*> MimeList; +Q_GLOBAL_STATIC(MimeList, globalMimeList) + +static void cleanup_mimes() +{ + MimeList *mimes = globalMimeList(); + while (!mimes->isEmpty()) + delete mimes->takeFirst(); +} + +Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList) + +/*! + \fn void qRegisterDraggedTypes(const QStringList &types) + \relates QMacPasteboardMime + + Registers the given \a types as custom pasteboard types. + + This function should be called to enable the Drag and Drop events + for custom pasteboard types on Cocoa implementations. This is required + in addition to a QMacPasteboardMime subclass implementation. By default + drag and drop is enabled for all standard pasteboard types. + + \sa QMacPasteboardMime +*/ +Q_GUI_EXPORT void qRegisterDraggedTypes(const QStringList &types) +{ + (*globalDraggedTypesList()) += types; +} + +const QStringList& qEnabledDraggedTypes() +{ + return (*globalDraggedTypesList()); +} + + +/***************************************************************************** + QDnD debug facilities + *****************************************************************************/ +//#define DEBUG_MIME_MAPS + +//functions +extern QString qt_mac_from_pascal_string(const Str255); //qglobal.cpp +extern void qt_mac_from_pascal_string(QString, Str255, TextEncoding encoding=0, int len=-1); //qglobal.cpp + +ScrapFlavorType qt_mac_mime_type = 'CUTE'; +CFStringRef qt_mac_mime_typeUTI = CFSTR("com.pasteboard.trolltech.marker"); + +/*! + \class QMacPasteboardMime + \brief The QMacPasteboardMime class converts between a MIME type and a + \l{http://developer.apple.com/macosx/uniformtypeidentifiers.html}{Uniform + Type Identifier (UTI)} format. + \since 4.2 + + \ingroup draganddrop + + Qt's drag and drop and clipboard facilities use the MIME + standard. On X11, this maps trivially to the Xdnd protocol. On + Mac, although some applications use MIME to describe clipboard + contents, it is more common to use Apple's UTI format. + + QMacPasteboardMime's role is to bridge the gap between MIME and UTI; + By subclasses this class, one can extend Qt's drag and drop + and clipboard handling to convert to and from unsupported, or proprietary, UTI formats. + + A subclass of QMacPasteboardMime will automatically be registered, and active, upon instantiation. + + Qt has predefined support for the following UTIs: + \list + \i public.utf8-plain-text - converts to "text/plain" + \i public.utf16-plain-text - converts to "text/plain" + \i public.html - converts to "text/html" + \i public.url - converts to "text/uri-list" + \i public.file-url - converts to "text/uri-list" + \i public.tiff - converts to "application/x-qt-image" + \i public.vcard - converts to "text/plain" + \i com.apple.traditional-mac-plain-text - converts to "text/plain" + \i com.apple.pict - converts to "application/x-qt-image" + \endlist + + When working with MIME data, Qt will interate through all instances of QMacPasteboardMime to + find an instance that can convert to, or from, a specific MIME type. It will do this by calling + canConvert() on each instance, starting with (and choosing) the last created instance first. + The actual conversions will be done by using convertToMime() and convertFromMime(). + + \note The API uses the term "flavor" in some cases. This is for backwards + compatibility reasons, and should now be understood as UTIs. +*/ + +/*! \enum QMacPasteboardMime::QMacPasteboardMimeType + \internal +*/ + +/*! + Constructs a new conversion object of type \a t, adding it to the + globally accessed list of available convertors. +*/ +QMacPasteboardMime::QMacPasteboardMime(char t) : type(t) +{ + globalMimeList()->append(this); +} + +/*! + Destroys a conversion object, removing it from the global + list of available convertors. +*/ +QMacPasteboardMime::~QMacPasteboardMime() +{ + if(!QApplication::closingDown()) + globalMimeList()->removeAll(this); +} + +class QMacPasteboardMimeAny : public QMacPasteboardMime { +private: + +public: + QMacPasteboardMimeAny() : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) { + } + ~QMacPasteboardMimeAny() { + } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeAny::convertorName() +{ + return QLatin1String("Any-Mime"); +} + +QString QMacPasteboardMimeAny::flavorFor(const QString &mime) +{ + // do not handle the mime type name in the drag pasteboard + if(mime == QLatin1String("application/x-qt-mime-type-name")) + return QString(); + QString ret = QLatin1String("com.trolltech.anymime.") + mime; + return ret.replace(QLatin1Char('/'), QLatin1String("--")); +} + +QString QMacPasteboardMimeAny::mimeFor(QString flav) +{ + const QString any_prefix = QLatin1String("com.trolltech.anymime."); + if(flav.size() > any_prefix.length() && flav.startsWith(any_prefix)) + return flav.mid(any_prefix.length()).replace(QLatin1String("--"), QLatin1String("/")); + return QString(); +} + +bool QMacPasteboardMimeAny::canConvert(const QString &mime, QString flav) +{ + return mimeFor(flav) == mime; +} + +QVariant QMacPasteboardMimeAny::convertToMime(const QString &mime, QList<QByteArray> data, QString) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeAny: Cannot handle multiple member data"); + QVariant ret; + if (mime == QLatin1String("text/plain")) + ret = QString::fromUtf8(data.first()); + else + ret = data.first(); + return ret; +} + +QList<QByteArray> QMacPasteboardMimeAny::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) + ret.append(data.toString().toUtf8()); + else + ret.append(data.toByteArray()); + return ret; +} + +class QMacPasteboardMimeTypeName : public QMacPasteboardMime { +private: + +public: + QMacPasteboardMimeTypeName() : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL) { + } + ~QMacPasteboardMimeTypeName() { + } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeTypeName::convertorName() +{ + return QLatin1String("Qt-Mime-Type"); +} + +QString QMacPasteboardMimeTypeName::flavorFor(const QString &mime) +{ + if(mime == QLatin1String("application/x-qt-mime-type-name")) + return QLatin1String("com.trolltech.qt.MimeTypeName"); + return QString(); +} + +QString QMacPasteboardMimeTypeName::mimeFor(QString) +{ + return QString(); +} + +bool QMacPasteboardMimeTypeName::canConvert(const QString &, QString) +{ + return false; +} + +QVariant QMacPasteboardMimeTypeName::convertToMime(const QString &, QList<QByteArray>, QString) +{ + QVariant ret; + return ret; +} + +QList<QByteArray> QMacPasteboardMimeTypeName::convertFromMime(const QString &, QVariant, QString) +{ + QList<QByteArray> ret; + ret.append(QString("x-qt-mime-type-name").toUtf8()); + return ret; +} + +class QMacPasteboardMimePlainText : public QMacPasteboardMime { +public: + QMacPasteboardMimePlainText() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimePlainText::convertorName() +{ + return QLatin1String("PlainText"); +} + +QString QMacPasteboardMimePlainText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/plain")) + return QLatin1String("com.apple.traditional-mac-plain-text"); + return QString(); +} + +QString QMacPasteboardMimePlainText::mimeFor(QString flav) +{ + if (flav == QLatin1String("com.apple.traditional-mac-plain-text")) + return QLatin1String("text/plain"); + return QString(); +} + +bool QMacPasteboardMimePlainText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimePlainText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimePlainText: Cannot handle multiple member data"); + const QByteArray &firstData = data.first(); + QVariant ret; + if(flavor == QCFString(QLatin1String("com.apple.traditional-mac-plain-text"))) { + QCFString str(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8 *>(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList<QByteArray> QMacPasteboardMimePlainText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + QString string = data.toString(); + if(flavor == QCFString(QLatin1String("com.apple.traditional-mac-plain-text"))) + ret.append(string.toLatin1()); + return ret; +} + +class QMacPasteboardMimeUnicodeText : public QMacPasteboardMime { +public: + QMacPasteboardMimeUnicodeText() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeUnicodeText::convertorName() +{ + return QLatin1String("UnicodeText"); +} + +QString QMacPasteboardMimeUnicodeText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/plain")) + return QLatin1String("public.utf16-plain-text"); + int i = mime.indexOf(QLatin1String("charset=")); + if (i >= 0) { + QString cs(mime.mid(i+8).toLower()); + i = cs.indexOf(QLatin1Char(';')); + if (i>=0) + cs = cs.left(i); + if (cs == QLatin1String("system")) + return QLatin1String("public.utf8-plain-text"); + else if (cs == QLatin1String("iso-10646-ucs-2") + || cs == QLatin1String("utf16")) + return QLatin1String("public.utf16-plain-text"); + } + return QString(); +} + +QString QMacPasteboardMimeUnicodeText::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.utf16-plain-text") || flav == QLatin1String("public.utf8-plain-text")) + return QLatin1String("text/plain"); + return QString(); +} + +bool QMacPasteboardMimeUnicodeText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimeUnicodeText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeUnicodeText: Cannot handle multiple member data"); + const QByteArray &firstData = data.first(); + // I can only handle two types (system and unicode) so deal with them that way + QVariant ret; + if(flavor == QLatin1String("public.utf8-plain-text")) { + QCFString str(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8 *>(firstData.constData()), + firstData.size(), CFStringGetSystemEncoding(), false)); + ret = QString(str); + } else if (flavor == QLatin1String("public.utf16-plain-text")) { + ret = QString(reinterpret_cast<const QChar *>(firstData.constData()), + firstData.size() / sizeof(QChar)); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList<QByteArray> QMacPasteboardMimeUnicodeText::convertFromMime(const QString &, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + QString string = data.toString(); + if(flavor == QLatin1String("public.utf8-plain-text")) + ret.append(string.toUtf8()); + else if (flavor == QLatin1String("public.utf16-plain-text")) + ret.append(QByteArray((char*)string.utf16(), string.length()*2)); + return ret; +} + +class QMacPasteboardMimeHTMLText : public QMacPasteboardMime { +public: + QMacPasteboardMimeHTMLText() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeHTMLText::convertorName() +{ + return QLatin1String("HTML"); +} + +QString QMacPasteboardMimeHTMLText::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/html")) + return QLatin1String("public.html"); + return QString(); +} + +QString QMacPasteboardMimeHTMLText::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.html")) + return QLatin1String("text/html"); + return QString(); +} + +bool QMacPasteboardMimeHTMLText::canConvert(const QString &mime, QString flav) +{ + return flavorFor(mime) == flav; +} + +QVariant QMacPasteboardMimeHTMLText::convertToMime(const QString &mimeType, QList<QByteArray> data, QString flavor) +{ + if (!canConvert(mimeType, flavor)) + return QVariant(); + if (data.count() > 1) + qWarning("QMacPasteboardMimeHTMLText: Cannot handle multiple member data"); + return data.first(); +} + +QList<QByteArray> QMacPasteboardMimeHTMLText::convertFromMime(const QString &mime, QVariant data, QString flavor) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flavor)) + return ret; + ret.append(data.toByteArray()); + return ret; +} + + +#ifdef Q_WS_MAC32 + +// This can be removed once 10.6 is the minimum (or we have to require 64-bit) whichever comes first. + +typedef ComponentResult (*PtrGraphicsImportSetDataHandle)(GraphicsImportComponent, Handle); +typedef ComponentResult (*PtrGraphicsImportCreateCGImage)(GraphicsImportComponent, CGImageRef*, UInt32); +typedef ComponentResult (*PtrGraphicsExportSetInputCGImage)(GraphicsExportComponent, CGImageRef); +typedef ComponentResult (*PtrGraphicsExportSetOutputHandle)(GraphicsExportComponent, Handle); +typedef ComponentResult (*PtrGraphicsExportDoExport)(GraphicsExportComponent, unsigned long *); + +static PtrGraphicsImportSetDataHandle ptrGraphicsImportSetDataHandle = 0; +static PtrGraphicsImportCreateCGImage ptrGraphicsImportCreateCGImage = 0; +static PtrGraphicsExportSetInputCGImage ptrGraphicsExportSetInputCGImage = 0; +static PtrGraphicsExportSetOutputHandle ptrGraphicsExportSetOutputHandle = 0; +static PtrGraphicsExportDoExport ptrGraphicsExportDoExport = 0; + +static bool resolveMimeQuickTimeSymbols() +{ + if (ptrGraphicsImportSetDataHandle == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/QuickTime.framework/QuickTime")); + ptrGraphicsImportSetDataHandle = reinterpret_cast<PtrGraphicsImportSetDataHandle>(library.resolve("GraphicsImportSetDataHandle")); + ptrGraphicsImportCreateCGImage = reinterpret_cast<PtrGraphicsImportCreateCGImage>(library.resolve("GraphicsImportCreateCGImage")); + ptrGraphicsExportSetInputCGImage = reinterpret_cast<PtrGraphicsExportSetInputCGImage>(library.resolve("GraphicsExportSetInputCGImage")); + ptrGraphicsExportSetOutputHandle = reinterpret_cast<PtrGraphicsExportSetOutputHandle>(library.resolve("GraphicsExportSetOutputHandle")); + ptrGraphicsExportDoExport = reinterpret_cast<PtrGraphicsExportDoExport>(library.resolve("GraphicsExportDoExport")); + } + + return ptrGraphicsImportSetDataHandle != 0 + && ptrGraphicsImportCreateCGImage != 0 && ptrGraphicsExportSetInputCGImage != 0 + && ptrGraphicsExportSetOutputHandle != 0 && ptrGraphicsExportDoExport != 0; +} + +class QMacPasteboardMimePict : public QMacPasteboardMime { +public: + QMacPasteboardMimePict() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimePict::convertorName() +{ + return QLatin1String("Pict"); +} + +QString QMacPasteboardMimePict::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("application/x-qt-image"))) + return QLatin1String("com.apple.pict"); + return QString(); +} + +QString QMacPasteboardMimePict::mimeFor(QString flav) +{ + if(flav == QLatin1String("com.apple.pict")) + return QLatin1String("application/x-qt-image"); + return QString(); +} + +bool QMacPasteboardMimePict::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("com.apple.pict") + && mime == QLatin1String("application/x-qt-image"); +} + + +QVariant QMacPasteboardMimePict::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimePict: Cannot handle multiple member data"); + QVariant ret; + if (!resolveMimeQuickTimeSymbols()) + return ret; + + if(!canConvert(mime, flav)) + return ret; + const QByteArray &a = data.first(); + + // This function expects the 512 header (just to skip it, so create the extra space for it). + Handle pic = NewHandle(a.size() + 512); + memcpy(*pic + 512, a.constData(), a.size()); + + GraphicsImportComponent graphicsImporter; + ComponentResult result = OpenADefaultComponent(GraphicsImporterComponentType, + kQTFileTypePicture, &graphicsImporter); + QCFType<CGImageRef> cgImage; + if (!result) + result = ptrGraphicsImportSetDataHandle(graphicsImporter, pic); + if (!result) + result = ptrGraphicsImportCreateCGImage(graphicsImporter, &cgImage, + kGraphicsImportCreateCGImageUsingCurrentSettings); + if (!result) + ret = QVariant(QPixmap::fromMacCGImageRef(cgImage).toImage()); + CloseComponent(graphicsImporter); + DisposeHandle(pic); + return ret; +} + +QList<QByteArray> QMacPasteboardMimePict::convertFromMime(const QString &mime, QVariant variant, + QString flav) +{ + QList<QByteArray> ret; + if (!resolveMimeQuickTimeSymbols()) + return ret; + + if (!canConvert(mime, flav)) + return ret; + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(qvariant_cast<QImage>(variant)); + Handle pic = NewHandle(0); + GraphicsExportComponent graphicsExporter; + ComponentResult result = OpenADefaultComponent(GraphicsExporterComponentType, + kQTFileTypePicture, &graphicsExporter); + if (!result) { + unsigned long sizeWritten; + result = ptrGraphicsExportSetInputCGImage(graphicsExporter, cgimage); + if (!result) + result = ptrGraphicsExportSetOutputHandle(graphicsExporter, pic); + if (!result) + result = ptrGraphicsExportDoExport(graphicsExporter, &sizeWritten); + + CloseComponent(graphicsExporter); + } + + int size = GetHandleSize((Handle)pic); + // Skip the Picture File header (512 bytes) and feed the raw data + QByteArray ar(reinterpret_cast<char *>(*pic + 512), size - 512); + ret.append(ar); + DisposeHandle(pic); + return ret; +} + + +#endif //Q_WS_MAC32 + +class QMacPasteboardMimeTiff : public QMacPasteboardMime { +public: + QMacPasteboardMimeTiff() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeTiff::convertorName() +{ + return QLatin1String("Tiff"); +} + +QString QMacPasteboardMimeTiff::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("application/x-qt-image"))) + return QLatin1String("public.tiff"); + return QString(); +} + +QString QMacPasteboardMimeTiff::mimeFor(QString flav) +{ + if(flav == QLatin1String("public.tiff")) + return QLatin1String("application/x-qt-image"); + return QString(); +} + +bool QMacPasteboardMimeTiff::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("public.tiff") && mime == QLatin1String("application/x-qt-image"); +} + +QVariant QMacPasteboardMimeTiff::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(data.count() > 1) + qWarning("QMacPasteboardMimeTiff: Cannot handle multiple member data"); + QVariant ret; + if (!canConvert(mime, flav)) + return ret; + const QByteArray &a = data.first(); + QCFType<CGImageRef> image; + QCFType<CFDataRef> tiffData = CFDataCreateWithBytesNoCopy(0, + reinterpret_cast<const UInt8 *>(a.constData()), + a.size(), kCFAllocatorNull); + QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); + image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0); + + if (image != 0) + ret = QVariant(QPixmap::fromMacCGImageRef(image).toImage()); + return ret; +} + +QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + + QImage img = qvariant_cast<QImage>(variant); + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(img); +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); + QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); + if (imageDestination != 0) { + CFTypeRef keys[2]; + QCFType<CFTypeRef> values[2]; + QCFType<CFDictionaryRef> options; + keys[0] = kCGImagePropertyPixelWidth; + keys[1] = kCGImagePropertyPixelHeight; + int width = img.width(); + int height = img.height(); + values[0] = CFNumberCreate(0, kCFNumberIntType, &width); + values[1] = CFNumberCreate(0, kCFNumberIntType, &height); + options = CFDictionaryCreate(0, reinterpret_cast<const void **>(keys), + reinterpret_cast<const void **>(values), 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CGImageDestinationAddImage(imageDestination, cgimage, options); + CGImageDestinationFinalize(imageDestination); + } + QByteArray ar(CFDataGetLength(data), 0); + CFDataGetBytes(data, + CFRangeMake(0, ar.size()), + reinterpret_cast<UInt8 *>(ar.data())); + ret.append(ar); + } else +#endif + { +#ifdef Q_WS_MAC32 + Handle tiff = NewHandle(0); + if (resolveMimeQuickTimeSymbols()) { + GraphicsExportComponent graphicsExporter; + ComponentResult result = OpenADefaultComponent(GraphicsExporterComponentType, + kQTFileTypeTIFF, &graphicsExporter); + if (!result) { + unsigned long sizeWritten; + result = ptrGraphicsExportSetInputCGImage(graphicsExporter, cgimage); + if (!result) + result = ptrGraphicsExportSetOutputHandle(graphicsExporter, tiff); + if (!result) + result = ptrGraphicsExportDoExport(graphicsExporter, &sizeWritten); + + CloseComponent(graphicsExporter); + } + } + int size = GetHandleSize((Handle)tiff); + QByteArray ar(reinterpret_cast<char *>(*tiff), size); + ret.append(ar); + DisposeHandle(tiff); +#endif + } + return ret; +} + + +class QMacPasteboardMimeFileUri : public QMacPasteboardMime { +public: + QMacPasteboardMimeFileUri() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeFileUri::convertorName() +{ + return QLatin1String("FileURL"); +} + +QString QMacPasteboardMimeFileUri::flavorFor(const QString &mime) +{ + if (mime == QLatin1String("text/uri-list")) + return QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0)); + return QString(); +} + +QString QMacPasteboardMimeFileUri::mimeFor(QString flav) +{ + if (flav == QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0))) + return QLatin1String("text/uri-list"); + return QString(); +} + +bool QMacPasteboardMimeFileUri::canConvert(const QString &mime, QString flav) +{ + return mime == QLatin1String("text/uri-list") + && flav == QCFString(UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, CFSTR("furl"), 0)); +} + +QVariant QMacPasteboardMimeFileUri::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + QList<QVariant> ret; + for(int i = 0; i < data.size(); ++i) { + QUrl url = QUrl::fromEncoded(data.at(i)); + if (url.host().toLower() == QLatin1String("localhost")) + url.setHost(QString()); + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> QMacPasteboardMimeFileUri::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + QList<QVariant> urls = data.toList(); + for(int i = 0; i < urls.size(); ++i) { + QUrl url = urls.at(i).toUrl(); + if (url.scheme().isEmpty()) + url.setScheme(QLatin1String("file")); + if (url.scheme().toLower() == QLatin1String("file")) { + if (url.host().isEmpty()) + url.setHost(QLatin1String("localhost")); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + ret.append(url.toEncoded()); + } + return ret; +} + +class QMacPasteboardMimeUrl : public QMacPasteboardMime { +public: + QMacPasteboardMimeUrl() : QMacPasteboardMime(MIME_ALL) { } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeUrl::convertorName() +{ + return QLatin1String("URL"); +} + +QString QMacPasteboardMimeUrl::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("text/uri-list"))) + return QLatin1String("public.url"); + return QString(); +} + +QString QMacPasteboardMimeUrl::mimeFor(QString flav) +{ + if(flav == QLatin1String("public.url")) + return QLatin1String("text/uri-list"); + return QString(); +} + +bool QMacPasteboardMimeUrl::canConvert(const QString &mime, QString flav) +{ + return flav == QLatin1String("public.url") + && mime == QLatin1String("text/uri-list"); +} + +QVariant QMacPasteboardMimeUrl::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) +{ + if(!canConvert(mime, flav)) + return QVariant(); + + QList<QVariant> ret; + for (int i=0; i<data.size(); ++i) { + QUrl url = QUrl::fromEncoded(data.at(i)); + if (url.host().toLower() == QLatin1String("localhost")) + url.setHost(QString()); + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> QMacPasteboardMimeUrl::convertFromMime(const QString &mime, QVariant data, QString flav) +{ + QList<QByteArray> ret; + if (!canConvert(mime, flav)) + return ret; + + QList<QVariant> urls = data.toList(); + for(int i=0; i<urls.size(); ++i) { + QUrl url = urls.at(i).toUrl(); + if (url.scheme().isEmpty()) + url.setScheme(QLatin1String("file")); + if (url.scheme().toLower() == QLatin1String("file")) { + if (url.host().isEmpty()) + url.setHost(QLatin1String("localhost")); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + ret.append(url.toEncoded()); + } + return ret; +} + +class QMacPasteboardMimeVCard : public QMacPasteboardMime +{ +public: + QMacPasteboardMimeVCard() : QMacPasteboardMime(MIME_ALL){ } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +QString QMacPasteboardMimeVCard::convertorName() +{ + return QString("VCard"); +} + +bool QMacPasteboardMimeVCard::canConvert(const QString &mime, QString flav) +{ + return mimeFor(flav) == mime; +} + +QString QMacPasteboardMimeVCard::flavorFor(const QString &mime) +{ + if(mime.startsWith(QLatin1String("text/plain"))) + return QLatin1String("public.vcard"); + return QString(); +} + +QString QMacPasteboardMimeVCard::mimeFor(QString flav) +{ + if (flav == QLatin1String("public.vcard")) + return QLatin1String("text/plain"); + return QString(); +} + +QVariant QMacPasteboardMimeVCard::convertToMime(const QString &mime, QList<QByteArray> data, QString) +{ + QByteArray cards; + if (mime == QLatin1String("text/plain")) { + for (int i=0; i<data.size(); ++i) + cards += data[i]; + } + return QVariant(cards); +} + +QList<QByteArray> QMacPasteboardMimeVCard::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) + ret.append(data.toString().toUtf8()); + return ret; +} + +#ifdef QT3_SUPPORT +class QMacPasteboardMimeQt3Any : public QMacPasteboardMime { +private: + int current_max; + QFile library_file; + QDateTime mime_registry_loaded; + QMap<QString, int> mime_registry; + int registerMimeType(const QString &mime); + bool loadMimeRegistry(); + +public: + QMacPasteboardMimeQt3Any() : QMacPasteboardMime(MIME_QT3_CONVERTOR) { + current_max = 'QT00'; + } + ~QMacPasteboardMimeQt3Any() { + } + QString convertorName(); + + QString flavorFor(const QString &mime); + QString mimeFor(QString flav); + bool canConvert(const QString &mime, QString flav); + QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); + QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); +}; + +static bool qt_mac_openMimeRegistry(bool global, QIODevice::OpenMode mode, QFile &file) +{ + QString dir = QLatin1String("/Library/Qt"); + if(!global) + dir.prepend(QDir::homePath()); + file.setFileName(dir + QLatin1String("/.mime_types")); + if(mode != QIODevice::ReadOnly) { + if(!QFile::exists(dir)) { + // Do it with a system call as I don't see much worth in + // doing it with QDir since we have to chmod anyway. + bool success = ::mkdir(dir.toLocal8Bit().constData(), S_IRUSR | S_IWUSR | S_IXUSR) == 0; + if (success) + success = ::chmod(dir.toLocal8Bit().constData(), S_IRUSR | S_IWUSR | S_IXUSR + | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH) == 0; + if (!success) + return false; + } + if (!file.exists()) { + // Create the file and chmod it so that everyone can write to it. + int fd = ::open(file.fileName().toLocal8Bit().constData(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + bool success = fd != -1; + if (success) + success = ::fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) == 0; + if (fd != -1) + ::close(fd); + if(!success) + return false; + } + } + return file.open(mode); +} + +static void qt_mac_loadMimeRegistry(QFile &file, QMap<QString, int> ®istry, int &max) +{ + file.reset(); + QTextStream stream(&file); + while(!stream.atEnd()) { + QString mime = stream.readLine(); + int mactype = stream.readLine().toInt(); + if(mactype > max) + max = mactype; + registry.insert(mime, mactype); + } +} + +bool QMacPasteboardMimeQt3Any::loadMimeRegistry() +{ + if(!library_file.isOpen()) { + if(!qt_mac_openMimeRegistry(true, QIODevice::ReadWrite, library_file)) { + QFile global; + if(qt_mac_openMimeRegistry(true, QIODevice::ReadOnly, global)) { + qt_mac_loadMimeRegistry(global, mime_registry, current_max); + global.close(); + } + if(!qt_mac_openMimeRegistry(false, QIODevice::ReadWrite, library_file)) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Failure to open mime resources %s -- %s", library_file.fileName().toLatin1().constData(), + library_file.errorString().toLatin1().constData()); + return false; + } + } + } + + QFileInfo fi(library_file); + if(!mime_registry_loaded.isNull() && mime_registry_loaded == fi.lastModified()) + return true; + mime_registry_loaded = fi.lastModified(); + qt_mac_loadMimeRegistry(library_file, mime_registry, current_max); + return true; +} + +int QMacPasteboardMimeQt3Any::registerMimeType(const QString &mime) +{ + if(!mime_registry.contains(mime)) { + if(!loadMimeRegistry()) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Internal error"); + return 0; + } + if(!mime_registry.contains(mime)) { + if(!library_file.isOpen()) { + if(!library_file.open(QIODevice::WriteOnly)) { + qWarning("QMacPasteboardMimeAnyQt3Mime: Failure to open %s -- %s", library_file.fileName().toLatin1().constData(), + library_file.errorString().toLatin1().constData()); + return false; + } + } + int ret = ++current_max; + mime_registry_loaded = QFileInfo(library_file).lastModified(); + QTextStream stream(&library_file); + stream << mime << endl; + stream << ret << endl; + mime_registry.insert(mime, ret); + library_file.flush(); //flush and set mtime + return ret; + } + } + return mime_registry[mime]; +} + +QString QMacPasteboardMimeQt3Any::convertorName() +{ + return QLatin1String("Qt3-Any-Mime"); +} + +QString QMacPasteboardMimeQt3Any::flavorFor(const QString &mime) +{ + const int os_flav = registerMimeType(mime); + QCFType<CFArrayRef> ids = UTTypeCreateAllIdentifiersForTag(0, kUTTagClassOSType, + QCFString(UTCreateStringForOSType(os_flav))); + if(ids) { + const int type_count = CFArrayGetCount(ids); + if(type_count) { + if(type_count > 1) + qDebug("Can't happen!"); + return QCFString::toQString((CFStringRef)CFArrayGetValueAtIndex(ids, 0)); + } + } + return QString(); +} + +QString QMacPasteboardMimeQt3Any::mimeFor(QString flav) +{ + loadMimeRegistry(); + const int os_flav = UTGetOSTypeFromString(UTTypeCopyPreferredTagWithClass(QCFString(flav), kUTTagClassOSType)); + for(QMap<QString, int>::const_iterator it = mime_registry.constBegin(); + it != mime_registry.constEnd(); ++it) { + if(it.value() == os_flav) + return QString::fromLatin1(it.key().toLatin1()); + } + return QString(); +} + +bool QMacPasteboardMimeQt3Any::canConvert(const QString &mime, QString flav) +{ + loadMimeRegistry(); + const int os_flav = UTGetOSTypeFromString(UTTypeCopyPreferredTagWithClass(QCFString(flav), kUTTagClassOSType)); + if(mime_registry.contains(mime) && mime_registry[mime] == os_flav) + return true; + return false; +} + +QVariant QMacPasteboardMimeQt3Any::convertToMime(const QString &, QList<QByteArray>, QString) +{ + qWarning("QMacPasteboardMimeAnyQt3Mime: Cannot write anything!"); + return QVariant(); +} + +QList<QByteArray> QMacPasteboardMimeQt3Any::convertFromMime(const QString &mime, QVariant data, QString) +{ + QList<QByteArray> ret; + if (mime == QLatin1String("text/plain")) { + ret.append(data.toString().toUtf8()); + } else { + ret.append(data.toByteArray()); + } + return ret; +} +#endif + +/*! + \internal + + This is an internal function. +*/ +void QMacPasteboardMime::initialize() +{ + if(globalMimeList()->isEmpty()) { + qAddPostRoutine(cleanup_mimes); + + //standard types that we wrap + new QMacPasteboardMimeTiff; +#ifdef Q_WS_MAC32 + // 10.6 does automatic synthesis to and from PICT to standard image types (like TIFF), + // so don't bother doing it ourselves, especially since it's not available in 64-bit. + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_6) + new QMacPasteboardMimePict; +#endif + new QMacPasteboardMimeUnicodeText; + new QMacPasteboardMimePlainText; + new QMacPasteboardMimeHTMLText; + new QMacPasteboardMimeFileUri; + new QMacPasteboardMimeUrl; + new QMacPasteboardMimeTypeName; + new QMacPasteboardMimeVCard; + //make sure our "non-standard" types are always last! --Sam + new QMacPasteboardMimeAny; +#ifdef QT3_SUPPORT + new QMacPasteboardMimeQt3Any; +#endif + } +} + +/*! + Returns the most-recently created QMacPasteboardMime of type \a t that can convert + between the \a mime and \a flav formats. Returns 0 if no such convertor + exists. +*/ +QMacPasteboardMime* +QMacPasteboardMime::convertor(uchar t, const QString &mime, QString flav) +{ + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { +#ifdef DEBUG_MIME_MAPS + qDebug("QMacPasteboardMime::convertor: seeing if %s (%d) can convert %s to %d[%c%c%c%c] [%d]", + (*it)->convertorName().toLatin1().constData(), + (*it)->type & t, mime.toLatin1().constData(), + flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF, + (*it)->canConvert(mime,flav)); + for(int i = 0; i < (*it)->countFlavors(); ++i) { + int f = (*it)->flavor(i); + qDebug(" %d) %d[%c%c%c%c] [%s]", i, f, + (f >> 24) & 0xFF, (f >> 16) & 0xFF, (f >> 8) & 0xFF, (f) & 0xFF, + (*it)->convertorName().toLatin1().constData()); + } +#endif + if(((*it)->type & t) && (*it)->canConvert(mime, flav)) + return (*it); + } + return 0; +} +/*! + Returns a MIME type of type \a t for \a flav, or 0 if none exists. +*/ +QString QMacPasteboardMime::flavorToMime(uchar t, QString flav) +{ + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { +#ifdef DEBUG_MIME_MAPS + qDebug("QMacMIme::flavorToMime: attempting %s (%d) for flavor %d[%c%c%c%c] [%s]", + (*it)->convertorName().toLatin1().constData(), + (*it)->type & t, flav, (flav >> 24) & 0xFF, (flav >> 16) & 0xFF, (flav >> 8) & 0xFF, (flav) & 0xFF, + (*it)->mimeFor(flav).toLatin1().constData()); + +#endif + if((*it)->type & t) { + QString mimeType = (*it)->mimeFor(flav); + if(!mimeType.isNull()) + return mimeType; + } + } + return QString(); +} + +/*! + Returns a list of all currently defined QMacPasteboardMime objects of type \a t. +*/ +QList<QMacPasteboardMime*> QMacPasteboardMime::all(uchar t) +{ + MimeList ret; + MimeList *mimes = globalMimeList(); + for(MimeList::const_iterator it = mimes->constBegin(); it != mimes->constEnd(); ++it) { + if((*it)->type & t) + ret.append((*it)); + } + return ret; +} + + +/*! + \fn QString QMacPasteboardMime::convertorName() + + Returns a name for the convertor. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn bool QMacPasteboardMime::canConvert(const QString &mime, QString flav) + + Returns true if the convertor can convert (both ways) between + \a mime and \a flav; otherwise returns false. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QString QMacPasteboardMime::mimeFor(QString flav) + + Returns the MIME UTI used for Mac flavor \a flav, or 0 if this + convertor does not support \a flav. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QString QMacPasteboardMime::flavorFor(const QString &mime) + + Returns the Mac UTI used for MIME type \a mime, or 0 if this + convertor does not support \a mime. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QVariant QMacPasteboardMime::convertToMime(const QString &mime, QList<QByteArray> data, QString flav) + + Returns \a data converted from Mac UTI \a flav to MIME type \a + mime. + + Note that Mac flavors must all be self-terminating. The input \a + data may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QList<QByteArray> QMacPasteboardMime::convertFromMime(const QString &mime, QVariant data, QString flav) + + Returns \a data converted from MIME type \a mime + to Mac UTI \a flav. + + Note that Mac flavors must all be self-terminating. The return + value may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qmultitouch_mac.mm b/src/gui/platforms/mac/qmultitouch_mac.mm new file mode 100644 index 0000000000..d9e845a01c --- /dev/null +++ b/src/gui/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 <private/qmultitouch_mac_p.h> +#include <qcursor.h> + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches; +QPointF QCocoaTouch::_screenReferencePos; +QPointF QCocoaTouch::_trackpadReferencePos; +int QCocoaTouch::_idAssignmentCount = 0; +int QCocoaTouch::_touchCount = 0; +bool QCocoaTouch::_updateInternalStateOnly = true; + +QCocoaTouch::QCocoaTouch(NSTouch *nstouch) +{ + if (_currentTouches.size() == 0) + _idAssignmentCount = 0; + + _touchPoint.setId(_idAssignmentCount++); + _touchPoint.setPressure(1.0); + _identity = qint64([nstouch identity]); + _currentTouches.insert(_identity, this); + updateTouchData(nstouch, NSTouchPhaseBegan); +} + +QCocoaTouch::~QCocoaTouch() +{ + _currentTouches.remove(_identity); +} + +void QCocoaTouch::updateTouchData(NSTouch *nstouch, NSTouchPhase phase) +{ + if (_touchCount == 1) + _touchPoint.setState(toTouchPointState(phase) | Qt::TouchPointPrimary); + else + _touchPoint.setState(toTouchPointState(phase)); + + // From the normalized position on the trackpad, calculate + // where on screen the touchpoint should be according to the + // reference position: + NSPoint npos = [nstouch normalizedPosition]; + QPointF qnpos = QPointF(npos.x, 1 - npos.y); + _touchPoint.setNormalizedPos(qnpos); + + if (_touchPoint.id() == 0 && phase == NSTouchPhaseBegan) { + _trackpadReferencePos = qnpos; + _screenReferencePos = QCursor::pos(); + } + + NSSize dsize = [nstouch deviceSize]; + float ppiX = (qnpos.x() - _trackpadReferencePos.x()) * dsize.width; + float ppiY = (qnpos.y() - _trackpadReferencePos.y()) * dsize.height; + QPointF relativePos = _trackpadReferencePos - QPointF(ppiX, ppiY); + _touchPoint.setScreenPos(_screenReferencePos - relativePos); +} + +QCocoaTouch *QCocoaTouch::findQCocoaTouch(NSTouch *nstouch) +{ + qint64 identity = qint64([nstouch identity]); + if (_currentTouches.contains(identity)) + return _currentTouches.value(identity); + return 0; +} + +Qt::TouchPointState QCocoaTouch::toTouchPointState(NSTouchPhase nsState) +{ + Qt::TouchPointState qtState = Qt::TouchPointReleased; + switch (nsState) { + case NSTouchPhaseBegan: + qtState = Qt::TouchPointPressed; + break; + case NSTouchPhaseMoved: + qtState = Qt::TouchPointMoved; + break; + case NSTouchPhaseStationary: + qtState = Qt::TouchPointStationary; + break; + case NSTouchPhaseEnded: + case NSTouchPhaseCancelled: + qtState = Qt::TouchPointReleased; + break; + default: + break; + } + return qtState; +} + +QList<QTouchEvent::TouchPoint> +QCocoaTouch::getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch) +{ + QMap<int, QTouchEvent::TouchPoint> touchPoints; + NSSet *ended = [event touchesMatchingPhase:NSTouchPhaseEnded | NSTouchPhaseCancelled inView:nil]; + NSSet *active = [event + touchesMatchingPhase:NSTouchPhaseBegan | NSTouchPhaseMoved | NSTouchPhaseStationary + inView:nil]; + _touchCount = [active count]; + + // First: remove touches that were ended by the user. If we are + // currently not accepting single touches, a corresponding 'begin' + // has never been send to the app for these events. + // So should therefore not send the following removes either. + + for (int i=0; i<int([ended count]); ++i) { + NSTouch *touch = [[ended allObjects] objectAtIndex:i]; + QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch); + if (qcocoaTouch) { + qcocoaTouch->updateTouchData(touch, [touch phase]); + if (!_updateInternalStateOnly) + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + delete qcocoaTouch; + } + } + + bool wasUpdateInternalStateOnly = _updateInternalStateOnly; + _updateInternalStateOnly = !acceptSingleTouch && _touchCount < 2; + + // Next: update, or create, existing touches. + // We always keep track of all touch points, even + // when not accepting single touches. + + for (int i=0; i<int([active count]); ++i) { + NSTouch *touch = [[active allObjects] objectAtIndex:i]; + QCocoaTouch *qcocoaTouch = findQCocoaTouch(touch); + if (!qcocoaTouch) + qcocoaTouch = new QCocoaTouch(touch); + else + qcocoaTouch->updateTouchData(touch, wasUpdateInternalStateOnly ? NSTouchPhaseBegan : [touch phase]); + if (!_updateInternalStateOnly) + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + } + + // Next: sadly, we need to check that our touch hash is in + // sync with cocoa. This is typically not the case after a system + // gesture happend (like a four-finger-swipe to show expose). + + if (_touchCount != _currentTouches.size()) { + // Remove all instances, and basically start from scratch: + touchPoints.clear(); + foreach (QCocoaTouch *qcocoaTouch, _currentTouches.values()) { + if (!_updateInternalStateOnly) { + qcocoaTouch->_touchPoint.setState(Qt::TouchPointReleased); + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + } + delete qcocoaTouch; + } + _currentTouches.clear(); + _updateInternalStateOnly = !acceptSingleTouch; + return touchPoints.values(); + } + + // Finally: If this call _started_ to reject single + // touches, we need to fake a relase for the remaining + // touch now (and refake a begin for it later, if needed). + + if (_updateInternalStateOnly && !wasUpdateInternalStateOnly && !_currentTouches.isEmpty()) { + QCocoaTouch *qcocoaTouch = _currentTouches.values().first(); + qcocoaTouch->_touchPoint.setState(Qt::TouchPointReleased); + touchPoints.insert(qcocoaTouch->_touchPoint.id(), qcocoaTouch->_touchPoint); + // Since this last touch also will end up beeing the first + // touch (if the user adds a second finger without lifting + // the first), we promote it to be the primary touch: + qcocoaTouch->_touchPoint.setId(0); + _idAssignmentCount = 1; + } + + return touchPoints.values(); +} + +#endif + +QT_END_NAMESPACE + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + diff --git a/src/gui/platforms/mac/qmultitouch_mac_p.h b/src/gui/platforms/mac/qmultitouch_mac_p.h new file mode 100644 index 0000000000..16be930d0a --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#endif + +#include <qevent.h> +#include <qhash.h> +#include <QtCore> + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA + +class QCocoaTouch +{ + public: + static QList<QTouchEvent::TouchPoint> getCurrentTouchPointList(NSEvent *event, bool acceptSingleTouch); + static void setMouseInDraggingState(bool inDraggingState); + + private: + static QHash<qint64, QCocoaTouch*> _currentTouches; + static QPointF _screenReferencePos; + static QPointF _trackpadReferencePos; + static int _idAssignmentCount; + static int _touchCount; + static bool _updateInternalStateOnly; + + QTouchEvent::TouchPoint _touchPoint; + qint64 _identity; + + QCocoaTouch(NSTouch *nstouch); + ~QCocoaTouch(); + + void updateTouchData(NSTouch *nstouch, NSTouchPhase phase); + static QCocoaTouch *findQCocoaTouch(NSTouch *nstouch); + static Qt::TouchPointState toTouchPointState(NSTouchPhase nsState); +}; + +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE + +#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + +#endif // QMULTITOUCH_MAC_P_H + diff --git a/src/gui/platforms/mac/qnsframeview_mac_p.h b/src/gui/platforms/mac/qnsframeview_mac_p.h new file mode 100644 index 0000000000..6ec3f64efa --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> + +@interface NSFrameView : NSView +{ + unsigned int styleMask; + NSString *_title; + NSCell *titleCell; + NSButton *closeButton; + NSButton *zoomButton; + NSButton *minimizeButton; + char resizeByIncrement; + char frameNeedsDisplay; + unsigned char tabViewCount; + NSSize resizeParameter; + int shadowState; +} + ++ (void)initialize; ++ (void)initTitleCell:fp8 styleMask:(unsigned int)fp12; ++ (struct _NSRect)frameRectForContentRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSRect)contentRectForFrameRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (struct _NSSize)minContentSizeForMinFrameSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (unsigned int)_validateStyleMask:(unsigned int)fp8; +- initWithFrame:(struct _NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- initWithFrame:(struct _NSRect)fp8; +- (void)dealloc; +- (void)shapeWindow; +- (void)tileAndSetWindowShape:(char)fp8; +- (void)tile; +- (void)drawRect:(struct _NSRect)fp8; +- (void)_drawFrameRects:(struct _NSRect)fp8; +- (void)drawFrame:(struct _NSRect)fp8; +- (void)drawThemeContentFill:(struct _NSRect)fp8 inView:fp24; +- (void)drawWindowBackgroundRect:(struct _NSRect)fp8; +- (void)drawWindowBackgroundRegion:(void *)fp8; +- (float)contentAlpha; +- (void)_windowChangedKeyState; +- (void)_updateButtonState; +- (char)_isSheet; +- (char)_isUtility; +- (void)setShadowState:(int)fp8; +- (int)shadowState; +- (char)_canHaveToolbar; +- (char)_toolbarIsInTransition; +- (char)_toolbarIsShown; +- (char)_toolbarIsHidden; +- (void)_showToolbarWithAnimation:(char)fp8; +- (void)_hideToolbarWithAnimation:(char)fp8; +- (float)_distanceFromToolbarBaseToTitlebar; +- (int)_shadowType; +- (unsigned int)_shadowFlags; +- (void)_setShadowParameters; +- (void)_drawFrameShadowAndFlushContext:fp8; +- (void)setUpGState; +- (void)adjustHalftonePhase; +- (void)systemColorsDidChange:fp8; +- frameColor; +- contentFill; +- (void)tabViewAdded; +- (void)tabViewRemoved; +- title; +- (void)setTitle:fp8; +- titleCell; +- (void)initTitleCell:fp8; +- (void)setResizeIncrements:(struct _NSSize)fp8; +- (struct _NSSize)resizeIncrements; +- (void)setAspectRatio:(struct _NSSize)fp8; +- (struct _NSSize)aspectRatio; +- (unsigned int)styleMask; +- representedFilename; +- (void)setRepresentedFilename:fp8; +- (void)setDocumentEdited:(char)fp8; +- (void)_setFrameNeedsDisplay:(char)fp8; +- (char)frameNeedsDisplay; +- titleFont; +- (struct _NSRect)_maxTitlebarTitleRect; +- (struct _NSRect)titlebarRect; +- (void)_setUtilityWindow:(char)fp8; +- (void)_setNonactivatingPanel:(char)fp8; +- (void)setIsClosable:(char)fp8; +- (void)setIsResizable:(char)fp8; +- closeButton; +- minimizeButton; +- zoomButton; +- (struct _NSSize)miniaturizedSize; +- (void)_clearDragMargins; +- (void)_resetDragMargins; +- (void)setTitle:fp8 andDefeatWrap:(char)fp12; +- (struct _NSRect)frameRectForContentRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSRect)contentRectForFrameRect:(struct _NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; +- (struct _NSRect)dragRectForFrameRect:(struct _NSRect)fp8; +- (struct _NSRect)contentRect; +- (struct _NSSize)minFrameSize; +- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(struct _NSRect)fp8 isVisibleRect:(char)fp24 rectIsVisibleRectForView:fp28 topView:(char)fp32; + +@end diff --git a/src/gui/platforms/mac/qnsthemeframe_mac_p.h b/src/gui/platforms/mac/qnsthemeframe_mac_p.h new file mode 100644 index 0000000000..2cb4916c06 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#import "qnstitledframe_mac_p.h" + +@interface NSThemeFrame : NSTitledFrame +{ + NSButton *toolbarButton; + int toolbarVisibleStatus; + NSImage *showToolbarTransitionImage; + NSSize showToolbarPreWindowSize; + NSButton *modeButton; + int leftGroupTrackingTagNum; + int rightGroupTrackingTagNum; + char mouseInsideLeftGroup; + char mouseInsideRightGroup; + int widgetState; + NSString *displayName; +} + ++ (void)initialize; ++ (float)_windowBorderThickness:(unsigned int)fp8; ++ (float)_minXWindowBorderWidth:(unsigned int)fp8; ++ (float)_maxXWindowBorderWidth:(unsigned int)fp8; ++ (float)_minYWindowBorderHeight:(unsigned int)fp8; ++ (float)_windowTitlebarButtonSpacingWidth:(unsigned int)fp8; ++ (float)_windowFileButtonSpacingWidth:(unsigned int)fp8; ++ (float)_minXTitlebarWidgetInset:(unsigned int)fp8; ++ (float)_maxXTitlebarWidgetInset:(unsigned int)fp8; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (float)_windowSideTitlebarTitleMinWidth:(unsigned int)fp8; ++ (float)_windowTitlebarTitleMinHeight:(unsigned int)fp8; ++ (float)_sideTitlebarWidth:(unsigned int)fp8; ++ (float)_titlebarHeight:(unsigned int)fp8; ++ (float)_resizeHeight:(unsigned int)fp8; ++ (char)_resizeFromEdge; ++ (struct _NSSize)sizeOfTitlebarButtons:(unsigned int)fp8; ++ (float)_contentToFrameMinXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMaxXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinYHeight:(unsigned int)fp8; ++ (float)_contentToFrameMaxYHeight:(unsigned int)fp8; ++ (unsigned int)_validateStyleMask:(unsigned int)fp8; +- (struct _NSSize)_topCornerSize; +- (struct _NSSize)_bottomCornerSize; +- (void *)_createWindowOpaqueShape; +- (void)shapeWindow; +- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)fp8 isVisibleRect:(char)fp24 rectIsVisibleRectForView:fp28 topView:(char)fp32; +- (void *)_regionForOpaqueDescendants:(NSRect)fp8 forMove:(char)fp24; +- (void)_drawFrameInterior:(NSRect *)fp8 clip:(NSRect)fp12; +- (void)_setTextShadow:(char)fp8; +- (void)_drawTitleBar:(NSRect)fp8; +- (void)_drawResizeIndicators:(NSRect)fp8; +- (void)_drawFrameRects:(NSRect)fp8; +- (void)drawFrame:(NSRect)fp8; +- contentFill; +- (void)viewDidEndLiveResize; +- (float)contentAlpha; +- (void)setThemeFrameWidgetState:(int)fp8; +- (char)constrainResizeEdge:(int *)fp8 withDelta:(struct _NSSize)fp12 elapsedTime:(float)fp20; +- (void)addFileButton:fp8; +- (void)_updateButtons; +- (void)_updateButtonState; +- newCloseButton; +- newZoomButton; +- newMiniaturizeButton; +- newToolbarButton; +- newFileButton; +- (void)_resetTitleBarButtons; +- (void)setDocumentEdited:(char)fp8; +- toolbarButton; +- modeButton; +- initWithFrame:(NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- (void)dealloc; +- (void)setFrameSize:(struct _NSSize)fp8; +- (char)_canHaveToolbar; +- (char)_toolbarIsInTransition; +- (char)_toolbarIsShown; +- (char)_toolbarIsHidden; +- _toolbarView; +- _toolbar; +- (float)_distanceFromToolbarBaseToTitlebar; +- (unsigned int)_shadowFlags; +- (NSRect)frameRectForContentRect:(NSRect)fp8 styleMask:(unsigned int)fp24; +- (NSRect)contentRectForFrameRect:(NSRect)fp8 styleMask:(unsigned int)fp24; +- (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; +- (NSRect)contentRect; +- (NSRect)_contentRectExcludingToolbar; +- (NSRect)_contentRectIncludingToolbarAtHome; +- (void)_setToolbarShowHideResizeWeightingOptimizationOn:(char)fp8; +- (char)_usingToolbarShowHideWeightingOptimization; +- (void)handleSetFrameCommonRedisplay; +- (void)_startLiveResizeAsTopLevel; +- (void)_endLiveResizeAsTopLevel; +- (void)_growContentReshapeContentAndToolbarView:(int)fp8 animate:(char)fp12; +- (char)_growWindowReshapeContentAndToolbarView:(int)fp8 animate:(char)fp12; +- (void)_reshapeContentAndToolbarView:(int)fp8 resizeWindow:(char)fp12 animate:(char)fp16; +- (void)_toolbarFrameSizeChanged:fp8 oldSize:(struct _NSSize)fp12; +- (void)_syncToolbarPosition; +- (void)_showHideToolbar:(int)fp8 resizeWindow:(char)fp12 animate:(char)fp16; +- (void)_showToolbarWithAnimation:(char)fp8; +- (void)_hideToolbarWithAnimation:(char)fp8; +- (void)_drawToolbarTransitionIfNecessary; +- (void)drawRect:(NSRect)fp8; +- (void)resetCursorRects; +- (char)shouldBeTreatedAsInkEvent:fp8; +- (char)_shouldBeTreatedAsInkEventInInactiveWindow:fp8; +//- hitTest:(struct _NSPoint)fp8; // collides with hittest in qcocoasharedwindowmethods_mac_p.h +- (NSRect)_leftGroupRect; +- (NSRect)_rightGroupRect; +- (void)_updateWidgets; +- (void)_updateMouseTracking; +- (void)mouseEntered:fp8; +- (void)mouseExited:fp8; +- (void)_setMouseEnteredGroup:(char)fp8 entered:(char)fp12; +- (char)_mouseInGroup:fp8; +- (struct _NSSize)miniaturizedSize; +- (float)_minXTitlebarDecorationMinWidth; +- (float)_maxXTitlebarDecorationMinWidth; +- (struct _NSSize)minFrameSize; +- (float)_windowBorderThickness; +- (float)_windowTitlebarXResizeBorderThickness; +- (float)_windowTitlebarYResizeBorderThickness; +- (float)_windowResizeBorderThickness; +- (float)_minXWindowBorderWidth; +- (float)_maxXWindowBorderWidth; +- (float)_minYWindowBorderHeight; +- (float)_maxYWindowBorderHeight; +- (float)_minYTitlebarButtonsOffset; +- (float)_minYTitlebarTitleOffset; +- (float)_sideTitlebarWidth; +- (float)_titlebarHeight; +- (NSRect)_titlebarTitleRect; +- (NSRect)titlebarRect; +- (float)_windowTitlebarTitleMinHeight; +- (struct _NSSize)_sizeOfTitlebarFileButton; +- (struct _NSSize)sizeOfTitlebarToolbarButton; +- (float)_windowTitlebarButtonSpacingWidth; +- (float)_windowFileButtonSpacingWidth; +- (float)_minXTitlebarWidgetInset; +- (float)_maxXTitlebarWidgetInset; +- (float)_minXTitlebarButtonsWidth; +- (float)_maxXTitlebarButtonsWidth; +- (struct _NSPoint)_closeButtonOrigin; +- (struct _NSPoint)_zoomButtonOrigin; +- (struct _NSPoint)_collapseButtonOrigin; +- (struct _NSPoint)_toolbarButtonOrigin; +- (struct _NSPoint)_fileButtonOrigin; +- (void)_tileTitlebar; +- (NSRect)_commandPopupRect; +- (void)_resetDragMargins; +- (float)_maxYTitlebarDragHeight; +- (float)_minXTitlebarDragWidth; +- (float)_maxXTitlebarDragWidth; +- (float)_contentToFrameMinXWidth; +- (float)_contentToFrameMaxXWidth; +- (float)_contentToFrameMinYHeight; +- (float)_contentToFrameMaxYHeight; +- (float)_windowResizeCornerThickness; +- (NSRect)_minYResizeRect; +- (NSRect)_minYminXResizeRect; +- (NSRect)_minYmaxXResizeRect; +- (NSRect)_minXResizeRect; +- (NSRect)_minXminYResizeRect; +- (NSRect)_minXmaxYResizeRect; +- (NSRect)_maxYResizeRect; +- (NSRect)_maxYminXResizeRect; +- (NSRect)_maxYmaxXResizeRect; +- (NSRect)_maxXResizeRect; +- (NSRect)_maxXminYResizeRect; +- (NSRect)_maxXmaxYResizeRect; +- (NSRect)_minXTitlebarResizeRect; +- (NSRect)_maxXTitlebarResizeRect; +- (NSRect)_minXBorderRect; +- (NSRect)_maxXBorderRect; +- (NSRect)_maxYBorderRect; +- (NSRect)_minYBorderRect; +- (void)_setUtilityWindow:(char)fp8; +- (char)_isUtility; +- (float)_sheetHeightAdjustment; +- (void)_setSheet:(char)fp8; +- (char)_isSheet; +- (char)_isResizable; +- (char)_isClosable; +- (char)_isMiniaturizable; +- (char)_hasToolbar; +- (NSRect)_growBoxRect; +- (void)_drawGrowBoxWithClip:(NSRect)fp8; +- (char)_inactiveButtonsNeedMask; +- (void)mouseDown:fp8; +- _displayName; +- (void)_setDisplayName:fp8; + +@end diff --git a/src/gui/platforms/mac/qnstitledframe_mac_p.h b/src/gui/platforms/mac/qnstitledframe_mac_p.h new file mode 100644 index 0000000000..4eb5332194 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#import "qnsframeview_mac_p.h" + + +@interface NSTitledFrame : NSFrameView +{ + int resizeFlags; + id fileButton; /* NSDocumentDragButton* */ + NSSize titleCellSize; +} + ++ (float)_windowBorderThickness:(unsigned int)fp8; ++ (float)_minXWindowBorderWidth:(unsigned int)fp8; ++ (float)_maxXWindowBorderWidth:(unsigned int)fp8; ++ (float)_minYWindowBorderHeight:(unsigned int)fp8; ++ (char)_resizeFromEdge; ++ (NSRect)frameRectForContentRect:(NSRect)fp8 styleMask:(unsigned int)fp24; ++ (NSRect)contentRectForFrameRect:(NSRect)fp8 styleMask:(unsigned int)fp24; ++ (struct _NSSize)minFrameSizeForMinContentSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (struct _NSSize)minContentSizeForMinFrameSize:(struct _NSSize)fp8 styleMask:(unsigned int)fp16; ++ (float)minFrameWidthWithTitle:fp8 styleMask:(unsigned int)fp12; ++ (struct _NSSize)_titleCellSizeForTitle:fp8 styleMask:(unsigned int)fp12; ++ (float)_titleCellHeight:(unsigned int)fp8; ++ (float)_windowTitlebarTitleMinHeight:(unsigned int)fp8; ++ (float)_titlebarHeight:(unsigned int)fp8; ++ (struct _NSSize)sizeOfTitlebarButtons:(unsigned int)fp8; ++ (float)windowTitlebarLinesSpacingWidth:(unsigned int)fp8; ++ (float)windowTitlebarTitleLinesSpacingWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMaxXWidth:(unsigned int)fp8; ++ (float)_contentToFrameMinYHeight:(unsigned int)fp8; ++ (float)_contentToFrameMaxYHeight:(unsigned int)fp8; +- initWithFrame:(NSRect)fp8 styleMask:(unsigned int)fp24 owner:fp28; +- (void)dealloc; +- (void)setIsClosable:(char)fp8; +- (void)setIsResizable:(char)fp8; +- (void)_resetTitleFont; +- (void)_setUtilityWindow:(char)fp8; +- (char)isOpaque; +- (char)worksWhenModal; +- (void)propagateFrameDirtyRects:(NSRect)fp8; +- (void)_showDrawRect:(NSRect)fp8; +- (void)_drawFrameInterior:(NSRect *)fp8 clip:(NSRect)fp12; +- (void)drawFrame:(NSRect)fp8; +- (void)_drawFrameRects:(NSRect)fp8; +- (void)_drawTitlebar:(NSRect)fp8; +- (void)_drawTitlebarPattern:(int)fp8 inRect:(NSRect)fp12 clippedByRect:(NSRect)fp28 forKey:(char)fp44 alignment:(int)fp48; +- (void)_drawTitlebarLines:(int)fp8 inRect:(NSRect)fp12 clippedByRect:(NSRect)fp28; +- frameHighlightColor; +- frameShadowColor; +- (void)setFrameSize:(struct _NSSize)fp8; +- (void)setFrameOrigin:(struct _NSPoint)fp8; +- (void)tileAndSetWindowShape:(char)fp8; +- (void)tile; +- (void)_tileTitlebar; +- (void)setTitle:fp8; +- (char)_shouldRepresentFilename; +- (void)setRepresentedFilename:fp8; +- (void)_drawTitleStringIn:(NSRect)fp8 withColor:fp24; +- titleFont; +- (void)_drawResizeIndicators:(NSRect)fp8; +- titleButtonOfClass:(Class)fp8; +- initTitleButton:fp8; +- newCloseButton; +- newZoomButton; +- newMiniaturizeButton; +- newFileButton; +- fileButton; +- (void)_removeButtons; +- (void)_updateButtons; +- (char)_eventInTitlebar:fp8; +- (char)acceptsFirstMouse:fp8; +- (void)mouseDown:fp8; +- (void)mouseUp:fp8; +- (void)rightMouseDown:fp8; +- (void)rightMouseUp:fp8; +- (int)resizeEdgeForEvent:fp8; +- (struct _NSSize)_resizeDeltaFromPoint:(struct _NSPoint)fp8 toEvent:fp16; +- (NSRect)_validFrameForResizeFrame:(NSRect)fp8 fromResizeEdge:(int)fp24; +- (NSRect)frame:(NSRect)fp8 resizedFromEdge:(int)fp24 withDelta:(struct _NSSize)fp28; +- (char)constrainResizeEdge:(int *)fp8 withDelta:(struct _NSSize)fp12 elapsedTime:(float)fp20; +- (void)resizeWithEvent:fp8; +- (int)resizeFlags; +- (void)resetCursorRects; +- (void)setDocumentEdited:(char)fp8; +- (struct _NSSize)miniaturizedSize; +- (struct _NSSize)minFrameSize; +- (float)_windowBorderThickness; +- (float)_windowTitlebarXResizeBorderThickness; +- (float)_windowTitlebarYResizeBorderThickness; +- (float)_windowResizeBorderThickness; +- (float)_minXWindowBorderWidth; +- (float)_maxXWindowBorderWidth; +- (float)_minYWindowBorderHeight; +- (void)_invalidateTitleCellSize; +- (void)_invalidateTitleCellWidth; +- (float)_titleCellHeight; +- (struct _NSSize)_titleCellSize; +- (float)_titlebarHeight; +- (NSRect)titlebarRect; +- (NSRect)_maxTitlebarTitleRect; +- (NSRect)_titlebarTitleRect; +- (float)_windowTitlebarTitleMinHeight; +- (NSRect)dragRectForFrameRect:(NSRect)fp8; +- (struct _NSSize)sizeOfTitlebarButtons; +- (struct _NSSize)_sizeOfTitlebarFileButton; +- (float)_windowTitlebarButtonSpacingWidth; +- (float)_minXTitlebarButtonsWidth; +- (float)_maxXTitlebarButtonsWidth; +- (int)_numberOfTitlebarLines; +- (float)windowTitlebarLinesSpacingWidth; +- (float)windowTitlebarTitleLinesSpacingWidth; +- (float)_minLinesWidthWithSpace; +- (NSRect)_minXTitlebarLinesRectWithTitleCellRect:(NSRect)fp8; +- (NSRect)_maxXTitlebarLinesRectWithTitleCellRect:(NSRect)fp8; +- (float)_minXTitlebarDecorationMinWidth; +- (float)_maxXTitlebarDecorationMinWidth; +- (struct _NSPoint)_closeButtonOrigin; +- (struct _NSPoint)_zoomButtonOrigin; +- (struct _NSPoint)_collapseButtonOrigin; +- (struct _NSPoint)_fileButtonOrigin; +- (float)_maxYTitlebarDragHeight; +- (float)_minXTitlebarDragWidth; +- (float)_maxXTitlebarDragWidth; +- (float)_contentToFrameMinXWidth; +- (float)_contentToFrameMaxXWidth; +- (float)_contentToFrameMinYHeight; +- (float)_contentToFrameMaxYHeight; +- (NSRect)contentRect; +- (float)_windowResizeCornerThickness; +- (NSRect)_minYResizeRect; +- (NSRect)_minYminXResizeRect; +- (NSRect)_minYmaxXResizeRect; +- (NSRect)_minXResizeRect; +- (NSRect)_minXminYResizeRect; +- (NSRect)_minXmaxYResizeRect; +- (NSRect)_maxYResizeRect; +- (NSRect)_maxYminXResizeRect; +- (NSRect)_maxYmaxXResizeRect; +- (NSRect)_maxXResizeRect; +- (NSRect)_maxXminYResizeRect; +- (NSRect)_maxXmaxYResizeRect; +- (NSRect)_minXTitlebarResizeRect; +- (NSRect)_maxXTitlebarResizeRect; +- (NSRect)_minXBorderRect; +- (NSRect)_maxXBorderRect; +- (NSRect)_maxYBorderRect; +- (NSRect)_minYBorderRect; + +@end diff --git a/src/gui/platforms/mac/qpaintdevice_mac.cpp b/src/gui/platforms/mac/qpaintdevice_mac.cpp new file mode 100644 index 0000000000..245408a0b0 --- /dev/null +++ b/src/gui/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 <qdebug.h> +#include <private/qt_mac_p.h> +#include <private/qprintengine_mac_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qpixmap_raster_p.h> + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + Internal variables and functions + *****************************************************************************/ + +/*! \internal */ +float qt_mac_defaultDpi_x() +{ + // Mac OS X currently assumes things to be 72 dpi. + // (see http://developer.apple.com/releasenotes/GraphicsImaging/RN-ResolutionIndependentUI/) + // This may need to be re-worked as we go further in the resolution-independence stuff. + return 72; +} + +/*! \internal */ +float qt_mac_defaultDpi_y() +{ + // Mac OS X currently assumes things to be 72 dpi. + // (see http://developer.apple.com/releasenotes/GraphicsImaging/RN-ResolutionIndependentUI/) + // This may need to be re-worked as we go further in the resolution-independence stuff. + return 72; +} + + +/*! \internal + + Returns the QuickDraw CGrafPtr of the paint device. 0 is returned + if it can't be obtained. Do not hold the pointer around for long + as it can be relocated. + + \warning This function is only available on Mac OS X. +*/ + +Q_GUI_EXPORT GrafPtr qt_mac_qd_context(const QPaintDevice *device) +{ + if (device->devType() == QInternal::Pixmap) { + return static_cast<GrafPtr>(static_cast<const QPixmap *>(device)->macQDHandle()); + } else if(device->devType() == QInternal::Widget) { + return static_cast<GrafPtr>(static_cast<const QWidget *>(device)->macQDHandle()); + } else if(device->devType() == QInternal::Printer) { + QPaintEngine *engine = static_cast<const QPrinter *>(device)->paintEngine(); + return static_cast<GrafPtr>(static_cast<const QMacPrintEngine *>(engine)->handle()); + } + return 0; +} + +extern CGColorSpaceRef qt_mac_colorSpaceForDeviceType(const QPaintDevice *pdev); + +/*! \internal + + Returns the CoreGraphics CGContextRef of the paint device. 0 is + returned if it can't be obtained. It is the caller's responsiblity to + CGContextRelease the context when finished using it. + + \warning This function is only available on Mac OS X. +*/ + +Q_GUI_EXPORT CGContextRef qt_mac_cg_context(const QPaintDevice *pdev) +{ + if (pdev->devType() == QInternal::Pixmap) { + const QPixmap *pm = static_cast<const QPixmap*>(pdev); + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + uint flags = kCGImageAlphaPremultipliedFirst; +#ifdef kCGBitmapByteOrder32Host //only needed because CGImage.h added symbols in the minor version + flags |= kCGBitmapByteOrder32Host; +#endif + CGContextRef ret = 0; + + // It would make sense to put this into a mac #ifdef'ed + // virtual function in the QPixmapData at some point + if (pm->data->classId() == QPixmapData::MacClass) { + const QMacPixmapData *pmData = static_cast<const QMacPixmapData*>(pm->data.data()); + ret = CGBitmapContextCreate(pmData->pixels, pmData->w, pmData->h, + 8, pmData->bytesPerRow, colorspace, + flags); + if(!ret) + qWarning("QPaintDevice: Unable to create context for pixmap (%d/%d/%d)", + pmData->w, pmData->h, (pmData->bytesPerRow * pmData->h)); + } else if (pm->data->classId() == QPixmapData::RasterClass) { + QImage *image = pm->data->buffer(); + ret = CGBitmapContextCreate(image->bits(), image->width(), image->height(), + 8, image->bytesPerLine(), colorspace, flags); + } + + CGContextTranslateCTM(ret, 0, pm->height()); + CGContextScaleCTM(ret, 1, -1); + return ret; + } else if (pdev->devType() == QInternal::Widget) { + CGContextRef ret = static_cast<CGContextRef>(static_cast<const QWidget *>(pdev)->macCGHandle()); + CGContextRetain(ret); + return ret; + } else if (pdev->devType() == QInternal::MacQuartz) { + return static_cast<const QMacQuartzPaintDevice *>(pdev)->cgContext(); + } + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qpaintengine_mac.cpp b/src/gui/platforms/mac/qpaintengine_mac.cpp new file mode 100644 index 0000000000..c6d061dea8 --- /dev/null +++ b/src/gui/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 <qbitmap.h> +#include <qpaintdevice.h> +#include <private/qpaintengine_mac_p.h> +#include <qpainterpath.h> +#include <qpixmapcache.h> +#include <private/qpaintengine_raster_p.h> +#include <private/qprintengine_mac_p.h> +#include <qprinter.h> +#include <qstack.h> +#include <qtextcodec.h> +#include <qwidget.h> +#include <qvarlengtharray.h> +#include <qdebug.h> +#include <qcoreapplication.h> +#include <qmath.h> + +#include <private/qfont_p.h> +#include <private/qfontengine_p.h> +#include <private/qfontengine_coretext_p.h> +#include <private/qfontengine_mac_p.h> +#include <private/qnumeric_p.h> +#include <private/qpainter_p.h> +#include <private/qpainterpath_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qt_mac_p.h> +#include <private/qtextengine_p.h> +#include <private/qwidget_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> + +#include <string.h> + +QT_BEGIN_NAMESPACE + +extern int qt_antialiasing_threshold; // QApplication.cpp + +/***************************************************************************** + External functions + *****************************************************************************/ +extern CGImageRef qt_mac_create_imagemask(const QPixmap &px, const QRectF &sr); //qpixmap_mac.cpp +extern QPoint qt_mac_posInWindow(const QWidget *w); //qwidget_mac.cpp +extern OSWindowRef qt_mac_window_for(const QWidget *); //qwidget_mac.cpp +extern CGContextRef qt_mac_cg_context(const QPaintDevice *); //qpaintdevice_mac.cpp +extern void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp +extern QPixmap qt_pixmapForBrush(int, bool); //qbrush.cpp + +void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform); + + +//Implemented for qt_mac_p.h +QMacCGContext::QMacCGContext(QPainter *p) +{ + QPaintEngine *pe = p->paintEngine(); + if (pe->type() == QPaintEngine::MacPrinter) + pe = static_cast<QMacPrintEngine*>(pe)->paintEngine(); + pe->syncState(); + context = 0; + if(pe->type() == QPaintEngine::CoreGraphics) + context = static_cast<QCoreGraphicsPaintEngine*>(pe)->handle(); + + int devType = p->device()->devType(); + if (pe->type() == QPaintEngine::Raster + && (devType == QInternal::Widget || + devType == QInternal::Pixmap || + 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<const QWidget *>(paintDevice) + : 0); +} + +inline static QCFType<CGColorRef> cgColorForQColor(const QColor &col, QPaintDevice *pdev) +{ + CGFloat components[] = { + qt_mac_convert_color_to_cg(col.red()), + qt_mac_convert_color_to_cg(col.green()), + qt_mac_convert_color_to_cg(col.blue()), + qt_mac_convert_color_to_cg(col.alpha()) + }; + return CGColorCreate(qt_mac_colorSpaceForDeviceType(pdev), components); +} + +// There's architectural problems with using native gradients +// on the Mac at the moment, so disable them. +// #define QT_MAC_USE_NATIVE_GRADIENTS + +#ifdef QT_MAC_USE_NATIVE_GRADIENTS +static bool drawGradientNatively(const QGradient *gradient) +{ + return gradient->spread() == QGradient::PadSpread; +} + +// gradiant callback +static void qt_mac_color_gradient_function(void *info, const CGFloat *in, CGFloat *out) +{ + QBrush *brush = static_cast<QBrush *>(info); + Q_ASSERT(brush && brush->gradient()); + + const QGradientStops stops = brush->gradient()->stops(); + const int n = stops.count(); + Q_ASSERT(n >= 1); + const QGradientStop *begin = stops.constBegin(); + const QGradientStop *end = begin + n; + + qreal p = in[0]; + const QGradientStop *i = begin; + while (i != end && i->first < p) + ++i; + + QRgb c; + if (i == begin) { + c = begin->second.rgba(); + } else if (i == end) { + c = (end - 1)->second.rgba(); + } else { + const QGradientStop &s1 = *(i - 1); + const QGradientStop &s2 = *i; + qreal p1 = s1.first; + qreal p2 = s2.first; + QRgb c1 = s1.second.rgba(); + QRgb c2 = s2.second.rgba(); + int idist = 256 * (p - p1) / (p2 - p1); + int dist = 256 - idist; + c = qRgba(INTERPOLATE_PIXEL_256(qRed(c1), dist, qRed(c2), idist), + INTERPOLATE_PIXEL_256(qGreen(c1), dist, qGreen(c2), idist), + INTERPOLATE_PIXEL_256(qBlue(c1), dist, qBlue(c2), idist), + INTERPOLATE_PIXEL_256(qAlpha(c1), dist, qAlpha(c2), idist)); + } + + out[0] = qt_mac_convert_color_to_cg(qRed(c)); + out[1] = qt_mac_convert_color_to_cg(qGreen(c)); + out[2] = qt_mac_convert_color_to_cg(qBlue(c)); + out[3] = qt_mac_convert_color_to_cg(qAlpha(c)); +} +#endif + +//clipping handling +void QCoreGraphicsPaintEnginePrivate::resetClip() +{ + static bool inReset = false; + if (inReset) + return; + inReset = true; + + CGAffineTransform old_xform = CGContextGetCTM(hd); + + //setup xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform)); + while (stackCount > 0) { + restoreGraphicsState(); + } + saveGraphicsState(); + inReset = false; + //reset xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGContextConcatCTM(hd, old_xform); +} + +static CGRect qt_mac_compose_rect(const QRectF &r, float off=0) +{ + return CGRectMake(r.x()+off, r.y()+off, r.width(), r.height()); +} + +static CGMutablePathRef qt_mac_compose_path(const QPainterPath &p, float off=0) +{ + CGMutablePathRef ret = CGPathCreateMutable(); + QPointF startPt; + for (int i=0; i<p.elementCount(); ++i) { + const QPainterPath::Element &elm = p.elementAt(i); + switch (elm.type) { + case QPainterPath::MoveToElement: + if(i > 0 + && p.elementAt(i - 1).x == startPt.x() + && p.elementAt(i - 1).y == startPt.y()) + CGPathCloseSubpath(ret); + startPt = QPointF(elm.x, elm.y); + CGPathMoveToPoint(ret, 0, elm.x+off, elm.y+off); + break; + case QPainterPath::LineToElement: + CGPathAddLineToPoint(ret, 0, elm.x+off, elm.y+off); + break; + case QPainterPath::CurveToElement: + Q_ASSERT(p.elementAt(i+1).type == QPainterPath::CurveToDataElement); + Q_ASSERT(p.elementAt(i+2).type == QPainterPath::CurveToDataElement); + CGPathAddCurveToPoint(ret, 0, + elm.x+off, elm.y+off, + p.elementAt(i+1).x+off, p.elementAt(i+1).y+off, + p.elementAt(i+2).x+off, p.elementAt(i+2).y+off); + i+=2; + break; + default: + qFatal("QCoreGraphicsPaintEngine::drawPath(), unhandled type: %d", elm.type); + break; + } + } + if(!p.isEmpty() + && p.elementAt(p.elementCount() - 1).x == startPt.x() + && p.elementAt(p.elementCount() - 1).y == startPt.y()) + CGPathCloseSubpath(ret); + return ret; +} + +CGColorSpaceRef QCoreGraphicsPaintEngine::m_genericColorSpace = 0; +QHash<CGDirectDisplayID, CGColorSpaceRef> QCoreGraphicsPaintEngine::m_displayColorSpaceHash; +bool QCoreGraphicsPaintEngine::m_postRoutineRegistered = false; + +CGColorSpaceRef QCoreGraphicsPaintEngine::macGenericColorSpace() +{ +#if 0 + if (!m_genericColorSpace) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + m_genericColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + } else +#endif + { + m_genericColorSpace = CGColorSpaceCreateDeviceRGB(); + } + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); + } + } + return m_genericColorSpace; +#else + // Just return the main display colorspace for the moment. + return macDisplayColorSpace(); +#endif +} + +/* + Ideally, we should pass the widget in here, and use CGGetDisplaysWithRect() etc. + to support multiple displays correctly. +*/ +CGColorSpaceRef QCoreGraphicsPaintEngine::macDisplayColorSpace(const QWidget *widget) +{ + CGColorSpaceRef colorSpace; + + CGDirectDisplayID displayID; + CMProfileRef displayProfile = 0; + if (widget == 0) { + displayID = CGMainDisplayID(); + } else { + const QRect &qrect = widget->window()->geometry(); + CGRect rect = CGRectMake(qrect.x(), qrect.y(), qrect.width(), qrect.height()); + CGDisplayCount throwAway; + CGDisplayErr dErr = CGGetDisplaysWithRect(rect, 1, &displayID, &throwAway); + if (dErr != kCGErrorSuccess) + return macDisplayColorSpace(0); // fall back on main display + } + if ((colorSpace = m_displayColorSpaceHash.value(displayID))) + return colorSpace; + + CMError err = CMGetProfileByAVID((CMDisplayIDType)displayID, &displayProfile); + if (err == noErr) { + colorSpace = CGColorSpaceCreateWithPlatformColorSpace(displayProfile); + } else if (widget) { + return macDisplayColorSpace(0); // fall back on main display + } + + if (colorSpace == 0) + colorSpace = CGColorSpaceCreateDeviceRGB(); + + m_displayColorSpaceHash.insert(displayID, colorSpace); + CMCloseProfile(displayProfile); + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); + } + return colorSpace; +} + +void QCoreGraphicsPaintEngine::cleanUpMacColorSpaces() +{ + if (m_genericColorSpace) { + CFRelease(m_genericColorSpace); + m_genericColorSpace = 0; + } + QHash<CGDirectDisplayID, CGColorSpaceRef>::const_iterator it = m_displayColorSpaceHash.constBegin(); + while (it != m_displayColorSpaceHash.constEnd()) { + if (it.value()) + CFRelease(it.value()); + ++it; + } + m_displayColorSpaceHash.clear(); +} + +void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig_xform) +{ + CGAffineTransform old_xform = CGAffineTransformIdentity; + if(orig_xform) { //setup xforms + old_xform = CGContextGetCTM(hd); + CGContextConcatCTM(hd, CGAffineTransformInvert(old_xform)); + CGContextConcatCTM(hd, *orig_xform); + } + + //do the clipping + CGContextBeginPath(hd); + if(rgn.isEmpty()) { + CGContextAddRect(hd, CGRectMake(0, 0, 0, 0)); + } else { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + QCFType<HIMutableShapeRef> shape = rgn.toHIMutableShape(); + Q_ASSERT(!HIShapeIsEmpty(shape)); + HIShapeReplacePathInCGContext(shape, hd); + } else +#endif + { + QVector<QRect> rects = rgn.rects(); + const int count = rects.size(); + for(int i = 0; i < count; i++) { + const QRect &r = rects[i]; + CGRect mac_r = CGRectMake(r.x(), r.y(), r.width(), r.height()); + CGContextAddRect(hd, mac_r); + } + } + + } + CGContextClip(hd); + + if(orig_xform) {//reset xforms + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGContextConcatCTM(hd, old_xform); + } +} + + +//pattern handling (tiling) +#if 1 +# define QMACPATTERN_MASK_MULTIPLIER 32 +#else +# define QMACPATTERN_MASK_MULTIPLIER 1 +#endif +class QMacPattern +{ +public: + QMacPattern() : as_mask(false), pdev(0), image(0) { data.bytes = 0; } + ~QMacPattern() { CGImageRelease(image); } + int width() { + if(image) + return CGImageGetWidth(image); + if(data.bytes) + return 8*QMACPATTERN_MASK_MULTIPLIER; + return data.pixmap.width(); + } + int height() { + if(image) + return CGImageGetHeight(image); + if(data.bytes) + return 8*QMACPATTERN_MASK_MULTIPLIER; + return data.pixmap.height(); + } + + //input + QColor foreground; + bool as_mask; + struct { + QPixmap pixmap; + const uchar *bytes; + } data; + QPaintDevice *pdev; + //output + CGImageRef image; +}; +static void qt_mac_draw_pattern(void *info, CGContextRef c) +{ + QMacPattern *pat = (QMacPattern*)info; + int w = 0, h = 0; + bool isBitmap = (pat->data.pixmap.depth() == 1); + if(!pat->image) { //lazy cache + if(pat->as_mask) { + Q_ASSERT(pat->data.bytes); + w = h = 8; +#if (QMACPATTERN_MASK_MULTIPLIER == 1) + CGDataProviderRef provider = CGDataProviderCreateWithData(0, pat->data.bytes, w*h, 0); + pat->image = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRelease(provider); +#else + const int numBytes = (w*h)/sizeof(uchar); + uchar xor_bytes[numBytes]; + for(int i = 0; i < numBytes; ++i) + xor_bytes[i] = pat->data.bytes[i] ^ 0xFF; + CGDataProviderRef provider = CGDataProviderCreateWithData(0, xor_bytes, w*h, 0); + CGImageRef swatch = CGImageMaskCreate(w, h, 1, 1, 1, provider, 0, false); + CGDataProviderRelease(provider); + + const QColor c0(0, 0, 0, 0), c1(255, 255, 255, 255); + QPixmap pm(w*QMACPATTERN_MASK_MULTIPLIER, h*QMACPATTERN_MASK_MULTIPLIER); + pm.fill(c0); + CGContextRef pm_ctx = qt_mac_cg_context(&pm); + CGContextSetFillColorWithColor(c, cgColorForQColor(c1, pat->pdev)); + CGRect rect = CGRectMake(0, 0, w, h); + for(int x = 0; x < QMACPATTERN_MASK_MULTIPLIER; ++x) { + rect.origin.x = x * w; + for(int y = 0; y < QMACPATTERN_MASK_MULTIPLIER; ++y) { + rect.origin.y = y * h; + qt_mac_drawCGImage(pm_ctx, &rect, swatch); + } + } + pat->image = qt_mac_create_imagemask(pm, pm.rect()); + CGImageRelease(swatch); + CGContextRelease(pm_ctx); + w *= QMACPATTERN_MASK_MULTIPLIER; + h *= QMACPATTERN_MASK_MULTIPLIER; +#endif + } else { + w = pat->data.pixmap.width(); + h = pat->data.pixmap.height(); + if (isBitmap) + pat->image = qt_mac_create_imagemask(pat->data.pixmap, pat->data.pixmap.rect()); + else + pat->image = (CGImageRef)pat->data.pixmap.macCGHandle(); + } + } else { + w = CGImageGetWidth(pat->image); + h = CGImageGetHeight(pat->image); + } + + //draw + bool needRestore = false; + if (CGImageIsMask(pat->image)) { + CGContextSaveGState(c); + CGContextSetFillColorWithColor(c, cgColorForQColor(pat->foreground, pat->pdev)); + } + CGRect rect = CGRectMake(0, 0, w, h); + qt_mac_drawCGImage(c, &rect, pat->image); + if(needRestore) + CGContextRestoreGState(c); +} +static void qt_mac_dispose_pattern(void *info) +{ + QMacPattern *pat = (QMacPattern*)info; + delete pat; +} + +/***************************************************************************** + QCoreGraphicsPaintEngine member functions + *****************************************************************************/ + +inline static QPaintEngine::PaintEngineFeatures qt_mac_cg_features() +{ + return QPaintEngine::PaintEngineFeatures(QPaintEngine::AllFeatures & ~QPaintEngine::PaintOutsidePaintEvent + & ~QPaintEngine::PerspectiveTransform + & ~QPaintEngine::ConicalGradientFill + & ~QPaintEngine::LinearGradientFill + & ~QPaintEngine::RadialGradientFill + & ~QPaintEngine::BrushStroke); +} + +QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine() +: QPaintEngine(*(new QCoreGraphicsPaintEnginePrivate), qt_mac_cg_features()) +{ +} + +QCoreGraphicsPaintEngine::QCoreGraphicsPaintEngine(QPaintEnginePrivate &dptr) +: QPaintEngine(dptr, qt_mac_cg_features()) +{ +} + +QCoreGraphicsPaintEngine::~QCoreGraphicsPaintEngine() +{ +} + +bool +QCoreGraphicsPaintEngine::begin(QPaintDevice *pdev) +{ + Q_D(QCoreGraphicsPaintEngine); + if(isActive()) { // already active painting + qWarning("QCoreGraphicsPaintEngine::begin: Painter already active"); + return false; + } + + //initialization + d->pdev = pdev; + d->complexXForm = false; + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; + d->cosmeticPenSize = 1; + d->current.clipEnabled = false; + d->pixelSize = QPoint(1,1); + d->hd = qt_mac_cg_context(pdev); + if(d->hd) { + d->saveGraphicsState(); + d->orig_xform = CGContextGetCTM(d->hd); + if (d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->setClip(0); //clear the context's clipping + } + + setActive(true); + + if(d->pdev->devType() == QInternal::Widget) { // device is a widget + QWidget *w = (QWidget*)d->pdev; + bool unclipped = w->testAttribute(Qt::WA_PaintUnclipped); + + if((w->windowType() == Qt::Desktop)) { + if(!unclipped) + qWarning("QCoreGraphicsPaintEngine::begin: Does not support clipped desktop on Mac OS X"); + // ## need to do [qt_mac_window_for(w) makeKeyAndOrderFront]; (need to rename the file) + } else if(unclipped) { + qWarning("QCoreGraphicsPaintEngine::begin: Does not support unclipped painting"); + } + } else if(d->pdev->devType() == QInternal::Pixmap) { // device is a pixmap + QPixmap *pm = (QPixmap*)d->pdev; + if(pm->isNull()) { + qWarning("QCoreGraphicsPaintEngine::begin: Cannot paint null pixmap"); + end(); + return false; + } + } + + setDirty(QPaintEngine::DirtyPen); + setDirty(QPaintEngine::DirtyBrush); + setDirty(QPaintEngine::DirtyBackground); + setDirty(QPaintEngine::DirtyHints); + return true; +} + +bool +QCoreGraphicsPaintEngine::end() +{ + Q_D(QCoreGraphicsPaintEngine); + setActive(false); + if(d->pdev->devType() == QInternal::Widget && static_cast<QWidget*>(d->pdev)->windowType() == Qt::Desktop) { +#ifndef QT_MAC_USE_COCOA + HideWindow(qt_mac_window_for(static_cast<QWidget*>(d->pdev))); +#else +// // ### need to do [qt_mac_window_for(static_cast<QWidget *>(d->pdev)) orderOut]; (need to rename) +#endif + + } + if(d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->pdev = 0; + if(d->hd) { + d->restoreGraphicsState(); + CGContextSynchronize(d->hd); + CGContextRelease(d->hd); + d->hd = 0; + } + return true; +} + +void +QCoreGraphicsPaintEngine::updateState(const QPaintEngineState &state) +{ + Q_D(QCoreGraphicsPaintEngine); + QPaintEngine::DirtyFlags flags = state.state(); + + if (flags & DirtyTransform) + updateMatrix(state.transform()); + + if (flags & DirtyClipEnabled) { + if (state.isClipEnabled()) + updateClipPath(painter()->clipPath(), Qt::ReplaceClip); + else + updateClipPath(QPainterPath(), Qt::NoClip); + } + + if (flags & DirtyClipPath) { + updateClipPath(state.clipPath(), state.clipOperation()); + } else if (flags & DirtyClipRegion) { + updateClipRegion(state.clipRegion(), state.clipOperation()); + } + + // If the clip has changed we need to update all other states + // too, since they are included in the system context on OSX, + // and changing the clip resets that context back to scratch. + if (flags & (DirtyClipPath | DirtyClipRegion | DirtyClipEnabled)) + flags |= AllDirty; + + if (flags & DirtyPen) + updatePen(state.pen()); + if (flags & (DirtyBrush|DirtyBrushOrigin)) + updateBrush(state.brush(), state.brushOrigin()); + if (flags & DirtyFont) + updateFont(state.font()); + if (flags & DirtyOpacity) + updateOpacity(state.opacity()); + if (flags & DirtyHints) + updateRenderHints(state.renderHints()); + if (flags & DirtyCompositionMode) + updateCompositionMode(state.compositionMode()); + + if (flags & (DirtyPen | DirtyTransform)) { + if (!d->current.pen.isCosmetic()) { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticNone; + } else if (d->current.transform.m11() < d->current.transform.m22()-1.0 || + d->current.transform.m11() > d->current.transform.m22()+1.0) { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath; + d->cosmeticPenSize = d->adjustPenWidth(d->current.pen.widthF()); + if (!d->cosmeticPenSize) + d->cosmeticPenSize = 1.0; + } else { + d->cosmeticPen = QCoreGraphicsPaintEnginePrivate::CosmeticSetPenWidth; + static const float sqrt2 = sqrt(2); + qreal width = d->current.pen.widthF(); + if (!width) + width = 1; + d->cosmeticPenSize = sqrt(pow(d->pixelSize.y(), 2) + pow(d->pixelSize.x(), 2)) / sqrt2 * width; + } + } +} + +void +QCoreGraphicsPaintEngine::updatePen(const QPen &pen) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + d->current.pen = pen; + d->setStrokePen(pen); +} + +void +QCoreGraphicsPaintEngine::updateBrush(const QBrush &brush, const QPointF &brushOrigin) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + d->current.brush = brush; + +#ifdef QT_MAC_USE_NATIVE_GRADIENTS + // Quartz supports only pad spread + if (const QGradient *gradient = brush.gradient()) { + if (drawGradientNatively(gradient)) { + gccaps |= QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill; + } else { + gccaps &= ~(QPaintEngine::LinearGradientFill | QPaintEngine::RadialGradientFill); + } + } +#endif + + if (d->shading) { + CGShadingRelease(d->shading); + d->shading = 0; + } + d->setFillBrush(brushOrigin); +} + +void +QCoreGraphicsPaintEngine::updateOpacity(qreal opacity) +{ + Q_D(QCoreGraphicsPaintEngine); + CGContextSetAlpha(d->hd, opacity); +} + +void +QCoreGraphicsPaintEngine::updateFont(const QFont &) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + updatePen(d->current.pen); +} + +void +QCoreGraphicsPaintEngine::updateMatrix(const QTransform &transform) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (qt_is_nan(transform.m11()) || qt_is_nan(transform.m12()) || qt_is_nan(transform.m13()) + || qt_is_nan(transform.m21()) || qt_is_nan(transform.m22()) || qt_is_nan(transform.m23()) + || qt_is_nan(transform.m31()) || qt_is_nan(transform.m32()) || qt_is_nan(transform.m33())) + return; + + d->current.transform = transform; + d->setTransform(transform.isIdentity() ? 0 : &transform); + d->complexXForm = (transform.m11() != 1 || transform.m22() != 1 + || transform.m12() != 0 || transform.m21() != 0); + d->pixelSize = d->devicePixelSize(d->hd); +} + +void +QCoreGraphicsPaintEngine::updateClipPath(const QPainterPath &p, Qt::ClipOperation op) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + if(op == Qt::NoClip) { + if(d->current.clipEnabled) { + d->current.clipEnabled = false; + d->current.clip = QRegion(); + d->setClip(0); + } + } else { + if(!d->current.clipEnabled) + op = Qt::ReplaceClip; + d->current.clipEnabled = true; + QRegion clipRegion(p.toFillPolygon().toPolygon(), p.fillRule()); + if(op == Qt::ReplaceClip) { + d->current.clip = clipRegion; + d->setClip(0); + if(p.isEmpty()) { + CGRect rect = CGRectMake(0, 0, 0, 0); + CGContextClipToRect(d->hd, rect); + } else { + CGMutablePathRef path = qt_mac_compose_path(p); + CGContextBeginPath(d->hd); + CGContextAddPath(d->hd, path); + if(p.fillRule() == Qt::WindingFill) + CGContextClip(d->hd); + else + CGContextEOClip(d->hd); + CGPathRelease(path); + } + } else if(op == Qt::IntersectClip) { + d->current.clip = d->current.clip.intersected(clipRegion); + d->setClip(&d->current.clip); + } else if(op == Qt::UniteClip) { + d->current.clip = d->current.clip.united(clipRegion); + d->setClip(&d->current.clip); + } + } +} + +void +QCoreGraphicsPaintEngine::updateClipRegion(const QRegion &clipRegion, Qt::ClipOperation op) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + if(op == Qt::NoClip) { + d->current.clipEnabled = false; + d->current.clip = QRegion(); + d->setClip(0); + } else { + if(!d->current.clipEnabled) + op = Qt::ReplaceClip; + d->current.clipEnabled = true; + if(op == Qt::IntersectClip) + d->current.clip = d->current.clip.intersected(clipRegion); + else if(op == Qt::ReplaceClip) + d->current.clip = clipRegion; + else if(op == Qt::UniteClip) + d->current.clip = d->current.clip.united(clipRegion); + d->setClip(&d->current.clip); + } +} + +void +QCoreGraphicsPaintEngine::drawPath(const QPainterPath &p) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = qt_mac_compose_path(p); + uchar ops = QCoreGraphicsPaintEnginePrivate::CGStroke; + if(p.fillRule() == Qt::WindingFill) + ops |= QCoreGraphicsPaintEnginePrivate::CGFill; + else + ops |= QCoreGraphicsPaintEnginePrivate::CGEOFill; + CGContextBeginPath(d->hd); + d->drawPath(ops, path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + for (int i=0; i<rectCount; ++i) { + QRectF r = rects[i]; + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, 0, qt_mac_compose_rect(r)); + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill|QCoreGraphicsPaintEnginePrivate::CGStroke, + path); + CGPathRelease(path); + } +} + +void +QCoreGraphicsPaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + if (d->current.pen.capStyle() == Qt::FlatCap) + CGContextSetLineCap(d->hd, kCGLineCapSquare); + + CGMutablePathRef path = CGPathCreateMutable(); + for(int i=0; i < pointCount; i++) { + float x = points[i].x(), y = points[i].y(); + CGPathMoveToPoint(path, 0, x, y); + CGPathAddLineToPoint(path, 0, x+0.001, y); + } + + bool doRestore = false; + if(d->cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticNone && !(state->renderHints() & QPainter::Antialiasing)) { + //we don't want adjusted pens for point rendering + doRestore = true; + d->saveGraphicsState(); + CGContextSetLineWidth(d->hd, d->current.pen.widthF()); + } + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); + if (doRestore) + d->restoreGraphicsState(); + CGPathRelease(path); + if (d->current.pen.capStyle() == Qt::FlatCap) + CGContextSetLineCap(d->hd, kCGLineCapButt); +} + +void +QCoreGraphicsPaintEngine::drawEllipse(const QRectF &r) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + CGAffineTransform transform = CGAffineTransformMakeScale(r.width() / r.height(), 1); + CGPathAddArc(path, &transform,(r.x() + (r.width() / 2)) / (r.width() / r.height()), + r.y() + (r.height() / 2), r.height() / 2, 0, (2 * M_PI), false); + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGFill | QCoreGraphicsPaintEnginePrivate::CGStroke, + path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + CGPathMoveToPoint(path, 0, points[0].x(), points[0].y()); + for(int x = 1; x < pointCount; ++x) + CGPathAddLineToPoint(path, 0, points[x].x(), points[x].y()); + if(mode != PolylineMode && points[0] != points[pointCount-1]) + CGPathAddLineToPoint(path, 0, points[0].x(), points[0].y()); + uint op = QCoreGraphicsPaintEnginePrivate::CGStroke; + if (mode != PolylineMode) + op |= mode == OddEvenMode ? QCoreGraphicsPaintEnginePrivate::CGEOFill + : QCoreGraphicsPaintEnginePrivate::CGFill; + d->drawPath(op, path); + CGPathRelease(path); +} + +void +QCoreGraphicsPaintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + CGMutablePathRef path = CGPathCreateMutable(); + for(int i = 0; i < lineCount; i++) { + const QPointF start = lines[i].p1(), end = lines[i].p2(); + CGPathMoveToPoint(path, 0, start.x(), start.y()); + CGPathAddLineToPoint(path, 0, end.x(), end.y()); + } + d->drawPath(QCoreGraphicsPaintEnginePrivate::CGStroke, path); + CGPathRelease(path); +} + +void QCoreGraphicsPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + if(pm.isNull()) + return; + + bool differentSize = (QRectF(0, 0, pm.width(), pm.height()) != sr), doRestore = false; + CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + QCFType<CGImageRef> image; + bool isBitmap = (pm.depth() == 1); + if (isBitmap) { + doRestore = true; + d->saveGraphicsState(); + + const QColor &col = d->current.pen.color(); + CGContextSetFillColorWithColor(d->hd, cgColorForQColor(col, d->pdev)); + image = qt_mac_create_imagemask(pm, sr); + } else if (differentSize) { + QCFType<CGImageRef> img = pm.toMacCGImageRef(); + image = CGImageCreateWithImageInRect(img, CGRectMake(qRound(sr.x()), qRound(sr.y()), qRound(sr.width()), qRound(sr.height()))); + } else { + image = (CGImageRef)pm.macCGHandle(); + } + qt_mac_drawCGImage(d->hd, &rect, image); + if (doRestore) + d->restoreGraphicsState(); +} + +static void drawImageReleaseData (void *info, const void *, size_t) +{ + delete static_cast<QImage *>(info); +} + +CGImageRef qt_mac_createCGImageFromQImage(const QImage &img, const QImage **imagePtr = 0) +{ + QImage *image; + if (img.depth() != 32) + image = new QImage(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + else + image = new QImage(img); + + uint cgflags = kCGImageAlphaNone; + switch (image->format()) { + case QImage::Format_ARGB32_Premultiplied: + cgflags = kCGImageAlphaPremultipliedFirst; + break; + case QImage::Format_ARGB32: + cgflags = kCGImageAlphaFirst; + break; + case QImage::Format_RGB32: + cgflags = kCGImageAlphaNoneSkipFirst; + default: + break; + } +#if defined(kCGBitmapByteOrder32Host) //only needed because CGImage.h added symbols in the minor version + cgflags |= kCGBitmapByteOrder32Host; +#endif + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(image, + static_cast<const QImage *>(image)->bits(), + image->byteCount(), + drawImageReleaseData); + if (imagePtr) + *imagePtr = image; + return CGImageCreate(image->width(), image->height(), 8, 32, + image->bytesPerLine(), + QCoreGraphicsPaintEngine::macGenericColorSpace(), + cgflags, dataProvider, 0, false, kCGRenderingIntentDefault); + +} + +void QCoreGraphicsPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags flags) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_UNUSED(flags); + Q_ASSERT(isActive()); + + if (img.isNull() || state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + const QImage *image; + QCFType<CGImageRef> cgimage = qt_mac_createCGImageFromQImage(img, &image); + CGRect rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + if (QRectF(0, 0, img.width(), img.height()) != sr) + cgimage = CGImageCreateWithImageInRect(cgimage, CGRectMake(sr.x(), sr.y(), + sr.width(), sr.height())); + qt_mac_drawCGImage(d->hd, &rect, cgimage); +} + +void QCoreGraphicsPaintEngine::initialize() +{ +} + +void QCoreGraphicsPaintEngine::cleanup() +{ +} + +CGContextRef +QCoreGraphicsPaintEngine::handle() const +{ + return d_func()->hd; +} + +void +QCoreGraphicsPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, + const QPointF &p) +{ + Q_D(QCoreGraphicsPaintEngine); + Q_ASSERT(isActive()); + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + //save the old state + d->saveGraphicsState(); + + //setup the pattern + QMacPattern *qpattern = new QMacPattern; + qpattern->data.pixmap = pixmap; + qpattern->foreground = d->current.pen.color(); + qpattern->pdev = d->pdev; + CGPatternCallbacks callbks; + callbks.version = 0; + callbks.drawPattern = qt_mac_draw_pattern; + callbks.releaseInfo = qt_mac_dispose_pattern; + const int width = qpattern->width(), height = qpattern->height(); + CGAffineTransform trans = CGContextGetCTM(d->hd); + CGPatternRef pat = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), + trans, width, height, + kCGPatternTilingNoDistortion, true, &callbks); + CGColorSpaceRef cs = CGColorSpaceCreatePattern(0); + CGContextSetFillColorSpace(d->hd, cs); + CGFloat component = 1.0; //just one + CGContextSetFillPattern(d->hd, pat, &component); + CGSize phase = CGSizeApplyAffineTransform(CGSizeMake(-(p.x()-r.x()), -(p.y()-r.y())), trans); + CGContextSetPatternPhase(d->hd, phase); + + //fill the rectangle + CGRect mac_rect = CGRectMake(r.x(), r.y(), r.width(), r.height()); + CGContextFillRect(d->hd, mac_rect); + + //restore the state + d->restoreGraphicsState(); + //cleanup + CGColorSpaceRelease(cs); + CGPatternRelease(pat); +} + +void QCoreGraphicsPaintEngine::drawTextItem(const QPointF &pos, const QTextItem &item) +{ + Q_D(QCoreGraphicsPaintEngine); + if (d->current.transform.type() == QTransform::TxProject +#ifndef QMAC_NATIVE_GRADIENTS + || painter()->pen().brush().gradient() //Just let the base engine "emulate" the gradient +#endif + ) { + QPaintEngine::drawTextItem(pos, item); + return; + } + + if (state->compositionMode() == QPainter::CompositionMode_Destination) + return; + + const QTextItemInt &ti = static_cast<const QTextItemInt &>(item); + + QPen oldPen = painter()->pen(); + QBrush oldBrush = painter()->brush(); + QPointF oldBrushOrigin = painter()->brushOrigin(); + updatePen(Qt::NoPen); + updateBrush(oldPen.brush(), QPointF(0, 0)); + + Q_ASSERT(type() == QPaintEngine::CoreGraphics); + + QFontEngine *fe = ti.fontEngine; + + const bool textAA = state->renderHints() & QPainter::TextAntialiasing && fe->fontDef.pointSize > qt_antialiasing_threshold && !(fe->fontDef.styleStrategy & QFont::NoAntialias); + const bool lineAA = state->renderHints() & QPainter::Antialiasing; + if(textAA != lineAA) + CGContextSetShouldAntialias(d->hd, textAA); + + if (ti.glyphs.numGlyphs) { + switch (fe->type()) { + case QFontEngine::Mac: +#ifdef QT_MAC_USE_COCOA + static_cast<QCoreTextFontEngine *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); +#else + static_cast<QFontEngineMac *>(fe)->draw(d->hd, pos.x(), pos.y(), ti, paintDevice()->height()); +#endif + break; + case QFontEngine::Box: + d->drawBoxTextItem(pos, ti); + break; + default: + break; + } + } + + if(textAA != lineAA) + CGContextSetShouldAntialias(d->hd, !textAA); + + updatePen(oldPen); + updateBrush(oldBrush, oldBrushOrigin); +} + +QPainter::RenderHints +QCoreGraphicsPaintEngine::supportedRenderHints() const +{ + return QPainter::RenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); +} +enum CGCompositeMode { + kCGCompositeModeClear = 0, + kCGCompositeModeCopy = 1, + kCGCompositeModeSourceOver = 2, + kCGCompositeModeSourceIn = 3, + kCGCompositeModeSourceOut = 4, + kCGCompositeModeSourceAtop = 5, + kCGCompositeModeDestinationOver = 6, + kCGCompositeModeDestinationIn = 7, + kCGCompositeModeDestinationOut = 8, + kCGCompositeModeDestinationAtop = 9, + kCGCompositeModeXOR = 10, + kCGCompositeModePlusDarker = 11, // (max (0, (1-d) + (1-s))) + kCGCompositeModePlusLighter = 12, // (min (1, s + d)) + }; +extern "C" { + extern void CGContextSetCompositeOperation(CGContextRef, int); +} // private function, but is in all versions of OS X. +void +QCoreGraphicsPaintEngine::updateCompositionMode(QPainter::CompositionMode mode) +{ +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + int cg_mode = kCGBlendModeNormal; + switch(mode) { + case QPainter::CompositionMode_Multiply: + cg_mode = kCGBlendModeMultiply; + break; + case QPainter::CompositionMode_Screen: + cg_mode = kCGBlendModeScreen; + break; + case QPainter::CompositionMode_Overlay: + cg_mode = kCGBlendModeOverlay; + break; + case QPainter::CompositionMode_Darken: + cg_mode = kCGBlendModeDarken; + break; + case QPainter::CompositionMode_Lighten: + cg_mode = kCGBlendModeLighten; + break; + case QPainter::CompositionMode_ColorDodge: + cg_mode = kCGBlendModeColorDodge; + break; + case QPainter::CompositionMode_ColorBurn: + cg_mode = kCGBlendModeColorBurn; + break; + case QPainter::CompositionMode_HardLight: + cg_mode = kCGBlendModeHardLight; + break; + case QPainter::CompositionMode_SoftLight: + cg_mode = kCGBlendModeSoftLight; + break; + case QPainter::CompositionMode_Difference: + cg_mode = kCGBlendModeDifference; + break; + case QPainter::CompositionMode_Exclusion: + cg_mode = kCGBlendModeExclusion; + break; + case QPainter::CompositionMode_Plus: + cg_mode = kCGBlendModePlusLighter; + break; + case QPainter::CompositionMode_SourceOver: + cg_mode = kCGBlendModeNormal; + break; + case QPainter::CompositionMode_DestinationOver: + cg_mode = kCGBlendModeDestinationOver; + break; + case QPainter::CompositionMode_Clear: + cg_mode = kCGBlendModeClear; + break; + case QPainter::CompositionMode_Source: + cg_mode = kCGBlendModeCopy; + break; + case QPainter::CompositionMode_Destination: + cg_mode = -1; + break; + case QPainter::CompositionMode_SourceIn: + cg_mode = kCGBlendModeSourceIn; + break; + case QPainter::CompositionMode_DestinationIn: + cg_mode = kCGCompositeModeDestinationIn; + break; + case QPainter::CompositionMode_SourceOut: + cg_mode = kCGBlendModeSourceOut; + break; + case QPainter::CompositionMode_DestinationOut: + cg_mode = kCGBlendModeDestinationOver; + break; + case QPainter::CompositionMode_SourceAtop: + cg_mode = kCGBlendModeSourceAtop; + break; + case QPainter::CompositionMode_DestinationAtop: + cg_mode = kCGBlendModeDestinationAtop; + break; + case QPainter::CompositionMode_Xor: + cg_mode = kCGBlendModeXOR; + break; + default: + break; + } + if (cg_mode > -1) { + CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode)); + } + } else +#endif + // The standard porter duff ops. + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_3 + && mode <= QPainter::CompositionMode_Xor) { + int cg_mode = kCGCompositeModeCopy; + switch (mode) { + case QPainter::CompositionMode_SourceOver: + cg_mode = kCGCompositeModeSourceOver; + break; + case QPainter::CompositionMode_DestinationOver: + cg_mode = kCGCompositeModeDestinationOver; + break; + case QPainter::CompositionMode_Clear: + cg_mode = kCGCompositeModeClear; + break; + default: + qWarning("QCoreGraphicsPaintEngine: Unhandled composition mode %d", (int)mode); + break; + case QPainter::CompositionMode_Source: + cg_mode = kCGCompositeModeCopy; + break; + case QPainter::CompositionMode_Destination: + cg_mode = CGCompositeMode(-1); + break; + case QPainter::CompositionMode_SourceIn: + cg_mode = kCGCompositeModeSourceIn; + break; + case QPainter::CompositionMode_DestinationIn: + cg_mode = kCGCompositeModeDestinationIn; + break; + case QPainter::CompositionMode_SourceOut: + cg_mode = kCGCompositeModeSourceOut; + break; + case QPainter::CompositionMode_DestinationOut: + cg_mode = kCGCompositeModeDestinationOut; + break; + case QPainter::CompositionMode_SourceAtop: + cg_mode = kCGCompositeModeSourceAtop; + break; + case QPainter::CompositionMode_DestinationAtop: + cg_mode = kCGCompositeModeDestinationAtop; + break; + case QPainter::CompositionMode_Xor: + cg_mode = kCGCompositeModeXOR; + break; + } + if (cg_mode > -1) + CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode)); + } else { +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + bool needPrivateAPI = false; + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + int cg_mode = kCGBlendModeNormal; + switch (mode) { + case QPainter::CompositionMode_Multiply: + cg_mode = kCGBlendModeMultiply; + break; + case QPainter::CompositionMode_Screen: + cg_mode = kCGBlendModeScreen; + break; + case QPainter::CompositionMode_Overlay: + cg_mode = kCGBlendModeOverlay; + break; + case QPainter::CompositionMode_Darken: + cg_mode = kCGBlendModeDarken; + break; + case QPainter::CompositionMode_Lighten: + cg_mode = kCGBlendModeLighten; + break; + case QPainter::CompositionMode_ColorDodge: + cg_mode = kCGBlendModeColorDodge; + break; + case QPainter::CompositionMode_ColorBurn: + cg_mode = kCGBlendModeColorBurn; + break; + case QPainter::CompositionMode_HardLight: + cg_mode = kCGBlendModeHardLight; + break; + case QPainter::CompositionMode_SoftLight: + cg_mode = kCGBlendModeSoftLight; + break; + case QPainter::CompositionMode_Difference: + cg_mode = kCGBlendModeDifference; + break; + case QPainter::CompositionMode_Exclusion: + cg_mode = kCGBlendModeExclusion; + break; + case QPainter::CompositionMode_Plus: + needPrivateAPI = true; + cg_mode = kCGCompositeModePlusLighter; + break; + default: + break; + } + if (!needPrivateAPI) + CGContextSetBlendMode(d_func()->hd, CGBlendMode(cg_mode)); + else + CGContextSetCompositeOperation(d_func()->hd, CGCompositeMode(cg_mode)); + } +#endif + } +} + +void +QCoreGraphicsPaintEngine::updateRenderHints(QPainter::RenderHints hints) +{ + Q_D(QCoreGraphicsPaintEngine); + CGContextSetShouldAntialias(d->hd, hints & QPainter::Antialiasing); + static const CGFloat ScaleFactor = qt_mac_get_scalefactor(); + if (ScaleFactor > 1.) { + CGContextSetInterpolationQuality(d->hd, kCGInterpolationHigh); + } else { + CGContextSetInterpolationQuality(d->hd, (hints & QPainter::SmoothPixmapTransform) ? + kCGInterpolationHigh : kCGInterpolationNone); + } + bool textAntialiasing = (hints & QPainter::TextAntialiasing) == QPainter::TextAntialiasing; + if (!textAntialiasing || d->disabledSmoothFonts) { + d->disabledSmoothFonts = !textAntialiasing; + CGContextSetShouldSmoothFonts(d->hd, textAntialiasing); + } +} + +/* + Returns the size of one device pixel in user-space coordinates. +*/ +QPointF QCoreGraphicsPaintEnginePrivate::devicePixelSize(CGContextRef) +{ + QPointF p1 = current.transform.inverted().map(QPointF(0, 0)); + QPointF p2 = current.transform.inverted().map(QPointF(1, 1)); + return QPointF(qAbs(p2.x() - p1.x()), qAbs(p2.y() - p1.y())); +} + +/* + Adjusts the pen width so we get correct line widths in the + non-transformed, aliased case. +*/ +float QCoreGraphicsPaintEnginePrivate::adjustPenWidth(float penWidth) +{ + Q_Q(QCoreGraphicsPaintEngine); + float ret = penWidth; + if (!complexXForm && !(q->state->renderHints() & QPainter::Antialiasing)) { + if (penWidth < 2) + ret = 1; + else if (penWidth < 3) + ret = 1.5; + else + ret = penWidth -1; + } + return ret; +} + +void +QCoreGraphicsPaintEnginePrivate::setStrokePen(const QPen &pen) +{ + //pencap + CGLineCap cglinecap = kCGLineCapButt; + if(pen.capStyle() == Qt::SquareCap) + cglinecap = kCGLineCapSquare; + else if(pen.capStyle() == Qt::RoundCap) + cglinecap = kCGLineCapRound; + CGContextSetLineCap(hd, cglinecap); + CGContextSetLineWidth(hd, adjustPenWidth(pen.widthF())); + + //join + CGLineJoin cglinejoin = kCGLineJoinMiter; + if(pen.joinStyle() == Qt::BevelJoin) + cglinejoin = kCGLineJoinBevel; + else if(pen.joinStyle() == Qt::RoundJoin) + cglinejoin = kCGLineJoinRound; + CGContextSetLineJoin(hd, cglinejoin); +// CGContextSetMiterLimit(hd, pen.miterLimit()); + + //pen style + QVector<CGFloat> linedashes; + if(pen.style() == Qt::CustomDashLine) { + QVector<qreal> customs = pen.dashPattern(); + for(int i = 0; i < customs.size(); ++i) + linedashes.append(customs.at(i)); + } else if(pen.style() == Qt::DashLine) { + linedashes.append(4); + linedashes.append(2); + } else if(pen.style() == Qt::DotLine) { + linedashes.append(1); + linedashes.append(2); + } else if(pen.style() == Qt::DashDotLine) { + linedashes.append(4); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + } else if(pen.style() == Qt::DashDotDotLine) { + linedashes.append(4); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + linedashes.append(1); + linedashes.append(2); + } + const CGFloat cglinewidth = pen.widthF() <= 0.0f ? 1.0f : float(pen.widthF()); + for(int i = 0; i < linedashes.size(); ++i) { + linedashes[i] *= cglinewidth; + if(cglinewidth < 3 && (cglinecap == kCGLineCapSquare || cglinecap == kCGLineCapRound)) { + if((i%2)) + linedashes[i] += cglinewidth/2; + else + linedashes[i] -= cglinewidth/2; + } + } + CGContextSetLineDash(hd, pen.dashOffset() * cglinewidth, linedashes.data(), linedashes.size()); + + // color + CGContextSetStrokeColorWithColor(hd, cgColorForQColor(pen.color(), pdev)); +} + +// Add our own patterns here to deal with the fact that the coordinate system +// is flipped vertically with Quartz2D. +static const uchar *qt_mac_patternForBrush(int brushStyle) +{ + Q_ASSERT(brushStyle > Qt::SolidPattern && brushStyle < Qt::LinearGradientPattern); + static const uchar dense1_pat[] = { 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x44, 0x00 }; + static const uchar dense2_pat[] = { 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88 }; + static const uchar dense3_pat[] = { 0x11, 0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x44, 0xaa }; + static const uchar dense4_pat[] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }; + static const uchar dense5_pat[] = { 0xee, 0x55, 0xbb, 0x55, 0xee, 0x55, 0xbb, 0x55 }; + static const uchar dense6_pat[] = { 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77 }; + static const uchar dense7_pat[] = { 0xff, 0xff, 0xbb, 0xff, 0xff, 0xff, 0xbb, 0xff }; + static const uchar hor_pat[] = { 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff }; + static const uchar ver_pat[] = { 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef }; + static const uchar cross_pat[] = { 0xef, 0xef, 0xef, 0xef, 0x00, 0xef, 0xef, 0xef }; + static const uchar fdiag_pat[] = { 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe }; + static const uchar bdiag_pat[] = { 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f }; + static const uchar dcross_pat[] = { 0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e }; + static const uchar *const pat_tbl[] = { + dense1_pat, dense2_pat, dense3_pat, dense4_pat, dense5_pat, + dense6_pat, dense7_pat, + hor_pat, ver_pat, cross_pat, bdiag_pat, fdiag_pat, dcross_pat }; + return pat_tbl[brushStyle - Qt::Dense1Pattern]; +} + +void QCoreGraphicsPaintEnginePrivate::setFillBrush(const QPointF &offset) +{ + // pattern + Qt::BrushStyle bs = current.brush.style(); +#ifdef QT_MAC_USE_NATIVE_GRADIENTS + if (bs == Qt::LinearGradientPattern || bs == Qt::RadialGradientPattern) { + const QGradient *grad = static_cast<const QGradient*>(current.brush.gradient()); + if (drawGradientNatively(grad)) { + Q_ASSERT(grad->spread() == QGradient::PadSpread); + + static const CGFloat domain[] = { 0.0f, +1.0f }; + static const CGFunctionCallbacks callbacks = { 0, qt_mac_color_gradient_function, 0 }; + CGFunctionRef fill_func = CGFunctionCreate(reinterpret_cast<void *>(¤t.brush), + 1, domain, 4, 0, &callbacks); + + CGColorSpaceRef colorspace = qt_mac_colorSpaceForDeviceType(pdev); + if (bs == Qt::LinearGradientPattern) { + const QLinearGradient *linearGrad = static_cast<const QLinearGradient *>(grad); + const QPointF start(linearGrad->start()); + const QPointF stop(linearGrad->finalStop()); + shading = CGShadingCreateAxial(colorspace, CGPointMake(start.x(), start.y()), + CGPointMake(stop.x(), stop.y()), fill_func, true, true); + } else { + Q_ASSERT(bs == Qt::RadialGradientPattern); + const QRadialGradient *radialGrad = static_cast<const QRadialGradient *>(grad); + QPointF center(radialGrad->center()); + QPointF focal(radialGrad->focalPoint()); + qreal radius = radialGrad->radius(); + shading = CGShadingCreateRadial(colorspace, CGPointMake(focal.x(), focal.y()), + 0.0, CGPointMake(center.x(), center.y()), radius, fill_func, false, true); + } + + CGFunctionRelease(fill_func); + } + } else +#endif + if(bs != Qt::SolidPattern && bs != Qt::NoBrush +#ifndef QT_MAC_USE_NATIVE_GRADIENTS + && (bs < Qt::LinearGradientPattern || bs > Qt::ConicalGradientPattern) +#endif + ) + { + QMacPattern *qpattern = new QMacPattern; + qpattern->pdev = pdev; + CGFloat components[4] = { 1.0, 1.0, 1.0, 1.0 }; + CGColorSpaceRef base_colorspace = 0; + if(bs == Qt::TexturePattern) { + qpattern->data.pixmap = current.brush.texture(); + if(qpattern->data.pixmap.isQBitmap()) { + const QColor &col = current.brush.color(); + components[0] = qt_mac_convert_color_to_cg(col.red()); + components[1] = qt_mac_convert_color_to_cg(col.green()); + components[2] = qt_mac_convert_color_to_cg(col.blue()); + base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + } + } else { + qpattern->as_mask = true; + + qpattern->data.bytes = qt_mac_patternForBrush(bs); + const QColor &col = current.brush.color(); + components[0] = qt_mac_convert_color_to_cg(col.red()); + components[1] = qt_mac_convert_color_to_cg(col.green()); + components[2] = qt_mac_convert_color_to_cg(col.blue()); + base_colorspace = QCoreGraphicsPaintEngine::macGenericColorSpace(); + } + int width = qpattern->width(), height = qpattern->height(); + qpattern->foreground = current.brush.color(); + + CGColorSpaceRef fill_colorspace = CGColorSpaceCreatePattern(base_colorspace); + CGContextSetFillColorSpace(hd, fill_colorspace); + + CGAffineTransform xform = CGContextGetCTM(hd); + xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(current.brush.transform()), xform); + xform = CGAffineTransformTranslate(xform, offset.x(), offset.y()); + + CGPatternCallbacks callbks; + callbks.version = 0; + callbks.drawPattern = qt_mac_draw_pattern; + callbks.releaseInfo = qt_mac_dispose_pattern; + CGPatternRef fill_pattern = CGPatternCreate(qpattern, CGRectMake(0, 0, width, height), + xform, width, height, kCGPatternTilingNoDistortion, + !base_colorspace, &callbks); + CGContextSetFillPattern(hd, fill_pattern, components); + + CGPatternRelease(fill_pattern); + CGColorSpaceRelease(fill_colorspace); + } else if(bs != Qt::NoBrush) { + CGContextSetFillColorWithColor(hd, cgColorForQColor(current.brush.color(), pdev)); + } +} + +void +QCoreGraphicsPaintEnginePrivate::setClip(const QRegion *rgn) +{ + Q_Q(QCoreGraphicsPaintEngine); + if(hd) { + resetClip(); + QRegion sysClip = q->systemClip(); + if(!sysClip.isEmpty()) + qt_mac_clip_cg(hd, sysClip, &orig_xform); + if(rgn) + qt_mac_clip_cg(hd, *rgn, 0); + } +} + +struct qt_mac_cg_transform_path { + CGMutablePathRef path; + CGAffineTransform transform; +}; + +void qt_mac_cg_transform_path_apply(void *info, const CGPathElement *element) +{ + Q_ASSERT(info && element); + qt_mac_cg_transform_path *t = (qt_mac_cg_transform_path*)info; + switch(element->type) { + case kCGPathElementMoveToPoint: + CGPathMoveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); + break; + case kCGPathElementAddLineToPoint: + CGPathAddLineToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y); + break; + case kCGPathElementAddQuadCurveToPoint: + CGPathAddQuadCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, + element->points[1].x, element->points[1].y); + break; + case kCGPathElementAddCurveToPoint: + CGPathAddCurveToPoint(t->path, &t->transform, element->points[0].x, element->points[0].y, + element->points[1].x, element->points[1].y, + element->points[2].x, element->points[2].y); + break; + case kCGPathElementCloseSubpath: + CGPathCloseSubpath(t->path); + break; + default: + qDebug() << "Unhandled path transform type: " << element->type; + } +} + +void QCoreGraphicsPaintEnginePrivate::drawPath(uchar ops, CGMutablePathRef path) +{ + Q_Q(QCoreGraphicsPaintEngine); + Q_ASSERT((ops & (CGFill | CGEOFill)) != (CGFill | CGEOFill)); //can't really happen + if((ops & (CGFill | CGEOFill))) { + if (shading) { + Q_ASSERT(path); + CGContextBeginPath(hd); + CGContextAddPath(hd, path); + saveGraphicsState(); + if (ops & CGFill) + CGContextClip(hd); + else if (ops & CGEOFill) + CGContextEOClip(hd); + if (current.brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode) { + CGRect boundingBox = CGPathGetBoundingBox(path); + CGContextConcatCTM(hd, + CGAffineTransformMake(boundingBox.size.width, 0, + 0, boundingBox.size.height, + boundingBox.origin.x, boundingBox.origin.y)); + } + CGContextDrawShading(hd, shading); + restoreGraphicsState(); + ops &= ~CGFill; + ops &= ~CGEOFill; + } else if (current.brush.style() == Qt::NoBrush) { + ops &= ~CGFill; + ops &= ~CGEOFill; + } + } + if((ops & CGStroke) && current.pen.style() == Qt::NoPen) + ops &= ~CGStroke; + + if(ops & (CGEOFill | CGFill)) { + CGContextBeginPath(hd); + CGContextAddPath(hd, path); + if (ops & CGEOFill) { + CGContextEOFillPath(hd); + } else { + CGContextFillPath(hd); + } + } + + // Avoid saving and restoring the context if we can. + const bool needContextSave = (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone || + !(q->state->renderHints() & QPainter::Antialiasing)); + if(ops & CGStroke) { + if (needContextSave) + saveGraphicsState(); + CGContextBeginPath(hd); + + // Translate a fraction of a pixel size in the y direction + // to make sure that primitives painted at pixel borders + // fills the right pixel. This is needed since the y xais + // in the Quartz coordinate system is inverted compared to Qt. + if (!(q->state->renderHints() & QPainter::Antialiasing)) { + if (current.pen.style() == Qt::SolidLine || current.pen.width() >= 3) + CGContextTranslateCTM(hd, double(pixelSize.x()) * 0.25, double(pixelSize.y()) * 0.25); + else if (current.pen.style() == Qt::DotLine && QSysInfo::MacintoshVersion == QSysInfo::MV_10_3) + ; // Do nothing. + else + CGContextTranslateCTM(hd, 0, double(pixelSize.y()) * 0.1); + } + + if (cosmeticPen != QCoreGraphicsPaintEnginePrivate::CosmeticNone) { + // If antialiazing is enabled, use the cosmetic pen size directly. + if (q->state->renderHints() & QPainter::Antialiasing) + CGContextSetLineWidth(hd, cosmeticPenSize); + else if (current.pen.widthF() <= 1) + CGContextSetLineWidth(hd, cosmeticPenSize * 0.9f); + else + CGContextSetLineWidth(hd, cosmeticPenSize); + } + if(cosmeticPen == QCoreGraphicsPaintEnginePrivate::CosmeticTransformPath) { + qt_mac_cg_transform_path t; + t.transform = qt_mac_convert_transform_to_cg(current.transform); + t.path = CGPathCreateMutable(); + CGPathApply(path, &t, qt_mac_cg_transform_path_apply); //transform the path + setTransform(0); //unset the context transform + CGContextSetLineWidth(hd, cosmeticPenSize); + CGContextAddPath(hd, t.path); + CGPathRelease(t.path); + } else { + CGContextAddPath(hd, path); + } + + CGContextStrokePath(hd); + if (needContextSave) + restoreGraphicsState(); + } +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qpaintengine_mac_p.h b/src/gui/platforms/mac/qpaintengine_mac_p.h new file mode 100644 index 0000000000..3e71d6ca6b --- /dev/null +++ b/src/gui/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<CGDirectDisplayID, CGColorSpaceRef> m_displayColorSpaceHash; + static void cleanUpMacColorSpaces(); + Q_DISABLE_COPY(QCoreGraphicsPaintEngine) +}; + +/***************************************************************************** + Private data + *****************************************************************************/ +class QCoreGraphicsPaintEnginePrivate : public QPaintEnginePrivate +{ + Q_DECLARE_PUBLIC(QCoreGraphicsPaintEngine) +public: + QCoreGraphicsPaintEnginePrivate() + : hd(0), shading(0), stackCount(0), complexXForm(false), disabledSmoothFonts(false) + { + } + + struct { + QPen pen; + QBrush brush; + uint clipEnabled : 1; + QRegion clip; + QTransform transform; + } current; + + //state info (shared with QD) + CGAffineTransform orig_xform; + + //cg structures + CGContextRef hd; + CGShadingRef shading; + int stackCount; + bool complexXForm; + bool disabledSmoothFonts; + enum { CosmeticNone, CosmeticTransformPath, CosmeticSetPenWidth } cosmeticPen; + + // pixel and cosmetic pen size in user coordinates. + QPointF pixelSize; + float cosmeticPenSize; + + //internal functions + enum { CGStroke=0x01, CGEOFill=0x02, CGFill=0x04 }; + void drawPath(uchar ops, CGMutablePathRef path = 0); + void setClip(const QRegion *rgn=0); + void resetClip(); + void setFillBrush(const QPointF &origin=QPoint()); + void setStrokePen(const QPen &pen); + inline void saveGraphicsState(); + inline void restoreGraphicsState(); + float penOffset(); + QPointF devicePixelSize(CGContextRef context); + float adjustPenWidth(float penWidth); + inline void setTransform(const QTransform *matrix=0) + { + CGContextConcatCTM(hd, CGAffineTransformInvert(CGContextGetCTM(hd))); + CGAffineTransform xform = orig_xform; + if(matrix) { + extern CGAffineTransform qt_mac_convert_transform_to_cg(const QTransform &); + xform = CGAffineTransformConcat(qt_mac_convert_transform_to_cg(*matrix), xform); + } + CGContextConcatCTM(hd, xform); + CGContextSetTextMatrix(hd, xform); + } +}; + +inline void QCoreGraphicsPaintEnginePrivate::saveGraphicsState() +{ + ++stackCount; + CGContextSaveGState(hd); +} + +inline void QCoreGraphicsPaintEnginePrivate::restoreGraphicsState() +{ + --stackCount; + Q_ASSERT(stackCount >= 0); + CGContextRestoreGState(hd); +} + +class QMacQuartzPaintDevice : public QPaintDevice +{ +public: + QMacQuartzPaintDevice(CGContextRef cg, int width, int height, int bytesPerLine) + : mCG(cg), mWidth(width), mHeight(height), mBytesPerLine(bytesPerLine) + { } + int devType() const { return QInternal::MacQuartz; } + CGContextRef cgContext() const { return mCG; } + int metric(PaintDeviceMetric metric) const { + switch (metric) { + case PdmWidth: + return mWidth; + case PdmHeight: + return mHeight; + case PdmWidthMM: + return (qt_defaultDpiX() * mWidth) / 2.54; + case PdmHeightMM: + return (qt_defaultDpiY() * mHeight) / 2.54; + case PdmNumColors: + return 0; + case PdmDepth: + return 32; + case PdmDpiX: + case PdmPhysicalDpiX: + return qt_defaultDpiX(); + case PdmDpiY: + case PdmPhysicalDpiY: + return qt_defaultDpiY(); + } + return 0; + } + QPaintEngine *paintEngine() const { qWarning("This function should never be called."); return 0; } +private: + CGContextRef mCG; + int mWidth; + int mHeight; + int mBytesPerLine; +}; + +QT_END_NAMESPACE + +#endif // QPAINTENGINE_MAC_P_H diff --git a/src/gui/platforms/mac/qpixmap_mac.cpp b/src/gui/platforms/mac/qpixmap_mac.cpp new file mode 100644 index 0000000000..72e2aa6e04 --- /dev/null +++ b/src/gui/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 <private/qdrawhelper_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qpixmap_raster_p.h> +#include <private/qpaintengine_mac_p.h> +#include <private/qt_mac_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> + +#include <limits.h> +#include <string.h> + +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<QMacPixmapData*>(pix->data.data())->pixels; +} + +Q_GUI_EXPORT int qt_mac_pixmap_get_bytes_per_line(const QPixmap *pix) +{ + return static_cast<QMacPixmapData*>(pix->data.data())->bytesPerRow; +} + +void qt_mac_cgimage_data_free(void *info, const void *memoryToFree, size_t) +{ + QMacPixmapData *pmdata = static_cast<QMacPixmapData *>(info); + if (!pmdata) { + free(const_cast<void *>(memoryToFree)); + } else { + if (QMacPixmapData::validDataPointers.contains(pmdata) == false) { + free(const_cast<void *>(memoryToFree)); + return; + } + if (pmdata->pixels == pmdata->pixelsToFree) { + // something we aren't expecting, just free it. + Q_ASSERT(memoryToFree != pmdata->pixelsToFree); + free(const_cast<void *>(memoryToFree)); + } else { + free(pmdata->pixelsToFree); + pmdata->pixelsToFree = static_cast<quint32 *>(const_cast<void *>(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<CGDataProviderRef> 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_bbits)-1; + static const int qt_green_mask = (1<<(qt_gbits+qt_bbits))-((1<<qt_bbits)-1); + static const int qt_red_mask = (1<<(qt_rbits+qt_gbits+qt_bbits))-(1<<(qt_gbits+qt_bbits)); + + const int r=(c & qt_red_mask); + const int g=(c & qt_green_mask); + const int b=(c & qt_blue_mask); + const int tr = r >> qt_red_shift; + const int tg = g >> qt_green_shift; + const int tb = b << qt_neg_blue_shift; + + return qRgb(tr,tg,tb); +} + +QSet<QMacPixmapData*> 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<const QImage &>(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<QRgb> 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;loopc<qi->colorCount();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<QMacPixmapData*>(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<QMacPixmapData*>(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<CGDataProviderRef> 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<quint32 *>(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 <OpenGL/OpenGL.h> +#include <OpenGL/gl.h> +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<char> 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<CGContextRef> bitmap = CGBitmapContextCreate(buffer.data(), rect.width(), + rect.height(), 8, bytewidth, + QCoreGraphicsPaintEngine::macGenericColorSpace(), + kCGImageAlphaNoneSkipFirst); + QCFType<CGImageRef> 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<GWorldPtr>(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<QMacPixmapData *>(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<QRasterPixmapData *>(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<QMacPixmapData*>(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<quint8 *>(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<CGDataProviderRef> 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<IconFamilyHandle>(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<Handle>(iconFamily)); + counter++; + return ret; + +} +#endif + +/*! \internal */ +QPaintEngine* QMacPixmapData::paintEngine() const +{ + if (!pengine) { + QMacPixmapData *that = const_cast<QMacPixmapData*>(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<const QMacPixmapData*>(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<char*>(pixels); + const char *src = reinterpret_cast<const char*>(macData->pixels + x) + y * macData->bytesPerRow; + for (int i = 0; i < h; ++i) { + memcpy(dest, src, w * 4); + dest += bytesPerRow; + src += macData->bytesPerRow; + } + + has_alpha = macData->has_alpha; + has_mask = macData->has_mask; +} + +bool QMacPixmapData::scroll(int dx, int dy, const QRect &rect) +{ + Q_UNUSED(dx); + Q_UNUSED(dy); + Q_UNUSED(rect); + return false; +} + +/*! + \since 4.2 + + Creates a \c CGImageRef equivalent to the QPixmap. Returns the \c CGImageRef handle. + + It is the caller's responsibility to release the \c CGImageRef data + after use. + + \warning This function is only available on Mac OS X. + + \sa fromMacCGImageRef() +*/ +CGImageRef QPixmap::toMacCGImageRef() const +{ + return (CGImageRef)macCGHandle(); +} + +/*! + \since 4.2 + + Returns a QPixmap that is equivalent to the given \a image. + + \warning This function is only available on Mac OS X. + + \sa toMacCGImageRef(), {QPixmap#Pixmap Conversion}{Pixmap Conversion} +*/ +QPixmap QPixmap::fromMacCGImageRef(CGImageRef image) +{ + const size_t w = CGImageGetWidth(image), + h = CGImageGetHeight(image); + QPixmap ret(w, h); + ret.fill(Qt::transparent); + CGRect rect = CGRectMake(0, 0, w, h); + CGContextRef ctx = qt_mac_cg_context(&ret); + qt_mac_drawCGImage(ctx, &rect, image); + CGContextRelease(ctx); + return ret; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qpixmap_mac_p.h b/src/gui/platforms/mac/qpixmap_mac_p.h new file mode 100644 index 0000000000..307e38aceb --- /dev/null +++ b/src/gui/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 <QtGui/private/qpixmapdata_p.h> +#include <QtGui/private/qpixmapdatafactory_p.h> +#include <QtGui/private/qt_mac_p.h> + +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<QMacPixmapData*> validDataPointers; + + QPaintEngine *pengine; + + friend class QPixmap; + friend class QRasterBuffer; + friend class QRasterPaintEngine; + friend class QCoreGraphicsPaintEngine; + friend CGImageRef qt_mac_create_imagemask(const QPixmap&, const QRectF&); + friend quint32 *qt_mac_pixmap_get_base(const QPixmap*); + friend int qt_mac_pixmap_get_bytes_per_line(const QPixmap*); + friend void qt_mac_cgimage_data_free(void *, const void*, size_t); + friend IconRef qt_mac_create_iconref(const QPixmap&); + friend CGContextRef qt_mac_cg_context(const QPaintDevice*); + friend QColor qcolorForThemeTextColor(ThemeTextColor themeColor); +}; + +QT_END_NAMESPACE + +#endif // QPIXMAP_MAC_P_H diff --git a/src/gui/platforms/mac/qprintengine_mac.mm b/src/gui/platforms/mac/qprintengine_mac.mm new file mode 100644 index 0000000000..1dce30301f --- /dev/null +++ b/src/gui/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 <private/qprintengine_mac_p.h> +#include <qdebug.h> +#include <qthread.h> +#include <QtCore/qcoreapplication.h> + +#ifndef QT_NO_PRINTER + +QT_BEGIN_NAMESPACE + +extern QSizeF qt_paperSizeToQSizeF(QPrinter::PaperSize size); + +QMacPrintEngine::QMacPrintEngine(QPrinter::PrinterMode mode) : QPaintEngine(*(new QMacPrintEnginePrivate)) +{ + Q_D(QMacPrintEngine); + d->mode = mode; + d->initialize(); +} + +bool QMacPrintEngine::begin(QPaintDevice *dev) +{ + Q_D(QMacPrintEngine); + + Q_ASSERT(dev && dev->devType() == QInternal::Printer); + if (!static_cast<QPrinter *>(dev)->isValid()) + return false; + + if (d->state == QPrinter::Idle && !d->isPrintSessionInitialized()) // Need to reinitialize + d->initialize(); + + d->paintEngine->state = state; + d->paintEngine->begin(dev); + Q_ASSERT_X(d->state == QPrinter::Idle, "QMacPrintEngine", "printer already active"); + + if (PMSessionValidatePrintSettings(d->session, d->settings, kPMDontWantBoolean) != noErr + || PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean) != noErr) { + d->state = QPrinter::Error; + return false; + } + + if (!d->outputFilename.isEmpty()) { + QCFType<CFURLRef> outFile = CFURLCreateWithFileSystemPath(kCFAllocatorSystemDefault, + QCFString(d->outputFilename), + kCFURLPOSIXPathStyle, + false); + if (PMSessionSetDestination(d->session, d->settings, kPMDestinationFile, + kPMDocumentFormatPDF, outFile) != noErr) { + qWarning("QMacPrintEngine::begin: Problem setting file [%s]", d->outputFilename.toUtf8().constData()); + return false; + } + } + OSStatus status = noErr; +#ifndef QT_MAC_USE_COCOA + status = d->shouldSuppressStatus() ? PMSessionBeginCGDocumentNoDialog(d->session, d->settings, d->format) + : PMSessionBeginCGDocument(d->session, d->settings, d->format); +#else + status = PMSessionBeginCGDocumentNoDialog(d->session, d->settings, d->format); +#endif + + if (status != noErr) { + d->state = QPrinter::Error; + return false; + } + + d->state = QPrinter::Active; + setActive(true); + d->newPage_helper(); + return true; +} + +bool QMacPrintEngine::end() +{ + Q_D(QMacPrintEngine); + if (d->state == QPrinter::Aborted) + return true; // I was just here a function call ago :) + if(d->paintEngine->type() == QPaintEngine::CoreGraphics) { + // We dont need the paint engine to call restoreGraphicsState() + static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->stackCount = 0; + static_cast<QCoreGraphicsPaintEngine*>(d->paintEngine)->d_func()->hd = 0; + } + d->paintEngine->end(); + if (d->state != QPrinter::Idle) + d->releaseSession(); + d->state = QPrinter::Idle; + return true; +} + +QPaintEngine * +QMacPrintEngine::paintEngine() const +{ + return d_func()->paintEngine; +} + +Qt::HANDLE QMacPrintEngine::handle() const +{ + QCoreGraphicsPaintEngine *cgEngine = static_cast<QCoreGraphicsPaintEngine*>(paintEngine()); + return cgEngine->d_func()->hd; +} + +QMacPrintEnginePrivate::~QMacPrintEnginePrivate() +{ +#ifdef QT_MAC_USE_COCOA + [printInfo release]; +#endif + delete paintEngine; +} + +void QMacPrintEnginePrivate::setPaperSize(QPrinter::PaperSize ps) +{ + Q_Q(QMacPrintEngine); + QSizeF newSize = qt_paperSizeToQSizeF(ps); + QCFType<CFArrayRef> formats; + PMPrinter printer; + + if (PMSessionGetCurrentPrinter(session, &printer) == noErr + && PMSessionCreatePageFormatList(session, printer, &formats) == noErr) { + CFIndex total = CFArrayGetCount(formats); + PMPageFormat tmp; + PMRect paper; + for (CFIndex idx = 0; idx < total; ++idx) { + tmp = static_cast<PMPageFormat>( + const_cast<void *>(CFArrayGetValueAtIndex(formats, idx))); + PMGetUnadjustedPaperRect(tmp, &paper); + int wMM = int((paper.right - paper.left) / 72 * 25.4 + 0.5); + int hMM = int((paper.bottom - paper.top) / 72 * 25.4 + 0.5); + if (newSize.width() == wMM && newSize.height() == hMM) { + PMCopyPageFormat(tmp, format); + // reset the orientation and resolution as they are lost in the copy. + q->setProperty(QPrintEngine::PPK_Orientation, orient); + if (PMSessionValidatePageFormat(session, format, kPMDontWantBoolean) != noErr) { + // Don't know, warn for the moment. + qWarning("QMacPrintEngine, problem setting format and resolution for this page size"); + } + break; + } + } + } +} + +QPrinter::PaperSize QMacPrintEnginePrivate::paperSize() const +{ + PMRect paper; + PMGetUnadjustedPaperRect(format, &paper); + int wMM = int((paper.right - paper.left) / 72 * 25.4 + 0.5); + int hMM = int((paper.bottom - paper.top) / 72 * 25.4 + 0.5); + for (int i = QPrinter::A4; i < QPrinter::NPaperSize; ++i) { + QSizeF s = qt_paperSizeToQSizeF(QPrinter::PaperSize(i)); + if (s.width() == wMM && s.height() == hMM) + return (QPrinter::PaperSize)i; + } + return QPrinter::Custom; +} + +QList<QVariant> QMacPrintEnginePrivate::supportedResolutions() const +{ + Q_ASSERT_X(session, "QMacPrinterEngine::supportedResolutions", + "must have a valid printer session"); + UInt32 resCount; + QList<QVariant> resolutions; + PMPrinter printer; + if (PMSessionGetCurrentPrinter(session, &printer) == noErr) { + PMResolution res; + OSStatus status = PMPrinterGetPrinterResolutionCount(printer, &resCount); + if (status == kPMNotImplemented) { +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_5) + // *Sigh* we have to use the non-indexed version. + if (PMPrinterGetPrinterResolution(printer, kPMMinSquareResolution, &res) == noErr) + resolutions.append(int(res.hRes)); + if (PMPrinterGetPrinterResolution(printer, kPMMaxSquareResolution, &res) == noErr) { + QVariant var(int(res.hRes)); + if (!resolutions.contains(var)) + resolutions.append(var); + } + if (PMPrinterGetPrinterResolution(printer, kPMDefaultResolution, &res) == noErr) { + QVariant var(int(res.hRes)); + if (!resolutions.contains(var)) + resolutions.append(var); + } +#endif + } else if (status == noErr) { + // According to the docs, index start at 1. + for (UInt32 i = 1; i <= resCount; ++i) { + if (PMPrinterGetIndexedPrinterResolution(printer, i, &res) == noErr) + resolutions.append(QVariant(int(res.hRes))); + } + } else { + qWarning("QMacPrintEngine::supportedResolutions: Unexpected error: %ld", long(status)); + } + } + return resolutions; +} + +bool QMacPrintEnginePrivate::shouldSuppressStatus() const +{ + if (suppressStatus == true) + return true; + + // Supress displaying the automatic progress dialog if we are printing + // from a non-gui thread. + return (qApp->thread() != QThread::currentThread()); +} + +QPrinter::PrinterState QMacPrintEngine::printerState() const +{ + return d_func()->state; +} + +bool QMacPrintEngine::newPage() +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + OSStatus err = +#ifndef QT_MAC_USE_COCOA + d->shouldSuppressStatus() ? PMSessionEndPageNoDialog(d->session) + : PMSessionEndPage(d->session); +#else + PMSessionEndPageNoDialog(d->session); +#endif + if (err != noErr) { + if (err == kPMCancel) { + // User canceled, we need to abort! + abort(); + } else { + // Not sure what the problem is... + qWarning("QMacPrintEngine::newPage: Cannot end current page. %ld", long(err)); + d->state = QPrinter::Error; + } + return false; + } + return d->newPage_helper(); +} + +bool QMacPrintEngine::abort() +{ + Q_D(QMacPrintEngine); + if (d->state != QPrinter::Active) + return false; + bool ret = end(); + d->state = QPrinter::Aborted; + return ret; +} + +static inline int qt_get_PDMWidth(PMPageFormat pformat, bool fullPage, + const PMResolution &resolution) +{ + int val = 0; + PMRect r; + qreal hRatio = resolution.hRes / 72; + if (fullPage) { + if (PMGetAdjustedPaperRect(pformat, &r) == noErr) + val = qRound((r.right - r.left) * hRatio); + } else { + if (PMGetAdjustedPageRect(pformat, &r) == noErr) + val = qRound((r.right - r.left) * hRatio); + } + return val; +} + +static inline int qt_get_PDMHeight(PMPageFormat pformat, bool fullPage, + const PMResolution &resolution) +{ + int val = 0; + PMRect r; + qreal vRatio = resolution.vRes / 72; + if (fullPage) { + if (PMGetAdjustedPaperRect(pformat, &r) == noErr) + val = qRound((r.bottom - r.top) * vRatio); + } else { + if (PMGetAdjustedPageRect(pformat, &r) == noErr) + val = qRound((r.bottom - r.top) * vRatio); + } + return val; +} + + +int QMacPrintEngine::metric(QPaintDevice::PaintDeviceMetric m) const +{ + Q_D(const QMacPrintEngine); + int val = 1; + switch (m) { + case QPaintDevice::PdmWidth: + if (d->hasCustomPaperSize) { + val = qRound(d->customSize.width()); + if (d->hasCustomPageMargins) { + val -= qRound(d->leftMargin + d->rightMargin); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + val -= qRound(margins.at(0).toDouble() + margins.at(2).toDouble()); + } + } else { + val = qt_get_PDMWidth(d->format, property(PPK_FullPage).toBool(), d->resolution); + } + break; + case QPaintDevice::PdmHeight: + if (d->hasCustomPaperSize) { + val = qRound(d->customSize.height()); + if (d->hasCustomPageMargins) { + val -= qRound(d->topMargin + d->bottomMargin); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + val -= qRound(margins.at(1).toDouble() + margins.at(3).toDouble()); + } + } else { + val = qt_get_PDMHeight(d->format, property(PPK_FullPage).toBool(), d->resolution); + } + break; + case QPaintDevice::PdmWidthMM: + val = metric(QPaintDevice::PdmWidth); + val = int((val * 254 + 5 * d->resolution.hRes) / (10 * d->resolution.hRes)); + break; + case QPaintDevice::PdmHeightMM: + val = metric(QPaintDevice::PdmHeight); + val = int((val * 254 + 5 * d->resolution.vRes) / (10 * d->resolution.vRes)); + break; + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: { + PMPrinter printer; + if(PMSessionGetCurrentPrinter(d->session, &printer) == noErr) { + PMResolution resolution; +#ifndef QT_MAC_USE_COCOA +# if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + PMPrinterGetOutputResolution(printer, d->settings, &resolution); + } else +# endif + { + PMPrinterGetPrinterResolution(printer, kPMCurrentValue, &resolution); + } +#else + PMPrinterGetOutputResolution(printer, d->settings, &resolution); +#endif + val = (int)resolution.vRes; + break; + } + //otherwise fall through + } + case QPaintDevice::PdmDpiY: + val = (int)d->resolution.vRes; + break; + case QPaintDevice::PdmDpiX: + val = (int)d->resolution.hRes; + break; + case QPaintDevice::PdmNumColors: + val = (1 << metric(QPaintDevice::PdmDepth)); + break; + case QPaintDevice::PdmDepth: + val = 24; + break; + default: + val = 0; + qWarning("QPrinter::metric: Invalid metric command"); + } + return val; +} + +void QMacPrintEnginePrivate::initialize() +{ + Q_Q(QMacPrintEngine); + +#ifndef QT_MAC_USE_COCOA + Q_ASSERT(!session); +#else + Q_ASSERT(!printInfo); +#endif + + if (!paintEngine) + paintEngine = new QCoreGraphicsPaintEngine(); + + q->gccaps = paintEngine->gccaps; + + fullPage = false; + +#ifndef QT_MAC_USE_COCOA + if (PMCreateSession(&session) != 0) + session = 0; +#else + QMacCocoaAutoReleasePool pool; + printInfo = [[NSPrintInfo alloc] initWithDictionary:[NSDictionary dictionary]]; + session = static_cast<PMPrintSession>([printInfo PMPrintSession]); +#endif + + PMPrinter printer; + if (session && PMSessionGetCurrentPrinter(session, &printer) == noErr) { + QList<QVariant> resolutions = supportedResolutions(); + if (!resolutions.isEmpty() && mode != QPrinter::ScreenResolution) { + if (resolutions.count() > 1 && mode == QPrinter::HighResolution) { + int max = 0; + for (int i = 0; i < resolutions.count(); ++i) { + int value = resolutions.at(i).toInt(); + if (value > max) + max = value; + } + resolution.hRes = resolution.vRes = max; + } else { + resolution.hRes = resolution.vRes = resolutions.at(0).toInt(); + } + if(resolution.hRes == 0) + resolution.hRes = resolution.vRes = 600; + } else { + resolution.hRes = resolution.vRes = qt_defaultDpi(); + } + } + +#ifndef QT_MAC_USE_COCOA + bool settingsInitialized = (settings != 0); + bool settingsOK = !settingsInitialized ? PMCreatePrintSettings(&settings) == noErr : true; + if (settingsOK && !settingsInitialized) + settingsOK = PMSessionDefaultPrintSettings(session, settings) == noErr; + + + bool formatInitialized = (format != 0); + bool formatOK = !formatInitialized ? PMCreatePageFormat(&format) == noErr : true; + if (formatOK) { + if (!formatInitialized) { + formatOK = PMSessionDefaultPageFormat(session, format) == noErr; + } + formatOK = PMSessionValidatePageFormat(session, format, kPMDontWantBoolean) == noErr; + } +#else + settings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]); + format = static_cast<PMPageFormat>([printInfo PMPageFormat]); +#endif + +#ifndef QT_MAC_USE_COCOA + if (!settingsOK || !formatOK) { + qWarning("QMacPrintEngine::initialize: Unable to initialize QPainter"); + state = QPrinter::Error; + } +#endif + + QHash<QMacPrintEngine::PrintEnginePropertyKey, QVariant>::const_iterator propC; + for (propC = valueCache.constBegin(); propC != valueCache.constEnd(); propC++) { + q->setProperty(propC.key(), propC.value()); + } +} + +void QMacPrintEnginePrivate::releaseSession() +{ +#ifndef QT_MAC_USE_COCOA + if (shouldSuppressStatus()) { + PMSessionEndPageNoDialog(session); + PMSessionEndDocumentNoDialog(session); + } else { + PMSessionEndPage(session); + PMSessionEndDocument(session); + } + PMRelease(session); +#else + PMSessionEndPageNoDialog(session); + PMSessionEndDocumentNoDialog(session); + [printInfo release]; +#endif + printInfo = 0; + session = 0; +} + +bool QMacPrintEnginePrivate::newPage_helper() +{ + Q_Q(QMacPrintEngine); + Q_ASSERT(state == QPrinter::Active); + + if (PMSessionError(session) != noErr) { + q->abort(); + return false; + } + + // pop the stack of saved graphic states, in case we get the same + // context back - either way, the stack count should be 0 when we + // get the new one + QCoreGraphicsPaintEngine *cgEngine = static_cast<QCoreGraphicsPaintEngine*>(paintEngine); + while (cgEngine->d_func()->stackCount > 0) + cgEngine->d_func()->restoreGraphicsState(); + + OSStatus status = +#ifndef QT_MAC_USE_COCOA + shouldSuppressStatus() ? PMSessionBeginPageNoDialog(session, format, 0) + : PMSessionBeginPage(session, format, 0); +#else + PMSessionBeginPageNoDialog(session, format, 0); +#endif + if(status != noErr) { + state = QPrinter::Error; + return false; + } + + QRect page = q->property(QPrintEngine::PPK_PageRect).toRect(); + QRect paper = q->property(QPrintEngine::PPK_PaperRect).toRect(); + + CGContextRef cgContext; + OSStatus err = noErr; + err = PMSessionGetCGGraphicsContext(session, &cgContext); + if(err != noErr) { + qWarning("QMacPrintEngine::newPage: Cannot retrieve CoreGraphics context: %ld", long(err)); + state = QPrinter::Error; + return false; + } + cgEngine->d_func()->hd = cgContext; + + // Set the resolution as a scaling ration of 72 (the default). + CGContextScaleCTM(cgContext, 72 / resolution.hRes, 72 / resolution.vRes); + + CGContextScaleCTM(cgContext, 1, -1); + CGContextTranslateCTM(cgContext, 0, -paper.height()); + if (!fullPage) + CGContextTranslateCTM(cgContext, page.x() - paper.x(), page.y() - paper.y()); + cgEngine->d_func()->orig_xform = CGContextGetCTM(cgContext); + cgEngine->d_func()->setClip(0); + cgEngine->state->dirtyFlags = QPaintEngine::DirtyFlag(QPaintEngine::AllDirty + & ~(QPaintEngine::DirtyClipEnabled + | QPaintEngine::DirtyClipRegion + | QPaintEngine::DirtyClipPath)); + if (cgEngine->painter()->hasClipping()) + cgEngine->state->dirtyFlags |= QPaintEngine::DirtyClipEnabled; + cgEngine->syncState(); + return true; +} + + +void QMacPrintEngine::updateState(const QPaintEngineState &state) +{ + d_func()->paintEngine->updateState(state); +} + +void QMacPrintEngine::drawRects(const QRectF *r, int num) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawRects(r, num); +} + +void QMacPrintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPoints(points, pointCount); +} + +void QMacPrintEngine::drawEllipse(const QRectF &r) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawEllipse(r); +} + +void QMacPrintEngine::drawLines(const QLineF *lines, int lineCount) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawLines(lines, lineCount); +} + +void QMacPrintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPolygon(points, pointCount, mode); +} + +void QMacPrintEngine::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPixmap(r, pm, sr); +} + +void QMacPrintEngine::drawImage(const QRectF &r, const QImage &pm, const QRectF &sr, Qt::ImageConversionFlags flags) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawImage(r, pm, sr, flags); +} + +void QMacPrintEngine::drawTextItem(const QPointF &p, const QTextItem &ti) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawTextItem(p, ti); +} + +void QMacPrintEngine::drawTiledPixmap(const QRectF &dr, const QPixmap &pixmap, const QPointF &sr) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawTiledPixmap(dr, pixmap, sr); +} + +void QMacPrintEngine::drawPath(const QPainterPath &path) +{ + Q_D(QMacPrintEngine); + Q_ASSERT(d->state == QPrinter::Active); + d->paintEngine->drawPath(path); +} + + +void QMacPrintEngine::setProperty(PrintEnginePropertyKey key, const QVariant &value) +{ + Q_D(QMacPrintEngine); + + d->valueCache.insert(key, value); + if (!d->session) + return; + + switch (key) { + case PPK_CollateCopies: + break; + case PPK_ColorMode: + break; + case PPK_Creator: + break; + case PPK_DocumentName: + break; + case PPK_PageOrder: + break; + case PPK_PaperSource: + break; + case PPK_SelectionOption: + break; + case PPK_Resolution: { + PMPrinter printer; + UInt32 count; + if (PMSessionGetCurrentPrinter(d->session, &printer) != noErr) + break; + if (PMPrinterGetPrinterResolutionCount(printer, &count) != noErr) + break; + PMResolution resolution = { 0.0, 0.0 }; + PMResolution bestResolution = { 0.0, 0.0 }; + int dpi = value.toInt(); + int bestDistance = INT_MAX; + for (UInt32 i = 1; i <= count; ++i) { // Yes, it starts at 1 + if (PMPrinterGetIndexedPrinterResolution(printer, i, &resolution) == noErr) { + if (dpi == int(resolution.hRes)) { + bestResolution = resolution; + break; + } else { + int distance = qAbs(dpi - int(resolution.hRes)); + if (distance < bestDistance) { + bestDistance = distance; + bestResolution = resolution; + } + } + } + } + PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean); + break; + } + + case PPK_FullPage: + d->fullPage = value.toBool(); + break; + case PPK_CopyCount: // fallthrough + case PPK_NumberOfCopies: + PMSetCopies(d->settings, value.toInt(), false); + break; + case PPK_Orientation: { + if (d->state == QPrinter::Active) { + qWarning("QMacPrintEngine::setOrientation: Orientation cannot be changed during a print job, ignoring change"); + } else { + QPrinter::Orientation newOrientation = QPrinter::Orientation(value.toInt()); + if (d->hasCustomPaperSize && (d->orient != newOrientation)) + d->customSize = QSizeF(d->customSize.height(), d->customSize.width()); + d->orient = newOrientation; + PMOrientation o = d->orient == QPrinter::Portrait ? kPMPortrait : kPMLandscape; + PMSetOrientation(d->format, o, false); + PMSessionValidatePageFormat(d->session, d->format, kPMDontWantBoolean); + } + break; } + case PPK_OutputFileName: + d->outputFilename = value.toString(); + break; + case PPK_PaperSize: + d->setPaperSize(QPrinter::PaperSize(value.toInt())); + break; + case PPK_PrinterName: { + bool printerNameSet = false; + OSStatus status = noErr; + QCFType<CFArrayRef> printerList; + status = PMServerCreatePrinterList(kPMServerLocal, &printerList); + if (status == noErr) { + CFIndex count = CFArrayGetCount(printerList); + for (CFIndex i=0; i<count; ++i) { + PMPrinter printer = static_cast<PMPrinter>(const_cast<void *>(CFArrayGetValueAtIndex(printerList, i))); + QString name = QCFString::toQString(PMPrinterGetName(printer)); + if (name == value.toString()) { + status = PMSessionSetCurrentPMPrinter(d->session, printer); + printerNameSet = true; + break; + } + } + } + if (status != noErr) + qWarning("QMacPrintEngine::setPrinterName: Error setting printer: %ld", long(status)); + if (!printerNameSet) { + qWarning("QMacPrintEngine::setPrinterName: Failed to set printer named '%s'.", qPrintable(value.toString())); + d->releaseSession(); + d->state = QPrinter::Idle; + } + break; } + case PPK_SuppressSystemPrintStatus: + d->suppressStatus = value.toBool(); + break; + case PPK_CustomPaperSize: + { + PMOrientation orientation; + PMGetOrientation(d->format, &orientation); + d->hasCustomPaperSize = true; + d->customSize = value.toSizeF(); + if (orientation != kPMPortrait) + d->customSize = QSizeF(d->customSize.height(), d->customSize.width()); + break; + } + case PPK_PageMargins: + { + QList<QVariant> margins(value.toList()); + Q_ASSERT(margins.size() == 4); + d->leftMargin = margins.at(0).toDouble(); + d->topMargin = margins.at(1).toDouble(); + d->rightMargin = margins.at(2).toDouble(); + d->bottomMargin = margins.at(3).toDouble(); + d->hasCustomPageMargins = true; + break; + } + + default: + break; + } +} + +QVariant QMacPrintEngine::property(PrintEnginePropertyKey key) const +{ + Q_D(const QMacPrintEngine); + QVariant ret; + + if (!d->session && d->valueCache.contains(key)) + return *d->valueCache.find(key); + + switch (key) { + case PPK_CollateCopies: + ret = false; + break; + case PPK_ColorMode: + ret = QPrinter::Color; + break; + case PPK_Creator: + break; + case PPK_DocumentName: + break; + case PPK_FullPage: + ret = d->fullPage; + break; + case PPK_NumberOfCopies: + ret = 1; + break; + case PPK_CopyCount: { + UInt32 copies = 1; + PMGetCopies(d->settings, &copies); + ret = (uint) copies; + break; + } + case PPK_SupportsMultipleCopies: + ret = true; + break; + case PPK_Orientation: + PMOrientation orientation; + PMGetOrientation(d->format, &orientation); + ret = orientation == kPMPortrait ? QPrinter::Portrait : QPrinter::Landscape; + break; + case PPK_OutputFileName: + ret = d->outputFilename; + break; + case PPK_PageOrder: + break; + case PPK_PaperSource: + break; + case PPK_PageRect: { + // PageRect is returned in device pixels + QRect r; + PMRect macrect, macpaper; + qreal hRatio = d->resolution.hRes / 72; + qreal vRatio = d->resolution.vRes / 72; + if (d->hasCustomPaperSize) { + r = QRect(0, 0, qRound(d->customSize.width() * hRatio), qRound(d->customSize.height() * vRatio)); + if (d->hasCustomPageMargins) { + r.adjust(qRound(d->leftMargin * hRatio), qRound(d->topMargin * vRatio), + -qRound(d->rightMargin * hRatio), -qRound(d->bottomMargin * vRatio)); + } else { + QList<QVariant> margins = property(QPrintEngine::PPK_PageMargins).toList(); + r.adjust(qRound(margins.at(0).toDouble() * hRatio), + qRound(margins.at(1).toDouble() * vRatio), + -qRound(margins.at(2).toDouble() * hRatio), + -qRound(margins.at(3).toDouble()) * vRatio); + } + } else if (PMGetAdjustedPageRect(d->format, ¯ect) == noErr + && PMGetAdjustedPaperRect(d->format, &macpaper) == noErr) + { + if (d->fullPage || d->hasCustomPageMargins) { + r.setCoords(int(macpaper.left * hRatio), int(macpaper.top * vRatio), + int(macpaper.right * hRatio), int(macpaper.bottom * vRatio)); + r.translate(-r.x(), -r.y()); + if (d->hasCustomPageMargins) { + r.adjust(qRound(d->leftMargin * hRatio), qRound(d->topMargin * vRatio), + -qRound(d->rightMargin * hRatio), -qRound(d->bottomMargin * vRatio)); + } + } else { + r.setCoords(int(macrect.left * hRatio), int(macrect.top * vRatio), + int(macrect.right * hRatio), int(macrect.bottom * vRatio)); + r.translate(int(-macpaper.left * hRatio), int(-macpaper.top * vRatio)); + } + } + ret = r; + break; } + case PPK_PaperSize: + ret = d->paperSize(); + break; + case PPK_PaperRect: { + QRect r; + PMRect macrect; + if (d->hasCustomPaperSize) { + r = QRect(0, 0, qRound(d->customSize.width()), qRound(d->customSize.height())); + } else if (PMGetAdjustedPaperRect(d->format, ¯ect) == noErr) { + qreal hRatio = d->resolution.hRes / 72; + qreal vRatio = d->resolution.vRes / 72; + r.setCoords(int(macrect.left * hRatio), int(macrect.top * vRatio), + int(macrect.right * hRatio), int(macrect.bottom * vRatio)); + r.translate(-r.x(), -r.y()); + } + ret = r; + break; } + case PPK_PrinterName: { + PMPrinter printer; + OSStatus status = PMSessionGetCurrentPrinter(d->session, &printer); + if (status != noErr) + qWarning("QMacPrintEngine::printerName: Failed getting current PMPrinter: %ld", long(status)); + if (printer) + ret = QCFString::toQString(PMPrinterGetName(printer)); + break; } + case PPK_Resolution: { + ret = d->resolution.hRes; + break; + } + case PPK_SupportedResolutions: + ret = d->supportedResolutions(); + break; + case PPK_CustomPaperSize: + ret = d->customSize; + break; + case PPK_PageMargins: + { + QList<QVariant> margins; + if (d->hasCustomPageMargins) { + margins << d->leftMargin << d->topMargin + << d->rightMargin << d->bottomMargin; + } else { + PMPaperMargins paperMargins; + PMPaper paper; + PMGetPageFormatPaper(d->format, &paper); + PMPaperGetMargins(paper, &paperMargins); + margins << paperMargins.left << paperMargins.top + << paperMargins.right << paperMargins.bottom; + } + ret = margins; + break; + } + default: + break; + } + return ret; +} + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER diff --git a/src/gui/platforms/mac/qprintengine_mac_p.h b/src/gui/platforms/mac/qprintengine_mac_p.h new file mode 100644 index 0000000000..511705d26f --- /dev/null +++ b/src/gui/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<QMacPrintEngine::PrintEnginePropertyKey, QVariant> valueCache; + QMacPrintEnginePrivate() : mode(QPrinter::ScreenResolution), state(QPrinter::Idle), + orient(QPrinter::Portrait), printInfo(0), format(0), settings(0), + session(0), paintEngine(0), suppressStatus(false), + hasCustomPaperSize(false), hasCustomPageMargins(false) {} + ~QMacPrintEnginePrivate(); + void initialize(); + void releaseSession(); + bool newPage_helper(); + void setPaperSize(QPrinter::PaperSize ps); + QPrinter::PaperSize paperSize() const; + QList<QVariant> supportedResolutions() const; + inline bool isPrintSessionInitialized() const + { +#ifndef QT_MAC_USE_COCOA + return session != 0; +#else + return printInfo != 0; +#endif + } + bool shouldSuppressStatus() const; +}; + +QT_END_NAMESPACE + +#endif // QT_NO_PRINTER + +#endif // QPRINTENGINE_WIN_P_H diff --git a/src/gui/platforms/mac/qprinterinfo_mac.cpp b/src/gui/platforms/mac/qprinterinfo_mac.cpp new file mode 100644 index 0000000000..b24ab70267 --- /dev/null +++ b/src/gui/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> QPrinterInfo::availablePrinters() +{ + QList<QPrinterInfo> printers; + + QCFType<CFArrayRef> array; + if (PMServerCreatePrinterList(kPMServerLocal, &array) == noErr) { + CFIndex count = CFArrayGetCount(array); + for (int i = 0; i < count; ++i) { + PMPrinter printer = static_cast<PMPrinter>(const_cast<void *>(CFArrayGetValueAtIndex(array, i))); + QString printerName = QCFString::toQString(PMPrinterGetName(printer)); + + QPrinterInfo printerInfo(printerName); + if (PMPrinterIsDefault(printer)) + printerInfo.d_ptr->isDefault = true; + printers.append(printerInfo); + } + } + + return printers; +} + +QPrinterInfo QPrinterInfo::defaultPrinter() +{ + QList<QPrinterInfo> printers = availablePrinters(); + foreach (const QPrinterInfo &printerInfo, printers) { + if (printerInfo.isDefault()) + return printerInfo; + } + + return printers.value(0); +} + +QList<QPrinter::PaperSize> QPrinterInfo::supportedPaperSizes() const +{ + const Q_D(QPrinterInfo); + + QList<QPrinter::PaperSize> paperSizes; + if (isNull()) + return paperSizes; + + PMPrinter cfPrn = PMPrinterCreateFromPrinterID(QCFString::toCFStringRef(d->name)); + if (!cfPrn) + return paperSizes; + + CFArrayRef array; + if (PMPrinterGetPaperList(cfPrn, &array) != noErr) { + PMRelease(cfPrn); + return paperSizes; + } + + int count = CFArrayGetCount(array); + for (int i = 0; i < count; ++i) { + PMPaper paper = static_cast<PMPaper>(const_cast<void *>(CFArrayGetValueAtIndex(array, i))); + double width, height; + if (PMPaperGetWidth(paper, &width) == noErr && PMPaperGetHeight(paper, &height) == noErr) { + QSizeF size(width * 0.3527, height * 0.3527); + paperSizes.append(qSizeFTopaperSize(size)); + } + } + + PMRelease(cfPrn); + + return paperSizes; +} + +#endif // QT_NO_PRINTER + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qrawfont_mac.cpp b/src/gui/platforms/mac/qrawfont_mac.cpp new file mode 100644 index 0000000000..1ed4185a5d --- /dev/null +++ b/src/gui/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 <QtCore/qglobal.h> + +#if !defined(QT_NO_RAWFONT) + +#include "qrawfont_p.h" +#include "qfontengine_coretext_p.h" + +QT_BEGIN_NAMESPACE + +void QRawFontPrivate::platformCleanUp() +{ +} + +extern int qt_defaultDpi(); + +void QRawFontPrivate::platformLoadFromData(const QByteArray &fontData, + int pixelSize, + QFont::HintingPreference hintingPreference) +{ + // Mac OS X ignores it + Q_UNUSED(hintingPreference); + + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(NULL, + fontData.constData(), fontData.size(), NULL); + + CGFontRef cgFont = CGFontCreateWithDataProvider(dataProvider); + + if (cgFont == NULL) { + qWarning("QRawFont::platformLoadFromData: CGFontCreateWithDataProvider failed"); + } else { + QFontDef def; + def.pixelSize = pixelSize; + def.pointSize = pixelSize * 72.0 / qt_defaultDpi(); + fontEngine = new QCoreTextFontEngine(cgFont, def); + CFRelease(cgFont); + fontEngine->ref.ref(); + } +} + +QT_END_NAMESPACE + +#endif // QT_NO_RAWFONT diff --git a/src/gui/platforms/mac/qregion_mac.cpp b/src/gui/platforms/mac/qregion_mac.cpp new file mode 100644 index 0000000000..50fd783df4 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include "qcoreapplication.h" +#include <qlibrary.h> + +QT_BEGIN_NAMESPACE + +QRegion::QRegionData QRegion::shared_empty = { Q_BASIC_ATOMIC_INITIALIZER(1), 0 }; + +#if defined(Q_WS_MAC32) && !defined(QT_MAC_USE_COCOA) +#define RGN_CACHE_SIZE 200 +#ifdef RGN_CACHE_SIZE +static bool rgncache_init = false; +static int rgncache_used; +static RgnHandle rgncache[RGN_CACHE_SIZE]; +static void qt_mac_cleanup_rgncache() +{ + rgncache_init = false; + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(rgncache[i]) { + --rgncache_used; + DisposeRgn(rgncache[i]); + rgncache[i] = 0; + } + } +} +#endif + +Q_GUI_EXPORT RgnHandle qt_mac_get_rgn() +{ +#ifdef RGN_CACHE_SIZE + if(!rgncache_init) { + rgncache_used = 0; + rgncache_init = true; + for(int i = 0; i < RGN_CACHE_SIZE; ++i) + rgncache[i] = 0; + qAddPostRoutine(qt_mac_cleanup_rgncache); + } else if(rgncache_used) { + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(rgncache[i]) { + RgnHandle ret = rgncache[i]; + SetEmptyRgn(ret); + rgncache[i] = 0; + --rgncache_used; + return ret; + } + } + } +#endif + return NewRgn(); +} + +Q_GUI_EXPORT void qt_mac_dispose_rgn(RgnHandle r) +{ +#ifdef RGN_CACHE_SIZE + if(rgncache_init && rgncache_used < RGN_CACHE_SIZE) { + for(int i = 0; i < RGN_CACHE_SIZE; ++i) { + if(!rgncache[i]) { + ++rgncache_used; + rgncache[i] = r; + return; + } + } + } +#endif + DisposeRgn(r); +} + +static OSStatus qt_mac_get_rgn_rect(UInt16 msg, RgnHandle, const Rect *rect, void *reg) +{ + if(msg == kQDRegionToRectsMsgParse) { + QRect rct(rect->left, rect->top, (rect->right - rect->left), (rect->bottom - rect->top)); + if(!rct.isEmpty()) + *((QRegion *)reg) += rct; + } + return noErr; +} + +Q_GUI_EXPORT QRegion qt_mac_convert_mac_region(RgnHandle rgn) +{ + return QRegion::fromQDRgn(rgn); +} + +QRegion QRegion::fromQDRgn(RgnHandle rgn) +{ + QRegion ret; + ret.detach(); + OSStatus oss = QDRegionToRects(rgn, kQDParseRegionFromTopLeft, qt_mac_get_rgn_rect, (void *)&ret); + if(oss != noErr) + return QRegion(); + return ret; +} + +/*! + \internal + Create's a RegionHandle, it's the caller's responsibility to release. +*/ +RgnHandle QRegion::toQDRgn() const +{ + RgnHandle rgnHandle = qt_mac_get_rgn(); + if(d->qt_rgn && d->qt_rgn->numRects) { + RgnHandle tmp_rgn = qt_mac_get_rgn(); + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + SetRectRgn(tmp_rgn, + qMax(SHRT_MIN, qt_r->x()), + qMax(SHRT_MIN, qt_r->y()), + qMin(SHRT_MAX, qt_r->right() + 1), + qMin(SHRT_MAX, qt_r->bottom() + 1)); + UnionRgn(rgnHandle, tmp_rgn, rgnHandle); + ++qt_r; + } + qt_mac_dispose_rgn(tmp_rgn); + } + return rgnHandle; +} + +/*! + \internal + Create's a RegionHandle, it's the caller's responsibility to release. + Returns 0 if the QRegion overflows. +*/ +RgnHandle QRegion::toQDRgnForUpdate_sys() const +{ + RgnHandle rgnHandle = qt_mac_get_rgn(); + if(d->qt_rgn && d->qt_rgn->numRects) { + RgnHandle tmp_rgn = qt_mac_get_rgn(); + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + + // detect overflow. Tested for use with HIViewSetNeedsDisplayInRegion + // in QWidgetPrivate::update_sys(). + enum { HIViewSetNeedsDisplayInRegionOverflow = 10000 }; // empirically determined conservative value + if (qt_r->right() > HIViewSetNeedsDisplayInRegionOverflow || qt_r->bottom() > HIViewSetNeedsDisplayInRegionOverflow) { + qt_mac_dispose_rgn(tmp_rgn); + qt_mac_dispose_rgn(rgnHandle); + return 0; + } + + SetRectRgn(tmp_rgn, + qMax(SHRT_MIN, qt_r->x()), + qMax(SHRT_MIN, qt_r->y()), + qMin(SHRT_MAX, qt_r->right() + 1), + qMin(SHRT_MAX, qt_r->bottom() + 1)); + UnionRgn(rgnHandle, tmp_rgn, rgnHandle); + ++qt_r; + } + qt_mac_dispose_rgn(tmp_rgn); + } + return rgnHandle; +} + +#endif + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +OSStatus QRegion::shape2QRegionHelper(int inMessage, HIShapeRef, + const CGRect *inRect, void *inRefcon) +{ + QRegion *region = static_cast<QRegion *>(inRefcon); + if (!region) + return paramErr; + + switch (inMessage) { + case kHIShapeEnumerateRect: + *region += QRect(inRect->origin.x, inRect->origin.y, + inRect->size.width, inRect->size.height); + break; + case kHIShapeEnumerateInit: + // Assume the region is already setup correctly + case kHIShapeEnumerateTerminate: + default: + break; + } + return noErr; +} +#endif + +/*! + \internal + Create's a mutable shape, it's the caller's responsibility to release. + WARNING: this function clamps the coordinates to SHRT_MIN/MAX on 10.4 and below. +*/ +HIMutableShapeRef QRegion::toHIMutableShape() const +{ + HIMutableShapeRef shape = HIShapeCreateMutable(); +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + if (d->qt_rgn && d->qt_rgn->numRects) { + int n = d->qt_rgn->numRects; + const QRect *qt_r = (n == 1) ? &d->qt_rgn->extents : d->qt_rgn->rects.constData(); + while (n--) { + CGRect cgRect = CGRectMake(qt_r->x(), qt_r->y(), qt_r->width(), qt_r->height()); + HIShapeUnionWithRect(shape, &cgRect); + ++qt_r; + } + } + } else +#endif + { +#ifndef QT_MAC_USE_COCOA + QCFType<HIShapeRef> qdShape = HIShapeCreateWithQDRgn(QMacSmartQuickDrawRegion(toQDRgn())); + HIShapeUnion(qdShape, shape, shape); +#endif + } + return shape; +} + +#if !defined(Q_WS_MAC64) && !defined(QT_MAC_USE_COCOA) +typedef OSStatus (*PtrHIShapeGetAsQDRgn)(HIShapeRef, RgnHandle); +static PtrHIShapeGetAsQDRgn ptrHIShapeGetAsQDRgn = 0; +#endif + + +QRegion QRegion::fromHIShapeRef(HIShapeRef shape) +{ + QRegion returnRegion; + returnRegion.detach(); + // Begin gratuitous #if-defery +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +# ifndef Q_WS_MAC64 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { +# endif + HIShapeEnumerate(shape, kHIShapeParseFromTopLeft, shape2QRegionHelper, &returnRegion); +# ifndef Q_WS_MAC64 + } else +# endif +#endif + { +#if !defined(Q_WS_MAC64) && !defined(QT_MAC_USE_COCOA) + if (ptrHIShapeGetAsQDRgn == 0) { + QLibrary library(QLatin1String("/System/Library/Frameworks/Carbon.framework/Carbon")); + library.setLoadHints(QLibrary::ExportExternalSymbolsHint); + ptrHIShapeGetAsQDRgn = reinterpret_cast<PtrHIShapeGetAsQDRgn>(library.resolve("HIShapeGetAsQDRgn")); + } + RgnHandle rgn = qt_mac_get_rgn(); + ptrHIShapeGetAsQDRgn(shape, rgn); + returnRegion = QRegion::fromQDRgn(rgn); + qt_mac_dispose_rgn(rgn); +#endif + } + return returnRegion; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qsound_mac.mm b/src/gui/platforms/mac/qsound_mac.mm new file mode 100644 index 0000000000..5a9af135b0 --- /dev/null +++ b/src/gui/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 <qapplication.h> +#include "qsound.h" +#include "qsound_p.h" +#include <private/qt_mac_p.h> +#include <qhash.h> +#include <qdebug.h> +#import <AppKit/AppKit.h> + +#include <AppKit/NSSound.h> + +QT_BEGIN_NAMESPACE + +void qt_mac_beep() +{ + NSBeep(); +} + +QT_END_NAMESPACE + +#ifndef QT_NO_SOUND + +QT_BEGIN_NAMESPACE + +typedef QHash<QSound *, NSSound const *> Sounds; +static Sounds sounds; + +class QAuServerMac : public QAuServer +{ + Q_OBJECT +public: + QAuServerMac(QObject* parent) : QAuServer(parent) { } + void play(const QString& filename); + void play(QSound *s); + void stop(QSound*); + bool okay() { return true; } + using QAuServer::decLoop; // promote to public. +protected: + NSSound *createNSSound(const QString &filename, QSound *qSound); +}; + +QT_END_NAMESPACE + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSSoundDelegate <NSObject> +-(void)sound:(NSSound *)sound didFinishPlaying:(BOOL)aBool; +@end +#endif + +QT_USE_NAMESPACE + +@interface QT_MANGLE_NAMESPACE(QMacSoundDelegate) : NSObject<NSSoundDelegate> { + QSound *qSound; // may be null. + QAuServerMac* server; +} +-(id)initWithQSound:(QSound*)sound:(QAuServerMac*)server; +@end + +@implementation QT_MANGLE_NAMESPACE(QMacSoundDelegate) +-(id)initWithQSound:(QSound*)s:(QAuServerMac*)serv { + self = [super init]; + if(self) { + qSound = s; + server = serv; + } + return self; +} + +// Delegate function that gets called each time a sound finishes. +-(void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finishedOk +{ + // qSound is null if this sound was started by play(QString), + // in which case there is no corresponding QSound object. + if (qSound == 0) { + [sound release]; + [self release]; + return; + } + + // finishedOk is false if the sound cold not be played or + // if it was interrupted by stop(). + if (finishedOk == false) { + sounds.remove(qSound); + [sound release]; + [self release]; + return; + } + + // Check if the sound should loop "forever" (until stop). + if (qSound->loops() == -1) { + [sound play]; + return; + } + + const int remainingIterations = server->decLoop(qSound); + if (remainingIterations > 0) { + [sound play]; + } else { + sounds.remove(qSound); + [sound release]; + [self release]; + } +} +@end + +QT_BEGIN_NAMESPACE + +void QAuServerMac::play(const QString &fileName) +{ + QMacCocoaAutoReleasePool pool; + NSSound * const nsSound = createNSSound(fileName, 0); + [nsSound play]; +} + +void QAuServerMac::play(QSound *qSound) +{ + QMacCocoaAutoReleasePool pool; + NSSound * const nsSound = createNSSound(qSound->fileName(), qSound); + [nsSound play]; + // Keep track of the nsSound object so we can find it again in stop(). + sounds[qSound] = nsSound; +} + +void QAuServerMac::stop(QSound *qSound) +{ + Sounds::const_iterator it = sounds.constFind(qSound); + if (it != sounds.constEnd()) + [*it stop]; +} + +// Creates an NSSound object and installs a "sound finished" callack delegate on it. +NSSound *QAuServerMac::createNSSound(const QString &fileName, QSound *qSound) +{ + NSString *nsFileName = const_cast<NSString *>(reinterpret_cast<const NSString *>(QCFString::toCFStringRef(fileName))); + NSSound * const nsSound = [[NSSound alloc] initWithContentsOfFile: nsFileName byReference:YES]; + QT_MANGLE_NAMESPACE(QMacSoundDelegate) * const delegate = [[QT_MANGLE_NAMESPACE(QMacSoundDelegate) alloc] initWithQSound:qSound:this]; + [nsSound setDelegate:delegate]; + [nsFileName release]; + return nsSound; +} + +QAuServer* qt_new_audio_server() +{ + return new QAuServerMac(qApp); +} + +QT_END_NAMESPACE + +#include "qsound_mac.moc" + +#endif // QT_NO_SOUND diff --git a/src/gui/platforms/mac/qt_cocoa_helpers_mac.mm b/src/gui/platforms/mac/qt_cocoa_helpers_mac.mm new file mode 100644 index 0000000000..32123ee682 --- /dev/null +++ b/src/gui/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 <private/qcore_mac_p.h> +#include <qaction.h> +#include <qwidget.h> +#include <qdesktopwidget.h> +#include <qevent.h> +#include <qpixmapcache.h> +#include <qvarlengtharray.h> +#include <private/qevent_p.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qt_mac_p.h> +#include <private/qapplication_p.h> +#include <private/qcocoaapplication_mac_p.h> +#include <private/qcocoawindow_mac_p.h> +#include <private/qcocoaview_mac_p.h> +#include <private/qkeymapper_p.h> +#include <private/qwidget_p.h> +#include <private/qcocoawindow_mac_p.h> + +QT_BEGIN_NAMESPACE + +#ifdef QT_MAC_USE_COCOA +// Cmd + left mousebutton should produce a right button +// press (mainly for mac users with one-button mice): +static bool qt_leftButtonIsRightButton = false; +#endif + +Q_GLOBAL_STATIC(QMacWindowFader, macwindowFader); + +QMacWindowFader::QMacWindowFader() + : m_duration(0.250) +{ +} + +QMacWindowFader *QMacWindowFader::currentFader() +{ + return macwindowFader(); +} + +void QMacWindowFader::registerWindowToFade(QWidget *window) +{ + m_windowsToFade.append(window); +} + +void QMacWindowFader::performFade() +{ + const QWidgetList myWidgetsToFade = m_windowsToFade; + const int widgetCount = myWidgetsToFade.count(); +#if QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [NSAnimationContext beginGrouping]; + [[NSAnimationContext currentContext] setDuration:NSTimeInterval(m_duration)]; +#endif + + for (int i = 0; i < widgetCount; ++i) { + QWidget *widget = m_windowsToFade.at(i); + OSWindowRef window = qt_mac_window_for(widget); +#if QT_MAC_USE_COCOA + [[window animator] setAlphaValue:0.0]; + QTimer::singleShot(qRound(m_duration * 1000), widget, SLOT(hide())); +#else + TransitionWindowOptions options = {0, m_duration, 0, 0}; + TransitionWindowWithOptions(window, kWindowFadeTransitionEffect, kWindowHideTransitionAction, + 0, 1, &options); +#endif + } +#if QT_MAC_USE_COCOA + [NSAnimationContext endGrouping]; +#endif + m_duration = 0.250; + m_windowsToFade.clear(); +} + +extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); // qapplication.cpp; +extern QWidget * mac_mouse_grabber; +extern QWidget *qt_button_down; //qapplication_mac.cpp +extern QPointer<QWidget> qt_last_mouse_receiver; +extern OSViewRef qt_mac_effectiveview_for(const QWidget *w); +extern void qt_mac_updateCursorWithWidgetUnderMouse(QWidget *widgetUnderMouse); // qcursor_mac.mm + +void macWindowFade(void * /*OSWindowRef*/ window, float durationSeconds) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#endif + OSWindowRef wnd = static_cast<OSWindowRef>(window); + if (wnd) { + QWidget *widget; +#if QT_MAC_USE_COCOA + widget = [wnd QT_MANGLE_NAMESPACE(qt_qwidget)]; +#else + const UInt32 kWidgetCreatorQt = kEventClassQt; + enum { + kWidgetPropertyQWidget = 'QWId' //QWidget * + }; + if (GetWindowProperty(static_cast<WindowRef>(window), kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(widget), 0, &widget) != noErr) + widget = 0; +#endif + if (widget) { + QMacWindowFader::currentFader()->setFadeDuration(durationSeconds); + QMacWindowFader::currentFader()->registerWindowToFade(widget); + QMacWindowFader::currentFader()->performFade(); + } + } +} +struct dndenum_mapper +{ + NSDragOperation mac_code; + Qt::DropAction qt_code; + bool Qt2Mac; +}; + +#if defined(QT_MAC_USE_COCOA) && defined(__OBJC__) + +static dndenum_mapper dnd_enums[] = { + { NSDragOperationLink, Qt::LinkAction, true }, + { NSDragOperationMove, Qt::MoveAction, true }, + { NSDragOperationCopy, Qt::CopyAction, true }, + { NSDragOperationGeneric, Qt::CopyAction, false }, + { NSDragOperationEvery, Qt::ActionMask, false }, + { NSDragOperationNone, Qt::IgnoreAction, false } +}; + +NSDragOperation qt_mac_mapDropAction(Qt::DropAction action) +{ + for (int i=0; dnd_enums[i].qt_code; i++) { + if (dnd_enums[i].Qt2Mac && (action & dnd_enums[i].qt_code)) { + return dnd_enums[i].mac_code; + } + } + return NSDragOperationNone; +} + +NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions) +{ + NSDragOperation nsActions = NSDragOperationNone; + for (int i=0; dnd_enums[i].qt_code; i++) { + if (dnd_enums[i].Qt2Mac && (actions & dnd_enums[i].qt_code)) + nsActions |= dnd_enums[i].mac_code; + } + return nsActions; +} + +Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions) +{ + Qt::DropAction action = Qt::IgnoreAction; + for (int i=0; dnd_enums[i].mac_code; i++) { + if (nsActions & dnd_enums[i].mac_code) + return dnd_enums[i].qt_code; + } + return action; +} + +Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions) +{ + Qt::DropActions actions = Qt::IgnoreAction; + for (int i=0; dnd_enums[i].mac_code; i++) { + if (nsActions & dnd_enums[i].mac_code) + actions |= dnd_enums[i].qt_code; + } + return actions; +} + +Q_GLOBAL_STATIC(DnDParams, currentDnDParameters); +DnDParams *macCurrentDnDParameters() +{ + return currentDnDParameters(); +} +#endif + +bool macWindowIsTextured( void * /*OSWindowRef*/ window ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + return ( [wnd styleMask] & NSTexturedBackgroundWindowMask ) ? true : false; +#else + WindowAttributes currentAttributes; + GetWindowAttributes(wnd, ¤tAttributes); + return (currentAttributes & kWindowMetalAttribute) ? true : false; +#endif +} + +void macWindowToolbarShow(const QWidget *widget, bool show ) +{ + OSWindowRef wnd = qt_mac_window_for(widget); +#if QT_MAC_USE_COCOA + if (NSToolbar *toolbar = [wnd toolbar]) { + QMacCocoaAutoReleasePool pool; + if (show != [toolbar isVisible]) { + [toolbar setVisible:show]; + } else { + // The toolbar may be in sync, but we are not, update our framestrut. + qt_widget_private(const_cast<QWidget *>(widget))->updateFrameStrut(); + } + } +#else + qt_widget_private(const_cast<QWidget *>(widget))->updateFrameStrut(); + ShowHideWindowToolbar(wnd, show, false); +#endif +} + + +void macWindowToolbarSet( void * /*OSWindowRef*/ window, void *toolbarRef ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd setToolbar:static_cast<NSToolbar *>(toolbarRef)]; +#else + SetWindowToolbar(wnd, static_cast<HIToolbarRef>(toolbarRef)); +#endif +} + +bool macWindowToolbarIsVisible( void * /*OSWindowRef*/ window ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + if (NSToolbar *toolbar = [wnd toolbar]) + return [toolbar isVisible]; + return false; +#else + return IsWindowToolbarVisible(wnd); +#endif +} + +void macWindowSetHasShadow( void * /*OSWindowRef*/ window, bool hasShadow ) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd setHasShadow:BOOL(hasShadow)]; +#else + if (hasShadow) + ChangeWindowAttributes(wnd, 0, kWindowNoShadowAttribute); + else + ChangeWindowAttributes(wnd, kWindowNoShadowAttribute, 0); +#endif +} + +void macWindowFlush(void * /*OSWindowRef*/ window) +{ + OSWindowRef wnd = static_cast<OSWindowRef>(window); +#if QT_MAC_USE_COCOA + [wnd flushWindowIfNeeded]; +#else + HIWindowFlush(wnd); +#endif +} + +void * /*NSImage */qt_mac_create_nsimage(const QPixmap &pm) +{ + QMacCocoaAutoReleasePool pool; + if(QCFType<CGImageRef> image = pm.toMacCGImageRef()) { + NSImage *newImage = 0; + NSRect imageRect = NSMakeRect(0.0, 0.0, CGImageGetWidth(image), CGImageGetHeight(image)); + newImage = [[NSImage alloc] initWithSize:imageRect.size]; + [newImage lockFocus]; + { + CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image); + } + [newImage unlockFocus]; + return newImage; + } + return 0; +} + +void qt_mac_update_mouseTracking(QWidget *widget) +{ +#ifdef QT_MAC_USE_COCOA + [qt_mac_nativeview_for(widget) updateTrackingAreas]; +#else + Q_UNUSED(widget); +#endif +} + +OSStatus qt_mac_drawCGImage(CGContextRef inContext, const CGRect *inBounds, CGImageRef inImage) +{ + // Verbatim copy if HIViewDrawCGImage (as shown on Carbon-Dev) + OSStatus err = noErr; + + require_action(inContext != NULL, InvalidContext, err = paramErr); + require_action(inBounds != NULL, InvalidBounds, err = paramErr); + require_action(inImage != NULL, InvalidImage, err = paramErr); + + CGContextSaveGState( inContext ); + CGContextTranslateCTM (inContext, 0, inBounds->origin.y + CGRectGetMaxY(*inBounds)); + CGContextScaleCTM(inContext, 1, -1); + + CGContextDrawImage(inContext, *inBounds, inImage); + + CGContextRestoreGState(inContext); +InvalidImage: +InvalidBounds: +InvalidContext: + return err; +} + +bool qt_mac_checkForNativeSizeGrip(const QWidget *widget) +{ +#ifndef QT_MAC_USE_COCOA + OSViewRef nativeSizeGrip = 0; + HIViewFindByID(HIViewGetRoot(HIViewGetWindow(HIViewRef(widget->winId()))), kHIViewWindowGrowBoxID, &nativeSizeGrip); + return (nativeSizeGrip != 0); +#else + return [[reinterpret_cast<NSView *>(widget->effectiveWinId()) window] showsResizeIndicator]; +#endif +} +struct qt_mac_enum_mapper +{ + int mac_code; + int qt_code; +#if defined(DEBUG_MOUSE_MAPS) +# define QT_MAC_MAP_ENUM(x) x, #x + const char *desc; +#else +# define QT_MAC_MAP_ENUM(x) x +#endif +}; + +//mouse buttons +static qt_mac_enum_mapper qt_mac_mouse_symbols[] = { +{ kEventMouseButtonPrimary, QT_MAC_MAP_ENUM(Qt::LeftButton) }, +{ kEventMouseButtonSecondary, QT_MAC_MAP_ENUM(Qt::RightButton) }, +{ kEventMouseButtonTertiary, QT_MAC_MAP_ENUM(Qt::MidButton) }, +{ 4, QT_MAC_MAP_ENUM(Qt::XButton1) }, +{ 5, QT_MAC_MAP_ENUM(Qt::XButton2) }, +{ 0, QT_MAC_MAP_ENUM(0) } +}; +Qt::MouseButtons qt_mac_get_buttons(int buttons) +{ +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: **Mapping buttons: %d (0x%04x)", buttons, buttons); +#endif + Qt::MouseButtons ret = Qt::NoButton; + for(int i = 0; qt_mac_mouse_symbols[i].qt_code; i++) { + if (buttons & (0x01<<(qt_mac_mouse_symbols[i].mac_code-1))) { +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: got button: %s", qt_mac_mouse_symbols[i].desc); +#endif + ret |= Qt::MouseButtons(qt_mac_mouse_symbols[i].qt_code); + } + } + return ret; +} +Qt::MouseButton qt_mac_get_button(EventMouseButton button) +{ +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: **Mapping button: %d (0x%04x)", button, button); +#endif + Qt::MouseButtons ret = 0; + for(int i = 0; qt_mac_mouse_symbols[i].qt_code; i++) { + if (button == qt_mac_mouse_symbols[i].mac_code) { +#ifdef DEBUG_MOUSE_MAPS + qDebug("Qt: internal: got button: %s", qt_mac_mouse_symbols[i].desc); +#endif + return Qt::MouseButton(qt_mac_mouse_symbols[i].qt_code); + } + } + return Qt::NoButton; +} + +void macSendToolbarChangeEvent(QWidget *widget) +{ + QToolBarChangeEvent ev(!(GetCurrentKeyModifiers() & cmdKey)); + qt_sendSpontaneousEvent(widget, &ev); +} + +Q_GLOBAL_STATIC(QMacTabletHash, tablet_hash) +QMacTabletHash *qt_mac_tablet_hash() +{ + return tablet_hash(); +} + +#ifdef QT_MAC_USE_COCOA + +// Clears the QWidget pointer that each QCocoaView holds. +void qt_mac_clearCocoaViewQWidgetPointers(QWidget *widget) +{ + QT_MANGLE_NAMESPACE(QCocoaView) *cocoaView = reinterpret_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(qt_mac_nativeview_for(widget)); + if (cocoaView && [cocoaView respondsToSelector:@selector(qt_qwidget)]) { + [cocoaView qt_clearQWidget]; + } +} + +void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent) +{ + NSEvent *proximityEvent = static_cast<NSEvent *>(tabletEvent); + // simply construct a Carbon proximity record and handle it all in one spot. + TabletProximityRec carbonProximityRec = { [proximityEvent vendorID], + [proximityEvent tabletID], + [proximityEvent pointingDeviceID], + [proximityEvent deviceID], + [proximityEvent systemTabletID], + [proximityEvent vendorPointingDeviceType], + [proximityEvent pointingDeviceSerialNumber], + [proximityEvent uniqueID], + [proximityEvent capabilityMask], + [proximityEvent pointingDeviceType], + [proximityEvent isEnteringProximity] }; + qt_dispatchTabletProximityEvent(carbonProximityRec); +} +#endif // QT_MAC_USE_COCOA + +void qt_dispatchTabletProximityEvent(const ::TabletProximityRec &proxRec) +{ + QTabletDeviceData proximityDevice; + proximityDevice.tabletUniqueID = proxRec.uniqueID; + proximityDevice.capabilityMask = proxRec.capabilityMask; + + switch (proxRec.pointerType) { + case NSUnknownPointingDevice: + default: + proximityDevice.tabletPointerType = QTabletEvent::UnknownPointer; + break; + case NSPenPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Pen; + break; + case NSCursorPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Cursor; + break; + case NSEraserPointingDevice: + proximityDevice.tabletPointerType = QTabletEvent::Eraser; + break; + } + uint bits = proxRec.vendorPointerType; + if (bits == 0 && proximityDevice.tabletUniqueID != 0) { + // Fallback. It seems that the driver doesn't always include all the information. + // High-End Wacom devices store their "type" in the uper bits of the Unique ID. + // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. + bits = proximityDevice.tabletUniqueID >> 32; + } + // Defined in the "EN0056-NxtGenImpGuideX" + // on Wacom's Developer Website (www.wacomeng.com) + if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { + proximityDevice.tabletDeviceType = QTabletEvent::Stylus; + } else { + switch (bits & 0x0F06) { + case 0x0802: + proximityDevice.tabletDeviceType = QTabletEvent::Stylus; + break; + case 0x0902: + proximityDevice.tabletDeviceType = QTabletEvent::Airbrush; + break; + case 0x0004: + proximityDevice.tabletDeviceType = QTabletEvent::FourDMouse; + break; + case 0x0006: + proximityDevice.tabletDeviceType = QTabletEvent::Puck; + break; + case 0x0804: + proximityDevice.tabletDeviceType = QTabletEvent::RotationStylus; + break; + default: + proximityDevice.tabletDeviceType = QTabletEvent::NoDevice; + } + } + // The deviceID is "unique" while in the proximity, it's a key that we can use for + // linking up TabletDeviceData to an event (especially if there are two devices in action). + bool entering = proxRec.enterProximity; + if (entering) { + qt_mac_tablet_hash()->insert(proxRec.deviceID, proximityDevice); + } else { + qt_mac_tablet_hash()->remove(proxRec.deviceID); + } + + QTabletEvent qtabletProximity(entering ? QEvent::TabletEnterProximity + : QEvent::TabletLeaveProximity, + QPoint(), QPoint(), QPointF(), proximityDevice.tabletDeviceType, + proximityDevice.tabletPointerType, 0., 0, 0, 0., 0., 0, 0, + proximityDevice.tabletUniqueID); + + qt_sendSpontaneousEvent(qApp, &qtabletProximity); +} + +// Use this method to keep all the information in the TextSegment. As long as it is ordered +// we are in OK shape, and we can influence that ourselves. +struct KeyPair +{ + QChar cocoaKey; + Qt::Key qtKey; +}; + +bool operator==(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey == qchar; +} + +bool operator<(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey < qchar; +} + +bool operator<(QChar qchar, const KeyPair &entry) +{ + return qchar < entry.cocoaKey; +} + +bool operator<(const Qt::Key &key, const KeyPair &entry) +{ + return key < entry.qtKey; +} + +bool operator<(const KeyPair &entry, const Qt::Key &key) +{ + return entry.qtKey < key; +} + +static bool qtKey2CocoaKeySortLessThan(const KeyPair &entry1, const KeyPair &entry2) +{ + return entry1.qtKey < entry2.qtKey; +} + +static const int NumEntries = 59; +static const KeyPair entries[NumEntries] = { + { NSEnterCharacter, Qt::Key_Enter }, + { NSBackspaceCharacter, Qt::Key_Backspace }, + { NSTabCharacter, Qt::Key_Tab }, + { NSNewlineCharacter, Qt::Key_Return }, + { NSCarriageReturnCharacter, Qt::Key_Return }, + { NSBackTabCharacter, Qt::Key_Backtab }, + { kEscapeCharCode, Qt::Key_Escape }, + // Cocoa sends us delete when pressing backspace! + // (NB when we reverse this list in qtKey2CocoaKey, there + // will be two indices of Qt::Key_Backspace. But is seems to work + // ok for menu shortcuts (which uses that function): + { NSDeleteCharacter, Qt::Key_Backspace }, + { NSUpArrowFunctionKey, Qt::Key_Up }, + { NSDownArrowFunctionKey, Qt::Key_Down }, + { NSLeftArrowFunctionKey, Qt::Key_Left }, + { NSRightArrowFunctionKey, Qt::Key_Right }, + { NSF1FunctionKey, Qt::Key_F1 }, + { NSF2FunctionKey, Qt::Key_F2 }, + { NSF3FunctionKey, Qt::Key_F3 }, + { NSF4FunctionKey, Qt::Key_F4 }, + { NSF5FunctionKey, Qt::Key_F5 }, + { NSF6FunctionKey, Qt::Key_F6 }, + { NSF7FunctionKey, Qt::Key_F7 }, + { NSF8FunctionKey, Qt::Key_F8 }, + { NSF9FunctionKey, Qt::Key_F8 }, + { NSF10FunctionKey, Qt::Key_F10 }, + { NSF11FunctionKey, Qt::Key_F11 }, + { NSF12FunctionKey, Qt::Key_F12 }, + { NSF13FunctionKey, Qt::Key_F13 }, + { NSF14FunctionKey, Qt::Key_F14 }, + { NSF15FunctionKey, Qt::Key_F15 }, + { NSF16FunctionKey, Qt::Key_F16 }, + { NSF17FunctionKey, Qt::Key_F17 }, + { NSF18FunctionKey, Qt::Key_F18 }, + { NSF19FunctionKey, Qt::Key_F19 }, + { NSF20FunctionKey, Qt::Key_F20 }, + { NSF21FunctionKey, Qt::Key_F21 }, + { NSF22FunctionKey, Qt::Key_F22 }, + { NSF23FunctionKey, Qt::Key_F23 }, + { NSF24FunctionKey, Qt::Key_F24 }, + { NSF25FunctionKey, Qt::Key_F25 }, + { NSF26FunctionKey, Qt::Key_F26 }, + { NSF27FunctionKey, Qt::Key_F27 }, + { NSF28FunctionKey, Qt::Key_F28 }, + { NSF29FunctionKey, Qt::Key_F29 }, + { NSF30FunctionKey, Qt::Key_F30 }, + { NSF31FunctionKey, Qt::Key_F31 }, + { NSF32FunctionKey, Qt::Key_F32 }, + { NSF33FunctionKey, Qt::Key_F33 }, + { NSF34FunctionKey, Qt::Key_F34 }, + { NSF35FunctionKey, Qt::Key_F35 }, + { NSInsertFunctionKey, Qt::Key_Insert }, + { NSDeleteFunctionKey, Qt::Key_Delete }, + { NSHomeFunctionKey, Qt::Key_Home }, + { NSEndFunctionKey, Qt::Key_End }, + { NSPageUpFunctionKey, Qt::Key_PageUp }, + { NSPageDownFunctionKey, Qt::Key_PageDown }, + { NSPrintScreenFunctionKey, Qt::Key_Print }, + { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, + { NSPauseFunctionKey, Qt::Key_Pause }, + { NSSysReqFunctionKey, Qt::Key_SysReq }, + { NSMenuFunctionKey, Qt::Key_Menu }, + { NSHelpFunctionKey, Qt::Key_Help }, +}; +static const KeyPair * const end = entries + NumEntries; + +QChar qtKey2CocoaKey(Qt::Key key) +{ + // The first time this function is called, create a reverse + // looup table sorted on Qt Key rather than Cocoa key: + static QVector<KeyPair> rev_entries(NumEntries); + static bool mustInit = true; + if (mustInit){ + mustInit = false; + for (int i=0; i<NumEntries; ++i) + rev_entries[i] = entries[i]; + qSort(rev_entries.begin(), rev_entries.end(), qtKey2CocoaKeySortLessThan); + } + const QVector<KeyPair>::iterator i + = qBinaryFind(rev_entries.begin(), rev_entries.end(), key); + if (i == rev_entries.end()) + return QChar(); + return i->cocoaKey; +} + +#ifdef QT_MAC_USE_COCOA +static Qt::Key cocoaKey2QtKey(QChar keyCode) +{ + const KeyPair *i = qBinaryFind(entries, end, keyCode); + if (i == end) + return Qt::Key(keyCode.unicode()); + return i->qtKey; +} + +Qt::KeyboardModifiers qt_cocoaModifiers2QtModifiers(ulong modifierFlags) +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSShiftKeyMask) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSControlKeyMask) + qtMods |= Qt::MetaModifier; + if (modifierFlags & NSAlternateKeyMask) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSCommandKeyMask) + qtMods |= Qt::ControlModifier; + if (modifierFlags & NSNumericPadKeyMask) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + int len = [string length]; + if (len) { + QVarLengthArray <unichar, 10> characters(len); + bool changed = false; + for (int i = 0; i<len; i++) { + characters[i] = [string characterAtIndex:i]; + // check if they belong to key codes in private unicode range + // currently we need to handle only the NSDeleteFunctionKey + if (characters[i] == NSDeleteFunctionKey) { + characters[i] = NSDeleteCharacter; + changed = true; + } + } + if (changed) + return [NSString stringWithCharacters:characters.data() length:len]; + } + return string; +} + +Qt::KeyboardModifiers qt_cocoaDragOperation2QtModifiers(uint dragOperations) +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (dragOperations & NSDragOperationLink) + qtMods |= Qt::MetaModifier; + if (dragOperations & NSDragOperationGeneric) + qtMods |= Qt::ControlModifier; + if (dragOperations & NSDragOperationCopy) + qtMods |= Qt::AltModifier; + return qtMods; +} + +static inline QEvent::Type cocoaEvent2QtEvent(NSUInteger eventType) +{ + // Handle the trivial cases that can be determined from the type. + switch (eventType) { + case NSKeyDown: + return QEvent::KeyPress; + case NSKeyUp: + return QEvent::KeyRelease; + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + return QEvent::MouseButtonPress; + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + return QEvent::MouseButtonRelease; + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSOtherMouseDragged: + return QEvent::MouseMove; + case NSScrollWheel: + return QEvent::Wheel; + } + return QEvent::None; +} + +static bool mustUseCocoaKeyEvent() +{ + QCFType<TISInputSourceRef> source = TISCopyCurrentKeyboardInputSource(); + return TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) == 0; +} + +bool qt_dispatchKeyEventWithCocoa(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent) +{ + NSEvent *event = static_cast<NSEvent *>(keyEvent); + NSString *keyChars = [event charactersIgnoringModifiers]; + int keyLength = [keyChars length]; + if (keyLength == 0) + return false; // Dead Key, nothing to do! + bool ignoreText = false; + Qt::Key qtKey = Qt::Key_unknown; + if (keyLength == 1) { + QChar ch([keyChars characterAtIndex:0]); + if (ch.isLower()) + ch = ch.toUpper(); + qtKey = cocoaKey2QtKey(ch); + // Do not set the text for Function-Key Unicodes characters (0xF700–0xF8FF). + ignoreText = (ch.unicode() >= 0xF700 && ch.unicode() <= 0xF8FF); + } + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + QString text; + + // To quote from the Carbon port: This is actually wrong--but it is the best that + // can be done for now because of the Control/Meta mapping issues + // (we always get text on the Mac) + if (!ignoreText && !(keyMods & (Qt::ControlModifier | Qt::MetaModifier))) + text = QCFString::toQString(reinterpret_cast<CFStringRef>(keyChars)); + + UInt32 macScanCode = 1; + QKeyEventEx ke(cocoaEvent2QtEvent([event type]), qtKey, keyMods, text, [event isARepeat], qMax(1, keyLength), + macScanCode, [event keyCode], [event modifierFlags]); + return qt_sendSpontaneousEvent(widgetToGetEvent, &ke) && ke.isAccepted(); +} +#endif + +Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum) +{ + if (buttonNum == 0) + return Qt::LeftButton; + if (buttonNum == 1) + return Qt::RightButton; + if (buttonNum == 2) + return Qt::MidButton; + if (buttonNum == 3) + return Qt::XButton1; + if (buttonNum == 4) + return Qt::XButton2; + return Qt::NoButton; +} + +bool qt_dispatchKeyEvent(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(keyEvent); + Q_UNUSED(widgetToGetEvent); + return false; +#else + NSEvent *event = static_cast<NSEvent *>(keyEvent); + EventRef key_event = static_cast<EventRef>(const_cast<void *>([event eventRef])); + Q_ASSERT(key_event); + unsigned int info = 0; + + if ([event type] == NSKeyDown) { + NSString *characters = [event characters]; + if ([characters length]) { + unichar value = [characters characterAtIndex:0]; + qt_keymapper_private()->updateKeyMap(0, key_event, (void *)&value); + info = value; + } + } + + if (qt_mac_sendMacEventToWidget(widgetToGetEvent, key_event)) + return true; + + if (mustUseCocoaKeyEvent()) + return qt_dispatchKeyEventWithCocoa(keyEvent, widgetToGetEvent); + + bool consumed = qt_keymapper_private()->translateKeyEvent(widgetToGetEvent, 0, key_event, &info, true); + return consumed && (info != 0); +#endif +} + +void qt_dispatchModifiersChanged(void * /*NSEvent * */flagsChangedEvent, QWidget *widgetToGetEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(flagsChangedEvent); + Q_UNUSED(widgetToGetEvent); +#else + UInt32 modifiers = 0; + // Sync modifiers with Qt + NSEvent *event = static_cast<NSEvent *>(flagsChangedEvent); + EventRef key_event = static_cast<EventRef>(const_cast<void *>([event eventRef])); + Q_ASSERT(key_event); + GetEventParameter(key_event, kEventParamKeyModifiers, typeUInt32, 0, + sizeof(modifiers), 0, &modifiers); + extern void qt_mac_send_modifiers_changed(quint32 modifiers, QObject *object); + qt_mac_send_modifiers_changed(modifiers, widgetToGetEvent); +#endif +} + +QPointF flipPoint(const NSPoint &p) +{ + return QPointF(p.x, flipYCoordinate(p.y)); +} + +NSPoint flipPoint(const QPoint &p) +{ + return NSMakePoint(p.x(), flipYCoordinate(p.y())); +} + +NSPoint flipPoint(const QPointF &p) +{ + return NSMakePoint(p.x(), flipYCoordinate(p.y())); +} + +#if QT_MAC_USE_COCOA && __OBJC__ + +void qt_mac_handleNonClientAreaMouseEvent(NSWindow *window, NSEvent *event) +{ + QWidget *widgetToGetEvent = [window QT_MANGLE_NAMESPACE(qt_qwidget)]; + if (widgetToGetEvent == 0) + return; + + NSEventType evtType = [event type]; + QPoint qlocalPoint; + QPoint qglobalPoint; + bool processThisEvent = false; + bool fakeNCEvents = false; + bool fakeMouseEvents = false; + + // Check if this is a mouse event. + if (evtType == NSLeftMouseDown || evtType == NSLeftMouseUp + || evtType == NSRightMouseDown || evtType == NSRightMouseUp + || evtType == NSOtherMouseDown || evtType == NSOtherMouseUp + || evtType == NSMouseMoved || evtType == NSLeftMouseDragged + || evtType == NSRightMouseDragged || evtType == NSOtherMouseDragged) { + // Check if we want to pass this message to another window + if (mac_mouse_grabber && mac_mouse_grabber != widgetToGetEvent) { + NSWindow *grabWindow = static_cast<NSWindow *>(qt_mac_window_for(mac_mouse_grabber)); + if (window != grabWindow) { + window = grabWindow; + widgetToGetEvent = mac_mouse_grabber; + fakeNCEvents = true; + } + } + // Dont generate normal NC mouse events for Left Button dragged + if(evtType != NSLeftMouseDragged || fakeNCEvents) { + NSPoint windowPoint = [event locationInWindow]; + NSPoint globalPoint = [[event window] convertBaseToScreen:windowPoint]; + NSRect frameRect = [window frame]; + if (fakeNCEvents || NSMouseInRect(globalPoint, frameRect, NO)) { + NSRect contentRect = [window contentRectForFrameRect:frameRect]; + qglobalPoint = QPoint(flipPoint(globalPoint).toPoint()); + QWidget *w = widgetToGetEvent->childAt(widgetToGetEvent->mapFromGlobal(qglobalPoint)); + // check that the mouse pointer is on the non-client area and + // there are not widgets in it. + if (fakeNCEvents || (!NSMouseInRect(globalPoint, contentRect, NO) && !w)) { + qglobalPoint = QPoint(flipPoint(globalPoint).toPoint()); + qlocalPoint = widgetToGetEvent->mapFromGlobal(qglobalPoint); + processThisEvent = true; + } + } + } + } + // This is not an NC area mouse message. + if (!processThisEvent) + return; + + // If the window is frame less, generate fake mouse events instead. (floating QToolBar) + // or if someone already got an explicit or implicit grab + if (mac_mouse_grabber || qt_button_down || + (fakeNCEvents && (widgetToGetEvent->window()->windowFlags() & Qt::FramelessWindowHint))) + fakeMouseEvents = true; + + Qt::MouseButton button; + QEvent::Type eventType; + // Convert to Qt::Event type + switch (evtType) { + case NSLeftMouseDown: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSLeftMouseUp: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSRightMouseDown: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSRightMouseUp: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSOtherMouseDown: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonPress + : QEvent::MouseButtonPress; + break; + case NSOtherMouseUp: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonRelease + : QEvent::MouseButtonRelease; + break; + case NSMouseMoved: + button = Qt::NoButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSLeftMouseDragged: + button = Qt::LeftButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSRightMouseDragged: + button = Qt::RightButton; + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + case NSOtherMouseDragged: + button = cocoaButton2QtButton([event buttonNumber]); + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseMove + : QEvent::MouseMove; + break; + default: + qWarning("not handled! Non client area mouse message"); + return; + } + + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + if (eventType == QEvent::NonClientAreaMouseButtonPress || eventType == QEvent::MouseButtonPress) { + NSInteger clickCount = [event clickCount]; + if (clickCount % 2 == 0) + eventType = (!fakeMouseEvents) ? QEvent::NonClientAreaMouseButtonDblClick + : QEvent::MouseButtonDblClick; + if (button == Qt::LeftButton && (keyMods & Qt::MetaModifier)) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = true; + } + } else if (eventType == QEvent::NonClientAreaMouseButtonRelease || eventType == QEvent::MouseButtonRelease) { + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = false; + } + } + + Qt::MouseButtons buttons = 0; + { + UInt32 mac_buttons; + if (GetEventParameter((EventRef)[event eventRef], kEventParamMouseChord, typeUInt32, 0, + sizeof(mac_buttons), 0, &mac_buttons) == noErr) + buttons = qt_mac_get_buttons(mac_buttons); + } + + QMouseEvent qme(eventType, qlocalPoint, qglobalPoint, button, buttons, keyMods); + qt_sendSpontaneousEvent(widgetToGetEvent, &qme); + + // We don't need to set the implicit grab widget here because we won't + // reach this point if then event type is Press over a Qt widget. + // However we might need to unset it if the event is Release. + if (eventType == QEvent::MouseButtonRelease) + qt_button_down = 0; +} + +QWidget *qt_mac_getTargetForKeyEvent(QWidget *widgetThatReceivedEvent) +{ + if (QWidget *popup = QApplication::activePopupWidget()) { + QWidget *focusInPopup = popup->focusWidget(); + return focusInPopup ? focusInPopup : popup; + } + + QWidget *widgetToGetKey = qApp->focusWidget(); + if (!widgetToGetKey) + widgetToGetKey = widgetThatReceivedEvent; + + return widgetToGetKey; +} + +// This function will find the widget that should receive the +// mouse event. Because of explicit/implicit mouse grabs, popups, +// etc, this might not end up being the same as the widget under +// the mouse (which is more interresting when handling enter/leave +// events +QWidget *qt_mac_getTargetForMouseEvent( + // You can call this function without providing an event. + NSEvent *event, + QEvent::Type eventType, + QPoint &returnLocalPoint, + QPoint &returnGlobalPoint, + QWidget *nativeWidget, + QWidget **returnWidgetUnderMouse) +{ + Q_UNUSED(event); + NSPoint nsglobalpoint = event ? [[event window] convertBaseToScreen:[event locationInWindow]] : [NSEvent mouseLocation]; + returnGlobalPoint = flipPoint(nsglobalpoint).toPoint(); + QWidget *mouseGrabber = QWidget::mouseGrabber(); + bool buttonDownNotBlockedByModal = qt_button_down && !QApplicationPrivate::isBlockedByModal(qt_button_down); + QWidget *popup = QApplication::activePopupWidget(); + + // Resolve the widget under the mouse: + QWidget *widgetUnderMouse = 0; + if (popup || qt_button_down || !nativeWidget || !nativeWidget->isVisible()) { + // Using QApplication::widgetAt for finding the widget under the mouse + // is most safe, since it ignores cocoas own mouse down redirections (which + // we need to be prepared for when using nativeWidget as starting point). + // (the only exception is for QMacNativeWidget, where QApplication::widgetAt fails). + // But it is also slower (I guess), so we try to avoid it and use nativeWidget if we can: + widgetUnderMouse = QApplication::widgetAt(returnGlobalPoint); + } + + if (!widgetUnderMouse && nativeWidget) { + // Entering here should be the common case. We + // also handle the QMacNativeWidget fallback case. + QPoint p = nativeWidget->mapFromGlobal(returnGlobalPoint); + widgetUnderMouse = nativeWidget->childAt(p); + if (!widgetUnderMouse && nativeWidget->rect().contains(p)) + widgetUnderMouse = nativeWidget; + } + + if (widgetUnderMouse) { + // Check if widgetUnderMouse is blocked by a modal + // window, or the mouse if over the frame strut: + if (widgetUnderMouse == qt_button_down) { + // Small optimization to avoid an extra call to isBlockedByModal: + if (buttonDownNotBlockedByModal == false) + widgetUnderMouse = 0; + } else if (QApplicationPrivate::isBlockedByModal(widgetUnderMouse)) { + widgetUnderMouse = 0; + } + + if (widgetUnderMouse && widgetUnderMouse->isWindow()) { + // Exclude the titlebar (and frame strut) when finding widget under mouse: + QPoint p = widgetUnderMouse->mapFromGlobal(returnGlobalPoint); + if (!widgetUnderMouse->rect().contains(p)) + widgetUnderMouse = 0; + } + } + if (returnWidgetUnderMouse) + *returnWidgetUnderMouse = widgetUnderMouse; + + // Resolve the target for the mouse event. Default will be + // widgetUnderMouse, except if there is a grab (popup/mouse/button-down): + if (popup && !mouseGrabber) { + // We special case handling of popups, since they have an implicitt mouse grab. + QWidget *candidate = buttonDownNotBlockedByModal ? qt_button_down : widgetUnderMouse; + if (!popup->isAncestorOf(candidate)) { + // INVARIANT: we have a popup, but the candidate is not + // in it. But the popup will grab the mouse anyway, + // except if the user scrolls: + if (eventType == QEvent::Wheel) + return 0; + returnLocalPoint = popup->mapFromGlobal(returnGlobalPoint); + return popup; + } else if (popup == candidate) { + // INVARIANT: The candidate is the popup itself, and not a child: + returnLocalPoint = popup->mapFromGlobal(returnGlobalPoint); + return popup; + } else { + // INVARIANT: The candidate is a child inside the popup: + returnLocalPoint = candidate->mapFromGlobal(returnGlobalPoint); + return candidate; + } + } + + QWidget *target = mouseGrabber; + if (!target && buttonDownNotBlockedByModal) + target = qt_button_down; + if (!target) + target = widgetUnderMouse; + if (!target) + return 0; + + returnLocalPoint = target->mapFromGlobal(returnGlobalPoint); + return target; +} + +QPointer<QWidget> qt_last_native_mouse_receiver = 0; + +static inline void qt_mac_checkEnterLeaveForNativeWidgets(QWidget *maybeEnterWidget) +{ + // Dispatch enter/leave for the cases where QApplicationPrivate::sendMouseEvent do + // not. This will in general be the cases when alien widgets are not involved: + // 1. from a native widget to another native widget or + // 2. from a native widget to no widget + // 3. from no widget to a native or alien widget + + if (qt_button_down || QWidget::mouseGrabber()) + return; + + if ((maybeEnterWidget == qt_last_native_mouse_receiver) && qt_last_native_mouse_receiver) + return; + if (maybeEnterWidget) { + if (!qt_last_native_mouse_receiver) { + // case 3 + QApplicationPrivate::dispatchEnterLeave(maybeEnterWidget, 0); + qt_last_native_mouse_receiver = maybeEnterWidget->internalWinId() ? maybeEnterWidget : maybeEnterWidget->nativeParentWidget(); + } else if (maybeEnterWidget->internalWinId()) { + // case 1 + QApplicationPrivate::dispatchEnterLeave(maybeEnterWidget, qt_last_native_mouse_receiver); + qt_last_native_mouse_receiver = maybeEnterWidget->internalWinId() ? maybeEnterWidget : maybeEnterWidget->nativeParentWidget(); + } // else at lest one of the widgets are alien, so enter/leave will be handled in QApplicationPrivate + } else { + if (qt_last_native_mouse_receiver) { + // case 2 + QApplicationPrivate::dispatchEnterLeave(0, qt_last_native_mouse_receiver); + qt_last_mouse_receiver = 0; + qt_last_native_mouse_receiver = 0; + } + } +} + +bool qt_mac_handleMouseEvent(NSEvent *event, QEvent::Type eventType, Qt::MouseButton button, QWidget *nativeWidget) +{ + // Give the Input Manager a chance to process the mouse events. + NSInputManager *currentIManager = [NSInputManager currentInputManager]; + if (currentIManager && [currentIManager wantsToHandleMouseEvents]) { + [currentIManager handleMouseEvent:event]; + } + + // Find the widget that should receive the event, and the widget under the mouse. Those + // can differ if an implicit or explicit mouse grab is active: + QWidget *widgetUnderMouse = 0; + QPoint localPoint, globalPoint; + QWidget *widgetToGetMouse = qt_mac_getTargetForMouseEvent(event, eventType, localPoint, globalPoint, nativeWidget, &widgetUnderMouse); + if (!widgetToGetMouse) + return false; + + // From here on, we let nativeWidget actually be the native widget under widgetUnderMouse. The reason + // for this, is that qt_mac_getTargetForMouseEvent will set cocoa's mouse event redirection aside when + // determining which widget is under the mouse (in other words, it will usually ignore nativeWidget). + // nativeWidget will be used in QApplicationPrivate::sendMouseEvent to correctly dispatch enter/leave events. + if (widgetUnderMouse) + nativeWidget = widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget(); + if (!nativeWidget) + return false; + NSView *view = qt_mac_effectiveview_for(nativeWidget); + + // Handle tablet events (if any) first. + if (qt_mac_handleTabletEvent(view, event)) { + // Tablet event was handled. In Qt we aren't supposed to send the mouse event. + return true; + } + + EventRef carbonEvent = static_cast<EventRef>(const_cast<void *>([event eventRef])); + if (qt_mac_sendMacEventToWidget(widgetToGetMouse, carbonEvent)) + return true; + + // Keep previousButton to make sure we don't send double click + // events when the user double clicks using two different buttons: + static Qt::MouseButton previousButton = Qt::NoButton; + + Qt::KeyboardModifiers keyMods = qt_cocoaModifiers2QtModifiers([event modifierFlags]); + NSInteger clickCount = [event clickCount]; + Qt::MouseButtons buttons = 0; + { + UInt32 mac_buttons; + if (GetEventParameter(carbonEvent, kEventParamMouseChord, typeUInt32, 0, + sizeof(mac_buttons), 0, &mac_buttons) == noErr) + buttons = qt_mac_get_buttons(mac_buttons); + } + + // Send enter/leave events for the cases when QApplicationPrivate::sendMouseEvent do not: + qt_mac_checkEnterLeaveForNativeWidgets(widgetUnderMouse); + + switch (eventType) { + default: + qWarning("not handled! %d", eventType); + break; + case QEvent::MouseMove: + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) + button = Qt::RightButton; + break; + case QEvent::MouseButtonPress: + qt_button_down = widgetUnderMouse; + if (clickCount % 2 == 0 && (previousButton == Qt::NoButton || previousButton == button)) + eventType = QEvent::MouseButtonDblClick; + if (button == Qt::LeftButton && (keyMods & Qt::MetaModifier)) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = true; + } + break; + case QEvent::MouseButtonRelease: + if (button == Qt::LeftButton && qt_leftButtonIsRightButton) { + button = Qt::RightButton; + qt_leftButtonIsRightButton = false; + } + qt_button_down = 0; + break; + } + + qt_mac_updateCursorWithWidgetUnderMouse(widgetUnderMouse); + + DnDParams *dndParams = currentDnDParameters(); + dndParams->view = view; + dndParams->theEvent = event; + dndParams->globalPoint = globalPoint; + + // Send the mouse event: + QMouseEvent qme(eventType, localPoint, globalPoint, button, buttons, keyMods); + QApplicationPrivate::sendMouseEvent( + widgetToGetMouse, &qme, widgetUnderMouse, nativeWidget, + &qt_button_down, qt_last_mouse_receiver, true); + + if (eventType == QEvent::MouseButtonPress && button == Qt::RightButton) { + QContextMenuEvent qcme(QContextMenuEvent::Mouse, localPoint, globalPoint, keyMods); + qt_sendSpontaneousEvent(widgetToGetMouse, &qcme); + } + + if (eventType == QEvent::MouseButtonRelease) { + // A mouse button was released, which means that the implicit grab was + // released. We therefore need to re-check if should send (delayed) enter leave events: + // qt_button_down has now become NULL since the call at the top of the function. Also, since + // the relase might have closed a window, we dont give the nativeWidget hint + qt_mac_getTargetForMouseEvent(0, QEvent::None, localPoint, globalPoint, nativeWidget, &widgetUnderMouse); + qt_mac_checkEnterLeaveForNativeWidgets(widgetUnderMouse); + } + + previousButton = button; + return true; +} +#endif + +bool qt_mac_handleTabletEvent(void * /*QCocoaView * */view, void * /*NSEvent * */tabletEvent) +{ +#ifndef QT_MAC_USE_COCOA + Q_UNUSED(view); + Q_UNUSED(tabletEvent); + return false; +#else + QT_MANGLE_NAMESPACE(QCocoaView) *theView = static_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(view); + NSView *theNSView = static_cast<NSView *>(view); + NSEvent *theTabletEvent = static_cast<NSEvent *>(tabletEvent); + + NSEventType eventType = [theTabletEvent type]; + if (eventType != NSTabletPoint && [theTabletEvent subtype] != NSTabletPointEventSubtype) + return false; // Not a tablet event. + + NSPoint windowPoint = [theTabletEvent locationInWindow]; + NSPoint globalPoint = [[theTabletEvent window] convertBaseToScreen:windowPoint]; + + QWidget *qwidget = [theView qt_qwidget]; + QWidget *widgetToGetMouse = qwidget; + QWidget *popup = qAppInstance()->activePopupWidget(); + if (popup && popup != qwidget->window()) + widgetToGetMouse = popup; + + if (qt_mac_sendMacEventToWidget(widgetToGetMouse, + static_cast<EventRef>(const_cast<void *>([theTabletEvent eventRef])))) + return true; + if (widgetToGetMouse != qwidget) { + theNSView = qt_mac_nativeview_for(widgetToGetMouse); + windowPoint = [[theNSView window] convertScreenToBase:globalPoint]; + } + NSPoint localPoint = [theNSView convertPoint:windowPoint fromView:nil]; + // Tablet events do not handle WA_TransparentForMouseEvents ATM + // In theory, people who set the WA_TransparentForMouseEvents attribute won't handle + // tablet events either in which case they will fall into the mouse event case and get + // them passed on. This will NOT handle the raw events, but that might not be a big problem. + + const QMacTabletHash *tabletHash = qt_mac_tablet_hash(); + if (!tabletHash->contains([theTabletEvent deviceID])) { + qWarning("QCocoaView handleTabletEvent: This tablet device is unknown" + " (received no proximity event for it). Discarding event."); + return false; + } + const QTabletDeviceData &deviceData = tabletHash->value([theTabletEvent deviceID]); + + + QEvent::Type qType; + switch (eventType) { + case NSLeftMouseDown: + case NSRightMouseDown: + qType = QEvent::TabletPress; + break; + case NSLeftMouseUp: + case NSRightMouseUp: + qType = QEvent::TabletRelease; + break; + case NSMouseMoved: + case NSTabletPoint: + case NSLeftMouseDragged: + case NSRightMouseDragged: + default: + qType = QEvent::TabletMove; + break; + } + + qreal pressure; + if (eventType != NSMouseMoved) { + pressure = [theTabletEvent pressure]; + } else { + pressure = 0.0; + } + + NSPoint tilt = [theTabletEvent tilt]; + int xTilt = qRound(tilt.x * 60.0); + int yTilt = qRound(tilt.y * -60.0); + qreal tangentialPressure = 0; + qreal rotation = 0; + int z = 0; + if (deviceData.capabilityMask & 0x0200) + z = [theTabletEvent absoluteZ]; + + if (deviceData.capabilityMask & 0x0800) + tangentialPressure = [theTabletEvent tangentialPressure]; + + rotation = [theTabletEvent rotation]; + QPointF hiRes = flipPoint(globalPoint); + QTabletEvent qtabletEvent(qType, QPoint(localPoint.x, localPoint.y), + hiRes.toPoint(), hiRes, + deviceData.tabletDeviceType, deviceData.tabletPointerType, + pressure, xTilt, yTilt, tangentialPressure, rotation, z, + qt_cocoaModifiers2QtModifiers([theTabletEvent modifierFlags]), + deviceData.tabletUniqueID); + + qt_sendSpontaneousEvent(widgetToGetMouse, &qtabletEvent); + return qtabletEvent.isAccepted(); +#endif +} + +void qt_mac_updateContentBorderMetricts(void * /*OSWindowRef */window, const ::HIContentBorderMetrics &metrics) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); +#if !defined(QT_MAC_USE_COCOA) +# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + ::HIWindowSetContentBorderThickness(theWindow, &metrics); + } +# else + Q_UNUSED(window); + Q_UNUSED(metrics); +# endif +#else + if ([theWindow styleMask] & NSTexturedBackgroundWindowMask) + [theWindow setContentBorderThickness:metrics.top forEdge:NSMaxYEdge]; + [theWindow setContentBorderThickness:metrics.bottom forEdge:NSMinYEdge]; +#endif +} + +#if QT_MAC_USE_COCOA +void qt_mac_replaceDrawRect(void * /*OSWindowRef */window, QWidgetPrivate *widget) +{ + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + if(!theWindow) + return; + id theClass = [[[theWindow contentView] superview] class]; + // What we do here is basically to add a new selector to NSThemeFrame called + // "drawRectOriginal:" which will contain the original implementation of + // "drawRect:". After that we get the new implementation from QCocoaWindow + // and exchange them. The new implementation is called drawRectSpecial. + // We cannot just add the method because it might have been added before and since + // we cannot remove a method once it has been added we need to ask QCocoaWindow if + // we did the swap or not. + if(!widget->drawRectOriginalAdded) { + Method m2 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m2) { + // This case is pretty extreme, no drawRect means no drawing! + return; + } + class_addMethod(theClass, @selector(drawRectOriginal:), method_getImplementation(m2), method_getTypeEncoding(m2)); + widget->drawRectOriginalAdded = true; + } + if(widget->originalDrawMethod) { + Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:)); + if(!m0) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m1) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + // We have the original method here. Proceed and swap the methods. + method_exchangeImplementations(m1, m0); + widget->originalDrawMethod = false; + [theWindow display]; + } +} + +void qt_mac_replaceDrawRectOriginal(void * /*OSWindowRef */window, QWidgetPrivate *widget) +{ + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + id theClass = [[[theWindow contentView] superview] class]; + // Now we need to revert the methods to their original state. + // We cannot remove the method, so we just keep track of it in QCocoaWindow. + Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:)); + if(!m0) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:)); + if(!m1) { + // Ok, this means the methods were never swapped. Just ignore + return; + } + method_exchangeImplementations(m1, m0); + widget->originalDrawMethod = true; + [theWindow display]; +} +#endif // QT_MAC_USE_COCOA + +#if QT_MAC_USE_COCOA +void qt_mac_showBaseLineSeparator(void * /*OSWindowRef */window, bool show) +{ + if(!window) + return; + QMacCocoaAutoReleasePool pool; + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSToolbar *macToolbar = [theWindow toolbar]; + [macToolbar setShowsBaselineSeparator:show]; +} +#endif // QT_MAC_USE_COCOA + +QStringList qt_mac_NSArrayToQStringList(void *nsarray) +{ + QStringList result; + NSArray *array = static_cast<NSArray *>(nsarray); + for (NSUInteger i=0; i<[array count]; ++i) + result << qt_mac_NSStringToQString([array objectAtIndex:i]); + return result; +} + +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list) +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:list.size()]; + for (int i=0; i<list.size(); ++i){ + [result addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(list[i]))]; + } + return result; +} + +#if QT_MAC_USE_COCOA +void qt_syncCocoaTitleBarButtons(OSWindowRef window, QWidget *widgetForWindow) +{ + if (!widgetForWindow) + return; + + Qt::WindowFlags flags = widgetForWindow->windowFlags(); + bool customize = flags & Qt::CustomizeWindowHint; + + NSButton *btn = [window standardWindowButton:NSWindowZoomButton]; + // BOOL is not an int, so the bitwise AND doesn't work. + bool go = uint(customize && !(flags & Qt::WindowMaximizeButtonHint)) == 0; + [btn setEnabled:go]; + + btn = [window standardWindowButton:NSWindowMiniaturizeButton]; + go = uint(customize && !(flags & Qt::WindowMinimizeButtonHint)) == 0; + [btn setEnabled:go]; + + btn = [window standardWindowButton:NSWindowCloseButton]; + go = uint(customize && !(flags & Qt::WindowSystemMenuHint + || flags & Qt::WindowCloseButtonHint)) == 0; + [btn setEnabled:go]; + + [window setShowsToolbarButton:uint(flags & Qt::MacWindowToolBarButtonHint) != 0]; +} +#endif // QT_MAC_USE_COCOA + +// Carbon: Make sure you call QDEndContext on the context when done with it. +CGContextRef qt_mac_graphicsContextFor(QWidget *widget) +{ + if (!widget) + return 0; + +#ifndef QT_MAC_USE_COCOA + CGContextRef context; + CGrafPtr port = GetWindowPort(qt_mac_window_for(widget)); + QDBeginCGContext(port, &context); +#else + CGContextRef context = (CGContextRef)[[NSGraphicsContext graphicsContextWithWindow:qt_mac_window_for(widget)] graphicsPort]; +#endif + return context; +} + +void qt_mac_dispatchPendingUpdateRequests(QWidget *widget) +{ + if (!widget) + return; +#ifndef QT_MAC_USE_COCOA + HIViewRender(qt_mac_nativeview_for(widget)); +#else + [qt_mac_nativeview_for(widget) displayIfNeeded]; +#endif +} + +CGFloat qt_mac_get_scalefactor() +{ +#ifndef QT_MAC_USE_COCOA + return HIGetScaleFactor(); +#else + return [[NSScreen mainScreen] userSpaceScaleFactor]; +#endif +} + +QString qt_mac_get_pasteboardString(OSPasteboardRef paste) +{ + QMacCocoaAutoReleasePool pool; + NSPasteboard *pb = nil; + CFStringRef pbname; + if (PasteboardCopyName(paste, &pbname) == noErr) { + pb = [NSPasteboard pasteboardWithName:const_cast<NSString *>(reinterpret_cast<const NSString *>(pbname))]; + CFRelease(pbname); + } else { + pb = [NSPasteboard generalPasteboard]; + } + if (pb) { + NSString *text = [pb stringForType:NSStringPboardType]; + if (text) + return qt_mac_NSStringToQString(text); + } + return QString(); +} + +QPixmap qt_mac_convert_iconref(const IconRef icon, int width, int height) +{ + QPixmap ret(width, height); + ret.fill(QColor(0, 0, 0, 0)); + + CGRect rect = CGRectMake(0, 0, width, height); + + CGContextRef ctx = qt_mac_cg_context(&ret); + CGAffineTransform old_xform = CGContextGetCTM(ctx); + CGContextConcatCTM(ctx, CGAffineTransformInvert(old_xform)); + CGContextConcatCTM(ctx, CGAffineTransformIdentity); + + ::RGBColor b; + b.blue = b.green = b.red = 255*255; + PlotIconRefInContext(ctx, &rect, kAlignNone, kTransformNone, &b, kPlotIconRefNormalFlags, icon); + CGContextRelease(ctx); + return ret; +} + +void qt_mac_constructQIconFromIconRef(const IconRef icon, const IconRef overlayIcon, QIcon *retIcon, QStyle::StandardPixmap standardIcon) +{ + int size = 16; + while (size <= 128) { + + const QString cacheKey = QLatin1String("qt_mac_constructQIconFromIconRef") + QString::number(standardIcon) + QString::number(size); + QPixmap mainIcon; + if (standardIcon >= QStyle::SP_CustomBase) { + mainIcon = qt_mac_convert_iconref(icon, size, size); + } else if (QPixmapCache::find(cacheKey, mainIcon) == false) { + mainIcon = qt_mac_convert_iconref(icon, size, size); + QPixmapCache::insert(cacheKey, mainIcon); + } + + if (overlayIcon) { + int littleSize = size / 2; + QPixmap overlayPix = qt_mac_convert_iconref(overlayIcon, littleSize, littleSize); + QPainter painter(&mainIcon); + painter.drawPixmap(size - littleSize, size - littleSize, overlayPix); + } + + retIcon->addPixmap(mainIcon); + size += size; // 16 -> 32 -> 64 -> 128 + } +} + +#ifdef QT_MAC_USE_COCOA +void qt_mac_menu_collapseSeparators(void */*NSMenu **/ theMenu, bool collapse) +{ + QMacCocoaAutoReleasePool pool; + OSMenuRef menu = static_cast<OSMenuRef>(theMenu); + if (collapse) { + bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. + NSMenuItem *previousItem = nil; + + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if ([item isSeparatorItem]) { + [item setHidden:previousIsSeparator]; + } + + if (![item isHidden]) { + previousItem = item; + previousIsSeparator = ([previousItem isSeparatorItem]); + } + } + + // We now need to check the final item since we don't want any separators at the end of the list. + if (previousItem && previousIsSeparator) + [previousItem setHidden:YES]; + } else { + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) + [item setHidden:!action->isVisible()]; + } + } +} + +class CocoaPostMessageAfterEventLoopExitHelp : public QObject +{ + id target; + SEL selector; + int argCount; + id arg1; + id arg2; +public: + CocoaPostMessageAfterEventLoopExitHelp(id target, SEL selector, int argCount, id arg1, id arg2) + : target(target), selector(selector), argCount(argCount), arg1(arg1), arg2(arg2){ + deleteLater(); + } + + ~CocoaPostMessageAfterEventLoopExitHelp() + { + qt_cocoaPostMessage(target, selector, argCount, arg1, arg2); + } +}; + +void qt_cocoaPostMessage(id target, SEL selector, int argCount, id arg1, id arg2) +{ + // WARNING: data1 and data2 is truncated to from 64-bit to 32-bit on OS 10.5! + // That is why we need to split the address in two parts: + QCocoaPostMessageArgs *args = new QCocoaPostMessageArgs(target, selector, argCount, arg1, arg2); + quint32 lower = quintptr(args); + quint32 upper = quintptr(args) >> 32; + NSEvent *e = [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 + context:nil subtype:QtCocoaEventSubTypePostMessage data1:lower data2:upper]; + [NSApp postEvent:e atStart:NO]; +} + +void qt_cocoaPostMessageAfterEventLoopExit(id target, SEL selector, int argCount, id arg1, id arg2) +{ + if (QApplicationPrivate::instance()->threadData->eventLoops.size() <= 1) + qt_cocoaPostMessage(target, selector, argCount, arg1, arg2); + else + new CocoaPostMessageAfterEventLoopExitHelp(target, selector, argCount, arg1, arg2); +} + +#endif + +QMacCocoaAutoReleasePool::QMacCocoaAutoReleasePool() +{ +#ifndef QT_MAC_USE_COCOA + NSApplicationLoad(); +#endif + pool = (void*)[[NSAutoreleasePool alloc] init]; +} + +QMacCocoaAutoReleasePool::~QMacCocoaAutoReleasePool() +{ + [(NSAutoreleasePool*)pool release]; +} + +void qt_mac_post_retranslateAppMenu() +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + qt_cocoaPostMessage([NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)], @selector(qtTranslateApplicationMenu)); +#endif +} + +QWidgetPrivate *QMacScrollOptimization::_target = 0; +bool QMacScrollOptimization::_inWheelEvent = false; +int QMacScrollOptimization::_dx = 0; +int QMacScrollOptimization::_dy = 0; +QRect QMacScrollOptimization::_scrollRect = QRect(0, 0, -1, -1); + +#ifdef QT_MAC_USE_COCOA +// This method implements the magic for the drawRectSpecial method. +// We draw a line at the upper edge of the content view in order to +// override the title baseline. +void macDrawRectOnTop(void * /*OSWindowRef */window) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSView *contentView = [theWindow contentView]; + if(!contentView) + return; + // Get coordinates of the content view + NSRect contentRect = [contentView frame]; + // Draw a line on top of the already drawn line. + // We need to check if we are active or not to use the proper color. + if([theWindow isKeyWindow] || [theWindow isMainWindow]) { + [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set]; + } else { + [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set]; + } + NSPoint origin = NSMakePoint(0, contentRect.size.height); + NSPoint end = NSMakePoint(contentRect.size.width, contentRect.size.height); + [NSBezierPath strokeLineFromPoint:origin toPoint:end]; +} + +// This method will (or at least should) get called only once. +// Its mission is to find out if we are active or not. If we are active +// we assume that we were launched via finder, otherwise we assume +// we were called from the command line. The distinction is important, +// since in the first case we don't need to trigger a paintEvent, while +// in the second case we do. +void macSyncDrawingOnFirstInvocation(void * /*OSWindowRef */window) +{ + OSWindowRef theWindow = static_cast<OSWindowRef>(window); + NSApplication *application = [NSApplication sharedApplication]; + NSToolbar *toolbar = [theWindow toolbar]; + if([application isActive]) { + // Launched from finder + [toolbar setShowsBaselineSeparator:NO]; + } else { + // Launched from commandline + [toolbar setVisible:false]; + [toolbar setShowsBaselineSeparator:NO]; + [toolbar setVisible:true]; + [theWindow display]; + } +} + +void qt_cocoaStackChildWindowOnTopOfOtherChildren(QWidget *childWidget) +{ + if (!childWidget) + return; + + QWidget *parent = childWidget->parentWidget(); + if (childWidget->isWindow() && parent) { + if ([[qt_mac_window_for(parent) childWindows] containsObject:qt_mac_window_for(childWidget)]) { + QWidgetPrivate *d = qt_widget_private(childWidget); + d->setSubWindowStacking(false); + d->setSubWindowStacking(true); + } + } +} + +void qt_mac_display(QWidget *widget) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + [theNSView display]; +} + +void qt_mac_setNeedsDisplay(QWidget *widget) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + [theNSView setNeedsDisplay:YES]; +} + +void qt_mac_setNeedsDisplayInRect(QWidget *widget, QRegion region) +{ + NSView *theNSView = qt_mac_nativeview_for(widget); + if (region.isEmpty()) { + [theNSView setNeedsDisplay:YES]; + return; + } + + QVector<QRect> rects = region.rects(); + for (int i = 0; i < rects.count(); ++i) { + const QRect &rect = rects.at(i); + NSRect nsrect = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + [theNSView setNeedsDisplayInRect:nsrect]; + } + +} + +#endif // QT_MAC_USE_COCOA + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qt_cocoa_helpers_mac_p.h b/src/gui/platforms/mac/qt_cocoa_helpers_mac_p.h new file mode 100644 index 0000000000..a49753ae2f --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> + +#include <qapplication.h> +#include <qdesktopwidget.h> +#include <qwidget.h> +#include <qevent.h> +#include <qhash.h> +#include <qlabel.h> +#include <qpointer.h> +#include <qstyle.h> +#include <qstyleoption.h> +#include <qstylepainter.h> +#include <qtimer.h> +#include <qtooltip.h> +#include <private/qeffects_p.h> +#include <private/qwidget_p.h> +#include <qtextdocument.h> +#include <qdebug.h> +#include <qpoint.h> +#include "private/qt_mac_p.h" + +struct HIContentBorderMetrics; + +#ifdef Q_WS_MAC32 +typedef struct _NSPoint NSPoint; // Just redefine here so I don't have to pull in all of Cocoa. +#else +typedef struct CGPoint NSPoint; +#endif + +QT_BEGIN_NAMESPACE + +enum { + QtCocoaEventSubTypeWakeup = SHRT_MAX, + QtCocoaEventSubTypePostMessage = SHRT_MAX-1 +}; + +Qt::MouseButtons qt_mac_get_buttons(int buttons); +Qt::MouseButton qt_mac_get_button(EventMouseButton button); +void macWindowFade(void * /*OSWindowRef*/ window, float durationSeconds = 0.15); +bool macWindowIsTextured(void * /*OSWindowRef*/ window); +void macWindowToolbarShow(const QWidget *widget, bool show ); +void macWindowToolbarSet( void * /*OSWindowRef*/ window, void* toolbarRef ); +bool macWindowToolbarIsVisible( void * /*OSWindowRef*/ window ); +void macWindowSetHasShadow( void * /*OSWindowRef*/ window, bool hasShadow ); +void macWindowFlush(void * /*OSWindowRef*/ window); +void macSendToolbarChangeEvent(QWidget *widget); +void qt_mac_updateContentBorderMetricts(void * /*OSWindowRef */window, const ::HIContentBorderMetrics &metrics); +void qt_mac_replaceDrawRect(void * /*OSWindowRef */window, QWidgetPrivate *widget); +void qt_mac_replaceDrawRectOriginal(void * /*OSWindowRef */window, QWidgetPrivate *widget); +void qt_mac_showBaseLineSeparator(void * /*OSWindowRef */window, bool show); +void * /*NSImage */qt_mac_create_nsimage(const QPixmap &pm); +void qt_mac_update_mouseTracking(QWidget *widget); +OSStatus qt_mac_drawCGImage(CGContextRef cg, const CGRect *inbounds, CGImageRef); +bool qt_mac_checkForNativeSizeGrip(const QWidget *widget); +void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent); +#ifdef QT_MAC_USE_COCOA +bool qt_dispatchKeyEventWithCocoa(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent); +// These methods exists only for supporting unified mode. +void macDrawRectOnTop(void * /*OSWindowRef */ window); +void macSyncDrawingOnFirstInvocation(void * /*OSWindowRef */window); +void qt_cocoaStackChildWindowOnTopOfOtherChildren(QWidget *widget); +void qt_mac_menu_collapseSeparators(void * /*NSMenu */ menu, bool collapse); +#endif +bool qt_dispatchKeyEvent(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent); +void qt_dispatchModifiersChanged(void * /*NSEvent * */flagsChangedEvent, QWidget *widgetToGetEvent); +bool qt_mac_handleTabletEvent(void * /*QCocoaView * */view, void * /*NSEvent * */event); +inline QApplication *qAppInstance() { return static_cast<QApplication *>(QCoreApplication::instance()); } +struct ::TabletProximityRec; +void qt_dispatchTabletProximityEvent(const ::TabletProximityRec &proxRec); +Qt::KeyboardModifiers qt_cocoaModifiers2QtModifiers(ulong modifierFlags); +Qt::KeyboardModifiers qt_cocoaDragOperation2QtModifiers(uint dragOperations); +QPixmap qt_mac_convert_iconref(const IconRef icon, int width, int height); +void qt_mac_constructQIconFromIconRef(const IconRef icon, const IconRef overlayIcon, QIcon *retIcon, + QStyle::StandardPixmap standardIcon = QStyle::SP_CustomBase); + +#if QT_MAC_USE_COCOA && __OBJC__ +struct DnDParams +{ + NSView *view; + NSEvent *theEvent; + QPoint globalPoint; + NSDragOperation performedAction; +}; + +DnDParams *macCurrentDnDParameters(); +NSDragOperation qt_mac_mapDropAction(Qt::DropAction action); +NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions); +Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions); +Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions); + +QWidget *qt_mac_getTargetForKeyEvent(QWidget *widgetThatReceivedEvent); +QWidget *qt_mac_getTargetForMouseEvent(NSEvent *event, QEvent::Type eventType, + QPoint &returnLocalPoint, QPoint &returnGlobalPoint, QWidget *nativeWidget, QWidget **returnWidgetUnderMouse); +bool qt_mac_handleMouseEvent(NSEvent *event, QEvent::Type eventType, Qt::MouseButton button, QWidget *nativeWidget); +void qt_mac_handleNonClientAreaMouseEvent(NSWindow *window, NSEvent *event); +#endif + +inline int flipYCoordinate(int y) +{ + return QApplication::desktop()->screenGeometry(0).height() - y; +} + +inline qreal flipYCoordinate(qreal y) +{ + return QApplication::desktop()->screenGeometry(0).height() - y; +} + +QPointF flipPoint(const NSPoint &p); +NSPoint flipPoint(const QPoint &p); +NSPoint flipPoint(const QPointF &p); + +QStringList qt_mac_NSArrayToQStringList(void *nsarray); +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list); + +void qt_syncCocoaTitleBarButtons(OSWindowRef window, QWidget *widgetForWindow); + +CGFloat qt_mac_get_scalefactor(); +QString qt_mac_get_pasteboardString(OSPasteboardRef paste); + +#ifdef __OBJC__ +inline NSMutableArray *qt_mac_QStringListToNSMutableArray(const QStringList &qstrlist) +{ return reinterpret_cast<NSMutableArray *>(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } + +inline QString qt_mac_NSStringToQString(const NSString *nsstr) +{ return QCFString::toQString(reinterpret_cast<const CFStringRef>(nsstr)); } + +inline NSString *qt_mac_QStringToNSString(const QString &qstr) +{ return [reinterpret_cast<const NSString *>(QCFString::toCFStringRef(qstr)) autorelease]; } + +#ifdef QT_MAC_USE_COCOA +class QCocoaPostMessageArgs { +public: + id target; + SEL selector; + int argCount; + id arg1; + id arg2; + QCocoaPostMessageArgs(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0) + : target(target), selector(selector), argCount(argCount), arg1(arg1), arg2(arg2) + { + [target retain]; + [arg1 retain]; + [arg2 retain]; + } + + ~QCocoaPostMessageArgs() + { + [arg2 release]; + [arg1 release]; + [target release]; + } +}; +void qt_cocoaPostMessage(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0); +void qt_cocoaPostMessageAfterEventLoopExit(id target, SEL selector, int argCount=0, id arg1=0, id arg2=0); +#endif + +#endif + +class QMacScrollOptimization { + // This class is made to optimize for the case when the user + // scrolls both horizontally and vertically at the same + // time. This will result in two QWheelEvents (one for each + // direction), which will typically result in two calls to + // QWidget::_scroll_sys. Rather than copying pixels twize on + // screen because of this, we add this helper class to try to + // get away with only one blit. + static QWidgetPrivate *_target; + static bool _inWheelEvent; + static int _dx; + static int _dy; + static QRect _scrollRect; + +public: + static void initDelayedScroll() + { + _inWheelEvent = true; + } + + static bool delayScroll(QWidgetPrivate *target, int dx, int dy, const QRect &scrollRect) + { + if (!_inWheelEvent) + return false; + if (_target && _target != target) + return false; + if (_scrollRect.width() != -1 && _scrollRect != scrollRect) + return false; + + _target = target; + _dx += dx; + _dy += dy; + _scrollRect = scrollRect; + return true; + } + + static void performDelayedScroll() + { + if (!_inWheelEvent) + return; + _inWheelEvent = false; + if (!_target) + return; + + _target->scroll_sys(_dx, _dy, _scrollRect); + + _target = 0; + _dx = 0; + _dy = 0; + _scrollRect = QRect(0, 0, -1, -1); + } +}; + +void qt_mac_post_retranslateAppMenu(); + +#ifdef QT_MAC_USE_COCOA +void qt_mac_display(QWidget *widget); +void qt_mac_setNeedsDisplay(QWidget *widget); +void qt_mac_setNeedsDisplayInRect(QWidget *widget, QRegion region); +#endif // QT_MAC_USE_COCOA + + +// Utility functions to ease the use of Core Graphics contexts. + +inline void qt_mac_retain_graphics_context(CGContextRef context) +{ + CGContextRetain(context); + CGContextSaveGState(context); +} + +inline void qt_mac_release_graphics_context(CGContextRef context) +{ + CGContextRestoreGState(context); + CGContextRelease(context); +} + +inline void qt_mac_draw_image(CGContextRef context, CGContextRef imageContext, CGRect area, CGRect drawingArea) +{ + CGImageRef image = CGBitmapContextCreateImage(imageContext); + CGImageRef subImage = CGImageCreateWithImageInRect(image, area); + + CGContextTranslateCTM (context, 0, drawingArea.origin.y + CGRectGetMaxY(drawingArea)); + CGContextScaleCTM(context, 1, -1); + CGContextDrawImage(context, drawingArea, subImage); + + CGImageRelease(subImage); + CGImageRelease(image); +} + +QT_END_NAMESPACE + +#endif // QT_COCOA_HELPERS_MAC_P_H diff --git a/src/gui/platforms/mac/qt_mac.cpp b/src/gui/platforms/mac/qt_mac.cpp new file mode 100644 index 0000000000..046bcf6a54 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include <private/qpixmap_mac_p.h> +#include <private/qnativeimage_p.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE +#ifdef QT_MAC_USE_COCOA +static CTFontRef CopyCTThemeFont(ThemeFontID themeID) +{ + CTFontUIFontType ctID = HIThemeGetUIFontType(themeID); + return CTFontCreateUIFontForLanguage(ctID, 0, 0); +} +#endif + +QFont qfontForThemeFont(ThemeFontID themeID) +{ +#ifndef QT_MAC_USE_COCOA + static const ScriptCode Script = smRoman; + Str255 f_name; + SInt16 f_size; + Style f_style; + GetThemeFont(themeID, Script, f_name, &f_size, &f_style); + extern QString qt_mac_from_pascal_string(const Str255); //qglobal.cpp + return QFont(qt_mac_from_pascal_string(f_name), f_size, + (f_style & ::bold) ? QFont::Bold : QFont::Normal, + (bool)(f_style & ::italic)); +#else + QCFType<CTFontRef> ctfont = CopyCTThemeFont(themeID); + QString familyName = QCFString(CTFontCopyFamilyName(ctfont)); + QCFType<CFDictionaryRef> dict = CTFontCopyTraits(ctfont); + CFNumberRef num = static_cast<CFNumberRef>(CFDictionaryGetValue(dict, kCTFontWeightTrait)); + float fW; + CFNumberGetValue(num, kCFNumberFloat32Type, &fW); + QFont::Weight wght = fW > 0. ? QFont::Bold : QFont::Normal; + num = static_cast<CFNumberRef>(CFDictionaryGetValue(dict, kCTFontSlantTrait)); + CFNumberGetValue(num, kCFNumberFloatType, &fW); + bool italic = (fW != 0.0); + return QFont(familyName, CTFontGetSize(ctfont), wght, italic); +#endif +} + +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) +static QColor qcolorFromCGColor(CGColorRef cgcolor) +{ + QColor pc; + CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(cgcolor)); + const CGFloat *components = CGColorGetComponents(cgcolor); + if (model == kCGColorSpaceModelRGB) { + pc.setRgbF(components[0], components[1], components[2], components[3]); + } else if (model == kCGColorSpaceModelCMYK) { + pc.setCmykF(components[0], components[1], components[2], components[3]); + } else if (model == kCGColorSpaceModelMonochrome) { + pc.setRgbF(components[0], components[0], components[0], components[1]); + } else { + // Colorspace we can't deal with. + qWarning("Qt: qcolorFromCGColor: cannot convert from colorspace model: %d", model); + Q_ASSERT(false); + } + return pc; +} + +static inline QColor leopardBrush(ThemeBrush brush) +{ + QCFType<CGColorRef> cgClr = 0; + HIThemeBrushCreateCGColor(brush, &cgClr); + return qcolorFromCGColor(cgClr); +} +#endif + +QColor qcolorForTheme(ThemeBrush brush) +{ +#ifndef QT_MAC_USE_COCOA +# if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5) { + return leopardBrush(brush); + } else +# endif + { + RGBColor rgbcolor; + GetThemeBrushAsColor(brush, 32, true, &rgbcolor); + return QColor(rgbcolor.red / 256, rgbcolor.green / 256, rgbcolor.blue / 256); + } +#else + return leopardBrush(brush); +#endif +} + +QColor qcolorForThemeTextColor(ThemeTextColor themeColor) +{ +#ifdef Q_OS_MAC32 + RGBColor c; + GetThemeTextColor(themeColor, 32, true, &c); + QColor color = QColor(c.red / 256, c.green / 256, c.blue / 256); + return color; +#else + // There is no equivalent to GetThemeTextColor in 64-bit and it was rather bad that + // I didn't file a request to implement this for Snow Leopard. So, in the meantime + // I've encoded the values from the GetThemeTextColor. This is not exactly ideal + // as if someone really wants to mess with themeing, these colors will be wrong. + // It also means that we need to make sure the values for differences between + // OS releases (and it will be likely that we are a step behind.) + switch (themeColor) { + case kThemeTextColorAlertActive: + case kThemeTextColorTabFrontActive: + case kThemeTextColorBevelButtonActive: + case kThemeTextColorListView: + case kThemeTextColorPlacardActive: + case kThemeTextColorPopupButtonActive: + case kThemeTextColorPopupLabelActive: + case kThemeTextColorPushButtonActive: + return Qt::black; + case kThemeTextColorAlertInactive: + case kThemeTextColorDialogInactive: + case kThemeTextColorPlacardInactive: + return QColor(69, 69, 69, 255); + case kThemeTextColorPopupButtonInactive: + case kThemeTextColorPopupLabelInactive: + case kThemeTextColorPushButtonInactive: + case kThemeTextColorTabFrontInactive: + case kThemeTextColorBevelButtonInactive: + return QColor(127, 127, 127, 255); + default: { + QNativeImage nativeImage(16,16, QNativeImage::systemFormat()); + CGRect cgrect = CGRectMake(0, 0, 16, 16); + HIThemeSetTextFill(themeColor, 0, nativeImage.cg, kHIThemeOrientationNormal); + CGContextFillRect(nativeImage.cg, cgrect); + QColor color = nativeImage.image.pixel(0,0); + return QColor(nativeImage.image.pixel(0 , 0)); + } + } +#endif +} +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qt_mac_p.h b/src/gui/platforms/mac/qt_mac_p.h new file mode 100644 index 0000000000..b2bb804ff0 --- /dev/null +++ b/src/gui/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 <Cocoa/Cocoa.h> +#ifdef QT_MAC_USE_COCOA +#include <objc/runtime.h> +#endif // QT_MAC_USE_COCOA +#endif + +#include <CoreServices/CoreServices.h> + +#include "QtCore/qglobal.h" +#include "QtCore/qvariant.h" +#include "QtCore/qmimedata.h" +#include "QtCore/qpointer.h" +#include "private/qcore_mac_p.h" + + +#include "QtGui/qpainter.h" + +#include <Carbon/Carbon.h> + +QT_BEGIN_NAMESPACE +class QWidget; +class QDragMoveEvent; + +/* Event masks */ +// internal Qt types + + // Event class for our own Carbon events. +#if defined(QT_NAMESPACE) && defined(QT_NAMESPACE_MAC_CRC) +// Take the CRC we generated at configure time. This *may* result in a +// collision with another value If that is the case, please change the value +// here to something other than 'Cute'. +const UInt32 kEventClassQt = QT_NAMESPACE_MAC_CRC; +#else +const UInt32 kEventClassQt = 'Cute'; +#endif + +enum { + //AE types + typeAEClipboardChanged = 1, + //types + typeQWidget = 1, /* QWidget * */ + //params + kEventParamQWidget = 'qwid', /* typeQWidget */ + //events + kEventQtRequestContext = 13, + kEventQtRequestMenubarUpdate = 14, + kEventQtRequestShowSheet = 17, + kEventQtRequestActivate = 18, + kEventQtRequestWindowChange = 20 +}; + +// Simple class to manage short-lived regions +class QMacSmartQuickDrawRegion +{ + RgnHandle qdRgn; + Q_DISABLE_COPY(QMacSmartQuickDrawRegion) +public: + explicit QMacSmartQuickDrawRegion(RgnHandle rgn) : qdRgn(rgn) {} + ~QMacSmartQuickDrawRegion() { + extern void qt_mac_dispose_rgn(RgnHandle); // qregion_mac.cpp + qt_mac_dispose_rgn(qdRgn); + } + operator RgnHandle() { + return qdRgn; + } +}; + +// Class for chaining to gether a bunch of fades. It pretty much is only used for qmenu fading. +class QMacWindowFader +{ + QWidgetList m_windowsToFade; + float m_duration; + Q_DISABLE_COPY(QMacWindowFader) +public: + QMacWindowFader(); // PLEASE DON'T CALL THIS. + static QMacWindowFader *currentFader(); + void registerWindowToFade(QWidget *window); + void setFadeDuration(float durationInSecs) { m_duration = durationInSecs; } + float fadeDuration() const { return m_duration; } + void performFade(); +}; + +class Q_GUI_EXPORT QMacCocoaAutoReleasePool +{ +private: + void *pool; +public: + QMacCocoaAutoReleasePool(); + ~QMacCocoaAutoReleasePool(); + + inline void *handle() const { return pool; } +}; + +QString qt_mac_removeMnemonics(const QString &original); //implemented in qmacstyle_mac.cpp + +class Q_GUI_EXPORT QMacWindowChangeEvent +{ +private: + static QList<QMacWindowChangeEvent*> *change_events; +public: + QMacWindowChangeEvent() { + } + virtual ~QMacWindowChangeEvent() { + } + static inline void exec(bool ) { + } +protected: + virtual void windowChanged() = 0; + virtual void flushWindowChanged() = 0; +}; + +class QMacCGContext +{ + CGContextRef context; +public: + QMacCGContext(QPainter *p); //qpaintengine_mac.cpp + inline QMacCGContext() { context = 0; } + inline QMacCGContext(const QPaintDevice *pdev) { + extern CGContextRef qt_mac_cg_context(const QPaintDevice *); + context = qt_mac_cg_context(pdev); + } + inline QMacCGContext(CGContextRef cg, bool takeOwnership=false) { + context = cg; + if(!takeOwnership) + CGContextRetain(context); + } + inline QMacCGContext(const QMacCGContext ©) : context(0) { *this = copy; } + inline ~QMacCGContext() { + if(context) + CGContextRelease(context); + } + inline bool isNull() const { return context; } + inline operator CGContextRef() { return context; } + inline QMacCGContext &operator=(const QMacCGContext ©) { + if(context) + CGContextRelease(context); + context = copy.context; + CGContextRetain(context); + return *this; + } + inline QMacCGContext &operator=(CGContextRef cg) { + if(context) + CGContextRelease(context); + context = cg; + CGContextRetain(context); //we do not take ownership + return *this; + } +}; + +class QMacPasteboardMime; +class QMimeData; + +class QMacPasteboard +{ + struct Promise { + Promise() : itemId(0), convertor(0) { } + Promise(int itemId, QMacPasteboardMime *c, QString m, QVariant d, int o=0) : itemId(itemId), offset(o), convertor(c), mime(m), data(d) { } + int itemId, offset; + QMacPasteboardMime *convertor; + QString mime; + QVariant data; + }; + QList<Promise> promises; + + OSPasteboardRef paste; + uchar mime_type; + mutable QPointer<QMimeData> mime; + mutable bool mac_mime_source; + static OSStatus promiseKeeper(OSPasteboardRef, PasteboardItemID, CFStringRef, void *); + void clear_helper(); +public: + QMacPasteboard(OSPasteboardRef p, uchar mime_type=0); + QMacPasteboard(uchar mime_type); + QMacPasteboard(CFStringRef name=0, uchar mime_type=0); + ~QMacPasteboard(); + + bool hasFlavor(QString flavor) const; + bool hasOSType(int c_flavor) const; + + OSPasteboardRef pasteBoard() const; + QMimeData *mimeData() const; + void setMimeData(QMimeData *mime); + + QStringList formats() const; + bool hasFormat(const QString &format) const; + QVariant retrieveData(const QString &format, QVariant::Type) const; + + void clear(); + bool sync() const; +}; + +extern QPaintDevice *qt_mac_safe_pdev; //qapplication_mac.cpp + +extern OSWindowRef qt_mac_window_for(const QWidget*); //qwidget_mac.mm +extern OSViewRef qt_mac_nativeview_for(const QWidget *); //qwidget_mac.mm +extern QPoint qt_mac_nativeMapFromParent(const QWidget *child, const QPoint &pt); //qwidget_mac.mm + +#ifdef check +# undef check +#endif + +QFont qfontForThemeFont(ThemeFontID themeID); + +QColor qcolorForTheme(ThemeBrush brush); + +QColor qcolorForThemeTextColor(ThemeTextColor themeColor); + +struct QMacDndAnswerRecord { + QRect rect; + Qt::KeyboardModifiers modifiers; + Qt::MouseButtons buttons; + Qt::DropAction lastAction; + unsigned int lastOperation; + void clear() { + rect = QRect(); + modifiers = Qt::NoModifier; + buttons = Qt::NoButton; + lastAction = Qt::IgnoreAction; + lastOperation = 0; + } +}; +extern QMacDndAnswerRecord qt_mac_dnd_answer_rec; +void qt_mac_copy_answer_rect(const QDragMoveEvent &event); +bool qt_mac_mouse_inside_answer_rect(QPoint mouse); + +QT_END_NAMESPACE + +#endif // QT_MAC_P_H diff --git a/src/gui/platforms/mac/qtextengine_mac.cpp b/src/gui/platforms/mac/qtextengine_mac.cpp new file mode 100644 index 0000000000..2c6e579b45 --- /dev/null +++ b/src/gui/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 <private/qfontengine_coretext_p.h> +#include <private/qfontengine_mac_p.h> + +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<QFontEngineMacMulti *>(font); +#else + QCoreTextFontEngineMulti *fe = static_cast<QCoreTextFontEngineMulti *>(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<const QChar *>(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<QArabicProperties> 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<const ushort *>(str); + + if ((si.analysis.flags == QScriptAnalysis::SmallCaps || si.analysis.flags == QScriptAnalysis::Uppercase + || si.analysis.flags == QScriptAnalysis::Lowercase) + && uc != upperCased) + delete [] uc; +} + +QT_END_NAMESPACE diff --git a/src/gui/platforms/mac/qwidget_mac.mm b/src/gui/platforms/mac/qwidget_mac.mm new file mode 100644 index 0000000000..354f05ba10 --- /dev/null +++ b/src/gui/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 <private/qt_mac_p.h> +#include <private/qeventdispatcher_mac_p.h> + +#include "qapplication.h" +#include "qapplication_p.h" +#include "qbitmap.h" +#include "qcursor.h" +#include "qdesktopwidget.h" +#include "qevent.h" +#include "qfileinfo.h" +#include "qimage.h" +#include "qlayout.h" +#include "qmenubar.h" +#include <private/qbackingstore_p.h> +#include <private/qwindowsurface_mac_p.h> +#include <private/qpaintengine_mac_p.h> +#include "qpainter.h" +#include "qstyle.h" +#include "qtimer.h" +#include "qfocusframe.h" +#include "qdebug.h" +#include <private/qmainwindowlayout_p.h> + +#include <private/qabstractscrollarea_p.h> +#include <qabstractscrollarea.h> +#include <ApplicationServices/ApplicationServices.h> +#include <limits.h> +#include <private/qt_cocoa_helpers_mac_p.h> +#include <private/qcocoaview_mac_p.h> +#include <private/qcocoawindow_mac_p.h> +#include <private/qcocoawindowdelegate_mac_p.h> +#include <private/qcocoapanel_mac_p.h> + +#include "qwidget_p.h" +#include "qevent_p.h" +#include "qdnd_p.h" +#include <QtGui/qgraphicsproxywidget.h> +#include "qmainwindow.h" + +QT_BEGIN_NAMESPACE + +// qmainwindow.cpp +extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window); + +#define XCOORD_MAX 16383 +#define WRECT_MAX 8191 + +#ifndef QT_MAC_USE_COCOA + +extern "C" { + extern OSStatus _HIViewScrollRectWithOptions(HIViewRef, const HIRect *, CGFloat, CGFloat, + OptionBits) __attribute__ ((weak)); +} +#define kHIViewScrollRectAdjustInvalid 1 +#define kHIViewScrollRectDontInvalidateRevealedArea 2 +#endif + + +/***************************************************************************** + QWidget debug facilities + *****************************************************************************/ +//#define DEBUG_WINDOW_RGNS +//#define DEBUG_WINDOW_CREATE +//#define DEBUG_WINDOW_STATE +//#define DEBUG_WIDGET_PAINT + +/***************************************************************************** + QWidget globals + *****************************************************************************/ +#ifndef QT_MAC_USE_COCOA +typedef QHash<Qt::WindowFlags, WindowGroupRef> WindowGroupHash; +Q_GLOBAL_STATIC(WindowGroupHash, qt_mac_window_groups) +const UInt32 kWidgetCreatorQt = kEventClassQt; +enum { + kWidgetPropertyQWidget = 'QWId' //QWidget * +}; +#endif + +static bool qt_mac_raise_process = true; +static OSWindowRef qt_root_win = 0; +QWidget *mac_mouse_grabber = 0; +QWidget *mac_keyboard_grabber = 0; + +#ifndef QT_MAC_USE_COCOA +#ifdef QT_NAMESPACE + +// produce the string "com.trolltech.qt-namespace.widget", where "namespace" is the contents of QT_NAMESPACE. +#define SS(x) #x +#define S0(x) SS(x) +#define S "com.trolltech.qt-" S0(QT_NAMESPACE) ".widget" + +static CFStringRef kObjectQWidget = CFSTR(S); + +#undef SS +#undef S0 +#undef S + +#else +static CFStringRef kObjectQWidget = CFSTR("com.trolltech.qt.widget"); +#endif // QT_NAMESPACE +#endif // QT_MAC_USE_COCOA + +/***************************************************************************** + Externals + *****************************************************************************/ +extern QPointer<QWidget> qt_button_down; //qapplication_mac.cpp +extern QWidget *qt_mac_modal_blocked(QWidget *); //qapplication_mac.mm +extern void qt_event_request_activate(QWidget *); //qapplication_mac.mm +extern bool qt_event_remove_activate(); //qapplication_mac.mm +extern void qt_mac_event_release(QWidget *w); //qapplication_mac.mm +extern void qt_event_request_showsheet(QWidget *); //qapplication_mac.mm +extern void qt_event_request_window_change(QWidget *); //qapplication_mac.mm +extern QPointer<QWidget> qt_last_mouse_receiver; //qapplication_mac.mm +extern QPointer<QWidget> qt_last_native_mouse_receiver; //qt_cocoa_helpers_mac.mm +extern IconRef qt_mac_create_iconref(const QPixmap &); //qpixmap_mac.cpp +extern void qt_mac_set_cursor(const QCursor *, const QPoint &); //qcursor_mac.mm +extern void qt_mac_update_cursor(); //qcursor_mac.mm +extern bool qt_nograb(); +extern CGImageRef qt_mac_create_cgimage(const QPixmap &, bool); //qpixmap_mac.cpp +extern RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp +extern QRegion qt_mac_convert_mac_region(RgnHandle rgn); //qregion_mac.cpp +extern void qt_mac_setMouseGrabCursor(bool set, QCursor *cursor = 0); // qcursor_mac.mm +extern QPointer<QWidget> topLevelAt_cache; // qapplication_mac.mm +/***************************************************************************** + QWidget utility functions + *****************************************************************************/ +void Q_GUI_EXPORT qt_mac_set_raise_process(bool b) { qt_mac_raise_process = b; } +static QSize qt_mac_desktopSize() +{ + int w = 0, h = 0; + CGDisplayCount cg_count; + CGGetActiveDisplayList(0, 0, &cg_count); + QVector<CGDirectDisplayID> displays(cg_count); + CGGetActiveDisplayList(cg_count, displays.data(), &cg_count); + Q_ASSERT(cg_count == (CGDisplayCount)displays.size()); + for(int i = 0; i < (int)cg_count; ++i) { + CGRect r = CGDisplayBounds(displays.at(i)); + w = qMax<int>(w, qRound(r.origin.x + r.size.width)); + h = qMax<int>(h, qRound(r.origin.y + r.size.height)); + } + return QSize(w, h); +} + +#ifdef QT_MAC_USE_COCOA +static NSDrawer *qt_mac_drawer_for(const QWidget *widget) +{ + NSView *widgetView = reinterpret_cast<NSView *>(widget->window()->effectiveWinId()); + NSArray *windows = [NSApp windows]; + for (NSWindow *window in windows) { + NSArray *drawers = [window drawers]; + for (NSDrawer *drawer in drawers) { + if ([drawer contentView] == widgetView) + return drawer; + } + } + return 0; +} +#endif + +static void qt_mac_destructView(OSViewRef view) +{ +#ifdef QT_MAC_USE_COCOA + NSWindow *window = [view window]; + if ([window contentView] == view) + [window setContentView:[[NSView alloc] initWithFrame:[view bounds]]]; + [view removeFromSuperview]; + [view release]; +#else + HIViewRemoveFromSuperview(view); + CFRelease(view); +#endif +} + +static void qt_mac_destructWindow(OSWindowRef window) +{ +#ifdef QT_MAC_USE_COCOA + if ([window isVisible] && [window isSheet]){ + [NSApp endSheet:window]; + [window orderOut:window]; + } + + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] resignDelegateForWindow:window]; + [window release]; +#else + // Remove property to clean up memory: + RemoveWindowProperty(window, kWidgetCreatorQt, kWidgetPropertyQWidget); + CFRelease(window); +#endif +} + +static void qt_mac_destructDrawer(NSDrawer *drawer) +{ +#ifdef QT_MAC_USE_COCOA + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] resignDelegateForDrawer:drawer]; + [drawer release]; +#else + Q_UNUSED(drawer); +#endif +} + +bool qt_mac_can_clickThrough(const QWidget *w) +{ + static int qt_mac_carbon_clickthrough = -1; + if (qt_mac_carbon_clickthrough < 0) + qt_mac_carbon_clickthrough = !qgetenv("QT_MAC_NO_COCOA_CLICKTHROUGH").isEmpty(); + bool ret = !qt_mac_carbon_clickthrough; + for ( ; w; w = w->parentWidget()) { + if (w->testAttribute(Qt::WA_MacNoClickThrough)) { + ret = false; + break; + } + } + return ret; +} + +bool qt_mac_is_macsheet(const QWidget *w) +{ + if (!w) + return false; + + Qt::WindowModality modality = w->windowModality(); + if (modality == Qt::ApplicationModal) + return false; + return w->parentWidget() && (modality == Qt::WindowModal || w->windowType() == Qt::Sheet); +} + +bool qt_mac_is_macdrawer(const QWidget *w) +{ + return (w && w->parentWidget() && w->windowType() == Qt::Drawer); +} + +bool qt_mac_insideKeyWindow(const QWidget *w) +{ +#ifdef QT_MAC_USE_COCOA + return [[reinterpret_cast<NSView *>(w->effectiveWinId()) window] isKeyWindow]; +#else + Q_UNUSED(w); +#endif + return false; +} + +bool qt_mac_set_drawer_preferred_edge(QWidget *w, Qt::DockWidgetArea where) //users of Qt for Mac OS X can use this.. +{ + if(!qt_mac_is_macdrawer(w)) + return false; + +#if QT_MAC_USE_COCOA + NSDrawer *drawer = qt_mac_drawer_for(w); + if (!drawer) + return false; + NSRectEdge edge; + if (where & Qt::LeftDockWidgetArea) + edge = NSMinXEdge; + else if (where & Qt::RightDockWidgetArea) + edge = NSMaxXEdge; + else if (where & Qt::TopDockWidgetArea) + edge = NSMaxYEdge; + else if (where & Qt::BottomDockWidgetArea) + edge = NSMinYEdge; + else + return false; + + if (edge == [drawer preferredEdge]) //no-op + return false; + + if (w->isVisible()) { + [drawer close]; + [drawer openOnEdge:edge]; + } + [drawer setPreferredEdge:edge]; +#else + OSWindowRef window = qt_mac_window_for(w); + OptionBits edge; + if(where & Qt::LeftDockWidgetArea) + edge = kWindowEdgeLeft; + else if(where & Qt::RightDockWidgetArea) + edge = kWindowEdgeRight; + else if(where & Qt::TopDockWidgetArea) + edge = kWindowEdgeTop; + else if(where & Qt::BottomDockWidgetArea) + edge = kWindowEdgeBottom; + else + return false; + + if(edge == GetDrawerPreferredEdge(window)) //no-op + return false; + + //do it + SetDrawerPreferredEdge(window, edge); + if(w->isVisible()) { + CloseDrawer(window, false); + OpenDrawer(window, edge, true); + } +#endif + return true; +} + +#ifndef QT_MAC_USE_COCOA +Q_GUI_EXPORT +#endif +QPoint qt_mac_posInWindow(const QWidget *w) +{ + QPoint ret = w->data->wrect.topLeft(); + while(w && !w->isWindow()) { + ret += w->pos(); + w = w->parentWidget(); + } + return ret; +} + +//find a QWidget from a OSWindowRef +QWidget *qt_mac_find_window(OSWindowRef window) +{ +#ifdef QT_MAC_USE_COCOA + return [window QT_MANGLE_NAMESPACE(qt_qwidget)]; +#else + if(!window) + return 0; + + QWidget *ret; + if(GetWindowProperty(window, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(ret), 0, &ret) == noErr) + return ret; + return 0; +#endif +} + +inline static void qt_mac_set_fullscreen_mode(bool b) +{ + extern bool qt_mac_app_fullscreen; //qapplication_mac.mm + if(qt_mac_app_fullscreen == b) + return; + qt_mac_app_fullscreen = b; + if (b) { + SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar); + } else { + SetSystemUIMode(kUIModeNormal, 0); + } +} + +Q_GUI_EXPORT OSViewRef qt_mac_nativeview_for(const QWidget *w) +{ + return reinterpret_cast<OSViewRef>(w->internalWinId()); +} + +Q_GUI_EXPORT OSViewRef qt_mac_effectiveview_for(const QWidget *w) +{ + // Get the first non-alien (parent) widget for + // w, and return its NSView (if it has one): + return reinterpret_cast<OSViewRef>(w->effectiveWinId()); +} + +Q_GUI_EXPORT OSViewRef qt_mac_get_contentview_for(OSWindowRef w) +{ +#ifdef QT_MAC_USE_COCOA + return [w contentView]; +#else + HIViewRef contentView = 0; + OSStatus err = GetRootControl(w, &contentView); // Returns the window's content view (Apple QA1214) + if (err == errUnknownControl) { + contentView = HIViewGetRoot(w); + } else if (err != noErr) { + qWarning("Qt:Could not get content or root view of window! %s:%d [%ld]", + __FILE__, __LINE__, err); + } + return contentView; +#endif +} + +bool qt_mac_sendMacEventToWidget(QWidget *widget, EventRef ref) +{ + return widget->macEvent(0, ref); +} + +Q_GUI_EXPORT OSWindowRef qt_mac_window_for(OSViewRef view) +{ +#ifdef QT_MAC_USE_COCOA + if (view) + return [view window]; + return 0; +#else + return HIViewGetWindow(view); +#endif +} + +static bool qt_isGenuineQWidget(OSViewRef ref) +{ +#ifdef QT_MAC_USE_COCOA + return [ref isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaView) class]]; +#else + return HIObjectIsOfClass(HIObjectRef(ref), kObjectQWidget); +#endif +} + +bool qt_isGenuineQWidget(const QWidget *window) +{ + if (!window) + return false; + + if (!window->internalWinId()) + return true; //alien + + return qt_isGenuineQWidget(OSViewRef(window->internalWinId())); +} + +Q_GUI_EXPORT OSWindowRef qt_mac_window_for(const QWidget *w) +{ + if (OSViewRef hiview = qt_mac_effectiveview_for(w)) { + OSWindowRef window = qt_mac_window_for(hiview); + if (window) + return window; + + if (qt_isGenuineQWidget(hiview)) { + // This is a workaround for NSToolbar. When a widget is hidden + // by clicking the toolbar button, Cocoa reparents the widgets + // to another window (but Qt doesn't know about it). + // When we start showing them, it reparents back, + // but at this point it's window is nil, but the window it's being brought + // into (the Qt one) is for sure created. + // This stops the hierarchy moving under our feet. + QWidget *toplevel = w->window(); + if (toplevel != w) { + hiview = qt_mac_nativeview_for(toplevel); + if (OSWindowRef w = qt_mac_window_for(hiview)) + return w; + } + + toplevel->d_func()->createWindow_sys(); + // Reget the hiview since "create window" could potentially move the view (I guess). + hiview = qt_mac_nativeview_for(toplevel); + return qt_mac_window_for(hiview); + } + } + return 0; +} + +#ifndef QT_MAC_USE_COCOA +/* Checks if the current group is a 'stay on top' group. If so, the + group gets removed from the hash table */ +static void qt_mac_release_stays_on_top_group(WindowGroupRef group) +{ + for (WindowGroupHash::iterator it = qt_mac_window_groups()->begin(); it != qt_mac_window_groups()->end(); ++it) { + if (it.value() == group) { + qt_mac_window_groups()->remove(it.key()); + return; + } + } +} + +/* Use this function instead of ReleaseWindowGroup, this will be sure to release the + stays on top window group (created with qt_mac_get_stays_on_top_group below) */ +static void qt_mac_release_window_group(WindowGroupRef group) +{ + ReleaseWindowGroup(group); + if (GetWindowGroupRetainCount(group) == 0) + qt_mac_release_stays_on_top_group(group); +} +#define ReleaseWindowGroup(x) Are you sure you wanted to do that? (you wanted qt_mac_release_window_group) + +SInt32 qt_mac_get_group_level(WindowClass wclass) +{ + SInt32 group_level; + CGWindowLevel tmpLevel; + GetWindowGroupLevelOfType(GetWindowGroupOfClass(wclass), kWindowGroupLevelActive, &tmpLevel); + group_level = tmpLevel; + return group_level; +} +#endif + +#ifndef QT_MAC_USE_COCOA +static void qt_mac_set_window_group(OSWindowRef window, Qt::WindowFlags flags, int level) +{ + WindowGroupRef group = 0; + if (qt_mac_window_groups()->contains(flags)) { + group = qt_mac_window_groups()->value(flags); + RetainWindowGroup(group); + } else { + CreateWindowGroup(kWindowActivationScopeNone, &group); + SetWindowGroupLevel(group, level); + SetWindowGroupParent(group, GetWindowGroupOfClass(kAllWindowClasses)); + qt_mac_window_groups()->insert(flags, group); + } + SetWindowGroup(window, group); +} + +inline static void qt_mac_set_window_group_to_stays_on_top(OSWindowRef window, Qt::WindowType type) +{ + // We create one static stays on top window group so that + // all stays on top (aka popups) will fall into the same + // group and be able to be raise()'d with releation to one another (from + // within the same window group). + qt_mac_set_window_group(window, type|Qt::WindowStaysOnTopHint, qt_mac_get_group_level(kOverlayWindowClass)); +} + +inline static void qt_mac_set_window_group_to_tooltip(OSWindowRef window) +{ + // Since new groups are created for 'stays on top' windows, the + // same must be done for tooltips. Otherwise, tooltips would be drawn + // below 'stays on top' widgets even tough they are on the same level. + // Also, add 'two' to the group level to make sure they also get on top of popups. + qt_mac_set_window_group(window, Qt::ToolTip, qt_mac_get_group_level(kHelpWindowClass)+2); +} + +inline static void qt_mac_set_window_group_to_popup(OSWindowRef window) +{ + // In Qt, a popup is seen as a 'stay on top' window. + // Since new groups are created for 'stays on top' windows, the + // same must be done for popups. Otherwise, popups would be drawn + // below 'stays on top' windows. Add 1 to get above pure stay-on-top windows. + qt_mac_set_window_group(window, Qt::Popup, qt_mac_get_group_level(kOverlayWindowClass)+1); +} +#endif + +inline static bool updateRedirectedToGraphicsProxyWidget(QWidget *widget, const QRect &rect) +{ + if (!widget) + return false; + +#ifndef QT_NO_GRAPHICSVIEW + QWidget *tlw = widget->window(); + QWExtra *extra = qt_widget_private(tlw)->extra; + if (extra && extra->proxyWidget) { + extra->proxyWidget->update(rect.translated(widget->mapTo(tlw, QPoint()))); + return true; + } +#endif + + return false; +} + +inline static bool updateRedirectedToGraphicsProxyWidget(QWidget *widget, const QRegion &rgn) +{ + if (!widget) + return false; + +#ifndef QT_NO_GRAPHICSVIEW + QWidget *tlw = widget->window(); + QWExtra *extra = qt_widget_private(tlw)->extra; + if (extra && extra->proxyWidget) { + const QPoint offset(widget->mapTo(tlw, QPoint())); + const QVector<QRect> rects = rgn.rects(); + for (int i = 0; i < rects.size(); ++i) + extra->proxyWidget->update(rects.at(i).translated(offset)); + return true; + } +#endif + + return false; +} + +void QWidgetPrivate::macSetNeedsDisplay(QRegion region) +{ + Q_Q(QWidget); +#ifndef QT_MAC_USE_COCOA + if (region.isEmpty()) + HIViewSetNeedsDisplay(qt_mac_nativeview_for(q), true); + else if (RgnHandle rgnHandle = region.toQDRgnForUpdate_sys()) + HIViewSetNeedsDisplayInRegion(qt_mac_nativeview_for(q), QMacSmartQuickDrawRegion(rgnHandle), true); + else + HIViewSetNeedsDisplay(qt_mac_nativeview_for(q), true); // do a complete repaint on overflow. +#else + if (NSView *nativeView = qt_mac_nativeview_for(q)) { + // INVARIANT: q is _not_ alien. So we can optimize a little: + if (region.isEmpty()) { + [nativeView setNeedsDisplay:YES]; + } else { + QVector<QRect> rects = region.rects(); + for (int i = 0; i<rects.count(); ++i) { + const QRect &rect = rects.at(i); + NSRect nsrect = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + [nativeView setNeedsDisplayInRect:nsrect]; + } + } + } else if (QWidget *effectiveWidget = q->nativeParentWidget()) { + // INVARIANT: q is alien, and effectiveWidget is native. + if (NSView *effectiveView = qt_mac_nativeview_for(effectiveWidget)) { + if (region.isEmpty()) { + const QRect &rect = q->rect(); + QPoint p = q->mapTo(effectiveWidget, rect.topLeft()); + NSRect nsrect = NSMakeRect(p.x(), p.y(), rect.width(), rect.height()); + [effectiveView setNeedsDisplayInRect:nsrect]; + } else { + QVector<QRect> rects = region.rects(); + for (int i = 0; i<rects.count(); ++i) { + const QRect &rect = rects.at(i); + QPoint p = q->mapTo(effectiveWidget, rect.topLeft()); + NSRect nsrect = NSMakeRect(p.x(), p.y(), rect.width(), rect.height()); + [effectiveView setNeedsDisplayInRect:nsrect]; + } + } + } + } +#endif +} + +void QWidgetPrivate::macUpdateIsOpaque() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; +#ifndef QT_MAC_USE_COCOA + HIViewFeatures bits; + HIViewRef hiview = qt_mac_nativeview_for(q); + HIViewGetFeatures(hiview, &bits); + if ((bits & kHIViewIsOpaque) == isOpaque) + return; + if (isOpaque) { + HIViewChangeFeatures(hiview, kHIViewIsOpaque, 0); + } else { + HIViewChangeFeatures(hiview, 0, kHIViewIsOpaque); + } + if (q->isVisible()) + HIViewReshapeStructure(qt_mac_nativeview_for(q)); +#else + if (isRealWindow() && !q->testAttribute(Qt::WA_MacBrushedMetal)) { + bool opaque = isOpaque; + if (extra && extra->imageMask) + opaque = false; // we are never opaque when we have a mask. + [qt_mac_window_for(q) setOpaque:opaque]; + } +#endif +} +#ifdef QT_MAC_USE_COCOA +static OSWindowRef qt_mac_create_window(QWidget *widget, WindowClass wclass, + NSUInteger wattr, const QRect &crect) +{ + // Determine if we need to add in our "custom window" attribute. Cocoa is rather clever + // in deciding if we need the maximize button or not (i.e., it's resizeable, so you + // must need a maximize button). So, the only buttons we have control over are the + // close and minimize buttons. If someone wants to customize and NOT have the maximize + // button, then we have to do our hack. We only do it for these cases because otherwise + // the window looks different when activated. This "QtMacCustomizeWindow" attribute is + // intruding on a public space and WILL BREAK in the future. + // One can hope that there is a more public API available by that time. + Qt::WindowFlags flags = widget ? widget->windowFlags() : Qt::WindowFlags(0); + if ((flags & Qt::CustomizeWindowHint)) { + if ((flags & (Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint | Qt::WindowTitleHint)) + && !(flags & Qt::WindowMaximizeButtonHint)) + wattr |= QtMacCustomizeWindow; + } + + // If we haven't created the desktop widget, you have to pass the rectangle + // in "cocoa coordinates" (i.e., top points to the lower left coordinate). + // Otherwise, we do the conversion for you. Since we are the only ones that + // create the desktop widget, this is OK (but confusing). + NSRect geo = NSMakeRect(crect.left(), + (qt_root_win != 0) ? flipYCoordinate(crect.bottom() + 1) : crect.top(), + crect.width(), crect.height()); + QMacCocoaAutoReleasePool pool; + OSWindowRef window; + switch (wclass) { + case kMovableModalWindowClass: + case kModalWindowClass: + case kSheetWindowClass: + case kFloatingWindowClass: + case kOverlayWindowClass: + case kHelpWindowClass: { + NSPanel *panel; + BOOL needFloating = NO; + BOOL worksWhenModal = widget && (widget->windowType() == Qt::Popup); + // Add in the extra flags if necessary. + switch (wclass) { + case kSheetWindowClass: + wattr |= NSDocModalWindowMask; + break; + case kFloatingWindowClass: + case kHelpWindowClass: + needFloating = YES; + wattr |= NSUtilityWindowMask; + break; + default: + break; + } + panel = [[QT_MANGLE_NAMESPACE(QCocoaPanel) alloc] QT_MANGLE_NAMESPACE(qt_initWithQWidget):widget contentRect:geo styleMask:wattr]; + [panel setFloatingPanel:needFloating]; + [panel setWorksWhenModal:worksWhenModal]; + window = panel; + break; + } + case kDrawerWindowClass: { + NSDrawer *drawer = [[NSDrawer alloc] initWithContentSize:geo.size preferredEdge:NSMinXEdge]; + [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] becomeDelegateForDrawer:drawer widget:widget]; + QWidget *parentWidget = widget->parentWidget(); + if (parentWidget) + [drawer setParentWindow:qt_mac_window_for(parentWidget)]; + [drawer setLeadingOffset:0.0]; + [drawer setTrailingOffset:25.0]; + window = [[drawer contentView] window]; // Just to make sure we actually return a window + break; + } + default: + window = [[QT_MANGLE_NAMESPACE(QCocoaWindow) alloc] QT_MANGLE_NAMESPACE(qt_initWithQWidget):widget contentRect:geo styleMask:wattr]; + break; + } + qt_syncCocoaTitleBarButtons(window, widget); + return window; +} +#else +static OSWindowRef qt_mac_create_window(QWidget *, WindowClass wclass, WindowAttributes wattr, + const QRect &crect) +{ + OSWindowRef window; + Rect geo; + SetRect(&geo, crect.left(), crect.top(), crect.right() + 1, crect.bottom() + 1); + OSStatus err; + if(geo.right <= geo.left) geo.right = geo.left + 1; + if(geo.bottom <= geo.top) geo.bottom = geo.top + 1; + Rect null_rect; + SetRect(&null_rect, 0, 0, 1, 1); + err = CreateNewWindow(wclass, wattr, &null_rect, &window); + if(err == noErr) { + err = SetWindowBounds(window, kWindowContentRgn, &geo); + if(err != noErr) + qWarning("QWidget: Internal error (%s:%d)", __FILE__, __LINE__); + } + return window; +} + +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 +/* We build the release package against the 10.4 SDK. + So, to enable gestures for applications running on + 10.6+, we define the missing constants here: */ +enum { + kEventClassGesture = 'gest', + kEventGestureStarted = 1, + kEventGestureEnded = 2, + kEventGestureMagnify = 4, + kEventGestureSwipe = 5, + kEventGestureRotate = 6, + kEventParamRotationAmount = 'rota', + kEventParamSwipeDirection = 'swip', + kEventParamMagnificationAmount = 'magn' +}; +#endif +#endif // QT_NO_GESTURES + +// window events +static EventTypeSpec window_events[] = { + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowExpanded }, + { kEventClassWindow, kEventWindowHidden }, + { kEventClassWindow, kEventWindowZoom }, + { kEventClassWindow, kEventWindowZoomed }, + { kEventClassWindow, kEventWindowCollapsed }, + { kEventClassWindow, kEventWindowToolbarSwitchMode }, + { kEventClassWindow, kEventWindowProxyBeginDrag }, + { kEventClassWindow, kEventWindowProxyEndDrag }, + { kEventClassWindow, kEventWindowResizeCompleted }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowGetRegion }, + { kEventClassWindow, kEventWindowGetClickModality }, + { kEventClassWindow, kEventWindowTransitionCompleted }, + { kEventClassGesture, kEventGestureStarted }, + { kEventClassGesture, kEventGestureEnded }, + { kEventClassGesture, kEventGestureMagnify }, + { kEventClassGesture, kEventGestureSwipe }, + { kEventClassGesture, kEventGestureRotate }, + { kEventClassMouse, kEventMouseDown } +}; +static EventHandlerUPP mac_win_eventUPP = 0; +static void cleanup_win_eventUPP() +{ + DisposeEventHandlerUPP(mac_win_eventUPP); + mac_win_eventUPP = 0; +} +static const EventHandlerUPP make_win_eventUPP() +{ + if(mac_win_eventUPP) + return mac_win_eventUPP; + qAddPostRoutine(cleanup_win_eventUPP); + return mac_win_eventUPP = NewEventHandlerUPP(QWidgetPrivate::qt_window_event); +} +OSStatus QWidgetPrivate::qt_window_event(EventHandlerCallRef er, EventRef event, void *) +{ + QScopedLoopLevelCounter loopLevelCounter(qApp->d_func()->threadData); + bool handled_event = true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassWindow: { + WindowRef wid = 0; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, + sizeof(WindowRef), 0, &wid); + QWidget *widget = qt_mac_find_window(wid); + if(!widget) { + handled_event = false; + } else if(ekind == kEventWindowGetClickModality) { + // Carbon will send us kEventWindowGetClickModality before every + // mouse press / release event. By returning 'true', we tell Carbon + // that we would like the event target to receive the mouse event even + // if the target is modally shaddowed. In Qt, this makes sense when we + // e.g. have a popup showing, as the popup will grab the event + // and perhaps use it to close itself. + // By also setting the current modal window back into the event, we + // help Carbon determining which window is supposed to be raised. + handled_event = qApp->activePopupWidget() ? true : false; + } else if(ekind == kEventWindowClose) { + widget->d_func()->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent); + QMenuBar::macUpdateMenuBar(); + } else if (ekind == kEventWindowTransitionCompleted) { + WindowTransitionAction transitionAction; + GetEventParameter(event, kEventParamWindowTransitionAction, typeWindowTransitionAction, + 0, sizeof(transitionAction), 0, &transitionAction); + if (transitionAction == kWindowHideTransitionAction) + widget->hide(); + } else if(ekind == kEventWindowExpanded) { + Qt::WindowStates currState = Qt::WindowStates(widget->data->window_state); + Qt::WindowStates newState = currState; + if (currState & Qt::WindowMinimized) + newState &= ~Qt::WindowMinimized; + if (!(currState & Qt::WindowActive)) + newState |= Qt::WindowActive; + if (newState != currState) { + // newState will differ from currState if the window + // was expanded after clicking on the jewels (as opposed + // to calling QWidget::setWindowState) + widget->data->window_state = newState; + QWindowStateChangeEvent e(currState); + QApplication::sendSpontaneousEvent(widget, &e); + } + + QShowEvent qse; + QApplication::sendSpontaneousEvent(widget, &qse); + } else if(ekind == kEventWindowZoom) { + widget->d_func()->topData()->normalGeometry = widget->geometry(); + handled_event = false; + } else if(ekind == kEventWindowZoomed) { + WindowPartCode windowPart; + GetEventParameter(event, kEventParamWindowPartCode, + typeWindowPartCode, 0, sizeof(windowPart), 0, &windowPart); + if(windowPart == inZoomIn && widget->isMaximized()) { + + widget->data->window_state = widget->data->window_state & ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state | Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + } else if(windowPart == inZoomOut && !widget->isMaximized()) { + widget->data->window_state = widget->data->window_state | Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state + & ~Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + } + qt_button_down = 0; + } else if(ekind == kEventWindowCollapsed) { + if (!widget->isMinimized()) { + widget->data->window_state = widget->data->window_state | Qt::WindowMinimized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state & ~Qt::WindowMinimized)); + QApplication::sendSpontaneousEvent(widget, &e); + } + + // Deactivate this window: + if (widget->isActiveWindow() && !(widget->windowType() == Qt::Popup)) { + QWidget *w = 0; + if (widget->parentWidget()) + w = widget->parentWidget()->window(); + if (!w || (!w->isVisible() && !w->isMinimized())) { + for (WindowPtr wp = GetFrontWindowOfClass(kDocumentWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kDocumentWindowClass, true)) { + if ((w = qt_mac_find_window(wp))) + break; + } + } + if(!(w && w->isVisible() && !w->isMinimized())) + qApp->setActiveWindow(0); + } + + //we send a hide to be like X11/Windows + QEvent e(QEvent::Hide); + QApplication::sendSpontaneousEvent(widget, &e); + qt_button_down = 0; + } else if(ekind == kEventWindowToolbarSwitchMode) { + macSendToolbarChangeEvent(widget); + HIToolbarRef toolbar; + if (GetWindowToolbar(wid, &toolbar) == noErr) { + if (toolbar) { + // Let HIToolbar do its thang, but things like the OpenGL context + // needs to know about it. + CallNextEventHandler(er, event); + qt_event_request_window_change(widget); + widget->data->fstrut_dirty = true; + } + } + } else if(ekind == kEventWindowGetRegion) { + WindowRef window; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, + sizeof(window), 0, &window); + WindowRegionCode wcode; + GetEventParameter(event, kEventParamWindowRegionCode, typeWindowRegionCode, 0, + sizeof(wcode), 0, &wcode); + if (wcode != kWindowOpaqueRgn){ + // If the region is kWindowOpaqueRgn, don't call next + // event handler cause this will make the shadow of + // masked windows become offset. Unfortunately, we're not sure why. + CallNextEventHandler(er, event); + } + RgnHandle rgn; + GetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + + if(QWidgetPrivate::qt_widget_rgn(qt_mac_find_window(window), wcode, rgn, false)) + SetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, sizeof(rgn), &rgn); + } else if(ekind == kEventWindowProxyBeginDrag) { + QIconDragEvent e; + QApplication::sendSpontaneousEvent(widget, &e); + } else if(ekind == kEventWindowResizeCompleted) { + // Create a mouse up event, since such an event is not send by carbon to the + // application event handler (while a mouse down <b>is</b> on kEventWindowResizeStarted) + EventRef mouseUpEvent; + CreateEvent(0, kEventClassMouse, kEventMouseUp, 0, kEventAttributeUserEvent, &mouseUpEvent); + UInt16 mbutton = kEventMouseButtonPrimary; + SetEventParameter(mouseUpEvent, kEventParamMouseButton, typeMouseButton, sizeof(mbutton), &mbutton); + WindowRef window; + GetEventParameter(event, kEventParamDirectObject, typeWindowRef, 0, sizeof(window), 0, &window); + Rect dragRect; + GetWindowBounds(window, kWindowGrowRgn, &dragRect); + Point pos = {dragRect.bottom, dragRect.right}; + SetEventParameter(mouseUpEvent, kEventParamMouseLocation, typeQDPoint, sizeof(pos), &pos); + SendEventToApplication(mouseUpEvent); + ReleaseEvent(mouseUpEvent); + } else if(ekind == kEventWindowBoundsChanging) { + UInt32 flags = 0; + GetEventParameter(event, kEventParamAttributes, typeUInt32, 0, + sizeof(flags), 0, &flags); + Rect nr; + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, 0, + sizeof(nr), 0, &nr); + + QRect newRect(nr.left, nr.top, nr.right - nr.left, nr.bottom - nr.top); + + QTLWExtra * const tlwExtra = widget->d_func()->maybeTopData(); + if (tlwExtra && tlwExtra->isSetGeometry == 1) { + widget->d_func()->setGeometry_sys_helper(newRect.left(), newRect.top(), newRect.width(), newRect.height(), tlwExtra->isMove); + } else { + //implicitly removes the maximized bit + if((widget->data->window_state & Qt::WindowMaximized) && + IsWindowInStandardState(wid, 0, 0)) { + widget->data->window_state &= ~Qt::WindowMaximized; + QWindowStateChangeEvent e(Qt::WindowStates(widget->data->window_state + | Qt::WindowMaximized)); + QApplication::sendSpontaneousEvent(widget, &e); + + } + + handled_event = false; + const QRect oldRect = widget->data->crect; + if((flags & kWindowBoundsChangeOriginChanged)) { + if(nr.left != oldRect.x() || nr.top != oldRect.y()) { + widget->data->crect.moveTo(nr.left, nr.top); + QMoveEvent qme(widget->data->crect.topLeft(), oldRect.topLeft()); + QApplication::sendSpontaneousEvent(widget, &qme); + } + } + if((flags & kWindowBoundsChangeSizeChanged)) { + if (widget->isWindow()) { + QSize newSize = QLayout::closestAcceptableSize(widget, newRect.size()); + int dh = newSize.height() - newRect.height(); + int dw = newSize.width() - newRect.width(); + if (dw != 0 || dh != 0) { + handled_event = true; // We want to change the bounds, so we handle the event + + // set the rect, so we can also do the resize down below (yes, we need to resize). + newRect.setBottom(newRect.bottom() + dh); + newRect.setRight(newRect.right() + dw); + + nr.left = newRect.x(); + nr.top = newRect.y(); + nr.right = nr.left + newRect.width(); + nr.bottom = nr.top + newRect.height(); + SetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &nr); + } + } + + if (oldRect.width() != newRect.width() || oldRect.height() != newRect.height()) { + widget->data->crect.setSize(newRect.size()); + HIRect bounds = CGRectMake(0, 0, newRect.width(), newRect.height()); + + // If the WA_StaticContents attribute is set we can optimize the resize + // by only repainting the newly exposed area. We do this by disabling + // painting when setting the size of the view. The OS will invalidate + // the newly exposed area for us. + const bool staticContents = widget->testAttribute(Qt::WA_StaticContents); + const HIViewRef view = qt_mac_nativeview_for(widget); + if (staticContents) + HIViewSetDrawingEnabled(view, false); + HIViewSetFrame(view, &bounds); + if (staticContents) + HIViewSetDrawingEnabled(view, true); + + QResizeEvent qre(newRect.size(), oldRect.size()); + QApplication::sendSpontaneousEvent(widget, &qre); + qt_event_request_window_change(widget); + } + } + } + } else if (ekind == kEventWindowHidden) { + // Make sure that we also hide any visible sheets on our window. + // Cocoa does the right thing for us. + const QObjectList children = widget->children(); + const int childCount = children.count(); + for (int i = 0; i < childCount; ++i) { + QObject *obj = children.at(i); + if (obj->isWidgetType()) { + QWidget *widget = static_cast<QWidget *>(obj); + if (qt_mac_is_macsheet(widget) && widget->isVisible()) + widget->hide(); + } + } + } else { + handled_event = false; + } + break; } + case kEventClassMouse: { +#if 0 + return SendEventToApplication(event); +#endif + + bool send_to_app = false; + { + WindowPartCode wpc; + if (GetEventParameter(event, kEventParamWindowPartCode, typeWindowPartCode, 0, + sizeof(wpc), 0, &wpc) == noErr && wpc != inContent) + send_to_app = true; + } + if(!send_to_app) { + WindowRef window; + if(GetEventParameter(event, kEventParamWindowRef, typeWindowRef, 0, + sizeof(window), 0, &window) == noErr) { + HIViewRef hiview; + if(HIViewGetViewForMouseEvent(HIViewGetRoot(window), event, &hiview) == noErr) { + if(QWidget *w = QWidget::find((WId)hiview)) { +#if 0 + send_to_app = !w->isActiveWindow(); +#else + Q_UNUSED(w); + send_to_app = true; +#endif + } + } + } + } + if(send_to_app) + return SendEventToApplication(event); + handled_event = false; + break; } + +#ifndef QT_NO_GESTURES + case kEventClassGesture: { + // First, find the widget that was under + // the mouse when the gesture happened: + HIPoint screenLocation; + if (GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, 0, + sizeof(screenLocation), 0, &screenLocation) != noErr) { + handled_event = false; + break; + } + QWidget *widget = QApplication::widgetAt(screenLocation.x, screenLocation.y); + if (!widget) { + handled_event = false; + break; + } + + QNativeGestureEvent qNGEvent; + qNGEvent.position = QPoint(screenLocation.x, screenLocation.y); + + switch (ekind) { + case kEventGestureStarted: + qNGEvent.gestureType = QNativeGestureEvent::GestureBegin; + break; + case kEventGestureEnded: + qNGEvent.gestureType = QNativeGestureEvent::GestureEnd; + break; + case kEventGestureRotate: { + CGFloat amount; + if (GetEventParameter(event, kEventParamRotationAmount, 'cgfl', 0, + sizeof(amount), 0, &amount) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Rotate; + qNGEvent.percentage = float(-amount); + break; } + case kEventGestureSwipe: { + HIPoint swipeDirection; + if (GetEventParameter(event, kEventParamSwipeDirection, typeHIPoint, 0, + sizeof(swipeDirection), 0, &swipeDirection) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Swipe; + if (swipeDirection.x == 1) + qNGEvent.angle = 180.0f; + else if (swipeDirection.x == -1) + qNGEvent.angle = 0.0f; + else if (swipeDirection.y == 1) + qNGEvent.angle = 90.0f; + else if (swipeDirection.y == -1) + qNGEvent.angle = 270.0f; + break; } + case kEventGestureMagnify: { + CGFloat amount; + if (GetEventParameter(event, kEventParamMagnificationAmount, 'cgfl', 0, + sizeof(amount), 0, &amount) != noErr) { + handled_event = false; + break; + } + qNGEvent.gestureType = QNativeGestureEvent::Zoom; + qNGEvent.percentage = float(amount); + break; } + } + + QApplication::sendSpontaneousEvent(widget, &qNGEvent); + break; } +#endif // QT_NO_GESTURES + + default: + handled_event = false; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +} + +// widget events +static HIObjectClassRef widget_class = 0; +static EventTypeSpec widget_events[] = { + { kEventClassHIObject, kEventHIObjectConstruct }, + { kEventClassHIObject, kEventHIObjectDestruct }, + + { kEventClassControl, kEventControlDraw }, + { kEventClassControl, kEventControlInitialize }, + { kEventClassControl, kEventControlGetPartRegion }, + { kEventClassControl, kEventControlGetClickActivation }, + { kEventClassControl, kEventControlSetFocusPart }, + { kEventClassControl, kEventControlDragEnter }, + { kEventClassControl, kEventControlDragWithin }, + { kEventClassControl, kEventControlDragLeave }, + { kEventClassControl, kEventControlDragReceive }, + { kEventClassControl, kEventControlOwningWindowChanged }, + { kEventClassControl, kEventControlBoundsChanged }, + { kEventClassControl, kEventControlGetSizeConstraints }, + { kEventClassControl, kEventControlVisibilityChanged }, + + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseMoved }, + { kEventClassMouse, kEventMouseDragged } +}; +static EventHandlerUPP mac_widget_eventUPP = 0; +static void cleanup_widget_eventUPP() +{ + DisposeEventHandlerUPP(mac_widget_eventUPP); + mac_widget_eventUPP = 0; +} +static const EventHandlerUPP make_widget_eventUPP() +{ + if(mac_widget_eventUPP) + return mac_widget_eventUPP; + qAddPostRoutine(cleanup_widget_eventUPP); + return mac_widget_eventUPP = NewEventHandlerUPP(QWidgetPrivate::qt_widget_event); +} +OSStatus QWidgetPrivate::qt_widget_event(EventHandlerCallRef er, EventRef event, void *) +{ + QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + + bool handled_event = true; + UInt32 ekind = GetEventKind(event), eclass = GetEventClass(event); + switch(eclass) { + case kEventClassHIObject: { + HIViewRef view = 0; + GetEventParameter(event, kEventParamHIObjectInstance, typeHIObjectRef, + 0, sizeof(view), 0, &view); + if(ekind == kEventHIObjectConstruct) { + if(view) { + HIViewChangeFeatures(view, kHIViewAllowsSubviews, 0); + SetEventParameter(event, kEventParamHIObjectInstance, + typeVoidPtr, sizeof(view), &view); + } + } else if(ekind == kEventHIObjectDestruct) { + //nothing to really do.. or is there? + } else { + handled_event = false; + } + break; } + case kEventClassControl: { + QWidget *widget = 0; + HIViewRef hiview = 0; + if(GetEventParameter(event, kEventParamDirectObject, typeControlRef, + 0, sizeof(hiview), 0, &hiview) == noErr) + widget = QWidget::find((WId)hiview); + if (widget && widget->macEvent(er, event)) + return noErr; + if(ekind == kEventControlDraw) { + if(widget && qt_isGenuineQWidget(hiview)) { + + // if there is a window change event pending for any gl child wigets, + // send it immediately. (required for flicker-free resizing) + extern void qt_mac_send_posted_gl_updates(QWidget *widget); + qt_mac_send_posted_gl_updates(widget); + + if (QApplicationPrivate::graphicsSystem() && !widget->d_func()->paintOnScreen()) { + widget->d_func()->syncBackingStore(); + widget->d_func()->dirtyOnWidget = QRegion(); + return noErr; + } + + //requested rgn + RgnHandle rgn; + GetEventParameter(event, kEventParamRgnHandle, typeQDRgnHandle, 0, sizeof(rgn), 0, &rgn); + QRegion qrgn(qt_mac_convert_mac_region(rgn)); + + //update handles + GrafPtr qd = 0; + CGContextRef cg = 0; + if(GetEventParameter(event, kEventParamCGContextRef, typeCGContextRef, 0, sizeof(cg), 0, &cg) != noErr) { + Q_ASSERT(false); + } + widget->d_func()->hd = cg; + widget->d_func()->qd_hd = qd; + CGContextSaveGState(cg); + +#ifdef DEBUG_WIDGET_PAINT + const bool doDebug = true; + if(doDebug) { + qDebug("asked to draw %p[%p] [%s::%s] %p[%p] [%d] [%dx%d]", widget, hiview, widget->metaObject()->className(), + widget->objectName().local8Bit().data(), widget->parentWidget(), + (HIViewRef)(widget->parentWidget() ? qt_mac_nativeview_for(widget->parentWidget()) : (HIViewRef)0), + HIViewIsCompositingEnabled(hiview), qt_mac_posInWindow(widget).x(), qt_mac_posInWindow(widget).y()); +#if 0 + QVector<QRect> region_rects = qrgn.rects(); + qDebug("Region! %d", region_rects.count()); + for(int i = 0; i < region_rects.count(); i++) + qDebug("%d %d %d %d", region_rects[i].x(), region_rects[i].y(), + region_rects[i].width(), region_rects[i].height()); + region_rects = widget->d_func()->clp.rects(); + qDebug("Widget Region! %d", region_rects.count()); + for(int i = 0; i < region_rects.count(); i++) + qDebug("%d %d %d %d", region_rects[i].x(), region_rects[i].y(), + region_rects[i].width(), region_rects[i].height()); +#endif + } +#endif + if (widget->isVisible() && widget->updatesEnabled()) { //process the actual paint event. + if(widget->testAttribute(Qt::WA_WState_InPaintEvent)) + qWarning("QWidget::repaint: Recursive repaint detected"); + if (widget->isWindow() && !widget->d_func()->isOpaque + && !widget->testAttribute(Qt::WA_MacBrushedMetal)) { + QRect qrgnRect = qrgn.boundingRect(); + CGContextClearRect(cg, CGRectMake(qrgnRect.x(), qrgnRect.y(), qrgnRect.width(), qrgnRect.height())); + } + + QPoint redirectionOffset(0, 0); + QWidget *tl = widget->window(); + if (tl) { + Qt::WindowFlags flags = tl->windowFlags(); + if (flags & Qt::FramelessWindowHint + || (flags & Qt::CustomizeWindowHint && !(flags & Qt::WindowTitleHint))) { + if(tl->d_func()->extra && !tl->d_func()->extra->mask.isEmpty()) + redirectionOffset += tl->d_func()->extra->mask.boundingRect().topLeft(); + } + } + + //setup the context + widget->setAttribute(Qt::WA_WState_InPaintEvent); + QPaintEngine *engine = widget->paintEngine(); + if (engine) + engine->setSystemClip(qrgn); + + //handle the erase + if (engine && (!widget->testAttribute(Qt::WA_NoSystemBackground) + && (widget->isWindow() || widget->autoFillBackground()) + || widget->testAttribute(Qt::WA_TintedBackground) + || widget->testAttribute(Qt::WA_StyledBackground))) { +#ifdef DEBUG_WIDGET_PAINT + if(doDebug) + qDebug(" Handling erase for [%s::%s]", widget->metaObject()->className(), + widget->objectName().local8Bit().data()); +#endif + if (!redirectionOffset.isNull()) + widget->d_func()->setRedirected(widget, redirectionOffset); + + bool was_unclipped = widget->testAttribute(Qt::WA_PaintUnclipped); + widget->setAttribute(Qt::WA_PaintUnclipped, false); + QPainter p(widget); + p.setClipping(false); + if(was_unclipped) + widget->setAttribute(Qt::WA_PaintUnclipped); + widget->d_func()->paintBackground(&p, qrgn, widget->isWindow() ? DrawAsRoot : 0); + if (widget->testAttribute(Qt::WA_TintedBackground)) { + QColor tint = widget->palette().window().color(); + tint.setAlphaF(.6); + const QVector<QRect> &rects = qrgn.rects(); + for (int i = 0; i < rects.size(); ++i) + p.fillRect(rects.at(i), tint); + } + p.end(); + if (!redirectionOffset.isNull()) + widget->d_func()->restoreRedirected(); + } + + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + + //send the paint + redirectionOffset += widget->data->wrect.topLeft(); // Map from system to qt coordinates + if (!redirectionOffset.isNull()) + widget->d_func()->setRedirected(widget, redirectionOffset); + qrgn.translate(redirectionOffset); + QPaintEvent e(qrgn); + widget->d_func()->dirtyOnWidget = QRegion(); +#ifdef QT3_SUPPORT + e.setErased(true); +#endif + QApplication::sendSpontaneousEvent(widget, &e); + if (!redirectionOffset.isNull()) + widget->d_func()->restoreRedirected(); + + //cleanup + if (engine) + engine->setSystemClip(QRegion()); + + widget->setAttribute(Qt::WA_WState_InPaintEvent, false); + if(!widget->testAttribute(Qt::WA_PaintOutsidePaintEvent) && widget->paintingActive()) + qWarning("QWidget: It is dangerous to leave painters active on a widget outside of the PaintEvent"); + } + + widget->d_func()->hd = 0; + widget->d_func()->qd_hd = 0; + CGContextRestoreGState(cg); + } else if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) { + CallNextEventHandler(er, event); + } + } else if(ekind == kEventControlInitialize) { + if(HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) { + UInt32 features = kControlSupportsDragAndDrop | kControlSupportsClickActivation | kControlSupportsFocus; + SetEventParameter(event, kEventParamControlFeatures, typeUInt32, sizeof(features), &features); + } else { + handled_event = false; + } + } else if(ekind == kEventControlSetFocusPart) { + if(widget) { + ControlPartCode part; + GetEventParameter(event, kEventParamControlPart, typeControlPartCode, 0, + sizeof(part), 0, &part); + if(part == kControlFocusNoPart){ + if (widget->hasFocus()) + QApplicationPrivate::setFocusWidget(0, Qt::OtherFocusReason); + } else + widget->setFocus(); + } + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + } else if(ekind == kEventControlGetClickActivation) { + ClickActivationResult clickT = kActivateAndIgnoreClick; + SetEventParameter(event, kEventParamClickActivation, typeClickActivationResult, + sizeof(clickT), &clickT); + } else if(ekind == kEventControlGetPartRegion) { + handled_event = false; + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget) && CallNextEventHandler(er, event) == noErr) { + handled_event = true; + break; + } + if(widget && !widget->isWindow()) { + ControlPartCode part; + GetEventParameter(event, kEventParamControlPart, typeControlPartCode, 0, + sizeof(part), 0, &part); + if(part == kControlClickableMetaPart && widget->testAttribute(Qt::WA_TransparentForMouseEvents)) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + SetEmptyRgn(rgn); + handled_event = true; + } else if(part == kControlStructureMetaPart || part == kControlClickableMetaPart) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(rgn), 0, &rgn); + SetRectRgn(rgn, 0, 0, widget->width(), widget->height()); + if(QWidgetPrivate::qt_widget_rgn(widget, kWindowStructureRgn, rgn, false)) + handled_event = true; + } else if(part == kControlOpaqueMetaPart) { + if(widget->d_func()->isOpaque) { + RgnHandle rgn; + GetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, 0, + sizeof(RgnHandle), 0, &rgn); + SetRectRgn(rgn, 0, 0, widget->width(), widget->height()); + QWidgetPrivate::qt_widget_rgn(widget, kWindowStructureRgn, rgn, false); + SetEventParameter(event, kEventParamControlRegion, typeQDRgnHandle, + sizeof(RgnHandle), &rgn); + handled_event = true; + } + } + } + } else if(ekind == kEventControlOwningWindowChanged) { + if(!HIObjectIsOfClass((HIObjectRef)hiview, kObjectQWidget)) + CallNextEventHandler(er, event); + if(widget && qt_mac_window_for(hiview)) { + WindowRef foo = 0; + GetEventParameter(event, kEventParamControlCurrentOwningWindow, typeWindowRef, 0, + sizeof(foo), 0, &foo); + widget->d_func()->initWindowPtr(); + } + if (widget) + qt_event_request_window_change(widget); + } else if(ekind == kEventControlDragEnter || ekind == kEventControlDragWithin || + ekind == kEventControlDragLeave || ekind == kEventControlDragReceive) { + // dnd are really handled in qdnd_mac.cpp, + // just modularize the code a little... + DragRef drag; + GetEventParameter(event, kEventParamDragRef, typeDragRef, 0, sizeof(drag), 0, &drag); + handled_event = false; + bool drag_allowed = false; + + QWidget *dropWidget = widget; + if (qobject_cast<QFocusFrame *>(widget)){ + // We might shadow widgets underneath the focus + // frame, so stay interrested, and let the dnd through + drag_allowed = true; + handled_event = true; + Point where; + GetDragMouse(drag, &where, 0); + dropWidget = QApplication::widgetAt(QPoint(where.h, where.v)); + + if (dropWidget != QDragManager::self()->currentTarget()) { + // We have to 'fake' enter and leave events for the shaddowed widgets: + if (ekind == kEventControlDragEnter) { + if (QDragManager::self()->currentTarget()) + QDragManager::self()->currentTarget()->d_func()->qt_mac_dnd_event(kEventControlDragLeave, drag); + if (dropWidget) { + dropWidget->d_func()->qt_mac_dnd_event(kEventControlDragEnter, drag); + } + // Set dropWidget to zero, so qt_mac_dnd_event + // doesn't get called a second time below: + dropWidget = 0; + } else if (ekind == kEventControlDragLeave) { + dropWidget = QDragManager::self()->currentTarget(); + if (dropWidget) { + dropWidget->d_func()->qt_mac_dnd_event(kEventControlDragLeave, drag); + } + // Set dropWidget to zero, so qt_mac_dnd_event + // doesn't get called a second time below: + dropWidget = 0; + } + } + } + + // Send the dnd event to the widget: + if (dropWidget && dropWidget->d_func()->qt_mac_dnd_event(ekind, drag)) { + drag_allowed = true; + handled_event = true; + } + + if (ekind == kEventControlDragEnter) { + // If we don't accept the enter event, we will + // receive no more drag events for this widget + const Boolean wouldAccept = drag_allowed ? true : false; + SetEventParameter(event, kEventParamControlWouldAcceptDrop, typeBoolean, + sizeof(wouldAccept), &wouldAccept); + } + } else if (ekind == kEventControlBoundsChanged) { + if (!widget || widget->isWindow() || widget->testAttribute(Qt::WA_Moved) || widget->testAttribute(Qt::WA_Resized)) { + handled_event = false; + } else { + // Sync our view in case some other (non-Qt) view is controlling us. + handled_event = true; + Rect newBounds; + GetEventParameter(event, kEventParamCurrentBounds, + typeQDRectangle, 0, sizeof(Rect), 0, &newBounds); + QRect rect(newBounds.left, newBounds.top, + newBounds.right - newBounds.left, newBounds.bottom - newBounds.top); + + bool moved = widget->testAttribute(Qt::WA_Moved); + bool resized = widget->testAttribute(Qt::WA_Resized); + widget->setGeometry(rect); + widget->setAttribute(Qt::WA_Moved, moved); + widget->setAttribute(Qt::WA_Resized, resized); + qt_event_request_window_change(widget); + } + } else if (ekind == kEventControlGetSizeConstraints) { + if (!widget || !qt_isGenuineQWidget(widget)) { + handled_event = false; + } else { + handled_event = true; + QWidgetItem item(widget); + QSize size = item.minimumSize(); + HISize hisize = { size.width(), size.height() }; + SetEventParameter(event, kEventParamMinimumSize, typeHISize, sizeof(HISize), &hisize); + size = item.maximumSize(); + hisize.width = size.width() + 2; // ### shouldn't have to add 2 (but it works). + hisize.height = size.height(); + SetEventParameter(event, kEventParamMaximumSize, typeHISize, sizeof(HISize), &hisize); + } + } else if (ekind == kEventControlVisibilityChanged) { + handled_event = false; + if (widget) { + qt_event_request_window_change(widget); + if (!HIViewIsVisible(HIViewRef(widget->winId()))) { + if (widget == qt_button_down) + qt_button_down = 0; + } + } + } + break; } + case kEventClassMouse: { + bool send_to_app = false; + if(qt_button_down) + send_to_app = true; + if(send_to_app) { + OSStatus err = SendEventToApplication(event); + if(err != noErr) + handled_event = false; + } else { + CallNextEventHandler(er, event); + } + break; } + default: + handled_event = false; + break; + } + if(!handled_event) //let the event go through + return eventNotHandledErr; + return noErr; //we eat the event +} +#endif + +OSViewRef qt_mac_create_widget(QWidget *widget, QWidgetPrivate *widgetPrivate, OSViewRef parent) +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaView) *view = [[QT_MANGLE_NAMESPACE(QCocoaView) alloc] initWithQWidget:widget widgetPrivate:widgetPrivate]; + +#ifdef ALIEN_DEBUG + qDebug() << "Creating NSView for" << widget; +#endif + + if (view && parent) + [parent addSubview:view]; + return view; +#else + Q_UNUSED(widget); + Q_UNUSED(widgetPrivate); + if(!widget_class) { + OSStatus err = HIObjectRegisterSubclass(kObjectQWidget, kHIViewClassID, 0, make_widget_eventUPP(), + GetEventTypeCount(widget_events), widget_events, + 0, &widget_class); + if (err && err != hiObjectClassExistsErr) + qWarning("QWidget: Internal error (%d)", __LINE__); + } + HIViewRef ret = 0; + if(HIObjectCreate(kObjectQWidget, 0, (HIObjectRef*)&ret) != noErr) + qWarning("QWidget: Internal error (%d)", __LINE__); + if(ret && parent) + HIViewAddSubview(parent, ret); + return ret; +#endif +} + +void qt_mac_unregister_widget() +{ +#ifndef QT_MAC_USE_COCOA + HIObjectUnregisterClass(widget_class); + widget_class = 0; +#endif +} + +void QWidgetPrivate::toggleDrawers(bool visible) +{ + for (int i = 0; i < children.size(); ++i) { + register QObject *object = children.at(i); + if (!object->isWidgetType()) + continue; + QWidget *widget = static_cast<QWidget*>(object); + if(qt_mac_is_macdrawer(widget)) { + bool oldState = widget->testAttribute(Qt::WA_WState_ExplicitShowHide); + if(visible) { + if (!widget->testAttribute(Qt::WA_WState_ExplicitShowHide)) + widget->show(); + } else { + widget->hide(); + if(!oldState) + widget->setAttribute(Qt::WA_WState_ExplicitShowHide, false); + } + } + } +} + +/***************************************************************************** + QWidgetPrivate member functions + *****************************************************************************/ +bool QWidgetPrivate::qt_mac_update_sizer(QWidget *w, int up) +{ + // I'm not sure what "up" is + if(!w || !w->isWindow()) + return false; + + QTLWExtra *topData = w->d_func()->topData(); + QWExtra *extraData = w->d_func()->extraData(); + // topData->resizer is only 4 bits, so subtracting -1 from zero causes bad stuff + // to happen, prevent that here (you really want the thing hidden). + if (up >= 0 || topData->resizer != 0) + topData->resizer += up; + OSWindowRef windowRef = qt_mac_window_for(OSViewRef(w->effectiveWinId())); + { +#ifndef QT_MAC_USE_COCOA + WindowClass wclass; + GetWindowClass(windowRef, &wclass); + if(!(GetAvailableWindowAttributes(wclass) & kWindowResizableAttribute)) + return true; +#endif + } + bool remove_grip = (topData->resizer || (w->windowFlags() & Qt::FramelessWindowHint) + || (extraData->maxw && extraData->maxh && + extraData->maxw == extraData->minw && extraData->maxh == extraData->minh)); +#ifndef QT_MAC_USE_COCOA + WindowAttributes attr; + GetWindowAttributes(windowRef, &attr); + if(remove_grip) { + if(attr & kWindowResizableAttribute) { + ChangeWindowAttributes(qt_mac_window_for(w), kWindowNoAttributes, + kWindowResizableAttribute); + ReshapeCustomWindow(qt_mac_window_for(w)); + } + } else if(!(attr & kWindowResizableAttribute)) { + ChangeWindowAttributes(windowRef, kWindowResizableAttribute, + kWindowNoAttributes); + ReshapeCustomWindow(windowRef); + } +#else + [windowRef setShowsResizeIndicator:!remove_grip]; +#endif + return true; +} + +void QWidgetPrivate::qt_clean_root_win() +{ +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + [qt_root_win release]; +#else + if(!qt_root_win) + return; + CFRelease(qt_root_win); +#endif + qt_root_win = 0; +} + +bool QWidgetPrivate::qt_create_root_win() +{ + if(qt_root_win) + return false; + const QSize desktopSize = qt_mac_desktopSize(); + QRect desktopRect(QPoint(0, 0), desktopSize); +#ifdef QT_MAC_USE_COCOA + qt_root_win = qt_mac_create_window(0, kOverlayWindowClass, NSBorderlessWindowMask, desktopRect); +#else + WindowAttributes wattr = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute); + qt_root_win = qt_mac_create_window(0, kOverlayWindowClass, wattr, desktopRect); +#endif + if(!qt_root_win) + return false; + qAddPostRoutine(qt_clean_root_win); + return true; +} + +bool QWidgetPrivate::qt_widget_rgn(QWidget *widget, short wcode, RgnHandle rgn, bool force = false) +{ + bool ret = false; +#ifndef QT_MAC_USE_COCOA + switch(wcode) { + case kWindowStructureRgn: { + if(widget) { + if(widget->d_func()->extra && !widget->d_func()->extra->mask.isEmpty()) { + QRegion rin = qt_mac_convert_mac_region(rgn); + if(!rin.isEmpty()) { + QPoint rin_tl = rin.boundingRect().topLeft(); //in offset + rin.translate(-rin_tl.x(), -rin_tl.y()); //bring into same space as below + QRegion mask = widget->d_func()->extra->mask; + Qt::WindowFlags flags = widget->windowFlags(); + if(widget->isWindow() + && !(flags & Qt::FramelessWindowHint + || (flags & Qt::CustomizeWindowHint && !(flags & Qt::WindowTitleHint)))) { + QRegion title; + { + QMacSmartQuickDrawRegion rgn(qt_mac_get_rgn()); + GetWindowRegion(qt_mac_window_for(widget), kWindowTitleBarRgn, rgn); + title = qt_mac_convert_mac_region(rgn); + } + QRect br = title.boundingRect(); + mask.translate(0, br.height()); //put the mask 'under' the title bar.. + title.translate(-br.x(), -br.y()); + mask += title; + } + + QRegion cr = rin & mask; + cr.translate(rin_tl.x(), rin_tl.y()); //translate back to incoming space + CopyRgn(QMacSmartQuickDrawRegion(cr.toQDRgn()), rgn); + } + ret = true; + } else if(force) { + QRegion cr(widget->geometry()); + CopyRgn(QMacSmartQuickDrawRegion(cr.toQDRgn()), rgn); + ret = true; + } + } + break; } + default: break; + } + //qDebug() << widget << ret << wcode << qt_mac_convert_mac_region(rgn); +#else + Q_UNUSED(widget); + Q_UNUSED(wcode); + Q_UNUSED(rgn); + Q_UNUSED(force); +#endif + return ret; +} + +/***************************************************************************** + QWidget member functions + *****************************************************************************/ +void QWidgetPrivate::determineWindowClass() +{ + Q_Q(QWidget); +#if !defined(QT_NO_MAINWINDOW) && !defined(QT_NO_TOOLBAR) + // Make sure that QMainWindow has the MacWindowToolBarButtonHint when the + // unifiedTitleAndToolBarOnMac property is ON. This is to avoid reentry of + // setParent() triggered by the QToolBar::event(QEvent::ParentChange). + QMainWindow *mainWindow = qobject_cast<QMainWindow *>(q); + if (mainWindow && mainWindow->unifiedTitleAndToolBarOnMac()) { + data.window_flags |= Qt::MacWindowToolBarButtonHint; + } +#endif +#ifndef QT_MAC_USE_COCOA +// ### COCOA:Interleave these better! + + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + const bool popup = (type == Qt::Popup); + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + WindowClass wclass = kSheetWindowClass; + if(qt_mac_is_macdrawer(q)) + wclass = kDrawerWindowClass; + else if (q->testAttribute(Qt::WA_ShowModal) && flags & Qt::CustomizeWindowHint) + wclass = kDocumentWindowClass; + else if(popup || (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 && type == Qt::SplashScreen)) + wclass = kModalWindowClass; + else if(q->testAttribute(Qt::WA_ShowModal)) + wclass = kMovableModalWindowClass; + else if(type == Qt::ToolTip) + wclass = kHelpWindowClass; + else if(type == Qt::Tool || (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5 + && type == Qt::SplashScreen)) + wclass = kFloatingWindowClass; + else + wclass = kDocumentWindowClass; + + WindowGroupRef grp = 0; + WindowAttributes wattr = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute); + if (q->testAttribute(Qt::WA_MacFrameworkScaled)) + wattr |= kWindowFrameworkScaledAttribute; + if(qt_mac_is_macsheet(q)) { + //grp = GetWindowGroupOfClass(kMovableModalWindowClass); + wclass = kSheetWindowClass; + } else { + grp = GetWindowGroupOfClass(wclass); + // Shift things around a bit to get the correct window class based on the presence + // (or lack) of the border. + bool customize = flags & Qt::CustomizeWindowHint; + bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint))); + if (framelessWindow) { + if(wclass == kDocumentWindowClass) { + wattr |= kWindowNoTitleBarAttribute; + } else if(wclass == kFloatingWindowClass) { + wattr |= kWindowNoTitleBarAttribute; + } else if (wclass == kMovableModalWindowClass) { + wclass = kModalWindowClass; + } + } else { + if(wclass != kModalWindowClass) + wattr |= kWindowResizableAttribute; + } + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + if(wclass != kModalWindowClass && wclass != kMovableModalWindowClass + && wclass != kSheetWindowClass && wclass != kPlainWindowClass + && !framelessWindow && wclass != kDrawerWindowClass + && wclass != kHelpWindowClass) { + if (flags & Qt::WindowMaximizeButtonHint) + wattr |= kWindowFullZoomAttribute; + if (flags & Qt::WindowMinimizeButtonHint) + wattr |= kWindowCollapseBoxAttribute; + if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint) + wattr |= kWindowCloseBoxAttribute; + if (flags & Qt::MacWindowToolBarButtonHint) + wattr |= kWindowToolbarButtonAttribute; + } else { + // Clear these hints so that we aren't call them on invalid windows + flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint + | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint); + } + } + if((popup || type == Qt::Tool) && !q->isModal()) + wattr |= kWindowHideOnSuspendAttribute; + wattr |= kWindowLiveResizeAttribute; + +#ifdef DEBUG_WINDOW_CREATE +#define ADD_DEBUG_WINDOW_NAME(x) { x, #x } + struct { + UInt32 tag; + const char *name; + } known_attribs[] = { + ADD_DEBUG_WINDOW_NAME(kWindowCompositingAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowMetalAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCollapseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHorizontalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowVerticalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowResizableAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoActivatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoUpdatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowOpaqueForEventsAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowLiveResizeAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCloseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + { 0, 0 } + }, known_classes[] = { + ADD_DEBUG_WINDOW_NAME(kHelpWindowClass), + ADD_DEBUG_WINDOW_NAME(kPlainWindowClass), + ADD_DEBUG_WINDOW_NAME(kDrawerWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kSheetWindowClass), + ADD_DEBUG_WINDOW_NAME(kFloatingWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kDocumentWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kMovableModalWindowClass), + ADD_DEBUG_WINDOW_NAME(kModalWindowClass), + { 0, 0 } + }; + qDebug("Qt: internal: ************* Creating new window %p (%s::%s)", q, q->metaObject()->className(), + q->objectName().toLocal8Bit().constData()); + bool found_class = false; + for(int i = 0; known_classes[i].name; i++) { + if(wclass == known_classes[i].tag) { + found_class = true; + qDebug("Qt: internal: ** Class: %s", known_classes[i].name); + break; + } + } + if(!found_class) + qDebug("Qt: internal: !! Class: Unknown! (%d)", (int)wclass); + if(wattr) { + WindowAttributes tmp_wattr = wattr; + qDebug("Qt: internal: ** Attributes:"); + for(int i = 0; tmp_wattr && known_attribs[i].name; i++) { + if((tmp_wattr & known_attribs[i].tag) == known_attribs[i].tag) { + tmp_wattr ^= known_attribs[i].tag; + qDebug("Qt: internal: * %s %s", known_attribs[i].name, + (GetAvailableWindowAttributes(wclass) & known_attribs[i].tag) ? "" : "(*)"); + } + } + if(tmp_wattr) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)tmp_wattr); + } +#endif + + /* Just to be extra careful we will change to the kUtilityWindowClass if the + requested attributes cannot be used */ + if((GetAvailableWindowAttributes(wclass) & wattr) != wattr) { + WindowClass tmp_class = wclass; + if(wclass == kToolbarWindowClass || wclass == kUtilityWindowClass) + wclass = kFloatingWindowClass; + if(tmp_class != wclass) { + if(!grp) + grp = GetWindowGroupOfClass(wclass); + wclass = tmp_class; + } + } + topData()->wclass = wclass; + topData()->wattr = wattr; +#else + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + const bool popup = (type == Qt::Popup); + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + WindowClass wclass = kSheetWindowClass; + if(qt_mac_is_macdrawer(q)) + wclass = kDrawerWindowClass; + else if (q->testAttribute(Qt::WA_ShowModal) && flags & Qt::CustomizeWindowHint) + wclass = kDocumentWindowClass; + else if(popup || (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_5 && type == Qt::SplashScreen)) + wclass = kModalWindowClass; + else if(type == Qt::Dialog) + wclass = kMovableModalWindowClass; + else if(type == Qt::ToolTip) + wclass = kHelpWindowClass; + else if(type == Qt::Tool || (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5 + && type == Qt::SplashScreen)) + wclass = kFloatingWindowClass; + else if(q->testAttribute(Qt::WA_ShowModal)) + wclass = kMovableModalWindowClass; + else + wclass = kDocumentWindowClass; + + WindowAttributes wattr = NSBorderlessWindowMask; + if(qt_mac_is_macsheet(q)) { + //grp = GetWindowGroupOfClass(kMovableModalWindowClass); + wclass = kSheetWindowClass; + wattr = NSTitledWindowMask | NSResizableWindowMask; + } else { +#ifndef QT_MAC_USE_COCOA + grp = GetWindowGroupOfClass(wclass); +#endif + // Shift things around a bit to get the correct window class based on the presence + // (or lack) of the border. + bool customize = flags & Qt::CustomizeWindowHint; + bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint))); + if (framelessWindow) { + if (wclass == kDocumentWindowClass) { + wclass = kSimpleWindowClass; + } else if (wclass == kFloatingWindowClass) { + wclass = kToolbarWindowClass; + } else if (wclass == kMovableModalWindowClass) { + wclass = kModalWindowClass; + } + } else { + wattr |= NSTitledWindowMask; + if (wclass != kModalWindowClass) + wattr |= NSResizableWindowMask; + } + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + if (wclass != kModalWindowClass + && wclass != kSheetWindowClass && wclass != kPlainWindowClass + && !framelessWindow && wclass != kDrawerWindowClass + && wclass != kHelpWindowClass) { + if (flags & Qt::WindowMinimizeButtonHint) + wattr |= NSMiniaturizableWindowMask; + if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint) + wattr |= NSClosableWindowMask; + } else { + // Clear these hints so that we aren't call them on invalid windows + flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint + | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint); + } + } + if (q->testAttribute(Qt::WA_MacBrushedMetal)) + wattr |= NSTexturedBackgroundWindowMask; + +#ifdef DEBUG_WINDOW_CREATE +#define ADD_DEBUG_WINDOW_NAME(x) { x, #x } + struct { + UInt32 tag; + const char *name; + } known_attribs[] = { + ADD_DEBUG_WINDOW_NAME(kWindowCompositingAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowMetalAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowStandardHandlerAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCollapseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHorizontalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowVerticalZoomAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowResizableAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoActivatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowNoUpdatesAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowOpaqueForEventsAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowLiveResizeAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowCloseBoxAttribute), + ADD_DEBUG_WINDOW_NAME(kWindowHideOnSuspendAttribute), + { 0, 0 } + }, known_classes[] = { + ADD_DEBUG_WINDOW_NAME(kHelpWindowClass), + ADD_DEBUG_WINDOW_NAME(kPlainWindowClass), + ADD_DEBUG_WINDOW_NAME(kDrawerWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kSheetWindowClass), + ADD_DEBUG_WINDOW_NAME(kFloatingWindowClass), + ADD_DEBUG_WINDOW_NAME(kUtilityWindowClass), + ADD_DEBUG_WINDOW_NAME(kDocumentWindowClass), + ADD_DEBUG_WINDOW_NAME(kToolbarWindowClass), + ADD_DEBUG_WINDOW_NAME(kMovableModalWindowClass), + ADD_DEBUG_WINDOW_NAME(kModalWindowClass), + { 0, 0 } + }; + qDebug("Qt: internal: ************* Creating new window %p (%s::%s)", q, q->metaObject()->className(), + q->objectName().toLocal8Bit().constData()); + bool found_class = false; + for(int i = 0; known_classes[i].name; i++) { + if(wclass == known_classes[i].tag) { + found_class = true; + qDebug("Qt: internal: ** Class: %s", known_classes[i].name); + break; + } + } + if(!found_class) + qDebug("Qt: internal: !! Class: Unknown! (%d)", (int)wclass); + if(wattr) { + WindowAttributes tmp_wattr = wattr; + qDebug("Qt: internal: ** Attributes:"); + for(int i = 0; tmp_wattr && known_attribs[i].name; i++) { + if((tmp_wattr & known_attribs[i].tag) == known_attribs[i].tag) { + tmp_wattr ^= known_attribs[i].tag; + } + } + if(tmp_wattr) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)tmp_wattr); + } +#endif + +#ifndef QT_MAC_USE_COCOA + /* Just to be extra careful we will change to the kUtilityWindowClass if the + requested attributes cannot be used */ + if((GetAvailableWindowAttributes(wclass) & wattr) != wattr) { + WindowClass tmp_class = wclass; + if(wclass == kToolbarWindowClass || wclass == kUtilityWindowClass) + wclass = kFloatingWindowClass; + if(tmp_class != wclass) { + if(!grp) + grp = GetWindowGroupOfClass(wclass); + wclass = tmp_class; + } + } +#endif +#endif + topData()->wclass = wclass; + topData()->wattr = wattr; +} + +#ifndef QT_MAC_USE_COCOA // This is handled in Cocoa via our category. +void QWidgetPrivate::initWindowPtr() +{ + Q_Q(QWidget); + OSWindowRef windowRef = qt_mac_window_for(qt_mac_nativeview_for(q)); //do not create! + if(!windowRef) + return; + QWidget *window = q->window(), *oldWindow = 0; + if(GetWindowProperty(windowRef, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(oldWindow), 0, &oldWindow) == noErr) { + Q_ASSERT(window == oldWindow); + return; + } + + if(SetWindowProperty(windowRef, kWidgetCreatorQt, kWidgetPropertyQWidget, sizeof(window), &window) != noErr) + qWarning("Qt:Internal error (%s:%d)", __FILE__, __LINE__); //no real way to recover + if(!q->windowType() != Qt::Desktop) { //setup an event callback handler on the window + InstallWindowEventHandler(windowRef, make_win_eventUPP(), GetEventTypeCount(window_events), + window_events, static_cast<void *>(qApp), &window_event); + } +} + +void QWidgetPrivate::finishCreateWindow_sys_Carbon(OSWindowRef windowRef) +{ + Q_Q(QWidget); + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + const bool desktop = (type == Qt::Desktop); + const bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + QTLWExtra *topExtra = topData(); + quint32 wattr = topExtra->wattr; + if (!desktop) + SetAutomaticControlDragTrackingEnabledForWindow(windowRef, true); + HIWindowChangeFeatures(windowRef, kWindowCanCollapse, 0); + if (wattr & kWindowHideOnSuspendAttribute) + HIWindowChangeAvailability(windowRef, kHIWindowExposeHidden, 0); + else + HIWindowChangeAvailability(windowRef, 0, kHIWindowExposeHidden); + if ((flags & Qt::WindowStaysOnTopHint)) + ChangeWindowAttributes(windowRef, kWindowNoAttributes, kWindowHideOnSuspendAttribute); + if (qt_mac_is_macdrawer(q) && parentWidget) + SetDrawerParent(windowRef, qt_mac_window_for (parentWidget)); + if (topExtra->group) { + qt_mac_release_window_group(topExtra->group); + topExtra->group = 0; + } + if (type == Qt::ToolTip) + qt_mac_set_window_group_to_tooltip(windowRef); + else if (type == Qt::Popup && (flags & Qt::WindowStaysOnTopHint)) + qt_mac_set_window_group_to_popup(windowRef); + else if (flags & Qt::WindowStaysOnTopHint) + qt_mac_set_window_group_to_stays_on_top(windowRef, type); + else if (dialog) + SetWindowGroup(windowRef, GetWindowGroupOfClass(kMovableModalWindowClass)); + +#ifdef DEBUG_WINDOW_CREATE + if (WindowGroupRef grpf = GetWindowGroup(windowRef)) { + QCFString cfname; + CopyWindowGroupName(grpf, &cfname); + SInt32 lvl; + GetWindowGroupLevel(grpf, &lvl); + const char *from = "Default"; + if (topExtra && grpf == topData()->group) + from = "Created"; + else if (grpf == grp) + from = "Copied"; + qDebug("Qt: internal: With window group '%s' [%p] @ %d: %s", + static_cast<QString>(cfname).toLatin1().constData(), grpf, (int)lvl, from); + } else { + qDebug("Qt: internal: No window group!!!"); + } + HIWindowAvailability hi_avail = 0; + if (HIWindowGetAvailability(windowRef, &hi_avail) == noErr) { + struct { + UInt32 tag; + const char *name; + } known_avail[] = { + ADD_DEBUG_WINDOW_NAME(kHIWindowExposeHidden), + { 0, 0 } + }; + qDebug("Qt: internal: ** HIWindowAvailibility:"); + for (int i = 0; hi_avail && known_avail[i].name; i++) { + if ((hi_avail & known_avail[i].tag) == known_avail[i].tag) { + hi_avail ^= known_avail[i].tag; + qDebug("Qt: internal: * %s", known_avail[i].name); + } + } + if (hi_avail) + qDebug("Qt: internal: !! Attributes: Unknown (%d)", (int)hi_avail); + } +#undef ADD_DEBUG_WINDOW_NAME +#endif + if (extra && !extra->mask.isEmpty()) + ReshapeCustomWindow(windowRef); + SetWindowModality(windowRef, kWindowModalityNone, 0); + if (qt_mac_is_macdrawer(q)) + SetDrawerOffsets(windowRef, 0.0, 25.0); + data.fstrut_dirty = true; // when we create a toplevel widget, the frame strut should be dirty + HIViewRef hiview = (HIViewRef)data.winid; + HIViewRef window_hiview = qt_mac_get_contentview_for(windowRef); + if(!hiview) { + hiview = qt_mac_create_widget(q, this, window_hiview); + setWinId((WId)hiview); + } else { + HIViewAddSubview(window_hiview, hiview); + } + if (hiview) { + Rect win_rect; + GetWindowBounds(qt_mac_window_for (window_hiview), kWindowContentRgn, &win_rect); + HIRect bounds = CGRectMake(0, 0, win_rect.right-win_rect.left, win_rect.bottom-win_rect.top); + HIViewSetFrame(hiview, &bounds); + HIViewSetVisible(hiview, true); + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + transferChildren(); + } + initWindowPtr(); + + if (topExtra->posFromMove) { + updateFrameStrut(); + const QRect &fStrut = frameStrut(); + Rect r; + SetRect(&r, data.crect.left(), data.crect.top(), data.crect.right() + 1, data.crect.bottom() + 1); + SetRect(&r, r.left + fStrut.left(), r.top + fStrut.top(), + (r.left + fStrut.left() + data.crect.width()) - fStrut.right(), + (r.top + fStrut.top() + data.crect.height()) - fStrut.bottom()); + SetWindowBounds(windowRef, kWindowContentRgn, &r); + topExtra->posFromMove = false; + } + + if (q->testAttribute(Qt::WA_WState_WindowOpacitySet)){ + q->setWindowOpacity(topExtra->opacity / 255.0f); + } else if (qt_mac_is_macsheet(q)){ + SetThemeWindowBackground(qt_mac_window_for(q), kThemeBrushSheetBackgroundTransparent, true); + CGFloat alpha = 0; + GetWindowAlpha(qt_mac_window_for(q), &alpha); + if (alpha == 1){ + // For some reason the 'SetThemeWindowBackground' does not seem + // to work. So we do this little hack until it hopefully starts to + // work in newer versions of mac OS. + q->setWindowOpacity(0.95f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + } else{ + // If the window has been recreated after beeing e.g. a sheet, + // make sure that we don't report a faulty opacity: + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + // Since we only now have a window, sync our state. + macUpdateHideOnSuspend(); + macUpdateOpaqueSizeGrip(); + macUpdateMetalAttribute(); + macUpdateIgnoreMouseEvents(); + setWindowTitle_helper(extra->topextra->caption); + setWindowIconText_helper(extra->topextra->iconText); + setWindowFilePath_helper(extra->topextra->filePath); + setWindowModified_sys(q->isWindowModified()); + updateFrameStrut(); + qt_mac_update_sizer(q); + applyMaxAndMinSizeOnWindow(); +} +#else // QT_MAC_USE_COCOA + +void QWidgetPrivate::setWindowLevel() +{ + Q_Q(QWidget); + const QWidget * const windowParent = q->window()->parentWidget(); + const QWidget * const primaryWindow = windowParent ? windowParent->window() : 0; + NSInteger winLevel = -1; + + if (q->windowType() == Qt::Popup) { + winLevel = NSPopUpMenuWindowLevel; + // Popup should be in at least the same level as its parent. + if (primaryWindow) { + OSWindowRef parentRef = qt_mac_window_for(primaryWindow); + winLevel = qMax([parentRef level], winLevel); + } + } else if (q->windowType() == Qt::Tool) { + winLevel = NSFloatingWindowLevel; + } else if (q->windowType() == Qt::Dialog) { + // Correct modality level (NSModalPanelWindowLevel) will be + // set by cocoa when creating a modal session later. + winLevel = NSNormalWindowLevel; + } + + // StayOnTop window should appear above Tool windows. + if (data.window_flags & Qt::WindowStaysOnTopHint) + winLevel = NSPopUpMenuWindowLevel; + // Tooltips should appear above StayOnTop windows. + if (q->windowType() == Qt::ToolTip) + winLevel = NSScreenSaverWindowLevel; + // All other types are Normal level. + if (winLevel == -1) + winLevel = NSNormalWindowLevel; + [qt_mac_window_for(q) setLevel:winLevel]; +} + +void QWidgetPrivate::finishCreateWindow_sys_Cocoa(void * /*NSWindow * */ voidWindowRef) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + NSWindow *windowRef = static_cast<NSWindow *>(voidWindowRef); + const Qt::WindowType type = q->windowType(); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + const bool popup = (type == Qt::Popup); + const bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + QTLWExtra *topExtra = topData(); + + if ((popup || type == Qt::Tool || type == Qt::ToolTip) && !q->isModal()) { + [windowRef setHidesOnDeactivate:YES]; + } else { + [windowRef setHidesOnDeactivate:NO]; + } + if (q->testAttribute(Qt::WA_MacNoShadow)) + [windowRef setHasShadow:NO]; + else + [windowRef setHasShadow:YES]; + Q_UNUSED(parentWidget); + Q_UNUSED(dialog); + + data.fstrut_dirty = true; // when we create a toplevel widget, the frame strut should be dirty + + OSViewRef nsview = (OSViewRef)data.winid; + if (!nsview) { + nsview = qt_mac_create_widget(q, this, 0); + setWinId(WId(nsview)); + } + [windowRef setContentView:nsview]; + [nsview setHidden:NO]; + transferChildren(); + + // Tell Cocoa explicit that we wan't the view to receive key events + // (regardless of focus policy) because this is how it works on other + // platforms (and in the carbon port): + [windowRef makeFirstResponder:nsview]; + + if (topExtra->posFromMove) { + updateFrameStrut(); + + const QRect &fStrut = frameStrut(); + const QRect &crect = data.crect; + const QRect frameRect(QPoint(crect.left(), crect.top()), + QSize(fStrut.left() + fStrut.right() + crect.width(), + fStrut.top() + fStrut.bottom() + crect.height())); + NSRect cocoaFrameRect = NSMakeRect(frameRect.x(), flipYCoordinate(frameRect.bottom() + 1), + frameRect.width(), frameRect.height()); + [windowRef setFrame:cocoaFrameRect display:NO]; + topExtra->posFromMove = false; + } + + if (q->testAttribute(Qt::WA_WState_WindowOpacitySet)){ + q->setWindowOpacity(topExtra->opacity / 255.0f); + } else if (qt_mac_is_macsheet(q)){ + CGFloat alpha = [qt_mac_window_for(q) alphaValue]; + if (alpha >= 1.0) { + q->setWindowOpacity(0.95f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + } else{ + // If the window has been recreated after beeing e.g. a sheet, + // make sure that we don't report a faulty opacity: + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + // Its more performant to handle the mouse cursor + // ourselves, expecially when using alien widgets: + [windowRef disableCursorRects]; + + setWindowLevel(); + macUpdateHideOnSuspend(); + macUpdateOpaqueSizeGrip(); + macUpdateIgnoreMouseEvents(); + setWindowTitle_helper(extra->topextra->caption); + setWindowIconText_helper(extra->topextra->iconText); + setWindowModified_sys(q->isWindowModified()); + updateFrameStrut(); + syncCocoaMask(); + macUpdateIsOpaque(); + qt_mac_update_sizer(q); + applyMaxAndMinSizeOnWindow(); +} + +#endif // QT_MAC_USE_COCOA + +/* + Recreates widget window. Useful if immutable + properties for it has changed. + */ +void QWidgetPrivate::recreateMacWindow() +{ + Q_Q(QWidget); + OSViewRef myView = qt_mac_nativeview_for(q); + OSWindowRef oldWindow = qt_mac_window_for(myView); +#ifndef QT_MAC_USE_COCOA + HIViewRemoveFromSuperview(myView); + determineWindowClass(); + createWindow_sys(); + + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + + if (IsWindowVisible(oldWindow)) + show_sys(); +#else + QMacCocoaAutoReleasePool pool; + [myView removeFromSuperview]; + determineWindowClass(); + createWindow_sys(); + if (NSToolbar *toolbar = [oldWindow toolbar]) { + OSWindowRef newWindow = qt_mac_window_for(myView); + [newWindow setToolbar:toolbar]; + [toolbar setVisible:[toolbar isVisible]]; + } + if ([oldWindow isVisible]){ + if ([oldWindow isSheet]) + [NSApp endSheet:oldWindow]; + [oldWindow orderOut:oldWindow]; + show_sys(); + } +#endif // QT_MAC_USE_COCOA + + // Release the window after creating the new window, because releasing it early + // may cause the app to quit ("close on last window closed attribute") + qt_mac_destructWindow(oldWindow); +} + +void QWidgetPrivate::createWindow_sys() +{ + Q_Q(QWidget); + Qt::WindowFlags &flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + QTLWExtra *topExtra = topData(); + if (topExtra->embedded) + return; // Simply return because this view "is" the top window. + quint32 wattr = topExtra->wattr; + + if(parentWidget && (parentWidget->window()->windowFlags() & Qt::WindowStaysOnTopHint)) // If our parent has Qt::WStyle_StaysOnTop, so must we + flags |= Qt::WindowStaysOnTopHint; + + data.fstrut_dirty = true; + + OSWindowRef windowRef = qt_mac_create_window(q, topExtra->wclass, wattr, data.crect); + if (windowRef == 0) + qWarning("QWidget: Internal error: %s:%d: If you reach this error please contact Qt Support and include the\n" + " WidgetFlags used in creating the widget.", __FILE__, __LINE__); +#ifndef QT_MAC_USE_COCOA + finishCreateWindow_sys_Carbon(windowRef); +#else + finishCreateWindow_sys_Cocoa(windowRef); +#endif +} + +void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + + OSViewRef destroyid = 0; +#ifndef QT_MAC_USE_COCOA + window_event = 0; +#endif + + Qt::WindowType type = q->windowType(); + Qt::WindowFlags flags = data.window_flags; + QWidget *parentWidget = q->parentWidget(); + + bool topLevel = (flags & Qt::Window); + bool popup = (type == Qt::Popup); + bool dialog = (type == Qt::Dialog + || type == Qt::Sheet + || type == Qt::Drawer + || (flags & Qt::MSWindowsFixedSizeDialogHint)); + bool desktop = (type == Qt::Desktop); + + // Determine this early for top-levels so, we can use it later. + if (topLevel) + determineWindowClass(); + + if (desktop) { + QSize desktopSize = qt_mac_desktopSize(); + q->setAttribute(Qt::WA_WState_Visible); + data.crect.setRect(0, 0, desktopSize.width(), desktopSize.height()); + dialog = popup = false; // force these flags off + } else { + if (topLevel && (type != Qt::Drawer)) { + if (QDesktopWidget *dsk = QApplication::desktop()) { // calc pos/size from screen + const bool wasResized = q->testAttribute(Qt::WA_Resized); + const bool wasMoved = q->testAttribute(Qt::WA_Moved); + int deskn = dsk->primaryScreen(); + if (parentWidget && parentWidget->windowType() != Qt::Desktop) + deskn = dsk->screenNumber(parentWidget); + QRect screenGeo = dsk->screenGeometry(deskn); + if (!wasResized) { +#ifndef QT_MAC_USE_COCOA + data.crect.setSize(QSize(screenGeo.width()/2, 4*screenGeo.height()/10)); +#else + NSRect newRect = [NSWindow frameRectForContentRect:NSMakeRect(0, 0, + screenGeo.width() / 2., + 4 * screenGeo.height() / 10.) + styleMask:topData()->wattr]; + data.crect.setSize(QSize(newRect.size.width, newRect.size.height)); +#endif + // Constrain to minimums and maximums we've set + if (extra->minw > 0) + data.crect.setWidth(qMax(extra->minw, data.crect.width())); + if (extra->minh > 0) + data.crect.setHeight(qMax(extra->minh, data.crect.height())); + if (extra->maxw > 0) + data.crect.setWidth(qMin(extra->maxw, data.crect.width())); + if (extra->maxh > 0) + data.crect.setHeight(qMin(extra->maxh, data.crect.height())); + } + if (!wasMoved && !q->testAttribute(Qt::WA_DontShowOnScreen)) + data.crect.moveTopLeft(QPoint(screenGeo.width()/4, + 3 * screenGeo.height() / 10)); + } + } + } + + + if(!window) // always initialize + initializeWindow=true; + + hd = 0; + if(window) { // override the old window (with a new NSView) + OSViewRef nativeView = OSViewRef(window); + OSViewRef parent = 0; +#ifndef QT_MAC_USE_COCOA + CFRetain(nativeView); +#else + [nativeView retain]; +#endif + if (destroyOldWindow) + destroyid = qt_mac_nativeview_for(q); + bool transfer = false; + setWinId((WId)nativeView); +#ifndef QT_MAC_USE_COCOA +#ifndef HIViewInstallEventHandler + // Macro taken from the CarbonEvents Header on Tiger +#define HIViewInstallEventHandler( target, handler, numTypes, list, userData, outHandlerRef ) \ + InstallEventHandler( HIObjectGetEventTarget( (HIObjectRef) (target) ), (handler), (numTypes), (list), (userData), (outHandlerRef) ) +#endif + HIViewInstallEventHandler(nativeView, make_widget_eventUPP(), GetEventTypeCount(widget_events), widget_events, 0, 0); +#endif + if(topLevel) { + for(int i = 0; i < 2; ++i) { + if(i == 1) { + if(!initializeWindow) + break; + createWindow_sys(); + } + if(OSWindowRef windowref = qt_mac_window_for(nativeView)) { +#ifndef QT_MAC_USE_COCOA + CFRetain(windowref); +#else + [windowref retain]; +#endif + if (initializeWindow) { + parent = qt_mac_get_contentview_for(windowref); + } else { +#ifndef QT_MAC_USE_COCOA + parent = HIViewGetSuperview(nativeView); +#else + parent = [nativeView superview]; +#endif + } + break; + } + } + if(!parent) + transfer = true; + } else if (parentWidget) { + // I need to be added to my parent, therefore my parent needs an NSView + // Alien note: a 'window' was supplied as argument, meaning this widget + // is not alien. So therefore the parent cannot be alien either. + parentWidget->createWinId(); + parent = qt_mac_nativeview_for(parentWidget); + } + if(parent != nativeView && parent) { +#ifndef QT_MAC_USE_COCOA + HIViewAddSubview(parent, nativeView); +#else + [parent addSubview:nativeView]; +#endif + } + if(transfer) + transferChildren(); + data.fstrut_dirty = true; // we'll re calculate this later + q->setAttribute(Qt::WA_WState_Visible, +#ifndef QT_MAC_USE_COCOA + HIViewIsVisible(nativeView) +#else + ![nativeView isHidden] +#endif + ); + if(initializeWindow) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + HIViewSetFrame(nativeView, &bounds); + q->setAttribute(Qt::WA_WState_Visible, HIViewIsVisible(nativeView)); +#else + NSRect bounds = NSMakeRect(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + [nativeView setFrame:bounds]; + q->setAttribute(Qt::WA_WState_Visible, [nativeView isHidden]); +#endif + } +#ifndef QT_MAC_USE_COCOA + initWindowPtr(); +#endif + } else if (desktop) { // desktop widget + if (!qt_root_win) + QWidgetPrivate::qt_create_root_win(); + Q_ASSERT(qt_root_win); + WId rootWinID = 0; +#ifndef QT_MAC_USE_COCOA + CFRetain(qt_root_win); + if(HIViewRef rootContentView = HIViewGetRoot(qt_root_win)) { + rootWinID = (WId)rootContentView; + CFRetain(rootContentView); + } +#else + [qt_root_win retain]; + if (OSViewRef rootContentView = [qt_root_win contentView]) { + rootWinID = (WId)rootContentView; + [rootContentView retain]; + } +#endif + setWinId(rootWinID); + } else if (topLevel) { + determineWindowClass(); + if(OSViewRef osview = qt_mac_create_widget(q, this, 0)) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), + data.crect.width(), data.crect.height()); + HIViewSetFrame(osview, &bounds); +#else + NSRect bounds = NSMakeRect(data.crect.x(), flipYCoordinate(data.crect.y()), + data.crect.width(), data.crect.height()); + [osview setFrame:bounds]; +#endif + setWinId((WId)osview); + } + } else { + data.fstrut_dirty = false; // non-toplevel widgets don't have a frame, so no need to update the strut + +#ifdef QT_MAC_USE_COCOA + if (q->testAttribute(Qt::WA_NativeWindow) == false || q->internalWinId() != 0) { + // INVARIANT: q is Alien, and we should not create an NSView to back it up. + } else +#endif + if (OSViewRef osview = qt_mac_create_widget(q, this, qt_mac_nativeview_for(parentWidget))) { +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + HIViewSetFrame(osview, &bounds); + setWinId((WId)osview); +#else + NSRect bounds = NSMakeRect(data.crect.x(), data.crect.y(), data.crect.width(), data.crect.height()); + [osview setFrame:bounds]; + setWinId((WId)osview); + if (q->isVisible()) { + // If q were Alien before, but now became native (e.g. if a call to + // winId was done from somewhere), we need to show the view immidiatly: + QMacCocoaAutoReleasePool pool; + [osview setHidden:NO]; + } +#endif + } + } + + updateIsOpaque(); + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + registerDropSite(true); + if (q->hasFocus()) + setFocus_sys(); + if (!topLevel && initializeWindow) + setWSGeometry(); + if (destroyid) + qt_mac_destructView(destroyid); +} + +/*! + Returns the QuickDraw handle of the widget. Use of this function is not + portable. This function will return 0 if QuickDraw is not supported, or + if the handle could not be created. + + \warning This function is only available on Mac OS X. +*/ + +Qt::HANDLE +QWidget::macQDHandle() const +{ +#ifndef QT_MAC_USE_COCOA + return d_func()->qd_hd; +#else + return 0; +#endif +} + +/*! + Returns the CoreGraphics handle of the widget. Use of this function is + not portable. This function will return 0 if no painter context can be + established, or if the handle could not be created. + + \warning This function is only available on Mac OS X. +*/ +Qt::HANDLE +QWidget::macCGHandle() const +{ + return handle(); +} + +void qt_mac_repaintParentUnderAlienWidget(QWidget *alienWidget) +{ + QWidget *nativeParent = alienWidget->nativeParentWidget(); + if (!nativeParent) + return; + + QPoint globalPos = alienWidget->mapToGlobal(QPoint(0, 0)); + QRect dirtyRect = QRect(nativeParent->mapFromGlobal(globalPos), alienWidget->size()); + nativeParent->repaint(dirtyRect); +} + +void QWidget::destroy(bool destroyWindow, bool destroySubWindows) +{ + Q_D(QWidget); + QMacCocoaAutoReleasePool pool; + d->aboutToDestroy(); + if (!isWindow() && parentWidget()) + parentWidget()->d_func()->invalidateBuffer(d->effectiveRectFor(geometry())); + if (!internalWinId()) + qt_mac_repaintParentUnderAlienWidget(this); + d->deactivateWidgetCleanup(); + qt_mac_event_release(this); + if(testAttribute(Qt::WA_WState_Created)) { + setAttribute(Qt::WA_WState_Created, false); + QObjectList chldrn = children(); + for(int i = 0; i < chldrn.size(); i++) { // destroy all widget children + QObject *obj = chldrn.at(i); + if(obj->isWidgetType()) + static_cast<QWidget*>(obj)->destroy(destroySubWindows, destroySubWindows); + } + if(mac_mouse_grabber == this) + releaseMouse(); + if(mac_keyboard_grabber == this) + releaseKeyboard(); + + if(testAttribute(Qt::WA_ShowModal)) // just be sure we leave modal + QApplicationPrivate::leaveModal(this); + else if((windowType() == Qt::Popup)) + qApp->d_func()->closePopup(this); + if (destroyWindow) { + if(OSViewRef hiview = qt_mac_nativeview_for(this)) { + OSWindowRef window = 0; + NSDrawer *drawer = nil; +#ifdef QT_MAC_USE_COCOA + if (qt_mac_is_macdrawer(this)) { + drawer = qt_mac_drawer_for(this); + } else +#endif + if (isWindow()) + window = qt_mac_window_for(hiview); + + // Because of how "destruct" works, we have to do just a normal release for the root_win. + if (window && window == qt_root_win) { +#ifndef QT_MAC_USE_COCOA + CFRelease(hiview); +#else + [hiview release]; +#endif + } else { + qt_mac_destructView(hiview); + } + if (drawer) + qt_mac_destructDrawer(drawer); + if (window) + qt_mac_destructWindow(window); + } + } + QT_TRY { + d->setWinId(0); + } QT_CATCH (const std::bad_alloc &) { + // swallow - destructors must not throw + } + } +} + +void QWidgetPrivate::transferChildren() +{ + Q_Q(QWidget); + if (!q->internalWinId()) + return; // Can't add any views anyway + + QObjectList chlist = q->children(); + for (int i = 0; i < chlist.size(); ++i) { + QObject *obj = chlist.at(i); + if (obj->isWidgetType()) { + QWidget *w = (QWidget *)obj; + if (!w->isWindow()) { + // This seems weird, no need to call it in a loop right? + if (!topData()->caption.isEmpty()) + setWindowTitle_helper(extra->topextra->caption); + if (w->internalWinId()) { +#ifndef QT_MAC_USE_COCOA + HIViewAddSubview(qt_mac_nativeview_for(q), qt_mac_nativeview_for(w)); +#else + // New NSWindows get an extra reference when drops are + // registered (at least in 10.5) which means that we may + // access the window later and get a crash (becasue our + // widget is dead). Work around this be having the drop + // site disabled until it is part of the new hierarchy. + bool oldRegistered = w->testAttribute(Qt::WA_DropSiteRegistered); + w->setAttribute(Qt::WA_DropSiteRegistered, false); + [qt_mac_nativeview_for(w) retain]; + [qt_mac_nativeview_for(w) removeFromSuperview]; + [qt_mac_nativeview_for(q) addSubview:qt_mac_nativeview_for(w)]; + [qt_mac_nativeview_for(w) release]; + w->setAttribute(Qt::WA_DropSiteRegistered, oldRegistered); +#endif + } + } + } + } +} + +#ifdef QT_MAC_USE_COCOA +void QWidgetPrivate::setSubWindowStacking(bool set) +{ + // After hitting too many unforeseen bugs trying to put Qt on top of the cocoa child + // window API, we have decided to revert this behaviour as much as we can. We + // therefore now only allow child windows to exist for children of modal dialogs. + static bool use_behaviour_qt473 = !qgetenv("QT_MAC_USE_CHILDWINDOWS").isEmpty(); + + // This will set/remove a visual relationship between parent and child on screen. + // The reason for doing this is to ensure that a child always stacks infront of + // its parent. Unfortunatly is turns out that [NSWindow addChildWindow] has + // several unwanted side-effects, one of them being the moving of a child when + // moving the parent, which we choose to accept. A way tougher side-effect is + // that Cocoa will hide the parent if you hide the child. And in the case of + // a tool window, since it will normally hide when you deactivate the + // application, Cocoa will hide the parent upon deactivate as well. The result often + // being no more visible windows on screen. So, to make a long story short, we only + // allow parent-child relationships between windows that both are either a plain window + // or a dialog. + + Q_Q(QWidget); + if (!q->isWindow()) + return; + NSWindow *qwin = [qt_mac_nativeview_for(q) window]; + if (!qwin) + return; + Qt::WindowType qtype = q->windowType(); + if (set && !(qtype == Qt::Window || qtype == Qt::Dialog)) + return; + if (set && ![qwin isVisible]) + return; + + if (QWidget *parent = q->parentWidget()) { + if (NSWindow *pwin = [qt_mac_nativeview_for(parent) window]) { + if (set) { + Qt::WindowType ptype = parent->window()->windowType(); + if ([pwin isVisible] + && (ptype == Qt::Window || ptype == Qt::Dialog) + && ![qwin parentWindow] + && (use_behaviour_qt473 || parent->windowModality() == Qt::ApplicationModal)) { + NSInteger level = [qwin level]; + [pwin addChildWindow:qwin ordered:NSWindowAbove]; + if ([qwin level] < level) + [qwin setLevel:level]; + } + } else { + [pwin removeChildWindow:qwin]; + } + } + } + + // Only set-up child windows for q if q is modal: + if (set && !use_behaviour_qt473 && q->windowModality() != Qt::ApplicationModal) + return; + + QObjectList widgets = q->children(); + for (int i=0; i<widgets.size(); ++i) { + QWidget *child = qobject_cast<QWidget *>(widgets.at(i)); + if (child && child->isWindow()) { + if (NSWindow *cwin = [qt_mac_nativeview_for(child) window]) { + if (set) { + Qt::WindowType ctype = child->window()->windowType(); + if ([cwin isVisible] && (ctype == Qt::Window || ctype == Qt::Dialog) && ![cwin parentWindow]) { + NSInteger level = [cwin level]; + [qwin addChildWindow:cwin ordered:NSWindowAbove]; + if ([cwin level] < level) + [cwin setLevel:level]; + } + } else { + [qwin removeChildWindow:qt_mac_window_for(child)]; + } + } + } + } +} +#endif + +void QWidgetPrivate::setParent_sys(QWidget *parent, Qt::WindowFlags f) +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + QTLWExtra *topData = maybeTopData(); + bool wasCreated = q->testAttribute(Qt::WA_WState_Created); +#ifdef QT_MAC_USE_COCOA + bool wasWindow = q->isWindow(); +#endif + OSViewRef old_id = 0; + + if (q->isVisible() && q->parentWidget() && parent != q->parentWidget()) + q->parentWidget()->d_func()->invalidateBuffer(effectiveRectFor(q->geometry())); + + // Maintain the glWidgets list on parent change: remove "our" gl widgets + // from the list on the old parent and grandparents. + if (glWidgets.isEmpty() == false) { + QWidget *current = q->parentWidget(); + while (current) { + for (QList<QWidgetPrivate::GlWidgetInfo>::const_iterator it = glWidgets.constBegin(); + it != glWidgets.constEnd(); ++it) + current->d_func()->glWidgets.removeAll(*it); + + if (current->isWindow()) + break; + current = current->parentWidget(); + } + } + +#ifndef QT_MAC_USE_COCOA + EventHandlerRef old_window_event = 0; +#else + bool oldToolbarVisible = false; + NSDrawer *oldDrawer = nil; + NSToolbar *oldToolbar = 0; +#endif + if (wasCreated && !(q->windowType() == Qt::Desktop)) { + old_id = qt_mac_nativeview_for(q); +#ifndef QT_MAC_USE_COCOA + old_window_event = window_event; +#else + if (qt_mac_is_macdrawer(q)) { + oldDrawer = qt_mac_drawer_for(q); + } + if (wasWindow) { + OSWindowRef oldWindow = qt_mac_window_for(old_id); + oldToolbar = [oldWindow toolbar]; + if (oldToolbar) { + [oldToolbar retain]; + oldToolbarVisible = [oldToolbar isVisible]; + [oldWindow setToolbar:nil]; + } + } +#endif + } + QWidget* oldtlw = q->window(); + + if (q->testAttribute(Qt::WA_DropSiteRegistered)) + q->setAttribute(Qt::WA_DropSiteRegistered, false); + + //recreate and setup flags + QObjectPrivate::setParent_helper(parent); + bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide); + if (wasCreated && !qt_isGenuineQWidget(q)) + return; + + if (!q->testAttribute(Qt::WA_WState_WindowOpacitySet)) { + q->setWindowOpacity(1.0f); + q->setAttribute(Qt::WA_WState_WindowOpacitySet, false); + } + + setWinId(0); //do after the above because they may want the id + + data.window_flags = f; + q->setAttribute(Qt::WA_WState_Created, false); + q->setAttribute(Qt::WA_WState_Visible, false); + q->setAttribute(Qt::WA_WState_Hidden, false); + adjustFlags(data.window_flags, q); + // keep compatibility with previous versions, we need to preserve the created state. + // (but we recreate the winId for the widget being reparented, again for compatibility, + // unless this is an alien widget. ) + const bool nonWindowWithCreatedParent = !q->isWindow() && parent->testAttribute(Qt::WA_WState_Created); + const bool nativeWidget = q->internalWinId() != 0; + if (wasCreated || (nativeWidget && nonWindowWithCreatedParent)) { + createWinId(); + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + // We do this down below for wasCreated, so avoid doing this twice + // (only for performance, it gets called a lot anyway). + if (!wasCreated) { + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + } +#else + // Simply transfer our toolbar over. Everything should stay put, unlike in Carbon. + if (oldToolbar && !(f & Qt::FramelessWindowHint)) { + OSWindowRef newWindow = qt_mac_window_for(q); + [newWindow setToolbar:oldToolbar]; + [oldToolbar release]; + [oldToolbar setVisible:oldToolbarVisible]; + } +#endif + } + } + if (q->isWindow() || (!parent || parent->isVisible()) || explicitlyHidden) + q->setAttribute(Qt::WA_WState_Hidden); + q->setAttribute(Qt::WA_WState_ExplicitShowHide, explicitlyHidden); + + if (wasCreated) { + transferChildren(); +#ifndef QT_MAC_USE_COCOA + // If we were a unified window, We just transfered our toolbars out of the unified toolbar. + // So redo the status one more time. It apparently is not an issue with Cocoa. + if (q->isWindow()) { + if (QMainWindowLayout *mwl = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q))) { + mwl->updateHIToolBarStatus(); + } + } +#endif + + if (topData && + (!topData->caption.isEmpty() || !topData->filePath.isEmpty())) + setWindowTitle_helper(q->windowTitle()); + } + + if (q->testAttribute(Qt::WA_AcceptDrops) + || (!q->isWindow() && q->parentWidget() + && q->parentWidget()->testAttribute(Qt::WA_DropSiteRegistered))) + q->setAttribute(Qt::WA_DropSiteRegistered, true); + + //cleanup +#ifndef QT_MAC_USE_COCOA + if (old_window_event) + RemoveEventHandler(old_window_event); +#endif + if (old_id) { //don't need old window anymore + OSWindowRef window = (oldtlw == q) ? qt_mac_window_for(old_id) : 0; + qt_mac_destructView(old_id); + +#ifdef QT_MAC_USE_COCOA + if (oldDrawer) { + qt_mac_destructDrawer(oldDrawer); + } else +#endif + if (window) + qt_mac_destructWindow(window); + } + + // Maintain the glWidgets list on parent change: add "our" gl widgets + // to the list on the new parent and grandparents. + if (glWidgets.isEmpty() == false) { + QWidget *current = q->parentWidget(); + while (current) { + current->d_func()->glWidgets += glWidgets; + if (current->isWindow()) + break; + current = current->parentWidget(); + } + } + invalidateBuffer(q->rect()); + qt_event_request_window_change(q); +} + +QPoint QWidget::mapToGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!internalWinId()) { + QPoint p = pos + data->crect.topLeft(); + return isWindow() ? p : parentWidget()->mapToGlobal(p); + } +#ifndef QT_MAC_USE_COCOA + QPoint tmp = d->mapToWS(pos); + HIPoint hi_pos = CGPointMake(tmp.x(), tmp.y()); + HIViewConvertPoint(&hi_pos, qt_mac_nativeview_for(this), 0); + Rect win_rect; + GetWindowBounds(qt_mac_window_for(this), kWindowStructureRgn, &win_rect); + return QPoint((int)hi_pos.x+win_rect.left, (int)hi_pos.y+win_rect.top); +#else + QPoint tmp = d->mapToWS(pos); + NSPoint hi_pos = NSMakePoint(tmp.x(), tmp.y()); + hi_pos = [qt_mac_nativeview_for(this) convertPoint:hi_pos toView:nil]; + NSRect win_rect = [qt_mac_window_for(this) frame]; + hi_pos.x += win_rect.origin.x; + hi_pos.y += win_rect.origin.y; + // If we aren't the desktop we need to flip, if you flip the desktop on itself, you get the other problem. + return ((window()->windowFlags() & Qt::Desktop) == Qt::Desktop) ? QPointF(hi_pos.x, hi_pos.y).toPoint() + : flipPoint(hi_pos).toPoint(); +#endif +} + +QPoint QWidget::mapFromGlobal(const QPoint &pos) const +{ + Q_D(const QWidget); + if (!internalWinId()) { + QPoint p = isWindow() ? pos : parentWidget()->mapFromGlobal(pos); + return p - data->crect.topLeft(); + } +#ifndef QT_MAC_USE_COCOA + Rect win_rect; + GetWindowBounds(qt_mac_window_for(this), kWindowStructureRgn, &win_rect); + HIPoint hi_pos = CGPointMake(pos.x()-win_rect.left, pos.y()-win_rect.top); + HIViewConvertPoint(&hi_pos, 0, qt_mac_nativeview_for(this)); + return d->mapFromWS(QPoint((int)hi_pos.x, (int)hi_pos.y)); +#else + NSRect win_rect = [qt_mac_window_for(this) frame]; + // The Window point is in "Cocoa coordinates," but the view is in "Qt coordinates" + // so make sure to keep them in sync. + NSPoint hi_pos = NSMakePoint(pos.x()-win_rect.origin.x, + flipYCoordinate(pos.y())-win_rect.origin.y); + hi_pos = [qt_mac_nativeview_for(this) convertPoint:hi_pos fromView:0]; + return d->mapFromWS(QPoint(qRound(hi_pos.x), qRound(hi_pos.y))); +#endif +} + +void QWidgetPrivate::updateSystemBackground() +{ +} + +void QWidgetPrivate::setCursor_sys(const QCursor &) +{ + qt_mac_update_cursor(); +} + +void QWidgetPrivate::unsetCursor_sys() +{ + qt_mac_update_cursor(); +} + +void QWidgetPrivate::setWindowTitle_sys(const QString &caption) +{ + Q_Q(QWidget); + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + SetWindowTitleWithCFString(qt_mac_window_for(q), QCFString(caption)); +#else + QMacCocoaAutoReleasePool pool; + [qt_mac_window_for(q) setTitle:qt_mac_QStringToNSString(caption)]; +#endif + } +} + +void QWidgetPrivate::setWindowModified_sys(bool mod) +{ + Q_Q(QWidget); + if (q->isWindow() && q->testAttribute(Qt::WA_WState_Created)) { +#ifndef QT_MAC_USE_COCOA + SetWindowModified(qt_mac_window_for(q), mod); +#else + [qt_mac_window_for(q) setDocumentEdited:mod]; +#endif + } +} + +void QWidgetPrivate::setWindowFilePath_sys(const QString &filePath) +{ + Q_Q(QWidget); +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + QFileInfo fi(filePath); + [qt_mac_window_for(q) setRepresentedFilename:fi.exists() ? qt_mac_QStringToNSString(filePath) : @""]; +#else + bool validRef = false; + FSRef ref; + bzero(&ref, sizeof(ref)); + OSStatus status; + + if (!filePath.isEmpty()) { + status = FSPathMakeRef(reinterpret_cast<const UInt8 *>(filePath.toUtf8().constData()), &ref, 0); + validRef = (status == noErr); + } + // Set the proxy regardless, since this is our way of clearing it as well, but ignore the + // return value as well. + if (validRef) { + status = HIWindowSetProxyFSRef(qt_mac_window_for(q), &ref); + } else { + status = RemoveWindowProxy(qt_mac_window_for(q)); + } + if (status != noErr) + qWarning("QWidget::setWindowFilePath: Error setting proxyicon for path (%s):%ld", + qPrintable(filePath), status); +#endif +} + +void QWidgetPrivate::setWindowIcon_sys(bool forceReset) +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + QTLWExtra *topData = this->topData(); + if (topData->iconPixmap && !forceReset) // already set + return; + + QIcon icon = q->windowIcon(); + QPixmap *pm = 0; + if (!icon.isNull()) { + // now create the extra + if (!topData->iconPixmap) { + pm = new QPixmap(icon.pixmap(QSize(22, 22))); + topData->iconPixmap = pm; + } else { + pm = topData->iconPixmap; + } + } + if (q->isWindow()) { +#ifndef QT_MAC_USE_COCOA + IconRef previousIcon = 0; + if (icon.isNull()) { + RemoveWindowProxy(qt_mac_window_for(q)); + previousIcon = topData->windowIcon; + topData->windowIcon = 0; + } else { + WindowClass wclass; + GetWindowClass(qt_mac_window_for(q), &wclass); + + if (wclass == kDocumentWindowClass) { + IconRef newIcon = qt_mac_create_iconref(*pm); + previousIcon = topData->windowIcon; + topData->windowIcon = newIcon; + SetWindowProxyIcon(qt_mac_window_for(q), newIcon); + } + } + + // Release the previous icon if it was set by this function. + if (previousIcon != 0) + ReleaseIconRef(previousIcon); +#else + QMacCocoaAutoReleasePool pool; + if (icon.isNull()) + return; + NSButton *iconButton = [qt_mac_window_for(q) standardWindowButton:NSWindowDocumentIconButton]; + if (iconButton == nil) { + QCFString string(q->windowTitle()); + const NSString *tmpString = reinterpret_cast<const NSString *>((CFStringRef)string); + [qt_mac_window_for(q) setRepresentedURL:[NSURL fileURLWithPath:const_cast<NSString *>(tmpString)]]; + iconButton = [qt_mac_window_for(q) standardWindowButton:NSWindowDocumentIconButton]; + } + if (icon.isNull()) { + [iconButton setImage:nil]; + } else { + QPixmap scaled = pm->scaled(QSize(16,16), Qt::KeepAspectRatio, Qt::SmoothTransformation); + NSImage *image = static_cast<NSImage *>(qt_mac_create_nsimage(scaled)); + [iconButton setImage:image]; + [image release]; + } +#endif + } +} + +void QWidgetPrivate::setWindowIconText_sys(const QString &iconText) +{ + Q_Q(QWidget); + if(q->isWindow() && !iconText.isEmpty()) { +#ifndef QT_MAC_USE_COCOA + SetWindowAlternateTitle(qt_mac_window_for(q), QCFString(iconText)); +#else + QMacCocoaAutoReleasePool pool; + [qt_mac_window_for(q) setMiniwindowTitle:qt_mac_QStringToNSString(iconText)]; +#endif + } +} + +void QWidget::grabMouse() +{ + if(isVisible() && !qt_nograb()) { + if(mac_mouse_grabber) + mac_mouse_grabber->releaseMouse(); + mac_mouse_grabber=this; + qt_mac_setMouseGrabCursor(true); + } +} + +#ifndef QT_NO_CURSOR +void QWidget::grabMouse(const QCursor &cursor) +{ + if(isVisible() && !qt_nograb()) { + if(mac_mouse_grabber) + mac_mouse_grabber->releaseMouse(); + mac_mouse_grabber=this; + qt_mac_setMouseGrabCursor(true, const_cast<QCursor *>(&cursor)); + } +} +#endif + +void QWidget::releaseMouse() +{ + if(!qt_nograb() && mac_mouse_grabber == this) { + mac_mouse_grabber = 0; + qt_mac_setMouseGrabCursor(false); + } +} + +void QWidget::grabKeyboard() +{ + if(!qt_nograb()) { + if(mac_keyboard_grabber) + mac_keyboard_grabber->releaseKeyboard(); + mac_keyboard_grabber = this; + } +} + +void QWidget::releaseKeyboard() +{ + if(!qt_nograb() && mac_keyboard_grabber == this) + mac_keyboard_grabber = 0; +} + +QWidget *QWidget::mouseGrabber() +{ + return mac_mouse_grabber; +} + +QWidget *QWidget::keyboardGrabber() +{ + return mac_keyboard_grabber; +} + +void QWidget::activateWindow() +{ + QWidget *tlw = window(); + if(!tlw->isVisible() || !tlw->isWindow() || (tlw->windowType() == Qt::Desktop)) + return; + qt_event_remove_activate(); + + QWidget *fullScreenWidget = tlw; + QWidget *parentW = tlw; + // Find the oldest parent or the parent with fullscreen, whichever comes first. + while (parentW) { + fullScreenWidget = parentW->window(); + if (fullScreenWidget->windowState() & Qt::WindowFullScreen) + break; + parentW = fullScreenWidget->parentWidget(); + } + + if (fullScreenWidget->windowType() != Qt::ToolTip) { + qt_mac_set_fullscreen_mode((fullScreenWidget->windowState() & Qt::WindowFullScreen) && + qApp->desktop()->screenNumber(this) == 0); + } + + bool windowActive; + OSWindowRef win = qt_mac_window_for(tlw); +#ifndef QT_MAC_USE_COCOA + windowActive = IsWindowActive(win); +#else + QMacCocoaAutoReleasePool pool; + windowActive = [win isKeyWindow]; +#endif + if ((tlw->windowType() == Qt::Popup) + || (tlw->windowType() == Qt::Tool) + || qt_mac_is_macdrawer(tlw) + || windowActive) { +#ifndef QT_MAC_USE_COCOA + ActivateWindow(win, true); + qApp->setActiveWindow(tlw); +#else + [win makeKeyWindow]; +#endif + } else if(!isMinimized()) { +#ifndef QT_MAC_USE_COCOA + SelectWindow(win); +#else + [win makeKeyAndOrderFront:win]; +#endif + } +} + +QWindowSurface *QWidgetPrivate::createDefaultWindowSurface_sys() +{ + return new QMacWindowSurface(q_func()); +} + +void QWidgetPrivate::update_sys(const QRect &r) +{ + Q_Q(QWidget); + if (updateRedirectedToGraphicsProxyWidget(q, r)) + return; + dirtyOnWidget += r; + macSetNeedsDisplay(r != q->rect() ? r : QRegion()); +} + +void QWidgetPrivate::update_sys(const QRegion &rgn) +{ + Q_Q(QWidget); + if (updateRedirectedToGraphicsProxyWidget(q, rgn)) + return; + dirtyOnWidget += rgn; + macSetNeedsDisplay(rgn); +} + +bool QWidgetPrivate::isRealWindow() const +{ + return q_func()->isWindow() && !topData()->embedded; +} + +void QWidgetPrivate::show_sys() +{ + Q_Q(QWidget); + if ((q->windowType() == Qt::Desktop)) //desktop is always visible + return; + + invalidateBuffer(q->rect()); + if (q->testAttribute(Qt::WA_OutsideWSRange)) + return; + QMacCocoaAutoReleasePool pool; + q->setAttribute(Qt::WA_Mapped); + if (q->testAttribute(Qt::WA_DontShowOnScreen)) + return; + + bool realWindow = isRealWindow(); +#ifndef QT_MAC_USE_COCOA + if (realWindow && !q->testAttribute(Qt::WA_Moved)) { + if (qt_mac_is_macsheet(q)) + recreateMacWindow(); + q->createWinId(); + if (QWidget *p = q->parentWidget()) { + p->createWinId(); + RepositionWindow(qt_mac_window_for(q), qt_mac_window_for(p), kWindowCenterOnParentWindow); + } else { + RepositionWindow(qt_mac_window_for(q), 0, kWindowCenterOnMainScreen); + } + } +#endif + + data.fstrut_dirty = true; + if (realWindow) { + bool isCurrentlyMinimized = (q->windowState() & Qt::WindowMinimized); + setModal_sys(); + OSWindowRef window = qt_mac_window_for(q); +#ifndef QT_MAC_USE_COCOA + SizeWindow(window, q->width(), q->height(), true); +#endif + +#ifdef QT_MAC_USE_COCOA + // Make sure that we end up sending a repaint event to + // the widget if the window has been visible one before: + [qt_mac_get_contentview_for(window) setNeedsDisplay:YES]; +#endif + if(qt_mac_is_macsheet(q)) { + qt_event_request_showsheet(q); + } else if(qt_mac_is_macdrawer(q)) { +#ifndef QT_MAC_USE_COCOA + OpenDrawer(window, kWindowEdgeDefault, false); +#else + NSDrawer *drawer = qt_mac_drawer_for(q); + [drawer openOnEdge:[drawer preferredEdge]]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + ShowHide(window, true); +#else + // sync the opacity value back (in case of a fade). + [window setAlphaValue:q->windowOpacity()]; + + QWidget *top = 0; + if (QApplicationPrivate::tryModalHelper(q, &top)) { + [window makeKeyAndOrderFront:window]; + // If this window is app modal, we need to start spinning + // a modal session for it. Interrupting + // the event dispatcher will make this happend: + if (data.window_modality == Qt::ApplicationModal) + QEventDispatcherMac::instance()->interrupt(); + } else { + // The window is modally shaddowed, so we need to make + // sure that we don't pop in front of the modal window: + [window orderFront:window]; + if (!top->testAttribute(Qt::WA_DontShowOnScreen)) { + if (NSWindow *modalWin = qt_mac_window_for(top)) + [modalWin orderFront:window]; + } + } + setSubWindowStacking(true); + qt_mac_update_cursor(); +#endif + if (q->windowType() == Qt::Popup) { + qt_button_down = 0; + if (q->focusWidget()) + q->focusWidget()->d_func()->setFocus_sys(); + else + setFocus_sys(); + } + toggleDrawers(true); + } + if (isCurrentlyMinimized) { //show in collapsed state +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, true); +#else + [window miniaturize:window]; +#endif + } else if (!q->testAttribute(Qt::WA_ShowWithoutActivating)) { +#ifndef QT_MAC_USE_COCOA + qt_event_request_activate(q); +#endif + } + } else if(topData()->embedded || !q->parentWidget() || q->parentWidget()->isVisible()) { +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + if (NSView *view = qt_mac_nativeview_for(q)) { + // INVARIANT: q is native. Just show the view: + [view setHidden:NO]; + } else { + // INVARIANT: q is alien. Repaint q instead: + q->repaint(); + } +#endif + } + +#ifdef QT_MAC_USE_COCOA + if ([NSApp isActive] && !qt_button_down && !QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, qt_last_mouse_receiver); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +#endif + + topLevelAt_cache = 0; + qt_event_request_window_change(q); +} + +QPoint qt_mac_nativeMapFromParent(const QWidget *child, const QPoint &pt) +{ +#ifndef QT_MAC_USE_COCOA + CGPoint nativePoint = CGPointMake(pt.x(), pt.y()); + HIViewConvertPoint(&nativePoint, qt_mac_nativeview_for(child->parentWidget()), + qt_mac_nativeview_for(child)); +#else + NSPoint nativePoint = [qt_mac_nativeview_for(child) convertPoint:NSMakePoint(pt.x(), pt.y()) fromView:qt_mac_nativeview_for(child->parentWidget())]; +#endif + return QPoint(nativePoint.x, nativePoint.y); +} + + +void QWidgetPrivate::hide_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) //you can't hide the desktop! + return; + QMacCocoaAutoReleasePool pool; + if(q->isWindow()) { +#ifdef QT_MAC_USE_COCOA + setSubWindowStacking(false); +#endif + OSWindowRef window = qt_mac_window_for(q); + if(qt_mac_is_macsheet(q)) { +#ifndef QT_MAC_USE_COCOA + WindowRef parent = 0; + if(GetSheetWindowParent(window, &parent) != noErr || !parent) + ShowHide(window, false); + else + HideSheetWindow(window); +#else + [NSApp endSheet:window]; + [window orderOut:window]; +#endif + } else if(qt_mac_is_macdrawer(q)) { +#ifndef QT_MAC_USE_COCOA + CloseDrawer(window, false); +#else + [qt_mac_drawer_for(q) close]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + ShowHide(window, false); +#else + [window orderOut:window]; + // Unfortunately it is not as easy as just hiding the window, we need + // to find out if we were in full screen mode. If we were and this is + // the last window in full screen mode then we need to unset the full screen + // mode. If this is not the last visible window in full screen mode then we + // don't change the full screen mode. + if(q->isFullScreen()) + { + bool keepFullScreen = false; + QWidgetList windowList = qApp->topLevelWidgets(); + int windowCount = windowList.count(); + for(int i = 0; i < windowCount; i++) + { + QWidget *w = windowList[i]; + // If it is the same window, we don't need to check :-) + if(q == w) + continue; + // If they are not visible or if they are minimized then + // we just ignore them. + if(!w->isVisible() || w->isMinimized()) + continue; + // Is it full screen? + // Notice that if there is one window in full screen mode then we + // cannot switch the full screen mode off, therefore we just abort. + if(w->isFullScreen()) { + keepFullScreen = true; + break; + } + } + // No windows in full screen mode, so let just unset that flag. + if(!keepFullScreen) + qt_mac_set_fullscreen_mode(false); + } +#endif + toggleDrawers(false); + qt_mac_update_cursor(); +#ifndef QT_MAC_USE_COCOA + // Clear modality (because it seems something that we've always done). + if (data.window_modality != Qt::NonModal) { + SetWindowModality(window, kWindowModalityNone, + q->parentWidget() ? qt_mac_window_for(q->parentWidget()->window()) : 0); + } +#endif + } +#ifndef QT_MAC_USE_COCOA + // If the window we now hide was the active window, we need + // to find, and activate another window on screen. NB: Cocoa takes care of this + // logic for us (and distinquishes between main windows and key windows) + if (q->isActiveWindow() && !(q->windowType() == Qt::Popup)) { + QWidget *w = 0; + if(q->parentWidget()) + w = q->parentWidget()->window(); + if(!w || (!w->isVisible() && !w->isMinimized())) { + for (WindowPtr wp = GetFrontWindowOfClass(kMovableModalWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kMovableModalWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + if (!w){ + for (WindowPtr wp = GetFrontWindowOfClass(kDocumentWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kDocumentWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + } + if (!w){ + for(WindowPtr wp = GetFrontWindowOfClass(kSimpleWindowClass, true); + wp; wp = GetNextWindowOfClass(wp, kSimpleWindowClass, true)) { + if((w = qt_mac_find_window(wp))) + break; + } + } + } + if(w && w->isVisible() && !w->isMinimized()) { + qt_event_request_activate(w); + } + } +#endif + } else { + invalidateBuffer(q->rect()); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), false); +#else + if (NSView *view = qt_mac_nativeview_for(q)) { + // INVARIANT: q is native. Just hide the view: + [view setHidden:YES]; + } else { + // INVARIANT: q is alien. Repaint where q is placed instead: + qt_mac_repaintParentUnderAlienWidget(q); + } +#endif + } + +#ifdef QT_MAC_USE_COCOA + if ([NSApp isActive] && !qt_button_down && !QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Leave, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, qt_last_native_mouse_receiver); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +#endif + + topLevelAt_cache = 0; + qt_event_request_window_change(q); + deactivateWidgetCleanup(); + qt_mac_event_release(q); +} + +void QWidget::setWindowState(Qt::WindowStates newstate) +{ + Q_D(QWidget); + bool needShow = false; + Qt::WindowStates oldstate = windowState(); + if (oldstate == newstate) + return; + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#endif + bool needSendStateChange = true; + if(isWindow()) { + if((oldstate & Qt::WindowFullScreen) != (newstate & Qt::WindowFullScreen)) { + if(newstate & Qt::WindowFullScreen) { + if(QTLWExtra *tlextra = d->topData()) { + if(tlextra->normalGeometry.width() < 0) { + if(!testAttribute(Qt::WA_Resized)) + adjustSize(); + tlextra->normalGeometry = geometry(); + } + tlextra->savedFlags = windowFlags(); + } + needShow = isVisible(); + const QRect fullscreen(qApp->desktop()->screenGeometry(qApp->desktop()->screenNumber(this))); + setParent(parentWidget(), Qt::Window | Qt::FramelessWindowHint | (windowFlags() & 0xffff0000)); //save + setGeometry(fullscreen); + if(!qApp->desktop()->screenNumber(this)) + qt_mac_set_fullscreen_mode(true); + } else { + needShow = isVisible(); + if(!qApp->desktop()->screenNumber(this)) + qt_mac_set_fullscreen_mode(false); + setParent(parentWidget(), d->topData()->savedFlags); + setGeometry(d->topData()->normalGeometry); + d->topData()->normalGeometry.setRect(0, 0, -1, -1); + } + } + + d->createWinId(); + + OSWindowRef window = qt_mac_window_for(this); + if((oldstate & Qt::WindowMinimized) != (newstate & Qt::WindowMinimized)) { + if (newstate & Qt::WindowMinimized) { +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, true); +#else + [window miniaturize:window]; +#endif + } else { +#ifndef QT_MAC_USE_COCOA + CollapseWindow(window, false); +#else + [window deminiaturize:window]; +#endif + } + needSendStateChange = oldstate == windowState(); // Collapse didn't change our flags. + } + + if((newstate & Qt::WindowMaximized) && !((newstate & Qt::WindowFullScreen))) { + if(QTLWExtra *tlextra = d->topData()) { + if(tlextra->normalGeometry.width() < 0) { + if(!testAttribute(Qt::WA_Resized)) + adjustSize(); + tlextra->normalGeometry = geometry(); + } + } + } else if(!(newstate & Qt::WindowFullScreen)) { +// d->topData()->normalGeometry = QRect(0, 0, -1, -1); + } + +#ifdef DEBUG_WINDOW_STATE +#define WSTATE(x) qDebug("%s -- %s --> %s", #x, (oldstate & x) ? "true" : "false", (newstate & x) ? "true" : "false") + WSTATE(Qt::WindowMinimized); + WSTATE(Qt::WindowMaximized); + WSTATE(Qt::WindowFullScreen); +#undef WSTATE +#endif + if(!(newstate & (Qt::WindowMinimized|Qt::WindowFullScreen)) && + ((oldstate & Qt::WindowFullScreen) || (oldstate & Qt::WindowMinimized) || + (oldstate & Qt::WindowMaximized) != (newstate & Qt::WindowMaximized))) { + if(newstate & Qt::WindowMaximized) { + data->fstrut_dirty = true; +#ifndef QT_MAC_USE_COCOA + HIToolbarRef toolbarRef; + if (GetWindowToolbar(window, &toolbarRef) == noErr && toolbarRef + && !isVisible() && !IsWindowToolbarVisible(window)) { + // HIToolbar, needs to be shown so that it's in the structure window + // Typically this is part of a main window and will get shown + // during the show, but it's will make the maximize all wrong. + ShowHideWindowToolbar(window, true, false); + d->updateFrameStrut(); // In theory the dirty would work, but it's optimized out if the window is not visible :( + } + Rect bounds; + QDesktopWidget *dsk = QApplication::desktop(); + QRect avail = dsk->availableGeometry(dsk->screenNumber(this)); + SetRect(&bounds, avail.x(), avail.y(), avail.x() + avail.width(), avail.y() + avail.height()); + if(QWExtra *extra = d->extraData()) { + if(bounds.right - bounds.left > extra->maxw) + bounds.right = bounds.left + extra->maxw; + if(bounds.bottom - bounds.top > extra->maxh) + bounds.bottom = bounds.top + extra->maxh; + } + if(d->topData()) { + QRect fs = d->frameStrut(); + bounds.left += fs.left(); + if(bounds.right < avail.x()+avail.width()) + bounds.right = qMin<short>((uint)avail.x()+avail.width(), bounds.right+fs.left()); + if(bounds.bottom < avail.y()+avail.height()) + bounds.bottom = qMin<short>((uint)avail.y()+avail.height(), bounds.bottom+fs.top()); + bounds.top += fs.top(); + bounds.right -= fs.right(); + bounds.bottom -= fs.bottom(); + } + QRect orect(geometry().x(), geometry().y(), width(), height()), + nrect(bounds.left, bounds.top, bounds.right - bounds.left, + bounds.bottom - bounds.top); + if(orect != nrect) { // the new rect differ from the old + Point idealSize = { nrect.height(), nrect.width() }; + ZoomWindowIdeal(window, inZoomOut, &idealSize); + } +#else + NSToolbar *toolbarRef = [window toolbar]; + if (toolbarRef && !isVisible() && ![toolbarRef isVisible]) { + // HIToolbar, needs to be shown so that it's in the structure window + // Typically this is part of a main window and will get shown + // during the show, but it's will make the maximize all wrong. + // ### Not sure this is right for NSToolbar... + [toolbarRef setVisible:true]; +// ShowHideWindowToolbar(window, true, false); + d->updateFrameStrut(); // In theory the dirty would work, but it's optimized out if the window is not visible :( + } + // Everything should be handled by Cocoa. + [window zoom:window]; +#endif + needSendStateChange = oldstate == windowState(); // Zoom didn't change flags. + } else if(oldstate & Qt::WindowMaximized && !(oldstate & Qt::WindowFullScreen)) { +#ifndef QT_MAC_USE_COCOA + Point idealSize; + ZoomWindowIdeal(window, inZoomIn, &idealSize); +#else + [window zoom:window]; +#endif + if(QTLWExtra *tlextra = d->topData()) { + setGeometry(tlextra->normalGeometry); + tlextra->normalGeometry.setRect(0, 0, -1, -1); + } + } + } + } + + data->window_state = newstate; + + if(needShow) + show(); + + if(newstate & Qt::WindowActive) + activateWindow(); + + qt_event_request_window_change(this); + if (needSendStateChange) { + QWindowStateChangeEvent e(oldstate); + QApplication::sendEvent(this, &e); + } +} + +void QWidgetPrivate::setFocus_sys() +{ + Q_Q(QWidget); + if (q->testAttribute(Qt::WA_WState_Created)) { +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSView *view = qt_mac_nativeview_for(q); + [[view window] makeFirstResponder:view]; +#else + SetKeyboardFocus(qt_mac_window_for(q), qt_mac_nativeview_for(q), 1); +#endif + } +} + +NSComparisonResult compareViews2Raise(id view1, id view2, void *context) +{ + id topView = reinterpret_cast<id>(context); + if (view1 == topView) + return NSOrderedDescending; + if (view2 == topView) + return NSOrderedAscending; + return NSOrderedSame; +} + +void QWidgetPrivate::raise_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) + return; + +#if QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + if (isRealWindow()) { + // With the introduction of spaces it is not as simple as just raising the window. + // First we need to check if we are in the right space. If we are, then we just continue + // as usual. The problem comes when we are not in the active space. There are two main cases: + // 1. Our parent was moved to a new space. In this case we want the window to be raised + // in the same space as its parent. + // 2. We don't have a parent. For this case we will just raise the window and let Cocoa + // switch to the corresponding space. + // NOTICE: There are a lot of corner cases here. We are keeping this simple for now, if + // required we will introduce special handling for some of them. + if (!q->testAttribute(Qt::WA_DontShowOnScreen) && q->isVisible()) { + OSWindowRef window = qt_mac_window_for(q); + // isOnActiveSpace is available only from 10.6 onwards, so we need to check if it is + // available before calling it. + if([window respondsToSelector:@selector(isOnActiveSpace)]) { + if(![window performSelector:@selector(isOnActiveSpace)]) { + QWidget *parentWidget = q->parentWidget(); + if(parentWidget) { + OSWindowRef parentWindow = qt_mac_window_for(parentWidget); + if(parentWindow && [parentWindow respondsToSelector:@selector(isOnActiveSpace)]) { + if ([parentWindow performSelector:@selector(isOnActiveSpace)]) { + // The window was created in a different space. Therefore if we want + // to show it in the current space we need to recreate it in the new + // space. + recreateMacWindow(); + window = qt_mac_window_for(q); + } + } + } + } + } + [window orderFront:window]; + } + if (qt_mac_raise_process) { //we get to be the active process now + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); + } + } else { + NSView *view = qt_mac_nativeview_for(q); + NSView *parentView = [view superview]; + [parentView sortSubviewsUsingFunction:compareViews2Raise context:reinterpret_cast<void *>(view)]; + } + topLevelAt_cache = 0; +#else + if(q->isWindow()) { + //raise this window + BringToFront(qt_mac_window_for(q)); + if(qt_mac_raise_process) { //we get to be the active process now + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); + } + } else if(q->parentWidget()) { + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderAbove, 0); + qt_event_request_window_change(q); + } +#endif +} + +NSComparisonResult compareViews2Lower(id view1, id view2, void *context) +{ + id topView = reinterpret_cast<id>(context); + if (view1 == topView) + return NSOrderedAscending; + if (view2 == topView) + return NSOrderedDescending; + return NSOrderedSame; +} + +void QWidgetPrivate::lower_sys() +{ + Q_Q(QWidget); + if((q->windowType() == Qt::Desktop)) + return; +#ifdef QT_MAC_USE_COCOA + if (isRealWindow()) { + OSWindowRef window = qt_mac_window_for(q); + [window orderBack:window]; + } else { + NSView *view = qt_mac_nativeview_for(q); + NSView *parentView = [view superview]; + [parentView sortSubviewsUsingFunction:compareViews2Lower context:reinterpret_cast<void *>(view)]; + } + topLevelAt_cache = 0; +#else + if(q->isWindow()) { + SendBehind(qt_mac_window_for(q), 0); + } else if(q->parentWidget()) { + invalidateBuffer(q->rect()); + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderBelow, 0); + qt_event_request_window_change(q); + } +#endif +} + +NSComparisonResult compareViews2StackUnder(id view1, id view2, void *context) +{ + const QHash<NSView *, int> &viewOrder = *reinterpret_cast<QHash<NSView *, int> *>(context); + if (viewOrder[view1] < viewOrder[view2]) + return NSOrderedAscending; + if (viewOrder[view1] > viewOrder[view2]) + return NSOrderedDescending; + return NSOrderedSame; +} + +void QWidgetPrivate::stackUnder_sys(QWidget *w) +{ + // stackUnder + Q_Q(QWidget); + if(!w || q->isWindow() || (q->windowType() == Qt::Desktop)) + return; +#ifdef QT_MAC_USE_COCOA + // Do the same trick as lower_sys() and put this widget before the widget passed in. + NSView *myView = qt_mac_nativeview_for(q); + NSView *wView = qt_mac_nativeview_for(w); + + QHash<NSView *, int> viewOrder; + NSView *parentView = [myView superview]; + NSArray *subviews = [parentView subviews]; + NSUInteger index = 1; + // make a hash of view->zorderindex and make sure z-value is always odd, + // so that when we modify the order we create a new (even) z-value which + // will not interfere with others. + for (NSView *subview in subviews) { + viewOrder.insert(subview, index * 2); + ++index; + } + viewOrder[myView] = viewOrder[wView] - 1; + + [parentView sortSubviewsUsingFunction:compareViews2StackUnder context:reinterpret_cast<void *>(&viewOrder)]; +#else + QWidget *p = q->parentWidget(); + if(!p || p != w->parentWidget()) + return; + invalidateBuffer(q->rect()); + HIViewSetZOrder(qt_mac_nativeview_for(q), kHIViewZOrderBelow, qt_mac_nativeview_for(w)); + qt_event_request_window_change(q); +#endif +} + +#ifndef QT_MAC_USE_COCOA +/* + Modifies the bounds for a widgets backing HIView during moves and resizes. Also updates the + widget, either by scrolling its contents or repainting, depending on the WA_StaticContents + flag +*/ +static void qt_mac_update_widget_position(QWidget *q, QRect oldRect, QRect newRect) +{ + HIRect bounds = CGRectMake(newRect.x(), newRect.y(), + newRect.width(), newRect.height()); + + const HIViewRef view = qt_mac_nativeview_for(q); + const bool isMove = (oldRect.topLeft() != newRect.topLeft()); + const bool isResize = (oldRect.size() != newRect.size()); + +// qDebug() << oldRect << newRect << isMove << isResize << q->testAttribute(Qt::WA_OpaquePaintEvent) << q->testAttribute(Qt::WA_StaticContents); + QWidgetPrivate *qd = qt_widget_private(q); + + // Perform a normal (complete repaint) update in some cases: + if ( + // always repaint on move. + (isMove) || + + // limited update on resize requires WA_StaticContents. + (isResize && q->testAttribute(Qt::WA_StaticContents) == false) || + + // one of the rects are invalid + (oldRect.isValid() == false || newRect.isValid() == false) || + + // the position update is a part of a drag-and-drop operation + QDragManager::self()->object || + + // we are on Panther (no HIViewSetNeedsDisplayInRect) + QSysInfo::MacintoshVersion < QSysInfo::MV_10_4 + ){ + HIViewSetFrame(view, &bounds); + return; + } + + const int dx = newRect.x() - oldRect.x(); + const int dy = newRect.y() - oldRect.y(); + + if (isMove) { + // HIViewScrollRect silently fails if we try to scroll anything under the grow box. + // Check if there's one present within the widget rect, and if there is fall back + // to repainting the entire widget. + QWidget const * const parentWidget = q->parentWidget(); + const HIViewRef parentView = qt_mac_nativeview_for(parentWidget); + HIViewRef nativeSizeGrip = 0; + if (q->testAttribute(Qt::WA_WState_Created)) + HIViewFindByID(HIViewGetRoot(HIViewGetWindow(HIViewRef(q->winId()))), kHIViewWindowGrowBoxID, &nativeSizeGrip); + if (nativeSizeGrip) { + QWidget * const window = q->window(); + + const int sizeGripSize = 20; + const QRect oldWidgetRect = QRect(q->mapTo(window, QPoint(0, 0)), QSize(oldRect.width(), oldRect.height())); + const QRect newWidgetRect = QRect(q->mapTo(window, QPoint(0, 0)), QSize(newRect.width(), newRect.height())); + const QRect sizeGripRect = QRect(window->rect().bottomRight() - QPoint(sizeGripSize, sizeGripSize), + window->rect().bottomRight()); + + if (sizeGripRect.intersects(oldWidgetRect) || sizeGripRect.intersects(newWidgetRect)) { + HIViewSetFrame(view, &bounds); + return; + } + } + + // Don't scroll anything outside the parent widget rect. + const QRect scrollRect = (oldRect | newRect) & parentWidget->rect(); + const HIRect scrollBounds = + CGRectMake(scrollRect.x(), scrollRect.y(), scrollRect.width(), scrollRect.height()); + + // We cannot scroll when the widget has a mask as that would + // scroll the masked out areas too + if (qd->extra && qd->extra->hasMask) { + HIViewMoveBy(view, dx, dy); + return; + } + + OSStatus err = HIViewScrollRect(parentView, &scrollBounds, dx, dy); + if (err != noErr) { + HIViewSetNeedsDisplay(view, true); + qWarning("QWidget: Internal error (%s:%d)", __FILE__, __LINE__); + } + } + // Set the view bounds with drawing disabled to prevent repaints. + HIViewSetDrawingEnabled(view, false); + HIViewSetFrame(view, &bounds); + HIViewSetDrawingEnabled(view, true); + + // Update any newly exposed areas due to resizing. + const int startx = oldRect.width(); + const int stopx = newRect.width(); + const int starty = oldRect.height(); + const int stopy = newRect.height(); + + const HIRect verticalSlice = CGRectMake(startx, 0, stopx , stopy); + HIViewSetNeedsDisplayInRect(view, &verticalSlice, true); + const HIRect horizontalSlice = CGRectMake(0, starty, startx, stopy); + HIViewSetNeedsDisplayInRect(view, &horizontalSlice, true); +} +#endif + +/* + Helper function for non-toplevel widgets. Helps to map Qt's 32bit + coordinate system to OS X's 16bit coordinate system. + + Sets the geometry of the widget to data.crect, but clipped to sizes + that OS X can handle. Unmaps widgets that are completely outside the + valid range. + + Maintains data.wrect, which is the geometry of the OS X widget, + measured in this widget's coordinate system. + + if the parent is not clipped, parentWRect is empty, otherwise + parentWRect is the geometry of the parent's OS X rect, measured in + parent's coord sys +*/ +void QWidgetPrivate::setWSGeometry(bool dontShow, const QRect &oldRect) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if (!q->internalWinId() && QApplicationPrivate::graphicsSystem() != 0) { + // We have no view to move, and no paint engine that + // we can update dirty regions on. So just return: + return; + } + + QMacCocoaAutoReleasePool pool; + + /* + There are up to four different coordinate systems here: + Qt coordinate system for this widget. + X coordinate system for this widget (relative to wrect). + Qt coordinate system for parent + X coordinate system for parent (relative to parent's wrect). + */ + + // wrect is the same as crect, except that it is + // clipped to fit inside parent (and screen): + QRect wrect; + + // wrectInParentCoordSys will be the same as wrect, except that it is + // originated in q's parent rather than q itself. It starts out in + // parent's Qt coord system, and ends up in parent's coordinate system: + QRect wrectInParentCoordSys = data.crect; + + // If q's parent has been clipped, parentWRect will + // be filled with the parents clipped crect: + QRect parentWRect; + + // Embedded have different meaning on each platform, and on + // Mac, it means that q is a QMacNativeWidget. + bool isEmbeddedWindow = (q->isWindow() && topData()->embedded); +#ifdef QT_MAC_USE_COCOA + NSView *nsview = qt_mac_nativeview_for(q); +#endif + if (!isEmbeddedWindow) { + parentWRect = q->parentWidget()->data->wrect; + } else { + // INVARIANT: q's parent view is not owned by Qt. So we need to + // do some extra calls to get the clipped rect of the parent view: +#ifndef QT_MAC_USE_COCOA + HIViewRef parentView = HIViewGetSuperview(qt_mac_nativeview_for(q)); +#else + NSView *parentView = [qt_mac_nativeview_for(q) superview]; +#endif + if (parentView) { +#ifndef QT_MAC_USE_COCOA + HIRect tmpRect; + HIViewGetFrame(parentView, &tmpRect); +#else + NSRect tmpRect = [parentView frame]; +#endif + parentWRect = QRect(tmpRect.origin.x, tmpRect.origin.y, + tmpRect.size.width, tmpRect.size.height); + } else { + const QRect wrectRange(-WRECT_MAX,-WRECT_MAX, 2*WRECT_MAX, 2*WRECT_MAX); + parentWRect = wrectRange; + } + } + + if (parentWRect.isValid()) { + // INVARIANT: q's parent has been clipped. + // So we fit our own wrects inside it: + if (!parentWRect.contains(wrectInParentCoordSys) && !isEmbeddedWindow) { + wrectInParentCoordSys &= parentWRect; + wrect = wrectInParentCoordSys; + // Make sure wrect is originated in q's coordinate system: + wrect.translate(-data.crect.topLeft()); + } + // // Make sure wrectInParentCoordSys originated in q's parent coordinate system: + wrectInParentCoordSys.translate(-parentWRect.topLeft()); + } else { + // INVARIANT: we dont know yet the clipping rect of q's parent. + // So we may or may not have to adjust our wrects: + + if (data.wrect.isValid() && QRect(QPoint(),data.crect.size()).contains(data.wrect)) { + // This is where the main optimization is: we have an old wrect from an earlier + // setGeometry call, and the new crect is smaller than it. If the final wrect is + // also inside the old wrect, we can just move q and its children to the new + // location without any clipping: + + // vrect will be the part of q that's will be visible inside + // q's parent. If it inside the old wrect, then we can just move: + QRect vrect = wrectInParentCoordSys & q->parentWidget()->rect(); + vrect.translate(-data.crect.topLeft()); + + if (data.wrect.contains(vrect)) { + wrectInParentCoordSys = data.wrect; + wrectInParentCoordSys.translate(data.crect.topLeft()); +#ifndef QT_MAC_USE_COCOA + HIRect bounds = CGRectMake(wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + HIViewSetFrame(qt_mac_nativeview_for(q), &bounds); +#else + if (nsview) { + // INVARIANT: q is native. Set view frame: + NSRect bounds = NSMakeRect(wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + [nsview setFrame:bounds]; + } else { + // INVARIANT: q is alien. Repaint wrect instead (includes old and new wrect): + QWidget *parent = q->parentWidget(); + QPoint globalPosWRect = parent->mapToGlobal(data.wrect.topLeft()); + + QWidget *nativeParent = q->nativeParentWidget(); + QRect dirtyWRect = QRect(nativeParent->mapFromGlobal(globalPosWRect), data.wrect.size()); + + nativeParent->update(dirtyWRect); + } +#endif + if (q->testAttribute(Qt::WA_OutsideWSRange)) { + q->setAttribute(Qt::WA_OutsideWSRange, false); + if (!dontShow) { + q->setAttribute(Qt::WA_Mapped); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:NO]; +#endif + } + } + return; + } + } + +#ifndef QT_MAC_USE_COCOA + const QRect validRange(-XCOORD_MAX,-XCOORD_MAX, 2*XCOORD_MAX, 2*XCOORD_MAX); + if (!validRange.contains(wrectInParentCoordSys)) { + // We're too big, and must clip: + QPoint screenOffset(0, 0); // offset of the part being on screen + const QWidget *parentWidget = q->parentWidget(); + while (parentWidget && !parentWidget->isWindow()) { + screenOffset -= parentWidget->data->crect.topLeft(); + parentWidget = parentWidget->parentWidget(); + } + QRect cropRect(screenOffset.x() - WRECT_MAX, + screenOffset.y() - WRECT_MAX, + 2*WRECT_MAX, + 2*WRECT_MAX); + + wrectInParentCoordSys &=cropRect; + wrect = wrectInParentCoordSys; + wrect.translate(-data.crect.topLeft()); + } +#endif //QT_MAC_USE_COCOA + } + + // unmap if we are outside the valid window system coord system + bool outsideRange = !wrectInParentCoordSys.isValid(); + bool mapWindow = false; + if (q->testAttribute(Qt::WA_OutsideWSRange) != outsideRange) { + q->setAttribute(Qt::WA_OutsideWSRange, outsideRange); + if (outsideRange) { +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), false); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:YES]; +#endif + q->setAttribute(Qt::WA_Mapped, false); + } else if (!q->isHidden()) { + mapWindow = true; + } + } + + if (outsideRange) + return; + + // Store the new clipped rect: + bool jump = (data.wrect != wrect); + data.wrect = wrect; + + // and now recursively for all children... + // ### can be optimized + for (int i = 0; i < children.size(); ++i) { + QObject *object = children.at(i); + if (object->isWidgetType()) { + QWidget *w = static_cast<QWidget *>(object); + if (!w->isWindow() && w->testAttribute(Qt::WA_WState_Created)) + w->d_func()->setWSGeometry(); + } + } + +#ifndef QT_MAC_USE_COCOA + // Move the actual HIView: + qt_mac_update_widget_position(q, oldRect, wrectInParentCoordSys); + if (jump) + q->update(); +#else + if (nsview) { + // INVARIANT: q is native. Move the actual NSView: + NSRect bounds = NSMakeRect( + wrectInParentCoordSys.x(), wrectInParentCoordSys.y(), + wrectInParentCoordSys.width(), wrectInParentCoordSys.height()); + [nsview setFrame:bounds]; + if (jump) + q->update(); + } else if (QApplicationPrivate::graphicsSystem() == 0){ + // INVARIANT: q is alien and we use native paint engine. + // Schedule updates where q is moved from and to: + const QWidget *parent = q->parentWidget(); + const QPoint globalPosOldWRect = parent->mapToGlobal(oldRect.topLeft()); + const QPoint globalPosNewWRect = parent->mapToGlobal(wrectInParentCoordSys.topLeft()); + + QWidget *nativeParent = q->nativeParentWidget(); + const QRegion dirtyOldWRect = QRect(nativeParent->mapFromGlobal(globalPosOldWRect), oldRect.size()); + const QRegion dirtyNewWRect = QRect(nativeParent->mapFromGlobal(globalPosNewWRect), wrectInParentCoordSys.size()); + + const bool sizeUnchanged = oldRect.size() == wrectInParentCoordSys.size(); + const bool posUnchanged = oldRect.topLeft() == wrectInParentCoordSys.topLeft(); + + // Resolve/minimize the region that needs to update: + if (sizeUnchanged && q->testAttribute(Qt::WA_OpaquePaintEvent)) { + // INVARIANT: q is opaque, and is only moved (not resized). So in theory we only + // need to blit pixels, and skip a repaint. But we can only make this work if we + // had access to the backbuffer, so we need to update all: + nativeParent->update(dirtyOldWRect | dirtyNewWRect); + } else if (posUnchanged && q->testAttribute(Qt::WA_StaticContents)) { + // We only need to redraw exposed areas: + nativeParent->update(dirtyNewWRect - dirtyOldWRect); + } else { + nativeParent->update(dirtyOldWRect | dirtyNewWRect); + } + } +#endif + + if (mapWindow && !dontShow) { + q->setAttribute(Qt::WA_Mapped); +#ifndef QT_MAC_USE_COCOA + HIViewSetVisible(qt_mac_nativeview_for(q), true); +#else + // If q is Alien, the following call does nothing: + [nsview setHidden:NO]; +#endif + } +} + +void QWidgetPrivate::adjustWithinMaxAndMinSize(int &w, int &h) +{ + if (QWExtra *extra = extraData()) { + w = qMin(w, extra->maxw); + h = qMin(h, extra->maxh); + w = qMax(w, extra->minw); + h = qMax(h, extra->minh); + + // Deal with size increment + if (QTLWExtra *top = topData()) { + if(top->incw) { + w = w/top->incw; + w *= top->incw; + } + if(top->inch) { + h = h/top->inch; + h *= top->inch; + } + } + } + + if (isRealWindow()) { + w = qMax(0, w); + h = qMax(0, h); + } +} + +void QWidgetPrivate::applyMaxAndMinSizeOnWindow() +{ + Q_Q(QWidget); + QMacCocoaAutoReleasePool pool; + + const float max_f(20000); +#ifndef QT_MAC_USE_COCOA +#define SF(x) ((x > max_f) ? max_f : x) + HISize max = CGSizeMake(SF(extra->maxw), SF(extra->maxh)); + HISize min = CGSizeMake(SF(extra->minw), SF(extra->minh)); +#undef SF + SetWindowResizeLimits(qt_mac_window_for(q), &min, &max); +#else +#define SF(x) ((x > max_f) ? max_f : x) + NSSize max = NSMakeSize(SF(extra->maxw), SF(extra->maxh)); + NSSize min = NSMakeSize(SF(extra->minw), SF(extra->minh)); +#undef SF + [qt_mac_window_for(q) setContentMinSize:min]; + [qt_mac_window_for(q) setContentMaxSize:max]; +#endif +} + +void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + Q_ASSERT(q->testAttribute(Qt::WA_WState_Created)); + + if(q->windowType() == Qt::Desktop) + return; + + QMacCocoaAutoReleasePool pool; + bool realWindow = isRealWindow(); + + if (realWindow && !q->testAttribute(Qt::WA_DontShowOnScreen)){ + adjustWithinMaxAndMinSize(w, h); +#ifndef QT_MAC_USE_COCOA + if (w != 0 && h != 0) { + topData()->isSetGeometry = 1; + topData()->isMove = isMove; + Rect r; SetRect(&r, x, y, x + w, y + h); + SetWindowBounds(qt_mac_window_for(q), kWindowContentRgn, &r); + topData()->isSetGeometry = 0; + } else { + setGeometry_sys_helper(x, y, w, h, isMove); + } +#else + if (!isMove && !q->testAttribute(Qt::WA_Moved) && !q->isVisible()) { + // INVARIANT: The location of the window has not yet been set. The default will + // instead be to center it on the desktop, or over the parent, if any. Since we now + // resize the window, we need to adjust the top left position to keep the window + // centeralized. And we need to to this now (and before show) in case the positioning + // of other windows (e.g. sub-windows) depend on this position: + if (QWidget *p = q->parentWidget()) { + x = p->geometry().center().x() - (w / 2); + y = p->geometry().center().y() - (h / 2); + } else { + QRect availGeo = QApplication::desktop()->availableGeometry(q); + x = availGeo.center().x() - (w / 2); + y = availGeo.center().y() - (h / 2); + } + } + + QSize olds = q->size(); + const bool isResize = (olds != QSize(w, h)); + NSWindow *window = qt_mac_window_for(q); + const QRect &fStrut = frameStrut(); + const QRect frameRect(QPoint(x - fStrut.left(), y - fStrut.top()), + QSize(fStrut.left() + fStrut.right() + w, + fStrut.top() + fStrut.bottom() + h)); + NSRect cocoaFrameRect = NSMakeRect(frameRect.x(), flipYCoordinate(frameRect.bottom() + 1), + frameRect.width(), frameRect.height()); + // The setFrame call will trigger a 'windowDidResize' notification for the corresponding + // NSWindow. The pending flag is set, so that the resize event can be send as non-spontaneous. + if (isResize) + q->setAttribute(Qt::WA_PendingResizeEvent); + QPoint currTopLeft = data.crect.topLeft(); + if (currTopLeft.x() == x && currTopLeft.y() == y + && cocoaFrameRect.size.width != 0 + && cocoaFrameRect.size.height != 0) { + [window setFrame:cocoaFrameRect display:realWindow]; + } else { + // The window is moved and resized (or resized to zero). + // Since Cocoa usually only sends us a resize callback after + // setting a window frame, we issue an explicit move as + // well. To stop Cocoa from optimize away the move (since the move + // would have the same origin as the setFrame call) we shift the + // window back and forth inbetween. + cocoaFrameRect.origin.y += 1; + [window setFrame:cocoaFrameRect display:realWindow]; + cocoaFrameRect.origin.y -= 1; + [window setFrameOrigin:cocoaFrameRect.origin]; + } +#endif + } else { + setGeometry_sys_helper(x, y, w, h, isMove); + } + + topLevelAt_cache = 0; +} + +void QWidgetPrivate::setGeometry_sys_helper(int x, int y, int w, int h, bool isMove) +{ + Q_Q(QWidget); + bool realWindow = isRealWindow(); + + QPoint oldp = q->pos(); + QSize olds = q->size(); + const bool isResize = (olds != QSize(w, h)); + + if (!realWindow && !isResize && QPoint(x, y) == oldp) + return; + + if (isResize) + data.window_state = data.window_state & ~Qt::WindowMaximized; + + const bool visible = q->isVisible(); + // Apply size restrictions, applicable for Windows & Widgets. + if (QWExtra *extra = extraData()) { + w = qMin(w, extra->maxw); + h = qMin(h, extra->maxh); + w = qMax(w, extra->minw); + h = qMax(h, extra->minh); + } + data.crect = QRect(x, y, w, h); + + if (realWindow) { + adjustWithinMaxAndMinSize(w, h); + qt_mac_update_sizer(q); + +#ifndef QT_MAC_USE_COCOA + if (q->windowFlags() & Qt::WindowMaximizeButtonHint) { + OSWindowRef window = qt_mac_window_for(q); + if (extra->maxw && extra->maxh && extra->maxw == extra->minw + && extra->maxh == extra->minh) { + ChangeWindowAttributes(window, kWindowNoAttributes, kWindowFullZoomAttribute); + } else { + ChangeWindowAttributes(window, kWindowFullZoomAttribute, kWindowNoAttributes); + } + } + HIRect bounds = CGRectMake(0, 0, w, h); + HIViewSetFrame(qt_mac_nativeview_for(q), &bounds); +#else + [qt_mac_nativeview_for(q) setFrame:NSMakeRect(0, 0, w, h)]; +#endif + } else { + const QRect oldRect(oldp, olds); + if (!isResize && QApplicationPrivate::graphicsSystem()) + moveRect(oldRect, x - oldp.x(), y - oldp.y()); + + setWSGeometry(false, oldRect); + + if (isResize && QApplicationPrivate::graphicsSystem()) + invalidateBuffer_resizeHelper(oldp, olds); + } + + if(isMove || isResize) { + if(!visible) { + if(isMove && q->pos() != oldp) + q->setAttribute(Qt::WA_PendingMoveEvent, true); + if(isResize) + q->setAttribute(Qt::WA_PendingResizeEvent, true); + } else { + if(isResize) { //send the resize event.. + QResizeEvent e(q->size(), olds); + QApplication::sendEvent(q, &e); + } + if(isMove && q->pos() != oldp) { //send the move event.. + QMoveEvent e(q->pos(), oldp); + QApplication::sendEvent(q, &e); + } + } + } + qt_event_request_window_change(q); +} + +void QWidgetPrivate::setConstraints_sys() +{ + updateMaximizeButton_sys(); + applyMaxAndMinSizeOnWindow(); +} + +void QWidgetPrivate::updateMaximizeButton_sys() +{ + Q_Q(QWidget); + if (q->data->window_flags & Qt::CustomizeWindowHint) + return; + + OSWindowRef window = qt_mac_window_for(q); + QTLWExtra * tlwExtra = topData(); +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + NSButton *maximizeButton = [window standardWindowButton:NSWindowZoomButton]; +#endif + if (extra->maxw && extra->maxh + && extra->maxw == extra->minw + && extra->maxh == extra->minh) { + // The window has a fixed size, so gray out the maximize button: + if (!tlwExtra->savedWindowAttributesFromMaximized) { +#ifndef QT_MAC_USE_COCOA + GetWindowAttributes(window, + (WindowAttributes*)&extra->topextra->savedWindowAttributesFromMaximized); + +#else + tlwExtra->savedWindowAttributesFromMaximized = (![maximizeButton isHidden] && [maximizeButton isEnabled]); +#endif + } +#ifndef QT_MAC_USE_COCOA + ChangeWindowAttributes(window, kWindowNoAttributes, kWindowFullZoomAttribute); +#else + [maximizeButton setEnabled:NO]; +#endif + + + } else { + if (tlwExtra->savedWindowAttributesFromMaximized) { +#ifndef QT_MAC_USE_COCOA + ChangeWindowAttributes(window, + extra->topextra->savedWindowAttributesFromMaximized, + kWindowNoAttributes); +#else + [maximizeButton setEnabled:YES]; +#endif + tlwExtra->savedWindowAttributesFromMaximized = 0; + } + } + + +} + +void QWidgetPrivate::scroll_sys(int dx, int dy) +{ + if (QApplicationPrivate::graphicsSystem() && !paintOnScreen()) { + // INVARIANT: Alien paint engine + scrollChildren(dx, dy); + scrollRect(q_func()->rect(), dx, dy); + } else { + scroll_sys(dx, dy, QRect()); + } +} + +void QWidgetPrivate::scroll_sys(int dx, int dy, const QRect &qscrollRect) +{ + if (QMacScrollOptimization::delayScroll(this, dx, dy, qscrollRect)) + return; + + Q_Q(QWidget); + if (QApplicationPrivate::graphicsSystem() && !paintOnScreen()) { + // INVARIANT: Alien paint engine + scrollRect(qscrollRect, dx, dy); + return; + } + + static int accelEnv = -1; + if (accelEnv == -1) { + accelEnv = qgetenv("QT_NO_FAST_SCROLL").toInt() == 0; + } + + // Scroll the whole widget if qscrollRect is not valid: + QRect validScrollRect = qscrollRect.isValid() ? qscrollRect : q->rect(); + validScrollRect &= clipRect(); + + // If q is overlapped by other widgets, we cannot just blit pixels since + // this will move overlapping widgets as well. In case we just update: + const bool overlapped = isOverlapped(validScrollRect.translated(data.crect.topLeft())); + const bool accelerateScroll = accelEnv && isOpaque && !overlapped; + const bool isAlien = (q->internalWinId() == 0); + const QPoint scrollDelta(dx, dy); + + // If qscrollRect is valid, we are _not_ supposed to scroll q's children (as documented). + // But we do scroll children (and the whole of q) if qscrollRect is invalid. This case is + // documented as undefined, but we exploit it to help factor our code into one function. + const bool scrollChildren = !qscrollRect.isValid(); + + if (!q->updatesEnabled()) { + // We are told not to update anything on q at this point. So unless + // we are supposed to scroll children, we bail out early: + if (!scrollChildren || q->children().isEmpty()) + return; + } + + if (!accelerateScroll) { + if (overlapped) { + QRegion region(validScrollRect); + subtractOpaqueSiblings(region); + update_sys(region); + }else { + update_sys(qscrollRect); + } + return; + } + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; +#else + Q_UNUSED(isAlien); + // We're not sure what the following call is supposed to achive + // but until we see what it breaks, we don't bring it into the + // Cocoa port: + qt_event_request_window_change(q); +#endif + + // First move all native children. Alien children will indirectly be + // moved when the parent is scrolled. All directly or indirectly moved + // children will receive a move event before the function call returns. + QWidgetList movedChildren; + if (scrollChildren) { + QObjectList children = q->children(); + + for (int i=0; i<children.size(); i++) { + QObject *obj = children.at(i); + if (QWidget *w = qobject_cast<QWidget*>(obj)) { + if (!w->isWindow()) { + w->data->crect = QRect(w->pos() + scrollDelta, w->size()); +#ifndef QT_MAC_USE_COCOA + if (w->testAttribute(Qt::WA_WState_Created)) { + HIRect bounds = CGRectMake(w->data->crect.x(), w->data->crect.y(), + w->data->crect.width(), w->data->crect.height()); + HIViewRef hiview = qt_mac_nativeview_for(w); + const bool opaque = q->testAttribute(Qt::WA_OpaquePaintEvent); + + if (opaque) + HIViewSetDrawingEnabled(hiview, false); + HIViewSetFrame(hiview, &bounds); + if (opaque) + HIViewSetDrawingEnabled(hiview, true); + } +#else + if (NSView *view = qt_mac_nativeview_for(w)) { + // INVARIANT: w is not alien + [view setFrame:NSMakeRect( + w->data->crect.x(), w->data->crect.y(), + w->data->crect.width(), w->data->crect.height())]; + } +#endif + movedChildren.append(w); + } + } + } + } + + if (q->testAttribute(Qt::WA_WState_Created) && q->isVisible()) { + // Scroll q itself according to the qscrollRect, and + // call update on any exposed areas so that they get redrawn: + +#ifndef QT_MAC_USE_COCOA + OSViewRef view = qt_mac_nativeview_for(q); + HIRect scrollrect = CGRectMake(qscrollRect.x(), qscrollRect.y(), qscrollRect.width(), qscrollRect.height()); + OSStatus err = _HIViewScrollRectWithOptions(view, qscrollRect.isValid() ? &scrollrect : 0, dx, dy, kHIViewScrollRectAdjustInvalid); + if (err) { + // The only parameter that can go wrong, is the rect. + qWarning("QWidget::scroll: Your rectangle was too big for the widget, clipping rect"); + scrollrect = CGRectMake(qMax(qscrollRect.x(), 0), qMax(qscrollRect.y(), 0), + qMin(qscrollRect.width(), q->width()), qMin(qscrollRect.height(), q->height())); + _HIViewScrollRectWithOptions(view, qscrollRect.isValid() ? &scrollrect : 0, dx, dy, kHIViewScrollRectAdjustInvalid); + } +#else + + QWidget *nativeWidget = isAlien ? q->nativeParentWidget() : q; + if (!nativeWidget) + return; + OSViewRef view = qt_mac_nativeview_for(nativeWidget); + if (!view) + return; + + // Calculate the rectangles that needs to be redrawn + // after the scroll. This will be source rect minus destination rect: + QRect deltaXRect; + if (dx != 0) { + deltaXRect.setY(validScrollRect.y()); + deltaXRect.setHeight(validScrollRect.height()); + if (dx > 0) { + deltaXRect.setX(validScrollRect.x()); + deltaXRect.setWidth(dx); + } else { + deltaXRect.setX(validScrollRect.x() + validScrollRect.width() + dx); + deltaXRect.setWidth(-dx); + } + } + + QRect deltaYRect; + if (dy != 0) { + deltaYRect.setX(validScrollRect.x()); + deltaYRect.setWidth(validScrollRect.width()); + if (dy > 0) { + deltaYRect.setY(validScrollRect.y()); + deltaYRect.setHeight(dy); + } else { + deltaYRect.setY(validScrollRect.y() + validScrollRect.height() + dy); + deltaYRect.setHeight(-dy); + } + } + + if (isAlien) { + // Adjust the scroll rect to the location as seen from the native parent: + QPoint scrollTopLeftInsideNative = nativeWidget->mapFromGlobal(q->mapToGlobal(validScrollRect.topLeft())); + validScrollRect.moveTo(scrollTopLeftInsideNative); + } + + // Make the pixel copy rect within the validScrollRect bounds: + NSRect nsscrollRect = NSMakeRect( + validScrollRect.x() + (dx < 0 ? -dx : 0), + validScrollRect.y() + (dy < 0 ? -dy : 0), + validScrollRect.width() + (dx > 0 ? -dx : 0), + validScrollRect.height() + (dy > 0 ? -dy : 0)); + + NSSize deltaSize = NSMakeSize(dx, dy); + [view scrollRect:nsscrollRect by:deltaSize]; + + // Some areas inside the scroll rect might have been marked as dirty from before, which + // means that they are scheduled to be redrawn. But as we now scroll, those dirty rects + // should also move along to ensure that q receives repaints on the correct places. + // Since some of the dirty rects might lay outside, or only intersect with, the scroll + // rect, the old calls to setNeedsDisplay still makes sense. + // NB: Using [view translateRectsNeedingDisplayInRect:nsscrollRect by:deltaSize] have + // so far not been proven fruitful to solve this problem. + const QVector<QRect> &dirtyRectsToScroll = dirtyOnWidget.rects(); + for (int i=0; i<dirtyRectsToScroll.size(); ++i) { + QRect qdirtyRect = dirtyRectsToScroll[i]; + qdirtyRect.translate(dx, dy); + update_sys(qdirtyRect); + } + + // Update newly exposed areas. This will generate new dirty areas on + // q, and therefore, we do it after updating the old dirty rects above: + if (dx != 0) + update_sys(deltaXRect); + if (dy != 0) + update_sys(deltaYRect); + +#endif // QT_MAC_USE_COCOA + } + + for (int i=0; i<movedChildren.size(); i++) { + QWidget *w = movedChildren.at(i); + QMoveEvent e(w->pos(), w->pos() - scrollDelta); + QApplication::sendEvent(w, &e); + } +} + +int QWidget::metric(PaintDeviceMetric m) const +{ + switch(m) { + case PdmHeightMM: + return qRound(metric(PdmHeight) * 25.4 / qreal(metric(PdmDpiY))); + case PdmWidthMM: + return qRound(metric(PdmWidth) * 25.4 / qreal(metric(PdmDpiX))); + case PdmHeight: + case PdmWidth: +#ifndef QT_MAC_USE_COCOA + { HIRect rect; + HIViewGetFrame(qt_mac_nativeview_for(this), &rect); + if(m == PdmWidth) + return (int)rect.size.width; + return (int)rect.size.height; } +#else + if (m == PdmWidth) + return data->crect.width(); + else + return data->crect.height(); +#endif + case PdmDepth: + return 32; + case PdmNumColors: + return INT_MAX; + case PdmDpiX: + case PdmPhysicalDpiX: { + Q_D(const QWidget); + if (d->extra && d->extra->customDpiX) + return d->extra->customDpiX; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + extern float qt_mac_defaultDpi_x(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_x()); } + case PdmDpiY: + case PdmPhysicalDpiY: { + Q_D(const QWidget); + if (d->extra && d->extra->customDpiY) + return d->extra->customDpiY; + else if (d->parent) + return static_cast<QWidget *>(d->parent)->metric(m); + extern float qt_mac_defaultDpi_y(); //qpaintdevice_mac.cpp + return int(qt_mac_defaultDpi_y()); } + default: //leave this so the compiler complains when new ones are added + qWarning("QWidget::metric: Unhandled parameter %d", m); + return QPaintDevice::metric(m); + } + return 0; +} + +void QWidgetPrivate::createSysExtra() +{ +#ifdef QT_MAC_USE_COCOA + extra->imageMask = 0; +#endif +} + +void QWidgetPrivate::deleteSysExtra() +{ +#ifdef QT_MAC_USE_COCOA + if (extra->imageMask) + CFRelease(extra->imageMask); +#endif +} + +void QWidgetPrivate::createTLSysExtra() +{ + extra->topextra->resizer = 0; + extra->topextra->isSetGeometry = 0; + extra->topextra->isMove = 0; + extra->topextra->wattr = 0; + extra->topextra->wclass = 0; + extra->topextra->group = 0; + extra->topextra->windowIcon = 0; + extra->topextra->savedWindowAttributesFromMaximized = 0; +} + +void QWidgetPrivate::deleteTLSysExtra() +{ +#ifndef QT_MAC_USE_COCOA + if (extra->topextra->group) { + qt_mac_release_window_group(extra->topextra->group); + extra->topextra->group = 0; + } + if (extra->topextra->windowIcon) { + ReleaseIconRef(extra->topextra->windowIcon); + extra->topextra->windowIcon = 0; + } +#endif +} + +void QWidgetPrivate::updateFrameStrut() +{ + Q_Q(QWidget); + + QWidgetPrivate *that = const_cast<QWidgetPrivate*>(this); + + that->data.fstrut_dirty = false; + QTLWExtra *top = that->topData(); + +#if QT_MAC_USE_COCOA + // 1 Get the window frame + OSWindowRef oswnd = qt_mac_window_for(q); + NSRect frameW = [oswnd frame]; + // 2 Get the content frame - so now + NSRect frameC = [oswnd contentRectForFrameRect:frameW]; + top->frameStrut.setCoords(frameC.origin.x - frameW.origin.x, + (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height), + (frameW.origin.x + frameW.size.width) - (frameC.origin.x + frameC.size.width), + frameC.origin.y - frameW.origin.y); +#else + Rect window_r; + GetWindowStructureWidths(qt_mac_window_for(q), &window_r); + top->frameStrut.setCoords(window_r.left, window_r.top, window_r.right, window_r.bottom); +#endif +} + +void QWidgetPrivate::registerDropSite(bool on) +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; +#ifndef QT_MAC_USE_COCOA + SetControlDragTrackingEnabled(qt_mac_nativeview_for(q), on); +#else + NSWindow *win = qt_mac_window_for(q); + if (on) { + if ([win isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaWindow) class]]) + [static_cast<QT_MANGLE_NAMESPACE(QCocoaWindow) *>(win) registerDragTypes]; + else if ([win isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaPanel) class]]) + [static_cast<QT_MANGLE_NAMESPACE(QCocoaPanel) *>(win) registerDragTypes]; + } +#endif +} + +void QWidgetPrivate::registerTouchWindow(bool enable) +{ + Q_UNUSED(enable); +#ifdef QT_MAC_USE_COCOA +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_6) + return; + + Q_Q(QWidget); + if (enable == touchEventsEnabled) + return; + + QCocoaView *view = static_cast<QCocoaView *>(qt_mac_effectiveview_for(q)); + if (!view) + return; + + if (enable) { + ++view->alienTouchCount; + if (view->alienTouchCount == 1) { + touchEventsEnabled = true; + [view setAcceptsTouchEvents:YES]; + } + } else { + --view->alienTouchCount; + if (view->alienTouchCount == 0) { + touchEventsEnabled = false; + [view setAcceptsTouchEvents:NO]; + } + } +#endif +#endif +} + +void QWidgetPrivate::setMask_sys(const QRegion ®ion) +{ + Q_UNUSED(region); + Q_Q(QWidget); + +#ifndef QT_MAC_USE_COCOA + if (q->isWindow()) + ReshapeCustomWindow(qt_mac_window_for(q)); + else + HIViewReshapeStructure(qt_mac_nativeview_for(q)); +#else + if (!q->internalWinId()) + return; + + if (extra->mask.isEmpty()) { + extra->maskBits = QImage(); + finishCocoaMaskSetup(); + } else { + syncCocoaMask(); + } + + topLevelAt_cache = 0; +#endif +} + +void QWidgetPrivate::setWindowOpacity_sys(qreal level) +{ + Q_Q(QWidget); + + if (!q->isWindow()) + return; + + level = qBound(0.0, level, 1.0); + topData()->opacity = (uchar)(level * 255); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + OSWindowRef oswindow = qt_mac_window_for(q); +#if QT_MAC_USE_COCOA + [oswindow setAlphaValue:level]; +#else + SetWindowAlpha(oswindow, level); +#endif +} + +#ifdef QT_MAC_USE_COCOA +void QWidgetPrivate::syncCocoaMask() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !extra) + return; + + if (extra->hasMask) { + if(extra->maskBits.size() != q->size()) { + extra->maskBits = QImage(q->size(), QImage::Format_Mono); + } + extra->maskBits.fill(QColor(Qt::color1).rgba()); + extra->maskBits.setNumColors(2); + extra->maskBits.setColor(0, QColor(Qt::color0).rgba()); + extra->maskBits.setColor(1, QColor(Qt::color1).rgba()); + QPainter painter(&extra->maskBits); + painter.setBrush(Qt::color1); + painter.setPen(Qt::NoPen); + painter.drawRects(extra->mask.rects()); + painter.end(); + finishCocoaMaskSetup(); + } +} + +void QWidgetPrivate::finishCocoaMaskSetup() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created) || !extra) + return; + + // Technically this is too late to release, because the data behind the image + // has already been released. But it's more tidy to do it here. + // If you are seeing a crash, consider doing a CFRelease before changing extra->maskBits. + if (extra->imageMask) { + CFRelease(extra->imageMask); + extra->imageMask = 0; + } + + if (!extra->maskBits.isNull()) { + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(0, + extra->maskBits.bits(), + extra->maskBits.numBytes(), + 0); // shouldn't need to release. + CGFloat decode[2] = {1, 0}; + extra->imageMask = CGImageMaskCreate(extra->maskBits.width(), extra->maskBits.height(), + 1, 1, extra->maskBits.bytesPerLine(), dataProvider, + decode, false); + } + if (q->isWindow()) { + NSWindow *window = qt_mac_window_for(q); + [window setOpaque:(extra->imageMask == 0)]; + [window invalidateShadow]; + } + macSetNeedsDisplay(QRegion()); +} +#endif + +struct QPaintEngineCleanupHandler +{ + inline QPaintEngineCleanupHandler() : engine(0) {} + inline ~QPaintEngineCleanupHandler() { delete engine; } + QPaintEngine *engine; +}; + +Q_GLOBAL_STATIC(QPaintEngineCleanupHandler, engineHandler) + +QPaintEngine *QWidget::paintEngine() const +{ + QPaintEngine *&pe = engineHandler()->engine; + if (!pe) + pe = new QCoreGraphicsPaintEngine(); + if (pe->isActive()) { + QPaintEngine *engine = new QCoreGraphicsPaintEngine(); + engine->setAutoDestruct(true); + return engine; + } + return pe; +} + +void QWidgetPrivate::setModal_sys() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow()) + return; + const QWidget * const windowParent = q->window()->parentWidget(); + const QWidget * const primaryWindow = windowParent ? windowParent->window() : 0; + OSWindowRef windowRef = qt_mac_window_for(q); + +#ifdef QT_MAC_USE_COCOA + QMacCocoaAutoReleasePool pool; + bool alreadySheet = [windowRef styleMask] & NSDocModalWindowMask; + + if (windowParent && q->windowModality() == Qt::WindowModal){ + // INVARIANT: Window should be window-modal (which implies a sheet). + if (!alreadySheet) { + // NB: the following call will call setModal_sys recursivly: + recreateMacWindow(); + windowRef = qt_mac_window_for(q); + } + if ([windowRef isKindOfClass:[NSPanel class]]){ + // If the primary window of the sheet parent is a child of a modal dialog, + // the sheet parent should not be modally shaddowed. + // This goes for the sheet as well: + OSWindowRef ref = primaryWindow ? qt_mac_window_for(primaryWindow) : 0; + bool isDialog = ref ? [ref isKindOfClass:[NSPanel class]] : false; + bool worksWhenModal = isDialog ? [static_cast<NSPanel *>(ref) worksWhenModal] : false; + if (worksWhenModal) + [static_cast<NSPanel *>(windowRef) setWorksWhenModal:YES]; + } + } else { + // INVARIANT: Window shold _not_ be window-modal (and as such, not a sheet). + if (alreadySheet){ + // NB: the following call will call setModal_sys recursivly: + recreateMacWindow(); + windowRef = qt_mac_window_for(q); + } + if (q->windowModality() == Qt::NonModal + && primaryWindow && primaryWindow->windowModality() == Qt::ApplicationModal) { + // INVARIANT: Our window has a parent that is application modal. + // This means that q is supposed to be on top of this window and + // not be modally shaddowed: + if ([windowRef isKindOfClass:[NSPanel class]]) + [static_cast<NSPanel *>(windowRef) setWorksWhenModal:YES]; + } + } + +#else + const bool primaryWindowModal = primaryWindow ? primaryWindow->testAttribute(Qt::WA_ShowModal) : false; + const bool modal = q->testAttribute(Qt::WA_ShowModal); + + WindowClass old_wclass; + GetWindowClass(windowRef, &old_wclass); + + if (modal || primaryWindowModal) { + if (q->windowModality() == Qt::WindowModal + || (primaryWindow && primaryWindow->windowModality() == Qt::WindowModal)){ + // Window should be window-modal (which implies a sheet). + if (old_wclass != kSheetWindowClass){ + // We cannot convert a created window to a sheet. + // So we recreate the window: + recreateMacWindow(); + return; + } + } else { + // Window should be application-modal (which implies NOT using a sheet). + if (old_wclass == kSheetWindowClass){ + // We cannot convert a sheet to a window. + // So we recreate the window: + recreateMacWindow(); + return; + } else if (!(q->data->window_flags & Qt::CustomizeWindowHint)) { + if (old_wclass == kDocumentWindowClass || old_wclass == kFloatingWindowClass || old_wclass == kUtilityWindowClass){ + // Only change the class to kMovableModalWindowClass if the no explicit jewels + // are set (kMovableModalWindowClass can't contain them), and the current window class + // can be converted to modal (according to carbon doc). Mind the order of + // HIWindowChangeClass and ChangeWindowAttributes. + WindowGroupRef group = GetWindowGroup(windowRef); + HIWindowChangeClass(windowRef, kMovableModalWindowClass); + quint32 tmpWattr = kWindowCloseBoxAttribute | kWindowHorizontalZoomAttribute; + ChangeWindowAttributes(windowRef, tmpWattr, kWindowNoAttributes); + ChangeWindowAttributes(windowRef, kWindowNoAttributes, tmpWattr); + // If the window belongs to a qt-created group, set that group once more: + if (data.window_flags & Qt::WindowStaysOnTopHint + || q->windowType() == Qt::Popup + || q->windowType() == Qt::ToolTip) + SetWindowGroup(windowRef, group); + } + // Popups are usually handled "special" and are never modal. + Qt::WindowType winType = q->windowType(); + if (winType != Qt::Popup && winType != Qt::ToolTip) + SetWindowModality(windowRef, kWindowModalityAppModal, 0); + } + } + } else if (windowRef) { + if (old_wclass == kSheetWindowClass){ + // Converting a sheet to a window is complex. It's easier to recreate: + recreateMacWindow(); + return; + } + + SetWindowModality(windowRef, kWindowModalityNone, 0); + if (!(q->data->window_flags & Qt::CustomizeWindowHint)) { + if (q->window()->d_func()->topData()->wattr |= kWindowCloseBoxAttribute) + ChangeWindowAttributes(windowRef, kWindowCloseBoxAttribute, kWindowNoAttributes); + if (q->window()->d_func()->topData()->wattr |= kWindowHorizontalZoomAttribute) + ChangeWindowAttributes(windowRef, kWindowHorizontalZoomAttribute, kWindowNoAttributes); + if (q->window()->d_func()->topData()->wattr |= kWindowCollapseBoxAttribute) + ChangeWindowAttributes(windowRef, kWindowCollapseBoxAttribute, kWindowNoAttributes); + } + + WindowClass newClass = q->window()->d_func()->topData()->wclass; + if (old_wclass != newClass && newClass != 0){ + WindowGroupRef group = GetWindowGroup(windowRef); + HIWindowChangeClass(windowRef, newClass); + // If the window belongs to a qt-created group, set that group once more: + if (data.window_flags & Qt::WindowStaysOnTopHint + || q->windowType() == Qt::Popup + || q->windowType() == Qt::ToolTip) + SetWindowGroup(windowRef, group); + } + } + + // Make sure that HIWindowChangeClass didn't remove drag support + // or reset the opaque size grip setting: + SetAutomaticControlDragTrackingEnabledForWindow(windowRef, true); + macUpdateOpaqueSizeGrip(); +#endif +} + +void QWidgetPrivate::macUpdateHideOnSuspend() +{ + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow() || q->windowType() != Qt::Tool) + return; +#ifndef QT_MAC_USE_COCOA + if(q->testAttribute(Qt::WA_MacAlwaysShowToolWindow)) + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowHideOnSuspendAttribute); + else + ChangeWindowAttributes(qt_mac_window_for(q), kWindowHideOnSuspendAttribute, 0); +#else + if(q->testAttribute(Qt::WA_MacAlwaysShowToolWindow)) + [qt_mac_window_for(q) setHidesOnDeactivate:NO]; + else + [qt_mac_window_for(q) setHidesOnDeactivate:YES]; +#endif +} + +void QWidgetPrivate::macUpdateOpaqueSizeGrip() +{ + Q_Q(QWidget); + + if (!q->testAttribute(Qt::WA_WState_Created) || !q->isWindow()) + return; + +#ifndef QT_MAC_USE_COCOA // Growbox is always transparent on Cocoa. Can emulate with setting a QSizeGrip + HIViewRef growBox; + HIViewFindByID(HIViewGetRoot(qt_mac_window_for(q)), kHIViewWindowGrowBoxID, &growBox); + if (!growBox) + return; + HIGrowBoxViewSetTransparent(growBox, !q->testAttribute(Qt::WA_MacOpaqueSizeGrip)); +#endif +} + +void QWidgetPrivate::macUpdateSizeAttribute() +{ + Q_Q(QWidget); + QEvent event(QEvent::MacSizeChange); + QApplication::sendEvent(q, &event); + for (int i = 0; i < children.size(); ++i) { + QWidget *w = qobject_cast<QWidget *>(children.at(i)); + if (w && (!w->isWindow() || w->testAttribute(Qt::WA_WindowPropagation)) + && !q->testAttribute(Qt::WA_MacMiniSize) // no attribute set? inherit from parent + && !w->testAttribute(Qt::WA_MacSmallSize) + && !w->testAttribute(Qt::WA_MacNormalSize)) + w->d_func()->macUpdateSizeAttribute(); + } + resolveFont(); +} + +void QWidgetPrivate::macUpdateIgnoreMouseEvents() +{ +#ifndef QT_MAC_USE_COCOA // This is handled inside the mouse handler on Cocoa. + Q_Q(QWidget); + if (!q->testAttribute(Qt::WA_WState_Created)) + return; + + if(q->isWindow()) + { + if(q->testAttribute(Qt::WA_TransparentForMouseEvents)) + ChangeWindowAttributes(qt_mac_window_for(q), kWindowIgnoreClicksAttribute, 0); + else + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowIgnoreClicksAttribute); + ReshapeCustomWindow(qt_mac_window_for(q)); + } else { +#ifndef kHIViewFeatureIgnoresClicks +#define kHIViewFeatureIgnoresClicks kHIViewIgnoresClicks +#endif + if(q->testAttribute(Qt::WA_TransparentForMouseEvents)) + HIViewChangeFeatures(qt_mac_nativeview_for(q), kHIViewFeatureIgnoresClicks, 0); + else + HIViewChangeFeatures(qt_mac_nativeview_for(q), 0, kHIViewFeatureIgnoresClicks); + HIViewReshapeStructure(qt_mac_nativeview_for(q)); + } +#endif +} + +void QWidgetPrivate::macUpdateMetalAttribute() +{ + Q_Q(QWidget); + bool realWindow = isRealWindow(); + if (!q->testAttribute(Qt::WA_WState_Created) || !realWindow) + return; + + if (realWindow) { +#if QT_MAC_USE_COCOA + // Cocoa doesn't let us change the style mask once it's been changed + // So, that means we need to recreate the window. + OSWindowRef cocoaWindow = qt_mac_window_for(q); + if ([cocoaWindow styleMask] & NSTexturedBackgroundWindowMask) + return; + recreateMacWindow(); +#else + QMainWindowLayout *layout = qt_mainwindow_layout(qobject_cast<QMainWindow *>(q)); + if (q->testAttribute(Qt::WA_MacBrushedMetal)) { + if (layout) + layout->updateHIToolBarStatus(); + ChangeWindowAttributes(qt_mac_window_for(q), kWindowMetalAttribute, 0); + ChangeWindowAttributes(qt_mac_window_for(q), kWindowMetalNoContentSeparatorAttribute, 0); + } else { + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowMetalNoContentSeparatorAttribute); + ChangeWindowAttributes(qt_mac_window_for(q), 0, kWindowMetalAttribute); + if (layout) + layout->updateHIToolBarStatus(); + } +#endif + } +} + +void QWidgetPrivate::setEnabled_helper_sys(bool enable) +{ +#ifdef QT_MAC_USE_COCOA + Q_Q(QWidget); + NSView *view = qt_mac_nativeview_for(q); + if ([view isKindOfClass:[NSControl class]]) + [static_cast<NSControl *>(view) setEnabled:enable]; +#else + Q_UNUSED(enable); +#endif +} + +QT_END_NAMESPACE |