2 Copyright 2007 Robert Knight <robertknight@gmail.com>
3 Copyright 2008 Sebastian Sauer <mail@dipe.org>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
25 #include <QtCore/QAbstractItemModel>
26 #include <QtCore/QStack>
27 #include <QtGui/QApplication>
28 #include <QtGui/QMouseEvent>
29 #include <QtCore/QPersistentModelIndex>
34 #include <KIconLoader>
37 #include "core/models.h"
38 #include "core/itemhandlers.h"
40 Q_DECLARE_METATYPE(QPersistentModelIndex
)
41 Q_DECLARE_METATYPE(QAction
*)
43 using namespace Kickoff
;
45 /// @internal d-pointer class
46 class MenuView::Private
49 enum { ActionRole
= Qt::UserRole
+ 52 };
51 Private(MenuView
*parent
) : q(parent
) , model(0) , column(0), launcher(new UrlItemLauncher(parent
)), formattype(MenuView::DescriptionName
) {}
53 QAction
*createActionForIndex(const QModelIndex
& index
, QMenu
*parent
) {
54 Q_ASSERT(index
.isValid());
58 if (model
->hasChildren(index
)) {
59 KMenu
*childMenu
= new KMenu(parent
);
60 childMenu
->installEventFilter(q
);
62 action
= childMenu
->menuAction();
64 if (model
->canFetchMore(index
)) {
65 model
->fetchMore(index
);
68 buildBranch(childMenu
, index
);
70 action
= q
->createLeafAction(index
, parent
);
73 q
->updateAction(action
, index
);
78 void buildBranch(QMenu
*menu
, const QModelIndex
& parent
) {
79 int rowCount
= model
->rowCount(parent
);
80 for (int i
= 0; i
< rowCount
; i
++) {
81 QAction
*action
= createActionForIndex(model
->index(i
, column
, parent
), menu
);
82 menu
->addAction(action
);
87 QAbstractItemModel
*model
;
89 UrlItemLauncher
*launcher
;
90 MenuView::FormatType formattype
;
94 MenuView::MenuView(QWidget
*parent
)
96 , d(new Private(this))
98 installEventFilter(this);
101 MenuView::~MenuView()
106 QAction
*MenuView::createLeafAction(const QModelIndex
&, QObject
*parent
)
108 return new QAction(parent
);
111 void MenuView::updateAction(QAction
*action
, const QModelIndex
& index
)
115 QString text
= index
.data(Qt::DisplayRole
).value
<QString
>().replace("&", "&&"); // describing text, e.g. "Spreadsheet" or "Rekall" (right, sometimes the text is also used for the generic app-name)
116 QString name
= index
.data(Kickoff::SubTitleRole
).value
<QString
>().replace("&", "&&"); // the generic name, e.g. "kspread" or "OpenOffice.org Spreadsheet" or just "" (right, it's a mess too)
117 if (action
->menu() != 0) { // if its an item with sub-menuitems, we probably like to thread them another way...
118 action
->setText(text
);
120 switch (d
->formattype
) {
122 if (name
.isEmpty()) {
123 action
->setText(text
);
125 action
->setText(name
);
130 if (name
.contains(text
, Qt::CaseInsensitive
)) {
133 action
->setText(text
);
136 case NameDescription
: // fall through
137 case NameDashDescription
: // fall through
138 case DescriptionName
: {
139 if (!name
.isEmpty()) { // seems we have a program, but some of them don't define a name at all
140 if (text
.contains(name
, Qt::CaseInsensitive
)) { // sometimes the description contains also the name
141 action
->setText(text
);
142 } else if (name
.contains(text
, Qt::CaseInsensitive
)) { // and sometimes the name also contains the description
143 action
->setText(name
);
144 } else { // seems we have a perfect desktop-file (likely a KDE one, heh) and name+description are clear separated
145 if (d
->formattype
== NameDescription
) {
146 action
->setText(QString("%1 %2").arg(name
).arg(text
));
147 } else if (d
->formattype
== NameDashDescription
) {
148 action
->setText(QString("%1 - %2").arg(name
).arg(text
));
150 action
->setText(QString("%1 (%2)").arg(text
).arg(name
));
153 } else { // if there is no name, let's just use the describing text
154 action
->setText(text
);
161 action
->setIcon(index
.data(Qt::DecorationRole
).value
<QIcon
>());
163 // we map modelindex and action together
164 action
->setData(qVariantFromValue(QPersistentModelIndex(index
)));
166 // don't emit the dataChanged-signal cause else we may end in a infinite loop
167 d
->model
->blockSignals(true);
168 d
->model
->setData(index
, qVariantFromValue(action
), Private::ActionRole
);
169 d
->model
->blockSignals(false);
172 bool MenuView::eventFilter(QObject
*watched
, QEvent
*event
)
174 if (event
->type() == QEvent::MouseMove
) {
175 QMouseEvent
*mouseEvent
= static_cast<QMouseEvent
*>(event
);
176 QMenu
*watchedMenu
= qobject_cast
<QMenu
*>(watched
);
177 const int mousePressDistance
= !d
->mousePressPos
.isNull() ? (mouseEvent
->pos() - d
->mousePressPos
).manhattanLength() : 0;
179 if (watchedMenu
&& mouseEvent
->buttons() & Qt::LeftButton
180 && mousePressDistance
>= QApplication::startDragDistance()) {
181 QAction
*action
= watchedMenu
->actionAt(mouseEvent
->pos());
183 return KMenu::eventFilter(watched
, event
);
186 QPersistentModelIndex index
= action
->data().value
<QPersistentModelIndex
>();
187 if (!index
.isValid()) {
188 return KMenu::eventFilter(watched
, event
);
191 QString urlString
= index
.data(UrlRole
).toString();
192 if (urlString
.isNull()) {
193 return KMenu::eventFilter(watched
, event
);
196 QMimeData
*mimeData
= new QMimeData();
197 mimeData
->setData("text/uri-list", urlString
.toAscii());
198 mimeData
->setText(mimeData
->text());
199 QDrag
*drag
= new QDrag(this);
200 drag
->setMimeData(mimeData
);
202 QIcon icon
= action
->icon();
203 drag
->setPixmap(icon
.pixmap(IconSize(KIconLoader::Desktop
)));
205 d
->mousePressPos
= QPoint();
207 Qt::DropAction dropAction
= drag
->exec();
208 Q_UNUSED(dropAction
);
212 } else if (event
->type() == QEvent::MouseButtonPress
) {
213 QMouseEvent
*mouseEvent
= static_cast<QMouseEvent
*>(event
);
214 QMenu
*watchedMenu
= qobject_cast
<QMenu
*>(watched
);
216 d
->mousePressPos
= mouseEvent
->pos();
218 } else if (event
->type() == QEvent::MouseButtonRelease
) {
219 QMenu
*watchedMenu
= qobject_cast
<QMenu
*>(watched
);
221 d
->mousePressPos
= QPoint();
225 return KMenu::eventFilter(watched
, event
);
228 void MenuView::setModel(QAbstractItemModel
*model
)
231 d
->model
->disconnect(this);
236 d
->buildBranch(this, QModelIndex());
237 connect(d
->model
, SIGNAL(rowsInserted(QModelIndex
, int, int)), this, SLOT(rowsInserted(QModelIndex
, int, int)));
238 connect(d
->model
, SIGNAL(rowsAboutToBeRemoved(QModelIndex
, int, int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex
, int, int)));
239 connect(d
->model
, SIGNAL(dataChanged(QModelIndex
, QModelIndex
)), this, SLOT(dataChanged(QModelIndex
, QModelIndex
)));
240 connect(d
->model
, SIGNAL(modelReset()), this, SLOT(modelReset()));
244 QAbstractItemModel
*MenuView::model() const
249 UrlItemLauncher
*MenuView::launcher() const
254 QModelIndex
MenuView::indexForAction(QAction
*action
) const
257 Q_ASSERT(action
!= 0);
258 QPersistentModelIndex index
= action
->data().value
<QPersistentModelIndex
>();
259 Q_ASSERT(index
.isValid());
263 QAction
*MenuView::actionForIndex(const QModelIndex
& index
) const
266 if (!index
.isValid()) {
267 return this->menuAction();
270 QVariant v
= d
->model
->data(index
, Private::ActionRole
);
271 Q_ASSERT(v
.isValid());
272 QAction
* a
= v
.value
<QAction
*>();
277 bool MenuView::isValidIndex(const QModelIndex
& index
) const
279 QVariant v
= (d
->model
&& index
.isValid()) ? d
->model
->data(index
, Private::ActionRole
) : QVariant();
280 return v
.isValid() && v
.value
<QAction
*>();
283 void MenuView::rowsInserted(const QModelIndex
& parent
, int start
, int end
)
285 if (!isValidIndex(parent
)) {
286 // can happen if the models data is incomplete yet
292 QAction
*menuAction
= actionForIndex(parent
);
293 Q_ASSERT(menuAction
);
295 QMenu
*menu
= menuAction
->menu();
298 QList
<QAction
*> newActions
;
299 for (int row
= start
; row
<= end
; row
++) {
300 QModelIndex index
= d
->model
->index(row
, d
->column
, parent
);
301 QAction
*newAction
= d
->createActionForIndex(index
, menu
);
302 newActions
<< newAction
;
305 if (start
< menu
->actions().count()) {
306 menu
->insertActions(menu
->actions()[start
], newActions
);
308 menu
->addActions(newActions
);
312 void MenuView::rowsAboutToBeRemoved(const QModelIndex
& parent
, int start
, int end
)
314 if (!isValidIndex(parent
)) {
315 // can happen if the models data is incomplete yet
321 QAction
*menuAction
= actionForIndex(parent
);
322 Q_ASSERT(menuAction
);
324 QMenu
*menu
= menuAction
->menu();
327 QList
<QAction
*> actions
= menu
->actions();
328 Q_ASSERT(end
< actions
.count());
329 for (int row
= end
; row
>= start
; row
--) {
330 menu
->removeAction(actions
[row
]);
334 void MenuView::dataChanged(const QModelIndex
& topLeft
, const QModelIndex
& bottomRight
)
336 if (!isValidIndex(topLeft
.parent())) {
337 // can happen if the models data is incomplete yet
343 QAction
*menuAction
= actionForIndex(topLeft
.parent());
344 Q_ASSERT(menuAction
);
346 QMenu
*menu
= menuAction
->menu();
349 QList
<QAction
*> actions
= menu
->actions();
350 Q_ASSERT(bottomRight
.row() < actions
.count());
352 for (int row
= topLeft
.row(); row
<= bottomRight
.row(); row
++) {
353 updateAction(actions
[row
], d
->model
->index(row
, d
->column
, topLeft
.parent()));
357 void MenuView::modelReset()
359 // force clearance of the menu and rebuild from scratch
363 void MenuView::setColumn(int column
)
369 int MenuView::column() const
374 MenuView::FormatType
MenuView::formatType() const
376 return d
->formattype
;
379 void MenuView::setFormatType(MenuView::FormatType formattype
)
381 d
->formattype
= formattype
;
384 void MenuView::actionTriggered(QAction
*action
)
387 QModelIndex index
= indexForAction(action
);
388 Q_ASSERT(index
.isValid());
389 d
->launcher
->openItem(index
);
392 #include "menuview.moc"