diff --git a/src/qtgui/rclm_idx.cpp b/src/qtgui/rclm_idx.cpp new file mode 100644 index 00000000..18d28a71 --- /dev/null +++ b/src/qtgui/rclm_idx.cpp @@ -0,0 +1,300 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include + +#include "execmd.h" +#include "debuglog.h" +#include "transcode.h" +#include "indexer.h" +#include "rclmain_w.h" + +using namespace std; + +void RclMain::idxStatus() +{ + ConfSimple cs(theconfig->getIdxStatusFile().c_str(), 1); + QString msg = tr("Indexing in progress: "); + DbIxStatus status; + string val; + cs.get("phase", val); + status.phase = DbIxStatus::Phase(atoi(val.c_str())); + cs.get("fn", status.fn); + cs.get("docsdone", val); + status.docsdone = atoi(val.c_str()); + cs.get("filesdone", val); + status.filesdone = atoi(val.c_str()); + cs.get("dbtotdocs", val); + status.dbtotdocs = atoi(val.c_str()); + + QString phs; + switch (status.phase) { + case DbIxStatus::DBIXS_NONE:phs=tr("None");break; + case DbIxStatus::DBIXS_FILES: phs=tr("Updating");break; + case DbIxStatus::DBIXS_PURGE: phs=tr("Purge");break; + case DbIxStatus::DBIXS_STEMDB: phs=tr("Stemdb");break; + case DbIxStatus::DBIXS_CLOSING:phs=tr("Closing");break; + case DbIxStatus::DBIXS_DONE:phs=tr("Done");break; + case DbIxStatus::DBIXS_MONITOR:phs=tr("Monitor");break; + default: phs=tr("Unknown");break; + } + msg += phs + " "; + if (status.phase == DbIxStatus::DBIXS_FILES) { + char cnts[100]; + if (status.dbtotdocs > 0) + sprintf(cnts,"(%d/%d/%d) ", status.docsdone, status.filesdone, + status.dbtotdocs); + else + sprintf(cnts, "(%d/%d) ", status.docsdone, status.filesdone); + msg += QString::fromUtf8(cnts) + " "; + } + string mf;int ecnt = 0; + string fcharset = theconfig->getDefCharset(true); + if (!transcode(status.fn, mf, fcharset, "UTF-8", &ecnt) || ecnt) { + mf = url_encode(status.fn, 0); + } + msg += QString::fromUtf8(mf.c_str()); + statusBar()->showMessage(msg, 4000); +} + +// This is called by a periodic timer to check the status of +// indexing, a possible need to exit, and cleanup exited viewers +void RclMain::periodic100() +{ + LOGDEB2(("Periodic100\n")); + if (m_idxproc) { + // An indexing process was launched. If its' done, see status. + int status; + bool exited = m_idxproc->maybereap(&status); + if (exited) { + deleteZ(m_idxproc); + if (status) { + if (m_idxkilled) { + QMessageBox::warning(0, "Recoll", + tr("Indexing interrupted")); + m_idxkilled = false; + } else { + QMessageBox::warning(0, "Recoll", + tr("Indexing failed")); + } + } else { + // On the first run, show missing helpers. We only do this once + if (m_firstIndexing) + showMissingHelpers(); + } + string reason; + maybeOpenDb(reason, 1); + } else { + // update/show status even if the status file did not + // change (else the status line goes blank during + // lengthy operations). + idxStatus(); + } + } + // Update the "start/stop indexing" menu entry, can't be done from + // the "start/stop indexing" slot itself + IndexerState prevstate = m_indexerState; + if (m_idxproc) { + m_indexerState = IXST_RUNNINGMINE; + fileToggleIndexingAction->setText(tr("Stop &Indexing")); + fileToggleIndexingAction->setEnabled(true); + fileRetryFailedAction->setEnabled(false); + fileRebuildIndexAction->setEnabled(false); + periodictimer->setInterval(200); + } else { + Pidfile pidfile(theconfig->getPidfile()); + if (pidfile.open() == 0) { + m_indexerState = IXST_NOTRUNNING; + fileToggleIndexingAction->setText(tr("Update &Index")); + fileRetryFailedAction->setEnabled(true); + fileToggleIndexingAction->setEnabled(true); + fileRebuildIndexAction->setEnabled(true); + periodictimer->setInterval(1000); + } else { + // Real time or externally started batch indexer running + m_indexerState = IXST_RUNNINGNOTMINE; + fileToggleIndexingAction->setText(tr("Stop &Indexing")); + fileToggleIndexingAction->setEnabled(true); + fileRetryFailedAction->setEnabled(false); + fileRebuildIndexAction->setEnabled(false); + periodictimer->setInterval(200); + } + } + + if ((prevstate == IXST_RUNNINGMINE || prevstate == IXST_RUNNINGNOTMINE) + && m_indexerState == IXST_NOTRUNNING) { + showTrayMessage("Indexing done"); + } + + // Possibly cleanup the dead viewers + for (vector::iterator it = m_viewers.begin(); + it != m_viewers.end(); it++) { + int status; + if ((*it)->maybereap(&status)) { + deleteZ(*it); + } + } + vector v; + for (vector::iterator it = m_viewers.begin(); + it != m_viewers.end(); it++) { + if (*it) + v.push_back(*it); + } + m_viewers = v; + + if (recollNeedsExit) + fileExit(); +} + +// This gets called when the "update index" action is activated. It executes +// the requested action, and disables the menu entry. This will be +// re-enabled by the indexing status check +void RclMain::toggleIndexing() +{ + switch (m_indexerState) { + case IXST_RUNNINGMINE: + if (m_idxproc) { + // Indexing was in progress, request stop. Let the periodic + // routine check for the results. + int pid = m_idxproc->getChildPid(); + if (pid > 0) { + kill(pid, SIGTERM); + m_idxkilled = true; + } + } + break; + case IXST_RUNNINGNOTMINE: + { + int rep = + QMessageBox::information(0, tr("Warning"), + tr("The current indexing process " + "was not started from this " + "interface. Click Ok to kill it " + "anyway, or Cancel to leave it alone"), + QMessageBox::Ok, + QMessageBox::Cancel, + QMessageBox::NoButton); + if (rep == QMessageBox::Ok) { + Pidfile pidfile(theconfig->getPidfile()); + pid_t pid = pidfile.open(); + if (pid > 0) + kill(pid, SIGTERM); + } + } + break; + case IXST_NOTRUNNING: + { + // Could also mean that no helpers are missing, but then we + // won't try to show a message anyway (which is what + // firstIndexing is used for) + string mhd; + m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); + + vector args; + + string badpaths; + args.push_back("recollindex"); + args.push_back("-E"); + ExecCmd::backtick(args, badpaths); + if (!badpaths.empty()) { + int rep = + QMessageBox::warning(0, tr("Bad paths"), + tr("Bad paths in configuration file:\n") + + QString::fromLocal8Bit(badpaths.c_str()), + QMessageBox::Ok, + QMessageBox::Cancel, + QMessageBox::NoButton); + if (rep == QMessageBox::Cancel) + return; + } + + args.clear(); + args.push_back("-c"); + args.push_back(theconfig->getConfDir()); + if (fileRetryFailedAction->isChecked()) + args.push_back("-k"); + m_idxproc = new ExecCmd; + m_idxproc->startExec("recollindex", args, false, false); + } + break; + case IXST_UNKNOWN: + return; + } +} + +void RclMain::rebuildIndex() +{ + switch (m_indexerState) { + case IXST_UNKNOWN: + case IXST_RUNNINGMINE: + case IXST_RUNNINGNOTMINE: + return; //?? Should not have been called + case IXST_NOTRUNNING: + { + int rep = + QMessageBox::warning(0, tr("Erasing index"), + tr("Reset the index and start " + "from scratch ?"), + QMessageBox::Ok, + QMessageBox::Cancel, + QMessageBox::NoButton); + if (rep == QMessageBox::Ok) { + // Could also mean that no helpers are missing, but then we + // won't try to show a message anyway (which is what + // firstIndexing is used for) + string mhd; + m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); + vector args; + args.push_back("-c"); + args.push_back(theconfig->getConfDir()); + args.push_back("-z"); + m_idxproc = new ExecCmd; + m_idxproc->startExec("recollindex", args, false, false); + } + } + break; + } +} + +void RclMain::updateIdxForDocs(vector& docs) +{ + if (m_idxproc) { + QMessageBox::warning(0, tr("Warning"), + tr("Can't update index: indexer running"), + QMessageBox::Ok, + QMessageBox::NoButton); + return; + } + + vector paths; + if (ConfIndexer::docsToPaths(docs, paths)) { + vector args; + args.push_back("-c"); + args.push_back(theconfig->getConfDir()); + args.push_back("-e"); + args.push_back("-i"); + args.insert(args.end(), paths.begin(), paths.end()); + m_idxproc = new ExecCmd; + m_idxproc->startExec("recollindex", args, false, false); + fileToggleIndexingAction->setText(tr("Stop &Indexing")); + } + fileToggleIndexingAction->setEnabled(false); + fileRetryFailedAction->setEnabled(false); +} + diff --git a/src/qtgui/rclm_preview.cpp b/src/qtgui/rclm_preview.cpp new file mode 100644 index 00000000..34ff56ad --- /dev/null +++ b/src/qtgui/rclm_preview.cpp @@ -0,0 +1,259 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include + +#include "debuglog.h" +#include "internfile.h" +#include "rclzg.h" +#include "rclmain_w.h" + +static const QKeySequence quitKeySeq("Ctrl+q"); + +// If a preview (toplevel) window gets closed by the user, we need to +// clean up because there is no way to reopen it. And check the case +// where the current one is closed +void RclMain::previewClosed(Preview *w) +{ + LOGDEB(("RclMain::previewClosed(%p)\n", w)); + if (w == curPreview) { + LOGDEB(("Active preview closed\n")); + curPreview = 0; + } else { + LOGDEB(("Old preview closed\n")); + } + delete w; +} + +// Document up to date check. The main problem we try to solve is +// displaying the wrong message from a compacted mail folder. +// +// Also we should re-run the query after updating the index because +// the ipaths may be wrong in the current result list. For now, the +// user does this by clicking search again once the indexing is done +// +// We only do this for the main index, else jump and prey (cant update +// anyway, even the makesig() call might not make sense for our base +// config) +bool RclMain::containerUpToDate(Rcl::Doc& doc) +{ + // If ipath is empty, we decide we don't care. Also, we need an index, + if (doc.ipath.empty() || rcldb == 0) + return true; + + string udi; + doc.getmeta(Rcl::Doc::keyudi, &udi); + if (udi.empty()) { + // Whatever... + return true; + } + + string sig; + if (!FileInterner::makesig(theconfig, doc, sig)) { + QMessageBox::warning(0, "Recoll", tr("Can't access file: ") + + QString::fromLocal8Bit(doc.url.c_str())); + // Let's try the preview anyway... + return true; + } + + if (!rcldb->needUpdate(udi, sig)) { + // Alles ist in ordnung + return true; + } + + // We can only run indexing on the main index (dbidx 0) + bool ismainidx = rcldb->whatDbIdx(doc) == 0; + // Indexer already running? + bool ixnotact = (m_indexerState == IXST_NOTRUNNING); + + QString msg = tr("Index not up to date for this file. " + "Refusing to risk showing the wrong entry. "); + if (ixnotact && ismainidx) { + msg += tr("Click Ok to update the " + "index for this file, then you will need to " + "re-run the query when indexing is done. "); + } else if (ismainidx) { + msg += tr("The indexer is running so things should " + "improve when it's done. "); + } else if (ixnotact) { + // Not main index + msg += tr("The document belongs to an external index" + "which I can't update. "); + } + msg += tr("Click Cancel to return to the list. " + "Click Ignore to show the preview anyway. "); + + QMessageBox::StandardButtons bts = + QMessageBox::Ignore | QMessageBox::Cancel; + + if (ixnotact &&ismainidx) + bts |= QMessageBox::Ok; + + int rep = + QMessageBox::warning(0, tr("Warning"), msg, bts, + (ixnotact && ismainidx) ? + QMessageBox::Cancel : QMessageBox::NoButton); + + if (m_indexerState == IXST_NOTRUNNING && rep == QMessageBox::Ok) { + LOGDEB(("Requesting index update for %s\n", doc.url.c_str())); + vector docs(1, doc); + updateIdxForDocs(docs); + } + if (rep != QMessageBox::Ignore) + return false; + return true; +} + +/** + * Open a preview window for a given document, or load it into new tab of + * existing window. + * + * @param docnum db query index + * @param mod keyboards modifiers like ControlButton, ShiftButton + */ +void RclMain::startPreview(int docnum, Rcl::Doc doc, int mod) +{ + LOGDEB(("startPreview(%d, doc, %d)\n", docnum, mod)); + + if (!containerUpToDate(doc)) + return; + + // Do the zeitgeist thing + zg_send_event(ZGSEND_PREVIEW, doc); + + if (mod & Qt::ShiftModifier) { + // User wants new preview window + curPreview = 0; + } + if (curPreview == 0) { + HighlightData hdata; + m_source->getTerms(hdata); + curPreview = new Preview(reslist->listId(), hdata); + + if (curPreview == 0) { + QMessageBox::warning(0, tr("Warning"), + tr("Can't create preview window"), + QMessageBox::Ok, + QMessageBox::NoButton); + return; + } + connect(new QShortcut(quitKeySeq, curPreview), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(curPreview, SIGNAL(previewClosed(Preview *)), + this, SLOT(previewClosed(Preview *))); + connect(curPreview, SIGNAL(wordSelect(QString)), + sSearch, SLOT(addTerm(QString))); + connect(curPreview, SIGNAL(showNext(Preview *, int, int)), + this, SLOT(previewNextInTab(Preview *, int, int))); + connect(curPreview, SIGNAL(showPrev(Preview *, int, int)), + this, SLOT(previewPrevInTab(Preview *, int, int))); + connect(curPreview, SIGNAL(previewExposed(Preview *, int, int)), + this, SLOT(previewExposed(Preview *, int, int))); + connect(curPreview, SIGNAL(saveDocToFile(Rcl::Doc)), + this, SLOT(saveDocToFile(Rcl::Doc))); + curPreview->setWindowTitle(getQueryDescription()); + curPreview->show(); + } + curPreview->makeDocCurrent(doc, docnum); +} + +/** + * Open a preview window for a given document, no linking to result list + * + * This is used to show ie parent documents, which have no corresponding + * entry in the result list. + * + */ +void RclMain::startPreview(Rcl::Doc doc) +{ + Preview *preview = new Preview(0, HighlightData()); + if (preview == 0) { + QMessageBox::warning(0, tr("Warning"), + tr("Can't create preview window"), + QMessageBox::Ok, + QMessageBox::NoButton); + return; + } + connect(new QShortcut(quitKeySeq, preview), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(preview, SIGNAL(wordSelect(QString)), + sSearch, SLOT(addTerm(QString))); + // Do the zeitgeist thing + zg_send_event(ZGSEND_PREVIEW, doc); + preview->show(); + preview->makeDocCurrent(doc, 0); +} + +// Show next document from result list in current preview tab +void RclMain::previewNextInTab(Preview * w, int sid, int docnum) +{ + previewPrevOrNextInTab(w, sid, docnum, true); +} + +// Show previous document from result list in current preview tab +void RclMain::previewPrevInTab(Preview * w, int sid, int docnum) +{ + previewPrevOrNextInTab(w, sid, docnum, false); +} + +// Combined next/prev from result list in current preview tab +void RclMain::previewPrevOrNextInTab(Preview * w, int sid, int docnum, bool nxt) +{ + LOGDEB(("RclMain::previewNextInTab sid %d docnum %d, listId %d\n", + sid, docnum, reslist->listId())); + + if (w == 0) // ?? + return; + + if (sid != reslist->listId()) { + QMessageBox::warning(0, "Recoll", + tr("This search is not active any more")); + return; + } + + if (nxt) + docnum++; + else + docnum--; + if (docnum < 0 || m_source.isNull() || docnum >= m_source->getResCnt()) { + QApplication::beep(); + return; + } + + Rcl::Doc doc; + if (!reslist->getDoc(docnum, doc)) { + QMessageBox::warning(0, "Recoll", + tr("Cannot retrieve document info from database")); + return; + } + + w->makeDocCurrent(doc, docnum, true); +} + +// Preview tab exposed: if the preview comes from the currently +// displayed result list, tell reslist (to color the paragraph) +void RclMain::previewExposed(Preview *, int sid, int docnum) +{ + LOGDEB2(("RclMain::previewExposed: sid %d docnum %d, m_sid %d\n", + sid, docnum, reslist->listId())); + if (sid != reslist->listId()) { + return; + } + reslist->previewExposed(docnum); +} diff --git a/src/qtgui/rclm_view.cpp b/src/qtgui/rclm_view.cpp new file mode 100644 index 00000000..ac2ce87e --- /dev/null +++ b/src/qtgui/rclm_view.cpp @@ -0,0 +1,442 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include + +#include + +#include "debuglog.h" +#include "fileudi.h" +#include "execmd.h" +#include "transcode.h" +#include "docseqhist.h" +#include "docseqdb.h" +#include "internfile.h" +#include "rclmain_w.h" +#include "rclzg.h" + +using namespace std; + +// Start native viewer or preview for input Doc. This is used to allow +// using recoll from another app (e.g. Unity Scope) to view embedded +// result docs (docs with an ipath). . We act as a proxy to extract +// the data and start a viewer. The Url are encoded as +// file://path#ipath +void RclMain::viewUrl() +{ + if (m_urltoview.isEmpty() || !rcldb) + return; + + QUrl qurl(m_urltoview); + LOGDEB(("RclMain::viewUrl: Path [%s] fragment [%s]\n", + (const char *)qurl.path().toLocal8Bit(), + (const char *)qurl.fragment().toLocal8Bit())); + + /* In theory, the url might not be for a file managed by the fs + indexer so that the make_udi() call here would be + wrong(). When/if this happens we'll have to hide this part + inside internfile and have some url magic to indicate the + appropriate indexer/identification scheme */ + string udi; + make_udi((const char *)qurl.path().toLocal8Bit(), + (const char *)qurl.fragment().toLocal8Bit(), udi); + + Rcl::Doc doc; + Rcl::Doc idxdoc; // idxdoc.idxi == 0 -> works with base index only + if (!rcldb->getDoc(udi, idxdoc, doc) || doc.pc == -1) + return; + + // StartNativeViewer needs a db source to call getEnclosing() on. + Rcl::Query *query = new Rcl::Query(rcldb); + DocSequenceDb *src = + new DocSequenceDb(RefCntr(query), "", + RefCntr(new Rcl::SearchData)); + m_source = RefCntr(src); + + + // Start a native viewer if the mimetype has one defined, else a + // preview. + string apptag; + doc.getmeta(Rcl::Doc::keyapptg, &apptag); + string viewer = theconfig->getMimeViewerDef(doc.mimetype, apptag, + prefs.useDesktopOpen); + if (viewer.empty()) { + startPreview(doc); + } else { + hide(); + startNativeViewer(doc); + // We have a problem here because xdg-open will exit + // immediately after starting the command instead of waiting + // for it, so we can't wait either and we don't know when we + // can exit (deleting the temp file). As a bad workaround we + // sleep some time then exit. The alternative would be to just + // prevent the temp file deletion completely, leaving it + // around forever. Better to let the user save a copy if he + // wants I think. + sleep(30); + fileExit(); + } +} + +/* Look for html browser. We make a special effort for html because it's + * used for reading help. This is only used if the normal approach + * (xdg-open etc.) failed */ +static bool lookForHtmlBrowser(string &exefile) +{ + static const char *htmlbrowserlist = + "opera google-chrome chromium-browser konqueror iceweasel firefox " + "mozilla netscape epiphany"; + vector blist; + stringToTokens(htmlbrowserlist, blist, " "); + + const char *path = getenv("PATH"); + if (path == 0) + path = "/bin:/usr/bin:/usr/bin/X11:/usr/X11R6/bin:/usr/local/bin"; + + // Look for each browser + for (vector::const_iterator bit = blist.begin(); + bit != blist.end(); bit++) { + if (ExecCmd::which(*bit, exefile, path)) + return true; + } + exefile.clear(); + return false; +} + +void RclMain::openWith(Rcl::Doc doc, string cmdspec) +{ + LOGDEB(("RclMain::openWith: %s\n", cmdspec.c_str())); + + // Split the command line + vector lcmd; + if (!stringToStrings(cmdspec, lcmd)) { + QMessageBox::warning(0, "Recoll", + tr("Bad desktop app spec for %1: [%2]\n" + "Please check the desktop file") + .arg(QString::fromUtf8(doc.mimetype.c_str())) + .arg(QString::fromLocal8Bit(cmdspec.c_str()))); + return; + } + + // Look for the command to execute in the exec path and the filters + // directory + string execname = lcmd.front(); + lcmd.erase(lcmd.begin()); + string url = doc.url; + string fn = fileurltolocalpath(doc.url); + + // Try to keep the letters used more or less consistent with the reslist + // paragraph format. + map subs; + subs["F"] = fn; + subs["f"] = fn; + subs["U"] = url; + subs["u"] = url; + + execViewer(subs, false, execname, lcmd, cmdspec, doc); +} + +void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) +{ + string apptag; + doc.getmeta(Rcl::Doc::keyapptg, &apptag); + LOGDEB(("RclMain::startNativeViewer: mtype [%s] apptag [%s] page %d " + "term [%s] url [%s] ipath [%s]\n", + doc.mimetype.c_str(), apptag.c_str(), pagenum, + (const char *)(term.toUtf8()), doc.url.c_str(), doc.ipath.c_str() + )); + + // Look for appropriate viewer + string cmdplusattr = theconfig->getMimeViewerDef(doc.mimetype, apptag, + prefs.useDesktopOpen); + if (cmdplusattr.empty()) { + QMessageBox::warning(0, "Recoll", + tr("No external viewer configured for mime type [") + + doc.mimetype.c_str() + "]"); + return; + } + + // Separate command string and viewer attributes (if any) + ConfSimple viewerattrs; + string cmd; + theconfig->valueSplitAttributes(cmdplusattr, cmd, viewerattrs); + bool ignoreipath = false; + if (viewerattrs.get("ignoreipath", cmdplusattr)) + ignoreipath = stringToBool(cmdplusattr); + + // Split the command line + vector lcmd; + if (!stringToStrings(cmd, lcmd)) { + QMessageBox::warning(0, "Recoll", + tr("Bad viewer command line for %1: [%2]\n" + "Please check the mimeview file") + .arg(QString::fromUtf8(doc.mimetype.c_str())) + .arg(QString::fromLocal8Bit(cmd.c_str()))); + return; + } + + // Look for the command to execute in the exec path and the filters + // directory + string execpath; + if (!ExecCmd::which(lcmd.front(), execpath)) { + execpath = theconfig->findFilter(lcmd.front()); + // findFilter returns its input param if the filter is not in + // the normal places. As we already looked in the path, we + // have no use for a simple command name here (as opposed to + // mimehandler which will just let execvp do its thing). Erase + // execpath so that the user dialog will be started further + // down. + if (!execpath.compare(lcmd.front())) + execpath.erase(); + + // Specialcase text/html because of the help browser need + if (execpath.empty() && !doc.mimetype.compare("text/html") && + apptag.empty()) { + if (lookForHtmlBrowser(execpath)) { + lcmd.clear(); + lcmd.push_back(execpath); + lcmd.push_back("%u"); + } + } + } + + // Command not found: start the user dialog to help find another one: + if (execpath.empty()) { + QString mt = QString::fromUtf8(doc.mimetype.c_str()); + QString message = tr("The viewer specified in mimeview for %1: %2" + " is not found.\nDo you want to start the " + " preferences dialog ?") + .arg(mt).arg(QString::fromLocal8Bit(lcmd.front().c_str())); + + switch(QMessageBox::warning(0, "Recoll", message, + "Yes", "No", 0, 0, 1)) { + case 0: + showUIPrefs(); + if (uiprefs) + uiprefs->showViewAction(mt); + break; + case 1: + break; + } + // The user will have to click on the link again to try the + // new command. + return; + } + // Get rid of the command name. lcmd is now argv[1...n] + lcmd.erase(lcmd.begin()); + + // Process the command arguments to determine if we need to create + // a temporary file. + + // If the command has a %i parameter it will manage the + // un-embedding. Else if ipath is not empty, we need a temp file. + // This can be overridden with the "ignoreipath" attribute + bool groksipath = (cmd.find("%i") != string::npos) || ignoreipath; + + // wantsfile: do we actually need a local file ? The only other + // case here is an url %u (ie: for web history). + bool wantsfile = cmd.find("%f") != string::npos && urlisfileurl(doc.url); + bool wantsparentfile = cmd.find("%F") != string::npos && + urlisfileurl(doc.url); + + if (wantsfile && wantsparentfile) { + QMessageBox::warning(0, "Recoll", + tr("Viewer command line for %1 specifies both " + "file and parent file value: unsupported") + .arg(QString::fromUtf8(doc.mimetype.c_str()))); + return; + } + + string url = doc.url; + string fn = fileurltolocalpath(doc.url); + Rcl::Doc pdoc; + if (wantsparentfile) { + // We want the path for the parent document. For example to + // open the chm file, not the internal page. Note that we just + // override the other file name in this case. + if (m_source.isNull() || !m_source->getEnclosing(doc, pdoc)) { + QMessageBox::warning(0, "Recoll", + tr("Cannot find parent document")); + return; + } + // Override fn with the parent's : + fn = fileurltolocalpath(pdoc.url); + + // If the parent document has an ipath too, we need to create + // a temp file even if the command takes an ipath + // parameter. We have no viewer which could handle a double + // embedding. Will have to change if such a one appears. + if (!pdoc.ipath.empty()) { + groksipath = false; + } + } + + bool istempfile = false; + + LOGDEB(("RclMain::startNV: groksipath %d wantsf %d wantsparentf %d\n", + groksipath, wantsfile, wantsparentfile)); + + // If the command wants a file but this is not a file url, or + // there is an ipath that it won't understand, we need a temp file: + theconfig->setKeyDir(path_getfather(fn)); + if (((wantsfile || wantsparentfile) && fn.empty()) || + (!groksipath && !doc.ipath.empty())) { + TempFile temp; + Rcl::Doc& thedoc = wantsparentfile ? pdoc : doc; + if (!FileInterner::idocToFile(temp, string(), theconfig, thedoc)) { + QMessageBox::warning(0, "Recoll", + tr("Cannot extract document or create " + "temporary file")); + return; + } + istempfile = true; + rememberTempFile(temp); + fn = temp->filename(); + url = string("file://") + fn; + } + + // If using an actual file, check that it exists, and if it is + // compressed, we may need an uncompressed version + if (!fn.empty() && theconfig->mimeViewerNeedsUncomp(doc.mimetype)) { + if (access(fn.c_str(), R_OK) != 0) { + QMessageBox::warning(0, "Recoll", + tr("Can't access file: ") + + QString::fromLocal8Bit(fn.c_str())); + return; + } + TempFile temp; + if (FileInterner::isCompressed(fn, theconfig)) { + if (!FileInterner::maybeUncompressToTemp(temp, fn, theconfig, + doc)) { + QMessageBox::warning(0, "Recoll", + tr("Can't uncompress file: ") + + QString::fromLocal8Bit(fn.c_str())); + return; + } + } + if (!temp.isNull()) { + rememberTempFile(temp); + fn = temp->filename(); + url = string("file://") + fn; + } + } + + // If we are not called with a page number (which would happen for a call + // from the snippets window), see if we can compute a page number anyway. + if (pagenum == -1) { + pagenum = 1; + string lterm; + if (m_source.isNotNull()) + pagenum = m_source->getFirstMatchPage(doc, lterm); + if (pagenum == -1) + pagenum = 1; + else // We get the match term used to compute the page + term = QString::fromUtf8(lterm.c_str()); + } + char cpagenum[20]; + sprintf(cpagenum, "%d", pagenum); + + + // Substitute %xx inside arguments + string efftime; + if (!doc.dmtime.empty() || !doc.fmtime.empty()) { + efftime = doc.dmtime.empty() ? doc.fmtime : doc.dmtime; + } else { + efftime = "0"; + } + // Try to keep the letters used more or less consistent with the reslist + // paragraph format. + map subs; + subs["D"] = efftime; + subs["f"] = fn; + subs["F"] = fn; + subs["i"] = FileInterner::getLastIpathElt(doc.ipath); + subs["M"] = doc.mimetype; + subs["p"] = cpagenum; + subs["s"] = (const char*)term.toLocal8Bit(); + subs["U"] = url; + subs["u"] = url; + // Let %(xx) access all metadata. + for (map::const_iterator it = doc.meta.begin(); + it != doc.meta.end(); it++) { + subs[it->first] = it->second; + } + execViewer(subs, istempfile, execpath, lcmd, cmd, doc); +} + +void RclMain::execViewer(const map& subs, bool istempfile, + const string& execpath, + const vector& _lcmd, const string& cmd, + Rcl::Doc doc) +{ + string ncmd; + vector lcmd; + for (vector::const_iterator it = _lcmd.begin(); + it != _lcmd.end(); it++) { + pcSubst(*it, ncmd, subs); + LOGDEB(("%s->%s\n", it->c_str(), ncmd.c_str())); + lcmd.push_back(ncmd); + } + + // Also substitute inside the unsplitted command line and display + // in status bar + pcSubst(cmd, ncmd, subs); + ncmd += " &"; + QStatusBar *stb = statusBar(); + if (stb) { + string fcharset = theconfig->getDefCharset(true); + string prcmd; + transcode(ncmd, prcmd, fcharset, "UTF-8"); + QString msg = tr("Executing: [") + + QString::fromUtf8(prcmd.c_str()) + "]"; + stb->showMessage(msg, 10000); + } + + if (!istempfile) + historyEnterDoc(g_dynconf, doc.meta[Rcl::Doc::keyudi]); + + // Do the zeitgeist thing + zg_send_event(ZGSEND_OPEN, doc); + + // We keep pushing back and never deleting. This can't be good... + ExecCmd *ecmd = new ExecCmd; + m_viewers.push_back(ecmd); + ecmd->startExec(execpath, lcmd, false, false); +} + +void RclMain::startManual() +{ + startManual(string()); +} + +void RclMain::startManual(const string& index) +{ + Rcl::Doc doc; + doc.url = "file://"; + doc.url = path_cat(doc.url, theconfig->getDatadir()); + doc.url = path_cat(doc.url, "doc"); + doc.url = path_cat(doc.url, "usermanual.html"); + LOGDEB(("RclMain::startManual: help index is %s\n", + index.empty()?"(null)":index.c_str())); + if (!index.empty()) { + doc.url += "#"; + doc.url += index; + } + doc.mimetype = "text/html"; + startNativeViewer(doc); +} diff --git a/src/qtgui/rclm_wins.cpp b/src/qtgui/rclm_wins.cpp new file mode 100644 index 00000000..d9cb87d1 --- /dev/null +++ b/src/qtgui/rclm_wins.cpp @@ -0,0 +1,390 @@ +/* Copyright (C) 2005 J.F.Dockes + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the + * Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#include "autoconfig.h" + +#include +#include + +#include "debuglog.h" +#include "internfile.h" +#include "listdialog.h" +#include "confgui/confguiindex.h" +#include "idxsched.h" +#include "crontool.h" +#include "rtitool.h" +#include "snippets_w.h" +#include "fragbuts.h" +#include "rclmain_w.h" + +using namespace std; + +static const QKeySequence quitKeySeq("Ctrl+q"); +static const QKeySequence closeKeySeq("Ctrl+w"); + +// Open advanced search dialog. +void RclMain::showAdvSearchDialog() +{ + if (asearchform == 0) { + asearchform = new AdvSearch(0); + if (asearchform == 0) { + return; + } + connect(new QShortcut(quitKeySeq, asearchform), SIGNAL (activated()), + this, SLOT (fileExit())); + + connect(asearchform, + SIGNAL(startSearch(RefCntr, bool)), + this, SLOT(startSearch(RefCntr, bool))); + asearchform->show(); + } else { + // Close and reopen, in hope that makes us visible... + asearchform->close(); + asearchform->show(); + } +} + +void RclMain::showSpellDialog() +{ + if (spellform == 0) { + spellform = new SpellW(0); + connect(new QShortcut(quitKeySeq, spellform), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(spellform, SIGNAL(wordSelect(QString)), + sSearch, SLOT(addTerm(QString))); + spellform->show(); + } else { + // Close and reopen, in hope that makes us visible... + spellform->close(); + spellform->show(); + } +} + +void RclMain::showFragButs() +{ + if (fragbuts && fragbuts->isStale(0)) { + deleteZ(fragbuts); + } + if (fragbuts == 0) { + fragbuts = new FragButs(0); + if (fragbuts->ok()) { + fragbuts->show(); + connect(fragbuts, SIGNAL(fragmentsChanged()), + this, SLOT(onFragmentsChanged())); + } else { + delete fragbuts; + fragbuts = 0; + } + } else { + // Close and reopen, in hope that makes us visible... + fragbuts->close(); + fragbuts->show(); + } +} + +void RclMain::showIndexConfig() +{ + showIndexConfig(false); +} +void RclMain::execIndexConfig() +{ + showIndexConfig(true); +} +void RclMain::showIndexConfig(bool modal) +{ + LOGDEB(("showIndexConfig()\n")); + if (indexConfig == 0) { + indexConfig = new ConfIndexW(0, theconfig); + connect(new QShortcut(quitKeySeq, indexConfig), SIGNAL (activated()), + this, SLOT (fileExit())); + } else { + // Close and reopen, in hope that makes us visible... + indexConfig->close(); + indexConfig->reloadPanels(); + } + if (modal) { + indexConfig->exec(); + indexConfig->setModal(false); + } else { + indexConfig->show(); + } +} + +void RclMain::showIndexSched() +{ + showIndexSched(false); +} +void RclMain::execIndexSched() +{ + showIndexSched(true); +} +void RclMain::showIndexSched(bool modal) +{ + LOGDEB(("showIndexSched()\n")); + if (indexSched == 0) { + indexSched = new IdxSchedW(this); + connect(new QShortcut(quitKeySeq, indexSched), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(indexSched->cronCLB, SIGNAL(clicked()), + this, SLOT(execCronTool())); + if (theconfig && theconfig->isDefaultConfig()) { +#ifdef RCL_MONITOR + connect(indexSched->rtidxCLB, SIGNAL(clicked()), + this, SLOT(execRTITool())); +#else + indexSched->rtidxCLB->setEnabled(false); + indexSched->rtidxCLB->setToolTip(tr("Disabled because the real time indexer was not compiled in.")); +#endif + } else { + indexSched->rtidxCLB->setEnabled(false); + indexSched->rtidxCLB->setToolTip(tr("This configuration tool only works for the main index.")); + } + } else { + // Close and reopen, in hope that makes us visible... + indexSched->close(); + } + if (modal) { + indexSched->exec(); + indexSched->setModal(false); + } else { + indexSched->show(); + } +} + +void RclMain::showCronTool() +{ + showCronTool(false); +} +void RclMain::execCronTool() +{ + showCronTool(true); +} +void RclMain::showCronTool(bool modal) +{ + LOGDEB(("showCronTool()\n")); + if (cronTool == 0) { + cronTool = new CronToolW(0); + connect(new QShortcut(quitKeySeq, cronTool), SIGNAL (activated()), + this, SLOT (fileExit())); + } else { + // Close and reopen, in hope that makes us visible... + cronTool->close(); + } + if (modal) { + cronTool->exec(); + cronTool->setModal(false); + } else { + cronTool->show(); + } +} + +void RclMain::showRTITool() +{ + showRTITool(false); +} +void RclMain::execRTITool() +{ + showRTITool(true); +} +void RclMain::showRTITool(bool modal) +{ + LOGDEB(("showRTITool()\n")); + if (rtiTool == 0) { + rtiTool = new RTIToolW(0); + connect(new QShortcut(quitKeySeq, rtiTool), SIGNAL (activated()), + this, SLOT (fileExit())); + } else { + // Close and reopen, in hope that makes us visible... + rtiTool->close(); + } + if (modal) { + rtiTool->exec(); + rtiTool->setModal(false); + } else { + rtiTool->show(); + } +} + +void RclMain::showUIPrefs() +{ + if (uiprefs == 0) { + uiprefs = new UIPrefsDialog(this); + connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs())); + connect(this, SIGNAL(stemLangChanged(const QString&)), + uiprefs, SLOT(setStemLang(const QString&))); + } else { + // Close and reopen, in hope that makes us visible... + uiprefs->close(); + } + uiprefs->show(); +} + +void RclMain::showExtIdxDialog() +{ + if (uiprefs == 0) { + uiprefs = new UIPrefsDialog(this); + connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs())); + } else { + // Close and reopen, in hope that makes us visible... + uiprefs->close(); + } + uiprefs->tabWidget->setCurrentIndex(3); + uiprefs->show(); +} + +void RclMain::showAboutDialog() +{ + string vstring = Rcl::version_string() + + string("
http://www.recoll.org") + + string("
http://www.xapian.org"); + QMessageBox::information(this, tr("About Recoll"), vstring.c_str()); +} + +void RclMain::showMissingHelpers() +{ + string miss; + if (!theconfig->getMissingHelperDesc(miss)) { + QMessageBox::information(this, "", tr("Indexing did not run yet")); + return; + } + QString msg = QString::fromUtf8("

