Manitou-Mail logo title

Source file: src/selectmail.cpp

/* Copyright (C) 2004-2014 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 "main.h"
#include "selectmail.h"
#include "msg_list_window.h"
#include "db.h"
#include "tags.h"
#include "msg_status_cache.h"
#include "sqlquery.h"
#include "sqlstream.h"
#include "addresses.h"
#include "helper.h"
#include "icons.h"
#include "sql_editor.h"
#include "words.h"

#include <QLineEdit>
#include <QComboBox>
#include <QLabel>
#include <QButtonGroup>
#include <QSpinBox>
#include <QMessageBox>
#include <QApplication>
#include <QCursor>
#include <QPushButton>
#include <QToolButton>
#include <QTimer>
#include <QFontMetrics>
#include <QRadioButton>
#include <QDateTimeEdit>
#include <QCheckBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QDialogButtonBox>

const int
msgs_filter::max_possible_prio=32767;

msgs_filter::msgs_filter()
{
  init();
}

void
msgs_filter::init()
{
  m_order=(get_config().get_msgs_sort_order()==Qt::AscendingOrder)?1:-1;
  m_mailId=0;
  m_tag_id=0;
  m_thread_id=0;
  m_status=-1;
  m_status_set=0;
  m_status_unset=0;
  m_include_trash = false;
  m_newer_than=0;
  m_date_clause=QString::null;
  m_max_results=get_config().get_number("max_msgs_per_selection");
  if (m_max_results==0)
    m_max_results=1000;
  m_addresses_count=0;
  m_sAddress=QString::null;
  m_subject=QString::null;
  m_body_substring=QString::null;
  m_addr_to=QString::null;
  m_sql_stmt=QString::null;
  m_tag_name=QString::null;
  m_date_min=QDate();
  m_date_max=QDate();
  //  m_word=QString::null;
  m_fts.m_words.clear();
  m_fts.m_exclude_words.clear();
  m_in_trash=false;
  m_auto_refresh=false;
  m_min_prio = max_possible_prio+1;
  m_nAddrType = 0;

  m_user_query=QString::null;

  // results
  m_fetched=false;
  m_fetch_results=NULL;
  m_date_bound=QString::null;
  m_mail_id_bound=0;
  m_has_more_results = false;
  
}

msgs_filter::~msgs_filter()
{
  if (m_fetch_results)
    delete m_fetch_results;
}

// Initialize a fetch_thread with conditions and state known by the filter
void
msgs_filter::preprocess_fetch(fetch_thread& thread)
{
  thread.m_psearch = m_psearch; // not necessary, also done by asynchronous_fetch
}


// Store post-fetch state to the filter from the fetch_thread
void
msgs_filter::postprocess_fetch(fetch_thread& thread)
{
  m_has_more_results = m_max_results>0 && (thread.m_tuples_count > m_max_results);
  DBG_PRINTF(6,"postprocess_fetch: m_has_more_results=%d", (int)m_has_more_results);
  m_boundary = thread.m_boundary;
  if (m_order>0) {
    m_mail_id_bound = thread.m_max_mail_id;
    m_date_bound = thread.m_max_msg_date;
  }
  else {
    m_mail_id_bound = thread.m_min_mail_id;
    m_date_bound = thread.m_min_msg_date;
    m_boundary = thread.m_boundary;
  }
  m_psearch = thread.m_psearch;
}

int
msgs_filter::add_address_selection (sql_query& q,
				    const QString email_addr,
				    int addr_type)
{
  mail_address addr;
  bool address_found;
  if (!addr.fetchByEmail(email_addr, &address_found) || !address_found) {
    // no address matches m_sAddress or an error has occurred when
    // trying to fetch: the result has to be empty
    return 0;
  }
  QString alias=QString("ma%1").arg(++m_addresses_count);
  q.add_table(QString("mail_addresses %1").arg(alias));
  q.add_clause(QString(alias) + ".addr_id", addr.id());
  q.add_clause(QString("m.mail_id=%1.mail_id").arg(alias));
  if (addr_type != 0) {
    q.add_clause(QString("%1.addr_type").arg(alias), addr_type);
  }
  return 1;
}


//static
int
msgs_filter::load_result_list(PGresult* res, std::list<mail_result>* l, int max_nb)
{
  if (res && PQresultStatus(res)==PGRES_TUPLES_OK) {
    DBG_PRINTF(5,"load_result_list %d results max=%d", PQntuples(res), max_nb);

    // if max_nb==-1, keep all the tuples
    // if max_nb>=0, keep at most max_nb tuples
    if (max_nb<0)
      max_nb=PQntuples(res);
    else if (max_nb > PQntuples(res))
      max_nb = PQntuples(res);

    for (int i=0; i < max_nb; i++) {
      mail_result r;
      r.m_id = atoi(PQgetvalue(res, i , 0));
      r.m_from = PQgetvalue(res, i, 1);
      r.m_subject = QString::fromUtf8(PQgetvalue(res, i, 2));
      r.m_date = PQgetvalue(res, i, 3);
      r.m_thread_id = atoi(PQgetvalue(res, i , 4));
      r.m_status = atoi(PQgetvalue(res, i, 5));
      msg_status_cache::update(r.m_id, r.m_status);
      r.m_in_replyto = atoi(PQgetvalue(res, i, 6));
      r.m_sender_name = QString::fromUtf8(PQgetvalue(res, i, 7));
      r.m_pri = atoi(PQgetvalue(res, i, 8));
      r.m_flags = (uint)atoi(PQgetvalue(res, i, 9));
      r.m_recipients = QString::fromUtf8(PQgetvalue(res, i, 10));
      l->push_back(r);
    }
    return max_nb;
  }
  else
    return 0;
}

int
fetch_thread::store_results(sql_stream& s, int max_nb)
{
  int i=0;
  QString date_stamp;
  mail_result r;

  while (!s.eos() && (max_nb==-1 || i<max_nb)) {
    s >> r.m_id >> r.m_from >> r.m_subject >> r.m_date >> r.m_thread_id
      >> r.m_status >> r.m_in_replyto >> r.m_sender_name >> r.m_pri >> r.m_flags
      >> r.m_recipients;
    msg_status_cache::update(r.m_id, r.m_status);
    m_results->push_back(r);

    date_stamp = r.m_date.isEmpty() ? QString("00000000000000%1").arg(r.m_id) :
      QString("%1%2").arg(r.m_date).arg(r.m_id);
    if (m_boundary.isEmpty() || m_boundary < date_stamp)
      m_boundary = date_stamp;

    if (m_min_msg_date.isEmpty() && !r.m_date.isEmpty())
      m_min_msg_date=r.m_date;
    else if (r.m_date < m_min_msg_date)
      m_min_msg_date=r.m_date;

    if (m_max_msg_date.isEmpty() && !r.m_date.isEmpty())
      m_max_msg_date=r.m_date;
    else if (r.m_date > m_max_msg_date)
      m_max_msg_date=r.m_date;

    if (m_min_mail_id==0)
      m_min_mail_id=r.m_id;
    else if (r.m_id < m_min_mail_id)
      m_min_mail_id=r.m_id;

    if (r.m_id > m_max_mail_id)
      m_max_mail_id=r.m_id;

    i++;
  }
  return i;
}

fetch_thread::fetch_thread()
{
  m_cnx=NULL;
  m_fetch_more=false;
  m_cancelled=false;
}

// Launch the query and fetch results fetch. Overrides QThread::run()
void
fetch_thread::run()
{
  if (!m_cnx) return;
  DBG_PRINTF(5,"fetch_thread::run(), max_results=%d", m_max_results);
  m_errstr=QString::null;
  QTime start = QTime::currentTime();

  m_tuples_count=0;
  m_min_mail_id = m_max_mail_id = 0;
  m_max_msg_date = QString::null;
  m_min_msg_date = QString::null;
  m_boundary = QString::null;

  // special case repeated executions of the query for piecemeal fetch of
  // IWI results
  if (!m_psearch.m_parts.isEmpty()) {
    /* if it's a "fetch more" kind of search, m_nb_fetched_parts is the index of
       the last IWI part that was joined against at the previous step,
       otherwise it's 0 */
    int parts_idx = m_psearch.m_nb_fetched_parts;
    do {
      int part_no = m_psearch.m_parts.at(parts_idx++);
      QString s=m_query;
      try {
	sql_stream sq(m_query, *m_cnx);
	sq << part_no;
	sq.execute();
	store_results(sq, m_max_results-m_tuples_count);
	m_tuples_count += sq.row_count();
      }
      catch(db_excpt& x) {
	m_errstr = x.errmsg();
	break;
      }
    } while (m_tuples_count<m_max_results && parts_idx<m_psearch.m_parts.size());
    m_psearch.m_nb_fetched_parts = parts_idx-1;
  }
  else {
    // search not involving the word indexes
    db_transaction trans(*m_cnx);
    try {
      sql_stream st("SET TRANSACTION READ ONLY", *m_cnx);
      sql_stream sq(m_query, *m_cnx);
      sq.execute();
      trans.commit();
      store_results(sq, m_max_results>0?m_max_results:-1);
      m_tuples_count = sq.row_count();
    }
    catch(db_excpt& x) {
      m_errstr = x.errmsg();
      trans.rollback();
    }
  }
  m_exec_time = start.elapsed();

}

