Manitou-Mail logo title

Source file: src/msg_list_window.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 "app_config.h"
#include "errors.h"

#include <fstream>
#include <iostream>
#include <list>

#include "msg_list_window.h"
#include "message_port.h"

#include <time.h>

#include "newmailwidget.h"
#include "body_edit.h"
#include "searchbox.h"
#include "selectmail.h"
#include "notewidget.h"
#include "tagsdialog.h"
#include "addressbook.h"
#include "mail_displayer.h"
#include "tags.h"
#include "filter_log.h"
#include "notepad.h"

#include <QApplication>
#include <QStatusBar>
#include <QProgressBar>

#include <QPushButton>
#include <QToolBar>

#include <QFileDialog>
#include <QFontDialog>
#include <QComboBox>
#include <QMessageBox>
#include <QLayout>
#include <QSplitter>
#include <QHeaderView>
#include <QCursor>
#include <QMenuBar>
#include <QAction>
#include <QTimer>
#include <QCloseEvent>
#include <QShortcut>

#include "msg_properties.h"
#include "users.h"
#include "mail_listview.h"
#include "attachment_listview.h"
#include "message_view.h"
#include "preferences.h"
#include "about.h"
#include "helper.h"
#include "edit_rules.h"
#include "mime_msg_viewer.h"

#include "icons.h"
#include "words.h"

#define STACKED_LV

void
msg_list_window::search_db()
{
  QString txt = m_ql_search->text().toLower();
  if (txt.length()<3) {
    QMessageBox::information(NULL, tr("Search"), tr("Please enter a string containing at least 3 characters"));
    return;
  }
  msgs_filter f;
  //  f.m_max_results=200;
  f.set_date_order(-1);	// latest results first
  f.parse_search_string(txt, f.m_fts);
  //  DBG_PRINTF(3, "words=(%s)\n", f.m_words.join("/").latin1());
  //  DBG_PRINTF(3, "substrs=(%s)\n", f.m_substrs.join("/").latin1());
  //  f.m_words = QStringList::split(" ", txt);
  sel_filter(f);
  m_query_lv->clear_selection();
  QStringList::Iterator it = f.m_fts.m_words.begin();
  m_highlighted_text.clear();
  for (; it!=f.m_fts.m_words.end(); ++it) {
    searched_text s;
    s.m_text=*it;
    s.m_is_cs=false;		// word search is case insensitive
    s.m_is_word=true;
    m_highlighted_text.push_back(s);
  }
}

void
msg_list_window::search_text_changed(const QString& newtext)
{
  // stop highlighting text when the input widget gets cleared
  if (newtext.isEmpty() && !m_highlighted_text.empty()) {
    m_highlighted_text.clear();
    if (!m_fetch_on_demand) {
      m_msgview->clear();
      display_body();
    }
  }
}

void
msg_list_window::make_search_toolbar()
{
  QToolBar* toolbar=addToolBar(tr("Search"));
  toolbar->addWidget(new QLabel(tr("Search:")));
  m_ql_search = new search_edit(this);
  toolbar->addWidget(m_ql_search);
  m_ql_search->setMinimumWidth(200);
  connect(m_ql_search, SIGNAL(returnPressed()), this, SLOT(search_db()));
  connect(m_ql_search, SIGNAL(textChanged(const QString&)), this, SLOT(search_text_changed(const QString&)));
  toolbar->addAction(m_action_search);
}

void
msg_list_window::create_actions()
{
  m_action_move_backward = new QAction(FT_MAKE_ICON(FT_ICON16_ARROW_LEFT),
				       tr("Go back one page"), this);
  m_action_move_backward->setShortcut(Qt::ALT+Qt::Key_Left);
  connect(m_action_move_backward, SIGNAL(triggered()),
	  this, SLOT(move_backward()));

  m_action_move_forward = new QAction(FT_MAKE_ICON(FT_ICON16_ARROW_RIGHT),
				      tr("Go forward one page"), this);
  m_action_move_forward->setShortcut(Qt::ALT+Qt::Key_Right);
  connect(m_action_move_forward, SIGNAL(triggered()), this, SLOT(move_forward()));

  m_action_reply_sender = new QAction(FT_MAKE_ICON(FT_ICON16_REPLY),
				      tr("Reply to sender"), this);
  m_action_reply_sender->setShortcut(Qt::Key_R);
  connect(m_action_reply_sender, SIGNAL(triggered()),
	  this, SLOT(mail_reply_sender()));

  m_action_reply_all = new QAction(FT_MAKE_ICON(FT_ICON16_REPLY_ALL),
				   tr("Reply all"), this);
  m_action_reply_all->setShortcut(Qt::CTRL+Qt::Key_R);
  connect(m_action_reply_all, SIGNAL(triggered()),
	  this, SLOT(mail_reply_all()));

  m_action_reply_list = new QAction(FT_MAKE_ICON(FT_ICON16_REPLY),
				    tr("Reply to list"), this);
  m_action_reply_list->setShortcut(Qt::CTRL+Qt::Key_L);
  addAction(m_action_reply_list);
  connect(m_action_reply_list, SIGNAL(triggered()),
	  this, SLOT(mail_reply_list()));

  m_action_msg_archive = new QAction(FT_MAKE_ICON(FT_ICON16_STATUS_PROCESSED),
				     tr("Archive"), this);
  m_action_msg_archive->setShortcut(Qt::Key_A);
  connect(m_action_msg_archive, SIGNAL(triggered()),
	  this, SLOT(msg_archive()));

  m_action_msg_trash = new QAction(FT_MAKE_ICON(FT_ICON16_ACTION_TRASH),
				   tr("Trash"), this);
  m_action_msg_trash->setShortcut(Qt::Key_Delete);
  connect(m_action_msg_trash, SIGNAL(triggered()),
	  this, SLOT(msg_trash()));

  QAction* action_goto_next = new QAction(tr("Move to next message"), this);
  action_goto_next->setShortcut(Qt::Key_N);
  addAction(action_goto_next);
  connect(action_goto_next, SIGNAL(triggered()), this, SLOT(goto_next_message()));

  QAction* action_goto_prev = new QAction(tr("Move to previous message"), this);
  action_goto_prev->setShortcut(Qt::Key_P);
  addAction(action_goto_prev);
  connect(action_goto_prev, SIGNAL(triggered()), this, SLOT(goto_previous_message()));
  
  m_action_msg_untrash = new QAction(FT_MAKE_ICON(FT_ICON16_UNTRASH),
				     tr("UnTrash"), this);
  connect(m_action_msg_untrash, SIGNAL(triggered()),
	  this, SLOT(msg_untrash()));

  m_action_msg_delete = new QAction(FT_MAKE_ICON(FT_ICON16_DELETE_MSG),
				    tr("Delete"), this);
  m_action_msg_delete->setShortcut(Qt::SHIFT+Qt::Key_Delete);
  connect(m_action_msg_delete, SIGNAL(triggered()),
	  this, SLOT(msg_delete()));

  m_action_cycle_headers = new QAction(tr("Cycle headers"), this);
  m_action_cycle_headers->setShortcut(Qt::Key_H);
  connect(m_action_cycle_headers, SIGNAL(triggered()),
	  this, SLOT(cycle_headers()));
  // this one needs to be added explicitly because it's not part of a menubar
  this->addAction(m_action_cycle_headers);

  m_action_msg_forward = new QAction(FT_MAKE_ICON(FT_ICON16_FORWARD),
				     tr("Forward"), this);
  m_action_msg_forward->setShortcut(Qt::Key_F);
  connect(m_action_msg_forward, SIGNAL(triggered()),
	  this, SLOT(forward()));

  m_action_find_text = new QAction(FT_MAKE_ICON(FT_ICON16_FIND),
				   tr("Find text"), this);
  m_action_find_text->setShortcut(Qt::CTRL+Qt::Key_F);
  connect(m_action_find_text, SIGNAL(triggered()),
	  this, SLOT(find_text()));

  m_action_msg_print = new QAction(FT_MAKE_ICON(FT_ICON16_PRINT),
				   tr("Print"), this);
  connect(m_action_msg_print, SIGNAL(triggered()),
	  this, SLOT(msg_print()));

  m_action_msg_sender_details = new QAction(FT_MAKE_ICON(FT_ICON16_PEOPLE),
					    tr("Sender details"), this);
  connect(m_action_msg_sender_details, SIGNAL(triggered()),
	  this, SLOT(sender_properties()));

  m_action_search = new QAction(FT_MAKE_ICON(FT_ICON16_FIND), tr("Search"), this);
  connect(m_action_search, SIGNAL(triggered()), this, SLOT(search_db()));

  m_action_new_mail = new QAction(FT_MAKE_ICON(FT_ICON16_EDIT), tr("New mail"), this);
  connect(m_action_new_mail, SIGNAL(triggered()), this, SLOT(new_mail()));

  m_action_new_selection = new QAction(FT_MAKE_ICON(FT_ICON16_NEW_QUERY),
				       tr("Query new selection"), this);
  connect(m_action_new_selection, SIGNAL(triggered()), this, SLOT(new_list()));


  m_action_refresh_results = new QAction(FT_MAKE_ICON(FT_ICON16_REFRESH),
					 tr("Refresh query"), this);
  connect(m_action_refresh_results, SIGNAL(triggered()), this, SLOT(sel_refresh()));

  m_action_goto_last_msg = new QAction(FT_MAKE_ICON(FT_ICON16_GOTO_BOTTOM),
				       tr("Select bottom message"), this);
  connect(m_action_goto_last_msg, SIGNAL(triggered()), this, SLOT(sel_bottom()));

  m_action_msgview_select_all = new QAction(tr("Select all"), this);
  connect(m_action_msgview_select_all, SIGNAL(triggered()), this, SLOT(select_all_text()));
}

void
msg_list_window::make_toolbar()
{
  QToolBar* toolbar=addToolBar(tr("Message Operations"));
  toolbar->setIconSize(QSize(16,16));

  toolbar->addAction(m_action_new_mail);
  toolbar->addSeparator();
  toolbar->addAction(m_action_move_backward);
  toolbar->addAction(m_action_move_forward);
  toolbar->addAction(m_action_new_selection);
  toolbar->addAction(m_action_refresh_results);
  toolbar->addAction(m_action_goto_last_msg);

  toolbar->addAction(m_action_reply_sender);
  toolbar->addAction(m_action_reply_all);

  toolbar->addAction(m_action_msg_forward);
  toolbar->addAction(m_action_msg_archive);
  toolbar->addAction(m_action_find_text);
  toolbar->addAction(m_action_msg_trash);
  toolbar->addAction(m_action_msg_print);

  toolbar->addAction(m_action_msg_print);

  toolbar->addAction(m_action_msg_sender_details);

  QAction* action_notepad = new QAction(UI_ICON(ICON16_NOTEPAD),
					tr("Global Notepad"), this);
  connect(action_notepad, SIGNAL(triggered()),
	  this, SLOT(open_global_notepad()));
  toolbar->addAction(action_notepad);

  m_toolbar = toolbar;
}


/*
  Called when a tag is switched on or off: applies the change
  to the selected mail(s)
*/
void
msg_list_window::tag_toggled(int tag_id, bool checked)
{
  DBG_PRINTF(5, "tag_toggled(%d)", tag_id);
  std::vector<mail_msg*> v;
  m_qlist->get_selected(v);

  const uint batch_size=20;
  uint idx=0;
  uint nb_msg = v.size();
  uint size = nb_msg;
  uint nsteps = (size+batch_size-1)/batch_size;
  if (nsteps>1) {
    uint processed=0;
    install_progressbar();
    show_progress(-nsteps);

    std::set<mail_msg*> mset;
    for (uint step=0; step < nsteps && !progress_aborted(); step++) {
      mset.clear();
      for (uint ivec=0; idx<size && ivec<batch_size; ivec++) {
	if (m_query_lv && v[idx]->is_current())
	  m_query_lv->mail_tag_changed(*(v[idx]), (uint)tag_id, checked);
	mset.insert(v[idx++]);
      }
      // process in database
      if (!mset.empty()) {
	processed += mail_msg::toggle_tags_set(mset, tag_id, checked);
      }
      show_progress(1+step);
    }
    uninstall_progressbar();
    statusBar()->showMessage(tr("%1 message(s) processed.").arg(processed), 3000);
  }
  else for (idx=0; idx < v.size(); idx++) {
    mail_msg* p = v[idx];
    if (checked) {
      // put the tag if it's not already on
      if (!p->hasTag(tag_id)) {
	p->set_tag(tag_id, true);
	m_tags_box->set_tag(tag_id);
      }
    }
    else {
      // take the tag off
      p->set_tag(tag_id, false);
      m_tags_box->unset_tag(tag_id);
    }
    // update the tags counters in the quick selection view
    DBG_PRINTF(5, "mail_id=%d current=%d, status=%d", p->get_id(), p->is_current(), p->status());
    if (m_query_lv && p->is_current()) {
      m_query_lv->mail_tag_changed(*p, (uint)tag_id, checked);
    }
  }
  if (m_pCurrentItem) {
    // redisplay the body
    display_body();
  }
}

