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

Last change on this file since 808 was 769, checked in by Dmitry A. Kuminov, 15 years ago

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

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