// stop the fetch
void
fetch_thread::cancel()
{
  if (m_cnx) {
    DBG_PRINTF(5, "fetch_thread::cancel()");
    PGconn* c = m_cnx->connection();
    char errbuf[256];
    PGcancel* cancel = PQgetCancel(c);
    if (cancel) {
      PQcancel(cancel, errbuf, sizeof(errbuf));
      PQfreeCancel(cancel);
      m_cancelled=true;
    }
  }
  m_fetch_more=false;
}

void
fetch_thread::release()
{
  DBG_PRINTF(4, "fetch_thread::release()");
  if (m_cnx) {
    delete m_cnx;
    m_cnx=NULL;
  }
  m_fetch_more=false;
}

int
msgs_filter::parse_search_string(QString s, fts_options& opt)
{
  int state=10;
  QString curr_word;
  QString curr_substr;
  QString curr_op, curr_opval;
  uint len=s.length();
  for (uint i=0; i<len; i++) {
    QChar c=s.at(i);
    DBG_PRINTF(7, "p i=%u, char=%c, state=%d", i, c.toLatin1(), state);
    if (c==QChar('"')) {
      if (state==10) state=40;
      else if (state==40) {
	if (!curr_substr.isEmpty())
	  opt.m_substrs.append(curr_substr);
	state=10;
      }
      else if (state==50) {
	curr_word.append(c);
	curr_substr.append(c);	
      }
    }
    else if (state==10 && c==QChar(':')) {
      state=20; // search option expressed as optname:optvalue
      curr_op=curr_word;
      curr_opval.truncate(0);
      curr_word.truncate(0);
    }
    else if (state==20) {
      if (c==' ') {
	opt.m_operators.insert(curr_op, curr_opval);
	state=10;
      }
      else
	curr_opval.append(c); // TODO: better fail if c is not LetterOrNumber
    }
    else if (state==40 && c==QChar('\\')) {
      // next character is quoted
      state=50;
    }
    else if (!(c.isLetterOrNumber() || c=='_' || c=='.' || c=='@' || c=='-')) {
      // delimiter
      if (state==10 || state==40 || state==50) {
	if (!curr_word.isEmpty()) {
	  if (curr_word.at(0)=='-')
	    opt.m_exclude_words.append(curr_word.mid(1));
	  else
	    opt.m_words.append(curr_word);
	  curr_word.truncate(0);
	}
      }
      if (state==40 || state==50) {
	curr_substr.append(c);
      }
    }
    else {
      // non-delimiter (potential word constituant)
      curr_word.append(c);	// A4
      if (state==40 || state==50)
	curr_substr.append(c); // A6
    }
  }
  if (state!=10 && state!=20) {
    DBG_PRINTF(3, "parse error: state=%d", state);
  }
  if (state==20)
    opt.m_operators.insert(curr_op, curr_opval);

  if (!curr_word.isEmpty()) {
    if (curr_word.at(0)=='-')
      opt.m_exclude_words.append(curr_word.mid(1));
    else
      opt.m_words.append(curr_word);
  }

  return 0;
}

QString
msgs_filter::quote_like_arg(const QString& s)
{
  QString r=s;
  r.replace('\\', "\\\\");
  r.replace('_', "\\_");
  r.replace('\'', "\\'");
  r.replace('%', "\\%");
  r.append('%');
  r.prepend('%');
  return r;
}

