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

View File

@ -1,6 +1,6 @@
#ifndef _PLAINTORICH_H_INCLUDED_ #ifndef _PLAINTORICH_H_INCLUDED_
#define _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> #include <string>
@ -14,8 +14,8 @@
* @param terms list of query terms * @param terms list of query terms
* @param termoffsets output: character offsets where we find terms. * @param termoffsets output: character offsets where we find terms.
*/ */
extern string plaintorich(const string &in, extern bool plaintorich(const string &in, string &out,
const list<string>& terms, const list<string>& terms,
list<pair<int, int> >&termoffsets); list<pair<int, int> >& termoffsets);
#endif /* _PLAINTORICH_H_INCLUDED_ */ #endif /* _PLAINTORICH_H_INCLUDED_ */

View File

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

View File

@ -29,6 +29,7 @@ using std::pair;
#include "plaintorich.h" #include "plaintorich.h"
#include "smallut.h" #include "smallut.h"
#include "wipedir.h" #include "wipedir.h"
#include "cancelcheck.h"
// We keep a list of data associated to each tab // We keep a list of data associated to each tab
class TabData { class TabData {
@ -59,7 +60,7 @@ void Preview::destroy()
void Preview::closeEvent(QCloseEvent *e) void Preview::closeEvent(QCloseEvent *e)
{ {
emit previewClosed(this); emit previewClosed((QWidget *)this);
QWidget::closeEvent(e); QWidget::closeEvent(e);
} }
@ -201,15 +202,15 @@ void Preview::prevPressed()
void Preview::currentChanged(QWidget * tw) void Preview::currentChanged(QWidget * tw)
{ {
QObject *o = tw->child("pvEdit"); QWidget *edit = (QWidget *)tw->child("pvEdit");
LOGDEB1(("Preview::currentChanged(). Edit %p\n", o)); LOGDEB1(("Preview::currentChanged(). Editor: %p\n", edit));
if (o == 0) { if (edit == 0) {
LOGERR(("Editor child not found\n")); LOGERR(("Editor child not found\n"));
} else { } else {
tw->installEventFilter(this); tw->installEventFilter(this);
o->installEventFilter(this); edit->installEventFilter(this);
((QWidget*)o)->setFocus(); 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 be even more complicated, and we probably don't want the user to click on
things during this time anyway. 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 */ /* A thread to to the file reading / format conversion */
@ -348,10 +351,14 @@ class LoadThread : public QThread {
return; return;
} }
FileInterner interner(filename, rclconfig, tmpdir, mtype); 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; *statusp = -1;
} else {
*statusp = 0;
} }
} }
}; };
@ -370,7 +377,11 @@ class ToRichThread : public QThread {
virtual void run() virtual void run()
{ {
DebugLog::getdbl()->setloglevel(DEBDEB1); 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()); 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; Rcl::Doc doc = idoc;
bool cancel = false;
if (doc.title.empty()) if (doc.title.empty())
doc.title = path_getsimple(doc.url); doc.title = path_getsimple(doc.url);
@ -401,11 +418,8 @@ void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
.arg(csz); .arg(csz);
// Create progress dialog and aux objects // Create progress dialog and aux objects
const int nsteps = 10; const int nsteps = 20;
QProgressDialog progress( msg, "", nsteps, this, "Loading", TRUE ); QProgressDialog progress(msg, tr("Cancel"), nsteps, this, "Loading", FALSE);
QPushButton *pb = new QPushButton("", this);
pb->setEnabled(false);
progress.setCancelButton(pb);
progress.setMinimumDuration(1000); progress.setMinimumDuration(1000);
WaiterThread waiter(100); WaiterThread waiter(100);
@ -416,57 +430,103 @@ void Preview::loadFileInCurrentTab(string fn, size_t sz, const Rcl::Doc &idoc)
int status = 1; int status = 1;
LoadThread lthr(&status, &fdoc, fn, doc.ipath, &doc.mimetype); LoadThread lthr(&status, &fdoc, fn, doc.ipath, &doc.mimetype);
lthr.start(); lthr.start();
int i; int prog;
for (i = 1;;i++) { for (prog = 1;;prog++) {
waiter.start(); waiter.start();
waiter.wait(); waiter.wait();
if (lthr.finished()) if (lthr.finished())
break; break;
progress.setProgress(i , i <= nsteps-1 ? nsteps : i+1); progress.setProgress(prog , prog <= nsteps-1 ? nsteps : prog+1);
qApp->processEvents(); qApp->processEvents();
if (i >= 5) if (progress.wasCanceled()) {
CancelCheck::instance().setCancel();
cancel = true;
}
if (prog >= 5)
sleep(1); sleep(1);
} }
if (cancel)
return false;
if (status != 0) { if (status != 0) {
QMessageBox::warning(0, "Recoll", QMessageBox::warning(0, "Recoll",
tr("Can't turn doc into internal rep for ") + tr("Can't turn doc into internal rep for ") +
doc.mimetype.c_str()); doc.mimetype.c_str());
return; return false;
} }
// Reset config just in case. // Reset config just in case.
rclconfig->setKeyDir(""); rclconfig->setKeyDir("");
// Highlight search terms: // Create preview text: highlight search terms:
progress.setLabelText(tr("Creating preview text")); progress.setLabelText(tr("Creating preview text"));
list<string> terms; list<string> terms;
rcldb->getQueryTerms(terms); rcldb->getQueryTerms(terms);
list<pair<int, int> > termoffsets; list<pair<int, int> > termoffsets;
QString str; QString richTxt;
ToRichThread rthr(fdoc.text, terms, termoffsets, str); ToRichThread rthr(fdoc.text, terms, termoffsets, richTxt);
rthr.start(); rthr.start();
for (;;i++) { for (;;prog++) {
waiter.start(); waiter.start(); waiter.wait();
waiter.wait();
if (rthr.finished()) if (rthr.finished())
break; break;
progress.setProgress(i , i <= nsteps-1 ? nsteps : i+1); progress.setProgress(prog , prog <= nsteps-1 ? nsteps : prog+1);
qApp->processEvents(); qApp->processEvents();
if (i >= 5) if (progress.wasCanceled()) {
CancelCheck::instance().setCancel();
cancel = true;
}
if (prog >= 5)
sleep(1); 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 // Load into editor
QTextEdit *editor = getCurrentEditor(); QTextEdit *editor = getCurrentEditor();
QStyleSheetItem *item = QStyleSheetItem *item =
new QStyleSheetItem(editor->styleSheet(), "termtag" ); new QStyleSheetItem(editor->styleSheet(), "termtag" );
item->setColor("blue"); item->setColor("blue");
item->setFontWeight(QFont::Bold); item->setFontWeight(QFont::Bold);
prog = 2 * nsteps / 3;
progress.setLabelText(tr("Loading preview text into editor")); progress.setLabelText(tr("Loading preview text into editor"));
qApp->processEvents(); 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; int para = 0, index = 1;
if (!termoffsets.empty()) { if (!termoffsets.empty()) {
index = (termoffsets.begin())->first; 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", LOGDEB(("PREVIEW len %d paragraphs: %d. Cpos: %d %d\n",
editor->length(), editor->paragraphs(), para, index)); editor->length(), editor->paragraphs(), para, index));
return true;
} }

