1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtGui module 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 http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://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 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** GNU General Public License Usage
31** Alternatively, this file may be used under the terms of the GNU
32** General Public License version 3.0 as published by the Free Software
33** Foundation and appearing in the file LICENSE.GPL included in the
34** packaging of this file.  Please review the following information to
35** ensure the GNU General Public License version 3.0 requirements will be
36** met: http://www.gnu.org/copyleft/gpl.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#import "private/qcocoawindowdelegate_mac_p.h"
43#ifdef QT_MAC_USE_COCOA
44#include <private/qwidget_p.h>
45#include <private/qapplication_p.h>
46#include <private/qt_cocoa_helpers_mac_p.h>
47#include <qevent.h>
48#include <qlayout.h>
49#include <qcoreapplication.h>
50#include <qmenubar.h>
51#include <QMainWindow>
52#include <QToolBar>
53#include <private/qmainwindowlayout_p.h>
54#include <private/qpaintengine_mac_p.h>
55
56QT_BEGIN_NAMESPACE
57extern QWidgetData *qt_qwidget_data(QWidget *); // qwidget.cpp
58extern void onApplicationWindowChangedActivation(QWidget *, bool); //qapplication_mac.mm
59extern bool qt_sendSpontaneousEvent(QObject *, QEvent *); // qapplication.cpp
60QT_END_NAMESPACE
61
62QT_USE_NAMESPACE
63
64static QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) *sharedCocoaWindowDelegate = nil;
65
66// This is a singleton, but unlike most Cocoa singletons, it lives in a library and could be
67// pontentially loaded and unloaded. This means we should at least attempt to do the
68// memory management correctly.
69
70static void cleanupCocoaWindowDelegate()
71{
72    [sharedCocoaWindowDelegate release];
73}
74
75@implementation QT_MANGLE_NAMESPACE(QCocoaWindowDelegate)
76
77- (id)init
78{
79    self = [super init];
80    if (self != nil) {
81        m_windowHash = new QHash<NSWindow *, QWidget *>();
82        m_drawerHash = new QHash<NSDrawer *, QWidget *>();
83    }
84    return self;
85}
86
87- (void)dealloc
88{
89    sharedCocoaWindowDelegate = nil;
90    QHash<NSWindow *, QWidget *>::const_iterator windowIt = m_windowHash->constBegin();
91    while (windowIt != m_windowHash->constEnd()) {
92        [windowIt.key() setDelegate:nil];
93        ++windowIt;
94    }
95    delete m_windowHash;
96    QHash<NSDrawer *, QWidget *>::const_iterator drawerIt = m_drawerHash->constBegin();
97    while (drawerIt != m_drawerHash->constEnd()) {
98        [drawerIt.key() setDelegate:nil];
99        ++drawerIt;
100    }
101    delete m_drawerHash;
102    [super dealloc];
103}
104
105+ (id)allocWithZone:(NSZone *)zone
106{
107    @synchronized(self) {
108        if (sharedCocoaWindowDelegate == nil) {
109            sharedCocoaWindowDelegate = [super allocWithZone:zone];
110            return sharedCocoaWindowDelegate;
111            qAddPostRoutine(cleanupCocoaWindowDelegate);
112        }
113    }
114    return nil;
115}
116
117+ (QT_MANGLE_NAMESPACE(QCocoaWindowDelegate)*)sharedDelegate
118{
119    @synchronized(self) {
120        if (sharedCocoaWindowDelegate == nil)
121            [[self alloc] init];
122    }
123    return [[sharedCocoaWindowDelegate retain] autorelease];
124}
125
126-(void)syncSizeForWidget:(QWidget *)qwidget toSize:(const QSize &)newSize fromSize:(const QSize &)oldSize
127{
128    qt_qwidget_data(qwidget)->crect.setSize(newSize);
129    // ### static contents optimization needs to go here
130    const OSViewRef view = qt_mac_nativeview_for(qwidget);
131    [view setFrameSize:NSMakeSize(newSize.width(), newSize.height())];
132    if (!qwidget->isVisible()) {
133        qwidget->setAttribute(Qt::WA_PendingResizeEvent, true);
134    } else {
135        QResizeEvent qre(newSize, oldSize);
136        if (qwidget->testAttribute(Qt::WA_PendingResizeEvent)) {
137            qwidget->setAttribute(Qt::WA_PendingResizeEvent, false);
138            QApplication::sendEvent(qwidget, &qre);
139        } else {
140            qt_sendSpontaneousEvent(qwidget, &qre);
141        }
142    }
143}
144
145- (void)dumpMaximizedStateforWidget:(QWidget*)qwidget window:(NSWindow *)window
146{
147    if (!window)
148        return; // Nothing to do.
149    QWidgetData *widgetData = qt_qwidget_data(qwidget);
150    if ((widgetData->window_state & Qt::WindowMaximized) && ![window isZoomed]) {
151        widgetData->window_state &= ~Qt::WindowMaximized;
152        QWindowStateChangeEvent e(Qt::WindowState(widgetData->window_state | Qt::WindowMaximized));
153        qt_sendSpontaneousEvent(qwidget, &e);
154    }
155}
156
157- (NSSize)closestAcceptableSizeForWidget:(QWidget *)qwidget window:(NSWindow *)window
158                             withNewSize:(NSSize)proposedSize
159{
160    [self dumpMaximizedStateforWidget:qwidget window:window];
161    QSize newSize = QLayout::closestAcceptableSize(qwidget,
162                                                   QSize(proposedSize.width, proposedSize.height));
163    return [NSWindow frameRectForContentRect:
164            NSMakeRect(0., 0., newSize.width(), newSize.height())
165                                   styleMask:[window styleMask]].size;
166}
167
168- (NSSize)windowWillResize:(NSWindow *)windowToResize toSize:(NSSize)proposedFrameSize
169{
170    QWidget *qwidget = m_windowHash->value(windowToResize);
171    return [self closestAcceptableSizeForWidget:qwidget window:windowToResize
172                                    withNewSize:[NSWindow contentRectForFrameRect:
173                                                 NSMakeRect(0, 0,
174                                                            proposedFrameSize.width,
175                                                            proposedFrameSize.height)
176                                                    styleMask:[windowToResize styleMask]].size];
177}
178
179- (NSSize)drawerWillResizeContents:(NSDrawer *)sender toSize:(NSSize)contentSize
180{
181    QWidget *qwidget = m_drawerHash->value(sender);
182    return [self closestAcceptableSizeForWidget:qwidget window:nil withNewSize:contentSize];
183}
184
185-(void)windowDidMiniaturize:(NSNotification*)notification
186{
187    QWidget *qwidget = m_windowHash->value([notification object]);
188    if (!qwidget->isMinimized()) {
189        QWidgetData *widgetData = qt_qwidget_data(qwidget);
190        widgetData->window_state = widgetData->window_state | Qt::WindowMinimized;
191        QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state & ~Qt::WindowMinimized));
192        qt_sendSpontaneousEvent(qwidget, &e);
193    }
194    // Send hide to match Qt on X11 and Windows
195    QEvent e(QEvent::Hide);
196    qt_sendSpontaneousEvent(qwidget, &e);
197}
198
199- (void)windowDidResize:(NSNotification *)notification
200{
201    NSWindow *window = [notification object];
202    QWidget *qwidget = m_windowHash->value(window);
203    QWidgetData *widgetData = qt_qwidget_data(qwidget);
204    if (!(qwidget->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen)) && [window isZoomed]) {
205        widgetData->window_state = widgetData->window_state | Qt::WindowMaximized;
206        QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state
207                                                   & ~Qt::WindowMaximized));
208        qt_sendSpontaneousEvent(qwidget, &e);
209    } else {
210        widgetData->window_state = widgetData->window_state & ~Qt::WindowMaximized;
211        QWindowStateChangeEvent e(Qt::WindowStates(widgetData->window_state
212                                                   | Qt::WindowMaximized));
213        qt_sendSpontaneousEvent(qwidget, &e);
214    }
215    NSRect rect = [[window contentView] frame];
216    const QSize newSize(rect.size.width, rect.size.height);
217    const QSize &oldSize = widgetData->crect.size();
218    if (newSize != oldSize) {
219        QWidgetPrivate::qt_mac_update_sizer(qwidget);
220        [self syncSizeForWidget:qwidget toSize:newSize fromSize:oldSize];
221    }
222
223    // We force the repaint to be synchronized with the resize of the window.
224    // Otherwise, the resize looks sluggish because we paint one event loop later.
225    if ([[window contentView] inLiveResize]) {
226        qwidget->repaint();
227
228        // We need to repaint the toolbar as well.
229        QMainWindow* mWindow = qobject_cast<QMainWindow*>(qwidget->window());
230        if (mWindow) {
231            QMainWindowLayout *mLayout = qobject_cast<QMainWindowLayout*>(mWindow->layout());
232            QList<QToolBar *> toolbarList = mLayout->qtoolbarsInUnifiedToolbarList;
233
234            for (int i = 0; i < toolbarList.size(); ++i) {
235                QToolBar* toolbar = toolbarList.at(i);
236                toolbar->repaint();
237            }
238        }
239    }
240}
241
242- (void)windowDidMove:(NSNotification *)notification
243{
244    // The code underneath needs to translate the window location
245    // from bottom left (which is the origin used by Cocoa) to
246    // upper left (which is the origin used by Qt):
247    NSWindow *window = [notification object];
248    NSRect newRect = [window frame];
249    QWidget *qwidget = m_windowHash->value(window);
250    QPoint qtPoint = flipPoint(NSMakePoint(newRect.origin.x,
251                                           newRect.origin.y + newRect.size.height)).toPoint();
252    const QRect &oldRect = qwidget->frameGeometry();
253
254    if (qtPoint.x() != oldRect.x() || qtPoint.y() != oldRect.y()) {
255        QWidgetData *widgetData = qt_qwidget_data(qwidget);
256        QRect oldCRect = widgetData->crect;
257        QWidgetPrivate *widgetPrivate = qt_widget_private(qwidget);
258        const QRect &fStrut = widgetPrivate->frameStrut();
259        widgetData->crect.moveTo(qtPoint.x() + fStrut.left(), qtPoint.y() + fStrut.top());
260        if (!qwidget->isVisible()) {
261            qwidget->setAttribute(Qt::WA_PendingMoveEvent, true);
262        } else {
263            QMoveEvent qme(qtPoint, oldRect.topLeft());
264            qt_sendSpontaneousEvent(qwidget, &qme);
265        }
266    }
267}
268
269-(BOOL)windowShouldClose:(id)windowThatWantsToClose
270{
271    QWidget *qwidget = m_windowHash->value(windowThatWantsToClose);
272    QScopedLoopLevelCounter counter(qt_widget_private(qwidget)->threadData);
273    return qt_widget_private(qwidget)->close_helper(QWidgetPrivate::CloseWithSpontaneousEvent);
274}
275
276-(void)windowDidDeminiaturize:(NSNotification *)notification
277{
278    QWidget *qwidget = m_windowHash->value([notification object]);
279    QWidgetData *widgetData = qt_qwidget_data(qwidget);
280    Qt::WindowStates currState = Qt::WindowStates(widgetData->window_state);
281    Qt::WindowStates newState = currState;
282    if (currState & Qt::WindowMinimized)
283        newState &= ~Qt::WindowMinimized;
284    if (!(currState & Qt::WindowActive))
285        newState |= Qt::WindowActive;
286    if (newState != currState) {
287        widgetData->window_state = newState;
288        QWindowStateChangeEvent e(currState);
289        qt_sendSpontaneousEvent(qwidget, &e);
290    }
291    QShowEvent qse;
292    qt_sendSpontaneousEvent(qwidget, &qse);
293}
294
295-(void)windowDidBecomeMain:(NSNotification*)notification
296{
297    QWidget *qwidget = m_windowHash->value([notification object]);
298    Q_ASSERT(qwidget);
299    onApplicationWindowChangedActivation(qwidget, true);
300}
301
302-(void)windowDidResignMain:(NSNotification*)notification
303{
304    QWidget *qwidget = m_windowHash->value([notification object]);
305    Q_ASSERT(qwidget);
306    onApplicationWindowChangedActivation(qwidget, false);
307}
308
309// These are the same as main, but they are probably better to keep separate since there is a
310// tiny difference between main and key windows.
311-(void)windowDidBecomeKey:(NSNotification*)notification
312{
313    QWidget *qwidget = m_windowHash->value([notification object]);
314    Q_ASSERT(qwidget);
315    onApplicationWindowChangedActivation(qwidget, true);
316}
317
318-(void)windowDidResignKey:(NSNotification*)notification
319{
320    QWidget *qwidget = m_windowHash->value([notification object]);
321    Q_ASSERT(qwidget);
322    onApplicationWindowChangedActivation(qwidget, false);
323}
324
325-(QWidget *)qt_qwidgetForWindow:(NSWindow *)window
326{
327    return m_windowHash->value(window);
328}
329
330- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
331{
332    Q_UNUSED(newFrame);
333    // saving the current window geometry before the window is maximized
334    QWidget *qwidget = m_windowHash->value(window);
335    QWidgetPrivate *widgetPrivate = qt_widget_private(qwidget);
336    if (qwidget->isWindow()) {
337        if(qwidget->windowState() & Qt::WindowMaximized) {
338            // Restoring
339            widgetPrivate->topData()->wasMaximized = false;
340        } else {
341            // Maximizing
342            widgetPrivate->topData()->normalGeometry = qwidget->geometry();
343            // If the window was maximized we need to update the coordinates since now it will start at 0,0.
344            // We do this in a special field that is only used when not restoring but manually resizing the window.
345            // Since the coordinates are fixed we just set a boolean flag.
346            widgetPrivate->topData()->wasMaximized = true;
347        }
348    }
349    return YES;
350}
351
352- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)defaultFrame
353{
354    NSRect frameToReturn = defaultFrame;
355    QWidget *qwidget = m_windowHash->value(window);
356    QSizeF size = qwidget->maximumSize();
357    NSRect windowFrameRect = [window frame];
358    NSRect viewFrameRect = [[window contentView] frame];
359    // consider additional size required for titlebar & frame
360    frameToReturn.size.width = qMin<CGFloat>(frameToReturn.size.width,
361            size.width()+(windowFrameRect.size.width - viewFrameRect.size.width));
362    frameToReturn.size.height = qMin<CGFloat>(frameToReturn.size.height,
363            size.height()+(windowFrameRect.size.height - viewFrameRect.size.height));
364    return frameToReturn;
365}
366
367- (void)becomeDelegateForWindow:(NSWindow *)window  widget:(QWidget *)widget
368{
369    m_windowHash->insert(window, widget);
370    [window setDelegate:self];
371}
372
373- (void)resignDelegateForWindow:(NSWindow *)window
374{
375    [window setDelegate:nil];
376    m_windowHash->remove(window);
377}
378
379- (void)becomeDelegateForDrawer:(NSDrawer *)drawer widget:(QWidget *)widget
380{
381    m_drawerHash->insert(drawer, widget);
382    [drawer setDelegate:self];
383    NSWindow *window = [[drawer contentView] window];
384    [self becomeDelegateForWindow:window widget:widget];
385}
386
387- (void)resignDelegateForDrawer:(NSDrawer *)drawer
388{
389    QWidget *widget = m_drawerHash->value(drawer);
390    [drawer setDelegate:nil];
391    if (widget)
392        [self resignDelegateForWindow:[[drawer contentView] window]];
393    m_drawerHash->remove(drawer);
394}
395
396- (void)windowDidChangeScreen:(NSNotification*)notification
397{
398    QWidget *qwidget = m_windowHash->value([notification object]);
399    QCoreGraphicsPaintEngine::clearColorSpace(qwidget);
400}
401
402- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu
403{
404    Q_UNUSED(menu);
405    QWidget *qwidget = m_windowHash->value(window);
406    if (qwidget && !qwidget->windowFilePath().isEmpty()) {
407        return YES;
408    }
409    return NO;
410}
411
412- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event
413                                                          from:(NSPoint)dragImageLocation
414                                                withPasteboard:(NSPasteboard *)pasteboard
415{
416    Q_UNUSED(event);
417    Q_UNUSED(dragImageLocation);
418    Q_UNUSED(pasteboard);
419    QWidget *qwidget = m_windowHash->value(window);
420    if (qwidget && !qwidget->windowFilePath().isEmpty()) {
421        return YES;
422    }
423    return NO;
424}
425
426- (void)syncContentViewFrame: (NSNotification *)notification
427{
428    NSView *cView = [notification object];
429    if (cView) {
430        NSWindow *window = [cView window];
431        QWidget *qwidget = m_windowHash->value(window);
432        if (qwidget) {
433            QWidgetData *widgetData = qt_qwidget_data(qwidget);
434            NSRect rect = [cView frame];
435            const QSize newSize(rect.size.width, rect.size.height);
436            const QSize &oldSize = widgetData->crect.size();
437            if (newSize != oldSize) {
438                [self syncSizeForWidget:qwidget toSize:newSize fromSize:oldSize];
439            }
440        }
441
442    }
443}
444
445@end
446#endif// QT_MAC_USE_COCOA
447