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 <QtGui/qtguiglobal.h>
41
42#include "qnsview.h"
43#include "qcocoawindow.h"
44#include "qcocoahelpers.h"
45#include "qcocoascreen.h"
46#include "qmultitouch_mac_p.h"
47#include "qcocoadrag.h"
48#include "qcocoainputcontext.h"
49#include <qpa/qplatformintegration.h>
50
51#include <qpa/qwindowsysteminterface.h>
52#include <QtGui/QTextFormat>
53#include <QtCore/QDebug>
54#include <QtCore/QPointer>
55#include <QtCore/QSet>
56#include <QtCore/qsysinfo.h>
57#include <QtGui/QAccessible>
58#include <QtGui/QImage>
59#include <private/qguiapplication_p.h>
60#include <private/qcoregraphics_p.h>
61#include <private/qwindow_p.h>
62#include "qcocoabackingstore.h"
63#ifndef QT_NO_OPENGL
64#include "qcocoaglcontext.h"
65#endif
66#include "qcocoaintegration.h"
67
68// Private interface
69@interface QNSView ()
70- (BOOL)isTransparentForUserInput;
71@property (assign) NSView* previousSuperview;
72@property (assign) NSWindow* previousWindow;
73@end
74
75@interface QNSView (Drawing) <CALayerDelegate>
76- (void)initDrawing;
77@end
78
79@interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject
80- (instancetype)initWithView:(QNSView *)theView;
81- (void)mouseMoved:(NSEvent *)theEvent;
82- (void)mouseEntered:(NSEvent *)theEvent;
83- (void)mouseExited:(NSEvent *)theEvent;
84- (void)cursorUpdate:(NSEvent *)theEvent;
85@end
86
87QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
88
89@interface QNSView (Mouse)
90- (void)initMouse;
91- (NSPoint)screenMousePoint:(NSEvent *)theEvent;
92- (void)mouseMovedImpl:(NSEvent *)theEvent;
93- (void)mouseEnteredImpl:(NSEvent *)theEvent;
94- (void)mouseExitedImpl:(NSEvent *)theEvent;
95@end
96
97@interface QNSView (Touch)
98@end
99
100@interface QNSView (Tablet)
101- (bool)handleTabletEvent:(NSEvent *)theEvent;
102@end
103
104@interface QNSView (Gestures)
105@end
106
107@interface QNSView (Dragging)
108-(void)registerDragTypes;
109@end
110
111@interface QNSView (Keys)
112@end
113
114@interface QNSView (ComplexText) <NSTextInputClient>
115- (void)textInputContextKeyboardSelectionDidChangeNotification:(NSNotification *)textInputContextKeyboardSelectionDidChangeNotification;
116@end
117
118@implementation QNSView {
119    QPointer<QCocoaWindow> m_platformWindow;
120    Qt::MouseButtons m_buttons;
121    Qt::MouseButtons m_acceptedMouseDowns;
122    Qt::MouseButtons m_frameStrutButtons;
123    QString m_composingText;
124    QPointer<QObject> m_composingFocusObject;
125    bool m_sendKeyEvent;
126    bool m_dontOverrideCtrlLMB;
127    bool m_sendUpAsRightButton;
128    Qt::KeyboardModifiers m_currentWheelModifiers;
129    NSString *m_inputSource;
130    QNSViewMouseMoveHelper *m_mouseMoveHelper;
131    bool m_resendKeyEvent;
132    bool m_scrolling;
133    bool m_updatingDrag;
134    NSEvent *m_currentlyInterpretedKeyEvent;
135    QSet<quint32> m_acceptedKeyDowns;
136}
137
138- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow
139{
140    if ((self = [super initWithFrame:NSZeroRect])) {
141        m_platformWindow = platformWindow;
142        m_sendKeyEvent = false;
143        m_inputSource = nil;
144        m_resendKeyEvent = false;
145        m_updatingDrag = false;
146        m_currentlyInterpretedKeyEvent = nil;
147
148        self.focusRingType = NSFocusRingTypeNone;
149
150        self.previousSuperview = nil;
151        self.previousWindow = nil;
152
153        [self initDrawing];
154        [self initMouse];
155        [self registerDragTypes];
156
157        [[NSNotificationCenter defaultCenter] addObserver:self
158                                              selector:@selector(textInputContextKeyboardSelectionDidChangeNotification:)
159                                              name:NSTextInputContextKeyboardSelectionDidChangeNotification
160                                              object:nil];
161    }
162    return self;
163}
164
165- (void)dealloc
166{
167    qCDebug(lcQpaWindow) << "Deallocating" << self;
168
169    [m_inputSource release];
170    [[NSNotificationCenter defaultCenter] removeObserver:self];
171    [m_mouseMoveHelper release];
172
173    [super dealloc];
174}
175
176- (NSString *)description
177{
178    NSMutableString *description = [NSMutableString stringWithString:[super description]];
179
180#ifndef QT_NO_DEBUG_STREAM
181    QString platformWindowDescription;
182    QDebug debug(&platformWindowDescription);
183    debug.nospace() << "; " << m_platformWindow << ">";
184
185    NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1];
186    [description replaceCharactersInRange:lastCharacter withString:platformWindowDescription.toNSString()];
187#endif
188
189    return description;
190}
191
192// ----------------------------- Re-parenting ---------------------------------
193
194- (void)removeFromSuperview
195{
196    QMacAutoReleasePool pool;
197    [super removeFromSuperview];
198}
199
200- (void)viewWillMoveToSuperview:(NSView *)newSuperview
201{
202    Q_ASSERT(!self.previousSuperview);
203    self.previousSuperview = self.superview;
204
205    if (newSuperview == self.superview)
206        qCDebug(lcQpaWindow) << "Re-ordering" << self << "inside" << self.superview;
207    else
208        qCDebug(lcQpaWindow) << "Re-parenting" << self << "from" << self.superview << "to" << newSuperview;
209}
210
211- (void)viewDidMoveToSuperview
212{
213    auto cleanup = qScopeGuard([&] { self.previousSuperview = nil; });
214
215    if (self.superview == self.previousSuperview) {
216        qCDebug(lcQpaWindow) << "Done re-ordering" << self << "new index:"
217            << [self.superview.subviews indexOfObject:self];
218        return;
219    }
220
221    qCDebug(lcQpaWindow) << "Done re-parenting" << self << "into" << self.superview;
222
223    // Note: at this point the view's window property hasn't been updated to match the window
224    // of the new superview. We have to wait for viewDidMoveToWindow for that to be reflected.
225
226    if (!m_platformWindow)
227        return;
228
229    if (!m_platformWindow->isEmbedded())
230        return;
231
232    if ([self superview]) {
233        QWindowSystemInterface::handleGeometryChange(m_platformWindow->window(), m_platformWindow->geometry());
234        [self setNeedsDisplay:YES];
235        QWindowSystemInterface::flushWindowSystemEvents();
236    }
237}
238
239- (void)viewWillMoveToWindow:(NSWindow *)newWindow
240{
241    Q_ASSERT(!self.previousWindow);
242    self.previousWindow = self.window;
243
244    // This callback is documented to be called also when a view is just moved between
245    // subviews in the same NSWindow, so we're not necessarily moving between NSWindows.
246    if (newWindow == self.window)
247        return;
248
249    qCDebug(lcQpaWindow) << "Moving" << self << "from" << self.window << "to" << newWindow;
250
251    // Note: at this point the superview has already been updated, so we know which view inside
252    // the new window the view will be a child of.
253}
254
255- (void)viewDidMoveToWindow
256{
257    auto cleanup = qScopeGuard([&] { self.previousWindow = nil; });
258
259    // This callback is documented to be called also when a view is just moved between
260    // subviews in the same NSWindow, so we're not necessarily moving between NSWindows.
261    if (self.window == self.previousWindow)
262        return;
263
264    qCDebug(lcQpaWindow) << "Done moving" << self << "to" << self.window;
265}
266
267// ----------------------------------------------------------------------------
268
269- (QWindow *)topLevelWindow
270{
271    if (!m_platformWindow)
272        return nullptr;
273
274    QWindow *focusWindow = m_platformWindow->window();
275
276    // For widgets we need to do a bit of trickery as the window
277    // to activate is the window of the top-level widget.
278    if (qstrcmp(focusWindow->metaObject()->className(), "QWidgetWindow") == 0) {
279        while (focusWindow->parent()) {
280            focusWindow = focusWindow->parent();
281        }
282    }
283
284    return focusWindow;
285}
286
287- (void)viewDidHide
288{
289    if (!m_platformWindow->isExposed())
290        return;
291
292    m_platformWindow->handleExposeEvent(QRegion());
293
294    // Note: setNeedsDisplay is automatically called for
295    // viewDidUnhide so no reason to override it here.
296}
297
298- (BOOL)isTransparentForUserInput
299{
300    return m_platformWindow->window() &&
301        m_platformWindow->window()->flags() & Qt::WindowTransparentForInput;
302}
303
304- (BOOL)becomeFirstResponder
305{
306    if (!m_platformWindow)
307        return NO;
308    if ([self isTransparentForUserInput])
309        return NO;
310    if (!m_platformWindow->windowIsPopupType())
311        QWindowSystemInterface::handleWindowActivated([self topLevelWindow]);
312    return YES;
313}
314
315- (BOOL)acceptsFirstResponder
316{
317    if (!m_platformWindow)
318        return NO;
319    if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder())
320        return NO;
321    if ([self isTransparentForUserInput])
322        return NO;
323    if ((m_platformWindow->window()->flags() & Qt::ToolTip) == Qt::ToolTip)
324        return NO;
325    return YES;
326}
327
328- (NSView *)hitTest:(NSPoint)aPoint
329{
330    NSView *candidate = [super hitTest:aPoint];
331    if (candidate == self) {
332        if ([self isTransparentForUserInput])
333            return nil;
334    }
335    return candidate;
336}
337
338- (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint
339{
340    // Calculate the mouse position in the QWindow and Qt screen coordinate system,
341    // starting from coordinates in the NSWindow coordinate system.
342    //
343    // This involves translating according to the window location on screen,
344    // as well as inverting the y coordinate due to the origin change.
345    //
346    // Coordinate system overview, outer to innermost:
347    //
348    // Name             Origin
349    //
350    // OS X screen      bottom-left
351    // Qt screen        top-left
352    // NSWindow         bottom-left
353    // NSView/QWindow   top-left
354    //
355    // NSView and QWindow are equal coordinate systems: the QWindow covers the
356    // entire NSView, and we've set the NSView's isFlipped property to true.
357
358    NSWindow *window = [self window];
359    NSPoint nsWindowPoint;
360    NSRect windowRect = [window convertRectFromScreen:NSMakeRect(mouseLocation.x, mouseLocation.y, 1, 1)];
361    nsWindowPoint = windowRect.origin;                    // NSWindow coordinates
362    NSPoint nsViewPoint = [self convertPoint: nsWindowPoint fromView: nil]; // NSView/QWindow coordinates
363    *qtWindowPoint = QPointF(nsViewPoint.x, nsViewPoint.y);                     // NSView/QWindow coordinates
364    *qtScreenPoint = QCocoaScreen::mapFromNative(mouseLocation);
365}
366
367@end
368
369#include "qnsview_drawing.mm"
370#include "qnsview_mouse.mm"
371#include "qnsview_touch.mm"
372#include "qnsview_gestures.mm"
373#include "qnsview_tablet.mm"
374#include "qnsview_dragging.mm"
375#include "qnsview_keys.mm"
376#include "qnsview_complextext.mm"
377#include "qnsview_menus.mm"
378#ifndef QT_NO_ACCESSIBILITY
379#include "qnsview_accessibility.mm"
380#endif
381
382// -----------------------------------------------------
383
384@implementation QNSView (QtExtras)
385
386- (QCocoaWindow*)platformWindow
387{
388    return m_platformWindow.data();;
389}
390
391@end
392