summaryrefslogtreecommitdiff
path: root/libopie2/opieui/opopupmenu.cpp
Unidiff
Diffstat (limited to 'libopie2/opieui/opopupmenu.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--libopie2/opieui/opopupmenu.cpp604
1 files changed, 604 insertions, 0 deletions
diff --git a/libopie2/opieui/opopupmenu.cpp b/libopie2/opieui/opopupmenu.cpp
new file mode 100644
index 0000000..ac73188
--- a/dev/null
+++ b/libopie2/opieui/opopupmenu.cpp
@@ -0,0 +1,604 @@
1/* This file is part of the KDE libraries
2 Copyright (C) 2000 Daniel M. Duley <mosfet@kde.org>
3 Copyright (C) 2002 Hamish Rodda <meddie@yoyo.its.monash.edu.au>
4
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 version 2 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 Boston, MA 02111-1307, USA.
18*/
19
20/* QT */
21
22#include <qapplication.h>
23#include <qcursor.h>
24#include <qpainter.h>
25#include <qdrawutil.h>
26#include <qtimer.h>
27#include <qfont.h>
28#include <qfontmetrics.h>
29#include <qregexp.h>
30#include <qstyle.h>
31
32/* OPIE */
33
34#include <opie2/opopupmenu.h>
35#include <opie2/oconfig.h>
36
37OPopupTitle::OPopupTitle(QWidget *parent, const char *name)
38 : QWidget(parent, name)
39{
40 setMinimumSize(16, fontMetrics().height()+8);
41}
42
43OPopupTitle::OPopupTitle(OPixmapEffect::GradientType /* gradient */,
44 const QColor &/* color */, const QColor &/* textColor */,
45 QWidget *parent, const char *name)
46 : QWidget(parent, name)
47{
48 setMinimumSize(16, fontMetrics().height()+8);
49}
50
51OPopupTitle::OPopupTitle(const OPixmap & /* background */, const QColor &/* color */,
52 const QColor &/* textColor */, QWidget *parent,
53 const char *name)
54 : QWidget(parent, name)
55{
56 setMinimumSize(16, fontMetrics().height()+8);
57}
58
59void OPopupTitle::setTitle(const QString &text, const QPixmap *icon)
60{
61 titleStr = text;
62 if (icon)
63 miniicon = *icon;
64 else
65 miniicon.resize(0, 0);
66
67 int w = miniicon.width()+fontMetrics().width(titleStr);
68 int h = QMAX( fontMetrics().height(), miniicon.height() );
69 setMinimumSize( w+16, h+8 );
70}
71
72void OPopupTitle::setText( const QString &text )
73{
74 titleStr = text;
75 int w = miniicon.width()+fontMetrics().width(titleStr);
76 int h = QMAX( fontMetrics().height(), miniicon.height() );
77 setMinimumSize( w+16, h+8 );
78}
79
80void OPopupTitle::setIcon( const QPixmap &pix )
81{
82 miniicon = pix;
83 int w = miniicon.width()+fontMetrics().width(titleStr);
84 int h = QMAX( fontMetrics().height(), miniicon.height() );
85 setMinimumSize( w+16, h+8 );
86}
87
88void OPopupTitle::paintEvent(QPaintEvent *)
89{
90 QRect r(rect());
91 QPainter p(this);
92 #if QT_VERSION > 290
93 qApp->style().drawPrimitive(QStyle::PE_HeaderSection, &p, r, palette().active());
94 #else
95 #warning OPopupMenu is not fully functional on Qt2
96 #endif
97
98 if (!miniicon.isNull())
99 p.drawPixmap(4, (r.height()-miniicon.height())/2, miniicon);
100
101 if (!titleStr.isNull())
102 {
103 p.setPen(palette().active().text());
104 QFont f = p.font();
105 f.setBold(true);
106 p.setFont(f);
107 if(!miniicon.isNull())
108 {
109 p.drawText(miniicon.width()+8, 0, width()-(miniicon.width()+8),
110 height(), AlignLeft | AlignVCenter | SingleLine,
111 titleStr);
112 }
113 else
114 {
115 p.drawText(0, 0, width(), height(),
116 AlignCenter | SingleLine, titleStr);
117 }
118 }
119
120 p.setPen(palette().active().highlight());
121 p.drawLine(0, 0, r.right(), 0);
122}
123
124QSize OPopupTitle::sizeHint() const
125{
126 return(minimumSize());
127}
128
129class OPopupMenu::OPopupMenuPrivate
130{
131public:
132 OPopupMenuPrivate ()
133 : noMatches(false)
134 , shortcuts(false)
135 , autoExec(false)
136 , lastHitIndex(-1)
137 , m_ctxMenu(0)
138 {}
139
140 ~OPopupMenuPrivate ()
141 {
142 delete m_ctxMenu;
143 }
144
145 QString m_lastTitle;
146
147 // variables for keyboard navigation
148 QTimer clearTimer;
149
150 bool noMatches : 1;
151 bool shortcuts : 1;
152 bool autoExec : 1;
153
154 QString keySeq;
155 QString originalText;
156
157 int lastHitIndex;
158
159 // support for RMB menus on menus
160 QPopupMenu* m_ctxMenu;
161 static bool s_continueCtxMenuShow;
162 static int s_highlightedItem;
163 static OPopupMenu* s_contextedMenu;
164};
165
166int OPopupMenu::OPopupMenuPrivate::s_highlightedItem(-1);
167OPopupMenu* OPopupMenu::OPopupMenuPrivate::s_contextedMenu(0);
168bool OPopupMenu::OPopupMenuPrivate::s_continueCtxMenuShow(true);
169
170OPopupMenu::OPopupMenu(QWidget *parent, const char *name)
171 : QPopupMenu(parent, name)
172{
173 d = new OPopupMenuPrivate;
174 resetKeyboardVars();
175 connect(&(d->clearTimer), SIGNAL(timeout()), SLOT(resetKeyboardVars()));
176}
177
178OPopupMenu::~OPopupMenu()
179{
180 if (OPopupMenuPrivate::s_contextedMenu == this)
181 {
182 OPopupMenuPrivate::s_contextedMenu = 0;
183 OPopupMenuPrivate::s_highlightedItem = -1;
184 }
185
186 delete d;
187}
188
189int OPopupMenu::insertTitle(const QString &text, int id, int index)
190{
191 OPopupTitle *titleItem = new OPopupTitle();
192 titleItem->setTitle(text);
193 int ret = insertItem(titleItem, id, index);
194 setItemEnabled(id, false);
195 return ret;
196}
197
198int OPopupMenu::insertTitle(const QPixmap &icon, const QString &text, int id,
199 int index)
200{
201 OPopupTitle *titleItem = new OPopupTitle();
202 titleItem->setTitle(text, &icon);
203 int ret = insertItem(titleItem, id, index);
204 setItemEnabled(id, false);
205 return ret;
206}
207
208void OPopupMenu::changeTitle(int id, const QString &text)
209{
210 QMenuItem *item = findItem(id);
211 if(item){
212 if(item->widget())
213 ((OPopupTitle *)item->widget())->setTitle(text);
214#ifndef NDEBUG
215 else
216 qWarning( "KPopupMenu: changeTitle() called with non-title id %d", id );
217#endif
218 }
219#ifndef NDEBUG
220 else
221 qWarning( "KPopupMenu: changeTitle() called with invalid id %d", id );
222#endif
223}
224
225void OPopupMenu::changeTitle(int id, const QPixmap &icon, const QString &text)
226{
227 QMenuItem *item = findItem(id);
228 if(item){
229 if(item->widget())
230 ((OPopupTitle *)item->widget())->setTitle(text, &icon);
231#ifndef NDEBUG
232 else
233 qWarning( "KPopupMenu: changeTitle() called with non-title id %d", id );
234#endif
235 }
236#ifndef NDEBUG
237 else
238 qWarning( "KPopupMenu: changeTitle() called with invalid id %d", id );
239#endif
240}
241
242QString OPopupMenu::title(int id) const
243{
244 if(id == -1) // obsolete
245 return(d->m_lastTitle);
246 QMenuItem *item = findItem(id);
247 if(item){
248 if(item->widget())
249 return(((OPopupTitle *)item->widget())->title());
250 else
251 qWarning("OPopupMenu: title() called with non-title id %d.", id);
252 }
253 else
254 qWarning("OPopupMenu: title() called with invalid id %d.", id);
255 return(QString::null);
256}
257
258QPixmap OPopupMenu::titlePixmap(int id) const
259{
260 QMenuItem *item = findItem(id);
261 if(item){
262 if(item->widget())
263 return(((OPopupTitle *)item->widget())->icon());
264 else
265 qWarning("KPopupMenu: titlePixmap() called with non-title id %d.", id);
266 }
267 else
268 qWarning("KPopupMenu: titlePixmap() called with invalid id %d.", id);
269 QPixmap tmp;
270 return(tmp);
271}
272
273/**
274 * This is re-implemented for keyboard navigation.
275 */
276void OPopupMenu::closeEvent(QCloseEvent*e)
277{
278 if (d->shortcuts)
279 resetKeyboardVars();
280 QPopupMenu::closeEvent(e);
281}
282
283void OPopupMenu::keyPressEvent(QKeyEvent* e)
284{
285 if (!d->shortcuts) {
286 // continue event processing by Qpopup
287 //e->ignore();
288 QPopupMenu::keyPressEvent(e);
289 return;
290 }
291
292 int i = 0;
293 bool firstpass = true;
294 QString keyString = e->text();
295
296 // check for common commands dealt with by QPopup
297 int key = e->key();
298 if (key == Key_Escape || key == Key_Return || key == Key_Enter
299 || key == Key_Up || key == Key_Down || key == Key_Left
300 || key == Key_Right || key == Key_F1) {
301
302 resetKeyboardVars();
303 // continue event processing by Qpopup
304 //e->ignore();
305 QPopupMenu::keyPressEvent(e);
306 return;
307 }
308
309 // check to see if the user wants to remove a key from the sequence (backspace)
310 // or clear the sequence (delete)
311 if (!d->keySeq.isNull()) {
312
313 if (key == Key_Backspace) {
314
315 if (d->keySeq.length() == 1) {
316 resetKeyboardVars();
317 return;
318 }
319
320 // keep the last sequence in keyString
321 keyString = d->keySeq.left(d->keySeq.length() - 1);
322
323 // allow sequence matching to be tried again
324 resetKeyboardVars();
325
326 } else if (key == Key_Delete) {
327 resetKeyboardVars();
328
329 // clear active item
330 setActiveItem(0);
331 return;
332
333 } else if (d->noMatches) {
334 // clear if there are no matches
335 resetKeyboardVars();
336
337 // clear active item
338 setActiveItem(0);
339
340 } else {
341 // the key sequence is not a null string
342 // therefore the lastHitIndex is valid
343 i = d->lastHitIndex;
344 }
345 } else if (key == Key_Backspace && parentMenu) {
346 // backspace with no chars in the buffer... go back a menu.
347 hide();
348 resetKeyboardVars();
349 return;
350 }
351
352 d->keySeq += keyString;
353 int seqLen = d->keySeq.length();
354
355 for (; i < (int)count(); i++) {
356 // compare typed text with text of this entry
357 int j = idAt(i);
358
359 // don't search disabled entries
360 if (!isItemEnabled(j))
361 continue;
362
363 QString thisText;
364
365 // retrieve the right text
366 // (the last selected item one may have additional ampersands)
367 if (i == d->lastHitIndex)
368 thisText = d->originalText;
369 else
370 thisText = text(j);
371
372 // if there is an accelerator present, remove it
373 if ((int)accel(j) != 0)
374 thisText = thisText.replace(QRegExp("&"), "");
375
376 // chop text to the search length
377 thisText = thisText.left(seqLen);
378
379 // do the search
380 if (thisText.find(d->keySeq, 0, false) == 0) {
381
382 if (firstpass) {
383 // match
384 setActiveItem(i);
385
386 // check to see if we're underlining a different item
387 if (d->lastHitIndex != i)
388 // yes; revert the underlining
389 changeItem(idAt(d->lastHitIndex), d->originalText);
390
391 // set the original text if it's a different item
392 if (d->lastHitIndex != i || d->lastHitIndex == -1)
393 d->originalText = text(j);
394
395 // underline the currently selected item
396 changeItem(j, underlineText(d->originalText, d->keySeq.length()));
397
398 // remeber what's going on
399 d->lastHitIndex = i;
400
401 // start/restart the clear timer
402 d->clearTimer.start(5000, true);
403
404 // go around for another try, to see if we can execute
405 firstpass = false;
406 } else {
407 // don't allow execution
408 return;
409 }
410 }
411
412 // fall through to allow execution
413 }
414
415 if (!firstpass) {
416 if (d->autoExec) {
417 // activate anything
418 activateItemAt(d->lastHitIndex);
419 resetKeyboardVars();
420
421 } else if (findItem(idAt(d->lastHitIndex)) &&
422 findItem(idAt(d->lastHitIndex))->popup()) {
423 // only activate sub-menus
424 activateItemAt(d->lastHitIndex);
425 resetKeyboardVars();
426 }
427
428 return;
429 }
430
431 // no matches whatsoever, clean up
432 resetKeyboardVars(true);
433 //e->ignore();
434 QPopupMenu::keyPressEvent(e);
435}
436
437QString OPopupMenu::underlineText(const QString& text, uint length)
438{
439 QString ret = text;
440 for (uint i = 0; i < length; i++) {
441 if (ret[2*i] != '&')
442 ret.insert(2*i, "&");
443 }
444 return ret;
445}
446
447void OPopupMenu::resetKeyboardVars(bool noMatches /* = false */)
448{
449 // Clean up keyboard variables
450 if (d->lastHitIndex != -1) {
451 changeItem(idAt(d->lastHitIndex), d->originalText);
452 d->lastHitIndex = -1;
453 }
454
455 if (!noMatches) {
456 d->keySeq = QString::null;
457 }
458
459 d->noMatches = noMatches;
460}
461
462void OPopupMenu::setKeyboardShortcutsEnabled(bool enable)
463{
464 d->shortcuts = enable;
465}
466
467void OPopupMenu::setKeyboardShortcutsExecute(bool enable)
468{
469 d->autoExec = enable;
470}
471/**
472 * End keyboard navigation.
473 */
474
475/**
476 * RMB menus on menus
477 */
478QPopupMenu* OPopupMenu::contextMenu()
479{
480 if (!d->m_ctxMenu)
481 {
482 d->m_ctxMenu = new QPopupMenu(this);
483 installEventFilter(this);
484 connect(d->m_ctxMenu, SIGNAL(aboutToHide()), this, SLOT(ctxMenuHiding()));
485 }
486
487 return d->m_ctxMenu;
488}
489
490void OPopupMenu::cancelContextMenuShow()
491{
492 OPopupMenuPrivate::s_continueCtxMenuShow = false;
493}
494
495int OPopupMenu::contextMenuFocusItem()
496{
497 return OPopupMenuPrivate::s_highlightedItem;
498}
499
500OPopupMenu* OPopupMenu::contextMenuFocus()
501{
502 return OPopupMenuPrivate::s_contextedMenu;
503}
504
505void OPopupMenu::itemHighlighted(int /* whichItem */)
506{
507 if (!d->m_ctxMenu || !d->m_ctxMenu->isVisible())
508 {
509 return;
510 }
511
512 d->m_ctxMenu->hide();
513 showCtxMenu(mapFromGlobal(QCursor::pos()));
514}
515
516void OPopupMenu::showCtxMenu(QPoint pos)
517{
518 OPopupMenuPrivate::s_highlightedItem = idAt(pos);
519
520 if (OPopupMenuPrivate::s_highlightedItem == -1)
521 {
522 OPopupMenuPrivate::s_contextedMenu = 0;
523 return;
524 }
525
526 emit aboutToShowContextMenu(this, OPopupMenuPrivate::s_highlightedItem, d->m_ctxMenu);
527
528 if (!OPopupMenuPrivate::s_continueCtxMenuShow)
529 {
530 OPopupMenuPrivate::s_continueCtxMenuShow = true;
531 return;
532 }
533
534 OPopupMenuPrivate::s_contextedMenu = this;
535 d->m_ctxMenu->popup(this->mapToGlobal(pos));
536 connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
537}
538
539void OPopupMenu::ctxMenuHiding()
540{
541 disconnect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
542 OPopupMenuPrivate::s_continueCtxMenuShow = true;
543}
544
545bool OPopupMenu::eventFilter(QObject* obj, QEvent* event)
546{
547 if (d->m_ctxMenu && obj == this)
548 {
549 if (event->type() == QEvent::MouseButtonRelease)
550 {
551 if (d->m_ctxMenu->isVisible())
552 {
553 return true;
554 }
555 }
556 #if QT_VERSION > 290
557 else if (event->type() == QEvent::ContextMenu)
558 #else
559 else if ( (event->type() == QEvent::MouseButtonPress) &&
560 ( (QMouseEvent*) event )->button() == QMouseEvent::RightButton )
561 #endif
562 {
563 showCtxMenu(mapFromGlobal(QCursor::pos()));
564 return true;
565 }
566 }
567
568 return QWidget::eventFilter(obj, event);
569}
570
571void OPopupMenu::hideEvent(QHideEvent*)
572{
573 if (d->m_ctxMenu)
574 {
575 d->m_ctxMenu->hide();
576 }
577}
578/**
579 * end of RMB menus on menus support
580 */
581
582// Obsolete
583OPopupMenu::OPopupMenu(const QString& title, QWidget *parent, const char *name)
584 : QPopupMenu(parent, name)
585{
586 d = new OPopupMenuPrivate;
587 setTitle(title);
588}
589
590// Obsolete
591void OPopupMenu::setTitle(const QString &title)
592{
593 OPopupTitle *titleItem = new OPopupTitle();
594 titleItem->setTitle(title);
595 insertItem(titleItem);
596 d->m_lastTitle = title;
597}
598
599void OPopupTitle::virtual_hook( int, void* )
600{ /*BASE::virtual_hook( id, data );*/ }
601
602void OPopupMenu::virtual_hook( int, void* )
603{ /*BASE::virtual_hook( id, data );*/ }
604