diff --git a/src/qtgui/guiutils.cpp b/src/qtgui/guiutils.cpp index 9cece549..86127d87 100644 --- a/src/qtgui/guiutils.cpp +++ b/src/qtgui/guiutils.cpp @@ -56,17 +56,6 @@ const char *v114reslistformat = "" "%A %K"; -bool getStemLangs(list& langs) -{ - string reason; - if (!maybeOpenDb(reason)) { - LOGERR(("getStemLangs: %s\n", reason.c_str())); - return false; - } - langs = rcldb->getStemLangs(); - return true; -} - // The global preferences structure PrefsPack prefs; @@ -77,8 +66,8 @@ PrefsPack prefs; if (writing) { \ settings.setValue(nm , var); \ } else { \ - var = settings.value(nm, def).to##tp \ - (); \ + var = settings.value(nm, def).to##tp \ + (); \ } /** diff --git a/src/qtgui/guiutils.h b/src/qtgui/guiutils.h index 7742ee0c..6fd42652 100644 --- a/src/qtgui/guiutils.h +++ b/src/qtgui/guiutils.h @@ -49,13 +49,6 @@ using std::list; using std::vector; #endif -/** Retrieve configured stemming languages */ -bool getStemLangs(list& langs); - -/** Start a browser on the help document */ -extern bool startHelpBrowser(const string& url = ""); - - /** Holder for preferences (gets saved to user Qt prefs) */ class PrefsPack { public: diff --git a/src/qtgui/main.cpp b/src/qtgui/main.cpp index be0155cf..b05ac7d7 100644 --- a/src/qtgui/main.cpp +++ b/src/qtgui/main.cpp @@ -96,6 +96,17 @@ bool maybeOpenDb(string &reason, bool force) return true; } +bool getStemLangs(list& langs) +{ + string reason; + if (!maybeOpenDb(reason)) { + LOGERR(("getStemLangs: %s\n", reason.c_str())); + return false; + } + langs = rcldb->getStemLangs(); + return true; +} + static void recollCleanup() { LOGDEB(("recollCleanup: writing settings\n")); diff --git a/src/qtgui/rclmain_w.cpp b/src/qtgui/rclmain_w.cpp index f62727f3..f02b9bd6 100644 --- a/src/qtgui/rclmain_w.cpp +++ b/src/qtgui/rclmain_w.cpp @@ -456,7 +456,7 @@ void RclMain::fileExit() prefs.mainwidth = width(); prefs.mainheight = height(); } - restable->saveSizeState(); + restable->saveColState(); prefs.ssearchTyp = sSearch->searchTypCMB->currentIndex(); if (asearchform) @@ -923,8 +923,8 @@ void RclMain::saveDocToFile(Rcl::Doc doc) { QString s = QFileDialog::getSaveFileName(this, //parent - tr("Save file"), // caption - QString::fromLocal8Bit(path_home().c_str()) //dir + tr("Save file"), + QString::fromLocal8Bit(path_home().c_str()) ); string tofile((const char *)s.toLocal8Bit()); TempFile temp; // not used @@ -1295,7 +1295,9 @@ void RclMain::showQueryDetails() if (m_source.isNull()) return; string oq = breakIntoLines(m_source->getDescription(), 100, 50); - QString desc = tr("Query details") + ": " + QString::fromUtf8(oq.c_str()); + QString str; + QString desc = tr("Result count (est.)") + ": " + str.setNum(m_source->getResCnt()) + "
"; + desc += tr("Query details") + ": " + QString::fromUtf8(oq.c_str()); QMessageBox::information(this, tr("Query details"), desc); } diff --git a/src/qtgui/rclmain_w.h b/src/qtgui/rclmain_w.h index ebac3c36..854029ca 100644 --- a/src/qtgui/rclmain_w.h +++ b/src/qtgui/rclmain_w.h @@ -78,7 +78,7 @@ public slots: virtual void enableNextPage(bool); virtual void enablePrevPage(bool); virtual void docExpand(Rcl::Doc); - virtual void startPreview(int doc, Rcl::Doc doc, int keymods); + virtual void startPreview(int docnum, Rcl::Doc doc, int keymods); virtual void startPreview(Rcl::Doc); virtual void startNativeViewer(Rcl::Doc); virtual void saveDocToFile(Rcl::Doc); @@ -87,9 +87,7 @@ public slots: virtual void previewExposed(Preview *, int sid, int docnum); virtual void resetSearch(); virtual void eraseDocHistory(); - // Callback for setting the stemming language through the prefs menu virtual void setStemLang(QAction *id); - // Prefs menu about to show, set the checked lang entry virtual void adjustPrefsMenu(); virtual void catgFilter(int); virtual void initDbOpen(); diff --git a/src/qtgui/recoll.h b/src/qtgui/recoll.h index 89b7b9e8..3a75d88b 100644 --- a/src/qtgui/recoll.h +++ b/src/qtgui/recoll.h @@ -29,6 +29,9 @@ // Open the database if needed. We now force a close/open by default extern bool maybeOpenDb(std::string &reason, bool force = true); +/** Retrieve configured stemming languages */ +bool getStemLangs(list& langs); + extern RclConfig *rclconfig; extern Rcl::Db *rcldb; extern int recollNeedsExit; diff --git a/src/qtgui/restable.cpp b/src/qtgui/restable.cpp index fcc8eff9..73a75da6 100644 --- a/src/qtgui/restable.cpp +++ b/src/qtgui/restable.cpp @@ -7,10 +7,13 @@ static char rcsid[] = "@(#$Id: reslist.cpp,v 1.52 2008-12-17 15:12:08 dockes Exp #include #include +#include + #include #include #include #include +#include #include "refcntr.h" #include "docseq.h" @@ -23,7 +26,7 @@ static char rcsid[] = "@(#$Id: reslist.cpp,v 1.52 2008-12-17 15:12:08 dockes Exp #include "plaintorich.h" ////////////////////////////////// -// Restable "pager". We use it to display doc details in the detail area +// Restable "pager". We use it to display a single doc details in the detail area /// class ResTablePager : public ResListPager { public: @@ -68,6 +71,9 @@ string ResTablePager::iconPath(const string& mtype) ////////////////////////////////////////////// //// Data model methods //// + +// Routines used to extract named data from an Rcl::Doc. The basic one just uses the meta map. Others +// (ie: the date ones) need to do a little processing static string gengetter(const string& fld, const Rcl::Doc& doc) { map::const_iterator it = doc.meta.find(fld); @@ -107,18 +113,60 @@ static string datetimegetter(const string&, const Rcl::Doc& doc) return datebuf; } +// Static map to translate from internal column names to displayable ones +map RecollModel::o_displayableFields = + create_map + ("abstract", QT_TR_NOOP("Abstract")) + ("author", QT_TR_NOOP("Author")) + ("dbytes", QT_TR_NOOP("Document size")) + ("dmtime", QT_TR_NOOP("Document date")) + ("fbytes", QT_TR_NOOP("File size")) + ("filename", QT_TR_NOOP("File name")) + ("fmtime", QT_TR_NOOP("File date")) + ("ipath", QT_TR_NOOP(" Ipath")) + ("keywords", QT_TR_NOOP("Keywords")) + ("mtype", QT_TR_NOOP("Mime type")) + ("origcharset", QT_TR_NOOP("Original character set")) + ("relevancyrating", QT_TR_NOOP("Relevancy rating")) + ("title", QT_TR_NOOP("Title")) + ("url", QT_TR_NOOP("URL")) + ("mtime", QT_TR_NOOP("Mtime")) + ("date", QT_TR_NOOP("Date")) + ("datetime", QT_TR_NOOP("Date and time")) + ; + +FieldGetter *RecollModel::chooseGetter(const string& field) +{ + if (!stringlowercmp("date", field)) + return dategetter; + else if (!stringlowercmp("datetime", field)) + return datetimegetter; + else + return gengetter; +} + RecollModel::RecollModel(const QStringList fields, QObject *parent) : QAbstractTableModel(parent) { + // Add dynamic "stored" fields to the full column list. This + // could be protected to be done only once, but it's no real + // problem + RclConfig *config = RclConfig::getMainConfig(); + if (config) { + const set& stored = config->getStoredFields(); + for (set::const_iterator it = stored.begin(); + it != stored.end(); it++) { + if (o_displayableFields.find(*it) == o_displayableFields.end()) { + o_displayableFields[*it] = *it; + } + } + } + + // Construct the actual list of column names for (QStringList::const_iterator it = fields.begin(); it != fields.end(); it++) { m_fields.push_back((const char *)(it->toUtf8())); - if (!stringlowercmp("date", m_fields[m_fields.size()-1])) - m_getters.push_back(dategetter); - else if (!stringlowercmp("datetime", m_fields[m_fields.size()-1])) - m_getters.push_back(datetimegetter); - else - m_getters.push_back(gengetter); + m_getters.push_back(chooseGetter(m_fields[m_fields.size()-1])); } } @@ -136,6 +184,12 @@ int RecollModel::columnCount(const QModelIndex&) const return m_fields.size(); } +void RecollModel::readDocSource() +{ + beginResetModel(); + endResetModel(); +} + void RecollModel::setDocSource(RefCntr nsource) { LOGDEB(("RecollModel::setDocSource\n")); @@ -143,16 +197,37 @@ void RecollModel::setDocSource(RefCntr nsource) m_source = RefCntr(); else m_source = RefCntr(new DocSource(nsource)); - beginResetModel(); - endResetModel(); + readDocSource(); } -bool RecollModel::getdoc(int index, Rcl::Doc &doc) +void RecollModel::deleteColumn(int col) { - LOGDEB(("RecollModel::getDoc\n")); - if (m_source.isNull()) - return false; - return m_source->getDoc(index, doc); + if (col > 0 && col < int(m_fields.size())) { + vector::iterator it = m_fields.begin(); + it += col; + m_fields.erase(it); + vector::iterator it1 = m_getters.begin(); + it1 += col; + m_getters.erase(it1); + readDocSource(); + } +} + +void RecollModel::addColumn(int col, const string& field) +{ + LOGDEB(("AddColumn: col %d fld [%s]\n", col, field.c_str())); + if (col >= 0 && col < int(m_fields.size())) { + col++; + vector::iterator it = m_fields.begin(); + vector::iterator it1 = m_getters.begin(); + if (col) { + it += col; + it1 += col; + } + m_fields.insert(it, field); + m_getters.insert(it1, chooseGetter(field)); + readDocSource(); + } } QVariant RecollModel::headerData(int idx, Qt::Orientation orientation, @@ -164,7 +239,12 @@ QVariant RecollModel::headerData(int idx, Qt::Orientation orientation, } if (orientation == Qt::Horizontal && role == Qt::DisplayRole && idx < int(m_fields.size())) { - return QString::fromUtf8(m_fields[idx].c_str()); + map::const_iterator it = + o_displayableFields.find(m_fields[idx]); + if (it == o_displayableFields.end()) + return QString::fromUtf8(m_fields[idx].c_str()); + else + return QString::fromUtf8(it->second.c_str()); } return QVariant(); } @@ -195,7 +275,7 @@ void RecollModel::sort(int column, Qt::SortOrder order) { LOGDEB(("RecollModel::sort(%d, %d)\n", column, int(order))); - if (column >= 0 && column < int(m_fields.size())) { + if (m_source.isNotNull() && column >= 0 && column < int(m_fields.size())) { DocSeqSortSpec spec; spec.field = m_fields[column]; if (!stringlowercmp("date", spec.field) || @@ -203,12 +283,11 @@ void RecollModel::sort(int column, Qt::SortOrder order) spec.field = "mtime"; spec.desc = order == Qt::AscendingOrder ? false : true; m_source->setSortSpec(spec); - setDocSource(m_source); + readDocSource(); emit sortDataChanged(spec); } } - /////////////////////////// // ResTable panel methods void ResTable::init() @@ -229,9 +308,13 @@ void ResTable::init() } header->setSortIndicatorShown(true); header->setSortIndicator(-1, Qt::AscendingOrder); + header->setContextMenuPolicy(Qt::CustomContextMenu); connect(header, SIGNAL(sectionResized(int,int,int)), this, SLOT(saveColWidths())); + connect(header, SIGNAL(customContextMenuRequested(const QPoint&)), + this, SLOT(createHeaderPopupMenu(const QPoint&))); } + header->setMovable(true); header = tableView->verticalHeader(); if (header) { @@ -257,10 +340,45 @@ void ResTable::init() } -void ResTable::saveSizeState() +// This is called by rclmain_w prior to exiting +void ResTable::saveColState() { QSettings settings; settings.setValue("resTableSplitterSizes", splitter->saveState()); + + QHeaderView *header = tableView->horizontalHeader(); + if (header && header->sectionsMoved()) { + // Remember the current column order. Walk in visual order and + // create new list + QStringList newfields; + vector newwidths; + for (int vi = 0; vi < header->count(); vi++) { + int li = header->logicalIndex(vi); + newfields.push_back(prefs.restableFields.at(li)); + newwidths.push_back(header->sectionSize(li)); + } + prefs.restableFields = newfields; + prefs.restableColWidths = newwidths; + } else { + const vector& vf = m_model->getFields(); + prefs.restableFields.clear(); + for (int i = 0; i < int(vf.size()); i++) { + prefs.restableFields.push_back(QString::fromUtf8(vf[i].c_str())); + } + saveColWidths(); + } +} + +void ResTable::saveColWidths() +{ + LOGDEB(("ResTable::saveColWidths()\n")); + QHeaderView *header = tableView->horizontalHeader(); + if (!header) + return; + prefs.restableColWidths.clear(); + for (int i = 0; i < header->count(); i++) { + prefs.restableColWidths.push_back(header->sectionSize(i)); + } } void ResTable::onTableView_currentChanged(const QModelIndex& index) @@ -268,12 +386,12 @@ void ResTable::onTableView_currentChanged(const QModelIndex& index) LOGDEB(("ResTable::onTableView_currentChanged(%d, %d)\n", index.row(), index.column())); - if (!m_model || m_model->m_source.isNull()) + if (!m_model || m_model->getDocSource().isNull()) return; HiliteData hdata; - m_model->m_source->getTerms(hdata.terms, hdata.groups, hdata.gslks); + m_model->getDocSource()->getTerms(hdata.terms, hdata.groups, hdata.gslks); Rcl::Doc doc; - if (m_model->getdoc(index.row(), doc)) { + if (m_model->getDocSource()->getDoc(index.row(), doc)) { textBrowser->clear(); m_detaildocnum = index.row(); m_pager->displayDoc(index.row(), doc, hdata); @@ -305,18 +423,6 @@ void ResTable::resetSource() setDocSource(RefCntr()); } -void ResTable::saveColWidths() -{ - LOGDEB(("ResTable::saveColWidths()\n")); - QHeaderView *header = tableView->horizontalHeader(); - if (!header) - return; - prefs.restableColWidths.clear(); - for (int i = 0; i < header->count(); i++) { - prefs.restableColWidths.push_back(header->sectionSize(i)); - } -} - // This is called when the sort order is changed from another widget void ResTable::onSortDataChanged(DocSeqSortSpec) { @@ -328,11 +434,14 @@ void ResTable::onSortDataChanged(DocSeqSortSpec) void ResTable::readDocSource() { - m_model->setDocSource(m_model->m_source); + m_model->readDocSource(); + textBrowser->clear(); } void ResTable::linkWasClicked(const QUrl &url) { + if (!m_model || m_model->getDocSource().isNull()) + return; QString s = url.toString(); const char *ascurl = s.toAscii(); LOGDEB(("ResTable::linkWasClicked: [%s]\n", ascurl)); @@ -344,7 +453,7 @@ void ResTable::linkWasClicked(const QUrl &url) case 'E': { Rcl::Doc doc; - if (!m_model->getdoc(i, doc)) { + if (!m_model->getDocSource()->getDoc(i, doc)) { LOGERR(("ResTable::linkWasClicked: can't get doc for %d\n", i)); return; } @@ -359,3 +468,50 @@ void ResTable::linkWasClicked(const QUrl &url) break;// ?? } } + +void ResTable::createHeaderPopupMenu(const QPoint& pos) +{ + LOGDEB(("ResTable::createHeaderPopupMenu(%d, %d)\n", pos.x(), pos.y())); + QHeaderView *header = tableView->horizontalHeader(); + if (!header || !m_model) + return; + + m_popcolumn = header->logicalIndexAt(pos); + if (m_popcolumn < 0) + return; + + const map& allfields = m_model->getAllFields(); + const vector& fields = m_model->getFields(); + QMenu *popup = new QMenu(this); + popup->addAction(tr("&Delete column"), this, SLOT(deleteColumn())); + QAction *act; + for (map::const_iterator it = allfields.begin(); + it != allfields.end(); it++) { + if (std::find(fields.begin(), fields.end(), it->first) != fields.end()) + continue; + act = new QAction(tr("Add ") + tr(it->second.c_str()), popup); + act->setData(QString::fromUtf8(it->first.c_str())); + connect(act, SIGNAL(triggered(bool)), this , SLOT(addColumn())); + popup->addAction(act); + } + popup->popup(mapToGlobal(pos)); +} + +void ResTable::deleteColumn() +{ + if (m_model) + m_model->deleteColumn(m_popcolumn); +} + +void ResTable::addColumn() +{ + if (!m_model) + return; + QAction *action = (QAction *)sender(); + LOGDEB(("addColumn: text %s, data %s\n", + (const char *)action->text().toUtf8(), + (const char *)action->data().toString().toUtf8() + )); + string field((const char *)action->data().toString().toUtf8()); + m_model->addColumn(m_popcolumn, field); +} diff --git a/src/qtgui/restable.h b/src/qtgui/restable.h index 10340c02..75cc78af 100644 --- a/src/qtgui/restable.h +++ b/src/qtgui/restable.h @@ -34,19 +34,27 @@ class RecollModel : public QAbstractTableModel { public: RecollModel(const QStringList fields, QObject *parent = 0); + + // Reimplemented methods virtual int rowCount (const QModelIndex& = QModelIndex()) const; virtual int columnCount(const QModelIndex& = QModelIndex()) const; - virtual QVariant headerData (int col, - Qt::Orientation orientation, + virtual QVariant headerData (int col, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole ) const; virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + // Specific methods + virtual void readDocSource(); virtual void setDocSource(RefCntr nsource); - virtual bool getdoc(int index, Rcl::Doc &doc); - - friend class ResTable; + virtual RefCntr getDocSource() {return m_source;} + virtual void deleteColumn(int); + virtual const vector& getFields() {return m_fields;} + virtual const map& getAllFields() + { + return o_displayableFields; + } + virtual void addColumn(int, const string&); signals: void sortDataChanged(DocSeqSortSpec); @@ -55,6 +63,8 @@ private: mutable RefCntr m_source; vector m_fields; vector m_getters; + static map o_displayableFields; + FieldGetter* chooseGetter(const string&); }; class ResTablePager; @@ -75,7 +85,7 @@ public: virtual ~ResTable() {} virtual RecollModel *getModel() {return m_model;} - virtual void saveSizeState(); + virtual void saveColState(); public slots: virtual void onTableView_currentChanged(const QModelIndex&); @@ -86,6 +96,9 @@ public slots: virtual void readDocSource(); virtual void onSortDataChanged(DocSeqSortSpec); virtual void linkWasClicked(const QUrl&); + virtual void createHeaderPopupMenu(const QPoint&); + virtual void deleteColumn(); + virtual void addColumn(); signals: void docPreviewClicked(int, Rcl::Doc, int); @@ -97,6 +110,7 @@ private: RecollModel *m_model; ResTablePager *m_pager; int m_detaildocnum; + int m_popcolumn; }; diff --git a/src/rcldb/rcldb_p.h b/src/rcldb/rcldb_p.h index ab9524af..d07ca2d4 100644 --- a/src/rcldb/rcldb_p.h +++ b/src/rcldb/rcldb_p.h @@ -104,7 +104,14 @@ class Db::Native { inline const string& docfToDatf(const string& df) { static const string keycap("caption"); - return df.compare(Doc::keytt) ? df : keycap; + static const string keydmtime("dmtime"); + if (!df.compare(Doc::keytt)) { + return keycap; + } else if (!df.compare(Doc::keymt)) { + return keydmtime; + } else { + return df; + } } } diff --git a/src/rcldb/rclquery.cpp b/src/rcldb/rclquery.cpp index b5a84a76..e4d72aa1 100644 --- a/src/rcldb/rclquery.cpp +++ b/src/rcldb/rclquery.cpp @@ -19,6 +19,7 @@ static char rcsid[] = "@(#$Id: rclquery.cpp,v 1.11 2008-12-19 09:55:36 dockes Ex #include "smallut.h" #include "searchdata.h" #include "rclconfig.h" +#include "unacpp.h" #ifndef NO_NAMESPACES namespace Rcl { @@ -30,10 +31,7 @@ public: QSorter(const string& f) : m_fld(docfToDatf(f) + "=") { - m_ismtime = !m_fld.compare("mtime="); - if (m_ismtime) { - m_fld = "dmtime="; - } + m_ismtime = !m_fld.compare("dmtime="); } virtual std::string operator()(const Xapian::Document& xdoc) const @@ -61,7 +59,27 @@ public: i2 = data.find_first_of("\n\r", i1); if (i2 == string::npos) return string(); - return data.substr(i1, i2-i1); + + // Process data for better sorting. We should actually do the + // unicode thing + // (http://unicode.org/reports/tr10/#Introduction), but just + // removing accents and majuscules will remove the most + // glaring weirdnesses (or not, depending on your national + // approach to collating...) + string term = data.substr(i1, i2-i1); + string sortterm; + // We're not even sure the term is utf8 here (ie: url) + if (!unacmaybefold(term, sortterm, "UTF-8", true)) { + sortterm = term; + } + // Also remove some common uninteresting starting characters + i1 = sortterm.find_first_not_of(" \t\\\"([*+,"); + if (i1 != 0 && i1 != string::npos) { + sortterm = sortterm.substr(i1, sortterm.size()-i1); + } + + LOGDEB2(("QSorter: [%s] -> [%s]\n", term.c_str(), sortterm.c_str())); + return sortterm; } private: diff --git a/src/utils/smallut.h b/src/utils/smallut.h index ee8f89be..431348d0 100644 --- a/src/utils/smallut.h +++ b/src/utils/smallut.h @@ -186,4 +186,31 @@ struct TempBuf { #define deleteZ(X) {delete X;X = 0;} #endif +// Code for static initialization of an stl map. Somewhat like Boost.assign. +// Ref: http://stackoverflow.com/questions/138600/initializing-a-static-stdmapint-int-in-c +// Example use: map m = map_list_of (1,2) (3,4) (5,6) (7,8); + +template +class create_map +{ +private: + std::map m_map; +public: + create_map(const T& key, const U& val) + { + m_map[key] = val; + } + + create_map& operator()(const T& key, const U& val) + { + m_map[key] = val; + return *this; + } + + operator std::map() + { + return m_map; + } +}; + #endif /* _SMALLUT_H_INCLUDED_ */