diff --git a/src/qtgui/advsearch.ui b/src/qtgui/advsearch.ui index 0dc59f1b..86560458 100644 --- a/src/qtgui/advsearch.ui +++ b/src/qtgui/advsearch.ui @@ -63,45 +63,6 @@ 8 - - - orWordsTL - - - Any of these words - - - - - orWordsLE - - - - - textLabel1_2 - - - File name matching - - - - - fileNameLE - - - - - noWordsTL - - - None of these words - - - - - noWordsLE - - andWordsTL @@ -128,6 +89,58 @@ phraseLE + + + orWordsTL + + + Any of these words + + + + + orWordsLE + + + + + orWords1TL + + + Any of these words + + + + + orWords1LE + + + + + noWordsTL + + + None of these words + + + + + noWordsLE + + + + + textLabel1_2 + + + File name matching + + + + + fileNameLE + + @@ -483,6 +496,7 @@ recoll.h + searchdata.h advsearch.ui.h diff --git a/src/qtgui/advsearch.ui.h b/src/qtgui/advsearch.ui.h index 4081373e..61f14801 100644 --- a/src/qtgui/advsearch.ui.h +++ b/src/qtgui/advsearch.ui.h @@ -40,6 +40,7 @@ using std::string; #include "recoll.h" #include "rclconfig.h" #include "debuglog.h" +#include "searchdata.h" extern RclConfig *rclconfig; @@ -132,11 +133,13 @@ void advsearch::searchPB_clicked() mydata.allwords = string((const char*)(andWordsLE->text().utf8())); mydata.phrase = string((const char*)(phraseLE->text().utf8())); mydata.orwords = string((const char*)(orWordsLE->text().utf8())); + mydata.orwords1 = string((const char*)(orWords1LE->text().utf8())); mydata.nowords = string((const char*)(noWordsLE->text().utf8())); mydata.filename = string((const char*)(fileNameLE->text().utf8())); if (mydata.allwords.empty() && mydata.phrase.empty() && - mydata.orwords.empty() && mydata.filename.empty()) { + mydata.orwords.empty() && mydata.orwords1.empty() && + mydata.filename.empty()) { if (mydata.nowords.empty()) return; QMessageBox::warning(0, "Recoll", diff --git a/src/qtgui/main.cpp b/src/qtgui/main.cpp index 0c8d6e37..17cbeb8a 100644 --- a/src/qtgui/main.cpp +++ b/src/qtgui/main.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: main.cpp,v 1.41 2006-04-15 17:15:01 dockes Exp $ (C) 2005 J.F.Dockes"; +static char rcsid[] = "@(#$Id: main.cpp,v 1.42 2006-04-19 08:26:08 dockes Exp $ (C) 2005 J.F.Dockes"; #endif /* * This program is free software; you can redistribute it and/or modify @@ -40,9 +40,6 @@ static char rcsid[] = "@(#$Id: main.cpp,v 1.41 2006-04-15 17:15:01 dockes Exp $ #include #include "rcldb.h" -#ifndef NO_NAMESPACES -using Rcl::AdvSearchData; -#endif /* NO_NAMESPACES */ #include "rclconfig.h" #include "pathut.h" #include "recoll.h" diff --git a/src/qtgui/rclmain.h b/src/qtgui/rclmain.h index ab2941d6..6a09ed56 100644 --- a/src/qtgui/rclmain.h +++ b/src/qtgui/rclmain.h @@ -26,6 +26,7 @@ #include "sort.h" #include "uiprefs.h" #include "rcldb.h" +#include "searchdata.h" #include "recollmain.h" diff --git a/src/qtgui/rclreslist.cpp b/src/qtgui/rclreslist.cpp index cad3071c..a2893913 100644 --- a/src/qtgui/rclreslist.cpp +++ b/src/qtgui/rclreslist.cpp @@ -1,5 +1,5 @@ #ifndef lint -static char rcsid[] = "@(#$Id: rclreslist.cpp,v 1.11 2006-04-18 08:53:28 dockes Exp $ (C) 2005 J.F.Dockes"; +static char rcsid[] = "@(#$Id: rclreslist.cpp,v 1.12 2006-04-19 08:26:08 dockes Exp $ (C) 2005 J.F.Dockes"; #endif #include @@ -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_ */