/*
  menuBar()->setFont() doesn't propagate to the child menus, we need
  to do it explicitly
*/
void
msg_list_window::set_menu_font(const QFont& f)
{
  QMenu* tab[] = {
    // top level
    m_pMenuFile, m_pMenuEdit, m_pMenuSelection, m_pMenuMessage, m_pMenuDisplay,
    m_pMenuHelp,
    // sub menus
    m_pPopupFonts, m_pPopupHeaders, m_popup_body, m_pPopupAttach
  };
  for (uint i=0; i<sizeof(tab)/sizeof(tab[0]); i++) {
    tab[i]->setFont(f);
  }
  menuBar()->setFont(f);
}


QMenuBar*
msg_list_window::init_menu()
{
  QIcon ico_cut(FT_MAKE_ICON(FT_ICON16_EDIT_CUT));
  QIcon ico_copy(FT_MAKE_ICON(FT_ICON16_EDIT_COPY));
  QIcon ico_paste(FT_MAKE_ICON(FT_ICON16_EDIT_PASTE));
  QIcon ico_prefs(FT_MAKE_ICON(FT_ICON16_PREFS));
  QIcon ico_filters(FT_MAKE_ICON(FT_ICON16_FILTERS));
  QIcon ico_mail_merge(FT_MAKE_ICON(ICON16_MAIL_MERGE));
  QIcon ico_tags(FT_MAKE_ICON(FT_ICON16_TAGS));
  QIcon ico_properties(FT_MAKE_ICON(FT_ICON16_PROPERTIES));
  QIcon ico_new_window(FT_MAKE_ICON(FT_ICON16_NEW_WINDOW));
  QIcon ico_close_window(FT_MAKE_ICON(FT_ICON16_CLOSE_WINDOW));
  QIcon ico_forward(FT_MAKE_ICON(FT_ICON16_FORWARD));
  QIcon ico_find(FT_MAKE_ICON(FT_ICON16_FIND));
  QIcon ico_print(FT_MAKE_ICON(FT_ICON16_PRINT));
  QIcon ico_new_msg(FT_MAKE_ICON(FT_ICON16_EDIT));
  QIcon ico_trash(FT_MAKE_ICON(FT_ICON16_ACTION_TRASH));
  QIcon ico_new_query(FT_MAKE_ICON(FT_ICON16_NEW_QUERY));
  QIcon ico_replace_query(FT_MAKE_ICON(FT_ICON16_REPLACE_QUERY));
  QIcon ico_refresh(FT_MAKE_ICON(FT_ICON16_REFRESH));
  QIcon ico_save_query(FT_MAKE_ICON(FT_ICON16_SAVE_SQL));
  QIcon ico_edit_note(FT_MAKE_ICON(FT_ICON16_EDIT_NOTE));
  QIcon ico_get_new_mail(FT_MAKE_ICON(FT_ICON16_GET_NEW_MAIL));
  QIcon ico_get_unproc_mail(FT_MAKE_ICON(FT_ICON16_GET_UNPROC_MAIL));
  QIcon ico_help(FT_MAKE_ICON(FT_ICON16_HELP));
  QIcon ico_about(FT_MAKE_ICON(FT_ICON16_ABOUT));
  QIcon ico_outbox(FT_MAKE_ICON(FT_ICON16_OUTBOX));
  QIcon ico_quit(FT_MAKE_ICON(FT_ICON16_QUIT));

  // Application
  m_pMenuFile=new QMenu(tr("&File"), this);
  CHECK_PTR(m_pMenuFile);

  m_menu_actions[me_File_New_Window] =
    m_pMenuFile->addAction(ico_new_window, tr("&New Window"), this,
			    SLOT(new_window()), Qt::CTRL+Qt::Key_N);

  m_menu_actions[me_File_Close_Window] =
    m_pMenuFile->addAction(ico_close_window, tr("&Close Window"), this,
			    SLOT(close_window()), Qt::CTRL+Qt::Key_W);
  m_pMenuFile->addSeparator();

  m_menu_actions[me_File_Preferences] =
    m_pMenuFile->addAction(ico_prefs, tr("&Preferences"), this, SLOT(preferences()));

  m_menu_actions[me_File_Global_Notepad] =
    m_pMenuFile->addAction(FT_MAKE_ICON(ICON16_NOTEPAD), tr("Global Notepad"), this, SLOT(open_global_notepad()), Qt::CTRL+Qt::Key_G);

  m_menu_actions[me_Configuration_EditTags] =
    m_pMenuFile->addAction(ico_tags, tr("&Tags"), this, SLOT(edit_tags()));

  m_menu_actions[me_Configuration_EditFilters] =
    m_pMenuFile->addAction(ico_filters, tr("Filters"), this, SLOT(edit_filters()));

  m_menu_actions[me_File_Mailing] =
    m_pMenuFile->addAction(ico_mail_merge, tr("Mailings"), this, SLOT(start_mailing()));

  m_menu_actions[me_File_Import_Mailbox] =
    m_pMenuFile->addAction(UI_ICON(ICON16_IMPORT_MBOX), tr("Import mailbox"), this, SLOT(import_mailbox()));

  m_pMenuFile->addSeparator();

  m_menu_actions[me_File_Quit] =
    m_pMenuFile->addAction(ico_quit, tr("&Quit"), gl_pApplication, SLOT(quit()));
/*
  m_menu_actions[me_Application_Import] =
    m_pMenuFile->addAction(tr("&Import mailbox"), this, SLOT(sel_import()));
*/

  // Edit
  m_pMenuEdit=new QMenu(tr("Edit"), this);
  CHECK_PTR(m_pMenuEdit);

  // Message
  m_pMenuMessage=new QMenu(tr("&Message"), this);
  CHECK_PTR(m_pMenuMessage);

  // Display
  m_pMenuDisplay=new QMenu(tr("&Display"), this);
  CHECK_PTR (m_pMenuDisplay);
  m_menu_actions[me_Display_Tags] = m_pMenuDisplay->addAction(tr("&Tags panel"), this, SLOT(toggle_show_tags(bool)));
  m_menu_actions[me_Display_Tags]->setCheckable(true);
  m_menu_actions[me_Display_Tags]->setChecked(display_vars.m_show_tags);

  m_menu_actions[me_Display_Threaded]=m_pMenuDisplay->addAction(tr("&Threaded"), this, SLOT(toggle_threaded(bool)));
  m_menu_actions[me_Display_Threaded]->setCheckable(true);
  m_menu_actions[me_Display_Threaded]->setChecked(display_vars.m_threaded);

  m_menu_actions[me_Display_WrapLines] = m_pMenuDisplay->addAction(tr("Wrap lines"), this, SLOT(toggle_wrap_lines(bool)));
  m_menu_actions[me_Display_WrapLines]->setCheckable(true);
  m_menu_actions[me_Display_WrapLines]->setChecked(display_vars.m_wrap_lines);

  m_menu_actions[me_Display_Hide_Quoted] = m_pMenuDisplay->addAction(tr("Hide quoted text"), this, SLOT(toggle_hide_quoted(bool)), Qt::CTRL+Qt::Key_H);
  m_menu_actions[me_Display_Hide_Quoted]->setCheckable(true);
  m_menu_actions[me_Display_Hide_Quoted]->setChecked(display_vars.m_hide_quoted);

  m_menu_actions[me_Display_FastBrowse] = m_pMenuDisplay->addAction(tr("Fetch on demand"), this, SLOT(toggle_fetch_on_demand(bool)));
  m_menu_actions[me_Display_FastBrowse]->setCheckable(true);
  m_menu_actions[me_Display_FastBrowse]->setChecked(m_fetch_on_demand);


  // Display->Fonts
  m_pPopupFonts = new QMenu(tr("&Fonts"), this);

  m_menu_actions[me_Display_Font_All] =
    m_pPopupFonts->addAction(tr("All"));
  m_menu_actions[me_Display_Font_Menus] =
    m_pPopupFonts->addAction(tr("Menus"));
  m_menu_actions[me_Display_Font_QuickSel] =
    m_pPopupFonts->addAction(tr("Quick selection"));
  m_menu_actions[me_Display_Font_Tags] =
    m_pPopupFonts->addAction(tr("Tags"));
  m_menu_actions[me_Display_Font_Msglist] =
    m_pPopupFonts->addAction(tr("Messages list"));
  m_menu_actions[me_Display_Font_Msgbody] =
    m_pPopupFonts->addAction(tr("Message body"));

  m_pMenuDisplay->addMenu(m_pPopupFonts);
  connect(m_pPopupFonts, SIGNAL(triggered(QAction*)), SLOT(change_font(QAction*)));

  // Display->Message
  m_popup_display_body = new QMenu(tr("Body"), this);
  m_pMenuDisplay->addMenu(m_popup_display_body);
  QAction* action;
  
  // Display->Message->Zoom In
  action = new QAction(tr("Zoom In"), this);
  action->setShortcut(tr("Ctrl++"));
  action->setStatusTip(tr("Zoom in on the message contents"));
  connect(action, SIGNAL(triggered()), this, SLOT(msg_zoom_in()));
  m_menu_actions[me_Display_Body_ZoomIn] = action;
  m_popup_display_body->addAction(action);

  // Display->Message->Zoom Out
  action = new QAction(tr("Zoom Out"), this);
  action->setShortcut(tr("Ctrl+-"));
  action->setStatusTip(tr("Zoom out on the message contents"));
  connect(action, SIGNAL(triggered()), this, SLOT(msg_zoom_out()));
  m_menu_actions[me_Display_Body_ZoomOut] = action;
  m_popup_display_body->addAction(action);

  // Display->Message->Reset Zoom
  action = new QAction(tr("Reset zoom"), this);
  action->setShortcut(tr("Ctrl+0"));
  action->setStatusTip(tr("Reset the zoom on the message contents"));
  connect(action, SIGNAL(triggered()), this, SLOT(msg_zoom_zero()));
  m_menu_actions[me_Display_Body_ZoomZero] = action;
  m_popup_display_body->addAction(action);

  // Display->Headers
  m_pPopupHeaders=new QMenu(tr("Headers"), this);
  m_menu_actions[me_Display_Headers_None] = m_pPopupHeaders->addAction(tr("None"));
  m_menu_actions[me_Display_Headers_Most] = m_pPopupHeaders->addAction(tr("Most"));
  m_menu_actions[me_Display_Headers_All] =  m_pPopupHeaders->addAction(tr("All"));
  m_menu_actions[me_Display_Headers_Raw] =  m_pPopupHeaders->addAction(tr("Raw"));
  m_menu_actions[me_Display_Headers_RawDec] = m_pPopupHeaders->addAction(tr("Decoded"));

  QActionGroup* qag_show_headers = new QActionGroup(this);
  qag_show_headers->setExclusive(true);
  int header_show_options[] = {
    me_Display_Headers_None, me_Display_Headers_Most, me_Display_Headers_All,
    me_Display_Headers_Raw, me_Display_Headers_RawDec
  };

  for (uint i=0; i<sizeof(header_show_options)/sizeof(int); i++) {
    qag_show_headers->addAction(m_menu_actions[header_show_options[i]]);
    m_menu_actions[header_show_options[i]]->setCheckable(true);
  }
  m_pPopupHeaders->addSeparator();
  // show tags in headers
  m_menu_actions[me_Display_Headers_Tags] = m_pPopupHeaders->addAction(tr("Include tags"), this, SLOT(toggle_include_tags_in_headers(bool)));
  m_menu_actions[me_Display_Headers_Tags]->setCheckable(true);
  m_menu_actions[me_Display_Headers_Tags]->setChecked(display_vars.m_show_tags_in_headers);

  // show filter logs in headers
  m_menu_actions[me_Display_Show_FiltersTrace] = m_pPopupHeaders->addAction(tr("Show Filters Log"), this, SLOT(toggle_show_filters_log(bool)));
  m_menu_actions[me_Display_Show_FiltersTrace]->setCheckable(true);
  m_menu_actions[me_Display_Show_FiltersTrace]->setChecked(display_vars.m_show_filters_trace);

  connect(m_pPopupHeaders, SIGNAL(triggered(QAction*)), SLOT(show_headers(QAction*)));
  m_pMenuDisplay->addMenu(m_pPopupHeaders);

  m_pMenuDisplay->addAction(tr("Store settings"), this, SLOT(save_display_settings()));


  
  QAction* header_checked=NULL;
  if (get_config().exists("show_headers_level")) {
    display_vars.m_show_headers_level=get_config().get_number("show_headers_level");
    if (display_vars.m_show_headers_level<0 ||
	display_vars.m_show_headers_level>4)
      {
	// incorrect value => use the default instead
	DBG_PRINTF(5,"warning: incorrect value '%d' for show_headers_level in configuration", display_vars.m_show_headers_level);
	display_vars.m_show_headers_level=1; // default=Most headers
      }
  }
  else
    display_vars.m_show_headers_level=1; // default=Most headers

  switch(display_vars.m_show_headers_level) {
  case 0:
    header_checked=m_menu_actions[me_Display_Headers_None];
    break;
  default:			// default shouldn't happen
  case 1:
    header_checked=m_menu_actions[me_Display_Headers_Most];
    break;
  case 2:
    header_checked=m_menu_actions[me_Display_Headers_All];
    break;
  case 3:
    header_checked=m_menu_actions[me_Display_Headers_Raw];
    break;
  case 4:
    header_checked=m_menu_actions[me_Display_Headers_RawDec];
    break;
  }
  if (header_checked!=NULL)
    header_checked->setChecked(true);

  // focus on F10 key
  (void)new QShortcut(Qt::Key_F10, this, SLOT(focus_on_msglist()));

  // Help menu
  m_pMenuHelp=new QMenu(tr("&Help"), this);
  CHECK_PTR(m_pMenuHelp);
  m_pMenuHelp->addAction(ico_help, tr("Help window"), this, SLOT(open_help()), Qt::Key_F1);
  m_menu_actions[me_Help_Dynamic] = m_pMenuHelp->addAction(tr("Track context"),
							  this, SLOT(dynamic_help()));
  m_menu_actions[me_Help_Dynamic]->setCheckable(true);
  m_menu_actions[me_Help_Dynamic]->setChecked(false);

  m_pMenuHelp->addAction(ico_about, tr("&About"), this, SLOT(about()));

  m_pMenuSelection=new QMenu(tr("&Selection"), this);
  CHECK_PTR(m_pMenuSelection);

  QMenuBar* menu=menuBar(); //new QMenuBar(this);
  menu->addMenu(m_pMenuFile);
  menu->addMenu(m_pMenuEdit);
  menu->addMenu(m_pMenuSelection);
  menu->addMenu(m_pMenuMessage);
  menu->addMenu(m_pMenuDisplay);
//  menu->addMenu(tr("&Configuration"), m_pMenuConfig);
  menu->addSeparator();
  menu->addMenu(m_pMenuHelp);

  connect(m_pMenuFile, SIGNAL(aboutToShow()), this, SLOT(enable_commands()));
  connect(m_pMenuSelection, SIGNAL(aboutToShow()), this, SLOT(enable_commands()));
  connect(m_pMenuMessage, SIGNAL(aboutToShow()), this, SLOT(enable_commands()));
  connect(m_pMenuDisplay, SIGNAL(aboutToShow()), this, SLOT(enable_commands()));
  connect(m_pPopupHeaders, SIGNAL(aboutToShow()), this, SLOT(enable_commands()));

  m_menu_actions[me_Edit_Cut] = m_pMenuEdit->addAction(ico_cut, tr("Cut"));
  m_menu_actions[me_Edit_Cut]->setEnabled(false);

  m_menu_actions[me_Edit_Copy] = m_pMenuEdit->addAction(ico_copy, tr("Copy"), this, SLOT(edit_copy()), Qt::CTRL+Qt::Key_C);
  m_menu_actions[me_Edit_Copy]->setEnabled(true);

  m_menu_actions[me_Edit_Paste] = m_pMenuEdit->addAction(ico_paste, tr("Paste"));
  m_menu_actions[me_Edit_Paste]->setEnabled(false);

  m_menu_actions[me_Message_New] =
    m_pMenuMessage->addAction(ico_new_msg, tr("&New message"),
			       this, SLOT(new_mail()));


  m_pMenuMessage->addAction(m_action_reply_sender);
  m_pMenuMessage->addAction(m_action_reply_all);
  m_pMenuMessage->addAction(m_action_msg_forward);
  m_pMenuMessage->addAction(m_action_find_text);

  m_menu_actions[me_Message_EditNote] =
    m_pMenuMessage->addAction(ico_edit_note, tr("&Edit note"),
			       this, SLOT(edit_note()));

  m_pMenuMessage->addAction(m_action_msg_archive);

  m_menu_actions[me_Message_Properties] =
    m_pMenuMessage->addAction(ico_properties, tr("Pr&operties"), this, SLOT(msg_properties()));

  m_pMenuMessage->addAction(m_action_msg_trash);
  m_pMenuMessage->addAction(m_action_msg_untrash);
  m_pMenuMessage->addAction(m_action_msg_delete);
  m_pMenuMessage->addAction(m_action_msg_print);

  m_popup_body=new QMenu(tr("Body"), this);
  m_menu_actions[me_Message_Body_Save] =
    m_popup_body->addAction(tr("Save"), this, SLOT(save_body()));
  m_menu_actions[me_Message_Body_Edit] =
    m_popup_body->addAction(tr("Edit"), this, SLOT(edit_body()));
  /*
  m_menu_actions[me_Message_Save_ToMbox] =
    m_pPopupSaveMsg->addAction(tr("To mailbox"), this, SLOT(save_to_mbox()));
  */

  m_pMenuMessage->addMenu(m_popup_body);

  m_pPopupAttach = new QMenu(tr("Attachment"), this);
  m_menu_actions[me_Message_Attch_View] =
    m_pPopupAttach->addAction(tr("View as text"), this, SLOT(view_attachment()));

  m_menu_actions[me_Message_Attch_Save] =
    m_pPopupAttach->addAction(tr("Save"), this, SLOT(save_attachment()));

  m_pMenuMessage->addMenu(m_pPopupAttach);

  m_menu_actions[me_Selection_NewMessages] =
    m_pMenuSelection->addAction(ico_get_new_mail, tr("New mail"),
				 this, SLOT(new_messages()));

  m_menu_actions[me_Selection_CurrentMessages] =
    m_pMenuSelection->addAction(ico_get_unproc_mail,
				 tr("Current mail"), this,
				 SLOT(non_processed_messages()));

  m_menu_actions[me_Selection_Trashcan] =
    m_pMenuSelection->addAction(ico_trash, tr("Trashcan"), this, SLOT(sel_trashcan()));

  m_menu_actions[me_Selection_Sent] =
    m_pMenuSelection->addAction(ico_outbox, tr("Sent"), this, SLOT(sel_sent()));

  m_pMenuSelection->addSeparator();

  m_menu_actions[me_Selection_New] =
    m_pMenuSelection->addAction(ico_new_query, tr("&New query"), this, SLOT(new_list()), Qt::Key_F2);
  m_menu_actions[me_Selection_Refine] =
    m_pMenuSelection->addAction(ico_replace_query, tr("&Modify query"), this, SLOT(sel_refine()));
  m_menu_actions[me_Selection_Refresh] =
    m_pMenuSelection->addAction(ico_refresh, tr("&Refresh"), this, SLOT(sel_refresh()), Qt::Key_F5);
  m_menu_actions[me_Selection_Save_Query] =
    m_pMenuSelection->addAction(ico_save_query, tr("&Save query"), this, SLOT(sel_save_query()));

#if 0
  m_pMenuSelection->addSeparator();
  m_menu_actions[me_Selection_Header_Analysis] =
    m_pMenuSelection->addAction(tr("Headers Analysis"), this, SLOT(sel_header_analysis()));
#endif

  return menu;
}

