Cocoa implementation of QPA menu interface.
[profile/ivi/qtbase.git] / src / plugins / platforms / cocoa / qcocoamenuitem.mm
1 /****************************************************************************
2 **
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/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qcocoamenuitem.h"
43
44 #include "qcocoamenu.h"
45 #include "qcocoahelpers.h"
46 #include "qcocoaautoreleasepool.h"
47 #include "qt_mac_p.h"
48 #include "qcocoaapplication.h" // for custom application category
49 #include "qcocoamenuloader.h"
50
51 #include <QtCore/QDebug>
52
53 static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader()
54 {
55     return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)];
56 }
57
58
59 static quint32 constructModifierMask(quint32 accel_key)
60 {
61     quint32 ret = 0;
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;
71     return ret;
72 }
73
74 // return an autoreleased string given a QKeySequence (currently only looks at the first one).
75 NSString *keySequenceToKeyEqivalent(const QKeySequence &accel)
76 {
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];
82 }
83
84 // return the cocoa modifier mask for the QKeySequence (currently only looks at the first one).
85 NSUInteger keySequenceModifierMask(const QKeySequence &accel)
86 {
87     return constructModifierMask(accel[0]);
88 }
89
90 QCocoaMenuItem::QCocoaMenuItem() :
91     m_native(NULL),
92     m_menu(NULL),
93     m_isVisible(true),
94     m_enabled(true),
95     m_isSeparator(false),
96     m_role(NoRole),
97     m_checked(false),
98     m_merged(false),
99     m_tag(0)
100 {
101 }
102
103 QCocoaMenuItem::~QCocoaMenuItem()
104 {
105     if (m_merged) {
106         [m_native setHidden:YES];
107     }
108
109     [m_native release];
110 }
111
112 void QCocoaMenuItem::setText(const QString &text)
113 {
114     m_text = qt_mac_removeAmpersandEscapes(text);
115 }
116
117 void QCocoaMenuItem::setIcon(const QImage &icon)
118 {
119     m_icon = icon;
120 }
121
122 void QCocoaMenuItem::setMenu(QPlatformMenu *menu)
123 {
124     if (menu == m_menu)
125         return;
126
127     QCocoaAutoReleasePool pool;
128     m_menu = static_cast<QCocoaMenu *>(menu);
129     if (m_menu) {
130         m_menu->setParentItem(this);
131     } else {
132         // we previously had a menu, but no longer
133         // clear out our item so the nexy sync() call builds a new one
134         [m_native release];
135         m_native = nil;
136     }
137 }
138
139 void QCocoaMenuItem::setVisible(bool isVisible)
140 {
141     m_isVisible = isVisible;
142 }
143
144 void QCocoaMenuItem::setIsSeparator(bool isSeparator)
145 {
146     m_isSeparator = isSeparator;
147 }
148
149 void QCocoaMenuItem::setFont(const QFont &font)
150 {
151     m_font = font;
152 }
153
154 void QCocoaMenuItem::setRole(MenuRole role)
155 {
156     m_role = role;
157 }
158
159 void QCocoaMenuItem::setShortcut(const QKeySequence& shortcut)
160 {
161     m_shortcut = shortcut;
162 }
163
164 void QCocoaMenuItem::setChecked(bool isChecked)
165 {
166     m_checked = isChecked;
167 }
168
169 void QCocoaMenuItem::setEnabled(bool enabled)
170 {
171     m_enabled = enabled;
172 }
173
174 NSMenuItem *QCocoaMenuItem::sync()
175 {
176     if (m_isSeparator != [m_native isSeparatorItem]) {
177         [m_native release];
178         if (m_isSeparator) {
179             m_native = [[NSMenuItem separatorItem] retain];
180             [m_native setTag:reinterpret_cast<NSInteger>(this)];
181         } else
182             m_native = nil;
183     }
184
185     if (m_menu) {
186         if (m_native != m_menu->nsMenuItem()) {
187             [m_native release];
188             m_native = [m_menu->nsMenuItem() retain];
189             [m_native setTag:reinterpret_cast<NSInteger>(this)];
190         }
191     }
192
193     if ((m_role != NoRole) || m_merged) {
194         NSMenuItem *mergeItem = nil;
195         QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader();
196         switch (m_role) {
197         case ApplicationSpecificRole:
198             mergeItem = [loader appSpecificMenuItem];
199             break;
200         case AboutRole:
201             mergeItem = [loader aboutMenuItem];
202             break;
203         case AboutQtRole:
204             mergeItem = [loader aboutQtMenuItem];
205             break;
206         case QuitRole:
207             mergeItem = [loader quitMenuItem];
208             break;
209         case PreferencesRole:
210             mergeItem = [loader preferencesMenuItem];
211             break;
212         case TextHeuristicRole: {
213             QString aboutString = tr("About").toLower();
214
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];
218                 else
219                     mergeItem = [loader aboutQtMenuItem];
220
221                 m_merged = true;
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];
231             }
232             break;
233         }
234
235         default:
236             qWarning() << Q_FUNC_INFO << "unsupported role" << (int) m_role;
237         }
238
239         if (mergeItem) {
240             m_merged = true;
241             [m_native release];
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
247             [m_native release];
248             m_native = nil; // create item below
249             m_merged = false;
250         }
251     }
252
253     if (!m_native) {
254         m_native = [[NSMenuItem alloc] initWithTitle:QCFString::toNSString(m_text)
255             action:nil
256                 keyEquivalent:@""];
257         [m_native retain];
258         [m_native setTag:reinterpret_cast<NSInteger>(this)];
259     }
260
261 //  [m_native setHidden:YES];
262 //  [m_native setHidden:NO];
263    [m_native setHidden: !m_isVisible];
264
265     QString text = m_text;
266     QKeySequence accel = m_shortcut;
267
268     {
269         int st = text.lastIndexOf(QLatin1Char('\t'));
270         if (st != -1) {
271             accel = QKeySequence(text.right(text.length()-(st+1)));
272             text.remove(st, text.length()-st);
273         }
274     }
275
276     text = mergeText();
277     accel = mergeAccel();
278
279     // Show multiple key sequences as part of the menu text.
280     if (accel.count() > 1)
281         text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")");
282
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];
294     } else {
295        [m_native setTitle: QCFString::toNSString(finalString)];
296     }
297
298     if (accel.count() == 1) {
299         [m_native setKeyEquivalent:keySequenceToKeyEqivalent(accel)];
300         [m_native setKeyEquivalentModifierMask:keySequenceModifierMask(accel)];
301     } else {
302         [m_native setKeyEquivalent:@""];
303         [m_native setKeyEquivalentModifierMask:NSCommandKeyMask];
304     }
305
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];
309     }
310
311     [m_native setState:m_checked ?  NSOnState : NSOffState];
312     return m_native;
313 }
314
315 QString QCocoaMenuItem::mergeText()
316 {
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");
324         else
325             return m_text;
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());
330     }
331     return m_text;
332 }
333
334 QKeySequence QCocoaMenuItem::mergeAccel()
335 {
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);
341
342     return m_shortcut;
343 }
344
345 void QCocoaMenuItem::syncMerged()
346 {
347     Q_ASSERT(m_merged);
348     [m_native setTag:reinterpret_cast<NSInteger>(this)];
349     [m_native setHidden: !m_isVisible];
350 }
351
352 void QCocoaMenuItem::syncModalState(bool modal)
353 {
354     if (modal)
355         [m_native setEnabled:NO];
356     else
357         [m_native setEnabled:m_enabled];
358 }