/*
  Return values:
  0: database error
  1: OK
  2: the query would return no result (currently, it happens when a condition
   is set on an email address that doesn't exist in the ADDRESSES table,
   or a tag that's not in the TAGS table)

  'fetch_more' can be set on subsequent invocations to fetch more results
  (think "next page") based on the lower/higher (msg_date,mail_id) previously
  retrieved
*/
int
msgs_filter::build_query(sql_query& q, bool fetch_more/*=false*/)
{
  Q_UNUSED(fetch_more);
  db_cnx db;
  try {
    bool done_with_status=false;	// true when tests on status have been short-circuited
    QString sWhere;
    QString main_table;
    int status_set = m_status_set;
    int status_unset = m_status_unset;
    if (!m_include_trash) {
      if (m_in_trash) {
	status_set |= mail_msg::statusTrashed;
      }
      else {
	status_unset |= mail_msg::statusTrashed;
      }
    }

    main_table="mail m";
    if (m_tag_name == "(No tag set)") {
      /* optimize the cases of "no tag set" condition AND'ed with
	 some particular values for the status */
      if (m_status == -1 && !m_in_trash && m_status_set==0 && m_status_unset == mail_msg::statusArchived) {
	// current and not tagged
	q.add_clause(QString("m.mail_id in (SELECT ms.mail_id FROM mail_status ms"
			     " LEFT OUTER JOIN mail_tags mt ON mt.mail_id=ms.mail_id"
			     " WHERE  mt.mail_id is null AND ms.status&%1=0)")
		     .arg(mail_msg::statusArchived|mail_msg::statusTrashed));
	done_with_status=true;
      }
      else if (m_status==0) {
	// new and not tagged
	q.add_clause(QString("m.mail_id in (SELECT ms.mail_id FROM mail_status ms"
			     " LEFT OUTER JOIN mail_tags mt ON mt.mail_id=ms.mail_id"
			     " WHERE  mt.mail_id is null AND ms.status=0)"));
	done_with_status=true;
      }
      else {
	main_table += " LEFT OUTER JOIN mail_tags mt ON mt.mail_id=m.mail_id";
	q.add_clause(" mt.mail_id is null");
      }
    }
    q.add_table(main_table);

    // bounds. m_mail_id_bound and m_date_bound should be either both set or both unset
#if 0
    if (!m_boundary.isEmpty() && !m_date_bound.isEmpty() && m_order<0) {
      q.add_clause(QString("msg_date<=to_date('%1','YYYYMMDDHH24MISS') AND to_char(msg_date,'YYYYMMDDHH24MISS')||m.mail_id::text<'%2'").arg(m_date_bound).arg(m_boundary));
    }
#endif

    if (!m_sql_stmt.isEmpty()) {
      q.add_clause(QString("m.mail_id in (") + m_sql_stmt + QString(")"));
    }

    if (m_min_prio <= max_possible_prio) {
      q.add_clause(QString("m.priority>=%1").arg(m_min_prio));
    }
    if (!m_sAddress.isEmpty()) {
      int addr_type;
      switch(m_nAddrType) {
	// is it really necessary to translate here? TODO: find a way
	// to get the caller to pass an addr_type from the mail_address enum
      case rFrom:
	addr_type = mail_address::addrFrom;
	break;
      case rTo:
	addr_type = mail_address::addrTo;
	break;
      case rCc:
	addr_type = mail_address::addrCc;
	break;
      default:
	addr_type = 0; // any
	break;
      }
      if (!add_address_selection(q, m_sAddress, addr_type))
	return 2;
    }
    if (!m_addr_to.isEmpty()) {
      if (!add_address_selection(q, m_addr_to, mail_address::addrTo))
	return 2;
    }
    //    if (!m_tag_name.isEmpty() && m_tag_name!="(No tag set)") {
    if (m_tag_id!=0) {
      q.add_table("mail_tags mq");
      q.add_clause(QString("mq.mail_id=m.mail_id and mq.tag=%1").arg(m_tag_id));
    }
    if (!m_subject.isEmpty()) {
      q.add_clause("subject", quote_like_arg(m_subject), " ILIKE ");
    }
    if (m_thread_id) {
      q.add_clause("thread_id", (int)m_thread_id);
    }

    // <date clause>
    if (!m_date_min.isNull() && m_date_min.isValid()) {
      QString date_clause;
      if (!m_date_max.isNull() && m_date_max.isValid()) {
	date_clause = QString("msg_date >= '%1'::date and msg_date<'%2'::date+1::int").arg(m_date_min.toString("yyyy/M/d")).arg(m_date_max.toString("yyyy/M/d"));
      }
      else {
	date_clause = QString("msg_date>='%1'::date").arg(m_date_min.toString("yyyy/M/d"));
      }
      q.add_clause(date_clause);
    }
    else if (!m_date_max.isNull() && m_date_max.isValid()) {
      q.add_clause(QString("msg_date<'%1'::date+1::int").arg(m_date_max.toString("yyyy/M/d")));
    }

    if (!m_date_clause.isEmpty()) {
      if (m_date_clause=="today") {
	q.add_clause("msg_date>=date_trunc('day',now()) AND msg_date<date_trunc('day',now())+'1 day'::interval");
      }
      else if (m_date_clause=="yesterday") {
	q.add_clause("msg_date>=date_trunc('day',now())-'1 day'::interval AND msg_date<date_trunc('day',now())");
      }
      else if (m_date_clause.at(0)=='-') {
	QRegExp rx("^-([0-9]+)$");
	if (m_date_clause.indexOf(rx)==0) {
	  int days=rx.capturedTexts().at(1).toInt();
	  q.add_clause(QString("msg_date>=now()-'%1 days'::interval").arg(days));
	}
      }
    }
    // </date clause>

    if (!m_body_substring.isEmpty()) {
      q.add_table("body b");
      q.add_clause(QString("strpos(b.bodytext,'") + m_body_substring + QString("')>0 and m.mail_id=b.mail_id"));
    }

    if (!m_fts.m_words.isEmpty()) {
      if (get_config().get_string("search/accents")=="unaccented" ||
	  m_fts.m_operators["accents"]=="i" ||
	  m_fts.m_operators["accents"]=="insensitive")
	{
	  db_word::unaccent(m_fts.m_words);
	  db_word::unaccent(m_fts.m_exclude_words);
	}
      QString words_incl = db_word::format_db_string_array(m_fts.m_words, db);
      QString words_excl = db_word::format_db_string_array(m_fts.m_exclude_words, db);
      q.add_clause(QString("m.mail_id in (select * from wordsearch(%1,%2))").arg(words_incl).arg(words_excl));
    }

    if (!m_fts.m_substrs.empty()) {
      QStringList::Iterator it = m_fts.m_substrs.begin();
      if (it!=m_fts.m_substrs.end())
	q.add_table("body b");
      for (; it!=m_fts.m_substrs.end(); ++it) {
	q.add_clause("bodytext ilike '" + quote_like_arg(*it) + "' AND m.mail_id=b.mail_id");
      }
    }

    if (m_mailId) {
      //sWhere.sprintf(" WHERE mail_id=%d", m_mailId);
      q.add_clause("mail_id", (int)m_mailId);
    }
    if (m_newer_than) {
      QString s;
      s.sprintf("(msg_date>=date_trunc('days',now()-interval '%d days'))", m_newer_than);
      //      s.sprintf("msg_day>=%d", days_to_now-(m_newer_than-1));
      q.add_clause(s);
    }
    // Selection on status bitmasks
    if ((status_set || status_unset) && !done_with_status && m_status==-1) {
      QString s;
      if (status_set) {
	if (!status_unset) {
	  s.sprintf("status&%d=%d", status_set, status_set);
	}
	else
	  s.sprintf("(status&%d=%d AND status&%d=0)", status_set, status_set, status_unset);
      }
      else {
	if (status_unset == mail_msg::statusArchived ||
	    status_unset == mail_msg::statusArchived+mail_msg::statusTrashed) {
	  // unprocessed messages: optimize by joining with mail_status
	  q.add_table("mail_status ms");
	  s.sprintf("ms.mail_id=m.mail_id AND ms.status&%d=0", status_unset);
	}
	else {
	  s.sprintf("status&%d=0", status_unset);
	}
      }
      q.add_clause(s);
    }
    if (m_status!=-1 && !done_with_status) {
      if (m_status==0) {
	// new messages: optimize by joining with mail_status
	q.add_table("mail_status ms");
	q.add_clause("ms.mail_id=m.mail_id AND ms.status=0");
      }
      else {
	QString s;
	s.sprintf("status=%d", m_status);
	q.add_clause(s);
      }
    }

    if (m_fts.m_words.isEmpty()) {
      QString sFinal="ORDER BY msg_date";
      if (m_order<0)
	sFinal+=" DESC";
      sFinal+=",mail_id";
      if (m_order<0)
	sFinal+=" DESC";
      if (m_max_results>0) {
	QString s;
	s.sprintf(" LIMIT %u", m_max_results+1);
	sFinal+=s;
      }
      q.add_final(sFinal);
    }
    else {
      // wordsearch has a different limit and sort
      // currently: none
      QString sFinal="ORDER BY to_char(msg_date,'YYYYMMDDHH24MISS') DESC, m.mail_id DESC";
      q.add_final(sFinal);
    }

    QString select = "SELECT m.mail_id,sender,subject,to_char(msg_date,'YYYYMMDDHH24MISS'),thread_id,m.status,in_reply_to,sender_fullname,priority,flags,recipients";
    q.start(select);
    if (m_sql_stmt.isEmpty())
      m_user_query = q.subquery("m.mail_id");
    else
      m_user_query=m_sql_stmt;
  }
  catch(db_excpt& p) {
    DBEXCPT(p);
    return 0;
  }
  return 1;
}

