00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include "menuview.h"
00023
00024
00025 #include <QtCore/QAbstractItemModel>
00026 #include <QtCore/QStack>
00027 #include <QtGui/QApplication>
00028 #include <QtGui/QMouseEvent>
00029 #include <QtCore/QPersistentModelIndex>
00030
00031
00032 #include <KDebug>
00033 #include <KUrl>
00034 #include <KIconLoader>
00035
00036
00037 #include "core/models.h"
00038 #include "core/itemhandlers.h"
00039
00040 Q_DECLARE_METATYPE(QPersistentModelIndex)
00041 Q_DECLARE_METATYPE(QAction*)
00042
00043 using namespace Kickoff;
00044
00046 class MenuView::Private
00047 {
00048 public:
00049 enum { ActionRole = Qt::UserRole + 52 };
00050
00051 Private(MenuView *parent) : q(parent) , model(0) , column(0), launcher(new UrlItemLauncher(parent)), formattype(MenuView::DescriptionName) {}
00052
00053 QAction *createActionForIndex(const QModelIndex& index, QMenu *parent) {
00054 Q_ASSERT(index.isValid());
00055
00056 QAction *action = 0;
00057
00058 if (model->hasChildren(index)) {
00059 KMenu *childMenu = new KMenu(parent);
00060 childMenu->installEventFilter(q);
00061
00062 action = childMenu->menuAction();
00063
00064 if (model->canFetchMore(index)) {
00065 model->fetchMore(index);
00066 }
00067
00068 buildBranch(childMenu, index);
00069 } else {
00070 action = q->createLeafAction(index, parent);
00071 }
00072
00073 q->updateAction(action, index);
00074
00075 return action;
00076 }
00077
00078 void buildBranch(QMenu *menu, const QModelIndex& parent) {
00079 int rowCount = model->rowCount(parent);
00080 for (int i = 0; i < rowCount; i++) {
00081 QAction *action = createActionForIndex(model->index(i, column, parent), menu);
00082 menu->addAction(action);
00083 }
00084 }
00085
00086 MenuView * const q;
00087 QAbstractItemModel *model;
00088 int column;
00089 UrlItemLauncher *launcher;
00090 MenuView::FormatType formattype;
00091 QPoint mousePressPos;
00092 };
00093
00094 MenuView::MenuView(QWidget *parent)
00095 : KMenu(parent)
00096 , d(new Private(this))
00097 {
00098 installEventFilter(this);
00099 }
00100
00101 MenuView::~MenuView()
00102 {
00103 delete d;
00104 }
00105
00106 QAction *MenuView::createLeafAction(const QModelIndex&, QObject *parent)
00107 {
00108 return new QAction(parent);
00109 }
00110
00111 void MenuView::updateAction(QAction *action, const QModelIndex& index)
00112 {
00113 Q_ASSERT(d->model);
00114
00115 QString text = index.data(Qt::DisplayRole).value<QString>().replace("&", "&&");
00116 QString name = index.data(Kickoff::SubTitleRole).value<QString>().replace("&", "&&");
00117 if (action->menu() != 0) {
00118 action->setText(text);
00119 } else {
00120 switch (d->formattype) {
00121 case Name: {
00122 if (name.isEmpty()) {
00123 action->setText(text);
00124 } else {
00125 action->setText(name);
00126 }
00127 }
00128 break;
00129 case Description: {
00130 if (name.contains(text, Qt::CaseInsensitive)) {
00131 text = name;
00132 }
00133 action->setText(text);
00134 }
00135 break;
00136 case NameDescription:
00137 case NameDashDescription:
00138 case DescriptionName: {
00139 if (!name.isEmpty()) {
00140 if (text.contains(name, Qt::CaseInsensitive)) {
00141 action->setText(text);
00142 } else if (name.contains(text, Qt::CaseInsensitive)) {
00143 action->setText(name);
00144 } else {
00145 if (d->formattype == NameDescription) {
00146 action->setText(QString("%1 %2").arg(name).arg(text));
00147 } else if (d->formattype == NameDashDescription) {
00148 action->setText(QString("%1 - %2").arg(name).arg(text));
00149 } else {
00150 action->setText(QString("%1 (%2)").arg(text).arg(name));
00151 }
00152 }
00153 } else {
00154 action->setText(text);
00155 }
00156 }
00157 break;
00158 }
00159 }
00160
00161 action->setIcon(index.data(Qt::DecorationRole).value<QIcon>());
00162
00163
00164 action->setData(qVariantFromValue(QPersistentModelIndex(index)));
00165
00166
00167 d->model->blockSignals(true);
00168 d->model->setData(index, qVariantFromValue(action), Private::ActionRole);
00169 d->model->blockSignals(false);
00170 }
00171
00172 bool MenuView::eventFilter(QObject *watched, QEvent *event)
00173 {
00174 if (event->type() == QEvent::MouseMove) {
00175 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
00176 QMenu *watchedMenu = qobject_cast<QMenu*>(watched);
00177 const int mousePressDistance = !d->mousePressPos.isNull() ? (mouseEvent->pos() - d->mousePressPos).manhattanLength() : 0;
00178
00179 if (watchedMenu && mouseEvent->buttons() & Qt::LeftButton
00180 && mousePressDistance >= QApplication::startDragDistance()) {
00181 QAction *action = watchedMenu->actionAt(mouseEvent->pos());
00182 if (!action) {
00183 return KMenu::eventFilter(watched, event);
00184 }
00185
00186 QPersistentModelIndex index = action->data().value<QPersistentModelIndex>();
00187 if (!index.isValid()) {
00188 return KMenu::eventFilter(watched, event);
00189 }
00190
00191 QString urlString = index.data(UrlRole).toString();
00192 if (urlString.isNull()) {
00193 return KMenu::eventFilter(watched, event);
00194 }
00195
00196 QMimeData *mimeData = new QMimeData();
00197 mimeData->setData("text/uri-list", urlString.toAscii());
00198 mimeData->setText(mimeData->text());
00199 QDrag *drag = new QDrag(this);
00200 drag->setMimeData(mimeData);
00201
00202 QIcon icon = action->icon();
00203 drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
00204
00205 d->mousePressPos = QPoint();
00206
00207 Qt::DropAction dropAction = drag->exec();
00208 Q_UNUSED(dropAction);
00209
00210 return true;
00211 }
00212 } else if (event->type() == QEvent::MouseButtonPress) {
00213 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
00214 QMenu *watchedMenu = qobject_cast<QMenu*>(watched);
00215 if (watchedMenu) {
00216 d->mousePressPos = mouseEvent->pos();
00217 }
00218 } else if (event->type() == QEvent::MouseButtonRelease) {
00219 QMenu *watchedMenu = qobject_cast<QMenu*>(watched);
00220 if (watchedMenu) {
00221 d->mousePressPos = QPoint();
00222 }
00223 }
00224
00225 return KMenu::eventFilter(watched, event);
00226 }
00227
00228 void MenuView::setModel(QAbstractItemModel *model)
00229 {
00230 if (d->model) {
00231 d->model->disconnect(this);
00232 }
00233 d->model = model;
00234 clear();
00235 if (d->model) {
00236 d->buildBranch(this, QModelIndex());
00237 connect(d->model, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(rowsInserted(QModelIndex, int, int)));
00238 connect(d->model, SIGNAL(rowsAboutToBeRemoved(QModelIndex, int, int)), this, SLOT(rowsAboutToBeRemoved(QModelIndex, int, int)));
00239 connect(d->model, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(dataChanged(QModelIndex, QModelIndex)));
00240 connect(d->model, SIGNAL(modelReset()), this, SLOT(modelReset()));
00241 }
00242 }
00243
00244 QAbstractItemModel *MenuView::model() const
00245 {
00246 return d->model;
00247 }
00248
00249 UrlItemLauncher *MenuView::launcher() const
00250 {
00251 return d->launcher;
00252 }
00253
00254 QModelIndex MenuView::indexForAction(QAction *action) const
00255 {
00256 Q_ASSERT(d->model);
00257 Q_ASSERT(action != 0);
00258 QPersistentModelIndex index = action->data().value<QPersistentModelIndex>();
00259 Q_ASSERT(index.isValid());
00260 return index;
00261 }
00262
00263 QAction *MenuView::actionForIndex(const QModelIndex& index) const
00264 {
00265 Q_ASSERT(d->model);
00266 if (!index.isValid()) {
00267 return this->menuAction();
00268 }
00269
00270 QVariant v = d->model->data(index, Private::ActionRole);
00271 Q_ASSERT(v.isValid());
00272 QAction* a = v.value<QAction*>();
00273 Q_ASSERT(a);
00274 return a;
00275 }
00276
00277 bool MenuView::isValidIndex(const QModelIndex& index) const
00278 {
00279 QVariant v = (d->model && index.isValid()) ? d->model->data(index, Private::ActionRole) : QVariant();
00280 return v.isValid() && v.value<QAction*>();
00281 }
00282
00283 void MenuView::rowsInserted(const QModelIndex& parent, int start, int end)
00284 {
00285 if (!isValidIndex(parent)) {
00286
00287 return;
00288 }
00289
00290 Q_ASSERT(d->model);
00291
00292 QAction *menuAction = actionForIndex(parent);
00293 Q_ASSERT(menuAction);
00294
00295 QMenu *menu = menuAction->menu();
00296 Q_ASSERT(menu);
00297
00298 QList<QAction*> newActions;
00299 for (int row = start; row <= end; row++) {
00300 QModelIndex index = d->model->index(row, d->column, parent);
00301 QAction *newAction = d->createActionForIndex(index, menu);
00302 newActions << newAction;
00303 }
00304
00305 if (start < menu->actions().count()) {
00306 menu->insertActions(menu->actions()[start], newActions);
00307 } else {
00308 menu->addActions(newActions);
00309 }
00310 }
00311
00312 void MenuView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
00313 {
00314 if (!isValidIndex(parent)) {
00315
00316 return;
00317 }
00318
00319 Q_ASSERT(d->model);
00320
00321 QAction *menuAction = actionForIndex(parent);
00322 Q_ASSERT(menuAction);
00323
00324 QMenu *menu = menuAction->menu();
00325 Q_ASSERT(menu);
00326
00327 QList<QAction*> actions = menu->actions();
00328 Q_ASSERT(end < actions.count());
00329 for (int row = end; row >= start; row--) {
00330 menu->removeAction(actions[row]);
00331 }
00332 }
00333
00334 void MenuView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
00335 {
00336 if (!isValidIndex(topLeft.parent())) {
00337
00338 return;
00339 }
00340
00341 Q_ASSERT(d->model);
00342
00343 QAction *menuAction = actionForIndex(topLeft.parent());
00344 Q_ASSERT(menuAction);
00345
00346 QMenu *menu = menuAction->menu();
00347 Q_ASSERT(menu);
00348
00349 QList<QAction*> actions = menu->actions();
00350 Q_ASSERT(bottomRight.row() < actions.count());
00351
00352 for (int row = topLeft.row(); row <= bottomRight.row(); row++) {
00353 updateAction(actions[row], d->model->index(row, d->column, topLeft.parent()));
00354 }
00355 }
00356
00357 void MenuView::modelReset()
00358 {
00359
00360 setModel(d->model);
00361 }
00362
00363 void MenuView::setColumn(int column)
00364 {
00365 d->column = column;
00366 modelReset();
00367 }
00368
00369 int MenuView::column() const
00370 {
00371 return d->column;
00372 }
00373
00374 MenuView::FormatType MenuView::formatType() const
00375 {
00376 return d->formattype;
00377 }
00378
00379 void MenuView::setFormatType(MenuView::FormatType formattype)
00380 {
00381 d->formattype = formattype;
00382 }
00383
00384 void MenuView::actionTriggered(QAction *action)
00385 {
00386 Q_ASSERT(d->model);
00387 QModelIndex index = indexForAction(action);
00388 Q_ASSERT(index.isValid());
00389 d->launcher->openItem(index);
00390 }
00391
00392 #include "menuview.moc"