Arrange for plaintorich to keep track of the link between match area and search terms. Use this in preview to allow walking the matches for a given search term group

This commit is contained in:
Jean-Francois Dockes 2012-08-18 12:15:43 +02:00
parent 35ed2b3ca9
commit 1ed0f4ddf8
8 changed files with 291 additions and 156 deletions

View file

@ -204,24 +204,32 @@ void RecollProtocol::queryDetails()
class PlainToRichKio : public PlainToRich { class PlainToRichKio : public PlainToRich {
public: public:
PlainToRichKio(const string& nm) PlainToRichKio(const string& nm)
: PlainToRich() , m_name(nm) : m_name(nm)
{ {
} }
virtual ~PlainToRichKio() {}
virtual string header() { virtual string header() {
if (m_inputhtml) { if (m_inputhtml) {
return snull; return cstr_null;
} else { } else {
return return string("<html><head>"
string("<html><head>" "<META http-equiv=\"Content-Type\""
"<META http-equiv=\"Content-Type\"" "content=\"text/html;charset=UTF-8\"><title>").
"content=\"text/html;charset=UTF-8\"><title>") + append(m_name).
m_name + append("</title></head><body><pre>");
string("</title></head><body><pre>");
} }
} }
virtual string startMatch() {return string("<font color=\"blue\">");}
virtual string endMatch() {return string("</font>");} virtual string startMatch(unsigned int)
{
return string("<font color=\"blue\">");
}
virtual string endMatch()
{
return string("</font>");
}
const string &m_name; const string &m_name;
}; };

View file

