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);