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?" ↓":" ↑"),
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