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
qwindowscursor.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#ifndef QT_NO_CURSOR
9
10#include <QtGui/qbitmap.h>
11#include <QtGui/qimage.h>
12#include <QtGui/qbitmap.h>
13#include <QtGui/qguiapplication.h>
14#include <QtGui/qscreen.h>
15#include <QtGui/private/qguiapplication_p.h> // getPixmapCursor()
16#include <QtGui/private/qhighdpiscaling_p.h>
17#include <QtGui/private/qpixmap_win_p.h>
18#include <QtCore/private/qwinregistry_p.h>
19
20#include <QtCore/qdebug.h>
21#include <QtCore/qscopedpointer.h>
22
23static bool initResources()
24{
25#if QT_CONFIG(imageformat_png)
26 Q_INIT_RESOURCE(cursors);
27#endif
28 return true;
29}
30
31QT_BEGIN_NAMESPACE
32
33using namespace Qt::StringLiterals;
34
35/*!
36 \class QWindowsCursorCacheKey
37 \brief Cache key for storing values in a QHash with a QCursor as key.
38
39 \internal
40*/
41
44{
45 if (!bitmapCacheKey) {
46 Q_ASSERT(!c.bitmap().isNull());
47 Q_ASSERT(!c.mask().isNull());
48 bitmapCacheKey = c.bitmap().cacheKey();
49 maskCacheKey = c.mask().cacheKey();
50 }
51 hotspotCacheKey.x = c.hotSpot().x();
52 hotspotCacheKey.y = c.hotSpot().y();
53}
54
55/*!
56 \class QWindowsCursor
57 \brief Platform cursor implementation
58
59 Note that whereas under X11, a cursor can be set as a property of
60 a window, there is only a global SetCursor() function on Windows.
61 Each Window sets on the global cursor on receiving a Enter-event
62 as do the Window manager frames (resize/move handles).
63
64 \internal
65 \sa QWindowsWindowCursor
66*/
67
68HCURSOR QWindowsCursor::createPixmapCursor(QPixmap pixmap, const QPoint &hotSpot, qreal scaleFactor)
69{
70 HCURSOR cur = nullptr;
71 const qreal pixmapScaleFactor = scaleFactor / pixmap.devicePixelRatio();
72 if (!qFuzzyCompare(pixmapScaleFactor, 1)) {
73 pixmap = pixmap.scaled((pixmapScaleFactor * QSizeF(pixmap.size())).toSize(),
74 Qt::KeepAspectRatio, Qt::SmoothTransformation);
75 }
76 QBitmap mask = pixmap.mask();
77 if (mask.isNull()) {
78 mask = QBitmap(pixmap.size());
79 mask.fill(Qt::color1);
80 }
81
82 HBITMAP ic = qt_pixmapToWinHBITMAP(pixmap, /* HBitmapAlpha */ 2);
83 const HBITMAP im = qt_createIconMask(mask);
84
85 ICONINFO ii;
86 ii.fIcon = 0;
87 ii.xHotspot = DWORD(qRound(hotSpot.x() * scaleFactor));
88 ii.yHotspot = DWORD(qRound(hotSpot.y() * scaleFactor));
89 ii.hbmMask = im;
90 ii.hbmColor = ic;
91
92 cur = CreateIconIndirect(&ii);
93
94 DeleteObject(ic);
95 DeleteObject(im);
96 return cur;
97}
98
99// Create a cursor from image and mask of the format QImage::Format_Mono.
100static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
101 QPoint hotSpot = QPoint(-1, -1),
102 bool invb = false, bool invm = false)
103{
104 const int width = bbits.width();
105 const int height = bbits.height();
106 if (hotSpot.x() < 0)
107 hotSpot.setX(width / 2);
108 if (hotSpot.y() < 0)
109 hotSpot.setY(height / 2);
110 // a ddb is word aligned, QImage depends on bow it was created
111 const auto bplDdb = qMax(1, ((width + 15) >> 4) << 1);
112 const auto bplImg = int(bbits.bytesPerLine());
113 QScopedArrayPointer<uchar> xBits(new uchar[height * bplDdb]);
114 QScopedArrayPointer<uchar> xMask(new uchar[height * bplDdb]);
115 int x = 0;
116 for (int i = 0; i < height; ++i) {
117 const uchar *bits = bbits.constScanLine(i);
118 const uchar *mask = mbits.constScanLine(i);
119 for (int j = 0; j < bplImg && j < bplDdb; ++j) {
120 uchar b = bits[j];
121 uchar m = mask[j];
122 if (invb)
123 b ^= 0xff;
124 if (invm)
125 m ^= 0xff;
126 xBits[x] = ~m;
127 xMask[x] = b ^ m;
128 ++x;
129 }
130 for (int i = bplImg; i < bplDdb; ++i) {
131 xBits[x] = 0;
132 xMask[x] = 0;
133 ++x;
134 }
135 }
136 return CreateCursor(GetModuleHandle(nullptr), hotSpot.x(), hotSpot.y(), width, height,
137 xBits.data(), xMask.data());
138}
139
140// Create a cursor from image and mask of the format QImage::Format_Mono.
141static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1)
142{
143 Q_ASSERT(cursor.shape() == Qt::BitmapCursor && !cursor.bitmap().isNull());
144 QImage bbits = cursor.bitmap().toImage();
145 QImage mbits = cursor.mask().toImage();
146 scaleFactor /= bbits.devicePixelRatio();
147 if (!qFuzzyCompare(scaleFactor, 1)) {
148 const QSize scaledSize = (QSizeF(bbits.size()) * scaleFactor).toSize();
149 bbits = bbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
150 mbits = mbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
151 }
152 bbits = std::move(bbits).convertToFormat(QImage::Format_Mono);
153 mbits = std::move(mbits).convertToFormat(QImage::Format_Mono);
154 const bool invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1));
155 const bool invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1));
156 return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm);
157}
158
159static QSize systemCursorSize() { return QSize(GetSystemMetrics(SM_CXCURSOR), GetSystemMetrics(SM_CYCURSOR)); }
160
161static QSize screenCursorSize(const QPlatformScreen *screen = nullptr)
162{
163 const QSize primaryScreenCursorSize = systemCursorSize();
164 if (screen) {
165 // Correct the size if the DPI value of the screen differs from
166 // that of the primary screen.
167 if (const QScreen *primaryQScreen = QGuiApplication::primaryScreen()) {
168 const QPlatformScreen *primaryScreen = primaryQScreen->handle();
169 if (screen != primaryScreen) {
170 const qreal logicalDpi = screen->logicalDpi().first;
171 const qreal primaryScreenLogicalDpi = primaryScreen->logicalDpi().first;
172 if (!qFuzzyCompare(logicalDpi, primaryScreenLogicalDpi))
173 return (QSizeF(primaryScreenCursorSize) * logicalDpi / primaryScreenLogicalDpi).toSize();
174 }
175 }
176 }
177 return primaryScreenCursorSize;
178}
179
180#if !QT_CONFIG(imageformat_png)
181
182static inline QSize standardCursorSize() { return QSize(32, 32); }
183
184// Create pixmap cursors from data and scale the image if the cursor size is
185// higher than the standard 32. Note that bitmap cursors as produced by
186// createBitmapCursor() only work for standard sizes (32,48,64...), which does
187// not work when scaling the 16x16 openhand cursor bitmaps to 150% (resulting
188// in a non-standard 24x24 size).
189static QWindowsCursor::PixmapCursor createPixmapCursorFromData(const QSize &screenCursorSize,
190 // The cursor size the bitmap is targeted for
191 const QSize &bitmapTargetCursorSize,
192 // The actual size of the bitmap data
193 int bitmapSize, const uchar *bits,
194 const uchar *maskBits)
195{
196 QPixmap rawImage = QPixmap::fromImage(QBitmap::fromData(QSize(bitmapSize, bitmapSize), bits).toImage());
197 rawImage.setMask(QBitmap::fromData(QSize(bitmapSize, bitmapSize), maskBits));
198
199 const qreal factor = qreal(screenCursorSize.width()) / qreal(bitmapTargetCursorSize.width());
200 // Scale images if the cursor size is significantly different, starting with 150% where the system cursor
201 // size is 48.
202 if (qAbs(factor - 1.0) > 0.4) {
203 const QTransform transform = QTransform::fromScale(factor, factor);
204 rawImage = rawImage.transformed(transform, Qt::SmoothTransformation);
205 }
206 const QPoint hotSpot(rawImage.width() / 2, rawImage.height() / 2);
207 return QWindowsCursor::PixmapCursor(rawImage, hotSpot);
208}
209
210QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursorShape,
211 const QPlatformScreen *screen)
212{
213 // Non-standard Windows cursors are created from bitmaps
214 static const uchar vsplit_bits[] = {
215 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
216 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00,
218 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
219 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00,
220 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00,
221 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
222 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00,
223 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
224 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
226 static const uchar vsplitm_bits[] = {
227 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
228 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
229 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00,
230 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00,
231 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00,
232 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00,
233 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00,
234 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00,
235 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
236 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
237 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
238 static const uchar hsplit_bits[] = {
239 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
240 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
242 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
243 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03,
244 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00,
245 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
246 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
247 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
248 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
250 static const uchar hsplitm_bits[] = {
251 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
252 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
253 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00,
254 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00,
255 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07,
256 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00,
257 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00,
258 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
259 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
260 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
261 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
262 static const uchar openhand_bits[] = {
263 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92,
264 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20,
265 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00};
266 static const uchar openhandm_bits[] = {
267 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff,
268 0xf6,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f,
269 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00};
270 static const uchar closedhand_bits[] = {
271 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50,
272 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10,
273 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00};
274 static const uchar closedhandm_bits[] = {
275 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f,
276 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f,
277 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00};
278
279 static const char * const moveDragCursorXpmC[] = {
280 "11 20 3 1",
281 ". c None",
282 "a c #FFFFFF",
283 "X c #000000", // X11 cursor is traditionally black
284 "aa.........",
285 "aXa........",
286 "aXXa.......",
287 "aXXXa......",
288 "aXXXXa.....",
289 "aXXXXXa....",
290 "aXXXXXXa...",
291 "aXXXXXXXa..",
292 "aXXXXXXXXa.",
293 "aXXXXXXXXXa",
294 "aXXXXXXaaaa",
295 "aXXXaXXa...",
296 "aXXaaXXa...",
297 "aXa..aXXa..",
298 "aa...aXXa..",
299 "a.....aXXa.",
300 "......aXXa.",
301 ".......aXXa",
302 ".......aXXa",
303 "........aa."};
304
305 static const char * const copyDragCursorXpmC[] = {
306 "24 30 3 1",
307 ". c None",
308 "a c #000000",
309 "X c #FFFFFF",
310 "XX......................",
311 "XaX.....................",
312 "XaaX....................",
313 "XaaaX...................",
314 "XaaaaX..................",
315 "XaaaaaX.................",
316 "XaaaaaaX................",
317 "XaaaaaaaX...............",
318 "XaaaaaaaaX..............",
319 "XaaaaaaaaaX.............",
320 "XaaaaaaXXXX.............",
321 "XaaaXaaX................",
322 "XaaXXaaX................",
323 "XaX..XaaX...............",
324 "XX...XaaX...............",
325 "X.....XaaX..............",
326 "......XaaX..............",
327 ".......XaaX.............",
328 ".......XaaX.............",
329 "........XX...aaaaaaaaaaa",
330 ".............aXXXXXXXXXa",
331 ".............aXXXXXXXXXa",
332 ".............aXXXXaXXXXa",
333 ".............aXXXXaXXXXa",
334 ".............aXXaaaaaXXa",
335 ".............aXXXXaXXXXa",
336 ".............aXXXXaXXXXa",
337 ".............aXXXXXXXXXa",
338 ".............aXXXXXXXXXa",
339 ".............aaaaaaaaaaa"};
340
341 static const char * const linkDragCursorXpmC[] = {
342 "24 30 3 1",
343 ". c None",
344 "a c #000000",
345 "X c #FFFFFF",
346 "XX......................",
347 "XaX.....................",
348 "XaaX....................",
349 "XaaaX...................",
350 "XaaaaX..................",
351 "XaaaaaX.................",
352 "XaaaaaaX................",
353 "XaaaaaaaX...............",
354 "XaaaaaaaaX..............",
355 "XaaaaaaaaaX.............",
356 "XaaaaaaXXXX.............",
357 "XaaaXaaX................",
358 "XaaXXaaX................",
359 "XaX..XaaX...............",
360 "XX...XaaX...............",
361 "X.....XaaX..............",
362 "......XaaX..............",
363 ".......XaaX.............",
364 ".......XaaX.............",
365 "........XX...aaaaaaaaaaa",
366 ".............aXXXXXXXXXa",
367 ".............aXXXaaaaXXa",
368 ".............aXXXXaaaXXa",
369 ".............aXXXaaaaXXa",
370 ".............aXXaaaXaXXa",
371 ".............aXXaaXXXXXa",
372 ".............aXXaXXXXXXa",
373 ".............aXXXaXXXXXa",
374 ".............aXXXXXXXXXa",
375 ".............aaaaaaaaaaa"};
376
377 switch (cursorShape) {
378 case Qt::SplitVCursor:
379 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 32, vsplit_bits, vsplitm_bits);
380 case Qt::SplitHCursor:
381 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 32, hsplit_bits, hsplitm_bits);
382 case Qt::OpenHandCursor:
383 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 16, openhand_bits, openhandm_bits);
384 case Qt::ClosedHandCursor:
385 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 16, closedhand_bits, closedhandm_bits);
386 case Qt::DragCopyCursor:
387 return QWindowsCursor::PixmapCursor(QPixmap(copyDragCursorXpmC), QPoint(0, 0));
388 case Qt::DragMoveCursor:
389 return QWindowsCursor::PixmapCursor(QPixmap(moveDragCursorXpmC), QPoint(0, 0));
390 case Qt::DragLinkCursor:
391 return QWindowsCursor::PixmapCursor(QPixmap(linkDragCursorXpmC), QPoint(0, 0));
392 }
393
394 return QWindowsCursor::PixmapCursor();
395}
396#else // QT_NO_IMAGEFORMAT_PNG
397struct QWindowsCustomPngCursor {
398 Qt::CursorShape shape;
399 int size;
400 const char *fileName;
401 int hotSpotX;
402 int hotSpotY;
403};
404
405QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursorShape, const QPlatformScreen *screen)
406{
407 static const QWindowsCustomPngCursor pngCursors[] = {
408 { Qt::SplitVCursor, 32, "splitvcursor_32.png", 11, 11 },
409 { Qt::SplitVCursor, 48, "splitvcursor_48.png", 16, 17 },
410 { Qt::SplitVCursor, 64, "splitvcursor_64.png", 22, 22 },
411 { Qt::SplitHCursor, 32, "splithcursor_32.png", 11, 11 },
412 { Qt::SplitHCursor, 48, "splithcursor_48.png", 16, 17 },
413 { Qt::SplitHCursor, 64, "splithcursor_64.png", 22, 22 },
414 { Qt::OpenHandCursor, 32, "openhandcursor_32.png", 10, 12 },
415 { Qt::OpenHandCursor, 48, "openhandcursor_48.png", 15, 16 },
416 { Qt::OpenHandCursor, 64, "openhandcursor_64.png", 20, 24 },
417 { Qt::ClosedHandCursor, 32, "closedhandcursor_32.png", 10, 12 },
418 { Qt::ClosedHandCursor, 48, "closedhandcursor_48.png", 15, 16 },
419 { Qt::ClosedHandCursor, 64, "closedhandcursor_64.png", 20, 24 },
420 { Qt::DragCopyCursor, 32, "dragcopycursor_32.png", 0, 0 },
421 { Qt::DragCopyCursor, 48, "dragcopycursor_48.png", 0, 0 },
422 { Qt::DragCopyCursor, 64, "dragcopycursor_64.png", 0, 0 },
423 { Qt::DragMoveCursor, 32, "dragmovecursor_32.png", 0, 0 },
424 { Qt::DragMoveCursor, 48, "dragmovecursor_48.png", 0, 0 },
425 { Qt::DragMoveCursor, 64, "dragmovecursor_64.png", 0, 0 },
426 { Qt::DragLinkCursor, 32, "draglinkcursor_32.png", 0, 0 },
427 { Qt::DragLinkCursor, 48, "draglinkcursor_48.png", 0, 0 },
428 { Qt::DragLinkCursor, 64, "draglinkcursor_64.png", 0, 0 }
429 };
430
431 const QSize cursorSize = screenCursorSize(screen);
432 const QWindowsCustomPngCursor *sEnd = pngCursors + sizeof(pngCursors) / sizeof(pngCursors[0]);
433 const QWindowsCustomPngCursor *bestFit = nullptr;
434 int sizeDelta = INT_MAX;
435 for (const QWindowsCustomPngCursor *s = pngCursors; s < sEnd; ++s) {
436 if (s->shape != cursorShape)
437 continue;
438 const int currentSizeDelta = qMax(s->size, cursorSize.width()) - qMin(s->size, cursorSize.width());
439 if (currentSizeDelta < sizeDelta) {
440 bestFit = s;
441 if (currentSizeDelta == 0)
442 break; // Perfect match found
443 sizeDelta = currentSizeDelta;
444 }
445 }
446
447 if (!bestFit)
448 return PixmapCursor();
449
450 const QPixmap rawImage(":/qt-project.org/windows/cursors/images/"_L1 +
451 QLatin1StringView(bestFit->fileName));
452 return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY));
453}
454#endif // !QT_NO_IMAGEFORMAT_PNG
455
460
461HCURSOR QWindowsCursor::createCursorFromShape(Qt::CursorShape cursorShape, const QPlatformScreen *screen)
462{
463 Q_ASSERT(cursorShape != Qt::BitmapCursor);
464
465 static const QWindowsStandardCursorMapping standardCursors[] = {
466 { Qt::ArrowCursor, IDC_ARROW},
467 { Qt::UpArrowCursor, IDC_UPARROW },
468 { Qt::CrossCursor, IDC_CROSS },
469 { Qt::WaitCursor, IDC_WAIT },
470 { Qt::IBeamCursor, IDC_IBEAM },
471 { Qt::SizeVerCursor, IDC_SIZENS },
472 { Qt::SizeHorCursor, IDC_SIZEWE },
473 { Qt::SizeBDiagCursor, IDC_SIZENESW },
474 { Qt::SizeFDiagCursor, IDC_SIZENWSE },
475 { Qt::SizeAllCursor, IDC_SIZEALL },
476 { Qt::ForbiddenCursor, IDC_NO },
477 { Qt::WhatsThisCursor, IDC_HELP },
478 { Qt::BusyCursor, IDC_APPSTARTING },
479 { Qt::PointingHandCursor, IDC_HAND }
480 };
481
482 switch (cursorShape) {
483 case Qt::BlankCursor: {
484 QImage blank = QImage(systemCursorSize(), QImage::Format_Mono);
485 blank.fill(0); // ignore color table
486 return createBitmapCursor(blank, blank);
487 }
488 case Qt::SplitVCursor:
489 case Qt::SplitHCursor:
490 case Qt::OpenHandCursor:
491 case Qt::ClosedHandCursor:
492 case Qt::DragCopyCursor:
493 case Qt::DragMoveCursor:
494 case Qt::DragLinkCursor:
495 return QWindowsCursor::createPixmapCursor(customCursor(cursorShape, screen));
496 default:
497 break;
498 }
499
500 // Load available standard cursors from resources
501 for (const QWindowsStandardCursorMapping &s : standardCursors) {
502 if (s.shape == cursorShape) {
503 return static_cast<HCURSOR>(LoadImage(nullptr, s.resource, IMAGE_CURSOR,
504 0, 0, LR_DEFAULTSIZE | LR_SHARED));
505 }
506 }
507
508 qWarning("%s: Invalid cursor shape %d", __FUNCTION__, cursorShape);
509 return nullptr;
510}
511
512/*!
513 \brief Return cached standard cursor resources or create new ones.
514*/
515
517{
518 StandardCursorCache::Iterator it = m_standardCursorCache.find(shape);
519 if (it == m_standardCursorCache.end()) {
520 if (const HCURSOR hc = QWindowsCursor::createCursorFromShape(shape, m_screen))
521 it = m_standardCursorCache.insert(shape, CursorHandlePtr(new CursorHandle(hc)));
522 }
523 return it != m_standardCursorCache.end() ? it.value() : CursorHandlePtr(new CursorHandle);
524}
525
526HCURSOR QWindowsCursor::m_overriddenCursor = nullptr;
527HCURSOR QWindowsCursor::m_overrideCursor = nullptr;
528POINT QWindowsCursor::m_cursorPositionCache = {0,0};
529
530/*!
531 \brief Return cached pixmap cursor or create new one.
532*/
533
535{
536 const qreal scaleFactor = QHighDpiScaling::factor(m_screen);
537 const QWindowsPixmapCursorCacheKey cacheKey(c, scaleFactor);
538 PixmapCursorCache::iterator it = m_pixmapCursorCache.find(cacheKey);
539 if (it == m_pixmapCursorCache.end()) {
540 if (m_pixmapCursorCache.size() > 50) {
541 // Prevent the cursor cache from growing indefinitely hitting GDI resource
542 // limits if new pixmap cursors are created repetitively by purging out
543 // all-noncurrent pixmap cursors (QTBUG-43515)
544 const HCURSOR currentCursor = GetCursor();
545 for (it = m_pixmapCursorCache.begin(); it != m_pixmapCursorCache.end() ; ) {
546 if (it.value()->handle() != currentCursor)
547 it = m_pixmapCursorCache.erase(it);
548 else
549 ++it;
550 }
551 }
552 const QPixmap pixmap = c.pixmap();
553 const HCURSOR hc = pixmap.isNull()
554 ? createBitmapCursor(c, scaleFactor)
555 : QWindowsCursor::createPixmapCursor(pixmap, c.hotSpot(), scaleFactor);
556 it = m_pixmapCursorCache.insert(cacheKey, CursorHandlePtr(new CursorHandle(hc)));
557 }
558 return it.value();
559}
560
561QWindowsCursor::QWindowsCursor(const QPlatformScreen *screen)
562 : m_screen(screen)
563{
564 static const bool dummy = initResources();
565 Q_UNUSED(dummy);
566}
567
568inline CursorHandlePtr QWindowsCursor::cursorHandle(const QCursor &cursor)
569{
570 return cursor.shape() == Qt::BitmapCursor
571 ? pixmapWindowCursor(cursor)
572 : standardWindowCursor(cursor.shape());
573}
574
575/*!
576 \brief Set a cursor on a window.
577
578 This is called frequently as the mouse moves over widgets in the window
579 (QLineEdits, etc).
580*/
581
582void QWindowsCursor::changeCursor(QCursor *cursorIn, QWindow *window)
583{
584 QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
585 if (!platformWindow) // Desktop/Foreign window.
586 return;
587
588 if (!cursorIn) {
589 platformWindow->setCursor(CursorHandlePtr(new CursorHandle));
590 return;
591 }
592 const CursorHandlePtr wcursor = cursorHandle(*cursorIn);
593 if (wcursor->handle()) {
594 platformWindow->setCursor(wcursor);
595 } else {
596 qWarning("%s: Unable to obtain system cursor for %d",
597 __FUNCTION__, cursorIn->shape());
598 }
599}
600
601// QTBUG-69637: Override cursors can get reset externally when moving across
602// window borders. Enforce the cursor again (to be called from enter event).
604{
605 if (hasOverrideCursor() && m_overrideCursor != GetCursor())
606 SetCursor(m_overrideCursor);
607}
608
609void QWindowsCursor::setOverrideCursor(const QCursor &cursor)
610{
611 const CursorHandlePtr wcursor = cursorHandle(cursor);
612 if (const auto overrideCursor = wcursor->handle()) {
613 m_overrideCursor = overrideCursor;
614 const HCURSOR previousCursor = SetCursor(overrideCursor);
615 if (m_overriddenCursor == nullptr)
616 m_overriddenCursor = previousCursor;
617 } else {
618 qWarning("%s: Unable to obtain system cursor for %d",
619 __FUNCTION__, cursor.shape());
620 }
621}
622
624{
625 if (m_overriddenCursor) {
626 SetCursor(m_overriddenCursor);
627 m_overriddenCursor = m_overrideCursor = nullptr;
628 }
629 auto &windows = QWindowsContext::instance()->windows();
630 for (auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
631 if (it.value()->screen() == m_screen)
632 it.value()->setFlag(QWindowsWindow::RestoreOverrideCursor);
633 }
634}
635
636QPoint QWindowsCursor::mousePosition()
637{
638 POINT p;
639 if (GetCursorPos(&p))
640 m_cursorPositionCache = p;
641 return QPoint(m_cursorPositionCache.x, m_cursorPositionCache.y);
642}
643
645{
646 enum { cursorShowing = 0x1, cursorSuppressed = 0x2 }; // Windows 8: CURSOR_SUPPRESSED
647 CURSORINFO cursorInfo;
648 cursorInfo.cbSize = sizeof(CURSORINFO);
649 if (GetCursorInfo(&cursorInfo)) {
650 if (cursorInfo.flags & cursorShowing)
651 return State::Showing;
652 if (cursorInfo.flags & cursorSuppressed)
653 return State::Suppressed;
654 }
655 return State::Hidden;
656}
657
659{
660 return mousePosition();
661}
662
663void QWindowsCursor::setPos(const QPoint &pos)
664{
665 m_cursorPositionCache = {pos.x(), pos.y()};
666 SetCursorPos(pos.x() , pos.y());
667}
668
669/*
670 The standard size is 32x32, even though the cursor is actually just
671 16 pixels large. If a large cursor is set in the accessibility settings,
672 then the cursor increases with 8 pixels for each step.
673*/
675{
676 const auto cursorSizeSetting =
677 QWinRegistryKey(HKEY_CURRENT_USER, LR"(Control Panel\Cursors)")
678 .value<DWORD>(L"CursorBaseSize");
679 const int baseSize = screenCursorSize(m_screen).width() / 2;
680 if (!cursorSizeSetting)
681 return QSize(baseSize / 2, baseSize / 2);
682
683 // The registry values are dpi-independent, so we need to scale the result.
684 int cursorSizeValue = *cursorSizeSetting * m_screen->logicalDpi().first
685 / m_screen->logicalBaseDpi().first;
686
687 // map from registry value 32-256 to 0-14, and from there to pixels
688 cursorSizeValue = (cursorSizeValue - 2 * baseSize) / baseSize;
689 const int cursorSize = baseSize + cursorSizeValue * (baseSize / 2);
690 return QSize(cursorSize, cursorSize);
691}
692
693QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const
694{
695 switch (action) {
696 case Qt::CopyAction:
697 if (m_copyDragCursor.isNull())
698 m_copyDragCursor = QWindowsCursor::customCursor(Qt::DragCopyCursor, m_screen).pixmap;
699 return m_copyDragCursor;
700 case Qt::TargetMoveAction:
701 case Qt::MoveAction:
702 if (m_moveDragCursor.isNull())
703 m_moveDragCursor = QWindowsCursor::customCursor(Qt::DragMoveCursor, m_screen).pixmap;
704 return m_moveDragCursor;
705 case Qt::LinkAction:
706 if (m_linkDragCursor.isNull())
707 m_linkDragCursor = QWindowsCursor::customCursor(Qt::DragLinkCursor, m_screen).pixmap;
708 return m_linkDragCursor;
709 default:
710 break;
711 }
712
713#if QT_CONFIG(imageformat_xpm)
714 static const char * const ignoreDragCursorXpmC[] = {
715 "24 30 3 1",
716 ". c None",
717 "a c #000000",
718 "X c #FFFFFF",
719 "aa......................",
720 "aXa.....................",
721 "aXXa....................",
722 "aXXXa...................",
723 "aXXXXa..................",
724 "aXXXXXa.................",
725 "aXXXXXXa................",
726 "aXXXXXXXa...............",
727 "aXXXXXXXXa..............",
728 "aXXXXXXXXXa.............",
729 "aXXXXXXaaaa.............",
730 "aXXXaXXa................",
731 "aXXaaXXa................",
732 "aXa..aXXa...............",
733 "aa...aXXa...............",
734 "a.....aXXa..............",
735 "......aXXa.....XXXX.....",
736 ".......aXXa..XXaaaaXX...",
737 ".......aXXa.XaaaaaaaaX..",
738 "........aa.XaaaXXXXaaaX.",
739 "...........XaaaaX..XaaX.",
740 "..........XaaXaaaX..XaaX",
741 "..........XaaXXaaaX.XaaX",
742 "..........XaaX.XaaaXXaaX",
743 "..........XaaX..XaaaXaaX",
744 "...........XaaX..XaaaaX.",
745 "...........XaaaXXXXaaaX.",
746 "............XaaaaaaaaX..",
747 ".............XXaaaaXX...",
748 "...............XXXX....."};
749#endif
750
751 if (m_ignoreDragCursor.isNull()) {
752 HCURSOR cursor = LoadCursor(nullptr, IDC_NO);
753 ICONINFO iconInfo = {0, 0, 0, nullptr, nullptr};
754 GetIconInfo(cursor, &iconInfo);
755 BITMAP bmColor = {0, 0, 0, 0, 0, 0, nullptr};
756
757 if (iconInfo.hbmColor
758 && GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bmColor)
759 && bmColor.bmWidth == bmColor.bmWidthBytes / 4) {
760 const int colorBitsLength = bmColor.bmHeight * bmColor.bmWidthBytes;
761 auto *colorBits = new uchar[colorBitsLength];
762 GetBitmapBits(iconInfo.hbmColor, colorBitsLength, colorBits);
763 const QImage colorImage(colorBits, bmColor.bmWidth, bmColor.bmHeight,
764 bmColor.bmWidthBytes, QImage::Format_ARGB32);
765
766 m_ignoreDragCursor = QPixmap::fromImage(colorImage);
767 delete [] colorBits;
768#if QT_CONFIG(imageformat_xpm)
769 } else {
770 m_ignoreDragCursor = QPixmap(ignoreDragCursorXpmC);
771#endif
772 }
773
774 DeleteObject(iconInfo.hbmMask);
775 DeleteObject(iconInfo.hbmColor);
776 DestroyCursor(cursor);
777 }
778 return m_ignoreDragCursor;
779}
780
781HCURSOR QWindowsCursor::hCursor(const QCursor &c) const
782{
783 const Qt::CursorShape shape = c.shape();
784 if (shape == Qt::BitmapCursor) {
785 const qreal scaleFactor = QHighDpiScaling::factor(m_screen);
786 const auto pit = m_pixmapCursorCache.constFind(QWindowsPixmapCursorCacheKey(c, scaleFactor));
787 if (pit != m_pixmapCursorCache.constEnd())
788 return pit.value()->handle();
789 } else {
790 const auto sit = m_standardCursorCache.constFind(shape);
791 if (sit != m_standardCursorCache.constEnd())
792 return sit.value()->handle();
793 }
794 return HCURSOR(nullptr);
795}
796
797/*!
798 \class QWindowsWindowCursor
799 \brief Per-Window cursor. Contains a QCursor and manages its associated system
800 cursor handle resource.
801
802 \internal
803 \sa QWindowsCursor
804*/
805
806QT_END_NAMESPACE
807
808#endif // !QT_NO_CURSOR
\inmodule QtCore\reentrant
Definition qpoint.h:29
Singleton container for all relevant information.
static QWindowsContext * instance()
Platform cursor implementation.
static bool hasOverrideCursor()
void setOverrideCursor(const QCursor &cursor) override
Reimplement this function in subclass to set an override cursor on the associated screen and return t...
void changeCursor(QCursor *widgetCursor, QWindow *widget) override
Set a cursor on a window.
HCURSOR hCursor(const QCursor &c) const
void clearOverrideCursor() override
Reimplement this function in subclass to clear the override cursor.
CursorHandlePtr standardWindowCursor(Qt::CursorShape s=Qt::ArrowCursor)
Return cached standard cursor resources or create new ones.
static State cursorState()
static void enforceOverrideCursor()
QPixmap dragDefaultCursor(Qt::DropAction action) const
CursorHandlePtr pixmapWindowCursor(const QCursor &c)
Return cached pixmap cursor or create new one.
QSize size() const override
Returns the size of the cursor, in native pixels.
QPoint pos() const override
Raster or OpenGL Window.
static bool initResources()
static QSize screenCursorSize(const QPlatformScreen *screen=nullptr)
static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, QPoint hotSpot=QPoint(-1, -1), bool invb=false, bool invm=false)
static QSize systemCursorSize()
static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor=1)
QWindowsPixmapCursorCacheKey(const QCursor &c, qreal scaleFactor)