implement cancellation in preview loading

This commit is contained in:
dockes 2006-01-27 13:42:03 +00:00
parent c8213f76d3
commit 3809fdc235
5 changed files with 165 additions and 68 deletions

View File

@ -1,5 +1,5 @@
#ifndef lint
static char rcsid[] = "@(#$Id: plaintorich.cpp,v 1.8 2006-01-23 13:32:05 dockes Exp $ (C) 2005 J.F.Dockes";
static char rcsid[] = "@(#$Id: plaintorich.cpp,v 1.9 2006-01-27 13:42:02 dockes Exp $ (C) 2005 J.F.Dockes";
#endif
/*
* This program is free software; you can redistribute it and/or modify
@ -22,9 +22,11 @@ static char rcsid[] = "@(#$Id: plaintorich.cpp,v 1.8 2006-01-23 13:32:05 dockes
#include <string>
#include <utility>
#include <list>
#include <set>
#ifndef NO_NAMESPACES
using std::list;
using std::pair;
using std::set;
#endif /* NO_NAMESPACES */
#include "rcldb.h"
@ -34,17 +36,24 @@ using std::pair;
#include "utf8iter.h"
#include "transcode.h"
#include "smallut.h"
#include "plaintorich.h"
#include "cancelcheck.h"
// Text splitter callback used to take note of the position of query terms
// inside the result text. This is then used to post highlight tags.
class myTextSplitCB : public TextSplitCB {
public:
const list<string> *terms; // in: query terms
set<string> terms; // in: user query terms
list<pair<int, int> > tboffs; // out: begin and end positions of
// query terms in text
myTextSplitCB(const list<string>& terms)
: terms(&terms) {
myTextSplitCB(const list<string>& its) {
for (list<string>::const_iterator it = its.begin(); it != its.end();
it++) {
string s;
Rcl::dumb_string(*it, s);
terms.insert(s);
}
}
// Callback called by the text-to-words breaker for each word
@ -53,14 +62,9 @@ class myTextSplitCB : public TextSplitCB {
Rcl::dumb_string(term, dumb);
//LOGDEB(("Input dumbbed term: '%s' %d %d %d\n", dumb.c_str(),
// pos, bts, bte));
for (list<string>::const_iterator it = terms->begin();
it != terms->end(); it++) {
if (!stringlowercmp(*it, dumb)) {
tboffs.push_back(pair<int, int>(bts, bte));
break;
}
}
if (terms.find(dumb) != terms.end())
tboffs.push_back(pair<int, int>(bts, bte));
CancelCheck::instance().checkCancel();
return true;
}
};
@ -72,12 +76,13 @@ class myTextSplitCB : public TextSplitCB {
// duplicate whitespace etc...). This is tricky business and it might
// be better to insert the text char by char, taking note of where qt
// thinks it is at each term.
string plaintorich(const string &in, const list<string>& terms,
list<pair<int, int> >&termoffsets)
bool plaintorich(const string& in, string& out, const list<string>& terms,
list<pair<int, int> >&termoffsets)
{
Chrono chron;
LOGDEB(("plaintorich: terms: %s\n",
stringlistdisp(terms).c_str()));
out.erase();
termoffsets.erase(termoffsets.begin(), termoffsets.end());
// We first use the text splitter to break the text into words,
@ -89,11 +94,10 @@ string plaintorich(const string &in, const list<string>& terms,
// character offset
splitter.text_to_words(in);
LOGDEB(("Split done\n"));
LOGDEB(("plaintorich: split done %d mS\n", chron.millis()));
// Rich text output
string out = "<qt><head><title></title></head><body><p>";
out = "<qt><head><title></title></head><body><p>";
// Iterator for the list of input term positions. We use it to
// output highlight tags and to compute term positions in the
@ -112,7 +116,10 @@ string plaintorich(const string &in, const list<string>& terms,
// consecutive blank chars
int atblank = 0;
for (string::size_type pos = 0; pos != string::npos; pos = chariter++) {
// If we still have terms, check (byte) position
if (pos && (pos % 1000) == 0) {
CancelCheck::instance().checkCancel();
}
// If we still have terms positions, check (byte) position
if (it != cb.tboffs.end()) {
int ibyteidx = chariter.getBpos();
if (ibyteidx == it->first) {
@ -148,7 +155,7 @@ string plaintorich(const string &in, const list<string>& terms,
break;
default:
// We don't change the eol status for whitespace, want a real line
if (*chariter == ' ' || *chariter == ' ') {
if (*chariter == ' ' || *chariter == '\t') {
if (!atblank)
outcpos++;
atblank = 1;
@ -167,5 +174,6 @@ string plaintorich(const string &in, const list<string>& terms,
fclose(fp);
}
#endif
return out;
LOGDEB(("plaintorich: done %d mS\n", chron.millis()));
return true;
}

View File

@ -1,6 +1,6 @@
#ifndef _PLAINTORICH_H_INCLUDED_
#define _PLAINTORICH_H_INCLUDED_
/* @(#$Id: plaintorich.h,v 1.3 2006-01-19 12:01:43 dockes Exp $ (C) 2004 J.F.Dockes */
/* @(#$Id: plaintorich.h,v 1.4 2006-01-27 13:42:02 dockes Exp $ (C) 2004 J.F.Dockes */
#include <string>
@ -14,8 +14,8 @@
* @param terms list of query terms
* @param termoffsets output: character offsets where we find terms.
*/
extern string plaintorich(const string &in,
const list<string>& terms,
list<pair<int, int> >&termoffsets);
extern bool plaintorich(const string &in, string &out,
const list<string>& terms,
list<pair<int, int> >& termoffsets);
#endif /* _PLAINTORICH_H_INCLUDED_ */

View File

@ -198,7 +198,7 @@
<variable>void *tabData;</variable>
</variables>
<signals>
<signal>previewClosed(Preview *)</signal>
<signal>previewClosed(QWidget *)</signal>
</signals>
<slots>
<slot>searchTextLine_textChanged( const QString &amp; text )</slot>
@ -208,7 +208,6 @@
<slot>currentChanged( QWidget * tw )</slot>
<slot>closeCurrentTab()</slot>
<slot>setCurTabProps( const string &amp; fn, const Rcl::Doc &amp; doc )</slot>
<slot>loadFileInCurrentTab( string fn, size_t sz, const Rcl::Doc &amp; idoc )</slot>
</slots>
<functions>
<function access="private" specifier="non virtual">init()</function>
@ -218,6 +217,7 @@
<function returnType="QTextEdit *">getCurrentEditor()</function>
<function returnType="QTextEdit *">addEditorTab()</function>
<function access="private">destroy()</function>
<function access="public" returnType="bool">loadFileInCurrentTab( string fn, size_t sz, const Rcl::Doc &amp; idoc )</function>
</functions>
<layoutdefaults spacing="6" margin="11"/>
</UI>

View File

@ -29,6 +29,7 @@ using std::pair;
#include "plaintorich.h"
#include "smallut.h"
#include "wipedir.h"
#include "cancelcheck.h"
// We keep a list of data associated to each tab
class TabData {
@ -59,7 +60,7 @@ void Preview::destroy()
void Preview::closeEvent(QCloseEvent *e)
{
emit previewClosed(this);
emit previewClosed((QWidget *)this);
QWidget::closeEvent(e);
}
@ -201,15 +202,15 @@ void Preview::prevPressed()
void Preview::currentChanged(QWidget * tw)
{
QObject *o = tw->child("pvEdit");
LOGDEB1(("Preview::currentChanged(). Edit %p\n", o));
QWidget *edit = (QWidget *)tw->child("pvEdit");
LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
if (o == 0) {
if (edit == 0) {
LOGERR(("Editor child not found\n"));
} else {
tw->installEventFilter(this);
o->installEventFilter(this);
((QWidget*)o)->setFocus();
edit->installEventFilter(this);
edit->setFocus();
}
}
@ -317,7 +318,9 @@ bool Preview::makeDocCurrent(const string &fn, const Rcl::Doc &doc)
be even more complicated, and we probably don't want the user to click on
things during this time anyway.
No cancel button is implemented, but this could conceivably be done
It might be possible, but complicated (need modifications in
handler) to implement a kind of bucket brigade, to have the
beginning of the text displayed faster
*/
/* A thread to to the file reading / format conversion */
@ -348,10 +351,14 @@ class LoadThread : public QThread {
return;
}
FileInterner interner(filename, rclconfig, tmpdir, mtype);
if (interner.internfile(*out, ipath) != FileInterner::FIDone) {
try {
if (interner.internfile(*out, ipath) != FileInterner::FIDone) {
*statusp = -1;
} else {
*statusp = 0;
}
} catch (CancelExcept) {
*statusp = -1;
} else {
*statusp = 0;
}
}
};
@ -370,7 +377,11 @@ class ToRichThread : public QThread {
virtual void run()
{
DebugLog::getdbl()->setloglevel(DEBDEB1);
string rich = plaintorich(in, terms, termoffsets);
string rich;
try {
plaintorich(in, rich, terms, termoffsets);
} catch (CancelExcept) {
}
out = QString::fromUtf8(rich.c_str(), rich.length());
}
};
@ -385,9 +396,15 @@ class WaiterThread : public QThread {
}
};
void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
#define CHUNKL 50*1000
#ifndef MIN
#define MIN(A,B) ((A)<(B)?(A):(B))
#endif
bool Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
{
Rcl::Doc doc = idoc;
bool cancel = false;
if (doc.title.empty())
doc.title = path_getsimple(doc.url);
@ -401,11 +418,8 @@ void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
.arg(csz);
// Create progress dialog and aux objects
const int nsteps = 10;
QProgressDialog progress( msg, "", nsteps, this, "Loading", TRUE );
QPushButton *pb = new QPushButton("", this);
pb->setEnabled(false);
progress.setCancelButton(pb);
const int nsteps = 20;
QProgressDialog progress(msg, tr("Cancel"), nsteps, this, "Loading", FALSE);
progress.setMinimumDuration(1000);
WaiterThread waiter(100);
@ -416,57 +430,103 @@ void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
int status = 1;
LoadThread lthr(&status, &fdoc, fn, doc.ipath, &doc.mimetype);
lthr.start();
int i;
for (i = 1;;i++) {
int prog;
for (prog = 1;;prog++) {
waiter.start();
waiter.wait();
if (lthr.finished())
break;
progress.setProgress(i , i <= nsteps-1 ? nsteps : i+1);
progress.setProgress(prog , prog <= nsteps-1 ? nsteps : prog+1);
qApp->processEvents();
if (i >= 5)
if (progress.wasCanceled()) {
CancelCheck::instance().setCancel();
cancel = true;
}
if (prog >= 5)
sleep(1);
}
if (cancel)
return false;
if (status != 0) {
QMessageBox::warning(0, "Recoll",
tr("Can't turn doc into internal rep for ") +
doc.mimetype.c_str());
return;
return false;
}
// Reset config just in case.
rclconfig->setKeyDir("");
// Highlight search terms:
// Create preview text: highlight search terms:
progress.setLabelText(tr("Creating preview text"));
list<string> terms;
rcldb->getQueryTerms(terms);
list<pair<int, int> > termoffsets;
QString str;
ToRichThread rthr(fdoc.text, terms, termoffsets, str);
QString richTxt;
ToRichThread rthr(fdoc.text, terms, termoffsets, richTxt);
rthr.start();
for (;;i++) {
waiter.start();
waiter.wait();
for (;;prog++) {
waiter.start(); waiter.wait();
if (rthr.finished())
break;
progress.setProgress(i , i <= nsteps-1 ? nsteps : i+1);
progress.setProgress(prog , prog <= nsteps-1 ? nsteps : prog+1);
qApp->processEvents();
if (i >= 5)
if (progress.wasCanceled()) {
CancelCheck::instance().setCancel();
cancel = true;
}
if (prog >= 5)
sleep(1);
}
LOGDEB(("Plaintorich done\n"));
if (cancel) {
if (richTxt.length() == 0) {
// We cant call closeCurrentTab here as it might delete
// the object which would be a nasty surprise to our
// caller.
return false;
} else {
richTxt += "<b>Cancelled !</b>";
}
}
// Load into editor
QTextEdit *editor = getCurrentEditor();
QStyleSheetItem *item =
new QStyleSheetItem(editor->styleSheet(), "termtag" );
item->setColor("blue");
item->setFontWeight(QFont::Bold);
prog = 2 * nsteps / 3;
progress.setLabelText(tr("Loading preview text into editor"));
qApp->processEvents();
editor->setText(str);
// Do it in several chunks
int l = 0;
for (unsigned int pos = 0; pos < richTxt.length(); pos += l, prog++) {
progress.setProgress(prog , prog <= nsteps-1 ? nsteps : prog+1);
qApp->processEvents();
l = MIN(CHUNKL, richTxt.length() - pos);
// Avoid breaking inside a tag. Our tags are short (ie: <br>)
for (int i = -4; i < 0; i++) {
if (richTxt[pos+l+i] == '<') {
l = l+i;
break;
}
}
editor->append(richTxt.mid(pos, l));
// Stay at top
if (pos < 5) {
editor->setCursorPosition(0,0);
editor->ensureCursorVisible();
}
if (progress.wasCanceled()) {
cancel = true;
editor->append("<b>Cancelled !</b>");
LOGDEB(("Cancelled\n"));
break;
}
}
int para = 0, index = 1;
if (!termoffsets.empty()) {
index = (termoffsets.begin())->first;
@ -479,6 +539,5 @@ void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
LOGDEB(("PREVIEW len %d paragraphs: %d. Cpos: %d %d\n",
editor->length(), editor->paragraphs(), para, index));
return true;
}

View File

@ -1,5 +1,5 @@
#ifndef lint
static char rcsid[] = "@(#$Id: rclmain.cpp,v 1.9 2006-01-26 14:02:01 dockes Exp $ (C) 2005 J.F.Dockes";
static char rcsid[] = "@(#$Id: rclmain.cpp,v 1.10 2006-01-27 13:42:02 dockes Exp $ (C) 2005 J.F.Dockes";
#endif
/*
* This program is free software; you can redistribute it and/or modify
@ -53,7 +53,6 @@ using std::pair;
#include "mimehandler.h"
#include "pathut.h"
#include "smallut.h"
#include "plaintorich.h"
#include "advsearch.h"
#include "rclversion.h"
#include "sortseq.h"
@ -435,7 +434,8 @@ void RclMain::startPreview(int docnum)
(void)curPreview->addEditorTab();
}
m_history->enterDocument(fn, doc.ipath);
curPreview->loadFileInCurrentTab(fn, st.st_size, doc);
if (!curPreview->loadFileInCurrentTab(fn, st.st_size, doc))
curPreview->closeCurrentTab();
}
void RclMain::startNativeViewer(int docnum)
@ -556,10 +556,40 @@ void RclMain::enablePrevPage(bool yesno)
prevPageAction->setEnabled(yesno);
}
/** Show detailed expansion of a query */
void RclMain::showQueryDetails()
{
// Bad number: must have clicked on header. Show details of query
// Break query into lines of reasonable length, avoid cutting words!
const int ll = 80;
string query = currentQueryData.description;
string oq;
while (query.length() > 0) {
string ss = query.substr(0, ll);
if (ss.length() == ll) {
string::size_type pos = ss.find_last_of(" ");
if (pos == string::npos) {
pos = query.find_first_of(" ");
if (pos != string::npos)
ss = query.substr(0, pos+1);
else
ss = query;
} else {
ss = ss.substr(0, pos+1);
}
}
// This cant happen, but anyway. Be very sure to avoid an infinite loop
if (ss.length() == 0) {
LOGDEB(("showQueryDetails: Internal error!\n"));
oq = query;
break;
}
oq += ss + "\n";
query= query.substr(ss.length());
LOGDEB1(("oq [%s]\n, query [%s]\n, ss [%s]\n",
oq.c_str(), query.c_str(), ss.c_str()));
}
QString desc = tr("Query details") + ": " +
QString::fromUtf8(currentQueryData.description.c_str());
QString::fromUtf8(oq.c_str());
QMessageBox::information(this, tr("Query details"), desc);
}