Switch to side-by-side view

--- a/src/qtgui/restable.cpp
+++ b/src/qtgui/restable.cpp
@@ -6,11 +6,14 @@
 
 #include <stdlib.h>
 #include <time.h>
+
+#include <algorithm>
 
 #include <Qt>
 #include <QShortcut>
 #include <QAbstractTableModel>
 #include <QSettings>
+#include <QMenu>
 
 #include "refcntr.h"
 #include "docseq.h"
@@ -23,7 +26,7 @@
 #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 @@
 //////////////////////////////////////////////
 //// 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<string, string>::const_iterator it = doc.meta.find(fld);
@@ -107,18 +113,60 @@
     return datebuf;
 }
 
+// Static map to translate from internal column names to displayable ones
+map<string, string> RecollModel::o_displayableFields = 
+    create_map<string, string>
+    ("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<string>& stored = config->getStoredFields();
+	for (set<string>::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]));
     }
 }
 
@@ -134,6 +182,12 @@
 {
     LOGDEB2(("RecollModel::columnCount\n"));
     return m_fields.size();
+}
+
+void RecollModel::readDocSource()
+{
+    beginResetModel();
+    endResetModel();
 }
 
 void RecollModel::setDocSource(RefCntr<DocSequence> nsource)
@@ -143,16 +197,37 @@
 	m_source = RefCntr<DocSequence>();
     else
 	m_source = RefCntr<DocSequence>(new DocSource(nsource));
-    beginResetModel();
-    endResetModel();
-}
-
-bool RecollModel::getdoc(int index, Rcl::Doc &doc)
-{
-    LOGDEB(("RecollModel::getDoc\n"));
-    if (m_source.isNull())
-	return false;
-    return m_source->getDoc(index, doc);
+    readDocSource();
+}
+
+void RecollModel::deleteColumn(int col)
+{
+    if (col > 0 && col < int(m_fields.size())) {
+	vector<string>::iterator it = m_fields.begin();
+	it += col;
+	m_fields.erase(it);
+	vector<FieldGetter*>::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<string>::iterator it = m_fields.begin();
+	vector<FieldGetter*>::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 @@
     }
     if (orientation == Qt::Horizontal && role == Qt::DisplayRole &&
 	idx < int(m_fields.size())) {
-	return QString::fromUtf8(m_fields[idx].c_str());
+	map<string, string>::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 @@
 {
     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,11 +283,10 @@
 	    spec.field = "mtime";
 	spec.desc = order == Qt::AscendingOrder ? false : true;
 	m_source->setSortSpec(spec);
-	setDocSource(m_source);
+	readDocSource();
 	emit sortDataChanged(spec);
     }
 }
-
 
 /////////////////////////// 
 // ResTable panel methods
@@ -229,9 +308,13 @@
 	}
 	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::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<int> 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<string>& 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 @@
     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,34 +423,25 @@
     setDocSource(RefCntr<DocSequence>());
 }
 
-void ResTable::saveColWidths()
-{
-    LOGDEB(("ResTable::saveColWidths()\n"));
+// This is called when the sort order is changed from another widget
+void ResTable::onSortDataChanged(DocSeqSortSpec)
+{
     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)
-{
-    QHeaderView *header = tableView->horizontalHeader();
-    if (!header)
-	return;
     header->setSortIndicator(-1, Qt::AscendingOrder);
 }
 
 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 @@
     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 @@
 	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<string, string>& allfields = m_model->getAllFields();
+    const vector<string>& fields = m_model->getFields();
+    QMenu *popup = new QMenu(this);
+    popup->addAction(tr("&Delete column"), this, SLOT(deleteColumn()));
+    QAction *act;
+    for (map<string, string>::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);
+}