msg_list_window::msg_list_window (const msgs_filter* filter, display_prefs* dprefs, QWidget *parent)  : QMainWindow(parent)
{
  m_abort = false;
  m_ignore_selection_change = false;
  m_waiting_for_results = true;
  m_qlist = NULL;
  m_filter = new msgs_filter(*filter);
  m_pCurrentItem = NULL;

  // application icon
  setWindowIcon(FT_MAKE_ICON(FT_ICON16_EDIT));

  QWidget* main_hb=new QWidget(this); // container widget
  setCentralWidget(main_hb);

  QHBoxLayout* topLayout = new QHBoxLayout(main_hb);
  topLayout->setSpacing(5);
  topLayout->setMargin(5);

  QSplitter* lvert = new QSplitter(main_hb);
  topLayout->addWidget(lvert);

  QSplitter* l2 = new QSplitter(Qt::Vertical, lvert);

  m_query_lv = new query_listview(l2);
  m_query_lv->init();

  connect(m_query_lv, SIGNAL(itemActivated(QTreeWidgetItem *,int)),
	  this, SLOT(quick_query_selection(QTreeWidgetItem *,int)));

  connect(m_query_lv, SIGNAL(itemPressed(QTreeWidgetItem *,int)),
	  this, SLOT(quick_query_selection(QTreeWidgetItem *,int)));

  connect(m_query_lv, SIGNAL(run_selection_filter(const msgs_filter&)),
	  this, SLOT(sel_filter(const msgs_filter&)));

  m_pages = new msgs_page_list(lvert);
  // the query listview should not be stretched horizontally
  lvert->setStretchFactor(lvert->indexOf(l2), 0);
  lvert->setStretchFactor(lvert->indexOf((QWidget*)m_pages->stacked_widget()), 1);

  m_tags_box=new tags_box_widget(l2/*main_hb*/);

  connect(m_tags_box, SIGNAL(state_changed(int,bool)), this, SLOT(tag_toggled(int,bool)));

//  (void)new QVBox(m_tags_box); // takes up the remaining space
//  topLayout->addWidget(m_tags_box);
  if (dprefs) {
    display_vars = *dprefs;
/*
    display_vars.m_threaded = dprefs->m_threaded;
    display_vars.m_wrap_lines = dprefs->m_wrap_lines;
    display_vars.m_show_tags = dprefs->m_show_tags;
    display_vars.m_hide_quoted = dprefs->m_hide_quoted;
    display_vars.m_clickable_urls = dprefs->m_clickable_urls;
    display_vars.m_show_filters_trace = dprefs->m_show_filters_trace;
    display_vars.m_show_tags_in_headers = dprefs->m_show_tags_in_headers;
*/
  }
  else {
    display_vars.init();
  }
  m_fetch_on_demand = false;

  if (!display_vars.m_show_tags) {
    m_tags_box->hide();
  }

  int height=get_config().get_number("display/msglist/height");
  if (height==0) height=600;
  int width=get_config().get_number("display/msglist/width");
  if (width==0) width=800;

  resize(width,height);

//  topLayout->setMenuBar(menu);

  create_actions();
  add_msgs_page(m_filter,false);

  make_toolbar();
  make_search_toolbar();

  init_menu();
  enable_forward_backward();
  init_fonts();

  m_new_mail_btn = new newmail_button(tr("New mail !"), statusBar());
  statusBar()->addPermanentWidget(m_new_mail_btn);
  m_new_mail_btn->enable(false);
  /* Hide the new mail button. This button has somehow become obsolete
     since new messages can be incorporated automatically into the
     list */
  m_new_mail_btn->hide();
  connect(m_new_mail_btn, SIGNAL(clicked()), this, SLOT(sel_refresh()));
  connect(m_new_mail_btn, SIGNAL(show_new_mail()), this, SLOT(raise_refresh()));
  m_timer_idle = new QTimer(this);
  m_timer_idle->start(3000);
  connect(m_timer_idle, SIGNAL(timeout()), this, SLOT(timer_idle()));

  m_timer = new QTimer(this);
  m_timer_ticks=0;
  m_timer->start(200);
  connect(m_timer, SIGNAL(timeout()), this, SLOT(timer_func()));

  connect(this, SIGNAL(mail_chg_status(int,mail_msg*)), SLOT(change_mail_status(int,mail_msg*)));

  connect(this, SIGNAL(mail_multi_chg_status(int,std::vector<mail_msg*>*)), SLOT(change_multi_mail_status(int,std::vector<mail_msg*>*)));

  // subscribe to refresh requests
  message_port::connect_receiver(SIGNAL(list_refresh_request()),
				 this, SLOT(sel_auto_refresh_list()));

}

void
msg_list_window::raise_refresh()
{
  sel_refresh();
  raise();
  activateWindow();
}

/*
  Set various display settings for m_qlist, once its mail_msg's
  are loaded
*/
void
msg_list_window::msg_list_postprocess()
{
  // sort by message date
  DBG_PRINTF(8, "start sort (sortingEnabled=%d)", m_qlist->isSortingEnabled());
  m_qlist->setSortingEnabled(true);
  m_qlist->sortByColumn(mail_item_model::column_date, get_config().get_msgs_sort_order());
  DBG_PRINTF(8, "end sort");
  m_qlist->header()->setSortIndicatorShown(true);
  m_qlist->setRootIsDecorated(display_vars.m_threaded);
  m_qlist->expandAll();
  set_title();
}

void
msg_list_window::edit_copy()
{
  m_msgview->copy();
}

void
msg_list_window::toggle_wrap_lines(bool wrap)
{
  display_vars.m_wrap_lines = wrap; // !display_vars.m_wrap_lines;
//  m_pMenuDisplay->setItemChecked(id, display_vars.m_wrap_lines);
  if (!m_fetch_on_demand)
    display_body();
}

void
msg_list_window::toggle_include_tags_in_headers(bool include)
{
  display_vars.m_show_tags_in_headers = include;
  if (!m_fetch_on_demand)
    display_body();  
}

void
msg_list_window::toggle_hide_quoted(bool hide)
{
  display_vars.m_hide_quoted = hide;
  if (!m_fetch_on_demand)
    display_body();
}

void
msg_list_window::toggle_show_filters_log(bool show)
{
  display_vars.m_show_filters_trace = show;
  if (!m_fetch_on_demand)
    display_body();
}

