/* This file is part of the KDE libraries Copyright (c) 2000,2001 Dawit Alemayehu Copyright (c) 2000,2001 Carsten Pfeiffer Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kcombobox.h" #include // getenv class KComboBox::KComboBoxPrivate { public: KComboBoxPrivate() : klineEdit(0L) { } ~KComboBoxPrivate() { } KLineEdit *klineEdit; }; KComboBox::KComboBox( QWidget *parent, const char *name ) : QComboBox( parent, name ), d(new KComboBoxPrivate) { init(); } KComboBox::KComboBox( bool rw, QWidget *parent, const char *name ) : QComboBox( rw, parent, name ), d(new KComboBoxPrivate) { init(); if ( rw ) { KLineEdit *edit = new KLineEdit( this, "combo lineedit" ); setLineEdit( edit ); } } KComboBox::~KComboBox() { delete d; } void KComboBox::init() { // Permanently set some parameters in the parent object. QComboBox::setAutoCompletion( false ); // Enable context menu by default if widget // is editable. setContextMenuEnabled( true ); } bool KComboBox::contains( const QString& _text ) const { if ( _text.isEmpty() ) return false; const int itemCount = count(); for (int i = 0; i < itemCount; ++i ) { if ( text(i) == _text ) return true; } return false; } void KComboBox::setAutoCompletion( bool autocomplete ) { if ( d->klineEdit ) { if ( autocomplete ) { d->klineEdit->setCompletionMode( KGlobalSettings::CompletionAuto ); setCompletionMode( KGlobalSettings::CompletionAuto ); } else { d->klineEdit->setCompletionMode( KGlobalSettings::completionMode() ); setCompletionMode( KGlobalSettings::completionMode() ); } } } void KComboBox::setContextMenuEnabled( bool showMenu ) { if( d->klineEdit ) d->klineEdit->setContextMenuEnabled( showMenu ); } void KComboBox::setURLDropsEnabled( bool enable ) { if ( d->klineEdit ) d->klineEdit->setURLDropsEnabled( enable ); } bool KComboBox::isURLDropsEnabled() const { return d->klineEdit && d->klineEdit->isURLDropsEnabled(); } void KComboBox::setCompletedText( const QString& text, bool marked ) { if ( d->klineEdit ) d->klineEdit->setCompletedText( text, marked ); } void KComboBox::setCompletedText( const QString& text ) { if ( d->klineEdit ) d->klineEdit->setCompletedText( text ); } void KComboBox::makeCompletion( const QString& text ) { if( d->klineEdit ) d->klineEdit->makeCompletion( text ); else // read-only combo completion { if( text.isNull() || !listBox() ) return; const int index = listBox()->index( listBox()->findItem( text ) ); if( index >= 0 ) setCurrentItem( index ); } } void KComboBox::rotateText( KCompletionBase::KeyBindingType type ) { if ( d->klineEdit ) d->klineEdit->rotateText( type ); } // not needed anymore bool KComboBox::eventFilter( QObject* o, QEvent* ev ) { return QComboBox::eventFilter( o, ev ); } void KComboBox::setTrapReturnKey( bool grab ) { if ( d->klineEdit ) d->klineEdit->setTrapReturnKey( grab ); else qWarning("KComboBox::setTrapReturnKey not supported with a non-KLineEdit."); } bool KComboBox::trapReturnKey() const { return d->klineEdit && d->klineEdit->trapReturnKey(); } void KComboBox::setEditURL( const KURL& url ) { QComboBox::setEditText( url.prettyURL() ); } void KComboBox::insertURL( const KURL& url, int index ) { QComboBox::insertItem( url.prettyURL(), index ); } void KComboBox::insertURL( const QPixmap& pixmap, const KURL& url, int index ) { QComboBox::insertItem( pixmap, url.prettyURL(), index ); } void KComboBox::changeURL( const KURL& url, int index ) { QComboBox::changeItem( url.prettyURL(), index ); } void KComboBox::changeURL( const QPixmap& pixmap, const KURL& url, int index ) { QComboBox::changeItem( pixmap, url.prettyURL(), index ); } void KComboBox::setCompletedItems( const QStringList& items ) { if ( d->klineEdit ) d->klineEdit->setCompletedItems( items ); } KCompletionBox * KComboBox::completionBox( bool create ) { if ( d->klineEdit ) return d->klineEdit->completionBox( create ); return 0; } // QWidget::create() turns off mouse-Tracking which would break auto-hiding void KComboBox::create( WId id, bool initializeWindow, bool destroyOldWindow ) { QComboBox::create( id, initializeWindow, destroyOldWindow ); KCursor::setAutoHideCursor( lineEdit(), true, true ); } void KComboBox::wheelEvent( QWheelEvent *ev ) { // Not necessary anymore QComboBox::wheelEvent( ev ); } void KComboBox::setLineEdit( QLineEdit *edit ) { if ( !editable() && edit && !qstrcmp( edit->className(), "QLineEdit" ) ) { // uic generates code that creates a read-only KComboBox and then // calls combo->setEditable( true ), which causes QComboBox to set up // a dumb QLineEdit instead of our nice KLineEdit. // As some KComboBox features rely on the KLineEdit, we reject // this order here. delete edit; edit = new KLineEdit( this, "combo edit" ); } QComboBox::setLineEdit( edit ); d->klineEdit = dynamic_cast( edit ); setDelegate( d->klineEdit ); // Connect the returnPressed signal for both Q[K]LineEdits' if (edit) connect( edit, SIGNAL( returnPressed() ), SIGNAL( returnPressed() )); if ( d->klineEdit ) { // someone calling KComboBox::setEditable( false ) destroys our // lineedit without us noticing. And KCompletionBase::delegate would // be a dangling pointer then, so prevent that. Note: only do this // when it is a KLineEdit! connect( edit, SIGNAL( destroyed() ), SLOT( lineEditDeleted() )); connect( d->klineEdit, SIGNAL( returnPressed( const QString& )), SIGNAL( returnPressed( const QString& ) )); connect( d->klineEdit, SIGNAL( completion( const QString& )), SIGNAL( completion( const QString& )) ); connect( d->klineEdit, SIGNAL( substringCompletion( const QString& )), SIGNAL( substringCompletion( const QString& )) ); connect( d->klineEdit, SIGNAL( textRotation( KCompletionBase::KeyBindingType )), SIGNAL( textRotation( KCompletionBase::KeyBindingType )) ); connect( d->klineEdit, SIGNAL( completionModeChanged( KGlobalSettings::Completion )), SIGNAL( completionModeChanged( KGlobalSettings::Completion))); connect( d->klineEdit, SIGNAL( aboutToShowContextMenu( QPopupMenu * )), SIGNAL( aboutToShowContextMenu( QPopupMenu * )) ); connect( d->klineEdit, SIGNAL( completionBoxActivated( const QString& )), SIGNAL( activated( const QString& )) ); } } void KComboBox::setCurrentItem( const QString& item, bool insert, int index ) { int sel = -1; const int itemCount = count(); for (int i = 0; i < itemCount; ++i) { if (text(i) == item) { sel = i; break; } } if (sel == -1 && insert) { insertItem(item, index); if (index >= 0) sel = index; else sel = count() - 1; } setCurrentItem(sel); } void KComboBox::lineEditDeleted() { // yes, we need those ugly casts due to the multiple inheritance // sender() is guaranteed to be a KLineEdit (see the connect() to the // destroyed() signal const KCompletionBase *base = static_cast( static_cast( sender() )); // is it our delegate, that is destroyed? if ( base == delegate() ) setDelegate( 0L ); } // ********************************************************************* // ********************************************************************* // we are always read-write KHistoryCombo::KHistoryCombo( QWidget *parent, const char *name ) : KComboBox( true, parent, name ), d(0) { init( true ); // using completion } // we are always read-write KHistoryCombo::KHistoryCombo( bool useCompletion, QWidget *parent, const char *name ) : KComboBox( true, parent, name ), d(0) { init( useCompletion ); } void KHistoryCombo::init( bool useCompletion ) { // Set a default history size to something reasonable, Qt sets it to INT_MAX by default setMaxCount( 50 ); if ( useCompletion ) completionObject()->setOrder( KCompletion::Weighted ); setInsertionPolicy( NoInsertion ); myIterateIndex = -1; myRotated = false; myPixProvider = 0L; // obey HISTCONTROL setting QCString histControl = getenv("HISTCONTROL"); if ( histControl == "ignoredups" || histControl == "ignoreboth" ) setDuplicatesEnabled( false ); connect( this, SIGNAL(aboutToShowContextMenu(QPopupMenu*)), SLOT(addContextMenuItems(QPopupMenu*)) ); connect( this, SIGNAL( activated(int) ), SLOT( slotReset() )); connect( this, SIGNAL( returnPressed(const QString&) ), SLOT(slotReset())); } KHistoryCombo::~KHistoryCombo() { delete myPixProvider; } void KHistoryCombo::setHistoryItems( QStringList items, bool setCompletionList ) { KComboBox::clear(); // limit to maxCount() const int itemCount = items.count(); const int toRemove = itemCount - maxCount(); if (toRemove >= itemCount) { items.clear(); } else { for (int i = 0; i < toRemove; ++i) items.pop_front(); } insertItems( items ); if ( setCompletionList && useCompletion() ) { // we don't have any weighting information here ;( KCompletion *comp = completionObject(); comp->setOrder( KCompletion::Insertion ); comp->setItems( items ); comp->setOrder( KCompletion::Weighted ); } clearEdit(); } QStringList KHistoryCombo::historyItems() const { QStringList list; const int itemCount = count(); for ( int i = 0; i < itemCount; ++i ) list.append( text( i ) ); return list; } void KHistoryCombo::clearHistory() { const QString temp = currentText(); KComboBox::clear(); if ( useCompletion() ) completionObject()->clear(); setEditText( temp ); } void KHistoryCombo::addContextMenuItems( QPopupMenu* menu ) { if ( menu ) { menu->insertSeparator(); int id = menu->insertItem( SmallIconSet("history_clear"), i18n("Clear &History"), this, SLOT( slotClear())); if (!count()) menu->setItemEnabled(id, false); } } void KHistoryCombo::addToHistory( const QString& item ) { if ( item.isEmpty() || (count() > 0 && item == text(0) )) { return; } bool wasCurrent = false; // remove all existing items before adding if ( !duplicatesEnabled() ) { int i = 0; int itemCount = count(); while ( i < itemCount ) { if ( text( i ) == item ) { if ( !wasCurrent ) wasCurrent = ( i == currentItem() ); removeItem( i ); --itemCount; } else { ++i; } } } // now add the item if ( myPixProvider ) insertItem( myPixProvider->pixmapFor(item, KIcon::SizeSmall), item, 0); else insertItem( item, 0 ); if ( wasCurrent ) setCurrentItem( 0 ); const bool useComp = useCompletion(); const int last = count() - 1; // last valid index const int mc = maxCount(); const int stopAt = QMAX(mc, 0); for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) { // remove the last item, as long as we are longer than maxCount() // remove the removed item from the completionObject if it isn't // anymore available at all in the combobox. const QString rmItem = text( rmIndex ); removeItem( rmIndex ); if ( useComp && !contains( rmItem ) ) completionObject()->removeItem( rmItem ); } if ( useComp ) completionObject()->addItem( item ); } bool KHistoryCombo::removeFromHistory( const QString& item ) { if ( item.isEmpty() ) return false; bool removed = false; const QString temp = currentText(); int i = 0; int itemCount = count(); while ( i < itemCount ) { if ( item == text( i ) ) { removed = true; removeItem( i ); --itemCount; } else { ++i; } } if ( removed && useCompletion() ) completionObject()->removeItem( item ); setEditText( temp ); return removed; } void KHistoryCombo::rotateUp() { // save the current text in the lineedit if ( myIterateIndex == -1 ) myText = currentText(); ++myIterateIndex; // skip duplicates/empty items const int last = count() - 1; // last valid index const QString currText = currentText(); while ( myIterateIndex < last && (currText == text( myIterateIndex ) || text( myIterateIndex ).isEmpty()) ) ++myIterateIndex; if ( myIterateIndex >= count() ) { myRotated = true; myIterateIndex = -1; // if the typed text is the same as the first item, skip the first if ( count() > 0 && myText == text(0) ) myIterateIndex = 0; setEditText( myText ); } else setEditText( text( myIterateIndex )); } void KHistoryCombo::rotateDown() { // save the current text in the lineedit if ( myIterateIndex == -1 ) myText = currentText(); --myIterateIndex; const QString currText = currentText(); // skip duplicates/empty items while ( myIterateIndex >= 0 && (currText == text( myIterateIndex ) || text( myIterateIndex ).isEmpty()) ) --myIterateIndex; if ( myIterateIndex < 0 ) { if ( myRotated && myIterateIndex == -2 ) { myRotated = false; myIterateIndex = count() - 1; setEditText( text(myIterateIndex) ); } else { // bottom of history if ( myIterateIndex == -2 ) { KNotifyClient::event( (int)winId(), KNotifyClient::notification, i18n("No further item in the history.")); } myIterateIndex = -1; if ( currentText() != myText ) setEditText( myText ); } } else setEditText( text( myIterateIndex )); } void KHistoryCombo::keyPressEvent( QKeyEvent *e ) { KKey event_key( e ); // going up in the history, rotating when reaching QListBox::count() if ( KStdAccel::rotateUp().contains(event_key) ) rotateUp(); // going down in the history, no rotation possible. Last item will be // the text that was in the lineedit before Up was called. else if ( KStdAccel::rotateDown().contains(event_key) ) rotateDown(); else KComboBox::keyPressEvent( e ); } void KHistoryCombo::wheelEvent( QWheelEvent *ev ) { // Pass to poppable listbox if it's up QListBox* const lb = listBox(); if ( lb && lb->isVisible() ) { QApplication::sendEvent( lb, ev ); return; } // Otherwise make it change the text without emitting activated if ( ev->delta() > 0 ) { rotateUp(); } else { rotateDown(); } ev->accept(); } void KHistoryCombo::slotReset() { myIterateIndex = -1; myRotated = false; } void KHistoryCombo::setPixmapProvider( KPixmapProvider *prov ) { if ( myPixProvider == prov ) return; delete myPixProvider; myPixProvider = prov; // re-insert all the items with/without pixmap // I would prefer to use changeItem(), but that doesn't honor the pixmap // when using an editable combobox (what we do) if ( count() > 0 ) { QStringList items( historyItems() ); clear(); insertItems( items ); } } void KHistoryCombo::insertItems( const QStringList& items ) { QStringList::ConstIterator it = items.constBegin(); const QStringList::ConstIterator itEnd = items.constEnd(); while ( it != itEnd ) { const QString item = *it; if ( !item.isEmpty() ) { // only insert non-empty items if ( myPixProvider ) insertItem( myPixProvider->pixmapFor(item, KIcon::SizeSmall), item ); else insertItem( item ); } ++it; } } void KHistoryCombo::slotClear() { clearHistory(); emit cleared(); } void KComboBox::virtual_hook( int id, void* data ) { KCompletionBase::virtual_hook( id, data ); } void KHistoryCombo::virtual_hook( int id, void* data ) { KComboBox::virtual_hook( id, data ); } #include "kcombobox.moc"