/*
  Return values: same as build_query()
*/
int
msgs_filter::asynchronous_fetch(fetch_thread* t, bool fetch_more/*=false*/)
{
  m_fetched = true;
  sql_query q;
  int r = build_query(q, fetch_more);
  if (r==1) {
    // start the query only if it might return results
    if (m_fetch_results)
      delete m_fetch_results;
    m_fetch_results = new std::list<mail_result>;
    t->m_results = m_fetch_results;
    t->m_fetch_more = fetch_more;
    t->m_max_results = m_max_results;
    t->m_psearch = m_psearch;
    if (!t->m_cnx) {
      t->m_cnx = new db_cnx(true);
      if (!t->m_cnx->ping()) {
	DBG_PRINTF(3, "Connection to database lost. Trying to reconnect");
	if (!t->m_cnx->datab()->reconnect()) {
	  DBG_PRINTF(3, "Failed to reconnect to the database");
	  m_errmsg = QObject::tr("No connection to the database.");
	  return 0;
	}
      }
    }
    t->m_query = q.get();
    m_start_time = QTime::currentTime();
    t->start();
  }
  return r;
}

/*
  Fetch the selection into a mail list widget
  Return values:
   0. fetch error. m_errmsg may contain an error message
   1. OK
   2. A condition doesn't match even before running the main query
     (example: non-existing email address)

  'fetch_more' can be set on subsequent invocations to fetch more results
  (think "next page") based on the lower/higher (msg_date,mail_id) previously
  retrieved
*/
int
msgs_filter::fetch(mail_listview* qlv, bool fetch_more/*=false*/)
{
  DBG_PRINTF(8, "msgs_filter::fetch()");
  m_errmsg=QString::null;
  m_fetched = true;
  int r=1;
  try {
    sql_query q;
    r=build_query(q, fetch_more);
    if (r==1) {
      db_cnx db;
      QString s=q.get();
      QByteArray qb_query = s.toUtf8();
      const char* query = qb_query.constData();
      m_exec_time=0;
      m_start_time = QTime::currentTime();
#ifdef WITH_PGSQL
      PGconn* c=GETDB();
      PGresult* res=NULL;
      PGresult* res1=NULL;
      PGresult* res2=NULL;

      res1 = PQexec(c, "BEGIN; SET TRANSACTION READ ONLY");
      if (!res1 || PQresultStatus(res1)!=PGRES_COMMAND_OK) {
	QMessageBox::warning(NULL, APP_NAME, QObject::tr("Unable to execute query.") + QString("\n")+ PQerrorMessage(c));
      }
      else {
	DBG_PRINTF(5,"query=%s", query);
	res = PQexec(c, query);
	if (res && PQresultStatus(res)==PGRES_TUPLES_OK) {
	  m_exec_time = m_start_time.elapsed();
	  m_fetch_results = new std::list<mail_result>;
	  if (m_fetch_results) {
	    load_result_list(res, m_fetch_results, m_max_results-1);
	    make_list(qlv);
	    delete m_fetch_results;
	    m_fetch_results=NULL;
	  }
	  PGresult* res4=PQexec(c, "END");
	  if (res4)
	    PQclear(res4);
	}
	else {
	  DBG_PRINTF(2, "PQexec error: %s", PQerrorMessage(c));
	  m_exec_time=-1;
	  m_errmsg = PQerrorMessage(c);
	  PGresult* res3 = PQexec(c, "ROLLBACK");
	  QMessageBox::warning(NULL, APP_NAME, QObject::tr("Unable to execute query.") + QString("\n")+ m_errmsg);
	  if (!res3 || PQresultStatus(res3)!=PGRES_COMMAND_OK) {
	    QMessageBox::warning(NULL, QObject::tr("Database error"), QObject::tr("In addition, could not execute ROLLBACK.") + QString("\n")+ PQerrorMessage(c));
	  }
	}
      }
      if (res)
	PQclear(res);
      if (res1)
	PQclear(res1);
      if (res2)
	PQclear(res2);
#endif
    }
    else if (r==0) {
      QMessageBox::information(NULL, APP_NAME, QObject::tr("Fetch error"));
    }
    else if (r==2) {
      QMessageBox::information(NULL, APP_NAME, QObject::tr("No results"));
    }
  }
  catch (int e) {
    r=e;
  }
  return r;
}