void
msg_list_window::toggle_fetch_on_demand(bool on_demand)
{
  m_fetch_on_demand = on_demand;
//  m_pMenuDisplay->setItemChecked(id, m_fetch_on_demand);
  m_msgview->set_show_on_demand(m_fetch_on_demand);

  if (!m_fetch_on_demand) {
    display_msg_contents();
    display_selection_tags();
  }
  else
    m_tags_box->reset_tags();
}

msg_list_window::~msg_list_window()
{
  if (m_wSearch) {
    delete m_wSearch;
  }
  // delete all pages
  m_pages->cut_pages(m_pages->first_page());
  delete m_pages;
}


// slot
void
msg_list_window::global_refresh_status(mail_id_t mail_id)
{
  mail_msg* first_msg=NULL;
  uint status=0;
  std::list<msgs_page*>::iterator page_it;
  for (page_it = msgs_page_list::m_all_pages_list.begin();
       page_it != msgs_page_list::m_all_pages_list.end();
       ++page_it)
  {
    mail_listview* l = (*page_it)->m_page_qlist;
    mail_msg* msg = l->find(mail_id);
    if (msg!=NULL) {
      if (!first_msg) {
	// re-read from database to get the latest status
	// as an optimisation, we read it only once and cache the value
	// for other occurrences in different views
	first_msg=msg;
	msg->refresh(); // db read
	status=msg->status();
      }
      else {
	msg->set_status(status); // status has been set previously in the loop
      }
      l->update_msg(msg);
    }
  }
  if (m_query_lv && first_msg) {
    m_query_lv->mail_status_changed(first_msg, status);
  }
}

// code=-1 if the message has been deleted
void
msg_list_window::propagate_status(mail_msg* item, int code/*=0*/)
{
  DBG_PRINTF(8, "propagate_status(id=%d, code=%d)", item->get_id(), code);
  // tell all pages (except the current page that should be the
  // caller) about the change
  std::list<msgs_page*>::iterator page_it;
  msgs_page* our_current_page = m_pages->current_page();
  for (page_it = msgs_page_list::m_all_pages_list.begin();
       page_it != msgs_page_list::m_all_pages_list.end();
       ++page_it)
  {
    if (*page_it != our_current_page)
      (*page_it)->refresh_status(item, code);
  }
  if (m_query_lv) {
    m_query_lv->mail_status_changed(item, (code==-1)?-1:(int)item->status());
  }
}

/*
  (slot) Called to change the status of a message
  Reflects the change in the listview.
  Can also remove the message(s) from the listview and make it non longer
  current.
*/
void
msg_list_window::change_mail_status (int status_mask, mail_msg* msg)
{
  DBG_PRINTF(8, "change_mail_status(mask=%d, mail_id=%d)", status_mask, msg->get_id());
  msg->set_status(msg->status()|status_mask);
  if (msg->update_status()) {
    DBG_PRINTF(5, "status of mail %d updated", msg->get_id());
    if (status_mask & (int)mail_msg::statusTrashed) {
      propagate_status(msg, 0);
      remove_msg(msg);
      set_title();
    }
    else if (status_mask & (int)mail_msg::statusArchived) {
      m_qlist->update_msg(msg);
      m_qlist->select_below(msg); // FIXME: should move this call somewhere else
      propagate_status(msg);
    }
    else {
      m_qlist->update_msg(msg);
      propagate_status(msg);
    }
  }
  // else database error in status update. TODO
}

void
msg_list_window::remove_msg(mail_msg* msg, bool auto_select_next)
{
  DBG_PRINTF(8, "remove_msg(id=%d, auto_select_next=%d)", msg->get_id(), auto_select_next);
  // remove from the treeview
  m_qlist->remove_msg(msg, auto_select_next);
  // remove from our list
  m_filter->m_list_msgs.remove(msg);
}

void
msg_list_window::msg_properties()
{
  if (m_qlist->selection_size()==1) {
    std::vector<mail_msg*> v;
    m_qlist->get_selected(v);
    properties_dialog* w = new properties_dialog (v[0], NULL);
    m_qlist->connect(w, SIGNAL(change_status_request(mail_id_t,uint,int)),
		     m_qlist, SLOT(force_msg_status(mail_id_t,uint,int)));
    w->show();
  }
}


void
msg_list_window::msg_delete()
{
  DBG_PRINTF(8, "msg_delete()");
  remove_selected_msgs(1);
}

void
msg_list_window::msg_trash()
{
  DBG_PRINTF(8, "msg_trash()");
  remove_selected_msgs(0);
}

/*
  action=0 => trash
  action=1 => delete
*/
void
msg_list_window::remove_selected_msgs(int action)
{
  DBG_PRINTF(8, "remove_selected_msgs(%d)", action);
  const QCursor cursor(Qt::WaitCursor);
  QApplication::setOverrideCursor(cursor);

  uint removed=0;
  std::vector<mail_msg*> v;
  m_qlist->get_selected (v);

  const uint batch_size=20;
  uint idx=0;
  uint nb_msg = v.size();
  DBG_PRINTF(6, "# of msgs to trash/delete = %d", nb_msg);
  uint size = nb_msg;
  uint nsteps = (size+batch_size-1)/batch_size;
  m_ignore_selection_change = true;
  if (nsteps > 1 && action==0) {
    // massive trash, do it in batches
    install_progressbar();
    show_progress(-nsteps);

    std::set<mail_msg*> mset;
    for (uint step=0; step < nsteps && !progress_aborted(); step++) {
      mset.clear();
      std::set<mail_msg*> imset;
      for (uint ivec=0; idx<size && ivec<batch_size; ivec++) {
	imset.insert(v[idx]);
	mset.insert(v[idx++]);
      }
      // process in database
      if (!mset.empty()) {
	mail_msg::trash_set(mset);
	removed += mset.size();
      }
      // propagate
      std::set<mail_msg*>::iterator it = imset.begin();
      for (unsigned int cnt=1; it!=imset.end(); ++it,++cnt) {
	propagate_status(*it);
	// auto-select next one only for the last message
	remove_msg(*it, (cnt==imset.size()));
      }
      show_progress(1+step);
    }
    if (nsteps > 1)
      uninstall_progressbar();
  }
  else for (uint i=0; i<nb_msg; i++) {
    // small update
    if (action==0) {
      statusBar()->showMessage(QString(tr("Trashing messages: %1 of %2")).arg(i+1).arg(nb_msg));
      statusBar()->repaint();
      QApplication::flush();
      if (v[i]->trash()) {
	propagate_status(v[i]);
	remove_msg(v[i], (i==nb_msg-1));
	removed++;
      }
    }
    else if (action==1) {
      statusBar()->showMessage(QString(tr("Deleting messages: %1 of %2")).arg(i+1).arg(nb_msg));
      statusBar()->repaint();
      QApplication::flush();
      if (v[i]->mdelete()) {
	propagate_status(v[i], -1);
	remove_msg(v[i], (i==nb_msg-1));
	removed++;
      }
    }
  }
  m_ignore_selection_change = false;
  mails_selected();
  set_title();
  QApplication::restoreOverrideCursor();
  if (action==0)
    statusBar()->showMessage(tr("%1 message(s) trashed.").arg(removed), 3000);
  else
    statusBar()->showMessage(tr("%1 message(s) deleted.").arg(removed), 3000);
}

void
msg_list_window::msg_untrash()
{
  std::vector<mail_msg*> v;
  m_qlist->get_selected (v);
  for (uint j=0; j < v.size(); j++) {
    mail_msg* msg = v[j];
    if (msg->status() & mail_msg::statusTrashed) {
      if (msg->untrash()) {
	m_qlist->update_msg(msg);
	propagate_status(msg);
      }
      // else database error in status update.
    }
  }
}

/*
  (slot) Called to change the status of a message
  Reflects the change in the listview.
  Can also remove the message(s) from the listview and make it non longer
  current.
*/
void
msg_list_window::change_multi_mail_status(int statusMask,
					  std::vector<mail_msg*>* v)
{
  const QCursor cursor(Qt::WaitCursor);
  QApplication::setOverrideCursor(cursor);

  const uint batch_size=20;
  uint idx=0;
  uint size=v->size();

  statusBar()->showMessage(tr("Updating messages..."));
  uint nsteps = (size+batch_size-1)/batch_size;
  if (nsteps>1) {
    install_progressbar();
    show_progress(-nsteps);
  }

  DBG_PRINTF(5, "nsteps=%d, size=%d", nsteps, size);
  std::set<mail_msg*> mset;
  for (uint step=0; step < nsteps && !progress_aborted(); step++) {
    mset.clear();
    std::set<mail_msg*> imset;
    for (uint ivec=0; idx<size && ivec<batch_size; ivec++) {
      imset.insert((*v)[idx]);
      mset.insert((*v)[idx++]);
    }
    // set in database
    if (!mset.empty()) {
      mail_msg::set_or_with_status(mset, statusMask);
    }
    // propagate
    std::set<mail_msg*>::iterator it = imset.begin();
    for (; it!=imset.end(); ++it) {
      if (statusMask & (int)mail_msg::statusArchived) {
	m_qlist->update_msg(*it);
	propagate_status(*it);
      }
    }
    // report progress
    if (nsteps>1)
      show_progress(1+step);
  }

  if (nsteps>1)
    uninstall_progressbar();
  m_qlist->update();
  QApplication::restoreOverrideCursor();
  statusBar()->showMessage(tr("Done."), 3000);
}

// set the window title. If no argument is given, display the
// number of messages in the list
void
msg_list_window::set_title(const QString title/*=QString::null*/)
{
  if (title.isEmpty()) {
    QString sTitle=QString("%3: %1%4 %2").arg(m_filter->m_list_msgs.size()).arg(tr("message(s)")).arg(db_cnx::dbname(), m_filter->has_more_results()?"+":"");
    setWindowTitle(sTitle);
  }
  else
    setWindowTitle(title);
}

/*
  Menu Handlers
*/

/* Enable or disable the menu entries according to what messages are
   currently selected (the current selection is in the vector 'v') */
void
msg_list_window::enable_commands()
{
  int size=m_qlist->selection_size();
  if (size==1) {
    // one mail selected
    std::vector<mail_msg*> v;
    m_qlist->get_selected(v);

    m_action_reply_all->setEnabled(true);
    m_action_reply_sender->setEnabled(true);
    m_action_reply_list->setEnabled(true);

    m_action_msg_forward->setEnabled(true);
    bool can_trash = ((v[0]->status() & mail_msg::statusTrashed)==0);
    m_action_msg_trash->setEnabled(can_trash);
    m_action_msg_untrash->setEnabled(!can_trash);
    m_action_msg_delete->setEnabled(true);
    m_action_msg_archive->setEnabled(true);

    m_action_msg_print->setEnabled(true);
    m_action_msg_sender_details->setEnabled(true);
    m_menu_actions[me_Message_Properties]->setEnabled(true);
  }
  else if (size==0) {
    // none selected
    m_action_reply_all->setEnabled(false);
    m_action_reply_sender->setEnabled(false);
    m_action_reply_list->setEnabled(false);
    m_action_msg_forward->setEnabled(false);
    m_action_msg_archive->setEnabled(false);

    m_action_msg_trash->setEnabled(false);
    m_action_msg_untrash->setEnabled(false);
    m_action_msg_delete->setEnabled(false);
    m_menu_actions[me_Message_Properties]->setEnabled(false);

    m_action_msg_print->setEnabled(false);
    m_action_msg_sender_details->setEnabled(false);
  }
  else {
    // several messages selected
    m_action_reply_all->setEnabled(false);
    m_action_reply_sender->setEnabled(false);
    m_action_reply_list->setEnabled(false);
    m_action_msg_forward->setEnabled(false);
    m_action_msg_archive->setEnabled(true);
    m_action_msg_trash->setEnabled(true);
    m_action_msg_untrash->setEnabled(true);
    m_action_msg_delete->setEnabled(true);
    m_action_msg_print->setEnabled(false);
    m_action_msg_sender_details->setEnabled(false);
    m_menu_actions[me_Message_Properties]->setEnabled(false);
  }
}

// Hide/Show the tags panel
void
msg_list_window::toggle_show_tags(bool on)
{
  display_vars.m_show_tags=on; // !display_vars.m_show_tags;

  //  m_pMenuDisplay->setItemChecked(id, display_vars.m_show_tags);
  if (display_vars.m_show_tags)
    m_tags_box->show();
  else
    m_tags_box->hide();
}

// Show/don't show mail threads as trees
void
msg_list_window::toggle_threaded(bool on)
{
  display_vars.m_threaded=on; // !display_vars.m_threaded;

//  m_pMenuDisplay->setItemChecked(id, display_vars.m_threaded);
  m_qlist->set_threaded(display_vars.m_threaded);
  m_qlist->clear();
  /* Re-insert the whole list because in-place reparenting is way slower
     with current versions of QTreeView (as of Qt-4.3.3) */
  m_qlist->insert_list(m_filter->m_list_msgs);
  m_qlist->setRootIsDecorated(display_vars.m_threaded);
}

