Manitou-Mail logo title

Source file: src/preferences.cpp

/* Copyright (C) 2004-2013 Daniel Verite

   This file is part of Manitou-Mail (see http://www.manitou-mail.org)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2 as
   published by the Free Software Foundation.

   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 "preferences.h"
#include "helper.h"
#include "identities.h"
#include "app_config.h"

#include <QTabWidget>
#include <QComboBox>
#include <QLabel>
#include <QDir>
#include <QFileDialog>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QLayout>
#include <QCheckBox>
#include <QRadioButton>
#include <QSpinBox>
#include <QDialog>
#include <QApplication>
#include <QPushButton>
#include <QValidator>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QGridLayout>
#include <QTreeWidget>
#include <QButtonGroup>
#include <QStyleFactory>

#include "db.h"
#include "ui_controls.h"
#include "icons.h"

class mt_dialog : public QDialog
{
public:
  mt_dialog();
  ~mt_dialog();
};

mt_dialog::mt_dialog() : QDialog(NULL)
{
  
}

mt_dialog::~mt_dialog()
{
}

struct prefs_dialog::preferences_widgets {
  // identities tab
  QLineEdit* w_email;
  QLineEdit* w_name;
  QLineEdit* w_xface;
  QPlainTextEdit* w_signature;
  QComboBox* w_ident_cb;
  QPushButton* w_del_ident;
  QCheckBox* w_default_identity;
  /* The email address that corresponds to the default identity. The
     value is overwritten when the "use as default" checkbox is
     checked for an identity, so that the final value should be the one for the
     'default_identity' configuration entry, no matter the final values of the
     m_default fields */
  QString m_default_email;

  // display tab
  button_group* w_show_tags;
  button_group* w_messages_order;
  button_group* w_display_threaded;
  button_group* w_display_sender;
  button_group* w_clickable_urls;
  button_group* w_preferred_format;
  button_group* w_dynamic_sender_column;
  QComboBox* w_date_format;
  button_group* w_notifications;
  QComboBox* w_style;

  // directories tab
  QLineEdit* w_attachments_dir;
  QLineEdit* w_xpm_dir;
  QLineEdit* w_help_dir;
  QLineEdit* w_browser_path;

  // fetching tab
  QLineEdit* w_initial_fetch;
  QLineEdit* w_fetch_ahead;
  QLineEdit* w_max_connections;
  QSpinBox* w_auto_check;
  QSpinBox* w_auto_refresh;
  QCheckBox* w_auto_incorporate;
  

  // mime types tab
  std::map<QString,QString> suffix_map;	// mime_type=>suffix
  std::map<QLineEdit*,QLineEdit*> ql_map; // mime_type=>suffix for widgets
  //QWidget* mimetypes_container;
  QTreeWidget* mt_lv;		// mimetypes listview

  // composer tab
  button_group* w_composer_format_new_mail;
  button_group* w_composer_format_replies;
  button_group* w_composer_address_check;

  // search tab
  button_group* w_search_accents;
};

viewer_edit_dialog::viewer_edit_dialog(QWidget*parent, const QString& mt, const QString& prog)
  : QDialog(parent)
{
  setWindowTitle(tr("Viewer program"));
  QVBoxLayout* tl = new QVBoxLayout(this);
  QGridLayout* layout = new QGridLayout();
  tl->addLayout(layout);
  layout->setMargin(5);
  int row=0;
  QLabel* l1 = new QLabel(tr("Mime Type"), this);
  m_mimetype = new QLineEdit(this);
  m_mimetype->setText(mt);
  layout->addWidget(l1, row, 0);
  layout->addWidget(m_mimetype, row, 1);

  row++;
  QLabel* l2 = new QLabel(tr("Program"), this);
  m_viewer = new QLineEdit(this);
  m_viewer->setText(prog);
  layout->addWidget(l2, row, 0);
  layout->addWidget(m_viewer, row, 1);
  tl->addStretch(1);

  row++;
  QHBoxLayout* hb = new QHBoxLayout();
  hb->setMargin(5);
  hb->setSpacing(5);
  QPushButton* ok = new QPushButton(tr("OK"));
  hb->addWidget(ok);
  hb->addStretch(10); // spacer
  QPushButton* cancel = new QPushButton(tr("Cancel"));
  hb->addWidget(cancel);
  connect(ok, SIGNAL(clicked()), this, SLOT(ok()));
  hb->setStretchFactor(ok, 2);
  connect(cancel, SIGNAL(clicked()), this, SLOT(cancel()));
  hb->setStretchFactor(cancel, 2);

  tl->addLayout(hb);
}

viewer_edit_dialog::~viewer_edit_dialog()
{
}

QString viewer_edit_dialog::mimetype() const
{
  return m_mimetype->text();
}

QString viewer_edit_dialog::viewer() const
{
  return m_viewer->text();
}

void
viewer_edit_dialog::ok()
{
  accept();
}

void
viewer_edit_dialog::cancel()
{
  reject();
}

void
prefs_dialog::edit_mimetype()
{
  QTreeWidget* lv = m_widgets->mt_lv;
  QTreeWidgetItem* item = lv->currentItem();
  if (!item)
    return;
  viewer_edit_dialog dlg(this, item->text(0), item->text(1));
  if (dlg.exec()==QDialog::Accepted) {
    item->setText(0, dlg.mimetype());
    item->setText(1, dlg.viewer());
    m_viewers_updated=true;
  }
}

