Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qappleiconengine.mm
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#if defined(Q_OS_MACOS)
8# include <AppKit/AppKit.h>
9#elif defined(QT_PLATFORM_UIKIT)
10# include <UIKit/UIKit.h>
11#endif
12
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qpainter.h>
15#include <QtGui/qpalette.h>
16#include <QtGui/qstylehints.h>
17
18#include <QtGui/private/qcoregraphics_p.h>
19#include <QtGui/private/qicon_p.h>
20
22
23using namespace Qt::StringLiterals;
24
25namespace {
26auto *loadImage(const QString &iconName)
27{
28 static constexpr std::pair<QLatin1StringView, NSString *> iconMap[] = {
29 {"address-book-new"_L1, @"book.closed"},
30 {"application-exit"_L1, @"xmark.circle"},
31 {"appointment-new"_L1, @"calendar.badge.plus"},
32 {"call-start"_L1, @"phone.arrow.up.right"},
33 {"call-stop"_L1, @"phone.down"},
34 {"contact-new"_L1, @"person.crop.circle.badge.plus"},
35 {"document-new"_L1, @"plus"},
36 {"document-open"_L1, @"arrow.up.forward"},
37 {"document-open-recent"_L1, @"clock"},
38 {"document-page-setup"_L1, @"text.page"},
39 {"document-print"_L1, @"printer"},
40 {"document-print-preview"_L1, @"richtext.page"},
41 {"document-properties"_L1, @"info.circle.text.page"},
42 {"document-revert"_L1, @"arrow.counterclockwise"},
43 {"document-save"_L1, @"square.and.arrow.down"},
44 {"document-save-as"_L1, @"square.and.arrow.down.on.square"},
45 {"document-send"_L1, @"paperplane"},
46 {"document-export"_L1, @"square.and.arrow.up"},
47 {"edit-clear"_L1, @"xmark.circle"},
48 {"edit-copy"_L1, @"doc.on.doc"},
49 {"edit-cut"_L1, @"scissors"},
50 {"edit-delete"_L1, @"trash"},
51 {"edit-find"_L1, @"text.page.badge.magnifyingglass"},
52 //{"edit-find-replace"_L1, @"arrow.up.left.and.down.right.magnifyingglass"},
53 {"edit-paste"_L1, @"document.on.clipboard"},
54 {"edit-redo"_L1, @"arrow.uturn.forward"},
55 {"edit-select-all"_L1, @"character.textbox"},
56 {"edit-undo"_L1, @"arrow.uturn.backward"},
57 {"folder-new"_L1, @"folder.badge.plus"},
58 {"format-indent-less"_L1, @"decrease.indent"},
59 {"format-indent-more"_L1, @"increase.indent"},
60 {"format-justify-center"_L1, @"text.aligncenter"},
61 {"format-justify-fill"_L1, @"text.justify"},
62 {"format-justify-left"_L1, @"text.justify.left"},
63 {"format-justify-right"_L1, @"text.justify.right"},
64 {"format-text-direction-ltr"_L1, @"text.justify.leading"},
65 {"format-text-direction-rtl"_L1, @"text.justify.trailing"},
66 {"format-text-bold"_L1, @"bold"},
67 {"format-text-italic"_L1, @"italic"},
68 {"format-text-underline"_L1, @"underline"},
69 {"format-text-strikethrough"_L1, @"strikethrough"},
70 //{"go-bottom"_L1, @""},
71 {"go-down"_L1, @"chevron.down"},
72 //{"go-first"_L1, @""},
73 {"go-home"_L1, @"house"},
74 //{"go-jump"_L1, @""},
75 //{"go-last"_L1, @""},
76 {"go-next"_L1, @"chevron.right"},
77 {"go-previous"_L1, @"chevron.left"},
78 //{"go-top"_L1, @""},
79 {"go-up"_L1, @"chevron.up"},
80 {"help-about"_L1, @"info.circle"},
81 //{"help-contents"_L1, @""},
82 {"help-faq"_L1, @"questionmark.app"},
83 {"insert-image"_L1, @"photo.badge.plus"},
84 {"insert-link"_L1, @"link.badge.plus"},
85 {"insert-object"_L1, @"widget.small.badge.plus"},
86 {"insert-text"_L1, @"text.badge.plus"},
87 {"list-add"_L1, @"plus.circle"},
88 {"list-remove"_L1, @"minus.circle"},
89 {"mail-forward"_L1, @"arrowshape.turn.up.right"},
90 {"mail-mark-important"_L1, @"star"},
91 {"mail-mark-junk"_L1, @"xmark.bin"},
92 {"mail-mark-notjunk"_L1, @"arrow.up.bin"},
93 {"mail-mark-read"_L1, @"envelope.open"},
94 {"mail-mark-unread"_L1, @"envelope.badge"},
95 {"mail-message-new"_L1, @"square.and.pencil"},
96 {"mail-reply-all"_L1, @"arrowshape.turn.up.left.2"},
97 {"mail-reply-sender"_L1, @"arrowshape.turn.up.left"},
98 {"mail-send"_L1, @"arrow.up.circle.fill"},
99 {"mail-send-receive"_L1, @"arrow.trianglehead.2.clockwise.rotate.90"},
100 {"mail-replied"_L1, @"envelope.and.arrow.trianglehead.branch"},
101 {"media-eject"_L1, @"eject"},
102 {"media-playback-pause"_L1, @"pause"},
103 {"media-playback-start"_L1, @"play"},
104 {"media-playback-stop"_L1, @"stop"},
105 {"media-record"_L1, @"record.circle"},
106 {"media-seek-backward"_L1, @"backward"},
107 {"media-seek-forward"_L1, @"forward"},
108 {"media-skip-backward"_L1, @"backward.end.alt"},
109 {"media-skip-forward"_L1, @"forward.end.alt"},
110 {"object-flip-horizontal"_L1, @"rectangle.landscape.rotate"},
111 {"object-flip-vertical"_L1, @"rectangle.portrait.rotate"},
112 {"object-rotate-left"_L1, @"rotate.left"},
113 {"object-rotate-right"_L1, @"rotate.right"},
114 {"process-stop"_L1, @"stop.circle"},
115 {"system-lock-screen"_L1, @"lock"},
116 {"system-log-out"_L1, @"person.crop.circle"},
117 //{"system-run"_L1, @""},
118 {"system-search"_L1, @"magnifyingglass"},
119 {"system-reboot"_L1, @"restart"},
120 {"system-shutdown"_L1, @"power"},
121 {"tools-check-spelling"_L1, @"textformat.characters.dottedunderline"},
122 {"view-fullscreen"_L1, @"arrow.up.left.and.arrow.down.right"},
123 {"view-refresh"_L1, @"arrow.clockwise"},
124 {"view-restore"_L1, @"arrow.down.right.and.arrow.up.left"},
125 {"view-sort-ascending"_L1, @"arrow.up"},
126 {"view-sort-descending"_L1, @"arrow.down"},
127 {"window-close"_L1, @"xmark.circle"},
128 {"window-new"_L1, @"macwindow.badge.plus"},
129 {"zoom-fit-best"_L1, @"square.arrowtriangle.4.outward"},
130 {"zoom-in"_L1, @"plus.magnifyingglass"},
131 {"zoom-original"_L1, @"1.magnifyingglass"},
132 {"zoom-out"_L1, @"minus.magnifyingglass"},
133 {"process-working"_L1, @"circle.dotted"},
134 {"accessories-calculator"_L1, @"plus.forwardslash.minus"},
135 {"accessories-character-map"_L1, @"keyboard.macwindow"},
136 {"accessories-dictionary"_L1, @"character.book.closed"},
137 {"accessories-text-editor"_L1, @"textformat"},
138 {"help-browser"_L1, @"folder.badge.questionmark"},
139 {"multimedia-volume-control"_L1, @"speaker.wave.3"},
140 {"preferences-desktop-accessibility"_L1, @"accessibility"},
141 {"preferences-desktop-font"_L1, @"textformat.characters"},
142 {"preferences-desktop-keyboard"_L1, @"keyboard"},
143 {"preferences-desktop-locale"_L1, @"mappin.and.ellipse"},
144 {"preferences-desktop-multimedia"_L1, @"music.note.tv"},
145 {"preferences-desktop-screensaver"_L1, @"display"},
146 {"preferences-desktop-theme"_L1, @"paintpalette"},
147 {"preferences-desktop-wallpaper"_L1, @"photo.tv"},
148 {"system-file-manager"_L1, @"folder.badge.gearshape"},
149 {"system-software-install"_L1, @"laptopcomputer.and.arrow.down"},
150 {"system-software-update"_L1, @"laptopcomputer.badge.checkmark"},
151 {"utilities-system-monitor"_L1, @"chart.bar.xaxis"},
152 {"utilities-terminal"_L1, @"apple.terminal"},
153 {"applications-accessories"_L1, @"scanner"},
154 {"applications-development"_L1, @"hammer"},
155 {"applications-engineering"_L1, @"wrench.and.screwdriver"},
156 {"applications-games"_L1, @"gamecontroller.circle"},
157 {"applications-graphics"_L1, @"theatermask.and.paintbrush"},
158 {"applications-internet"_L1, @"network"},
159 {"applications-multimedia"_L1, @"tv.and.mediabox"},
160 {"applications-office"_L1, @"square.and.pencil.circle"},
161 {"applications-other"_L1, @"app.grid"},
162 {"applications-science"_L1, @"atom"},
163 {"applications-system"_L1, @"gear"},
164 {"applications-utilities"_L1, @"wrench.and.screwdriver"},
165 {"preferences-desktop"_L1, @"menubar.dock.rectangle"},
166 {"preferences-desktop-peripherals"_L1, @"iphone.and.ipod"},
167 {"preferences-desktop-personal"_L1, @"person.badge.key"},
168 {"preferences-other"_L1, @"gear"},
169 {"preferences-system"_L1, @"gear"},
170 {"preferences-system-network"_L1, @"network"},
171 {"system-help"_L1, @"questionmark.diamond"},
172 {"audio-card"_L1, @"waveform.circle"},
173 {"audio-input-microphone"_L1, @"mic"},
174 {"battery"_L1, @"battery.100percent"},
175 {"camera-photo"_L1, @"camera"},
176 {"camera-video"_L1, @"video"},
177 {"camera-web"_L1, @"web.camera"},
178 {"computer"_L1, @"desktopcomputer"},
179 {"drive-harddisk"_L1, @"internaldrive"},
180 {"drive-optical"_L1, @"opticaldiscdrive"},
181 {"drive-removable-media"_L1, @"externaldrive"},
182 {"input-gaming"_L1, @"gamecontroller"},
183 {"input-keyboard"_L1, @"keyboard"},
184 {"input-mouse"_L1, @"computermouse"},
185 {"input-tablet"_L1, @"ipad"},
186 {"media-flash"_L1, @"mediastick"},
187 //{"media-floppy"_L1, @""},
188 {"media-optical"_L1, @"opticaldisc"},
189 {"media-tape"_L1, @"recordingtape"},
190 {"modem"_L1, @"phone.connection"},
191 {"multimedia-player"_L1, @"ipod"},
192 {"network-wired"_L1, @"app.connected.to.app.below.fill"},
193 {"network-wireless"_L1, @"wifi"},
194 {"network-workgroup"_L1, @"network"},
195 {"pda"_L1, @"flipphone"},
196 {"phone"_L1, @"iphone"},
197 {"printer"_L1, @"printer"},
198 {"scanner"_L1, @"scanner"},
199 {"video-display"_L1, @"play.display"},
200 {"emblem-default"_L1, @"circle"},
201 {"emblem-documents"_L1, @"doc.circle"},
202 {"emblem-downloads"_L1, @"arrow.down.circle"},
203 {"emblem-favorite"_L1, @"star.circle"},
204 {"emblem-important"_L1, @"exclamationmark.circle"},
205 {"emblem-mail"_L1, @"envelope.circle"},
206 {"emblem-photos"_L1, @"photo.circle"},
207 {"emblem-readonly"_L1, @"eye.circle"},
208 {"emblem-shared"_L1, @"person.2.circle"},
209 {"emblem-symbolic-link"_L1, @"link.circle"},
210 {"emblem-synchronized"_L1, @"arrow.triangle.2.circlepath.circle"},
211 {"emblem-system"_L1, @"gear.circle"},
212 {"emblem-unreadable"_L1, @"eye.slash.circle"},
213 {"text-x-generic"_L1, @"doc"}, // until iOS 18/macOS 15; @"document" after that
214 {"folder"_L1, @"folder"},
215 {"folder-remote"_L1, @"icloud"},
216 {"network-server"_L1, @"server.rack"},
217 //{"start-here"_L1, @""},
218 {"user-bookmarks"_L1, @"bookmark.circle"},
219 {"user-desktop"_L1, @"menubar.dock.rectangle"}, // used by finder
220 {"user-home"_L1, @"house"}, //"go-home" also using this one
221 {"user-trash"_L1, @"trash"},
222 {"appointment-missed"_L1, @"calendar.badge.exclamationmark"},
223 {"appointment-soon"_L1, @"calendar.badge.clock"},
224 {"audio-volume-high"_L1, @"speaker.wave.3"},
225 {"audio-volume-low"_L1, @"speaker.wave.1"},
226 {"audio-volume-medium"_L1, @"speaker.wave.2"},
227 {"audio-volume-muted"_L1, @"speaker.slash"},
228 {"battery-caution"_L1, @"minus.plus.batteryblock.exclamationmark"},
229 {"battery-low"_L1, @"battery.25percent"}, // there are different levels that can be low battery
230 {"dialog-error"_L1, @"exclamationmark.bubble"},
231 {"dialog-information"_L1, @"info.circle"},
232 {"dialog-password"_L1, @"lock"},
233 {"dialog-question"_L1, @"questionmark.circle"},
234 {"dialog-warning"_L1, @"exclamationmark.octagon"},
235 {"folder-drag-accept"_L1, @"plus.rectangle.on.folder"},
236 {"folder-open"_L1, @"arrow.forward.folder"},
237 {"folder-visiting"_L1, @"folder.circle"},
238 {"image-loading"_L1, @"photo.badge.arrow.down"},
239 {"image-missing"_L1, @"photo.badge.exclamationmark"},
240 {"mail-attachment"_L1, @"paperclip"},
241 {"mail-unread"_L1, @"envelope.badge"},
242 {"mail-read"_L1, @"envelope.open"},
243 //{"mail-replied"_L1, @""},
244 //{"mail-signed"_L1, @""},
245 {"mail-signed-verified"_L1, @"envelope.badge.shield.half.filled"},
246 {"media-playlist-repeat"_L1, @"repeat"},
247 {"media-playlist-shuffle"_L1, @"shuffle"},
248 {"network-error"_L1, @"xmark.icloud"},
249 {"network-idle"_L1, @"icloud"},
250 {"network-offline"_L1, @"icloud.slash"},
251 {"network-receive"_L1, @"icloud.and.arrow.down"},
252 {"network-transmit"_L1, @"icloud.and.arrow.up"},
253 {"network-transmit-receive"_L1, @"arrow.trianglehead.2.clockwise.rotate.90.icloud"},
254 //{"printer-error"_L1, @""},
255 {"printer-printing"_L1, @"printer.dotmatrix"},
256 {"security-high"_L1, @"lock.badge.checkmark"},
257 {"security-medium"_L1, @"lock"},
258 {"security-low"_L1, @"lock.trianglebadge.exclamationmark"},
259 {"software-update-available"_L1, @"arrowshape.up.circle"},
260 {"software-update-urgent"_L1, @"exclamationmark.transmission"},
261 {"sync-error"_L1, @"exclamationmark.arrow.triangle.2.circlepath"},
262 {"sync-synchronizing"_L1, @"arrow.triangle.2.circlepath"},
263 {"task-due"_L1, @"clock.badge.exclamationmark"},
264 {"task-past-due"_L1, @"clock.badge.xmark"},
265 {"user-available"_L1, @"person.crop.circle.badge"},
266 {"user-away"_L1, @"person.crop.circle.badge.moon"},
267 {"user-idle"_L1, @"person.crop.circle.badge.clock"},
268 {"user-offline"_L1, @"person.crop.circle.badge.xmark"},
269 //{"user-trash-full"_L1, @""},
270 {"weather-clear"_L1, @"sun.max"},
271 {"weather-clear-night"_L1, @"moon"},
272 {"weather-few-clouds"_L1, @"cloud.sun"},
273 {"weather-few-clouds-night"_L1, @"cloud.moon"},
274 {"weather-fog"_L1, @"cloud.fog"},
275 {"weather-overcast"_L1, @"cloud"},
276 {"weather-severe-alert"_L1, @"cloud.bolt.rain"},
277 {"weather-showers"_L1, @"cloud.heavyrain"},
278 {"weather-showers-scattered"_L1, @"cloud.drizzle"},
279 {"weather-snow"_L1, @"cloud.snow"},
280 {"weather-storm"_L1, @"tropicalstorm"},
281 };
282 const auto it = std::find_if(std::begin(iconMap), std::end(iconMap), [iconName](const auto &c){
283 return c.first == iconName;
284 });
285 NSString *systemIconName = it != std::end(iconMap) ? it->second : iconName.toNSString();
286#if defined(Q_OS_MACOS)
287 return [NSImage imageWithSystemSymbolName:systemIconName accessibilityDescription:nil];
288#elif defined(QT_PLATFORM_UIKIT)
289 return [UIImage systemImageNamed:systemIconName];
290#endif
291}
292}
293
294QAppleIconEngine::QAppleIconEngine(const QString &iconName)
295 : m_iconName(iconName), m_image(loadImage(iconName))
296{
297 if (m_image)
298 [m_image retain];
299}
300
301QAppleIconEngine::~QAppleIconEngine()
302{
303 if (m_image)
304 [m_image release];
305}
306
307QIcon QAppleIconEngine::fromTheme(const QString &iconName)
308{
309 return QIcon(new QAppleIconEngine(iconName));
310}
311
312QIconEngine *QAppleIconEngine::clone() const
313{
314 return new QAppleIconEngine(m_iconName);
315}
316
317QString QAppleIconEngine::key() const
318{
319 return u"QAppleIconEngine"_s;
320}
321
322QString QAppleIconEngine::iconName()
323{
324 return m_iconName;
325}
326
327bool QAppleIconEngine::isNull()
328{
329 return m_image == nullptr;
330}
331
332QList<QSize> QAppleIconEngine::availableIconSizes(double aspectRatio)
333{
334 const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
335 const QList<QSize> sizes = {
336 {qRound(16 * devicePixelRatio), qRound(16. * devicePixelRatio / aspectRatio)},
337 {qRound(32 * devicePixelRatio), qRound(32. * devicePixelRatio / aspectRatio)},
338 {qRound(64 * devicePixelRatio), qRound(64. * devicePixelRatio / aspectRatio)},
339 {qRound(128 * devicePixelRatio), qRound(128. * devicePixelRatio / aspectRatio)},
340 {qRound(256 * devicePixelRatio), qRound(256. * devicePixelRatio / aspectRatio)},
341 };
342 return sizes;
343}
344
345QList<QSize> QAppleIconEngine::availableSizes(QIcon::Mode, QIcon::State)
346{
347 const double aspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
348 return availableIconSizes(aspectRatio);
349}
350
351QSize QAppleIconEngine::actualSize(const QSize &size, QIcon::Mode /*mode*/, QIcon::State /*state*/)
352{
353 const double inputAspectRatio = isNull() ? 1.0 : m_image.size.width / m_image.size.height;
354 const double outputAspectRatio = size.width() / size.height();
355 QSize result = size;
356 if (outputAspectRatio > inputAspectRatio)
357 result.rwidth() = result.height() * inputAspectRatio;
358 else
359 result.rheight() = result.width() / inputAspectRatio;
360 return result;
361}
362
363QPixmap QAppleIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
364{
365 return scaledPixmap(size, mode, state, 1.0);
366}
367
368namespace {
369#if defined(Q_OS_MACOS)
370auto *configuredImage(const NSImage *image, const QColor &color)
371{
372 auto *config = [NSImageSymbolConfiguration configurationWithPointSize:48
373 weight:NSFontWeightRegular
374 scale:NSImageSymbolScaleLarge];
375
376 // Apply tint color first, which switches the configuration to palette mode
377 config = [config configurationByApplyingConfiguration:
378 [NSImageSymbolConfiguration configurationWithPaletteColors:@[
379 [NSColor colorWithSRGBRed:color.redF() green:color.greenF()
380 blue:color.blueF() alpha:color.alphaF()]
381 ]]];
382
383 // Then switch back to monochrome, as palette mode gives a different look
384 // than monochrome, even with a single color.
385 config = [config configurationByApplyingConfiguration:
386 [NSImageSymbolConfiguration configurationPreferringMonochrome]];
387
388 return [image imageWithSymbolConfiguration:config];
389}
390#elif defined(QT_PLATFORM_UIKIT)
391auto *configuredImage(const UIImage *image, const QColor &color)
392{
393 auto *config = [UIImageSymbolConfiguration configurationWithPointSize:48
394 weight:UIImageSymbolWeightRegular
395 scale:UIImageSymbolScaleLarge];
396
397 auto *primaryColor = [UIColor colorWithRed:color.redF()
398 green:color.greenF()
399 blue:color.blueF()
400 alpha:color.alphaF()];
401
402 return [[image imageByApplyingSymbolConfiguration:config] imageWithTintColor:primaryColor];
403}
404#endif
405}
406
407QPixmap QAppleIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
408{
409 const CacheKey key(mode, state, size, scale);
410 QPixmap pixmap = m_cache.value(key);
411 if (pixmap.isNull()) {
412 const QSize paintSize = actualSize(size, mode, state);
413 const QSize paintOffset = paintSize != size
414 ? (QSizeF(size - paintSize) * 0.5).toSize()
415 : QSize();
416
417 pixmap = QPixmap(size * scale);
418 pixmap.setDevicePixelRatio(scale);
419 pixmap.fill(Qt::transparent);
420
421 if (!pixmap.isNull()) {
422 QPainter painter(&pixmap);
423 paint(&painter, QRect(paintOffset.width(), paintOffset.height(),
424 paintSize.width(), paintSize.height()), mode, state);
425 m_cache.insert(key, pixmap);
426 }
427 }
428 return pixmap;
429}
430
431void QAppleIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
432{
433 Q_UNUSED(state);
434
435 QColor color;
436 const QPalette palette;
437 switch (mode) {
438 case QIcon::Normal:
439 color = palette.color(QPalette::Inactive, QPalette::Text);
440 break;
441 case QIcon::Disabled:
442 color = palette.color(QPalette::Disabled, QPalette::Text);
443 break;
444 case QIcon::Active:
445 color = palette.color(QPalette::Active, QPalette::Text);
446 break;
447 case QIcon::Selected:
448 color = palette.color(QPalette::Active, QPalette::HighlightedText);
449 break;
450 }
451 const auto *image = configuredImage(m_image, color);
452
453 QMacCGContext ctx(painter);
454
455#if defined(Q_OS_MACOS)
456 NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithCGContext:ctx flipped:YES];
457 [NSGraphicsContext saveGraphicsState];
458 [NSGraphicsContext setCurrentContext:gc];
459
460 const NSSize pixmapSize = NSMakeSize(rect.width(), rect.height());
461 [image setSize:pixmapSize];
462 const NSRect sourceRect = NSMakeRect(0, 0, pixmapSize.width, pixmapSize.height);
463 const NSRect iconRect = NSMakeRect(rect.x(), rect.y(), pixmapSize.width, pixmapSize.height);
464
465 [image drawInRect:iconRect fromRect:sourceRect operation:NSCompositingOperationSourceOver
466 fraction:1.0 respectFlipped:YES hints:@{
467 NSImageHintUserInterfaceLayoutDirection: painter->layoutDirection() == Qt::RightToLeft ?
468 @(NSUserInterfaceLayoutDirectionRightToLeft) : @(NSUserInterfaceLayoutDirectionLeftToRight)
469 }];
470 [NSGraphicsContext restoreGraphicsState];
471#elif defined(QT_PLATFORM_UIKIT)
472 auto *layoutDirectionTrait = [UITraitCollection traitCollectionWithLayoutDirection:
473 painter->layoutDirection() == Qt::RightToLeft ?
474 UITraitEnvironmentLayoutDirectionRightToLeft :
475 UITraitEnvironmentLayoutDirectionLeftToRight];
476
477 image = [image imageWithConfiguration:layoutDirectionTrait.imageConfiguration];
478
479 UIGraphicsPushContext(ctx);
480 const CGRect cgrect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
481 [image drawInRect:cgrect];
482 UIGraphicsPopContext();
483#endif
484}
485
486void QAppleIconEngine::virtual_hook(int hookIdentifier, void *data)
487{
488 // Expose underlying NSImage so we can pass it on to AppKit
489 // directly without flattening, preserving the symbol image.
490 if (hookIdentifier == QIconPrivate::PlatformIconHook)
491 *static_cast<decltype(m_image)*>(data) = m_image;
492 else
493 QIconEngine::virtual_hook(hookIdentifier, data);
494}
495
496QT_END_NAMESPACE
Combined button and popup list for selecting options.