int
msgs_filter::exec_time() const
{
  return m_exec_time;
}

void
msgs_filter::add_result(mail_result& r, mail_listview* qlv)
{
  mail_result* iter = &r;
  if (qlv->find(iter->m_id)!=NULL)
    return;		// avoid duplicates
  {
    mail_msg* msg = new mail_msg(r);
    m_list_msgs.push_back(msg); // FIXME: check if we still need that list
    std::list<mail_msg*> list;
    list.push_back(msg);
    qlv->insert_list(list);
  }
}

void
msgs_filter::make_list(mail_listview* qlv)
{
  bool all_outgoing=false;
  if (!m_fetch_results)
    return;       // No result

  bool refetch=false;
  if (!qlv->empty()) {
    /* the list already contains some messages. That means
       that we'll test the messages coming from the db against
       those already in the list to avoid duplicates */
    refetch=true;
  }


  std::list<mail_result>::const_iterator iter = m_fetch_results->begin();
  if (iter!=m_fetch_results->end())
    all_outgoing = (iter->m_status & mail_msg::statusOutgoing) == mail_msg::statusOutgoing;

  for (; iter != m_fetch_results->end(); ++iter) {
    if (refetch) {
      if (qlv->find(iter->m_id)!=NULL)
	continue;		// avoid duplicates
    }

    mail_msg* msg = new mail_msg(iter->m_id, iter->m_from, iter->m_subject,
				 date(iter->m_date));
    msg->setThread(iter->m_thread_id);
    msg->set_orig_status(iter->m_status);
    msg->setStatus(iter->m_status);
    all_outgoing &= (iter->m_status & mail_msg::statusOutgoing) == mail_msg::statusOutgoing;
    msg->setInReplyTo((mail_id_t)iter->m_in_replyto);
    msg->set_sender_name(iter->m_sender_name);
    msg->set_flags(iter->m_flags);
    msg->set_priority(iter->m_pri);
    msg->set_recipients(iter->m_recipients);

    m_list_msgs.push_back(msg);
  }
  
  if (all_outgoing && qlv->empty() && get_config().get_bool("display/auto_sender_column"))
    qlv->swap_sender_recipient(true);

  qlv->insert_list(m_list_msgs);
}