void
prefs_dialog::new_mimetype()
{
  viewer_edit_dialog dlg(this, QString::null, QString::null);
  if (dlg.exec()==QDialog::Accepted && !dlg.mimetype().isEmpty()) {
    QTreeWidget* lv = m_widgets->mt_lv;
    QTreeWidgetItem* item=new QTreeWidgetItem(lv);
    item->setText(0, dlg.mimetype());
    item->setText(1, dlg.viewer());
    lv->setCurrentItem(item);
    m_viewers_updated=true;
  }
}

void
prefs_dialog::delete_mimetype()
{
  QTreeWidget* lv = m_widgets->mt_lv;
  QTreeWidgetItem* item = lv->currentItem();
  if (!item)
    return;
  int rep=QMessageBox::question(NULL, tr("Confirmation"), 
				tr("Are you sure you want to delete this entry?"),
				QMessageBox::Yes, QMessageBox::No);
  if (rep==QMessageBox::No)
    return;
  delete item;
  m_viewers_updated=true;
}

QWidget*
prefs_dialog::mimetypes_widget()
{
  QWidget* w1=new QWidget(this);
  CHECK_PTR(w1);
  QVBoxLayout* top_layout = new QVBoxLayout(w1);
  top_layout->setMargin(3);

  QHBoxLayout* hbl = new QHBoxLayout();
  top_layout->addLayout(hbl);
  hbl->addStretch(1);

  QPushButton* new_entry = new QPushButton(tr("New"), w1);
  hbl->addWidget(new_entry);
  connect(new_entry, SIGNAL(clicked()), this, SLOT(new_mimetype()));

  QPushButton* edit_entry = new QPushButton(tr("Edit"), w1);
  hbl->addWidget(edit_entry);
  connect(edit_entry, SIGNAL(clicked()), this, SLOT(edit_mimetype()));

  QPushButton* del_entry = new QPushButton(tr("Delete"), w1);
  hbl->addWidget(del_entry);
  connect(del_entry, SIGNAL(clicked()), this, SLOT(delete_mimetype()));
  hbl->addStretch(1);

  QTreeWidget* lv = new QTreeWidget(w1);
  m_widgets->mt_lv = lv;
  top_layout->addWidget(lv);
  QStringList headers;
  headers << tr("MIME type") << tr("Viewer command");
  lv->setHeaderLabels(headers);
  connect(lv, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
	  this, SLOT(edit_mimetype()));

  attch_viewer_list lmt;
  lmt.fetch(get_config().name());
  attch_viewer_list::iterator iter = lmt.begin();
  for (; iter != lmt.end(); ++iter) {
    QTreeWidgetItem* lvi = new QTreeWidgetItem(lv);
    lvi->setText(0, iter->m_mime_type);
    lvi->setText(1, iter->m_program);
  }
  return w1;
}

QWidget*
prefs_dialog::fetching_widget()
{
  QWidget* w=new QWidget(this);
  CHECK_PTR(w);
  QVBoxLayout* top_layout = new QVBoxLayout(w);
  QGridLayout* grid = new QGridLayout();
  top_layout->addLayout(grid);
  int row=0;
  {
    QLabel* l=new QLabel(tr("Initial fetch"), w);
    grid->addWidget(l, row, 0);
    QLineEdit* le = new QLineEdit(w);
    m_widgets->w_initial_fetch = le;
    le->setMaxLength(5);
    le->setMaximumWidth(50);
    grid->addWidget(le, row, 1);
    QIntValidator *v1 = new QIntValidator(this);
    v1->setBottom(0);
    le->setValidator(v1);
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Max fetch ahead"), w);
    grid->addWidget(l, row, 0);
    QLineEdit* le = new QLineEdit(w);
    m_widgets->w_fetch_ahead = le;
    le->setMaxLength(5);
    le->setMaximumWidth(50);
    grid->addWidget(le, row, 1);
    QIntValidator *v1 = new QIntValidator(this);
    v1->setBottom(0);
    le->setValidator(v1);
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Max database connections"), w);
    grid->addWidget(l, row, 0);
    QLineEdit* le = new QLineEdit(w);
    m_widgets->w_max_connections = le;
    le->setMaxLength(3);
    le->setMaximumWidth(50);
    grid->addWidget(le, row, 1);
    le->setValidator(new QIntValidator(2, 10, this));
  }

  row++;
  {
    QLabel* l=new QLabel(tr("Automatically refresh results every"), w);
    QHBoxLayout* hb = new QHBoxLayout;
    hb->setSpacing(3);
    QSpinBox* sp = new QSpinBox();
    hb->addWidget(sp);
    sp->setMinimum(0);
    hb->addWidget(new QLabel(tr("mn")));
    grid->addWidget(l, row, 0);
    grid->addLayout(hb, row, 1);
    m_widgets->w_auto_refresh=sp;
  }

  row++;
  {
    QLabel* l=new QLabel(tr("Auto-incorporate new messages into lists"), w);
    QCheckBox* sp = new QCheckBox();
    grid->addWidget(l, row, 0);
    grid->addWidget(sp, row, 1);
    m_widgets->w_auto_incorporate=sp;
  }


  top_layout->addStretch(1);
  return w;
}

