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/
6 ** This file is part of the plugins of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include "qcocoamenu.h"
44 #include "qcocoahelpers.h"
45 #include "qcocoaautoreleasepool.h"
47 @interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> {
51 - (id) initWithMenu:(QCocoaMenu*) m;
55 @implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate)
57 - (id) initWithMenu:(QCocoaMenu*) m
59 if ((self = [super init]))
65 - (void) menuWillOpen:(NSMenu*)m
68 emit m_menu->aboutToShow();
71 - (void) menuDidClose:(NSMenu*)m
74 // wrong, but it's the best we can do
75 emit m_menu->aboutToHide();
78 - (void) itemFired:(NSMenuItem*) item
80 QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
81 cocoaItem->activated();
84 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
90 QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
91 return cocoaItem->isEnabled();
98 QCocoaMenu::QCocoaMenu() :
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];
110 void QCocoaMenu::setText(const QString &text)
112 QString stripped = qt_mac_removeAmpersandEscapes(text);
113 [m_nativeMenu setTitle:QCFString::toNSString(stripped)];
114 [m_nativeItem setTitle:QCFString::toNSString(stripped)];
117 void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
119 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
120 QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before);
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);
129 m_menuItems.append(cocoaItem);
132 insertNative(cocoaItem, beforeItem);
135 void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
137 [item->nsItem() setTarget:m_delegate];
139 [item->nsItem() setAction:@selector(itemFired:)];
141 if (item->isMerged())
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);
151 Q_ASSERT(!beforeItem->isMerged());
152 NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
153 [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex];
155 [m_nativeMenu addItem: item->nsItem()];
159 void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
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()];
169 QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const
171 if ((index < 0) || (index >= m_menuItems.size()))
174 return m_menuItems.at(index);
177 void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
179 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
180 Q_ASSERT(m_menuItems.contains(cocoaItem));
182 bool wasMerged = cocoaItem->isMerged();
183 NSMenuItem *oldItem = [m_nativeMenu itemWithTag:(NSInteger) cocoaItem];
185 if (cocoaItem->sync() != oldItem) {
186 // native item was changed for some reason
188 [m_nativeMenu removeItem:oldItem];
191 QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1);
192 insertNative(cocoaItem, beforeItem);
196 void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
198 QCocoaAutoReleasePool pool;
200 bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
201 NSMenuItem *previousItem = nil;
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];
209 if (![item isHidden]) {
211 previousIsSeparator = ([previousItem isSeparatorItem]);
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];
219 foreach (QCocoaMenuItem *item, m_menuItems) {
220 if (!item->isSeparator())
223 // sync the visiblity directly
229 void QCocoaMenu::setParentItem(QCocoaMenuItem *item)
234 void QCocoaMenu::setEnabled(bool enabled)
239 QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
241 return m_menuItems.at(position);
244 QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
246 foreach (QCocoaMenuItem *item, m_menuItems) {
247 if (item->tag() == tag)
254 QList<QCocoaMenuItem *> QCocoaMenu::merged() const
256 QList<QCocoaMenuItem *> result;
257 foreach (QCocoaMenuItem *item, m_menuItems) {
258 if (item->menu()) { // recurse into submenus
259 result.append(item->menu()->merged());
263 if (item->isMerged())
270 void QCocoaMenu::syncModalState(bool modal)
275 [m_nativeItem setEnabled:!modal];
277 foreach (QCocoaMenuItem *item, m_menuItems) {
278 if (item->menu()) { // recurse into submenus
279 item->menu()->syncModalState(modal);
283 item->syncModalState(modal);