Warning, file /pim/kalarm/src/find.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  find.cpp  -  search facility
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2005-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "find.h"
0010 
0011 #include "alarmlistview.h"
0012 #include "eventlistview.h"
0013 #include "preferences.h"
0014 #include "resources/eventmodel.h"
0015 #include "lib/messagebox.h"
0016 #include "kalarmcalendar/kaevent.h"
0017 #include "config-kalarm.h"
0018 
0019 #include <KFindDialog>
0020 #include <KFind>
0021 #include <KSeparator>
0022 #include <KLocalizedString>
0023 #if ENABLE_X11
0024 #include <KX11Extras>
0025 #endif
0026 
0027 #include <QGroupBox>
0028 #include <QCheckBox>
0029 #include <QVBoxLayout>
0030 #include <QGridLayout>
0031 #include <QRegularExpression>
0032 
0033 using namespace KAlarmCal;
0034 
0035 // KAlarm-specific options for Find dialog
0036 enum {
0037     FIND_LIVE     = KFind::MinimumUserOption,
0038     FIND_ARCHIVED = KFind::MinimumUserOption << 1,
0039     FIND_MESSAGE  = KFind::MinimumUserOption << 2,
0040     FIND_FILE     = KFind::MinimumUserOption << 3,
0041     FIND_COMMAND  = KFind::MinimumUserOption << 4,
0042     FIND_EMAIL    = KFind::MinimumUserOption << 5,
0043     FIND_AUDIO    = KFind::MinimumUserOption << 6
0044 };
0045 static long FIND_KALARM_OPTIONS = FIND_LIVE | FIND_ARCHIVED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL | FIND_AUDIO;
0046 
0047 
0048 Find::Find(EventListView* parent)
0049     : QObject(parent)
0050     , mListView(parent)
0051     , mDialog(nullptr)
0052 {
0053     connect(mListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &Find::slotSelectionChanged);
0054 }
0055 
0056 Find::~Find()
0057 {
0058     delete mDialog;    // automatically set to null
0059     delete mFind;
0060     mFind = nullptr;
0061 }
0062 
0063 void Find::slotSelectionChanged()
0064 {
0065     if (mDialog)
0066         mDialog->setHasCursor(mListView->selectionModel()->currentIndex().isValid());
0067 }
0068 
0069 /******************************************************************************
0070 * Display the Find dialog.
0071 */
0072 void Find::display()
0073 {
0074     if (!mOptions)
0075     {
0076         // Set defaults the first time the Find dialog is activated
0077         mOptions = FIND_LIVE | FIND_ARCHIVED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL | FIND_AUDIO;
0078     }
0079     bool noArchived = !Preferences::archivedKeepDays();
0080     bool showArchived = qobject_cast<AlarmListView*>(mListView)
0081                         && (static_cast<AlarmListModel*>(mListView->model())->eventTypeFilter() & CalEvent::ARCHIVED);
0082     if (noArchived  ||  !showArchived)      // these settings could change between activations
0083         mOptions &= ~FIND_ARCHIVED;
0084 
0085     if (mDialog)
0086     {
0087 #if ENABLE_X11
0088         KX11Extras::activateWindow(mDialog->winId());
0089 #endif
0090     }
0091     else
0092     {
0093         mDialog = new KFindDialog(mListView, mOptions, mHistory, (mListView->selectionModel()->selectedRows().count() > 1));
0094         mDialog->setModal(false);
0095         mDialog->setObjectName(QLatin1StringView("FindDlg"));
0096         mDialog->setHasSelection(false);
0097         QWidget* kalarmWidgets = mDialog->findExtension();
0098 
0099         // Alarm types
0100         auto layout = new QVBoxLayout(kalarmWidgets);
0101         layout->setContentsMargins(0, 0, 0, 0);
0102         QGroupBox* group = new QGroupBox(i18nc("@title:group", "Alarm Type"), kalarmWidgets);
0103         layout->addWidget(group);
0104         auto grid = new QGridLayout(group);
0105         grid->setColumnStretch(1, 1);
0106 
0107         // Live & archived alarm selection
0108         mLive = new QCheckBox(i18nc("@option:check Alarm type", "Active"), group);
0109         mLive->setWhatsThis(i18nc("@info:whatsthis", "Check to include active alarms in the search."));
0110         grid->addWidget(mLive, 1, 0, Qt::AlignLeft);
0111 
0112         mArchived = new QCheckBox(i18nc("@option:check Alarm type", "Archived"), group);
0113         mArchived->setWhatsThis(i18nc("@info:whatsthis", "Check to include archived alarms in the search. "
0114                                      "This option is only available if archived alarms are currently being displayed."));
0115         grid->addWidget(mArchived, 1, 2, Qt::AlignLeft);
0116 
0117         mActiveArchivedSep = new KSeparator(Qt::Horizontal, kalarmWidgets);
0118         grid->addWidget(mActiveArchivedSep, 2, 0, 1, 3);
0119 
0120         // Alarm actions
0121         mMessageType = new QCheckBox(i18nc("@option:check Alarm action = text display", "Text"), group);
0122         mMessageType->setWhatsThis(i18nc("@info:whatsthis", "Check to include text message alarms in the search."));
0123         grid->addWidget(mMessageType, 3, 0);
0124 
0125         mFileType = new QCheckBox(i18nc("@option:check Alarm action = file display", "File"), group);
0126         mFileType->setWhatsThis(i18nc("@info:whatsthis", "Check to include file alarms in the search."));
0127         grid->addWidget(mFileType, 3, 2);
0128 
0129         mCommandType = new QCheckBox(i18nc("@option:check Alarm action", "Command"), group);
0130         mCommandType->setWhatsThis(i18nc("@info:whatsthis", "Check to include command alarms in the search."));
0131         grid->addWidget(mCommandType, 4, 0);
0132 
0133         mEmailType = new QCheckBox(i18nc("@option:check Alarm action", "Email"), group);
0134         mEmailType->setWhatsThis(i18nc("@info:whatsthis", "Check to include email alarms in the search."));
0135         grid->addWidget(mEmailType, 4, 2);
0136 
0137         mAudioType = new QCheckBox(i18nc("@option:check Alarm action", "Audio"), group);
0138         mAudioType->setWhatsThis(i18nc("@info:whatsthis", "Check to include audio alarms in the search."));
0139         grid->addWidget(mAudioType, 5, 0);
0140 
0141         // Set defaults
0142         mLive->setChecked(mOptions & FIND_LIVE);
0143         mArchived->setChecked(mOptions & FIND_ARCHIVED);
0144         mMessageType->setChecked(mOptions & FIND_MESSAGE);
0145         mFileType->setChecked(mOptions & FIND_FILE);
0146         mCommandType->setChecked(mOptions & FIND_COMMAND);
0147         mEmailType->setChecked(mOptions & FIND_EMAIL);
0148         mAudioType->setChecked(mOptions & FIND_AUDIO);
0149 
0150         connect(mDialog.data(), &KFindDialog::okClicked, this, &Find::slotFind);
0151     }
0152 
0153     // Only display active/archived options if archived alarms are being kept
0154     if (noArchived)
0155     {
0156         mLive->hide();
0157         mArchived->hide();
0158         mActiveArchivedSep->hide();
0159     }
0160     else
0161     {
0162         mLive->show();
0163         mArchived->show();
0164         mActiveArchivedSep->show();
0165     }
0166 
0167     // Disable options where no displayed alarms match them
0168     bool live     = false;
0169     bool archived = false;
0170     bool text     = false;
0171     bool file     = false;
0172     bool command  = false;
0173     bool email    = false;
0174     bool audio    = false;
0175     const int rowCount = mListView->model()->rowCount();
0176     for (int row = 0;  row < rowCount;  ++row)
0177     {
0178         const KAEvent viewEvent = mListView->event(row);
0179         const KAEvent* event = &viewEvent;
0180         if (event->expired())
0181             archived = true;
0182         else
0183             live = true;
0184         switch (event->actionTypes())
0185         {
0186             case KAEvent::Action::Email:    email   = true;  break;
0187             case KAEvent::Action::Audio:    audio   = true;  break;
0188             case KAEvent::Action::Command:  command = true;  break;
0189             case KAEvent::Action::Display:
0190                 if (event->actionSubType() == KAEvent::SubAction::File)
0191                 {
0192                     file = true;
0193                     break;
0194                 }
0195                 // fall through to DisplayCommand
0196                 [[fallthrough]];
0197             case KAEvent::Action::DisplayCommand:
0198             default:
0199                 text = true;
0200                 break;
0201         }
0202     }
0203     mLive->setEnabled(live);
0204     mArchived->setEnabled(archived);
0205     mMessageType->setEnabled(text);
0206     mFileType->setEnabled(file);
0207     mCommandType->setEnabled(command);
0208     mEmailType->setEnabled(email);
0209     mAudioType->setEnabled(audio);
0210 
0211     mDialog->setHasCursor(mListView->selectionModel()->currentIndex().isValid());
0212     mDialog->show();
0213 }
0214 
0215 /******************************************************************************
0216 * Called when the user requests a search by clicking the dialog OK button.
0217 */
0218 void Find::slotFind()
0219 {
0220     if (!mDialog)
0221         return;
0222     mHistory = mDialog->findHistory();    // save search history so that it can be displayed again
0223     mOptions = mDialog->options() & ~FIND_KALARM_OPTIONS;
0224     if ((mOptions & KFind::RegularExpression)  &&  !QRegularExpression(mDialog->pattern()).isValid())
0225         return;
0226     mOptions |= (mLive->isEnabled()        && mLive->isChecked()        ? FIND_LIVE : 0)
0227              |  (mArchived->isEnabled()    && mArchived->isChecked()    ? FIND_ARCHIVED : 0)
0228              |  (mMessageType->isEnabled() && mMessageType->isChecked() ? FIND_MESSAGE : 0)
0229              |  (mFileType->isEnabled()    && mFileType->isChecked()    ? FIND_FILE : 0)
0230              |  (mCommandType->isEnabled() && mCommandType->isChecked() ? FIND_COMMAND : 0)
0231              |  (mEmailType->isEnabled()   && mEmailType->isChecked()   ? FIND_EMAIL : 0)
0232              |  (mAudioType->isEnabled()   && mAudioType->isChecked()   ? FIND_AUDIO : 0);
0233     if (!(mOptions & (FIND_LIVE | FIND_ARCHIVED))
0234     ||  !(mOptions & (FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL | FIND_AUDIO)))
0235     {
0236         KAMessageBox::error(mDialog, i18nc("@info", "No alarm types are selected to search"));
0237         return;
0238     }
0239 
0240     // Supply KFind with only those options which relate to the text within alarms
0241     const long options = mOptions & (KFind::WholeWordsOnly | KFind::CaseSensitive | KFind::RegularExpression);
0242     const bool newFind = !mFind;
0243     const bool newPattern = (mDialog->pattern() != mLastPattern);
0244     mLastPattern = mDialog->pattern();
0245     if (mFind)
0246     {
0247         mFind->resetCounts();
0248         mFind->setPattern(mLastPattern);
0249         mFind->setOptions(options);
0250     }
0251     else
0252     {
0253         mFind = new KFind(mLastPattern, options, mListView, mDialog);
0254         connect(mFind, &KFind::destroyed, this, &Find::slotKFindDestroyed);
0255         mFind->closeFindNextDialog();    // prevent 'Find Next' dialog appearing
0256     }
0257 
0258     // Set the starting point for the search
0259     mStartID.clear();
0260     mNoCurrentItem = newPattern;
0261     bool checkEnd = false;
0262     if (newPattern)
0263     {
0264         mFound = false;
0265         if (mOptions & KFind::FromCursor)
0266         {
0267             const QModelIndex index = mListView->selectionModel()->currentIndex();
0268             if (index.isValid())
0269             {
0270                 mStartID       = mListView->event(index).id();
0271                 mNoCurrentItem = false;
0272                 checkEnd = true;
0273             }
0274         }
0275     }
0276 
0277     // Execute the search
0278     findNext(true, checkEnd, false);
0279     if (mFind  &&  newFind)
0280         Q_EMIT active(true);
0281 }
0282 
0283 /******************************************************************************
0284 * Perform the search.
0285 * If 'fromCurrent' is true, the search starts with the current search item;
0286 * otherwise, it starts from the next item.
0287 */
0288 void Find::findNext(bool forward, bool checkEnd, bool fromCurrent)
0289 {
0290     QModelIndex index;
0291     if (!mNoCurrentItem)
0292         index = mListView->selectionModel()->currentIndex();
0293     if (!fromCurrent)
0294         index = nextItem(index, forward);
0295 
0296     // Search successive alarms until a match is found or the end is reached
0297     bool found = false;
0298     bool last = false;
0299     for ( ;  index.isValid() && !last;  index = nextItem(index, forward))
0300     {
0301         const KAEvent viewEvent = mListView->event(index);
0302         const KAEvent* event = &viewEvent;
0303         if (!fromCurrent  &&  !mStartID.isNull()  &&  mStartID == event->id())
0304             last = true;    // we've wrapped round and reached the starting alarm again
0305         fromCurrent = false;
0306         const bool live = !event->expired();
0307         if ((live  &&  !(mOptions & FIND_LIVE))
0308         ||  (!live  &&  !(mOptions & FIND_ARCHIVED)))
0309             continue;     // we're not searching this type of alarm
0310         switch (event->actionTypes())
0311         {
0312             case KAEvent::Action::Email:
0313                 if (!(mOptions & FIND_EMAIL))
0314                     break;
0315                 mFind->setData(event->emailAddresses(QStringLiteral(", ")));
0316                 found = (mFind->find() == KFind::Match);
0317                 if (found)
0318                     break;
0319                 mFind->setData(event->emailSubject());
0320                 found = (mFind->find() == KFind::Match);
0321                 if (found)
0322                     break;
0323                 mFind->setData(event->emailAttachments().join(QLatin1String(", ")));
0324                 found = (mFind->find() == KFind::Match);
0325                 if (found)
0326                     break;
0327                 mFind->setData(event->cleanText());
0328                 found = (mFind->find() == KFind::Match);
0329                 break;
0330 
0331             case KAEvent::Action::Audio:
0332                 if (!(mOptions & FIND_AUDIO))
0333                     break;
0334                 mFind->setData(event->audioFile());
0335                 found = (mFind->find() == KFind::Match);
0336                 break;
0337 
0338             case KAEvent::Action::Command:
0339                 if (!(mOptions & FIND_COMMAND))
0340                     break;
0341                 mFind->setData(event->cleanText());
0342                 found = (mFind->find() == KFind::Match);
0343                 break;
0344 
0345             case KAEvent::Action::Display:
0346                 if (event->actionSubType() == KAEvent::SubAction::File)
0347                 {
0348                     if (!(mOptions & FIND_FILE))
0349                         break;
0350                     mFind->setData(event->cleanText());
0351                     found = (mFind->find() == KFind::Match);
0352                     break;
0353                 }
0354                 // fall through to DisplayCommand
0355                 [[fallthrough]];
0356             case KAEvent::Action::DisplayCommand:
0357                 if (!(mOptions & FIND_MESSAGE))
0358                     break;
0359                 mFind->setData(event->cleanText());
0360                 found = (mFind->find() == KFind::Match);
0361                 break;
0362             default:
0363                 break;
0364         }
0365         if (found)
0366             break;
0367     }
0368 
0369     // Process the search result
0370     mNoCurrentItem = !index.isValid();
0371     if (found)
0372     {
0373         // A matching alarm was found - highlight it and make it current
0374         mFound = true;
0375         QItemSelectionModel* sel = mListView->selectionModel();
0376         sel->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0377         sel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0378         mListView->scrollTo(index);
0379     }
0380     else
0381     {
0382         // No match was found
0383         if (mFound  ||  checkEnd)
0384         {
0385             const QString msg = forward ? xi18nc("@info", "<para>End of alarm list reached.</para><para>Continue from the beginning?</para>")
0386                                         : xi18nc("@info", "<para>Beginning of alarm list reached.</para><para>Continue from the end?</para>");
0387             if (KAMessageBox::questionYesNo(mListView, msg, QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel()) == KMessageBox::ButtonCode::PrimaryAction)
0388             {
0389                 mNoCurrentItem = true;
0390                 findNext(forward, false, false);
0391                 return;
0392             }
0393         }
0394         else
0395             mFind->displayFinalDialog();     // display "no match was found"
0396         mNoCurrentItem = false;    // restart from the currently highlighted alarm if Find Next etc selected
0397     }
0398 }
0399 
0400 /******************************************************************************
0401 * Get the next alarm item to search.
0402 */
0403 QModelIndex Find::nextItem(const QModelIndex& index, bool forward) const
0404 {
0405     if (mOptions & KFind::FindBackwards)
0406         forward = !forward;
0407     if (!index.isValid())
0408     {
0409         QAbstractItemModel* model = mListView->model();
0410         if (forward)
0411             return model->index(0, 0);
0412         else
0413             return model->index(model->rowCount() - 1, 0);
0414     }
0415     if (forward)
0416         return mListView->indexBelow(index);
0417     else
0418         return mListView->indexAbove(index);
0419 }
0420 
0421 #include "moc_find.cpp"
0422 
0423 // vim: et sw=4: