klistviewsearchline.cpp
00001 /* This file is part of the KDE libraries 00002 Copyright (c) 2003 Scott Wheeler <wheeler@kde.org> 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License version 2 as published by the Free Software Foundation. 00007 00008 This library is distributed in the hope that it will be useful, 00009 but WITHOUT ANY WARRANTY; without even the implied warranty of 00010 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00011 Library General Public License for more details. 00012 00013 You should have received a copy of the GNU Library General Public License 00014 along with this library; see the file COPYING.LIB. If not, write to 00015 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00016 Boston, MA 02110-1301, USA. 00017 */ 00018 00019 #include "klistviewsearchline.h" 00020 00021 #include <klistview.h> 00022 #include <kiconloader.h> 00023 #include <ktoolbar.h> 00024 #include <ktoolbarbutton.h> 00025 #include <kdebug.h> 00026 #include <klocale.h> 00027 00028 #include <tqapplication.h> 00029 #include <tqtimer.h> 00030 #include <tqpopupmenu.h> 00031 #include <tqlabel.h> 00032 #include <tqheader.h> 00033 00034 #define KLISTVIEWSEARCHLINE_ALLVISIBLECOLUMNS_ID 2004 00035 00036 class KListViewSearchLine::KListViewSearchLinePrivate 00037 { 00038 public: 00039 KListViewSearchLinePrivate() : 00040 listView(0), 00041 caseSensitive(false), 00042 activeSearch(false), 00043 keepParentsVisible(true), 00044 queuedSearches(0) {} 00045 00046 KListView *listView; 00047 bool caseSensitive; 00048 bool activeSearch; 00049 bool keepParentsVisible; 00050 TQString search; 00051 int queuedSearches; 00052 TQValueList<int> searchColumns; 00053 }; 00054 00056 // public methods 00058 00059 KListViewSearchLine::KListViewSearchLine(TQWidget *parent, KListView *listView, const char *name) : 00060 KLineEdit(parent, name) 00061 { 00062 d = new KListViewSearchLinePrivate; 00063 00064 d->listView = listView; 00065 00066 connect(this, TQT_SIGNAL(textChanged(const TQString &)), 00067 this, TQT_SLOT(queueSearch(const TQString &))); 00068 00069 if(listView) { 00070 connect(listView, TQT_SIGNAL(destroyed()), 00071 this, TQT_SLOT(listViewDeleted())); 00072 00073 connect(listView, TQT_SIGNAL(itemAdded(TQListViewItem *)), 00074 this, TQT_SLOT(itemAdded(TQListViewItem *))); 00075 } 00076 else 00077 setEnabled(false); 00078 } 00079 00080 KListViewSearchLine::KListViewSearchLine(TQWidget *parent, const char *name) : 00081 KLineEdit(parent, name) 00082 { 00083 d = new KListViewSearchLinePrivate; 00084 00085 d->listView = 0; 00086 00087 connect(this, TQT_SIGNAL(textChanged(const TQString &)), 00088 this, TQT_SLOT(queueSearch(const TQString &))); 00089 00090 setEnabled(false); 00091 } 00092 00093 KListViewSearchLine::~KListViewSearchLine() 00094 { 00095 delete d; 00096 } 00097 00098 bool KListViewSearchLine::caseSensitive() const 00099 { 00100 return d->caseSensitive; 00101 } 00102 00103 TQValueList<int> KListViewSearchLine::searchColumns() const 00104 { 00105 return d->searchColumns; 00106 } 00107 00108 bool KListViewSearchLine::keepParentsVisible() const 00109 { 00110 return d->keepParentsVisible; 00111 } 00112 00113 KListView *KListViewSearchLine::listView() const 00114 { 00115 return d->listView; 00116 } 00117 00119 // public slots 00121 00122 void KListViewSearchLine::updateSearch(const TQString &s) 00123 { 00124 if(!d->listView) 00125 return; 00126 00127 d->search = s.isNull() ? text() : s; 00128 00129 // If there's a selected item that is visible, make sure that it's visible 00130 // when the search changes too (assuming that it still matches). 00131 00132 TQListViewItem *currentItem = 0; 00133 00134 switch(d->listView->selectionMode()) 00135 { 00136 case KListView::NoSelection: 00137 break; 00138 case KListView::Single: 00139 currentItem = d->listView->selectedItem(); 00140 break; 00141 default: 00142 { 00143 int flags = TQListViewItemIterator::Selected | TQListViewItemIterator::Visible; 00144 for(TQListViewItemIterator it(d->listView, flags); 00145 it.current() && !currentItem; 00146 ++it) 00147 { 00148 if(d->listView->itemRect(it.current()).isValid()) 00149 currentItem = it.current(); 00150 } 00151 } 00152 } 00153 00154 if(d->keepParentsVisible) 00155 checkItemParentsVisible(d->listView->firstChild()); 00156 else 00157 checkItemParentsNotVisible(); 00158 00159 if(currentItem) 00160 d->listView->ensureItemVisible(currentItem); 00161 } 00162 00163 void KListViewSearchLine::setCaseSensitive(bool cs) 00164 { 00165 d->caseSensitive = cs; 00166 } 00167 00168 void KListViewSearchLine::setKeepParentsVisible(bool v) 00169 { 00170 d->keepParentsVisible = v; 00171 } 00172 00173 void KListViewSearchLine::setSearchColumns(const TQValueList<int> &columns) 00174 { 00175 d->searchColumns = columns; 00176 } 00177 00178 void KListViewSearchLine::setListView(KListView *lv) 00179 { 00180 if(d->listView) { 00181 disconnect(d->listView, TQT_SIGNAL(destroyed()), 00182 this, TQT_SLOT(listViewDeleted())); 00183 00184 disconnect(d->listView, TQT_SIGNAL(itemAdded(TQListViewItem *)), 00185 this, TQT_SLOT(itemAdded(TQListViewItem *))); 00186 } 00187 00188 d->listView = lv; 00189 00190 if(lv) { 00191 connect(d->listView, TQT_SIGNAL(destroyed()), 00192 this, TQT_SLOT(listViewDeleted())); 00193 00194 connect(d->listView, TQT_SIGNAL(itemAdded(TQListViewItem *)), 00195 this, TQT_SLOT(itemAdded(TQListViewItem *))); 00196 } 00197 00198 setEnabled(bool(lv)); 00199 } 00200 00202 // protected members 00204 00205 bool KListViewSearchLine::itemMatches(const TQListViewItem *item, const TQString &s) const 00206 { 00207 if(s.isEmpty()) 00208 return true; 00209 00210 // If the search column list is populated, search just the columns 00211 // specifified. If it is empty default to searching all of the columns. 00212 00213 if(!d->searchColumns.isEmpty()) { 00214 TQValueList<int>::ConstIterator it = d->searchColumns.begin(); 00215 for(; it != d->searchColumns.end(); ++it) { 00216 if(*it < item->listView()->columns() && 00217 item->text(*it).find(s, 0, d->caseSensitive) >= 0) 00218 return true; 00219 } 00220 } 00221 else { 00222 for(int i = 0; i < item->listView()->columns(); i++) { 00223 if(item->listView()->columnWidth(i) > 0 && 00224 item->text(i).find(s, 0, d->caseSensitive) >= 0) 00225 { 00226 return true; 00227 } 00228 } 00229 } 00230 00231 return false; 00232 } 00233 00234 TQPopupMenu *KListViewSearchLine::createPopupMenu() 00235 { 00236 TQPopupMenu *popup = KLineEdit::createPopupMenu(); 00237 00238 if (d->listView->columns()>1) { 00239 TQPopupMenu *subMenu = new TQPopupMenu(popup); 00240 connect(subMenu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(searchColumnsMenuActivated(int))); 00241 00242 popup->insertSeparator(); 00243 popup->insertItem(i18n("Search Columns"), subMenu); 00244 00245 subMenu->insertItem(i18n("All Visible Columns"), KLISTVIEWSEARCHLINE_ALLVISIBLECOLUMNS_ID); 00246 subMenu->insertSeparator(); 00247 00248 bool allColumnsAreSearchColumns = true; 00249 // TODO Make the entry order match the actual column order 00250 TQHeader* const header = d->listView->header(); 00251 int visibleColumns = 0; 00252 for(int i = 0; i < d->listView->columns(); i++) { 00253 if(d->listView->columnWidth(i)>0) { 00254 TQString columnText = d->listView->columnText(i); 00255 if(columnText.isEmpty()) { 00256 int visiblePosition=1; 00257 for(int j = 0; j < header->mapToIndex(i); j++) 00258 if(d->listView->columnWidth(header->mapToSection(j))>0) 00259 visiblePosition++; 00260 columnText = i18n("Column number %1","Column No. %1").arg(visiblePosition); 00261 } 00262 subMenu->insertItem(columnText, visibleColumns); 00263 if(d->searchColumns.isEmpty() || d->searchColumns.find(i) != d->searchColumns.end()) 00264 subMenu->setItemChecked(visibleColumns, true); 00265 else 00266 allColumnsAreSearchColumns = false; 00267 visibleColumns++; 00268 } 00269 } 00270 subMenu->setItemChecked(KLISTVIEWSEARCHLINE_ALLVISIBLECOLUMNS_ID, allColumnsAreSearchColumns); 00271 00272 // searchColumnsMenuActivated() relies on one possible "all" representation 00273 if(allColumnsAreSearchColumns && !d->searchColumns.isEmpty()) 00274 d->searchColumns.clear(); 00275 } 00276 00277 return popup; 00278 } 00279 00281 // protected slots 00283 00284 void KListViewSearchLine::queueSearch(const TQString &search) 00285 { 00286 d->queuedSearches++; 00287 d->search = search; 00288 TQTimer::singleShot(200, this, TQT_SLOT(activateSearch())); 00289 } 00290 00291 void KListViewSearchLine::activateSearch() 00292 { 00293 --(d->queuedSearches); 00294 00295 if(d->queuedSearches == 0) 00296 updateSearch(d->search); 00297 } 00298 00300 // private slots 00302 00303 void KListViewSearchLine::itemAdded(TQListViewItem *item) const 00304 { 00305 item->setVisible(itemMatches(item, text())); 00306 } 00307 00308 void KListViewSearchLine::listViewDeleted() 00309 { 00310 d->listView = 0; 00311 setEnabled(false); 00312 } 00313 00314 void KListViewSearchLine::searchColumnsMenuActivated(int id) 00315 { 00316 if(id == KLISTVIEWSEARCHLINE_ALLVISIBLECOLUMNS_ID) { 00317 if(d->searchColumns.isEmpty()) 00318 d->searchColumns.append(0); 00319 else 00320 d->searchColumns.clear(); 00321 } 00322 else { 00323 if(d->searchColumns.find(id) != d->searchColumns.end()) 00324 d->searchColumns.remove(id); 00325 else { 00326 if(d->searchColumns.isEmpty()) { 00327 for(int i = 0; i < d->listView->columns(); i++) { 00328 if(i != id) 00329 d->searchColumns.append(i); 00330 } 00331 } 00332 else 00333 d->searchColumns.append(id); 00334 } 00335 } 00336 updateSearch(); 00337 } 00338 00340 // private methods 00342 00343 void KListViewSearchLine::checkItemParentsNotVisible() 00344 { 00345 TQListViewItemIterator it(d->listView); 00346 for(; it.current(); ++it) 00347 { 00348 TQListViewItem *item = it.current(); 00349 if(itemMatches(item, d->search)) 00350 item->setVisible(true); 00351 else 00352 item->setVisible(false); 00353 } 00354 } 00355 00356 #include <kdebug.h> 00357 00367 bool KListViewSearchLine::checkItemParentsVisible(TQListViewItem *item, TQListViewItem *highestHiddenParent) 00368 { 00369 bool visible = false; 00370 TQListViewItem * first = item; 00371 for(; item; item = item->nextSibling()) 00372 { 00373 //What we pass to our children as highestHiddenParent: 00374 TQListViewItem * hhp = highestHiddenParent ? highestHiddenParent : item->isVisible() ? 0L : item; 00375 bool childMatch = false; 00376 if(item->firstChild() && checkItemParentsVisible(item->firstChild(), hhp)) 00377 childMatch = true; 00378 // Should this item be shown? It should if any children should be, or if it matches. 00379 if(childMatch || itemMatches(item, d->search)) 00380 { 00381 visible = true; 00382 if (highestHiddenParent) 00383 { 00384 highestHiddenParent->setVisible(true); 00385 // Calling setVisible on our ancestor will unhide all its descendents. Hide the ones 00386 // before us that should not be shown. 00387 for(TQListViewItem *hide = first; hide != item; hide = hide->nextSibling()) 00388 hide->setVisible(false); 00389 highestHiddenParent = 0; 00390 // If we matched, than none of our children matched, yet the setVisible() call on our 00391 // ancestor unhid them, undo the damage: 00392 if(!childMatch) 00393 for(TQListViewItem *hide = item->firstChild(); hide; hide = hide->nextSibling()) 00394 hide->setVisible(false); 00395 } 00396 else 00397 item->setVisible(true); 00398 } 00399 else 00400 item->setVisible(false); 00401 } 00402 return visible; 00403 } 00404 00406 // KListViewSearchLineWidget 00408 00409 class KListViewSearchLineWidget::KListViewSearchLineWidgetPrivate 00410 { 00411 public: 00412 KListViewSearchLineWidgetPrivate() : listView(0), searchLine(0), clearButton(0) {} 00413 KListView *listView; 00414 KListViewSearchLine *searchLine; 00415 TQToolButton *clearButton; 00416 }; 00417 00418 KListViewSearchLineWidget::KListViewSearchLineWidget(KListView *listView, 00419 TQWidget *parent, 00420 const char *name) : 00421 TQHBox(parent, name) 00422 { 00423 d = new KListViewSearchLineWidgetPrivate; 00424 d->listView = listView; 00425 00426 setSpacing(5); 00427 00428 TQTimer::singleShot(0, this, TQT_SLOT(createWidgets())); 00429 } 00430 00431 KListViewSearchLineWidget::~KListViewSearchLineWidget() 00432 { 00433 delete d; 00434 } 00435 00436 KListViewSearchLine *KListViewSearchLineWidget::createSearchLine(KListView *listView) 00437 { 00438 if(!d->searchLine) 00439 d->searchLine = new KListViewSearchLine(this, listView); 00440 return d->searchLine; 00441 } 00442 00443 void KListViewSearchLineWidget::createWidgets() 00444 { 00445 positionInToolBar(); 00446 00447 if(!d->clearButton) { 00448 d->clearButton = new TQToolButton(this); 00449 TQIconSet icon = SmallIconSet(TQApplication::reverseLayout() ? "clear_left" : "locationbar_erase"); 00450 d->clearButton->setIconSet(icon); 00451 } 00452 00453 d->clearButton->show(); 00454 00455 TQLabel *label = new TQLabel(i18n("S&earch:"), this, "kde toolbar widget"); 00456 00457 d->searchLine = createSearchLine(d->listView); 00458 d->searchLine->show(); 00459 00460 label->setBuddy(d->searchLine); 00461 label->show(); 00462 00463 connect(d->clearButton, TQT_SIGNAL(clicked()), d->searchLine, TQT_SLOT(clear())); 00464 } 00465 00466 KListViewSearchLine *KListViewSearchLineWidget::searchLine() const 00467 { 00468 return d->searchLine; 00469 } 00470 00471 void KListViewSearchLineWidget::positionInToolBar() 00472 { 00473 KToolBar *toolBar = tqt_dynamic_cast<KToolBar *>(parent()); 00474 00475 if(toolBar) { 00476 00477 // Here we have The Big Ugly. Figure out how many widgets are in the 00478 // and do a hack-ish iteration over them to find this widget so that we 00479 // can insert the clear button before it. 00480 00481 int widgetCount = toolBar->count(); 00482 00483 for(int index = 0; index < widgetCount; index++) { 00484 int id = toolBar->idAt(index); 00485 if(toolBar->getWidget(id) == this) { 00486 toolBar->setItemAutoSized(id); 00487 if(!d->clearButton) { 00488 TQString icon = TQApplication::reverseLayout() ? "clear_left" : "locationbar_erase"; 00489 d->clearButton = new KToolBarButton(icon, 2005, toolBar); 00490 } 00491 toolBar->insertWidget(2005, d->clearButton->width(), d->clearButton, index); 00492 break; 00493 } 00494 } 00495 } 00496 00497 if(d->searchLine) 00498 d->searchLine->show(); 00499 } 00500 00501 #include "klistviewsearchline.moc"