void
msg_list_window::change_font(QAction* which)
{
  bool ok;
  QFont current_font=m_msgview->font(); // sane default

  if (which==m_menu_actions[me_Display_Font_Msglist]) {
    current_font=m_qlist->font();
  }
  else if (which==m_menu_actions[me_Display_Font_Msgbody]) {
    current_font=m_msgview->font();
  }
  else if (which==m_menu_actions[me_Display_Font_Tags]) {
    current_font=m_tags_box->font();
  }
  else if (which==m_menu_actions[me_Display_Font_QuickSel]) {
    current_font=m_query_lv->font();
  }
  else if (which==m_menu_actions[me_Display_Font_Menus]) {
    current_font=menuBar()->font();
  }

  QFont f=QFontDialog::getFont(&ok, current_font);
  if (ok) {
    app_config& conf=get_config();
    QAction* all=m_menu_actions[me_Display_Font_All];
    if (which==all || which==m_menu_actions[me_Display_Font_Msglist]) {
      if (!m_pages->empty())
	set_pages_font(1,f);
      else {
	m_qlist->setFont(f);
	m_qAttch->setFont(f);
      }
      conf.set_string("display/font/msglist", f.toString());
    }
    if (which==all || which==m_menu_actions[me_Display_Font_Msgbody]) {
      if (!m_pages->empty()) {
	set_pages_font(2,f);
      }
      else {
	m_msgview->setFont(f);
      }
      if (!m_fetch_on_demand)
	display_msg_contents();	// redisplaying is necessary with Qt2
      conf.set_string("display/font/msgbody", f.toString());
    }
    if (which==all || which==m_menu_actions[me_Display_Font_Tags]) {
      m_tags_box->setFont(f);
      conf.set_string("display/font/tags", f.toString());
    }
    if (which==all || which==m_menu_actions[me_Display_Font_QuickSel]) {
      m_query_lv->setFont(f);
      conf.set_string("display/font/quicksel", f.toString());
    }
    if (which==all || which==m_menu_actions[me_Display_Font_Menus]) {
      menuBar()->setFont(f);
      set_menu_font(f);
      // by extension, the statusbar and toolbar are made to use
      // the same font than the menus
      if (m_ql_search) m_ql_search->setFont(f);
      if (m_toolbar) m_toolbar->setFont(f);
      statusBar()->setFont(f);
      if (m_new_mail_btn)
	m_new_mail_btn->update_font(f);
      conf.set_string("display/font/menus", f.toString());
    }
    if (which==all) {
      QApplication::setFont(f);
      conf.set_string("display/font/all", f.toString());
    }
  }
}

static void
initf(QFont& f, const QString v)
{
  if (v=="xft") return;
  if (v.at(0)=='-')
    f.setRawName(v);		// for pre-0.9.5 entries
  else
    f.fromString(v);
}

void
msg_list_window::init_fonts()
{
  QFont f;
  app_config& conf=get_config();
  QString fontname;

  fontname=conf.get_string("display/font/all");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    QApplication::setFont(f);
  }

  fontname=conf.get_string("display/font/msglist");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    if (!m_pages->empty())
      set_pages_font(1,f);
    else {
      m_qlist->setFont(f);
      m_qAttch->setFont(f);
    }
  }

  fontname=conf.get_string("display/font/msgbody");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    if (!m_pages->empty()) {
      set_pages_font(2,f);
    }
    else {
      m_msgview->setFont(f);
    }
  }

  fontname=conf.get_string("display/font/tags");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    m_tags_box->setFont(f);
  }

  fontname=conf.get_string("display/font/quicksel");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    m_query_lv->setFont(f);
  }

  fontname=conf.get_string("display/font/menus");
  if (!fontname.isEmpty()) {
    initf(f, fontname);
    set_menu_font(f);
  }
}

void
msg_list_window::save_display_settings()
{
  app_config& conf=get_config();

  msgs_page* p = m_pages->current_page();
  if (p) {
    QSplitter* l = p->m_page_splitter;
    QList<int> sizes = l->sizes();
    QList<int>::Iterator it;
    int i=0;
    QString s;
    for (it=sizes.begin(); it != sizes.end(); ++it) {
      s.sprintf("display/msglist/panel%d_size", ++i);
      conf.set_number(s, *it);
    }
  }
  else {
    ERR_PRINTF("no current page");
  }
  conf.set_number("display/msglist/height", height());
  conf.set_number("display/msglist/width", width());

  // if sender and recipient are swapped, we want to save the non-swapped order
  bool col_swap=false;
  if (m_qlist->sender_recipient_swapped()) {
    col_swap=true;
    m_qlist->swap_sender_recipient(false);
  }
  QString colpos;
  for (int visual_idx=0; visual_idx<m_qlist->header()->count(); ++visual_idx) {
    int logical_idx = m_qlist->header()->logicalIndex(visual_idx);
    if (logical_idx==-1) // shouldn't happen
      continue;
    conf.set_number(QString("display/msglist/column%1_size").arg(logical_idx),
		    m_qlist->header()->sectionSize(logical_idx));
    if (!m_qlist->header()->isSectionHidden(logical_idx)) {
      colpos.append('0'+logical_idx);
    }
  }
  conf.set_string("display/msglist/columns_ordering", colpos);
  if (col_swap)
    m_qlist->swap_sender_recipient(true);   // back to the initial state

  conf.set_number("show_tags", display_vars.m_show_tags);
  conf.set_number("show_tags", display_vars.m_show_tags);
  conf.set_number("display_threads", display_vars.m_threaded);
  conf.set_number("show_headers_level", display_vars.m_show_headers_level);
  conf.set_number("display/filters_trace", display_vars.m_show_filters_trace);
  conf.set_number("display/tags_in_headers", display_vars.m_show_tags_in_headers);

  db_cnx db;
  db.begin_transaction();
  if (conf.store("display/")
      && conf.store("show_tags")
      && conf.store("display_threads")
      && conf.store("show_headers_level"))
    {
      db.commit_transaction();
    }
  else {
    // FIXME: the transaction is already aborted with pgsql if a database
    // error has occurred
    db.rollback_transaction();
  }
  QString user_msg;
  if (conf.name().isEmpty())
    user_msg = QString(tr("The display settings have been saved in the default configuration."));
  else
    user_msg = QString(tr("The display settings have been saved in the '%1' configuration.")).arg(conf.name());
  QMessageBox::information(NULL, tr("Confirmation"), user_msg);
}

void
msg_list_window::cycle_headers()
{
  // if not "most", then display "most" headers, else display "all" headers
  if (display_vars.m_show_headers_level!=1) {
    show_headers(m_menu_actions[me_Display_Headers_Most]);
    m_menu_actions[me_Display_Headers_Most]->setChecked(true);
  }
  else {
    show_headers(m_menu_actions[me_Display_Headers_All]);
    m_menu_actions[me_Display_Headers_All]->setChecked(true);
  }
}

// Select how much of the mail headers should be displayed
void
msg_list_window::show_headers(QAction* which)
{
  if (which==m_menu_actions[me_Display_Headers_None])
    display_vars.m_show_headers_level=0;
  else if (which==m_menu_actions[me_Display_Headers_Most])
    display_vars.m_show_headers_level=1;
  else if (which==m_menu_actions[me_Display_Headers_All])
    display_vars.m_show_headers_level=2;
  else if (which==m_menu_actions[me_Display_Headers_Raw])
    display_vars.m_show_headers_level=3;
  else if (which==m_menu_actions[me_Display_Headers_RawDec])
    display_vars.m_show_headers_level=4;
  // redisplay the current message
  mails_selected();
}

void
msg_list_window::open_global_notepad()
{
  notepad* n = notepad::open_unique();
  if (n) {
    n->show();
    n->activateWindow();
    n->raise();
  }
}

void
msg_list_window::import_mailbox()
{
  extern void open_import_window();
  open_import_window();
}

void
msg_list_window::edit_tags()
{
  tags_dialog* w=NULL;
  try {
    w=new tags_dialog(this);
    if (!w) return;
    connect(w, SIGNAL(tags_restructured()), m_tags_box, SLOT(tags_restructured()));
    w->setWindowModality(Qt::WindowModal);
    w->show();
  }
  catch (ui_error& e) {
    e.display();
    if (w) delete w;
  }
}

void
msg_list_window::start_mailing()
{
  extern void open_mailing_window();
  open_mailing_window();
}

void
msg_list_window::edit_filters()
{
  filter_edit* w = new filter_edit(0);
  CHECK_PTR(w);
#if 0
  std::list<unsigned int> l;
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);

  if (v_sel.size() > 1) {
    std::vector<mail_msg*>::iterator it;
    for (it=v_sel.begin(); it!=v_sel.end(); ++it) {
      l.push_back((*it)->get_id());
    }
    w->set_sel_list(l);
  }
#endif
  w->show();
}

#if 0				// later
void
msg_list_window::open_address_book()
{
  address_book* w = new address_book;
  CHECK_PTR (w);
  w->show();
}
#endif

void
msg_list_window::action_click_msg_list(const QModelIndex& index)
{
  if (index.column() == mail_item_model::column_note) {
    if (m_pCurrentItem) {
      edit_note();
    }
  }
}

void
msg_list_window::mail_reply_all()
{
  mail_reply(2);
}

void
msg_list_window::mail_reply_list()
{
  mail_reply(3);
}

void
msg_list_window::mail_reply_sender()
{
  mail_reply(1);
}

void
msg_list_window::mail_reply(int whom_to)
{
  if (!m_pCurrentItem)
    return;
  QString text_to_quote = m_msgview->selected_text();

  mail_msg::body_format format = mail_msg::format_plain_text;;
  QString html_to_quote;
  QString fm_rep = get_config().get_string("composer/format_for_replies");

  if (m_msgview->content_type_shown() <= 1) {
    // plain text
    if (text_to_quote.isEmpty()) {
      m_pCurrentItem->find_text_to_quote(text_to_quote);
    }
    if (fm_rep == "text/html") {
      format = mail_msg::format_html;
      if (text_to_quote.isEmpty()) {
	// Extract an HTML fragment containing the selection if
	// there is one, otherwise get the whole body
	text_to_quote = m_msgview->selected_html_fragment();
      }
      else {
	text_to_quote = mail_displayer::htmlize(text_to_quote);
	text_to_quote.replace("\n", "<br>\n");
      }
    }
  }
  else {
    // the current mail is HTML and displayed as such
    if (fm_rep == "text/html" || fm_rep == "same_as_sender") {
      format = mail_msg::format_html;
      if (text_to_quote.isEmpty()) {
	// Extract an HTML fragment containing the selection if
	// there is one, otherwise get the whole body
	text_to_quote = m_msgview->selected_html_fragment();
      }
      else
	text_to_quote = mail_displayer::htmlize(text_to_quote);
    }
    else { /* should be limited to (fm_rep == "text/plain"), but we let's have
	      other cases be treated as text/plain as well */
      // Convert HTML into quoted plain text
      format = mail_msg::format_plain_text;
      if (text_to_quote.isEmpty()) {
	text_to_quote = m_msgview->body_as_text();
      }
    }
  }
  mail_msg msg = m_pCurrentItem->setup_reply(text_to_quote, whom_to, format);

  new_mail_widget* w = new new_mail_widget(&msg, 0);
  if (format == mail_msg::format_html)
    w->format_html_text();
  else
    w->format_plain_text();

  w->show_tags();
  w->insert_signature();
  w->start_edit(); 

  /* A signal will be emitted when the reply is stored, and we connect
     that to a visual update on all our listviews of the original mail
     new status (now replied)  */
  connect(w, SIGNAL(refresh_request(mail_id_t)),
	  this, SLOT(global_refresh_status(mail_id_t)));

  if (w->errmsg().isEmpty()) {
    w->show();
  }
  else {
    delete w;
  }

}

void
msg_list_window::forward()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected (v_sel);

  if (v_sel.size()==0)
    return;
  if (v_sel.size()>1) {
    QMessageBox::information(this, APP_NAME, tr("Only one message can be forwarded at a time."));
    return;
  }
  mail_msg msg = v_sel[0]->setup_forward();
  new_mail_widget* w = new new_mail_widget(&msg, 0);
  w->insert_signature();
  w->start_edit();
  /* A signal will be emitted when the reply is stored, and we connect
     that to a visual update on all our listviews of the original mail
     new status (now forwarded)  */
  connect(w, SIGNAL(refresh_request(mail_id_t)),
	  this, SLOT(global_refresh_status(mail_id_t)));

  if (w->errmsg().isEmpty()) {
    w->show();
  }
  else {
    delete w;
  }
}

void
msg_list_window::bounce()
{
  if (m_pCurrentItem)
    m_pCurrentItem->bounce();
}

void
msg_list_window::show_tags(bool show)
{
  display_vars.m_show_tags=show;
  if (show)
    m_tags_box->show();
  else
    m_tags_box->hide();
}

