diff --git a/src/doc/user/usermanual.sgml b/src/doc/user/usermanual.sgml index e81ac2eb..6b156363 100644 --- a/src/doc/user/usermanual.sgml +++ b/src/doc/user/usermanual.sgml @@ -4563,6 +4563,14 @@ x-my-tag = mailmytag snippet. + %s + Search term. The value will only be set for documents + with indexed page numbers (ie: PDF). The value will be one of + the matched search terms. It would allow pre-setting the + value in the "Find" entry inside Evince for example, for easy + highlighting of the term. + + %U, %u Url. diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index a7854570..97a3ae31 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -1498,7 +1498,7 @@ static bool lookForHtmlBrowser(string &exefile) return false; } -void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum) +void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) { LOGDEB(("RclMain::startNativeViewer: page %d\n", pagenum)); // Look for appropriate viewer @@ -1520,10 +1520,13 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum) if (pagenum == -1) { pagenum = 1; + string lterm; if (m_source.isNotNull()) - pagenum = m_source->getFirstMatchPage(doc); + pagenum = m_source->getFirstMatchPage(doc, lterm); if (pagenum == -1) pagenum = 1; + else + term = QString::fromUtf8(lterm.c_str()); } char cpagenum[20]; sprintf(cpagenum, "%d", pagenum); @@ -1666,6 +1669,7 @@ void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum) subs["i"] = doc.ipath; subs["M"] = doc.mimetype; subs["p"] = cpagenum; + subs["s"] = (const char*)term.toLocal8Bit(); subs["U"] = url; subs["u"] = url; // Let %(xx) access all metadata. diff --git a/src/qtgui/rclmain_w.h b/src/qtgui/rclmain_w.h index 35b67ce9..134accf2 100644 --- a/src/qtgui/rclmain_w.h +++ b/src/qtgui/rclmain_w.h @@ -119,7 +119,8 @@ public slots: virtual void docExpand(Rcl::Doc); virtual void startPreview(int docnum, Rcl::Doc doc, int keymods); virtual void startPreview(Rcl::Doc); - virtual void startNativeViewer(Rcl::Doc, int pagenum = -1); + virtual void startNativeViewer(Rcl::Doc, int pagenum = -1, + QString term=QString()); virtual void saveDocToFile(Rcl::Doc); virtual void previewNextInTab(Preview *, int sid, int docnum); virtual void previewPrevInTab(Preview *, int sid, int docnum); diff --git a/src/qtgui/reslist.cpp b/src/qtgui/reslist.cpp index 7953492f..85c6564b 100644 --- a/src/qtgui/reslist.cpp +++ b/src/qtgui/reslist.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #ifndef __APPLE__ #include #endif @@ -59,9 +60,8 @@ #include "rclaspell.h" #endif -#ifndef MIN -#define MIN(A,B) ((A) < (B) ? (A) : (B)) -#endif +static const QKeySequence quitKeySeq("Ctrl+q"); +static const QKeySequence closeKeySeq("Ctrl+w"); #ifndef RESLIST_TEXTBROWSER #include @@ -805,9 +805,13 @@ void ResList::newSnippetsW(const Rcl::Doc& doc) { SnippetsW *sp = new SnippetsW(doc, m_source); if (m_parent) { - connect(sp, SIGNAL(startNativeViewer(Rcl::Doc, int)), - m_parent, SLOT(startNativeViewer(Rcl::Doc, int))); + connect(sp, SIGNAL(startNativeViewer(Rcl::Doc, int, QString)), + m_parent, SLOT(startNativeViewer(Rcl::Doc, int, QString))); + connect(new QShortcut(quitKeySeq, sp), SIGNAL (activated()), + m_parent, SLOT (fileExit())); } + connect(new QShortcut(closeKeySeq, sp), SIGNAL (activated()), + sp, SLOT (close())); sp->show(); } diff --git a/src/qtgui/snippets_w.cpp b/src/qtgui/snippets_w.cpp index 3fb7d26c..6e1bff26 100644 --- a/src/qtgui/snippets_w.cpp +++ b/src/qtgui/snippets_w.cpp @@ -61,7 +61,7 @@ void SnippetsW::init() setWindowTitle(QString::fromUtf8(titleOrFilename.c_str())); - vector > vpabs; + vector vpabs; m_source->getAbstract(m_doc, vpabs); HighlightData hdata; @@ -77,21 +77,23 @@ void SnippetsW::init() g_hiliter.set_inputhtml(false); - for (vector >::const_iterator it = vpabs.begin(); + for (vector::const_iterator it = vpabs.begin(); it != vpabs.end(); it++) { html += ""; - if (it->first > 0) { - char buf[100]; - sprintf(buf, "P. %d", it->first); + if (it->page > 0) { + char txt[100]; + sprintf(txt, "P. %d", it->page); + char url[100]; + sprintf(url, "P%dT%s", it->page, it->term.c_str()); html += ""; - html += buf; + html += txt; html += ""; } html += ""; list lr; - g_hiliter.plaintorich(it->second, lr, hdata); + g_hiliter.plaintorich(it->snippet, lr, hdata); html.append(QString::fromUtf8(lr.front().c_str())); html.append("\n"); } @@ -117,7 +119,12 @@ void SnippetsW::linkWasClicked(const QUrl &url) if (numpos == string::npos) return; int page = atoi(ascurl.c_str() + numpos); - emit startNativeViewer(m_doc, page); + string::size_type termpos = ascurl.find_first_of("T"); + string term; + if (termpos != string::npos) + term = ascurl.substr(termpos+1); + emit startNativeViewer(m_doc, page, + QString::fromUtf8(term.c_str())); return; } } diff --git a/src/qtgui/snippets_w.h b/src/qtgui/snippets_w.h index 27f3b571..6b50152b 100644 --- a/src/qtgui/snippets_w.h +++ b/src/qtgui/snippets_w.h @@ -16,6 +16,7 @@ */ #ifndef _SNIPPETS_W_H_INCLUDED_ #define _SNIPPETS_W_H_INCLUDED_ +#include #include "rcldoc.h" #include "refcntr.h" @@ -39,7 +40,7 @@ protected slots: virtual void linkWasClicked(const QUrl &); signals: - void startNativeViewer(Rcl::Doc, int pagenum); + void startNativeViewer(Rcl::Doc, int pagenum, QString term); private: void init(); diff --git a/src/query/docseq.h b/src/query/docseq.h index 3eaf57b4..66a53c86 100644 --- a/src/query/docseq.h +++ b/src/query/docseq.h @@ -25,6 +25,9 @@ #include "refcntr.h" #include "hldata.h" +// Need this for the "Snippet" class def. +#include "rclquery.h" + // A result list entry. struct ResListEntry { Rcl::Doc doc; @@ -96,13 +99,12 @@ class DocSequence { return true; } virtual bool getAbstract(Rcl::Doc& doc, - std::vector >& abs) + std::vector& abs) { - abs.push_back(std::pair(0, - doc.meta[Rcl::Doc::keyabs])); + abs.push_back(Rcl::Snippet(0, doc.meta[Rcl::Doc::keyabs])); return true; } - virtual int getFirstMatchPage(Rcl::Doc&) + virtual int getFirstMatchPage(Rcl::Doc&, std::string&) { return -1; } @@ -173,7 +175,7 @@ public: return m_seq->getAbstract(doc, abs); } virtual bool getAbstract(Rcl::Doc& doc, - std::vector >& abs) + std::vector& abs) { if (m_seq.isNull()) return false; diff --git a/src/query/docseqdb.cpp b/src/query/docseqdb.cpp index 054546f3..c7ece824 100644 --- a/src/query/docseqdb.cpp +++ b/src/query/docseqdb.cpp @@ -64,11 +64,11 @@ int DocSequenceDb::getResCnt() } return m_rescnt; } +static const string cstr_mre("[...]"); // This one only gets called to fill-up the snippets window // We ignore most abstract/snippets preferences. -bool DocSequenceDb::getAbstract(Rcl::Doc &doc, - vector >& vpabs) +bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vpabs) { LOGDEB(("DocSequenceDb::getAbstract/pair\n")); setQuery(); @@ -77,15 +77,16 @@ bool DocSequenceDb::getAbstract(Rcl::Doc &doc, int maxoccs = 500; Rcl::abstract_result ret = Rcl::ABSRES_ERROR; if (m_q->whatDb()) { - ret = m_q->makeDocAbstract(doc,vpabs, maxoccs, + ret = m_q->makeDocAbstract(doc, vpabs, maxoccs, m_q->whatDb()->getAbsCtxLen()+ 2); } if (vpabs.empty()) - vpabs.push_back(pair(0, doc.meta[Rcl::Doc::keyabs])); + vpabs.push_back(Rcl::Snippet(0, doc.meta[Rcl::Doc::keyabs])); // If the list was probably truncated, indicate it. - if (ret == Rcl::ABSRES_TRUNC) - vpabs.push_back(pair(-1, "[...]")); + if (ret == Rcl::ABSRES_TRUNC) { + vpabs.push_back(Rcl::Snippet(-1, cstr_mre)); + } return true; } @@ -102,11 +103,11 @@ bool DocSequenceDb::getAbstract(Rcl::Doc &doc, vector& vabs) return true; } -int DocSequenceDb::getFirstMatchPage(Rcl::Doc &doc) +int DocSequenceDb::getFirstMatchPage(Rcl::Doc &doc, string& term) { setQuery(); if (m_q->whatDb()) { - return m_q->getFirstMatchPage(doc); + return m_q->getFirstMatchPage(doc, term); } return -1; } diff --git a/src/query/docseqdb.h b/src/query/docseqdb.h index eda520de..05a42235 100644 --- a/src/query/docseqdb.h +++ b/src/query/docseqdb.h @@ -34,10 +34,10 @@ class DocSequenceDb : public DocSequence { // Called to fill-up the snippets window. Ignoers // buildabstract/replaceabstract and syntabslen - virtual bool getAbstract(Rcl::Doc &doc, vector >&); + virtual bool getAbstract(Rcl::Doc &doc, vector&); virtual bool getAbstract(Rcl::Doc &doc, vector&); - virtual int getFirstMatchPage(Rcl::Doc&); + virtual int getFirstMatchPage(Rcl::Doc&, std::string& term); virtual bool getEnclosing(Rcl::Doc& doc, Rcl::Doc& pdoc); virtual string getDescription(); virtual list expand(Rcl::Doc &doc); diff --git a/src/rcldb/rclabstract.cpp b/src/rcldb/rclabstract.cpp index e6ecb4dd..b1a28b3f 100644 --- a/src/rcldb/rclabstract.cpp +++ b/src/rcldb/rclabstract.cpp @@ -19,6 +19,9 @@ #include #include +#include +using std::tr1::unordered_set; + using namespace std; #include "debuglog.h" @@ -204,11 +207,11 @@ double Query::Native::qualityTerms(Xapian::docid docid, } // Return page number for first match of "significant" term. -int Query::Native::getFirstMatchPage(Xapian::docid docid) +int Query::Native::getFirstMatchPage(Xapian::docid docid, string& term) { if (!m_q|| !m_q->m_db || !m_q->m_db->m_ndb || !m_q->m_db->m_ndb->m_isopen) { LOGERR(("Query::getFirstMatchPage: no db\n")); - return false; + return -1; } Rcl::Db::Native *ndb(m_q->m_db->m_ndb); Xapian::Database& xrdb(ndb->xrdb); @@ -246,8 +249,10 @@ int Query::Native::getFirstMatchPage(Xapian::docid docid) for (pos = xrdb.positionlist_begin(docid, qterm); pos != xrdb.positionlist_end(docid, qterm); pos++) { int pagenum = ndb->getPageNumberForPosition(pagepos, *pos); - if (pagenum > 0) + if (pagenum > 0) { + term = qterm; return pagenum; + } } } catch (...) { // Term does not occur. No problem. @@ -263,7 +268,7 @@ int Query::Native::getFirstMatchPage(Xapian::docid docid) // DatabaseModified and other general exceptions are catched and // possibly retried by our caller abstract_result Query::Native::makeAbstract(Xapian::docid docid, - vector >& vabs, + vector& vabs, int imaxoccs, int ictxwords) { Chrono chron; @@ -315,6 +320,9 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, // The terms 'array' that we partially populate with the document // terms, at their positions around the search terms positions: map sparseDoc; + // Also remember apart the search term positions so that we can list + // them with their snippets. + unordered_set searchTermPositions; // Total number of occurences for all terms. We stop when we have too much unsigned int totaloccs = 0; @@ -392,6 +400,7 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, for (unsigned int ii = sta; ii <= sto; ii++) { if (ii == (unsigned int)ipos) { sparseDoc[ii] = qterm; + searchTermPositions.insert(ii); } else if (ii > (unsigned int)ipos && ii < (unsigned int)ipos + qtrmwrdcnt) { sparseDoc[ii] = occupiedmarker; @@ -470,7 +479,7 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, break; } map::iterator vit; - if ((vit=sparseDoc.find(*pos)) != sparseDoc.end()) { + if ((vit = sparseDoc.find(*pos)) != sparseDoc.end()) { // Don't replace a term: the terms list is in // alphabetic order, and we may have several terms // at the same position, we want to keep only the @@ -513,6 +522,7 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, string chunk; bool incjk = false; int page = 0; + string term; for (map::const_iterator it = sparseDoc.begin(); it != sparseDoc.end(); it++) { LOGDEB2(("Abtract:output %u -> [%s]\n", it->first,it->second.c_str())); @@ -522,6 +532,7 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, page = ndb->getPageNumberForPosition(vpbreaks, it->first); if (page < 0) page = 0; + term.clear(); } Utf8Iter uit(it->second); bool newcjk = false; @@ -530,8 +541,10 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, if (!incjk || (incjk && !newcjk)) chunk += " "; incjk = newcjk; + if (searchTermPositions.find(it->first) != searchTermPositions.end()) + term = it->second; if (it->second == cstr_ellipsis) { - vabs.push_back(pair(page, chunk)); + vabs.push_back(Snippet(page, chunk).setTerm(term)); chunk.clear(); } else { if (it->second.compare(end_of_field_term) && @@ -540,7 +553,7 @@ abstract_result Query::Native::makeAbstract(Xapian::docid docid, } } if (!chunk.empty()) - vabs.push_back(pair(page, chunk)); + vabs.push_back(Snippet(page, chunk).setTerm(term)); LOGDEB2(("makeAbtract: done in %d mS\n", chron.millis())); return ret; diff --git a/src/rcldb/rclquery.cpp b/src/rcldb/rclquery.cpp index a49529a2..2e6fffac 100644 --- a/src/rcldb/rclquery.cpp +++ b/src/rcldb/rclquery.cpp @@ -300,7 +300,7 @@ bool Query::getMatchTerms(unsigned long xdocid, vector& terms) } abstract_result Query::makeDocAbstract(Doc &doc, - vector >& abstract, + vector& abstract, int maxoccs, int ctxwords) { LOGDEB(("makeDocAbstract: maxoccs %d ctxwords %d\n", maxoccs, ctxwords)); @@ -318,19 +318,19 @@ abstract_result Query::makeDocAbstract(Doc &doc, bool Query::makeDocAbstract(Doc &doc, vector& abstract) { - vector > vpabs; + vector vpabs; if (!makeDocAbstract(doc, vpabs)) return false; - for (vector >::const_iterator it = vpabs.begin(); + for (vector::const_iterator it = vpabs.begin(); it != vpabs.end(); it++) { string chunk; - if (it->first > 0) { + if (it->page > 0) { doc.haspages = true; ostringstream ss; - ss << it->first; + ss << it->page; chunk += string(" [p ") + ss.str() + "] "; } - chunk += it->second; + chunk += it->snippet; abstract.push_back(chunk); } return true; @@ -338,18 +338,18 @@ bool Query::makeDocAbstract(Doc &doc, vector& abstract) bool Query::makeDocAbstract(Doc &doc, string& abstract) { - vector > vpabs; + vector vpabs; if (!makeDocAbstract(doc, vpabs)) return false; - for (vector >::const_iterator it = vpabs.begin(); + for (vector::const_iterator it = vpabs.begin(); it != vpabs.end(); it++) { - abstract.append(it->second); + abstract.append(it->snippet); abstract.append(cstr_ellipsis); } return m_reason.empty() ? true : false; } -int Query::getFirstMatchPage(Doc &doc) +int Query::getFirstMatchPage(Doc &doc, string& term) { LOGDEB1(("Db::getFirstMatchPages\n"));; if (!m_nq) { @@ -357,7 +357,7 @@ int Query::getFirstMatchPage(Doc &doc) return false; } int pagenum = -1; - XAPTRY(pagenum = m_nq->getFirstMatchPage(Xapian::docid(doc.xdocid)), + XAPTRY(pagenum = m_nq->getFirstMatchPage(Xapian::docid(doc.xdocid), term), m_db->m_ndb->xrdb, m_reason); return m_reason.empty() ? pagenum : -1; } diff --git a/src/rcldb/rclquery.h b/src/rcldb/rclquery.h index 5eaebc54..150a819a 100644 --- a/src/rcldb/rclquery.h +++ b/src/rcldb/rclquery.h @@ -25,12 +25,12 @@ using std::vector; #endif #include "refcntr.h" +#include "searchdata.h" #ifndef NO_NAMESPACES namespace Rcl { #endif -class SearchData; class Db; class Doc; @@ -40,6 +40,24 @@ enum abstract_result { ABSRES_TRUNC = 2 }; +// Snippet entry for makeDocAbstract +class Snippet { +public: + Snippet(int page, const string& snip) + : page(page), snippet(snip) + { + } + Snippet& setTerm(const string& trm) + { + term = trm; + return *this; + } + int page; + string term; + string snippet; +}; + + /** * An Rcl::Query is a question (SearchData) applied to a * database. Handles access to the results. Somewhat equivalent to a @@ -89,10 +107,10 @@ class Query { // Returned as a snippets vector bool makeDocAbstract(Doc &doc, vector& abstract); // Returned as a vector of pair page is 0 if unknown - abstract_result makeDocAbstract(Doc &doc, vector >& abst, + abstract_result makeDocAbstract(Doc &doc, vector& abst, int maxoccs= -1, int ctxwords = -1); /** Retrieve detected page breaks positions */ - int getFirstMatchPage(Doc &doc); + int getFirstMatchPage(Doc &doc, std::string& term); /** Expand query to look for documents like the one passed in */ vector expand(const Doc &doc); diff --git a/src/rcldb/rclquery_p.h b/src/rcldb/rclquery_p.h index 179b1a3f..b4519c20 100644 --- a/src/rcldb/rclquery_p.h +++ b/src/rcldb/rclquery_p.h @@ -54,9 +54,9 @@ public: delete xenquire; xenquire = 0; termfreqs.clear(); } - abstract_result makeAbstract(Xapian::docid id, vector >&, + abstract_result makeAbstract(Xapian::docid id, vector&, int maxoccs = -1, int ctxwords = -1); - int getFirstMatchPage(Xapian::docid docid); + int getFirstMatchPage(Xapian::docid docid, std::string& term); void setDbWideQTermsFreqs(); double qualityTerms(Xapian::docid docid, const std::vector& terms,