00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "kmenu.h"
00022 #include "khbox.h"
00023
00024 #include <QtCore/QObject>
00025 #include <QtCore/QPointer>
00026 #include <QtCore/QTimer>
00027 #include <QtGui/QApplication>
00028 #include <QtGui/QCursor>
00029 #include <QtGui/QFontMetrics>
00030 #include <QtGui/QHBoxLayout>
00031 #include <QtGui/QKeyEvent>
00032 #include <QtGui/QMenuItem>
00033 #include <QtGui/QLabel>
00034 #include <QtGui/QPainter>
00035 #include <QtGui/QStyle>
00036 #include <QtGui/QToolButton>
00037 #include <QtGui/QWidgetAction>
00038
00039 #include <kdebug.h>
00040 #include <kglobal.h>
00041 #include <klocale.h>
00042 #include <kacceleratormanager.h>
00043
00044 class KMenu::KMenuPrivate
00045 {
00046 public:
00047 KMenuPrivate (KMenu *_parent);
00048 ~KMenuPrivate ();
00049
00050 void resetKeyboardVars(bool noMatches = false);
00051 void actionHovered(QAction* action);
00052 void showCtxMenu(const QPoint &pos);
00053
00054 KMenu *parent;
00055
00056
00057 QTimer clearTimer;
00058
00059 bool noMatches : 1;
00060 bool shortcuts : 1;
00061 bool autoExec : 1;
00062
00063 QString keySeq;
00064 QString originalText;
00065
00066 QAction* lastHitAction;
00067 QAction* lastHoveredAction;
00068 Qt::MouseButtons mouseButtons;
00069 Qt::KeyboardModifiers keyboardModifiers;
00070
00071
00072 QMenu* ctxMenu;
00073 QPointer<QAction> highlightedAction;
00074
00075 class EventSniffer;
00076 EventSniffer *eventSniffer;
00077 };
00078
00089 class KMenu::KMenuPrivate::EventSniffer
00090 : public QObject
00091 {
00092 public:
00093 EventSniffer(QObject *parent = 0)
00094 : QObject(parent) { }
00095
00096 ~EventSniffer() { }
00097
00098 bool eventFilter(QObject *object, QEvent *event)
00099 {
00100 Q_UNUSED(object);
00101
00102 if (event->type() == QEvent::Paint ||
00103 event->type() == QEvent::KeyPress ||
00104 event->type() == QEvent::KeyRelease) {
00105 return false;
00106 }
00107
00108 event->accept();
00109 return true;
00110 }
00111 };
00112
00113 KMenu::KMenuPrivate::KMenuPrivate (KMenu *_parent)
00114 : parent(_parent)
00115 , noMatches(false)
00116 , shortcuts(false)
00117 , autoExec(false)
00118 , lastHitAction(0L)
00119 , lastHoveredAction(0L)
00120 , mouseButtons(Qt::NoButton)
00121 , keyboardModifiers(Qt::NoModifier)
00122 , ctxMenu(0)
00123 , highlightedAction(0)
00124 , eventSniffer(new EventSniffer)
00125 {
00126 resetKeyboardVars();
00127 KAcceleratorManager::manage(parent);
00128 }
00129
00130 KMenu::KMenuPrivate::~KMenuPrivate ()
00131 {
00132 delete ctxMenu;
00133 delete eventSniffer;
00134 }
00135
00136
00141 class KMenuContext {
00142 public:
00143 KMenuContext();
00144 KMenuContext(const KMenuContext& o);
00145 KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action);
00146
00147 inline QPointer<KMenu> menu() const { return m_menu; }
00148 inline QPointer<QAction> action() const { return m_action; }
00149
00150 private:
00151 QPointer<KMenu> m_menu;
00152 QPointer<QAction> m_action;
00153 };
00154
00155
00156 Q_DECLARE_METATYPE(KMenuContext)
00157
00158
00159
00160 KMenu::KMenu(QWidget *parent)
00161 : QMenu(parent)
00162 , d(new KMenuPrivate(this))
00163 {
00164 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00165 }
00166
00167 KMenu::KMenu( const QString & title, QWidget * parent )
00168 : QMenu(title, parent)
00169 , d(new KMenuPrivate(this))
00170 {
00171 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00172 }
00173
00174 KMenu::~KMenu()
00175 {
00176 delete d;
00177 }
00178
00179 QAction* KMenu::addTitle(const QString &text, QAction* before)
00180 {
00181 return addTitle(QIcon(), text, before);
00182 }
00183
00184 QAction* KMenu::addTitle(const QIcon &icon, const QString &text, QAction* before)
00185 {
00186 QAction *buttonAction = new QAction(this);
00187 QFont font = buttonAction->font();
00188 font.setBold(true);
00189 buttonAction->setFont(font);
00190 buttonAction->setText(text);
00191 buttonAction->setIcon(icon);
00192
00193 QWidgetAction *action = new QWidgetAction(this);
00194 QToolButton *titleButton = new QToolButton(this);
00195 titleButton->installEventFilter(d->eventSniffer);
00196 titleButton->setDefaultAction(buttonAction);
00197 titleButton->setDown(true);
00198 titleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
00199 action->setDefaultWidget(titleButton);
00200
00201 insertAction(before, action);
00202 return action;
00203 }
00204
00208 void KMenu::closeEvent(QCloseEvent*e)
00209 {
00210 if (d->shortcuts)
00211 d->resetKeyboardVars();
00212 QMenu::closeEvent(e);
00213 }
00214
00215 Qt::MouseButtons KMenu::mouseButtons() const
00216 {
00217 return d->mouseButtons;
00218 }
00219
00220 Qt::KeyboardModifiers KMenu::keyboardModifiers() const
00221 {
00222 return d->keyboardModifiers;
00223 }
00224
00225 void KMenu::keyPressEvent(QKeyEvent* e)
00226 {
00227 d->mouseButtons = Qt::NoButton;
00228 d->keyboardModifiers = Qt::NoModifier;
00229
00230 if (!d->shortcuts) {
00231 d->keyboardModifiers = e->modifiers();
00232 QMenu::keyPressEvent(e);
00233 return;
00234 }
00235
00236 QAction* a = 0L;
00237 bool firstpass = true;
00238 QString keyString = e->text();
00239
00240
00241 int key = e->key();
00242 if (key == Qt::Key_Escape || key == Qt::Key_Return || key == Qt::Key_Enter
00243 || key == Qt::Key_Up || key == Qt::Key_Down || key == Qt::Key_Left
00244 || key == Qt::Key_Right || key == Qt::Key_F1 || key == Qt::Key_PageUp
00245 || key == Qt::Key_PageDown || key == Qt::Key_Back || key == Qt::Key_Select) {
00246
00247 d->resetKeyboardVars();
00248
00249
00250 d->keyboardModifiers = e->modifiers();
00251 QMenu::keyPressEvent(e);
00252 return;
00253 } else if ( key == Qt::Key_Shift || key == Qt::Key_Control || key == Qt::Key_Alt || key == Qt::Key_Meta )
00254 return QMenu::keyPressEvent(e);
00255
00256
00257
00258 if (!d->keySeq.isNull()) {
00259 if (key == Qt::Key_Backspace) {
00260
00261 if (d->keySeq.length() == 1) {
00262 d->resetKeyboardVars();
00263 return;
00264 }
00265
00266
00267 keyString = d->keySeq.left(d->keySeq.length() - 1);
00268
00269
00270 d->resetKeyboardVars();
00271
00272 } else if (key == Qt::Key_Delete) {
00273 d->resetKeyboardVars();
00274
00275
00276 setActiveAction(0L);
00277 return;
00278
00279 } else if (d->noMatches) {
00280
00281 d->resetKeyboardVars();
00282
00283
00284 setActiveAction(0L);
00285
00286 } else {
00287
00288
00289 a = d->lastHitAction;
00290 }
00291
00292 } else if (key == Qt::Key_Backspace && menuAction()) {
00293
00294 hide();
00295 d->resetKeyboardVars();
00296 return;
00297 }
00298
00299 d->keySeq += keyString;
00300 const int seqLen = d->keySeq.length();
00301
00302 foreach (a, actions()) {
00303
00304 if (!a->isEnabled())
00305 continue;
00306
00307 QString thisText;
00308
00309
00310
00311 if (a == d->lastHitAction)
00312 thisText = d->originalText;
00313 else
00314 thisText = a->text();
00315
00316
00317 thisText = KGlobal::locale()->removeAcceleratorMarker(thisText);
00318
00319
00320 thisText = thisText.left(seqLen);
00321
00322
00323 if (!thisText.indexOf(d->keySeq, 0, Qt::CaseInsensitive)) {
00324
00325 if (firstpass) {
00326
00327 setActiveAction(a);
00328
00329
00330 if (d->lastHitAction && d->lastHitAction != a)
00331
00332 d->lastHitAction->setText(d->originalText);
00333
00334
00335 if (d->lastHitAction != a || d->lastHitAction == 0L)
00336 d->originalText = a->text();
00337
00338
00339 a->setText(underlineText(d->originalText, d->keySeq.length()));
00340
00341
00342 d->lastHitAction = a;
00343
00344
00345 d->clearTimer.setSingleShot(true);
00346 d->clearTimer.start(5000);
00347
00348
00349 firstpass = false;
00350 } else {
00351
00352 return;
00353 }
00354 }
00355
00356
00357 }
00358
00359 if (!firstpass) {
00360 if (d->autoExec) {
00361
00362 d->lastHitAction->activate(QAction::Trigger);
00363 d->resetKeyboardVars();
00364
00365 } else if (d->lastHitAction && d->lastHitAction->menu()) {
00366
00367 d->lastHitAction->activate(QAction::Trigger);
00368 d->resetKeyboardVars();
00369 }
00370
00371 return;
00372 }
00373
00374
00375 d->resetKeyboardVars(true);
00376
00377 QMenu::keyPressEvent(e);
00378 }
00379
00380 bool KMenu::focusNextPrevChild( bool next )
00381 {
00382 d->resetKeyboardVars();
00383 return QMenu::focusNextPrevChild( next );
00384 }
00385
00386 QString KMenu::underlineText(const QString& text, uint length)
00387 {
00388 QString ret = text;
00389 for (uint i = 0; i < length; i++) {
00390 if (ret[2*i] != '&')
00391 ret.insert(2*i, '&');
00392 }
00393 return ret;
00394 }
00395
00396 void KMenu::KMenuPrivate::resetKeyboardVars(bool _noMatches)
00397 {
00398
00399 if (lastHitAction) {
00400 lastHitAction->setText(originalText);
00401 lastHitAction = 0L;
00402 }
00403
00404 if (!noMatches) {
00405 keySeq.clear();
00406 }
00407
00408 noMatches = _noMatches;
00409 }
00410
00411 void KMenu::setKeyboardShortcutsEnabled(bool enable)
00412 {
00413 d->shortcuts = enable;
00414 }
00415
00416 void KMenu::setKeyboardShortcutsExecute(bool enable)
00417 {
00418 d->autoExec = enable;
00419 }
00428 void KMenu::mousePressEvent(QMouseEvent* e)
00429 {
00430 if (d->ctxMenu && d->ctxMenu->isVisible())
00431 {
00432
00433 d->ctxMenu->hide();
00434 }
00435
00436 if( e->button() == Qt::MidButton)
00437 return;
00438
00439 QMenu::mousePressEvent(e);
00440 }
00441
00442 void KMenu::mouseReleaseEvent(QMouseEvent* e)
00443 {
00444
00445 d->keyboardModifiers = e->modifiers();
00446 d->mouseButtons = e->buttons();
00447
00448 if ( e->button() == Qt::MidButton) {
00449 if(activeAction() ) {
00450 QMetaObject::invokeMethod(activeAction(), "triggered", Qt::DirectConnection,
00451 Q_ARG(Qt::MouseButtons, e->button()),
00452 Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers() ));
00453 }
00454 return;
00455 }
00456
00457 if ( !d->ctxMenu || !d->ctxMenu->isVisible() )
00458 QMenu::mouseReleaseEvent(e);
00459 }
00460
00461 QMenu* KMenu::contextMenu()
00462 {
00463 if (!d->ctxMenu)
00464 {
00465 d->ctxMenu = new QMenu(this);
00466 connect(this, SIGNAL(hovered(QAction*)), SLOT(actionHovered(QAction*)));
00467 }
00468
00469 return d->ctxMenu;
00470 }
00471
00472 const QMenu* KMenu::contextMenu() const
00473 {
00474 return const_cast< KMenu* >( this )->contextMenu();
00475 }
00476
00477 void KMenu::hideContextMenu()
00478 {
00479 if (!d->ctxMenu || !d->ctxMenu->isVisible())
00480 {
00481 return;
00482 }
00483
00484 d->ctxMenu->hide();
00485 }
00486
00487 void KMenu::KMenuPrivate::actionHovered(QAction* action)
00488 {
00489 lastHoveredAction = action;
00490 parent->hideContextMenu();
00491 }
00492
00493 static void KMenuSetActionData(QMenu *menu,KMenu* contextedMenu, QAction* contextedAction) {
00494 const QList<QAction*> actions=menu->actions();
00495 QVariant v;
00496 v.setValue(KMenuContext(contextedMenu,contextedAction));
00497 for(int i=0;i<actions.count();i++) {
00498 actions[i]->setData(v);
00499 }
00500 }
00501
00502 void KMenu::KMenuPrivate::showCtxMenu(const QPoint &pos)
00503 {
00504 highlightedAction = parent->activeAction();
00505
00506 if (!highlightedAction)
00507 {
00508 KMenuSetActionData(parent,0,0);
00509 return;
00510 }
00511
00512 emit parent->aboutToShowContextMenu(parent, highlightedAction, ctxMenu);
00513 KMenuSetActionData(parent,parent,highlightedAction);
00514
00515
00516 if (QMenu* subMenu = highlightedAction->menu())
00517 {
00518 QTimer::singleShot(100, subMenu, SLOT(hide()));
00519 }
00520
00521
00522 ctxMenu->popup(parent->mapToGlobal(pos));
00523 }
00524
00525 KMenu * KMenu::contextMenuFocus( )
00526 {
00527 return qobject_cast<KMenu*>(QApplication::activePopupWidget());
00528 }
00529
00530 QAction * KMenu::contextMenuFocusAction( )
00531 {
00532 if (KMenu* menu = qobject_cast<KMenu*>(QApplication::activePopupWidget())) {
00533 if (!menu->d->lastHoveredAction) {
00534 return 0;
00535 }
00536 QVariant var = menu->d->lastHoveredAction->data();
00537 KMenuContext ctx = var.value<KMenuContext>();
00538 Q_ASSERT(ctx.menu() == menu);
00539 return ctx.action();
00540 }
00541
00542 return 0L;
00543 }
00544
00545 void KMenu::contextMenuEvent(QContextMenuEvent* e)
00546 {
00547 if (d->ctxMenu)
00548 {
00549 if (e->reason() == QContextMenuEvent::Mouse)
00550 {
00551 d->showCtxMenu(e->pos());
00552 }
00553 else if (activeAction())
00554 {
00555 d->showCtxMenu(actionGeometry(activeAction()).center());
00556 }
00557
00558 e->accept();
00559 return;
00560 }
00561
00562 QMenu::contextMenuEvent(e);
00563 }
00564
00565 void KMenu::hideEvent(QHideEvent *e)
00566 {
00567 if (d->ctxMenu && d->ctxMenu->isVisible())
00568 {
00569
00570
00571
00572
00573
00574
00575
00576
00577 bool blocked = blockSignals(true);
00578 d->ctxMenu->hide();
00579 blockSignals(blocked);
00580 }
00581 QMenu::hideEvent(e);
00582 }
00591 KMenuContext::KMenuContext( )
00592 : m_menu(0L)
00593 , m_action(0L)
00594 {
00595 }
00596
00597 KMenuContext::KMenuContext( const KMenuContext & o )
00598 : m_menu(o.m_menu)
00599 , m_action(o.m_action)
00600 {
00601 }
00602
00603 KMenuContext::KMenuContext(QPointer<KMenu> menu,QPointer<QAction> action)
00604 : m_menu(menu)
00605 , m_action(action)
00606 {
00607 }
00608
00609 #include "kmenu.moc"