/*
  if b=false, disable all widgets and record previous state
  if b=true, restore previous state
  only one state is recorded (no stack)
*/
void
msg_list_window::enable_interaction(bool b)
{
  QWidget* w[] = {
    m_qAttch, menuBar(), m_toolbar, m_new_mail_btn,
    m_tags_box, m_query_lv, m_qlist, m_ql_search, m_msgview
  };
  QAction* actions[] = {
    m_action_search  // search toolbar
  };

  if (!b) {
    m_widgets_enable_state.clear();
    m_actions_enable_state.clear();
  }
  // Widgets
  for (uint i=0; i<sizeof(w)/sizeof(w[0]); i++) {
    if (!b) {
      m_widgets_enable_state.push_back(w[i]->isEnabled());
      w[i]->setEnabled(false);
    }
    else {
      w[i]->setEnabled(m_widgets_enable_state.takeFirst());
    }
  }
  // Actions
  for (uint i=0; i<sizeof(actions)/sizeof(actions[0]); i++) {
    if (!b) {
      m_actions_enable_state.push_back(actions[i]->isEnabled());
      actions[i]->setEnabled(false);
    }
    else {
      actions[i]->setEnabled(m_actions_enable_state.takeFirst());
    }
  }
}

void
msg_list_window::attch_run(attch_lvitem* item)
{
  attachment* pa = item->get_attachment();
  if (!pa) return;

  QString tmpname = pa->get_temp_location();
  install_progressbar(tr("Downloading attached file: %1").arg(tmpname));
  item->download(tmpname, &m_abort);
  if (!m_abort)
    pa->launch_os_viewer(tmpname);
  uninstall_progressbar();
}

/* Returns: 0: cancel, 1: save_file, 2: run default application */
int
msg_list_window::attachment_dest(attachment* pa)
{
  QString default_app = pa->default_os_application();
  if (default_app.isEmpty())
    return 1;
  attch_dialog_save dlg;
  dlg.set_app_name(default_app);
  dlg.set_file_name(pa->filename());
  int r=dlg.exec();
  if (r==QDialog::Rejected)
    return 0;
  return dlg.choice();
}

void
msg_list_window::attch_selected(QTreeWidgetItem* p, int column _UNUSED_)
{
  attch_lvitem* pItem=dynamic_cast<attch_lvitem*>(p);
  attachment* pa = pItem->get_attachment();
  if (!pa) {			// if it's not an attachment, it's a note
    edit_note();
    return;
  }
  if (pa->mime_type()=="message/rfc822" || pa->mime_type()=="text/rfc822-headers") {
    const char* contents = pa->get_contents();
    if (contents) {
      mime_msg_viewer* v = new mime_msg_viewer(contents, display_vars);
      v->show();
    }
  }
  else {
    if (!pa->application().isEmpty()) {
      // launch helper application (MIME viewer)
      QString tmpname = pa->get_temp_location();
      install_progressbar(tr("Downloading attached file: %1").arg(tmpname));
      pItem->download(tmpname, &m_abort);
      if (!m_abort) {
	DBG_PRINTF(5,"launch_external_viewer");
	pa->launch_external_viewer(tmpname);
      }
      uninstall_progressbar();
    }
    else {
      int d=attachment_dest(pa);
      if (d==2) { // run default app
	attch_run(pItem);
      }
      if (d==1) { // save to disk
	QString fname = pa->filename();
	if (attch_listview::m_last_attch_dir.isEmpty()) {
	  attch_listview::m_last_attch_dir = get_config().get_string("attachments_directory");
	}
	if (!attch_listview::m_last_attch_dir.isEmpty()) {
	  fname = attch_listview::m_last_attch_dir + "/" + fname;
	}
  //      DBG_PRINTF(5, "fname=%s", fname.latin1());
	fname = QFileDialog::getSaveFileName(this, tr("Save File"), fname);
	if (!fname.isEmpty()) {
	  install_progressbar(tr("Downloading attached file: %1").arg(fname));
	  QString dir=pItem->save_to_disk(fname, &m_abort);
	  if (!dir.isEmpty())
	    attch_listview::m_last_attch_dir = dir;
	  uninstall_progressbar();
	}
      }
    }
  }
}

bool
msg_list_window::progress_aborted()
{
  return m_abort;
}

void
msg_list_window::install_progressbar(QString msg)
{
  m_new_mail_btn->hide();
  m_progress_bar = new QProgressBar(this);
  statusBar()->addPermanentWidget(m_progress_bar);
  if (!msg.isEmpty())
    statusBar()->showMessage(msg);
  //  connect(widget, SIGNAL(progress(int)), this, SLOT(show_progress(int)));
  enable_interaction(false);
  show_abort_button();
}

void
msg_list_window::show_abort_button()
{
  m_abort=false;
  m_abort_button = new QPushButton(tr("Abort"), this);
  m_abort_button->setIcon(UI_ICON(ICON16_CANCEL));
  statusBar()->addPermanentWidget(m_abort_button);
  connect(m_abort_button, SIGNAL(clicked()), this, SLOT(abort_operation()));
}

void
msg_list_window::hide_abort_button()
{
  if (m_abort_button) {
    delete m_abort_button;
    m_abort_button=NULL;
  }
  m_abort=false;
}

void
msg_list_window::uninstall_progressbar()
{
  hide_abort_button();
  enable_interaction(true);
  if (m_progress_bar) {
    delete m_progress_bar;
    m_progress_bar=NULL;
  }
  statusBar()->clearMessage();
  m_new_mail_btn->show();
}

void
msg_list_window::abort_operation()
{
  m_abort=true;
  if (m_waiting_for_results) {	// query in progress
    // m_waiting_for_results = false;
    m_thread.cancel();
  }
  emit abort_progress(); // currently used for attachments downloads
}

void
msg_list_window::new_mail()
{
  mail_msg msg;;
  new_mail_widget* w = new new_mail_widget(&msg, 0);
  if (w->errmsg().isEmpty()) {
    QString fm_rep = get_config().get_string("composer/format_for_new_mail");
    if (fm_rep == "text/html") {
      w->format_html_text();
    }
    w->show();
    w->insert_signature();
    w->start_edit();
  }
  else {
    delete w;
  }
}

/*
  The selection of messages
*/
bool
msg_list_window::want_new_window() const
{
  QString mode=get_config().get_string("preferred_open_mode");
  return (mode=="new_window")?true:false;
}

void
msg_list_window::new_list()
{
  bool new_window=want_new_window();
  msg_select_dialog* w=new msg_select_dialog(new_window);
  if (!new_window) {
    connect(w,SIGNAL(fetch_done(msgs_filter*)), this,
	    SLOT(fill_fetch_new_page(msgs_filter*)));
  }
  w->show();
}

void
msg_list_window::sel_filter(const msgs_filter& f)
{
  setCursor(Qt::WaitCursor);
//  m_new_mail_btn->hide();
  show_abort_button();
  enable_interaction(false);
  statusBar()->showMessage(tr("Querying database..."));

  m_loading_filter = new msgs_filter(f);
  int r = m_loading_filter->asynchronous_fetch(&m_thread);
  if (r==1) {
    m_waiting_for_results = true;
  }
  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_list_window::sel_save_query()
{
  extern void save_filter_query(msgs_filter*, int,const QString);
  save_filter_query(m_filter, 0, QString::null);
  m_query_lv->reload_user_queries();
}

#if 0
void msg_list_window::sel_header_analysis()
{
  std::list<unsigned int> l;
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);

  if (v_sel.size() <= 1) {
    // If there is zero or one item selected, the whole page is taken
    // as the data source...
    msgs_filter::mlist_t::iterator it;
    for (it=m_filter->m_list_msgs.begin(); it!=m_filter->m_list_msgs.end(); ++it) {
      l.push_back((*it)->get_id());
    }
  }
  else {
    // ... else only the selected items are taken
    std::vector<mail_msg*>::iterator it;
    for (it=v_sel.begin(); it!=v_sel.end(); ++it) {
      l.push_back((*it)->get_id());
    }
  }
  extern void analyze_headers(const std::list<unsigned int>&);
  analyze_headers(l);
}
#endif

void
msg_list_window::sel_sent()
{
  msgs_filter f;
  f.m_status_set = mail_msg::statusOutgoing;
  sel_filter(f);
  store_quick_sel(query_lvitem::virtfold_sent);
}

void
msg_list_window::sel_trashcan()
{
  msgs_filter f;
  f.m_in_trash=true;
  f.m_status_set = mail_msg::statusTrashed;
  sel_filter(f);
  store_quick_sel(query_lvitem::virtfold_trashcan);
}

/* keep a reference to the entry in the quick selection listview
   for later use */
void
msg_list_window::store_quick_sel(query_lvitem::item_type type, uint tag_id)
{
  int id = m_query_lv->highlight_entry(type, tag_id);
  msgs_page* current = m_pages->current_page();
  if (current) {
    DBG_PRINTF(5, "store_quick_sel id=%d", id);
    current->m_query_lvitem_id = id;
  }
}

void
msg_list_window::new_messages()
{
  msgs_filter f;
  f.m_status=0;
  f.set_auto_refresh();
  sel_filter(f);
  store_quick_sel(query_lvitem::new_all);
}

void msg_list_window::sel_tag(const QString tagname)
{
  msgs_filter f;
  f.m_tag_name = tagname;
  sel_filter(f);
}

void msg_list_window::sel_tag(uint tag_id)
{
  DBG_PRINTF(5, "sel_tag(%d)", tag_id);
  msgs_filter f;
  f.m_tag_id = tag_id;
  sel_filter(f);
  //  store_quick_sel(query_lvitem::tagged, tag_id);
}

void msg_list_window::sel_tag_status(unsigned int tag_id, int status_set,
				     int status_unset)
{
  DBG_PRINTF(5, "sel_tag_status(%d)", tag_id);
  msgs_filter f;
  //  f.m_tag_name = tagname;
  f.m_tag_id=tag_id;
  f.m_status_set = status_set;
  f.m_status_unset = status_unset;
  sel_filter(f);
  if (status_set==0 && status_unset==(mail_msg::statusArchived|mail_msg::statusTrashed)) {
    //    store_quick_sel(query_lvitem::current_tagged, tag_id);
  }
}

void
msg_list_window::non_processed_messages()
{
  msgs_filter f;
  f.m_status_unset = mail_msg::statusArchived|mail_msg::statusTrashed;
  f.set_auto_refresh();
  sel_filter(f);
}

void
msg_list_window::sel_refine()
{
  msg_select_dialog* w = new msg_select_dialog(false);
  w->filter_to_dialog(m_filter);
  w->show();
  connect(w,SIGNAL(fetch_done(msgs_filter*)), this, SLOT(fill_fetch(msgs_filter*)));
}

void
msg_list_window::fetch_more()
{
  setCursor(Qt::WaitCursor);
//  m_new_mail_btn->hide();
  show_abort_button();
  enable_interaction(false);
  statusBar()->showMessage(tr("Querying database..."));

  m_filter->m_fetch_results=NULL;
  int r = m_filter->asynchronous_fetch(&m_thread, true);
  DBG_PRINTF(8, "after async_fetch m_filter->results as %d elements", m_filter->m_list_msgs.size());
  if (r==1) {
    m_waiting_for_results = true;
  }
  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_list_window::fill_fetch(msgs_filter* f)
{
  DBG_PRINTF(5,"fill_fetch()");

  // clear old contents
  m_qlist->clear();
  m_filter->m_list_msgs.clear();
  m_msgview->clear();
  m_qAttch->hide();

  // show new contents
  *m_filter=*f;
  m_filter->make_list(m_qlist);
  set_title();
}

void
msg_list_window::fill_fetch_new_page(msgs_filter* f)
{
  DBG_PRINTF(5,"fill_fetch_new_page()");
  clear_quick_query_selection();
  add_msgs_page(f, false);
  // Remove any highlighting in the quick query selection to avoid confusion
  // between a user query and and a selection branch
}


void
msg_list_window::goto_next_message()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()>0) {
    m_qlist->select_below(v_sel.back());
  }
  else {
    mail_msg* first = m_qlist->first_msg();
    if (first) {
      m_qlist->select_msg(first);
    }
  }
}

void
msg_list_window::goto_previous_message()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()>0)
    m_qlist->select_above(v_sel.front());
  else {
    mail_msg* first = m_qlist->first_msg();
    if (first) {
      m_qlist->select_msg(first);
    }
  }    
}

void
msg_list_window::sel_bottom()
{
  m_qlist->scrollToBottom();
}

void
msg_list_window::sel_refresh_list()
{
  m_filter->fetch(m_qlist);
  set_title();
  if (m_filter->auto_refresh()) {
//    m_new_mail_btn->enable(false);
    statusBar()->clearMessage();
  }
}

/* Refresh the current list of messages, but only if it's bound to a
   filter in auto-refresh mode */
void
msg_list_window::sel_auto_refresh_list()
{
  if (m_filter->auto_refresh()) {
    m_filter->fetch(m_qlist);
    set_title();
  }
}

void
msg_list_window::sel_refresh()
{
  sel_refresh_list();
  m_query_lv->refresh();
}

void msg_list_window::sel_import()
{
  // TODO
}

void
msg_list_window::about()
{
  about_box* b = new about_box(NULL);
  b->show();
}

void
msg_list_window::focus_on_msglist()
{
  m_qlist->setFocus();
}

void
msg_list_window::open_help()
{
  helper::show_help("help");
}

void
msg_list_window::dynamic_help()
{
  // auto-track ON = menu item checked
  helper::auto_track(m_menu_actions[me_Help_Dynamic]->isChecked());
}

void
msg_list_window::close_window()
{
  close();
}

