1 /*
2     kftabdlg.cpp
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 
6 */
7 
8 #include "kftabdlg.h"
9 
10 #include <QApplication>
11 #include <QButtonGroup>
12 #include <QCheckBox>
13 #include <QDesktopWidget>
14 #include <QFileDialog>
15 #include <QLabel>
16 #include <QMimeDatabase>
17 #include <QPushButton>
18 #include <QRadioButton>
19 #include <QSpinBox>
20 #include <QStandardPaths>
21 #include <QWhatsThis>
22 #include <QtConcurrent/QtConcurrentRun>
23 #include <QFutureWatcher>
24 #include <QGridLayout>
25 
26 #include <KComboBox>
27 #include <KConfigGroup>
28 #include <KDateComboBox>
29 #include <KLineEdit>
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 #include <KSharedConfig>
33 #include <KShell>
34 #include <KUrlComboBox>
35 #include <KUrlCompletion>
36 
37 #include <algorithm>
38 
39 #include "kquery.h"
40 
41 // Static utility functions
42 static void save_pattern(KComboBox *, const QString &, const QString &);
43 
44 static const int specialMimeTypeCount = 10;
45 
46 struct MimeTypes
47 {
48     QVector<QMimeType> all;
49     QStringList image;
50     QStringList video;
51     QStringList audio;
52 };
53 
KfindTabWidget(QWidget * parent)54 KfindTabWidget::KfindTabWidget(QWidget *parent)
55     : QTabWidget(parent)
56 {
57     // ************ Page One ************
58 
59     pages[0] = new QWidget;
60     pages[0]->setObjectName(QStringLiteral("page1"));
61 
62     nameBox = new KComboBox(pages[0]);
63     nameBox->setEditable(true);
64     nameBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);  // allow smaller than widest entry
65     QLabel *namedL = new QLabel(i18nc("this is the label for the name textfield", "&Named:"), pages[0]);
66     namedL->setBuddy(nameBox);
67     namedL->setObjectName(QStringLiteral("named"));
68     namedL->setToolTip(i18n("You can use wildcard matching and \";\" for separating multiple names"));
69     dirBox = new KUrlComboBox(KUrlComboBox::Directories, pages[0]);
70     dirBox->setEditable(true);
71     dirBox->setCompletionObject(new KUrlCompletion(KUrlCompletion::DirCompletion));
72     dirBox->setAutoDeleteCompletionObject(true);
73     dirBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);  // allow smaller than widest entry
74     QLabel *lookinL = new QLabel(i18n("Look &in:"), pages[0]);
75     lookinL->setBuddy(dirBox);
76     lookinL->setObjectName(QStringLiteral("lookin"));
77     subdirsCb = new QCheckBox(i18n("Include &subfolders"), pages[0]);
78     caseSensCb = new QCheckBox(i18n("Case s&ensitive search"), pages[0]);
79     browseB = new QPushButton(i18n("&Browse..."), pages[0]);
80     useLocateCb = new QCheckBox(i18n("&Use files index"), pages[0]);
81     hiddenFilesCb = new QCheckBox(i18n("Show &hidden files"), pages[0]);
82     // Setup
83 
84     subdirsCb->setChecked(true);
85     caseSensCb->setChecked(false);
86     useLocateCb->setChecked(false);
87     hiddenFilesCb->setChecked(false);
88     if (QStandardPaths::findExecutable(QStringLiteral("locate")).isEmpty()) {
89         useLocateCb->setEnabled(false);
90     }
91 
92     nameBox->setDuplicatesEnabled(false);
93     nameBox->setFocus();
94     dirBox->setDuplicatesEnabled(false);
95 
96     nameBox->setInsertPolicy(QComboBox::InsertAtTop);
97     dirBox->setInsertPolicy(QComboBox::InsertAtTop);
98 
99     const QString nameWhatsThis
100         = i18n("<qt>Enter the filename you are looking for. <br />"
101                "Alternatives may be separated by a semicolon \";\".<br />"
102                "<br />"
103                "The filename may contain the following special characters:"
104                "<ul>"
105                "<li><b>?</b> matches any single character</li>"
106                "<li><b>*</b> matches zero or more of any characters</li>"
107                "<li><b>[...]</b> matches any of the characters between the braces</li>"
108                "</ul>"
109                "<br />"
110                "Example searches:"
111                "<ul>"
112                "<li><b>*.kwd;*.txt</b> finds all files ending with .kwd or .txt</li>"
113                "<li><b>go[dt]</b> finds god and got</li>"
114                "<li><b>Hel?o</b> finds all files that start with \"Hel\" and end with \"o\", "
115                "having one character in between</li>"
116                "<li><b>My Document.kwd</b> finds a file of exactly that name</li>"
117                "</ul></qt>");
118     nameBox->setWhatsThis(nameWhatsThis);
119     namedL->setWhatsThis(nameWhatsThis);
120     const QString whatsfileindex
121         = i18n("<qt>This lets you use the files' index created by the <i>slocate</i> "
122                "package to speed-up the search; remember to update the index from time to time "
123                "(using <i>updatedb</i>)."
124                "</qt>");
125     useLocateCb->setWhatsThis(whatsfileindex);
126 
127     // Layout
128 
129     QGridLayout *grid = new QGridLayout(pages[0]);
130     QVBoxLayout *subgrid = new QVBoxLayout();
131     grid->addWidget(namedL, 0, 0);
132     grid->addWidget(nameBox, 0, 1, 1, 3);
133     grid->addWidget(lookinL, 1, 0);
134     grid->addWidget(dirBox, 1, 1);
135     grid->addWidget(browseB, 1, 2);
136     grid->setColumnStretch(1, 1);
137     grid->addLayout(subgrid, 2, 1, 1, 2);
138 
139     QHBoxLayout *layoutOne = new QHBoxLayout();
140     layoutOne->addWidget(subdirsCb);
141     layoutOne->addWidget(hiddenFilesCb);
142 
143     QHBoxLayout *layoutTwo = new QHBoxLayout();
144     layoutTwo->addWidget(caseSensCb);
145     layoutTwo->addWidget(useLocateCb);
146 
147     subgrid->addLayout(layoutOne);
148     subgrid->addLayout(layoutTwo);
149 
150     subgrid->addStretch(1);
151 
152     // Signals
153 
154     connect(browseB, &QPushButton::clicked, this, &KfindTabWidget::getDirectory);
155 
156     connect(nameBox, QOverload<const QString &>::of(&KComboBox::returnPressed), this, &KfindTabWidget::startSearch);
157 
158     connect(dirBox, QOverload<const QString &>::of(&KUrlComboBox::returnPressed), this, &KfindTabWidget::startSearch);
159 
160     // ************ Page Two
161 
162     pages[1] = new QWidget;
163     pages[1]->setObjectName(QStringLiteral("page2"));
164 
165     findCreated = new QCheckBox(i18n("Find all files created or &modified:"), pages[1]);
166     bg = new QButtonGroup();
167     rb[0] = new QRadioButton(i18n("&between"), pages[1]);
168     rb[1] = new QRadioButton(pages[1]); // text set in updateDateLabels
169     andL = new QLabel(i18n("and"), pages[1]);
170     andL->setObjectName(QStringLiteral("and"));
171     betweenType = new KComboBox(pages[1]);
172     betweenType->setObjectName(QStringLiteral("comboBetweenType"));
173     betweenType->addItems(QVector<QString>(5).toList()); // texts set in updateDateLabels
174     betweenType->setCurrentIndex(1);
175     updateDateLabels(1, 1);
176 
177     const QDate dt = QDate::currentDate().addYears(-1);
178 
179     fromDate = new KDateComboBox(pages[1]);
180     fromDate->setObjectName(QStringLiteral("fromDate"));
181     fromDate->setDate(dt);
182     toDate = new KDateComboBox(pages[1]);
183     toDate->setObjectName(QStringLiteral("toDate"));
184     timeBox = new QSpinBox(pages[1]);
185     timeBox->setRange(1, 60);
186     timeBox->setSingleStep(1);
187     timeBox->setObjectName(QStringLiteral("timeBox"));
188 
189     sizeBox = new KComboBox(pages[1]);
190     sizeBox->setObjectName(QStringLiteral("sizeBox"));
191     QLabel *sizeL = new QLabel(i18n("File &size is:"), pages[1]);
192     sizeL->setBuddy(sizeBox);
193     sizeEdit = new QSpinBox(pages[1]);
194     sizeEdit->setRange(0, INT_MAX);
195     sizeEdit->setSingleStep(1);
196     sizeEdit->setObjectName(QStringLiteral("sizeEdit"));
197     sizeEdit->setValue(1);
198     sizeUnitBox = new KComboBox(pages[1]);
199     sizeUnitBox->setObjectName(QStringLiteral("sizeUnitBox"));
200 
201     m_usernameBox = new KComboBox(pages[1]);
202     m_usernameBox->setEditable(true);
203     m_usernameBox->setObjectName(QStringLiteral("m_combo1"));
204     QLabel *usernameLabel = new QLabel(i18n("Files owned by &user:"), pages[1]);
205     usernameLabel->setBuddy(m_usernameBox);
206     m_groupBox = new KComboBox(pages[1]);
207     m_groupBox->setEditable(true);
208     m_groupBox->setObjectName(QStringLiteral("m_combo2"));
209     QLabel *groupLabel = new QLabel(i18n("Owned by &group:"), pages[1]);
210     groupLabel->setBuddy(m_groupBox);
211 
212     sizeBox->addItem(i18nc("file size isn't considered in the search", "(none)"));
213     sizeBox->addItem(i18n("At Least"));
214     sizeBox->addItem(i18n("At Most"));
215     sizeBox->addItem(i18n("Equal To"));
216 
217     sizeUnitBox->addItem(i18np("Byte", "Bytes", 1));
218     sizeUnitBox->addItem(i18n("KiB"));
219     sizeUnitBox->addItem(i18n("MiB"));
220     sizeUnitBox->addItem(i18n("GiB"));
221     sizeUnitBox->setCurrentIndex(1);
222 
223     int tmp = sizeEdit->fontMetrics().boundingRect(QStringLiteral(" 000000000 ")).width();
224     sizeEdit->setMinimumSize(tmp, sizeEdit->sizeHint().height());
225 
226     m_usernameBox->setDuplicatesEnabled(false);
227     m_groupBox->setDuplicatesEnabled(false);
228     m_usernameBox->setInsertPolicy(QComboBox::InsertAtTop);
229     m_groupBox->setInsertPolicy(QComboBox::InsertAtTop);
230 
231     // Setup
232     rb[0]->setChecked(true);
233     bg->addButton(rb[0]);
234     bg->addButton(rb[1]);
235 
236     // Layout
237 
238     QGridLayout *grid1 = new QGridLayout(pages[1]);
239 
240     grid1->addWidget(findCreated, 0, 0, 1, 3);
241 
242     grid1->addWidget(rb[0], 1, 1);
243     grid1->addWidget(fromDate, 1, 2);
244     grid1->addWidget(andL, 1, 3, Qt::AlignHCenter);
245     grid1->addWidget(toDate, 1, 4);
246 
247     grid1->addWidget(rb[1], 2, 1);
248     grid1->addWidget(timeBox, 2, 2, 1, 2);
249     grid1->addWidget(betweenType, 2, 4);
250 
251     grid1->addWidget(sizeL, 3, 0, 1, 2);
252     grid1->addWidget(sizeBox, 3, 2);
253     grid1->addWidget(sizeEdit, 3, 3);
254     grid1->addWidget(sizeUnitBox, 3, 4);
255 
256     grid1->addWidget(usernameLabel, 4, 0, 1, 2);
257     grid1->addWidget(m_usernameBox, 4, 2);
258     grid1->addWidget(groupLabel, 4, 3);
259     grid1->addWidget(m_groupBox, 4, 4);
260 
261     for (int c = 1; c <= 4; c++) {
262         grid1->setColumnStretch(c, 1);
263     }
264 
265     grid1->setRowStretch(6, 1);
266 
267     // Connect
268     connect(findCreated, &QCheckBox::toggled, this, &KfindTabWidget::fixLayout);
269     connect(bg, static_cast<void (QButtonGroup::*)(QAbstractButton *)>(&QButtonGroup::buttonClicked), this, &KfindTabWidget::fixLayout);
270     connect(sizeBox, static_cast<void (KComboBox::*)(int)>(&KComboBox::activated), this, &KfindTabWidget::slotSizeBoxChanged);
271     connect(timeBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KfindTabWidget::slotUpdateDateLabelsForNumber);
272     connect(betweenType, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &KfindTabWidget::slotUpdateDateLabelsForType);
273     connect(sizeEdit, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KfindTabWidget::slotUpdateByteComboBox);
274 
275     // ************ Page Three
276 
277     pages[2] = new QWidget;
278     pages[2]->setObjectName(QStringLiteral("page3"));
279 
280     typeBox = new KComboBox(pages[2]);
281     typeBox->setObjectName(QStringLiteral("typeBox"));
282     typeBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);  // allow smaller than widest entry
283     QLabel *typeL = new QLabel(i18nc("label for the file type combobox", "File &type:"), pages[2]);
284     typeL->setBuddy(typeBox);
285     textEdit = new KLineEdit(pages[2]);
286     textEdit->setClearButtonEnabled(true);
287     textEdit->setObjectName(QStringLiteral("textEdit"));
288     QLabel *textL = new QLabel(i18n("C&ontaining text:"), pages[2]);
289     textL->setBuddy(textEdit);
290 
291     connect(textEdit, &QLineEdit::returnPressed, this, &KfindTabWidget::startSearch);
292 
293     const QString containingtext
294         = i18n("<qt>If specified, only files that contain this text"
295                " are found. Note that not all file types from the list"
296                " above are supported. Please refer to the documentation"
297                " for a list of supported file types."
298                "</qt>");
299     textEdit->setToolTip(containingtext);
300     textL->setWhatsThis(containingtext);
301 
302     caseContextCb = new QCheckBox(i18n("Case s&ensitive"), pages[2]);
303     binaryContextCb = new QCheckBox(i18n("Include &binary files"), pages[2]);
304 
305     const QString binaryTooltip
306         = i18n("<qt>This lets you search in any type of file, "
307                "even those that usually do not contain text (for example "
308                "program files and images).</qt>");
309     binaryContextCb->setToolTip(binaryTooltip);
310 
311     metainfokeyEdit = new KLineEdit(pages[2]);
312     metainfoEdit = new KLineEdit(pages[2]);
313     QLabel *textMetaInfo = new QLabel(i18nc("as in search for", "fo&r:"), pages[2]);
314     textMetaInfo->setBuddy(metainfoEdit);
315     QLabel *textMetaKey = new QLabel(i18n("Search &metainfo sections:"), pages[2]);
316     textMetaKey->setBuddy(metainfokeyEdit);
317 
318     // Setup
319     typeBox->addItem(i18n("All Files & Folders"));
320     typeBox->addItem(i18n("Files"));
321     typeBox->addItem(i18n("Folders"));
322     typeBox->addItem(i18n("Symbolic Links"));
323     typeBox->addItem(i18n("Special Files (Sockets, Device Files, ...)"));
324     typeBox->addItem(i18n("Executable Files"));
325     typeBox->addItem(i18n("SUID Executable Files"));
326     typeBox->addItem(i18n("All Images"));
327     typeBox->addItem(i18n("All Video"));
328     typeBox->addItem(i18n("All Sounds"));
329     typeBox->insertSeparator(typeBox->count()); // append separator
330 
331     auto mimeTypeFuture = QtConcurrent::run([] {
332         MimeTypes mimeTypes;
333 
334         const auto types = QMimeDatabase().allMimeTypes();
335         for (const QMimeType &type : types) {
336             if ((!type.comment().isEmpty())
337                 && (!type.name().startsWith(QLatin1String("kdedevice/")))
338                 && (!type.name().startsWith(QLatin1String("all/")))) {
339                 mimeTypes.all.append(type);
340 
341                 if (type.name().startsWith(QLatin1String("image/"))) {
342                     mimeTypes.image.append(type.name());
343                 } else if (type.name().startsWith(QLatin1String("video/"))) {
344                     mimeTypes.video.append(type.name());
345                 } else if (type.name().startsWith(QLatin1String("audio/"))) {
346                     mimeTypes.audio.append(type.name());
347                 }
348             }
349         }
350 
351         std::sort(mimeTypes.all.begin(), mimeTypes.all.end(), [](const QMimeType &lhs, const QMimeType &rhs) {
352             return lhs.comment() < rhs.comment();
353         });
354 
355         return mimeTypes;
356     });
357 
358     auto *watcher = new QFutureWatcher<MimeTypes>(this);
359     watcher->setFuture(mimeTypeFuture);
360     connect(watcher, &QFutureWatcher<MimeTypes>::finished, this, [this, watcher] {
361         const MimeTypes &mimeTypes = watcher->result();
362 
363         for (const auto &mime : std::as_const(mimeTypes.all)) {
364             typeBox->addItem(mime.comment());
365         }
366 
367         m_types = mimeTypes.all;
368 
369         m_ImageTypes = mimeTypes.image;
370         m_VideoTypes = mimeTypes.video;
371         m_AudioTypes = mimeTypes.audio;
372 
373         watcher->deleteLater();
374     });
375 
376     // Layout
377     tmp = sizeEdit->fontMetrics().boundingRect(QStringLiteral(" 00000 ")).width();
378     sizeEdit->setMinimumSize(tmp, sizeEdit->sizeHint().height());
379 
380     QGridLayout *grid2 = new QGridLayout(pages[2]);
381     grid2->addWidget(typeL, 0, 0);
382     grid2->addWidget(textL, 1, 0);
383     grid2->addWidget(typeBox, 0, 1, 1, 3);
384     grid2->addWidget(textEdit, 1, 1, 1, 3);
385     grid2->addWidget(caseContextCb, 2, 1);
386     grid2->addWidget(binaryContextCb, 3, 1);
387 
388     grid2->addWidget(textMetaKey, 4, 0);
389     grid2->addWidget(metainfokeyEdit, 4, 1);
390     grid2->addWidget(textMetaInfo, 4, 2, Qt::AlignHCenter);
391     grid2->addWidget(metainfoEdit, 4, 3);
392 
393     metainfokeyEdit->setText(QStringLiteral("*"));
394 
395     addTab(pages[0], i18n("Name/&Location"));
396     addTab(pages[2], i18nc("tab name: search by contents", "C&ontents"));
397     addTab(pages[1], i18n("&Properties"));
398 
399     // Setup
400     const QString whatsmetainfo
401         = i18n("<qt>Search within files' specific comments/metainfo<br />"
402                "These are some examples:<br />"
403                "<ul>"
404                "<li><b>Audio files (mp3...)</b> Search in id3 tag for a title, an album</li>"
405                "<li><b>Images (png...)</b> Search images with a special resolution, comment...</li>"
406                "</ul>"
407                "</qt>");
408     const QString whatsmetainfokey
409         = i18n("<qt>If specified, search only in this field<br />"
410                "<ul>"
411                "<li><b>Audio files (mp3...)</b> This can be Title, Album...</li>"
412                "<li><b>Images (png...)</b> Search only in Resolution, Bitdepth...</li>"
413                "</ul>"
414                "</qt>");
415     textMetaInfo->setWhatsThis(whatsmetainfo);
416     metainfoEdit->setToolTip(whatsmetainfo);
417     textMetaKey->setWhatsThis(whatsmetainfokey);
418     metainfokeyEdit->setToolTip(whatsmetainfokey);
419 
420     fixLayout();
421     loadHistory();
422 }
423 
~KfindTabWidget()424 KfindTabWidget::~KfindTabWidget()
425 {
426     delete pages[0];
427     delete pages[1];
428     delete pages[2];
429     delete bg;
430 }
431 
setURL(const QUrl & url)432 void KfindTabWidget::setURL(const QUrl &url)
433 {
434     KConfigGroup conf(KSharedConfig::openConfig(), "History");
435     m_url = url;
436     QStringList sl = conf.readPathEntry("Directories", QStringList());
437     dirBox->clear(); // make sure there is no old Stuff in there
438 
439     if (!sl.isEmpty()) {
440         dirBox->addItems(sl);
441         // If the _searchPath already exists in the list we do not
442         // want to add it again
443         int indx = sl.indexOf(m_url.toDisplayString());
444         if (indx == -1) {
445             dirBox->insertItem(0, m_url.toDisplayString()); // make it the first one
446             dirBox->setCurrentIndex(0);
447         } else {
448             dirBox->setCurrentIndex(indx);
449         }
450     } else {
451         fillDirBox();
452     }
453 }
454 
fillDirBox()455 void KfindTabWidget::fillDirBox()
456 {
457     QDir m_dir(QStringLiteral("/lib"));
458     dirBox->insertItem(0, m_url.toDisplayString());
459     dirBox->addUrl(QUrl::fromLocalFile(QDir::homePath()));
460     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/")));
461     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/usr")));
462     if (m_dir.exists()) {
463         dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("lib")));
464     }
465     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/home")));
466     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/etc")));
467     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/var")));
468     dirBox->addUrl(QUrl::fromLocalFile(QStringLiteral("/mnt")));
469     dirBox->setCurrentIndex(0);
470 }
471 
saveHistory()472 void KfindTabWidget::saveHistory()
473 {
474     save_pattern(nameBox, QStringLiteral("History"), QStringLiteral("Patterns"));
475     save_pattern(dirBox, QStringLiteral("History"), QStringLiteral("Directories"));
476 }
477 
loadHistory()478 void KfindTabWidget::loadHistory()
479 {
480     // Load pattern history
481     KConfigGroup conf(KSharedConfig::openConfig(), QStringLiteral("History"));
482     QStringList sl = conf.readEntry(QStringLiteral("Patterns"), QStringList());
483     if (!sl.isEmpty()) {
484         nameBox->addItems(sl);
485     } else {
486         nameBox->addItem(QStringLiteral("*"));
487     }
488 
489     sl = conf.readPathEntry(QStringLiteral("Directories"), QStringList());
490     if (!sl.isEmpty()) {
491         dirBox->addItems(sl);
492         // If the _searchPath already exists in the list we do not
493         // want to add it again
494         int indx = sl.indexOf(m_url.toDisplayString());
495         if (indx == -1) {
496             dirBox->insertItem(0, m_url.toDisplayString()); // make it the first one
497             dirBox->setCurrentIndex(0);
498         } else {
499             dirBox->setCurrentIndex(indx);
500         }
501     } else {
502         fillDirBox();
503     }
504 }
505 
setFocus()506 void KfindTabWidget::setFocus()
507 {
508     nameBox->setFocus();
509     nameBox->lineEdit()->selectAll();
510 }
511 
slotSizeBoxChanged(int index)512 void KfindTabWidget::slotSizeBoxChanged(int index)
513 {
514     sizeEdit->setEnabled((bool)(index != 0));
515     sizeUnitBox->setEnabled((bool)(index != 0));
516 }
517 
setDefaults()518 void KfindTabWidget::setDefaults()
519 {
520     const QDate dt = QDate::currentDate().addYears(-1);
521 
522     fromDate->setDate(dt);
523     toDate->setDate(QDate::currentDate());
524 
525     timeBox->setValue(1);
526     betweenType->setCurrentIndex(1);
527 
528     typeBox->setCurrentIndex(0);
529     sizeBox->setCurrentIndex(0);
530     sizeUnitBox->setCurrentIndex(1);
531     sizeEdit->setValue(1);
532 }
533 
534 /*
535   Checks if dates are correct and popups a error box
536   if they are not.
537 */
isDateValid()538 bool KfindTabWidget::isDateValid()
539 {
540     // All files
541     if (!findCreated->isChecked()) {
542         return true;
543     }
544 
545     if (rb[1]->isChecked()) {
546         if (timeBox->value() > 0) {
547             return true;
548         }
549 
550         KMessageBox::sorry(this, i18n("Unable to search within a period which is less than a minute."));
551         return false;
552     }
553 
554     // If we can not parse either of the dates or
555     // "from" date is bigger than "to" date return false.
556     const QDate from = fromDate->date();
557     const QDate to = toDate->date();
558 
559     QString str;
560     if (!from.isValid() || !to.isValid()) {
561         str = i18n("The date is not valid.");
562     } else if (from > to) {
563         str = i18n("Invalid date range.");
564     } else if (from > QDate::currentDate()) {
565         str = i18n("Unable to search dates in the future.");
566     }
567 
568     if (!str.isEmpty()) {
569         KMessageBox::sorry(nullptr, str);
570         return false;
571     }
572     return true;
573 }
574 
setQuery(KQuery * query)575 void KfindTabWidget::setQuery(KQuery *query)
576 {
577     KIO::filesize_t size;
578     KIO::filesize_t sizeunit;
579     bool itemAlreadyContained(false);
580     // only start if we have valid dates
581     if (!isDateValid()) {
582         return;
583     }
584 
585     const QString trimmedDirBoxText = dirBox->currentText().trimmed();
586     const QString tildeExpandedPath = KShell::tildeExpand(trimmedDirBoxText);
587 
588     query->setPath(QUrl::fromUserInput(tildeExpandedPath, m_url.toLocalFile(), QUrl::AssumeLocalFile));
589 
590     for (int idx = 0; idx < dirBox->count(); idx++) {
591         if (dirBox->itemText(idx) == dirBox->currentText()) {
592             itemAlreadyContained = true;
593         }
594     }
595 
596     if (!itemAlreadyContained) {
597         dirBox->addItem(dirBox->currentText().trimmed(), 0);
598     }
599 
600     QString regex = nameBox->currentText().isEmpty() ? QStringLiteral("*") : nameBox->currentText();
601     query->setRegExp(regex, caseSensCb->isChecked());
602     itemAlreadyContained = false;
603     for (int idx = 0; idx < nameBox->count(); idx++) {
604         if (nameBox->itemText(idx) == nameBox->currentText()) {
605             itemAlreadyContained = true;
606         }
607     }
608 
609     if (!itemAlreadyContained) {
610         nameBox->addItem(nameBox->currentText(), 0);
611     }
612 
613     query->setRecursive(subdirsCb->isChecked());
614 
615     switch (sizeUnitBox->currentIndex()) {
616     case 0:
617         sizeunit = 1;  //one byte
618         break;
619     case 2:
620         sizeunit = 1048576;  //1Mi
621         break;
622     case 3:
623         sizeunit = 1073741824;  //1Gi
624         break;
625     case 1:  // fall to default case
626     default:
627         sizeunit = 1024; //1Ki
628         break;
629     }
630     size = sizeEdit->value() * sizeunit;
631 
632 // TODO: troeder: do we need this check since it is very unlikely
633 // to exceed ULLONG_MAX with INT_MAX * 1024^3.
634 // Or is there an arch where this can happen?
635 #if 0
636     if (size < 0) { // overflow
637         if (KMessageBox::warningYesNo(this, i18n("Size is too big. Set maximum size value?"), i18n("Error"), i18n("Set"), i18n("Do Not Set"))
638             == KMessageBox::Yes) {
639             sizeEdit->setValue(INT_MAX);
640             sizeUnitBox->setCurrentIndex(0);
641             size = INT_MAX;
642         } else {
643             return;
644         }
645     }
646 #endif
647 
648     // set range mode and size value
649     query->setSizeRange(sizeBox->currentIndex(), size, 0);
650 
651     // dates
652     QDateTime epoch;
653     epoch.setSecsSinceEpoch(0);
654 
655     // Add date predicate
656     if (findCreated->isChecked()) { // Modified
657         if (rb[0]->isChecked()) { // Between dates
658             const QDate &from = fromDate->date();
659             const QDate &to = toDate->date();
660             // do not generate negative numbers .. find doesn't handle that
661             time_t time1 = epoch.secsTo(QDateTime(from.startOfDay()));
662             time_t time2 = epoch.secsTo(QDateTime(to.addDays(1).startOfDay())) - 1; // Include the last day
663 
664             query->setTimeRange(time1, time2);
665         } else {
666             time_t cur = time(nullptr);
667             time_t minutes = cur;
668 
669             switch (betweenType->currentIndex()) {
670             case 0: // minutes
671                 minutes = timeBox->value();
672                 break;
673             case 1: // hours
674                 minutes = 60 * timeBox->value();
675                 break;
676             case 2: // days
677                 minutes = 60 * 24 * timeBox->value();
678                 break;
679             case 3: // months
680                 minutes = 60 * 24 * (time_t)(timeBox->value() * 30.41667);
681                 break;
682             case 4: // years
683                 minutes = 12 * 60 * 24 * (time_t)(timeBox->value() * 30.41667);
684                 break;
685             }
686 
687             query->setTimeRange(cur - minutes * 60, 0);
688         }
689     } else {
690         query->setTimeRange(0, 0);
691     }
692 
693     query->setUsername(m_usernameBox->currentText());
694     query->setGroupname(m_groupBox->currentText());
695 
696     query->setFileType(typeBox->currentIndex());
697 
698     int id = typeBox->currentIndex() - specialMimeTypeCount - 1 /*the separator*/;
699 
700     if ((id >= -4) && (id < (int)m_types.count())) {
701         switch (id) {
702         case -4:
703             query->setMimeType(m_ImageTypes);
704             break;
705         case -3:
706             query->setMimeType(m_VideoTypes);
707             break;
708         case -2:
709             query->setMimeType(m_AudioTypes);
710             break;
711         default:
712             query->setMimeType(QStringList() += m_types.value(id).name());
713         }
714     } else {
715         query->setMimeType(QStringList());
716     }
717 
718     //Metainfo
719     query->setMetaInfo(metainfoEdit->text(), metainfokeyEdit->text());
720 
721     //Use locate to speed-up search ?
722     query->setUseFileIndex(useLocateCb->isChecked());
723 
724     query->setShowHiddenFiles(hiddenFilesCb->isChecked());
725 
726     query->setContext(textEdit->text(), caseContextCb->isChecked(), binaryContextCb->isChecked());
727 }
728 
getDirectory()729 void KfindTabWidget::getDirectory()
730 {
731     const QString result
732         = QFileDialog::getExistingDirectory(this, QString(), dirBox->currentText().trimmed());
733 
734     if (!result.isEmpty()) {
735         for (int i = 0; i < dirBox->count(); i++) {
736             if (result == dirBox->itemText(i)) {
737                 dirBox->setCurrentIndex(i);
738                 return;
739             }
740         }
741         dirBox->insertItem(0, result);
742         dirBox->setCurrentIndex(0);
743     }
744 }
745 
beginSearch()746 void KfindTabWidget::beginSearch()
747 {
748 ///  dirlister->openUrl(KUrl(dirBox->currentText().trimmed()));
749 
750     saveHistory();
751 
752     for(QWidget *page : pages) {
753         page->setEnabled(false);
754     }
755 }
756 
endSearch()757 void KfindTabWidget::endSearch()
758 {
759     for(QWidget *page : pages) {
760         page->setEnabled(true);
761     }
762 }
763 
764 /*
765   Disables/enables all edit fields depending on their
766   respective check buttons.
767 */
fixLayout()768 void KfindTabWidget::fixLayout()
769 {
770     int i;
771     // If "All files" is checked - disable all edits
772     // and second radio group on page two
773 
774     if (!findCreated->isChecked()) {
775         fromDate->setEnabled(false);
776         toDate->setEnabled(false);
777         andL->setEnabled(false);
778         timeBox->setEnabled(false);
779         for (i = 0; i < 2; ++i) {
780             rb[i]->setEnabled(false);
781         }
782         betweenType->setEnabled(false);
783     } else {
784         for (i = 0; i < 2; ++i) {
785             rb[i]->setEnabled(true);
786         }
787 
788         fromDate->setEnabled(rb[0]->isChecked());
789         toDate->setEnabled(rb[0]->isChecked());
790         andL->setEnabled(rb[0]->isEnabled());
791         timeBox->setEnabled(rb[1]->isChecked());
792         betweenType->setEnabled(rb[1]->isChecked());
793     }
794 
795     // Size box on page three
796     sizeEdit->setEnabled(sizeBox->currentIndex() != 0);
797     sizeUnitBox->setEnabled(sizeBox->currentIndex() != 0);
798 }
799 
isSearchRecursive()800 bool KfindTabWidget::isSearchRecursive()
801 {
802     return subdirsCb->isChecked();
803 }
804 
slotUpdateDateLabelsForNumber(int value)805 void KfindTabWidget::slotUpdateDateLabelsForNumber(int value)
806 {
807     updateDateLabels(betweenType->currentIndex(), value);
808 }
809 
slotUpdateDateLabelsForType(int index)810 void KfindTabWidget::slotUpdateDateLabelsForType(int index)
811 {
812     updateDateLabels(index, timeBox->value());
813 }
814 
updateDateLabels(int type,int value)815 void KfindTabWidget::updateDateLabels(int type, int value)
816 {
817     QString typeKey(type == 0 ? QLatin1Char('i') : type == 1 ? QLatin1Char('h') : type == 2 ? QLatin1Char('d') : type == 3 ? QLatin1Char('m') : QLatin1Char('y'));
818     rb[1]->setText(ki18ncp("during the previous minute(s)/hour(s)/...; "
819                            "dynamic context 'type': 'i' minutes, 'h' hours, 'd' days, 'm' months, 'y' years",
820                            "&during the previous", "&during the previous").subs(value).inContext(QStringLiteral("type"), typeKey).toString());
821     betweenType->setItemText(0, i18ncp("use date ranges to search files by modified time", "minute", "minutes", value));
822     betweenType->setItemText(1, i18ncp("use date ranges to search files by modified time", "hour", "hours", value));
823     betweenType->setItemText(2, i18ncp("use date ranges to search files by modified time", "day", "days", value));
824     betweenType->setItemText(3, i18ncp("use date ranges to search files by modified time", "month", "months", value));
825     betweenType->setItemText(4, i18ncp("use date ranges to search files by modified time", "year", "years", value));
826 }
827 
slotUpdateByteComboBox(int value)828 void KfindTabWidget::slotUpdateByteComboBox(int value)
829 {
830     sizeUnitBox->setItemText(0, i18np("Byte", "Bytes", value));
831 }
832 
833 //*******************************************************
834 //             Static utility functions
835 //*******************************************************
save_pattern(KComboBox * obj,const QString & group,const QString & entry)836 static void save_pattern(KComboBox *obj, const QString &group, const QString &entry)
837 {
838     // QComboBox allows insertion of items more than specified by
839     // maxCount() (QT bug?). This API call will truncate list if needed.
840     obj->setMaxCount(15);
841 
842     // make sure the current item is saved first so it will be the
843     // default when started next time
844     QStringList sl;
845     QString cur = obj->itemText(obj->currentIndex());
846     sl.append(cur);
847     for (int i = 0; i < obj->count(); i++) {
848         if (cur != obj->itemText(i)) {
849             sl.append(obj->itemText(i));
850         }
851     }
852 
853     KConfigGroup conf(KSharedConfig::openConfig(), group);
854     conf.writePathEntry(entry, sl);
855 }
856 
sizeHint() const857 QSize KfindTabWidget::sizeHint() const
858 {
859     // #44662: avoid a huge default size when the comboboxes have very large items
860     // Like in minicli, we changed the combobox size policy so that they can resize down,
861     // and then we simply provide a reasonable size hint for the whole window, depending
862     // on the screen width.
863     QSize sz = QTabWidget::sizeHint();
864     KfindTabWidget *me = const_cast<KfindTabWidget *>(this);
865     const int screenWidth = qApp->desktop()->screenGeometry(me).width();
866     if (sz.width() > screenWidth / 2) {
867         sz.setWidth(screenWidth / 2);
868     }
869     return sz;
870 }
871