1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcocoacursor.h"
41#include "qcocoawindow.h"
42#include "qcocoascreen.h"
43#include "qcocoahelpers.h"
44#include <QtGui/private/qcoregraphics_p.h>
45
46#include <QtGui/QBitmap>
47
48QT_BEGIN_NAMESPACE
49
50QCocoaCursor::QCocoaCursor()
51{
52}
53
54QCocoaCursor::~QCocoaCursor()
55{
56    // release cursors
57    QHash<Qt::CursorShape, NSCursor *>::const_iterator i = m_cursors.constBegin();
58    while (i != m_cursors.constEnd()) {
59        [*i release];
60        ++i;
61    }
62}
63
64void QCocoaCursor::changeCursor(QCursor *cursor, QWindow *window)
65{
66    NSCursor *cocoaCursor = convertCursor(cursor);
67
68    if (QPlatformWindow * platformWindow = window->handle())
69        static_cast<QCocoaWindow *>(platformWindow)->setWindowCursor(cocoaCursor);
70}
71
72QPoint QCocoaCursor::pos() const
73{
74    return QCocoaScreen::mapFromNative([NSEvent mouseLocation]).toPoint();
75}
76
77void QCocoaCursor::setPos(const QPoint &position)
78{
79    CGPoint pos;
80    pos.x = position.x();
81    pos.y = position.y();
82
83    CGEventRef e = CGEventCreateMouseEvent(nullptr, kCGEventMouseMoved, pos, kCGMouseButtonLeft);
84    CGEventPost(kCGHIDEventTap, e);
85    CFRelease(e);
86}
87
88
89QSize QCocoaCursor::size() const
90{
91    NSCursor *cocoaCursor = NSCursor.currentSystemCursor;
92    if (!cocoaCursor)
93        return QPlatformCursor::size();
94    NSImage *cursorImage = cocoaCursor.image;
95    if (!cursorImage)
96        return QPlatformCursor::size();
97
98    QSizeF size = QSizeF::fromCGSize(cursorImage.size);
99    NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
100    NSDictionary *accessSettings = [defaults persistentDomainForName:@"com.apple.universalaccess"];
101    if (accessSettings == nil)
102        return size.toSize();
103
104    float sizeScale = [accessSettings[@"mouseDriverCursorSize"] floatValue];
105    if (sizeScale > 0) {
106        size.rwidth() *= sizeScale;
107        size.rheight() *= sizeScale;
108    }
109
110    return size.toSize();
111}
112
113NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
114{
115    if (!cursor)
116        return nil;
117
118    const Qt::CursorShape newShape = cursor->shape();
119    NSCursor *cocoaCursor;
120
121    // Check for a suitable built-in NSCursor first:
122    switch (newShape) {
123    case Qt::ArrowCursor:
124        cocoaCursor= [NSCursor arrowCursor];
125        break;
126    case Qt::ForbiddenCursor:
127        cocoaCursor = [NSCursor operationNotAllowedCursor];
128        break;
129    case Qt::CrossCursor:
130        cocoaCursor = [NSCursor crosshairCursor];
131        break;
132    case Qt::IBeamCursor:
133        cocoaCursor = [NSCursor IBeamCursor];
134        break;
135    case Qt::WhatsThisCursor: //for now just use the pointing hand
136    case Qt::PointingHandCursor:
137        cocoaCursor = [NSCursor pointingHandCursor];
138        break;
139    case Qt::SplitVCursor:
140        cocoaCursor = [NSCursor resizeUpDownCursor];
141        break;
142    case Qt::SplitHCursor:
143        cocoaCursor = [NSCursor resizeLeftRightCursor];
144        break;
145    case Qt::OpenHandCursor:
146        cocoaCursor = [NSCursor openHandCursor];
147        break;
148    case Qt::ClosedHandCursor:
149        cocoaCursor = [NSCursor closedHandCursor];
150        break;
151    case Qt::DragMoveCursor:
152        cocoaCursor = [NSCursor crosshairCursor];
153        break;
154    case Qt::DragCopyCursor:
155        cocoaCursor = [NSCursor dragCopyCursor];
156        break;
157    case Qt::DragLinkCursor:
158        cocoaCursor = [NSCursor dragLinkCursor];
159        break;
160    default : {
161        // No suitable OS cursor exist, use cursors provided
162        // by Qt for the rest. Check for a cached cursor:
163        cocoaCursor = m_cursors.value(newShape);
164        if (cocoaCursor && cursor->shape() == Qt::BitmapCursor) {
165            [cocoaCursor release];
166            cocoaCursor = nil;
167        }
168        if (!cocoaCursor) {
169            cocoaCursor = createCursorData(cursor);
170            if (!cocoaCursor)
171                return [NSCursor arrowCursor];
172
173            m_cursors.insert(newShape, cocoaCursor);
174        }
175
176        break; }
177    }
178    return cocoaCursor;
179}
180
181
182// Creates an NSCursor for the given QCursor.
183NSCursor *QCocoaCursor::createCursorData(QCursor *cursor)
184{
185    /* Note to self... ***
186     * mask x data
187     * 0xFF x 0x00 == fully opaque white
188     * 0x00 x 0xFF == xor'd black
189     * 0xFF x 0xFF == fully opaque black
190     * 0x00 x 0x00 == fully transparent
191     */
192#define QT_USE_APPROXIMATE_CURSORS
193#ifdef QT_USE_APPROXIMATE_CURSORS
194    static const uchar cur_ver_bits[] = {
195        0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0,
196        0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0f, 0xf0,
197        0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00 };
198    static const uchar mcur_ver_bits[] = {
199        0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x0f, 0xe0, 0x1f, 0xf0, 0x3f, 0xf8,
200        0x7f, 0xfc, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x7f, 0xfc, 0x3f, 0xf8,
201        0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80 };
202
203    static const uchar cur_hor_bits[] = {
204        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x18, 0x30,
205        0x38, 0x38, 0x7f, 0xfc, 0x7f, 0xfc, 0x38, 0x38, 0x18, 0x30, 0x08, 0x20,
206        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
207    static const uchar mcur_hor_bits[] = {
208        0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x0c, 0x60, 0x1c, 0x70, 0x3c, 0x78,
209        0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3c, 0x78,
210        0x1c, 0x70, 0x0c, 0x60, 0x04, 0x40, 0x00, 0x00 };
211
212    static const uchar cur_fdiag_bits[] = {
213        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0x78,
214        0x00, 0xf8, 0x01, 0xd8, 0x23, 0x88, 0x37, 0x00, 0x3e, 0x00, 0x3c, 0x00,
215        0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00 };
216    static const uchar mcur_fdiag_bits[] = {
217        0x00, 0x00, 0x00, 0x00, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, 0xfc,
218        0x41, 0xfc, 0x63, 0xfc, 0x77, 0xdc, 0x7f, 0x8c, 0x7f, 0x04, 0x7e, 0x00,
219        0x7f, 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x00, 0x00 };
220
221    static const uchar cur_bdiag_bits[] = {
222        0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, 0x00,
223        0x37, 0x00, 0x23, 0x88, 0x01, 0xd8, 0x00, 0xf8, 0x00, 0x78, 0x00, 0xf8,
224        0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
225    static const uchar mcur_bdiag_bits[] = {
226        0x00, 0x00, 0x7f, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7e, 0x00, 0x7f, 0x04,
227        0x7f, 0x8c, 0x77, 0xdc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, 0xfc,
228        0x03, 0xfc, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x00 };
229
230    static const unsigned char cur_up_arrow_bits[] = {
231        0x00, 0x80, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20, 0x04, 0x10,
232        0x04, 0x10, 0x08, 0x08, 0x0f, 0x78, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40,
233        0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0 };
234    static const unsigned char mcur_up_arrow_bits[] = {
235        0x00, 0x80, 0x01, 0xc0, 0x01, 0xc0, 0x03, 0xe0, 0x03, 0xe0, 0x07, 0xf0,
236        0x07, 0xf0, 0x0f, 0xf8, 0x0f, 0xf8, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
237        0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0 };
238#endif
239    const uchar *cursorData = nullptr;
240    const uchar *cursorMaskData = nullptr;
241    QPoint hotspot = cursor->hotSpot();
242
243    switch (cursor->shape()) {
244    case Qt::BitmapCursor: {
245        if (cursor->pixmap().isNull())
246            return createCursorFromBitmap(cursor->bitmap(Qt::ReturnByValue), cursor->mask(Qt::ReturnByValue), hotspot);
247        else
248            return createCursorFromPixmap(cursor->pixmap(), hotspot);
249        break; }
250    case Qt::BlankCursor: {
251        QPixmap pixmap = QPixmap(16, 16);
252        pixmap.fill(Qt::transparent);
253        return createCursorFromPixmap(pixmap);
254        break; }
255    case Qt::WaitCursor: {
256        QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/spincursor.png"));
257        return createCursorFromPixmap(pixmap, hotspot);
258        break; }
259    case Qt::SizeAllCursor: {
260        QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/sizeallcursor.png"));
261        return createCursorFromPixmap(pixmap, QPoint(8, 8));
262        break; }
263    case Qt::BusyCursor: {
264        QPixmap pixmap = QPixmap(QLatin1String(":/qt-project.org/mac/cursors/images/waitcursor.png"));
265        return createCursorFromPixmap(pixmap, hotspot);
266        break; }
267#define QT_USE_APPROXIMATE_CURSORS
268#ifdef QT_USE_APPROXIMATE_CURSORS
269    case Qt::SizeVerCursor:
270        cursorData = cur_ver_bits;
271        cursorMaskData = mcur_ver_bits;
272        hotspot = QPoint(8, 8);
273        break;
274    case Qt::SizeHorCursor:
275        cursorData = cur_hor_bits;
276        cursorMaskData = mcur_hor_bits;
277        hotspot = QPoint(8, 8);
278        break;
279    case Qt::SizeBDiagCursor:
280        cursorData = cur_fdiag_bits;
281        cursorMaskData = mcur_fdiag_bits;
282        hotspot = QPoint(8, 8);
283        break;
284    case Qt::SizeFDiagCursor:
285        cursorData = cur_bdiag_bits;
286        cursorMaskData = mcur_bdiag_bits;
287        hotspot = QPoint(8, 8);
288        break;
289    case Qt::UpArrowCursor:
290        cursorData = cur_up_arrow_bits;
291        cursorMaskData = mcur_up_arrow_bits;
292        hotspot = QPoint(8, 0);
293        break;
294#endif
295    default:
296        qWarning("Qt: QCursor::update: Invalid cursor shape %d", cursor->shape());
297        return nil;
298    }
299
300    // Create an NSCursor from image data if this a self-provided cursor.
301    if (cursorData) {
302        QBitmap bitmap(QBitmap::fromData(QSize(16, 16), cursorData, QImage::Format_Mono));
303        QBitmap mask(QBitmap::fromData(QSize(16, 16), cursorMaskData, QImage::Format_Mono));
304        return (createCursorFromBitmap(bitmap, mask, hotspot));
305    }
306
307    return nil; // should not happen, all cases covered above
308}
309
310NSCursor *QCocoaCursor::createCursorFromBitmap(const QBitmap &bitmap, const QBitmap &mask, const QPoint hotspot)
311{
312    QImage finalCursor(bitmap.size(), QImage::Format_ARGB32);
313    QImage bmi = bitmap.toImage().convertToFormat(QImage::Format_RGB32);
314    QImage bmmi = mask.toImage().convertToFormat(QImage::Format_RGB32);
315    for (int row = 0; row < finalCursor.height(); ++row) {
316        QRgb *bmData = reinterpret_cast<QRgb *>(bmi.scanLine(row));
317        QRgb *bmmData = reinterpret_cast<QRgb *>(bmmi.scanLine(row));
318        QRgb *finalData = reinterpret_cast<QRgb *>(finalCursor.scanLine(row));
319        for (int col = 0; col < finalCursor.width(); ++col) {
320            if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) {
321                finalData[col] = 0xffffffff;
322            } else if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) {
323                finalData[col] = 0x7f000000;
324            } else if (bmmData[col] == 0xffffffff && bmData[col] == 0xffffffff) {
325                finalData[col] = 0x00000000;
326            } else {
327                finalData[col] = 0xff000000;
328            }
329        }
330    }
331
332    return createCursorFromPixmap(QPixmap::fromImage(finalCursor), hotspot);
333}
334
335NSCursor *QCocoaCursor::createCursorFromPixmap(const QPixmap &pixmap, const QPoint hotspot)
336{
337    NSPoint hotSpot = NSMakePoint(hotspot.x(), hotspot.y());
338    auto *image = [NSImage imageFromQImage:pixmap.toImage()];
339    return [[NSCursor alloc] initWithImage:image hotSpot:hotSpot];
340}
341
342QT_END_NAMESPACE
343