") + + tr("External applications/commands needed for your file types " + "and not found, as stored by the last indexing pass in "); + msg += ""; + msg += QString::fromLocal8Bit(theconfig->getConfDir().c_str()); + msg += "/missing:

\n";
+    if (!miss.empty()) {
+	msg += QString::fromUtf8(miss.c_str());
+    } else {
+	msg += tr("No helpers found missing");
+    }
+    msg += "
"; + QMessageBox::information(this, tr("Missing helper programs"), msg); +} + +void RclMain::showActiveTypes() +{ + if (rcldb == 0) { + QMessageBox::warning(0, tr("Error"), + tr("Index not open"), + QMessageBox::Ok, + QMessageBox::NoButton); + return; + } + + // All mime types in index. + vector vdbtypes; + if (!rcldb->getAllDbMimeTypes(vdbtypes)) { + QMessageBox::warning(0, tr("Error"), + tr("Index query error"), + QMessageBox::Ok, + QMessageBox::NoButton); + return; + } + set mtypesfromdb; + mtypesfromdb.insert(vdbtypes.begin(), vdbtypes.end()); + + // All types listed in mimeconf: + vector mtypesfromconfig = theconfig->getAllMimeTypes(); + + // Intersect file system types with config types (those not in the + // config can be indexed by name, not by content) + set mtypesfromdbconf; + for (vector::const_iterator it = mtypesfromconfig.begin(); + it != mtypesfromconfig.end(); it++) { + if (mtypesfromdb.find(*it) != mtypesfromdb.end()) + mtypesfromdbconf.insert(*it); + } + + // Substract the types for missing helpers (the docs are indexed + // by name only): + string miss; + if (theconfig->getMissingHelperDesc(miss) && !miss.empty()) { + FIMissingStore st(miss); + map >::const_iterator it; + for (it = st.m_typesForMissing.begin(); + it != st.m_typesForMissing.end(); it++) { + set::const_iterator it1; + for (it1 = it->second.begin(); + it1 != it->second.end(); it1++) { + set::iterator it2 = mtypesfromdbconf.find(*it1); + if (it2 != mtypesfromdbconf.end()) + mtypesfromdbconf.erase(it2); + } + } + } + ListDialog dialog; + dialog.setWindowTitle(tr("Indexed MIME Types")); + + // Turn the result into a string and display + dialog.groupBox->setTitle(tr("Content has been indexed for these mime types:")); + + // We replace the list with an editor so that the user can copy/paste + delete dialog.listWidget; + QTextEdit *editor = new QTextEdit(dialog.groupBox); + editor->setReadOnly(true); + dialog.horizontalLayout->addWidget(editor); + + for (set::const_iterator it = mtypesfromdbconf.begin(); + it != mtypesfromdbconf.end(); it++) { + editor->append(QString::fromUtf8(it->c_str())); + } + editor->moveCursor(QTextCursor::Start); + editor->ensureCursorVisible(); + dialog.exec(); +} + +void RclMain::newDupsW(const Rcl::Doc, const vector dups) +{ + ListDialog dialog; + dialog.setWindowTitle(tr("Duplicate documents")); + + dialog.groupBox->setTitle(tr("These Urls ( | ipath) share the same" + " content:")); + // We replace the list with an editor so that the user can copy/paste + delete dialog.listWidget; + QTextEdit *editor = new QTextEdit(dialog.groupBox); + editor->setReadOnly(true); + dialog.horizontalLayout->addWidget(editor); + + for (vector::const_iterator it = dups.begin(); + it != dups.end(); it++) { + if (it->ipath.empty()) + editor->append(QString::fromLocal8Bit(it->url.c_str())); + else + editor->append(QString::fromLocal8Bit(it->url.c_str()) + " | " + + QString::fromUtf8(it->ipath.c_str())); + } + editor->moveCursor(QTextCursor::Start); + editor->ensureCursorVisible(); + dialog.exec(); +} + +void RclMain::showSnippets(Rcl::Doc doc) +{ + SnippetsW *sp = new SnippetsW(doc, m_source); + connect(sp, SIGNAL(startNativeViewer(Rcl::Doc, int, QString)), + this, SLOT(startNativeViewer(Rcl::Doc, int, QString))); + connect(new QShortcut(quitKeySeq, sp), SIGNAL (activated()), + this, SLOT (fileExit())); + connect(new QShortcut(closeKeySeq, sp), SIGNAL (activated()), + sp, SLOT (close())); + sp->show(); +} diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index a20bb818..7dd90d62 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -16,18 +16,9 @@ */ #include "autoconfig.h" -#include -#include -#include -#include -#include #include -#include #include -#ifndef NO_NAMESPACES -using std::pair; -#endif /* NO_NAMESPACES */ #include #include @@ -41,12 +32,9 @@ using std::pair; #include #include #include -#include -#include #include #include #include -#include #include #include #include @@ -64,37 +52,26 @@ using std::pair; #include "uiprefs_w.h" #include "guiutils.h" #include "reslist.h" -#include "transcode.h" #include "refcntr.h" #include "ssearch_w.h" -#include "execmd.h" #include "internfile.h" #include "docseqdb.h" #include "docseqhist.h" #include "docseqdocs.h" -#include "confguiindex.h" #include "restable.h" -#include "listdialog.h" #include "firstidx.h" -#include "idxsched.h" -#include "crontool.h" -#include "rtitool.h" #include "indexer.h" #include "rclzg.h" -#include "fileudi.h" #include "snippets_w.h" #include "fragbuts.h" #include "systray.h" - -using namespace confgui; - #include "rclmain_w.h" #include "rclhelp.h" #include "moc_rclmain_w.cpp" +using std::pair; + QString g_stringAllStem, g_stringNoStem; -static const QKeySequence quitKeySeq("Ctrl+q"); -static const QKeySequence closeKeySeq("Ctrl+w"); static Qt::ToolBarArea int2area(int in) { @@ -546,66 +523,6 @@ void RclMain::initDbOpen() viewUrl(); } -// Start native viewer or preview for input Doc. This is used allow -// using Recoll result docs with an ipath in another app. We act -// as a proxy to extract the data and start a viewer. -// The Url are encoded as file://path#ipath -void RclMain::viewUrl() -{ - if (m_urltoview.isEmpty() || !rcldb) - return; - - QUrl qurl(m_urltoview); - LOGDEB(("RclMain::viewUrl: Path [%s] fragment [%s]\n", - (const char *)qurl.path().toLocal8Bit(), - (const char *)qurl.fragment().toLocal8Bit())); - - /* In theory, the url might not be for a file managed by the fs - indexer so that the make_udi() call here would be - wrong(). When/if this happens we'll have to hide this part - inside internfile and have some url magic to indicate the - appropriate indexer/identification scheme */ - string udi; - make_udi((const char *)qurl.path().toLocal8Bit(), - (const char *)qurl.fragment().toLocal8Bit(), udi); - - Rcl::Doc doc; - Rcl::Doc idxdoc; // idxdoc.idxi == 0 -> works with base index only - if (!rcldb->getDoc(udi, idxdoc, doc) || doc.pc == -1) - return; - - // StartNativeViewer needs a db source to call getEnclosing() on. - Rcl::Query *query = new Rcl::Query(rcldb); - DocSequenceDb *src = - new DocSequenceDb(RefCntr(query), "", - RefCntr(new Rcl::SearchData)); - m_source = RefCntr(src); - - - // Start a native viewer if the mimetype has one defined, else a - // preview. - string apptag; - doc.getmeta(Rcl::Doc::keyapptg, &apptag); - string viewer = theconfig->getMimeViewerDef(doc.mimetype, apptag, - prefs.useDesktopOpen); - if (viewer.empty()) { - startPreview(doc); - } else { - hide(); - startNativeViewer(doc); - // We have a problem here because xdg-open will exit - // immediately after starting the command instead of waiting - // for it, so we can't wait either and we don't know when we - // can exit (deleting the temp file). As a bad workaround we - // sleep some time then exit. The alternative would be to just - // prevent the temp file deletion completely, leaving it - // around forever. Better to let the user save a copy if he - // wants I think. - sleep(30); - fileExit(); - } -} - void RclMain::setStemLang(QAction *id) { LOGDEB(("RclMain::setStemLang(%p)\n", id)); @@ -712,251 +629,6 @@ void RclMain::fileExit() exit(0); } -void RclMain::idxStatus() -{ - ConfSimple cs(theconfig->getIdxStatusFile().c_str(), 1); - QString msg = tr("Indexing in progress: "); - DbIxStatus status; - string val; - cs.get("phase", val); - status.phase = DbIxStatus::Phase(atoi(val.c_str())); - cs.get("fn", status.fn); - cs.get("docsdone", val); - status.docsdone = atoi(val.c_str()); - cs.get("filesdone", val); - status.filesdone = atoi(val.c_str()); - cs.get("dbtotdocs", val); - status.dbtotdocs = atoi(val.c_str()); - - QString phs; - switch (status.phase) { - case DbIxStatus::DBIXS_NONE:phs=tr("None");break; - case DbIxStatus::DBIXS_FILES: phs=tr("Updating");break; - case DbIxStatus::DBIXS_PURGE: phs=tr("Purge");break; - case DbIxStatus::DBIXS_STEMDB: phs=tr("Stemdb");break; - case DbIxStatus::DBIXS_CLOSING:phs=tr("Closing");break; - case DbIxStatus::DBIXS_DONE:phs=tr("Done");break; - case DbIxStatus::DBIXS_MONITOR:phs=tr("Monitor");break; - default: phs=tr("Unknown");break; - } - msg += phs + " "; - if (status.phase == DbIxStatus::DBIXS_FILES) { - char cnts[100]; - if (status.dbtotdocs > 0) - sprintf(cnts,"(%d/%d/%d) ", status.docsdone, status.filesdone, - status.dbtotdocs); - else - sprintf(cnts, "(%d/%d) ", status.docsdone, status.filesdone); - msg += QString::fromUtf8(cnts) + " "; - } - string mf;int ecnt = 0; - string fcharset = theconfig->getDefCharset(true); - if (!transcode(status.fn, mf, fcharset, "UTF-8", &ecnt) || ecnt) { - mf = url_encode(status.fn, 0); - } - msg += QString::fromUtf8(mf.c_str()); - statusBar()->showMessage(msg, 4000); -} - -// This is called by a periodic timer to check the status of -// indexing, a possible need to exit, and cleanup exited viewers -void RclMain::periodic100() -{ - LOGDEB2(("Periodic100\n")); - if (m_idxproc) { - // An indexing process was launched. If its' done, see status. - int status; - bool exited = m_idxproc->maybereap(&status); - if (exited) { - deleteZ(m_idxproc); - if (status) { - if (m_idxkilled) { - QMessageBox::warning(0, "Recoll", - tr("Indexing interrupted")); - m_idxkilled = false; - } else { - QMessageBox::warning(0, "Recoll", - tr("Indexing failed")); - } - } else { - // On the first run, show missing helpers. We only do this once - if (m_firstIndexing) - showMissingHelpers(); - } - string reason; - maybeOpenDb(reason, 1); - } else { - // update/show status even if the status file did not - // change (else the status line goes blank during - // lengthy operations). - idxStatus(); - } - } - // Update the "start/stop indexing" menu entry, can't be done from - // the "start/stop indexing" slot itself - IndexerState prevstate = m_indexerState; - if (m_idxproc) { - m_indexerState = IXST_RUNNINGMINE; - fileToggleIndexingAction->setText(tr("Stop &Indexing")); - fileToggleIndexingAction->setEnabled(true); - fileRetryFailedAction->setEnabled(false); - fileRebuildIndexAction->setEnabled(false); - periodictimer->setInterval(200); - } else { - Pidfile pidfile(theconfig->getPidfile()); - if (pidfile.open() == 0) { - m_indexerState = IXST_NOTRUNNING; - fileToggleIndexingAction->setText(tr("Update &Index")); - fileRetryFailedAction->setEnabled(true); - fileToggleIndexingAction->setEnabled(true); - fileRebuildIndexAction->setEnabled(true); - periodictimer->setInterval(1000); - } else { - // Real time or externally started batch indexer running - m_indexerState = IXST_RUNNINGNOTMINE; - fileToggleIndexingAction->setText(tr("Stop &Indexing")); - fileToggleIndexingAction->setEnabled(true); - fileRetryFailedAction->setEnabled(false); - fileRebuildIndexAction->setEnabled(false); - periodictimer->setInterval(200); - } - } - - if ((prevstate == IXST_RUNNINGMINE || prevstate == IXST_RUNNINGNOTMINE) - && m_indexerState == IXST_NOTRUNNING) { - showTrayMessage("Indexing done"); - } - - // Possibly cleanup the dead viewers - for (vector::iterator it = m_viewers.begin(); - it != m_viewers.end(); it++) { - int status; - if ((*it)->maybereap(&status)) { - deleteZ(*it); - } - } - vector v; - for (vector::iterator it = m_viewers.begin(); - it != m_viewers.end(); it++) { - if (*it) - v.push_back(*it); - } - m_viewers = v; - - if (recollNeedsExit) - fileExit(); -} - -// This gets called when the "update index" action is activated. It executes -// the requested action, and disables the menu entry. This will be -// re-enabled by the indexing status check -void RclMain::toggleIndexing() -{ - switch (m_indexerState) { - case IXST_RUNNINGMINE: - if (m_idxproc) { - // Indexing was in progress, request stop. Let the periodic - // routine check for the results. - int pid = m_idxproc->getChildPid(); - if (pid > 0) { - kill(pid, SIGTERM); - m_idxkilled = true; - } - } - break; - case IXST_RUNNINGNOTMINE: - { - int rep = - QMessageBox::information(0, tr("Warning"), - tr("The current indexing process " - "was not started from this " - "interface. Click Ok to kill it " - "anyway, or Cancel to leave it alone"), - QMessageBox::Ok, - QMessageBox::Cancel, - QMessageBox::NoButton); - if (rep == QMessageBox::Ok) { - Pidfile pidfile(theconfig->getPidfile()); - pid_t pid = pidfile.open(); - if (pid > 0) - kill(pid, SIGTERM); - } - } - break; - case IXST_NOTRUNNING: - { - // Could also mean that no helpers are missing, but then we - // won't try to show a message anyway (which is what - // firstIndexing is used for) - string mhd; - m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); - - vector args; - - string badpaths; - args.push_back("recollindex"); - args.push_back("-E"); - ExecCmd::backtick(args, badpaths); - if (!badpaths.empty()) { - int rep = - QMessageBox::warning(0, tr("Bad paths"), - tr("Bad paths in configuration file:\n") + - QString::fromLocal8Bit(badpaths.c_str()), - QMessageBox::Ok, - QMessageBox::Cancel, - QMessageBox::NoButton); - if (rep == QMessageBox::Cancel) - return; - } - - args.clear(); - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - if (fileRetryFailedAction->isChecked()) - args.push_back("-k"); - m_idxproc = new ExecCmd; - m_idxproc->startExec("recollindex", args, false, false); - } - break; - case IXST_UNKNOWN: - return; - } -} - -void RclMain::rebuildIndex() -{ - switch (m_indexerState) { - case IXST_UNKNOWN: - case IXST_RUNNINGMINE: - case IXST_RUNNINGNOTMINE: - return; //?? Should not have been called - case IXST_NOTRUNNING: - { - int rep = - QMessageBox::warning(0, tr("Erasing index"), - tr("Reset the index and start " - "from scratch ?"), - QMessageBox::Ok, - QMessageBox::Cancel, - QMessageBox::NoButton); - if (rep == QMessageBox::Ok) { - // Could also mean that no helpers are missing, but then we - // won't try to show a message anyway (which is what - // firstIndexing is used for) - string mhd; - m_firstIndexing = !theconfig->getMissingHelperDesc(mhd); - vector args; - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - args.push_back("-z"); - m_idxproc = new ExecCmd; - m_idxproc->startExec("recollindex", args, false, false); - } - } - break; - } -} - // Start a db query and set the reslist docsource void RclMain::startSearch(RefCntr sdata, bool issimple) { @@ -1077,580 +749,6 @@ void RclMain::resetSearch() emit searchReset(); } -// Open advanced search dialog. -void RclMain::showAdvSearchDialog() -{ - if (asearchform == 0) { - asearchform = new AdvSearch(0); - if (asearchform == 0) { - return; - } - connect(new QShortcut(quitKeySeq, asearchform), SIGNAL (activated()), - this, SLOT (fileExit())); - - connect(asearchform, - SIGNAL(startSearch(RefCntr, bool)), - this, SLOT(startSearch(RefCntr, bool))); - asearchform->show(); - } else { - // Close and reopen, in hope that makes us visible... - asearchform->close(); - asearchform->show(); - } -} - -void RclMain::showSpellDialog() -{ - if (spellform == 0) { - spellform = new SpellW(0); - connect(new QShortcut(quitKeySeq, spellform), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(spellform, SIGNAL(wordSelect(QString)), - sSearch, SLOT(addTerm(QString))); - spellform->show(); - } else { - // Close and reopen, in hope that makes us visible... - spellform->close(); - spellform->show(); - } -} - -void RclMain::showFragButs() -{ - if (fragbuts && fragbuts->isStale(0)) { - deleteZ(fragbuts); - } - if (fragbuts == 0) { - fragbuts = new FragButs(0); - if (fragbuts->ok()) { - fragbuts->show(); - connect(fragbuts, SIGNAL(fragmentsChanged()), - this, SLOT(onFragmentsChanged())); - } else { - delete fragbuts; - fragbuts = 0; - } - } else { - // Close and reopen, in hope that makes us visible... - fragbuts->close(); - fragbuts->show(); - } -} - -void RclMain::showIndexConfig() -{ - showIndexConfig(false); -} -void RclMain::execIndexConfig() -{ - showIndexConfig(true); -} -void RclMain::showIndexConfig(bool modal) -{ - LOGDEB(("showIndexConfig()\n")); - if (indexConfig == 0) { - indexConfig = new ConfIndexW(0, theconfig); - connect(new QShortcut(quitKeySeq, indexConfig), SIGNAL (activated()), - this, SLOT (fileExit())); - } else { - // Close and reopen, in hope that makes us visible... - indexConfig->close(); - indexConfig->reloadPanels(); - } - if (modal) { - indexConfig->exec(); - indexConfig->setModal(false); - } else { - indexConfig->show(); - } -} - -void RclMain::showIndexSched() -{ - showIndexSched(false); -} -void RclMain::execIndexSched() -{ - showIndexSched(true); -} -void RclMain::showIndexSched(bool modal) -{ - LOGDEB(("showIndexSched()\n")); - if (indexSched == 0) { - indexSched = new IdxSchedW(this); - connect(new QShortcut(quitKeySeq, indexSched), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(indexSched->cronCLB, SIGNAL(clicked()), - this, SLOT(execCronTool())); - if (theconfig && theconfig->isDefaultConfig()) { -#ifdef RCL_MONITOR - connect(indexSched->rtidxCLB, SIGNAL(clicked()), - this, SLOT(execRTITool())); -#else - indexSched->rtidxCLB->setEnabled(false); - indexSched->rtidxCLB->setToolTip(tr("Disabled because the real time indexer was not compiled in.")); -#endif - } else { - indexSched->rtidxCLB->setEnabled(false); - indexSched->rtidxCLB->setToolTip(tr("This configuration tool only works for the main index.")); - } - } else { - // Close and reopen, in hope that makes us visible... - indexSched->close(); - } - if (modal) { - indexSched->exec(); - indexSched->setModal(false); - } else { - indexSched->show(); - } -} - -void RclMain::showCronTool() -{ - showCronTool(false); -} -void RclMain::execCronTool() -{ - showCronTool(true); -} -void RclMain::showCronTool(bool modal) -{ - LOGDEB(("showCronTool()\n")); - if (cronTool == 0) { - cronTool = new CronToolW(0); - connect(new QShortcut(quitKeySeq, cronTool), SIGNAL (activated()), - this, SLOT (fileExit())); - } else { - // Close and reopen, in hope that makes us visible... - cronTool->close(); - } - if (modal) { - cronTool->exec(); - cronTool->setModal(false); - } else { - cronTool->show(); - } -} - -void RclMain::showRTITool() -{ - showRTITool(false); -} -void RclMain::execRTITool() -{ - showRTITool(true); -} -void RclMain::showRTITool(bool modal) -{ - LOGDEB(("showRTITool()\n")); - if (rtiTool == 0) { - rtiTool = new RTIToolW(0); - connect(new QShortcut(quitKeySeq, rtiTool), SIGNAL (activated()), - this, SLOT (fileExit())); - } else { - // Close and reopen, in hope that makes us visible... - rtiTool->close(); - } - if (modal) { - rtiTool->exec(); - rtiTool->setModal(false); - } else { - rtiTool->show(); - } -} - -void RclMain::showUIPrefs() -{ - if (uiprefs == 0) { - uiprefs = new UIPrefsDialog(this); - connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs())); - connect(this, SIGNAL(stemLangChanged(const QString&)), - uiprefs, SLOT(setStemLang(const QString&))); - } else { - // Close and reopen, in hope that makes us visible... - uiprefs->close(); - } - uiprefs->show(); -} - -void RclMain::showExtIdxDialog() -{ - if (uiprefs == 0) { - uiprefs = new UIPrefsDialog(this); - connect(new QShortcut(quitKeySeq, uiprefs), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(uiprefs, SIGNAL(uiprefsDone()), this, SLOT(setUIPrefs())); - } else { - // Close and reopen, in hope that makes us visible... - uiprefs->close(); - } - uiprefs->tabWidget->setCurrentIndex(3); - uiprefs->show(); -} - -void RclMain::showAboutDialog() -{ - string vstring = Rcl::version_string() + - string("
http://www.recoll.org") + - string("
http://www.xapian.org"); - QMessageBox::information(this, tr("About Recoll"), vstring.c_str()); -} - -void RclMain::showMissingHelpers() -{ - string miss; - if (!theconfig->getMissingHelperDesc(miss)) { - QMessageBox::information(this, "", tr("Indexing did not run yet")); - return; - } - QString msg = QString::fromUtf8("

