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:
parent
35ed2b3ca9
commit
1ed0f4ddf8
8 changed files with 291 additions and 156 deletions
|
@ -204,24 +204,32 @@ void RecollProtocol::queryDetails()
|
|||
class PlainToRichKio : public PlainToRich {
|
||||
public:
|
||||
PlainToRichKio(const string& nm)
|
||||
: PlainToRich() , m_name(nm)
|
||||
: m_name(nm)
|
||||
{
|
||||
}
|
||||
virtual ~PlainToRichKio() {}
|
||||
|
||||
virtual string header() {
|
||||
if (m_inputhtml) {
|
||||
return snull;
|
||||
return cstr_null;
|
||||
} else {
|
||||
return
|
||||
string("<html><head>"
|
||||
return string("<html><head>"
|
||||
"<META http-equiv=\"Content-Type\""
|
||||
"content=\"text/html;charset=UTF-8\"><title>") +
|
||||
m_name +
|
||||
string("</title></head><body><pre>");
|
||||
"content=\"text/html;charset=UTF-8\"><title>").
|
||||
append(m_name).
|
||||
append("</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;
|
||||
};
|
||||
|
||||
|
|
|
@ -68,54 +68,130 @@ using std::pair;
|
|||
// Subclass plainToRich to add <termtag>s and anchors to the preview text
|
||||
class PlainToRichQtPreview : public PlainToRich {
|
||||
public:
|
||||
int lastanchor;
|
||||
|
||||
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) {
|
||||
return snull;
|
||||
return cstr_null;
|
||||
} else {
|
||||
if (prefs.previewPlainPre) {
|
||||
m_eolbr = false;
|
||||
return string("<qt><head><title></title></head><body>"
|
||||
"<pre>");
|
||||
// Note we could also use the following for line-folding instead of <br>s
|
||||
// This would be possible without recomputing the whole text, much better perfs
|
||||
// for toggling wrap/no-wrap
|
||||
// "<pre style=\"white-space: pre-wrap\">");
|
||||
// Note: we could also use the following for
|
||||
// line-folding instead of <br>s This would be
|
||||
// possible without recomputing the whole text, much
|
||||
// better perfs for toggling wrap/no-wrap:
|
||||
// <pre style=\"white-space: pre-wrap\">
|
||||
} else {
|
||||
m_eolbr = true;
|
||||
return string("<qt><head><title></title></head><body>");
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual string startMatch()
|
||||
|
||||
virtual string startMatch(unsigned int grpidx)
|
||||
{
|
||||
return string("<span style='color: ")
|
||||
+ string((const char *)(prefs.qtermcolor.toUtf8()))
|
||||
+ string(";font-weight: bold;")
|
||||
+ string("'>");
|
||||
LOGDEB2(("startMatch, grpidx %u\n", grpidx));
|
||||
grpidx = m_hdata->grpsugidx[grpidx];
|
||||
LOGDEB2(("startMatch, ugrpidx %u\n", grpidx));
|
||||
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";
|
||||
char acname[sizeof(termAnchorNameBase) + 20];
|
||||
sprintf(acname, "%s%d", termAnchorNameBase, i);
|
||||
if (i > lastanchor)
|
||||
lastanchor = i;
|
||||
return string(acname);
|
||||
}
|
||||
|
||||
virtual string startAnchor(int i) {
|
||||
return string("<a name=\"") + termAnchorName(i) + "\">";
|
||||
virtual string startChunk()
|
||||
{
|
||||
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));
|
||||
}
|
||||
virtual string startChunk() { return "<pre>";}
|
||||
return m_curanchor;
|
||||
}
|
||||
|
||||
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()
|
||||
|
@ -141,8 +217,24 @@ void Preview::init()
|
|||
QHBoxLayout *layout3 = new QHBoxLayout(0);
|
||||
searchLabel = new QLabel(this);
|
||||
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->setEnabled(TRUE);
|
||||
layout3->addWidget(nextButton);
|
||||
|
@ -160,7 +252,7 @@ void Preview::init()
|
|||
resize(QSize(640, 480).expandedTo(minimumSizeHint()));
|
||||
|
||||
// buddies
|
||||
searchLabel->setBuddy(searchTextLine);
|
||||
searchLabel->setBuddy(searchTextCMB);
|
||||
|
||||
searchLabel->setText(tr("&Search for:"));
|
||||
nextButton->setText(tr("&Next"));
|
||||
|
@ -176,26 +268,25 @@ void Preview::init()
|
|||
"RCL.SEARCH.PREVIEW");
|
||||
|
||||
// signals and slots connections
|
||||
connect(searchTextLine, SIGNAL(textChanged(const QString&)),
|
||||
this, SLOT(searchTextLine_textChanged(const QString&)));
|
||||
connect(searchTextCMB, SIGNAL(activated(int)),
|
||||
this, SLOT(searchTextFromIndex(int)));
|
||||
connect(searchTextCMB, SIGNAL(editTextChanged(const QString&)),
|
||||
this, SLOT(searchTextChanged(const QString&)));
|
||||
connect(nextButton, SIGNAL(clicked()), this, SLOT(nextPressed()));
|
||||
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 *)),
|
||||
this, SLOT(currentChanged(QWidget *)));
|
||||
connect(bt, SIGNAL(clicked()), this, SLOT(closeCurrentTab()));
|
||||
|
||||
m_dynSearchActive = false;
|
||||
m_canBeep = true;
|
||||
m_currentW = 0;
|
||||
if (prefs.pvwidth > 100) {
|
||||
resize(prefs.pvwidth, prefs.pvheight);
|
||||
}
|
||||
m_loading = false;
|
||||
currentChanged(pvTab->currentWidget());
|
||||
m_justCreated = true;
|
||||
m_haveAnchors = false;
|
||||
m_curAnchor = 1;
|
||||
}
|
||||
|
||||
void Preview::closeEvent(QCloseEvent *e)
|
||||
|
@ -273,11 +364,11 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
|
|||
} else if (m_dynSearchActive) {
|
||||
if (keyEvent->key() == Qt::Key_F3) {
|
||||
LOGDEB2(("Preview::eventFilter: got F3\n"));
|
||||
doSearch(searchTextLine->text(), true, false);
|
||||
doSearch(searchTextCMB->currentText(), true, false);
|
||||
return true;
|
||||
}
|
||||
if (target != searchTextLine)
|
||||
return QApplication::sendEvent(searchTextLine, event);
|
||||
if (target != searchTextCMB)
|
||||
return QApplication::sendEvent(searchTextCMB, event);
|
||||
} else {
|
||||
if (edit &&
|
||||
(target == edit || target == edit->viewport())) {
|
||||
|
@ -285,7 +376,7 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
|
|||
(keyEvent->key() == Qt::Key_F &&
|
||||
(keyEvent->modifiers() & Qt::ControlModifier))) {
|
||||
LOGDEB2(("Preview::eventFilter: got / or C-F\n"));
|
||||
searchTextLine->setFocus();
|
||||
searchTextCMB->setFocus();
|
||||
m_dynSearchActive = true;
|
||||
return true;
|
||||
} else if (keyEvent->key() == Qt::Key_Space) {
|
||||
|
@ -307,23 +398,27 @@ bool Preview::eventFilter(QObject *target, QEvent *event)
|
|||
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()) {
|
||||
m_dynSearchActive = false;
|
||||
// nextButton->setEnabled(false);
|
||||
// prevButton->setEnabled(false);
|
||||
clearPB->setEnabled(false);
|
||||
} else {
|
||||
m_dynSearchActive = true;
|
||||
// nextButton->setEnabled(true);
|
||||
// prevButton->setEnabled(true);
|
||||
clearPB->setEnabled(true);
|
||||
doSearch(text, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::searchTextFromIndex(int idx)
|
||||
{
|
||||
LOGDEB1(("search line from index %d\n", idx));
|
||||
m_searchTextFromIndex = idx;
|
||||
}
|
||||
|
||||
PreviewTextEdit *Preview::currentEditor()
|
||||
{
|
||||
LOGDEB2(("Preview::currentEditor()\n"));
|
||||
|
@ -351,8 +446,8 @@ void Preview::emitSaveDocToFile()
|
|||
void Preview::doSearch(const QString &_text, bool next, bool reverse,
|
||||
bool wordOnly)
|
||||
{
|
||||
LOGDEB(("Preview::doSearch: text [%s] txtlen %d next %d rev %d word %d\n",
|
||||
(const char *)_text.toUtf8(), _text.length(), int(next),
|
||||
LOGDEB(("Preview::doSearch: text [%s] idx %d next %d rev %d word %d\n",
|
||||
(const char *)_text.toUtf8(), m_searchTextFromIndex, int(next),
|
||||
int(reverse), int(wordOnly)));
|
||||
QString text = _text;
|
||||
|
||||
|
@ -363,25 +458,19 @@ void Preview::doSearch(const QString &_text, bool next, bool reverse,
|
|||
return;
|
||||
}
|
||||
|
||||
if (text.isEmpty()) {
|
||||
if (m_haveAnchors == false) {
|
||||
if (text.isEmpty() || m_searchTextFromIndex != -1) {
|
||||
if (!edit->m_plaintorich->haveAnchors()) {
|
||||
LOGDEB(("NO ANCHORS\n"));
|
||||
return;
|
||||
}
|
||||
// The combobox indices are equal to the search ugroup indices
|
||||
// in hldata, that's how we built the list.
|
||||
if (reverse) {
|
||||
if (m_curAnchor == 1)
|
||||
m_curAnchor = edit->m_plaintorich->lastanchor;
|
||||
else
|
||||
m_curAnchor--;
|
||||
edit->m_plaintorich->prevAnchorNum(m_searchTextFromIndex);
|
||||
} else {
|
||||
if (m_curAnchor == edit->m_plaintorich->lastanchor)
|
||||
m_curAnchor = 1;
|
||||
else
|
||||
m_curAnchor++;
|
||||
edit->m_plaintorich->nextAnchorNum(m_searchTextFromIndex);
|
||||
}
|
||||
LOGDEB(("m_curAnchor: %d\n", m_curAnchor));
|
||||
QString aname =
|
||||
QString::fromUtf8(edit->m_plaintorich->termAnchorName(m_curAnchor).c_str());
|
||||
QString aname = edit->m_plaintorich->curAnchorName();
|
||||
LOGDEB(("Calling scrollToAnchor(%s)\n", (const char *)aname.toUtf8()));
|
||||
edit->scrollToAnchor(aname);
|
||||
// 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()
|
||||
{
|
||||
LOGDEB2(("PreviewTextEdit::nextPressed\n"));
|
||||
doSearch(searchTextLine->text(), true, false);
|
||||
LOGDEB2(("Preview::nextPressed\n"));
|
||||
doSearch(searchTextCMB->currentText(), true, false);
|
||||
}
|
||||
|
||||
void Preview::prevPressed()
|
||||
{
|
||||
LOGDEB2(("PreviewTextEdit::prevPressed\n"));
|
||||
doSearch(searchTextLine->text(), true, true);
|
||||
LOGDEB2(("Preview::prevPressed\n"));
|
||||
doSearch(searchTextCMB->currentText(), true, true);
|
||||
}
|
||||
|
||||
// Called when user clicks on tab
|
||||
|
@ -456,7 +545,6 @@ void Preview::currentChanged(QWidget * tw)
|
|||
LOGDEB2(("PreviewTextEdit::currentChanged\n"));
|
||||
PreviewTextEdit *edit =
|
||||
tw->findChild<PreviewTextEdit*>("pvEdit");
|
||||
m_currentW = tw;
|
||||
LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
|
||||
|
||||
if (edit == 0) {
|
||||
|
@ -470,7 +558,7 @@ void Preview::currentChanged(QWidget * tw)
|
|||
connect(this, SIGNAL(printCurrentPreviewRequest()), edit, SLOT(print()));
|
||||
edit->installEventFilter(this);
|
||||
edit->viewport()->installEventFilter(this);
|
||||
searchTextLine->installEventFilter(this);
|
||||
searchTextCMB->installEventFilter(this);
|
||||
emit(previewExposed(this, m_searchId, edit->m_docnum));
|
||||
}
|
||||
|
||||
|
@ -507,7 +595,7 @@ PreviewTextEdit *Preview::addEditorTab()
|
|||
|
||||
void Preview::setCurTabProps(const Rcl::Doc &doc, int docnum)
|
||||
{
|
||||
LOGDEB1(("PreviewTextEdit::setCurTabProps\n"));
|
||||
LOGDEB1(("Preview::setCurTabProps\n"));
|
||||
QString title;
|
||||
string ctitle;
|
||||
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);
|
||||
CancelCheck::instance().setCancel(false);
|
||||
|
||||
m_haveAnchors = false;
|
||||
|
||||
setCurTabProps(idoc, docnum);
|
||||
|
||||
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
|
||||
m_haveAnchors = editor->m_plaintorich->lastanchor != 0;
|
||||
if (searchTextLine->text().length() != 0) {
|
||||
if (searchTextCMB->currentText().length() != 0) {
|
||||
// If there is a current search string, perform the search
|
||||
m_canBeep = true;
|
||||
doSearch(searchTextLine->text(), true, false);
|
||||
doSearch(searchTextCMB->currentText(), true, false);
|
||||
} else {
|
||||
// Position to the first query term
|
||||
if (m_haveAnchors) {
|
||||
QString aname =
|
||||
QString::fromUtf8(editor->m_plaintorich->termAnchorName(1).c_str());
|
||||
if (editor->m_plaintorich->haveAnchors()) {
|
||||
QString aname = editor->m_plaintorich->curAnchorName();
|
||||
LOGDEB2(("Call movetoanchor(%s)\n", (const char *)aname.toUtf8()));
|
||||
editor->scrollToAnchor(aname);
|
||||
// Position the cursor approximately at the anchor (top of
|
||||
// viewport) so that searches start from here
|
||||
QTextCursor cursor = editor->cursorForPosition(QPoint(0, 0));
|
||||
editor->setTextCursor(cursor);
|
||||
m_curAnchor = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -989,14 +1072,15 @@ bool Preview::loadDocInCurrentTab(const Rcl::Doc &idoc, int docnum)
|
|||
return true;
|
||||
}
|
||||
|
||||
PreviewTextEdit::PreviewTextEdit(QWidget* parent,const char* name, Preview *pv)
|
||||
: QTextEdit(parent), m_preview(pv), m_dspflds(false), m_docnum(-1)
|
||||
PreviewTextEdit::PreviewTextEdit(QWidget* parent, const char* nm, Preview *pv)
|
||||
: QTextEdit(parent), m_preview(pv),
|
||||
m_plaintorich(new PlainToRichQtPreview()),
|
||||
m_dspflds(false), m_docnum(-1)
|
||||
{
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
setObjectName(name);
|
||||
setObjectName(nm);
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
|
||||
this, SLOT(createPopupMenu(const QPoint&)));
|
||||
m_plaintorich = new PlainToRichQtPreview();
|
||||
}
|
||||
|
||||
PreviewTextEdit::~PreviewTextEdit()
|
||||
|
|
|
@ -58,22 +58,27 @@ protected:
|
|||
void mouseDoubleClickEvent(QMouseEvent *);
|
||||
|
||||
private:
|
||||
PlainToRichQtPreview *m_plaintorich;
|
||||
Preview *m_preview;
|
||||
PlainToRichQtPreview *m_plaintorich;
|
||||
|
||||
bool m_dspflds;
|
||||
string m_url; // filename for this tab
|
||||
string m_ipath; // Internal doc path inside file
|
||||
int m_docnum; // Index of doc in db search results.
|
||||
|
||||
// doc out of internfile (previous fields come from the index) with
|
||||
// main text erased (for space).
|
||||
Rcl::Doc m_fdoc;
|
||||
|
||||
// The input doc out of the index/query list
|
||||
Rcl::Doc m_dbdoc;
|
||||
|
||||
// Saved rich (or plain actually) text: the textedit seems to
|
||||
// sometimes (but not always) return its text stripped of tags, so
|
||||
// this is needed (for printing for example)
|
||||
QString m_richtxt;
|
||||
Qt::TextFormat m_format;
|
||||
|
||||
// Temporary file name (possibly, if displaying image). The
|
||||
// TempFile itself is kept inside main.cpp (because that's where
|
||||
// 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
|
||||
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();
|
||||
}
|
||||
~Preview(){}
|
||||
|
||||
virtual void closeEvent(QCloseEvent *e );
|
||||
virtual bool eventFilter(QObject *target, QEvent *event );
|
||||
|
||||
/**
|
||||
* Arrange for the document to be displayed either by exposing the tab
|
||||
* if already loaded, or by creating a new tab and loading it.
|
||||
|
@ -112,7 +117,8 @@ class Preview : public QWidget {
|
|||
friend class PreviewTextEdit;
|
||||
|
||||
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,
|
||||
bool wo = false);
|
||||
virtual void nextPressed();
|
||||
|
@ -138,18 +144,17 @@ private:
|
|||
int m_searchId;
|
||||
|
||||
bool m_dynSearchActive;
|
||||
// Index value the search text comes from. -1 if text was edited
|
||||
int m_searchTextFromIndex;
|
||||
|
||||
bool m_canBeep;
|
||||
bool m_loading;
|
||||
QWidget *m_currentW;
|
||||
HighlightData m_hData;
|
||||
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;
|
||||
QLabel* searchLabel;
|
||||
QLineEdit* searchTextLine;
|
||||
QComboBox *searchTextCMB;
|
||||
QPushButton* nextButton;
|
||||
QPushButton* prevButton;
|
||||
QPushButton* clearPB;
|
||||
|
|
|
@ -250,12 +250,22 @@ string QtGuiResListPager::iconUrl(RclConfig *config, Rcl::Doc& doc)
|
|||
|
||||
class PlainToRichQtReslist : public PlainToRich {
|
||||
public:
|
||||
virtual ~PlainToRichQtReslist() {}
|
||||
virtual string startMatch() {
|
||||
virtual string startMatch(unsigned int idx)
|
||||
{
|
||||
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: ")
|
||||
+ string((const char *)prefs.qtermcolor.toAscii()) + string("'>");
|
||||
}
|
||||
virtual string endMatch() {return string("</span>");}
|
||||
virtual string endMatch()
|
||||
{
|
||||
return string("</span>");
|
||||
}
|
||||
};
|
||||
static PlainToRichQtReslist g_hiliter;
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ public:
|
|||
setupUi(this);
|
||||
init();
|
||||
}
|
||||
~SSearch(){}
|
||||
|
||||
virtual void init();
|
||||
virtual void setAnyTermMode();
|
||||
|
|
|
@ -49,13 +49,22 @@ static string vecStringToString(const vector<string>& t)
|
|||
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
|
||||
// inside the result text. This is then used to insert highlight tags.
|
||||
class TextSplitPTR : public TextSplit {
|
||||
public:
|
||||
|
||||
// Out: begin and end byte positions of query terms/groups in text
|
||||
vector<pair<int, int> > tboffs;
|
||||
vector<MatchEntry> tboffs;
|
||||
|
||||
TextSplitPTR(const HighlightData& 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();
|
||||
vit != hdata.groups.end(); vit++) {
|
||||
if (vit->size() == 1) {
|
||||
m_terms.insert(vit->front());
|
||||
m_terms[vit->front()] = vit - hdata.groups.begin();
|
||||
} else if (vit->size() > 1) {
|
||||
for (vector<string>::const_iterator it = vit->begin();
|
||||
it != vit->end(); it++) {
|
||||
|
@ -91,8 +100,9 @@ class TextSplitPTR : public TextSplit {
|
|||
// pos, bts, bte));
|
||||
|
||||
// If this word is a search term, remember its byte-offset span.
|
||||
if (m_terms.find(dumb) != m_terms.end()) {
|
||||
tboffs.push_back(pair<int, int>(bts, bte));
|
||||
map<string, unsigned int>::const_iterator it = m_terms.find(dumb);
|
||||
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
|
||||
|
@ -114,13 +124,13 @@ class TextSplitPTR : public TextSplit {
|
|||
virtual bool matchGroups();
|
||||
|
||||
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.
|
||||
int m_wcount;
|
||||
|
||||
// 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
|
||||
set<string> m_gterms;
|
||||
|
@ -191,9 +201,12 @@ static bool do_proximity_test(int window, vector<vector<int>* >& plists,
|
|||
return false;
|
||||
}
|
||||
|
||||
// Find NEAR matches for the input group of terms, update highlight map
|
||||
bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
|
||||
// Find NEAR matches for one group of terms, update highlight map
|
||||
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,
|
||||
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
|
||||
// sorting the plists by length.
|
||||
map<vector<int>*, string> plistToTerm;
|
||||
// For traces
|
||||
vector<string> realgroup;
|
||||
|
||||
// Find the position list for each term in the group. Not all
|
||||
// necessarily exist (esp for NEAR where terms have been
|
||||
// stem-expanded: we don't know which matched)
|
||||
// Find the position list for each term in the group. It is
|
||||
// possible that this particular group was not actually matched by
|
||||
// the search, so that some terms are not found.
|
||||
for (vector<string>::const_iterator it = terms.begin();
|
||||
it != terms.end(); it++) {
|
||||
map<string, vector<int> >::iterator pl = m_plists.find(*it);
|
||||
if (pl == m_plists.end()) {
|
||||
LOGDEB0(("TextSplitPTR::matchGroup: [%s] not found in m_plists\n",
|
||||
(*it).c_str()));
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
plists.push_back(&(pl->second));
|
||||
plistToTerm[&(pl->second)] = *it;
|
||||
realgroup.push_back(*it);
|
||||
}
|
||||
LOGDEB0(("TextSplitPTR::matchGroup:d %d:real group after expansion %s\n",
|
||||
window, vecStringToString(realgroup).c_str()));
|
||||
// I think this can't actually happen, was useful when we used to
|
||||
// prune the groups, but doesn't hurt.
|
||||
if (plists.size() < 2) {
|
||||
LOGDEB0(("TextSplitPTR::matchGroup: no actual groups found\n"));
|
||||
return false;
|
||||
|
@ -261,15 +271,13 @@ bool TextSplitPTR::matchGroup(const vector<string>& terms, int window)
|
|||
SETMINMAX(pos, sta, sto);
|
||||
minpos = sto+1;
|
||||
// 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 i2 = m_gpostobytes.find(sto);
|
||||
if (i1 != m_gpostobytes.end() && i2 != m_gpostobytes.end()) {
|
||||
LOGDEB0(("TextSplitPTR::matchGroup: pushing bpos %d %d\n",
|
||||
i1->second.first, i2->second.second));
|
||||
tboffs.push_back(pair<int, int>(i1->second.first,
|
||||
i2->second.second));
|
||||
bs = i1->second.first;
|
||||
tboffs.push_back(MatchEntry(i1->second.first,
|
||||
i2->second.second, grpidx));
|
||||
} else {
|
||||
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 */
|
||||
class PairIntCmpFirst {
|
||||
public:
|
||||
bool operator()(pair<int,int> a, pair<int, int>b) {
|
||||
if (a.first != b.first)
|
||||
return a.first < b.first;
|
||||
return a.second > b.second;
|
||||
bool operator()(const MatchEntry& a, const MatchEntry& b) {
|
||||
if (a.offs.first != b.offs.first)
|
||||
return a.offs.first < b.offs.first;
|
||||
return a.offs.second > b.offs.second;
|
||||
}
|
||||
};
|
||||
|
||||
// Look for matches to PHRASE and NEAR term groups. Actually, we
|
||||
// handle all groups as NEAR (ignore order).
|
||||
// Look for matches to PHRASE and NEAR term groups and finalize the
|
||||
// matched regions list (sort it by increasing start then decreasing
|
||||
// length)
|
||||
// Actually, we handle all groups as NEAR (ignore order).
|
||||
bool TextSplitPTR::matchGroups()
|
||||
{
|
||||
for (unsigned int i = 0; i < m_hdata.groups.size(); i++) {
|
||||
if (m_hdata.groups[i].size() <= 1)
|
||||
continue;
|
||||
matchGroup(m_hdata.groups[i],
|
||||
m_hdata.groups[i].size() + m_hdata.slacks[i]);
|
||||
matchGroup(i);
|
||||
}
|
||||
|
||||
// Sort regions by increasing start and decreasing width.
|
||||
|
@ -324,6 +333,7 @@ bool PlainToRich::plaintorich(const string& in,
|
|||
{
|
||||
Chrono chron;
|
||||
|
||||
m_hdata = &hdata;
|
||||
// Compute the positions for the query terms. We use the text
|
||||
// splitter to break the text into words, and compare the words to
|
||||
// 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
|
||||
// output highlight tags and to compute term positions in the
|
||||
// output text
|
||||
vector<pair<int, int> >::iterator tPosIt = splitter.tboffs.begin();
|
||||
vector<pair<int, int> >::iterator tPosEnd = splitter.tboffs.end();
|
||||
vector<MatchEntry>::iterator tPosIt = splitter.tboffs.begin();
|
||||
vector<MatchEntry>::iterator tPosEnd = splitter.tboffs.end();
|
||||
|
||||
#if 0
|
||||
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 inindent = 1;
|
||||
|
||||
// Value for numbered anchors at each term match
|
||||
int anchoridx = 1;
|
||||
// HTML state
|
||||
bool intag = false, inparamvalue = false;
|
||||
// My tag state
|
||||
|
@ -391,22 +399,20 @@ bool PlainToRich::plaintorich(const string& in,
|
|||
// we are at or after a term match, mark.
|
||||
if (tPosIt != tPosEnd) {
|
||||
int ibyteidx = chariter.getBpos();
|
||||
if (ibyteidx == tPosIt->first) {
|
||||
if (ibyteidx == tPosIt->offs.first) {
|
||||
if (!intag && ibyteidx >= (int)headend) {
|
||||
*olit += startAnchor(anchoridx);
|
||||
*olit += startMatch();
|
||||
*olit += startMatch(tPosIt->grpidx);
|
||||
}
|
||||
anchoridx++;
|
||||
inrcltag = 1;
|
||||
} else if (ibyteidx == tPosIt->second) {
|
||||
} else if (ibyteidx == tPosIt->offs.second) {
|
||||
// Output end of match region tags
|
||||
if (!intag && ibyteidx > (int)headend) {
|
||||
*olit += endMatch();
|
||||
*olit += endAnchor();
|
||||
}
|
||||
// Skip all highlight areas that would overlap this one
|
||||
int crend = tPosIt->second;
|
||||
while (tPosIt != splitter.tboffs.end() && tPosIt->first < crend)
|
||||
int crend = tPosIt->offs.second;
|
||||
while (tPosIt != splitter.tboffs.end() &&
|
||||
tPosIt->offs.first < crend)
|
||||
tPosIt++;
|
||||
inrcltag = 0;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <list>
|
||||
|
||||
#include "hldata.h"
|
||||
#include "cstr.h"
|
||||
|
||||
/**
|
||||
* A class for highlighting search results. Overridable methods allow
|
||||
|
@ -31,7 +32,7 @@
|
|||
class PlainToRich {
|
||||
public:
|
||||
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 */
|
||||
virtual std::string header() {return snull;}
|
||||
virtual std::string startMatch() {return snull;}
|
||||
virtual std::string endMatch() {return snull;}
|
||||
virtual std::string startAnchor(int) {return snull;}
|
||||
virtual std::string endAnchor() {return snull;}
|
||||
virtual std::string startChunk() {return snull;}
|
||||
|
||||
virtual std::string header()
|
||||
{
|
||||
return cstr_null;
|
||||
}
|
||||
|
||||
/** 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:
|
||||
const std::string snull;
|
||||
bool m_inputhtml;
|
||||
// Use <br> to break plain text lines (else caller has used a <pre> tag)
|
||||
bool m_eolbr;
|
||||
const HighlightData *m_hdata;
|
||||
};
|
||||
|
||||
#endif /* _PLAINTORICH_H_INCLUDED_ */
|
||||
|
|
|
@ -40,9 +40,14 @@ static const string cstr_hlfontcolor("<font color=\"blue\">");
|
|||
static const string cstr_hlendfont("</font>");
|
||||
class PlainToRichHtReslist : public PlainToRich {
|
||||
public:
|
||||
virtual ~PlainToRichHtReslist() {}
|
||||
virtual string startMatch() {return cstr_hlfontcolor;}
|
||||
virtual string endMatch() {return cstr_hlendfont;}
|
||||
virtual string startMatch(unsigned int)
|
||||
{
|
||||
return cstr_hlfontcolor;
|
||||
}
|
||||
virtual string endMatch()
|
||||
{
|
||||
return cstr_hlendfont;
|
||||
}
|
||||
};
|
||||
static PlainToRichHtReslist g_hiliter;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue