diff options
Diffstat (limited to 'src/gui/platform/darwin')
-rw-r--r-- | src/gui/platform/darwin/qappleiconengine.mm | 464 | ||||
-rw-r--r-- | src/gui/platform/darwin/qappleiconengine_p.h | 64 | ||||
-rw-r--r-- | src/gui/platform/darwin/qapplekeymapper.mm | 283 | ||||
-rw-r--r-- | src/gui/platform/darwin/qapplekeymapper_p.h | 56 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmacmime.mm | 1048 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmacmime_p.h | 99 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmacmimeregistry.mm | 118 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmacmimeregistry_p.h | 42 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmetallayer.mm | 73 | ||||
-rw-r--r-- | src/gui/platform/darwin/qmetallayer_p.h | 41 | ||||
-rw-r--r-- | src/gui/platform/darwin/qutimimeconverter.h | 62 | ||||
-rw-r--r-- | src/gui/platform/darwin/qutimimeconverter.mm | 823 |
12 files changed, 1867 insertions, 1306 deletions
diff --git a/src/gui/platform/darwin/qappleiconengine.mm b/src/gui/platform/darwin/qappleiconengine.mm new file mode 100644 index 0000000000..7e0ed184dc --- /dev/null +++ b/src/gui/platform/darwin/qappleiconengine.mm @@ -0,0 +1,464 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qappleiconengine_p.h" + +#if defined(Q_OS_MACOS) +# include <AppKit/AppKit.h> +#elif defined(QT_PLATFORM_UIKIT) +# include <UIKit/UIKit.h> +#endif + +#include <QtGui/qguiapplication.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpalette.h> +#include <QtGui/qstylehints.h> + +#include <QtGui/private/qcoregraphics_p.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +auto *loadImage(const QString &iconName) +{ + static constexpr std::pair<QLatin1StringView, NSString *> iconMap[] = { + {"address-book-new"_L1, @"book.closed"}, + {"application-exit"_L1, @"xmark.circle"}, + {"appointment-new"_L1, @"calendar.badge.plus"}, + {"call-start"_L1, @"phone.arrow.up.right"}, + {"call-stop"_L1, @"phone.down"}, + {"contact-new"_L1, @"person.crop.circle.badge.plus"}, + {"document-new"_L1, @"doc.badge.plus"}, + {"document-open"_L1, @"folder"}, + {"document-open-recent"_L1, @"doc.badge.clock"}, + {"document-page-setup"_L1, @"doc.badge.gearshape"}, + {"document-print"_L1, @"printer"}, + //{"document-print-preview"_L1, @""}, + {"document-properties"_L1, @"doc.badge.ellipsis"}, + //{"document-revert"_L1, @""}, + {"document-save"_L1, @"square.and.arrow.down"}, + //{"document-save-as"_L1, @""}, + {"document-send"_L1, @"paperplane"}, + {"edit-clear"_L1, @"xmark.circle"}, + {"edit-copy"_L1, @"doc.on.doc"}, + {"edit-cut"_L1, @"scissors"}, + {"edit-delete"_L1, @"delete.left"}, + {"edit-find"_L1, @"magnifyingglass"}, + //{"edit-find-replace"_L1, @"arrow.up.left.and.down.right.magnifyingglass"}, + {"edit-paste"_L1, @"clipboard"}, + {"edit-redo"_L1, @"arrowshape.turn.up.right"}, + //{"edit-select-all"_L1, @""}, + {"edit-undo"_L1, @"arrowshape.turn.up.left"}, + {"folder-new"_L1, @"folder.badge.plus"}, + {"format-indent-less"_L1, @"decrease.indent"}, + {"format-indent-more"_L1, @"increase.indent"}, + {"format-justify-center"_L1, @"text.aligncenter"}, + {"format-justify-fill"_L1, @"text.justify"}, + {"format-justify-left"_L1, @"text.justify.left"}, + {"format-justify-right"_L1, @"text.justify.right"}, + {"format-text-direction-ltr"_L1, @"text.justify.leading"}, + {"format-text-direction-rtl"_L1, @"text.justify.trailing"}, + {"format-text-bold"_L1, @"bold"}, + {"format-text-italic"_L1, @"italic"}, + {"format-text-underline"_L1, @"underline"}, + {"format-text-strikethrough"_L1, @"strikethrough"}, + //{"go-bottom"_L1, @""}, + {"go-down"_L1, @"arrowshape.down"}, + {"go-first"_L1, @"increase.indent"}, + {"go-home"_L1, @"house"}, + //{"go-jump"_L1, @""}, + //{"go-last"_L1, @""}, + {"go-next"_L1, @"arrowshape.right"}, + {"go-previous"_L1, @"arrowshape.left"}, + //{"go-top"_L1, @""}, + {"go-up"_L1, @"arrowshape.up"}, + {"help-about"_L1, @"info.circle"}, + //{"help-contents"_L1, @""}, + {"help-faq"_L1, @"questionmark.app"}, + {"insert-image"_L1, @"photo.badge.plus"}, + {"insert-link"_L1, @"link.badge.plus"}, + //{"insert-object"_L1, @""}, + {"insert-text"_L1, @"textformat"}, + {"list-add"_L1, @"plus.circle"}, + {"list-remove"_L1, @"minus.circle"}, + {"mail-forward"_L1, @"arrowshape.turn.up.right"}, + {"mail-mark-important"_L1, @"star"}, + {"mail-mark-junk"_L1, @"xmark.bin"}, + {"mail-mark-notjunk"_L1, @"trash.slash"}, + {"mail-mark-read"_L1, @"envelope.open"}, + {"mail-mark-unread"_L1, @"envelope.fill"}, + {"mail-message-new"_L1, @"square.and.pencil"}, + {"mail-reply-all"_L1, @"arrowshape.turn.up.left.2"}, + {"mail-reply-sender"_L1, @"arrowshape.turn.up.left"}, + {"mail-send"_L1, @"paperplane"}, + {"mail-send-receive"_L1, @"envelope.arrow.triangle.branch"}, + {"media-eject"_L1, @"eject"}, + {"media-playback-pause"_L1, @"pause"}, + {"media-playback-start"_L1, @"play"}, + {"media-playback-stop"_L1, @"stop"}, + {"media-record"_L1, @"record.circle"}, + {"media-seek-backward"_L1, @"backward"}, + {"media-seek-forward"_L1, @"forward"}, + {"media-skip-backward"_L1, @"backward.end.alt"}, + {"media-skip-forward"_L1, @"forward.end.alt"}, + {"object-flip-horizontal"_L1, @"rectangle.landscape.rotate"}, + {"object-flip-vertical"_L1, @"rectangle.portrait.rotate"}, + {"object-rotate-left"_L1, @"rotate.left"}, + {"object-rotate-right"_L1, @"rotate.right"}, + {"process-stop"_L1, @"stop.circle"}, + {"system-lock-screen"_L1, @"lock.display"}, + {"system-log-out"_L1, @"door.left.hand.open"}, + //{"system-run"_L1, @""}, + {"system-search"_L1, @"magnifyingglass"}, + //{"system-reboot"_L1, @""}, + {"system-shutdown"_L1, @"power"}, + //{"tools-check-spelling"_L1, @""}, + {"view-fullscreen"_L1, @"arrow.up.left.and.arrow.down.right"}, + {"view-refresh"_L1, @"arrow.clockwise"}, + {"view-restore"_L1, @"arrow.down.right.and.arrow.up.left"}, + //{"view-sort-ascending"_L1, @""}, + //{"view-sort-descending"_L1, @""}, + {"window-close"_L1, @"xmark.circle"}, + {"window-new"_L1, @"macwindow.badge.plus"}, + {"zoom-fit-best"_L1, @"square.arrowtriangle.4.outward"}, + {"zoom-in"_L1, @"plus.magnifyingglass"}, + //{"zoom-original"_L1, @""}, + {"zoom-out"_L1, @"minus.magnifyingglass"}, + {"process-working"_L1, @"circle.dotted"}, + //{"accessories-calculator"_L1, @""}, + //{"accessories-character-map"_L1, @""}, + {"accessories-dictionary"_L1, @"character.book.closed"}, + {"accessories-text-editor"_L1, @"textformat"}, + {"help-browser"_L1, @"folder.badge.questionmark"}, + {"multimedia-volume-control"_L1, @"speaker.wave.3"}, + {"preferences-desktop-accessibility"_L1, @"accessibility"}, + //{"preferences-desktop-font"_L1, @""}, + {"preferences-desktop-keyboard"_L1, @"keyboard.badge.ellipsis"}, + //{"preferences-desktop-locale"_L1, @""}, + //{"preferences-desktop-multimedia"_L1, @""}, + //{"preferences-desktop-screensaver"_L1, @""}, + //{"preferences-desktop-theme"_L1, @""}, + //{"preferences-desktop-wallpaper"_L1, @""}, + {"system-file-manager"_L1, @"folder.badge.gearshape"}, + //{"system-software-install"_L1, @""}, + //{"system-software-update"_L1, @""}, d + //{"utilities-system-monitor"_L1, @""}, + {"utilities-terminal"_L1, @"apple.terminal"}, + //{"applications-accessories"_L1, @""}, + //{"applications-development"_L1, @""}, + //{"applications-engineering"_L1, @""}, + {"applications-games"_L1, @"gamecontroller"}, + //{"applications-graphics"_L1, @""}, + {"applications-internet"_L1, @"network"}, + {"applications-multimedia"_L1, @"tv.and.mediabox"}, + //{"applications-office"_L1, @""}, + //{"applications-other"_L1, @""}, + {"applications-science"_L1, @"atom"}, + //{"applications-system"_L1, @""}, + //{"applications-utilities"_L1, @""}, + {"preferences-desktop"_L1, @"menubar.dock.rectangle"}, + //{"preferences-desktop-peripherals"_L1, @""}, + //{"preferences-desktop-personal"_L1, @""}, + //{"preferences-other"_L1, @""}, + //{"preferences-system"_L1, @""}, + {"preferences-system-network"_L1, @"network"}, + {"system-help"_L1, @"questionmark.diamond"}, + {"audio-card"_L1, @"waveform.circle"}, + {"audio-input-microphone"_L1, @"mic"}, + {"battery"_L1, @"battery.100percent"}, + {"camera-photo"_L1, @"camera"}, + {"camera-video"_L1, @"video"}, + {"camera-web"_L1, @"web.camera"}, + {"computer"_L1, @"desktopcomputer"}, + {"drive-harddisk"_L1, @"internaldrive"}, + {"drive-optical"_L1, @"opticaldiscdrive"}, + {"drive-removable-media"_L1, @"externaldrive"}, + {"input-gaming"_L1, @"gamecontroller"}, // "games" also using this one + {"input-keyboard"_L1, @"keyboard"}, + {"input-mouse"_L1, @"computermouse"}, + {"input-tablet"_L1, @"ipad"}, + {"media-flash"_L1, @"mediastick"}, + //{"media-floppy"_L1, @""}, + //{"media-optical"_L1, @""}, + {"media-tape"_L1, @"recordingtape"}, + //{"modem"_L1, @""}, + {"multimedia-player"_L1, @"play.rectangle"}, + {"network-wired"_L1, @"app.connected.to.app.below.fill"}, + {"network-wireless"_L1, @"wifi"}, + //{"pda"_L1, @""}, + {"phone"_L1, @"iphone"}, + {"printer"_L1, @"printer"}, + {"scanner"_L1, @"scanner"}, + {"video-display"_L1, @"play.display"}, + //{"emblem-default"_L1, @""}, + {"emblem-documents"_L1, @"doc.circle"}, + {"emblem-downloads"_L1, @"arrow.down.circle"}, + {"emblem-favorite"_L1, @"star"}, + {"emblem-important"_L1, @"exclamationmark.bubble.circle"}, + {"emblem-mail"_L1, @"envelope"}, + {"emblem-photos"_L1, @"photo.stack"}, + //{"emblem-readonly"_L1, @""}, + {"emblem-shared"_L1, @"folder.badge.person.crop"}, + {"emblem-symbolic-link"_L1, @"link.circle"}, + {"emblem-synchronized"_L1, @"arrow.triangle.2.circlepath.circle"}, + {"emblem-system"_L1, @"gear"}, + //{"emblem-unreadable"_L1, @""}, + {"folder"_L1, @"folder"}, + //{"folder-remote"_L1, @""}, + {"network-server"_L1, @"server.rack"}, + //{"network-workgroup"_L1, @""}, + //{"start-here"_L1, @""}, + {"user-bookmarks"_L1, @"bookmark.circle"}, + {"user-desktop"_L1, @"desktopcomputer"}, //"computer" also using this one + {"user-home"_L1, @"house"}, //"go-home" also using this one + {"user-trash"_L1, @"trash"}, + {"appointment-missed"_L1, @"calendar.badge.exclamationmark"}, + {"appointment-soon"_L1, @"calendar.badge.clock"}, + {"audio-volume-high"_L1, @"speaker.wave.3"}, + {"audio-volume-low"_L1, @"speaker.wave.1"}, + {"audio-volume-medium"_L1, @"speaker.wave.2"}, + {"audio-volume-muted"_L1, @"speaker.slash"}, + {"battery-caution"_L1, @"minus.plus.batteryblock.exclamationmark"}, + {"battery-low"_L1, @"battery.25percent"}, // there are different levels that can be low battery + {"dialog-error"_L1, @"exclamationmark.bubble"}, + {"dialog-information"_L1, @"info.circle"}, + {"dialog-password"_L1, @"lock"}, + {"dialog-question"_L1, @"questionmark.circle"}, + {"dialog-warning"_L1, @"exclamationmark.octagon"}, + {"folder-drag-accept"_L1, @"plus.rectangle.on.folder"}, + //{"folder-open"_L1, @""}, + {"folder-visiting"_L1, @"folder.circle"}, + {"image-loading"_L1, @"photo.circle"}, + {"image-missing"_L1, @"photo"}, + {"mail-attachment"_L1, @"paperclip"}, + {"mail-unread"_L1, @"envelope.badge"}, + {"mail-read"_L1, @"envelope.open"}, + {"mail-replied"_L1, @"arrowshape.turn.up.left"}, + //{"mail-signed"_L1, @""}, + //{"mail-signed-verified"_L1, @""}, + {"media-playlist-repeat"_L1, @"repet"}, + {"media-playlist-shuffle"_L1, @"shuffle"}, + //{"network-error"_L1, @""}, + //{"network-idle"_L1, @""}, + {"network-offline"_L1, @"network.slash"}, + //{"network-receive"_L1, @""}, + //{"network-transmit"_L1, @""}, + //{"network-transmit-receive"_L1, @""}, + //{"printer-error"_L1, @""}, + {"printer-printing"_L1, @"printer.dotmatrix.filled.and.paper"}, // not sure + {"security-high"_L1, @"lock.shield"}, + //{"security-medium"_L1, @""}, + {"security-low"_L1, @"lock.trianglebadge.exclamationmark"}, + {"software-update-available"_L1, @"arrowshape.up.circle"}, + {"software-update-urgent"_L1, @"exclamationmark.transmission"}, + {"sync-error"_L1, @"exclamationmark.arrow.triangle.2.circlepath"}, + {"sync-synchronizing"_L1, @"arrow.triangle.2.circlepath"}, + {"task-due"_L1, @"clock.badge.exclamationmark"}, + {"task-past-due"_L1, @"clock.badge.xmark"}, + {"user-available"_L1, @"person.crop.circle.badge.checkmark"}, + {"user-away"_L1, @"person.crop.circle.badge.clock"}, + //{"user-idle"_L1, @""}, + {"user-offline"_L1, @"person.crop.circle.badge.xmark"}, + //{"user-trash-full"_L1, @""}, + {"weather-clear"_L1, @"sun.max"}, + {"weather-clear-night"_L1, @"moon"}, + {"weather-few-clouds"_L1, @"cloud.sun"}, + {"weather-few-clouds-night"_L1, @"cloud.moon"}, + {"weather-fog"_L1, @"cloud.fog"}, + {"weather-overcast"_L1, @"cloud"}, + //{"weather-severe-alert"_L1, @""}, + {"weather-showers"_L1, @"cloud.rain"}, + //{"weather-showers-scattered"_L1, @""}, + {"weather-snow"_L1, @"cloud.snow"}, + {"weather-storm"_L1, @"tropicalstorm"}, + }; + const auto it = std::find_if(std::begin(iconMap), std::end(iconMap), [iconName](const auto &c){ + return c.first == iconName; + }); + NSString *systemIconName = it != std::end(iconMap) ? it->second : iconName.toNSString(); +#if defined(Q_OS_MACOS) + return [NSImage imageWithSystemSymbolName:systemIconName accessibilityDescription:nil]; +#elif defined(QT_PLATFORM_UIKIT) + return [UIImage systemImageNamed:systemIconName]; +#endif +} +} + +QAppleIconEngine::QAppleIconEngine(const QString &iconName) + : m_iconName(iconName), m_image(loadImage(iconName)) +{ + if (m_image) + [m_image retain]; +} + +QAppleIconEngine::~QAppleIconEngine() +{ + if (m_image) + [m_image release]; +} + +QIconEngine *QAppleIconEngine::clone() const +{ + return new QAppleIconEngine(m_iconName); +} + +QString QAppleIconEngine::key() const +{ + return u"QAppleIconEngine"_s; +} + +QString QAppleIconEngine::iconName() +{ + return m_iconName; +} + +bool QAppleIconEngine::isNull() +{ + return m_image == nullptr; +} + +QList<QSize> QAppleIconEngine::availableIconSizes(double aspectRatio) +{ + const qreal devicePixelRatio = qGuiApp->devicePixelRatio(); + const QList<QSize> sizes = { + {qRound(16 * devicePixelRatio), qRound(16. * devicePixelRatio / aspectRatio)}, + {qRound(32 * devicePixelRatio), qRound(32. * devicePixelRatio / aspectRatio)}, + {qRound(64 * devicePixelRatio), qRound(64. * devicePixelRatio / aspectRatio)}, + {qRound(128 * devicePixelRatio), qRound(128. * devicePixelRatio / aspectRatio)}, + {qRound(256 * devicePixelRatio), qRound(256. * devicePixelRatio / aspectRatio)}, + }; + return sizes; +} + +QList<QSize> QAppleIconEngine::availableSizes(QIcon::Mode, QIcon::State) +{ + const double aspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height; + return availableIconSizes(aspectRatio); +} + +QSize QAppleIconEngine::actualSize(const QSize &size, QIcon::Mode /*mode*/, QIcon::State /*state*/) +{ + const double inputAspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height; + const double outputAspectRatio = size.width() / size.height(); + QSize result = size; + if (outputAspectRatio > inputAspectRatio) + result.rwidth() = result.height() * inputAspectRatio; + else + result.rheight() = result.width() / inputAspectRatio; + return result; +} + +QPixmap QAppleIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return scaledPixmap(size, mode, state, 1.0); +} + +namespace { +#if defined(Q_OS_MACOS) +auto *configuredImage(const NSImage *image, const QColor &color) +{ + auto *config = [NSImageSymbolConfiguration configurationWithPointSize:48 + weight:NSFontWeightRegular + scale:NSImageSymbolScaleLarge]; + if (@available(macOS 12, *)) { + auto *primaryColor = [NSColor colorWithSRGBRed:color.redF() + green:color.greenF() + blue:color.blueF() + alpha:color.alphaF()]; + + auto *colorConfig = [NSImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor]; + config = [config configurationByApplyingConfiguration:colorConfig]; + } + + return [image imageWithSymbolConfiguration:config]; +} +#elif defined(QT_PLATFORM_UIKIT) +auto *configuredImage(const UIImage *image, const QColor &color) +{ + auto *config = [UIImageSymbolConfiguration configurationWithPointSize:48 + weight:UIImageSymbolWeightRegular + scale:UIImageSymbolScaleLarge]; + + if (@available(iOS 15, *)) { + auto *primaryColor = [UIColor colorWithRed:color.redF() + green:color.greenF() + blue:color.blueF() + alpha:color.alphaF()]; + + auto *colorConfig = [UIImageSymbolConfiguration configurationWithHierarchicalColor:primaryColor]; + config = [config configurationByApplyingConfiguration:colorConfig]; + } + return [image imageByApplyingSymbolConfiguration:config]; +} +#endif +} + +QPixmap QAppleIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) +{ + const quint64 cacheKey = calculateCacheKey(mode, state); + if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) { + const QSize paintSize = actualSize(size, mode, state); + const QSize paintOffset = paintSize != size + ? (QSizeF(size - paintSize) * 0.5).toSize() + : QSize(); + + m_pixmap = QPixmap(size * scale); + m_pixmap.setDevicePixelRatio(scale); + m_pixmap.fill(Qt::transparent); + + QPainter painter(&m_pixmap); + paint(&painter, QRect(paintOffset.width(), paintOffset.height(), + paintSize.width(), paintSize.height()), mode, state); + + m_cacheKey = cacheKey; + } + return m_pixmap; +} + +void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + QColor color; + const QPalette palette; + switch (mode) { + case QIcon::Normal: + color = palette.color(QPalette::Inactive, QPalette::Text); + break; + case QIcon::Disabled: + color = palette.color(QPalette::Disabled, QPalette::Text); + break; + case QIcon::Active: + color = palette.color(QPalette::Active, QPalette::Text); + break; + case QIcon::Selected: + color = palette.color(QPalette::Active, QPalette::HighlightedText); + break; + } + const auto *image = configuredImage(m_image, color); + + QMacCGContext ctx(painter); + +#if defined(Q_OS_MACOS) + NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES]; + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:gc]; + + const NSSize pixmapSize = NSMakeSize(rect.width(), rect.height()); + [image setSize:pixmapSize]; + const NSRect sourceRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height); + const NSRect iconRect = NSMakeRect(rect.x(), rect.y(), pixmapSize.width, pixmapSize.height); + + [image drawInRect:iconRect fromRect:sourceRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil]; + [NSGraphicsContext restoreGraphicsState]; +#elif defined(QT_PLATFORM_UIKIT) + UIGraphicsPushContext(ctx); + const CGRect cgrect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()); + [image drawInRect:cgrect]; + UIGraphicsPopContext(); +#endif +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/darwin/qappleiconengine_p.h b/src/gui/platform/darwin/qappleiconengine_p.h new file mode 100644 index 0000000000..2a4ff7fc64 --- /dev/null +++ b/src/gui/platform/darwin/qappleiconengine_p.h @@ -0,0 +1,64 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QAPPLEICONENGINE_P_H +#define QAPPLEICONENGINE_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/qiconengine.h> + +#include <QtCore/private/qcore_mac_p.h> + +Q_FORWARD_DECLARE_OBJC_CLASS(UIImage); +Q_FORWARD_DECLARE_OBJC_CLASS(NSImage); + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QAppleIconEngine : public QIconEngine +{ +public: + QAppleIconEngine(const QString &iconName); + ~QAppleIconEngine(); + QIconEngine *clone() const override; + QString key() const override; + QString iconName() override; + bool isNull() override; + + QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override; + QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override; + QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override; + void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override; + + static QList<QSize> availableIconSizes(double aspectRatio = 1.0); + +private: + static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state) + { + return (quint64(mode) << 32) | state; + } + + const QString m_iconName; +#if defined(Q_OS_MACOS) + const NSImage *m_image; +#elif defined(QT_PLATFORM_UIKIT) + const UIImage *m_image; +#endif + mutable QPixmap m_pixmap; + mutable quint64 m_cacheKey = {}; +}; + + +QT_END_NAMESPACE + +#endif // QAPPLEICONENGINE_P_H diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm index 738fab3863..b8ff5c9d6d 100644 --- a/src/gui/platform/darwin/qapplekeymapper.mm +++ b/src/gui/platform/darwin/qapplekeymapper.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qglobal.h> @@ -54,7 +18,6 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper"); Q_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys"); static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers) @@ -73,36 +36,6 @@ static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers m return swappedModifiers; } -Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters, - NSString *charactersIgnoringModifiers, QString &text) -{ - if ([characters isEqualToString:@"\t"]) { - if (qtModifiers & Qt::ShiftModifier) - return Qt::Key_Backtab; - return Qt::Key_Tab; - } else if ([characters isEqualToString:@"\r"]) { - if (qtModifiers & Qt::KeypadModifier) - return Qt::Key_Enter; - return Qt::Key_Return; - } - if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { - QChar ch; - if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) && - ([charactersIgnoringModifiers length] != 0)) { - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - } else if ([characters length] != 0) { - ch = QChar([characters characterAtIndex:0]); - } - if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) && - (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) { - text = QString::fromNSString(characters); - } - if (!ch.isNull()) - return Qt::Key(ch.toUpper().unicode()); - } - return Qt::Key_unknown; -} - #ifdef Q_OS_MACOS static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = { { NSEventModifierFlagShift, Qt::ShiftModifier }, @@ -396,11 +329,11 @@ QChar QAppleKeyMapper::toCocoaKey(Qt::Key key) { // Prioritize overloaded keys if (key == Qt::Key_Return) - return QChar(NSNewlineCharacter); + return QChar(NSCarriageReturnCharacter); if (key == Qt::Key_Backspace) return QChar(NSBackspaceCharacter); - static QHash<Qt::Key, char16_t> reverseCocoaKeys; + Q_CONSTINIT static QHash<Qt::Key, char16_t> reverseCocoaKeys; if (reverseCocoaKeys.isEmpty()) { reverseCocoaKeys.reserve(cocoaKeys.size()); for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it) @@ -420,7 +353,7 @@ Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode) // ------------------------------------------------ -Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() +Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const { return fromCocoaModifiers(NSEvent.modifierFlags); } @@ -437,7 +370,6 @@ bool QAppleKeyMapper::updateKeyboard() Q_ASSERT(source); m_currentInputSource = source; m_keyboardKind = LMGetKbdType(); - m_deadKeyState = 0; m_keyMap.clear(); @@ -508,12 +440,15 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt auto carbonModifiers = toCarbonModifiers(qtModifiers); const UInt32 modifierKeyState = (carbonModifiers >> 8) & 0xFF; + UInt32 deadKeyState = 0; static const UniCharCount maxStringLength = 10; static UniChar unicodeString[maxStringLength]; UniCharCount actualStringLength = 0; OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey, - kUCKeyActionDown, modifierKeyState, m_keyboardKind, OptionBits(0), - &m_deadKeyState, maxStringLength, &actualStringLength, unicodeString); + kUCKeyActionDown, modifierKeyState, m_keyboardKind, + kUCKeyTranslateNoDeadKeysMask, &deadKeyState, + maxStringLength, &actualStringLength, + unicodeString); // Use translated Unicode key if valid QChar carbonUnicodeKey; @@ -549,15 +484,32 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt qCDebug(lcQpaKeyMapper).verbosity(0) << "\t" << qtModifiers << "+" << qUtf8Printable(QString::asprintf("0x%02x", virtualKey)) << "=" << qUtf8Printable(QString::asprintf("%d / 0x%02x /", qtKey, qtKey)) - << QString::asprintf("%c", qtKey); + << QKeySequence(qtKey).toString(); } return keyMap; } -QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const +/* + Compute the possible key combinations that can map to the event's + virtual key and modifiers, in the current keyboard layout. + + For example, given a normal US keyboard layout, the virtual key + 23 combined with the Alt (⌥) and Shift (⇧) modifiers, can map + to the following key combinations: + + - Alt+Shift+5 + - Alt+% + - Shift+∞ + - fi + + The function builds on a key map produced by keyMapForKey(), + where each modifier-key combination has been mapped to the + key it will produce. +*/ +QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const { - QList<int> ret; + QList<QKeyCombination> ret; const auto nativeVirtualKey = event->nativeVirtualKey(); if (!nativeVirtualKey) @@ -570,21 +522,93 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const auto eventModifiers = event->modifiers(); - // The base key, with the complete set of modifiers, - // is always valid, and the first priority. - ret << int(unmodifiedKey) + int(eventModifiers); + int startingModifierLayer = 0; + if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) { + // When the Command key is pressed AppKit seems to do key equivalent + // matching using a Latin/Roman interpretation of the current keyboard + // layout. For example, for a Greek layout, pressing Option+Command+C + // produces a key event with chars="ç" and unmodchars="ψ", but AppKit + // still treats this as a match for a key equivalent of Option+Command+C. + // We can't do the same by just applying the modifiers to our key map, + // as that too contains "ψ" for the Option+Command combination. What we + // can do instead is take advantage of the fact that the Command + // modifier layer in all/most keyboard layouts contains a Latin + // layer. We then combine that with the modifiers of the event + // to produce the resulting "Latin" key combination. + static constexpr int kCommandLayer = 2; + ret << QKeyCombination::fromCombined( + int(eventModifiers) + int(keyMap[kCommandLayer])); + + // If the unmodified key is outside of Latin1, we also treat + // that as a valid key combination, even if AppKit natively + // does not. For example, for a Greek layout, we still want + // to support Option+Command+ψ as a key combination, as it's + // unlikely to clash with the Latin key combination we added + // above. + + // However, if the unmodified key is within Latin1, we skip + // it, to avoid these types of conflicts. For example, in + // the same Greek layout, pressing the key next to Tab will + // produce a Latin ';' symbol, but we've already treated that + // as 'q' above, thanks to the Command modifier, so we skip + // the potential Command+; key combination. This is also in + // line with what AppKit natively does. + + // Skipping Latin1 unmodified keys also handles the case of + // a Latin layout, where the unmodified and modified keys + // are the same. + + if (unmodifiedKey <= 0xff) + startingModifierLayer = 1; + } // FIXME: We only compute the first 8 combinations. Why? - for (int i = 1; i < 8; ++i) { + for (int i = startingModifierLayer; i < 15; ++i) { auto keyAfterApplyingModifiers = keyMap[i]; - if (keyAfterApplyingModifiers == unmodifiedKey) - continue; + if (!keyAfterApplyingModifiers) + continue; - // Include key if event modifiers includes, or matches - // perfectly, the current candidate modifiers. + // Include key if the event modifiers match exactly, + // or are a superset of the current candidate modifiers. auto candidateModifiers = modifierCombinations[i]; - if ((eventModifiers & candidateModifiers) == candidateModifiers) - ret << int(keyAfterApplyingModifiers) + int(eventModifiers & ~candidateModifiers); + if ((eventModifiers & candidateModifiers) == candidateModifiers) { + // If the event includes more modifiers than the candidate they + // will need to be included in the resulting key combination. + auto additionalModifiers = eventModifiers & ~candidateModifiers; + + auto keyCombination = QKeyCombination::fromCombined( + int(additionalModifiers) + int(keyAfterApplyingModifiers)); + + // If there's an existing key combination with the same key, + // but a different set of modifiers, we want to choose only + // one of them, by priority (see below). + const auto existingCombination = std::find_if( + ret.begin(), ret.end(), [&](auto existingCombination) { + return existingCombination.key() == keyAfterApplyingModifiers; + }); + + if (existingCombination != ret.end()) { + // We prioritize the combination with the more specific + // modifiers. In the case where the number of modifiers + // are the same, we want to prioritize Command over Option + // over Control over Shift. Unfortunately the order (and + // hence value) of the modifiers in Qt::KeyboardModifier + // does not match our preferred order when Control and + // Meta is switched, but we can work around that by + // explicitly swapping the modifiers and using that + // for the comparison. This also works when the + // Qt::AA_MacDontSwapCtrlAndMeta application attribute + // is set, as the incoming modifiers are then left + // as is, and we can still trust the order. + auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers()); + auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers); + if (replacementModifiers > existingModifiers) + *existingCombination = keyCombination; + } else { + // All is good, no existing combination has this key + ret << keyCombination; + } + } } return ret; @@ -592,36 +616,65 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const -#else -// Keyboard keys (non-modifiers) -API_AVAILABLE(ios(13.4)) static QHash<NSString *, Qt::Key> uiKitKeys = { -#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) - { UIKeyInputF1, Qt::Key_F1 }, - { UIKeyInputF2, Qt::Key_F2 }, - { UIKeyInputF3, Qt::Key_F3 }, - { UIKeyInputF4, Qt::Key_F4 }, - { UIKeyInputF5, Qt::Key_F5 }, - { UIKeyInputF6, Qt::Key_F6 }, - { UIKeyInputF7, Qt::Key_F7 }, - { UIKeyInputF8, Qt::Key_F8 }, - { UIKeyInputF9, Qt::Key_F9 }, - { UIKeyInputF10, Qt::Key_F10 }, - { UIKeyInputF11, Qt::Key_F11 }, - { UIKeyInputF12, Qt::Key_F12 }, - { UIKeyInputHome, Qt::Key_Home }, - { UIKeyInputEnd, Qt::Key_End }, - { UIKeyInputPageUp, Qt::Key_PageUp }, - { UIKeyInputPageDown, Qt::Key_PageDown }, -#endif - { UIKeyInputEscape, Qt::Key_Escape }, - { UIKeyInputUpArrow, Qt::Key_Up }, - { UIKeyInputDownArrow, Qt::Key_Down }, - { UIKeyInputLeftArrow, Qt::Key_Left }, - { UIKeyInputRightArrow, Qt::Key_Right } -}; +#else // iOS +Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters, + NSString *charactersIgnoringModifiers, QString &text) +{ + if ([characters isEqualToString:@"\t"]) { + if (qtModifiers & Qt::ShiftModifier) + return Qt::Key_Backtab; + return Qt::Key_Tab; + } else if ([characters isEqualToString:@"\r"]) { + if (qtModifiers & Qt::KeypadModifier) + return Qt::Key_Enter; + return Qt::Key_Return; + } + if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { + QChar ch; + if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) && + ([charactersIgnoringModifiers length] != 0)) { + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + } else if ([characters length] != 0) { + ch = QChar([characters characterAtIndex:0]); + } + if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) && + (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) { + text = QString::fromNSString(characters); + } + if (!ch.isNull()) + return Qt::Key(ch.toUpper().unicode()); + } + return Qt::Key_unknown; +} + +// Keyboard keys (non-modifiers) API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode) { + static QHash<NSString *, Qt::Key> uiKitKeys = { + { UIKeyInputF1, Qt::Key_F1 }, + { UIKeyInputF2, Qt::Key_F2 }, + { UIKeyInputF3, Qt::Key_F3 }, + { UIKeyInputF4, Qt::Key_F4 }, + { UIKeyInputF5, Qt::Key_F5 }, + { UIKeyInputF6, Qt::Key_F6 }, + { UIKeyInputF7, Qt::Key_F7 }, + { UIKeyInputF8, Qt::Key_F8 }, + { UIKeyInputF9, Qt::Key_F9 }, + { UIKeyInputF10, Qt::Key_F10 }, + { UIKeyInputF11, Qt::Key_F11 }, + { UIKeyInputF12, Qt::Key_F12 }, + { UIKeyInputHome, Qt::Key_Home }, + { UIKeyInputEnd, Qt::Key_End }, + { UIKeyInputPageUp, Qt::Key_PageUp }, + { UIKeyInputPageDown, Qt::Key_PageDown }, + { UIKeyInputEscape, Qt::Key_Escape }, + { UIKeyInputUpArrow, Qt::Key_Up }, + { UIKeyInputDownArrow, Qt::Key_Down }, + { UIKeyInputLeftArrow, Qt::Key_Left }, + { UIKeyInputRightArrow, Qt::Key_Right } + }; + if (auto key = uiKitKeys.value(keyCode)) return key; diff --git a/src/gui/platform/darwin/qapplekeymapper_p.h b/src/gui/platform/darwin/qapplekeymapper_p.h index 78e71ac37d..1f3494d16f 100644 --- a/src/gui/platform/darwin/qapplekeymapper_p.h +++ b/src/gui/platform/darwin/qapplekeymapper_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QAPPLEKEYMAPPER_H #define QAPPLEKEYMAPPER_H @@ -55,20 +19,22 @@ #include <Carbon/Carbon.h> #endif +#include <qpa/qplatformkeymapper.h> + #include <QtCore/QList> +#include <QtCore/QHash> #include <QtGui/QKeyEvent> #include <QtCore/private/qcore_mac_p.h> QT_BEGIN_NAMESPACE -class Q_GUI_EXPORT QAppleKeyMapper +class Q_GUI_EXPORT QAppleKeyMapper : public QPlatformKeyMapper { public: - static Qt::KeyboardModifiers queryKeyboardModifiers(); - QList<int> possibleKeys(const QKeyEvent *event) const; - static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters, - NSString *charactersIgnoringModifiers, QString &text); + Qt::KeyboardModifiers queryKeyboardModifiers() const override; + QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const override; + #ifdef Q_OS_MACOS static Qt::KeyboardModifiers fromCocoaModifiers(NSEventModifierFlags cocoaModifiers); static NSEventModifierFlags toCocoaModifiers(Qt::KeyboardModifiers); @@ -76,6 +42,9 @@ public: static QChar toCocoaKey(Qt::Key key); static Qt::Key fromCocoaKey(QChar keyCode); #else + static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters, + NSString *charactersIgnoringModifiers, QString &text); + static Qt::Key fromUIKitKey(NSString *keyCode); static Qt::KeyboardModifiers fromUIKitModifiers(ulong uikitModifiers); static ulong toUIKitModifiers(Qt::KeyboardModifiers); @@ -102,7 +71,6 @@ private: enum { NullMode, UnicodeMode, OtherMode } m_keyboardMode = NullMode; const UCKeyboardLayout *m_keyboardLayoutFormat = nullptr; KeyboardLayoutKind m_keyboardKind = kKLKCHRuchrKind; - mutable UInt32 m_deadKeyState = 0; // Maintains dead key state beween calls to UCKeyTranslate mutable QHash<VirtualKeyCode, KeyMap> m_keyMap; #endif diff --git a/src/gui/platform/darwin/qmacmime.mm b/src/gui/platform/darwin/qmacmime.mm deleted file mode 100644 index a248f8fb15..0000000000 --- a/src/gui/platform/darwin/qmacmime.mm +++ /dev/null @@ -1,1048 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <ImageIO/ImageIO.h> - -#include <QtCore/qsystemdetection.h> -#include <QtCore/qurl.h> -#include <QtGui/qimage.h> -#include <QtCore/qmimedata.h> -#include <QtCore/qstringconverter.h> - -#if defined(Q_OS_MACOS) -#import <AppKit/AppKit.h> -#else -#include <MobileCoreServices/MobileCoreServices.h> -#endif - -#if defined(QT_PLATFORM_UIKIT) -#import <UIKit/UIKit.h> -#endif - -#include "qmacmime_p.h" -#include "qguiapplication.h" -#include "private/qcore_mac_p.h" - -QT_BEGIN_NAMESPACE - -typedef QList<QMacInternalPasteboardMime*> MimeList; -Q_GLOBAL_STATIC(MimeList, globalMimeList) -Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList) - -void qt_mac_addToGlobalMimeList(QMacInternalPasteboardMime *macMime) -{ - // globalMimeList is in decreasing priority order. Recently added - // converters take prioity over previously added converters: prepend - // to the list. - globalMimeList()->prepend(macMime); -} - -void qt_mac_removeFromGlobalMimeList(QMacInternalPasteboardMime *macMime) -{ - if (!QGuiApplication::closingDown()) - globalMimeList()->removeAll(macMime); -} - -/*! - \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 -*/ -void qt_mac_registerDraggedTypes(const QStringList &types) -{ - (*globalDraggedTypesList()) += types; -} - -const QStringList& qt_mac_enabledDraggedTypes() -{ - return (*globalDraggedTypesList()); -} - -/***************************************************************************** - QDnD debug facilities - *****************************************************************************/ -//#define DEBUG_MIME_MAPS - -/*! - \class QMacInternalPasteboardMime - \internal - \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 - \inmodule QtWidgets - - 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 - \li public.utf8-plain-text - converts to "text/plain" - \li public.utf16-plain-text - converts to "text/plain" - \li public.text - converts to "text/plain" - \li public.html - converts to "text/html" - \li public.url - converts to "text/uri-list" - \li public.file-url - converts to "text/uri-list" - \li public.tiff - converts to "application/x-qt-image" - \li public.vcard - converts to "text/plain" - \li com.apple.traditional-mac-plain-text - converts to "text/plain" - \li 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. -*/ -QMacInternalPasteboardMime::QMacInternalPasteboardMime(char t) : type(t) -{ - qt_mac_addToGlobalMimeList(this); -} - -/* - Destroys a conversion object, removing it from the global - list of available convertors. -*/ -QMacInternalPasteboardMime::~QMacInternalPasteboardMime() -{ - qt_mac_removeFromGlobalMimeList(this); -} - -/* - Returns the item count for the given \a mimeData -*/ -int QMacInternalPasteboardMime::count(QMimeData *mimeData) -{ - Q_UNUSED(mimeData); - return 1; -} - -class QMacPasteboardMimeAny : public QMacInternalPasteboardMime { -private: - -public: - QMacPasteboardMimeAny() : QMacInternalPasteboardMime(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 QMacInternalPasteboardMime { -private: - -public: - QMacPasteboardMimeTypeName() : QMacInternalPasteboardMime(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(QLatin1String("x-qt-mime-type-name")).toUtf8()); - return ret; -} - -class QMacPasteboardMimePlainTextFallback : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimePlainTextFallback() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString QMacPasteboardMimePlainTextFallback::convertorName() -{ - return QLatin1String("PlainText (public.text)"); -} - -QString QMacPasteboardMimePlainTextFallback::flavorFor(const QString &mime) -{ - if (mime == QLatin1String("text/plain")) - return QLatin1String("public.text"); - return QString(); -} - -QString QMacPasteboardMimePlainTextFallback::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.text")) - return QLatin1String("text/plain"); - return QString(); -} - -bool QMacPasteboardMimePlainTextFallback::canConvert(const QString &mime, QString flav) -{ - return mime == mimeFor(flav); -} - -QVariant QMacPasteboardMimePlainTextFallback::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor) -{ - if (data.count() > 1) - qWarning("QMacPasteboardMimePlainTextFallback: Cannot handle multiple member data"); - - if (flavor == QLatin1String("public.text")) { - // Note that public.text is documented by Apple to have an undefined encoding. From - // testing it seems that utf8 is normally used, at least by Safari on iOS. - const QByteArray &firstData = data.first(); - return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault, - reinterpret_cast<const UInt8 *>(firstData.constData()), - firstData.size(), kCFStringEncodingUTF8, false))); - } else { - qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); - } - return QVariant(); -} - -QList<QByteArray> QMacPasteboardMimePlainTextFallback::convertFromMime(const QString &, QVariant data, QString flavor) -{ - QList<QByteArray> ret; - QString string = data.toString(); - if (flavor == QLatin1String("public.text")) - ret.append(string.toUtf8()); - return ret; -} - -class QMacPasteboardMimeUnicodeText : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeUnicodeText() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString 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 (mime == QLatin1String("text/plain") - && (flav == QLatin1String("public.utf8-plain-text") || (flav == QLatin1String("public.utf16-plain-text")))); -} - -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")) { - ret = QString::fromUtf8(firstData); - } else if (flavor == QLatin1String("public.utf16-plain-text")) { - QString str = QStringDecoder(QStringDecoder::Utf16)(firstData); - ret = str; - } 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")) { - QStringEncoder::Flags f; -#if defined(Q_OS_MACOS) - // Some applications such as Microsoft Excel, don't deal well with - // a BOM present, so we follow the traditional approach of Qt on - // macOS to not generate public.utf16-plain-text with a BOM. - f = QStringEncoder::Flag::Default; -#else - // Whereas iOS applications will fail to paste if we do _not_ - // include a BOM in the public.utf16-plain-text content, most - // likely due to converting the data using NSUTF16StringEncoding - // which assumes big-endian byte order if there is no BOM. - f = QStringEncoder::Flag::WriteBom; -#endif - QStringEncoder encoder(QStringEncoder::Utf16, f); - ret.append(encoder(string)); - } - return ret; -} - -class QMacPasteboardMimeHTMLText : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeHTMLText() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString 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; -} - -class QMacPasteboardMimeRtfText : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeRtfText() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString QMacPasteboardMimeRtfText::convertorName() -{ - return QLatin1String("Rtf"); -} - -QString QMacPasteboardMimeRtfText::flavorFor(const QString &mime) -{ - if (mime == QLatin1String("text/html")) - return QLatin1String("public.rtf"); - return QString(); -} - -QString QMacPasteboardMimeRtfText::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.rtf")) - return QLatin1String("text/html"); - return QString(); -} - -bool QMacPasteboardMimeRtfText::canConvert(const QString &mime, QString flav) -{ - return mime == mimeFor(flav); -} - -QVariant QMacPasteboardMimeRtfText::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"); - - // Read RTF into to NSAttributedString, then convert the string to HTML - NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.at(0).toNSData() - options:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType} - documentAttributes:nil - error:nil]; - - NSError *error; - NSRange range = NSMakeRange(0, [string length]); - NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}; - NSData *htmlData = [string dataFromRange:range documentAttributes:dict error:&error]; - return QByteArray::fromNSData(htmlData); -} - -QList<QByteArray> QMacPasteboardMimeRtfText::convertFromMime(const QString &mime, QVariant data, QString flavor) -{ - QList<QByteArray> ret; - if (!canConvert(mime, flavor)) - return ret; - - NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.toByteArray().toNSData() - options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType} - documentAttributes:nil - error:nil]; - - NSError *error; - NSRange range = NSMakeRange(0, [string length]); - NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}; - NSData *rtfData = [string dataFromRange:range documentAttributes:dict error:&error]; - ret << QByteArray::fromNSData(rtfData); - return ret; -} - -class QMacPasteboardMimeFileUri : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeFileUri() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); - int count(QMimeData *mimeData); -}; - -QString QMacPasteboardMimeFileUri::convertorName() -{ - return QLatin1String("FileURL"); -} - -QString QMacPasteboardMimeFileUri::flavorFor(const QString &mime) -{ - if (mime == QLatin1String("text/uri-list")) - return QLatin1String("public.file-url"); - return QString(); -} - -QString QMacPasteboardMimeFileUri::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.file-url")) - return QLatin1String("text/uri-list"); - return QString(); -} - -bool QMacPasteboardMimeFileUri::canConvert(const QString &mime, QString flav) -{ - return mime == QLatin1String("text/uri-list") && flav == QLatin1String("public.file-url"); -} - -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) { - const QByteArray &a = data.at(i); - NSString *urlString = [[[NSString alloc] initWithBytesNoCopy:(void *)a.data() length:a.size() - encoding:NSUTF8StringEncoding freeWhenDone:NO] autorelease]; - NSURL *nsurl = [NSURL URLWithString:urlString]; - QUrl url; - // OS X 10.10 sends file references instead of file paths - if ([nsurl isFileReferenceURL]) { - url = QUrl::fromNSURL([nsurl filePathURL]); - } else { - url = QUrl::fromNSURL(nsurl); - } - - 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() == QLatin1String("file")) { - if (url.host().isEmpty()) - url.setHost(QLatin1String("localhost")); - url.setPath(url.path().normalized(QString::NormalizationForm_D)); - } - if (url.isLocalFile()) - ret.append(url.toEncoded()); - } - return ret; -} - -int QMacPasteboardMimeFileUri::count(QMimeData *mimeData) -{ - return mimeData->urls().count(); -} - -class QMacPasteboardMimeUrl : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeUrl() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString 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() == 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 QMacInternalPasteboardMime -{ -public: - QMacPasteboardMimeVCard() : QMacInternalPasteboardMime(MIME_ALL){ } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString QMacPasteboardMimeVCard::convertorName() -{ - return QLatin1String("VCard"); -} - -bool QMacPasteboardMimeVCard::canConvert(const QString &mime, QString flav) -{ - return mimeFor(flav) == mime; -} - -QString QMacPasteboardMimeVCard::flavorFor(const QString &mime) -{ - if (mime.startsWith(QLatin1String("text/vcard"))) - return QLatin1String("public.vcard"); - return QString(); -} - -QString QMacPasteboardMimeVCard::mimeFor(QString flav) -{ - if (flav == QLatin1String("public.vcard")) - return QLatin1String("text/vcard"); - return QString(); -} - -QVariant QMacPasteboardMimeVCard::convertToMime(const QString &mime, QList<QByteArray> data, QString) -{ - QByteArray cards; - if (mime == QLatin1String("text/vcard")) { - 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/vcard")) - ret.append(data.toString().toUtf8()); - return ret; -} - -extern QImage qt_mac_toQImage(CGImageRef image); -extern CGImageRef qt_mac_toCGImage(const QImage &qImage); - -class QMacPasteboardMimeTiff : public QMacInternalPasteboardMime { -public: - QMacPasteboardMimeTiff() : QMacInternalPasteboardMime(MIME_ALL) { } - QString convertorName(); - - QString flavorFor(const QString &mime); - QString mimeFor(QString flav); - bool canConvert(const QString &mime, QString flav); - QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav); - QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav); -}; - -QString 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"); - - if (!canConvert(mime, flav)) - return QVariant(); - - QCFType<CFDataRef> tiffData = data.first().toRawCFData(); - QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); - - if (QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0)) - return QVariant(qt_mac_toQImage(image)); - - return QVariant(); -} - -QList<QByteArray> QMacPasteboardMimeTiff::convertFromMime(const QString &mime, QVariant variant, QString flav) -{ - if (!canConvert(mime, flav)) - return QList<QByteArray>(); - - QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); - QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, kUTTypeTIFF, 1, 0); - - if (!imageDestination) - return QList<QByteArray>(); - - QImage img = qvariant_cast<QImage>(variant); - NSDictionary *props = @{ - static_cast<NSString *>(kCGImagePropertyPixelWidth): @(img.width()), - static_cast<NSString *>(kCGImagePropertyPixelHeight): @(img.height()) - }; - - CGImageDestinationAddImage(imageDestination, qt_mac_toCGImage(img), static_cast<CFDictionaryRef>(props)); - CGImageDestinationFinalize(imageDestination); - - return QList<QByteArray>() << QByteArray::fromCFData(data); -} - -/*! - \internal - - This is an internal function. -*/ -void QMacInternalPasteboardMime::initializeMimeTypes() -{ - if (globalMimeList()->isEmpty()) { - // Create QMacPasteboardMimeAny first to put it at the end of globalMimeList - // with lowest priority. (the constructor prepends to the list) - new QMacPasteboardMimeAny; - - //standard types that we wrap - new QMacPasteboardMimeTiff; - new QMacPasteboardMimePlainTextFallback; - new QMacPasteboardMimeUnicodeText; - new QMacPasteboardMimeRtfText; - new QMacPasteboardMimeHTMLText; - new QMacPasteboardMimeFileUri; - new QMacPasteboardMimeUrl; - new QMacPasteboardMimeTypeName; - new QMacPasteboardMimeVCard; - } -} - -/*! - \internal -*/ -void QMacInternalPasteboardMime::destroyMimeTypes() -{ - MimeList *mimes = globalMimeList(); - while (!mimes->isEmpty()) - delete mimes->takeFirst(); -} - -/* - 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. -*/ -QMacInternalPasteboardMime* -QMacInternalPasteboardMime::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 QMacInternalPasteboardMime::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<QMacInternalPasteboardMime*> QMacInternalPasteboardMime::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 \c true if the convertor can convert (both ways) between - \a mime and \a flav; otherwise returns \c 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/platform/darwin/qmacmime_p.h b/src/gui/platform/darwin/qmacmime_p.h deleted file mode 100644 index 3082683834..0000000000 --- a/src/gui/platform/darwin/qmacmime_p.h +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QMACMIME_H -#define QMACMIME_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/qtguiglobal_p.h> - -#include <CoreFoundation/CoreFoundation.h> - -QT_BEGIN_NAMESPACE - -// Duplicate of QMacPasteboardMime in QtMacExtras. Keep in sync! -class Q_GUI_EXPORT QMacInternalPasteboardMime { - char type; -public: - enum QMacPasteboardMimeType { MIME_DND=0x01, - MIME_CLIP=0x02, - MIME_QT_CONVERTOR=0x04, - MIME_QT3_CONVERTOR=0x08, - MIME_ALL=MIME_DND|MIME_CLIP - }; - explicit QMacInternalPasteboardMime(char); - virtual ~QMacInternalPasteboardMime(); - - static void initializeMimeTypes(); - static void destroyMimeTypes(); - - static QList<QMacInternalPasteboardMime*> all(uchar); - static QMacInternalPasteboardMime *convertor(uchar, const QString &mime, QString flav); - static QString flavorToMime(uchar, QString flav); - - virtual QString convertorName() = 0; - - virtual bool canConvert(const QString &mime, QString flav) = 0; - virtual QString mimeFor(QString flav) = 0; - virtual QString flavorFor(const QString &mime) = 0; - virtual QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav) = 0; - virtual QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav) = 0; - virtual int count(QMimeData *mimeData); -}; - -Q_GUI_EXPORT void qt_mac_addToGlobalMimeList(QMacInternalPasteboardMime *macMime); -Q_GUI_EXPORT void qt_mac_removeFromGlobalMimeList(QMacInternalPasteboardMime *macMime); -Q_GUI_EXPORT void qt_mac_registerDraggedTypes(const QStringList &types); -Q_GUI_EXPORT const QStringList& qt_mac_enabledDraggedTypes(); - -QT_END_NAMESPACE - -#endif - diff --git a/src/gui/platform/darwin/qmacmimeregistry.mm b/src/gui/platform/darwin/qmacmimeregistry.mm new file mode 100644 index 0000000000..6710a0656f --- /dev/null +++ b/src/gui/platform/darwin/qmacmimeregistry.mm @@ -0,0 +1,118 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtCore/qmimedata.h> + +#include "qutimimeconverter.h" +#include "qmacmimeregistry_p.h" +#include "qguiapplication.h" +#include "private/qcore_mac_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace QMacMimeRegistry { + +typedef QList<QUtiMimeConverter*> MimeList; +Q_GLOBAL_STATIC(MimeList, globalMimeList) +Q_GLOBAL_STATIC(QStringList, globalDraggedTypesList) + +// implemented in qutimimeconverter.mm +void registerBuiltInTypes(); + +void registerDraggedTypes(const QStringList &types) +{ + (*globalDraggedTypesList()) += types; +} + +const QStringList& enabledDraggedTypes() +{ + return (*globalDraggedTypesList()); +} + +/***************************************************************************** + QDnD debug facilities + *****************************************************************************/ +//#define DEBUG_MIME_MAPS + +/*! + \class QMacMimeRegistry + \internal + \ingroup draganddrop +*/ + +/*! + \internal + + This is an internal function. +*/ +void initializeMimeTypes() +{ + if (globalMimeList()->isEmpty()) + registerBuiltInTypes(); +} + +/*! + \internal +*/ +void destroyMimeTypes() +{ + MimeList *mimes = globalMimeList(); + while (!mimes->isEmpty()) + delete mimes->takeFirst(); +} + +/* + Returns a MIME type of for scope \a scope for \a uti, or \nullptr if none exists. +*/ +QString flavorToMime(QUtiMimeConverter::HandlerScope scope, const QString &uti) +{ + const MimeList &mimes = *globalMimeList(); + for (const auto &mime : mimes) { + const bool relevantScope = mime->scope() & scope; +#ifdef DEBUG_MIME_MAPS + qDebug("QMacMimeRegistry::flavorToMime: attempting (%d) for uti %s [%s]", + relevantScope, qPrintable(uti), qPrintable((*it)->mimeForUti(uti))); +#endif + if (relevantScope) { + const QString mimeType = mime->mimeForUti(uti); + if (!mimeType.isNull()) + return mimeType; + } + } + return QString(); +} + +void registerMimeConverter(QUtiMimeConverter *macMime) +{ + // globalMimeList is in decreasing priority order. Recently added + // converters take prioity over previously added converters: prepend + // to the list. + globalMimeList()->prepend(macMime); +} + +void unregisterMimeConverter(QUtiMimeConverter *macMime) +{ + if (!QGuiApplication::closingDown()) + globalMimeList()->removeAll(macMime); +} + + +/* + Returns a list of all currently defined QUtiMimeConverter objects for scope \a scope. +*/ +QList<QUtiMimeConverter *> all(QUtiMimeConverter::HandlerScope scope) +{ + MimeList ret; + const MimeList &mimes = *globalMimeList(); + for (const auto &mime : mimes) { + if (mime->scope() & scope) + ret.append(mime); + } + return ret; +} + +} // namespace QMacMimeRegistry + +QT_END_NAMESPACE diff --git a/src/gui/platform/darwin/qmacmimeregistry_p.h b/src/gui/platform/darwin/qmacmimeregistry_p.h new file mode 100644 index 0000000000..5928b81959 --- /dev/null +++ b/src/gui/platform/darwin/qmacmimeregistry_p.h @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMACMIMEREGISTRY_H +#define QMACMIMEREGISTRY_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/qtguiglobal_p.h> +#include <QtGui/qutimimeconverter.h> + +#include <CoreFoundation/CoreFoundation.h> + +QT_BEGIN_NAMESPACE + +namespace QMacMimeRegistry { + Q_GUI_EXPORT void initializeMimeTypes(); + Q_GUI_EXPORT void destroyMimeTypes(); + + Q_GUI_EXPORT void registerMimeConverter(QUtiMimeConverter *); + Q_GUI_EXPORT void unregisterMimeConverter(QUtiMimeConverter *); + + Q_GUI_EXPORT QList<QUtiMimeConverter *> all(QUtiMimeConverter::HandlerScope scope); + Q_GUI_EXPORT QString flavorToMime(QUtiMimeConverter::HandlerScope scope, const QString &flav); + + Q_GUI_EXPORT void registerDraggedTypes(const QStringList &types); + Q_GUI_EXPORT const QStringList& enabledDraggedTypes(); +}; + +QT_END_NAMESPACE + +#endif // QMACMIMEREGISTRY_H diff --git a/src/gui/platform/darwin/qmetallayer.mm b/src/gui/platform/darwin/qmetallayer.mm new file mode 100644 index 0000000000..e8a27a7b06 --- /dev/null +++ b/src/gui/platform/darwin/qmetallayer.mm @@ -0,0 +1,73 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qmetallayer_p.h" + +#include <QtCore/qreadwritelock.h> +#include <QtCore/qrect.h> + +using namespace std::chrono_literals; + +QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcMetalLayer, "qt.gui.metal") +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@implementation QMetalLayer +{ + std::unique_ptr<QReadWriteLock> m_displayLock; +} + +- (instancetype)init +{ + if ((self = [super init])) { + m_displayLock.reset(new QReadWriteLock(QReadWriteLock::Recursive)); + self.mainThreadPresentation = nil; + } + + return self; +} + +- (QReadWriteLock &)displayLock +{ + return *m_displayLock.get(); +} + +- (void)setNeedsDisplay +{ + [self setNeedsDisplayInRect:CGRectInfinite]; +} + +- (void)setNeedsDisplayInRect:(CGRect)rect +{ + if (!self.needsDisplay) { + // We lock for writing here, blocking in case a secondary thread is in + // the middle of presenting to the layer, as we want the main thread's + // display to happen after the secondary thread finishes presenting. + qCDebug(lcMetalLayer) << "Locking" << self << "for writing" + << "due to needing display in rect" << QRectF::fromCGRect(rect); + + // For added safety, we use a 5 second timeout, and try to fail + // gracefully by not marking the layer as needing display, as + // doing so would lead us to unlock and unheld lock in displayLayer. + if (!self.displayLock.tryLockForWrite(5s)) { + qCWarning(lcMetalLayer) << "Timed out waiting for display lock"; + return; + } + } + + [super setNeedsDisplayInRect:rect]; +} + +- (id<CAMetalDrawable>)nextDrawable +{ + // Drop the presentation block early, so that if the main thread for + // some reason doesn't handle the presentation, the block won't hold on + // to a drawable unnecessarily. + self.mainThreadPresentation = nil; + return [super nextDrawable]; +} + + +@end diff --git a/src/gui/platform/darwin/qmetallayer_p.h b/src/gui/platform/darwin/qmetallayer_p.h new file mode 100644 index 0000000000..81f8760ec2 --- /dev/null +++ b/src/gui/platform/darwin/qmetallayer_p.h @@ -0,0 +1,41 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QMETALLAYER_P_H +#define QMETALLAYER_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/qtguiglobal.h> + +#include <QtCore/qreadwritelock.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/private/qcore_mac_p.h> + +#include <QuartzCore/CAMetalLayer.h> + +QT_BEGIN_NAMESPACE +class QReadWriteLock; + +QT_DECLARE_EXPORTED_QT_LOGGING_CATEGORY(lcMetalLayer, Q_GUI_EXPORT) + +QT_END_NAMESPACE + +#if defined(__OBJC__) +Q_GUI_EXPORT +#endif +QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMetalLayer, CAMetalLayer +@property (nonatomic, readonly) QT_PREPEND_NAMESPACE(QReadWriteLock) &displayLock; +@property (atomic, copy) void (^mainThreadPresentation)(); +) + +#endif // QMETALLAYER_P_H diff --git a/src/gui/platform/darwin/qutimimeconverter.h b/src/gui/platform/darwin/qutimimeconverter.h new file mode 100644 index 0000000000..e9297b5fa0 --- /dev/null +++ b/src/gui/platform/darwin/qutimimeconverter.h @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QUTIMIMECONVERTER_H +#define QUTIMIMECONVERTER_H + +#include <QtGui/qtguiglobal.h> + +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +class QByteArray; +class QString; +class QVariant; +class QMimeData; + +class Q_GUI_EXPORT QUtiMimeConverter +{ + Q_DISABLE_COPY(QUtiMimeConverter) +public: + enum class HandlerScopeFlag : uint8_t + { + DnD = 0x01, + Clipboard = 0x02, + Qt_compatible = 0x04, + Qt3_compatible = 0x08, + All = DnD|Clipboard, + AllCompatible = All|Qt_compatible + }; + Q_DECLARE_FLAGS(HandlerScope, HandlerScopeFlag) + + QUtiMimeConverter(); + virtual ~QUtiMimeConverter(); + + HandlerScope scope() const { return m_scope; } + bool canConvert(const QString &mime, const QString &uti) const { return mimeForUti(uti) == mime; } + + // for converting from Qt + virtual QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, const QString &uti) const = 0; + virtual QString utiForMime(const QString &mime) const = 0; + + // for converting to Qt + virtual QString mimeForUti(const QString &uti) const = 0; + virtual QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, const QString &uti) const = 0; + virtual int count(const QMimeData *mimeData) const; + +private: + friend class QMacMimeTypeName; + friend class QMacMimeAny; + + explicit QUtiMimeConverter(HandlerScope scope); + + const HandlerScope m_scope; +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QUtiMimeConverter::HandlerScope) + + +QT_END_NAMESPACE + +#endif + diff --git a/src/gui/platform/darwin/qutimimeconverter.mm b/src/gui/platform/darwin/qutimimeconverter.mm new file mode 100644 index 0000000000..ee643fd0c6 --- /dev/null +++ b/src/gui/platform/darwin/qutimimeconverter.mm @@ -0,0 +1,823 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <ImageIO/ImageIO.h> +#include <CoreFoundation/CoreFoundation.h> +#include <UniformTypeIdentifiers/UTCoreTypes.h> + +#include <QtCore/qsystemdetection.h> +#include <QtCore/qurl.h> +#include <QtGui/qimage.h> +#include <QtCore/qmimedata.h> +#include <QtCore/qstringconverter.h> + +#if defined(Q_OS_MACOS) +#import <AppKit/AppKit.h> +#else +#include <MobileCoreServices/MobileCoreServices.h> +#endif + +#if defined(QT_PLATFORM_UIKIT) +#import <UIKit/UIKit.h> +#endif + +#include "qutimimeconverter.h" +#include "qmacmimeregistry_p.h" +#include "qguiapplication.h" +#include "private/qcore_mac_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/***************************************************************************** + QDnD debug facilities + *****************************************************************************/ +//#define DEBUG_MIME_MAPS + +/*! + \class QUtiMimeConverter + \brief The QUtiMimeConverter class converts between a MIME type and a + \l{https://developer.apple.com/documentation/uniformtypeidentifiers} + {Uniform Type Identifier (UTI)} format. + \since 6.5 + + \ingroup draganddrop + \inmodule QtGui + + 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. + + QUtiMimeConverter'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. + + Construct an instance of your converter implementation after instantiating + QGuiApplication: + + \code + int main(int argc, char **argv) + { + QGuiApplication app(argc, argv); + JsonMimeConverter jsonConverter; + } + \endcode + + Destroying the instance will unregister the converter and remove support + for the conversion. It is also valid to heap-allocate the converter + instance; Qt takes ownership and will delete the converter object during + QGuiApplication shut-down. + + Qt has predefined support for the following UTIs: + \list + \li public.utf8-plain-text - converts to "text/plain" + \li public.utf16-plain-text - converts to "text/plain" + \li public.text - converts to "text/plain" + \li public.html - converts to "text/html" + \li public.url - converts to "text/uri-list" + \li public.file-url - converts to "text/uri-list" + \li public.tiff - converts to "application/x-qt-image" + \li public.vcard - converts to "text/plain" + \li com.apple.traditional-mac-plain-text - converts to "text/plain" + \li com.apple.pict - converts to "application/x-qt-image" + \endlist + + When working with MIME data, Qt will iterate through all instances of QUtiMimeConverter to find + find an instance that can convert to, or from, a specific MIME type. It will do this by calling + mimeForUti() or utiForMime() on each instance, starting with (and choosing) the last created + instance first. The actual conversions will be done by using convertToMime() and convertFromMime(). +*/ + +/*! + \enum QUtiMimeConverter::HandlerScope + \internal +*/ + +/*! + \internal + Constructs a new conversion object of type \a scope, adding it to the + globally accessed list of available converters. +*/ +QUtiMimeConverter::QUtiMimeConverter(HandlerScope scope) + : m_scope(scope) +{ + QMacMimeRegistry::registerMimeConverter(this); +} + +/*! + Constructs a new conversion object and adds it to the + globally accessed list of available converters. + + Call this constructor after QGuiApplication has been created. +*/ +QUtiMimeConverter::QUtiMimeConverter() + : QUtiMimeConverter(HandlerScopeFlag::All) +{ +} + +/*! + Destroys a conversion object, removing it from the global + list of available converters. +*/ +QUtiMimeConverter::~QUtiMimeConverter() +{ + QMacMimeRegistry::unregisterMimeConverter(this); +} + +/*! + Returns the item count for the given \a mimeData +*/ +int QUtiMimeConverter::count(const QMimeData *mimeData) const +{ + Q_UNUSED(mimeData); + return 1; +} + +/*! + \fn bool QUtiMimeConverter::canConvert(const QString &mime, const QString &uti) const + + Returns \c true if the converter can convert (both ways) between + \a mime and \a uti; otherwise returns \c false. +*/ + +/*! + \fn QString QUtiMimeConverter::mimeForUti(const QString &uti) const + + Returns the MIME type used for Mac UTI \a uti, or an empty string if + this converter does not support converting from \a uti. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QString QUtiMimeConverter::utiForMime(const QString &mime) const + + Returns the Mac UTI used for MIME type \a mime, or an empty string if + this converter does not support converting from \a mime. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QVariant QUtiMimeConverter::convertToMime(const QString &mime, + const QList<QByteArray> &data, const QString &uti) const + + Returns \a data converted from Mac UTI \a uti to MIME type \a mime. + + Note that Mac UTIs must all be self-terminating. The input \a data + may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + +/*! + \fn QList<QByteArray> QUtiMimeConverter::convertFromMime(const QString &mime, + const QVariant &data, const QString & uti) const + + Returns \a data converted from MIME type \a mime to Mac UTI \a uti. + + Note that Mac UTIs must all be self-terminating. The return + value may contain trailing data. + + All subclasses must reimplement this pure virtual function. +*/ + + +class QMacMimeAny : public QUtiMimeConverter { +public: + QMacMimeAny() : QUtiMimeConverter(HandlerScopeFlag::AllCompatible) {} + + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeAny::utiForMime(const QString &mime) const +{ + // do not handle the mime type name in the drag pasteboard + if (mime == "application/x-qt-mime-type-name"_L1) + return QString(); + QString ret = "com.trolltech.anymime."_L1 + mime; + return ret.replace(u'/', "--"_L1); +} + +QString QMacMimeAny::mimeForUti(const QString &uti) const +{ + const QString any_prefix = "com.trolltech.anymime."_L1; + if (uti.size() > any_prefix.length() && uti.startsWith(any_prefix)) + return uti.mid(any_prefix.length()).replace("--"_L1, "/"_L1); + return QString(); +} + +QVariant QMacMimeAny::convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &) const +{ + if (data.count() > 1) + qWarning("QMacMimeAny: Cannot handle multiple member data"); + QVariant ret; + if (mime == "text/plain"_L1) + ret = QString::fromUtf8(data.first()); + else + ret = data.first(); + return ret; +} + +QList<QByteArray> QMacMimeAny::convertFromMime(const QString &mime, const QVariant &data, + const QString &) const +{ + QList<QByteArray> ret; + if (mime == "text/plain"_L1) + ret.append(data.toString().toUtf8()); + else + ret.append(data.toByteArray()); + return ret; +} + +class QMacMimeTypeName : public QUtiMimeConverter { +private: + +public: + QMacMimeTypeName(): QUtiMimeConverter(HandlerScopeFlag::AllCompatible) {} + + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, const QString &uti) const override; +}; + +QString QMacMimeTypeName::utiForMime(const QString &mime) const +{ + if (mime == "application/x-qt-mime-type-name"_L1) + return u"com.trolltech.qt.MimeTypeName"_s; + return QString(); +} + +QString QMacMimeTypeName::mimeForUti(const QString &) const +{ + return QString(); +} + +QVariant QMacMimeTypeName::convertToMime(const QString &, const QList<QByteArray> &, const QString &) const +{ + QVariant ret; + return ret; +} + +QList<QByteArray> QMacMimeTypeName::convertFromMime(const QString &, const QVariant &, const QString &) const +{ + QList<QByteArray> ret; + ret.append(QString("x-qt-mime-type-name"_L1).toUtf8()); + return ret; +} + +class QMacMimePlainTextFallback : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimePlainTextFallback::utiForMime(const QString &mime) const +{ + if (mime == "text/plain"_L1) + return "public.text"_L1; + return QString(); +} + +QString QMacMimePlainTextFallback::mimeForUti(const QString &uti) const +{ + if (uti == "public.text"_L1) + return "text/plain"_L1; + return QString(); +} + +QVariant +QMacMimePlainTextFallback::convertToMime(const QString &mimetype, + const QList<QByteArray> &data, const QString &uti) const +{ + if (data.count() > 1) + qWarning("QMacMimePlainTextFallback: Cannot handle multiple member data"); + + if (uti == "public.text"_L1) { + // Note that public.text is documented by Apple to have an undefined encoding. From + // testing it seems that utf8 is normally used, at least by Safari on iOS. + const QByteArray &firstData = data.first(); + return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault, + reinterpret_cast<const UInt8 *>(firstData.constData()), + firstData.size(), kCFStringEncodingUTF8, false))); + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return QVariant(); +} + +QList<QByteArray> +QMacMimePlainTextFallback::convertFromMime(const QString &, const QVariant &data, + const QString &uti) const +{ + QList<QByteArray> ret; + QString string = data.toString(); + if (uti == "public.text"_L1) + ret.append(string.toUtf8()); + return ret; +} + +class QMacMimeUnicodeText : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeUnicodeText::utiForMime(const QString &mime) const +{ + if (mime == "text/plain"_L1) + return "public.utf16-plain-text"_L1; + if (qsizetype i = mime.indexOf("charset="_L1); i >= 0) { + QString cs(mime.mid(i + 8).toLower()); + i = cs.indexOf(u';'); + if (i >= 0) + cs = cs.left(i); + if (cs == "system"_L1) + return "public.utf8-plain-text"_L1; + else if (cs == "iso-10646-ucs-2"_L1 || cs == "utf16"_L1) + return "public.utf16-plain-text"_L1; + } + return QString(); +} + +QString QMacMimeUnicodeText::mimeForUti(const QString &uti) const +{ + if (uti == "public.utf16-plain-text"_L1 || uti == "public.utf8-plain-text"_L1) + return "text/plain"_L1; + return QString(); +} + +QVariant +QMacMimeUnicodeText::convertToMime(const QString &mimetype, + const QList<QByteArray> &data, const QString &uti) const +{ + if (data.count() > 1) + qWarning("QMacMimeUnicodeText: 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 (uti == "public.utf8-plain-text"_L1) { + ret = QString::fromUtf8(firstData); + } else if (uti == "public.utf16-plain-text"_L1) { + QString str = QStringDecoder(QStringDecoder::Utf16)(firstData); + ret = str; + } else { + qWarning("QMime::convertToMime: unhandled mimetype: %s", qPrintable(mimetype)); + } + return ret; +} + +QList<QByteArray> +QMacMimeUnicodeText::convertFromMime(const QString &, const QVariant &data, + const QString &uti) const +{ + QList<QByteArray> ret; + QString string = data.toString(); + if (uti == "public.utf8-plain-text"_L1) + ret.append(string.toUtf8()); + else if (uti == "public.utf16-plain-text"_L1) { + QStringEncoder::Flags f; +#if defined(Q_OS_MACOS) + // Some applications such as Microsoft Excel, don't deal well with + // a BOM present, so we follow the traditional approach of Qt on + // macOS to not generate public.utf16-plain-text with a BOM. + f = QStringEncoder::Flag::Default; +#else + // Whereas iOS applications will fail to paste if we do _not_ + // include a BOM in the public.utf16-plain-text content, most + // likely due to converting the data using NSUTF16StringEncoding + // which assumes big-endian byte order if there is no BOM. + f = QStringEncoder::Flag::WriteBom; +#endif + QStringEncoder encoder(QStringEncoder::Utf16, f); + ret.append(encoder(string)); + } + return ret; +} + +class QMacMimeHTMLText : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeHTMLText::utiForMime(const QString &mime) const +{ + if (mime == "text/html"_L1) + return "public.html"_L1; + return QString(); +} + +QString QMacMimeHTMLText::mimeForUti(const QString &uti) const +{ + if (uti == "public.html"_L1) + return "text/html"_L1; + return QString(); +} + +QVariant +QMacMimeHTMLText::convertToMime(const QString &mimeType, + const QList<QByteArray> &data, const QString &uti) const +{ + if (!canConvert(mimeType, uti)) + return QVariant(); + if (data.count() > 1) + qWarning("QMacMimeHTMLText: Cannot handle multiple member data"); + return data.first(); +} + +QList<QByteArray> +QMacMimeHTMLText::convertFromMime(const QString &mime, + const QVariant &data, const QString &uti) const +{ + QList<QByteArray> ret; + if (!canConvert(mime, uti)) + return ret; + ret.append(data.toByteArray()); + return ret; +} + +class QMacMimeRtfText : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeRtfText::utiForMime(const QString &mime) const +{ + if (mime == "text/html"_L1) + return "public.rtf"_L1; + return QString(); +} + +QString QMacMimeRtfText::mimeForUti(const QString &uti) const +{ + if (uti == "public.rtf"_L1) + return "text/html"_L1; + return QString(); +} + +QVariant +QMacMimeRtfText::convertToMime(const QString &mimeType, + const QList<QByteArray> &data, const QString &uti) const +{ + if (!canConvert(mimeType, uti)) + return QVariant(); + if (data.count() > 1) + qWarning("QMacMimeHTMLText: Cannot handle multiple member data"); + + // Read RTF into to NSAttributedString, then convert the string to HTML + NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.at(0).toNSData() + options:@{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType} + documentAttributes:nil + error:nil]; + + NSError *error; + NSRange range = NSMakeRange(0, [string length]); + NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}; + NSData *htmlData = [string dataFromRange:range documentAttributes:dict error:&error]; + return QByteArray::fromNSData(htmlData); +} + +QList<QByteArray> +QMacMimeRtfText::convertFromMime(const QString &mime, + const QVariant &data, const QString &uti) const +{ + QList<QByteArray> ret; + if (!canConvert(mime, uti)) + return ret; + + NSAttributedString *string = [[NSAttributedString alloc] initWithData:data.toByteArray().toNSData() + options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType} + documentAttributes:nil + error:nil]; + + NSError *error; + NSRange range = NSMakeRange(0, [string length]); + NSDictionary *dict = @{NSDocumentTypeDocumentAttribute: NSRTFTextDocumentType}; + NSData *rtfData = [string dataFromRange:range documentAttributes:dict error:&error]; + ret << QByteArray::fromNSData(rtfData); + return ret; +} + +class QMacMimeFileUri : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; + int count(const QMimeData *mimeData) const override; +}; + +QString QMacMimeFileUri::utiForMime(const QString &mime) const +{ + if (mime == "text/uri-list"_L1) + return "public.file-url"_L1; + return QString(); +} + +QString QMacMimeFileUri::mimeForUti(const QString &uti) const +{ + if (uti == "public.file-url"_L1) + return "text/uri-list"_L1; + return QString(); +} + +QVariant +QMacMimeFileUri::convertToMime(const QString &mime, + const QList<QByteArray> &data, const QString &uti) const +{ + if (!canConvert(mime, uti)) + return QVariant(); + QList<QVariant> ret; + for (int i = 0; i < data.size(); ++i) { + const QByteArray &a = data.at(i); + NSString *urlString = [[[NSString alloc] initWithBytesNoCopy:(void *)a.data() length:a.size() + encoding:NSUTF8StringEncoding freeWhenDone:NO] autorelease]; + NSURL *nsurl = [NSURL URLWithString:urlString]; + QUrl url; + // OS X 10.10 sends file references instead of file paths + if ([nsurl isFileReferenceURL]) { + url = QUrl::fromNSURL([nsurl filePathURL]); + } else { + url = QUrl::fromNSURL(nsurl); + } + + if (url.host().toLower() == "localhost"_L1) + url.setHost(QString()); + + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> +QMacMimeFileUri::convertFromMime(const QString &mime, + const QVariant &data, const QString &uti) const +{ + QList<QByteArray> ret; + if (!canConvert(mime, uti)) + 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("file"_L1); + if (url.scheme() == "file"_L1) { + if (url.host().isEmpty()) + url.setHost("localhost"_L1); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + if (url.isLocalFile()) + ret.append(url.toEncoded()); + } + return ret; +} + +int QMacMimeFileUri::count(const QMimeData *mimeData) const +{ + return mimeData->urls().count(); +} + +class QMacMimeUrl : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeUrl::utiForMime(const QString &mime) const +{ + if (mime.startsWith("text/uri-list"_L1)) + return "public.url"_L1; + return QString(); +} + +QString QMacMimeUrl::mimeForUti(const QString &uti) const +{ + if (uti == "public.url"_L1) + return "text/uri-list"_L1; + return QString(); +} + +QVariant QMacMimeUrl::convertToMime(const QString &mime, + const QList<QByteArray> &data, const QString &uti) const +{ + if (!canConvert(mime, uti)) + return QVariant(); + + QList<QVariant> ret; + for (int i=0; i<data.size(); ++i) { + QUrl url = QUrl::fromEncoded(data.at(i)); + if (url.host().toLower() == "localhost"_L1) + url.setHost(QString()); + url.setPath(url.path().normalized(QString::NormalizationForm_C)); + ret.append(url); + } + return QVariant(ret); +} + +QList<QByteArray> QMacMimeUrl::convertFromMime(const QString &mime, + const QVariant &data, const QString &uti) const +{ + QList<QByteArray> ret; + if (!canConvert(mime, uti)) + 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("file"_L1); + if (url.scheme() == "file"_L1) { + if (url.host().isEmpty()) + url.setHost("localhost"_L1); + url.setPath(url.path().normalized(QString::NormalizationForm_D)); + } + ret.append(url.toEncoded()); + } + return ret; +} + +class QMacMimeVCard : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeVCard::utiForMime(const QString &mime) const +{ + if (mime.startsWith("text/vcard"_L1)) + return "public.vcard"_L1; + return QString(); +} + +QString QMacMimeVCard::mimeForUti(const QString &uti) const +{ + if (uti == "public.vcard"_L1) + return "text/vcard"_L1; + return QString(); +} + +QVariant QMacMimeVCard::convertToMime(const QString &mime, + const QList<QByteArray> &data, const QString &uti) const +{ + if (!canConvert(mime, uti)) + return QVariant(); + QByteArray cards; + if (uti == "public.vcard"_L1) { + for (int i=0; i<data.size(); ++i) + cards += data[i]; + } + return QVariant(cards); +} + +QList<QByteArray> QMacMimeVCard::convertFromMime(const QString &mime, + const QVariant &data, const QString &uti) const +{ + QList<QByteArray> ret; + if (!canConvert(mime, uti)) + return ret; + + if (mime == "text/vcard"_L1) + ret.append(data.toString().toUtf8()); + return ret; +} + +extern QImage qt_mac_toQImage(CGImageRef image); +extern CGImageRef qt_mac_toCGImage(const QImage &qImage); + +class QMacMimeTiff : public QUtiMimeConverter +{ +public: + QString utiForMime(const QString &mime) const override; + QString mimeForUti(const QString &uti) const override; + QVariant convertToMime(const QString &mime, const QList<QByteArray> &data, + const QString &uti) const override; + QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data, + const QString &uti) const override; +}; + +QString QMacMimeTiff::utiForMime(const QString &mime) const +{ + if (mime.startsWith("application/x-qt-image"_L1)) + return "public.tiff"_L1; + return QString(); +} + +QString QMacMimeTiff::mimeForUti(const QString &uti) const +{ + if (uti == "public.tiff"_L1) + return "application/x-qt-image"_L1; + return QString(); +} + +QVariant QMacMimeTiff::convertToMime(const QString &mime, + const QList<QByteArray> &data, const QString &uti) const +{ + if (data.count() > 1) + qWarning("QMacMimeTiff: Cannot handle multiple member data"); + + if (!canConvert(mime, uti)) + return QVariant(); + + QCFType<CFDataRef> tiffData = data.first().toRawCFData(); + QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithData(tiffData, 0); + + if (QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, 0)) + return QVariant(qt_mac_toQImage(image)); + + return QVariant(); +} + +QList<QByteArray> QMacMimeTiff::convertFromMime(const QString &mime, + const QVariant &variant, const QString &uti) const +{ + if (!canConvert(mime, uti)) + return QList<QByteArray>(); + + QCFType<CFMutableDataRef> data = CFDataCreateMutable(0, 0); + QCFType<CGImageDestinationRef> imageDestination = CGImageDestinationCreateWithData(data, + (CFStringRef)UTTypeTIFF.identifier, 1, 0); + + if (!imageDestination) + return QList<QByteArray>(); + + QImage img = qvariant_cast<QImage>(variant); + NSDictionary *props = @{ + static_cast<NSString *>(kCGImagePropertyPixelWidth): @(img.width()), + static_cast<NSString *>(kCGImagePropertyPixelHeight): @(img.height()) + }; + + CGImageDestinationAddImage(imageDestination, qt_mac_toCGImage(img), + static_cast<CFDictionaryRef>(props)); + CGImageDestinationFinalize(imageDestination); + + return QList<QByteArray>() << QByteArray::fromCFData(data); +} + +namespace QMacMimeRegistry { + +void registerBuiltInTypes() +{ + // Create QMacMimeAny first to put it at the end of globalMimeList + // with lowest priority. (the constructor prepends to the list) + new QMacMimeAny; + + //standard types that we wrap + new QMacMimeTiff; + new QMacMimePlainTextFallback; + new QMacMimeUnicodeText; + new QMacMimeRtfText; + new QMacMimeHTMLText; + new QMacMimeFileUri; + new QMacMimeUrl; + new QMacMimeTypeName; + new QMacMimeVCard; +} + +} + +QT_END_NAMESPACE |