/*
  Dialog used to fill in the criteria for a database fetch

  if 'open_new' is true, open a new msg_list_window with the results.
  if false, the caller has to provide a slot for the 'fetch_done' signal
  that will be emitted when the results are available.
*/
msg_select_dialog::msg_select_dialog(bool open_new/*=true*/) : QDialog(0)
{
  setWindowTitle(tr("Query selection"));
  setWindowIcon(UI_ICON(FT_ICON16_NEW_QUERY));
  m_waiting_for_results = false;
  m_new_selection=open_new;

  QVBoxLayout* topLayout = new QVBoxLayout(this);
  topLayout->addWidget(new QLabel(tr("Fill in the selection criteria:")));

  QGridLayout* gridLayout = new QGridLayout();
  topLayout->addLayout(gridLayout);

  int nRow=0;
  m_wAddrType=new QComboBox(this);
  // each index in the combobox must match an entry in the enum
  // msgs_filter::recipient_type
  m_wAddrType->addItem("From");
  m_wAddrType->addItem("To");
  m_wAddrType->addItem("Cc");
  m_wAddrType->addItem("Any");
  gridLayout->addWidget(m_wAddrType,nRow,0);
  m_wcontact = new edit_address_widget(this);
  gridLayout->addWidget(m_wcontact, nRow, 1);

  nRow++;
  gridLayout->addWidget(new QLabel(tr("Date:")), nRow, 0);
  QHBoxLayout* hldate = new QHBoxLayout;
  hldate->setSpacing(2);

  m_date_cb = new QComboBox;
  m_date_cb->addItem(tr("Any date"), QVariant("")); // index for "Any date" must be 0
  m_date_cb->addItem(tr("Today"), QVariant("today"));
  m_date_cb->addItem(tr("Yesterday"), QVariant("yesterday"));
  m_date_cb->addItem(tr("Last 7 days"), QVariant("-7"));
  m_date_cb->addItem(tr("Last 30 days"), QVariant("-30"));
  m_date_cb->addItem(tr("Last 60 days"), QVariant("-60"));
  m_date_cb->addItem(tr("Last 90 days"), QVariant("-90"));
  m_date_cb->addItem(tr("Last 365 days"), QVariant("-365"));
  m_date_cb->addItem(tr("Range..."), QVariant("range"));
  connect(m_date_cb, SIGNAL(currentIndexChanged(int)), this, SLOT(date_cb_changed(int)));
  hldate->addWidget(m_date_cb);

  m_chk_datemin = new QCheckBox(tr("Start"));
  m_chk_datemin->setStyleSheet("QCheckBox{spacing:0px}");
  m_wmin_date = new QDateTimeEdit;
  m_wmin_date->setCalendarPopup(true);
  m_wmin_date->setDate(QDate::currentDate());

  m_chk_datemax = new QCheckBox(tr("End"));
  m_chk_datemax->setStyleSheet("QCheckBox{spacing:0px}");
  m_wmax_date = new QDateTimeEdit;
  m_wmax_date->setCalendarPopup(true);
  m_wmax_date->setDate(QDate::currentDate());

  hldate->addWidget(m_chk_datemin);
  hldate->addWidget(m_wmin_date);
  hldate->addWidget(m_chk_datemax);
  hldate->addWidget(m_wmax_date);

  gridLayout->addLayout(hldate, nRow, 1);

  QString df = get_config().get_string("date_format");
  QString dorder="yyyy/MM/dd";
  if (df.startsWith("DD/MM/YYYY")) {
    dorder="dd/MM/yyyy";		// european-style date
  }
  m_wmax_date->setDisplayFormat(dorder);
  m_wmin_date->setDisplayFormat(dorder);

  nRow++;
  QLabel* lTo = new QLabel(tr("To:"), this);
  gridLayout->addWidget(lTo,nRow,0);
  m_wto = new edit_address_widget(this);
  gridLayout->addWidget(m_wto, nRow, 1);

  nRow++;
  QLabel* lSubject=new QLabel(tr("Subject:"), this);
  gridLayout->addWidget(lSubject,nRow,0);
  m_wSubject=new QLineEdit(this);
  gridLayout->addWidget(m_wSubject, nRow, 1);

  nRow++;
  QLabel* lString=new QLabel(tr("Contains:"), this);
  gridLayout->addWidget(lString,nRow,0);
  m_wString=new QLineEdit(this);
  gridLayout->addWidget(m_wString, nRow, 1);

  nRow++;
  QLabel* lSql=new QLabel(tr("SQL statement:"), this);
  gridLayout->addWidget(lSql,nRow,0);
  QHBoxLayout* hbz = new QHBoxLayout();
  QIcon zoom_icon(FT_MAKE_ICON(FT_ICON16_ZOOM_PAGE));
  m_wSqlStmt=new QLineEdit();
  hbz->addWidget(m_wSqlStmt);
  m_zoom_button = new QToolButton();
  hbz->addWidget(m_zoom_button);
  m_zoom_button->setIcon(zoom_icon);
  m_zoom_button->setToolTip(tr("Zoom"));
  gridLayout->addLayout(hbz, nRow, 1);
  connect(m_zoom_button, SIGNAL(clicked()), this, SLOT(zoom_on_sql()));

  // Selection by tag
  nRow++;
  QLabel* lTag = new QLabel(tr("Tag:"), this);
  gridLayout->addWidget(lTag,nRow,0);

  m_qtag_sel = new tag_selector(this);
  m_qtag_sel->init(true);
  gridLayout->addWidget(m_qtag_sel, nRow, 1);

  nRow++;
  gridLayout->addWidget(new QLabel(tr("Status:"), this), nRow,0);
  {
    QHBoxLayout* hb=new QHBoxLayout();
    hb->setSpacing(5);
    m_wStatus=new QLineEdit("status");
    hb->addWidget(m_wStatus);
    m_wStatus->setReadOnly(true);
    m_wStatus->setText(tr("Any"));
    m_wStatusMoreButton = new QPushButton(tr("Edit..."));
    hb->addWidget(m_wStatusMoreButton);
    gridLayout->addLayout(hb, nRow, 1);
    connect(m_wStatusMoreButton, SIGNAL(clicked()), this, SLOT(more_status()));
    // the default choice for the status is "either" for both set
    // and unset bits (=no filtering against status)
    m_status_set_mask = 0;
    m_status_unset_mask = 0; //mail_msg::statusArchived;
  }

  nRow++;
  gridLayout->addWidget(new QLabel(tr("Limit to:"),this), nRow, 0);
  // Limit to: [max results] messages (3 widgets)
  QHBoxLayout* hbox_lt=new QHBoxLayout();
  hbox_lt->setSpacing(10);
  m_wMaxResults=new QLineEdit();
  hbox_lt->addWidget(m_wMaxResults);
  QFontMetrics fm(m_wMaxResults->font());
  m_wMaxResults->setMaximumWidth(fm.width("000000")+15);

  m_wMaxResults->setText(get_config().get_string("max_msgs_per_selection"));
  hbox_lt->addWidget(new QLabel(tr("messages")));
  hbox_lt->addStretch(10);
  gridLayout->addLayout(hbox_lt, nRow, 1);

  nRow++;
  m_trash_checkbox = new QCheckBox(tr("In trashcan"),this);
  gridLayout->addWidget(m_trash_checkbox, nRow, 1);

  nRow++;
  QHBoxLayout* hbox=new QHBoxLayout;
  hbox->setMargin(10);
  hbox->setSpacing(20);

  m_btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help);
  gridLayout->addWidget(m_btn_box, nRow, 0, 1, -1);

  connect(m_btn_box, SIGNAL(helpRequested()), this, SLOT(help()));
  connect(m_btn_box, SIGNAL(accepted()), this, SLOT(ok()));
  connect(m_btn_box, SIGNAL(rejected()), this, SLOT(cancel()));
  enable_date_range();
}

void
msg_select_dialog::date_cb_changed(int index)
{
  Q_UNUSED(index);
  enable_date_range();
  if (m_date_cb->itemData(index).toString()=="range") {
    m_chk_datemax->setEnabled(true);
    m_chk_datemin->setEnabled(true);
    if (!m_chk_datemax->isChecked() && !m_chk_datemin->isChecked()) {
      /* if the user chooses the range option and no bound is checked yet,
	 check one automatically to avoid no-range-at-all results */
      m_chk_datemin->setChecked(true);
    }
  }
}

QString
msg_select_dialog::str_status_mask()
{
  struct st_status {
    const char* name;
    int value;
  };
  static const st_status status_tab[] = {
    {QT_TR_NOOP("Rd"), mail_msg::statusRead},
    {QT_TR_NOOP("Rp"), mail_msg::statusReplied},
    {QT_TR_NOOP("F"), mail_msg::statusFwded},
    {QT_TR_NOOP("T"), mail_msg::statusTrashed},
    {QT_TR_NOOP("A"), mail_msg::statusArchived},
    {QT_TR_NOOP("O"), mail_msg::statusOutgoing},
    {QT_TR_NOOP("C"), mail_msg::statusComposed},
    {QT_TR_NOOP("S"), mail_msg::statusSent}
  };

  QString s;
  const QChar sep='+';
  for (uint i=0; i<sizeof(status_tab)/sizeof(status_tab[0]); i++) {
    if (m_status_set_mask & status_tab[i].value) {
      if (!s.isEmpty())
	s.append(sep);
      s.append(tr(status_tab[i].name));
    }
    if (m_status_unset_mask & status_tab[i].value) {
      if (!s.isEmpty())
	s.append(sep);
      s.append("!" + tr(status_tab[i].name));
    }
  }
  if (s.isEmpty())
    s="Any";
  return s;
}

