Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

opopupmenu.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2000 Daniel M. Duley <mosfet@kde.org>
00003    Copyright (C) 2002 Hamish Rodda <meddie@yoyo.its.monash.edu.au>
00004 
00005    This library is free software; you can redistribute it and/or
00006    modify it under the terms of the GNU Library General Public
00007    License version 2 as published by the Free Software Foundation.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00017    Boston, MA 02111-1307, USA.
00018 */
00019 
00020 /* OPIE */
00021 #include <opie2/opopupmenu.h>
00022 #include <opie2/oconfig.h>
00023 #include <opie2/odebug.h>
00024 
00025 /* QT */
00026 #include <qdrawutil.h>
00027 #include <qtimer.h>
00028 
00029 
00030 using namespace Opie::Core;
00031 using namespace Opie::Ui;
00032 
00033 OPopupTitle::OPopupTitle(QWidget *parent, const char *name)
00034     : QWidget(parent, name)
00035 {
00036     setMinimumSize(16, fontMetrics().height()+8);
00037 }
00038 
00039 OPopupTitle::OPopupTitle(OPixmapEffect::GradientType /* gradient */,
00040                          const QColor &/* color */, const QColor &/* textColor */,
00041                          QWidget *parent, const char *name)
00042    : QWidget(parent, name)
00043 {
00044     setMinimumSize(16, fontMetrics().height()+8);
00045 }
00046 
00047 OPopupTitle::OPopupTitle(const OPixmap & /* background */, const QColor &/* color */,
00048                          const QColor &/* textColor */, QWidget *parent,
00049                          const char *name)
00050     : QWidget(parent, name)
00051 {
00052     setMinimumSize(16, fontMetrics().height()+8);
00053 }
00054 
00055 void OPopupTitle::setTitle(const QString &text, const QPixmap *icon)
00056 {
00057     titleStr = text;
00058     if (icon)
00059         miniicon = *icon;
00060     else
00061         miniicon.resize(0, 0);
00062 
00063     int w = miniicon.width()+fontMetrics().width(titleStr);
00064     int h = QMAX( fontMetrics().height(), miniicon.height() );
00065     setMinimumSize( w+16, h+8 );
00066 }
00067 
00068 void OPopupTitle::setText( const QString &text )
00069 {
00070     titleStr = text;
00071     int w = miniicon.width()+fontMetrics().width(titleStr);
00072     int h = QMAX( fontMetrics().height(), miniicon.height() );
00073     setMinimumSize( w+16, h+8 );
00074 }
00075 
00076 void OPopupTitle::setIcon( const QPixmap &pix )
00077 {
00078     miniicon = pix;
00079     int w = miniicon.width()+fontMetrics().width(titleStr);
00080     int h = QMAX( fontMetrics().height(), miniicon.height() );
00081     setMinimumSize( w+16, h+8 );
00082 }
00083 
00084 void OPopupTitle::paintEvent(QPaintEvent *)
00085 {
00086     QRect r(rect());
00087     QPainter p(this);
00088     #if QT_VERSION >= 0x030000
00089     qApp->style().drawPrimitive(QStyle::PE_HeaderSection, &p, r, palette().active());
00090     #else
00091     #warning OPopupMenu is not fully functional on Qt2
00092     #endif
00093 
00094     if (!miniicon.isNull())
00095         p.drawPixmap(4, (r.height()-miniicon.height())/2, miniicon);
00096 
00097     if (!titleStr.isNull())
00098     {
00099         p.setPen(palette().active().text());
00100         QFont f = p.font();
00101         f.setBold(true);
00102         p.setFont(f);
00103         if(!miniicon.isNull())
00104         {
00105             p.drawText(miniicon.width()+8, 0, width()-(miniicon.width()+8),
00106                        height(), AlignLeft | AlignVCenter | SingleLine,
00107                        titleStr);
00108         }
00109         else
00110         {
00111             p.drawText(0, 0, width(), height(),
00112                        AlignCenter | SingleLine, titleStr);
00113         }
00114     }
00115 
00116     p.setPen(palette().active().highlight());
00117     p.drawLine(0, 0, r.right(), 0);
00118 }
00119 
00120 QSize OPopupTitle::sizeHint() const
00121 {
00122     return(minimumSize());
00123 }
00124 
00125 class OPopupMenu::OPopupMenuPrivate
00126 {
00127 public:
00128     OPopupMenuPrivate ()
00129         : noMatches(false)
00130         , shortcuts(false)
00131         , autoExec(false)
00132         , lastHitIndex(-1)
00133         , m_ctxMenu(0)
00134     {}
00135 
00136     ~OPopupMenuPrivate ()
00137     {
00138         delete m_ctxMenu;
00139     }
00140 
00141     QString m_lastTitle;
00142 
00143     // variables for keyboard navigation
00144     QTimer clearTimer;
00145 
00146     bool noMatches : 1;
00147     bool shortcuts : 1;
00148     bool autoExec : 1;
00149 
00150     QString keySeq;
00151     QString originalText;
00152 
00153     int lastHitIndex;
00154 
00155     // support for RMB menus on menus
00156     QPopupMenu* m_ctxMenu;
00157     static bool s_continueCtxMenuShow;
00158     static int s_highlightedItem;
00159     static OPopupMenu* s_contextedMenu;
00160 };
00161 
00162 int OPopupMenu::OPopupMenuPrivate::s_highlightedItem(-1);
00163 OPopupMenu* OPopupMenu::OPopupMenuPrivate::s_contextedMenu(0);
00164 bool OPopupMenu::OPopupMenuPrivate::s_continueCtxMenuShow(true);
00165 
00166 OPopupMenu::OPopupMenu(QWidget *parent, const char *name)
00167     : QPopupMenu(parent, name)
00168 {
00169     d = new OPopupMenuPrivate;
00170     resetKeyboardVars();
00171     connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
00172 }
00173 
00174 OPopupMenu::~OPopupMenu()
00175 {
00176     if (OPopupMenuPrivate::s_contextedMenu == this)
00177     {
00178         OPopupMenuPrivate::s_contextedMenu = 0;
00179         OPopupMenuPrivate::s_highlightedItem = -1;
00180     }
00181 
00182     delete d;
00183 }
00184 
00185 int OPopupMenu::insertTitle(const QString &text, int id, int index)
00186 {
00187     OPopupTitle *titleItem = new OPopupTitle();
00188     titleItem->setTitle(text);
00189     int ret = insertItem(titleItem, id, index);
00190     setItemEnabled(id, false);
00191     return ret;
00192 }
00193 
00194 int OPopupMenu::insertTitle(const QPixmap &icon, const QString &text, int id,
00195                             int index)
00196 {
00197     OPopupTitle *titleItem = new OPopupTitle();
00198     titleItem->setTitle(text, &icon);
00199     int ret = insertItem(titleItem, id, index);
00200     setItemEnabled(id, false);
00201     return ret;
00202 }
00203 
00204 void OPopupMenu::changeTitle(int id, const QString &text)
00205 {
00206     QMenuItem *item = findItem(id);
00207     if(item){
00208         if(item->widget())
00209             ((OPopupTitle *)item->widget())->setTitle(text);
00210 #ifndef NDEBUG
00211         else
00212             owarn << "KPopupMenu: changeTitle() called with non-title id " << id << "" << oendl;
00213 #endif
00214     }
00215 #ifndef NDEBUG
00216     else
00217         owarn << "KPopupMenu: changeTitle() called with invalid id " << id << "" << oendl;
00218 #endif
00219 }
00220 
00221 void OPopupMenu::changeTitle(int id, const QPixmap &icon, const QString &text)
00222 {
00223     QMenuItem *item = findItem(id);
00224     if(item){
00225         if(item->widget())
00226             ((OPopupTitle *)item->widget())->setTitle(text, &icon);
00227 #ifndef NDEBUG
00228         else
00229             owarn << "KPopupMenu: changeTitle() called with non-title id " << id << "" << oendl;
00230 #endif
00231     }
00232 #ifndef NDEBUG
00233     else
00234         owarn << "KPopupMenu: changeTitle() called with invalid id " << id << "" << oendl;
00235 #endif
00236 }
00237 
00238 QString OPopupMenu::title(int id) const
00239 {
00240     if(id == -1) // obsolete
00241         return(d->m_lastTitle);
00242     QMenuItem *item = findItem(id);
00243     if(item){
00244         if(item->widget())
00245             return(((OPopupTitle *)item->widget())->title());
00246         else
00247             owarn << "OPopupMenu: title() called with non-title id " << id << "." << oendl;
00248     }
00249     else
00250         owarn << "OPopupMenu: title() called with invalid id " << id << "." << oendl;
00251     return(QString::null);
00252 }
00253 
00254 QPixmap OPopupMenu::titlePixmap(int id) const
00255 {
00256     QMenuItem *item = findItem(id);
00257     if(item){
00258         if(item->widget())
00259             return(((OPopupTitle *)item->widget())->icon());
00260         else
00261             owarn << "KPopupMenu: titlePixmap() called with non-title id " << id << "." << oendl;
00262     }
00263     else
00264         owarn << "KPopupMenu: titlePixmap() called with invalid id " << id << "." << oendl;
00265     QPixmap tmp;
00266     return(tmp);
00267 }
00268 
00272 void OPopupMenu::closeEvent(QCloseEvent*e)
00273 {
00274     if (d->shortcuts)
00275         resetKeyboardVars();
00276     QPopupMenu::closeEvent(e);
00277 }
00278 
00279 void OPopupMenu::keyPressEvent(QKeyEvent* e)
00280 {
00281     if (!d->shortcuts) {
00282         // continue event processing by Qpopup
00283         //e->ignore();
00284         QPopupMenu::keyPressEvent(e);
00285         return;
00286     }
00287 
00288     int i = 0;
00289     bool firstpass = true;
00290     QString keyString = e->text();
00291 
00292     // check for common commands dealt with by QPopup
00293     int key = e->key();
00294     if (key == Key_Escape || key == Key_Return || key == Key_Enter
00295             || key == Key_Up || key == Key_Down || key == Key_Left
00296             || key == Key_Right || key == Key_F1) {
00297 
00298         resetKeyboardVars();
00299         // continue event processing by Qpopup
00300         //e->ignore();
00301         QPopupMenu::keyPressEvent(e);
00302         return;
00303     }
00304 
00305     // check to see if the user wants to remove a key from the sequence (backspace)
00306     // or clear the sequence (delete)
00307     if (!d->keySeq.isNull()) {
00308 
00309         if (key == Key_Backspace) {
00310 
00311             if (d->keySeq.length() == 1) {
00312                 resetKeyboardVars();
00313                 return;
00314             }
00315 
00316             // keep the last sequence in keyString
00317             keyString = d->keySeq.left(d->keySeq.length() - 1);
00318 
00319             // allow sequence matching to be tried again
00320             resetKeyboardVars();
00321 
00322         } else if (key == Key_Delete) {
00323             resetKeyboardVars();
00324 
00325             // clear active item
00326             setActiveItem(0);
00327             return;
00328 
00329         } else if (d->noMatches) {
00330             // clear if there are no matches
00331             resetKeyboardVars();
00332 
00333             // clear active item
00334             setActiveItem(0);
00335 
00336         } else {
00337             // the key sequence is not a null string
00338             // therefore the lastHitIndex is valid
00339             i = d->lastHitIndex;
00340         }
00341     } else if (key == Key_Backspace && parentMenu) {
00342         // backspace with no chars in the buffer... go back a menu.
00343         hide();
00344         resetKeyboardVars();
00345         return;
00346     }
00347 
00348     d->keySeq += keyString;
00349     int seqLen = d->keySeq.length();
00350 
00351     for (; i < (int)count(); i++) {
00352         // compare typed text with text of this entry
00353         int j = idAt(i);
00354 
00355         // don't search disabled entries
00356         if (!isItemEnabled(j))
00357             continue;
00358 
00359         QString thisText;
00360 
00361         // retrieve the right text
00362         // (the last selected item one may have additional ampersands)
00363         if (i == d->lastHitIndex)
00364             thisText = d->originalText;
00365         else
00366             thisText = text(j);
00367 
00368         // if there is an accelerator present, remove it
00369         if ((int)accel(j) != 0)
00370             thisText = thisText.replace(QRegExp("&"), "");
00371 
00372         // chop text to the search length
00373         thisText = thisText.left(seqLen);
00374 
00375         // do the search
00376         if (thisText.find(d->keySeq, 0, false) == 0) {
00377 
00378             if (firstpass) {
00379                 // match
00380                 setActiveItem(i);
00381 
00382                 // check to see if we're underlining a different item
00383                 if (d->lastHitIndex != i)
00384                     // yes; revert the underlining
00385                     changeItem(idAt(d->lastHitIndex), d->originalText);
00386 
00387                 // set the original text if it's a different item
00388                 if (d->lastHitIndex != i || d->lastHitIndex == -1)
00389                     d->originalText = text(j);
00390 
00391                 // underline the currently selected item
00392                 changeItem(j, underlineText(d->originalText, d->keySeq.length()));
00393 
00394                 // remeber what's going on
00395                 d->lastHitIndex = i;
00396 
00397                 // start/restart the clear timer
00398                 d->clearTimer.start(5000, true);
00399 
00400                 // go around for another try, to see if we can execute
00401                 firstpass = false;
00402             } else {
00403                 // don't allow execution
00404                 return;
00405             }
00406         }
00407 
00408         // fall through to allow execution
00409     }
00410 
00411     if (!firstpass) {
00412         if (d->autoExec) {
00413             // activate anything
00414             activateItemAt(d->lastHitIndex);
00415             resetKeyboardVars();
00416 
00417         } else if (findItem(idAt(d->lastHitIndex)) &&
00418                  findItem(idAt(d->lastHitIndex))->popup()) {
00419             // only activate sub-menus
00420             activateItemAt(d->lastHitIndex);
00421             resetKeyboardVars();
00422         }
00423 
00424         return;
00425     }
00426 
00427     // no matches whatsoever, clean up
00428     resetKeyboardVars(true);
00429     //e->ignore();
00430     QPopupMenu::keyPressEvent(e);
00431 }
00432 
00433 QString OPopupMenu::underlineText(const QString& text, uint length)
00434 {
00435     QString ret = text;
00436     for (uint i = 0; i < length; i++) {
00437         if (ret[2*i] != '&')
00438             ret.insert(2*i, "&");
00439     }
00440     return ret;
00441 }
00442 
00443 void OPopupMenu::resetKeyboardVars(bool noMatches /* = false */)
00444 {
00445     // Clean up keyboard variables
00446     if (d->lastHitIndex != -1) {
00447         changeItem(idAt(d->lastHitIndex), d->originalText);
00448         d->lastHitIndex = -1;
00449     }
00450 
00451     if (!noMatches) {
00452         d->keySeq = QString::null;
00453     }
00454 
00455     d->noMatches = noMatches;
00456 }
00457 
00458 void OPopupMenu::setKeyboardShortcutsEnabled(bool enable)
00459 {
00460     d->shortcuts = enable;
00461 }
00462 
00463 void OPopupMenu::setKeyboardShortcutsExecute(bool enable)
00464 {
00465     d->autoExec = enable;
00466 }
00474 QPopupMenu* OPopupMenu::contextMenu()
00475 {
00476     if (!d->m_ctxMenu)
00477     {
00478         d->m_ctxMenu = new QPopupMenu(this);
00479         installEventFilter(this);
00480         connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding()));
00481     }
00482 
00483     return d->m_ctxMenu;
00484 }
00485 
00486 void OPopupMenu::cancelContextMenuShow()
00487 {
00488     OPopupMenuPrivate::s_continueCtxMenuShow = false;
00489 }
00490 
00491 int OPopupMenu::contextMenuFocusItem()
00492 {
00493     return OPopupMenuPrivate::s_highlightedItem;
00494 }
00495 
00496 OPopupMenu* OPopupMenu::contextMenuFocus()
00497 {
00498     return OPopupMenuPrivate::s_contextedMenu;
00499 }
00500 
00501 void OPopupMenu::itemHighlighted(int /* whichItem */)
00502 {
00503     if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible())
00504     {
00505         return;
00506     }
00507 
00508     d->m_ctxMenu->hide();
00509     showCtxMenu(mapFromGlobal(QCursor::pos()));
00510 }
00511 
00512 void OPopupMenu::showCtxMenu(QPoint pos)
00513 {
00514     OPopupMenuPrivate::s_highlightedItem = idAt(pos);
00515 
00516     if (OPopupMenuPrivate::s_highlightedItem == -1)
00517     {
00518         OPopupMenuPrivate::s_contextedMenu = 0;
00519         return;
00520     }
00521 
00522     emit aboutToShowContextMenu(this, OPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu);
00523 
00524     if (!OPopupMenuPrivate::s_continueCtxMenuShow)
00525     {
00526         OPopupMenuPrivate::s_continueCtxMenuShow = true;
00527         return;
00528     }
00529 
00530     OPopupMenuPrivate::s_contextedMenu = this;
00531     d->m_ctxMenu->popup(this->mapToGlobal(pos));
00532     connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00533 }
00534 
00535 void OPopupMenu::ctxMenuHiding()
00536 {
00537     disconnect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
00538     OPopupMenuPrivate::s_continueCtxMenuShow = true;
00539 }
00540 
00541 bool OPopupMenu::eventFilter(QObject* obj, QEvent* event)
00542 {
00543     if (d->m_ctxMenu && obj == this)
00544     {
00545         if (event->type() == QEvent::MouseButtonRelease)
00546         {
00547             if (d->m_ctxMenu->isVisible())
00548             {
00549                 return true;
00550             }
00551         }
00552         #if QT_VERSION >= 0x030000
00553         else if (event->type() == QEvent::ContextMenu)
00554         #else
00555         else if ( (event->type() == QEvent::MouseButtonPress) &&
00556                   ( (QMouseEvent*) event )->button() == QMouseEvent::RightButton )
00557         #endif
00558         {
00559             showCtxMenu(mapFromGlobal(QCursor::pos()));
00560             return true;
00561         }
00562     }
00563 
00564     return QWidget::eventFilter(obj, event);
00565 }
00566 
00567 void OPopupMenu::hideEvent(QHideEvent*)
00568 {
00569     if (d->m_ctxMenu)
00570     {
00571         d->m_ctxMenu->hide();
00572     }
00573 }
00578 // Obsolete
00579 OPopupMenu::OPopupMenu(const QString& title, QWidget *parent, const char *name)
00580     : QPopupMenu(parent, name)
00581 {
00582     d = new OPopupMenuPrivate;
00583     setTitle(title);
00584 }
00585 
00586 // Obsolete
00587 void OPopupMenu::setTitle(const QString &title)
00588 {
00589     OPopupTitle *titleItem = new OPopupTitle();
00590     titleItem->setTitle(title);
00591     insertItem(titleItem);
00592     d->m_lastTitle = title;
00593 }
00594 
00595 void OPopupTitle::virtual_hook( int, void* )
00596 { /*BASE::virtual_hook( id, data );*/ }
00597 
00598 void OPopupMenu::virtual_hook( int, void* )
00599 { /*BASE::virtual_hook( id, data );*/ }
00600 

Generated on Sat Nov 5 16:16:27 2005 for OPIE by  doxygen 1.4.2