Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / konqueror / src / konqcombo.cpp
blob9286eee91e4b443fc0c29774f5befe77393711e3
1 /* This file is part of the KDE project
2 Copyright (C) 2001 Carsten Pfeiffer <pfeiffer@kde.org>
3 Copyright (C) 2007 Fredrik Höglund <fredrik@kde.org>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 // Own
22 #include "konqcombo.h"
24 // Qt
25 #include <QtGui/QPainter>
26 #include <QtGui/QStyle>
27 #include <QtGui/QPixmap>
28 #include <QtGui/QKeyEvent>
29 #include <QtGui/QItemDelegate>
30 #include <QtGui/QListWidgetItem>
31 #include <QtCore/QEvent>
33 // KDE
34 #include <kapplication.h>
35 #include <kconfig.h>
36 #include <kconfiggroup.h>
37 #include <kcompletionbox.h>
38 #include <kdebug.h>
39 #include <kiconloader.h>
40 #include <kicontheme.h>
41 #include <klineedit.h>
42 #include <konqpixmapprovider.h>
43 #include <kstandardshortcut.h>
44 #include <konqmainwindow.h>
45 #include <kstringhandler.h>
47 // Local
48 #include "konqview.h"
49 #include "KonquerorAdaptor.h"
50 #include "konqueror_interface.h"
51 #include "konqhistorymanager.h"
53 KConfig * KonqCombo::s_config = 0L;
54 const int KonqCombo::temporary = 0;
56 static QString titleOfURL( const QString& urlStr )
58 KUrl url( urlStr );
59 KonqHistoryList historylist = KonqHistoryManager::kself()->entries();
60 KonqHistoryList::iterator historyentry = historylist.findEntry( url );
61 if ( historyentry == historylist.end() && !url.url().endsWith('/') ) {
62 url.adjustPath(KUrl::AddTrailingSlash);
63 historyentry = historylist.findEntry( url );
65 return ( historyentry != historylist.end() ? (*historyentry).title : QString() );
68 ///////////////////////////////////////////////////////////////////////////////
70 class KonqListWidgetItem : public QListWidgetItem
72 public:
73 enum ItemType { KonqItemType = 0x1845D5CC };
75 KonqListWidgetItem( QListWidget *parent = 0 );
76 KonqListWidgetItem( const QString &text, QListWidget *parent = 0 );
78 QVariant data( int role ) const;
80 bool reuse( const QString &newText );
82 private:
83 mutable bool lookupPending;
86 ///////////////////////////////////////////////////////////////////////////////
88 class KonqComboItemDelegate : public QItemDelegate
90 public:
91 KonqComboItemDelegate( QObject *parent ) : QItemDelegate( parent ) {}
92 QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const;
93 void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const;
96 ///////////////////////////////////////////////////////////////////////////////
98 class KonqComboLineEdit : public KLineEdit
100 public:
101 KonqComboLineEdit( QWidget *parent=0 );
102 void setCompletedItems( const QStringList& items, bool );
104 protected:
105 void mouseDoubleClickEvent( QMouseEvent *e );
108 class KonqComboCompletionBox : public KCompletionBox
110 public:
111 KonqComboCompletionBox( QWidget *parent );
112 void setItems( const QStringList& items );
113 void insertStringList( const QStringList & list, int index = -1 );
116 KonqCombo::KonqCombo( QWidget *parent )
117 : KHistoryComboBox( parent ),
118 m_returnPressed( false ),
119 m_permanent( false ),
120 m_pageSecurity( KonqMainWindow::NotCrypted )
122 setLayoutDirection(Qt::LeftToRight);
123 setInsertPolicy( NoInsert );
124 setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ));
125 setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLength );
127 Q_ASSERT( s_config );
129 KConfigGroup locationBarGroup( s_config, "Location Bar" );
130 setMaxCount( locationBarGroup.readEntry("Maximum of URLs in combo", 20 ));
132 // We should also connect the completionBox' highlighted signal to
133 // our setEditText() slot, because we're handling the signals ourselves.
134 // But we're lazy and let KCompletionBox do this and simply switch off
135 // handling of signals later.
136 setHandleSignals( true );
138 KonqComboLineEdit *edit = new KonqComboLineEdit( this );
139 edit->setHandleSignals( true );
140 edit->setCompletionBox( new KonqComboCompletionBox( edit ) );
141 setLineEdit( edit );
142 setItemDelegate( new KonqComboItemDelegate( this ) );
144 // we use KUrlCompletion, so let KLineEdit handle Key_Tab so that subdir<tab>/subdir<tab> works (#65877).
145 // KCompletionBox's handling of Key_Tab is just the same as Up/Down keys, not very useful.
146 completionBox()->setTabHandling(false);
147 completionBox()->setItemDelegate( new KonqComboItemDelegate( this ) );
149 // Make the lineedit consume the Qt::Key_Enter event...
150 setTrapReturnKey( true );
152 connect( KonqHistoryManager::kself(), SIGNAL(cleared()), SLOT(slotCleared()) );
153 connect( this, SIGNAL(cleared() ), SLOT(slotCleared()) );
154 connect( this, SIGNAL(highlighted( int )), SLOT(slotSetIcon( int )) );
155 connect( this, SIGNAL(activated( const QString& )),
156 SLOT(slotActivated( const QString& )) );
159 KonqCombo::~KonqCombo()
163 void KonqCombo::init( KCompletion *completion )
165 setCompletionObject( completion, false ); //KonqMainWindow handles signals
166 setAutoDeleteCompletionObject( false );
167 setCompletionMode( completion->completionMode() );
169 loadItems();
172 void KonqCombo::setURL( const QString& url )
174 //kDebug(1202) << url << "returnPressed=" << m_returnPressed;
175 setTemporary( url );
177 if ( m_returnPressed ) { // Really insert...
178 m_returnPressed = false;
179 QDBusMessage message = QDBusMessage::createSignal( KONQ_MAIN_PATH, "org.kde.Konqueror.Main", "addToCombo" );
180 message << url;
181 QDBusConnection::sessionBus().send( message );
183 // important security consideration: always display the beginning
184 // of the url rather than its end to prevent spoofing attempts.
185 lineEdit()->setCursorPosition( 0 );
188 void KonqCombo::setTemporary( const QString& text )
190 setTemporary( text, KonqPixmapProvider::self()->pixmapFor(text) );
193 void KonqCombo::setTemporary( const QString& url, const QPixmap& pix )
195 //kDebug(1202) << url << "temporary=" << temporary;
197 // Insert a temporary item when we don't have one yet
198 if ( count() == 0 )
199 insertItem( pix, url, temporary, titleOfURL( url ) );
200 else
202 if (url != temporaryItem())
203 applyPermanent();
205 updateItem( pix, url, temporary, titleOfURL( url ) );
208 setCurrentIndex( temporary );
211 void KonqCombo::removeDuplicates( int index )
213 //kDebug(1202) << "starting index= " << index;
215 QString url(temporaryItem());
216 if (url.endsWith('/'))
217 url.truncate(url.length()-1);
219 // Remove all dupes, if available...
220 for ( int i = index; i < count(); i++ )
222 QString item(itemText(i));
223 if (item.endsWith('/'))
224 item.truncate(item.length()-1);
226 if ( item == url )
227 removeItem( i );
231 // called via DBUS in all instances
232 void KonqCombo::insertPermanent( const QString& url )
234 //kDebug(1202) << "url=" << url;
235 saveState();
236 setTemporary( url );
237 m_permanent = true;
238 restoreState();
241 // called right before a new (different!) temporary item will be set. So we
242 // insert an item that was marked permanent properly at position 1.
243 void KonqCombo::applyPermanent()
245 if ( m_permanent && !temporaryItem().isEmpty() ) {
247 // Remove as many items as needed to honor maxCount()
248 int index = count();
249 while ( count() >= maxCount() )
250 removeItem( --index );
252 QString item = temporaryItem();
253 insertItem( KonqPixmapProvider::self()->pixmapFor( item ), item, 1, titleOfURL( item ) );
254 //kDebug(1202) << url;
256 // Remove all duplicates starting from index = 2
257 removeDuplicates( 2 );
258 m_permanent = false;
262 void KonqCombo::insertItem( const QString &text, int index, const QString& title )
264 KHistoryComboBox::insertItem( index, text, title );
267 void KonqCombo::insertItem( const QPixmap &pixmap, const QString& text, int index, const QString& title )
269 KHistoryComboBox::insertItem( index, pixmap, text, title );
272 void KonqCombo::updateItem( const QPixmap& pix, const QString& t, int index, const QString& title )
274 // No need to flicker
275 if (itemText( index ) == t &&
276 (!itemIcon(index).isNull() && itemIcon(index).pixmap(iconSize()).serialNumber() == pix.serialNumber()))
277 return;
279 // kDebug(1202) << "item=" << t << "index=" << index;
281 setItemText( index, t );
282 setItemIcon( index, pix );
283 setItemData( index, title );
285 update();
288 void KonqCombo::saveState()
290 m_cursorPos = cursorPosition();
291 m_currentText = currentText();
292 m_currentIndex = currentIndex();
295 void KonqCombo::restoreState()
297 setTemporary( m_currentText );
298 lineEdit()->setCursorPosition( m_cursorPos );
301 void KonqCombo::updatePixmaps()
303 saveState();
305 setUpdatesEnabled( false );
306 KonqPixmapProvider *prov = KonqPixmapProvider::self();
307 for ( int i = 1; i < count(); i++ ) {
308 setItemIcon( i, prov->pixmapFor( itemText( i ) ) );
310 setUpdatesEnabled( true );
311 repaint();
313 restoreState();
316 void KonqCombo::loadItems()
318 clear();
319 int i = 0;
321 KConfigGroup historyConfigGroup( s_config, "History" ); // delete the old 2.0.x completion
322 historyConfigGroup.writeEntry( "CompletionItems", "unused" );
324 KConfigGroup locationBarGroup( s_config, "Location Bar" );
325 const QStringList items = locationBarGroup.readPathEntry( "ComboContents", QStringList() );
326 QStringList::ConstIterator it = items.begin();
327 QString item;
328 while ( it != items.end() ) {
329 item = *it;
330 if ( !item.isEmpty() ) { // only insert non-empty items
331 insertItem( KonqPixmapProvider::self()->pixmapFor( item, KIconLoader::SizeSmall ),
332 item, i++, titleOfURL( item ) );
334 ++it;
337 if ( count() > 0 )
338 m_permanent = true; // we want the first loaded item to stay
341 void KonqCombo::slotSetIcon( int index )
343 if( itemIcon( index ).isNull())
344 // on-demand icon loading
345 setItemIcon( index, KonqPixmapProvider::self()->pixmapFor( itemText( index ),
346 KIconLoader::SizeSmall ) );
347 update();
350 void KonqCombo::getStyleOption(QStyleOptionComboBox* comboOpt)
352 //We only use this for querying metrics,so it's rough..
353 comboOpt->init(this);
354 comboOpt->editable = isEditable();
355 comboOpt->frame = hasFrame();
356 comboOpt->iconSize = iconSize();
357 comboOpt->currentIcon = itemIcon(currentIndex());
358 comboOpt->currentText = currentText();
361 void KonqCombo::popup()
363 for( int i = 0; i < count(); ++i )
365 if( itemIcon( i ).isNull() )
367 // on-demand icon loading
368 setItemIcon( i, KonqPixmapProvider::self()->pixmapFor( itemText( i ),
369 KIconLoader::SizeSmall ) );
372 KHistoryComboBox::showPopup();
375 void KonqCombo::saveItems()
377 QStringList items;
378 int i = m_permanent ? 0 : 1;
380 for ( ; i < count(); i++ )
381 items.append( itemText( i ) );
383 KConfigGroup locationBarGroup( s_config, "Location Bar" );
384 locationBarGroup.writePathEntry( "ComboContents", items );
385 KonqPixmapProvider::self()->save( locationBarGroup, "ComboIconCache", items );
387 s_config->sync();
390 void KonqCombo::clearTemporary( bool makeCurrent )
392 applyPermanent();
393 setItemText( temporary, QString() ); // ### default pixmap?
394 if ( makeCurrent )
395 setCurrentIndex( temporary );
398 bool KonqCombo::eventFilter( QObject *o, QEvent *ev )
400 // Handle Ctrl+Del/Backspace etc better than the Qt widget, which always
401 // jumps to the next whitespace.
402 QLineEdit *edit = lineEdit();
403 if ( o == edit ) {
404 const int type = ev->type();
405 if (type == QEvent::KeyPress) {
406 QKeyEvent *e = static_cast<QKeyEvent *>( ev );
408 KShortcut key( e->key() | e->modifiers() );
410 if ( key == KStandardShortcut::deleteWordBack() ||
411 key == KStandardShortcut::deleteWordForward() ||
412 ((e->modifiers() & Qt::ControlModifier) &&
413 (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right) ) ) {
414 selectWord(e);
415 e->accept();
416 return true;
420 else if ( type == QEvent::MouseButtonDblClick ) {
421 edit->selectAll();
422 return true;
425 return KComboBox::eventFilter( o, ev );
428 void KonqCombo::keyPressEvent( QKeyEvent *e )
430 KHistoryComboBox::keyPressEvent( e );
431 // we have to set it as temporary, otherwise we wouldn't get our nice
432 // pixmap. Yes, QComboBox still sucks.
433 KShortcut key( e->key() | e->modifiers() );
434 if ( key == KStandardShortcut::rotateUp() ||
435 key == KStandardShortcut::rotateDown() )
436 setTemporary( currentText() );
440 Handle Ctrl+Cursor etc better than the Qt widget, which always
441 jumps to the next whitespace. This code additionally jumps to
442 the next [/#?:], which makes more sense for URLs. The list of
443 chars that will stop the cursor are '/', '.', '?', '#', ':'.
445 void KonqCombo::selectWord(QKeyEvent *e)
447 QLineEdit* edit = lineEdit();
448 QString text = edit->text();
449 int pos = edit->cursorPosition();
450 int pos_old = pos;
451 int count = 0;
453 // TODO: make these a parameter when in kdelibs/kdeui...
454 QList<QChar> chars;
455 chars << QChar('/') << QChar('.') << QChar('?') << QChar('#') << QChar(':');
456 bool allow_space_break = true;
458 if( e->key() == Qt::Key_Left || e->key() == Qt::Key_Backspace ) {
459 do {
460 pos--;
461 count++;
462 if( allow_space_break && text[pos].isSpace() && count > 1 )
463 break;
464 } while( pos >= 0 && (chars.indexOf(text[pos]) == -1 || count <= 1) );
466 if( e->modifiers() & Qt::ShiftModifier ) {
467 edit->cursorForward(true, 1-count);
469 else if( e->key() == Qt::Key_Backspace ) {
470 edit->cursorForward(false, 1-count);
471 QString text = edit->text();
472 int pos_to_right = edit->text().length() - pos_old;
473 QString cut = text.left(edit->cursorPosition()) + text.right(pos_to_right);
474 edit->setText(cut);
475 edit->setCursorPosition(pos_old-count+1);
477 else {
478 edit->cursorForward(false, 1-count);
481 else if( e->key() == Qt::Key_Right || e->key() == Qt::Key_Delete ){
482 do {
483 pos++;
484 count++;
485 if( allow_space_break && text[pos].isSpace() )
486 break;
487 } while( pos < (int) text.length() && chars.indexOf(text[pos]) == -1 );
489 if( e->modifiers() & Qt::ShiftModifier ) {
490 edit->cursorForward(true, count+1);
492 else if( e->key() == Qt::Key_Delete ) {
493 edit->cursorForward(false, -count-1);
494 QString text = edit->text();
495 int pos_to_right = text.length() - pos - 1;
496 QString cut = text.left(pos_old) +
497 (pos_to_right > 0 ? text.right(pos_to_right) : QString() );
498 edit->setText(cut);
499 edit->setCursorPosition(pos_old);
501 else {
502 edit->cursorForward(false, count+1);
507 void KonqCombo::slotCleared()
509 QDBusMessage message = QDBusMessage::createSignal( KONQ_MAIN_PATH, "org.kde.Konqueror.Main", "comboCleared" );
510 QDBusConnection::sessionBus().send( message );
513 void KonqCombo::removeURL( const QString& url )
515 setUpdatesEnabled( false );
516 lineEdit()->setUpdatesEnabled( false );
518 removeFromHistory( url );
519 applyPermanent();
520 setTemporary( currentText() );
522 setUpdatesEnabled( true );
523 lineEdit()->setUpdatesEnabled( true );
524 update();
527 void KonqCombo::mousePressEvent( QMouseEvent *e )
529 m_dragStart = QPoint(); // null QPoint
531 if ( e->button() == Qt::LeftButton && !itemIcon( currentIndex()).isNull() ) {
532 // check if the pixmap was clicked
533 int x = e->pos().x();
534 QStyleOptionComboBox comboOpt;
535 getStyleOption(&comboOpt);
536 int x0 = QStyle::visualRect( layoutDirection(), rect(),
537 style()->subControlRect( QStyle::CC_ComboBox, &comboOpt, QStyle::SC_ComboBoxEditField,
538 this ) ).x();
540 if ( x > x0 + 2 && x < lineEdit()->x() ) {
541 m_dragStart = e->pos();
542 return; // don't call KComboBox::mousePressEvent!
546 QStyleOptionComboBox optCombo;
547 optCombo.initFrom(this);
548 if ( e->button() == Qt::LeftButton && m_pageSecurity != KonqMainWindow::NotCrypted &&
549 style()->subElementRect( QStyle::SE_ComboBoxFocusRect, &optCombo, this ).contains( e->pos() ) )
550 emit showPageSecurity();
552 KComboBox::mousePressEvent( e );
555 void KonqCombo::mouseMoveEvent( QMouseEvent *e )
557 KComboBox::mouseMoveEvent( e );
558 if ( m_dragStart.isNull() || currentText().isEmpty() )
559 return;
561 if ( e->buttons() & Qt::LeftButton &&
562 (e->pos() - m_dragStart).manhattanLength() >
563 KGlobalSettings::dndEventDelay() )
565 KUrl url( currentText() );
566 if ( url.isValid() )
568 QDrag* drag = new QDrag(this);
569 QMimeData* mime = new QMimeData();
570 url.populateMimeData(mime);
571 drag->setMimeData(mime);
572 QPixmap pix = KonqPixmapProvider::self()->pixmapFor( currentText(),
573 KIconLoader::SizeMedium );
574 if ( !pix.isNull() )
575 drag->setPixmap( pix );
576 drag->start();
581 void KonqCombo::slotActivated( const QString& text )
583 applyPermanent();
584 m_returnPressed = true;
585 emit activated( text, qApp->keyboardModifiers() );
588 void KonqCombo::setConfig( KConfig *kc )
590 s_config = kc;
593 void KonqCombo::paintEvent( QPaintEvent *pe )
595 QComboBox::paintEvent( pe );
597 QLineEdit *edit = lineEdit();
599 QStyleOptionComboBox comboOpt;
600 getStyleOption(&comboOpt);
601 QRect re = style()->subControlRect( QStyle::CC_ComboBox, &comboOpt,
602 QStyle::SC_ComboBoxEditField, this );
603 re = QStyle::visualRect(layoutDirection(), rect(), re);
605 if ( m_pageSecurity!=KonqMainWindow::NotCrypted ) {
606 QColor color(245, 246, 190);
607 // From George: disabled for now. We're going to match IE behavior in
608 // 4.0 which means:
609 // 1) No color, but padlock for SSL verified
610 // 2) No padlock for non-verified SSL
611 // 3) Green fill, padlock, and site info for high-assurance verified
612 // 4) If available, red fill for known phishing site
613 bool useColor = false; //hasSufficientContrast(color,edit->paletteForegroundColor());
615 QPainter p( this );
616 p.setClipRect( re );
618 QPixmap pix = KonqPixmapProvider::self()->pixmapFor( currentText() );
619 if ( useColor ) {
620 p.fillRect( re.x(), re.y(), pix.width() + 4, re.height(), QBrush( color ));
621 p.drawPixmap( re.x() + 2, re.y() + ( re.height() - pix.height() ) / 2, pix );
624 QRect r = edit->geometry();
625 r.setRight( re.right() - pix.width() - 4 );
626 if ( r != edit->geometry() )
627 edit->setGeometry( r );
629 if ( useColor){
630 QPalette palette;
631 palette.setColor(edit->backgroundRole(), color);
632 edit->setPalette(palette);
635 pix = SmallIcon( m_pageSecurity==KonqMainWindow::Encrypted ? "security-high" : "security-medium" );
636 if ( useColor ) {
637 p.fillRect( re.right() - pix.width() - 3 , re.y(), pix.width() + 4, re.height(),
638 QBrush( color ));
640 p.drawPixmap( re.right() - pix.width() -1 , re.y() + ( re.height() - pix.height() ) / 2, pix );
641 p.setClipping( false );
642 } else {
643 QRect r = edit->geometry();
644 r.setRight( re.right() );
645 if ( r != edit->geometry() )
646 edit->setGeometry( r );
647 QPalette palette = edit->palette();
648 palette.setColor(edit->backgroundRole(), palette.color(QPalette::Active, QPalette::Base));
649 edit->setPalette(palette);
653 void KonqCombo::setPageSecurity( int pageSecurity )
655 int ops = m_pageSecurity;
656 m_pageSecurity = pageSecurity;
657 if (ops != pageSecurity)
658 update();
661 bool KonqCombo::hasSufficientContrast(const QColor &c1, const QColor &c2)
663 // Taken from khtml/misc/helper.cc
664 #define HUE_DISTANCE 40
665 #define CONTRAST_DISTANCE 10
667 int h1, s1, v1, h2, s2, v2;
668 int hdist = -CONTRAST_DISTANCE;
669 c1.getHsv(&h1,&s1,&v1);
670 c2.getHsv(&h2,&s2,&v2);
671 if(h1!=-1 && h2!=-1) { // grey values have no hue
672 hdist = qAbs(h1-h2);
673 if (hdist > 180) hdist = 360-hdist;
674 if (hdist < HUE_DISTANCE) {
675 hdist -= HUE_DISTANCE;
676 // see if they are high key or low key colours
677 bool hk1 = h1>=45 && h1<=225;
678 bool hk2 = h2>=45 && h2<=225;
679 if (hk1 && hk2)
680 hdist = (5*hdist)/3;
681 else if (!hk1 && !hk2)
682 hdist = (7*hdist)/4;
684 hdist = qMin(hdist, HUE_DISTANCE*2);
686 return hdist + (qAbs(s1-s2)*128)/(160+qMin(s1,s2)) + qAbs(v1-v2) > CONTRAST_DISTANCE;
689 ///////////////////////////////////////////////////////////////////////////////
691 KonqListWidgetItem::KonqListWidgetItem( QListWidget *parent )
692 : QListWidgetItem( parent, KonqItemType ), lookupPending( true )
696 KonqListWidgetItem::KonqListWidgetItem( const QString &text, QListWidget *parent )
697 : QListWidgetItem( text, parent, KonqItemType ), lookupPending( true )
701 QVariant KonqListWidgetItem::data( int role ) const
703 if ( lookupPending && role != Qt::DisplayRole )
705 QString title = titleOfURL( text() );
706 QPixmap pixmap;
708 KonqPixmapProvider *provider = KonqPixmapProvider::self();
710 if ( !title.isEmpty() )
711 pixmap = provider->pixmapFor( text(), KIconLoader::SizeSmall );
712 else if ( text().indexOf( "://" ) == -1 ) {
713 title = titleOfURL( "http://"+text() );
714 if ( !title.isEmpty() )
715 pixmap = provider->pixmapFor( "http://"+text(), KIconLoader::SizeSmall );
716 else
717 pixmap = provider->pixmapFor( text(), KIconLoader::SizeSmall );
720 const_cast<KonqListWidgetItem*>( this )->setIcon( pixmap );
721 const_cast<KonqListWidgetItem*>( this )->setData( Qt::UserRole, title );
723 lookupPending = false;
726 return QListWidgetItem::data( role );
729 bool KonqListWidgetItem::reuse(const QString &newText)
731 if ( text() == newText )
732 return false;
734 lookupPending = true;
735 setText( newText );
736 return true;
739 ///////////////////////////////////////////////////////////////////////////////
741 QSize KonqComboItemDelegate::sizeHint( const QStyleOptionViewItem &option,
742 const QModelIndex &index ) const
744 Q_UNUSED(index);
745 int vMargin = QApplication::style()->pixelMetric( QStyle::PM_FocusFrameVMargin );
747 QSize size( 1, qMax( option.fontMetrics.lineSpacing(), option.decorationSize.height() ) );
748 size.rheight() += vMargin * 2;
750 return size.expandedTo( QApplication::globalStrut() );
753 void KonqComboItemDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option,
754 const QModelIndex &index ) const
756 QIcon icon = qvariant_cast<QIcon>( index.data( Qt::DecorationRole ) );
757 QString url = index.data( Qt::DisplayRole ).toString();
758 QString title = index.data( Qt::UserRole ).toString();
760 QIcon::Mode mode = option.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled;
761 const QSize size = icon.actualSize( option.decorationSize, mode );
762 QPixmap pixmap = icon.pixmap( size, mode );
764 QStyleOptionViewItemV3 opt( option );
766 painter->save();
768 // Draw the item background
770 // ### When Qt 4.4 is released we need to change this code to draw the background
771 // by calling QStyle::drawPrimitive() with PE_PanelItemViewRow.
772 if ( opt.state & QStyle::State_Selected ) {
773 painter->fillRect( option.rect, option.palette.brush( QPalette::Highlight ) );
774 painter->setPen( QPen( option.palette.brush( QPalette::HighlightedText ), 0 ) );
777 int hMargin = QApplication::style()->pixelMetric( QStyle::PM_FocusFrameHMargin );
778 int vMargin = QApplication::style()->pixelMetric( QStyle::PM_FocusFrameVMargin );
780 const QRect bounding = option.rect.adjusted( hMargin, vMargin, -hMargin, -vMargin );
781 const QSize textSize( bounding.width() - pixmap.width() - 2, bounding.height() );
782 const QRect pixmapRect = QStyle::alignedRect( option.direction, Qt::AlignLeft | Qt::AlignVCenter,
783 pixmap.size(), bounding );
784 const QRect textRect = QStyle::alignedRect( option.direction, Qt::AlignRight, textSize, bounding );
786 if ( !pixmap.isNull() )
787 painter->drawPixmap( pixmapRect.topLeft(), pixmap );
789 const QSize titleSize( ( bounding.width() / 3 ) - 1, textRect.height() );
790 const QSize urlSize( textRect.width() - titleSize.width() - 2, textRect.height() );
791 const QRect titleRect = QStyle::alignedRect( option.direction, Qt::AlignRight, titleSize, textRect );
792 const QRect urlRect = QStyle::alignedRect( option.direction, Qt::AlignLeft, urlSize, textRect );
794 if ( !url.isEmpty() ) {
795 QString squeezedText = option.fontMetrics.elidedText( url, Qt::ElideRight, urlRect.width() );
796 painter->drawText( urlRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText );
799 if ( !title.isEmpty() ) {
800 QString squeezedText = option.fontMetrics.elidedText( title, Qt::ElideRight, titleRect.width() );
801 QFont font = painter->font();
802 font.setItalic( true );
803 painter->setFont( font );
804 QColor color = painter->pen().color();
805 color.setAlphaF(.75);
806 painter->setPen(color);
807 painter->drawText( titleRect, Qt::AlignLeft | Qt::AlignVCenter, squeezedText );
810 painter->restore();
813 ///////////////////////////////////////////////////////////////////////////////
815 KonqComboLineEdit::KonqComboLineEdit( QWidget *parent )
816 :KLineEdit( parent )
818 setClearButtonShown( true );
821 void KonqComboLineEdit::mouseDoubleClickEvent( QMouseEvent *e )
823 if ( e->button() == Qt::LeftButton ) {
824 selectAll();
825 return;
827 KLineEdit::mouseDoubleClickEvent( e );
830 void KonqComboLineEdit::setCompletedItems( const QStringList& items, bool )
832 QString txt;
833 KonqComboCompletionBox *completionbox = static_cast<KonqComboCompletionBox*>( completionBox() );
835 if ( completionbox && completionbox->isVisible() )
836 // The popup is visible already - do the matching on the initial string,
837 // not on the currently selected one.
838 txt = completionbox->cancelledText();
839 else
840 txt = text();
842 if ( !items.isEmpty() && !(items.count() == 1 && txt == items.first()) ) {
843 if ( !completionBox( false ) ) {
844 setCompletionBox( new KonqComboCompletionBox( this ) );
845 completionbox = static_cast<KonqComboCompletionBox*>( completionBox() );
848 if ( completionbox->isVisible() ) {
850 QListWidgetItem* currentItem = completionbox->currentItem();
851 bool wasSelected = false;
852 QString currentSelection;
854 if ( currentItem != 0 ) {
855 wasSelected = currentItem->isSelected();
856 currentSelection = currentItem->text();
859 completionbox->setItems( items );
860 QList<QListWidgetItem*> matchedItems = completionbox->findItems
861 ( currentSelection, Qt::MatchExactly );
863 QListWidgetItem* item = matchedItems.isEmpty() ? 0 : matchedItems.first();
865 if( !item || !wasSelected )
867 wasSelected = false;
868 item = completionbox->item( 0 );
870 if ( item ) {
871 completionbox->blockSignals( true );
872 completionbox->setCurrentItem( item );
873 item->setSelected(wasSelected);
874 completionbox->blockSignals( false );
877 else { // completion box not visible yet -> show it
878 if ( !txt.isEmpty() )
879 completionbox->setCancelledText( txt );
880 completionbox->setItems( items );
881 completionbox->popup();
884 if ( autoSuggest() ) {
885 int index = items.first().indexOf( txt );
886 QString newText = items.first().mid( index );
887 setUserSelection( false );
888 setCompletedText( newText, true );
891 else
892 if ( completionbox && completionbox->isVisible() )
893 completionbox->hide();
896 ///////////////////////////////////////////////////////////////////////////////
898 KonqComboCompletionBox::KonqComboCompletionBox( QWidget *parent )
899 :KCompletionBox( parent )
901 setLayoutDirection(Qt::LeftToRight);
904 void KonqComboCompletionBox::setItems( const QStringList& items )
906 bool block = signalsBlocked();
907 blockSignals( true );
909 int rowIndex = 0;
911 if ( count() == 0 )
912 insertStringList( items );
913 else {
914 //Keep track of whether we need to change anything,
915 //so we can avoid a repaint for identical updates,
916 //to reduce flicker
917 bool dirty = false;
919 QStringList::ConstIterator it = items.constBegin();
920 const QStringList::ConstIterator itEnd = items.constEnd();
922 for ( ; it != itEnd; ++it) {
923 if ( rowIndex < count() ) {
924 const bool changed = ((KonqListWidgetItem*)item(rowIndex))->reuse( *it );
925 dirty = dirty || changed;
927 else {
928 dirty = true;
929 //Inserting an item is a way of making this dirty
930 addItem( new KonqListWidgetItem( *it ) );
932 rowIndex++;
935 //If there is an unused item, mark as dirty -> less items now
936 if ( rowIndex < count() )
937 dirty = true;
939 while ( rowIndex < count() ) {
940 delete item(rowIndex);
943 //TODO KDE 4 - Port this
944 //if ( dirty )
945 // triggerUpdate( false );
948 if ( isVisible() && size().height() != sizeHint().height() )
949 sizeAndPosition();
951 blockSignals( block );
953 // Trigger d->down_workaround = true within KCompletionBox
954 QStringList dummy;
955 KCompletionBox::insertItems( dummy, 1 );
958 void KonqComboCompletionBox::insertStringList( const QStringList & list, int index )
960 if ( index < 0 )
961 index = count();
963 foreach ( const QString &text, list )
964 insertItem( index++, new KonqListWidgetItem( text ) );
966 #include "konqcombo.moc"