add more spacing
[personal-kdebase.git] / workspace / plasma / applets / kickoff / simpleapplet / menuview.cpp
blobec69fbd43958387505a8a5f402a568d6d3007492
1 /*
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.
21 // Own
22 #include "menuview.h"
24 // Qt
25 #include <QtCore/QAbstractItemModel>
26 #include <QtCore/QStack>
27 #include <QtGui/QApplication>
28 #include <QtGui/QMouseEvent>
29 #include <QtCore/QPersistentModelIndex>
31 // KDE
32 #include <KDebug>
33 #include <KUrl>
34 #include <KIconLoader>
36 // Local
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
48 public:
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());
56 QAction *action = 0;
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);
69 } else {
70 action = q->createLeafAction(index, parent);
73 q->updateAction(action, index);
75 return action;
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);
86 MenuView * const q;
87 QAbstractItemModel *model;
88 int column;
89 UrlItemLauncher *launcher;
90 MenuView::FormatType formattype;
91 QPoint mousePressPos;
94 MenuView::MenuView(QWidget *parent)
95 : KMenu(parent)
96 , d(new Private(this))
98 installEventFilter(this);
101 MenuView::~MenuView()
103 delete d;
106 QAction *MenuView::createLeafAction(const QModelIndex&, QObject *parent)
108 return new QAction(parent);
111 void MenuView::updateAction(QAction *action, const QModelIndex& index)
113 Q_ASSERT(d->model);
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);
119 } else {
120 switch (d->formattype) {
121 case Name: {
122 if (name.isEmpty()) {
123 action->setText(text);
124 } else {
125 action->setText(name);
128 break;
129 case Description: {
130 if (name.contains(text, Qt::CaseInsensitive)) {
131 text = name;
133 action->setText(text);
135 break;
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));
149 } else {
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);
157 break;
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());
182 if (!action) {
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);
210 return true;
212 } else if (event->type() == QEvent::MouseButtonPress) {
213 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
214 QMenu *watchedMenu = qobject_cast<QMenu*>(watched);
215 if (watchedMenu) {
216 d->mousePressPos = mouseEvent->pos();
218 } else if (event->type() == QEvent::MouseButtonRelease) {
219 QMenu *watchedMenu = qobject_cast<QMenu*>(watched);
220 if (watchedMenu) {
221 d->mousePressPos = QPoint();
225 return KMenu::eventFilter(watched, event);
228 void MenuView::setModel(QAbstractItemModel *model)
230 if (d->model) {
231 d->model->disconnect(this);
233 d->model = model;
234 clear();
235 if (d->model) {
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
246 return d->model;
249 UrlItemLauncher *MenuView::launcher() const
251 return d->launcher;
254 QModelIndex MenuView::indexForAction(QAction *action) const
256 Q_ASSERT(d->model);
257 Q_ASSERT(action != 0);
258 QPersistentModelIndex index = action->data().value<QPersistentModelIndex>();
259 Q_ASSERT(index.isValid());
260 return index;
263 QAction *MenuView::actionForIndex(const QModelIndex& index) const
265 Q_ASSERT(d->model);
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*>();
273 Q_ASSERT(a);
274 return a;
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
287 return;
290 Q_ASSERT(d->model);
292 QAction *menuAction = actionForIndex(parent);
293 Q_ASSERT(menuAction);
295 QMenu *menu = menuAction->menu();
296 Q_ASSERT(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);
307 } else {
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
316 return;
319 Q_ASSERT(d->model);
321 QAction *menuAction = actionForIndex(parent);
322 Q_ASSERT(menuAction);
324 QMenu *menu = menuAction->menu();
325 Q_ASSERT(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
338 return;
341 Q_ASSERT(d->model);
343 QAction *menuAction = actionForIndex(topLeft.parent());
344 Q_ASSERT(menuAction);
346 QMenu *menu = menuAction->menu();
347 Q_ASSERT(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
360 setModel(d->model);
363 void MenuView::setColumn(int column)
365 d->column = column;
366 modelReset();
369 int MenuView::column() const
371 return d->column;
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)
386 Q_ASSERT(d->model);
387 QModelIndex index = indexForAction(action);
388 Q_ASSERT(index.isValid());
389 d->launcher->openItem(index);
392 #include "menuview.moc"