kioslave sort of works

This commit is contained in:
dockes 2008-11-20 13:10:23 +00:00
parent be8f0a2b4e
commit 31f704f564
7 changed files with 396 additions and 166 deletions

View File

@ -1,6 +1,4 @@
(this used to work but last time I tried with konqueror (kde3, 11-2007), it failed with "protocol not authorized" or such. Didn't investigate more).
This is a small experiment with a recoll kio_slave This is a small experiment with a recoll kio_slave
A kio_slave was implemented, supporting the "get" operation. Ie, you type A kio_slave was implemented, supporting the "get" operation. Ie, you type
@ -15,25 +13,38 @@ quite useful in this case.
Implementation notes: Implementation notes:
----------------------- -----------------------
- There are two main ways to do this:
- a-la kio_beagle, using listDir() to list entries pointing to the
different operations or objects (help, status, search result
entries, bookmarks, whatever...). The nice thing is that the
results really look like file object in a directory (probably,
didn't try it actually), no need for look and feel, it's provided by kde
- Or a la strigi: all interactions are through html pages and get()
operations. Looks less like a normal konqueror file-system
listing, and needs more html coding but all in all probably
simpler.
Recoll is currently doing the html thing. As far as I understand, the
way to trigger a listdir is to have a inode/directory default mime
type in the protocol file, and return inode/directory when
appropriate in mimetype() (look at kio_beagle). Some kde daemon needs
to be restarted when doing this (the protocol file is cached
somewhere).
Also would need a page header, configuration polish etc... Not done for
the same reason, this is a proof of concept.
KDE3 notes
- Not using libtool. Probably should. compilation flags in the Makefile - Not using libtool. Probably should. compilation flags in the Makefile
were copy-pasted from a kdebase compilation tree on FreeBSD (kio/man). were copy-pasted from a kdebase compilation tree on FreeBSD (kio/man).
- You MUST install a kio_recoll.la in lib/kde3 along with kio_recoll.so, - You MUST install a kio_recoll.la in lib/kde3 along with kio_recoll.so,
else kdeinit won't be able to load the lib (probably uses the libltdl else kdeinit won't be able to load the lib (probably uses the libltdl
thingy?). The one in this directory was duplicated/adjusted from thingy?). The one in this directory was duplicated/adjusted from
kio_man.la. The contents don't seem too critical, just needs to exist. kio_man.la. The contents don't seem too critical, just needs to exist.
- Currently retrieves all results on one page. Need to add state and
previous/next buttons. As I didn't find this thing to be particularly
useful, I didn't bothered to.
- Also would need a page header, configuration polish etc... Not done for
the same reason, this is a proof of concept.
- If you want to try, compile, then install kio_recoll.la kio_recoll.so - If you want to try, compile, then install kio_recoll.la kio_recoll.so
wherever kde keeps its plugins (ie: lib/kde3), and recoll.protocol in the wherever kde keeps its plugins (ie: lib/kde3), and recoll.protocol in the
services directory (share/services ? look for other .protocol file). services directory (share/services ? look for other .protocol file).
- I saw after doing the build/config mockup that kdevelop can generate a - I saw after doing the build/config mockup that kdevelop can generate a
kio_slave project. This might be the next thing to do. otoh would need to kio_slave project. This might be the next thing to do. otoh would need to
separate the kio from the main source to avoid having to distribute 2megs separate the kio from the main source to avoid having to distribute 2megs

View File

@ -7,40 +7,44 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules )
include(InstallRequiredSystemLibraries) include(InstallRequiredSystemLibraries)
#set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Recoll KIO Slave") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Recoll KIO Slave")
#set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") #set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README")
#set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") #set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING")
#set(CPACK_PACKAGE_VERSION "${VERSION}") set(CPACK_PACKAGE_VERSION "${VERSION}")
#set(CPACK_GENERATOR TGZ) set(CPACK_GENERATOR TGZ)
#set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION}" CACHE INTERNAL " set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${VERSION}" CACHE INTERNAL "tarball basename")
#tarball basename") SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${CPACK_PACKAGE_VERSION}")
#SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${CPACK_PACKAGE_VERSION}") include(CPack)
#include(CPack)
# search packages used by KDE # search packages used by KDE
find_package(KDE4 REQUIRED) find_package(KDE4 REQUIRED)
include (UsePkgConfig) include(UsePkgConfig)
include (KDE4Defaults) include(KDE4Defaults)
include (MacroLibrary) include(MacroLibrary)
include(MacroOptionalAddSubdirectory) include(MacroOptionalAddSubdirectory)
add_definitions (${QT_DEFINITIONS} ${KDE4_DEFINITIONS}) add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
add_definitions(-DKDE_DEFAULT_DEBUG_AREA=7130)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
set(depth ../../../) set(depth ../../../)
set(rcltop ${CMAKE_CURRENT_SOURCE_DIR}/${depth})
include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES} include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${KDE4_INCLUDES}
${depth}/utils ${depth}/rcldb ${depth}/query ${depth}/unac ${rcltop}/utils ${rcltop}/rcldb ${rcltop}/query ${rcltop}/unac
${depth}/common ${depth}/internfile ${depth}/index ${depth}/bincimapmime ${rcltop}/common ${rcltop}/internfile ${rcltop}/index ${rcltop}/bincimapmime
) )
set(kio_recoll_SRCS set(kio_recoll_SRCS kio_recoll.cpp)
kio_recoll.cpp
)
kde4_add_plugin(kio_recoll ${kio_recoll_SRCS}) kde4_add_plugin(kio_recoll ${kio_recoll_SRCS})
link_directories(/home/dockes/projets/fulltext/recoll/src/lib)
target_link_libraries(kio_recoll rcl xapian ${KDE4_KIO_LIBS})
target_link_libraries(kio_recoll
${rcltop}/lib/librcl.a
${rcltop}/bincimapmime/libmime.a
xapian ${KDE4_KIO_LIBS})
install(TARGETS kio_recoll DESTINATION ${PLUGIN_INSTALL_DIR}) install(TARGETS kio_recoll DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES recoll.protocol DESTINATION ${SERVICES_INSTALL_DIR} ) install(FILES recoll.protocol DESTINATION ${SERVICES_INSTALL_DIR})
install(FILES data/welcome.html
DESTINATION ${DATA_INSTALL_DIR}/kio_recoll)