") + - tr("External applications/commands needed for your file types " - "and not found, as stored by the last indexing pass in "); - msg += ""; - msg += QString::fromLocal8Bit(theconfig->getConfDir().c_str()); - msg += "/missing:

\n";
-    if (!miss.empty()) {
-	msg += QString::fromUtf8(miss.c_str());
-    } else {
-	msg += tr("No helpers found missing");
-    }
-    msg += "
"; - QMessageBox::information(this, tr("Missing helper programs"), msg); -} - -void RclMain::showActiveTypes() -{ - if (rcldb == 0) { - QMessageBox::warning(0, tr("Error"), - tr("Index not open"), - QMessageBox::Ok, - QMessageBox::NoButton); - return; - } - - // All mime types in index. - vector vdbtypes; - if (!rcldb->getAllDbMimeTypes(vdbtypes)) { - QMessageBox::warning(0, tr("Error"), - tr("Index query error"), - QMessageBox::Ok, - QMessageBox::NoButton); - return; - } - set mtypesfromdb; - mtypesfromdb.insert(vdbtypes.begin(), vdbtypes.end()); - - // All types listed in mimeconf: - vector mtypesfromconfig = theconfig->getAllMimeTypes(); - - // Intersect file system types with config types (those not in the - // config can be indexed by name, not by content) - set mtypesfromdbconf; - for (vector::const_iterator it = mtypesfromconfig.begin(); - it != mtypesfromconfig.end(); it++) { - if (mtypesfromdb.find(*it) != mtypesfromdb.end()) - mtypesfromdbconf.insert(*it); - } - - // Substract the types for missing helpers (the docs are indexed - // by name only): - string miss; - if (theconfig->getMissingHelperDesc(miss) && !miss.empty()) { - FIMissingStore st(miss); - map >::const_iterator it; - for (it = st.m_typesForMissing.begin(); - it != st.m_typesForMissing.end(); it++) { - set::const_iterator it1; - for (it1 = it->second.begin(); - it1 != it->second.end(); it1++) { - set::iterator it2 = mtypesfromdbconf.find(*it1); - if (it2 != mtypesfromdbconf.end()) - mtypesfromdbconf.erase(it2); - } - } - } - ListDialog dialog; - dialog.setWindowTitle(tr("Indexed MIME Types")); - - // Turn the result into a string and display - dialog.groupBox->setTitle(tr("Content has been indexed for these mime types:")); - - // We replace the list with an editor so that the user can copy/paste - delete dialog.listWidget; - QTextEdit *editor = new QTextEdit(dialog.groupBox); - editor->setReadOnly(true); - dialog.horizontalLayout->addWidget(editor); - - for (set::const_iterator it = mtypesfromdbconf.begin(); - it != mtypesfromdbconf.end(); it++) { - editor->append(QString::fromUtf8(it->c_str())); - } - editor->moveCursor(QTextCursor::Start); - editor->ensureCursorVisible(); - dialog.exec(); -} - -// If a preview (toplevel) window gets closed by the user, we need to -// clean up because there is no way to reopen it. And check the case -// where the current one is closed -void RclMain::previewClosed(Preview *w) -{ - LOGDEB(("RclMain::previewClosed(%p)\n", w)); - if (w == curPreview) { - LOGDEB(("Active preview closed\n")); - curPreview = 0; - } else { - LOGDEB(("Old preview closed\n")); - } - delete w; -} - -// Document up to date check. The main problem we try to solve is -// displaying the wrong message from a compacted mail folder. -// -// Also we should re-run the query after updating the index because -// the ipaths may be wrong in the current result list. For now, the -// user does this by clicking search again once the indexing is done -// -// We only do this for the main index, else jump and prey (cant update -// anyway, even the makesig() call might not make sense for our base -// config) -bool RclMain::containerUpToDate(Rcl::Doc& doc) -{ - // If ipath is empty, we decide we don't care. Also, we need an index, - if (doc.ipath.empty() || rcldb == 0) - return true; - - string udi; - doc.getmeta(Rcl::Doc::keyudi, &udi); - if (udi.empty()) { - // Whatever... - return true; - } - - string sig; - if (!FileInterner::makesig(theconfig, doc, sig)) { - QMessageBox::warning(0, "Recoll", tr("Can't access file: ") + - QString::fromLocal8Bit(doc.url.c_str())); - // Let's try the preview anyway... - return true; - } - - if (!rcldb->needUpdate(udi, sig)) { - // Alles ist in ordnung - return true; - } - - // We can only run indexing on the main index (dbidx 0) - bool ismainidx = rcldb->whatDbIdx(doc) == 0; - // Indexer already running? - bool ixnotact = (m_indexerState == IXST_NOTRUNNING); - - QString msg = tr("Index not up to date for this file. " - "Refusing to risk showing the wrong entry. "); - if (ixnotact && ismainidx) { - msg += tr("Click Ok to update the " - "index for this file, then you will need to " - "re-run the query when indexing is done. "); - } else if (ismainidx) { - msg += tr("The indexer is running so things should " - "improve when it's done. "); - } else if (ixnotact) { - // Not main index - msg += tr("The document belongs to an external index" - "which I can't update. "); - } - msg += tr("Click Cancel to return to the list. " - "Click Ignore to show the preview anyway. "); - - QMessageBox::StandardButtons bts = - QMessageBox::Ignore | QMessageBox::Cancel; - - if (ixnotact &&ismainidx) - bts |= QMessageBox::Ok; - - int rep = - QMessageBox::warning(0, tr("Warning"), msg, bts, - (ixnotact && ismainidx) ? - QMessageBox::Cancel : QMessageBox::NoButton); - - if (m_indexerState == IXST_NOTRUNNING && rep == QMessageBox::Ok) { - LOGDEB(("Requesting index update for %s\n", doc.url.c_str())); - vector docs(1, doc); - updateIdxForDocs(docs); - } - if (rep != QMessageBox::Ignore) - return false; - return true; -} - -/** - * Open a preview window for a given document, or load it into new tab of - * existing window. - * - * @param docnum db query index - * @param mod keyboards modifiers like ControlButton, ShiftButton - */ -void RclMain::startPreview(int docnum, Rcl::Doc doc, int mod) -{ - LOGDEB(("startPreview(%d, doc, %d)\n", docnum, mod)); - - if (!containerUpToDate(doc)) - return; - - // Do the zeitgeist thing - zg_send_event(ZGSEND_PREVIEW, doc); - - if (mod & Qt::ShiftModifier) { - // User wants new preview window - curPreview = 0; - } - if (curPreview == 0) { - HighlightData hdata; - m_source->getTerms(hdata); - curPreview = new Preview(reslist->listId(), hdata); - - if (curPreview == 0) { - QMessageBox::warning(0, tr("Warning"), - tr("Can't create preview window"), - QMessageBox::Ok, - QMessageBox::NoButton); - return; - } - connect(new QShortcut(quitKeySeq, curPreview), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(curPreview, SIGNAL(previewClosed(Preview *)), - this, SLOT(previewClosed(Preview *))); - connect(curPreview, SIGNAL(wordSelect(QString)), - sSearch, SLOT(addTerm(QString))); - connect(curPreview, SIGNAL(showNext(Preview *, int, int)), - this, SLOT(previewNextInTab(Preview *, int, int))); - connect(curPreview, SIGNAL(showPrev(Preview *, int, int)), - this, SLOT(previewPrevInTab(Preview *, int, int))); - connect(curPreview, SIGNAL(previewExposed(Preview *, int, int)), - this, SLOT(previewExposed(Preview *, int, int))); - connect(curPreview, SIGNAL(saveDocToFile(Rcl::Doc)), - this, SLOT(saveDocToFile(Rcl::Doc))); - curPreview->setWindowTitle(getQueryDescription()); - curPreview->show(); - } - curPreview->makeDocCurrent(doc, docnum); -} - -void RclMain::updateIdxForDocs(vector& docs) -{ - if (m_idxproc) { - QMessageBox::warning(0, tr("Warning"), - tr("Can't update index: indexer running"), - QMessageBox::Ok, - QMessageBox::NoButton); - return; - } - - vector paths; - if (ConfIndexer::docsToPaths(docs, paths)) { - vector args; - args.push_back("-c"); - args.push_back(theconfig->getConfDir()); - args.push_back("-e"); - args.push_back("-i"); - args.insert(args.end(), paths.begin(), paths.end()); - m_idxproc = new ExecCmd; - m_idxproc->startExec("recollindex", args, false, false); - fileToggleIndexingAction->setText(tr("Stop &Indexing")); - } - fileToggleIndexingAction->setEnabled(false); - fileRetryFailedAction->setEnabled(false); -} - -/** - * Open a preview window for a given document, no linking to result list - * - * This is used to show ie parent documents, which have no corresponding - * entry in the result list. - * - */ -void RclMain::startPreview(Rcl::Doc doc) -{ - Preview *preview = new Preview(0, HighlightData()); - if (preview == 0) { - QMessageBox::warning(0, tr("Warning"), - tr("Can't create preview window"), - QMessageBox::Ok, - QMessageBox::NoButton); - return; - } - connect(new QShortcut(quitKeySeq, preview), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(preview, SIGNAL(wordSelect(QString)), - sSearch, SLOT(addTerm(QString))); - // Do the zeitgeist thing - zg_send_event(ZGSEND_PREVIEW, doc); - preview->show(); - preview->makeDocCurrent(doc, 0); -} - -// Show next document from result list in current preview tab -void RclMain::previewNextInTab(Preview * w, int sid, int docnum) -{ - previewPrevOrNextInTab(w, sid, docnum, true); -} - -// Show previous document from result list in current preview tab -void RclMain::previewPrevInTab(Preview * w, int sid, int docnum) -{ - previewPrevOrNextInTab(w, sid, docnum, false); -} - -// Combined next/prev from result list in current preview tab -void RclMain::previewPrevOrNextInTab(Preview * w, int sid, int docnum, bool nxt) -{ - LOGDEB(("RclMain::previewNextInTab sid %d docnum %d, listId %d\n", - sid, docnum, reslist->listId())); - - if (w == 0) // ?? - return; - - if (sid != reslist->listId()) { - QMessageBox::warning(0, "Recoll", - tr("This search is not active any more")); - return; - } - - if (nxt) - docnum++; - else - docnum--; - if (docnum < 0 || m_source.isNull() || docnum >= m_source->getResCnt()) { - QApplication::beep(); - return; - } - - Rcl::Doc doc; - if (!reslist->getDoc(docnum, doc)) { - QMessageBox::warning(0, "Recoll", - tr("Cannot retrieve document info from database")); - return; - } - - w->makeDocCurrent(doc, docnum, true); -} - -// Preview tab exposed: if the preview comes from the currently -// displayed result list, tell reslist (to color the paragraph) -void RclMain::previewExposed(Preview *, int sid, int docnum) -{ - LOGDEB2(("RclMain::previewExposed: sid %d docnum %d, m_sid %d\n", - sid, docnum, reslist->listId())); - if (sid != reslist->listId()) { - return; - } - reslist->previewExposed(docnum); -} - void RclMain::onSortCtlChanged() { if (m_sortspecnochange) @@ -1775,67 +873,6 @@ void RclMain::saveDocToFile(Rcl::Doc doc) } } -/* Look for html browser. We make a special effort for html because it's - * used for reading help */ -static bool lookForHtmlBrowser(string &exefile) -{ - static const char *htmlbrowserlist = - "opera google-chrome konqueror firefox mozilla netscape epiphany"; - vector blist; - stringToTokens(htmlbrowserlist, blist, " "); - - const char *path = getenv("PATH"); - if (path == 0) - path = "/bin:/usr/bin:/usr/bin/X11:/usr/X11R6/bin:/usr/local/bin"; - - // Look for each browser - for (vector::const_iterator bit = blist.begin(); - bit != blist.end(); bit++) { - if (ExecCmd::which(*bit, exefile, path)) - return true; - } - exefile.clear(); - return false; -} - -void RclMain::newDupsW(const Rcl::Doc, const vector dups) -{ - ListDialog dialog; - dialog.setWindowTitle(tr("Duplicate documents")); - - dialog.groupBox->setTitle(tr("These Urls ( | ipath) share the same" - " content:")); - // We replace the list with an editor so that the user can copy/paste - delete dialog.listWidget; - QTextEdit *editor = new QTextEdit(dialog.groupBox); - editor->setReadOnly(true); - dialog.horizontalLayout->addWidget(editor); - - for (vector::const_iterator it = dups.begin(); - it != dups.end(); it++) { - if (it->ipath.empty()) - editor->append(QString::fromLocal8Bit(it->url.c_str())); - else - editor->append(QString::fromLocal8Bit(it->url.c_str()) + " | " + - QString::fromUtf8(it->ipath.c_str())); - } - editor->moveCursor(QTextCursor::Start); - editor->ensureCursorVisible(); - dialog.exec(); -} - -void RclMain::showSnippets(Rcl::Doc doc) -{ - SnippetsW *sp = new SnippetsW(doc, m_source); - connect(sp, SIGNAL(startNativeViewer(Rcl::Doc, int, QString)), - this, SLOT(startNativeViewer(Rcl::Doc, int, QString))); - connect(new QShortcut(quitKeySeq, sp), SIGNAL (activated()), - this, SLOT (fileExit())); - connect(new QShortcut(closeKeySeq, sp), SIGNAL (activated()), - sp, SLOT (close())); - sp->show(); -} - void RclMain::showSubDocs(Rcl::Doc doc) { LOGDEB(("RclMain::showSubDocs\n")); @@ -1857,336 +894,12 @@ void RclMain::showSubDocs(Rcl::Doc doc) source(new DocSource(theconfig, RefCntr(src))); ResTable *res = new ResTable(); -// ResList *res = new ResList(); res->setRclMain(this, false); res->setDocSource(source); res->readDocSource(); res->show(); } -void RclMain::openWith(Rcl::Doc doc, string cmdspec) -{ - LOGDEB(("RclMain::openWith: %s\n", cmdspec.c_str())); - - // Split the command line - vector lcmd; - if (!stringToStrings(cmdspec, lcmd)) { - QMessageBox::warning(0, "Recoll", - tr("Bad desktop app spec for %1: [%2]\n" - "Please check the desktop file") - .arg(QString::fromUtf8(doc.mimetype.c_str())) - .arg(QString::fromLocal8Bit(cmdspec.c_str()))); - return; - } - - // Look for the command to execute in the exec path and the filters - // directory - string execname = lcmd.front(); - lcmd.erase(lcmd.begin()); - string url = doc.url; - string fn = fileurltolocalpath(doc.url); - - // Try to keep the letters used more or less consistent with the reslist - // paragraph format. - map subs; - subs["F"] = fn; - subs["f"] = fn; - subs["U"] = url; - subs["u"] = url; - - execViewer(subs, false, execname, lcmd, cmdspec, doc); -} - -void RclMain::startNativeViewer(Rcl::Doc doc, int pagenum, QString term) -{ - string apptag; - doc.getmeta(Rcl::Doc::keyapptg, &apptag); - LOGDEB(("RclMain::startNativeViewer: mtype [%s] apptag [%s] page %d " - "term [%s] url [%s] ipath [%s]\n", - doc.mimetype.c_str(), apptag.c_str(), pagenum, - (const char *)(term.toUtf8()), doc.url.c_str(), doc.ipath.c_str() - )); - - // Look for appropriate viewer - string cmdplusattr = theconfig->getMimeViewerDef(doc.mimetype, apptag, - prefs.useDesktopOpen); - if (cmdplusattr.empty()) { - QMessageBox::warning(0, "Recoll", - tr("No external viewer configured for mime type [") - + doc.mimetype.c_str() + "]"); - return; - } - - // Separate command string and viewer attributes (if any) - ConfSimple viewerattrs; - string cmd; - theconfig->valueSplitAttributes(cmdplusattr, cmd, viewerattrs); - bool ignoreipath = false; - if (viewerattrs.get("ignoreipath", cmdplusattr)) - ignoreipath = stringToBool(cmdplusattr); - - // Split the command line - vector lcmd; - if (!stringToStrings(cmd, lcmd)) { - QMessageBox::warning(0, "Recoll", - tr("Bad viewer command line for %1: [%2]\n" - "Please check the mimeview file") - .arg(QString::fromUtf8(doc.mimetype.c_str())) - .arg(QString::fromLocal8Bit(cmd.c_str()))); - return; - } - - // Look for the command to execute in the exec path and the filters - // directory - string execpath; - if (!ExecCmd::which(lcmd.front(), execpath)) { - execpath = theconfig->findFilter(lcmd.front()); - // findFilter returns its input param if the filter is not in - // the normal places. As we already looked in the path, we - // have no use for a simple command name here (as opposed to - // mimehandler which will just let execvp do its thing). Erase - // execpath so that the user dialog will be started further - // down. - if (!execpath.compare(lcmd.front())) - execpath.erase(); - - // Specialcase text/html because of the help browser need - if (execpath.empty() && !doc.mimetype.compare("text/html") && - apptag.empty()) { - if (lookForHtmlBrowser(execpath)) { - lcmd.clear(); - lcmd.push_back(execpath); - lcmd.push_back("%u"); - } - } - } - - // Command not found: start the user dialog to help find another one: - if (execpath.empty()) { - QString mt = QString::fromUtf8(doc.mimetype.c_str()); - QString message = tr("The viewer specified in mimeview for %1: %2" - " is not found.\nDo you want to start the " - " preferences dialog ?") - .arg(mt).arg(QString::fromLocal8Bit(lcmd.front().c_str())); - - switch(QMessageBox::warning(0, "Recoll", message, - "Yes", "No", 0, 0, 1)) { - case 0: - showUIPrefs(); - if (uiprefs) - uiprefs->showViewAction(mt); - break; - case 1: - break; - } - // The user will have to click on the link again to try the - // new command. - return; - } - // Get rid of the command name. lcmd is now argv[1...n] - lcmd.erase(lcmd.begin()); - - // Process the command arguments to determine if we need to create - // a temporary file. - - // If the command has a %i parameter it will manage the - // un-embedding. Else if ipath is not empty, we need a temp file. - // This can be overridden with the "ignoreipath" attribute - bool groksipath = (cmd.find("%i") != string::npos) || ignoreipath; - - // wantsfile: do we actually need a local file ? The only other - // case here is an url %u (ie: for web history). - bool wantsfile = cmd.find("%f") != string::npos && urlisfileurl(doc.url); - bool wantsparentfile = cmd.find("%F") != string::npos && - urlisfileurl(doc.url); - - if (wantsfile && wantsparentfile) { - QMessageBox::warning(0, "Recoll", - tr("Viewer command line for %1 specifies both " - "file and parent file value: unsupported") - .arg(QString::fromUtf8(doc.mimetype.c_str()))); - return; - } - - string url = doc.url; - string fn = fileurltolocalpath(doc.url); - Rcl::Doc pdoc; - if (wantsparentfile) { - // We want the path for the parent document. For example to - // open the chm file, not the internal page. Note that we just - // override the other file name in this case. - if (m_source.isNull() || !m_source->getEnclosing(doc, pdoc)) { - QMessageBox::warning(0, "Recoll", - tr("Cannot find parent document")); - return; - } - // Override fn with the parent's : - fn = fileurltolocalpath(pdoc.url); - - // If the parent document has an ipath too, we need to create - // a temp file even if the command takes an ipath - // parameter. We have no viewer which could handle a double - // embedding. Will have to change if such a one appears. - if (!pdoc.ipath.empty()) { - groksipath = false; - } - } - - bool istempfile = false; - - LOGDEB(("RclMain::startNV: groksipath %d wantsf %d wantsparentf %d\n", - groksipath, wantsfile, wantsparentfile)); - - // If the command wants a file but this is not a file url, or - // there is an ipath that it won't understand, we need a temp file: - theconfig->setKeyDir(path_getfather(fn)); - if (((wantsfile || wantsparentfile) && fn.empty()) || - (!groksipath && !doc.ipath.empty())) { - TempFile temp; - Rcl::Doc& thedoc = wantsparentfile ? pdoc : doc; - if (!FileInterner::idocToFile(temp, string(), theconfig, thedoc)) { - QMessageBox::warning(0, "Recoll", - tr("Cannot extract document or create " - "temporary file")); - return; - } - istempfile = true; - rememberTempFile(temp); - fn = temp->filename(); - url = string("file://") + fn; - } - - // If using an actual file, check that it exists, and if it is - // compressed, we may need an uncompressed version - if (!fn.empty() && theconfig->mimeViewerNeedsUncomp(doc.mimetype)) { - if (access(fn.c_str(), R_OK) != 0) { - QMessageBox::warning(0, "Recoll", - tr("Can't access file: ") + - QString::fromLocal8Bit(fn.c_str())); - return; - } - TempFile temp; - if (FileInterner::isCompressed(fn, theconfig)) { - if (!FileInterner::maybeUncompressToTemp(temp, fn, theconfig, - doc)) { - QMessageBox::warning(0, "Recoll", - tr("Can't uncompress file: ") + - QString::fromLocal8Bit(fn.c_str())); - return; - } - } - if (!temp.isNull()) { - rememberTempFile(temp); - fn = temp->filename(); - url = string("file://") + fn; - } - } - - // If we are not called with a page number (which would happen for a call - // from the snippets window), see if we can compute a page number anyway. - if (pagenum == -1) { - pagenum = 1; - string lterm; - if (m_source.isNotNull()) - pagenum = m_source->getFirstMatchPage(doc, lterm); - if (pagenum == -1) - pagenum = 1; - else // We get the match term used to compute the page - term = QString::fromUtf8(lterm.c_str()); - } - char cpagenum[20]; - sprintf(cpagenum, "%d", pagenum); - - - // Substitute %xx inside arguments - string efftime; - if (!doc.dmtime.empty() || !doc.fmtime.empty()) { - efftime = doc.dmtime.empty() ? doc.fmtime : doc.dmtime; - } else { - efftime = "0"; - } - // Try to keep the letters used more or less consistent with the reslist - // paragraph format. - map subs; - subs["D"] = efftime; - subs["f"] = fn; - subs["F"] = fn; - subs["i"] = FileInterner::getLastIpathElt(doc.ipath); - subs["M"] = doc.mimetype; - subs["p"] = cpagenum; - subs["s"] = (const char*)term.toLocal8Bit(); - subs["U"] = url; - subs["u"] = url; - // Let %(xx) access all metadata. - for (map::const_iterator it = doc.meta.begin(); - it != doc.meta.end(); it++) { - subs[it->first] = it->second; - } - execViewer(subs, istempfile, execpath, lcmd, cmd, doc); -} - -void RclMain::execViewer(const map& subs, bool istempfile, - const string& execpath, - const vector& _lcmd, const string& cmd, - Rcl::Doc doc) -{ - string ncmd; - vector lcmd; - for (vector::const_iterator it = _lcmd.begin(); - it != _lcmd.end(); it++) { - pcSubst(*it, ncmd, subs); - LOGDEB(("%s->%s\n", it->c_str(), ncmd.c_str())); - lcmd.push_back(ncmd); - } - - // Also substitute inside the unsplitted command line and display - // in status bar - pcSubst(cmd, ncmd, subs); - ncmd += " &"; - QStatusBar *stb = statusBar(); - if (stb) { - string fcharset = theconfig->getDefCharset(true); - string prcmd; - transcode(ncmd, prcmd, fcharset, "UTF-8"); - QString msg = tr("Executing: [") + - QString::fromUtf8(prcmd.c_str()) + "]"; - stb->showMessage(msg, 10000); - } - - if (!istempfile) - historyEnterDoc(g_dynconf, doc.meta[Rcl::Doc::keyudi]); - - // Do the zeitgeist thing - zg_send_event(ZGSEND_OPEN, doc); - - // We keep pushing back and never deleting. This can't be good... - ExecCmd *ecmd = new ExecCmd; - m_viewers.push_back(ecmd); - ecmd->startExec(execpath, lcmd, false, false); -} - -void RclMain::startManual() -{ - startManual(string()); -} - -void RclMain::startManual(const string& index) -{ - Rcl::Doc doc; - doc.url = "file://"; - doc.url = path_cat(doc.url, theconfig->getDatadir()); - doc.url = path_cat(doc.url, "doc"); - doc.url = path_cat(doc.url, "usermanual.html"); - LOGDEB(("RclMain::startManual: help index is %s\n", - index.empty()?"(null)":index.c_str())); - if (!index.empty()) { - doc.url += "#"; - doc.url += index; - } - doc.mimetype = "text/html"; - startNativeViewer(doc); -} - // Search for document 'like' the selected one. We ask rcldb/xapian to find // significant terms, and add them to the simple search entry. void RclMain::docExpand(Rcl::Doc doc) @@ -2359,7 +1072,6 @@ void RclMain::setFiltSpec() initiateQuery(); } - void RclMain::onFragmentsChanged() { setFiltSpec(); diff --git a/src/qtgui/recoll.pro.in b/src/qtgui/recoll.pro.in index a2ccfb9b..13f9c0c8 100644 --- a/src/qtgui/recoll.pro.in +++ b/src/qtgui/recoll.pro.in @@ -52,7 +52,11 @@ SOURCES += \ ptrans_w.cpp \ rclhelp.cpp \ rclmain_w.cpp \ + rclm_idx.cpp \ + rclm_preview.cpp \ rclm_saveload.cpp \ + rclm_view.cpp \ + rclm_wins.cpp \ rclzg.cpp \ respopup.cpp \ reslist.cpp \ diff --git a/src/query/docseqdb.h b/src/query/docseqdb.h index 44bf9c7b..a1304ea8 100644 --- a/src/query/docseqdb.h +++ b/src/query/docseqdb.h @@ -40,7 +40,7 @@ class DocSequenceDb : public DocSequence { virtual int getFirstMatchPage(Rcl::Doc&, std::string& term); virtual bool docDups(const Rcl::Doc& doc, std::vector& dups); virtual string getDescription(); - virtual list expand(Rcl::Doc &doc); + virtual std::list expand(Rcl::Doc &doc); virtual bool canFilter() {return true;} virtual bool setFiltSpec(const DocSeqFiltSpec &filtspec); virtual bool canSort() {return true;} diff --git a/src/query/docseqhist.h b/src/query/docseqhist.h index 5292a016..b176c776 100644 --- a/src/query/docseqhist.h +++ b/src/query/docseqhist.h @@ -58,9 +58,9 @@ private: RclDynConf *m_hist; int m_prevnum; long m_prevtime; - string m_description; // This is just an nls translated 'doc history' - list m_hlist; - list::const_iterator m_it; + std::string m_description; // This is just an nls translated 'doc history' + std::list m_hlist; + std::list::const_iterator m_it; }; extern bool historyEnterDoc(RclDynConf *dncf, const string& udi);