void
msg_list_window::new_window()
{
  msgs_filter f;
  f.m_sql_stmt="0";		// FIXME (empty filter)
  msg_list_window* w=new msg_list_window(&f);
  w->show();
}

void
msg_list_window::preferences()
{
  prefs_dialog* w = new prefs_dialog;
  w->show();
}

void
msg_list_window::find_text()
{
  if (!m_wSearch) {
    m_wSearch=new search_box(NULL);
    connect(m_wSearch, SIGNAL(mail_find(const QString&, int, int)),
	    SLOT(search_generic(const QString&, int, int)));
    connect (m_wSearch, SIGNAL(search_closed()),
	     SLOT(search_finished()));
  }
  m_wSearch->show();
}

// stop highlighting the searched text found in the body
void
msg_list_window::search_finished()
{
  m_highlighted_text.clear();
  if (!m_fetch_on_demand) {
    m_msgview->clear();
    display_body();
  }
}

void
msg_list_window::save_body()
{
  // get the mails whose body we want to save
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()==0)
    return;			// shouldn't happen

  QString filename=QFileDialog::getSaveFileName(this, tr("Filename"),
						QString(), QString("*.txt"));
  if (filename.isEmpty())
    return;
  QByteArray filename_qba = filename.toLocal8Bit();
  std::ofstream of(filename_qba.constData(), std::ios::out);

  // stream the bodies to the file
  std::vector<mail_msg*>::iterator iter;
  for (iter=v_sel.begin(); iter!=v_sel.end(); ++iter) {
    (*iter)->streamout_body(of);
  }
  of.close();
}

void
msg_list_window::edit_body()
{
  if (!m_pCurrentItem)
    return;
  body_edit* be = new body_edit(NULL);
  be->set_contents(m_pCurrentItem->get_id(), m_pCurrentItem->get_body_text(false));
  be->resize(600,500);
  connect(be, SIGNAL(text_updated(uint,const QString*)), this,
	  SLOT(body_edited(uint,const QString*)));
  be->show();
}

void
msg_list_window::body_edited(uint mail_id, const QString* new_text)
{
  mail_msg* p = m_qlist->find(mail_id);
  if (p) {
    p->set_body_text(*new_text);
    if (p==m_pCurrentItem) {
      display_body();		// refresh
    }
  }
  else {
   DBG_PRINTF(2, "body_edited: mail_msg not found");
  }
}

#if 0
void
msg_list_window::save_to_mbox()
{
  std::vector<mail_lvitem*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()==0)
    return;

  QString filename=QFileDialog::getSaveFileName(this, tr("Save mailbox"));
  if (filename.isEmpty())
    return;
  std::ofstream of(filename, std::ios::out|std::ios::app);

  for (int i=0; i<v_sel.size(); i++) {
    v_sel[i]->streamout_mbox(of);
  }
  of.close();
}
#endif

void
msg_list_window::view_attachment()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()!=1)		// act upon one and only one message
    return;

#if 0
  attch_lvitem* item = static_cast<attch_lvitem*>(m_qAttch->firstChild());
  while (item) {
    if (item->is_note())
      continue;
    if (item->isSelected())
      break;
    item = static_cast<attch_lvitem*>(item->itemBelow());
  }
  if (!item)
    return;
#else
  attch_lvitem* item=dynamic_cast<attch_lvitem*>(m_qAttch->currentItem());
  if (!item || item->is_note()) return;
#endif
  attachment* a = item->get_attachment();

  QTextEdit* qe = new QTextEdit(NULL);
  qe->setReadOnly(true);
  if (a->get_contents())
    qe->setPlainText(a->get_contents());
  qe->resize(400,200);
  qe->show();
}


void
msg_list_window::save_attachment()
{
  m_qAttch->save_attachments();
}

// slot
void
msg_list_window::show_progress(int progress)
{
  DBG_PRINTF(5, "show_progress(%d)", progress);
  if (!m_progress_bar) {
    DBG_PRINTF(2, "show_progress: no progress bar!");
    return;
  }
  // when negative, progress is the total number of steps expected
  if (progress<0) {
    m_progress_bar->setMaximum(-progress);
  }
  else {
    m_progress_bar->setValue(progress);
  }
  gl_pApplication->processEvents();
  m_progress_bar->show();
  m_progress_bar->repaint();
  statusBar()->repaint();
  QApplication::flush();
}

void
msg_list_window::sender_properties()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);
  if (v_sel.size()>1) {
    QMessageBox::warning(NULL, "Error", tr("Please select one message only"));
  }
  else if (v_sel.size()==0) {
    QMessageBox::warning(NULL, "Error", tr("Please select a message"));
  }
  else {
    if (!v_sel[0]->From().isEmpty()) {
      void open_address_book(QString email);
      open_address_book(v_sel[0]->From());
    }
    else {
      QMessageBox::warning(NULL, "Error", tr("The message has no sender!"));
    }
  }
}

void
msg_list_window::msg_print()
{
  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected (v_sel);
  if (v_sel.size()!=1) {
    QMessageBox::warning(NULL, "Error", tr("Please select a message to print"));
    return;
  }
  m_msgview->print();
}

// Edit the current message's private note
void
msg_list_window::edit_note()
{
  if (!m_pCurrentItem)
    return;
  if (!m_pCurrentItem->fetchNote())
    return;
  note_widget* w=new note_widget(this);
  QString initial_note = m_pCurrentItem->getNote();
  w->set_note_text (initial_note);
  int ret=w->exec();
  if (ret && w->get_note_text() != initial_note) {
    m_pCurrentItem->set_note(w->get_note_text());
    m_pCurrentItem->store_note();
    display_msg_note();
    m_qlist->update_msg(m_pCurrentItem); // visual update
  }
  w->close();
}

/*
  Update the note entry in the attachments & note's listview
  There are 4 cases:
  1. no note in db/no note in listview => nothing to do
  2. no note in db/a note in listview => remove it
  3. a note in db/no note in listview => add it
  4. a note in db/a note in listview => update the text
*/
void
msg_list_window::display_msg_note()
{
  if (!m_pCurrentItem || !m_qAttch)
    return;
  m_pCurrentItem->fetchNote();
  QString n = m_pCurrentItem->getNote();
  uint index=0;
  attch_lvitem* lvpItem = dynamic_cast<attch_lvitem*>(m_qAttch->topLevelItem(0));
  while (lvpItem) {
    if (!lvpItem->get_attachment()) {
      break;
    }
    index++;
    lvpItem = dynamic_cast<attch_lvitem*>(m_qAttch->topLevelItem(index));
  }

  if (n.isEmpty()) {
    if (lvpItem) {		// case 2
      delete lvpItem;
      lvpItem=NULL;
    }
  }
  else {
    if (!lvpItem) {		// case 3
      lvpItem = new attch_lvitem(m_qAttch, NULL);
    }
    // case 3 & 4
    lvpItem->set_note(n);
    lvpItem->fill_columns();
  }
  // don't show the attachments & note's listview when it's empty
  if (m_qAttch->topLevelItem(0)) {
    m_qAttch->show();
#if QT_VERSION<0x040400
    /* Qt-4.3 has a bug that zeroes the attachments panel's size when
       it's hidden and the user moves the splitter betweens msgs list
       and body. As a workaround, we force here a non-zero size */
    QSplitter* sp = (QSplitter*)m_qAttch->parent();
    if (sp->sizes().at(2)==0) {
      QList<int> sz = sp->sizes();
      sz.replace(2, 10); // arbitrary height of 10, the actual size on screen will be larger
      sp->setSizes(sz);
    }
#endif
  }
  else
    m_qAttch->hide();
}

void
msg_list_window::closeEvent(QCloseEvent *e)
{
  if (m_wSearch)
    m_wSearch->close();
  e->accept();
}

void
msg_list_window::search_generic(const QString& text, int where, int options)
{
  bool found=FALSE;
  bool wrapped=FALSE;

  static mail_msg* msg_last_hit = NULL;
  static QString string_last_hit;
  mail_msg* cur_msg = NULL;

  std::vector<mail_msg*> v_sel;
  m_qlist->get_selected(v_sel);

  /* if one (and only one) message is selected, start the search from
     this one */
  if (v_sel.size() == 1) {
    cur_msg = v_sel[0];
    /* If the search is to start from the same message where we found a hit
       last time, and the text searched for is the same,
       then we start from the next message instead.
       This is an implicit "Find next". */
    if (cur_msg==msg_last_hit && text==string_last_hit) {
      cur_msg = m_qlist->nearest_msg(cur_msg, 1); // below
      if (!cur_msg)
	cur_msg=m_qlist->first_msg(); // wrap around
    }
  }
  else {
    /* else start from the beginning of the list */
    cur_msg = m_qlist->first_msg();
  }

  if (cur_msg) {
    DBG_PRINTF(3, "search starts at msg #%d", cur_msg->get_id());
  }
  else {
    DBG_PRINTF(3, "no first message in the list");
    return;			// looks like there's no message at all
  }


  Qt::CaseSensitivity cs=((options&FT::caseInsensitive)==0)?Qt::CaseSensitive:Qt::CaseInsensitive;

  {
    const QCursor cursor(Qt::WaitCursor);
    QApplication::setOverrideCursor(cursor);

    mail_msg* start_msg=cur_msg;

    while (!found && cur_msg) {
      if (where & FT::searchInSubjects) {
	found=(cur_msg->Subject().indexOf(text, 0, cs)>=0);
      }
      if (!found && (where & FT::searchInHeaders)) {
	found=(cur_msg->get_headers().indexOf(text, 0, cs)>=0);
      }
      if (!found && (where & FT::searchInBodies)) {
	found=(cur_msg->get_body_text().indexOf(text, 0, cs)>=0);
      }
      if (!found) {
	cur_msg = m_qlist->nearest_msg(cur_msg, 1);	// below
	if (cur_msg==start_msg)	// all done
	  cur_msg=NULL;
	else if (cur_msg==NULL && !wrapped) {
	  wrapped=TRUE;
	  cur_msg=m_qlist->first_msg(); // wrap around
	}
      }
    }
    QApplication::restoreOverrideCursor();
  }

  if (found && cur_msg) {
    string_last_hit=text;

    m_highlighted_text.clear();
    searched_text s;
    s.m_text=text;
    s.m_is_cs = ((options&FT::caseInsensitive)==0);
    s.m_is_word=false;
    m_highlighted_text.push_back(s);
    m_highlightedCaseSensitive = s.m_is_cs;
    
    m_qlist->select_msg(cur_msg);
    msg_last_hit=cur_msg;
  }
  else {
    string_last_hit = "";
    m_highlighted_text.clear();
    msg_last_hit=NULL;
    QString msg = "'" + text + "' not found.";
    QMessageBox::information (this, APP_NAME, msg);
  }
}

// Slot. To be called when the selection of messages changes
void
msg_list_window::mails_selected()
{
  if (m_ignore_selection_change)
    return;
  std::vector<mail_msg*> v;
  m_qlist->get_selected(v);
  DBG_PRINTF(5, "%u mails are selected", v.size());
  if (v.size()==1) {
    mail_selected(v[0]);
  }
  else {
    m_msgview->clear();
    m_qAttch->hide();
  }
  enable_commands();
}



/* Contextual popup menu on body view */
void
msg_list_window::body_menu()
{
  QMenu qmenu(this);
  QAction* actions[] = { 
    m_action_move_backward,
    m_action_move_forward,
    m_action_reply_sender,
    m_action_reply_all,
    m_action_reply_list
  };
  for (unsigned int i=0; i<sizeof(actions)/sizeof(actions[0]); i++) {
    qmenu.addAction(actions[i]);
  }
  qmenu.addSeparator();

  qmenu.addAction(m_action_msgview_select_all);
  qmenu.addAction(m_menu_actions[me_Edit_Copy]);

  qmenu.exec(QCursor::pos());
}

/* Display body and attachments */
void
msg_list_window::display_msg_contents()
{
  if (!m_pCurrentItem) {
    m_msgview->clear();
    return;
  }

  display_body();

  // display attachments
  m_qAttch->clear();

  attachments_list& attchs=m_pCurrentItem->attachments();
  if (m_pCurrentItem->has_attachments()) {
    attchs.fetch();
  }

  if (attchs.size()) {
    attachments_list::iterator iter;
    attch_lvitem* last_item=NULL; // used to insert the attachments in the same order as they're in the database
    for (iter=attchs.begin(); iter!=attchs.end(); ++iter) {
      attch_lvitem* lvpItem = new attch_lvitem(m_qAttch, last_item, &(*iter));
      last_item = lvpItem;
      lvpItem->fill_columns();
    }
  }
  display_msg_note();
}

void
msg_list_window::display_selection_tags()
{
  // TODO: display tags intersection when several messages are selected
  if (m_pCurrentItem) {
    m_tags_box->set_tags(m_pCurrentItem->get_tags());
  }
}