status_dialog::status_dialog(QWidget* parent): QDialog(parent)
{
  QVBoxLayout* topLayout = new QVBoxLayout(this);
  m_statusBox = new select_status_box(true, this);
  topLayout->addWidget(m_statusBox);

  QHBoxLayout* hbox=new QHBoxLayout;
  hbox->setMargin(10);
  hbox->setSpacing(20);
  QPushButton* wOk=new QPushButton(tr("OK"));
  QPushButton* wCancel=new QPushButton(tr("Cancel"));
  hbox->addWidget(wOk);
  hbox->addWidget(wCancel);
  connect(wOk,SIGNAL(clicked()), this, SLOT(accept()));
  connect(wCancel,SIGNAL(clicked()), this, SLOT(reject()));
  topLayout->addLayout(hbox);
}

// Pops up the full status selection panel
void
msg_select_dialog::more_status()
{
  status_dialog* statusDlg=new status_dialog(this);

  statusDlg->m_statusBox->set_mask(m_status_set_mask, m_status_unset_mask);
  int r=statusDlg->exec();
  if (r==QDialog::Accepted) {
    m_status_set_mask=statusDlg->m_statusBox->mask_yes();
    m_status_unset_mask=statusDlg->m_statusBox->mask_no();
    m_wStatus->setText(str_status_mask());
  }
  delete statusDlg;
}

// Pops up a dialog for full text SQL editing
void
msg_select_dialog::zoom_on_sql()
{
  sql_editor* w=new sql_editor(this);
  QString initial_txt = m_wSqlStmt->text();
  w->set_text(initial_txt);
  int ret=w->exec();
  if (ret && w->get_text() != initial_txt) {
    m_wSqlStmt->setText(w->get_text());
  }
  w->close();
}

void
msg_select_dialog::to_filter(msgs_filter* filter)
{
  filter->m_body_substring = m_wString->text();
  filter->m_subject = m_wSubject->text();
  filter->m_sql_stmt = m_wSqlStmt->text();
  if (!m_wcontact->text().trimmed().isEmpty())
    filter->m_sAddress = mail_address::parse_extract_email(m_wcontact->text().trimmed());
  else
    filter->m_sAddress = QString::null;
  filter->m_nAddrType = m_wAddrType->currentIndex();

  if (!m_wto->text().trimmed().isEmpty())
    filter->m_addr_to = mail_address::parse_extract_email(m_wto->text().trimmed());
  else
    filter->m_addr_to = QString::null;
    
  filter->m_tag_id = m_qtag_sel->current_tag_id();

  filter->m_in_trash = m_trash_checkbox->isChecked();

  if (m_date_cb->currentIndex()>0) {
    QString str=m_date_cb->itemData(m_date_cb->currentIndex()).toString();
    filter->m_date_clause=str;
    if (str=="range") {
      if (m_chk_datemax->isChecked())
	filter->m_date_max = m_wmax_date->date();
      else
	filter->m_date_max = QDate();
      if (m_chk_datemin->isChecked())
	filter->m_date_min = m_wmin_date->date();
      else
	filter->m_date_min = QDate();
    }
  }
  else {
    filter->m_date_clause=QString::null;
    filter->m_date_min = QDate();
    filter->m_date_max = QDate();
  }

  if (m_wMaxResults->text().isEmpty())
    filter->m_max_results=0;	// no limit
  else {
    bool ok;
    filter->m_max_results=m_wMaxResults->text().toUInt(&ok);
    if (!ok) {
      QMessageBox::information(this, "Error", tr("Error: non-numeric value for the maximum number of messages"));
      return;
    }
  }
  filter->m_status_set=m_status_set_mask;
  filter->m_status_unset=m_status_unset_mask;
}

void
msg_select_dialog::filter_to_dialog(const msgs_filter* filter)
{
  m_wmin_date->setDate(filter->m_date_min);
  if (!filter->m_date_min.isNull())
    m_chk_datemin->setChecked(true);
  m_wmax_date->setDate(filter->m_date_max);
  if (!filter->m_date_max.isNull())
    m_chk_datemax->setChecked(true);
  m_wString->setText(filter->m_body_substring);
  m_wSubject->setText(filter->m_subject);
  m_wSqlStmt->setText(filter->m_sql_stmt);
  m_wcontact->setText(filter->m_sAddress);
  m_wAddrType->setCurrentIndex(filter->m_nAddrType);
  m_trash_checkbox->setChecked(filter->m_in_trash);

  // tags
  if (filter->m_tag_id!=0) {
    m_qtag_sel->set_current_tag_id(filter->m_tag_id);
  }

  m_wto->setText(filter->m_addr_to);
  if (filter->m_max_results > 0) {
    m_wMaxResults->setText(QString("%1").arg(filter->m_max_results));
  }
  else
    m_wMaxResults->setText(QString::null);

  if (!filter->m_date_clause.isEmpty()) {
    for (int i=1; i<m_date_cb->count(); i++) {
      if (m_date_cb->itemData(i)==filter->m_date_clause) {
	m_date_cb->setCurrentIndex(i);
	break;
      }
    }
  }
  else
    m_date_cb->setCurrentIndex(0);

  m_status_set_mask = filter->m_status_set;
  m_status_unset_mask = filter->m_status_unset;
  m_wStatus->setText(str_status_mask());
  enable_date_range();
}

void
msg_select_dialog::timer_done()
{
  if (m_waiting_for_results && m_thread.isFinished()) {
    m_waiting_for_results=false;
    DBG_PRINTF(5,"msg_select_dialog::timer_done()");
    QApplication::restoreOverrideCursor();

    m_filter.postprocess_fetch(m_thread);

    if (m_filter.m_fetch_results && m_filter.m_fetch_results->size() > 0) {
      if (m_new_selection) {
	msg_list_window* w = new msg_list_window(&m_filter, 0);
	w->show();
      }
      else {
	emit fetch_done(&m_filter);
      }
      m_thread.release();
      close();
    }
    else {
      m_waiting_for_results = false;
      m_timer->stop();
      delete m_timer;
      if (!m_thread.m_errstr.isEmpty()) {
	QMessageBox::information(this, APP_NAME, m_thread.m_errstr);
      }
      else
	QMessageBox::information(this, APP_NAME, tr("No results"));
      m_btn_box->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
      enable_inputs(true);
      enable_date_range();
      m_thread.release();
    }
  }
}

