find.cpp
00001 /* 00002 * find.cpp - search facility 00003 * Program: kalarm 00004 * Copyright © 2005,2006,2008 by David Jarvie <djarvie@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kalarm.h" 00022 00023 #include <tqlayout.h> 00024 #include <tqwhatsthis.h> 00025 #include <tqgroupbox.h> 00026 #include <tqcheckbox.h> 00027 00028 #include <kfinddialog.h> 00029 #include <kfind.h> 00030 #include <kseparator.h> 00031 #include <kwin.h> 00032 #include <klocale.h> 00033 #include <kmessagebox.h> 00034 #include <kdebug.h> 00035 00036 #include "alarmlistview.h" 00037 #include "preferences.h" 00038 #include "find.moc" 00039 00040 // KAlarm-specific options for Find dialog 00041 enum { 00042 FIND_LIVE = KFindDialog::MinimumUserOption, 00043 FIND_EXPIRED = KFindDialog::MinimumUserOption << 1, 00044 FIND_MESSAGE = KFindDialog::MinimumUserOption << 2, 00045 FIND_FILE = KFindDialog::MinimumUserOption << 3, 00046 FIND_COMMAND = KFindDialog::MinimumUserOption << 4, 00047 FIND_EMAIL = KFindDialog::MinimumUserOption << 5 00048 }; 00049 static long FIND_KALARM_OPTIONS = FIND_LIVE | FIND_EXPIRED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL; 00050 00051 00052 Find::Find(EventListViewBase* parent) 00053 : TQObject(parent), 00054 mListView(parent), 00055 mDialog(0), 00056 mFind(0), 00057 mOptions(0) 00058 { 00059 } 00060 00061 Find::~Find() 00062 { 00063 delete mDialog; // automatically set to 0 00064 delete mFind; 00065 mFind = 0; 00066 } 00067 00068 /****************************************************************************** 00069 * Display the Find dialog. 00070 */ 00071 void Find::display() 00072 { 00073 if (!mOptions) 00074 // Set defaults the first time the Find dialog is activated 00075 mOptions = FIND_LIVE | FIND_EXPIRED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL; 00076 bool noExpired = !Preferences::expiredKeepDays(); 00077 bool showExpired = mListView->isA("AlarmListView") && ((AlarmListView*)mListView)->showingExpired(); 00078 if (noExpired || !showExpired) // these settings could change between activations 00079 mOptions &= ~FIND_EXPIRED; 00080 00081 if (mDialog) 00082 { 00083 KWin::activateWindow(mDialog->winId()); 00084 } 00085 else 00086 { 00087 #ifdef MODAL_FIND 00088 mDialog = new KFindDialog(mListView, "FindDlg", mOptions, mHistory, (mListView->selectedCount() > 1)); 00089 #else 00090 mDialog = new KFindDialog(false, mListView, "FindDlg", mOptions, mHistory, (mListView->selectedCount() > 1)); 00091 #endif 00092 mDialog->setHasSelection(false); 00093 TQWidget* kalarmWidgets = mDialog->findExtension(); 00094 00095 // Alarm types 00096 TQBoxLayout* layout = new TQVBoxLayout(kalarmWidgets, 0, KDialog::spacingHint()); 00097 TQGroupBox* group = new TQGroupBox(i18n("Alarm Type"), kalarmWidgets); 00098 layout->addWidget(group); 00099 TQGridLayout* grid = new TQGridLayout(group, 2, 2, KDialog::marginHint(), KDialog::spacingHint()); 00100 grid->addRowSpacing(0, mDialog->fontMetrics().lineSpacing()/2); 00101 grid->setColStretch(1, 1); 00102 00103 // Live & expired alarm selection 00104 mLive = new TQCheckBox(i18n("Acti&ve"), group); 00105 mLive->setFixedSize(mLive->sizeHint()); 00106 TQWhatsThis::add(mLive, i18n("Check to include active alarms in the search.")); 00107 grid->addWidget(mLive, 1, 0, TQt::AlignAuto); 00108 00109 mExpired = new TQCheckBox(i18n("Ex&pired"), group); 00110 mExpired->setFixedSize(mExpired->sizeHint()); 00111 TQWhatsThis::add(mExpired, 00112 i18n("Check to include expired alarms in the search. " 00113 "This option is only available if expired alarms are currently being displayed.")); 00114 grid->addWidget(mExpired, 1, 2, TQt::AlignAuto); 00115 00116 mActiveExpiredSep = new KSeparator(Qt::Horizontal, kalarmWidgets); 00117 grid->addMultiCellWidget(mActiveExpiredSep, 2, 2, 0, 2); 00118 00119 // Alarm actions 00120 mMessageType = new TQCheckBox(i18n("Text"), group, "message"); 00121 mMessageType->setFixedSize(mMessageType->sizeHint()); 00122 TQWhatsThis::add(mMessageType, i18n("Check to include text message alarms in the search.")); 00123 grid->addWidget(mMessageType, 3, 0); 00124 00125 mFileType = new TQCheckBox(i18n("Fi&le"), group, "file"); 00126 mFileType->setFixedSize(mFileType->sizeHint()); 00127 TQWhatsThis::add(mFileType, i18n("Check to include file alarms in the search.")); 00128 grid->addWidget(mFileType, 3, 2); 00129 00130 mCommandType = new TQCheckBox(i18n("Co&mmand"), group, "command"); 00131 mCommandType->setFixedSize(mCommandType->sizeHint()); 00132 TQWhatsThis::add(mCommandType, i18n("Check to include command alarms in the search.")); 00133 grid->addWidget(mCommandType, 4, 0); 00134 00135 mEmailType = new TQCheckBox(i18n("&Email"), group, "email"); 00136 mEmailType->setFixedSize(mEmailType->sizeHint()); 00137 TQWhatsThis::add(mEmailType, i18n("Check to include email alarms in the search.")); 00138 grid->addWidget(mEmailType, 4, 2); 00139 00140 // Set defaults 00141 mLive->setChecked(mOptions & FIND_LIVE); 00142 mExpired->setChecked(mOptions & FIND_EXPIRED); 00143 mMessageType->setChecked(mOptions & FIND_MESSAGE); 00144 mFileType->setChecked(mOptions & FIND_FILE); 00145 mCommandType->setChecked(mOptions & FIND_COMMAND); 00146 mEmailType->setChecked(mOptions & FIND_EMAIL); 00147 00148 #ifndef MODAL_FIND 00149 connect(mDialog, TQT_SIGNAL(okClicked()), this, TQT_SLOT(slotFind())); 00150 #endif 00151 } 00152 00153 // Only display active/expired options if expired alarms are being kept 00154 if (noExpired) 00155 { 00156 mLive->hide(); 00157 mExpired->hide(); 00158 mActiveExpiredSep->hide(); 00159 } 00160 else 00161 { 00162 mLive->show(); 00163 mExpired->show(); 00164 mActiveExpiredSep->show(); 00165 } 00166 00167 // Disable options where no displayed alarms match them 00168 bool live = false; 00169 bool expired = false; 00170 bool text = false; 00171 bool file = false; 00172 bool command = false; 00173 bool email = false; 00174 for (EventListViewItemBase* item = mListView->firstChild(); item; item = item->nextSibling()) 00175 { 00176 const KAEvent& event = item->event(); 00177 if (event.expired()) 00178 expired = true; 00179 else 00180 live = true; 00181 switch (event.action()) 00182 { 00183 case KAEvent::MESSAGE: text = true; break; 00184 case KAEvent::FILE: file = true; break; 00185 case KAEvent::COMMAND: command = true; break; 00186 case KAEvent::EMAIL: email = true; break; 00187 } 00188 } 00189 mLive->setEnabled(live); 00190 mExpired->setEnabled(expired); 00191 mMessageType->setEnabled(text); 00192 mFileType->setEnabled(file); 00193 mCommandType->setEnabled(command); 00194 mEmailType->setEnabled(email); 00195 00196 mDialog->setHasCursor(mListView->currentItem()); 00197 #ifdef MODAL_FIND 00198 if (mDialog->exec() == TQDialog::Accepted) 00199 slotFind(); 00200 else 00201 delete mDialog; 00202 #else 00203 mDialog->show(); 00204 #endif 00205 } 00206 00207 /****************************************************************************** 00208 * Called when the user requests a search by clicking the dialog OK button. 00209 */ 00210 void Find::slotFind() 00211 { 00212 if (!mDialog) 00213 return; 00214 mHistory = mDialog->findHistory(); // save search history so that it can be displayed again 00215 mOptions = mDialog->options() & ~FIND_KALARM_OPTIONS; 00216 mOptions |= (mLive->isEnabled() && mLive->isChecked() ? FIND_LIVE : 0) 00217 | (mExpired->isEnabled() && mExpired->isChecked() ? FIND_EXPIRED : 0) 00218 | (mMessageType->isEnabled() && mMessageType->isChecked() ? FIND_MESSAGE : 0) 00219 | (mFileType->isEnabled() && mFileType->isChecked() ? FIND_FILE : 0) 00220 | (mCommandType->isEnabled() && mCommandType->isChecked() ? FIND_COMMAND : 0) 00221 | (mEmailType->isEnabled() && mEmailType->isChecked() ? FIND_EMAIL : 0); 00222 if (!(mOptions & (FIND_LIVE | FIND_EXPIRED)) 00223 || !(mOptions & (FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL))) 00224 { 00225 KMessageBox::sorry(mDialog, i18n("No alarm types are selected to search")); 00226 return; 00227 } 00228 00229 // Supply KFind with only those options which relate to the text within alarms 00230 long options = mOptions & (KFindDialog::WholeWordsOnly | KFindDialog::CaseSensitive | KFindDialog::RegularExpression); 00231 bool newFind = !mFind; 00232 bool newPattern = (mDialog->pattern() != mLastPattern); 00233 mLastPattern = mDialog->pattern(); 00234 if (mFind) 00235 { 00236 mFind->resetCounts(); 00237 mFind->setPattern(mLastPattern); 00238 mFind->setOptions(options); 00239 } 00240 else 00241 { 00242 #ifdef MODAL_FIND 00243 mFind = new KFind(mLastPattern, options, mListView); 00244 mDialog->deleteLater(); // automatically set to 0 00245 #else 00246 mFind = new KFind(mLastPattern, options, mListView, mDialog); 00247 #endif 00248 connect(mFind, TQT_SIGNAL(destroyed()), TQT_SLOT(slotKFindDestroyed())); 00249 mFind->closeFindNextDialog(); // prevent 'Find Next' dialog appearing 00250 } 00251 00252 // Set the starting point for the search 00253 mStartID = TQString(); 00254 mNoCurrentItem = newPattern; 00255 bool checkEnd = false; 00256 if (newPattern) 00257 { 00258 mFound = false; 00259 if (mOptions & KFindDialog::FromCursor) 00260 { 00261 EventListViewItemBase* item = mListView->currentItem(); 00262 if (item) 00263 { 00264 mStartID = item->event().id(); 00265 mNoCurrentItem = false; 00266 checkEnd = true; 00267 } 00268 } 00269 } 00270 00271 // Execute the search 00272 findNext(true, true, checkEnd, false); 00273 if (mFind && newFind) 00274 emit active(true); 00275 } 00276 00277 /****************************************************************************** 00278 * Perform the search. 00279 * If 'fromCurrent' is true, the search starts with the current search item; 00280 * otherwise, it starts from the next item. 00281 */ 00282 void Find::findNext(bool forward, bool sort, bool checkEnd, bool fromCurrent) 00283 { 00284 if (sort) 00285 mListView->sort(); // ensure the whole list is sorted, not just the visible items 00286 00287 EventListViewItemBase* item = mNoCurrentItem ? 0 : mListView->currentItem(); 00288 if (!fromCurrent) 00289 item = nextItem(item, forward); 00290 00291 // Search successive alarms until a match is found or the end is reached 00292 bool found = false; 00293 bool last = false; 00294 for ( ; item && !last; item = nextItem(item, forward)) 00295 { 00296 const KAEvent& event = item->event(); 00297 if (!fromCurrent && !mStartID.isNull() && mStartID == event.id()) 00298 last = true; // we've wrapped round and reached the starting alarm again 00299 fromCurrent = false; 00300 bool live = !event.expired(); 00301 if (live && !(mOptions & FIND_LIVE) 00302 || !live && !(mOptions & FIND_EXPIRED)) 00303 continue; // we're not searching this type of alarm 00304 switch (event.action()) 00305 { 00306 case KAEvent::MESSAGE: 00307 if (!(mOptions & FIND_MESSAGE)) 00308 break; 00309 mFind->setData(event.cleanText()); 00310 found = (mFind->find() == KFind::Match); 00311 break; 00312 00313 case KAEvent::FILE: 00314 if (!(mOptions & FIND_FILE)) 00315 break; 00316 mFind->setData(event.cleanText()); 00317 found = (mFind->find() == KFind::Match); 00318 break; 00319 00320 case KAEvent::COMMAND: 00321 if (!(mOptions & FIND_COMMAND)) 00322 break; 00323 mFind->setData(event.cleanText()); 00324 found = (mFind->find() == KFind::Match); 00325 break; 00326 00327 case KAEvent::EMAIL: 00328 if (!(mOptions & FIND_EMAIL)) 00329 break; 00330 mFind->setData(event.emailAddresses(", ")); 00331 found = (mFind->find() == KFind::Match); 00332 if (found) 00333 break; 00334 mFind->setData(event.emailSubject()); 00335 found = (mFind->find() == KFind::Match); 00336 if (found) 00337 break; 00338 mFind->setData(event.emailAttachments().join(", ")); 00339 found = (mFind->find() == KFind::Match); 00340 if (found) 00341 break; 00342 mFind->setData(event.cleanText()); 00343 found = (mFind->find() == KFind::Match); 00344 break; 00345 } 00346 if (found) 00347 break; 00348 } 00349 00350 // Process the search result 00351 mNoCurrentItem = !item; 00352 if (found) 00353 { 00354 // A matching alarm was found - highlight it and make it current 00355 mFound = true; 00356 mListView->clearSelection(); 00357 mListView->setSelected(item, true); 00358 mListView->setCurrentItem(item); 00359 mListView->ensureItemVisible(item); 00360 } 00361 else 00362 { 00363 // No match was found 00364 if (mFound || checkEnd) 00365 { 00366 TQString msg = forward ? i18n("End of alarm list reached.\nContinue from the beginning?") 00367 : i18n("Beginning of alarm list reached.\nContinue from the end?"); 00368 if (KMessageBox::questionYesNo(mListView, msg, TQString(), KStdGuiItem::cont(), KStdGuiItem::cancel()) == KMessageBox::Yes) 00369 { 00370 mNoCurrentItem = true; 00371 findNext(forward, false); 00372 return; 00373 } 00374 } 00375 else 00376 mFind->displayFinalDialog(); // display "no match was found" 00377 mNoCurrentItem = false; // restart from the currently highlighted alarm if Find Next etc selected 00378 } 00379 } 00380 00381 /****************************************************************************** 00382 * Get the next alarm item to search. 00383 */ 00384 EventListViewItemBase* Find::nextItem(EventListViewItemBase* item, bool forward) const 00385 { 00386 TQListViewItem* it; 00387 if (mOptions & KFindDialog::FindBackwards) 00388 forward = !forward; 00389 if (forward) 00390 it = item ? item->itemBelow() : mListView->firstChild(); 00391 else 00392 it = item ? item->itemAbove() : mListView->lastItem(); 00393 return (EventListViewItemBase*)it; 00394 }