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

@ -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()
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));
}
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()
@ -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,9 +446,9 @@ 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),
int(reverse), int(wordOnly)));
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;
bool matchCase = matchCheck->isChecked();
@ -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()