void
msg_select_dialog::enable_inputs(bool enable)
{
  QWidget* widgets[] = {
    m_wcontact, m_wAddrType, m_qtag_sel, m_wto,
    m_wSubject, m_wString, m_wSqlStmt, m_wStatus, m_wMaxResults,
    m_wStatusMoreButton, m_wmin_date, m_wmax_date,
    m_chk_datemax, m_chk_datemin,
    m_zoom_button
  };
  for (uint i=0; i<sizeof(widgets)/sizeof(widgets[0]); i++)
    widgets[i]->setEnabled(enable);
  m_btn_box->button(QDialogButtonBox::Ok)->setEnabled(enable);
}

/* Enable the controls for the date range, but only if Range is selected
   in the Date combobox */
void
msg_select_dialog::enable_date_range()
{
  int idx=m_date_cb->currentIndex();
  bool en=(idx>=0 && m_date_cb->itemData(idx)=="range");
  m_chk_datemax->setEnabled(en);
  m_wmax_date->setEnabled(en);
  m_chk_datemin->setEnabled(en);
  m_wmin_date->setEnabled(en);
}

void
msg_select_dialog::ok()
{
  //  msgs_filter cFilter;
  to_filter(&m_filter);
  int r = m_filter.asynchronous_fetch(&m_thread);
  // at this point, the query is currently being run in m_thread,
  // and we'll check for its completion in timer_done()
  if (r==1) {
    m_timer = new QTimer(this);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(timer_done()));
    m_waiting_for_results = true;
    m_timer->start(100);		// check results every 1/10s

    m_btn_box->button(QDialogButtonBox::Cancel)->setText(tr("Abort"));
    enable_inputs(false);
    const QCursor cursor(Qt::WaitCursor);
    QApplication::setOverrideCursor(cursor);
  }
  else if (r==0) {
    QMessageBox::information(this, APP_NAME, tr("Fetch error"));
  }
  else if (r==2) {
    QMessageBox::information(this, APP_NAME, tr("No results"));
  }
}

void
msg_select_dialog::cancel()
{
  if (m_waiting_for_results) {
    // stop the query but don't close the dialog
    m_waiting_for_results = false;
    m_thread.cancel();
    m_thread.release();
    m_btn_box->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
    QApplication::restoreOverrideCursor();
    enable_inputs(true);
    enable_date_range();
  }
  else {
    // close the dialog
    close();
  }
}

void
msg_select_dialog::help()
{
  helper::show_help("query selection");
}


//static
select_status_box::st_status select_status_box::m_status_tab[] = {
  {QT_TR_NOOP("Read"), mail_msg::statusRead},
  {QT_TR_NOOP("Replied"), mail_msg::statusReplied},
  {QT_TR_NOOP("Forwarded"), mail_msg::statusFwded},
  {QT_TR_NOOP("Trashed"), mail_msg::statusTrashed},
  {QT_TR_NOOP("Archived"), mail_msg::statusArchived},
  {QT_TR_NOOP("Outgoing"), mail_msg::statusOutgoing},
  {QT_TR_NOOP("Sent"), mail_msg::statusSent},
  {QT_TR_NOOP("Composed"), mail_msg::statusComposed}
};

select_status_box::select_status_box(bool either, QWidget* parent):
  QFrame (parent)
{
  //  setTitle("Status");
//    setFrameStyle( QFrame::Box | QFrame::Plain );
//    setLineWidth (2);
//    setMargin(10);

  m_either = either;
  QGridLayout* grid = new QGridLayout(this);
  grid->setMargin(4);
  grid->setSpacing(4);
  int button_id=0;
  for (uint i=0; i<sizeof(m_status_tab)/sizeof(m_status_tab[0]); i++) {
    grid->addWidget(new QLabel(tr(m_status_tab[i].name)), i, 0);
    QCheckBox* b1=new QCheckBox(tr("Yes"));
    QCheckBox* b2=new QCheckBox(tr("No"));
    QCheckBox* b3=new QCheckBox(tr("Either"));
    grid->addWidget(b1, i, 1);
    grid->addWidget(b2, i, 2);
    if (!either)
      b3->hide();
    else
      grid->addWidget(b3, i, 3);
    QButtonGroup* g=new QButtonGroup;
    g->setExclusive(TRUE);
    g->addButton(b1, button_id++);
    g->addButton(b2, button_id++);
    g->addButton(b3, button_id++);
    connect(g, SIGNAL(buttonClicked(int)),SLOT(status_changed(int)));
    m_button_groups.push_back(g);
  }
  m_mask_set=m_mask_unset=0;
}

void
select_status_box::set_mask (int mask_yes, int mask_no)
{
  DBG_PRINTF(3, "mask_yes=%x, mask_no=%x", mask_yes, mask_no);
  const int buttons_per_status = 3;
  for (uint i=0; i<sizeof(m_status_tab)/sizeof(m_status_tab[0]); i++) {
    QButtonGroup* g=m_button_groups[i];
    int v=m_status_tab[i].value;
    QAbstractButton* button;
    if (v & mask_yes) {
      button = g->button(i*buttons_per_status+0); // Yes
      button->setChecked(true);
    }
    else if (v & mask_no) {
      button = g->button(i*buttons_per_status+1); // No
      button->setChecked(true);
    }
    else if (m_either) {
      button = g->button(i*buttons_per_status+2); // Either
      button->setChecked(true);
    }
  }
  m_mask_set=mask_yes;
  m_mask_unset=mask_no;
}

// Ignore the "either" choice and just get the value of the status
int
select_status_box::status() const
{
  return m_mask_set;
}

int
select_status_box::mask_yes() const
{
  return m_mask_set;
}

int
select_status_box::mask_no() const
{
  return m_mask_unset;
}

void
select_status_box::status_changed(int id)
{
  const int buttons_per_status = 3;
  int status = id / buttons_per_status;		// 2 or 3 buttons per status
  QButtonGroup* g=m_button_groups[status];
  if (g) {
    QAbstractButton* b=g->button(id);
    if (b && b->isChecked()) {
      int v=m_status_tab[status].value;
      switch (id % buttons_per_status) {
      case 0:			// Yes
	m_mask_set |= v;
	m_mask_unset &= ~v;
	break;
      case 1:			// No
	m_mask_set &= ~v;
	m_mask_unset |= v;
	break;
      case 2:			// Either
	m_mask_set &= ~v;
	m_mask_unset &= ~v;
	break;
      default:
	DBG_PRINTF(5,"id out of (0,1,2)");
	break;
      }
    }
  }
}


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

List of all available source files