@ -68,54 +68,130 @@ using std::pair;
// Subclass plainToRich to add <termtag>s and anchors to the preview text // Subclass plainToRich to add <termtag>s and anchors to the preview text
class PlainToRichQtPreview : public PlainToRich { class PlainToRichQtPreview : public PlainToRich {
public: public:
int lastanchor;
PlainToRichQtPreview() PlainToRichQtPreview()
: m_curanchor(1), m_lastanchor(0)
{ {
lastanchor = 0;
} }
virtual ~PlainToRichQtPreview() {}
virtual string header() { bool haveAnchors()
{
return m_lastanchor != 0;
}
virtual string header()
{
if (m_inputhtml) { if (m_inputhtml) {
return snull; return cstr_null;
} else { } else {
if (prefs.previewPlainPre) { if (prefs.previewPlainPre) {
m_eolbr = false; m_eolbr = false;
return string("<qt><head><title></title></head><body>" return string("<qt><head><title></title></head><body>"
"<pre>"); "<pre>");
// Note we could also use the following for line-folding instead of <br>s // Note: we could also use the following for
// This would be possible without recomputing the whole text, much better perfs // line-folding instead of <br>s This would be
// for toggling wrap/no-wrap // possible without recomputing the whole text, much
// "<pre style=\"white-space: pre-wrap\">"); // better perfs for toggling wrap/no-wrap:
// <pre style=\"white-space: pre-wrap\">
} else { } else {
m_eolbr = true; m_eolbr = true;
return string("<qt><head><title></title></head><body>"); return string("<qt><head><title></title></head><body>");
} }
} }
} }
virtual string startMatch()
virtual string startMatch(unsigned int grpidx)
{ {
return string("<span style='color: ") LOGDEB2(("startMatch, grpidx %u\n", grpidx));
+ string((const char *)(prefs.qtermcolor.toUtf8())) grpidx = m_hdata->grpsugidx[grpidx];
+ string(";font-weight: bold;") LOGDEB2(("startMatch, ugrpidx %u\n", grpidx));
+ string("'>"); m_groupanchors[grpidx].push_back(++m_lastanchor);
m_groupcuranchors[grpidx] = 0;
return string("<span style='color: ").
append((const char *)(prefs.qtermcolor.toUtf8())).
append(";font-weight: bold;").
append("'>").
append("<a name=\"").
append(termAnchorName(m_lastanchor)).
append("\">");
} }
virtual string endMatch() {return string("</span>");}
virtual string termAnchorName(int i) { virtual string endMatch()
{
return string("</a></span>");
}
virtual string termAnchorName(int i) const
{
static const char *termAnchorNameBase = "TRM"; static const char *termAnchorNameBase = "TRM";
char acname[sizeof(termAnchorNameBase) + 20]; char acname[sizeof(termAnchorNameBase) + 20];
sprintf(acname, "%s%d", termAnchorNameBase, i); sprintf(acname, "%s%d", termAnchorNameBase, i);
if (i > lastanchor)
lastanchor = i;
return string(acname); return string(acname);
} }
virtual string startAnchor(int i) { virtual string startChunk()
return string("<a name=\"") + termAnchorName(i) + "\">"; {
return "<pre>";
} }
virtual string endAnchor() {
return string("</a>"); int nextAnchorNum(int grpidx)
{
LOGDEB2(("nextAnchorNum: group %d\n", grpidx));
map<unsigned int, unsigned int>::iterator curit =
m_groupcuranchors.find(grpidx);
map<unsigned int, vector<int> >::iterator vecit =
m_groupanchors.find(grpidx);
if (grpidx == -1 || curit == m_groupcuranchors.end() ||
vecit == m_groupanchors.end()) {
if (m_curanchor >= m_lastanchor)
m_curanchor = 1;
else
m_curanchor++;
} else {
if (curit->second >= vecit->second.size() -1)
m_groupcuranchors[grpidx] = 0;
else
m_groupcuranchors[grpidx]++;
m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
LOGDEB2(("nextAnchorNum: curanchor now %d\n", m_curanchor));
}
return m_curanchor;
} }
virtual string startChunk() { return "<pre>";}
int prevAnchorNum(int grpidx)
{
map<unsigned int, unsigned int>::iterator curit =
m_groupcuranchors.find(grpidx);
map<unsigned int, vector<int> >::iterator vecit =
m_groupanchors.find(grpidx);
if (grpidx == -1 || curit == m_groupcuranchors.end() ||
vecit == m_groupanchors.end()) {
if (m_curanchor <= 1)
m_curanchor = m_lastanchor;
else
m_curanchor--;
} else {
if (curit->second <= 0)
m_groupcuranchors[grpidx] = vecit->second.size() -1;
else
m_groupcuranchors[grpidx]--;
m_curanchor = vecit->second[m_groupcuranchors[grpidx]];
}
return m_curanchor;
}
QString curAnchorName() const
{
return QString::fromUtf8(termAnchorName(m_curanchor).c_str());
}
private:
int m_curanchor;
int m_lastanchor;
// Lists of anchor numbers (match locations) for the term (groups)
// in the query (the map key is and index into HighlightData.groups).
map<unsigned int, vector<int> > m_groupanchors;
map<unsigned int, unsigned int> m_groupcuranchors;
}; };
void Preview::init() void Preview::init()
@ -141,8 +217,24 @@ void Preview::init()
QHBoxLayout *layout3 = new QHBoxLayout(0); QHBoxLayout *layout3 = new QHBoxLayout(0);
searchLabel = new QLabel(this); searchLabel = new QLabel(this);
layout3->addWidget(searchLabel); layout3->addWidget(searchLabel);
searchTextLine = new QLineEdit(this);
layout3->addWidget(searchTextLine); searchTextCMB = new QComboBox(this);
searchTextCMB->setEditable(true);
searchTextCMB->setInsertPolicy(QComboBox::NoInsert);
searchTextCMB->setDuplicatesEnabled(false);
for (unsigned int i = 0; i < m_hData.ugroups.size(); i++) {
QString s;
for (unsigned int j = 0; j < m_hData.ugroups[i].size(); j++) {
s.append(QString::fromUtf8(m_hData.ugroups[i][j].c_str()));
if (j != m_hData.ugroups[i].size()-1)
s.append(" ");
}
searchTextCMB->addItem(s);
}
searchTextCMB->setEditText("");
layout3->addWidget(searchTextCMB);
nextButton = new QPushButton(this); nextButton = new QPushButton(this);
nextButton->setEnabled(TRUE); nextButton->setEnabled(TRUE);
layout3->addWidget(nextButton); layout3->addWidget(nextButton);
@ -160,7 +252,7 @@ void Preview::init()
resize(QSize(640, 480).expandedTo(minimumSizeHint())); resize(QSize(640, 480).expandedTo(minimumSizeHint()));
// buddies // buddies
searchLabel->setBuddy(searchTextLine); searchLabel->setBuddy(searchTextCMB);
searchLabel->setText(tr("&Search for:")); searchLabel->setText(tr("&Search for:"));
nextButton->setText(tr("&Next")); nextButton->setText(tr("&Next"));
@ -176,26 +268,25 @@ void Preview::init()
"RCL.SEARCH.PREVIEW"); "RCL.SEARCH.PREVIEW");
// signals and slots connections // signals and slots connections
connect(searchTextLine, SIGNAL(textChanged(const QString&)), connect(searchTextCMB, SIGNAL(activated(int)),
this, SLOT(searchTextLine_textChanged(const QString&))); this, SLOT(searchTextFromIndex(int)));
connect(searchTextCMB, SIGNAL(editTextChanged(const QString&)),
this, SLOT(searchTextChanged(const QString&)));
connect(nextButton, SIGNAL(clicked()), this, SLOT(nextPressed())); connect(nextButton, SIGNAL(clicked()), this, SLOT(nextPressed()));
connect(prevButton, SIGNAL(clicked()), this, SLOT(prevPressed())); connect(prevButton, SIGNAL(clicked()), this, SLOT(prevPressed()));
connect(clearPB, SIGNAL(clicked()), searchTextLine, SLOT(clear())); connect(clearPB, SIGNAL(clicked()), searchTextCMB, SLOT(clearEditText()));
connect(pvTab, SIGNAL(currentChanged(QWidget *)), connect(pvTab, SIGNAL(currentChanged(QWidget *)),
this, SLOT(currentChanged(QWidget *))); this, SLOT(currentChanged(QWidget *)));
connect(bt, SIGNAL(clicked()), this, SLOT(closeCurrentTab())); connect(bt, SIGNAL(clicked()), this, SLOT(closeCurrentTab()));
m_dynSearchActive = false; m_dynSearchActive = false;
m_canBeep = true; m_canBeep = true;
m_currentW = 0;
if (prefs.pvwidth > 100) { if (prefs.pvwidth > 100) {
resize(prefs.pvwidth, prefs.pvheight); resize(prefs.pvwidth, prefs.pvheight);
} }
m_loading = false; m_loading = false;
currentChanged(pvTab->currentWidget()); currentChanged(pvTab->currentWidget());
m_justCreated = true; m_justCreated = true;
m_haveAnchors = false;
m_curAnchor = 1;
} }
void Preview::closeEvent(QCloseEvent *e) void Preview::closeEvent(QCloseEvent *e)
@ -273,11 +364,11 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
} else if (m_dynSearchActive) { } else if (m_dynSearchActive) {
if (keyEvent->key() == Qt::Key_F3) { if (keyEvent->key() == Qt::Key_F3) {
LOGDEB2(("Preview::eventFilter: got F3\n")); LOGDEB2(("Preview::eventFilter: got F3\n"));
doSearch(searchTextLine->text(), true, false); doSearch(searchTextCMB->currentText(), true, false);
return true; return true;
} }
if (target != searchTextLine) if (target != searchTextCMB)
return QApplication::sendEvent(searchTextLine, event); return QApplication::sendEvent(searchTextCMB, event);
} else { } else {
if (edit && if (edit &&
(target == edit || target == edit->viewport())) { (target == edit || target == edit->viewport())) {
@ -285,7 +376,7 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
(keyEvent->key() == Qt::Key_F && (keyEvent->key() == Qt::Key_F &&
(keyEvent->modifiers() & Qt::ControlModifier))) { (keyEvent->modifiers() & Qt::ControlModifier))) {
LOGDEB2(("Preview::eventFilter: got / or C-F\n")); LOGDEB2(("Preview::eventFilter: got / or C-F\n"));
searchTextLine->setFocus(); searchTextCMB->setFocus();
m_dynSearchActive = true; m_dynSearchActive = true;
return true; return true;
} else if (keyEvent->key() == Qt::Key_Space) { } else if (keyEvent->key() == Qt::Key_Space) {
@ -307,23 +398,27 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
return false; return false;
} }
void Preview::searchTextLine_textChanged(const QString & text) void Preview::searchTextChanged(const QString & text)
{ {
LOGDEB2(("search line text changed. text: '%s'\n", text.ascii())); LOGDEB1(("Search line text changed. text: '%s'\n",
(const char *)text.toAscii()));
m_searchTextFromIndex = -1;
if (text.isEmpty()) { if (text.isEmpty()) {
m_dynSearchActive = false; m_dynSearchActive = false;
// nextButton->setEnabled(false);
// prevButton->setEnabled(false);
clearPB->setEnabled(false); clearPB->setEnabled(false);
} else { } else {
m_dynSearchActive = true; m_dynSearchActive = true;
// nextButton->setEnabled(true);
// prevButton->setEnabled(true);
clearPB->setEnabled(true); clearPB->setEnabled(true);
doSearch(text, false, false); doSearch(text, false, false);
} }
} }
void Preview::searchTextFromIndex(int idx)
{
LOGDEB1(("search line from index %d\n", idx));
m_searchTextFromIndex = idx;
}
PreviewTextEdit *Preview::currentEditor() PreviewTextEdit *Preview::currentEditor()
{ {
LOGDEB2(("Preview::currentEditor()\n")); LOGDEB2(("Preview::currentEditor()\n"));
@ -351,9 +446,9 @@ void Preview::emitSaveDocToFile()
void Preview::doSearch(const QString &_text, bool next, bool reverse, void Preview::doSearch(const QString &_text, bool next, bool reverse,
bool wordOnly) bool wordOnly)
{ {
LOGDEB(("Preview::doSearch: text [%s] txtlen %d next %d rev %d word %d\n", LOGDEB(("Preview::doSearch: text [%s] idx %d next %d rev %d word %d\n",
(const char *)_text.toUtf8(), _text.length(), int(next), (const char *)_text.toUtf8(), m_searchTextFromIndex, int(next),
int(reverse), int(wordOnly))); int(reverse), int(wordOnly)));
QString text = _text; QString text = _text;
bool matchCase = matchCheck->isChecked(); bool matchCase = matchCheck->isChecked();
@ -363,25 +458,19 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
return; return;
} }
if (text.isEmpty()) { if (text.isEmpty() || m_searchTextFromIndex != -1) {
if (m_haveAnchors == false) { if (!edit->m_plaintorich->haveAnchors()) {
LOGDEB(("NO ANCHORS\n")); LOGDEB(("NO ANCHORS\n"));
return; return;
} }
// The combobox indices are equal to the search ugroup indices
// in hldata, that's how we built the list.
if (reverse) { if (reverse) {
if (m_curAnchor == 1) edit->m_plaintorich->prevAnchorNum(m_searchTextFromIndex);
m_curAnchor = edit->m_plaintorich->lastanchor;
else
m_curAnchor--;
} else { } else {
if (m_curAnchor == edit->m_plaintorich->lastanchor) edit->m_plaintorich->nextAnchorNum(m_searchTextFromIndex);
m_curAnchor = 1;
else
m_curAnchor++;
} }
LOGDEB(("m_curAnchor: %d\n", m_curAnchor)); QString aname = edit->m_plaintorich->curAnchorName();
QString aname =
QString::fromUtf8(edit->m_plaintorich->termAnchorName(m_curAnchor).c_str());
LOGDEB(("Calling scrollToAnchor(%s)\n", (const char *)aname.toUtf8())); LOGDEB(("Calling scrollToAnchor(%s)\n", (const char *)aname.toUtf8()));
edit->scrollToAnchor(aname); edit->scrollToAnchor(aname);
// Position the cursor approximately at the anchor (top of // Position the cursor approximately at the anchor (top of
@ -440,14 +529,14 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
void Preview::nextPressed() void Preview::nextPressed()
{ {
LOGDEB2(("PreviewTextEdit::nextPressed\n")); LOGDEB2(("Preview::nextPressed\n"));
doSearch(searchTextLine->text(), true, false); doSearch(searchTextCMB->currentText(), true, false);
} }
void Preview::prevPressed() void Preview::prevPressed()
{ {
LOGDEB2(("PreviewTextEdit::prevPressed\n")); LOGDEB2(("Preview::prevPressed\n"));
doSearch(searchTextLine->text(), true, true); doSearch(searchTextCMB->currentText(), true, true);
} }
// Called when user clicks on tab // Called when user clicks on tab
@ -456,7 +545,6 @@ void Preview::currentChanged(QWidget * tw)
LOGDEB2(("PreviewTextEdit::currentChanged\n")); LOGDEB2(("PreviewTextEdit::currentChanged\n"));
PreviewTextEdit *edit = PreviewTextEdit *edit =
tw->findChild<PreviewTextEdit*>("pvEdit"); tw->findChild<PreviewTextEdit*>("pvEdit");
m_currentW = tw;
LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit)); LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
if (edit == 0) { if (edit == 0) {
@ -470,7 +558,7 @@ void Preview::currentChanged(QWidget * tw)
connect(this, SIGNAL(printCurrentPreviewRequest()), edit, SLOT(print())); connect(this, SIGNAL(printCurrentPreviewRequest()), edit, SLOT(print()));
edit->installEventFilter(this); edit->installEventFilter(this);
edit->viewport()->installEventFilter(this); edit->viewport()->installEventFilter(this);
searchTextLine->installEventFilter(this); searchTextCMB->installEventFilter(this);
emit(previewExposed(this, m_searchId, edit->m_docnum)); emit(previewExposed(this, m_searchId, edit->m_docnum));
} }
@ -507,7 +595,7 @@ PreviewTextEdit *Preview::addEditorTab()
void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum) void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum)
{ {
LOGDEB1(("PreviewTextEdit::setCurTabProps\n")); LOGDEB1(("Preview::setCurTabProps\n"));
QString title; QString title;
string ctitle; string ctitle;
if (doc.getmeta(Rcl::Doc::keytt, &ctitle) && !ctitle.empty()) { if (doc.getmeta(Rcl::Doc::keytt, &ctitle) && !ctitle.empty()) {
@ -721,8 +809,6 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
LoadGuard guard(&m_loading); LoadGuard guard(&m_loading);
CancelCheck::instance().setCancel(false); CancelCheck::instance().setCancel(false);
m_haveAnchors = false;
setCurTabProps(idoc, docnum); setCurTabProps(idoc, docnum);
QString msg = QString("Loading: %1 (size %2 bytes)") QString msg = QString("Loading: %1 (size %2 bytes)")
@ -956,23 +1042,20 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
// Position the editor so that the first search term is visible // Position the editor so that the first search term is visible
m_haveAnchors = editor->m_plaintorich->lastanchor != 0; if (searchTextCMB->currentText().length() != 0) {
if (searchTextLine->text().length() != 0) {
// If there is a current search string, perform the search // If there is a current search string, perform the search
m_canBeep = true; m_canBeep = true;
doSearch(searchTextLine->text(), true, false); doSearch(searchTextCMB->currentText(), true, false);
} else { } else {
// Position to the first query term // Position to the first query term
if (m_haveAnchors) { if (editor->m_plaintorich->haveAnchors()) {
QString aname = QString aname = editor->m_plaintorich->curAnchorName();
QString::fromUtf8(editor->m_plaintorich->termAnchorName(1).c_str());
LOGDEB2(("Call movetoanchor(%s)\n", (const char *)aname.toUtf8())); LOGDEB2(("Call movetoanchor(%s)\n", (const char *)aname.toUtf8()));
editor->scrollToAnchor(aname); editor->scrollToAnchor(aname);
// Position the cursor approximately at the anchor (top of // Position the cursor approximately at the anchor (top of
// viewport) so that searches start from here // viewport) so that searches start from here
QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0)); QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0));
editor->setTextCursor(cursor); editor->setTextCursor(cursor);
m_curAnchor = 1;
} }
} }
@ -989,14 +1072,15 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
return true; return true;
} }
PreviewTextEdit::PreviewTextEdit(QWidget* parent,const char* name, Preview *pv) PreviewTextEdit::PreviewTextEdit(QWidget* parent, const char* nm, Preview *pv)
: QTextEdit(parent), m_preview(pv), m_dspflds(false), m_docnum(-1) : QTextEdit(parent), m_preview(pv),
m_plaintorich(new PlainToRichQtPreview()),
m_dspflds(false), m_docnum(-1)
{ {
setContextMenuPolicy(Qt::CustomContextMenu); setContextMenuPolicy(Qt::CustomContextMenu);
setObjectName(name); setObjectName(nm);
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(createPopupMenu(const QPoint&))); this, SLOT(createPopupMenu(const QPoint&)));
m_plaintorich = new PlainToRichQtPreview();
} }
PreviewTextEdit::~PreviewTextEdit() PreviewTextEdit::~PreviewTextEdit()

