source: trunk/src/gui/kernel/qsoftkeymanager_s60.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 17.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qapplication.h"
43#include "qevent.h"
44#include "qbitmap.h"
45#include "qstyle.h"
46#include "qmenubar.h"
47#include "private/qt_s60_p.h"
48#include "private/qmenu_p.h"
49#include "private/qaction_p.h"
50#include "private/qsoftkeymanager_p.h"
51#include "private/qsoftkeymanager_s60_p.h"
52#include "private/qobject_p.h"
53#include <eiksoftkeyimage.h>
54#include <eikcmbut.h>
55
56#ifndef QT_NO_SOFTKEYMANAGER
57QT_BEGIN_NAMESPACE
58
59const int S60_COMMAND_START = 6000;
60const int LSK_POSITION = 0;
61const int MSK_POSITION = 3;
62const int RSK_POSITION = 2;
63
64QSoftKeyManagerPrivateS60::QSoftKeyManagerPrivateS60() : cbaHasImage(4) // 4 since MSK position index is 3
65{
66 cachedCbaIconSize[0] = QSize(0,0);
67 cachedCbaIconSize[1] = QSize(0,0);
68 cachedCbaIconSize[2] = QSize(0,0);
69 cachedCbaIconSize[3] = QSize(0,0);
70}
71
72bool QSoftKeyManagerPrivateS60::skipCbaUpdate()
73{
74 // Lets not update softkeys if
75 // 1. We don't have application panes, i.e. cba
76 // 2. Our CBA is not active, i.e. S60 native dialog or menu with custom CBA is shown
77 // 2.1. Except if thre is no current CBA at all and WindowSoftkeysRespondHint is set
78
79 // Note: Cannot use IsDisplayingMenuOrDialog since CBA update can be triggered before
80 // menu/dialog CBA is actually displayed i.e. it is being costructed.
81 CEikButtonGroupContainer *appUiCba = S60->buttonGroupContainer();
82 if (!appUiCba)
83 return true;
84 // CEikButtonGroupContainer::Current returns 0 if CBA is not visible at all
85 CEikButtonGroupContainer *currentCba = CEikButtonGroupContainer::Current();
86 // Check if softkey need to be update even they are not visible
87 bool cbaRespondsWhenInvisible = false;
88 QWidget *window = QApplication::activeWindow();
89 if (window && (window->windowFlags() & Qt::WindowSoftkeysRespondHint))
90 cbaRespondsWhenInvisible = true;
91
92 if (QApplication::testAttribute(Qt::AA_S60DontConstructApplicationPanes)
93 || (appUiCba != currentCba && !cbaRespondsWhenInvisible)) {
94 return true;
95 }
96 return false;
97}
98
99void QSoftKeyManagerPrivateS60::ensureCbaVisibilityAndResponsiviness(CEikButtonGroupContainer &cba)
100{
101 RDrawableWindow *cbaWindow = cba.DrawableWindow();
102 Q_ASSERT_X(cbaWindow, Q_FUNC_INFO, "Native CBA does not have window!");
103 // Make sure CBA is visible, i.e. CBA window is on top
104 cbaWindow->SetOrdinalPosition(0);
105 // Qt shares same CBA instance between top-level widgets,
106 // make sure we are not faded by underlying window.
107 cbaWindow->SetFaded(EFalse, RWindowTreeNode::EFadeIncludeChildren);
108 // Modal dialogs capture pointer events, but shared cba instance
109 // shall stay responsive. Raise pointer capture priority to keep
110 // softkeys responsive in modal dialogs
111 cbaWindow->SetPointerCapturePriority(1);
112}
113
114void QSoftKeyManagerPrivateS60::clearSoftkeys(CEikButtonGroupContainer &cba)
115{
116 QT_TRAP_THROWING(
117 //Using -1 instead of EAknSoftkeyEmpty to avoid flickering.
118 cba.SetCommandL(0, -1, KNullDesC);
119 // TODO: Should we clear also middle SK?
120 cba.SetCommandL(2, -1, KNullDesC);
121 );
122 realSoftKeyActions.clear();
123}
124
125QString QSoftKeyManagerPrivateS60::softkeyText(QAction &softkeyAction)
126{
127 // In S60 softkeys and menu items do not support key accelerators (i.e.
128 // CTRL+X). Therefore, removing the accelerator characters from both softkey
129 // and menu item texts.
130 const int underlineShortCut = QApplication::style()->styleHint(QStyle::SH_UnderlineShortcut);
131 QString iconText = softkeyAction.iconText();
132 return underlineShortCut ? softkeyAction.text() : iconText;
133}
134
135QAction *QSoftKeyManagerPrivateS60::highestPrioritySoftkey(QAction::SoftKeyRole role)
136{
137 QAction *ret = NULL;
138 // Priority look up is two level
139 // 1. First widget with softkeys always has highest priority
140 for (int level = 0; !ret; level++) {
141 // 2. Highest priority action within widget
142 QList<QAction*> actions = requestedSoftKeyActions.values(level);
143 if (actions.isEmpty())
144 break;
145 qSort(actions.begin(), actions.end(), QSoftKeyManagerPrivateS60::actionPriorityMoreThan);
146 foreach (QAction *action, actions) {
147 if (action->softKeyRole() == role) {
148 ret = action;
149 break;
150 }
151 }
152 }
153 return ret;
154}
155
156bool QSoftKeyManagerPrivateS60::actionPriorityMoreThan(const QAction *firstItem, const QAction *secondItem)
157{
158 return firstItem->priority() > secondItem->priority();
159}
160
161void QSoftKeyManagerPrivateS60::setNativeSoftkey(CEikButtonGroupContainer &cba,
162 TInt position, TInt command, const TDesC &text)
163{
164 // Calling SetCommandL causes CBA redraw
165 QT_TRAP_THROWING(cba.SetCommandL(position, command, text));
166}
167
168QPoint QSoftKeyManagerPrivateS60::softkeyIconPosition(int position, QSize sourceSize, QSize targetSize)
169{
170 QPoint iconPosition(0,0);
171 switch( AknLayoutUtils::CbaLocation() )
172 {
173 case AknLayoutUtils::EAknCbaLocationBottom:
174 // RSK must be moved to right, LSK in on correct position by default
175 if (position == RSK_POSITION)
176 iconPosition.setX(targetSize.width() - sourceSize.width());
177 break;
178 case AknLayoutUtils::EAknCbaLocationRight:
179 case AknLayoutUtils::EAknCbaLocationLeft:
180 // Already in correct position
181 default:
182 break;
183 }
184
185 // Align horizontally to center
186 iconPosition.setY((targetSize.height() - sourceSize.height()) >> 1);
187 return iconPosition;
188}
189
190QPixmap QSoftKeyManagerPrivateS60::prepareSoftkeyPixmap(QPixmap src, int position, QSize targetSize)
191{
192 QPixmap target(targetSize);
193 target.fill(Qt::transparent);
194 QPainter p;
195 p.begin(&target);
196 p.drawPixmap(softkeyIconPosition(position, src.size(), targetSize), src);
197 p.end();
198 return target;
199}
200
201bool QSoftKeyManagerPrivateS60::isOrientationLandscape()
202{
203 // Hard to believe that there is no public API in S60 to
204 // get current orientation. This workaround works with currently supported resolutions
205 return S60->screenHeightInPixels < S60->screenWidthInPixels;
206}
207
208QSize QSoftKeyManagerPrivateS60::cbaIconSize(CEikButtonGroupContainer *cba, int position)
209{
210
211 int index = position;
212 index += isOrientationLandscape() ? 0 : 1;
213 if(cachedCbaIconSize[index].isNull()) {
214 // Only way I figured out to get CBA icon size without RnD SDK, was
215 // to set some dummy icon to CBA first and then ask CBA button CCoeControl::Size()
216 // The returned value is cached to avoid unnecessary icon setting every time.
217 const bool left = (position == LSK_POSITION);
218 if(position == LSK_POSITION || position == RSK_POSITION) {
219 CEikImage* tmpImage = NULL;
220 QT_TRAP_THROWING(tmpImage = new (ELeave) CEikImage);
221 EikSoftkeyImage::SetImage(cba, *tmpImage, left); // Takes myimage ownership
222 int command = S60_COMMAND_START + position;
223 setNativeSoftkey(*cba, position, command, KNullDesC());
224 cachedCbaIconSize[index] = qt_TSize2QSize(cba->ControlOrNull(command)->Size());
225 EikSoftkeyImage::SetLabel(cba, left);
226
227 if(cachedCbaIconSize[index] == QSize(138,72)) {
228 // Hack for S60 5.0 (5800) landscape orientation, which return wrong icon size
229 cachedCbaIconSize[index] = QSize(60,60);
230 }
231 }
232 }
233
234 return cachedCbaIconSize[index];
235}
236
237bool QSoftKeyManagerPrivateS60::setSoftkeyImage(CEikButtonGroupContainer *cba,
238 QAction &action, int position)
239{
240 bool ret = false;
241
242 const bool left = (position == LSK_POSITION);
243 if(position == LSK_POSITION || position == RSK_POSITION) {
244 QIcon icon = action.icon();
245 if (!icon.isNull()) {
246 // Get size of CBA icon area based on button position and orientation
247 QSize requiredIconSize = cbaIconSize(cba, position);
248 // Get pixmap out of icon based on preferred size, the aspect ratio is kept
249 QPixmap pmWihtAspectRatio = icon.pixmap(requiredIconSize);
250 // Native softkeys require that pixmap size is exactly the same as requiredIconSize
251 // prepareSoftkeyPixmap creates a new pixmap with requiredIconSize and blits the 'pmWihtAspectRatio'
252 // to correct location of it
253 QPixmap softkeyPixmap = prepareSoftkeyPixmap(pmWihtAspectRatio, position, requiredIconSize);
254
255 QPixmap softkeyAlpha = softkeyPixmap.alphaChannel();
256 // Alpha channel in 5.1 and older devices need to be inverted
257 // TODO: Switch to use toSymbianCFbsBitmap with invert when available
258 if(QSysInfo::s60Version() <= QSysInfo::SV_S60_5_1) {
259 QImage alphaImage = softkeyAlpha.toImage();
260 alphaImage.invertPixels();
261 softkeyAlpha = QPixmap::fromImage(alphaImage);
262 }
263
264 CFbsBitmap* nBitmap = softkeyPixmap.toSymbianCFbsBitmap();
265 CFbsBitmap* nMask = softkeyAlpha.toSymbianCFbsBitmap();
266
267 CEikImage* myimage = new (ELeave) CEikImage;
268 myimage->SetPicture( nBitmap, nMask ); // nBitmap and nMask ownership transferred
269
270 EikSoftkeyImage::SetImage(cba, *myimage, left); // Takes myimage ownership
271 cbaHasImage[position] = true;
272 ret = true;
273 } else {
274 // Restore softkey to text based
275 if (cbaHasImage[position]) {
276 EikSoftkeyImage::SetLabel(cba, left);
277 cbaHasImage[position] = false;
278 }
279 }
280 }
281 return ret;
282}
283
284bool QSoftKeyManagerPrivateS60::setSoftkey(CEikButtonGroupContainer &cba,
285 QAction::SoftKeyRole role, int position)
286{
287 QAction *action = highestPrioritySoftkey(role);
288 if (action) {
289 setSoftkeyImage(&cba, *action, position);
290 QString text = softkeyText(*action);
291 TPtrC nativeText = qt_QString2TPtrC(text);
292 int command = S60_COMMAND_START + position;
293 setNativeSoftkey(cba, position, command, nativeText);
294 const bool dimmed = !action->isEnabled() && !QSoftKeyManager::isForceEnabledInSofkeys(action);
295 cba.DimCommand(command, dimmed);
296 realSoftKeyActions.insert(command, action);
297 return true;
298 }
299 return false;
300}
301
302bool QSoftKeyManagerPrivateS60::setLeftSoftkey(CEikButtonGroupContainer &cba)
303{
304 return setSoftkey(cba, QAction::PositiveSoftKey, LSK_POSITION);
305}
306
307bool QSoftKeyManagerPrivateS60::setMiddleSoftkey(CEikButtonGroupContainer &cba)
308{
309 // Note: In order to get MSK working, application has to have EAknEnableMSK flag set
310 // Currently it is not possible very easily)
311 // For more information see: http://wiki.forum.nokia.com/index.php/Middle_softkey_usage
312 return setSoftkey(cba, QAction::SelectSoftKey, MSK_POSITION);
313}
314
315bool QSoftKeyManagerPrivateS60::setRightSoftkey(CEikButtonGroupContainer &cba)
316{
317 if (!setSoftkey(cba, QAction::NegativeSoftKey, RSK_POSITION)) {
318 const Qt::WindowType windowType = initialSoftKeySource
319 ? initialSoftKeySource->window()->windowType() : Qt::Window;
320 if (windowType != Qt::Dialog && windowType != Qt::Popup) {
321 QString text(QSoftKeyManager::tr("Exit"));
322 TPtrC nativeText = qt_QString2TPtrC(text);
323 if (cbaHasImage[RSK_POSITION]) {
324 EikSoftkeyImage::SetLabel(&cba, false);
325 cbaHasImage[RSK_POSITION] = false;
326 }
327 setNativeSoftkey(cba, RSK_POSITION, EAknSoftkeyExit, nativeText);
328 cba.DimCommand(EAknSoftkeyExit, false);
329 return true;
330 }
331 }
332 return false;
333}
334
335void QSoftKeyManagerPrivateS60::setSoftkeys(CEikButtonGroupContainer &cba)
336{
337 int requestedSoftkeyCount = requestedSoftKeyActions.count();
338 const int maxSoftkeyCount = 2; // TODO: differs based on orientation ans S60 versions (some have MSK)
339 if (requestedSoftkeyCount > maxSoftkeyCount) {
340 // We have more softkeys than available slots
341 // Put highest priority negative action to RSK and Options menu with rest of softkey actions to LSK
342 // TODO: Build menu
343 setLeftSoftkey(cba);
344 if(AknLayoutUtils::MSKEnabled())
345 setMiddleSoftkey(cba);
346 setRightSoftkey(cba);
347 } else {
348 // We have less softkeys than available slots
349 // Put softkeys to request slots based on role
350 setLeftSoftkey(cba);
351 if(AknLayoutUtils::MSKEnabled())
352 setMiddleSoftkey(cba);
353 setRightSoftkey(cba);
354 }
355}
356
357void QSoftKeyManagerPrivateS60::updateSoftKeys_sys()
358{
359 if (skipCbaUpdate())
360 return;
361
362 CEikButtonGroupContainer *nativeContainer = S60->buttonGroupContainer();
363 Q_ASSERT_X(nativeContainer, Q_FUNC_INFO, "Native CBA does not exist!");
364 ensureCbaVisibilityAndResponsiviness(*nativeContainer);
365 clearSoftkeys(*nativeContainer);
366 setSoftkeys(*nativeContainer);
367
368 nativeContainer->DrawDeferred(); // 3.1 needs an extra invitation
369}
370
371static void resetMenuBeingConstructed(TAny* /*aAny*/)
372{
373 S60->menuBeingConstructed = false;
374}
375
376void QSoftKeyManagerPrivateS60::tryDisplayMenuBarL()
377{
378 CleanupStack::PushL(TCleanupItem(resetMenuBeingConstructed, NULL));
379 S60->menuBeingConstructed = true;
380 S60->menuBar()->TryDisplayMenuBarL();
381 CleanupStack::PopAndDestroy(); // Reset menuBeingConstructed to false in all cases
382}
383
384bool QSoftKeyManagerPrivateS60::handleCommand(int command)
385{
386 QAction *action = realSoftKeyActions.value(command);
387 if (action) {
388 bool property = QActionPrivate::get(action)->menuActionSoftkeys;
389 if (property) {
390 QT_TRAP_THROWING(tryDisplayMenuBarL());
391 } else if (action->menu()) {
392 // TODO: This is hack, in order to use exising QMenuBar implementation for Symbian
393 // menubar needs to have widget to which it is associated. Since we want to associate
394 // menubar to action (which is inherited from QObject), we create and associate QWidget
395 // to action and pass that for QMenuBar. This associates the menubar to action, and we
396 // can have own menubar for each action.
397 QWidget *actionContainer = action->property("_q_action_widget").value<QWidget*>();
398 if(!actionContainer) {
399 actionContainer = new QWidget(action->parentWidget());
400 QMenuBar *menuBar = new QMenuBar(actionContainer);
401 foreach(QAction *menuAction, action->menu()->actions()) {
402 QMenu *menu = menuAction->menu();
403 if(menu)
404 menuBar->addMenu(menu);
405 else
406 menuBar->addAction(menuAction);
407 }
408 QVariant v;
409 v.setValue(actionContainer);
410 action->setProperty("_q_action_widget", v);
411 }
412 qt_symbian_next_menu_from_action(actionContainer);
413 QT_TRAP_THROWING(tryDisplayMenuBarL());
414 }
415
416 Q_ASSERT(action->softKeyRole() != QAction::NoSoftKey);
417 QWidget *actionParent = action->parentWidget();
418 Q_ASSERT_X(actionParent, Q_FUNC_INFO, "No parent set for softkey action!");
419 if (actionParent->isEnabled()) {
420 action->activate(QAction::Trigger);
421 return true;
422 }
423 }
424 return false;
425}
426
427QT_END_NAMESPACE
428#endif //QT_NO_SOFTKEYMANAGER
Note: See TracBrowser for help on using the repository browser.