1 /* Copyright (C) 2005-2019 J.F.Dockes
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the
14 * Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 */
17 #include "autoconfig.h"
18
19 #include <QShortcut>
20 #include <QMessageBox>
21
22 #include "log.h"
23 #include "internfile.h"
24 #include "listdialog.h"
25 #include "confgui/confguiindex.h"
26 #include "idxsched.h"
27 #ifdef _WIN32
28 #include "winschedtool.h"
29 #else
30 #include "crontool.h"
31 #include "rtitool.h"
32 #endif
33 #include "snippets_w.h"
34 #include "fragbuts.h"
35 #include "specialindex.h"
36 #include "rclmain_w.h"
37 #include "webcache.h"
38 #include "restable.h"
39 #include "actsearch_w.h"
40
41 using namespace std;
42
43 static const QKeySequence quitKeySeq("Ctrl+q");
44 static const QKeySequence closeKeySeq("Ctrl+w");
45
46 // Open advanced search dialog.
showAdvSearchDialog()47 void RclMain::showAdvSearchDialog()
48 {
49 if (asearchform == 0) {
50 asearchform = new AdvSearch(0);
51 if (asearchform == 0) {
52 return;
53 }
54 connect(new QShortcut(quitKeySeq, asearchform), SIGNAL (activated()),
55 this, SLOT (fileExit()));
56
57 connect(asearchform,
58 SIGNAL(startSearch(std::shared_ptr<Rcl::SearchData>, bool)),
59 this, SLOT(startSearch(std::shared_ptr<Rcl::SearchData>, bool)));
60 connect(asearchform, SIGNAL(setDescription(QString)),
61 this, SLOT(onSetDescription(QString)));
62 asearchform->show();
63 } else {
64 // Close and reopen, in hope that makes us visible...
65 asearchform->close();
66 asearchform->show();
67 }
68 }
69
showSpellDialog()70 void RclMain::showSpellDialog()
71 {
72 if (spellform == 0) {
73 spellform = new SpellW(0);
74 connect(new QShortcut(quitKeySeq, spellform), SIGNAL (activated()),
75 this, SLOT (fileExit()));
76 connect(spellform, SIGNAL(wordSelect(QString)),
77 sSearch, SLOT(addTerm(QString)));
78 spellform->show();
79 } else {
80 // Close and reopen, in hope that makes us visible...
81 spellform->close();
82 spellform->show();
83 }
84 }
85
showWebcacheDialog()86 void RclMain::showWebcacheDialog()
87 {
88 switch (indexerState()) {
89 case RclMain::IXST_UNKNOWN:
90 QMessageBox::warning(0, "Recoll", tr("Unknown indexer state. "
91 "Can't access webcache file."));
92 return;
93 case RclMain::IXST_RUNNINGMINE:
94 case RclMain::IXST_RUNNINGNOTMINE:
95 QMessageBox::warning(0, "Recoll", tr("Indexer is running. "
96 "Can't access webcache file."));
97 return;
98 case RclMain::IXST_NOTRUNNING:
99 break;
100 }
101
102 if (!m_pidfile) {
103 m_pidfile = new Pidfile(theconfig->getPidfile());
104 if (m_pidfile->open() != 0) {
105 deleteZ(m_pidfile);
106 return;
107 }
108 if (m_pidfile->write_pid() != 0) {
109 deleteZ(m_pidfile);
110 return;
111 }
112 }
113
114 if (webcache == 0) {
115 webcache = new WebcacheEdit(this);
116 webcache->setAttribute(Qt::WA_DeleteOnClose);
117 connect(new QShortcut(quitKeySeq, webcache), SIGNAL (activated()),
118 this, SLOT (fileExit()));
119 connect(webcache, SIGNAL(destroyed(QObject*)),
120 this, SLOT(onWebcacheDestroyed(QObject*)) );
121 webcache->show();
122 }
123 }
onWebcacheDestroyed(QObject *)124 void RclMain::onWebcacheDestroyed(QObject *)
125 {
126 deleteZ(m_pidfile);
127 webcache = 0;
128 }
129
showIndexStatistics()130 void RclMain::showIndexStatistics()
131 {
132 showSpellDialog();
133 if (spellform == 0)
134 return;
135 spellform->setMode(SpellW::TYPECMB_STATS);
136 }
137
showFragButs()138 void RclMain::showFragButs()
139 {
140 if (fragbuts && fragbuts->isStale(0)) {
141 deleteZ(fragbuts);
142 }
143 if (fragbuts == 0) {
144 fragbuts = new FragButs(0);
145 if (fragbuts->ok()) {
146 fragbuts->show();
147 connect(fragbuts, SIGNAL(fragmentsChanged()),
148 this, SLOT(onFragmentsChanged()));
149 } else {
150 deleteZ(fragbuts);
151 }
152 } else {
153 // Close and reopen, in hope that makes us visible...
154 fragbuts->close();
155 fragbuts->show();
156 }
157 }
158
showSpecIdx()159 void RclMain::showSpecIdx()
160 {
161 if (specidx == 0) {
162 specidx = new SpecIdxW(0);
163 connect(specidx, SIGNAL(accepted()), this, SLOT(specialIndex()));
164 specidx->show();
165 } else {
166 // Close and reopen, in hope that makes us visible...
167 specidx->close();
168 specidx->show();
169 }
170 }
171
showIndexConfig()172 void RclMain::showIndexConfig()
173 {
174 showIndexConfig(false);
175 }
execIndexConfig()176 void RclMain::execIndexConfig()
177 {
178 showIndexConfig(true);
179 }
showIndexConfig(bool modal)180 void RclMain::showIndexConfig(bool modal)
181 {
182 LOGDEB("showIndexConfig()\n" );
183 bool created{false};
184 if (indexConfig == 0) {
185 created = true;
186 indexConfig = new ConfIndexW(0, theconfig);
187 }
188 indexConfig->showPrefs(modal);
189 if (created) {
190 connect(new QShortcut(quitKeySeq, indexConfig->getDialog()),
191 SIGNAL (activated()), this, SLOT (fileExit()));
192 }
193 }
194
showIndexSched()195 void RclMain::showIndexSched()
196 {
197 showIndexSched(false);
198 }
execIndexSched()199 void RclMain::execIndexSched()
200 {
201 showIndexSched(true);
202 }
showIndexSched(bool modal)203 void RclMain::showIndexSched(bool modal)
204 {
205 LOGDEB("showIndexSched()\n" );
206 if (indexSched == 0) {
207 indexSched = new IdxSchedW(this);
208 connect(new QShortcut(quitKeySeq, indexSched), SIGNAL (activated()),
209 this, SLOT (fileExit()));
210 #ifdef _WIN32
211 indexSched->cronCLB->setText(tr("Batch scheduling"));
212 indexSched->cronCLB->setDescription(
213 tr("The tool will let you decide at what time indexing should run. "
214 " It uses the Windows task scheduler."));
215 indexSched->mainExplainLBL->hide();
216 indexSched->rtidxCLB->hide();
217 #endif
218 connect(indexSched->cronCLB, SIGNAL(clicked()),
219 this, SLOT(execCronTool()));
220 if (theconfig && theconfig->isDefaultConfig()) {
221 #ifdef RCL_MONITOR
222 connect(indexSched->rtidxCLB, SIGNAL(clicked()),
223 this, SLOT(execRTITool()));
224 #else
225 indexSched->rtidxCLB->setEnabled(false);
226 indexSched->rtidxCLB->setToolTip(tr("Disabled because the real time indexer was not compiled in."));
227 #endif
228 } else {
229 indexSched->rtidxCLB->setEnabled(false);
230 indexSched->rtidxCLB->setToolTip(tr("This configuration tool only works for the main index."));
231 }
232 } else {
233 // Close and reopen, in hope that makes us visible...
234 indexSched->close();
235 }
236 if (modal) {
237 indexSched->exec();
238 indexSched->setModal(false);
239 } else {
240 indexSched->show();
241 }
242 }
243
showCronTool()244 void RclMain::showCronTool()
245 {
246 showCronTool(false);
247 }
execCronTool()248 void RclMain::execCronTool()
249 {
250 showCronTool(true);
251 }
252
showCronTool(bool modal)253 void RclMain::showCronTool(bool modal)
254 {
255 LOGDEB("showCronTool()\n" );
256 if (cronTool == 0) {
257 #ifdef _WIN32
258 cronTool = new WinSchedToolW(0);
259 #else
260 cronTool = new CronToolW(0);
261 #endif
262 connect(new QShortcut(quitKeySeq, cronTool), SIGNAL (activated()),
263 this, SLOT (fileExit()));
264 } else {
265 // Close and reopen, in hope that makes us visible...
266 cronTool->close();
267 }
268 if (modal) {
269 cronTool->exec();
270 cronTool->setModal(false);
271 } else {
272 cronTool->show();
273 }
274 }
275
showRTITool()276 void RclMain::showRTITool()
277 {
278 showRTITool(false);
279 }
execRTITool()280 void RclMain::execRTITool()
281 {
282 showRTITool(true);
283 }
showRTITool(bool modal)284 void RclMain::showRTITool(bool modal)
285 {
286 #ifndef _WIN32
287 LOGDEB("showRTITool()\n" );
288 if (rtiTool == 0) {
289 rtiTool = new RTIToolW(0);
290 connect(new QShortcut(quitKeySeq, rtiTool), SIGNAL (activated()),
291 this, SLOT (fileExit()));
292 } else {
293 // Close and reopen, in hope that makes us visible...
294 rtiTool->close();
295 }
296 if (modal) {
297 rtiTool->exec();
298 rtiTool->setModal(false);
299 } else {
300 rtiTool->show();
301 }
302 #else
303 PRETEND_USE(modal);
304 #endif
305 }
306
showUIPrefs()307 void RclMain::showUIPrefs()
308 {
309 prefs.useTmpActiveExtraDbs = false;
310 prefs.tmpActiveExtraDbs.clear();
311 if (uiprefs == 0) {
312 uiprefs = new UIPrefsDialog(this);
313 connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()),
314 this, SLOT (fileExit()));
315 connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs()));
316 connect(this, SIGNAL(stemLangChanged(const QString&)),
317 uiprefs, SLOT(setStemLang(const QString&)));
318 } else {
319 // Close and reopen, in hope that makes us visible...
320 uiprefs->close();
321 rwSettings(false);
322 uiprefs->setFromPrefs();
323 }
324 uiprefs->show();
325 }
326
showExtIdxDialog()327 void RclMain::showExtIdxDialog()
328 {
329 showUIPrefs();
330 uiprefs->tabWidget->setCurrentIndex(5);
331 }
332
showAboutDialog()333 void RclMain::showAboutDialog()
334 {
335 QString vstring = QString("<html><head>") + prefs.darkreslistheadertext + "</head><body>" +
336 u8s2qs(Rcl::version_string()) +
337 "<br><a href='https://www.recoll.org'>www.recoll.org</a>" +
338 "<br><a href='https://www.xapian.org'>www.xapian.org</a>";
339 QMessageBox::information(this, tr("About Recoll"), vstring);
340 }
341
showMissingHelpers()342 void RclMain::showMissingHelpers()
343 {
344 string miss;
345 if (!theconfig->getMissingHelperDesc(miss)) {
346 QMessageBox::information(
347 this, "", tr("No information: initial indexing not yet performed."));
348 return;
349 }
350 QString msg = QString::fromUtf8("<p>") +
351 tr("External applications/commands needed for your file types "
352 "and not found, as stored by the last indexing pass in ");
353 msg += "<i>";
354 msg += path2qs(theconfig->getConfDir());
355 msg += "/missing</i>:<pre>\n";
356 if (!miss.empty()) {
357 msg += QString::fromUtf8(miss.c_str());
358 } else {
359 msg += tr("No helpers found missing");
360 }
361 msg += "</pre>";
362 QMessageBox::information(this, tr("Missing helper programs"), msg);
363 }
364
showActiveTypes()365 void RclMain::showActiveTypes()
366 {
367 string reason;
368 bool maindberror;
369 if (!maybeOpenDb(reason, true, &maindberror)) {
370 QMessageBox::warning(0, tr("Error"),
371 u8s2qs(reason),
372 QMessageBox::Ok,
373 QMessageBox::NoButton);
374 return;
375 }
376
377 // All mime types in index.
378 vector<string> vdbtypes;
379 if (!rcldb->getAllDbMimeTypes(vdbtypes)) {
380 QMessageBox::warning(0, tr("Error"),
381 tr("Index query error"),
382 QMessageBox::Ok,
383 QMessageBox::NoButton);
384 return;
385 }
386 set<string> mtypesfromdb;
387 mtypesfromdb.insert(vdbtypes.begin(), vdbtypes.end());
388
389 // All types listed in mimeconf:
390 vector<string> mtypesfromconfig = theconfig->getAllMimeTypes();
391
392 // Intersect file system types with config types (those not in the
393 // config can be indexed by name, not by content)
394 set<string> mtypesfromdbconf;
395 for (vector<string>::const_iterator it = mtypesfromconfig.begin();
396 it != mtypesfromconfig.end(); it++) {
397 if (mtypesfromdb.find(*it) != mtypesfromdb.end())
398 mtypesfromdbconf.insert(*it);
399 }
400
401 // Substract the types for missing helpers (the docs are indexed
402 // by name only):
403 string miss;
404 if (theconfig->getMissingHelperDesc(miss) && !miss.empty()) {
405 FIMissingStore st(miss);
406 map<string, set<string> >::const_iterator it;
407 for (it = st.m_typesForMissing.begin();
408 it != st.m_typesForMissing.end(); it++) {
409 set<string>::const_iterator it1;
410 for (it1 = it->second.begin();
411 it1 != it->second.end(); it1++) {
412 set<string>::iterator it2 = mtypesfromdbconf.find(*it1);
413 if (it2 != mtypesfromdbconf.end())
414 mtypesfromdbconf.erase(it2);
415 }
416 }
417 }
418 ListDialog dialog;
419 dialog.setWindowTitle(tr("Indexed MIME Types"));
420
421 // Turn the result into a string and display
422 dialog.groupBox->setTitle(tr("Content has been indexed for these MIME types:"));
423
424 // We replace the list with an editor so that the user can copy/paste
425 deleteZ(dialog.listWidget);
426 QTextEdit *editor = new QTextEdit(dialog.groupBox);
427 editor->setReadOnly(true);
428 dialog.horizontalLayout->addWidget(editor);
429
430 if (mtypesfromdbconf.empty()) {
431 editor->append(tr("Types list empty: maybe wait for indexing to "
432 "progress?"));
433 } else {
434 for (set<string>::const_iterator it = mtypesfromdbconf.begin();
435 it != mtypesfromdbconf.end(); it++) {
436 editor->append(QString::fromUtf8(it->c_str()));
437 }
438 }
439 editor->moveCursor(QTextCursor::Start);
440 editor->ensureCursorVisible();
441 dialog.exec();
442 }
443
newDupsW(const Rcl::Doc,const vector<Rcl::Doc> dups)444 void RclMain::newDupsW(const Rcl::Doc, const vector<Rcl::Doc> dups)
445 {
446 ListDialog dialog;
447 dialog.setWindowTitle(tr("Duplicate documents"));
448
449 dialog.groupBox->setTitle(tr("These Urls ( | ipath) share the same"
450 " content:"));
451 // We replace the list with an editor so that the user can copy/paste
452 deleteZ(dialog.listWidget);
453 QTextEdit *editor = new QTextEdit(dialog.groupBox);
454 editor->setReadOnly(true);
455 dialog.horizontalLayout->addWidget(editor);
456
457 for (vector<Rcl::Doc>::const_iterator it = dups.begin();
458 it != dups.end(); it++) {
459 if (it->ipath.empty())
460 editor->append(path2qs(it->url));
461 else
462 editor->append(path2qs(it->url) + " | " + u8s2qs(it->ipath));
463 }
464 editor->moveCursor(QTextCursor::Start);
465 editor->ensureCursorVisible();
466 dialog.exec();
467 }
468
showSnippets(Rcl::Doc doc)469 void RclMain::showSnippets(Rcl::Doc doc)
470 {
471 if (!m_source)
472 return;
473 if (!m_snippets) {
474 m_snippets = new SnippetsW(doc, m_source);
475 connect(m_snippets, SIGNAL(startNativeViewer(Rcl::Doc, int, QString)),
476 this, SLOT(startNativeViewer(Rcl::Doc, int, QString)));
477 connect(new QShortcut(quitKeySeq, m_snippets), SIGNAL (activated()),
478 this, SLOT (fileExit()));
479 connect(new QShortcut(closeKeySeq, m_snippets), SIGNAL (activated()),
480 m_snippets, SLOT (close()));
481 if (restable) {
482 connect(
483 restable,
484 SIGNAL(detailDocChanged(Rcl::Doc, std::shared_ptr<DocSequence>)),
485 m_snippets,
486 SLOT(onSetDoc(Rcl::Doc, std::shared_ptr<DocSequence>)));
487 }
488 } else {
489 m_snippets->onSetDoc(doc, m_source);
490 }
491 m_snippets->show();
492 }
493
showActionsSearch()494 void RclMain::showActionsSearch()
495 {
496 if (nullptr == actsearchw) {
497 actsearchw = new ActSearchW(this);
498 actsearchw->setActList(findChildren<QAction *>());
499 connect(actsearchw->actCMB, SIGNAL(editTextChanged(const QString&)),
500 actsearchw, SLOT(onTextChanged(const QString&)));
501 }
502 actsearchw->actCMB->setCurrentIndex(-1);
503 actsearchw->actCMB->clearEditText();
504 actsearchw->show();
505 }
506