1 /* Copyright (C) 2005-2020 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 <utility>
20 #include <memory>
21 #include <fstream>
22 #include <stdlib.h>
23 
24 #include <qapplication.h>
25 #include <qmessagebox.h>
26 #include <qfiledialog.h>
27 #include <qshortcut.h>
28 #include <qtimer.h>
29 #include <qstatusbar.h>
30 #include <qwindowdefs.h>
31 #include <qcheckbox.h>
32 #include <qfontdialog.h>
33 #include <qspinbox.h>
34 #include <qcombobox.h>
35 #include <qaction.h>
36 #include <qpushbutton.h>
37 #include <qimage.h>
38 #include <qcursor.h>
39 #include <qevent.h>
40 #include <QFileSystemWatcher>
41 #include <QThread>
42 #include <QProgressDialog>
43 #include <QToolBar>
44 #include <QSettings>
45 #include <QToolTip>
46 
47 #include "recoll.h"
48 #include "log.h"
49 #include "mimehandler.h"
50 #include "pathut.h"
51 #include "smallut.h"
52 #include "advsearch_w.h"
53 #include "sortseq.h"
54 #include "uiprefs_w.h"
55 #include "guiutils.h"
56 #include "reslist.h"
57 #include "ssearch_w.h"
58 #include "internfile.h"
59 #include "docseqdb.h"
60 #include "docseqhist.h"
61 #include "docseqdocs.h"
62 #include "restable.h"
63 #include "firstidx.h"
64 #include "indexer.h"
65 #include "rclzg.h"
66 #include "snippets_w.h"
67 #include "fragbuts.h"
68 #include "systray.h"
69 #include "rclmain_w.h"
70 #include "rclhelp.h"
71 #include "readfile.h"
72 #include "moc_rclmain_w.cpp"
73 #include "scbase.h"
74 
75 QString g_stringAllStem, g_stringNoStem;
76 static const char *settingskey_toolarea="/Recoll/geometry/toolArea";
77 static const char *settingskey_resarea="/Recoll/geometry/resArea";
78 
int2area(int in)79 static Qt::ToolBarArea int2area(int in)
80 {
81     switch (in) {
82     case Qt::LeftToolBarArea: return Qt::LeftToolBarArea;
83     case Qt::RightToolBarArea: return Qt::RightToolBarArea;
84     case Qt::BottomToolBarArea: return Qt::BottomToolBarArea;
85     case Qt::TopToolBarArea:
86     default:
87         return Qt::TopToolBarArea;
88     }
89 }
90 
configToTitle()91 static QString configToTitle()
92 {
93     string confdir = path_getsimple(theconfig->getConfDir());
94     // Lower-case version. This only works with the ascii part, but
95     // that's ok even if there are non-ascii chars in there, because
96     // we further operate only on ascii substrings.
97     string lconfdir = stringtolower((const string&)confdir);
98 
99     if (!lconfdir.empty() && lconfdir[0] == '.') {
100         lconfdir = lconfdir.substr(1);
101         confdir = confdir.substr(1);
102     }
103     string::size_type pos = lconfdir.find("recoll");
104     if (pos != string::npos) {
105         lconfdir = lconfdir.substr(0, pos) + lconfdir.substr(pos+6);
106         confdir = confdir.substr(0, pos) + confdir.substr(pos+6);
107     }
108     if (!confdir.empty()) {
109         switch (confdir[0]) {
110         case '.': case '-': case '_':
111             confdir = confdir.substr(1);
112             break;
113         default:
114             break;
115         }
116     }
117     if (confdir.empty()) {
118         confdir = "Recoll";
119     } else {
120         confdir = string("Recoll - ") + confdir;
121     }
122     return QString::fromUtf8(confdir.c_str());
123 }
124 
init()125 void RclMain::init()
126 {
127     setWindowTitle(configToTitle());
128 
129     buildMenus();
130 
131     periodictimer = new QTimer(this);
132 
133     // idxstatus file. Make sure it exists before trying to watch it
134     // (case where we're started on an older index, or if the status
135     // file was deleted since indexing)
136     QString idxfn = path2qs(theconfig->getIdxStatusFile());
137     QFile qf(idxfn);
138     qf.open(QIODevice::ReadWrite);
139     qf.setPermissions(QFile::ReadOwner|QFile::WriteOwner);
140     qf.close();
141     m_watcher.addPath(idxfn);
142 
143     setupStatusBar();
144     setupMenus();
145 
146     (void)new HelpClient(this);
147     HelpClient::installMap((const char *)this->objectName().toUtf8(),
148                            "RCL.SEARCH.GUI.SIMPLE");
149 
150     // Set the focus to the search terms entry:
151     sSearch->takeFocus();
152 
153     enbSynAction->setDisabled(prefs.synFile.isEmpty());
154     enbSynAction->setChecked(prefs.synFileEnable);
155 
156     setupToolbars();
157 
158     //////// 3 versions of results category filtering: buttons, combobox, menu
159     setupCategoryFiltering();
160 
161     restable = new ResTable(this);
162     verticalLayout->insertWidget(2, restable);
163     actionShowResultsAsTable->setChecked(prefs.showResultsAsTable);
164     on_actionShowResultsAsTable_toggled(prefs.showResultsAsTable);
165 
166     onNewShortcuts();
167     Preview::listShortcuts();
168     SnippetsW::listShortcuts();
169     AdvSearch::listShortcuts();
170 
171     connect(&SCBase::scBase(), SIGNAL(shortcutsChanged()),
172             this, SLOT(onNewShortcuts()));
173 
174     connect(&m_watcher, SIGNAL(fileChanged(QString)),
175             this, SLOT(updateIdxStatus()));
176 
177     connect(sSearch,
178             SIGNAL(startSearch(std::shared_ptr<Rcl::SearchData>, bool)),
179             this, SLOT(startSearch(std::shared_ptr<Rcl::SearchData>, bool)));
180     connect(sSearch, SIGNAL(setDescription(QString)),
181             this, SLOT(onSetDescription(QString)));
182     connect(sSearch, SIGNAL(clearSearch()),
183             this, SLOT(resetSearch()));
184     connect(this, SIGNAL(uiPrefsChanged()), sSearch, SLOT(setPrefs()));
185     connect(preferencesMenu, SIGNAL(triggered(QAction*)),
186             this, SLOT(setStemLang(QAction*)));
187     connect(preferencesMenu, SIGNAL(aboutToShow()),
188             this, SLOT(adjustPrefsMenu()));
189     connect(fileExitAction, SIGNAL(triggered() ),
190             this, SLOT(fileExit() ) );
191     connect(fileToggleIndexingAction, SIGNAL(triggered()),
192             this, SLOT(toggleIndexing()));
193 #ifndef _WIN32
194     fileMenu->insertAction(fileRebuildIndexAction, fileBumpIndexingAction);
195     connect(fileBumpIndexingAction, SIGNAL(triggered()),
196             this, SLOT(bumpIndexing()));
197 #endif
198     connect(fileRebuildIndexAction, SIGNAL(triggered()),
199             this, SLOT(rebuildIndex()));
200     connect(fileEraseDocHistoryAction, SIGNAL(triggered()),
201             this, SLOT(eraseDocHistory()));
202     connect(fileEraseSearchHistoryAction, SIGNAL(triggered()),
203             this, SLOT(eraseSearchHistory()));
204     connect(fileExportSSearchHistoryAction, SIGNAL(triggered()),
205             this, SLOT(exportSimpleSearchHistory()));
206     connect(actionSave_last_query, SIGNAL(triggered()),
207             this, SLOT(saveLastQuery()));
208     connect(actionLoad_saved_query, SIGNAL(triggered()),
209             this, SLOT(loadSavedQuery()));
210     connect(actionShow_index_statistics, SIGNAL(triggered()),
211             this, SLOT(showIndexStatistics()));
212     connect(helpAbout_RecollAction, SIGNAL(triggered()),
213             this, SLOT(showAboutDialog()));
214     connect(showMissingHelpers_Action, SIGNAL(triggered()),
215             this, SLOT(showMissingHelpers()));
216     connect(showActiveTypes_Action, SIGNAL(triggered()),
217             this, SLOT(showActiveTypes()));
218     connect(userManualAction, SIGNAL(triggered()),
219             this, SLOT(startManual()));
220     connect(toolsDoc_HistoryAction, SIGNAL(triggered()),
221             this, SLOT(showDocHistory()));
222     connect(toolsAdvanced_SearchAction, SIGNAL(triggered()),
223             this, SLOT(showAdvSearchDialog()));
224     connect(toolsSpellAction, SIGNAL(triggered()),
225             this, SLOT(showSpellDialog()));
226     connect(actionWebcache_Editor, SIGNAL(triggered()),
227             this, SLOT(showWebcacheDialog()));
228     connect(actionQuery_Fragments, SIGNAL(triggered()),
229             this, SLOT(showFragButs()));
230     connect(actionSpecial_Indexing, SIGNAL(triggered()),
231             this, SLOT(showSpecIdx()));
232     connect(indexConfigAction, SIGNAL(triggered()),
233             this, SLOT(showIndexConfig()));
234     connect(indexScheduleAction, SIGNAL(triggered()),
235             this, SLOT(showIndexSched()));
236     connect(queryPrefsAction, SIGNAL(triggered()),
237             this, SLOT(showUIPrefs()));
238     connect(extIdxAction, SIGNAL(triggered()),
239             this, SLOT(showExtIdxDialog()));
240     connect(enbSynAction, SIGNAL(toggled(bool)),
241             this, SLOT(setSynEnabled(bool)));
242 
243     connect(toggleFullScreenAction, SIGNAL(triggered()), this, SLOT(toggleFullScreen()));
244     zoomInAction->setShortcut(QKeySequence::ZoomIn);
245     connect(zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
246     zoomOutAction->setShortcut(QKeySequence::ZoomOut);
247     connect(zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
248 
249     connect(actionShowQueryDetails, SIGNAL(triggered()),
250             reslist, SLOT(showQueryDetails()));
251     connect(periodictimer, SIGNAL(timeout()),
252             this, SLOT(periodic100()));
253 
254     restable->setRclMain(this, true);
255     connect(actionSaveResultsAsCSV, SIGNAL(triggered()),
256             restable, SLOT(saveAsCSV()));
257     connect(this, SIGNAL(docSourceChanged(std::shared_ptr<DocSequence>)),
258             restable, SLOT(setDocSource(std::shared_ptr<DocSequence>)));
259     connect(this, SIGNAL(searchReset()),
260             restable, SLOT(resetSource()));
261     connect(this, SIGNAL(resultsReady()),
262             restable, SLOT(readDocSource()));
263     connect(this, SIGNAL(sortDataChanged(DocSeqSortSpec)),
264             restable, SLOT(onSortDataChanged(DocSeqSortSpec)));
265     connect(this, SIGNAL(sortDataChanged(DocSeqSortSpec)),
266             this, SLOT(onSortDataChanged(DocSeqSortSpec)));
267     connect(this, SIGNAL(uiPrefsChanged()), restable, SLOT(onUiPrefsChanged()));
268 
269     connect(restable->getModel(), SIGNAL(sortDataChanged(DocSeqSortSpec)),
270             this, SLOT(onSortDataChanged(DocSeqSortSpec)));
271 
272     connect(restable, SIGNAL(docPreviewClicked(int, Rcl::Doc, int)),
273             this, SLOT(startPreview(int, Rcl::Doc, int)));
274     connect(restable, SIGNAL(docExpand(Rcl::Doc)),
275             this, SLOT(docExpand(Rcl::Doc)));
276     connect(restable, SIGNAL(showSubDocs(Rcl::Doc)),
277             this, SLOT(showSubDocs(Rcl::Doc)));
278     connect(restable, SIGNAL(openWithRequested(Rcl::Doc, string)),
279             this, SLOT(openWith(Rcl::Doc, string)));
280 
281     reslist->setRclMain(this, true);
282     connect(this, SIGNAL(docSourceChanged(std::shared_ptr<DocSequence>)),
283             reslist, SLOT(setDocSource(std::shared_ptr<DocSequence>)));
284     connect(firstPageAction, SIGNAL(triggered()),
285             reslist, SLOT(resultPageFirst()));
286     connect(prevPageAction, SIGNAL(triggered()),
287             reslist, SLOT(resPageUpOrBack()));
288     connect(nextPageAction, SIGNAL(triggered()),
289             reslist, SLOT(resPageDownOrNext()));
290     connect(this, SIGNAL(searchReset()),
291             reslist, SLOT(resetList()));
292     connect(this, SIGNAL(resultsReady()),
293             reslist, SLOT(readDocSource()));
294     connect(this, SIGNAL(uiPrefsChanged()), reslist, SLOT(onUiPrefsChanged()));
295 
296     connect(reslist, SIGNAL(hasResults(int)),
297             this, SLOT(resultCount(int)));
298     connect(reslist, SIGNAL(wordSelect(QString)),
299             sSearch, SLOT(addTerm(QString)));
300     connect(reslist, SIGNAL(wordReplace(const QString&, const QString&)),
301             sSearch, SLOT(onWordReplace(const QString&, const QString&)));
302     connect(reslist, SIGNAL(nextPageAvailable(bool)),
303             this, SLOT(enableNextPage(bool)));
304     connect(reslist, SIGNAL(prevPageAvailable(bool)),
305             this, SLOT(enablePrevPage(bool)));
306 
307     connect(reslist, SIGNAL(docExpand(Rcl::Doc)),
308             this, SLOT(docExpand(Rcl::Doc)));
309     connect(reslist, SIGNAL(showSnippets(Rcl::Doc)),
310             this, SLOT(showSnippets(Rcl::Doc)));
311     connect(reslist, SIGNAL(showSubDocs(Rcl::Doc)),
312             this, SLOT(showSubDocs(Rcl::Doc)));
313     connect(reslist, SIGNAL(docSaveToFileClicked(Rcl::Doc)),
314             this, SLOT(saveDocToFile(Rcl::Doc)));
315     connect(reslist, SIGNAL(editRequested(Rcl::Doc)),
316             this, SLOT(startNativeViewer(Rcl::Doc)));
317     connect(reslist, SIGNAL(openWithRequested(Rcl::Doc, string)),
318             this, SLOT(openWith(Rcl::Doc, string)));
319     connect(reslist, SIGNAL(docPreviewClicked(int, Rcl::Doc, int)),
320             this, SLOT(startPreview(int, Rcl::Doc, int)));
321     connect(reslist, SIGNAL(previewRequested(Rcl::Doc)),
322             this, SLOT(startPreview(Rcl::Doc)));
323 
324     setFilterCtlStyle(prefs.filterCtlStyle);
325 
326     if (prefs.keepSort && prefs.sortActive) {
327         m_sortspec.field = (const char *)prefs.sortField.toUtf8();
328         m_sortspec.desc = prefs.sortDesc;
329         emit sortDataChanged(m_sortspec);
330     }
331     QSettings settings;
332     restoreGeometry(settings.value("/Recoll/geometry/maingeom").toByteArray());
333 
334     enableTrayIcon(prefs.showTrayIcon);
335 
336     fileRebuildIndexAction->setEnabled(false);
337     fileToggleIndexingAction->setEnabled(false);
338     fileRetryFailedAction->setEnabled(false);
339     // Start timer on a slow period (used for checking ^C). Will be
340     // speeded up during indexing
341     periodictimer->start(1000);
342 }
343 
zoomIn()344 void RclMain::zoomIn()
345 {
346     prefs.reslistfontsize++;
347     emit uiPrefsChanged();
348 }
zoomOut()349 void RclMain::zoomOut()
350 {
351     prefs.reslistfontsize--;
352     emit uiPrefsChanged();
353 }
354 
onNewShortcuts()355 void RclMain::onNewShortcuts()
356 {
357     SCBase& scb = SCBase::scBase();
358     QKeySequence ks;
359 
360     SETSHORTCUT(sSearch, "main:347", tr("Main Window"), tr("Clear search"),
361                 "Ctrl+S", m_clearsearchsc, clearAll);
362     SETSHORTCUT(sSearch, "main:349", tr("Main Window"),
363                 tr("Move keyboard focus to search entry"),
364                 "Ctrl+L", m_focustosearchsc, takeFocus);
365     SETSHORTCUT(sSearch, "main:352", tr("Main Window"),
366                 tr("Move keyboard focus to search, alt."),
367                 "Ctrl+Shift+S", m_focustosearcholdsc, takeFocus);
368     // We could set this as an action shortcut, but then, it would not
369     // be editable
370     SETSHORTCUT(this, "main:357", tr("Main Window"), tr("Toggle tabular display"),
371                 "Ctrl+T", m_toggletablesc, toggleTable);
372     SETSHORTCUT(this, "main:373", tr("Main Window"), tr("Show menu search dialog"),
373                 "Alt+/", m_actionssearchsc, showActionsSearch);
374     ks = scb.get("rclmain:361", tr("Main Window"), tr("Move keyboard focus to table"), "Ctrl+R");
375     if (!ks.isEmpty()) {
376         delete m_focustotablesc;
377         m_focustotablesc = new QShortcut(ks, this);
378         if (displayingTable) {
379             connect(m_focustotablesc, SIGNAL(activated()),
380                     restable, SLOT(takeFocus()));
381         } else {
382             disconnect(m_focustotablesc, SIGNAL(activated()),
383                        restable, SLOT(takeFocus()));
384         }
385     }
386 }
387 
setupToolbars()388 void RclMain::setupToolbars()
389 {
390     if (nullptr == m_toolsTB) {
391         m_toolsTB = new QToolBar(tr("Tools"), this);
392         m_toolsTB->setObjectName(QString::fromUtf8("m_toolsTB"));
393         m_toolsTB->addAction(toolsAdvanced_SearchAction);
394         m_toolsTB->addAction(toolsDoc_HistoryAction);
395         m_toolsTB->addAction(toolsSpellAction);
396         m_toolsTB->addAction(actionQuery_Fragments);
397     }
398     QSettings settings;
399     int val;
400     if (!prefs.noToolbars) {
401         val = settings.value(settingskey_toolarea).toInt();
402         this->addToolBar(int2area(val), m_toolsTB);
403         m_toolsTB->show();
404     } else {
405         m_toolsTB->hide();
406     }
407     if (nullptr == m_resTB) {
408         m_resTB = new QToolBar(tr("Results"), this);
409         m_resTB->setObjectName(QString::fromUtf8("m_resTB"));
410     }
411     if (!prefs.noToolbars) {
412         val = settings.value(settingskey_resarea).toInt();
413         this->addToolBar(int2area(val), m_resTB);
414         m_resTB->show();
415     } else {
416         m_resTB->hide();
417     }
418 }
419 
setupStatusBar()420 void RclMain::setupStatusBar()
421 {
422     auto bar = statusBar();
423     if (prefs.noStatusBar) {
424         bar->hide();
425     } else {
426         bar->show();
427     }
428 }
429 
setupMenus()430 void RclMain::setupMenus()
431 {
432     if (prefs.noMenuBar) {
433         MenuBar->hide();
434         sSearch->menuPB->show();
435         butmenuSC = new QShortcut(QKeySequence("Alt+m"), this);
436         connect(butmenuSC, SIGNAL(activated()),
437                 sSearch->menuPB, SLOT(showMenu()));
438     } else {
439         MenuBar->show();
440         sSearch->menuPB->hide();
441         deleteZ(butmenuSC);
442     }
443 }
444 
445 
enableTrayIcon(bool on)446 void RclMain::enableTrayIcon(bool on)
447 {
448     on =  on && QSystemTrayIcon::isSystemTrayAvailable();
449     if (on) {
450         if (nullptr == m_trayicon) {
451             m_trayicon = new RclTrayIcon(this,
452                                          QIcon(QString(":/images/recoll.png")));
453         }
454         m_trayicon->show();
455     } else {
456         deleteZ(m_trayicon);
457     }
458 }
459 
setupCategoryFiltering()460 void RclMain::setupCategoryFiltering()
461 {
462     // This is just to get the common catg strings into the message file
463     static const char* catg_strings[] = {
464         QT_TR_NOOP("All"), QT_TR_NOOP("media"),  QT_TR_NOOP("message"),
465         QT_TR_NOOP("other"),  QT_TR_NOOP("presentation"),
466         QT_TR_NOOP("spreadsheet"),  QT_TR_NOOP("text"),
467         QT_TR_NOOP("sorted"), QT_TR_NOOP("filtered")
468     };
469 
470     //// Combobox version
471     m_filtCMB = new QComboBox(m_resTB);
472     m_filtCMB->setEditable(false);
473     m_filtCMB->addItem(tr("All"));
474     m_filtCMB->setToolTip(tr("Document filter"));
475 
476     //// Buttons version
477     m_filtFRM = new QFrame(this);
478     m_filtFRM->setObjectName(QString::fromUtf8("m_filtFRM"));
479     QHBoxLayout *bgrphbox = new QHBoxLayout(m_filtFRM);
480     verticalLayout->insertWidget(1, m_filtFRM);
481     QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Maximum);
482     sizePolicy2.setHorizontalStretch(0);
483     sizePolicy2.setVerticalStretch(0);
484     sizePolicy2.setHeightForWidth(m_filtFRM->sizePolicy().hasHeightForWidth());
485     m_filtFRM->setSizePolicy(sizePolicy2);
486     m_filtBGRP  = new QButtonGroup(m_filtFRM);
487     QRadioButton *allRDB = new QRadioButton(m_filtFRM);
488     allRDB->setObjectName("allRDB");
489     allRDB->setText(tr("All"));
490     bgrphbox->addWidget(allRDB);
491     int bgrpid = 0;
492     m_filtBGRP->addButton(allRDB, bgrpid++);
493     allRDB->setChecked(true);
494     m_filtFRM->setLayout(bgrphbox);
495 
496     //// Menu version of the document filter control
497     m_filtMN = new QMenu(MenuBar);
498     m_filtMN->setObjectName("m_filtMN");
499     MenuBar->insertMenu(viewMenu->menuAction(), m_filtMN);
500     buttonTopMenu->insertMenu(viewMenu->menuAction(), m_filtMN);
501     m_filtMN->setTitle(tr("F&ilter"));
502     QActionGroup *fltag = new QActionGroup(this);
503     fltag->setExclusive(true);
504     QAction *act = fltag->addAction(tr("All"));
505     m_filtMN->addAction(act);
506     act->setCheckable(true);
507     act->setData((int)0);
508 
509     //// Go through the categories list and setup menu, buttons and combobox
510     vector<string> cats;
511     theconfig->getGuiFilterNames(cats);
512     m_catgbutvec.push_back(catg_strings[0]);
513     for (const auto& cat : cats) {
514         QRadioButton *but = new QRadioButton(m_filtFRM);
515         QString catgnm = u8s2qs(cat);
516         m_catgbutvec.push_back(cat);
517         // We strip text before the first colon before setting the button name.
518         // This is so that the user can decide the order of buttons by naming
519         // the filter,ie, a:media b:messages etc.
520         QString but_txt = catgnm;
521         int colon = catgnm.indexOf(':');
522         if (colon != -1) {
523             but_txt = catgnm.right(catgnm.size()-(colon+1));
524         }
525         but->setText(tr(but_txt.toUtf8()));
526         m_filtCMB->addItem(tr(but_txt.toUtf8()));
527         bgrphbox->addWidget(but);
528         m_filtBGRP->addButton(but, bgrpid++);
529         QAction *act = fltag->addAction(tr(but_txt.toUtf8()));
530         m_filtMN->addAction(act);
531         act->setCheckable(true);
532         act->setData((int)(m_catgbutvec.size()-1));
533         m_filtMN->connect(m_filtMN, SIGNAL(triggered(QAction *)), this,
534                           SLOT(catgFilter(QAction *)));
535     }
536     connect(m_filtBGRP, SIGNAL(buttonClicked(int)),this, SLOT(catgFilter(int)));
537     connect(m_filtCMB, SIGNAL(activated(int)), this, SLOT(catgFilter(int)));
538 }
539 
setSynEnabled(bool on)540 void RclMain::setSynEnabled(bool on)
541 {
542     prefs.synFileEnable = on;
543     if (uiprefs)
544         uiprefs->synFileCB->setChecked(prefs.synFileEnable);
545 }
546 
resultCount(int n)547 void RclMain::resultCount(int n)
548 {
549     actionSortByDateAsc->setEnabled(n>0);
550     actionSortByDateDesc->setEnabled(n>0);
551 }
552 
setFilterCtlStyle(int stl)553 void RclMain::setFilterCtlStyle(int stl)
554 {
555     switch (stl) {
556     case PrefsPack::FCS_MN:
557         setupResTB(false);
558         m_filtFRM->setVisible(false);
559         m_filtMN->menuAction()->setVisible(true);
560         break;
561     case PrefsPack::FCS_CMB:
562         setupResTB(true);
563         m_filtFRM->setVisible(false);
564         m_filtMN->menuAction()->setVisible(false);
565         break;
566     case PrefsPack::FCS_BT:
567     default:
568         setupResTB(false);
569         m_filtFRM->setVisible(true);
570         m_filtMN->menuAction()->setVisible(false);
571     }
572 }
573 
574 // Set up the "results" toolbox, adding the filter combobox or not depending
575 // on config option
setupResTB(bool combo)576 void RclMain::setupResTB(bool combo)
577 {
578     m_resTB->clear();
579     m_resTB->addAction(firstPageAction);
580     m_resTB->addAction(prevPageAction);
581     m_resTB->addAction(nextPageAction);
582     m_resTB->addSeparator();
583     m_resTB->addAction(actionSortByDateAsc);
584     m_resTB->addAction(actionSortByDateDesc);
585     if (combo) {
586         m_resTB->addSeparator();
587         m_filtCMB->show();
588         m_resTB->addWidget(m_filtCMB);
589     } else {
590         m_filtCMB->hide();
591     }
592     m_resTB->addSeparator();
593     m_resTB->addAction(actionShowResultsAsTable);
594 }
595 
596 // This is called by a timer right after we come up. Try to open
597 // the database and talk to the user if we can't
initDbOpen()598 void RclMain::initDbOpen()
599 {
600     bool nodb = false;
601     string reason;
602     bool maindberror;
603     if (!maybeOpenDb(reason, true, &maindberror)) {
604         nodb = true;
605         if (maindberror) {
606             FirstIdxDialog fidia(this);
607             connect(fidia.idxconfCLB, SIGNAL(clicked()),
608                     this, SLOT(execIndexConfig()));
609             connect(fidia.idxschedCLB, SIGNAL(clicked()),
610                     this, SLOT(execIndexSched()));
611             connect(fidia.runidxPB, SIGNAL(clicked()),
612                     this, SLOT(rebuildIndex()));
613             fidia.exec();
614             // Don't open adv search or run cmd line search in this case.
615             return;
616         } else {
617             QMessageBox::warning(0, "Recoll",
618                                  tr("Could not open external index. Db not "
619                                     "open. Check external indexes list."));
620         }
621     }
622 
623     if (prefs.startWithAdvSearchOpen)
624         showAdvSearchDialog();
625     // If we have something in the search entry, it comes from a
626     // command line argument
627     if (!nodb && sSearch->hasSearchString())
628         QTimer::singleShot(0, sSearch, SLOT(startSimpleSearch()));
629 
630     if (!m_urltoview.isEmpty())
631         viewUrl();
632 }
633 
setStemLang(QAction * id)634 void RclMain::setStemLang(QAction *id)
635 {
636     LOGDEB("RclMain::setStemLang(" << id << ")\n");
637     // Check that the menu entry is for a stemming language change
638     // (might also be "show prefs" etc.
639     bool isLangId = false;
640     for (const auto& entry : m_stemLangToId) {
641         if (id == entry.second)
642             isLangId = true;
643     }
644     if (!isLangId)
645         return;
646 
647     // Set the "checked" item state for lang entries
648     for (auto& entry : m_stemLangToId) {
649         entry.second->setChecked(false);
650     }
651     id->setChecked(true);
652 
653     // Retrieve language value (also handle special cases), set prefs,
654     // notify that we changed
655     QString lang;
656     if (id == m_idNoStem) {
657         lang = "";
658     } else if (id == m_idAllStem) {
659         lang = "ALL";
660     } else {
661         lang = id->text();
662     }
663     prefs.queryStemLang = lang;
664     LOGDEB("RclMain::setStemLang(" << id << "): lang [" <<
665            qs2utf8s(prefs.queryStemLang) << "]\n");
666     rwSettings(true);
667     emit stemLangChanged(lang);
668 }
669 
670 // Set the checked stemming language item before showing the prefs menu
setStemLang(const QString & lang)671 void RclMain::setStemLang(const QString& lang)
672 {
673     LOGDEB("RclMain::setStemLang(" << qs2utf8s(lang) << ")\n");
674     QAction *id;
675     if (lang == "") {
676         id = m_idNoStem;
677     } else if (lang == "ALL") {
678         id = m_idAllStem;
679     } else {
680         auto it = m_stemLangToId.find(lang);
681         if (it == m_stemLangToId.end())
682             return;
683         id = it->second;
684     }
685     for (const auto& entry : m_stemLangToId) {
686         entry.second->setChecked(false);
687     }
688     id->setChecked(true);
689 }
690 
691 // Prefs menu about to show
adjustPrefsMenu()692 void RclMain::adjustPrefsMenu()
693 {
694     setStemLang(prefs.queryStemLang);
695 }
696 
showTrayMessage(const QString & text)697 void RclMain::showTrayMessage(const QString& text)
698 {
699     if (m_trayicon && prefs.trayMessages)
700         m_trayicon->showMessage("Recoll", text,
701                                 QSystemTrayIcon::Information, 2000);
702 }
703 
closeEvent(QCloseEvent * ev)704 void RclMain::closeEvent(QCloseEvent *ev)
705 {
706     LOGDEB("RclMain::closeEvent\n");
707     if (isFullScreen()) {
708         prefs.showmode = PrefsPack::SHOW_FULL;
709     } else if (isMaximized()) {
710         prefs.showmode = PrefsPack::SHOW_MAX;
711     } else {
712         prefs.showmode = PrefsPack::SHOW_NORMAL;
713     }
714     ev->ignore();
715     if (prefs.closeToTray && m_trayicon && m_trayicon->isVisible()) {
716         hide();
717         return;
718     }
719     fileExit();
720 }
721 
fileExit()722 void RclMain::fileExit()
723 {
724     LOGDEB("RclMain: fileExit\n");
725     // Have to do this both in closeEvent (for close to tray) and fileExit
726     // (^Q, doesnt go through closeEvent)
727     if (isFullScreen()) {
728         prefs.showmode = PrefsPack::SHOW_FULL;
729     } else if (isMaximized()) {
730         prefs.showmode = PrefsPack::SHOW_MAX;
731     } else {
732         prefs.showmode = PrefsPack::SHOW_NORMAL;
733     }
734     if (m_trayicon) {
735         m_trayicon->setVisible(false);
736     }
737 
738     // Don't save geometry if we're currently maximized. At least under X11
739     // this saves the maximized size. otoh isFullscreen() does not seem needed
740     QSettings settings;
741     if (!isMaximized()) {
742         settings.setValue("/Recoll/geometry/maingeom", saveGeometry());
743     }
744     if (!prefs.noToolbars) {
745         settings.setValue(settingskey_toolarea, toolBarArea(m_toolsTB));
746         settings.setValue(settingskey_resarea, toolBarArea(m_resTB));
747     }
748     restable->saveColState();
749 
750     if (prefs.ssearchTypSav) {
751         prefs.ssearchTyp = sSearch->searchTypCMB->currentIndex();
752     }
753 
754     rwSettings(true);
755 
756     deleteAllTempFiles();
757     qApp->exit(0);
758 }
759 
760 // Start a db query and set the reslist docsource
startSearch(std::shared_ptr<Rcl::SearchData> sdata,bool issimple)761 void RclMain::startSearch(std::shared_ptr<Rcl::SearchData> sdata, bool issimple)
762 {
763     LOGDEB("RclMain::startSearch. Indexing " << (m_idxproc?"on":"off") <<
764            " Active " << m_queryActive << "\n");
765     if (m_queryActive) {
766         LOGDEB("startSearch: already active\n");
767         return;
768     }
769     m_queryActive = true;
770     restable->setEnabled(false);
771     m_source = std::shared_ptr<DocSequence>();
772 
773     m_searchIsSimple = issimple;
774 
775     // The db may have been closed at the end of indexing
776     string reason;
777     // If indexing is being performed, we reopen the db at each query.
778     if (!maybeOpenDb(reason, m_idxproc != 0)) {
779         QMessageBox::critical(0, "Recoll", u8s2qs(reason));
780         m_queryActive = false;
781         restable->setEnabled(true);
782         return;
783     }
784 
785     if (prefs.synFileEnable && !prefs.synFile.isEmpty()) {
786         if (!rcldb->setSynGroupsFile(qs2path(prefs.synFile))) {
787             QMessageBox::warning(0, "Recoll",
788                                  tr("Can't set synonyms file (parse error?)"));
789             return;
790         }
791     } else {
792         rcldb->setSynGroupsFile("");
793     }
794 
795     Rcl::Query *query = new Rcl::Query(rcldb.get());
796     query->setCollapseDuplicates(prefs.collapseDuplicates);
797 
798     curPreview = 0;
799     DocSequenceDb *src =
800         new DocSequenceDb(rcldb, std::shared_ptr<Rcl::Query>(query),
801                           qs2utf8s(tr("Query results")), sdata);
802     src->setAbstractParams(prefs.queryBuildAbstract,
803                            prefs.queryReplaceAbstract);
804     m_source = std::shared_ptr<DocSequence>(src);
805 
806     // If this is a file name search sort by mtype so that directories
807     // come first (see the rclquery sort key generator)
808     if (sSearch->searchTypCMB->currentIndex() == SSearch::SST_FNM &&
809         m_sortspec.field.empty()) {
810         m_sortspec.field = "mtype";
811         m_sortspec.desc = false;
812     }
813     m_source->setSortSpec(m_sortspec);
814     m_source->setFiltSpec(m_filtspec);
815 
816     emit docSourceChanged(m_source);
817     emit sortDataChanged(m_sortspec);
818     initiateQuery();
819 }
820 
821 class QueryThread : public QThread {
822     std::shared_ptr<DocSequence> m_source;
823 public:
QueryThread(std::shared_ptr<DocSequence> source)824     QueryThread(std::shared_ptr<DocSequence> source)
825         : m_source(source)
826     {
827     }
~QueryThread()828     ~QueryThread() { }
run()829     virtual void run()
830     {
831         cnt = m_source->getResCnt();
832     }
833     int cnt;
834 };
835 
hideToolTip()836 void RclMain::hideToolTip()
837 {
838     QToolTip::hideText();
839 }
840 
initiateQuery()841 void RclMain::initiateQuery()
842 {
843     if (!m_source)
844         return;
845 
846     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
847     QueryThread qthr(m_source);
848     qthr.start();
849 
850     QProgressDialog progress(this);
851     progress.setLabelText(tr("Query in progress.<br>"
852                              "Due to limitations of the indexing library,<br>"
853                              "cancelling will exit the program"));
854     progress.setWindowModality(Qt::WindowModal);
855     progress.setRange(0,0);
856 
857     // For some reason setMinimumDuration() does not seem to work with
858     // a busy dialog (range 0,0) Have to call progress.show() inside
859     // the loop.
860     // progress.setMinimumDuration(2000);
861     // Also the multiple processEvents() seem to improve the responsiveness??
862     for (int i = 0;;i++) {
863         qApp->processEvents();
864         if (qthr.wait(100)) {
865             break;
866         }
867         if (i == 20)
868             progress.show();
869         qApp->processEvents();
870         if (progress.wasCanceled()) {
871             // Just get out of there asap.
872             exit(1);
873         }
874 
875         qApp->processEvents();
876     }
877 
878     int cnt = qthr.cnt;
879     QString msg;
880     if (cnt > 0) {
881         QString str;
882         msg = tr("Result count (est.)") + ": " +
883             str.setNum(cnt);
884     } else {
885         msg = tr("No results found");
886     }
887 
888     statusBar()->showMessage(msg, 0);
889     QApplication::restoreOverrideCursor();
890     m_queryActive = false;
891     restable->setEnabled(true);
892     emit(resultsReady());
893 }
894 
resetSearch()895 void RclMain::resetSearch()
896 {
897     m_source = std::shared_ptr<DocSequence>();
898     emit searchReset();
899 }
900 
onSortCtlChanged()901 void RclMain::onSortCtlChanged()
902 {
903     if (m_sortspecnochange)
904         return;
905 
906     LOGDEB("RclMain::onSortCtlChanged()\n");
907     m_sortspec.reset();
908     if (actionSortByDateAsc->isChecked()) {
909         m_sortspec.field = "mtime";
910         m_sortspec.desc = false;
911         prefs.sortActive = true;
912         prefs.sortDesc = false;
913         prefs.sortField = "mtime";
914     } else if (actionSortByDateDesc->isChecked()) {
915         m_sortspec.field = "mtime";
916         m_sortspec.desc = true;
917         prefs.sortActive = true;
918         prefs.sortDesc = true;
919         prefs.sortField = "mtime";
920     } else {
921         prefs.sortActive = prefs.sortDesc = false;
922         prefs.sortField = "";
923         // If this is a file name search sort by mtype so that directories
924         // come first (see the rclquery sort key generator)
925         if (sSearch->searchTypCMB->currentIndex() == SSearch::SST_FNM) {
926             m_sortspec.field = "mtype";
927             m_sortspec.desc = false;
928         }
929     }
930     if (m_source)
931         m_source->setSortSpec(m_sortspec);
932     emit sortDataChanged(m_sortspec);
933     initiateQuery();
934 }
935 
onSortDataChanged(DocSeqSortSpec spec)936 void RclMain::onSortDataChanged(DocSeqSortSpec spec)
937 {
938     LOGDEB("RclMain::onSortDataChanged\n");
939     m_sortspecnochange = true;
940     if (spec.field.compare("mtime")) {
941         actionSortByDateDesc->setChecked(false);
942         actionSortByDateAsc->setChecked(false);
943     } else {
944         actionSortByDateDesc->setChecked(spec.desc);
945         actionSortByDateAsc->setChecked(!spec.desc);
946     }
947     m_sortspecnochange = false;
948     if (m_source)
949         m_source->setSortSpec(spec);
950     m_sortspec = spec;
951 
952     prefs.sortField = QString::fromUtf8(spec.field.c_str());
953     prefs.sortDesc = spec.desc;
954     prefs.sortActive = !spec.field.empty();
955 
956     std::string fld;
957     if (!m_sortspec.field.empty()) {
958         fld = qs2utf8s(RecollModel::displayableField(m_sortspec.field));
959     }
960     DocSequence::set_translations(
961         qs2utf8s(tr("sorted")) + ": " + fld +
962         (m_sortspec.desc?" &darr;":" &uarr;"),
963         qs2utf8s(tr("filtered")));
964     initiateQuery();
965 }
966 
967 // Needed only because an action is not a widget, so can't be used
968 // with SETSHORTCUT
toggleTable()969 void RclMain::toggleTable()
970 {
971     actionShowResultsAsTable->toggle();
972 }
973 
on_actionShowResultsAsTable_toggled(bool on)974 void RclMain::on_actionShowResultsAsTable_toggled(bool on)
975 {
976     LOGDEB("RclMain::on_actionShowResultsAsTable_toggled(" << on << ")\n");
977     prefs.showResultsAsTable = on;
978     displayingTable = on;
979     restable->setVisible(on);
980     reslist->setVisible(!on);
981     actionSaveResultsAsCSV->setEnabled(on);
982     if (!on) {
983         int docnum = restable->getDetailDocNumOrTopRow();
984         if (docnum >= 0) {
985             reslist->resultPageFor(docnum);
986         }
987         if (m_focustotablesc)
988             disconnect(m_focustotablesc, SIGNAL(activated()),
989                        restable, SLOT(takeFocus()));
990         sSearch->takeFocus();
991     } else {
992         int docnum = reslist->pageFirstDocNum();
993         if (docnum >= 0) {
994             restable->makeRowVisible(docnum);
995         }
996         nextPageAction->setEnabled(false);
997         prevPageAction->setEnabled(false);
998         firstPageAction->setEnabled(false);
999         if (m_focustotablesc)
1000             connect(m_focustotablesc, SIGNAL(activated()),
1001                     restable, SLOT(takeFocus()));
1002     }
1003 }
1004 
on_actionSortByDateAsc_toggled(bool on)1005 void RclMain::on_actionSortByDateAsc_toggled(bool on)
1006 {
1007     LOGDEB("RclMain::on_actionSortByDateAsc_toggled(" << on << ")\n");
1008     if (on) {
1009         if (actionSortByDateDesc->isChecked()) {
1010             actionSortByDateDesc->setChecked(false);
1011             // Let our buddy work.
1012             return;
1013         }
1014     }
1015     onSortCtlChanged();
1016 }
1017 
on_actionSortByDateDesc_toggled(bool on)1018 void RclMain::on_actionSortByDateDesc_toggled(bool on)
1019 {
1020     LOGDEB("RclMain::on_actionSortByDateDesc_toggled(" << on << ")\n");
1021     if (on) {
1022         if (actionSortByDateAsc->isChecked()) {
1023             actionSortByDateAsc->setChecked(false);
1024             // Let our buddy work.
1025             return;
1026         }
1027     }
1028     onSortCtlChanged();
1029 }
1030 
saveDocToFile(Rcl::Doc doc)1031 void RclMain::saveDocToFile(Rcl::Doc doc)
1032 {
1033     QString s = QFileDialog::getSaveFileName(
1034         this, tr("Save file"), path2qs(path_home()));
1035     string tofile = qs2path(s);
1036     TempFile temp; // not used because tofile is set.
1037     if (!FileInterner::idocToFile(temp, tofile, theconfig, doc)) {
1038         QMessageBox::warning(0, "Recoll",
1039                              tr("Cannot extract document or create "
1040                                 "temporary file"));
1041         return;
1042     }
1043 }
1044 
showSubDocs(Rcl::Doc doc)1045 void RclMain::showSubDocs(Rcl::Doc doc)
1046 {
1047     LOGDEB("RclMain::showSubDocs\n");
1048     string reason;
1049     if (!maybeOpenDb(reason, false)) {
1050         QMessageBox::critical(0, "Recoll", QString(reason.c_str()));
1051         return;
1052     }
1053     vector<Rcl::Doc> docs;
1054     if (!rcldb->getSubDocs(doc, docs)) {
1055         QMessageBox::warning(0, "Recoll", QString("Can't get subdocs"));
1056         return;
1057     }
1058     DocSequenceDocs *src =
1059         new DocSequenceDocs(rcldb, docs,
1060                             qs2utf8s(tr("Sub-documents and attachments")));
1061     src->setDescription(qs2utf8s(tr("Sub-documents and attachments")));
1062     std::shared_ptr<DocSequence>
1063         source(new DocSource(theconfig, std::shared_ptr<DocSequence>(src)));
1064 
1065     ResTable *res = new ResTable();
1066     res->setRclMain(this, false);
1067     res->setDocSource(source);
1068     res->readDocSource();
1069     res->show();
1070 }
1071 
1072 // Search for document 'like' the selected one. We ask rcldb/xapian to find
1073 // significant terms, and add them to the simple search entry.
docExpand(Rcl::Doc doc)1074 void RclMain::docExpand(Rcl::Doc doc)
1075 {
1076     LOGDEB("RclMain::docExpand()\n");
1077     if (!rcldb)
1078         return;
1079     list<string> terms;
1080 
1081     terms = m_source->expand(doc);
1082     if (terms.empty()) {
1083         LOGDEB("RclMain::docExpand: no terms\n");
1084         return;
1085     }
1086     // Do we keep the original query. I think we'd better not.
1087     // rcldb->expand is set to keep the original query terms instead.
1088     QString text;// = sSearch->queryText->currentText();
1089     for (list<string>::iterator it = terms.begin(); it != terms.end(); it++) {
1090         text += QString::fromLatin1(" \"") +
1091             QString::fromUtf8((*it).c_str()) + QString::fromLatin1("\"");
1092     }
1093     // We need to insert item here, its not auto-done like when the user types
1094     // CR
1095     sSearch->setSearchString(text);
1096     sSearch->setAnyTermMode();
1097     sSearch->startSimpleSearch();
1098 }
1099 
showDocHistory()1100 void RclMain::showDocHistory()
1101 {
1102     LOGDEB("RclMain::showDocHistory\n");
1103     resetSearch();
1104     curPreview = 0;
1105 
1106     string reason;
1107     if (!maybeOpenDb(reason, false)) {
1108         QMessageBox::critical(0, "Recoll", QString(reason.c_str()));
1109         return;
1110     }
1111     // Construct a bogus SearchData structure
1112     std::shared_ptr<Rcl::SearchData>searchdata =
1113         std::shared_ptr<Rcl::SearchData>(new Rcl::SearchData(Rcl::SCLT_AND,
1114                                                              cstr_null));
1115     searchdata->setDescription((const char *)tr("History data").toUtf8());
1116 
1117 
1118     // If you change the title, also change it in eraseDocHistory()
1119     DocSequenceHistory *src =
1120         new DocSequenceHistory(rcldb, g_dynconf,
1121                                string(tr("Document history").toUtf8()));
1122     src->setDescription((const char *)tr("History data").toUtf8());
1123     DocSource *source = new DocSource(theconfig,
1124                                       std::shared_ptr<DocSequence>(src));
1125     m_source = std::shared_ptr<DocSequence>(source);
1126     m_source->setSortSpec(m_sortspec);
1127     m_source->setFiltSpec(m_filtspec);
1128     emit docSourceChanged(m_source);
1129     emit sortDataChanged(m_sortspec);
1130     initiateQuery();
1131 }
1132 
1133 // Erase all memory of documents viewed
eraseDocHistory()1134 void RclMain::eraseDocHistory()
1135 {
1136     // Clear file storage
1137     if (g_dynconf)
1138         g_dynconf->eraseAll(docHistSubKey);
1139     // Clear possibly displayed history
1140     if (reslist->displayingHistory()) {
1141         showDocHistory();
1142     }
1143 }
1144 
eraseSearchHistory()1145 void RclMain::eraseSearchHistory()
1146 {
1147     int rep = QMessageBox::warning(
1148         0, tr("Confirm"),
1149         tr("Erasing simple and advanced search history lists, "
1150            "please click Ok to confirm"),
1151         QMessageBox::Ok, QMessageBox::Cancel, QMessageBox::NoButton);
1152     if (rep == QMessageBox::Ok) {
1153         prefs.ssearchHistory.clear();
1154         if (sSearch)
1155             sSearch->clearAll();
1156         if (g_advshistory)
1157             g_advshistory->clear();
1158     }
1159 }
1160 
exportSimpleSearchHistory()1161 void RclMain::exportSimpleSearchHistory()
1162 {
1163     QFileDialog dialog(0, "Saving simple search history");
1164     dialog.setFileMode(QFileDialog::AnyFile);
1165     dialog.setAcceptMode(QFileDialog::AcceptSave);
1166     dialog.setViewMode(QFileDialog::List);
1167     QFlags<QDir::Filter> flags = QDir::NoDotAndDotDot|QDir::Files;
1168     dialog.setFilter(flags);
1169     if (dialog.exec() != QDialog::Accepted) {
1170         return;
1171     }
1172     string path = qs2utf8s(dialog.selectedFiles().value(0));
1173     LOGDEB("Chosen path: " << path << "\n");
1174     std::fstream fp;
1175     if (!path_streamopen(path, std::ios::out | std::ios::trunc, fp)) {
1176         QMessageBox::warning(0, "Recoll", tr("Could not open/create file"));
1177         return;
1178     }
1179     for (int i = 0; i < prefs.ssearchHistory.count(); i++) {
1180         fp << qs2utf8s(prefs.ssearchHistory[i]) << "\n";
1181     }
1182     fp.close();
1183 }
1184 
1185 // Called when the uiprefs dialog is ok'd
setUIPrefs()1186 void RclMain::setUIPrefs()
1187 {
1188     if (!uiprefs)
1189         return;
1190     LOGDEB("Recollmain::setUIPrefs\n");
1191     emit uiPrefsChanged();
1192     enbSynAction->setDisabled(prefs.synFile.isEmpty());
1193     enbSynAction->setChecked(prefs.synFileEnable);
1194 }
1195 
enableNextPage(bool yesno)1196 void RclMain::enableNextPage(bool yesno)
1197 {
1198     if (!displayingTable)
1199         nextPageAction->setEnabled(yesno);
1200 }
1201 
enablePrevPage(bool yesno)1202 void RclMain::enablePrevPage(bool yesno)
1203 {
1204     if (!displayingTable) {
1205         prevPageAction->setEnabled(yesno);
1206         firstPageAction->setEnabled(yesno);
1207     }
1208 }
1209 
onSetDescription(QString desc)1210 void RclMain::onSetDescription(QString desc)
1211 {
1212     m_queryDescription = desc;
1213 }
1214 
getQueryDescription()1215 QString RclMain::getQueryDescription()
1216 {
1217     if (!m_source)
1218         return "";
1219     return m_queryDescription.isEmpty() ?
1220         u8s2qs(m_source->getDescription()) : m_queryDescription;
1221 }
1222 
1223 // Set filter, action style
catgFilter(QAction * act)1224 void RclMain::catgFilter(QAction *act)
1225 {
1226     int id = act->data().toInt();
1227     catgFilter(id);
1228 }
1229 
1230 // User pressed a filter button: set filter params in reslist
catgFilter(int id)1231 void RclMain::catgFilter(int id)
1232 {
1233     LOGDEB("RclMain::catgFilter: id " << id << "\n");
1234     if (id < 0 || id >= int(m_catgbutvec.size()))
1235         return;
1236 
1237     switch (prefs.filterCtlStyle) {
1238     case PrefsPack::FCS_MN:
1239         m_filtCMB->setCurrentIndex(id);
1240         m_filtBGRP->buttons()[id]->setChecked(true);
1241         break;
1242     case PrefsPack::FCS_CMB:
1243         m_filtBGRP->buttons()[id]->setChecked(true);
1244         m_filtMN->actions()[id]->setChecked(true);
1245         break;
1246     case PrefsPack::FCS_BT:
1247     default:
1248         m_filtCMB->setCurrentIndex(id);
1249         m_filtMN->actions()[id]->setChecked(true);
1250     }
1251 
1252     m_catgbutvecidx = id;
1253     setFiltSpec();
1254 }
1255 
setFiltSpec()1256 void RclMain::setFiltSpec()
1257 {
1258     m_filtspec.reset();
1259 
1260     // "Category" buttons
1261     if (m_catgbutvecidx != 0)  {
1262         string catg = m_catgbutvec[m_catgbutvecidx];
1263         string frag;
1264         theconfig->getGuiFilter(catg, frag);
1265         m_filtspec.orCrit(DocSeqFiltSpec::DSFS_QLANG, frag);
1266     }
1267 
1268     // Fragments from the fragbuts buttonbox tool
1269     if (fragbuts) {
1270         vector<string> frags;
1271         fragbuts->getfrags(frags);
1272         for (vector<string>::const_iterator it = frags.begin();
1273              it != frags.end(); it++) {
1274             m_filtspec.orCrit(DocSeqFiltSpec::DSFS_QLANG, *it);
1275         }
1276     }
1277 
1278     if (m_source)
1279         m_source->setFiltSpec(m_filtspec);
1280     initiateQuery();
1281 }
1282 
onFragmentsChanged()1283 void RclMain::onFragmentsChanged()
1284 {
1285     setFiltSpec();
1286 }
1287 
toggleFullScreen()1288 void RclMain::toggleFullScreen()
1289 {
1290     if (isFullScreen())
1291         showNormal();
1292     else
1293         showFullScreen();
1294 }
1295 
showEvent(QShowEvent * ev)1296 void RclMain::showEvent(QShowEvent *ev)
1297 {
1298     sSearch->takeFocus();
1299     QMainWindow::showEvent(ev);
1300 }
1301 
applyStyleSheet()1302 void RclMain::applyStyleSheet()
1303 {
1304     ::applyStyleSheet(prefs.qssFile);
1305     if (m_source) {
1306         emit docSourceChanged(m_source);
1307         emit sortDataChanged(m_sortspec);
1308     } else {
1309         resetSearch();
1310     }
1311 }
1312