View File

@ -0,0 +1,20 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Recoll Search</title>
</head>
<body>
<h3>Recoll search</h3>
<p><form method="get" action="recoll://search/query">
Query type:<br>
<input type="radio" name="qtp" value="l" checked>Query language<br>
<input type="radio" name="qtp" value="a">All terms<br>
<input type="radio" name="qtp" value="o">Any term<br>
Enter search string: <input type="text" name="q" size="40" value="%Q">
<input type="submit" value="Search"></p>
</body>
</html>

View File

@ -1,5 +1,5 @@
#ifndef lint #ifndef lint
static char rcsid[] = "@(#$Id: kio_recoll.cpp,v 1.14 2008-11-19 12:28:59 dockes Exp $ (C) 2005 J.F.Dockes"; static char rcsid[] = "@(#$Id: kio_recoll.cpp,v 1.15 2008-11-20 13:10:23 dockes Exp $ (C) 2005 J.F.Dockes";
#endif #endif
#include <stdio.h> #include <stdio.h>
@ -26,6 +26,7 @@ using namespace std;
//#include <kinstance.h> //#include <kinstance.h>
#include <kcomponentdata.h> #include <kcomponentdata.h>
#include <kstandarddirs.h>
#include "rclconfig.h" #include "rclconfig.h"
#include "rcldb.h" #include "rcldb.h"
@ -36,9 +37,65 @@ using namespace std;
#include "wasastringtoquery.h" #include "wasastringtoquery.h"
#include "wasatorcl.h" #include "wasatorcl.h"
#include "kio_recoll.h" #include "kio_recoll.h"
#include "docseqdb.h"
#include "readfile.h"
#include "smallut.h"
using namespace KIO; using namespace KIO;
bool RecollKioPager::append(const string& data)
{
if (!m_parent) return false;
m_parent->data(QByteArray(data.c_str()));
return true;
}
string RecollKioPager::detailsLink()
{
string chunk = "<a href=\"recoll://command/QueryDetails\">";
chunk += tr("(show query)") + "</a>";
return chunk;
}
const static string parformat =
"<a href=\"%U\"><img src=\"%I\" align=\"left\"></a>"
"%R %S "
"<a href=\"%U\">Open</a>&nbsp;&nbsp;<b>%T</b><br>"
"%M&nbsp;%D&nbsp;&nbsp;&nbsp;<i>%U</i><br>"
"%A %K";
const string &RecollKioPager::parFormat()
{
return parformat;
}
string RecollKioPager::pageTop()
{
return "<p align=\"center\"><a href=\"recoll://welcome\">New Search</a></p>";
}
string RecollKioPager::nextUrl()
{
int pagenum = pageNumber();
if (pagenum < 0)
pagenum = 0;
else
pagenum++;
char buf[100];
sprintf(buf, "recoll://command/Page%d", pagenum);
return buf;
}
string RecollKioPager::prevUrl()
{
int pagenum = pageNumber();
if (pagenum <= 0)
pagenum = 0;
else
pagenum--;
char buf[100];
sprintf(buf, "recoll://command/Page%d", pagenum);
return buf;
}
static RclConfig *rclconfig; static RclConfig *rclconfig;
RclConfig *RclConfig::getMainConfig() RclConfig *RclConfig::getMainConfig()
{ {
@ -49,6 +106,7 @@ RecollProtocol::RecollProtocol(const QByteArray &pool, const QByteArray &app)
: SlaveBase("recoll", pool, app), m_initok(false), : SlaveBase("recoll", pool, app), m_initok(false),
m_rclconfig(0), m_rcldb(0) m_rclconfig(0), m_rcldb(0)
{ {
m_pager.setParent(this);
string reason; string reason;
rclconfig = m_rclconfig = recollinit(0, 0, m_reason); rclconfig = m_rclconfig = recollinit(0, 0, m_reason);
if (!m_rclconfig || !m_rclconfig->ok()) { if (!m_rclconfig || !m_rclconfig->ok()) {
@ -99,9 +157,86 @@ bool RecollProtocol::maybeOpenDb(string &reason)
return true; return true;
} }
static string welcomedata;
void RecollProtocol::welcomePage()
{
kDebug() << endl;
if (welcomedata.empty()) {
QString location =
KStandardDirs::locate("data", "kio_recoll/welcome.html");
string reason;
if (location.isEmpty() ||
!file_to_string((const char *)location.toUtf8(),
welcomedata, &reason)) {
welcomedata = "<html><head><title>Recoll Error</title></head>"
"<body><p>Could not locate Recoll welcome.html file: ";
welcomedata += reason;
welcomedata += "</p></body></html>";
}
}
string tmp;
map<char, string> subs;
subs['Q'] = "";
pcSubst(welcomedata, tmp, subs);
data(tmp.c_str());
kDebug() << "WelcomePage done" << endl;
}
void RecollProtocol::doSearch(const QString& q, char opt)
{
string qs = (const char *)q.toUtf8();
Rcl::SearchData *sd = 0;
if (opt != 'l') {
Rcl::SearchDataClause *clp = 0;
if (opt == 'f') {
clp = new Rcl::SearchDataClauseFilename(qs);
} else {
// If there is no white space inside the query, then the user
// certainly means it as a phrase.
bool isreallyaphrase = false;
if (qs.find_first_of(" \t") == string::npos)
isreallyaphrase = true;
clp = isreallyaphrase ?
new Rcl::SearchDataClauseDist(Rcl::SCLT_PHRASE, qs, 0) :
new Rcl::SearchDataClauseSimple(opt == 'o' ?
Rcl::SCLT_OR : Rcl::SCLT_AND,
qs);
}
sd = new Rcl::SearchData(Rcl::SCLT_OR);
if (sd && clp)
sd->addClause(clp);
} else {
kDebug() << "Parsing query";
sd = wasaStringToRcl(qs, m_reason);
}
if (!sd) {
m_reason = "Internal Error: cant allocate new query";
outputError(m_reason.c_str());
finished();
return;
}
RefCntr<Rcl::SearchData> sdata(sd);
sdata->setStemlang("english");
RefCntr<Rcl::Query>query(new Rcl::Query(m_rcldb));
if (!query->setQuery(sdata)) {
m_reason = "Internal Error: setQuery failed";
outputError(m_reason.c_str());
finished();
return;
}
DocSequenceDb *src =
new DocSequenceDb(RefCntr<Rcl::Query>(query), "Query results",
sdata);
m_pager.setDocSource(RefCntr<DocSequence>(src));
m_pager.resultPageNext();
}
void RecollProtocol::get(const KUrl & url) void RecollProtocol::get(const KUrl & url)
{ {
kDebug() << "RecollProtocol::get:" << url << endl; kDebug() << url << endl;
mimeType("text/html"); mimeType("text/html");
@ -111,98 +246,75 @@ void RecollProtocol::get(const KUrl & url)
return; return;
} }
string iconsdir; QString host = url.host();
m_rclconfig->getConfParam("iconsdir", iconsdir);
if (iconsdir.empty()) {
iconsdir = path_cat("/usr/local/share/recoll", "images");
} else {
iconsdir = path_tildexpand(iconsdir);
}
QString path = url.path(); QString path = url.path();
kDebug() << "RecollProtocol::get:path:" << path << endl; kDebug() << "host:" << host << " path:" << path;
QByteArray u8 = path.toUtf8(); if (host.isEmpty() || !host.compare("welcome")) {
kDebug() << "Host is empty";
if (path.isEmpty() || !path.compare("/")) {
kDebug() << "Path is empty or strange";
// Display welcome page
welcomePage();
finished();
return;
}
// Ie: "recoll: some search string"
doSearch(path);
} else if (!host.compare("search")) {
if (path.compare("/query")) {
finished(); return;
}
// Decode the forms' arguments
QString query = url.queryItem("q");
if (query.isEmpty()) {
finished(); return;
}
QString opt = url.queryItem("qtp");
if (opt.isEmpty()) {
opt = "l";
}
doSearch(query, opt.toUtf8().at(0));
} else if (!host.compare("command")) {
if (path.isEmpty()) {
finished();return;
} else if (path.indexOf("/Page") == 0) {
int newpage = 0;
sscanf(path.toUtf8(), "/Page%d", &newpage);
if (newpage > m_pager.pageNumber()) {
int npages = newpage - m_pager.pageNumber();
for (int i = 0; i < npages; i++)
m_pager.resultPageNext();
} else if (newpage < m_pager.pageNumber()) {
int npages = m_pager.pageNumber() - newpage;
for (int i = 0; i < npages; i++)
m_pager.resultPageBack();
}
} else if (path.indexOf("/QueryDetails") == 0) {
QByteArray array;
QTextStream os(&array, QIODevice::WriteOnly);
RefCntr<Rcl::SearchData> sdata = wasaStringToRcl((const char*)u8, m_reason); os << "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
sdata->setStemlang("english"); os << "<title>" << "Recoll query details" << "</title>\n" << endl;
os << "</head>" << endl;
RefCntr<Rcl::Query>query(new Rcl::Query(m_rcldb)); os << "<body><h3>Query details:</h3>" << endl;
if (!query->setQuery(sdata)) { os << "<p>" << m_pager.queryDescription().c_str() <<"</p>"<< endl;
m_reason = "Internal Error: setQuery failed"; os << "<p><a href=\"recoll://command/Page" <<
outputError(m_reason.c_str()); m_pager.pageNumber() << "\">Return to results</a>" << endl;
finished(); os << "</body></html>" << endl;
return; data(array);
finished();
return;
} else {
// Unknown //command/???
finished(); return;
}
} else {
// Unknown 'host' //??? value
finished(); return;
} }
string explain = sdata->getDescription(); m_pager.displayPage();
QByteArray output;
QTextStream os(&output, QIODevice::ReadWrite);
os << "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">" << endl;
os << "<html><head>" << endl;
os << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" << endl;
os << "<meta http-equiv=\"Pragma\" content=\"no-cache\">" << endl;
os << "<title>Recoll: query results</title>" << endl;
os << "</head><body>" << endl;
os << "<p><b>Actual query performed: </b>" << endl;
os << explain.c_str() << "</p>";
Rcl::Doc doc;
int cnt = query->getResCnt();
for (int i = 0; i < cnt; i++) {
string sh;
doc.erase();
if (!query->getDoc(i, doc)) {
// This may very well happen for history if the doc has
// been removed since. So don't treat it as fatal.
doc.meta[Rcl::Doc::keykw] = string("Unavailable document");
}
string iconname = m_rclconfig->getMimeIconName(doc.mimetype);
if (iconname.empty())
iconname = "document";
string imgfile = iconsdir + "/" + iconname + ".png";
string result;
if (!sh.empty())
result += string("<p><b>") + sh + "</p>\n<p>";
else
result = "<p>";
char perbuf[10];
sprintf(perbuf, "%3d%%", doc.pc);
if (doc.meta[Rcl::Doc::keytt].empty())
doc.meta[Rcl::Doc::keytt] = path_getsimple(doc.url);
char datebuf[100];
datebuf[0] = 0;
if (!doc.dmtime.empty() || !doc.fmtime.empty()) {
time_t mtime = doc.dmtime.empty() ?
atol(doc.fmtime.c_str()) : atol(doc.dmtime.c_str());
struct tm *tm = localtime(&mtime);
strftime(datebuf, 99,
"<i>Modified:</i>&nbsp;%Y-%m-%d&nbsp;%H:%M:%S", tm);
}
result += "<a href=\"" + doc.url + "\">" +
"<img src=\"file://" + imgfile + "\" align=\"left\">" + "</a>";
string abst = escapeHtml(doc.meta[Rcl::Doc::keyabs]);
result += string(perbuf) + " <b>" + doc.meta[Rcl::Doc::keytt] + "</b><br>" +
doc.mimetype + "&nbsp;" +
(datebuf[0] ? string(datebuf) + "<br>" : string("<br>")) +
(!abst.empty() ? abst + "<br>" : string("")) +
(!doc.meta[Rcl::Doc::keytt].empty() ? doc.meta[Rcl::Doc::keykw] +
"<br>" : string("")) +
"<a href=\"" + doc.url + "\">" + doc.url + "</a><br></p>\n";
QString str = QString::fromUtf8(result.c_str(), result.length());
os << str;
}
os << "</body></html>";
data(output);
kDebug() << "call finished" << endl; kDebug() << "call finished" << endl;
finished(); finished();
} }
@ -221,6 +333,8 @@ void RecollProtocol::outputError(const QString& errmsg)
data(array); data(array);
} }
// Note: KDE_EXPORT is actually needed on Unix when building with // Note: KDE_EXPORT is actually needed on Unix when building with
// cmake. Says something like __attribute__(visibility(defautl)) // cmake. Says something like __attribute__(visibility(defautl))
// (cmake apparently sets all symbols to not exported) // (cmake apparently sets all symbols to not exported)
@ -228,9 +342,6 @@ extern "C" {KDE_EXPORT int kdemain(int argc, char **argv);}
int kdemain(int argc, char **argv) int kdemain(int argc, char **argv)
{ {
FILE*mf = fopen("/tmp/toto","w");
fprintf(mf, "KIORECOLL\n");
fclose(mf);
#ifdef KDE_VERSION_3 #ifdef KDE_VERSION_3
KInstance instance("kio_recoll"); KInstance instance("kio_recoll");
#else #else

View File

@ -1,5 +1,5 @@
#ifndef _RECOLL_H #ifndef _RECOLL_H
/* @(#$Id: kio_recoll.h,v 1.5 2008-11-17 14:51:38 dockes Exp $ (C) 2005 J.F.Dockes */ /* @(#$Id: kio_recoll.h,v 1.6 2008-11-20 13:10:23 dockes Exp $ (C) 2005 J.F.Dockes */
#define _RECOLL_H #define _RECOLL_H
#include <string> #include <string>
@ -17,6 +17,25 @@ using std::string;
#include <kio/global.h> #include <kio/global.h>
#include <kio/slavebase.h> #include <kio/slavebase.h>
#include "reslistpager.h"
class RecollProtocol;
class RecollKioPager : public ResListPager {
public:
RecollKioPager() : m_parent(0) {}
void setParent(RecollProtocol *proto) {m_parent = proto;}
virtual bool append(const string& data);
virtual string detailsLink();
virtual const string &parFormat();
virtual string nextUrl();
virtual string prevUrl();
virtual string pageTop();
private:
RecollProtocol *m_parent;
};
class RecollProtocol : public KIO::SlaveBase { class RecollProtocol : public KIO::SlaveBase {
public: public:
RecollProtocol(const QByteArray &pool, const QByteArray &app ); RecollProtocol(const QByteArray &pool, const QByteArray &app );
@ -25,13 +44,18 @@ class RecollProtocol : public KIO::SlaveBase {
virtual void get(const KUrl & url ); virtual void get(const KUrl & url );
private: private:
bool m_initok;
RclConfig *m_rclconfig;
Rcl::Db *m_rcldb;
std::string m_dbdir;
std::string m_reason;
bool maybeOpenDb(string &reason); bool maybeOpenDb(string &reason);
void outputError(const QString& errmsg); void outputError(const QString& errmsg);
void doSearch(const QString& q, char opt = 'l');
void welcomePage();
bool m_initok;
RclConfig *m_rclconfig;
Rcl::Db *m_rcldb;
std::string m_dbdir;
std::string m_reason;
RecollKioPager m_pager;
}; };
extern "C" {int kdemain(int, char**);} extern "C" {int kdemain(int, char**);}

View File

@ -1,7 +1,11 @@
#ifndef lint #ifndef lint
static char rcsid[] = "@(#$Id: reslistpager.cpp,v 1.1 2008-11-19 12:19:40 dockes Exp $ (C) 2007 J.F.Dockes"; static char rcsid[] = "@(#$Id: reslistpager.cpp,v 1.2 2008-11-20 13:10:23 dockes Exp $ (C) 2007 J.F.Dockes";
#endif #endif
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "reslistpager.h" #include "reslistpager.h"
#include "debuglog.h" #include "debuglog.h"
#include "rclconfig.h" #include "rclconfig.h"
@ -63,31 +67,9 @@ void ResListPager::resultPageNext()
m_respage = npage; m_respage = npage;
} }
bool ResListPager::append(const string& data)
{
fprintf(stderr, "%s", data.c_str());
return true;
}
string ResListPager::tr(const string& in)
{
return in;
}
void ResListPager::displayPage() void ResListPager::displayPage()
{ {
string chunk; string chunk;
if (pageEmpty()) {
chunk = "<html><head></head><body>"
"<p><font size=+1><b>";
chunk += m_docSource->title();
chunk += "</b></font>";
chunk += "<a href=\"H-1\">";
chunk += tr("Show query details");
chunk += "</a><br>";
append(chunk);
return;
}
// Display list header // Display list header
// We could use a <title> but the textedit doesnt display // We could use a <title> but the textedit doesnt display
@ -97,8 +79,12 @@ void ResListPager::displayPage()
// gets confused. Hence the use of the 'chunk' text // gets confused. Hence the use of the 'chunk' text
// accumulator // accumulator
// Also note that there can be results beyond the estimated resCnt. // Also note that there can be results beyond the estimated resCnt.
chunk = "<qt><head></head><body><p>" chunk = "<html><head>"
"<font size=+1><b>"; "<meta http-equiv=\"content-type\""
"content=\"text/html; charset=utf-8\">"
"</head><body>";
chunk += pageTop();
chunk += "<p><font size=+1><b>";
chunk += m_docSource->title(); chunk += m_docSource->title();
chunk += "</b></font>" chunk += "</b></font>"
"&nbsp;&nbsp;&nbsp;"; "&nbsp;&nbsp;&nbsp;";
@ -121,9 +107,21 @@ void ResListPager::displayPage()
chunk += buf; chunk += buf;
} }
} }
chunk += "<a href=\"H-1\">"; chunk += detailsLink();
chunk += tr("(show query)"); if (hasPrev() || hasNext()) {
chunk += "</a></p>"; chunk += "&nbsp;&nbsp;";
if (hasPrev()) {
chunk += "<a href=\"" + prevUrl() + "\"><b>";
chunk += tr("Previous");
chunk += "</b></a>&nbsp;&nbsp;&nbsp;";
}
if (hasNext()) {
chunk += "<a href=\""+ nextUrl() + "\"><b>";
chunk += tr("Next");
chunk += "</b></a>";
}
}
chunk += "</p>";
append(chunk); append(chunk);
if (pageEmpty()) if (pageEmpty())
@ -154,6 +152,7 @@ void ResListPager::displayPage()
// Determine icon to display if any // Determine icon to display if any
string iconpath; string iconpath;
RclConfig::getMainConfig()->getMimeIconName(doc.mimetype, &iconpath); RclConfig::getMainConfig()->getMimeIconName(doc.mimetype, &iconpath);
iconpath = string("file://") + iconpath;
// Printable url: either utf-8 if transcoding succeeds, or url-encoded // Printable url: either utf-8 if transcoding succeeds, or url-encoded
string url; string url;
@ -249,7 +248,7 @@ void ResListPager::displayPage()
subs['U'] = url; subs['U'] = url;
string formatted; string formatted;
pcSubst(m_parformat, formatted, subs); pcSubst(parFormat(), formatted, subs);
chunk += formatted; chunk += formatted;
chunk += "</p>\n"; chunk += "</p>\n";
@ -262,12 +261,12 @@ void ResListPager::displayPage()
chunk = "<p align=\"center\">"; chunk = "<p align=\"center\">";
if (hasPrev() || hasNext()) { if (hasPrev() || hasNext()) {
if (hasPrev()) { if (hasPrev()) {
chunk += "<a href=\"p-1\"><b>"; chunk += "<a href=\"" + prevUrl() + "\"><b>";
chunk += tr("Previous"); chunk += tr("Previous");
chunk += "</b></a>&nbsp;&nbsp;&nbsp;"; chunk += "</b></a>&nbsp;&nbsp;&nbsp;";
} }
if (hasNext()) { if (hasNext()) {
chunk += "<a href=\"n-1\"><b>"; chunk += "<a href=\""+ nextUrl() + "\"><b>";
chunk += tr("Next"); chunk += tr("Next");
chunk += "</b></a>"; chunk += "</b></a>";
} }
@ -276,3 +275,41 @@ void ResListPager::displayPage()
chunk += "</body></html>\n"; chunk += "</body></html>\n";
append(chunk); append(chunk);
} }
string ResListPager::nextUrl()
{
return "n-1";
}
string ResListPager::prevUrl()
{
return "p-1";
}
// Default implementations for things that should be re-implemented by our user.
bool ResListPager::append(const string& data)
{
fprintf(stderr, "%s", data.c_str());
return true;
}
string ResListPager::tr(const string& in)
{
return in;
}
string ResListPager::detailsLink()
{
string chunk = "<a href=\"H-1\">";
chunk += tr("(show query)") + "</a>";
return chunk;
}
const string &ResListPager::parFormat()
{
const static string format("<img src=\"%I\" align=\"left\">"
"%R %S %L &nbsp;&nbsp;<b>%T</b><br>"
"%M&nbsp;%D&nbsp;&nbsp;&nbsp;<i>%U</i><br>"
"%A %K");
return format;
}

