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>
49 @interface QT_MANGLE_NAMESPACE(QCocoaMenuDelegate) : NSObject <NSMenuDelegate> {
53 - (id) initWithMenu:(QCocoaMenu*) m;
57 @implementation QT_MANGLE_NAMESPACE(QCocoaMenuDelegate)
59 - (id) initWithMenu:(QCocoaMenu*) m
61 if ((self = [super init]))
67 - (void) menuWillOpen:(NSMenu*)m
70 emit m_menu->aboutToShow();
73 - (void) menuDidClose:(NSMenu*)m
76 // wrong, but it's the best we can do
77 emit m_menu->aboutToHide();
80 - (void) itemFired:(NSMenuItem*) item
82 QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
83 cocoaItem->activated();
86 - (BOOL)validateMenuItem:(NSMenuItem*)menuItem
92 QCocoaMenuItem* cocoaItem = reinterpret_cast<QCocoaMenuItem *>([menuItem tag]);
93 return cocoaItem->isEnabled();
100 QCocoaMenu::QCocoaMenu() :
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];
112 QCocoaMenu::~QCocoaMenu()
114 QCocoaAutoReleasePool pool;
115 [m_nativeItem setSubmenu:nil];
116 [m_nativeMenu release];
117 [m_delegate release];
118 [m_nativeItem release];
121 void QCocoaMenu::setText(const QString &text)
123 QCocoaAutoReleasePool pool;
124 QString stripped = qt_mac_removeAmpersandEscapes(text);
125 [m_nativeMenu setTitle:QCFString::toNSString(stripped)];
126 [m_nativeItem setTitle:QCFString::toNSString(stripped)];
129 void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
131 QCocoaAutoReleasePool pool;
132 QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
133 QCocoaMenuItem *beforeItem = static_cast<QCocoaMenuItem *>(before);
137 int index = m_menuItems.indexOf(beforeItem);
138 // if a before item is supplied, it should be in the menu
140 qWarning() << Q_FUNC_INFO << "Before menu item not found";
143 m_menuItems.insert(index, cocoaItem);
145 m_menuItems.append(cocoaItem);
148 insertNative(cocoaItem, beforeItem);
151 void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
153 [item->nsItem() setTarget:m_delegate];
155 [item->nsItem() setAction:@selector(itemFired:)];
157 if (item->isMerged())
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";
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);
171 if (beforeItem->isMerged()) {
172 qWarning() << Q_FUNC_INFO << "No non-merged before menu item found";
175 NSUInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
176 [m_nativeMenu insertItem: item->nsItem() atIndex: nativeIndex];
178 [m_nativeMenu addItem: item->nsItem()];
182 void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
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";
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";
196 [m_nativeMenu removeItem: cocoaItem->nsItem()];
200 QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const
202 if ((index < 0) || (index >= m_menuItems.size()))
205 return m_menuItems.at(index);
208 void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem)
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";
217 bool wasMerged = cocoaItem->isMerged();
218 NSMenuItem *oldItem = [m_nativeMenu itemWithTag:(NSInteger) cocoaItem];
220 if (cocoaItem->sync() != oldItem) {
221 // native item was changed for some reason
222 if (!wasMerged && oldItem)
223 [m_nativeMenu removeItem:oldItem];
225 QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1);
226 insertNative(cocoaItem, beforeItem);
230 void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
232 QCocoaAutoReleasePool pool;
234 bool previousIsSeparator = true; // setting to true kills all the separators placed at the top.
235 NSMenuItem *previousItem = nil;
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];
243 if (![item isHidden]) {
245 previousIsSeparator = ([previousItem isSeparatorItem]);
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];
253 foreach (QCocoaMenuItem *item, m_menuItems) {
254 if (!item->isSeparator())
257 // sync the visiblity directly
263 void QCocoaMenu::setParentItem(QCocoaMenuItem *item)
268 void QCocoaMenu::setEnabled(bool enabled)
273 QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
275 return m_menuItems.at(position);
278 QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
280 foreach (QCocoaMenuItem *item, m_menuItems) {
281 if (item->tag() == tag)
288 QList<QCocoaMenuItem *> QCocoaMenu::merged() const
290 QList<QCocoaMenuItem *> result;
291 foreach (QCocoaMenuItem *item, m_menuItems) {
292 if (item->menu()) { // recurse into submenus
293 result.append(item->menu()->merged());
297 if (item->isMerged())
304 void QCocoaMenu::syncModalState(bool modal)
309 [m_nativeItem setEnabled:!modal];
311 foreach (QCocoaMenuItem *item, m_menuItems) {
312 if (item->menu()) { // recurse into submenus
313 item->menu()->syncModalState(modal);
317 item->syncModalState(modal);