KDEUI
khistorycombobox.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 00003 Copyright (c) 2000,2001 Dawit Alemayehu <adawit@kde.org> 00004 Copyright (c) 2000,2001 Carsten Pfeiffer <pfeiffer@kde.org> 00005 Copyright (c) 2000 Stefan Schimanski <1Stein@gmx.de> 00006 00007 This library is free software; you can redistribute it and/or 00008 modify it under the terms of the GNU Lesser General Public 00009 License (LGPL) as published by the Free Software Foundation; either 00010 version 2 of the License, or (at your option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, 00013 but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 Lesser General Public License for more details. 00016 00017 You should have received a copy of the GNU Lesser General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to 00019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00020 Boston, MA 02110-1301, USA. 00021 */ 00022 00023 #include "khistorycombobox.h" 00024 00025 #include <QtGui/QAbstractItemView> 00026 #include <QtGui/QApplication> 00027 #include <QtGui/QKeyEvent> 00028 #include <QtGui/QMenu> 00029 #include <QtGui/QWheelEvent> 00030 00031 #include <klocale.h> 00032 #include <knotification.h> 00033 #include <kpixmapprovider.h> 00034 #include <kstandardshortcut.h> 00035 #include <kicontheme.h> 00036 #include <kicon.h> 00037 00038 #include <kdebug.h> 00039 00040 class KHistoryComboBox::Private 00041 { 00042 public: 00043 Private(KHistoryComboBox *q): q(q), myPixProvider(0) {} 00044 00045 KHistoryComboBox *q; 00046 00050 int myIterateIndex; 00051 00055 QString myText; 00056 00061 bool myRotated; 00062 KPixmapProvider *myPixProvider; 00063 }; 00064 00065 // we are always read-write 00066 KHistoryComboBox::KHistoryComboBox( QWidget *parent ) 00067 : KComboBox( true, parent ), d(new Private(this)) 00068 { 00069 init( true ); // using completion 00070 } 00071 00072 // we are always read-write 00073 KHistoryComboBox::KHistoryComboBox( bool useCompletion, 00074 QWidget *parent ) 00075 : KComboBox( true, parent ), d(new Private(this)) 00076 { 00077 init( useCompletion ); 00078 } 00079 00080 void KHistoryComboBox::init( bool useCompletion ) 00081 { 00082 // Set a default history size to something reasonable, Qt sets it to INT_MAX by default 00083 setMaxCount( 50 ); 00084 00085 if ( useCompletion ) 00086 completionObject()->setOrder( KCompletion::Weighted ); 00087 00088 setInsertPolicy( NoInsert ); 00089 d->myIterateIndex = -1; 00090 d->myRotated = false; 00091 d->myPixProvider = 0L; 00092 00093 // obey HISTCONTROL setting 00094 QByteArray histControl = qgetenv("HISTCONTROL"); 00095 if ( histControl == "ignoredups" || histControl == "ignoreboth" ) 00096 setDuplicatesEnabled( false ); 00097 00098 connect(this, SIGNAL(aboutToShowContextMenu(QMenu*)), SLOT(addContextMenuItems(QMenu*))); 00099 connect(this, SIGNAL(activated(int)), SLOT(slotReset())); 00100 connect(this, SIGNAL(returnPressed(QString)), SLOT(slotReset())); 00101 // We want slotSimulateActivated to be called _after_ QComboBoxPrivate::_q_returnPressed 00102 // otherwise there's a risk of emitting activated twice (slotSimulateActivated will find 00103 // the item, after some app's slotActivated inserted the item into the combo). 00104 connect(this, SIGNAL(returnPressed(QString)), SLOT(slotSimulateActivated(QString)), Qt::QueuedConnection); 00105 } 00106 00107 KHistoryComboBox::~KHistoryComboBox() 00108 { 00109 delete d->myPixProvider; 00110 delete d; 00111 } 00112 00113 void KHistoryComboBox::setHistoryItems( const QStringList &items ) 00114 { 00115 setHistoryItems(items, false); 00116 } 00117 00118 void KHistoryComboBox::setHistoryItems( const QStringList &items, 00119 bool setCompletionList ) 00120 { 00121 QStringList insertingItems = items; 00122 KComboBox::clear(); 00123 00124 // limit to maxCount() 00125 const int itemCount = insertingItems.count(); 00126 const int toRemove = itemCount - maxCount(); 00127 00128 if (toRemove >= itemCount) { 00129 insertingItems.clear(); 00130 } else { 00131 for (int i = 0; i < toRemove; ++i) 00132 insertingItems.pop_front(); 00133 } 00134 00135 insertItems( insertingItems ); 00136 00137 if ( setCompletionList && useCompletion() ) { 00138 // we don't have any weighting information here ;( 00139 KCompletion *comp = completionObject(); 00140 comp->setOrder( KCompletion::Insertion ); 00141 comp->setItems( insertingItems ); 00142 comp->setOrder( KCompletion::Weighted ); 00143 } 00144 00145 clearEditText(); 00146 } 00147 00148 QStringList KHistoryComboBox::historyItems() const 00149 { 00150 QStringList list; 00151 const int itemCount = count(); 00152 for ( int i = 0; i < itemCount; ++i ) 00153 list.append( itemText( i ) ); 00154 00155 return list; 00156 } 00157 00158 bool KHistoryComboBox::useCompletion() const 00159 { 00160 return compObj(); 00161 } 00162 00163 void KHistoryComboBox::clearHistory() 00164 { 00165 const QString temp = currentText(); 00166 KComboBox::clear(); 00167 if ( useCompletion() ) 00168 completionObject()->clear(); 00169 setEditText( temp ); 00170 } 00171 00172 void KHistoryComboBox::addContextMenuItems( QMenu* menu ) 00173 { 00174 if ( menu ) 00175 { 00176 menu->addSeparator(); 00177 QAction* clearHistory = menu->addAction( KIcon("edit-clear-history"), i18n("Clear &History"), this, SLOT( slotClear())); 00178 if (!count()) 00179 clearHistory->setEnabled(false); 00180 } 00181 } 00182 00183 void KHistoryComboBox::addToHistory( const QString& item ) 00184 { 00185 if ( item.isEmpty() || (count() > 0 && item == itemText(0) )) { 00186 return; 00187 } 00188 00189 bool wasCurrent = false; 00190 // remove all existing items before adding 00191 if ( !duplicatesEnabled() ) { 00192 int i = 0; 00193 int itemCount = count(); 00194 while ( i < itemCount ) { 00195 if ( itemText( i ) == item ) { 00196 if ( !wasCurrent ) 00197 wasCurrent = ( i == currentIndex() ); 00198 removeItem( i ); 00199 --itemCount; 00200 } else { 00201 ++i; 00202 } 00203 } 00204 } 00205 00206 // now add the item 00207 if ( d->myPixProvider ) 00208 insertItem( 0, d->myPixProvider->pixmapFor(item, KIconLoader::SizeSmall), item); 00209 else 00210 insertItem( 0, item ); 00211 00212 if ( wasCurrent ) 00213 setCurrentIndex( 0 ); 00214 00215 const bool useComp = useCompletion(); 00216 00217 const int last = count() - 1; // last valid index 00218 const int mc = maxCount(); 00219 const int stopAt = qMax(mc, 0); 00220 00221 for (int rmIndex = last; rmIndex >= stopAt; --rmIndex) { 00222 // remove the last item, as long as we are longer than maxCount() 00223 // remove the removed item from the completionObject if it isn't 00224 // anymore available at all in the combobox. 00225 const QString rmItem = itemText( rmIndex ); 00226 removeItem( rmIndex ); 00227 if ( useComp && !contains( rmItem ) ) 00228 completionObject()->removeItem( rmItem ); 00229 } 00230 00231 if ( useComp ) 00232 completionObject()->addItem( item ); 00233 } 00234 00235 bool KHistoryComboBox::removeFromHistory( const QString& item ) 00236 { 00237 if ( item.isEmpty() ) 00238 return false; 00239 00240 bool removed = false; 00241 const QString temp = currentText(); 00242 int i = 0; 00243 int itemCount = count(); 00244 while ( i < itemCount ) { 00245 if ( item == itemText( i ) ) { 00246 removed = true; 00247 removeItem( i ); 00248 --itemCount; 00249 } else { 00250 ++i; 00251 } 00252 } 00253 00254 if ( removed && useCompletion() ) 00255 completionObject()->removeItem( item ); 00256 00257 setEditText( temp ); 00258 return removed; 00259 } 00260 00261 // going up in the history, rotating when reaching QListBox::count() 00262 // 00263 // Note: this differs from QComboBox because "up" means ++index here, 00264 // to simulate the way shell history works (up goes to the most 00265 // recent item). In QComboBox "down" means ++index, to match the popup... 00266 // 00267 void KHistoryComboBox::rotateUp() 00268 { 00269 // save the current text in the lineedit 00270 // (This is also where this differs from standard up/down in QComboBox, 00271 // where a single keypress can make you lose your typed text) 00272 if ( d->myIterateIndex == -1 ) 00273 d->myText = currentText(); 00274 00275 ++d->myIterateIndex; 00276 00277 // skip duplicates/empty items 00278 const int last = count() - 1; // last valid index 00279 const QString currText = currentText(); 00280 00281 while ( d->myIterateIndex < last && 00282 (currText == itemText( d->myIterateIndex ) || 00283 itemText( d->myIterateIndex ).isEmpty()) ) 00284 ++d->myIterateIndex; 00285 00286 if ( d->myIterateIndex >= count() ) { 00287 d->myRotated = true; 00288 d->myIterateIndex = -1; 00289 00290 // if the typed text is the same as the first item, skip the first 00291 if ( count() > 0 && d->myText == itemText(0) ) 00292 d->myIterateIndex = 0; 00293 00294 setEditText( d->myText ); 00295 } else { 00296 setCurrentIndex(d->myIterateIndex); 00297 } 00298 } 00299 00300 // going down in the history, no rotation possible. Last item will be 00301 // the text that was in the lineedit before Up was called. 00302 void KHistoryComboBox::rotateDown() 00303 { 00304 // save the current text in the lineedit 00305 if ( d->myIterateIndex == -1 ) 00306 d->myText = currentText(); 00307 00308 --d->myIterateIndex; 00309 00310 const QString currText = currentText(); 00311 // skip duplicates/empty items 00312 while ( d->myIterateIndex >= 0 && 00313 (currText == itemText( d->myIterateIndex ) || 00314 itemText( d->myIterateIndex ).isEmpty()) ) 00315 --d->myIterateIndex; 00316 00317 00318 if ( d->myIterateIndex < 0 ) { 00319 if ( d->myRotated && d->myIterateIndex == -2 ) { 00320 d->myRotated = false; 00321 d->myIterateIndex = count() - 1; 00322 setEditText( itemText(d->myIterateIndex) ); 00323 } 00324 else { // bottom of history 00325 if ( d->myIterateIndex == -2 ) { 00326 KNotification::event( "Textcompletion: No Match" , 00327 i18n("No further items in the history."), 00328 QPixmap() , this, KNotification::DefaultEvent); 00329 } 00330 00331 d->myIterateIndex = -1; 00332 if ( currentText() != d->myText ) 00333 setEditText( d->myText ); 00334 } 00335 } else { 00336 setCurrentIndex(d->myIterateIndex); 00337 } 00338 } 00339 00340 void KHistoryComboBox::keyPressEvent( QKeyEvent *e ) 00341 { 00342 int event_key = e->key() | e->modifiers(); 00343 00344 if ( KStandardShortcut::rotateUp().contains(event_key) ) 00345 rotateUp(); 00346 else if ( KStandardShortcut::rotateDown().contains(event_key) ) 00347 rotateDown(); 00348 else 00349 KComboBox::keyPressEvent( e ); 00350 } 00351 00352 void KHistoryComboBox::wheelEvent( QWheelEvent *ev ) 00353 { 00354 // Pass to poppable listbox if it's up 00355 QAbstractItemView* const iv = view(); 00356 if ( iv && iv->isVisible() ) 00357 { 00358 QApplication::sendEvent( iv, ev ); 00359 return; 00360 } 00361 // Otherwise make it change the text without emitting activated 00362 if ( ev->delta() > 0 ) { 00363 rotateUp(); 00364 } else { 00365 rotateDown(); 00366 } 00367 ev->accept(); 00368 } 00369 00370 void KHistoryComboBox::slotReset() 00371 { 00372 d->myIterateIndex = -1; 00373 d->myRotated = false; 00374 } 00375 00376 00377 void KHistoryComboBox::setPixmapProvider( KPixmapProvider *prov ) 00378 { 00379 if ( d->myPixProvider == prov ) 00380 return; 00381 00382 delete d->myPixProvider; 00383 d->myPixProvider = prov; 00384 00385 // re-insert all the items with/without pixmap 00386 // I would prefer to use changeItem(), but that doesn't honor the pixmap 00387 // when using an editable combobox (what we do) 00388 if ( count() > 0 ) { 00389 QStringList items( historyItems() ); 00390 clear(); 00391 insertItems( items ); 00392 } 00393 } 00394 00395 void KHistoryComboBox::insertItems( const QStringList& items ) 00396 { 00397 QStringList::ConstIterator it = items.constBegin(); 00398 const QStringList::ConstIterator itEnd = items.constEnd(); 00399 00400 while ( it != itEnd ) { 00401 const QString item = *it; 00402 if ( !item.isEmpty() ) { // only insert non-empty items 00403 if ( d->myPixProvider ) 00404 addItem( d->myPixProvider->pixmapFor(item, KIconLoader::SizeSmall), 00405 item ); 00406 else 00407 addItem( item ); 00408 } 00409 ++it; 00410 } 00411 } 00412 00413 void KHistoryComboBox::slotClear() 00414 { 00415 clearHistory(); 00416 emit cleared(); 00417 } 00418 00419 void KHistoryComboBox::slotSimulateActivated( const QString& text ) 00420 { 00421 /* With the insertion policy NoInsert, which we use by default, 00422 Qt doesn't emit activated on typed text if the item is not already there, 00423 which is perhaps reasonable. Generate the signal ourselves if that's the case. 00424 */ 00425 if ((insertPolicy() == NoInsert && findText(text, Qt::MatchFixedString|Qt::MatchCaseSensitive) == -1)) { 00426 emit activated(text); 00427 } 00428 00429 /* 00430 Qt also doesn't emit it if the box is full, and policy is not 00431 InsertAtCurrent 00432 */ 00433 else if (insertPolicy() != InsertAtCurrent && count() >= maxCount()) { 00434 emit activated(text); 00435 } 00436 } 00437 00438 KPixmapProvider *KHistoryComboBox::pixmapProvider() const 00439 { 00440 return d->myPixProvider; 00441 } 00442 00443 void KHistoryComboBox::reset() 00444 { 00445 slotReset(); 00446 } 00447 00448 #include "khistorycombobox.moc"
KDE 4.6 API Reference