QWidget*
prefs_dialog::paths_widget()
{
  QWidget* w1=new QWidget(this);
  CHECK_PTR(w1);
  QVBoxLayout* top_layout = new QVBoxLayout(w1);
  QGridLayout* grid = new QGridLayout();
  top_layout->addLayout(grid);
  int row=0;
  {
    QLabel* l=new QLabel(tr("Attachments\n(temporary)"), w1);
    grid->addWidget(l, row, 0);
    m_widgets->w_attachments_dir = new QLineEdit(w1);
    grid->addWidget(m_widgets->w_attachments_dir, row, 1);
    QPushButton* disk1 = new QPushButton(UI_ICON(ICON16_FOLDER), "");
    grid->addWidget(disk1, row, 2);
    connect(disk1, SIGNAL(clicked()), this, SLOT(click_attachments_dir()));
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Bitmaps"), w1);
    grid->addWidget(l, row, 0);
    m_widgets->w_xpm_dir = new QLineEdit(w1);
    grid->addWidget(m_widgets->w_xpm_dir, row, 1);
    QPushButton* disk2 = new QPushButton(UI_ICON(ICON16_FOLDER), "");
    grid->addWidget(disk2, row, 2);
    connect(disk2, SIGNAL(clicked()), this, SLOT(click_bitmaps_dir()));
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Help files"), w1);
    grid->addWidget(l, row, 0);
    m_widgets->w_help_dir = new QLineEdit(w1);
    grid->addWidget(m_widgets->w_help_dir, row, 1);
    QPushButton* disk3 = new QPushButton(UI_ICON(ICON16_FOLDER), "");
    grid->addWidget(disk3, row, 2);
    connect(disk3, SIGNAL(clicked()), this, SLOT(click_help_dir()));
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Browser"), w1);
    grid->addWidget(l, row, 0);
    m_widgets->w_browser_path = new QLineEdit(w1);
    grid->addWidget(m_widgets->w_browser_path, row, 1);
    QPushButton* disk4 = new QPushButton(UI_ICON(ICON16_FOLDER), "");
    grid->addWidget(disk4, row, 2);
    connect(disk4, SIGNAL(clicked()), this, SLOT(click_browser_path()));
  }
  top_layout->addStretch(1);
  grid->setColumnStretch(1, 4);
  grid->setColumnStretch(2, 0);
  return w1;
}

void
prefs_dialog::ask_directory(QLineEdit* line)
{
  QString current = line->text();
  QDir d(current);
  if (!d.exists())
    current = QString();
  
  QString dir = QFileDialog::getExistingDirectory(this, tr("Choose an existing directory"), current);
  if (!dir.isEmpty())
    line->setText(dir);
}

void
prefs_dialog::click_help_dir()
{
  ask_directory(m_widgets->w_help_dir);
}

void
prefs_dialog::click_bitmaps_dir()
{
  ask_directory(m_widgets->w_xpm_dir);
}

void
prefs_dialog::click_attachments_dir()
{
  ask_directory(m_widgets->w_attachments_dir);
}

void
prefs_dialog::click_browser_path()
{
  QString filename = QFileDialog::getOpenFileName(this, tr("Choose browser"));
  if (!filename.isEmpty())
    m_widgets->w_browser_path->setText(filename);
}


QWidget*
prefs_dialog::composer_widget()
{
  QWidget* w1 = new QWidget(this);
  CHECK_PTR(w1);
  QVBoxLayout* top_layout = new QVBoxLayout(w1);
  QGridLayout* grid = new QGridLayout();
  top_layout->addLayout(grid);
  int row = 0;
  {
    QLabel* l=new QLabel(tr("Format for new messages"),w1);
    button_group* g = new button_group(QBoxLayout::TopToBottom, w1);
    QRadioButton* b1 = new QRadioButton(tr("Plain text"));
    QRadioButton* b2 = new QRadioButton(tr("HTML"));
    g->addButton(b1, 0, "text/plain");
    g->addButton(b2, 1, "text/html");
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_composer_format_new_mail = g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Format for replies"),w1);
    button_group* g = new button_group(QBoxLayout::TopToBottom, w1);
    QRadioButton* b1 = new QRadioButton(tr("Plain text"));
    QRadioButton* b2 = new QRadioButton(tr("HTML"));
    QRadioButton* b3 = new QRadioButton(tr("Same as sender"));
    g->addButton(b1, 0, "text/plain");
    g->addButton(b2, 1, "text/html");
    g->addButton(b3, 2, "same_as_sender");
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_composer_format_replies = g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Basic syntax check on addresses"),w1);
    button_group* g = new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* b1 = new QRadioButton(tr("Yes"));
    QRadioButton* b2 = new QRadioButton(tr("No"));

    g->addButton(b1, 0, "basic");
    g->addButton(b2, 1, "none");
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_composer_address_check = g;
  }
  row++;
  top_layout->addStretch(1);
  return w1;
}

prefs_dialog::sub_label::sub_label(const QString text)
{
  setTextFormat(Qt::RichText);
  setText(text);
  QFont f = font();
  f.setPointSize((f.pointSize()*8)/10);
  setFont(f);
}

QWidget*
prefs_dialog::search_widget()
{
  QWidget* w1 = new QWidget(this);
  CHECK_PTR(w1);
  QVBoxLayout* top_layout = new QVBoxLayout(w1);
  QGridLayout* grid = new QGridLayout();
  top_layout->addLayout(grid);
  int row = 0;
  {
    QLabel* l=new QLabel(tr("Accents & diacritic marks"),w1);
    button_group* g = new button_group(QBoxLayout::TopToBottom, w1);
    QRadioButton* b1 = new QRadioButton(tr("Search with accents"));
    QRadioButton* b2 = new QRadioButton(tr("Search without accents"));
    g->addButton(b1, 0, "accented");
    g->addButton(b2, 1, "unaccented");
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_search_accents = g;
  }
  row++;
  {
    sub_label* l = new sub_label(tr("This can be overriden with the <i>accents:insensitive</i> or <i>accents:sensitive</i> operators in the search box."));    
    grid->addWidget(l, row, 0, 1, -1);
  }
  top_layout->addStretch(1);
  return w1;
}

