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