00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include <kapplication.h>
00022 #include <kdebug.h>
00023 #include <klocale.h>
00024 #include <knotifyclient.h>
00025 #include <kglobal.h>
00026
00027 #include <qptrvector.h>
00028
00029 #include "kcompletion.h"
00030 #include "kcompletion_private.h"
00031
00032
00033 class KCompletionPrivate
00034 {
00035 public:
00036
00037
00038 KCompletionMatchesWrapper matches;
00039 };
00040
00041 KCompletion::KCompletion()
00042 {
00043 d = new KCompletionPrivate;
00044
00045 myCompletionMode = KGlobalSettings::completionMode();
00046 myTreeRoot = new KCompTreeNode;
00047 myBeep = true;
00048 myIgnoreCase = false;
00049 myHasMultipleMatches = false;
00050 myRotationIndex = 0;
00051 setOrder( Insertion );
00052 }
00053
00054 KCompletion::~KCompletion()
00055 {
00056 delete d;
00057 delete myTreeRoot;
00058 }
00059
00060 void KCompletion::setOrder( CompOrder order )
00061 {
00062 myOrder = order;
00063 d->matches.setSorting( order == Weighted );
00064 }
00065
00066 void KCompletion::setIgnoreCase( bool ignoreCase )
00067 {
00068 myIgnoreCase = ignoreCase;
00069 }
00070
00071 void KCompletion::setItems( const QStringList& items )
00072 {
00073 clear();
00074 insertItems( items );
00075 }
00076
00077
00078 void KCompletion::insertItems( const QStringList& items )
00079 {
00080 bool weighted = (myOrder == Weighted);
00081 QStringList::ConstIterator it;
00082 if ( weighted ) {
00083 for ( it = items.begin(); it != items.end(); ++it )
00084 addWeightedItem( *it );
00085 }
00086 else {
00087 for ( it = items.begin(); it != items.end(); ++it )
00088 addItem( *it, 0 );
00089 }
00090 }
00091
00092 QStringList KCompletion::items() const
00093 {
00094 KCompletionMatchesWrapper list;
00095 bool addWeight = (myOrder == Weighted);
00096 extractStringsFromNode( myTreeRoot, QString::null, &list, addWeight );
00097
00098 return list.list();
00099 }
00100
00101 bool KCompletion::isEmpty() const
00102 {
00103 return (myTreeRoot->childrenCount() == 0);
00104 }
00105
00106 void KCompletion::addItem( const QString& item )
00107 {
00108 d->matches.clear();
00109 myRotationIndex = 0;
00110 myLastString = QString::null;
00111
00112 addItem( item, 0 );
00113 }
00114
00115 void KCompletion::addItem( const QString& item, uint weight )
00116 {
00117 if ( item.isEmpty() )
00118 return;
00119
00120 KCompTreeNode *node = myTreeRoot;
00121 uint len = item.length();
00122
00123 bool sorted = (myOrder == Sorted);
00124 bool weighted = ((myOrder == Weighted) && weight > 1);
00125
00126
00127
00128
00129 for ( uint i = 0; i < len; i++ ) {
00130 node = node->insert( item.at(i), sorted );
00131 if ( weighted )
00132 node->confirm( weight -1 );
00133 }
00134
00135
00136 node = node->insert( 0x0, true );
00137 if ( weighted )
00138 node->confirm( weight -1 );
00139
00140 }
00141
00142 void KCompletion::addWeightedItem( const QString& item )
00143 {
00144 if ( myOrder != Weighted ) {
00145 addItem( item, 0 );
00146 return;
00147 }
00148
00149 uint len = item.length();
00150 uint weight = 0;
00151
00152
00153 int index = item.findRev(':');
00154 if ( index > 0 ) {
00155 bool ok;
00156 weight = item.mid( index + 1 ).toUInt( &ok );
00157 if ( !ok )
00158 weight = 0;
00159
00160 len = index;
00161 }
00162
00163 addItem( item.left( len ), weight );
00164 return;
00165 }
00166
00167
00168 void KCompletion::removeItem( const QString& item )
00169 {
00170 d->matches.clear();
00171 myRotationIndex = 0;
00172 myLastString = QString::null;
00173
00174 myTreeRoot->remove( item );
00175 }
00176
00177
00178 void KCompletion::clear()
00179 {
00180 d->matches.clear();
00181 myRotationIndex = 0;
00182 myLastString = QString::null;
00183
00184 delete myTreeRoot;
00185 myTreeRoot = new KCompTreeNode;
00186 }
00187
00188
00189 QString KCompletion::makeCompletion( const QString& string )
00190 {
00191 if ( myCompletionMode == KGlobalSettings::CompletionNone )
00192 return QString::null;
00193
00194
00195
00196 d->matches.clear();
00197 myRotationIndex = 0;
00198 myHasMultipleMatches = false;
00199 myLastMatch = myCurrentMatch;
00200
00201
00202
00203 if ( myCompletionMode == KGlobalSettings::CompletionShell &&
00204 string == myLastString ) {
00205
00206
00207
00208
00209 findAllCompletions( string, &d->matches, myHasMultipleMatches );
00210 QStringList l = d->matches.list();
00211 postProcessMatches( &l );
00212 emit matches( l );
00213
00214 if ( l.isEmpty() )
00215 doBeep( NoMatch );
00216
00217 return QString::null;
00218 }
00219
00220 QString completion;
00221
00222 if ( myCompletionMode == KGlobalSettings::CompletionPopup ||
00223 myCompletionMode == KGlobalSettings::CompletionPopupAuto ) {
00224 findAllCompletions( string, &d->matches, myHasMultipleMatches );
00225 if ( !d->matches.isEmpty() )
00226 completion = d->matches.first();
00227 }
00228 else
00229 completion = findCompletion( string );
00230
00231 if ( myHasMultipleMatches )
00232 emit multipleMatches();
00233
00234 myLastString = string;
00235 myCurrentMatch = completion;
00236
00237 postProcessMatch( &completion );
00238
00239 if ( !string.isEmpty() ) {
00240
00241 emit match( completion );
00242 }
00243
00244 if ( completion.isNull() )
00245 doBeep( NoMatch );
00246
00247 return completion;
00248 }
00249
00250
00251 QStringList KCompletion::substringCompletion( const QString& string ) const
00252 {
00253
00254 bool sorted = (myOrder == Weighted);
00255 KCompletionMatchesWrapper allItems( sorted );
00256 extractStringsFromNode( myTreeRoot, QString::null, &allItems, false );
00257
00258 QStringList list = allItems.list();
00259
00260
00261
00262 if ( list.isEmpty() ) {
00263 doBeep( NoMatch );
00264 return list;
00265 }
00266
00267 if ( string.isEmpty() ) {
00268 postProcessMatches( &list );
00269 return list;
00270 }
00271
00272 QStringList matches;
00273 QStringList::ConstIterator it = list.begin();
00274
00275 for( ; it != list.end(); ++it ) {
00276 QString item = *it;
00277 if ( item.find( string, 0, false ) != -1 ) {
00278 matches.append( item );
00279 }
00280 }
00281
00282 postProcessMatches( &matches );
00283
00284 if ( matches.isEmpty() )
00285 doBeep( NoMatch );
00286
00287 return matches;
00288 }
00289
00290
00291 void KCompletion::setCompletionMode( KGlobalSettings::Completion mode )
00292 {
00293 myCompletionMode = mode;
00294 }
00295
00296 QStringList KCompletion::allMatches()
00297 {
00298
00299
00300
00301 KCompletionMatchesWrapper matches( myOrder == Weighted );
00302 bool dummy;
00303 findAllCompletions( myLastString, &matches, dummy );
00304 QStringList l = matches.list();
00305 postProcessMatches( &l );
00306 return l;
00307 }
00308
00309 KCompletionMatches KCompletion::allWeightedMatches()
00310 {
00311
00312
00313
00314 KCompletionMatchesWrapper matches( myOrder == Weighted );
00315 bool dummy;
00316 findAllCompletions( myLastString, &matches, dummy );
00317 KCompletionMatches ret( matches );
00318 postProcessMatches( &ret );
00319 return ret;
00320 }
00321
00322 QStringList KCompletion::allMatches( const QString &string )
00323 {
00324 KCompletionMatchesWrapper matches( myOrder == Weighted );
00325 bool dummy;
00326 findAllCompletions( string, &matches, dummy );
00327 QStringList l = matches.list();
00328 postProcessMatches( &l );
00329 return l;
00330 }
00331
00332 KCompletionMatches KCompletion::allWeightedMatches( const QString &string )
00333 {
00334 KCompletionMatchesWrapper matches( myOrder == Weighted );
00335 bool dummy;
00336 findAllCompletions( string, &matches, dummy );
00337 KCompletionMatches ret( matches );
00338 postProcessMatches( &ret );
00339 return ret;
00340 }
00341
00344
00345
00346 QString KCompletion::nextMatch()
00347 {
00348 QString completion;
00349 myLastMatch = myCurrentMatch;
00350
00351 if ( d->matches.isEmpty() ) {
00352 findAllCompletions( myLastString, &d->matches, myHasMultipleMatches );
00353 completion = d->matches.first();
00354 myCurrentMatch = completion;
00355 myRotationIndex = 0;
00356 postProcessMatch( &completion );
00357 emit match( completion );
00358 return completion;
00359 }
00360
00361 QStringList matches = d->matches.list();
00362 myLastMatch = matches[ myRotationIndex++ ];
00363
00364 if ( myRotationIndex == matches.count() -1 )
00365 doBeep( Rotation );
00366
00367 else if ( myRotationIndex == matches.count() )
00368 myRotationIndex = 0;
00369
00370 completion = matches[ myRotationIndex ];
00371 myCurrentMatch = completion;
00372 postProcessMatch( &completion );
00373 emit match( completion );
00374 return completion;
00375 }
00376
00377
00378
00379 QString KCompletion::previousMatch()
00380 {
00381 QString completion;
00382 myLastMatch = myCurrentMatch;
00383
00384 if ( d->matches.isEmpty() ) {
00385 findAllCompletions( myLastString, &d->matches, myHasMultipleMatches );
00386 completion = d->matches.last();
00387 myCurrentMatch = completion;
00388 myRotationIndex = 0;
00389 postProcessMatch( &completion );
00390 emit match( completion );
00391 return completion;
00392 }
00393
00394 QStringList matches = d->matches.list();
00395 myLastMatch = matches[ myRotationIndex ];
00396 if ( myRotationIndex == 1 )
00397 doBeep( Rotation );
00398
00399 else if ( myRotationIndex == 0 )
00400 myRotationIndex = matches.count();
00401
00402 myRotationIndex--;
00403
00404 completion = matches[ myRotationIndex ];
00405 myCurrentMatch = completion;
00406 postProcessMatch( &completion );
00407 emit match( completion );
00408 return completion;
00409 }
00410
00411
00412
00413
00414 QString KCompletion::findCompletion( const QString& string )
00415 {
00416 QChar ch;
00417 QString completion;
00418 const KCompTreeNode *node = myTreeRoot;
00419
00420
00421 for( uint i = 0; i < string.length(); i++ ) {
00422 ch = string.at( i );
00423 node = node->find( ch );
00424
00425 if ( node )
00426 completion += ch;
00427 else
00428 return QString::null;
00429 }
00430
00431
00432
00433
00434
00435 while ( node->childrenCount() == 1 ) {
00436 node = node->firstChild();
00437 if ( !node->isNull() )
00438 completion += *node;
00439 }
00440
00441
00442 if ( node && node->childrenCount() > 1 ) {
00443 myHasMultipleMatches = true;
00444
00445 if ( myCompletionMode == KGlobalSettings::CompletionAuto ) {
00446 myRotationIndex = 1;
00447 if (myOrder != Weighted) {
00448 while ( (node = node->firstChild()) ) {
00449 if ( !node->isNull() )
00450 completion += *node;
00451 else
00452 break;
00453 }
00454 }
00455 else {
00456
00457
00458
00459 const KCompTreeNode* temp_node = 0L;
00460 while(1) {
00461 int count = node->childrenCount();
00462 temp_node = node->firstChild();
00463 uint weight = temp_node->weight();
00464 const KCompTreeNode* hit = temp_node;
00465 for( int i = 1; i < count; i++ ) {
00466 temp_node = node->childAt(i);
00467 if( temp_node->weight() > weight ) {
00468 hit = temp_node;
00469 weight = hit->weight();
00470 }
00471 }
00472
00473 if ( hit->isNull() )
00474 break;
00475
00476 node = hit;
00477 completion += *node;
00478 }
00479 }
00480 }
00481
00482 else
00483 doBeep( PartialMatch );
00484 }
00485
00486 return completion;
00487 }
00488
00489
00490 void KCompletion::findAllCompletions(const QString& string,
00491 KCompletionMatchesWrapper *matches,
00492 bool& hasMultipleMatches) const
00493 {
00494
00495
00496 if ( string.isEmpty() )
00497 return;
00498
00499 if ( myIgnoreCase ) {
00500 extractStringsFromNodeCI( myTreeRoot, QString::null, string, matches );
00501 hasMultipleMatches = (matches->count() > 1);
00502 return;
00503 }
00504
00505 QChar ch;
00506 QString completion;
00507 const KCompTreeNode *node = myTreeRoot;
00508
00509
00510 for( uint i = 0; i < string.length(); i++ ) {
00511 ch = string.at( i );
00512 node = node->find( ch );
00513
00514 if ( node )
00515 completion += ch;
00516 else
00517 return;
00518 }
00519
00520
00521
00522
00523
00524 while ( node->childrenCount() == 1 ) {
00525 node = node->firstChild();
00526 if ( !node->isNull() )
00527 completion += *node;
00528
00529 }
00530
00531
00532
00533 if ( node->childrenCount() == 0 )
00534 matches->append( node->weight(), completion );
00535
00536 else {
00537
00538
00539 hasMultipleMatches = true;
00540 extractStringsFromNode( node, completion, matches );
00541 }
00542 }
00543
00544
00545 void KCompletion::extractStringsFromNode( const KCompTreeNode *node,
00546 const QString& beginning,
00547 KCompletionMatchesWrapper *matches,
00548 bool addWeight ) const
00549 {
00550 if ( !node || !matches )
00551 return;
00552
00553
00554 const KCompTreeChildren *list = node->children();
00555 QString string;
00556 QString w;
00557
00558
00559 for ( KCompTreeNode *cur = list->begin(); cur ; cur = cur->next) {
00560 string = beginning;
00561 node = cur;
00562 if ( !node->isNull() )
00563 string += *node;
00564
00565 while ( node && node->childrenCount() == 1 ) {
00566 node = node->firstChild();
00567 if ( node->isNull() )
00568 break;
00569 string += *node;
00570 }
00571
00572 if ( node && node->isNull() ) {
00573 if ( addWeight ) {
00574
00575 string += ':';
00576 w.setNum( node->weight() );
00577 string.append( w );
00578 }
00579 matches->append( node->weight(), string );
00580 }
00581
00582
00583 if ( node && node->childrenCount() > 1 )
00584 extractStringsFromNode( node, string, matches, addWeight );
00585 }
00586 }
00587
00588 void KCompletion::extractStringsFromNodeCI( const KCompTreeNode *node,
00589 const QString& beginning,
00590 const QString& restString,
00591 KCompletionMatchesWrapper *matches ) const
00592 {
00593 if ( restString.isEmpty() ) {
00594 extractStringsFromNode( node, beginning, matches, false );
00595 return;
00596 }
00597
00598 QChar ch1 = restString.at(0);
00599 QString newRest = restString.mid(1);
00600 KCompTreeNode *child1, *child2;
00601
00602 child1 = node->find( ch1 );
00603 if ( child1 )
00604 extractStringsFromNodeCI( child1, beginning + *child1, newRest,
00605 matches );
00606
00607
00608 if ( ch1.isLetter() ) {
00609
00610 QChar ch2 = ch1.lower();
00611 if ( ch1 == ch2 )
00612 ch2 = ch1.upper();
00613 if ( ch1 != ch2 ) {
00614 child2 = node->find( ch2 );
00615 if ( child2 )
00616 extractStringsFromNodeCI( child2, beginning + *child2, newRest,
00617 matches );
00618 }
00619 }
00620 }
00621
00622 void KCompletion::doBeep( BeepMode mode ) const
00623 {
00624 if ( !myBeep )
00625 return;
00626
00627 QString text, event;
00628
00629 switch ( mode ) {
00630 case Rotation:
00631 event = QString::fromLatin1("Textcompletion: rotation");
00632 text = i18n("You reached the end of the list\nof matching items.\n");
00633 break;
00634 case PartialMatch:
00635 if ( myCompletionMode == KGlobalSettings::CompletionShell ||
00636 myCompletionMode == KGlobalSettings::CompletionMan ) {
00637 event = QString::fromLatin1("Textcompletion: partial match");
00638 text = i18n("The completion is ambiguous, more than one\nmatch is available.\n");
00639 }
00640 break;
00641 case NoMatch:
00642 if ( myCompletionMode == KGlobalSettings::CompletionShell ) {
00643 event = QString::fromLatin1("Textcompletion: no match");
00644 text = i18n("There is no matching item available.\n");
00645 }
00646 break;
00647 }
00648
00649 if ( !text.isEmpty() )
00650 KNotifyClient::event( event, text );
00651 }
00652
00653
00656
00657
00658
00659
00660
00661
00662
00663 KCompTreeNode::~KCompTreeNode()
00664 {
00665
00666 KCompTreeNode *cur = myChildren.begin();
00667 while (cur) {
00668 KCompTreeNode * next = cur->next;
00669 delete myChildren.remove(cur);
00670 cur = next;
00671 }
00672 }
00673
00674
00675
00676
00677 KCompTreeNode * KCompTreeNode::insert( const QChar& ch, bool sorted )
00678 {
00679 KCompTreeNode *child = find( ch );
00680 if ( !child ) {
00681 child = new KCompTreeNode( ch );
00682
00683
00684 if ( sorted ) {
00685 KCompTreeNode * prev = 0;
00686 KCompTreeNode * cur = myChildren.begin();
00687 while ( cur ) {
00688 if ( ch > *cur ) {
00689 prev = cur;
00690 cur = cur->next;
00691 } else
00692 break;
00693 }
00694 if (prev)
00695 myChildren.insert( prev, child );
00696 else
00697 myChildren.prepend(child);
00698 }
00699
00700 else
00701 myChildren.append( child );
00702 }
00703
00704
00705
00706 child->confirm();
00707
00708 return child;
00709 }
00710
00711
00712
00713
00714 void KCompTreeNode::remove( const QString& str )
00715 {
00716 QString string = str;
00717 string += QChar(0x0);
00718
00719 QPtrVector<KCompTreeNode> deletables( string.length() + 1 );
00720
00721 KCompTreeNode *child = 0L;
00722 KCompTreeNode *parent = this;
00723 deletables.insert( 0, parent );
00724
00725 uint i = 0;
00726 for ( ; i < string.length(); i++ )
00727 {
00728 child = parent->find( string.at( i ) );
00729 if ( child )
00730 deletables.insert( i + 1, child );
00731 else
00732 break;
00733
00734 parent = child;
00735 }
00736
00737 for ( ; i >= 1; i-- )
00738 {
00739 parent = deletables.at( i - 1 );
00740 child = deletables.at( i );
00741 if ( child->myChildren.count() == 0 )
00742 delete parent->myChildren.remove( child );
00743 }
00744 }
00745
00746 QStringList KCompletionMatchesWrapper::list() const
00747 {
00748 if ( sortedList && dirty ) {
00749 sortedList->sort();
00750 dirty = false;
00751
00752 stringList.clear();
00753
00754
00755 QValueListConstIterator<KSortableItem<QString> > it;
00756 for ( it = sortedList->begin(); it != sortedList->end(); ++it )
00757 stringList.prepend( (*it).value() );
00758 }
00759
00760 return stringList;
00761 }
00762
00763 KCompletionMatches::KCompletionMatches( bool sort_P )
00764 : _sorting( sort_P )
00765 {
00766 }
00767
00768 KCompletionMatches::KCompletionMatches( const KCompletionMatchesWrapper& matches )
00769 : _sorting( matches.sorting())
00770 {
00771 if( matches.sortedList != 0L )
00772 KCompletionMatchesList::operator=( *matches.sortedList );
00773 else {
00774 QStringList l = matches.list();
00775 for( QStringList::ConstIterator it = l.begin();
00776 it != l.end();
00777 ++it )
00778 prepend( KSortableItem<QString, int>( 1, *it ) );
00779 }
00780 }
00781
00782 KCompletionMatches::~KCompletionMatches()
00783 {
00784 }
00785
00786 QStringList KCompletionMatches::list( bool sort_P ) const
00787 {
00788 if( _sorting && sort_P )
00789 const_cast< KCompletionMatches* >( this )->sort();
00790 QStringList stringList;
00791
00792 for ( ConstIterator it = begin(); it != end(); ++it )
00793 stringList.prepend( (*it).value() );
00794 return stringList;
00795 }
00796
00797 void KCompletionMatches::removeDuplicates()
00798 {
00799 Iterator it1, it2;
00800 for ( it1 = begin(); it1 != end(); ++it1 ) {
00801 for ( (it2 = it1), ++it2; it2 != end();) {
00802 if( (*it1).value() == (*it2).value()) {
00803
00804 (*it1).first = kMax( (*it1).index(), (*it2).index());
00805 it2 = remove( it2 );
00806 continue;
00807 }
00808 ++it2;
00809 }
00810 }
00811 }
00812
00813 void KCompTreeNodeList::append(KCompTreeNode *item)
00814 {
00815 m_count++;
00816 if (!last) {
00817 last = item;
00818 last->next = 0;
00819 first = item;
00820 return;
00821 }
00822 last->next = item;
00823 item->next = 0;
00824 last = item;
00825 }
00826
00827 void KCompTreeNodeList::prepend(KCompTreeNode *item)
00828 {
00829 m_count++;
00830 if (!last) {
00831 last = item;
00832 last->next = 0;
00833 first = item;
00834 return;
00835 }
00836 item->next = first;
00837 first = item;
00838 }
00839
00840 void KCompTreeNodeList::insert(KCompTreeNode *after, KCompTreeNode *item)
00841 {
00842 if (!after) {
00843 append(item);
00844 return;
00845 }
00846
00847 m_count++;
00848
00849 item->next = after->next;
00850 after->next = item;
00851
00852 if (after == last)
00853 last = item;
00854 }
00855
00856 KCompTreeNode *KCompTreeNodeList::remove(KCompTreeNode *item)
00857 {
00858 if (!first || !item)
00859 return 0;
00860 KCompTreeNode *cur = 0;
00861
00862 if (item == first)
00863 first = first->next;
00864 else {
00865 cur = first;
00866 while (cur && cur->next != item) cur = cur->next;
00867 if (!cur)
00868 return 0;
00869 cur->next = item->next;
00870 }
00871 if (item == last)
00872 last = cur;
00873 m_count--;
00874 return item;
00875 }
00876
00877 KCompTreeNode *KCompTreeNodeList::at(uint index) const
00878 {
00879 KCompTreeNode *cur = first;
00880 while (index-- && cur) cur = cur->next;
00881 return cur;
00882 }
00883
00884 KZoneAllocator KCompTreeNode::alloc(8192);
00885
00886 void KCompletion::virtual_hook( int, void* )
00887 { }
00888
00889 void KCompletionBase::virtual_hook( int, void* )
00890 { }
00891
00892 #include "kcompletion.moc"