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