QWidget*
prefs_dialog::display_widget()
{
  QWidget* w1=new QWidget(this);
  CHECK_PTR(w1);
  QGridLayout* grid = new QGridLayout(w1);
  int row=0;
  {
    QLabel* l=new QLabel(tr("Messages order"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    g->addButton(new QRadioButton(tr("Newest first")), 1);
    g->addButton(new QRadioButton(tr("Oldest first")), 0);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_messages_order=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Show tags panel"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* yes=new QRadioButton(tr("Yes"));
    QRadioButton* no=new QRadioButton(tr("No"));
    g->addButton(yes, 0);
    g->addButton(no, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_show_tags=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Display sender as"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("Email"));
    QRadioButton* hm=new QRadioButton(tr("Name"));
    g->addButton(hn, 0);
    g->addButton(hm, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_display_sender=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Clickable URLs in body"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("Yes"));
    QRadioButton* hm=new QRadioButton(tr("No"));
    g->addButton(hn, 0);
    g->addButton(hm, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_clickable_urls=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Preferred display format"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("HTML"));
    QRadioButton* hm=new QRadioButton(tr("Text"));
    g->addButton(hn, 0);
    g->addButton(hm, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_preferred_format=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Dynamic recipient column"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("Yes"));
    QRadioButton* hm=new QRadioButton(tr("No"));
    g->addButton(hn, 0);
    g->addButton(hm, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_dynamic_sender_column=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Threaded display"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("Yes"));
    QRadioButton* hm=new QRadioButton(tr("No"));
    g->addButton(hn, 0);
    g->addButton(hm, 1);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_display_threaded=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Date format"),w1);
    QComboBox* g=new QComboBox(w1);
    g->addItem(tr("Localized [%1]").arg(QLocale::system().name()));
    g->addItem("DD/MM/YYYY HH:MI");
    g->addItem("YYYY/MM/DD HH:MI");
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_date_format=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("New mail notifications"),w1);
    button_group* g=new button_group(QBoxLayout::LeftToRight, w1);
    QRadioButton* hn=new QRadioButton(tr("System"));
    QRadioButton* hm=new QRadioButton(tr("None"));
    g->addButton(hn, 1);
    g->addButton(hm, 0);
    grid->addWidget(l, row, 0);
    grid->addWidget(g, row, 1);
    m_widgets->w_notifications=g;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Style"),w1);
    QComboBox* ln=new QComboBox(w1);
    // list available Qt styles in the combobox
    QStringList list = QStyleFactory::keys();
    list.sort();
    ln->addItem("(default)");
    for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) {
      ln->addItem(*it);
    }
    grid->addWidget(l, row, 0);
    grid->addWidget(ln, row, 1);
    m_widgets->w_style=ln;
  }
  row++;
  return w1;
}

QWidget*
prefs_dialog::ident_widget()
{
  QWidget* w=new QWidget(this);
  CHECK_PTR(w);
  QVBoxLayout* top_layout = new QVBoxLayout(w);

  QHBoxLayout* hbl = new QHBoxLayout();
  top_layout->addLayout(hbl);

  hbl->addStretch(1);
  m_widgets->w_ident_cb = new QComboBox(w);
  QPushButton* new_id = new QPushButton(tr("New"), w);
  hbl->addWidget(new_id);
  hbl->addWidget(m_widgets->w_ident_cb, 5);
  connect(new_id, SIGNAL(clicked()), this, SLOT(new_identity()));
  m_widgets->w_del_ident = new QPushButton(tr("Delete"), w);
  hbl->addWidget(m_widgets->w_del_ident);
  connect(m_widgets->w_del_ident, SIGNAL(clicked()), this, SLOT(delete_identity()));

  hbl->addStretch(1);
  connect(m_widgets->w_ident_cb, SIGNAL(activated(int)), this,
	  SLOT(ident_changed(int)));

  QGridLayout* grid = new QGridLayout();
  top_layout->addLayout(grid);

  int row=0;
  {
    QLabel* l=new QLabel(tr("Email address"),w);
    m_widgets->w_email=new QLineEdit(w);
    grid->addWidget(l, row, 0);
    grid->addWidget(m_widgets->w_email, row, 1);
    connect(m_widgets->w_email, SIGNAL(lostFocus()), this, SLOT(email_lost_focus()));

  }
  row++;
  {
    QLabel* l=new QLabel(tr("Name"),w);
    QLineEdit* ln=new QLineEdit(w);
    grid->addWidget(l, row, 0);
    grid->addWidget(ln, row, 1);
    m_widgets->w_name=ln;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Use as default"),w);
    QCheckBox* cb=new QCheckBox(w);
    grid->addWidget(l, row, 0);
    grid->addWidget(cb, row, 1);
    m_widgets->w_default_identity=cb;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("Signature"),w);
    QPlainTextEdit* ln=new QPlainTextEdit(w);
    grid->addWidget(l, row, 0);
    grid->addWidget(ln, row, 1);
    m_widgets->w_signature=ln;
  }
  row++;
  {
    QLabel* l=new QLabel(tr("X-Face"),w);
    QLineEdit* ln=new QLineEdit(w);
    grid->addWidget(l, row, 0);
    grid->addWidget(ln, row, 1);
    m_widgets->w_xface=ln;
  }

  // fetch identities
  if (m_ids_map.fetch()) {
    identities::iterator iter = m_ids_map.begin();
    while (iter != m_ids_map.end()) {
      mail_identity* p= new mail_identity();
      *p = iter->second;
      m_ids.push_back(p);
      m_widgets->w_ident_cb->addItem(p->m_email_addr);
      if (p->m_is_default)
	m_widgets->m_default_email = p->m_email_addr;
      ++iter;
    }
  }
  m_curr_ident = get_identity(m_widgets->w_ident_cb->currentText());
  if (m_curr_ident==NULL) {
    m_curr_ident = new mail_identity();
    m_ids.push_back(m_curr_ident);
  }
  ident_to_widgets();

  return w;
}

prefs_dialog::prefs_dialog(QWidget* parent): QDialog(parent)
{
  m_widgets=NULL;
  m_viewers_updated=false;

  setWindowTitle(tr("Preferences"));
  app_config& conf=get_config();
  QVBoxLayout* layout = new QVBoxLayout(this);

#if 0
  m_conf_selector = new QComboBox(this);
  QStringList all_confs;
  if (app_config::get_all_conf_names(&all_confs)) {
    m_conf_selector->insertStringList(all_confs);
    connect(m_conf_selector, SIGNAL(activated(const QString&)),
	    this, SLOT(other_conf(const QString&)));
  }
  m_conf_selector->setCurrentText(conf.name());
  layout->addWidget(m_conf_selector);
#endif
  m_widgets = new struct preferences_widgets;

  m_tabw = new QTabWidget(this);
  layout->addWidget(m_tabw);
  m_display_page = display_widget();
  m_tabw->addTab(m_display_page, tr("Display"));

  m_ident_page = ident_widget();
  m_tabw->addTab(m_ident_page, tr("Identities"));

  m_mimetypes_page = mimetypes_widget();
  m_tabw->addTab(m_mimetypes_page, tr("MIME viewers"));

  m_composer_page = composer_widget();
  m_tabw->addTab(m_composer_page, tr("Composer"));

  m_search_page = search_widget();
  m_tabw->addTab(m_search_page, tr("Search"));

  m_paths_page = paths_widget();
  m_tabw->addTab(m_paths_page, tr("Paths"));

  m_fetching_page = fetching_widget();
  m_tabw->addTab(m_fetching_page, tr("Fetching"));


  connect(m_tabw, SIGNAL(currentChanged(QWidget*)), this, SLOT(page_changed(QWidget*)));

  QHBoxLayout* hb = new QHBoxLayout;
  hb->setMargin(5);
  hb->setSpacing(5);
  layout->addLayout(hb);

  hb->insertStretch(0, 10);
  QPushButton* help = new QPushButton(tr("Help"));
  hb->addWidget(help, 1);
  QPushButton* ok = new QPushButton(tr("OK"));
  hb->addWidget(ok, 1);
  QPushButton* cancel = new QPushButton(tr("Cancel"));
  hb->addWidget(cancel, 1);
  connect(help, SIGNAL(clicked()), this, SLOT(help()));
  connect(ok, SIGNAL(clicked()), this, SLOT(accept()));
  connect(cancel, SIGNAL(clicked()), this, SLOT(reject()));

  conf_to_widgets(conf);
}

prefs_dialog::~prefs_dialog()
{
  if (m_widgets)
    delete m_widgets;
}

/*
  Returns the help topic corresponding to a tab
*/
QString
prefs_dialog::help_topic(QWidget* tab)
{
  QString help_topic;
  if (tab == m_display_page)
    help_topic = "preferences/display";
  else if (tab == m_ident_page)
    help_topic = "preferences/identities";
  else if (tab == m_paths_page)
    help_topic = "preferences/paths";
  else if (tab == m_fetching_page)
    help_topic = "preferences/fetching";
  else if (tab == m_mimetypes_page)
    help_topic = "preferences/mimeviewers";
  return help_topic;
}

#if 0
void
prefs_dialog::other_conf(const QString& conf)
{
  DBG_PRINTF(3, "to other conf: %s\n", conf.latin1());
  // TODO: switch to new configuration
  m_conf_selector->setCurrentText(get_config().name());
}
#endif

void
prefs_dialog::help()
{
  QString topic = help_topic(m_tabw->currentWidget());
  if (!topic.isEmpty())
    helper::show_help(topic);
}

void
prefs_dialog::page_changed(QWidget* tab)
{
  QString topic = help_topic(m_tabw->currentWidget());
  if (!topic.isEmpty())
    helper::track(topic);
  if (tab==m_mimetypes_page)
    load_viewers();
}

void
prefs_dialog::load_viewers()
{

}

void
prefs_dialog::conf_to_widgets(app_config& conf)
{
  int idx;
  QString date_format=conf.get_string("date_format");
  if (date_format=="local")
    m_widgets->w_date_format->setCurrentIndex(0);
  else {
    for (idx=1; idx<m_widgets->w_date_format->count(); idx++) {
      if (m_widgets->w_date_format->itemText(idx)==date_format)
	m_widgets->w_date_format->setCurrentIndex(idx);
    }
  }

  switch(conf.get_number("show_tags")) {
  case 0:
    m_widgets->w_show_tags->setButton(1);
    break;
  case 1:
    m_widgets->w_show_tags->setButton(0);
    break;
  }

  QString mo = conf.get_string("messages_order");
  if (mo=="oldest_first")
    m_widgets->w_messages_order->setButton(0);
  else // newest first
    m_widgets->w_messages_order->setButton(1);

  QString dspl_as=conf.get_string("sender_displayed_as");
  if (dspl_as=="name")
    m_widgets->w_display_sender->setButton(1);
  else
    m_widgets->w_display_sender->setButton(0);

  switch(conf.get_bool("body_clickable_urls")) {
  case true:
    m_widgets->w_clickable_urls->setButton(0);
    break;
  case false:
    m_widgets->w_clickable_urls->setButton(1);
    break;
  }

  QString new_mail_notif=conf.get_string("display/notifications/new_mail");
  if (new_mail_notif=="none") {
    m_widgets->w_notifications->setButton(0);
  }
  else if (new_mail_notif=="system") {
    m_widgets->w_notifications->setButton(1);
  }
  else
    m_widgets->w_notifications->setButton(1); // system is default

  QString preferred_dspl=conf.get_string("display/body/preferred_format");
  if (preferred_dspl.toUpper() == "HTML") {
    m_widgets->w_preferred_format->setButton(0);
  }
  else if (preferred_dspl.toUpper() == "TEXT") {
    m_widgets->w_preferred_format->setButton(1);
  }
  else
    m_widgets->w_preferred_format->setButton(0); // HTML is the default

  switch(conf.get_bool("display/auto_sender_column")) {
  case true:
    m_widgets->w_dynamic_sender_column->setButton(0);
    break;
  case false:
    m_widgets->w_dynamic_sender_column->setButton(1);
    break;
  }

  switch(conf.get_number("display_threads")) {
  case 1:
    m_widgets->w_display_threaded->setButton(0);
    break;
  case 0:
    m_widgets->w_display_threaded->setButton(1);
    break;
  }

  QString style=conf.get_string("ui_style");
  if (!style.isEmpty()) {
    for (int i=0; i<m_widgets->w_style->count(); i++) {
      QString t=m_widgets->w_style->itemText(i);
      if (t==style) {
	m_widgets->w_style->setCurrentIndex(i);
      }
    }
  }


  // paths
  m_widgets->w_attachments_dir->setText(conf.get_string("attachments_directory"));
  m_widgets->w_xpm_dir->setText(conf.get_string("xpm_path"));
  m_widgets->w_help_dir->setText(conf.get_string("help_path"));
  m_widgets->w_browser_path->setText(conf.get_string("browser"));

  // fetching
  m_widgets->w_max_connections->setText(conf.get_string("max_db_connections"));
  m_widgets->w_fetch_ahead->setText(conf.get_string("fetch_ahead_max_msgs"));
  m_widgets->w_initial_fetch->setText(conf.get_string("max_msgs_per_selection"));
  int auto_refresh = conf.get_number("fetch/auto_refresh_messages_list");
  m_widgets->w_auto_refresh->setValue(auto_refresh);
  bool auto_incorp = conf.get_bool("fetch/auto_incorporate_new_results", false);
  m_widgets->w_auto_incorporate->setChecked(auto_incorp);


  // composer
  {
    QString fm_new = conf.get_string("composer/format_for_new_mail");
    QString fm_rep = conf.get_string("composer/format_for_replies");
    if (!fm_new.isEmpty()) {
      int id = m_widgets->w_composer_format_new_mail->code_to_id(fm_new);
      if (id >= 0)
	m_widgets->w_composer_format_new_mail->setButton(id);
    }
    if (!fm_rep.isEmpty()) {
      int id = m_widgets->w_composer_format_replies->code_to_id(fm_rep);
      if (id >= 0)
	m_widgets->w_composer_format_replies->setButton(id);
    }

    QString ac = conf.get_string("composer/address_check");
    if (!ac.isEmpty()) {
      if (ac=="none")
	m_widgets->w_composer_address_check->setButton(1);
      else
	m_widgets->w_composer_address_check->setButton(0);
    }
  }

  // search
  {
    QString a = conf.get_string("search/accents");
    if (!a.isEmpty()) {
      int id = m_widgets->w_search_accents->code_to_id(a);
      if (id >= 0)
	m_widgets->w_search_accents->setButton(id);
    }
  }
}

void
prefs_dialog::widgets_to_conf(app_config& conf)
{
  conf.set_string("default_identity", m_widgets->m_default_email);
  if (m_widgets->w_date_format->currentIndex()==0)
    conf.set_string("date_format", "local");
  else
    conf.set_string("date_format", m_widgets->w_date_format->currentText());


  if (m_widgets->w_messages_order->selected_id()==1)
    conf.set_string("messages_order", "newest_first");
  else if (m_widgets->w_messages_order->selected_id()==0)
    conf.set_string("messages_order", "oldest_first");

  if (m_widgets->w_show_tags->selected_id()==1)
    conf.set_number("show_tags", 0);
  else if (m_widgets->w_show_tags->selected_id()==0)
    conf.set_number("show_tags", 1);

  switch (m_widgets->w_display_sender->selected_id()) {
  case 1:
    conf.set_string("sender_displayed_as", "name");
    break;
  case 0:
    conf.set_string("sender_displayed_as", "email");
    break;
  }

  switch (m_widgets->w_display_threaded->selected_id()) {
  case 1:
    conf.set_number("display_threads", 0);
    break;
  case 0:
    conf.set_number("display_threads", 1);
    break;
  }

  switch (m_widgets->w_dynamic_sender_column->selected_id()) {
  case 0:
    conf.set_bool("display/auto_sender_column", true);;
    break;
  case 1:
    conf.set_bool("display/auto_sender_column", false);
    break;
  }

  switch (m_widgets->w_notifications->selected_id()) {
  case 1:
    conf.set_string("display/notifications/new_mail", "system");
    break;
  case 0:
    conf.set_string("display/notifications/new_mail", "none");
    break;
  }

  switch (m_widgets->w_preferred_format->selected_id()) {
  case 1:
    conf.set_string("display/body/preferred_format", "text");
    break;
  case 0:
    conf.set_string("display/body/preferred_format", "html");
    break;
  }

  switch (m_widgets->w_clickable_urls->selected_id()) {
  case 0:
    conf.set_number("body_clickable_urls", 1);
    break;
  case 1:
    conf.set_number("body_clickable_urls", 0);
    break;
  }

  if (m_widgets->w_style->currentText()!="(default)") {
    conf.set_string("ui_style", m_widgets->w_style->currentText());
  }
  else {
    conf.set_string("ui_style", QString());
  }


#if 0
  bool ok;
  int autocheck = m_widgets->w_auto_check->text().toInt(&ok);
  if (ok) {
    conf.set_number("autocheck_newmail", autocheck);
  }
#endif

  // paths
  conf.set_string("attachments_directory", m_widgets->w_attachments_dir->text());
  conf.set_string("xpm_path", m_widgets->w_xpm_dir->text());
  conf.set_string("help_path", m_widgets->w_help_dir->text());
  conf.set_string("browser", m_widgets->w_browser_path->text());

  // fetching
  conf.set_number("fetch_ahead_max_msgs", m_widgets->w_fetch_ahead->text().toInt());
  conf.set_number("max_msgs_per_selection", m_widgets->w_initial_fetch->text().toInt());
  conf.set_number("max_db_connections", m_widgets->w_max_connections->text().toInt());
  conf.set_bool("fetch/auto_incorporate_new_results", m_widgets->w_auto_incorporate->isChecked());
  conf.set_number("fetch/auto_refresh_messages_list", m_widgets->w_auto_refresh->text().toInt());

  // composer
  if (m_widgets->w_composer_format_new_mail->selected()) {
    conf.set_string("composer/format_for_new_mail",
		    m_widgets->w_composer_format_new_mail->selected_code());
  }
  if (m_widgets->w_composer_format_replies->selected()) {
    conf.set_string("composer/format_for_replies",
		    m_widgets->w_composer_format_replies->selected_code());
  }

  if (m_widgets->w_composer_address_check->selected()) {
    conf.set_string("composer/address_check",
		    m_widgets->w_composer_address_check->selected_code());
  }

  // search
  if (m_widgets->w_search_accents->selected()) {
    conf.set_string("search/accents",
		    m_widgets->w_search_accents->selected_code());
  }
}

void
prefs_dialog::done(int code)
{
  DBG_PRINTF(5,"done(%d)\n", code);
  if (code==1) {
    widgets_to_ident();
    app_config newconf;
    widgets_to_conf(newconf);
    //DBG_PRINTF(5,"new_conf\n");
    //newconf.dump();
    get_config().diff_update(newconf);
    get_config().diff_replace(newconf);
    get_config().apply();
    if (m_viewers_updated && !update_viewers_db())
	return;
    if (!update_identities_db())
      return;
  }
  QDialog::done(code);
}

/*
  Save the current displayed values into memory
*/
void
prefs_dialog::widgets_to_ident()
{
  mail_identity* id = m_curr_ident;
  // update the values
  id->m_name = m_widgets->w_name->text();
  id->m_xface = m_widgets->w_xface->text();
  id->m_email_addr = m_widgets->w_email->text();
  id->m_signature = m_widgets->w_signature->toPlainText();
  id->m_is_default = m_widgets->w_default_identity->isChecked();
  if (id->m_is_default) {
    // if id is now the "default" identity,
    // remove that attribute to the previous default unless it's the same
    mail_identity* prev = get_identity(m_widgets->m_default_email);
    if (prev && prev!=id) {
      prev->m_is_default=false;
    }
    m_widgets->m_default_email=id->m_email_addr;
  }
  else {
    // when the "default" property gets unchecked, we reflect that
    // on m_widgets->m_default_email
    if (m_widgets->m_default_email == id->m_email_addr) // if it was the default
      m_widgets->m_default_email = QString::null;
  }
}

mail_identity*
prefs_dialog::get_identity(const QString& email)
{
  QList<mail_identity*>::iterator it;
  for (it = m_ids.begin(); it!=m_ids.end(); ++it) {
    if ((*it)->m_email_addr==email)
      return (*it);
  }
  return NULL;
}

void
prefs_dialog::ident_to_widgets()
{
  mail_identity* id = m_curr_ident;
  if (!id->m_email_addr.isEmpty()) {
    m_widgets->w_name->setText(id->m_name);
    m_widgets->w_xface->setText(id->m_xface);
    m_widgets->w_email->setText(id->m_email_addr);
    m_widgets->w_signature->setPlainText(id->m_signature);
    m_widgets->w_default_identity->setChecked(id->m_is_default);
  }
  else {
    m_widgets->w_name->setText(QString::null);
    m_widgets->w_xface->setText(QString::null);
    m_widgets->w_email->setText(QString::null);
    m_widgets->w_signature->setPlainText(QString::null);
    m_widgets->w_default_identity->setChecked(false);
  }
}

void
prefs_dialog::ident_changed(int id)
{
  DBG_PRINTF(2,"ident_changed(%d)\n", id);

  //    m_widgets->w_del_ident->setEnabled(true);
  // store the displayed values into memory
  widgets_to_ident();

  // display the values for the identity that becomes current
  QString email=m_widgets->w_ident_cb->currentText();
  m_curr_ident=get_identity(email);
  if (m_curr_ident==NULL) {
    DBG_PRINTF(2, "ERR: m_curr_ident=null for email='%s'\n", email.toLatin1().constData());
  }
  else
    ident_to_widgets();
}

void
prefs_dialog::delete_identity()
{
  if (m_ids.isEmpty())
    return;
  QString cur = m_widgets->w_ident_cb->currentText();
  m_ids.removeAll(m_curr_ident);
  m_widgets->w_ident_cb->removeItem(m_widgets->w_ident_cb->currentIndex());
  m_curr_ident = get_identity(m_widgets->w_ident_cb->currentText());
  if (m_curr_ident==NULL) {
    add_new_identity();
  }
  ident_to_widgets();
}

void
prefs_dialog::add_new_identity()
{
  m_curr_ident = new mail_identity();
  m_ids.push_back(m_curr_ident);
  ident_to_widgets();
  m_widgets->w_ident_cb->addItem("");
  m_widgets->w_ident_cb->setCurrentIndex(m_widgets->w_ident_cb->count()-1);
  m_widgets->w_email->setFocus();
}

void
prefs_dialog::new_identity()
{
  // no new identity if the email address of the current identity is not set
  if (m_widgets->w_ident_cb->count() > 0 &&
      m_widgets->w_email->text().isEmpty()) {
    return;
  }

  email_lost_focus();
  widgets_to_ident();

  // if an empty identity is already available in the combobox
  // then we try to use it
  mail_identity* p = get_identity("");
  if (p) {
    QComboBox* cb = m_widgets->w_ident_cb;
    int c=cb->count();
    for (int i=0; i<c; i++) {
      if (cb->itemText(i)=="") {
	cb->setCurrentIndex(i);
	m_curr_ident=p;
	ident_to_widgets();
	return;
      }
    }
  }

  // if no empty identity has been found, let's create one
  add_new_identity();
}

void
prefs_dialog::email_lost_focus()
{
  if (m_widgets->w_email->text() != m_curr_ident->m_email_addr) {
    // change the identity in the combobox as soon as the user
    // has given it a name or changed its name
    m_curr_ident->m_email_addr = m_widgets->w_email->text();
    if (m_widgets->w_ident_cb->count()>0) {
      m_widgets->w_ident_cb->setItemText(m_widgets->w_ident_cb->currentIndex(),
					 m_curr_ident->m_email_addr);
					
    }
    else {
      m_widgets->w_ident_cb->addItem(m_curr_ident->m_email_addr);
    }
  }
}

bool
prefs_dialog::update_identities_db()
{
  db_cnx db;
  bool in_trans=false;

  try {
    /* Identities that have their email changed are completely removed
       using the old email as the key and then reinserted with the new
       email. This is because the email being the primary key, changing
       rows by successive update can fail, for instance if two
       email addresses are to be swapped, we would get a PK violation
       when doing the first update. While doing a first pass with
       updates, we're collecting such identities in 'ri_list' */
    QList<mail_identity*> ri_list;

    QList<mail_identity*>::iterator it;
    mail_identity* p;
    identities::iterator iter;
    // identities (original emails) that have been processed
    std::set<QString> done_set;

    for (it=m_ids.begin(); it!=m_ids.end(); ++it) {
      p=(*it);
      if (p->m_email_addr.isEmpty())
	continue;			// ignore empty identities, shouldn't happen
      done_set.insert(p->m_orig_email_addr);
      if (p->m_orig_email_addr != p->m_email_addr) {
	// when an identity is newly created, its p->m_orig_email_addr is empty
	// ri_list includes those new identities as well as renamed identities
	ri_list.append(p);
      }
      else {
	// update db
	iter=m_ids_map.find(p->m_email_addr);
	if (iter!=m_ids_map.end()) {
	  mail_identity* ps = &(iter->second); // before change
	  if ((ps->m_name != p->m_name ||
	       ps->m_signature != p->m_signature ||
	       ps->m_xface != p->m_xface)) {
	    if (!in_trans) {
	      db.begin_transaction();
	      in_trans=true;
	    }
	    p->update_db();
	  }
	}
	else {
	  /* If the identity is not to be found in the list before
	     editing let's process it later by insert. Currently this
	     case shouldn't happen anyway since it's m_orig_email_addr
	     should be empty */
	  ri_list.append(p);
	}
      }
    }

    /* Delete the identities that were present initially but not in the end map */
    sql_stream sd("DELETE FROM identities WHERE email_addr=:p1", db);
    for (iter=m_ids_map.begin(); iter!=m_ids_map.end(); ++iter) {
      if (done_set.find(iter->first) == done_set.end()) {
	if (!in_trans) {
	  db.begin_transaction();
	  in_trans=true;
	}
	sd << iter->second.m_email_addr;
      }
    }

    /* Now process 'ri_list' */
    if (!ri_list.isEmpty()) {
      if (!in_trans) {
	db.begin_transaction();
	in_trans=true;
      }

      for (it=ri_list.begin(); it!=ri_list.end(); ++it) {
	p=*it;
	if (!p->m_orig_email_addr.isEmpty())
	  sd << p->m_email_addr;
      }

      for (it=ri_list.begin(); it!=ri_list.end(); ++it) {
	p=*it;
	/* update_db() will do an insert because the row, if it existed, has
	   been deleted just above */
	p->update_db();
      }
    }

    if (in_trans) {
      db.commit_transaction();
    }
  }
  catch(db_excpt& p) {
    if (in_trans)
      db.rollback_transaction();
    DBEXCPT(p);
    return false;
  }
  return true;
}

bool
prefs_dialog::update_viewers_db()
{
  db_cnx db;

  try {
    const QString confname=get_config().name();

    db.begin_transaction();
    sql_stream sd("DELETE FROM programs WHERE conf_name=:n", db);
    sd << confname;

    sql_stream si("INSERT INTO programs(program_name,content_type,conf_name) VALUES(:p1,:p2,:p3)", db);
    QTreeWidget* qw = m_widgets->mt_lv;
    for (int index=0; index<qw->topLevelItemCount(); index++) {
      QTreeWidgetItem* item = qw->topLevelItem(index);
      QString mt_name = item->text(0);
      QString viewer = item->text(1);
      si << viewer << mt_name << confname;
    }
    db.commit_transaction();
  }
  catch(db_excpt& p) {
    db.rollback_transaction();
    DBEXCPT(p);
    return false;
  }
  return true;
}

HTML source code generated by GNU Source-Highlight plus some custom post-processing

List of all available source files