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