File indexing completed on 2024-11-24 04:48:17
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 1998 Preston Brown <pbrown@kde.org> 0005 SPDX-FileCopyrightText: 2000, 2001 Cornelius Schumacher <schumacher@kde.org> 0006 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0009 */ 0010 0011 #include "searchdialog.h" 0012 0013 #include "calendarview.h" 0014 #include "koeventpopupmenu.h" 0015 #include "korganizer_debug.h" 0016 #include "ui_searchdialog_base.h" 0017 0018 #include <EventViews/ListView> 0019 #include <PimCommon/PimUtil> 0020 0021 #include <KConfigGroup> 0022 #include <KMessageBox> 0023 #include <KSharedConfig> 0024 #include <KWindowConfig> 0025 #include <QWindow> 0026 0027 #include <QDialogButtonBox> 0028 #include <QPushButton> 0029 0030 namespace 0031 { 0032 static const char mySearchDialogConfigGroupName[] = "SearchDialog"; 0033 } 0034 0035 SearchDialog::SearchDialog(CalendarView *calendarview) 0036 : QDialog(calendarview) 0037 , m_ui(new Ui::SearchDialog) 0038 , m_calendarview(calendarview) 0039 { 0040 setWindowTitle(i18nc("@title:window", "Find in Calendars")); 0041 setModal(false); 0042 0043 auto mainLayout = new QVBoxLayout(this); 0044 0045 auto mainWidget = new QWidget(this); 0046 m_ui->setupUi(mainWidget); 0047 0048 // Set nice initial start and end dates for the search 0049 const QDate currDate = QDate::currentDate(); 0050 m_ui->startDate->setDate(currDate); 0051 m_ui->endDate->setDate(currDate.addYears(1)); 0052 0053 connect(m_ui->searchEdit, &QLineEdit::textChanged, this, &SearchDialog::searchPatternChanged); 0054 0055 connect(m_ui->dateRangeCheckbox, &QCheckBox::toggled, this, &SearchDialog::dateRangeCheckboxToggled); 0056 dateRangeCheckboxToggled(); 0057 0058 // Results list view 0059 auto layout = new QVBoxLayout; 0060 layout->setContentsMargins({}); 0061 m_listView = new EventViews::ListView(this); 0062 layout->addWidget(m_listView); 0063 m_ui->listViewFrame->setLayout(layout); 0064 0065 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close | QDialogButtonBox::Help, this); 0066 mainLayout->addWidget(mainWidget); 0067 m_user1Button = new QPushButton; 0068 buttonBox->addButton(m_user1Button, QDialogButtonBox::ActionRole); 0069 connect(buttonBox, &QDialogButtonBox::rejected, this, &SearchDialog::reject); 0070 connect(buttonBox, &QDialogButtonBox::helpRequested, this, &SearchDialog::slotHelpRequested); 0071 mainLayout->addWidget(buttonBox); 0072 m_user1Button->setDefault(true); 0073 KGuiItem::assign(m_user1Button, KGuiItem(i18nc("@action:button search in calendar", "&Search"))); 0074 m_user1Button->setIcon(QIcon::fromTheme(QStringLiteral("search"))); 0075 m_user1Button->setToolTip(i18nc("@info:tooltip", "Start the search")); 0076 m_user1Button->setWhatsThis(i18nc("@info:whatsthis", "Press this button to start the search.")); 0077 0078 connect(m_user1Button, &QPushButton::clicked, this, &SearchDialog::doSearch); 0079 0080 // Propagate edit and delete event signals from event list view 0081 connect(m_listView, &EventViews::ListView::showIncidenceSignal, this, &SearchDialog::showIncidenceSignal); 0082 connect(m_listView, &EventViews::ListView::editIncidenceSignal, this, &SearchDialog::editIncidenceSignal); 0083 connect(m_listView, &EventViews::ListView::deleteIncidenceSignal, this, &SearchDialog::deleteIncidenceSignal); 0084 0085 m_popupMenu = new KOEventPopupMenu(KOEventPopupMenu::MiniList, this); 0086 connect(m_listView, &EventViews::ListView::showIncidencePopupSignal, m_popupMenu, &KOEventPopupMenu::showIncidencePopup); 0087 0088 connect(m_popupMenu, &KOEventPopupMenu::showIncidenceSignal, this, &SearchDialog::showIncidenceSignal); 0089 connect(m_popupMenu, &KOEventPopupMenu::editIncidenceSignal, this, &SearchDialog::editIncidenceSignal); 0090 connect(m_popupMenu, &KOEventPopupMenu::deleteIncidenceSignal, this, &SearchDialog::deleteIncidenceSignal); 0091 // TODO: add these 0092 // connect(m_popupMenu, &KOEventPopupMenu::toggleAlarmSignal, this, 0093 // &SearchDialog::toggleAlarmSignal); 0094 // connect(m_popupMenu, &KOEventPopupMenu::toggleTodoCompletedSignal, this, 0095 // &SearchDialog::toggleTodoCompletedSignal); 0096 0097 for (const auto &calendar : m_calendarview->enabledCalendars()) { 0098 m_listView->addCalendar(calendar); 0099 } 0100 0101 readConfig(); 0102 } 0103 0104 SearchDialog::~SearchDialog() 0105 { 0106 writeConfig(); 0107 delete m_popupMenu; 0108 delete m_ui; 0109 } 0110 0111 void SearchDialog::showEvent(QShowEvent *event) 0112 { 0113 Q_UNUSED(event) 0114 activateWindow(); 0115 m_ui->searchEdit->setFocus(); 0116 } 0117 0118 void SearchDialog::searchPatternChanged(const QString &pattern) 0119 { 0120 m_user1Button->setEnabled(!pattern.isEmpty()); 0121 } 0122 0123 void SearchDialog::dateRangeCheckboxToggled() 0124 { 0125 m_ui->startDate->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0126 m_ui->startDateLabel->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0127 m_ui->endDate->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0128 m_ui->endDateLabel->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0129 m_ui->inclusiveCheck->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0130 m_ui->includeUndatedTodos->setEnabled(m_ui->dateRangeCheckbox->isChecked()); 0131 } 0132 0133 void SearchDialog::doSearch() 0134 { 0135 QRegularExpression re; 0136 re.setPatternOptions(QRegularExpression::CaseInsensitiveOption); 0137 QRegularExpression::WildcardConversionOptions options; 0138 options |= QRegularExpression::UnanchoredWildcardConversion; 0139 options |= QRegularExpression::NonPathWildcardConversion; 0140 const QString pattern = QRegularExpression::wildcardToRegularExpression(m_ui->searchEdit->text(), options); 0141 re.setPattern(pattern); 0142 if (!re.isValid()) { 0143 KMessageBox::error(this, 0144 i18nc("@info", 0145 "Invalid search expression, cannot perform the search. " 0146 "Please enter a search expression using the wildcard characters " 0147 "'*' and '?' where needed.")); 0148 return; 0149 } 0150 0151 search(re); 0152 m_listView->showIncidences(m_matchedEvents, QDate()); 0153 updateMatchesText(); 0154 if (m_matchedEvents.isEmpty()) { 0155 m_ui->numItems->setText(QString()); 0156 KMessageBox::information(this, 0157 i18nc("@info", "No items were found that match your search pattern."), 0158 i18nc("@title:window", "Search Results"), 0159 QStringLiteral("NoSearchResults")); 0160 } 0161 } 0162 0163 void SearchDialog::popupMenu(const QPoint &point) 0164 { 0165 m_listView->popupMenu(point); 0166 } 0167 0168 void SearchDialog::updateMatchesText() 0169 { 0170 if (m_matchedEvents.isEmpty()) { 0171 m_ui->numItems->setText(QString()); 0172 } else { 0173 m_ui->numItems->setText(i18ncp("@label", "%1 match", "%1 matches", m_matchedEvents.count())); 0174 } 0175 } 0176 0177 void SearchDialog::updateView() 0178 { 0179 QRegularExpression re; 0180 re.setPatternOptions(QRegularExpression::CaseInsensitiveOption); 0181 re.setPattern(m_ui->searchEdit->text()); 0182 m_listView->clear(); 0183 if (re.isValid()) { 0184 search(re); 0185 } else { 0186 m_matchedEvents.clear(); 0187 } 0188 m_listView->showIncidences(m_matchedEvents, QDate()); 0189 updateMatchesText(); 0190 } 0191 0192 void SearchDialog::search(const QRegularExpression ®ularExpression) 0193 { 0194 const QDate startDt = m_ui->startDate->date(); 0195 const QDate endDt = m_ui->endDate->date(); 0196 0197 KCalendarCore::Event::List events; 0198 KCalendarCore::Todo::List todos; 0199 KCalendarCore::Journal::List journals; 0200 for (const auto &calendar : m_calendarview->enabledCalendars()) { 0201 if (m_ui->dateRangeCheckbox->isChecked()) { 0202 if (m_ui->eventsCheck->isChecked()) { 0203 events += calendar->events(startDt, endDt, QTimeZone::systemTimeZone(), m_ui->inclusiveCheck->isChecked()); 0204 } 0205 if (m_ui->includeUndatedTodos->isChecked()) { 0206 for (const KCalendarCore::Todo::Ptr &todo : calendar->todos()) { 0207 Q_ASSERT(todo); 0208 if ((!todo->hasStartDate() && !todo->hasDueDate()) // undated 0209 || (todo->hasStartDate() && (todo->dtStart().toLocalTime().date() >= startDt) 0210 && (todo->dtStart().toLocalTime().date() <= endDt)) // start dt in range 0211 || (todo->hasDueDate() && (todo->dtDue().toLocalTime().date() >= startDt) 0212 && (todo->dtDue().toLocalTime().date() <= endDt)) // due dt in range 0213 || (todo->hasCompletedDate() && (todo->completed().toLocalTime().date() >= startDt) 0214 && (todo->completed().toLocalTime().date() <= endDt))) { // completed dt in range 0215 todos.append(todo); 0216 } 0217 } 0218 } else { 0219 todos += calendar->todos(startDt, endDt, QTimeZone::systemTimeZone(), m_ui->inclusiveCheck->isChecked()); 0220 } 0221 0222 if (m_ui->journalsCheck->isChecked()) { 0223 for (auto dt = startDt; dt <= endDt; dt = dt.addDays(1)) { 0224 journals += calendar->journals(dt); 0225 } 0226 } 0227 0228 } else { 0229 if (m_ui->eventsCheck->isChecked()) { 0230 events += calendar->events(); 0231 } 0232 if (m_ui->todosCheck->isChecked()) { 0233 todos += calendar->todos(); 0234 } 0235 if (m_ui->journalsCheck->isChecked()) { 0236 journals += calendar->journals(); 0237 } 0238 } 0239 } 0240 0241 m_matchedEvents.clear(); 0242 const KCalendarCore::Incidence::List incidences = Akonadi::ETMCalendar::mergeIncidenceList(events, todos, journals); 0243 for (const KCalendarCore::Incidence::Ptr &ev : incidences) { 0244 Q_ASSERT(ev); 0245 auto collectionId = ev->customProperty("VOLATILE", "COLLECTION-ID").toLongLong(); 0246 Akonadi::Item item; 0247 for (const auto &calendar : m_calendarview->enabledCalendars()) { 0248 if (calendar->collection().id() == collectionId) { 0249 item = calendar->item(ev); 0250 } 0251 } 0252 if (!item.isValid()) { 0253 qCWarning(KORGANIZER_LOG) << "Failed to translate incidence " << ev->uid() << " to Akonadi Item"; 0254 continue; 0255 } 0256 0257 if (m_ui->summaryCheck->isChecked()) { 0258 if (regularExpression.match(ev->summary()).hasMatch()) { 0259 m_matchedEvents.append(item); 0260 continue; 0261 } 0262 } 0263 if (m_ui->descriptionCheck->isChecked()) { 0264 if (regularExpression.match(ev->description()).hasMatch()) { 0265 m_matchedEvents.append(item); 0266 continue; 0267 } 0268 } 0269 if (m_ui->categoryCheck->isChecked()) { 0270 if (regularExpression.match(ev->categoriesStr()).hasMatch()) { 0271 m_matchedEvents.append(item); 0272 continue; 0273 } 0274 } 0275 if (m_ui->locationCheck->isChecked()) { 0276 if (regularExpression.match(ev->location()).hasMatch()) { 0277 m_matchedEvents.append(item); 0278 continue; 0279 } 0280 } 0281 if (m_ui->attendeeCheck->isChecked()) { 0282 const KCalendarCore::Attendee::List lstAttendees = ev->attendees(); 0283 for (const KCalendarCore::Attendee &attendee : lstAttendees) { 0284 if (regularExpression.match(attendee.fullName()).hasMatch()) { 0285 m_matchedEvents.append(item); 0286 break; 0287 } 0288 } 0289 } 0290 } 0291 } 0292 0293 void SearchDialog::readConfig() 0294 { 0295 create(); // ensure a window is created 0296 windowHandle()->resize(QSize(775, 600)); 0297 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(mySearchDialogConfigGroupName)); 0298 KWindowConfig::restoreWindowSize(windowHandle(), group); 0299 resize(windowHandle()->size()); // workaround for QTBUG-40584 0300 0301 m_listView->readSettings(group); 0302 } 0303 0304 void SearchDialog::writeConfig() 0305 { 0306 KConfigGroup group(KSharedConfig::openStateConfig(), QLatin1StringView(mySearchDialogConfigGroupName)); 0307 KWindowConfig::saveWindowSize(windowHandle(), group); 0308 m_listView->writeSettings(group); 0309 group.sync(); 0310 } 0311 0312 void SearchDialog::slotHelpRequested() 0313 { 0314 PimCommon::Util::invokeHelp(QStringLiteral("korganizer/search-view.html")); 0315 } 0316 0317 #include "moc_searchdialog.cpp"