1 /****************************************************************************
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
6 ** This file is part of the plugins of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qcocoamenu.h"
44 #include "qcocoahelpers.h"
45 #include "qcocoaautoreleasepool.h"
47 #include <QtCore/QtDebug>
48 #include "qcocoaapplication.h"
49 #include "qcocoamenuloader.h"
51 static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
53 return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
56 @interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> {
60 - (id) initWithMenu:(QCocoaMenu*) m;
64 @implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate)
66 - (id) initWithMenu:(QCocoaMenu*) m
68 if ((self = [super init]))
74 - (void) menuWillOpen:(NSMenu*)m
77 emit m_menu->aboutToShow();
80 - (void) menuDidClose:(NSMenu*)m
83 // wrong, but it's the best we can do
84 emit m_menu->aboutToHide();
87 - (void) itemFired:(NSMenuItem*) item
89 QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
90 cocoaItem->activated();
93 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
99 QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
100 return cocoaItem->isEnabled();
107 QCocoaMenu::QCocoaMenu() :
111 m_delegate = [[QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) alloc] initWithMenu:this];
112 m_nativeItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
113 m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"];
114 [m_nativeMenu setAutoenablesItems:YES];
115 m_nativeMenu.delegate = (QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) *) m_delegate;
116 [m_nativeItem setSubmenu:m_nativeMenu];
119 QCocoaMenu::~QCocoaMenu()
121 QCocoaAutoReleasePool pool;
122 [m_nativeItem setSubmenu:nil];
123 [m_nativeMenu release];
124 [m_delegate release];
125 [m_nativeItem release];
128 void QCocoaMenu::setText(const QString &text)
130 QCocoaAutoReleasePool pool;
131 QString stripped = qt_mac_removeAmpersandEscapes(text);
132 [m_nativeMenu setTitle:QCFString::toNSString(stripped)];
133 [m_nativeItem setTitle:QCFString::toNSString(stripped)];
136 void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
138 QCocoaAutoReleasePool pool;
139 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
140 QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before);
144 int index = m_menuItems.indexOf(beforeItem);
145 // if a before item is supplied, it should be in the menu
147 qWarning() << Q_FUNC_INFO << "Before menu item not found";
150 m_menuItems.insert(index, cocoaItem);
152 m_menuItems.append(cocoaItem);
155 insertNative(cocoaItem, beforeItem);
158 void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
160 [item->nsItem() setTarget:m_delegate];
162 [item->nsItem() setAction:@selector(itemFired:)];
164 if (item->isMerged())
167 if ([item->nsItem() menu]) {
168 qWarning() << Q_FUNC_INFO << "Menu item is already in a menu, remove it from the other menu first before inserting";
171 // if the item we're inserting before is merged, skip along until
172 // we find a non-merged real item to insert ahead of.
173 while (beforeItem && beforeItem->isMerged()) {
174 beforeItem = itemOrNull(m_menuItems.indexOf(beforeItem) + 1);
178 if (beforeItem->isMerged()) {
179 qWarning() << Q_FUNC_INFO << "No non-merged before menu item found";
182 NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
183 [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex];
185 [m_nativeMenu addItem: item->nsItem()];
189 void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
191 QCocoaAutoReleasePool pool;
192 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
193 if (!m_menuItems.contains(cocoaItem)) {
194 qWarning() << Q_FUNC_INFO << "Menu does not contain the item to be removed";
197 m_menuItems.removeOne(cocoaItem);
198 if (!cocoaItem->isMerged()) {
199 if (m_nativeMenu != [cocoaItem->nsItem() menu]) {
200 qWarning() << Q_FUNC_INFO << "Item to remove does not belong to this menu";
203 [m_nativeMenu removeItem: cocoaItem->nsItem()];
207 QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const
209 if ((index < 0) || (index >= m_menuItems.size()))
212 return m_menuItems.at(index);
215 void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
217 QCocoaAutoReleasePool pool;
218 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
219 if (!m_menuItems.contains(cocoaItem)) {
220 qWarning() << Q_FUNC_INFO << "Item does not belong to this menu";
224 bool wasMerged = cocoaItem->isMerged();
225 NSMenu *oldMenu = wasMerged ? [getMenuLoader() applicationMenu] : m_nativeMenu;
226 NSMenuItem *oldItem = [oldMenu itemWithTag:(NSInteger) cocoaItem];
228 if (cocoaItem->sync() != oldItem) {
229 // native item was changed for some reason
232 [oldItem setEnabled:NO];
233 [oldItem setHidden:YES];
235 [m_nativeMenu removeItem:oldItem];
239 QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1);
240 insertNative(cocoaItem, beforeItem);
244 void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
246 QCocoaAutoReleasePool pool;
248 bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
249 NSMenuItem *previousItem = nil;
251 NSArray *itemArray = [m_nativeMenu itemArray];
252 for (unsigned int i = 0; i < [itemArray count]; ++i) {
253 NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]);
254 if ([item isSeparatorItem])
255 [item setHidden:previousIsSeparator];
257 if (![item isHidden]) {
259 previousIsSeparator = ([previousItem isSeparatorItem]);
263 // We now need to check the final item since we don't want any separators at the end of the list.
264 if (previousItem && previousIsSeparator)
265 [previousItem setHidden:YES];
267 foreach (QCocoaMenuItem *item, m_menuItems) {
268 if (!item->isSeparator())
271 // sync the visiblity directly
277 void QCocoaMenu::setParentItem(QCocoaMenuItem *item)
282 void QCocoaMenu::setEnabled(bool enabled)
285 syncModalState(!m_enabled);
288 void QCocoaMenu::setVisible(bool visible)
290 [m_nativeItem setSubmenu:(visible ? m_nativeMenu : nil)];
293 QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
295 return m_menuItems.at(position);
298 QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
300 foreach (QCocoaMenuItem *item, m_menuItems) {
301 if (item->tag() == tag)
308 QList<QCocoaMenuItem *> QCocoaMenu::merged() const
310 QList<QCocoaMenuItem *> result;
311 foreach (QCocoaMenuItem *item, m_menuItems) {
312 if (item->menu()) { // recurse into submenus
313 result.append(item->menu()->merged());
317 if (item->isMerged())
324 void QCocoaMenu::syncModalState(bool modal)
329 [m_nativeItem setEnabled:!modal];
331 foreach (QCocoaMenuItem *item, m_menuItems) {
332 if (item->menu()) { // recurse into submenus
333 item->menu()->syncModalState(modal);
337 item->syncModalState(modal);