implemented advanced search history feature

This commit is contained in:
Jean-Francois Dockes
2012-10-16 13:37:56 +02:00
parent 9ccb862666
commit 816980a1c4
20 changed files with 736 additions and 97 deletions
+135 -22
View File
@@ -27,18 +27,12 @@
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qmessagebox.h>
#include <QShortcut>
#include <list>
#include <string>
#include <map>
#include <algorithm>
#ifndef NO_NAMESPACES
using std::list;
using std::string;
using std::map;
using std::unique;
#endif /* NO_NAMESPACES */
using namespace std;
#include "recoll.h"
#include "rclconfig.h"
@@ -52,7 +46,6 @@ 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);
@@ -83,6 +76,9 @@ void AdvSearch::init()
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"));
@@ -92,7 +88,7 @@ void AdvSearch::init()
}
// Tune initial state according to last saved
{
std::list<SearchClauseW *>::iterator cit = m_clauseWins.begin();
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()) {
@@ -109,6 +105,8 @@ void AdvSearch::init()
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));
}
@@ -158,7 +156,7 @@ void AdvSearch::saveCnf()
{
// Save my state
prefs.advSearchClauses.clear();
for (std::list<SearchClauseW *>::iterator cit = m_clauseWins.begin();
for (vector<SearchClauseW *>::iterator cit = m_clauseWins.begin();
cit != m_clauseWins.end(); cit++) {
prefs.advSearchClauses.push_back((*cit)->sTpCMB->currentIndex());
}
@@ -170,12 +168,6 @@ bool AdvSearch::close()
return QWidget::close();
}
void AdvSearch::delAFiltypPB_clicked()
{
yesFiltypsLB->selectAll();
delFiltypPB_clicked();
}
void AdvSearch::addClause()
{
addClause(0);
@@ -208,6 +200,12 @@ void AdvSearch::delClause()
}
}
void AdvSearch::delAFiltypPB_clicked()
{
yesFiltypsLB->selectAll();
delFiltypPB_clicked();
}
// Move selected file types from the searched to the ignored box
void AdvSearch::delFiltypPB_clicked()
{
@@ -329,6 +327,12 @@ void AdvSearch::saveFileTypes()
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);
@@ -361,7 +365,7 @@ void AdvSearch::runSearch()
SCLT_AND : SCLT_OR, stemLang));
bool hasclause = false;
for (list<SearchClauseW*>::iterator it = m_clauseWins.begin();
for (vector<SearchClauseW*>::iterator it = m_clauseWins.begin();
it != m_clauseWins.end(); it++) {
SearchDataClause *cl;
if ((cl = (*it)->getClause())) {
@@ -441,12 +445,121 @@ void AdvSearch::runSearch()
prefs.asearchSubdirHist.push_back(subtreeCMB->itemText(index));
}
saveCnf();
g_advshistory && g_advshistory->push(sdata);
emit startSearch(sdata);
}
void AdvSearch::browsePB_clicked()
// Set up fields from existing search data, which must be compatible
// with what we can do...
void AdvSearch::fromSearch(RefCntr<SearchData> sdata)
{
QString dir = myGetFileName(true);
subtreeCMB->setEditText(dir);
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);
}
+8 -2
View File
@@ -16,7 +16,9 @@
*/
#ifndef _ADVSEARCH_W_H_INCLUDED_
#define _ADVSEARCH_W_H_INCLUDED_
#include <list>
#include <vector>
#include <qvariant.h>
#include <qdialog.h>
@@ -24,6 +26,7 @@
#include "recoll.h"
#include "refcntr.h"
#include "searchdata.h"
#include "advshist.h"
class QDialog;
@@ -53,19 +56,22 @@ public slots:
virtual void restrictFtCB_toggled(bool);
virtual void restrictCtCB_toggled(bool);
virtual void runSearch();
virtual void fromSearch(RefCntr<Rcl::SearchData> sdata);
virtual void browsePB_clicked();
virtual void saveFileTypes();
virtual void delClause();
virtual void addClause();
virtual void addClause(int);
virtual bool close();
virtual void slotHistoryNext();
virtual void slotHistoryPrev();
signals:
void startSearch(RefCntr<Rcl::SearchData>);
private:
virtual void init();
std::list<SearchClauseW *> m_clauseWins;
std::vector<SearchClauseW *> m_clauseWins;
QStringList m_ignTypes;
bool m_ignByCats;
void saveCnf();
+253
View File
@@ -0,0 +1,253 @@
/* 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 "autoconfig.h"
#include "advshist.h"
#include "guiutils.h"
#include "debuglog.h"
#include <QtXml/QXmlDefaultHandler>
using namespace std;
using namespace Rcl;
class SDHXMLHandler : public QXmlDefaultHandler {
public:
SDHXMLHandler()
: slack(0)
{
}
bool startElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName,
const QXmlAttributes &attributes);
bool endElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName);
bool characters(const QString &str)
{
currentText += str;
return true;
}
// The object we set up
RefCntr<SearchData> sd;
private:
void resetTemps()
{
currentText = whatclause = "";
text.clear();
field.clear();
slack = 0;
d = m = y = di.d1 = di.m1 = di.y1 = di.d2 = di.m2 = di.y2 = 0;
hasdates = false;
}
// Temporary data while parsing.
QString currentText;
QString whatclause;
string field, text;
int slack;
int d, m, y;
DateInterval di;
bool hasdates;
};
bool SDHXMLHandler::startElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName,
const QXmlAttributes &)
{
LOGDEB2(("SDHXMLHandler::startElement: name [%s]\n",
(const char *)qName.toAscii()));
if (qName == "SD") {
resetTemps();
// A new search descriptor. Allocate data structure
sd = RefCntr<SearchData>(new SearchData);
if (sd.isNull()) {
LOGERR(("SDHXMLHandler::startElement: out of memory\n"));
return false;
}
}
return true;
}
bool SDHXMLHandler::endElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName)
{
LOGDEB2(("SDHXMLHandler::endElement: name [%s]\n",
(const char *)qName.toAscii()));
if (qName == "CLT") {
if (currentText == "OR") {
sd->setTp(SCLT_OR);
}
} else if (qName == "CT") {
whatclause = currentText.trimmed();
} else if (qName == "F") {
field = base64_decode(qs2utf8s(currentText.trimmed()));
} else if (qName == "T") {
text = base64_decode(qs2utf8s(currentText.trimmed()));
} else if (qName == "S") {
slack = atoi((const char *)currentText.toAscii());
} else if (qName == "C") {
SearchDataClauseSimple *c;
if (whatclause == "AND" || whatclause.isEmpty()) {
c = new SearchDataClauseSimple(SCLT_AND, text, field);
} else if (whatclause == "OR") {
c = new SearchDataClauseSimple(SCLT_OR, text, field);
} else if (whatclause == "EX") {
c = new SearchDataClauseSimple(SCLT_EXCL, text, field);
} else if (whatclause == "FN") {
c = new SearchDataClauseFilename(text);
} else if (whatclause == "PH") {
c = new SearchDataClauseDist(SCLT_PHRASE, text, slack, field);
} else if (whatclause == "NE") {
c = new SearchDataClauseDist(SCLT_NEAR, text, slack, field);
} else {
LOGERR(("Bad clause type [%s]\n", qs2utf8s(whatclause).c_str()));
return false;
}
sd->addClause(c);
whatclause = "";
text.clear();
field.clear();
slack = 0;
} else if (qName == "D") {
d = atoi((const char *)currentText.toAscii());
} else if (qName == "M") {
m = atoi((const char *)currentText.toAscii());
} else if (qName == "Y") {
y = atoi((const char *)currentText.toAscii());
} else if (qName == "DMI") {
di.d1 = d;
di.m1 = m;
di.y1 = y;
hasdates = true;
} else if (qName == "DMA") {
di.d2 = d;
di.m2 = m;
di.y2 = y;
hasdates = true;
} else if (qName == "MIS") {
sd->setMinSize(atoll((const char *)currentText.toAscii()));
} else if (qName == "MAS") {
sd->setMaxSize(atoll((const char *)currentText.toAscii()));
} else if (qName == "ST") {
string types = (const char *)currentText.toAscii();
vector<string> vt;
stringToTokens(types, vt);
for (unsigned int i = 0; i < vt.size(); i++)
sd->addFiletype(vt[i]);
} else if (qName == "IT") {
string types = (const char *)currentText.toAscii();
vector<string> vt;
stringToTokens(types, vt);
for (unsigned int i = 0; i < vt.size(); i++)
sd->remFiletype(vt[i]);
} else if (qName == "YD") {
string d;
base64_decode((const char*)currentText.trimmed().toAscii(), d);
sd->addDirSpec(d);
} else if (qName == "ND") {
string d;
base64_decode((const char*)currentText.trimmed().toAscii(), d);
sd->addDirSpec(d, true);
} else if (qName == "SD") {
// Closing current search descriptor. Finishing touches...
if (hasdates)
sd->setDateSpan(&di);
resetTemps();
}
currentText.clear();
return true;
}
AdvSearchHist::AdvSearchHist()
{
read();
m_current = -1;
}
AdvSearchHist::~AdvSearchHist()
{
for (vector<RefCntr<SearchData> >::iterator it = m_entries.begin();
it != m_entries.end(); it++) {
it->release();
}
}
RefCntr<Rcl::SearchData> AdvSearchHist::getolder()
{
m_current++;
if (m_current >= int(m_entries.size())) {
m_current--;
return RefCntr<Rcl::SearchData>();
}
return m_entries[m_current];
}
RefCntr<Rcl::SearchData> AdvSearchHist::getnewer()
{
if (m_current == -1 || m_current == 0 || m_entries.empty())
return RefCntr<Rcl::SearchData>();
return m_entries[--m_current];
}
bool AdvSearchHist::push(RefCntr<SearchData> sd)
{
m_entries.insert(m_entries.begin(), sd);
if (m_current != -1)
m_current++;
string xml = sd->asXML();
g_dynconf->enterString(advSearchHistSk, xml, 100);
return true;
}
bool AdvSearchHist::read()
{
if (!g_dynconf)
return false;
list<string> lxml = g_dynconf->getStringList(advSearchHistSk);
for (list<string>::const_iterator it = lxml.begin(); it != lxml.end();
it++) {
SDHXMLHandler handler;
QXmlSimpleReader reader;
reader.setContentHandler(&handler);
reader.setErrorHandler(&handler);
QXmlInputSource xmlInputSource;
xmlInputSource.setData(QString::fromUtf8(it->c_str()));
if (!reader.parse(xmlInputSource)) {
LOGERR(("AdvSearchHist::read: parse failed for [%s]\n",
it->c_str()));
return false;
}
m_entries.push_back(handler.sd);
}
return true;
}
void AdvSearchHist::clear()
{
g_dynconf->eraseAll(advSearchHistSk);
}
+43
View File
@@ -0,0 +1,43 @@
/* 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.
*/
#ifndef _ADVSHIST_H_INCLUDED_
#define _ADVSHIST_H_INCLUDED_
#include <vector>
#include "recoll.h"
#include "refcntr.h"
#include "searchdata.h"
class AdvSearchHist {
public:
AdvSearchHist();
~AdvSearchHist();
RefCntr<Rcl::SearchData> getolder();
RefCntr<Rcl::SearchData> getnewer();
bool push(RefCntr<Rcl::SearchData>);
void clear();
private:
bool read();
int m_current;
std::vector<RefCntr<Rcl::SearchData> > m_entries;
};
#endif // _ADVSHIST_H_INCLUDED_
+2
View File
@@ -25,12 +25,14 @@
#include "guiutils.h"
#include "pathut.h"
#include "base64.h"
#include "advshist.h"
#include <QSettings>
#include <QStringList>
#include <QFileDialog>
RclDynConf *g_dynconf;
AdvSearchHist *g_advshistory;
// The table should not be necessary, but I found no css way to get
// qt 4.6 qtextedit to clear the margins after the float img without
+3
View File
@@ -27,6 +27,9 @@
#include "dynconf.h"
extern RclDynConf *g_dynconf;
#include "advshist.h"
extern AdvSearchHist *g_advshistory;
#ifndef NO_NAMESPACES
using std::string;
using std::list;
+1
View File
@@ -338,6 +338,7 @@ int main(int argc, char **argv)
QMessageBox::critical(0, "Recoll", msg);
exit(1);
}
g_advshistory = new AdvSearchHist;
// fprintf(stderr, "History done\n");
rwSettings(false);
+4 -1
View File
@@ -1855,7 +1855,10 @@ void RclMain::eraseDocHistory()
void RclMain::eraseSearchHistory()
{
prefs.ssearchHistory.clear();
sSearch->queryText->clear();
if (sSearch)
sSearch->queryText->clear();
if (g_advshistory)
g_advshistory->clear();
}
// Called when the uiprefs dialog is ok'd
+5
View File
@@ -16,6 +16,7 @@
*/
#ifndef _RECOLL_H_INCLUDED_
#define _RECOLL_H_INCLUDED_
#include <string>
#include "rclconfig.h"
@@ -51,4 +52,8 @@ class Aspell;
extern Aspell *aspell;
#endif
inline std::string qs2utf8s(const QString& qs)
{
return std::string((const char *)qs.toUtf8());
}
#endif /* _RECOLL_H_INCLUDED_ */
+4 -1
View File
@@ -7,10 +7,13 @@ LANGUAGE = C++
@QMAKE_ENABLE_ZEITGEIST@QT += dbus
@QMAKE_ENABLE_ZEITGEIST@QMAKE_CXXFLAGS += -DUSE_ZEITGEIST
QT += xml
CONFIG += qt warn_on thread release
HEADERS += \
advsearch_w.h \
advshist.h \
confgui/confgui.h \
confgui/confguiindex.h \
crontool.h \
@@ -34,6 +37,7 @@ HEADERS += \
SOURCES += \
advsearch_w.cpp \
advshist.cpp \
confgui/confgui.cpp \
confgui/confguiindex.cpp \
crontool.cpp \
@@ -54,7 +58,6 @@ SOURCES += \
viewaction_w.cpp \
FORMS = \
advsearch.ui \
crontool.ui \
+59 -4
View File
@@ -17,6 +17,7 @@
#include "autoconfig.h"
#include "recoll.h"
#include "debuglog.h"
#include "searchclause_w.h"
@@ -28,6 +29,8 @@
#include <qtooltip.h>
#include <qwhatsthis.h>
using namespace Rcl;
/*
* Constructs a SearchClauseW as a child of 'parent', with the
* name 'name' and widget flags set to 'f'.
@@ -100,11 +103,8 @@ void SearchClauseW::languageChange()
proxSlackSB->setToolTip(tr("Number of additional words that may be interspersed with the chosen ones"));
}
using namespace Rcl;
// Translate my window state into an Rcl search clause
SearchDataClause *
SearchClauseW::getClause()
SearchDataClause *SearchClauseW::getClause()
{
if (wordsLE->text().isEmpty())
return 0;
@@ -134,6 +134,61 @@ SearchClauseW::getClause()
}
}
void SearchClauseW::setFromClause(SearchDataClauseSimple *cl)
{
LOGDEB(("SearchClauseW::setFromClause\n"));
switch(cl->getTp()) {
case SCLT_OR: tpChange(0); break;
case SCLT_AND: tpChange(1); break;
case SCLT_EXCL: tpChange(2); break;
case SCLT_PHRASE: tpChange(3); break;
case SCLT_NEAR: tpChange(4); break;
case SCLT_FILENAME: tpChange(5); break;
default: return;
}
LOGDEB(("SearchClauseW::setFromClause: calling erase\n"));
clear();
QString text = QString::fromUtf8(cl->gettext().c_str());
QString field = QString::fromUtf8(cl->getfield().c_str());
switch(cl->getTp()) {
case SCLT_OR: case SCLT_AND: case SCLT_EXCL:
case SCLT_PHRASE: case SCLT_NEAR:
if (!field.isEmpty()) {
int idx = fldCMB->findText(field);
if (field >= 0) {
fldCMB->setCurrentIndex(idx);
} else {
fldCMB->setEditText(field);
}
}
/* FALLTHROUGH */
case SCLT_FILENAME:
wordsLE->setText(text);
break;
default: break;
}
switch(cl->getTp()) {
case SCLT_PHRASE: case SCLT_NEAR:
{
SearchDataClauseDist *cls = dynamic_cast<SearchDataClauseDist*>(cl);
proxSlackSB->setValue(cls->getslack());
}
break;
default: break;
}
}
void SearchClauseW::clear()
{
wordsLE->setText("");
fldCMB->setCurrentIndex(0);
proxSlackSB->setValue(0);
}
// Handle combobox change: may need to enable/disable the distance
// spinbox and field spec
void SearchClauseW::tpChange(int index)
+2
View File
@@ -37,6 +37,8 @@ public:
SearchClauseW(QWidget* parent = 0);
~SearchClauseW();
Rcl::SearchDataClause *getClause();
void setFromClause(Rcl::SearchDataClauseSimple *cl);
void clear();
QComboBox* sTpCMB;
QComboBox* fldCMB;