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

Last change on this file since 744 was 651, checked in by Dmitry A. Kuminov, 16 years ago

trunk: Merged in qt 4.6.2 sources.

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