// Called when and if only one message gets selected
void
msg_list_window::mail_selected(mail_msg* msg)
{
  m_pCurrentItem=msg;
  if (!msg) {
    DBG_PRINTF(6, "mail_selected: null msg");
    return;
  }
  DBG_PRINTF(5,"mail_selected: %d", msg->GetId());
  m_qlist->refresh(msg->get_id()); // will update from database
  // display body
  m_msgview->set_mail_item(msg);
  if (!m_fetch_on_demand) {
    display_msg_contents();
  }
  else {
    m_msgview->set_show_on_demand(true);
    m_qAttch->clear();
    m_qAttch->hide();
  }

  // show tags
  if (!m_fetch_on_demand)
    display_selection_tags();

  // set status
  // we don't change the status of a message that is an
  // attachment to a database message: we would have no way of saving
  // this information.
  // also we don't update the status in fetch on demand mode
  if (!(msg->status() & mail_msg::statusAttached) && !m_fetch_on_demand) {
    uint known_status=msg->status();
    // update in memory
    msg->set_status(msg->status() | mail_msg::statusRead);
    // update in database
    msg->update_status();
    // show for all views
    if (known_status != msg->status()) {
      m_qlist->update_msg(msg);
      propagate_status(msg);
    }
  }
}

void
msg_list_window::msg_archive()
{
  DBG_PRINTF(5, "msg_archive");
  std::vector<mail_msg*> v;
  m_qlist->get_selected(v);
  if (v.empty()) {
    DBG_PRINTF(5,"no mail selected");
    return;
  }
  else if (v.size()==1) {
    DBG_PRINTF(5, "emit mail_chg_status processed 0x%p", v[0]);
    emit mail_chg_status(mail_msg::statusArchived+mail_msg::statusRead ,v[0]);
  }
  else {
    DBG_PRINTF(5,"%d mails selected", v.size());
    emit mail_multi_chg_status(mail_msg::statusArchived+mail_msg::statusRead, &v);
  }
}

void
msg_list_window::clear_quick_query_selection()
{
  m_query_lv->clear_selection();
}

void
msg_list_window::quick_query_selection(QTreeWidgetItem* qt_item, int column)
{
  Q_UNUSED(column);
  DBG_PRINTF(5, "quick_query_selection");

  query_lvitem* item = (query_lvitem*)qt_item;
  if (!item) {
    DBG_PRINTF(1, "no item selected!");
    return;
  }
  switch(item->m_type) {
  case query_lvitem::new_all:
    new_messages();
    break;
  case query_lvitem::new_not_tagged:
    {
      msgs_filter f;
      f.m_status=0;
      f.m_tag_name=("(No tag set)");
      sel_filter(f);
    }
    break;
  case query_lvitem::nonproc_all:
    non_processed_messages();
    break;
  case query_lvitem::nonproc_not_tagged:
    {
      msgs_filter f;
      f.m_status_unset = mail_msg::statusArchived|mail_msg::statusTrashed;
      f.m_tag_name=("(No tag set)");
      sel_filter(f);
    }
    break;
  case query_lvitem::current_prio:
    {
      msgs_filter f;
      f.m_min_prio = 1;		// TODO: user definable?
      f.m_status_unset = mail_msg::statusArchived|mail_msg::statusTrashed;
      sel_filter(f);
    }
    break;
  case query_lvitem::archived_tagged:
    {
      query_tag_lvitem* ti = dynamic_cast<query_tag_lvitem*>(item);
      if (ti && ti->m_tag_id)
	sel_tag_status(ti->m_tag_id, mail_msg::statusArchived, 0);
    }
    break;
  case query_lvitem::current_tagged:
    {
      query_tag_lvitem* ti = dynamic_cast<query_tag_lvitem*>(item);
      if (ti && ti->m_tag_id)
	sel_tag_status(ti->m_tag_id, 0, mail_msg::statusArchived|mail_msg::statusTrashed);
    }
    break;
  case query_lvitem::virtfold_sent:
    sel_sent();
    break;
  case query_lvitem::virtfold_trashcan:
    sel_trashcan();
    break;
  case query_lvitem::user_defined:
    {
      msgs_filter f;
      f.m_sql_stmt = item->m_sql;
      sel_filter(f);
    }
    break;
  case query_lvitem::tree_node:
    break;			// nothing to do
  default:
    QMessageBox::warning(this, APP_NAME, tr("Not implemented yet!"));
    break;
  }
}

void
msg_list_window::check_new_mail()
{
  try {
    statusBar()->showMessage(tr("Checking for new mail..."));
    sql_query q;
    m_filter->build_query(q);
#ifdef WITH_PGSQL
    PGconn* c=GETDB();
    QString s=q.get();
    QByteArray qb = s.toLatin1();
    const char* query=qb.constData();
    DBG_PRINTF(5,"%s", query);
    PGresult* res = PQexec(c, query);
    m_auto_refresh_results.clear();
    m_filter->load_result_list(res, &m_auto_refresh_results);
    if (res) PQclear(res);
#endif // WITH_PGSQL
    if (!m_auto_refresh_results.empty()) {
      DBG_PRINTF(5, "non empty refresh result list");
      // check if any of these results is new
      std::list<mail_result>::iterator iter = m_auto_refresh_results.begin();
      while (iter != m_auto_refresh_results.end()) {
	if (m_qlist->find(iter->m_id)!=NULL) {
	  // remove the messages that are already in m_filter
	  iter = m_auto_refresh_results.erase(iter);
	}
	else
	  ++iter;
      }
    }
    if (!m_auto_refresh_results.empty()) {
//      m_new_mail_btn->set_number(m_auto_refresh_results.size());
//      m_new_mail_btn->enable(true);
      statusBar()->showMessage(tr("New mail is available."));
    }
    else {
//      m_new_mail_btn->enable(false);
      statusBar()->showMessage(tr("No new mail."), 3000);
    }
  }
  catch (int) {
    statusBar()->showMessage(tr("Error while checking for new mail."));
    return;
  }
}

void
msg_list_window::show_status_message(const QString& msg)
{
  if (msg.isEmpty())
    statusBar()->clearMessage();
  else
    statusBar()->showMessage(msg);
}

void
msg_list_window::blip_status_message(const QString& msg)
{
  if (msg.isEmpty())
    statusBar()->clearMessage();
  else
    statusBar()->showMessage(msg, 3000);
}

void
msg_list_window::timer_func()
{
  m_timer_ticks++;

  // Check if we got results from a fetch
  if (m_waiting_for_results && m_thread.isFinished()) {
    DBG_PRINTF(5, "End of asynchronous fetch detected in timer_func()");
    m_waiting_for_results = false;

    enable_interaction(true);

    if (m_thread.m_fetch_more) { // FIXME: use a better abstraction
      // this is a "fetch more" operation. It uses the current filter (m_filter)
      m_filter->postprocess_fetch(m_thread);
      DBG_PRINTF(8, "fetch_more -> make_list");
      m_filter->make_list(m_qlist);
      DBG_PRINTF(8, "after async_fetch m_filter->results as %d elements", m_filter->m_list_msgs.size());
      set_title();
    }
    else if (m_loading_filter && m_loading_filter->m_fetch_results) {
      // this is a fetch for a new list of results. It uses a temporary filter
      m_loading_filter->postprocess_fetch(m_thread);
      if (want_new_window()) {
	msg_list_window* w = new msg_list_window(m_loading_filter, 0);
	w->show();
      }
      else {
	add_msgs_page(m_loading_filter, false); // will instantiate m_filter
      }
    }

    m_thread.release();

    unsetCursor();
    hide_abort_button();
//    m_new_mail_btn->show();

    if (!m_thread.m_cancelled) {
      double exec_time = m_thread.m_exec_time/1000.0; // in seconds
      statusBar()->showMessage(tr("Query executed in %1 s.").arg(exec_time, 0, 'f', 2),3000);
    }
    else
      statusBar()->showMessage(tr("Query cancelled."));

    if (m_loading_filter) {
      delete m_loading_filter;
      m_loading_filter = NULL;
    }
  }

  else {
    int delay=get_config().get_number("fetch/auto_refresh_messages_list"); // minutes
    if (delay!=0 && m_timer_ticks%(delay*60*5)==0) {
      db_cnx db;
      if (!db.ping()) {
	DBG_PRINTF(3, "No reply to database ping");
	if (!db.datab()->reconnect()) {
	  DBG_PRINTF(3, "Failed to reconnect to database");
	  return;
	}
	else {
	  DBG_PRINTF(3, "Database reconnect successful");
	}
      }

      m_query_lv->refresh();

      if (get_config().get_bool("fetch/auto_incorporate_new_results", false)) {
	if (m_filter->auto_refresh())
	  sel_refresh_list();
      }
      else {
	check_new_mail();
      }
    }
  }
}

void
msg_list_window::timer_idle()
{
  int max_ahead = get_config().get_number("fetch_ahead_max_msgs");
  if (max_ahead==0 || !db_cnx::idle())
    return;			// do nothing if we're busy with the db already
  std::vector<mail_msg*> v;
  m_qlist->get_selected(v);
  if (v.size()==1) {
    mail_msg* item = v.front();
    while ((max_ahead--)>0 && (item = m_qlist->nearest_msg(item, 1))!=NULL) {
      if (!item->body_in_cache()) {
	DBG_PRINTF(8, "body fetched in idle timer");
	item->fetch_body_text(true); // partial fetch
	return;
      }
    }
  }
}

void
newmail_button::update_font(QFont f)
{
  int ps=f.pointSize();
  f.setPointSize((ps*80)/100); // reduce the font size by 1/5
  setFont(f);
}

newmail_button::newmail_button(QString txt, QWidget* parent) :
  QPushButton(txt, parent)
{
  update_font(font());
  QIcon ico(FT_MAKE_ICON(FT_ICON16_INBOX));
  setIcon(ico);
}

newmail_button::~newmail_button()
{
}

void
newmail_button::trayicon_click()
{
  emit show_new_mail();
}

void
newmail_button::enable(bool enable)
{
  if (enable) {
    QIcon ico(FT_MAKE_ICON(FT_ICON16_INBOX));
    //    setPixmap(FT_MAKE_ICON(FT_ICON16_INBOX));
    setWindowIcon(ico);
    if (m_number>0)
      setText(QString("%1").arg(m_number));
    else
      setText("");
    setEnabled(true);
  }
  else {
    //    setPixmap(QPixmap());	// empty pixmap
    setEnabled(false);
    setText(tr("New mail !"));
  }
}


//static
void
msg_list_window::apply_conf_all_windows()
{
  std::map<msg_list_window*,int> window_processed;
  std::list<msgs_page*>::iterator page_it;
  for (page_it=msgs_page_list::m_all_pages_list.begin();
       page_it!=msgs_page_list::m_all_pages_list.end();
       ++page_it)
  {
    msg_list_window* w = (*page_it)->m_msgs_window;
    if (window_processed.find(w) == window_processed.end()) {
      window_processed[w]=1;
      w->apply_conf(get_config());
    }
  }
}

void
msg_list_window::apply_conf(app_config& conf)
{
  if (display_vars.m_show_tags != conf.get_bool("show_tags")) {
    toggle_show_tags(display_vars.m_show_tags);
    m_menu_actions[me_Display_Tags]->setChecked(display_vars.m_show_tags);
  }

  if (conf.get_bool("display_threads") != display_vars.m_threaded) {
    toggle_threaded(display_vars.m_threaded);
    m_menu_actions[me_Display_Threaded]->setChecked(display_vars.m_threaded);
  }

  int headers_level = conf.get_number("show_headers_level");
  if (display_vars.m_show_headers_level != headers_level) {
    QAction* action=NULL;
    switch(headers_level) {
    case 0:
      action=m_menu_actions[me_Display_Headers_None];
      break;
    case 1:
    default:
      action=m_menu_actions[me_Display_Headers_Most];
      break;
    case 2:
      action=m_menu_actions[me_Display_Headers_All];
      break;
    case 3:
      action=m_menu_actions[me_Display_Headers_Raw];
      break;
    case 4:
      action=m_menu_actions[me_Display_Headers_RawDec];
      break;
    }
    if (action) {
      show_headers(action);
      action->setChecked(true);
    }
  }

  bool clickable_urls = conf.get_bool("body_clickable_urls");
  if (display_vars.m_clickable_urls != clickable_urls) {
    display_vars.m_clickable_urls = clickable_urls;
    display_body();
  }

  // redisplay each page if the date format has changed
  std::list<msgs_page*>::iterator it1;
  for (it1=m_pages->begin(); it1!=m_pages->end(); ++it1) {
    (*it1)->m_page_qlist->change_display_config(conf);
  }
}

void
msg_list_window::display_body()
{
  if (m_pCurrentItem) {
    m_msgview->display_body(display_vars, 0);
    m_msgview->highlight_terms(m_highlighted_text);
  }
}

void
msg_list_window::msg_zoom_in()
{
  m_msgview->change_zoom(+1);
}

void
msg_list_window::msg_zoom_out()
{
  m_msgview->change_zoom(-1);
}
void
msg_list_window::msg_zoom_zero()
{
  m_msgview->change_zoom(0);
}

const display_prefs&
msg_list_window::get_display_prefs()
{
  return display_vars;
}

void
msg_list_window::incorporate_message(mail_result& r)
{
  m_filter->add_result(r, m_qlist);
  QApplication::processEvents();
}

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

List of all available source files