View File

@ -1,5 +1,5 @@
#ifndef lint #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 #endif
/* /*
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -53,7 +53,6 @@ using std::pair;
#include "mimehandler.h" #include "mimehandler.h"
#include "pathut.h" #include "pathut.h"
#include "smallut.h" #include "smallut.h"
#include "plaintorich.h"
#include "advsearch.h" #include "advsearch.h"
#include "rclversion.h" #include "rclversion.h"
#include "sortseq.h" #include "sortseq.h"
@ -435,7 +434,8 @@ void RclMain::startPreview(int docnum)
(void)curPreview->addEditorTab(); (void)curPreview->addEditorTab();
} }
m_history->enterDocument(fn, doc.ipath); 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) void RclMain::startNativeViewer(int docnum)
@ -556,10 +556,40 @@ void RclMain::enablePrevPage(bool yesno)
prevPageAction->setEnabled(yesno); prevPageAction->setEnabled(yesno);
} }
/** Show detailed expansion of a query */
void RclMain::showQueryDetails() 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 desc = tr("Query details") + ": " +
QString::fromUtf8(currentQueryData.description.c_str()); QString::fromUtf8(oq.c_str());
QMessageBox::information(this, tr("Query details"), desc); QMessageBox::information(this, tr("Query details"), desc);
} }