Check if there is an old item before removing it
[profile/ivi/qtbase.git] / src / plugins / platforms / cocoa / qcocoamenu.mm
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner <james.turner@kdab.com>
4 ** Contact: http://www.qt-project.org/legal
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 Digia.  For licensing terms and
14 ** conditions see http://qt.digia.com/licensing.  For further information
15 ** use the contact form at http://qt.digia.com/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 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights.  These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file.  Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qcocoamenu.h"
43
44 #include "qcocoahelpers.h"
45 #include "qcocoaautoreleasepool.h"
46
47 #include <QtCore/QtDebug>
48
49 @interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> {
50     QCocoaMenu *m_menu;
51 }
52
53 - (id) initWithMenu:(QCocoaMenu*) m;
54
55 @end
56
57 @implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate)
58
59 - (id) initWithMenu:(QCocoaMenu*) m
60 {
61     if ((self = [super init]))
62         m_menu = m;
63
64     return self;
65 }
66
67 - (void) menuWillOpen:(NSMenu*)m
68 {
69     Q_UNUSED(m);
70     emit m_menu->aboutToShow();
71 }
72
73 - (void) menuDidClose:(NSMenu*)m
74 {
75     Q_UNUSED(m);
76     // wrong, but it's the best we can do
77     emit m_menu->aboutToHide();
78 }
79
80 - (void) itemFired:(NSMenuItem*) item
81 {
82     QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
83     cocoaItem->activated();
84 }
85
86 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
87 {
88     if (![menuItem tag])
89         return YES;
90
91
92     QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
93     return cocoaItem->isEnabled();
94 }
95
96 @end
97
98 QT_BEGIN_NAMESPACE
99
100 QCocoaMenu::QCocoaMenu() :
101     m_enabled(true),
102     m_tag(0)
103 {
104     m_delegate = [[QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) alloc] initWithMenu:this];
105     m_nativeItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
106     m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"];
107     [m_nativeMenu setAutoenablesItems:YES];
108     m_nativeMenu.delegate = (QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) *) m_delegate;
109     [m_nativeItem setSubmenu:m_nativeMenu];
110 }
111
112 QCocoaMenu::~QCocoaMenu()
113 {
114     QCocoaAutoReleasePool pool;
115     [m_nativeItem setSubmenu:nil];
116     [m_nativeMenu release];
117     [m_delegate release];
118     [m_nativeItem release];
119 }
120
121 void QCocoaMenu::setText(const QString &text)
122 {
123     QCocoaAutoReleasePool pool;
124     QString stripped = qt_mac_removeAmpersandEscapes(text);
125     [m_nativeMenu setTitle:QCFString::toNSString(stripped)];
126     [m_nativeItem setTitle:QCFString::toNSString(stripped)];
127 }
128
129 void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
130 {
131     QCocoaAutoReleasePool pool;
132     QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
133     QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before);
134
135     cocoaItem->sync();
136     if (beforeItem) {
137         int index = m_menuItems.indexOf(beforeItem);
138         // if a before item is supplied, it should be in the menu
139         if (index < 0) {
140             qWarning() << Q_FUNC_INFO << "Before menu item not found";
141             return;
142         }
143         m_menuItems.insert(index, cocoaItem);
144     } else {
145         m_menuItems.append(cocoaItem);
146     }
147
148     insertNative(cocoaItem, beforeItem);
149 }
150
151 void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
152 {
153     [item->nsItem() setTarget:m_delegate];
154     if (!item->menu())
155         [item->nsItem() setAction:@selector(itemFired:)];
156
157     if (item->isMerged())
158         return;
159
160     if ([item->nsItem() menu]) {
161         qWarning() << Q_FUNC_INFO << "Menu item is already in a menu, remove it from the other menu first before inserting";
162         return;
163     }
164     // if the item we're inserting before is merged, skip along until
165     // we find a non-merged real item to insert ahead of.
166     while (beforeItem && beforeItem->isMerged()) {
167         beforeItem = itemOrNull(m_menuItems.indexOf(beforeItem) + 1);
168     }
169
170     if (beforeItem) {
171         if (beforeItem->isMerged()) {
172             qWarning() << Q_FUNC_INFO << "No non-merged before menu item found";
173             return;
174         }
175         NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
176         [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex];
177     } else {
178         [m_nativeMenu addItem: item->nsItem()];
179     }
180 }
181
182 void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
183 {
184     QCocoaAutoReleasePool pool;
185     QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
186     if (!m_menuItems.contains(cocoaItem)) {
187         qWarning() << Q_FUNC_INFO << "Menu does not contain the item to be removed";
188         return;
189     }
190     m_menuItems.removeOne(cocoaItem);
191     if (!cocoaItem->isMerged()) {
192         if (m_nativeMenu != [cocoaItem->nsItem() menu]) {
193             qWarning() << Q_FUNC_INFO << "Item to remove does not belong to this menu";
194             return;
195         }
196         [m_nativeMenu removeItem: cocoaItem->nsItem()];
197     }
198 }
199
200 QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const
201 {
202     if ((index < 0) || (index >= m_menuItems.size()))
203         return 0;
204
205     return m_menuItems.at(index);
206 }
207
208 void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
209 {
210     QCocoaAutoReleasePool pool;
211     QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
212     if (!m_menuItems.contains(cocoaItem)) {
213         qWarning() << Q_FUNC_INFO << "Item does not belong to this menu";
214         return;
215     }
216
217     bool wasMerged = cocoaItem->isMerged();
218     NSMenuItem *oldItem = [m_nativeMenu itemWithTag:(NSInteger) cocoaItem];
219
220     if (cocoaItem->sync() != oldItem) {
221         // native item was changed for some reason
222         if (!wasMerged && oldItem)
223             [m_nativeMenu removeItem:oldItem];
224
225         QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1);
226         insertNative(cocoaItem, beforeItem);
227     }
228 }
229
230 void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
231 {
232     QCocoaAutoReleasePool pool;
233     if (enable) {
234         bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
235         NSMenuItem *previousItem = nil;
236
237         NSArray *itemArray = [m_nativeMenu itemArray];
238         for (unsigned int i = 0; i < [itemArray count]; ++i) {
239             NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]);
240             if ([item isSeparatorItem])
241                 [item setHidden:previousIsSeparator];
242
243             if (![item isHidden]) {
244                 previousItem = item;
245                 previousIsSeparator = ([previousItem isSeparatorItem]);
246             }
247         }
248
249         // We now need to check the final item since we don't want any separators at the end of the list.
250         if (previousItem && previousIsSeparator)
251             [previousItem setHidden:YES];
252     } else {
253         foreach (QCocoaMenuItem *item, m_menuItems) {
254             if (!item->isSeparator())
255                 continue;
256
257             // sync the visiblity directly
258             item->sync();
259         }
260     }
261 }
262
263 void QCocoaMenu::setParentItem(QCocoaMenuItem *item)
264 {
265     Q_UNUSED(item);
266 }
267
268 void QCocoaMenu::setEnabled(bool enabled)
269 {
270     m_enabled = enabled;
271 }
272
273 QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
274 {
275     return m_menuItems.at(position);
276 }
277
278 QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
279 {
280     foreach (QCocoaMenuItem *item, m_menuItems) {
281         if (item->tag() ==  tag)
282             return item;
283     }
284
285     return 0;
286 }
287
288 QList<QCocoaMenuItem *> QCocoaMenu::merged() const
289 {
290     QList<QCocoaMenuItem *> result;
291     foreach (QCocoaMenuItem *item, m_menuItems) {
292         if (item->menu()) { // recurse into submenus
293             result.append(item->menu()->merged());
294             continue;
295         }
296
297         if (item->isMerged())
298             result.append(item);
299     }
300
301     return result;
302 }
303
304 void QCocoaMenu::syncModalState(bool modal)
305 {
306     if (!m_enabled)
307         modal = true;
308
309     [m_nativeItem setEnabled:!modal];
310
311     foreach (QCocoaMenuItem *item, m_menuItems) {
312         if (item->menu()) { // recurse into submenus
313             item->menu()->syncModalState(modal);
314             continue;
315         }
316
317         item->syncModalState(modal);
318     }
319 }
320
321 QT_END_NAMESPACE