@@ -283,8 +283,7 @@ void RclResList::showResultPage()
time_t mtime = doc.dmtime.empty() ?
atol(doc.fmtime.c_str()) : atol(doc.dmtime.c_str());
struct tm *tm = localtime(&mtime);
- strftime(datebuf, 99,
- "Modified: %Y-%m-%d %H:%M:%S", tm);
+ strftime(datebuf, 99, " %Y-%m-%d %H:%M:%S", tm);
}
// Size information. We print both doc and file if they differ a lot
@@ -301,6 +300,7 @@ void RclResList::showResultPage()
} else if (fsize >= 0) {
sizebuf = displayableBytes(fsize);
}
+
// Abstract
string abst = escapeHtml(doc.abstract);
LOGDEB1(("Abstract: {%s}\n", abst.c_str()));
@@ -311,12 +311,9 @@ void RclResList::showResultPage()
result += string("") + sh + "
\n";
else
result += "
";
- result += string(perbuf) + sizebuf + "" + doc.title + "
";
- result += doc.mimetype + " ";
- result += string(datebuf) + " ";
-
- // Set up the preview and edit links
+ // Percent relevant + size + preview/edit links + title
+ result += string(perbuf) + sizebuf;
char vlbuf[100];
if (canIntern(doc.mimetype, rclconfig)) {
sprintf(vlbuf, "\"P%d\"", m_winfirst+i);
@@ -327,12 +324,17 @@ void RclResList::showResultPage()
sprintf(vlbuf, "E%d", m_winfirst+i);
result += string("" + "Edit" + "";
}
- result += string("
");
+ result += " " + doc.title + "
";
+ // Mime type, date modified, url
+ result += doc.mimetype + " ";
+ result += string(datebuf) + " ";
if (!img_name.empty()) {
result += "
";
}
result += "" + url + +"
";
+
+ // Text: abstract and keywords
if (!abst.empty())
result += abst + "
";
if (!doc.keywords.empty())
diff --git a/src/qtgui/ssearchb.ui b/src/qtgui/ssearchb.ui
index 8c02ad89..b84450bc 100644
--- a/src/qtgui/ssearchb.ui
+++ b/src/qtgui/ssearchb.ui
@@ -132,6 +132,7 @@
recoll.h
+ searchdata.h
ssearchb.ui.h
@@ -144,6 +145,8 @@
init()
+ completion()
+ event( QEvent * evt )
diff --git a/src/qtgui/ssearchb.ui.h b/src/qtgui/ssearchb.ui.h
index a5d59303..5bafc962 100644
--- a/src/qtgui/ssearchb.ui.h
+++ b/src/qtgui/ssearchb.ui.h
@@ -25,9 +25,12 @@
** These will automatically be called by the form's constructor and
** destructor.
*****************************************************************************/
+#include
+#include
#include "debuglog.h"
#include "guiutils.h"
+#include "searchdata.h"
void SSearchBase::init()
{
@@ -72,3 +75,72 @@ void SSearchBase::startSimpleSearch()
emit startSearch(sdata);
}
+
+// Complete last word in input by querying db for all possible terms.
+void SSearchBase::completion()
+{
+ if (!rcldb)
+ return;
+
+ // Extract last word in text
+ string txt = (const char *)queryText->text().utf8();
+ string::size_type cs = txt.find_last_of(" ");
+ if (cs == string::npos)
+ cs = 0;
+ else
+ cs++;
+ if (txt.size() == 0 || cs == txt.size()) {
+ QApplication::beep();
+ return;
+ }
+ string s = txt.substr(cs);
+ LOGDEB(("Completing: [%s]\n", s.c_str()));
+
+ // Query database
+ const int max = 100;
+ list strs = rcldb->completions(s, prefs.queryStemLang.ascii(),max);
+ if (strs.size() == 0 || strs.size() == (unsigned int)max) {
+ QApplication::beep();
+ return;
+ }
+
+ // If list from db is single word, insert it, else ask user to select
+ QString res;
+ bool ok = false;
+ if (strs.size() == 1) {
+ res = QString::fromUtf8(strs.begin()->c_str());
+ ok = true;
+ } else {
+ QStringList lst;
+ for (list::iterator it=strs.begin(); it != strs.end(); it++)
+ lst.push_back(QString::fromUtf8(it->c_str()));
+ res = QInputDialog::getItem(tr("Completions"),
+ tr("Select an item:"), lst, 0,
+ FALSE, &ok, this);
+ }
+
+ // Insert result
+ if (ok) {
+ txt.erase(cs);
+ txt.append(res.utf8());
+ queryText->setText(QString::fromUtf8(txt.c_str()));
+ } else {
+ return;
+ }
+}
+
+// Handle CTRL-TAB to mean completion
+bool SSearchBase::event( QEvent *evt )
+{
+ if ( evt->type() == QEvent::KeyPress ) {
+ QKeyEvent *ke = (QKeyEvent *)evt;
+ if ( ke->key() == Key_Tab && (ke->state() & Qt::ControlButton)) {
+ // special tab handling here
+ completion();
+ ke->accept();
+ return TRUE;
+ }
+ }
+ return QWidget::event( evt );
+}
+
diff --git a/src/rcldb/rcldb.cpp b/src/rcldb/rcldb.cpp
index 7cc5e61e..5e254455 100644
--- a/src/rcldb/rcldb.cpp
+++ b/src/rcldb/rcldb.cpp
@@ -1,5 +1,5 @@
#ifndef lint
-static char rcsid[] = "@(#$Id: rcldb.cpp,v 1.68 2006-04-13 09:50:02 dockes Exp $ (C) 2004 J.F.Dockes";
+static char rcsid[] = "@(#$Id: rcldb.cpp,v 1.69 2006-04-19 08:26:08 dockes Exp $ (C) 2004 J.F.Dockes";
#endif
/*
* This program is free software; you can redistribute it and/or modify
@@ -43,6 +43,7 @@ using namespace std;
#include "smallut.h"
#include "pathhash.h"
#include "utf8iter.h"
+#include "searchdata.h"
#include "xapian.h"
@@ -120,7 +121,7 @@ class Native {
string url;
parms.get(string("url"), url);
url = url.substr(7);
- if (url.find(rdb->m_asdata.topdir) == 0)
+ if (url.find(rdb->m_filterTopDir) == 0)
return true;
return false;
}
@@ -853,27 +854,6 @@ static void stringToXapianQueries(const string &iq,
}
}
-// Prepare query out of simple query string
-bool Db::setQuery(const std::string &iqstring, QueryOpts opts,
- const string& stemlang)
-{
- LOGDEB(("Db::setQuery: q: [%s], opts 0x%x, stemlang %s\n",
- iqstring.c_str(), (unsigned int)opts, stemlang.c_str()));
- if (!m_ndb)
- return false;
- m_asdata.erase();
- m_dbindices.clear();
- list pqueries;
- stringToXapianQueries(iqstring, stemlang, m_ndb, pqueries, opts);
- m_ndb->query = Xapian::Query(Xapian::Query::OP_OR, pqueries.begin(),
- pqueries.end());
- delete m_ndb->enquire;
- m_ndb->enquire = new Xapian::Enquire(m_ndb->db);
- m_ndb->enquire->set_query(m_ndb->query);
- m_ndb->mset = Xapian::MSet();
- return true;
-}
-
// Prepare query out of "advanced search" data
bool Db::setQuery(AdvSearchData &sdata, QueryOpts opts,
const string& stemlang)
@@ -882,7 +862,9 @@ bool Db::setQuery(AdvSearchData &sdata, QueryOpts opts,
LOGDEB((" allwords: %s\n", sdata.allwords.c_str()));
LOGDEB((" phrase: %s\n", sdata.phrase.c_str()));
LOGDEB((" orwords: %s\n", sdata.orwords.c_str()));
+ LOGDEB((" orwords1: %s\n", sdata.orwords1.c_str()));
LOGDEB((" nowords: %s\n", sdata.nowords.c_str()));
+ LOGDEB((" filename: %s\n", sdata.filename.c_str()));
string ft;
for (list::iterator it = sdata.filetypes.begin();
@@ -892,7 +874,7 @@ bool Db::setQuery(AdvSearchData &sdata, QueryOpts opts,
if (!sdata.topdir.empty())
LOGDEB((" restricted to: %s\n", sdata.topdir.c_str()));
- m_asdata = sdata;
+ m_filterTopDir = sdata.topdir;
m_dbindices.clear();
if (!m_ndb)
@@ -965,7 +947,19 @@ bool Db::setQuery(AdvSearchData &sdata, QueryOpts opts,
Xapian::Query(Xapian::Query::OP_OR, pqueries.begin(),
pqueries.end());
xq = xq.empty() ? nq :
- Xapian::Query(Xapian::Query::OP_AND_MAYBE, xq, nq);
+ Xapian::Query(Xapian::Query::OP_AND, xq, nq);
+ pqueries.clear();
+ }
+ }
+
+ if (!sdata.orwords1.empty()) {
+ stringToXapianQueries(sdata.orwords1, stemlang, m_ndb, pqueries, opts);
+ if (!pqueries.empty()) {
+ Xapian::Query nq =
+ Xapian::Query(Xapian::Query::OP_OR, pqueries.begin(),
+ pqueries.end());
+ xq = xq.empty() ? nq :
+ Xapian::Query(Xapian::Query::OP_AND, xq, nq);
pqueries.clear();
}
}
@@ -1028,6 +1022,42 @@ bool Db::setQuery(AdvSearchData &sdata, QueryOpts opts,
return true;
}
+list Db::completions(const string &root, const string &lang, int max)
+{
+ Xapian::Database db;
+ list res;
+ if (!m_ndb || !m_ndb->m_isopen)
+ return res;
+ string droot;
+ dumb_string(root, droot);
+ db = m_ndb->m_iswritable ? m_ndb->wdb: m_ndb->db;
+ Xapian::TermIterator it = db.allterms_begin();
+ it.skip_to(droot.c_str());
+ for (int n = 0;it != db.allterms_end(); it++) {
+ if ((*it).find(droot) != 0)
+ break;
+ if (lang.empty()) {
+ res.push_back(*it);
+ ++n;
+ } else {
+ list stemexps =
+ StemDb::stemExpand(m_ndb->m_basedir, lang, *it);
+ unsigned int cnt =
+ (int)stemexps.size() > max - n ? max - n : stemexps.size();
+ list::iterator sit = stemexps.begin();
+ while (cnt--) {
+ res.push_back(*sit++);
+ n++;
+ }
+ }
+ if (n >= max)
+ break;
+ }
+ res.sort();
+ res.unique();
+ return res;
+}
+
bool Db::getQueryTerms(list& terms)
{
if (!m_ndb)
@@ -1118,7 +1148,7 @@ bool Db::getDoc(int exti, Doc &doc, int *percent)
}
// For now the only post-query filter is on dir subtree
- bool postqfilter = !m_asdata.topdir.empty();
+ bool postqfilter = !m_filterTopDir.empty();
LOGDEB1(("Topdir %s postqflt %d\n", m_asdata.topdir.c_str(), postqfilter));
int xapi;
diff --git a/src/rcldb/rcldb.h b/src/rcldb/rcldb.h
index f0717bd6..236ce227 100644
--- a/src/rcldb/rcldb.h
+++ b/src/rcldb/rcldb.h
@@ -16,7 +16,7 @@
*/
#ifndef _DB_H_INCLUDED_
#define _DB_H_INCLUDED_
-/* @(#$Id: rcldb.h,v 1.32 2006-04-12 10:41:39 dockes Exp $ (C) 2004 J.F.Dockes */
+/* @(#$Id: rcldb.h,v 1.33 2006-04-19 08:26:08 dockes Exp $ (C) 2004 J.F.Dockes */
#include
#include
@@ -99,32 +99,7 @@ class Doc {
}
};
-/**
- * Holder for the advanced query data
- */
-class AdvSearchData {
- public:
- string allwords;
- string phrase;
- string orwords;
- string nowords;
- string filename;
- list filetypes; // restrict to types. Empty if inactive
- string topdir; // restrict to subtree. Empty if inactive
- string description; // Printable expanded version of the complete query
- // returned after setQuery.
- void erase() {
- allwords.erase();
- phrase.erase();
- orwords.erase();
- nowords.erase();
- filetypes.clear();
- topdir.erase();
- filename.erase();
- description.erase();
- }
-};
-
+class AdvSearchData;
class Native;
/**
@@ -155,12 +130,14 @@ class Db {
// Query-related functions
// Parse query string and initialize query
- bool setQuery(const string &q, QueryOpts opts = QO_NONE,
- const string& stemlang = "english");
bool setQuery(AdvSearchData &q, QueryOpts opts = QO_NONE,
const string& stemlang = "english");
bool getQueryTerms(list& terms);
+ // Return a list of database terms that begin with the input string
+ // Stem expansion is performed if lang is not empty
+ list completions(const string &s, const string &lang, int max=20);
+
/// Add extra database for querying
bool addQueryDb(const string &dir);
/// Remove extra database. if dir == "", remove all.
@@ -188,7 +165,7 @@ class Db {
string getDbDir();
private:
- AdvSearchData m_asdata;
+ string m_filterTopDir; // Current query filter on subtree top directory
vector m_dbindices; // In case there is a postq filter: sequence of
// db indices that match
diff --git a/src/rcldb/searchdata.h b/src/rcldb/searchdata.h
new file mode 100644
index 00000000..24afb286
--- /dev/null
+++ b/src/rcldb/searchdata.h
@@ -0,0 +1,36 @@
+#ifndef _SEARCHDATA_H_INCLUDED_
+#define _SEARCHDATA_H_INCLUDED_
+/* @(#$Id: searchdata.h,v 1.1 2006-04-19 08:26:08 dockes Exp $ (C) 2004 J.F.Dockes */
+
+namespace Rcl {
+/**
+ * Holder for query data
+ */
+class AdvSearchData {
+ public:
+ string allwords;
+ string phrase;
+ string orwords;
+ string orwords1; // Have two instances of orwords for and'ing them
+ string nowords;
+ string filename;
+ list filetypes; // restrict to types. Empty if inactive
+ string topdir; // restrict to subtree. Empty if inactive
+ string description; // Printable expanded version of the complete query
+ // returned after setQuery.
+ void erase() {
+ allwords.erase();
+ phrase.erase();
+ orwords.erase();
+ orwords1.erase();
+ nowords.erase();
+ filetypes.clear();
+ topdir.erase();
+ filename.erase();
+ description.erase();
+ }
+};
+
+}
+
+#endif /* _SEARCHDATA_H_INCLUDED_ */