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 "qcocoamenuitem.h"
44 #include "qcocoamenu.h"
45 #include "qcocoahelpers.h"
46 #include "qcocoaautoreleasepool.h"
48 #include "qcocoaapplication.h" // for custom application category
49 #include "qcocoamenuloader.h"
51 #include <QtCore/QDebug>
53 static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
55 return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
59 static quint32 constructModifierMask(quint32 accel_key)
62 const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta);
63 if ((accel_key & Qt::CTRL) == Qt::CTRL)
64 ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask);
65 if ((accel_key & Qt::META) == Qt::META)
66 ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask);
67 if ((accel_key & Qt::ALT) == Qt::ALT)
68 ret |= NSAlternateKeyMask;
69 if ((accel_key & Qt::SHIFT) == Qt::SHIFT)
70 ret |= NSShiftKeyMask;
74 // return an autoreleased string given a QKeySequence (currently only looks at the first one).
75 NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
77 quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL));
78 QChar cocoa_key = qt_mac_qtKey2CocoaKey(Qt::Key(accel_key));
79 if (cocoa_key.isNull())
80 cocoa_key = QChar(accel_key).toLower().unicode();
81 return [NSString stringWithCharacters:&cocoa_key.unicode() length:1];
84 // return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
85 NSUInteger keySequenceModifierMask(const QKeySequence &accel)
87 return constructModifierMask(accel[0]);
90 QCocoaMenuItem::QCocoaMenuItem() :
103 QCocoaMenuItem::~QCocoaMenuItem()
106 [m_native setHidden:YES];
112 void QCocoaMenuItem::setText(const QString &text)
114 m_text = qt_mac_removeAmpersandEscapes(text);
117 void QCocoaMenuItem::setIcon(const QImage &icon)
122 void QCocoaMenuItem::setMenu(QPlatformMenu *menu)
127 QCocoaAutoReleasePool pool;
128 m_menu = static_cast<QCocoaMenu *>(menu);
130 m_menu->setParentItem(this);
132 // we previously had a menu, but no longer
133 // clear out our item so the nexy sync() call builds a new one
139 void QCocoaMenuItem::setVisible(bool isVisible)
141 m_isVisible = isVisible;
144 void QCocoaMenuItem::setIsSeparator(bool isSeparator)
146 m_isSeparator = isSeparator;
149 void QCocoaMenuItem::setFont(const QFont &font)
154 void QCocoaMenuItem::setRole(MenuRole role)
159 void QCocoaMenuItem::setShortcut(const QKeySequence& shortcut)
161 m_shortcut = shortcut;
164 void QCocoaMenuItem::setChecked(bool isChecked)
166 m_checked = isChecked;
169 void QCocoaMenuItem::setEnabled(bool enabled)
174 NSMenuItem *QCocoaMenuItem::sync()
176 if (m_isSeparator != [m_native isSeparatorItem]) {
179 m_native = [[NSMenuItem separatorItem] retain];
180 [m_native setTag:reinterpret_cast<NSInteger>(this)];
186 if (m_native != m_menu->nsMenuItem()) {
188 m_native = [m_menu->nsMenuItem() retain];
189 [m_native setTag:reinterpret_cast<NSInteger>(this)];
193 if ((m_role != NoRole) || m_merged) {
194 NSMenuItem *mergeItem = nil;
195 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
197 case ApplicationSpecificRole:
198 mergeItem = [loader appSpecificMenuItem];
201 mergeItem = [loader aboutMenuItem];
204 mergeItem = [loader aboutQtMenuItem];
207 mergeItem = [loader quitMenuItem];
209 case PreferencesRole:
210 mergeItem = [loader preferencesMenuItem];
212 case TextHeuristicRole: {
213 QString aboutString = tr("About").toLower();
215 if (m_text.startsWith(aboutString) || m_text.endsWith(aboutString)) {
216 if (m_text.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1)
217 mergeItem = [loader aboutMenuItem];
219 mergeItem = [loader aboutQtMenuItem];
222 } else if (m_text.startsWith(tr("Config").toLower())
223 || m_text.startsWith(tr("Preference").toLower())
224 || m_text.startsWith(tr("Options").toLower())
225 || m_text.startsWith(tr("Setting").toLower())
226 || m_text.startsWith(tr("Setup").toLower())) {
227 mergeItem = [loader preferencesMenuItem];
228 } else if (m_text.startsWith(tr("Quit").toLower())
229 || m_text.startsWith(tr("Exit").toLower())) {
230 mergeItem = [loader quitMenuItem];
236 qWarning() << Q_FUNC_INFO << "unsupported role" << (int) m_role;
242 m_native = mergeItem;
243 [m_native retain]; // balance out release!
244 [m_native setTag:reinterpret_cast<NSInteger>(this)];
245 } else if (m_merged) {
246 // was previously merged, but no longer
248 m_native = nil; // create item below
254 m_native = [[NSMenuItem alloc] initWithTitle:QCFString::toNSString(m_text)
258 [m_native setTag:reinterpret_cast<NSInteger>(this)];
261 // [m_native setHidden:YES];
262 // [m_native setHidden:NO];
263 [m_native setHidden: !m_isVisible];
265 QString text = m_text;
266 QKeySequence accel = m_shortcut;
269 int st = text.lastIndexOf(QLatin1Char('\t'));
271 accel = QKeySequence(text.right(text.length()-(st+1)));
272 text.remove(st, text.length()-st);
277 accel = mergeAccel();
279 // Show multiple key sequences as part of the menu text.
280 if (accel.count() > 1)
281 text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
283 QString finalString = qt_mac_removeMnemonics(text);
284 // Cocoa Font and title
285 if (m_font.resolve()) {
286 NSFont *customMenuFont = [NSFont fontWithName:QCFString::toNSString(m_font.family())
287 size:m_font.pointSize()];
288 NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil];
289 NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil];
290 NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
291 NSAttributedString *str = [[[NSAttributedString alloc] initWithString:QCFString::toNSString(finalString)
292 attributes:attributes] autorelease];
293 [m_native setAttributedTitle: str];
295 [m_native setTitle: QCFString::toNSString(finalString)];
298 if (accel.count() == 1) {
299 [m_native setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
300 [m_native setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
302 [m_native setKeyEquivalent:@""];
303 [m_native setKeyEquivalentModifierMask:NSCommandKeyMask];
306 if (!m_icon.isNull()) {
307 NSImage *img = qt_mac_cgimage_to_nsimage(qt_mac_image_to_cgimage(m_icon));
308 [m_native setImage: img];
311 [m_native setState:m_checked ? NSOnState : NSOffState];
315 QString QCocoaMenuItem::mergeText()
317 extern QString qt_mac_applicationmenu_string(int type);
318 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
319 if (m_native == [loader aboutMenuItem]) {
320 return qt_mac_applicationmenu_string(6).arg(qt_mac_applicationName());
321 } else if (m_native== [loader aboutQtMenuItem]) {
322 if (m_text == QString("About Qt"))
323 return tr("About Qt");
326 } else if (m_native == [loader preferencesMenuItem]) {
327 return qt_mac_applicationmenu_string(4);
328 } else if (m_native == [loader quitMenuItem]) {
329 return qt_mac_applicationmenu_string(5).arg(qt_mac_applicationName());
334 QKeySequence QCocoaMenuItem::mergeAccel()
336 QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
337 if (m_native == [loader preferencesMenuItem])
338 return QKeySequence(QKeySequence::Preferences);
339 else if (m_native == [loader quitMenuItem])
340 return QKeySequence(QKeySequence::Quit);
345 void QCocoaMenuItem::syncMerged()
348 [m_native setTag:reinterpret_cast<NSInteger>(this)];
349 [m_native setHidden: !m_isVisible];
352 void QCocoaMenuItem::syncModalState(bool modal)
355 [m_native setEnabled:NO];
357 [m_native setEnabled:m_enabled];