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