2
* Copyright (C) 2011 Canonical, Ltd.
5
* Renato Araujo Oliveira Filho <renato.filho@canonical.com>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; version 3.
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
20
#include "focuspath.h"
22
#include <QtCore/qmath.h>
28
\brief FocusPath is used to help keyboard navigation between QML elements
30
The following example shows how to use FocusPath no navigate through a QML Grid element cells.
35
columns: myGrid.columns
48
color: activeFocus ? "red" : "blue"
55
The FocusPath class should be used with the attached property 'index' to determiner the focus path order.
59
\qmlproperty Component item
61
The parent item for navigation
65
\qmlproperty int columns
67
Number of columns in the item element, this will allow vertical naviagtion
71
\qmlproperty Flow flow
73
Holds the flow of the path
77
\qmlproperty Direction direction
79
Holds the allowed navigation direction
83
\qmlproperty int index
85
Attached property used to determine the focus sequence
89
\qmlproperty bool skip
91
Attached property to remove item from the focus path
94
FocusPath::FocusPath(QObject *parent)
99
m_currentPosition(-1),
100
m_flow(FocusPath::LeftToRight),
101
m_direction(FocusPath::HorizontalAndVertical)
105
FocusPath::~FocusPath()
110
QDeclarativeItem* FocusPath::item() const
115
int FocusPath::columns() const
120
int FocusPath::currentIndex() const
122
if (m_currentPosition >= 0 && m_currentPosition < m_path.size()) {
123
return m_path[m_currentPosition].first;
129
QDeclarativeItem* FocusPath::currentItem() const
131
if (m_currentPosition >= 0 && m_currentPosition < m_path.size()) {
132
return m_path[m_currentPosition].second;
138
FocusPath::Flow FocusPath::flow() const
143
FocusPath::NavigationDirection FocusPath::direction() const
148
QList<PathItem > FocusPath::path() const
153
void FocusPath::setItem(QDeclarativeItem* item)
155
if (m_item != item) {
157
Q_EMIT itemChanged();
161
void FocusPath::setColumns(int columns)
163
if (m_columns != columns) {
166
m_rows = qCeil(m_path.size() / m_columns);
170
Q_EMIT columnsChanged();
174
void FocusPath::setCurrentIndex(int index)
176
QList<PathItem>::const_iterator i = m_path.begin();
178
while(i != m_path.end()) {
179
if ((*i).first == index) {
180
updatePosition(newPosition);
188
void FocusPath::updatePosition(int index)
190
if ((m_currentPosition != index) &&
192
(index < m_path.size())) {
193
QDeclarativeItem* focus = m_path[index].second;
195
focus->setFocus(true);
196
m_currentPosition = index;
197
Q_EMIT currentIndexChanged();
198
Q_EMIT currentItemChanged();
202
void FocusPath::setFlow(FocusPath::Flow value)
204
if (m_flow != value) {
206
Q_EMIT flowChanged();
210
void FocusPath::setDirection(FocusPath::NavigationDirection value)
212
if (m_direction != value) {
214
Q_EMIT directionChanged();
218
void FocusPath::onInfoChanged()
220
FocusPathAttached *info = qobject_cast<FocusPathAttached *> (sender());
221
QDeclarativeItem *item = qobject_cast<QDeclarativeItem *>(info->parent());
227
void FocusPath::onItemChanged()
229
QDeclarativeItem *item = qobject_cast<QDeclarativeItem *>(sender());
236
Reset focus, moving the current focus to the first element
239
void FocusPath::reset()
241
if (m_path.size() > 0) {
242
QGraphicsItem* gi = m_path[0].second;
243
if (gi->flags() & QGraphicsItem::ItemIsFocusScope) {
245
m_path[0].second->setFocus(true);
249
m_rows = qCeil(m_path.size() / m_columns);
251
m_currentPosition = 0;
252
Q_EMIT currentIndexChanged();
253
Q_EMIT currentItemChanged();
256
void FocusPath::addItem(QDeclarativeItem *item)
258
if (item->flags() & QGraphicsItem::ItemIsFocusScope) {
259
QObject *attached = qmlAttachedPropertiesObject<FocusPath>(item);
260
FocusPathAttached *info = static_cast<FocusPathAttached *>(attached);
262
if (!info->skip() && item->isVisible()) {
263
QList<PathItem>::iterator i = m_path.begin();
266
for(; i != m_path.end(); i++) {
267
if (info->index() < (*i).first) {
273
if (i == m_path.begin()) {
274
m_path.prepend(qMakePair(info->index(), item));
275
} else if (i == m_path.end()) {
276
m_path.append(qMakePair(info->index(), item));
278
m_path.insert(i, qMakePair(info->index(), item));
281
if (m_currentPosition == -1) {
283
} else if (itemPos <= m_currentPosition) {
288
m_rows = qCeil(m_path.size() / m_columns);
294
QObject::connect(info, SIGNAL(indexChanged()), this, SLOT(onInfoChanged()));
295
QObject::connect(info, SIGNAL(skipChanged()), this, SLOT(onInfoChanged()));
296
QObject::connect(item, SIGNAL(visibleChanged()), this, SLOT(onItemChanged()));
300
void FocusPath::removeItem(QDeclarativeItem *item)
302
QList<PathItem>::iterator i = m_path.begin();
304
for(int index=0; i != m_path.end(); i++, index++) {
305
if ((*i).second == item) {
307
if (itemPos == m_currentPosition) {
308
int oldPosition = m_currentPosition;
309
/* Check if this position is the last one */
310
if (oldPosition >= m_path.size()) {
311
oldPosition = m_path.size() - 1;
313
/* invalidade current position to allow update */
314
m_currentPosition = -1;
315
updatePosition(oldPosition);
316
} else if (itemPos < m_currentPosition) {
321
m_rows = qCeil(m_path.size() / m_columns);
328
QObject *attached = qmlAttachedPropertiesObject<FocusPath>(item);
329
FocusPathAttached *info = static_cast<FocusPathAttached *>(attached);
332
QObject::disconnect(info, SIGNAL(indexChanged()), this, SLOT(onInfoChanged()));
333
QObject::disconnect(info, SIGNAL(skipChanged()), this, SLOT(onInfoChanged()));
336
QObject::disconnect(item, SIGNAL(visibleChanged()), this, SLOT(onItemChanged()));
337
m_items.removeOne(item);
340
bool FocusPath::eventFilter(QObject* obj, QEvent* event)
343
switch(event->type()) {
344
case QEvent::KeyPress: {
345
int nextFocus = m_currentPosition;
347
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
348
switch(keyEvent->key()) {
350
if ((m_direction & FocusPath::Horizontal) == FocusPath::Horizontal) {
352
case FocusPath::LeftToRight:
355
case FocusPath::RightToLeft:
358
case FocusPath::TopToBottom:
367
if ((m_direction & FocusPath::Horizontal) == FocusPath::Horizontal) {
369
case FocusPath::LeftToRight:
372
case FocusPath::RightToLeft:
375
case FocusPath::TopToBottom:
384
if (((m_direction & FocusPath::Vertical) == FocusPath::Vertical) && (m_columns >= 0)) {
385
nextFocus = (m_flow == FocusPath::TopToBottom) ? nextFocus - 1 : nextFocus - m_columns;
391
if (((m_direction & FocusPath::Vertical) == FocusPath::Vertical) && (m_columns >= 0)) {
392
nextFocus = (m_flow == FocusPath::TopToBottom) ? nextFocus + 1 : nextFocus + m_columns;
402
if ((nextFocus >= 0) && (nextFocus < m_path.size())) {
403
updatePosition(nextFocus);
415
void FocusPath::onChildrenChanged()
417
QList<QGraphicsItem *> items = m_item->childItems();
419
Q_FOREACH(QDeclarativeItem *i, m_items) {
420
QDeclarativeItem *di = qobject_cast<QDeclarativeItem *>(i);
421
if (!items.contains(di)) {
426
Q_FOREACH(QGraphicsItem *i, items) {
427
QGraphicsObject *obj = i->toGraphicsObject();
429
QDeclarativeItem *di = qobject_cast<QDeclarativeItem *>(obj);
430
if (!m_items.contains(di)) {
437
void FocusPath::updateItem(QDeclarativeItem* item)
443
m_item->removeEventFilter(this);
444
Q_FOREACH(QGraphicsItem *c, m_item->childItems()) {
445
QGraphicsObject *obj = c->toGraphicsObject();
447
QDeclarativeItem *ci = qobject_cast<QDeclarativeItem *>(obj);
452
QObject::disconnect(m_item, SIGNAL(destroyed(QObject*)), this, SLOT(onItemDestroyed()));
453
QObject::disconnect(m_item, SIGNAL(childrenChanged()), this, SLOT(onChildrenChanged()));
457
Q_FOREACH(QGraphicsItem *c, m_item->childItems()) {
458
QGraphicsObject *obj = c->toGraphicsObject();
460
QDeclarativeItem *ci = qobject_cast<QDeclarativeItem *>(obj);
464
m_item->installEventFilter(this);
465
QObject::connect(m_item, SIGNAL(destroyed(QObject*)), this, SLOT(onItemDestroyed()));
466
QObject::connect(m_item, SIGNAL(childrenChanged()), this, SLOT(onChildrenChanged()));
470
void FocusPath::onItemDestroyed()
472
Q_FOREACH(QDeclarativeItem *i, m_items) {
478
FocusPathAttached *FocusPath::qmlAttachedProperties(QObject *object)
480
return new FocusPathAttached(object);
483
FocusPathAttached::FocusPathAttached(QObject *object)
490
int FocusPathAttached::index() const
495
bool FocusPathAttached::skip() const
500
void FocusPathAttached::setIndex(int index)
502
if (m_index != index) {
504
Q_EMIT indexChanged();
508
void FocusPathAttached::setSkip(bool skip)
510
if (m_skip != skip) {
512
Q_EMIT skipChanged();
516
#include "focuspath.moc"