566 lines
16 KiB
C++
566 lines
16 KiB
C++
/* Copyright (C) 2005 J.F.Dockes
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the
|
|
* Free Software Foundation, Inc.,
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
#include "advsearch_w.h"
|
|
|
|
#include <qvariant.h>
|
|
#include <qpushbutton.h>
|
|
#include <qlabel.h>
|
|
#include <qlineedit.h>
|
|
#include <qframe.h>
|
|
#include <qcheckbox.h>
|
|
#include <qevent.h>
|
|
#include <qlayout.h>
|
|
#include <qtooltip.h>
|
|
#include <qwhatsthis.h>
|
|
#include <qmessagebox.h>
|
|
#include <QShortcut>
|
|
|
|
#include <string>
|
|
#include <map>
|
|
#include <algorithm>
|
|
using namespace std;
|
|
|
|
#include "recoll.h"
|
|
#include "rclconfig.h"
|
|
#include "debuglog.h"
|
|
#include "searchdata.h"
|
|
#include "guiutils.h"
|
|
#include "rclhelp.h"
|
|
|
|
static const int initclausetypes[] = {1, 3, 0, 2, 5};
|
|
static const unsigned int iclausescnt = sizeof(initclausetypes) / sizeof(int);
|
|
static map<QString,QString> cat_translations;
|
|
static map<QString,QString> cat_rtranslations;
|
|
|
|
void AdvSearch::init()
|
|
{
|
|
(void)new HelpClient(this);
|
|
HelpClient::installMap((const char *)objectName().toUtf8(),
|
|
"RCL.SEARCH.COMPLEX");
|
|
|
|
// signals and slots connections
|
|
connect(delFiltypPB, SIGNAL(clicked()), this, SLOT(delFiltypPB_clicked()));
|
|
connect(searchPB, SIGNAL(clicked()), this, SLOT(runSearch()));
|
|
connect(filterDatesCB, SIGNAL(toggled(bool)),
|
|
this, SLOT(filterDatesCB_toggled(bool)));
|
|
connect(filterSizesCB, SIGNAL(toggled(bool)),
|
|
this, SLOT(filterSizesCB_toggled(bool)));
|
|
connect(restrictFtCB, SIGNAL(toggled(bool)),
|
|
this, SLOT(restrictFtCB_toggled(bool)));
|
|
connect(restrictCtCB, SIGNAL(toggled(bool)),
|
|
this, SLOT(restrictCtCB_toggled(bool)));
|
|
connect(dismissPB, SIGNAL(clicked()), this, SLOT(close()));
|
|
connect(browsePB, SIGNAL(clicked()), this, SLOT(browsePB_clicked()));
|
|
connect(addFiltypPB, SIGNAL(clicked()), this, SLOT(addFiltypPB_clicked()));
|
|
|
|
connect(delAFiltypPB, SIGNAL(clicked()),
|
|
this, SLOT(delAFiltypPB_clicked()));
|
|
connect(addAFiltypPB, SIGNAL(clicked()),
|
|
this, SLOT(addAFiltypPB_clicked()));
|
|
connect(saveFileTypesPB, SIGNAL(clicked()),
|
|
this, SLOT(saveFileTypes()));
|
|
connect(addClausePB, SIGNAL(clicked()), this, SLOT(addClause()));
|
|
connect(delClausePB, SIGNAL(clicked()), this, SLOT(delClause()));
|
|
|
|
new QShortcut(QKeySequence(Qt::Key_Up), this, SLOT(slotHistoryNext()));;
|
|
new QShortcut(QKeySequence(Qt::Key_Down), this, SLOT(slotHistoryPrev()));
|
|
|
|
conjunctCMB->insertItem(1, tr("All clauses"));
|
|
conjunctCMB->insertItem(2, tr("Any clause"));
|
|
|
|
// Create preconfigured clauses
|
|
for (unsigned int i = 0; i < iclausescnt; i++) {
|
|
addClause(initclausetypes[i]);
|
|
}
|
|
// Tune initial state according to last saved
|
|
{
|
|
vector<SearchClauseW *>::iterator cit = m_clauseWins.begin();
|
|
for (vector<int>::iterator it = prefs.advSearchClauses.begin();
|
|
it != prefs.advSearchClauses.end(); it++) {
|
|
if (cit != m_clauseWins.end()) {
|
|
(*cit)->tpChange(*it);
|
|
cit++;
|
|
} else {
|
|
addClause(*it);
|
|
}
|
|
}
|
|
}
|
|
(*m_clauseWins.begin())->wordsLE->setFocus();
|
|
|
|
// Initialize min/max mtime from extrem values in the index
|
|
int minyear, maxyear;
|
|
if (rcldb) {
|
|
rcldb->maxYearSpan(&minyear, &maxyear);
|
|
minDateDTE->setDisplayFormat("dd.MM.yyyy");
|
|
maxDateDTE->setDisplayFormat("dd.MM.yyyy");
|
|
minDateDTE->setDate(QDate(minyear, 1, 1));
|
|
maxDateDTE->setDate(QDate(maxyear, 12, 31));
|
|
}
|
|
|
|
// Initialize lists of accepted and ignored mime types from config
|
|
// and settings
|
|
m_ignTypes = prefs.asearchIgnFilTyps;
|
|
m_ignByCats = prefs.fileTypesByCats;
|
|
restrictCtCB->setEnabled(false);
|
|
restrictCtCB->setChecked(m_ignByCats);
|
|
fillFileTypes();
|
|
|
|
subtreeCMB->insertItems(0, prefs.asearchSubdirHist);
|
|
subtreeCMB->setEditText("");
|
|
|
|
// The clauseline frame is needed to force designer to accept a
|
|
// vbox to englobe the base clauses grid and 'something else' (the
|
|
// vbox is so that we can then insert SearchClauseWs), but we
|
|
// don't want to see it.
|
|
clauseline->close();
|
|
|
|
bool calpop = 0;
|
|
minDateDTE->setCalendarPopup(calpop);
|
|
maxDateDTE->setCalendarPopup(calpop);
|
|
|
|
// Translations for known categories
|
|
cat_translations[QString::fromUtf8("texts")] = tr("text");
|
|
cat_rtranslations[tr("texts")] = QString::fromUtf8("text");
|
|
|
|
cat_translations[QString::fromUtf8("spreadsheet")] = tr("spreadsheet");
|
|
cat_rtranslations[tr("spreadsheets")] = QString::fromUtf8("spreadsheet");
|
|
|
|
cat_translations[QString::fromUtf8("presentation")] = tr("presentation");
|
|
cat_rtranslations[tr("presentation")] =QString::fromUtf8("presentation");
|
|
|
|
cat_translations[QString::fromUtf8("media")] = tr("media");
|
|
cat_rtranslations[tr("media")] = QString::fromUtf8("media");
|
|
|
|
cat_translations[QString::fromUtf8("message")] = tr("message");
|
|
cat_rtranslations[tr("message")] = QString::fromUtf8("message");
|
|
|
|
cat_translations[QString::fromUtf8("other")] = tr("other");
|
|
cat_rtranslations[tr("other")] = QString::fromUtf8("other");
|
|
}
|
|
|
|
void AdvSearch::saveCnf()
|
|
{
|
|
// Save my state
|
|
prefs.advSearchClauses.clear();
|
|
for (vector<SearchClauseW *>::iterator cit = m_clauseWins.begin();
|
|
cit != m_clauseWins.end(); cit++) {
|
|
prefs.advSearchClauses.push_back((*cit)->sTpCMB->currentIndex());
|
|
}
|
|
}
|
|
|
|
bool AdvSearch::close()
|
|
{
|
|
saveCnf();
|
|
return QWidget::close();
|
|
}
|
|
|
|
void AdvSearch::addClause()
|
|
{
|
|
addClause(0);
|
|
}
|
|
|
|
void AdvSearch::addClause(int tp)
|
|
{
|
|
SearchClauseW *w = new SearchClauseW(clauseFRM);
|
|
m_clauseWins.push_back(w);
|
|
((QVBoxLayout *)(clauseFRM->layout()))->addWidget(w);
|
|
w->show();
|
|
w->tpChange(tp);
|
|
if (m_clauseWins.size() > iclausescnt) {
|
|
delClausePB->setEnabled(true);
|
|
} else {
|
|
delClausePB->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void AdvSearch::delClause()
|
|
{
|
|
if (m_clauseWins.size() <= iclausescnt)
|
|
return;
|
|
delete m_clauseWins.back();
|
|
m_clauseWins.pop_back();
|
|
if (m_clauseWins.size() > iclausescnt) {
|
|
delClausePB->setEnabled(true);
|
|
} else {
|
|
delClausePB->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void AdvSearch::delAFiltypPB_clicked()
|
|
{
|
|
yesFiltypsLB->selectAll();
|
|
delFiltypPB_clicked();
|
|
}
|
|
|
|
// Move selected file types from the searched to the ignored box
|
|
void AdvSearch::delFiltypPB_clicked()
|
|
{
|
|
QList<QListWidgetItem *> items = yesFiltypsLB->selectedItems();
|
|
for (QList<QListWidgetItem *>::iterator it = items.begin();
|
|
it != items.end(); it++) {
|
|
int row = yesFiltypsLB->row(*it);
|
|
QListWidgetItem *item = yesFiltypsLB->takeItem(row);
|
|
noFiltypsLB->insertItem(0, item);
|
|
}
|
|
guiListsToIgnTypes();
|
|
}
|
|
|
|
// Move selected file types from the ignored to the searched box
|
|
void AdvSearch::addFiltypPB_clicked()
|
|
{
|
|
QList<QListWidgetItem *> items = noFiltypsLB->selectedItems();
|
|
for (QList<QListWidgetItem *>::iterator it = items.begin();
|
|
it != items.end(); it++) {
|
|
int row = noFiltypsLB->row(*it);
|
|
QListWidgetItem *item = noFiltypsLB->takeItem(row);
|
|
yesFiltypsLB->insertItem(0, item);
|
|
}
|
|
guiListsToIgnTypes();
|
|
}
|
|
|
|
// Compute list of ignored mime type from widget lists
|
|
void AdvSearch::guiListsToIgnTypes()
|
|
{
|
|
yesFiltypsLB->sortItems();
|
|
noFiltypsLB->sortItems();
|
|
m_ignTypes.clear();
|
|
for (int i = 0; i < noFiltypsLB->count();i++) {
|
|
QListWidgetItem *item = noFiltypsLB->item(i);
|
|
m_ignTypes.append(item->text());
|
|
}
|
|
}
|
|
void AdvSearch::addAFiltypPB_clicked()
|
|
{
|
|
noFiltypsLB->selectAll();
|
|
addFiltypPB_clicked();
|
|
}
|
|
|
|
// Activate file type selection
|
|
void AdvSearch::restrictFtCB_toggled(bool on)
|
|
{
|
|
restrictCtCB->setEnabled(on);
|
|
yesFiltypsLB->setEnabled(on);
|
|
delFiltypPB->setEnabled(on);
|
|
addFiltypPB->setEnabled(on);
|
|
delAFiltypPB->setEnabled(on);
|
|
addAFiltypPB->setEnabled(on);
|
|
noFiltypsLB->setEnabled(on);
|
|
saveFileTypesPB->setEnabled(on);
|
|
}
|
|
|
|
// Activate file type selection
|
|
void AdvSearch::filterSizesCB_toggled(bool on)
|
|
{
|
|
minSizeLE->setEnabled(on);
|
|
maxSizeLE->setEnabled(on);
|
|
}
|
|
// Activate file type selection
|
|
void AdvSearch::filterDatesCB_toggled(bool on)
|
|
{
|
|
minDateDTE->setEnabled(on);
|
|
maxDateDTE->setEnabled(on);
|
|
}
|
|
|
|
void AdvSearch::restrictCtCB_toggled(bool on)
|
|
{
|
|
m_ignByCats = on;
|
|
// Only reset the list if we're enabled. Else this is init from prefs
|
|
if (restrictCtCB->isEnabled())
|
|
m_ignTypes.clear();
|
|
fillFileTypes();
|
|
}
|
|
|
|
void AdvSearch::fillFileTypes()
|
|
{
|
|
noFiltypsLB->clear();
|
|
yesFiltypsLB->clear();
|
|
noFiltypsLB->insertItems(0, m_ignTypes);
|
|
|
|
QStringList ql;
|
|
if (m_ignByCats == false) {
|
|
vector<string> types = theconfig->getAllMimeTypes();
|
|
for (vector<string>::iterator it = types.begin();
|
|
it != types.end(); it++) {
|
|
QString qs = QString::fromUtf8(it->c_str());
|
|
if (m_ignTypes.indexOf(qs) < 0)
|
|
ql.append(qs);
|
|
}
|
|
} else {
|
|
vector<string> cats;
|
|
theconfig->getMimeCategories(cats);
|
|
for (vector<string>::const_iterator it = cats.begin();
|
|
it != cats.end(); it++) {
|
|
map<QString, QString>::const_iterator it1;
|
|
QString cat;
|
|
if ((it1 = cat_translations.find(QString::fromUtf8(it->c_str())))
|
|
!= cat_translations.end()) {
|
|
cat = it1->second;
|
|
} else {
|
|
cat = QString::fromUtf8(it->c_str());
|
|
}
|
|
if (m_ignTypes.indexOf(cat) < 0)
|
|
ql.append(cat);
|
|
}
|
|
}
|
|
yesFiltypsLB->insertItems(0, ql);
|
|
}
|
|
|
|
// Save current set of ignored file types to prefs
|
|
void AdvSearch::saveFileTypes()
|
|
{
|
|
prefs.asearchIgnFilTyps = m_ignTypes;
|
|
prefs.fileTypesByCats = m_ignByCats;
|
|
rwSettings(true);
|
|
}
|
|
|
|
void AdvSearch::browsePB_clicked()
|
|
{
|
|
QString dir = myGetFileName(true);
|
|
subtreeCMB->setEditText(dir);
|
|
}
|
|
|
|
size_t AdvSearch::stringToSize(QString qsize)
|
|
{
|
|
size_t size = size_t(-1);
|
|
qsize.replace(QRegExp("[\\s]+"), "");
|
|
if (!qsize.isEmpty()) {
|
|
string csize((const char*)qsize.toAscii());
|
|
char *cp;
|
|
size = strtoll(csize.c_str(), &cp, 10);
|
|
if (*cp != 0) {
|
|
switch (*cp) {
|
|
case 'k': case 'K': size *= 1E3;break;
|
|
case 'm': case 'M': size *= 1E6;break;
|
|
case 'g': case 'G': size *= 1E9;break;
|
|
case 't': case 'T': size *= 1E12;break;
|
|
default:
|
|
QMessageBox::warning(0, "Recoll",
|
|
tr("Bad multiplier suffix in size filter"));
|
|
size = size_t(-1);
|
|
}
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
using namespace Rcl;
|
|
void AdvSearch::runSearch()
|
|
{
|
|
string stemLang = prefs.stemlang();
|
|
RefCntr<SearchData> sdata(new SearchData(conjunctCMB->currentIndex() == 0 ?
|
|
SCLT_AND : SCLT_OR, stemLang));
|
|
bool hasclause = false;
|
|
|
|
for (vector<SearchClauseW*>::iterator it = m_clauseWins.begin();
|
|
it != m_clauseWins.end(); it++) {
|
|
SearchDataClause *cl;
|
|
if ((cl = (*it)->getClause())) {
|
|
sdata->addClause(cl);
|
|
hasclause = true;
|
|
}
|
|
}
|
|
if (!hasclause)
|
|
return;
|
|
|
|
if (restrictFtCB->isChecked() && noFiltypsLB->count() > 0) {
|
|
for (int i = 0; i < yesFiltypsLB->count(); i++) {
|
|
if (restrictCtCB->isChecked()) {
|
|
QString qcat = yesFiltypsLB->item(i)->text();
|
|
map<QString,QString>::const_iterator qit;
|
|
string cat;
|
|
if ((qit = cat_rtranslations.find(qcat)) !=
|
|
cat_rtranslations.end()) {
|
|
cat = (const char *)qit->second.toUtf8();
|
|
} else {
|
|
cat = (const char *)qcat.toUtf8();
|
|
}
|
|
vector<string> types;
|
|
theconfig->getMimeCatTypes(cat, types);
|
|
for (vector<string>::const_iterator it = types.begin();
|
|
it != types.end(); it++) {
|
|
sdata->addFiletype(*it);
|
|
}
|
|
} else {
|
|
sdata->addFiletype((const char *)
|
|
yesFiltypsLB->item(i)->text().toUtf8());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filterDatesCB->isChecked()) {
|
|
QDate mindate = minDateDTE->date();
|
|
QDate maxdate = maxDateDTE->date();
|
|
DateInterval di;
|
|
di.y1 = mindate.year();
|
|
di.m1 = mindate.month();
|
|
di.d1 = mindate.day();
|
|
di.y2 = maxdate.year();
|
|
di.m2 = maxdate.month();
|
|
di.d2 = maxdate.day();
|
|
sdata->setDateSpan(&di);
|
|
}
|
|
if (filterSizesCB->isChecked()) {
|
|
size_t size = stringToSize(minSizeLE->text());
|
|
sdata->setMinSize(size);
|
|
size = stringToSize(maxSizeLE->text());
|
|
sdata->setMaxSize(size);
|
|
}
|
|
|
|
if (!subtreeCMB->currentText().isEmpty()) {
|
|
QString current = subtreeCMB->currentText();
|
|
sdata->addDirSpec((const char*)subtreeCMB->currentText().toUtf8(),
|
|
direxclCB->isChecked());
|
|
// Keep history clean and sorted. Maybe there would be a
|
|
// simpler way to do this
|
|
list<QString> entries;
|
|
for (int i = 0; i < subtreeCMB->count(); i++) {
|
|
entries.push_back(subtreeCMB->itemText(i));
|
|
}
|
|
entries.push_back(subtreeCMB->currentText());
|
|
entries.sort();
|
|
entries.unique();
|
|
LOGDEB(("Subtree list now has %d entries\n", entries.size()));
|
|
subtreeCMB->clear();
|
|
for (list<QString>::iterator it = entries.begin();
|
|
it != entries.end(); it++) {
|
|
subtreeCMB->addItem(*it);
|
|
}
|
|
subtreeCMB->setCurrentIndex(subtreeCMB->findText(current));
|
|
prefs.asearchSubdirHist.clear();
|
|
for (int index = 0; index < subtreeCMB->count(); index++)
|
|
prefs.asearchSubdirHist.push_back(subtreeCMB->itemText(index));
|
|
}
|
|
saveCnf();
|
|
g_advshistory && g_advshistory->push(sdata);
|
|
emit startSearch(sdata);
|
|
}
|
|
|
|
|
|
// Set up fields from existing search data, which must be compatible
|
|
// with what we can do...
|
|
void AdvSearch::fromSearch(RefCntr<SearchData> sdata)
|
|
{
|
|
if (sdata->m_tp == SCLT_OR)
|
|
conjunctCMB->setCurrentIndex(1);
|
|
else
|
|
conjunctCMB->setCurrentIndex(0);
|
|
|
|
while (sdata->m_query.size() > m_clauseWins.size()) {
|
|
addClause();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < sdata->m_query.size(); i++) {
|
|
// Set fields from clause
|
|
if (sdata->m_query[i]->getTp() == SCLT_SUB) {
|
|
LOGERR(("AdvSearch::fromSearch: SUB clause found !\n"));
|
|
continue;
|
|
}
|
|
SearchDataClauseSimple *cs =
|
|
dynamic_cast<SearchDataClauseSimple*>(sdata->m_query[i]);
|
|
m_clauseWins[i]->setFromClause(cs);
|
|
}
|
|
for (unsigned int i = sdata->m_query.size(); i < m_clauseWins.size(); i++) {
|
|
m_clauseWins[i]->clear();
|
|
}
|
|
|
|
restrictCtCB->setChecked(0);
|
|
if (!sdata->m_filetypes.empty()) {
|
|
restrictFtCB_toggled(1);
|
|
delAFiltypPB_clicked();
|
|
for (unsigned int i = 0; i < sdata->m_filetypes.size(); i++) {
|
|
QString ft = QString::fromUtf8(sdata->m_filetypes[i].c_str());
|
|
QList<QListWidgetItem *> lst =
|
|
noFiltypsLB->findItems(ft, Qt::MatchExactly);
|
|
if (!lst.isEmpty()) {
|
|
int row = noFiltypsLB->row(lst[0]);
|
|
QListWidgetItem *item = noFiltypsLB->takeItem(row);
|
|
yesFiltypsLB->insertItem(0, item);
|
|
}
|
|
}
|
|
yesFiltypsLB->sortItems();
|
|
} else {
|
|
addAFiltypPB_clicked();
|
|
restrictFtCB_toggled(0);
|
|
}
|
|
|
|
if (sdata->m_haveDates) {
|
|
filterDatesCB->setChecked(1);
|
|
DateInterval &di(sdata->m_dates);
|
|
QDate mindate(di.y1, di.m1, di.d1);
|
|
QDate maxdate(di.y2, di.m2, di.d2);
|
|
minDateDTE->setDate(mindate);
|
|
maxDateDTE->setDate(maxdate);
|
|
} else {
|
|
filterDatesCB->setChecked(0);
|
|
QDate date;
|
|
minDateDTE->setDate(date);
|
|
maxDateDTE->setDate(date);
|
|
}
|
|
|
|
if (sdata->m_maxSize != (size_t)-1 || sdata->m_minSize != (size_t)-1) {
|
|
filterSizesCB->setChecked(1);
|
|
QString sz;
|
|
if (sdata->m_minSize != (size_t)-1) {
|
|
sz.setNum(sdata->m_minSize);
|
|
minSizeLE->setText(sz);
|
|
} else {
|
|
minSizeLE->setText("");
|
|
}
|
|
if (sdata->m_maxSize != (size_t)-1) {
|
|
sz.setNum(sdata->m_maxSize);
|
|
maxSizeLE->setText(sz);
|
|
} else {
|
|
maxSizeLE->setText("");
|
|
}
|
|
} else {
|
|
filterSizesCB->setChecked(0);
|
|
minSizeLE->setText("");
|
|
maxSizeLE->setText("");
|
|
}
|
|
|
|
if (!sdata->m_dirspecs.empty()) {
|
|
// Can only use one entry
|
|
QString qdir = QString::fromLocal8Bit(sdata->m_dirspecs[0].dir.c_str());
|
|
subtreeCMB->setEditText(qdir);
|
|
direxclCB->setChecked(sdata->m_dirspecs[0].exclude);
|
|
} else {
|
|
subtreeCMB->setEditText("");
|
|
direxclCB->setChecked(0);
|
|
}
|
|
}
|
|
|
|
void AdvSearch::slotHistoryNext()
|
|
{
|
|
if (g_advshistory == 0)
|
|
return;
|
|
RefCntr<Rcl::SearchData> sd = g_advshistory->getnewer();
|
|
if (sd.isNull())
|
|
return;
|
|
fromSearch(sd);
|
|
}
|
|
|
|
void AdvSearch::slotHistoryPrev()
|
|
{
|
|
if (g_advshistory == 0)
|
|
return;
|
|
RefCntr<Rcl::SearchData> sd = g_advshistory->getolder();
|
|
if (sd.isNull())
|
|
return;
|
|
fromSearch(sd);
|
|
}
|
|
|