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