View File

@ -1,6 +1,6 @@
#ifndef _reslistpager_h_included_ #ifndef _reslistpager_h_included_
#define _reslistpager_h_included_ #define _reslistpager_h_included_
/* @(#$Id: reslistpager.h,v 1.1 2008-11-19 12:19:40 dockes Exp $ (C) 2007 J.F.Dockes */ /* @(#$Id: reslistpager.h,v 1.2 2008-11-20 13:10:23 dockes Exp $ (C) 2007 J.F.Dockes */
#include <vector> #include <vector>
using std::vector; using std::vector;
@ -13,12 +13,25 @@ using std::vector;
*/ */
class ResListPager { class ResListPager {
public: public:
ResListPager(RefCntr<DocSequence> src, int pagesize, ResListPager() : m_pagesize(10), m_hasNext(false) {}
const string& parformat) void setDocSource(RefCntr<DocSequence> src)
: m_docSource(src), m_pagesize(pagesize), m_parformat(parformat), {
m_hasNext(false) m_winfirst = -1;
m_docSource = src;
m_hasNext = false;
m_respage.clear();
}
ResListPager(RefCntr<DocSequence> src, int pagesize)
: m_winfirst(-1), m_docSource(src), m_pagesize(pagesize),
m_hasNext(false)
{} {}
virtual ~ResListPager() {} virtual ~ResListPager() {}
int pageNumber()
{
if (m_winfirst < 0 || m_pagesize <= 0)
return -1;
return m_winfirst / m_pagesize;
}
void pageNext(); void pageNext();
bool hasNext() {return m_hasNext;} bool hasNext() {return m_hasNext;}
bool hasPrev() {return m_winfirst > 0;} bool hasPrev() {return m_winfirst > 0;}
@ -35,14 +48,24 @@ public:
void resultPageNext(); void resultPageNext();
void displayPage(); void displayPage();
bool pageEmpty() {return m_respage.size() == 0;} bool pageEmpty() {return m_respage.size() == 0;}
string queryDescription() {return m_docSource.isNull() ? "" :
m_docSource->getDescription();}
// Things that need to be reimplemented in the subclass:
virtual bool append(const string& data); virtual bool append(const string& data);
virtual string tr(const string& in); virtual string tr(const string& in);
virtual string detailsLink();
virtual const string &parFormat();
virtual string nextUrl();
virtual string prevUrl();
virtual string pageTop() {return string();}
private: private:
// First docnum (from docseq) in current page // First docnum (from docseq) in current page
int m_winfirst; int m_winfirst;
RefCntr<DocSequence> m_docSource; RefCntr<DocSequence> m_docSource;
int m_pagesize; int m_pagesize;
string m_parformat;
bool m_hasNext; bool m_hasNext;
vector<ResListEntry> m_respage; vector<ResListEntry> m_respage;