View file

@ -58,22 +58,27 @@ protected:
void mouseDoubleClickEvent(QMouseEvent *); void mouseDoubleClickEvent(QMouseEvent *);
private: private:
PlainToRichQtPreview *m_plaintorich;
Preview *m_preview; Preview *m_preview;
bool m_dspflds; PlainToRichQtPreview *m_plaintorich;
bool m_dspflds;
string m_url; // filename for this tab string m_url; // filename for this tab
string m_ipath; // Internal doc path inside file string m_ipath; // Internal doc path inside file
int m_docnum; // Index of doc in db search results. int m_docnum; // Index of doc in db search results.
// doc out of internfile (previous fields come from the index) with // doc out of internfile (previous fields come from the index) with
// main text erased (for space). // main text erased (for space).
Rcl::Doc m_fdoc; Rcl::Doc m_fdoc;
// The input doc out of the index/query list // The input doc out of the index/query list
Rcl::Doc m_dbdoc; Rcl::Doc m_dbdoc;
// Saved rich (or plain actually) text: the textedit seems to // Saved rich (or plain actually) text: the textedit seems to
// sometimes (but not always) return its text stripped of tags, so // sometimes (but not always) return its text stripped of tags, so
// this is needed (for printing for example) // this is needed (for printing for example)
QString m_richtxt; QString m_richtxt;
Qt::TextFormat m_format; Qt::TextFormat m_format;
// Temporary file name (possibly, if displaying image). The // Temporary file name (possibly, if displaying image). The
// TempFile itself is kept inside main.cpp (because that's where // TempFile itself is kept inside main.cpp (because that's where
// signal cleanup happens), but we use its name to ask for release // signal cleanup happens), but we use its name to ask for release
@ -92,14 +97,14 @@ class Preview : public QWidget {
Preview(int sid, // Search Id Preview(int sid, // Search Id
const HighlightData& hdata) // Search terms etc. for highlighting const HighlightData& hdata) // Search terms etc. for highlighting
: QWidget(0), m_searchId(sid), m_hData(hdata) : QWidget(0), m_searchId(sid), m_searchTextFromIndex(-1), m_hData(hdata)
{ {
init(); init();
} }
~Preview(){}
virtual void closeEvent(QCloseEvent *e ); virtual void closeEvent(QCloseEvent *e );
virtual bool eventFilter(QObject *target, QEvent *event ); virtual bool eventFilter(QObject *target, QEvent *event );
/** /**
* Arrange for the document to be displayed either by exposing the tab * Arrange for the document to be displayed either by exposing the tab
* if already loaded, or by creating a new tab and loading it. * if already loaded, or by creating a new tab and loading it.
@ -112,7 +117,8 @@ class Preview : public QWidget {
friend class PreviewTextEdit; friend class PreviewTextEdit;
public slots: public slots:
virtual void searchTextLine_textChanged(const QString& text); virtual void searchTextChanged(const QString& text);
virtual void searchTextFromIndex(int);
virtual void doSearch(const QString& str, bool next, bool reverse, virtual void doSearch(const QString& str, bool next, bool reverse,
bool wo = false); bool wo = false);
virtual void nextPressed(); virtual void nextPressed();
@ -138,18 +144,17 @@ private:
int m_searchId; int m_searchId;
bool m_dynSearchActive; bool m_dynSearchActive;
// Index value the search text comes from. -1 if text was edited
int m_searchTextFromIndex;
bool m_canBeep; bool m_canBeep;
bool m_loading; bool m_loading;
QWidget *m_currentW;
HighlightData m_hData; HighlightData m_hData;
bool m_justCreated; // First tab create is different bool m_justCreated; // First tab create is different
bool m_haveAnchors; // Search terms are marked in text
int m_lastAnchor; // Number of last anchor. Then rewind to 1
int m_curAnchor;
QTabWidget* pvTab; QTabWidget* pvTab;
QLabel* searchLabel; QLabel* searchLabel;
QLineEdit* searchTextLine; QComboBox *searchTextCMB;
QPushButton* nextButton; QPushButton* nextButton;
QPushButton* prevButton; QPushButton* prevButton;
QPushButton* clearPB; QPushButton* clearPB;

View file

@ -250,12 +250,22 @@ string QtGuiResListPager::iconUrl(RclConfig *config, Rcl::Doc& doc)
class PlainToRichQtReslist : public PlainToRich { class PlainToRichQtReslist : public PlainToRich {
public: public:
virtual ~PlainToRichQtReslist() {} virtual string startMatch(unsigned int idx)
virtual string startMatch() { {
if (m_hdata) {
string s1, s2;
stringsToString<vector<string> >(m_hdata->groups[idx], s1);
stringsToString<vector<string> >(m_hdata->ugroups[m_hdata->grpsugidx[idx]], s2);
LOGDEB(("Reslist startmatch: group %s user group %s\n", s1.c_str(), s2.c_str()));
}
return string("<span class='rclmatch' style='color: ") return string("<span class='rclmatch' style='color: ")
+ string((const char *)prefs.qtermcolor.toAscii()) + string("'>"); + string((const char *)prefs.qtermcolor.toAscii()) + string("'>");
} }
virtual string endMatch() {return string("</span>");} virtual string endMatch()
{
return string("</span>");
}
}; };
static PlainToRichQtReslist g_hiliter; static PlainToRichQtReslist g_hiliter;

View file

@ -38,7 +38,6 @@ public:
setupUi(this); setupUi(this);
init(); init();
} }
~SSearch(){}
virtual void init(); virtual void init();
virtual void setAnyTermMode(); virtual void setAnyTermMode();

View file

@ -49,13 +49,22 @@ static string vecStringToString(const vector<string>& t)
return sterms; return sterms;
} }
struct MatchEntry {
pair<int, int> offs;
unsigned int grpidx;
MatchEntry(int sta, int sto, unsigned int idx)
: offs(sta, sto), grpidx(idx)
{
}
};
// Text splitter used to take note of the position of query terms // Text splitter used to take note of the position of query terms
// inside the result text. This is then used to insert highlight tags. // inside the result text. This is then used to insert highlight tags.
class TextSplitPTR : public TextSplit { class TextSplitPTR : public TextSplit {
public: public:
// Out: begin and end byte positions of query terms/groups in text // Out: begin and end byte positions of query terms/groups in text
vector<pair<int, int> > tboffs; vector<MatchEntry> tboffs;
TextSplitPTR(const HighlightData& hdata) TextSplitPTR(const HighlightData& hdata)
: m_wcount(0), m_hdata(hdata) : m_wcount(0), m_hdata(hdata)
@ -67,7 +76,7 @@ class TextSplitPTR : public TextSplit {
for (vector<vector<string> >::const_iterator vit = hdata.groups.begin(); for (vector<vector<string> >::const_iterator vit = hdata.groups.begin();
vit != hdata.groups.end(); vit++) { vit != hdata.groups.end(); vit++) {
if (vit->size() == 1) { if (vit->size() == 1) {
m_terms.insert(vit->front()); m_terms[vit->front()] = vit - hdata.groups.begin();
} else if (vit->size() > 1) { } else if (vit->size() > 1) {
for (vector<string>::const_iterator it = vit->begin(); for (vector<string>::const_iterator it = vit->begin();
it != vit->end(); it++) { it != vit->end(); it++) {
@ -91,8 +100,9 @@ class TextSplitPTR : public TextSplit {
// pos, bts, bte)); // pos, bts, bte));
// If this word is a search term, remember its byte-offset span. // If this word is a search term, remember its byte-offset span.
if (m_terms.find(dumb) != m_terms.end()) { map<string, unsigned int>::const_iterator it = m_terms.find(dumb);
tboffs.push_back(pair<int, int>(bts, bte)); if (it != m_terms.end()) {
tboffs.push_back(MatchEntry(bts, bte, (*it).second));
} }
// If word is part of a search group, update its positions list // If word is part of a search group, update its positions list
@ -114,13 +124,13 @@ class TextSplitPTR : public TextSplit {
virtual bool matchGroups(); virtual bool matchGroups();
private: private:
virtual bool matchGroup(const vector<string>& terms, int dist); virtual bool matchGroup(unsigned int idx);
// Word count. Used to call checkCancel from time to time. // Word count. Used to call checkCancel from time to time.
int m_wcount; int m_wcount;
// In: user query terms // In: user query terms
set<string> m_terms; map<string, unsigned int> m_terms;
// m_gterms holds all the terms in m_groups, as a set for quick lookup // m_gterms holds all the terms in m_groups, as a set for quick lookup
set<string> m_gterms; set<string> m_gterms;
@ -191,9 +201,12 @@ static bool do_proximity_test(int window, vector<vector<int>* >& plists,
return false; return false;
} }
// Find NEAR matches for the input group of terms, update highlight map // Find NEAR matches for one group of terms, update highlight map
bool TextSplitPTR::matchGroup(const vector<string>& terms, int window) bool TextSplitPTR::matchGroup(unsigned int grpidx)
{ {
const vector<string>& terms = m_hdata.groups[grpidx];
int window = m_hdata.groups[grpidx].size() + m_hdata.slacks[grpidx];
LOGDEB0(("TextSplitPTR::matchGroup:d %d: %s\n", window, LOGDEB0(("TextSplitPTR::matchGroup:d %d: %s\n", window,
vecStringToString(terms).c_str())); vecStringToString(terms).c_str()));
@ -203,26 +216,23 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
// A revert plist->term map. This is so that we can find who is who after // A revert plist->term map. This is so that we can find who is who after
// sorting the plists by length. // sorting the plists by length.
map<vector<int>*, string> plistToTerm; map<vector<int>*, string> plistToTerm;
// For traces
vector<string> realgroup;
// Find the position list for each term in the group. Not all // Find the position list for each term in the group. It is
// necessarily exist (esp for NEAR where terms have been // possible that this particular group was not actually matched by
// stem-expanded: we don't know which matched) // the search, so that some terms are not found.
for (vector<string>::const_iterator it = terms.begin(); for (vector<string>::const_iterator it = terms.begin();
it != terms.end(); it++) { it != terms.end(); it++) {
map<string, vector<int> >::iterator pl = m_plists.find(*it); map<string, vector<int> >::iterator pl = m_plists.find(*it);
if (pl == m_plists.end()) { if (pl == m_plists.end()) {
LOGDEB0(("TextSplitPTR::matchGroup: [%s] not found in m_plists\n", LOGDEB0(("TextSplitPTR::matchGroup: [%s] not found in m_plists\n",
(*it).c_str())); (*it).c_str()));
continue; return false;
} }
plists.push_back(&(pl->second)); plists.push_back(&(pl->second));
plistToTerm[&(pl->second)] = *it; plistToTerm[&(pl->second)] = *it;
realgroup.push_back(*it);
} }
LOGDEB0(("TextSplitPTR::matchGroup:d %d:real group after expansion %s\n", // I think this can't actually happen, was useful when we used to
window, vecStringToString(realgroup).c_str())); // prune the groups, but doesn't hurt.
if (plists.size() < 2) { if (plists.size() < 2) {
LOGDEB0(("TextSplitPTR::matchGroup: no actual groups found\n")); LOGDEB0(("TextSplitPTR::matchGroup: no actual groups found\n"));
return false; return false;
@ -261,15 +271,13 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
SETMINMAX(pos, sta, sto); SETMINMAX(pos, sta, sto);
minpos = sto+1; minpos = sto+1;
// Translate the position window into a byte offset window // Translate the position window into a byte offset window
int bs = 0;
map<int, pair<int, int> >::iterator i1 = m_gpostobytes.find(sta); map<int, pair<int, int> >::iterator i1 = m_gpostobytes.find(sta);
map<int, pair<int, int> >::iterator i2 = m_gpostobytes.find(sto); map<int, pair<int, int> >::iterator i2 = m_gpostobytes.find(sto);
if (i1 != m_gpostobytes.end() && i2 != m_gpostobytes.end()) { if (i1 != m_gpostobytes.end() && i2 != m_gpostobytes.end()) {
LOGDEB0(("TextSplitPTR::matchGroup: pushing bpos %d %d\n", LOGDEB0(("TextSplitPTR::matchGroup: pushing bpos %d %d\n",
i1->second.first, i2->second.second)); i1->second.first, i2->second.second));
tboffs.push_back(pair<int, int>(i1->second.first, tboffs.push_back(MatchEntry(i1->second.first,
i2->second.second)); i2->second.second, grpidx));
bs = i1->second.first;
} else { } else {
LOGDEB(("matchGroup: no bpos found for %d or %d\n", sta, sto)); LOGDEB(("matchGroup: no bpos found for %d or %d\n", sta, sto));
} }
@ -284,22 +292,23 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
/** Sort integer pairs by increasing first value and decreasing width */ /** Sort integer pairs by increasing first value and decreasing width */
class PairIntCmpFirst { class PairIntCmpFirst {
public: public:
bool operator()(pair<int,int> a, pair<int, int>b) { bool operator()(const MatchEntry& a, const MatchEntry& b) {
if (a.first != b.first) if (a.offs.first != b.offs.first)
return a.first < b.first; return a.offs.first < b.offs.first;
return a.second > b.second; return a.offs.second > b.offs.second;
} }
}; };
// Look for matches to PHRASE and NEAR term groups. Actually, we // Look for matches to PHRASE and NEAR term groups and finalize the
// handle all groups as NEAR (ignore order). // matched regions list (sort it by increasing start then decreasing
// length)
// Actually, we handle all groups as NEAR (ignore order).
bool TextSplitPTR::matchGroups() bool TextSplitPTR::matchGroups()
{ {
for (unsigned int i = 0; i < m_hdata.groups.size(); i++) { for (unsigned int i = 0; i < m_hdata.groups.size(); i++) {
if (m_hdata.groups[i].size() <= 1) if (m_hdata.groups[i].size() <= 1)
continue; continue;
matchGroup(m_hdata.groups[i], matchGroup(i);
m_hdata.groups[i].size() + m_hdata.slacks[i]);
} }
// Sort regions by increasing start and decreasing width. // Sort regions by increasing start and decreasing width.
@ -324,6 +333,7 @@ bool PlainToRich::plaintorich(const string& in,
{ {
Chrono chron; Chrono chron;
m_hdata = &hdata;
// Compute the positions for the query terms. We use the text // Compute the positions for the query terms. We use the text
// splitter to break the text into words, and compare the words to // splitter to break the text into words, and compare the words to
// the search terms, // the search terms,
@ -346,8 +356,8 @@ bool PlainToRich::plaintorich(const string& in,
// Iterator for the list of input term positions. We use it to // Iterator for the list of input term positions. We use it to
// output highlight tags and to compute term positions in the // output highlight tags and to compute term positions in the
// output text // output text
vector<pair<int, int> >::iterator tPosIt = splitter.tboffs.begin(); vector<MatchEntry>::iterator tPosIt = splitter.tboffs.begin();
vector<pair<int, int> >::iterator tPosEnd = splitter.tboffs.end(); vector<MatchEntry>::iterator tPosEnd = splitter.tboffs.end();
#if 0 #if 0
for (vector<pair<int, int> >::const_iterator it = splitter.tboffs.begin(); for (vector<pair<int, int> >::const_iterator it = splitter.tboffs.begin();
@ -365,8 +375,6 @@ bool PlainToRich::plaintorich(const string& in,
int hadcr = 0; int hadcr = 0;
int inindent = 1; int inindent = 1;
// Value for numbered anchors at each term match
int anchoridx = 1;
// HTML state // HTML state
bool intag = false, inparamvalue = false; bool intag = false, inparamvalue = false;
// My tag state // My tag state
@ -391,22 +399,20 @@ bool PlainToRich::plaintorich(const string& in,
// we are at or after a term match, mark. // we are at or after a term match, mark.
if (tPosIt != tPosEnd) { if (tPosIt != tPosEnd) {
int ibyteidx = chariter.getBpos(); int ibyteidx = chariter.getBpos();
if (ibyteidx == tPosIt->first) { if (ibyteidx == tPosIt->offs.first) {
if (!intag && ibyteidx >= (int)headend) { if (!intag && ibyteidx >= (int)headend) {
*olit += startAnchor(anchoridx); *olit += startMatch(tPosIt->grpidx);
*olit += startMatch();
} }
anchoridx++;
inrcltag = 1; inrcltag = 1;
} else if (ibyteidx == tPosIt->second) { } else if (ibyteidx == tPosIt->offs.second) {
// Output end of match region tags // Output end of match region tags
if (!intag && ibyteidx > (int)headend) { if (!intag && ibyteidx > (int)headend) {
*olit += endMatch(); *olit += endMatch();
*olit += endAnchor();
} }
// Skip all highlight areas that would overlap this one // Skip all highlight areas that would overlap this one
int crend = tPosIt->second; int crend = tPosIt->offs.second;
while (tPosIt != splitter.tboffs.end() && tPosIt->first < crend) while (tPosIt != splitter.tboffs.end() &&
tPosIt->offs.first < crend)
tPosIt++; tPosIt++;
inrcltag = 0; inrcltag = 0;
} }

View file

@ -21,6 +21,7 @@
#include <list> #include <list>
#include "hldata.h" #include "hldata.h"
#include "cstr.h"
/** /**
* A class for highlighting search results. Overridable methods allow * A class for highlighting search results. Overridable methods allow
@ -31,7 +32,7 @@
class PlainToRich { class PlainToRich {
public: public:
PlainToRich() PlainToRich()
: m_inputhtml(false) : m_inputhtml(false), m_eolbr(false), m_hdata(0)
{ {
} }
@ -71,18 +72,35 @@ public:
); );
/* Overridable output methods for headers, highlighting and marking tags */ /* Overridable output methods for headers, highlighting and marking tags */
virtual std::string header() {return snull;}
virtual std::string startMatch() {return snull;} virtual std::string header()
virtual std::string endMatch() {return snull;} {
virtual std::string startAnchor(int) {return snull;} return cstr_null;
virtual std::string endAnchor() {return snull;} }
virtual std::string startChunk() {return snull;}
/** Return match prefix (e.g.: <div class="match">).
@param groupidx the index into hdata.groups */
virtual std::string startMatch(unsigned int)
{
return cstr_null;
}
/** Return data for end of match area (e.g.: </div>). */
virtual std::string endMatch()
{
return cstr_null;
}
virtual std::string startChunk()
{
return cstr_null;
}
protected: protected:
const std::string snull;
bool m_inputhtml; bool m_inputhtml;
// Use <br> to break plain text lines (else caller has used a <pre> tag) // Use <br> to break plain text lines (else caller has used a <pre> tag)
bool m_eolbr; bool m_eolbr;
const HighlightData *m_hdata;
}; };
#endif /* _PLAINTORICH_H_INCLUDED_ */ #endif /* _PLAINTORICH_H_INCLUDED_ */

View file

@ -40,9 +40,14 @@ static const string cstr_hlfontcolor("<font color=\"blue\">");
static const string cstr_hlendfont("</font>"); static const string cstr_hlendfont("</font>");
class PlainToRichHtReslist : public PlainToRich { class PlainToRichHtReslist : public PlainToRich {
public: public:
virtual ~PlainToRichHtReslist() {} virtual string startMatch(unsigned int)
virtual string startMatch() {return cstr_hlfontcolor;} {
virtual string endMatch() {return cstr_hlendfont;} return cstr_hlfontcolor;
}
virtual string endMatch()
{
return cstr_hlendfont;
}
}; };
static PlainToRichHtReslist g_hiliter; static PlainToRichHtReslist g_hiliter;