/*
 * Copyright (C) 2014-2021 CZ.NIC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <cinttypes> /* PRId64 */
#include <cstdlib> /* exit(3) */
#include <QCloseEvent>
#include <QDesktopServices>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPrinter>
#include <QProgressBar>
#include <QSet>
#include <QSettings>
#include <QStackedWidget>
#include <QTableView>
#include <QTimer>
#include <QUrl>

#include "datovka.h"
#include "src/about.h"
#include "src/crypto/crypto_funcs.h"
#include "src/datovka_shared/compat_qt/misc.h" /* Q_FALLTHROUGH */
#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/isds/message_interface.h"
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/pin.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/datovka_shared/settings/prefs_helper.h"
#include "src/datovka_shared/settings/records_management.h"
#include "src/datovka_shared/worker/pool.h"
#include "src/delegates/tags_delegate.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gov_services/gui/dlg_gov_services.h"
#include "src/gui/dlg_about.h"
#include "src/gui/dlg_change_pwd.h"
#include "src/gui/dlg_account_from_db.h"
#include "src/gui/dlg_backup.h"
#include "src/gui/dlg_create_account.h"
#include "src/gui/dlg_download_messages.h"
#include "src/gui/dlg_change_directory.h"
#include "src/gui/dlg_correspondence_overview.h"
#include "src/gui/dlg_download_messages.h"
#include "src/gui/dlg_ds_search.h"
#include "src/gui/dlg_msg_box_informative.h"
#include "src/gui/dlg_msg_box_nonmodal.h"
#include "src/gui/dlg_msg_search.h"
#include "src/gui/dlg_preferences.h"
#include "src/gui/dlg_prefs.h"
#include "src/gui/dlg_proxysets.h"
#include "src/gui/dlg_restore.h"
#include "src/gui/dlg_send_message.h"
#include "src/gui/dlg_signature_detail.h"
#include "src/gui/dlg_view_log.h"
#include "src/gui/dlg_view_zfo.h"
#include "src/gui/dlg_import_zfo.h"
#include "src/gui/dlg_import_zfo_result.h"
#include "src/gui/dlg_timestamp_expir.h"
#include "src/gui/dlg_wait_progress.h"
#include "src/gui/dlg_yes_no_checkbox.h"
#include "src/gui/dlg_tags.h"
#include "src/gui/icon_container.h"
#include "src/gui/isds_login.h"
#include "src/gui/message_operations.h"
#include "src/identifiers/account_id_db.h"
#include "src/io/account_shadow_helper.h"
#include "src/io/dbs.h"
#include "src/io/filesystem.h"
#include "src/io/isds_helper.h"
#include "src/io/isds_login.h"
#include "src/io/isds_sessions.h"
#include "src/io/imports.h"
#include "src/io/message_db_single.h"
#include "src/io/message_db_set_container.h"
#include "src/io/message_db_set.h"
#include "src/io/tag_db.h"
#include "src/io/update_checker.h"
#include "src/model_interaction/account_interaction.h"
#include "src/model_interaction/attachment_interaction.h"
#include "src/records_management/content/automatic_upload_target.h"
#include "src/records_management/content/automatic_upload_target_interaction.h"
#include "src/records_management/gui/dlg_records_management.h"
#include "src/records_management/gui/dlg_records_management_stored.h"
#include "src/records_management/gui/dlg_records_management_upload.h"
#include "src/settings/preferences.h"
#include "src/settings/prefs_specific.h"
#include "src/tags/tag_assignment.h"
#include "src/tags/tag_entry.h"
#include "src/views/table_home_end_filter.h"
#include "src/views/table_key_press_filter.h"
#include "src/views/table_tab_ignore_filter.h"
#include "src/worker/message_emitter.h"
#include "src/worker/task_authenticate_message.h"
#include "src/worker/task_download_message.h"
#include "src/worker/task_download_message_list.h"
#include "src/worker/task_erase_message.h"
#include "src/worker/task_download_owner_info.h"
#include "src/worker/task_download_password_info.h"
#include "src/worker/task_download_user_info.h"
#include "src/worker/task_import_zfo.h"
#include "src/worker/task_split_db.h"
#include "src/worker/task_records_management_upload_account_status.h"
#include "src/worker/task_vacuum_db_set.h"
#include "src/worker/task_verify_message.h"
#include "ui_datovka.h"

#define SW_BANNER 0 /* Stacked widget page with banner. */
#define SW_MESSAGES 1 /* Stacked widget page with message list. */

/*
 * If defined then no message table is going to be generated when clicking
 * on all sent or received messages.
 */
#define DISABLE_ALL_TABLE 1

/*!
 * @brief Returns QModelIndexList containing first column indexes of selected
 *     attachment model rows.
 */
#define currentFrstColAttachmentIndexes() \
	(ui->attachmentList->selectionModel()->selectedRows(0))

/* Used for detecting newest available update. */
static UpdateChecker *updateChecker = Q_NULLPTR;

MainWindow::~MainWindow(void)
{
	saveSettings();
	delete ui;
}

void MainWindow::loadWindowGeometry(const Prefs &prefs)
{
	/* Default geometry. */
	QRect defaultDimensions(Dimensions::windowDimensions(this, 76.0, 48.0));

	/* Stored geometry. */
	int x = defaultDimensions.x();
	int y = defaultDimensions.y();
	int w = defaultDimensions.width();
	int h = defaultDimensions.height();
	bool max = false;
	{
		qint64 val = 0;
		if (prefs.intVal("window.main.position.x", val)) {
			x = val;
		}
		if (prefs.intVal("window.main.position.y", val)) {
			y = val;
		}
		if (prefs.intVal("window.main.position.width", val)) {
			w = val;
		}
		if (prefs.intVal("window.main.position.height", val)) {
			h = val;
		}
	}
	{
		bool val = false;
		if (prefs.boolVal("window.main.position.maximised", val)) {
			max = val;
		}
	}

	this->setGeometry(x, y, w, h);
	if (!max) {
		/*
		 * adjustSize() causes problems in Cinnamon WM in maximised
		 * mode. That's why its called only when not maximised.
		 */
		this->adjustSize();
		/*
		 * adjustSize() shrinks the window in Cinnamon WM. Therefore,
		 * set window geometry again.
		 */
		this->setGeometry(x, y, w, h);
	}
	/* Update the window and force repaint. */
	this->update();
	this->repaint();

	/* Adjust for screen size. */
	this->setGeometry(Dimensions::windowOnScreenDimensions(this));

	if (max) {
		m_geometry = QRect(x, y, w, h);
		this->showMaximized();
		this->update();
		this->repaint();

		/*
		 * It appears that showMaximized() and repaint() doesn't
		 * immediately call the event.  Therefore the event loop is
		 * enforced to process pending events.
		 *
		 * The method needs to be called especially on Windows when
		 * restoring the layout of a maximised window.
		 */
		QCoreApplication::processEvents();
	}

	/* Set minimal size of splitter content and disable collapsing. */
	ui->accountList->setMinimumSize(QSize(100, 100));
	ui->hSplitterAccount->setChildrenCollapsible(false);
	ui->messageList->setMinimumSize(QSize(100, 100));
	ui->vSplitterMessage->setChildrenCollapsible(false);
	ui->messageInfo->setMinimumSize(QSize(100, 100));
	ui->attachmentList->setMinimumSize(QSize(100, 100));
	ui->hSplitterMessageInfo->setChildrenCollapsible(false);

	/* Splitter geometry. */

	/* Set main splitter - hSplitterAccount. */
	w = ui->centralWidgetWindows->width();
	QList<int> sizes = ui->hSplitterAccount->sizes();
	int tmp = 226;
	{
		qint64 val = 0;
		if (prefs.intVal("window.main.pane.account_tree", val)) {
			tmp = val;
		}
	}
	sizes[0] = tmp;
	sizes[1] = w
	    - ui->hSplitterAccount->handleWidth()
	    - sizes[0];
	ui->hSplitterAccount->setSizes(sizes);
	ui->hSplitterAccount->adjustSize();

	/* Set message list splitter - vSplitterMessage. */
	h = ui->centralWidgetWindows->height();
	sizes = ui->vSplitterMessage->sizes();
	sizes[0] = 265;
	{
		qint64 val = 0;
		if (prefs.intVal("window.main.pane.message_list", val)) {
			sizes[0] = val;
		}
	}
	sizes[1] = h -
	    ui->vSplitterMessage->handleWidth()
	    - sizes[0];
	ui->vSplitterMessage->setSizes(sizes);

	/* Set message info splitter - hSplitterMessageInfo. */
	sizes = ui->hSplitterMessageInfo->sizes();
	sizes[0] = 505;
	{
		qint64 val = 0;
		if (prefs.intVal("window.main.pane.message_info", val)) {
			sizes[0] = val;
		}
	}
	sizes[1] = w
	    - tmp
	    - ui->hSplitterAccount->handleWidth()
	    - ui->hSplitterMessageInfo->handleWidth()
	    - sizes[0];
	ui->hSplitterMessageInfo->setSizes(sizes);
}

/*!
 * @brief Ask the user whether to abort all pending tasks.
 *
 * @param[in] parent Parent widget.
 * @param[in] quitApp Set to true if the user should be asked whether to quit
 *                    the application.
 * @return True if pending tasks should be aborted.
 */
static
bool askCancelTasks(QWidget *parent, bool quitApp)
{
	int dlgRet = DlgMsgBox::message(parent, QMessageBox::Question,
	    MainWindow::tr("Pending Tasks"),
	    MainWindow::tr("The application is currently processing some tasks."),
	    quitApp ?
	        MainWindow::tr("Do you want to abort all pending actions and close the application?") :
	        MainWindow::tr("Do you want to abort all pending actions and continue with this operation?"),
	    QString(), QMessageBox::No | QMessageBox::Yes, QMessageBox::No);
	return dlgRet == QMessageBox::Yes;
}

void MainWindow::closeEvent(QCloseEvent *event)
{
	debugFuncCall();

	/*
	 * Check whether any tasks are currently being processed or are
	 * pending. If nothing is being processed finish immediately else ask
	 * the user.
	 */
	if (GlobInstcs::workPoolPtr->working()) {
		if (askCancelTasks(this, true)) {
			GlobInstcs::workPoolPtr->stop();
			GlobInstcs::workPoolPtr->clear();
		} else {
			event->ignore();
		}
	}
}

void MainWindow::moveEvent(QMoveEvent *event)
{
	/* Event precedes actual maximisation. */
	if (!isMaximized()) {
		QPoint oldPos(event->oldPos());
		if (oldPos != event->pos()) {
			m_geometry.setTopLeft(oldPos);
		}
	}
	QMainWindow::moveEvent(event);
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
	/* Event precedes actual maximisation. */
	if (!isMaximized()) {
		QSize oldSize(event->oldSize());
		if (oldSize != event->size()) {
			m_geometry.setSize(oldSize);
		}
	}
	QMainWindow::resizeEvent(event);
}

void MainWindow::showEvent(QShowEvent *event)
{
	debugFuncCall();

	QMainWindow::showEvent(event);

	QRect availRect(Dimensions::availableScreenSize());
	QRect frameRect(frameGeometry());

	if (isMaximized() ||
	    ((frameRect.width() <= availRect.width()) &&
	    (frameRect.height() <= availRect.height()))) {
		/* Window fits to screen. */
		return;
	}

	this->setGeometry(Dimensions::windowDimensions(this, -1.0, -1.0));
}

/*!
 * @brief Set tab order.
 *
 * @param[in,out] ui Main window user interface.
 */
static
void setUpTabOrder(Ui::MainWindow *ui)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	ui->toolBar->setFocusPolicy(Qt::StrongFocus);

	/* Tool bar elements need to have focus enabled. */
	QWidget::setTabOrder(ui->toolBar, ui->accountList);
	QWidget::setTabOrder(ui->accountList, ui->accountTextInfo);
	QWidget::setTabOrder(ui->accountTextInfo, ui->messageList);
	QWidget::setTabOrder(ui->messageList, ui->signatureDetails);
	QWidget::setTabOrder(ui->signatureDetails, ui->messageStateCombo);
	QWidget::setTabOrder(ui->messageStateCombo, ui->downloadComplete);
	QWidget::setTabOrder(ui->downloadComplete, ui->saveAttachments);
	QWidget::setTabOrder(ui->saveAttachments, ui->saveAttachment);
	QWidget::setTabOrder(ui->saveAttachment, ui->openAttachment);
	QWidget::setTabOrder(ui->openAttachment, ui->messageInfo);
	QWidget::setTabOrder(ui->messageInfo, ui->attachmentList);
}

/*!
 * @brief Sets action icons.
 *
 * @param[in,out] ui Main window user interface.
 */
static
void setUpMenuActionIcons(Ui::MainWindow *ui)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	/*
	 * Don't remove the isEnabled() calls as they serve as placeholders
	 * for existing actions.
	 *
	 * You may replace them with a setIcon() if you wan to specify an icon.
	 */

	/* File menu. */
	ui->actionSynchroniseAllAccounts->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_ALL_ACCOUNTS_SYNC));
	    /* Separator. */
	ui->actionAddAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_PLUS));
	ui->actionRemoveAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELETE));
	ui->actionAccountSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GEAR));
	    /* Separator. */
	ui->actionImportAccountFromDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_AT));
	ui->actionBackUp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE));
	ui->actionRestore->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE));
	    /* Separator. */
	ui->actionProxySettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GLOBE));
	    /* Separator. */
	{
		const QIcon ico(
		    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE));
		ui->actionRecordsManagementSettings->setIcon(ico);
		ui->actionUpdateRecordsManagementInformation->setIcon(ico);
	}
	    /* Separator. */
	ui->actionPreferences->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GEAR));
	/* actionQuit -- connected in ui file. */
	ui->actionQuit->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELETE));

	/* Data box menu. */
	ui->actionSynchroniseAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_ACCOUNT_SYNC));
	ui->actionSynchroniseAccountReceived->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_ACCOUNT_SYNC_RECEIVED));
	ui->actionSynchroniseAccountSent->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_ACCOUNT_SYNC_SENT));
	ui->actionSendMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE));
	    /* Separator. */
	ui->actionSendEGovRequest->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_UPLOAD));
	    /* Separator. */
	         /* Mark All received as menu. */
	ui->actionMarkAllReceivedRead->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL));
	ui->actionMarkAllReceivedUnread->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GREEN_BALL));
	        /* Separator. */
	ui->actionMarkAllReceivedUnsettled->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_RED_BALL));
	ui->actionMarkAllReceivedInProgress->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_YELLOW_BALL));
	ui->actionMarkAllReceivedSettled->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL));
	    /* Separator. */
	ui->actionChangePassword->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_USER));
	    /* Separator. */
	ui->actionAccountProperties->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_LETTER));
	    /* Separator. */
	ui->actionMoveAccountUp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_UP));
	ui->actionMoveAccountDown->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DOWN));
	    /* Separator. */
	ui->actionChangeDataDirectory->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_FOLDER));
	    /* Separator. */
	{
		const QIcon ico(
		    IconContainer::construcIcon(IconContainer::ICON_CLIPBOARD));
		ui->actionImportMessagesFromDatabase->setIcon(ico);
		ui->actionImportZfoIntoDatabase->setIcon(ico);
	}
	    /* Separator. */
	ui->actionVacuumMessageDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_TRASH));
	ui->actionSplitDatabaseYears->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_STATISTICS));

	/* Message menu. */
	ui->actionDownloadMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_DOWNLOAD));
	ui->actionReply->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_REPLY));
	ui->actionForward->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE));
	ui->actionUseAsTemplate->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE));
	    /* Separator. */
	ui->actionSignatureDetail->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_SIGNATURE));
	ui->actionVerifyMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_VERIFY));
	    /* Separator. */
	ui->actionOpenMessageExternal->isEnabled();
	ui->actionOpenAcceptanceInfoExternal->isEnabled();
	    /* Separator. */
	ui->actionSendToRecordsManagement->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE));
	    /* Separator. */
	ui->actionExportMessageZfo->isEnabled();
	ui->actionExportAcceptanceInfoZfo->isEnabled();
	ui->actionExportAcceptanceInfoPdf->isEnabled();
	ui->actionExportEnvelopePdf->isEnabled();
	ui->actionExportEnvelopePdfAndAttachments->isEnabled();
	    /* Separator. */
	ui->actionEmailZfos->isEnabled();
	ui->actionEmailAllAttachments->isEnabled();
	    /* Separator. */
	ui->actionSaveAllAttachments->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SAVE_ALL));
	ui->actionSaveSelectedAttachments->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SAVE));
	ui->actionOpenAttachment->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_FOLDER));
	    /* Separator. */
	ui->actionDeleteMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELETE));

	/* Tools menu. */
	ui->actionFindDataBox->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SEARCH));
	    /* Separator. */
	ui->actionAuthenticateMessageFile->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE_VERIFY));
	ui->actionViewMessageFile->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MONITOR));
	ui->actionExportCorrespondenceOverview->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SAVE));
	ui->actionCheckMessageTimestampExpiration->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SHIELD));
	    /* Separator. */
	ui->actionMessageSearch->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_SEARCH));
	    /* Separator. */
	ui->actionTagSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_LABEL));

	/* Help. */
	ui->actionAboutApplication->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_INFO));
	ui->actionHomepage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_HOME));
	ui->actionHelp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_HELP));

	/* Actions that are not shown in the top menu. */
	ui->actionEmail_selected_attachments->isEnabled();
}

/*!
 * @brief Add action to a tool bar.
 *
 * @param[in,out] toolBar Tool bar.
 * @param[in]     action Action to be added.
 * @param[in]     focusPolicy Focus policy.
 * @return True if action has been added and policy set.
 */
static
bool addToolBarAction(QToolBar *toolBar, QAction *action,
    enum Qt::FocusPolicy focusPolicy = Qt::StrongFocus)
{
	if (Q_UNLIKELY((toolBar == Q_NULLPTR) || (action == Q_NULLPTR))) {
		Q_ASSERT(0);
		return false;
	}

	/* TODO -- Abort if action already held. */

	toolBar->addAction(action);

	if (focusPolicy == Qt::NoFocus) {
		return true;
	}

	QWidget *w = toolBar->widgetForAction(action);
	if (Q_UNLIKELY(w == Q_NULLPTR)) {
		return false;
	}
	w->setFocusPolicy(focusPolicy);
	return true;
}

/*!
 * @brief Adds actions to to tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @return Pointer to created filter line.
 */
static
QLineEdit *setUpTopToolBar(Ui::MainWindow *ui)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	/* Add actions to the top tool bar. */
	addToolBarAction(ui->toolBar, ui->actionSynchroniseAllAccounts);
	addToolBarAction(ui->toolBar, ui->actionSynchroniseAccount);
	ui->toolBar->addSeparator();
	addToolBarAction(ui->toolBar, ui->actionSendMessage);
	addToolBarAction(ui->toolBar, ui->actionReply);
	addToolBarAction(ui->toolBar, ui->actionVerifyMessage);
	ui->toolBar->addSeparator();
	addToolBarAction(ui->toolBar, ui->actionMessageSearch);
	ui->toolBar->addSeparator();
	addToolBarAction(ui->toolBar, ui->actionAccountProperties);
	addToolBarAction(ui->toolBar, ui->actionPreferences);

	{
		QWidget *spacer = new (::std::nothrow) QWidget;
		if (spacer != Q_NULLPTR) {
			spacer->setSizePolicy(QSizePolicy::Expanding,
			    QSizePolicy::Expanding);
			ui->toolBar->addWidget(spacer);
		}
	}

	{
		QLabel *searchLabel = new (::std::nothrow) QLabel;
		if (searchLabel != Q_NULLPTR) {
			searchLabel->setText(MainWindow::tr("Search: "));
			ui->toolBar->addWidget(searchLabel);
		}
	}

	/* Message filter field. */
	QLineEdit *filterLine = new (::std::nothrow) QLineEdit;
	if (filterLine != Q_NULLPTR) {
		filterLine->setFixedWidth(200);
		filterLine->setToolTip(MainWindow::tr("Enter sought expression"));
		filterLine->setClearButtonEnabled(true);
		ui->toolBar->addWidget(filterLine);
	}

	return filterLine;
}

/*!
 * @brief Generate application banner.
 *
 * @param[in] version Version string.
 * @return Brief HTML application description.
 */
static
QString appBanner(const QString &version)
{
	QString html = "<br><center>";
	html += "<h2>" +
	    MainWindow::tr("Datovka - Free client for data boxes") + "</h2>";
#ifdef PORTABLE_APPLICATION
	html += "<h3>" + MainWindow::tr("Portable version") + "</h3>";
#endif /* PORTABLE_APPLICATION */
	html += strongAccountInfoLine(MainWindow::tr("Version"), version);
	html += QString("<br><img src=") + ICON_128x128_PATH + "logo.png />";
	html += "<h3>" + MainWindow::tr("Powered by") + "</h3>";
	html += QString("<br><img src=") + ICON_128x128_PATH + "cznic.png />";
	html += "</center>";
	return html;
}

void MainWindow::setUpUi(void)
{
	ui->setupUi(this);
	/*
	 * Setting menuMarkAllReceivedAs menu role because of macOS.
	 * It cannot be set in the UI file.
	 */
	ui->menuMarkAllReceivedAs->menuAction()->setMenuRole(QAction::NoRole);
	setUpTabOrder(ui);

	setWindowIcon(IconContainer::construcIcon(IconContainer::ICON_DATOVKA));

	/* Set default line height for table views/widgets. */
	ui->accountList->setNarrowedLineHeight();
	ui->messageList->setNarrowedLineHeight();
	ui->attachmentList->setNarrowedLineHeight();

	/* Header content alignment. */
	ui->accountList->header()->setDefaultAlignment(Qt::AlignLeft);
	ui->messageList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
	ui->attachmentList->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);

	/* Window title. */
	setWindowTitle(tr("Datovka - Free client for data boxes"));
#ifdef PORTABLE_APPLICATION
	setWindowTitle(windowTitle() + " - " + tr("Portable version"));
#endif /* PORTABLE_APPLICATION */

	setUpMenuActionIcons(ui);

	mui_filterLine = setUpTopToolBar(ui);
	if (mui_filterLine != Q_NULLPTR) {
		connect(mui_filterLine, SIGNAL(textChanged(QString)),
		    this, SLOT(filterMessages(QString)));
	}

	/* Create status bar. */
	mui_statusBar = new (::std::nothrow) QStatusBar;
	if (mui_statusBar != Q_NULLPTR) {
		mui_statusBar->setSizeGripEnabled(false);
		ui->statusBar->addWidget(mui_statusBar, 1);
		showStatusTextWithTimeout(tr("Welcome..."));
	}

	/* Create status bar label showing database mode (memory/disk). */
	mui_statusDbMode = new (::std::nothrow) QLabel;
	if (mui_statusDbMode != Q_NULLPTR) {
		mui_statusDbMode->setText(tr("Storage: disk | disk"));
		mui_statusDbMode->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
		ui->statusBar->addWidget(mui_statusDbMode, 0);
	}

	/* Create status bar online/offline label. */
	mui_statusOnlineLabel = new (::std::nothrow) QLabel;
	if (mui_statusOnlineLabel != Q_NULLPTR) {
		mui_statusOnlineLabel->setText(tr("Mode: offline"));
		mui_statusOnlineLabel->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
		ui->statusBar->addWidget(mui_statusOnlineLabel, 0);
	}

	/* Create progress bar object and set the default value. */
	mui_statusProgressBar = new (::std::nothrow) QProgressBar;
	if (mui_statusProgressBar != Q_NULLPTR) {
		mui_statusProgressBar->setAlignment(Qt::AlignRight);
		mui_statusProgressBar->setMinimumWidth(100);
		mui_statusProgressBar->setMaximumWidth(200);
		mui_statusProgressBar->setTextVisible(true);
		mui_statusProgressBar->setRange(0, 100);
		mui_statusProgressBar->setValue(0);
		clearProgressBar();
		ui->statusBar->addWidget(mui_statusProgressBar, 1);
	}

	/* Message state combo box. */
	ui->messageStateCombo->setInsertPolicy(QComboBox::InsertAtBottom);
	ui->messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_RED_BALL),
	    tr("Unsettled"));
	ui->messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_YELLOW_BALL),
	    tr("In Progress"));
	ui->messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL),
	    tr("Settled"));

	/* Override tool button sizes. */
	ui->signatureDetails->setVerticalSizeHintOrigin(ui->messageStateCombo);
	ui->downloadComplete->setVerticalSizeHintOrigin(ui->messageStateCombo);
	ui->saveAttachments->setVerticalSizeHintOrigin(ui->messageStateCombo);
	ui->saveAttachment->setVerticalSizeHintOrigin(ui->messageStateCombo);
	ui->openAttachment->setVerticalSizeHintOrigin(ui->messageStateCombo);

	/* Show banner. */
	ui->messageStackedWidget->setCurrentIndex(SW_BANNER);
	ui->accountTextInfo->setHtml(
	    appBanner(QCoreApplication::applicationVersion()));
	ui->accountTextInfo->setReadOnly(true);
}

/*!
 * @brief Load collapse info of account items from preferences.
 *
 * @param[in]     prefs Preferences to load data from.
 * @param[in,out] accountList Account list.
 */
static
void loadAccountCollapseInfo(const Prefs &prefs, QTreeView &accountList)
{
	debugFuncCall();

	AccountModel *accountModel =
	    qobject_cast<AccountModel *>(accountList.model());
	if (Q_UNLIKELY(accountModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	foreach (const QString &name,
	         prefs.names(QRegularExpression("^window\\.main\\.account_tree\\.account\\..*\\.collapsed.*$"))) {
		QStringList nameSplit = name.split(".");
		QString username;
		bool testing = false;
		if (Q_UNLIKELY(!PrefsHelper::accountIdentifierData(
		                   nameSplit[4], username, testing))) {
			continue;
		}

		QModelIndex index = accountModel->topAcntIndex(
		    AcntId(username, testing));
		if (Q_UNLIKELY(!index.isValid())) {
			continue;
		}

		bool collapsed = false;
		if (Q_UNLIKELY(!prefs.boolVal(name, collapsed))) {
			continue;
		}

		switch (nameSplit.size()) {
		case 6: /* Account name item. */
			accountList.setExpanded(index, !collapsed);
			break;
		case 7: /* All item. */
			index = accountModel->index(nameSplit[6].toInt(), 0, index);
			accountList.setExpanded(index, !collapsed);
			break;
		case 8:
			index = accountModel->index(nameSplit[6].toInt(), 0, index);
			index = accountModel->index(nameSplit[7].toInt(), 0, index);
			accountList.setExpanded(index, !collapsed);
			break;
		default:
			break;
		}
	}
}

/*!
 * @brief Save collapse info of account items to preferences.
 *
 * @param[in]     accountList Account list to take collapse information from.
 * @param[in,out] prefs Preferences to save data to.
 */
static
void saveAccountCollapseInfo(const QTreeView &accountList, Prefs &prefs)
{
	debugFuncCall();

	const AccountModel *accountModel =
	    qobject_cast<AccountModel *>(accountList.model());
	if (Q_UNLIKELY(accountModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex index;
	for (int i = 0; i < accountModel->rowCount(); ++i) {
		index = accountModel->index(i, 0);
		const AcntId acntId = accountModel->acntId(index);
		const QString accountId = PrefsHelper::accountIdentifier(
		    acntId.username(), acntId.testing());
		bool topLevelExp = accountList.isExpanded(index);
		prefs.setBoolVal(
		    "window.main.account_tree.account." + accountId + ".collapsed",
		    !topLevelExp);
		if (topLevelExp) {
			/* Here index points to nodeAccountTop. */
			QModelIndex allIndex = accountModel->index(2, 0, index);
			bool allExp = accountList.isExpanded(allIndex);
			prefs.setBoolVal(
			    "window.main.account_tree.account." + accountId + ".collapsed.2",
			    !allExp);
			if (allExp) {
				index = accountModel->index(0, 0, allIndex);
				prefs.setBoolVal(
				    "window.main.account_tree.account." + accountId + ".collapsed.2.0",
				    !accountList.isExpanded(index));
				index = accountModel->index(1, 0, allIndex);
				prefs.setBoolVal(
				    "window.main.account_tree.account." + accountId + ".collapsed.2.1",
				    !accountList.isExpanded(index));
			}
		}
	}
}

void MainWindow::loadSettings(void)
{
	debugFuncCall();

	QSettings settings(GlobInstcs::iniPrefsPtr->loadConfPath(),
	    QSettings::IniFormat);
	settings.setIniCodec("UTF-8");

	/* Received Sent messages Column widths */
	loadMsgListPrefs(*GlobInstcs::prefsPtr);

	/* No global preferences are stored into INI file. */

	/* Proxy settings. */
	GlobInstcs::proxSetPtr->loadFromSettings(*GlobInstcs::prefsPtr);

	/* PIN should already be set because main window is running. */

	/* Records management settings. */
	GlobInstcs::recMgmtSetPtr->loadFromSettings(settings);
	GlobInstcs::recMgmtSetPtr->decryptToken(GlobInstcs::pinSetPtr->_pinVal);

	/* Accounts. */
	GlobInstcs::acntMapPtr->loadFromSettings(GlobInstcs::iniPrefsPtr->confDir(),
	    settings);
	m_accountModel.loadContent(GlobInstcs::acntMapPtr);
	GlobInstcs::acntMapPtr->decryptAllPwds(GlobInstcs::pinSetPtr->_pinVal);
	/* Shadow accounts. */
	GlobInstcs::shadowAcntsPtr->loadFromSettings(
	    GlobInstcs::iniPrefsPtr->confDir(), settings);
	GlobInstcs::shadowAcntsPtr->decryptAllPwds(GlobInstcs::pinSetPtr->_pinVal);

	/* Select last used account. */
	/* It should be done after all necessary signals an slots are connected. */
	//setDefaultAccount(*GlobInstcs::prefsPtr);

	/* Scan databases. */
	regenerateAllAccountModelYears();

	/* Load collapse info of account items from settings */
	loadAccountCollapseInfo(*GlobInstcs::prefsPtr, *(ui->accountList));
}

/*!
 * @brief Store configuration file version.
 *
 * @param[in,out] settings Settings to be updated.
 */
static
void saveAppIdConfigFormat(QSettings &settings)
{
	settings.beginGroup("version");
	settings.setValue("config_format", 1);
	settings.endGroup();
}

void MainWindow::saveSettings(void) const
{
	debugFuncCall();

	/*
	 * TODO -- Target file name differs from source for testing purposes.
	 */
	QSettings settings(GlobInstcs::iniPrefsPtr->saveConfPath(),
	    QSettings::IniFormat);
	settings.setIniCodec("UTF-8");

	settings.clear();

	/* Store application ID and config format. */
	saveAppIdConfigFormat(settings);

	/* PIN settings. */
	GlobInstcs::pinSetPtr->saveToSettings(settings);

	/* Accounts. */
	GlobInstcs::acntMapPtr->saveToSettings(GlobInstcs::pinSetPtr->_pinVal,
	    GlobInstcs::iniPrefsPtr->confDir(), settings);
	GlobInstcs::shadowAcntsPtr->saveToSettings(GlobInstcs::pinSetPtr->_pinVal,
	    GlobInstcs::iniPrefsPtr->confDir(), settings);

	/* Store last-used account. */
	saveAccountIndex(*GlobInstcs::prefsPtr);

	/* TODO */
	saveMsgListPrefs(*GlobInstcs::prefsPtr);

	/* Window geometry. */
	saveWindowGeometry(*GlobInstcs::prefsPtr);

	/* Store account collapses */
	saveAccountCollapseInfo(*(ui->accountList), *GlobInstcs::prefsPtr);

	/* Proxy settings. */
	GlobInstcs::proxSetPtr->saveToSettings(*GlobInstcs::prefsPtr);

	/* Records management settings. */
	GlobInstcs::recMgmtSetPtr->saveToSettings(
	    GlobInstcs::pinSetPtr->_pinVal, settings);

	/* No global preferences are stored into INI file. */

	settings.sync();

	/* Remove '"' symbols from passwords in dsgui.conf. */
	confFileRemovePwdQuotes(GlobInstcs::iniPrefsPtr->saveConfPath());
}

#define DFLT_COL_WIDTH 200
#define COL_WIDTH_COUNT 3
#define DFLT_COL_SORT 0

void MainWindow::loadMsgListPrefs(const Prefs &prefs)
{
	qint64 tmp = 0;

	m_colWidth[0] = DFLT_COL_WIDTH;
	if (prefs.intVal("window.main.message_list.column.annotation.width", tmp)) {
		m_colWidth[0] = tmp;
	}
	m_colWidth[1] = DFLT_COL_WIDTH;
	if (prefs.intVal("window.main.message_list.column.sender.width", tmp)) {
		m_colWidth[1] = tmp;
	}
	m_colWidth[2] = DFLT_COL_WIDTH;
	if (prefs.intVal("window.main.message_list.column.recipient.width", tmp)) {
		m_colWidth[2] = tmp;
	}

	m_sortCol = DFLT_COL_SORT;
	if (prefs.intVal("window.main.message_list.sort.column_number", tmp)) {
		m_sortCol = tmp;
	}
	/* Sort column saturation from python-based application. */
	if (m_sortCol > 5) {
		m_sortCol = DFLT_COL_SORT;
	}
	m_sort_order.clear();
	if (prefs.intVal("window.main.message_list.sort.order", tmp)) {
		if (tmp == 0) {
			m_sort_order = "SORT_ASCENDING";
		} else if (tmp == 1) {
			m_sort_order = "SORT_DESCENDING";
		}
	}
}

void MainWindow::saveMsgListPrefs(Prefs &prefs) const
{
	prefs.setIntVal("window.main.message_list.column.annotation.width",
	    m_colWidth[0]);
	prefs.setIntVal("window.main.message_list.column.sender.width",
	    m_colWidth[1]);
	prefs.setIntVal("window.main.message_list.column.recipient.width",
	    m_colWidth[2]);

	prefs.setIntVal("window.main.message_list.sort.column_number",
	    m_sortCol);
	if (m_sort_order == "SORT_ASCENDING") {
		prefs.setIntVal("window.main.message_list.sort.order", 0);
	} else if (m_sort_order == "SORT_DESCENDING") {
		prefs.setIntVal("window.main.message_list.sort.order", 1);
	}
}

void MainWindow::topMenuConnectActions(void) const
{
	debugFuncCall();

	/*
	 * Actions that cannot be automatically connected
	 * via QMetaObject::connectSlotsByName because of mismatching names.
	 */

	/* File menu. */
	connect(ui->actionSynchroniseAllAccounts, SIGNAL(triggered()),
	    this, SLOT(synchroniseAllAccounts()));
	    /* Separator. */
	connect(ui->actionAddAccount, SIGNAL(triggered()),
	    this, SLOT(viewAddAccountDlg()));
	connect(ui->actionRemoveAccount, SIGNAL(triggered()),
	    this, SLOT(viewRemoveAccountDlg()));
	connect(ui->actionAccountSettings, SIGNAL(triggered()),
	    this, SLOT(viewManageAccountsDlg()));
	    /* Separator. */
	connect(ui->actionImportAccountFromDatabase, SIGNAL(triggered()),
	    this, SLOT(viewImportDatabaseDlg()));
	connect(ui->actionBackUp, SIGNAL(triggered()),
	    this, SLOT(viewDataBackupDlg()));
	connect(ui->actionRestore, SIGNAL(triggered()),
	    this, SLOT(viewDataRestoreDlg()));
	    /* Separator. */
	connect(ui->actionProxySettings, SIGNAL(triggered()),
	    this, SLOT(viewProxySettingsDlg()));
	    /* Separator */
	connect(ui->actionRecordsManagementSettings, SIGNAL(triggered()),
	    this, SLOT(viewRecordsManagementDlg()));
	connect(ui->actionUpdateRecordsManagementInformation, SIGNAL(triggered()),
	    this, SLOT(getStoredMsgInfoFromRecordsManagement()));
	    /* Separator. */
	connect(ui->actionPreferences, SIGNAL(triggered()),
	    this, SLOT(viewPreferencesDlg()));
	/* actionQuit -- connected in ui file. */

	/* Data box menu. */
	connect(ui->actionSynchroniseAccount, SIGNAL(triggered()),
	    this, SLOT(synchroniseSelectedAccount()));
	connect(ui->actionSynchroniseAccountReceived, SIGNAL(triggered()),
	    this, SLOT(synchroniseSelectedAccountReceived()));
	connect(ui->actionSynchroniseAccountSent, SIGNAL(triggered()),
	    this, SLOT(synchroniseSelectedAccountSent()));
	connect(ui->actionSendMessage, SIGNAL(triggered()),
	    this, SLOT(viewCreateMessageDlg()));
	    /* Separator. */
	connect(ui->actionSendEGovRequest, SIGNAL(triggered()),
	    this, SLOT(viewCreateGovRequestDlg()));
	    /* Separator. */
	        /* Mark All received as menu. */
	connect(ui->actionMarkAllReceivedRead, SIGNAL(triggered()),
	    this, SLOT(accountMarkReceivedRead()));
	connect(ui->actionMarkAllReceivedUnread, SIGNAL(triggered()),
	    this, SLOT(accountMarkReceivedUnread()));
	        /* Separator. */
	connect(ui->actionMarkAllReceivedUnsettled, SIGNAL(triggered()),
	    this, SLOT(accountMarkReceivedUnsettled()));
	connect(ui->actionMarkAllReceivedInProgress, SIGNAL(triggered()),
	    this, SLOT(accountMarkReceivedInProgress()));
	connect(ui->actionMarkAllReceivedSettled, SIGNAL(triggered()),
	    this, SLOT(accountMarkReceivedSettled()));
	    /* Separator. */
	connect(ui->actionChangePassword, SIGNAL(triggered()),
	    this, SLOT(viewChangePasswordDlg()));
	    /* Separator. */
	connect(ui->actionAccountProperties, SIGNAL(triggered()),
	    this, SLOT(viewManageSelectedAccountDlg()));
	    /* Separator. */
	connect(ui->actionMoveAccountUp, SIGNAL(triggered()),
	    this, SLOT(moveSelectedAccountUp()));
	connect(ui->actionMoveAccountDown, SIGNAL(triggered()),
	    this, SLOT(moveSelectedAccountDown()));
	    /* Separator. */
	connect(ui->actionChangeDataDirectory, SIGNAL(triggered()),
	    this, SLOT(viewChangeDataDirectoryDlg()));
	    /* Separator. */
	connect(ui->actionImportMessagesFromDatabase, SIGNAL(triggered()),
	    this, SLOT(viewImportMessagesFromDatabaseDlg()));
	connect(ui->actionImportZfoIntoDatabase, SIGNAL(triggered()),
	    this, SLOT(viewImportMessagesFromZfosDlg()));
	    /* Separator. */
	connect(ui->actionVacuumMessageDatabase, SIGNAL(triggered()),
	    this, SLOT(viewVacuumMessageDatabaseDlg()));
	connect(ui->actionSplitDatabaseYears, SIGNAL(triggered()),
	    this, SLOT(viewSplitMessageDatabaseByYearsDlg()));

	/* Message menu. */
	connect(ui->actionDownloadMessage, SIGNAL(triggered()),
	    this, SLOT(downloadSelectedMessageAttachments()));
	connect(ui->actionReply, SIGNAL(triggered()),
	    this, SLOT(viewReplyMessageDlg()));
	connect(ui->actionForward, SIGNAL(triggered()),
	    this, SLOT(viewForwardMessageDlg()));
	connect(ui->actionUseAsTemplate, SIGNAL(triggered()),
	    this, SLOT(viewCreateMessageFromMessageDlg()));
	    /* Separator. */
	connect(ui->actionSignatureDetail, SIGNAL(triggered()),
	    this, SLOT(viewSignatureDetailsDlg()));
	connect(ui->actionVerifyMessage, SIGNAL(triggered()),
	    this, SLOT(viewVerifySelectedMessageDlg()));
	    /* Separator. */
	connect(ui->actionOpenMessageExternal, SIGNAL(triggered()),
	    this, SLOT(openSelectedMessageExternally()));
	connect(ui->actionOpenAcceptanceInfoExternal, SIGNAL(triggered()),
	    this, SLOT(openAcceptanceInfoExternally()));
	    /* Separator. */
	connect(ui->actionSendToRecordsManagement, SIGNAL(triggered()),
	    this, SLOT(viewSendSelectedMessageToRecordsManagementDlg()));
	    /* Separator. */
	connect(ui->actionExportMessageZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedMessageZfos()));
	connect(ui->actionExportAcceptanceInfoZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoZfos()));
	connect(ui->actionExportAcceptanceInfoPdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoPdfs()));
	connect(ui->actionExportEnvelopePdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopePdfs()));
	connect(ui->actionExportEnvelopePdfAndAttachments, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopeAttachments()));
	    /* Separator. */
	connect(ui->actionEmailZfos, SIGNAL(triggered()),
	    this, SLOT(createEmailWithZfos()));
	connect(ui->actionEmailAllAttachments, SIGNAL(triggered()),
	    this, SLOT(createEmailWithAllAttachments()));
	    /* Separator. */
	connect(ui->actionSaveAllAttachments, SIGNAL(triggered()),
	    this, SLOT(viewSaveAllAttachmentsDlg()));
	connect(ui->actionSaveSelectedAttachments, SIGNAL(triggered()),
	    this, SLOT(viewSaveSelectedAttachmentsDlgs()));
	connect(ui->actionOpenAttachment, SIGNAL(triggered()),
	    this, SLOT(openSelectedAttachment()));
	    /* Separator. */
	connect(ui->actionDeleteMessage, SIGNAL(triggered()),
	    this, SLOT(deleteSelectedMessages()));

	/* Tools menu. */
	connect(ui->actionFindDataBox, SIGNAL(triggered()),
	    this, SLOT(viewFindDataBoxDlg()));
	    /* Separator. */
	connect(ui->actionAuthenticateMessageFile, SIGNAL(triggered()),
	    this, SLOT(viewAuthenticateMessageDlg()));
	connect(ui->actionViewMessageFile, SIGNAL(triggered()),
	    this, SLOT(viewMessageFromZfoDlg()));
	connect(ui->actionExportCorrespondenceOverview, SIGNAL(triggered()),
	    this, SLOT(viewExportCorrespondenceOverviewDlg()));
	connect(ui->actionCheckMessageTimestampExpiration, SIGNAL(triggered()),
	    this, SLOT(viewTimestampExpirationDlg()));
	    /* Separator. */
	connect(ui->actionMessageSearch, SIGNAL(triggered()),
	    this, SLOT(viewMessageSearchDlg()));
	    /* Separator. */
	connect(ui->actionTagSettings, SIGNAL(triggered()),
	    this, SLOT(viewTagDlg()));
	    /* Separator. */
	connect(ui->actionViewLog, SIGNAL(triggered()),
	    this, SLOT(viewLogDlg()));
	connect(ui->actionViewAllSettings, SIGNAL(triggered()),
	    this, SLOT(viewAllSettingsDlg()));

	/* Help. */
	connect(ui->actionAboutApplication, SIGNAL(triggered()),
	    this, SLOT(viewAboutAppDlg()));
	connect(ui->actionHomepage, SIGNAL(triggered()),
	    this, SLOT(openHomepageUrl()));
	connect(ui->actionHelp, SIGNAL(triggered()),
	    this, SLOT(openHelpUrl()));

	/* Actions that are not shown in the top menu. */
	connect(ui->actionEmail_selected_attachments, SIGNAL(triggered()),
	    this, SLOT(sendAttachmentsEmail()));
}

void MainWindow::messageBarAssignActions(void)
{
	debugFuncCall();

	/*
	 * Actions that cannot be automatically connected
	 * via QMetaObject::connectSlotsByName because of mismatching names.
	 */

	/* Downloading attachments also triggers signature verification. */
	ui->signatureDetails->setDefaultAction(ui->actionSignatureDetail);

	/* Sets message processing state. */
	connect(ui->messageStateCombo, SIGNAL(activated(int)),
	    this, SLOT(msgSetSelectedMessageProcessState(int)));

	/* Message/attachment related buttons. */
	ui->downloadComplete->setDefaultAction(ui->actionDownloadMessage);
	ui->saveAttachments->setDefaultAction(ui->actionSaveAllAttachments);
	ui->saveAttachment->setDefaultAction(ui->actionSaveSelectedAttachments);
	ui->openAttachment->setDefaultAction(ui->actionOpenAttachment);
}

void MainWindow::databaseConnectActions(void)
{
	connect(GlobInstcs::msgDbsPtr, SIGNAL(changedLocallyRead(AcntId, QList<MsgId>, bool)),
	    this, SLOT(watchChangedLocallyRead(AcntId, QList<MsgId>, bool)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(changedProcessState(AcntId, QList<MsgId>, enum MessageDb::MessageProcessState)),
	    this, SLOT(watchChangedProcessState(AcntId, QList<MsgId>, enum MessageDb::MessageProcessState)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageInserted(AcntId, MsgId, int)),
	    this, SLOT(watchMessageInserted(AcntId, MsgId, int)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(messageDataDeleted(AcntId, MsgId)),
	    this, SLOT(watchMessageDataDeleted(AcntId, MsgId)));

	connect(GlobInstcs::tagDbPtr, SIGNAL(tagUpdated(QList<TagEntry>)),
	    this, SLOT(watchTagUpdated(QList<TagEntry>)));
	connect(GlobInstcs::tagDbPtr, SIGNAL(tagDeleted(QList<int>)),
	    this, SLOT(watchTagDeleted(QList<int>)));
	connect(GlobInstcs::tagDbPtr, SIGNAL(tagAssignmentChanged(QList<TagAssignment>)),
	    this, SLOT(watchTagAssignmentChanged(QList<TagAssignment>)));
}

void MainWindow::synchroniseAllAccounts(void)
{
	debugSlotCall();

	showStatusTextPermanently(
	    tr("Synchronise all accounts with ISDS server."));

	synchroniseAccounts(false);
}

void MainWindow::viewAddAccountDlg(void)
{
	debugSlotCall();

	showStatusTextWithTimeout(tr("Add a new account."));

	AcntData newAcntSettings;
	if (!DlgCreateAccount::modify(newAcntSettings,
	        DlgCreateAccount::ACT_ADDNEW,
	        ui->actionSynchroniseAllAccounts->text(), this)) {
		return;
	}

	getAccountUserDataboxInfo(newAcntSettings);

	if (m_accountModel.rowCount() > 0) {
		updateActionActivation();
		saveSettings();
	}
}

void MainWindow::viewRemoveAccountDlg(void)
{
	debugSlotCall();

	removeAccount(m_selectionStatus.mwStatus().acntId());
}

/*!
 * @brief Timer control action.
 */
enum TimerAction {
	TA_ENFORCE_SETUP = 0, /*!< Enforce fresh settings an start afresh if enabled. */
	TA_PAUSE = 1, /*!< Pause (i.e. store remaining time and stop) the timer. */
	TA_RUN = 2, /*!< Start (i.e. restore remaining time or run afresh) the timer. */
	TA_STOP = 4 /*!< Stop the timer. */
};

/*!
 * @brief Control a timer.
 *
 * @param[in,out] timer Timer to be managed.
 * @param[in]     action Action to be performed.
 * @param[in,out] msecsRemaining Remaining time value to be red or written.
 * @param[in]     setUp Function callback used to set the timer afresh.
 */
static
void manageTimer(QTimer &timer, enum TimerAction action, int &msecsRemaining,
    void(*setUp)(QTimer &))
{
	timer.setSingleShot(true);

	switch (action) {
	case TA_ENFORCE_SETUP:
		setUp(timer);
		break;
	case TA_PAUSE:
		/* Store time and stop timer. */
		msecsRemaining = timer.remainingTime();
		timer.stop();
		break;
	case TA_RUN:
		if (msecsRemaining > 0) {
			timer.start(msecsRemaining);
		} else {
			setUp(timer);
		}
		break;
	case TA_STOP:
		msecsRemaining = -1;
		timer.stop();
		break;
	default:
		Q_ASSERT(0);
		break;
	}
}

/*!
 * @brief Set background download timer according to settings.
 *
 * @param[in,out] timer Timer to be managed.
 */
static
void setUpBackgroundSync(QTimer &timer)
{
	if (PrefsSpecific::backgroundSync(*GlobInstcs::prefsPtr)) {
		int minutes = PrefsSpecific::backgroundSyncTimerMinutes(
		    *GlobInstcs::prefsPtr);
		int msecs = 0;
		if (minutes > 4) {
			msecs = minutes * 60000;
		} else {
			msecs = TIMER_DEFAULT_TIMEOUT_MS;
		}
		timer.start(msecs);
		logInfoNL("Background download timer set to %d minutes and started.", msecs / 60000);
	} else {
		timer.stop();
	}
}

/*!
 * @brief Control background download timer.
 *
 * @note Keeps a remaining time value.
 *
 * @param[in,out] timer Timer to be managed.
 * @param[in]     action Action to be performed.
 */
static
void manageBackgroundTimer(QTimer &timer, enum TimerAction action)
{
	static int msecsRemaining = 0;
	manageTimer(timer, action, msecsRemaining, setUpBackgroundSync);
}

/*!
 * @brief Set shadow download timer according to settings.
 *
 * @param[in,out] timer Timer to be managed.
 */
static
void setUpShadowSync(QTimer &timer)
{
	int minutes = PrefsSpecific::shadowSyncTimerMinutes(*GlobInstcs::prefsPtr);

	if (minutes > 0) {
		int msecs = 0;
		if (minutes > 4) {
			msecs = minutes * 60000;
		} else {
			msecs = TIMER_DEFAULT_TIMEOUT_MS;
		}
		timer.start(msecs);
		logInfoNL("Shadow download timer set to %d minutes and started.", msecs / 60000);
	} else {
		timer.stop();
	}
}

/*!
 * @brief Control shadow download timer.
 *
 * @note Keeps a remaining time value.
 *
 * @param[in,out] timer Timer to be managed.
 * @param[in]     action Action to be performed.
 */
static
void manageShadowTimer(QTimer &timer, enum TimerAction action)
{
	static int msecsRemaining = 0;
	manageTimer(timer, action, msecsRemaining, setUpShadowSync);
}

void MainWindow::viewManageAccountsDlg(void)
{
	/* Pause timers while editing accounts. */
	manageBackgroundTimer(m_timerSyncAccounts, TA_PAUSE);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_PAUSE);

	if (!DlgCreateAccount::manage(*GlobInstcs::acntMapPtr,
	    *GlobInstcs::shadowAcntsPtr, ui->actionSynchroniseAllAccounts->text(),
	    this)) {
		return;
	}

	saveSettings();

	manageBackgroundTimer(m_timerSyncAccounts, TA_RUN);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_RUN);
}

void MainWindow::viewImportDatabaseDlg(void)
{
	QString importDir = PrefsSpecific::importDbDir(*GlobInstcs::prefsPtr);

	QList<AcntId> createdAcntIds = DlgCreateAccountFromDb::createAccount(
	    *GlobInstcs::acntMapPtr, importDir, this);

	PrefsSpecific::setImportDbDir(*GlobInstcs::prefsPtr, importDir);

	if (!createdAcntIds.isEmpty()) {
		const AcntId acntId(createdAcntIds.last());
		refreshAccountList(acntId);
		saveSettings();
	}

	updateActionActivation();
	ui->accountList->expandAll();
}

/*!
 * @brief Get message db set related to given account.
 *
 * @param[in] acntId Account identifier.
 * @param[in] parent Parent widget.
 * @return Pointer to database set, Q_NULLPTR on error.
 */
static
MessageDbSet *accountDbSet(const AcntId &acntId, QWidget *parent)
{
	enum AccountInteraction::AccessStatus status =
	    AccountInteraction::AS_ERR;
	QString dbDir, namesStr;

	MessageDbSet *dbSet = AccountInteraction::accessDbSet(acntId, status,
	    dbDir, namesStr);

	dbDir = QDir::toNativeSeparators(dbDir);

	switch (status) {
	case AccountInteraction::AS_DB_ALREADY_PRESENT:
		QMessageBox::information(parent,
		    MainWindow::tr("Database file present"),
		    MainWindow::tr("Database file for account '%1' already exists.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("The existing database files %1 in '%2' are going to be used.").arg(namesStr).arg(dbDir) +
		    "\n\n" +
		    MainWindow::tr("If you want to use a new blank file then delete, rename or move the existing file so that the application can create a new empty file."),
		    QMessageBox::Ok);
		break;
	case AccountInteraction::AS_DB_NOT_PRESENT:
		QMessageBox::warning(parent,
		    MainWindow::tr("Problem loading database"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Database files are missing in '%1'.").arg(dbDir) +
		    "\n\n" +
		    MainWindow::tr("I'll try to create an empty one."),
		    QMessageBox::Ok);
		break;
	case AccountInteraction::AS_DB_NOT_FILES:
		QMessageBox::warning(parent,
		    MainWindow::tr("Problem loading database"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Some databases of %1 in '%2' are not a file.").arg(namesStr).arg(dbDir),
		    QMessageBox::Ok);
		break;
	case AccountInteraction::AS_DB_FILES_INACCESSIBLE:
		QMessageBox::warning(parent,
		    MainWindow::tr("Problem loading database"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Some databases of '%1' in '%2' cannot be accessed.").arg(namesStr).arg(dbDir) +
		    "\n\n" +
		    MainWindow::tr("You don't have enough access rights to use the file."),
		    QMessageBox::Ok);
		break;
	case AccountInteraction::AS_DB_FILES_CORRUPT:
		QMessageBox::warning(parent,
		    MainWindow::tr("Problem loading database"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Some databases of %1 in '%2' cannot be used.").arg(namesStr).arg(dbDir) +
		    "\n\n" +
		    MainWindow::tr("The file either does not contain an SQLite database or the file is corrupted."),
		    QMessageBox::Ok);
		break;
	case AccountInteraction::AS_DB_CONFUSING_ORGANISATION:
		QMessageBox::warning(parent,
		    MainWindow::tr("Problem loading database"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Conflicting databases %1 in '%2' cannot be used.").arg(namesStr).arg(dbDir) +
		    "\n\n" +
		    MainWindow::tr("Please remove the conflicting files."),
		    QMessageBox::Ok);
		break;
	default:
		break;
	}

	/*
	 * TODO -- Give the user some recovery options such as
	 * move/rename/remove the corrupted file or remove/ignore the affected
	 * account.
	 */

	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		/*
		 * TODO -- generate notification dialogue and give the user
		 * a choice between aborting program and skipping account?
		 */
		QMessageBox::critical(parent,
		    MainWindow::tr("Database opening error"),
		    MainWindow::tr("Could not load data from the database for account '%1'.").arg(acntId.username()) +
		    "\n\n" +
		    MainWindow::tr("Database files in '%1' cannot be created or are corrupted.").arg(dbDir),
		    QMessageBox::Ok);
		/*
		 * The program has to be aborted right now. The method
		 * QCoreApplication::exit(EXIT_FAILURE) uses the event loop
		 * whereas some event may be already planned and will crash
		 * because of the returning null pointer.
		 * Therefore exit() should be used.
		 */
	}

	return dbSet;
}

/*!
 * @brief Return list of available usernames and associated database sets.
 *
 * @param[in] accountModel Account model.
 * @param[in] parent Parent widget.
 * @return Account descriptor list. Empty list on any error.
 */
static
QList<AcntIdDb> allAcntIdDbList(const AccountModel &accountModel,
    QWidget *parent)
{
	QList<AcntIdDb> acntIdDbList;

	for (int i = 0; i < accountModel.rowCount(); ++i) {
		const AcntId acntId(
		    accountModel.acntId(accountModel.index(i, 0)));
		MessageDbSet *dbSet = accountDbSet(acntId, parent);
		if (Q_UNLIKELY((!acntId.isValid()) || (Q_NULLPTR == dbSet))) {
			Q_ASSERT(0);
			continue;
		}
		acntIdDbList.append(AcntIdDb(acntId, dbSet));
	}

	return acntIdDbList;
}

void MainWindow::viewDataBackupDlg(void)
{
	/*
	 * Check whether currently some tasks are being processed or are
	 * pending. If nothing works finish immediately, else show question.
	 */
	if (GlobInstcs::workPoolPtr->working()) {
		if (!askCancelTasks(this, false)) {
			return;
		}
	}

	GlobInstcs::workPoolPtr->stop();
	GlobInstcs::workPoolPtr->clear();

	DlgBackup::backup(allAcntIdDbList(m_accountModel, this), this);

	GlobInstcs::workPoolPtr->start();

	updateActionActivation();
}

void MainWindow::viewDataRestoreDlg(void)
{
	if (!PrefsSpecific::messageDbOnDisk(*GlobInstcs::prefsPtr)) {
		showStatusTextWithTimeout(
		    tr("Data restoration cannot be performed on databases in memory."));

		QMessageBox::warning(this, tr("Database operation error"),
		    tr("Data restoration cannot be performed on databases in memory."));
		return;
	}

	/*
	 * Check whether currently some tasks are being processed or are
	 * pending. If nothing works finish immediately, else show question.
	 */
	if (GlobInstcs::workPoolPtr->working()) {
		if (!askCancelTasks(this, false)) {
			return;
		}
	}

	GlobInstcs::workPoolPtr->stop();
	GlobInstcs::workPoolPtr->clear();

	bool ret = DlgRestore::restore(allAcntIdDbList(m_accountModel, this),
	    this);
	if (ret) {
		close(); /* Close this window. */
	}

	GlobInstcs::workPoolPtr->start();
}

void MainWindow::viewProxySettingsDlg(void)
{
	debugSlotCall();

	if (DlgProxysets::modify(*GlobInstcs::proxSetPtr, this)) {
		/* Dialogue accepted, store all settings. */
		saveSettings();
	}
}

/*!
 * @brief Shows all columns except the supplied ones.
 *
 * @param[in] view Table view.
 * @param[in] hideCols Indexes of column to be hidden, may also be negative.
 */
static
void showAllColumnsExcept(QTableView *view, const QList<int> &hideCols)
{
	if (Q_UNLIKELY(Q_NULLPTR == view)) {
		Q_ASSERT(0);
		return;
	}

	QAbstractItemModel *model = view->model();
	if (Q_UNLIKELY(Q_NULLPTR == model)) {
		return;
	}

	/* Set all columns visible. */
	for (int col = 0; col < model->columnCount(); ++col) {
		view->setColumnHidden(col, false);
	}

	foreach (int col, hideCols) {
		if (col < 0) {
			/* Negative are taken from the end. */
			col = model->columnCount() + col;
		}
		if (Q_UNLIKELY((col < 0) || (col >= model->columnCount()))) {
			Q_ASSERT(0);
			continue;
		}
		view->setColumnHidden(col, true);
	}
}

/*!
 * @brief Shows/hides message table columns according to functionality.
 *
 * @param[in,out] view Table view.
 * @param[in]     type Whether received or sent messages are displayed.
 */
static
void showMessageColumnsAccordingToFunctionality(QTableView *view,
    enum DbMsgsTblModel::Type type)
{
	QList<int> hideCols;

	if (type == DbMsgsTblModel::RCVD_MODEL) {
		hideCols.append(DbMsgsTblModel::RECIP_COL);
		hideCols.append(DbMsgsTblModel::MSGSTAT_COL);
	} else if (type == DbMsgsTblModel::SNT_MODEL) {
		hideCols.append(DbMsgsTblModel::PERSDELIV_COL);
		hideCols.append(DbMsgsTblModel::SENDER_COL);
		hideCols.append(DbMsgsTblModel::READLOC_COL);
		hideCols.append(DbMsgsTblModel::PROCSNG_COL);
	}

	if (!GlobInstcs::recMgmtSetPtr->isValid()) {
		hideCols.append(DbMsgsTblModel::RECMGMT_NEG_COL);
	}

	showAllColumnsExcept(view, hideCols);
}

void MainWindow::viewRecordsManagementDlg(void)
{
	debugSlotCall();

	if (DlgRecordsManagement::updateSettings(*GlobInstcs::recMgmtSetPtr,
	        *GlobInstcs::prefsPtr, this)) {
		saveSettings();
	}

	updateActionActivation(); /* Enable records management actions. */

	m_messageTableModel.setRecordsManagementIcon();
	m_messageTableModel.fillRecordsManagementColumn(
	    DbMsgsTblModel::RECMGMT_NEG_COL);

	showMessageColumnsAccordingToFunctionality(ui->messageList,
	    m_messageTableModel.type());
	if (AccountModel::nodeTypeIsReceived(
	        m_selectionStatus.mwStatus().acntNodeType())) {
		setReceivedColumnWidths();
	} else {
		setSentColumnWidths();
	}
}

void MainWindow::getStoredMsgInfoFromRecordsManagement(void)
{
	debugSlotCall();

	QList<AcntId> acntIds;
	for (int row = 0; row < m_accountModel.rowCount(); ++row) {
		acntIds.append(
		    m_accountModel.acntId(m_accountModel.index(row, 0)));
	}

	QList<DlgRecordsManagementStored::LocAcntData> accounts;
	foreach (const AcntId &acntId, acntIds) {
		MessageDbSet *dbSet = accountDbSet(acntId, this);
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			return;
		}
		accounts.append(DlgRecordsManagementStored::LocAcntData(
		    GlobInstcs::acntMapPtr->acntData(acntId).accountName(),
		    acntId, dbSet));
	}

	DlgRecordsManagementStored::updateStoredInformation(
	    *GlobInstcs::recMgmtSetPtr, accounts, this);

	m_messageTableModel.fillRecordsManagementColumn(
	    DbMsgsTblModel::RECMGMT_NEG_COL);
}

void MainWindow::viewPreferencesDlg(void)
{
	debugSlotCall();

	if (!DlgPreferences::modify(*GlobInstcs::prefsPtr,
	        *GlobInstcs::pinSetPtr, this)) {
		return;
	}

	/* Set actual download timer values from settings if enabled. */
	manageBackgroundTimer(m_timerSyncAccounts, TA_ENFORCE_SETUP);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_ENFORCE_SETUP);
}

void MainWindow::synchroniseSelectedAccount(void)
{
	debugSlotCall();

	/*
	 * TODO -- Save/restore the position of selected account and message.
	 */

	synchroniseAccount(m_selectionStatus.mwStatus().acntId());
}

void MainWindow::synchroniseSelectedAccountReceived(void)
{
	debugSlotCall();

	/*
	 * TODO -- Save/restore the position of selected account and message.
	 */

	synchroniseAccount(m_selectionStatus.mwStatus().acntId(), AcntId(),
	    MSG_RECEIVED);
}

void MainWindow::synchroniseSelectedAccountSent(void)
{
	debugSlotCall();

	/*
	 * TODO -- Save/restore the position of selected account and message.
	 */

	synchroniseAccount(m_selectionStatus.mwStatus().acntId(), AcntId(),
	    MSG_SENT);
}

void MainWindow::viewCreateMessageDlg(void)
{
	debugSlotCall();

	showSendMessageDialog(DlgSendMessage::ACT_NEW);
}

void MainWindow::viewCreateGovRequestDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	Q_ASSERT(mwStatus.acntId().isValid());

	/* The dialogue window checks whether the account is connected. */
	DlgGovServices::sendRequest(allAcntIdDbList(m_accountModel, this),
	    mwStatus.acntId(), this, this);
}

void MainWindow::accountMarkReceivedRead(void)
{
	debugSlotCall();

	accountMarkReceivedLocallyRead(true);
}

void MainWindow::accountMarkReceivedUnread(void)
{
	debugSlotCall();

	accountMarkReceivedLocallyRead(false);
}

void MainWindow::accountMarkReceivedUnsettled(void)
{
	debugSlotCall();

	accountMarkReceivedProcessState(MessageDb::UNSETTLED);
}

void MainWindow::accountMarkReceivedInProgress(void)
{
	debugSlotCall();

	accountMarkReceivedProcessState(MessageDb::IN_PROGRESS);
}

void MainWindow::accountMarkReceivedSettled(void)
{
	debugSlotCall();

	accountMarkReceivedProcessState(MessageDb::SETTLED);
}

void MainWindow::viewChangePasswordDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		return;
	}

	/* Method connectToIsds() acquires account information. */
	const QString dbId(
	    GlobInstcs::accntDbPtr->dbId(AccountDb::keyFromLogin(acntId.username())));
	Q_ASSERT(!dbId.isEmpty());

	showStatusTextWithTimeout(tr("Change password of account '%1'.")
	    .arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()));

	DlgChangePwd::changePassword(dbId, acntId, this);
}

void MainWindow::viewManageSelectedAccountDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	AcntId acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	AcntData newSettings(GlobInstcs::acntMapPtr->acntData(acntId));

	showStatusTextWithTimeout(tr("Change properties of account '%1'.")
	    .arg(newSettings.accountName()));

	bool quit = false;
	if (!DlgCreateAccount::modify(newSettings,
	        DlgCreateAccount::ACT_EDIT,
	        ui->actionSynchroniseAllAccounts->text(), this)) {
		return;
	}
	AcntId newAcntId(newSettings.userName(), newSettings.isTestAccount());
	if (Q_UNLIKELY(newAcntId.testing() != acntId.testing())) {
		/* Cannot change testing status. */
		Q_ASSERT(0);
		return;
	}
	if (newAcntId == acntId) {
		/* Store settings if username unchanged.  */
		GlobInstcs::acntMapPtr->updateAccount(newSettings);
	} else if (GlobInstcs::acntMapPtr->acntIds().contains(newAcntId)) {
		/* Cannot change username if it already exists. */
		QMessageBox::warning(this, tr("Username Already Present"),
		    tr("An account '%1' for the newly entered username '%2' is already present. Cannot change the username to '%2'.")
		        .arg(GlobInstcs::acntMapPtr->acntData(newAcntId).accountName())
		        .arg(newSettings.userName()),
		    QMessageBox::Ok);
		return;
	} else {
		/* Username changed and new username is not used in application. */
		int dlgRet = DlgMsgBox::message(this,
		    QMessageBox::Question, tr("Username Change"),
		    tr("Do you want to change the username from '%1' to '%2' for the account '%3'?")
		        .arg(acntId.username())
		        .arg(newSettings.userName())
		        .arg(newSettings.accountName()),
		    tr("Note: This will also change all related local database names and stored account information."),
		    QString(), QMessageBox::No | QMessageBox::Yes,
		    QMessageBox::No);
		if (QMessageBox::No == dlgRet) {
			return;
		}

		if (!firstConnectToIsds(newSettings)) {
			logErrorNL("Could not log in using a new username '%s'.",
			    newSettings.userName().toUtf8().constData());
			/* Stop if login fails. */
			return;
		}
		/* Account info should be downloaded by now. */

		const QString existingBoxId(GlobInstcs::accntDbPtr->dbId(
		    AccountDb::keyFromLogin(acntId.username())));
		const QString newBoxId(GlobInstcs::accntDbPtr->dbId(
		    AccountDb::keyFromLogin(newSettings.userName())));
		if (Q_UNLIKELY(existingBoxId.isEmpty() || newBoxId.isEmpty())) {
			logErrorNL("Cannot compare existing '%s' or new '%s' box identifier.",
			    existingBoxId.toUtf8().constData(),
			    newBoxId.toUtf8().constData());
			Q_ASSERT(0);
			return;
		}
		if (existingBoxId != newBoxId) {
			/* Cannot change to login which uses a different data box. */
			logErrorNL("Old username '%s' (box '%s') does not match new username '%s' (box '%s').",
			    acntId.username().toUtf8().constData(),
			    existingBoxId.toUtf8().constData(),
			    newSettings.userName().toUtf8().constData(),
			    newBoxId.toUtf8().constData());
			DlgMsgBox::message(this, QMessageBox::Warning,
			    tr("Data Box Mismatch"),
			    tr("Cannot change the username '%1' to '%2' for the account '%3'.")
			        .arg(acntId.username())
			        .arg(newSettings.userName())
			        .arg(newSettings.accountName()),
			    tr("The old username was used to access the data box '%1' whereas the new username accesses the data box '%2'." " "
			       "You cannot change the username if it accesses a different data box.")
			        .arg(existingBoxId).arg(newBoxId),
			    QString(), QMessageBox::Ok);
			return;
		}

		/* Acquire database set. This operation should not fail. */
		MessageDbSet *dbSet = accountDbSet(acntId, this);
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			return;
		}

		/* Can log in using the new credentials. Data boxes do match. */

		if (GlobInstcs::workPoolPtr->working()) {
			if (!askCancelTasks(this, false)) {
				QMessageBox::information(this, tr("Operation Cancelled"),
				    tr("The operation has been cancelled."),
				    QMessageBox::Ok);
				return;
			}
		}

		GlobInstcs::workPoolPtr->stop();
		GlobInstcs::workPoolPtr->clear();

		/*
		 * TODO -- The following slot reads the old account name and
		 * generates a false warning therefore it is disconnected.
		 * The main window code must be rewritten.
		 */
		GlobInstcs::msgDbsPtr->disconnect(SIGNAL(opened(AcntId)),
		    this, SLOT(refreshAccountList(AcntId)));

		/* Change primary key in database set. */
		dbSet->changePrimaryKey(newSettings.userName());
		/* Remove old settings and store new settings. */
		GlobInstcs::acntMapPtr->updateUsernameAccount(acntId, newSettings);
		/* Change username in account model. */
		/*
		 * Username has been replaced. Use the new one when notifying
		 * about the account data change.
		 */
		acntId = newAcntId;

		GlobInstcs::workPoolPtr->start();

		QMessageBox::information(this, tr("Restart Needed"),
		    tr("The username has been successfully changed. The application is going to be closed. Then you may start it again."));

		quit = true;
	}

	/* Save changes. */
	showStatusTextWithTimeout(
	    tr("Account '%1' was updated.").arg(acntId.username()));
	saveSettings();

	if (quit) {
		close();
	}
}

void MainWindow::moveSelectedAccountUp(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	Q_ASSERT(mwStatus.acntId().isValid());

	if (m_accountModel.changePosition(mwStatus.acntId(), -1)) {
		showStatusTextWithTimeout(tr("Account was moved up."));
	}
}

void MainWindow::moveSelectedAccountDown(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	Q_ASSERT(mwStatus.acntId().isValid());

	if (m_accountModel.changePosition(mwStatus.acntId(), 1)) {
		showStatusTextWithTimeout(tr("Account was moved down."));
	}
}

void MainWindow::viewChangeDataDirectoryDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	showStatusTextWithTimeout(tr("Change data directory of account '%1'.")
	    .arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()));

	if (DlgChangeDirectory::changeDataDirectory(acntId, dbSet, this)) {
		saveSettings();
		adaptToAccountSelection();
	}
}

void MainWindow::viewImportMessagesFromDatabaseDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	{
		int dlgRet = DlgMsgBox::message(this, QMessageBox::Question,
		    tr("Import Messages from Database"),
		    tr("Here you can import messages from selected database files into the current account.") + " " +
		    tr("Keep in mind that this operation may take a while. "
		        "The actual duration depends on the number of messages in the database.") + " " +
		    tr("The progress will be displayed in the status bar."),
		    tr("Do you want to continue?"), QString(),
		    QMessageBox::No | QMessageBox::Yes, QMessageBox::No);
		if (QMessageBox::No == dlgRet) {
			return;
		}
	}

	/* Get list of selected database files. */
	QStringList dbFileList = QFileDialog::getOpenFileNames(this,
	    tr("Select Database Files"),
	    PrefsSpecific::importDbDir(*GlobInstcs::prefsPtr),
	    tr("DB file (*.db)"));

	if (dbFileList.isEmpty()) {
		logDebugLv1NL("%s", "No database files selected.");
		showStatusTextWithTimeout(tr("No database files selected."));
		return;
	}

	/* Remember path. */
	PrefsSpecific::setImportDbDir(*GlobInstcs::prefsPtr,
	    QFileInfo(dbFileList.at(0)).absoluteDir().absolutePath());

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const QString dbId(
	    GlobInstcs::accntDbPtr->dbId(AccountDb::keyFromLogin(acntId.username())));

	Imports::importDbMsgsIntoDatabase(*dbSet, dbFileList, acntId.username(), dbId);

	/* Update account model. */
	refreshAccountList(acntId);
}

/*!
 * @brief ZFO import context singleton.
 */
class ZFOImportCtx {
private:
	/*!
	 * @brief Constructor.
	 */
	ZFOImportCtx(void)
	    : zfoFilesToImport(), numFilesToImport(0),
	    importSucceeded(), importExisted(), importFailed()
	{
	}

public:
	/*!
	 * @brief Return reference to singleton instance.
	 */
	static
	ZFOImportCtx &getInstance(void)
	{
		static
		ZFOImportCtx ctx;

		return ctx;
	}

	/*!
	 * @brief Erase content.
	 */
	void clear(void)
	{
		zfoFilesToImport.clear();
		numFilesToImport = 0;
		importSucceeded.clear();
		importExisted.clear();
		importFailed.clear();
	}

	QSet<QString> zfoFilesToImport; /*!< Set of names of files to be imported. */
	int numFilesToImport; /*!< Input ZFO count. */
	/*
	 * QPair in following lists means:
	 * first string - ZFO file name,
	 * second - import result text
	 */
	QList< QPair<QString, QString> > importSucceeded; /*!< Successful import result lists. */
	QList< QPair<QString, QString> > importExisted; /*!< Import existed result lists. */
	QList< QPair<QString, QString> > importFailed; /*!< Import error result lists. */
};

void MainWindow::viewImportMessagesFromZfosDlg(void)
{
	debugSlotCall();

	enum Imports::Type zfoType = Imports::IMPORT_MESSAGE;
	enum DlgImportZFO::ZFOlocation locationType =
	    DlgImportZFO::IMPORT_FROM_DIR;
	bool checkZfoOnServer = false;

	/* Import setting dialogue. */
	if (!DlgImportZFO::getImportConfiguration(zfoType, locationType,
	        checkZfoOnServer, this)) {
		return;
	}

	/* Get username and pointer to database for all accounts from settings. */
	QList<AcntIdDb> acntIdDbList(allAcntIdDbList(m_accountModel, this));
	if (checkZfoOnServer) {
		/* Exclude accounts with inactive server connection. */
		QList<AcntIdDb> connectedList;
		foreach (const AcntIdDb &acntIdDb, acntIdDbList) {
			if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntIdDb.username()) &&
			    !connectToIsds(acntIdDb)) {
				continue;
			}
			connectedList.append(acntIdDb);
		}
		acntIdDbList = connectedList;
	}

	QStringList filePathList;

	/* Perform actions depending on user decision. */
	if ((locationType == DlgImportZFO::IMPORT_FROM_SUBDIR) ||
	    (locationType == DlgImportZFO::IMPORT_FROM_DIR)) {
		QString importDir = QFileDialog::getExistingDirectory(this,
		    tr("Select Directory"),
		    PrefsSpecific::importDbZfo(*GlobInstcs::prefsPtr),
		    QFileDialog::ShowDirsOnly |
		    QFileDialog::DontResolveSymlinks);

		if (importDir.isEmpty()) {
			return;
		}

		PrefsSpecific::setImportDbZfo(*GlobInstcs::prefsPtr, importDir);

		const QStringList nameFilter("*.zfo");
		if (locationType == DlgImportZFO::IMPORT_FROM_SUBDIR) {
			/* Include subdirectories. */
			QDirIterator it(importDir, nameFilter, QDir::Files,
			    QDirIterator::Subdirectories);
			while (it.hasNext()) {
				filePathList.append(it.next());
			}
		} else {
			QStringList fileList =
			    QDir(importDir).entryList(nameFilter);
			for (int i = 0; i < fileList.size(); ++i) {
				filePathList.append(
				    importDir + "/" + fileList.at(i));
			}
		}

		if (filePathList.isEmpty()) {
			logWarningNL(
			    "No ZFO files in selected directory '%s'.",
			    importDir.toUtf8().constData());
			showStatusTextWithTimeout(
			    tr("No ZFO files found in selected directory."));
			QMessageBox::warning(this,
			    tr("No ZFO Files"),
			    tr("No ZFO files found in selected directory."),
			    QMessageBox::Ok);
			return;
		}
	} else if (locationType == DlgImportZFO::IMPORT_SEL_FILES) {
		filePathList = QFileDialog::getOpenFileNames(this,
		    tr("Select ZFO Files"),
		    PrefsSpecific::importDbZfo(*GlobInstcs::prefsPtr),
		    tr("ZFO file (*.zfo)"));

		if (filePathList.isEmpty()) {
			logWarningNL("%s", "No ZFO files selected.");
			showStatusTextWithTimeout(tr("No ZFO files selected."));
			return;
		}

		PrefsSpecific::setImportDbZfo(*GlobInstcs::prefsPtr,
		    QFileInfo(filePathList.at(0)).absoluteDir().absolutePath());
	}

	logInfoNL("Trying to import %d ZFO files.", filePathList.count());

	if (filePathList.count() == 0) {
		logInfoNL("%s", "No ZFO files in received file list.");
		showStatusTextWithTimeout(tr("No ZFO files to import."));
		return;
	}

	if (acntIdDbList.isEmpty()) {
		logInfoNL("%s", "No accounts to import into.");
		showStatusTextWithTimeout(
		    tr("There is no account to import ZFO files into."));
		return;
	}

	/* Block import GUI buttons. */
	m_selectionStatus.setFlagBlockImports(true);

	QString errTxt;

	ZFOImportCtx &importCtx(ZFOImportCtx::getInstance());
	importCtx.clear();

	Imports::importZfoIntoDatabase(filePathList, acntIdDbList,
	    zfoType, checkZfoOnServer, importCtx.zfoFilesToImport,
	    importCtx.importFailed, importCtx.numFilesToImport, errTxt);

	if (!errTxt.isEmpty()) {
		logInfoNL("%s", errTxt.toUtf8().constData());
		showStatusTextWithTimeout(errTxt);
	}

	clearProgressBar();
}

void MainWindow::viewVacuumMessageDatabaseDlg(void)
{
	debugSlotCall();

	if (!PrefsSpecific::messageDbOnDisk(*GlobInstcs::prefsPtr)) {
		showStatusTextWithTimeout(
		    tr("Vacuum cannot be performed on databases in memory."));

		DlgMsgBox::message(this, QMessageBox::Warning,
		    tr("Database Operation Error"),
		    tr("Database clean-up cannot be performed on database in memory."),
		    tr("Cannot call VACUUM on database in memory."), QString(),
		    QMessageBox::Ok, QMessageBox::Ok);
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	MessageDbSet *msgDbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == msgDbSet)) {
		return;
	}

	qint64 dbSizeInBytes = msgDbSet->fileSize(MessageDbSet::SC_LARGEST);
	if (dbSizeInBytes <= 0) {
		return;
	}

	QString size = QString::number(dbSizeInBytes) + " B";
	if (dbSizeInBytes >= 1000000000) {
		size = QString::number(dbSizeInBytes / 1000000000) + " GB";
	} else if (dbSizeInBytes >= 1000000) {
		size = QString::number(dbSizeInBytes / 1000000) + " MB";
	} else if (dbSizeInBytes >= 1000) {
		size = QString::number(dbSizeInBytes / 1000) + " KB";
	}

	{
		int dlgRet = DlgMsgBox::message(this, QMessageBox::Question,
		    tr("Clean Message Database"),
		    tr("Performs a message database clean-up for the selected account. "
		        "This action will block the entire application. "
		        "The action may take several minutes to be completed. "
		        "Furthermore, it requires more than %1 of free disk space to successfully proceed.")
		        .arg(size),
		    tr("Do you want to continue?"), QString(),
		    QMessageBox::No | QMessageBox::Yes, QMessageBox::No);
		if (QMessageBox::Yes != dlgRet) {
			return;
		}
	}

	showStatusTextPermanently(tr("Performing database clean-up."));
	QApplication::setOverrideCursor(Qt::WaitCursor);

	TaskVacuumDbSet *task = new (::std::nothrow) TaskVacuumDbSet(msgDbSet);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}
	task->setAutoDelete(false);
	/* This will block the GUI and all workers. */
	GlobInstcs::workPoolPtr->runSingle(task);

	showStatusTextWithTimeout(tr("Database clean-up finished."));
	QApplication::restoreOverrideCursor();

	if (task->m_success) {
		QMessageBox::information(this,
		    tr("Database Clean-Up Successful"),
		    tr("The database clean-up has finished successfully."),
		    QMessageBox::Ok);
	} else {
		QMessageBox::warning(this,
		    tr("Database Clean-Up Failed"),
		    tr("The database clean-up failed with error message: %1").arg(task->m_error),
		    QMessageBox::Ok);
	}

	delete task;
}

void MainWindow::viewSplitMessageDatabaseByYearsDlg(void)
{
	debugSlotCall();

	{
		int dlgRet = DlgMsgBox::message(this, QMessageBox::Question,
		    tr("Split Database"),
		    tr("The following operation splits the message database of the selected account into several separate databases. "
		        "Each of which will contain messages related only to a single year. "
		        "Dividing large databases according to years is recommended because it may improve the performance."),
		    tr("The original database file will be copied to a selected directory and new database files will be created in the original location. "
		        "If the operation finishes successfully then the newly created databases will be used instead of the original one. "
		        "Application restart will be required.")
		    + "\n\n" + tr("Note:") + " "
		    + tr("Keep in mind that this operation may take a while. "
		        "The actual duration depends on the number of messages in the database.")
		    + "\n\n" + tr("Do you want to continue?"), QString(),
		    QMessageBox::No | QMessageBox::Yes, QMessageBox::No);

		if (QMessageBox::No == dlgRet) {
			return;
		}
	}

	QString newDbDir;
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	/* Get current database file location. */
	QString dbDir = GlobInstcs::acntMapPtr->acntData(acntId).dbDir();
	if (dbDir.isEmpty()) {
		dbDir = GlobInstcs::iniPrefsPtr->confDir();
	}

	/* Get origin message database set based on username. */
	MessageDbSet *msgDbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == msgDbSet)) {
		return;
	}

	/* Get target directory for split database files. */
	do {
		newDbDir = QFileDialog::getExistingDirectory(this,
		    tr("Select Directory for New Databases"),
		    PrefsSpecific::importDbDir(*GlobInstcs::prefsPtr),
		    QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

		if (newDbDir.isEmpty()) {
			return;
		}

		/*
		 * New databases cannot be saved into same location as
		 * the original database.
		 */
		if (dbDir == newDbDir) {
			clearProgressBar();
			showStatusTextWithTimeout(
			    tr("Splitting message database finished with an error."));

			DlgMsgBox::message(this, QMessageBox::Critical,
			    tr("Database File Error"),
			    tr("Database file cannot be split into original directory."),
			    tr("Please choose another directory."), QString(),
			    QMessageBox::Ok);

			clearStatusBar();
		}
	} while (dbDir == newDbDir);

	/* Remember import path. */
	PrefsSpecific::setImportDbDir(*GlobInstcs::prefsPtr, newDbDir);

	QApplication::setOverrideCursor(Qt::WaitCursor);

	TaskSplitDb *task = new (::std::nothrow) TaskSplitDb(
	    AcntIdDb(acntId, msgDbSet), dbDir, newDbDir);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}
	task->setAutoDelete(false);
	/* This will block the GUI and all workers. */
	GlobInstcs::workPoolPtr->runSingle(task);

	QApplication::restoreOverrideCursor();

	/* Show final notification. */
	if (task->m_success) {
		showStatusTextWithTimeout(
		    tr("Splitting message database finished."));

		DlgMsgBox::message(this, QMessageBox::Information,
		    tr("Database Split Result"),
		    tr("Message database for account '%1' was split successfully. "
		        "Restart the application in order to load the new databases.")
		        .arg(acntId.username()),
		    tr("Note: Original database file was backed up to:") + "\n" + newDbDir,
		    QString(), QMessageBox::Ok);
	} else {
		DlgMsgBox::message(this, QMessageBox::Critical,
		    tr("Database Split Result"),
		    tr("Message database splitting for account '%1' was not successful. "
		        "Restart the application in order to reload the original database.")
		        .arg(acntId.username()),
		    task->m_error, QString(), QMessageBox::Ok);
	}

	delete task;
	clearProgressBar();

	clearStatusBar();

	/* refresh account model and account list */
	refreshAccountList(acntId);
}

/*!
 * @brief Returns whether we are working with sent or received messages.
 *
 * @param[in] nodeType Account model node type.
 * @param[in] dfltDirect Default direction to be returned.
 * @return Message direction identifier.
 */
static
enum MessageDirection messageDirection(enum AccountModel::NodeType nodeType,
    enum MessageDirection dfltDirect)
{
	enum MessageDirection ret = dfltDirect;

	if (AccountModel::nodeTypeIsReceived(nodeType)) {
		ret = MSG_RECEIVED;
	} else if (AccountModel::nodeTypeIsSent(nodeType)) {
		ret = MSG_SENT;
	} else {
		Q_ASSERT(0);
	}

	return ret;
}

/*!
 * @brief Message identifier from index.
 *
 * @param[in] msgIdx Index into the message table view.
 * @return Message identifier.
 */
static inline
qint64 msgIdentifier(const QModelIndex &msgIdx)
{
	Q_ASSERT(msgIdx.isValid());
	if (msgIdx.column() == DbMsgsTblModel::DMID_COL) {
		return msgIdx.data().toLongLong();
	} else {
		return msgIdx.sibling(msgIdx.row(), DbMsgsTblModel::DMID_COL)
		    .data().toLongLong();
	}
}

/*!
 * @brief Message time from index.
 *
 * @param[in] msgIdx Index into the message table view.
 * @return Delivery time.
 */
static inline
QDateTime msgDeliveryTime(const QModelIndex &msgIdx)
{
	QModelIndex deliveryIdx(
	    msgIdx.sibling(msgIdx.row(), DbMsgsTblModel::DELIVERY_COL));
	Q_ASSERT(deliveryIdx.isValid());

	return dateTimeFromDbFormat(
	    deliveryIdx.data(DbMsgsTblModel::ROLE_PLAIN_DISPLAY).toString());
}

/*!
 * @brief Returns a full message identifier from index.
 *
 * @param[in] msgIdx Index into the message table view.
 * @return Full message identifier.
 */
static inline
MsgId idx2MsgId(const QModelIndex &msgIdx)
{
	Q_ASSERT(msgIdx.isValid());
	return MsgId(msgIdentifier(msgIdx), msgDeliveryTime(msgIdx));
}

/*!
 * @brief Returns set of full message identifiers.
 *
 * @param[in] msgIdxs List of indexes.
 * @return Set of message identifiers.
 */
static inline
QSet<MsgId> idxs2MsgIdSet(const QModelIndexList &msgIdxs)
{
	QSet<MsgId> msgIds;
	foreach (const QModelIndex &idx, msgIdxs) {
		if (Q_UNLIKELY(!idx.isValid())) {
			Q_ASSERT(0);
			return QSet<MsgId>();
		}
		MsgId msgId(idx2MsgId(idx));
		if (Q_UNLIKELY(msgId.dmId() < 0)) {
			Q_ASSERT(0);
			return QSet<MsgId>();
		}
		msgIds.insert(msgId);
	}
	return msgIds;
}

#define unacceptedState(state) \
	((state) < Isds::Type::MS_ACCEPTED)

/*!
 * @brief Show warning about possible download failure.
 *
 * @param[in] msgDirect Received or sent.
 * @param[in] mdgId Message Identifier.
 * @param[in] dbSet Database container.
 * @param[in] parent Parent widget.
 */
static
void warnAboutPossibleDownloadFailure(enum MessageDirection msgDirect,
    const MsgId &msgId, MessageDbSet *dbSet, QWidget *parent)
{
	if (Q_UNLIKELY((msgDirect != MSG_RECEIVED) || (!msgId.isValid()) ||
	        (dbSet == Q_NULLPTR))) {
		return;
	}

	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(), false);
	if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
		return;
	}

	enum Isds::Type::DmState dmState = messageDb->getMessageStatus(msgId.dmId());
	if (unacceptedState(dmState)) {
		QMessageBox::warning(parent,
		    MainWindow::tr("Downloading Message '%1'.").arg(msgId.dmId()),
		    MainWindow::tr("The message '%1' is in an unaccepted state. "
		        "It is likely that you won't be able to download the message without synchronising the account first.").arg(msgId.dmId()),
		    QMessageBox::Ok);
	}
}

/*!
 * @brief Construct exhaustive message selection list.
 *
 * @param[in] mwStatus Main window status.
 * @param[in] parent Parent widget.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginList(const MWStatus &mwStatus, QWidget *parent)
{
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		/* No account selected. */
		return QList<MsgOrigin>();
	}
	MessageDbSet *dbSet = accountDbSet(acntId, parent);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return QList<MsgOrigin>();
	}
	const QSet<MsgId> &msgIds = mwStatus.msgIds();
	if (Q_UNLIKELY(msgIds.isEmpty())) {
		/* No message selected. */
		return QList<MsgOrigin>();
	}
	/* Taken from main window, all messages have same direction. */
	const enum MessageDirection msgDirect =
	    messageDirection(mwStatus.acntNodeType(), MSG_ALL);
	if (Q_UNLIKELY(msgDirect == MSG_ALL)) {
		Q_ASSERT(0);
		return QList<MsgOrigin>();
	}

	QList<MsgOrigin> originList;

	foreach (const MsgId &msgId, msgIds) {
		MessageDb *messageDb =
		    dbSet->constAccessMessageDb(msgId.deliveryTime());
		if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
			Q_ASSERT(0);
			return QList<MsgOrigin>();
		}
		const bool downloaded =
		    messageDb->isCompleteMessageInDb(msgId.dmId());

		originList.append(MsgOrigin(AcntIdDb(acntId, dbSet),
		    msgId, msgDirect, downloaded));
	}

	return originList;
}

/*!
 * @brief Determines upload hierarchy targets from the envelope.
 *
 * @param[in] originList Message origins to determine upload targets for.
 * @param[in] recMgmtTargets Automatic upload targets for records management.
 * @return List of message origins ans upload targets.
 */
static
QList<MsgOriginAndUpladTarget> computeUploadTargets(
    const QList<MsgOrigin> &originList,
    const RecMgmt::AutomaticUploadTarget &recMgmtTargets)
{
	QList<MsgOriginAndUpladTarget> originsAndTargets =
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList);

	QList<MsgOriginAndUpladTarget>::iterator it;
	for (it = originsAndTargets.begin(); it != originsAndTargets.end(); ++it) {
		if (it->downloaded) {
			continue;
		}
		if (it->direction != MSG_RECEIVED) {
			continue;
		}

		MessageDbSet *dbSet = it->acntIdDb.messageDbSet();
		if (Q_UNLIKELY(dbSet == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}
		MessageDb *messageDb = dbSet->accessMessageDb(
		    it->msgId.deliveryTime(), false);
		if (Q_UNLIKELY(messageDb == Q_NULLPTR)) {
			Q_ASSERT(0);
			continue;
		}
		/*
		 * Similar code is in
		 * TaskDownloadMessageList::downloadMessageList().
		 */
		Isds::Envelope env = messageDb->getMessageEnvelope(
		    it->msgId.dmId());
		bool ambiguous = false;
		it->recMgmtHierarchyId = RecMgmt::uniqueUploadReceived(
		    env, recMgmtTargets, &ambiguous);
		if (!it->recMgmtHierarchyId.isEmpty()) {
			logDebugLv0NL(
			    "Marking message '%" PRId64 "' to be uploaded into records management hierarchy id '%s'.",
			    UGLY_QINT64_CAST env.dmId(),
			    it->recMgmtHierarchyId.toUtf8().constData());
		}

		if (!it->recMgmtHierarchyId.isEmpty()) {
			it->taskFlags |= Task::PROC_IMM_RM_UPLOAD;
		}
		if (ambiguous) {
			it->taskFlags |= Task::PROC_IMM_RM_UPLOAD_AMBIG;
		}
	}

	return originsAndTargets;
}

void MainWindow::downloadSelectedMessageAttachments(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);

	QList<MsgOriginAndUpladTarget> oautList;

	if (GlobInstcs::recMgmtSetPtr->isValid() &&
	    PrefsSpecific::recMgmtAutoUpload(*GlobInstcs::prefsPtr)) {
		/* Determine message upload targets. */
		RecMgmt::AutomaticUploadTarget uploadTarget =
		    DlgRecordsManagementUpload::uploadHierarchyTargets(
		        *GlobInstcs::recMgmtSetPtr, this);
		oautList = computeUploadTargets(originList, uploadTarget);
	} else {
		oautList =
		    MsgOriginAndUpladTarget::fromMsgOriginList(originList);
	}

	DlgDownloadMessages::downloadAll(oautList, this, this);
}

void MainWindow::viewReplyMessageDlg(void)
{
	debugSlotCall();

	showSendMessageDialog(DlgSendMessage::ACT_REPLY);
}

void MainWindow::viewForwardMessageDlg(void)
{
	debugSlotCall();

	showSendMessageDialog(DlgSendMessage::ACT_FORWARD);
}

void MainWindow::viewCreateMessageFromMessageDlg(void)
{
	debugSlotCall();

	showSendMessageDialog(DlgSendMessage::ACT_NEW_FROM_TMP);
}

/*!
 * @brief Return first message identifier from the set.
 *
 * @note The set mustn't be empty.
 */
#define firstMsgId(msgIds) \
	(*(msgIds).constBegin())

void MainWindow::viewSignatureDetailsDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return;
	}
	if (!msgId.deliveryTime().isValid()) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	DlgSignatureDetail::detail(*dbSet, msgId, this);
}

/*!
 * @brief Calls message hash verification task.
 *
 * @note The ISDS service VerifyMessage is marked as deprecated in the
 *     Operating Rules of ISDS.
 *
 * @param[in] acntId Account identifier.
 * @param[in] dmId Numeric message identifier.
 * @param[in] hash Message hash.
 * @return TaskVerifyMessage::Result.
 */
static
enum TaskVerifyMessage::Result verifyMessageHash(const AcntId &acntId,
    qint64 dmId, const Isds::Hash &hash)
{
	debugFuncCall();

	Q_ASSERT(acntId.isValid());
	Q_ASSERT(dmId >= 0);
	Q_ASSERT(!hash.isNull());

	TaskVerifyMessage *task = new (::std::nothrow) TaskVerifyMessage(acntId,
	    dmId, hash);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return TaskVerifyMessage::VERIFY_ERR;
	}
	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->runSingle(task);

	enum TaskVerifyMessage::Result result = task->m_result;
	delete task; task = Q_NULLPTR;

	return result;
}

void MainWindow::viewVerifySelectedMessageDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());
	Q_ASSERT(msgId.dmId() >= 0);
	if (Q_UNLIKELY(!msgId.deliveryTime().isValid())) {
		Q_ASSERT(0);
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	const Isds::Hash hashDb(messageDb->getMessageHash(msgId.dmId()));
	if (hashDb.isNull()) {
		logErrorNL(
		    "Error obtaining hash of message '%" PRId64 "' from local database.",
		    UGLY_QINT64_CAST msgId.dmId());
		showStatusTextWithTimeout(tr("Message verification failed."));
		QMessageBox::warning(this, tr("Verification Error"),
		    tr("The message hash is not in local database.\n"
		        "Download the complete message from ISDS and try again."),
		    QMessageBox::Ok);
		return;
	}

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		return;
	}

	switch (verifyMessageHash(acntId, msgId.dmId(), hashDb)) {
	case TaskVerifyMessage::VERIFY_SUCCESS:
		showStatusTextWithTimeout(
		    tr("The ISDS server confirms that the message hash is valid."));
		QMessageBox::information(this, tr("Message Hash Valid"),
		    tr("The message hash was <b>successfully verified</b> against data on the ISDS server.") +
		    "<br/><br/>" +
		    tr("This message has passed through the ISDS system and has not been tampered with since."),
		    QMessageBox::Ok);
		break;
	case TaskVerifyMessage::VERIFY_NOT_EQUAL:
		showStatusTextWithTimeout(
		    tr("The ISDS server informs that the message hash is not valid."));
		QMessageBox::critical(this, tr("Message Hash Invalid"),
		    tr("The message hash was <b>not</b> verified by the ISDS.") +
		    "<br/><br/>" +
		    tr("It is either not a valid ZFO file or it was modified since it was obtained from ISDS."),
		     QMessageBox::Ok);
		break;
	case TaskVerifyMessage::VERIFY_ISDS_ERR:
		showStatusTextWithTimeout(tr("Message hash verification failed."));
		QMessageBox::warning(this, tr("Verification Failed"),
		    tr("Verification of the message hash has been interrupted because the connection to ISDS failed.") +
		    "<br/><br/>" +
		    tr("Check your internet connection."),
		    QMessageBox::Ok);
		break;
	case TaskVerifyMessage::VERIFY_ERR:
	default:
		showStatusTextWithTimeout(tr("Message hash verification failed."));
		QMessageBox::critical(this, tr("Verification Failed"),
		    tr("An undefined error occurred.") + "<br/><br/>" +
		    tr("Try it again later."),
		    QMessageBox::Ok);
		break;
	}
}

void MainWindow::openSelectedMessageExternally(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return;
	}
	if (!msgId.deliveryTime().isValid()) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	QByteArray base64 = messageDb->getCompleteMessageBase64(msgId.dmId());
	if (base64.isEmpty()) {
		DlgMsgBox::message(this, QMessageBox::Warning,
		    tr("Export Error"),
		    tr("Cannot export message '%1'.").arg(msgId.dmId()),
		    tr("First you must download the complete message."),
		    QString());
		return;
	}

	QString fileName(TMP_ATTACHMENT_PREFIX +
	    QString("%1_%2.zfo").arg(dzPrefix(messageDb, msgId.dmId())).arg(msgId.dmId()));
	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	QByteArray data = QByteArray::fromBase64(base64);

	fileName = writeTemporaryFile(fileName, data);
	if (!fileName.isEmpty()) {
		showStatusTextWithTimeout(
		    tr("Message '%1' stored into temporary file '%2'.")
		        .arg(msgId.dmId()).arg(fileName));
		QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
		/* TODO -- Handle openUrl() return value. */
	} else {
		showStatusTextWithTimeout(
		    tr("Message '%1' couldn't be stored into temporary file.")
		        .arg(msgId.dmId()));
		QMessageBox::warning(this,
		    tr("Error opening message '%1'.").arg(msgId.dmId()),
		    tr("Cannot write file '%1'.").arg(fileName),
		    QMessageBox::Ok);
	}
}

void MainWindow::openAcceptanceInfoExternally(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return;
	}
	if (!msgId.deliveryTime().isValid()) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	QByteArray base64 = messageDb->getDeliveryInfoBase64(msgId.dmId());
	if (base64.isEmpty()) {
		DlgMsgBox::message(this, QMessageBox::Warning,
		    tr("Export Error"),
		    tr("Cannot export acceptance information '%1'.").arg(msgId.dmId()),
		    tr("First you must download the complete message."),
		    QString());
		return;
	}

	QString fileName(TMP_ATTACHMENT_PREFIX +
	    QString("%1_%2_info.zfo").arg(dzPrefix(messageDb, msgId.dmId())).arg(msgId.dmId()));
	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	QByteArray data = QByteArray::fromBase64(base64);

	fileName = writeTemporaryFile(fileName, data);
	if (!fileName.isEmpty()) {
		showStatusTextWithTimeout(
		    tr("Acceptance information '%1' stored into temporary file '%2'.")
		        .arg(msgId.dmId()).arg(fileName));
		QDesktopServices::openUrl(QUrl::fromLocalFile(fileName));
		/* TODO -- Handle openUrl() return value. */
	} else {
		showStatusTextWithTimeout(
		    tr("Acceptance information '%1' couldn't be stored into temporary file.")
		        .arg(msgId.dmId()));
		QMessageBox::warning(this,
		    tr("Error Opening Acceptance Information '%1'").arg(msgId.dmId()),
		    tr("Cannot write file '%1'.").arg(fileName),
		    QMessageBox::Ok);
	}
}

void MainWindow::viewSendSelectedMessageToRecordsManagementDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());
	if (Q_UNLIKELY(msgId.dmId() < 0)) {
		Q_ASSERT(0);
		return;
	}

	sendMessageToRecordsManagement(acntId, msgId);
}

void MainWindow::exportSelectedMessageZfos(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::exportSelectedData(originList, Exports::ZFO_MESSAGE, this);
}

void MainWindow::exportSelectedAcceptanceInfoZfos(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::exportSelectedData(originList, Exports::ZFO_DELIVERY, this);
}

void MainWindow::exportSelectedAcceptanceInfoPdfs(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::exportSelectedData(originList, Exports::PDF_DELIVERY, this);
}

void MainWindow::exportSelectedEnvelopePdfs(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::exportSelectedData(originList, Exports::PDF_ENVELOPE, this);
}

void MainWindow::exportSelectedEnvelopeAttachments(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::exportSelectedEnvelopeAndAttachments(originList, this);
}

void MainWindow::createEmailWithZfos(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::createEmailWithZfos(originList);
}

void MainWindow::createEmailWithAllAttachments(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

	GuiMsgOps::createEmailWithAllAttachments(originList);
}

void MainWindow::viewSaveAllAttachmentsDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);
	if (Q_UNLIKELY(originList.isEmpty())) {
		return;
	}

	/* Recall last path. */
	QString attSaveDir;
	if (PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
		attSaveDir = PrefsSpecific::saveAttachDir(
		    *GlobInstcs::prefsPtr);
	} else {
		attSaveDir = PrefsSpecific::acntSaveAttachDir(
		    *GlobInstcs::prefsPtr, acntId);
	}

	/* Ask whether the user wants to change the target path. */
	attSaveDir = QFileDialog::getExistingDirectory(this,
	    tr("Select Directory"), attSaveDir,
	    QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
	if (Q_UNLIKELY(attSaveDir.isEmpty())) {
		return;
	}

	/* Remember selected path. */
	if (!PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
		/* The path is not empty here. */
		PrefsSpecific::setAcntSaveAttachDir(*GlobInstcs::prefsPtr,
		    acntId, attSaveDir);
	}

	for (int i = 0; i < originList.size(); ++i) {
		const MsgOrigin &origin = originList.at(i);
		const QString &username = origin.acntIdDb.username();
		const QString dbId = GlobInstcs::accntDbPtr->dbId(
		    AccountDb::keyFromLogin(username));
		const QString accountName =
		    GlobInstcs::acntMapPtr->acntData(origin.acntIdDb).accountName();

		/* Save attachments and export zfo/pdf files. */
		QString errStr;
		if (Exports::EXP_SUCCESS == Exports::saveAttachmentsWithExports(
		        *origin.acntIdDb.messageDbSet(), attSaveDir, username,
		        accountName, dbId, origin.msgId, errStr)) {
			showStatusTextWithTimeout(errStr);
		} else {
			showStatusTextWithTimeout(errStr);
			const QString title = tr("Error Saving Attachments");
			const QString message =
			    tr("Some attachments of the message '%1' could not be saved to target directory '%2'.")
			        .arg(origin.msgId.dmId())
			        .arg(attSaveDir);
			if ((i + 1) < originList.size()) {
				/* Not last message. */
				int dlgRet = DlgMsgBox::message(this,
				    QMessageBox::Warning, title, message,
				    tr("There may be a problem with the storage which the files are written to.") +
				    QLatin1String("\n\n") +
				    tr("Do you want to cancel the remaining operations?"),
				    QString(), QMessageBox::Yes | QMessageBox::No,
				    QMessageBox::Yes);
				if (QMessageBox::Yes == dlgRet) {
					/* Cancel remaining exports. */
					return;
				}
			} else {
				QMessageBox::warning(this, title, message,
				    QMessageBox::Ok);
			}
		}
	}
}

void MainWindow::viewSaveSelectedAttachmentsDlgs(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());

	QModelIndexList attachmentIndexes(currentFrstColAttachmentIndexes());

	foreach (const QModelIndex &attachmentIndex, attachmentIndexes) {
		saveAttachmentToFile(acntId, msgId, attachmentIndex);
	}
}

void MainWindow::openSelectedAttachment(const QModelIndex &index)
{
	debugSlotCall();

	QString attachName;
	QString tmpPath;

	if (AttachmentInteraction::openAttachment(this,
	        *ui->attachmentList, index, &attachName, &tmpPath)) {
		showStatusTextWithTimeout(tr(
		    "Attachment '%1' stored into temporary file '%2'.")
		    .arg(attachName).arg(tmpPath));
	} else {
		showStatusTextWithTimeout(tr(
		    "Attachment '%1' couldn't be stored into temporary file.")
		    .arg(attachName));
	}
}

void MainWindow::deleteSelectedMessages(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}
	const QSet<MsgId> &msgIds = mwStatus.msgIds();
	if (Q_UNLIKELY(msgIds.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	bool delMsgIsds = false;
	{
		QString dlgTitleText, questionText, checkBoxText, detailText;

		if (1 == msgIds.size()) {
			qint64 dmId = firstMsgId(msgIds).dmId();
			dlgTitleText = tr("Delete Message '%1'").arg(dmId);
			questionText = tr("Do you want to delete the message '%1'?").arg(dmId);
			checkBoxText = tr("Delete this message also from the ISDS server.");
			detailText = tr("Warning:") + " " +
			    tr("If you delete the message from ISDS then this message will be lost forever.");
		} else {
			dlgTitleText = tr("Delete Messages");
			questionText = tr("Do you want to delete the selected messages?");
			checkBoxText = tr("Delete these messages also from the ISDS server.");
			detailText = tr("Warning:") + " " +
			    tr("If you delete the messages from ISDS then these messages will be lost forever.");
		}

		DlgYesNoCheckbox questionDlg(dlgTitleText, questionText,
		    checkBoxText, detailText, this);
		int retVal = questionDlg.exec();

		if (retVal == DlgYesNoCheckbox::YesChecked) {
			/* Delete messages in the local database and ISDS. */
			delMsgIsds = true;
		} else if (retVal == DlgYesNoCheckbox::YesUnchecked) {
			/* Delete messages only in local storage. */
			delMsgIsds = false;
		} else {
			/* Cancel the deletion. */
			return;
		}
	}

	const enum MessageDirection msgDirect =
	    messageDirection(mwStatus.acntNodeType(), MSG_RECEIVED);

	foreach (const MsgId &msgId, msgIds) {
		if (eraseMessage(acntId, msgDirect, msgId, delMsgIsds)) {
			/*
			 * Delete all tags from message_tags table.
			 * Tags in the tag table are kept.
			 *
			 * TODO -- Deleting tag association with a message may
			 * become undesirable if the tag database should be
			 * shared between multiple application instances.
			 */
			GlobInstcs::tagDbPtr->removeAllTagsFromMsg(
			    acntId.username(), acntId.testing(), msgId.dmId());
		}
	}

	/* Refresh account list. */
	refreshAccountList(acntId);
}

void MainWindow::viewFindDataBoxDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		return;
	}

	/* Method connectToIsds() acquires account information. */
	const Isds::DbOwnerInfo dbOwnerInfo(GlobInstcs::accntDbPtr->getOwnerInfo(
	    AccountDb::keyFromLogin(acntId.username())));
	if (dbOwnerInfo.isNull()) {
		return;
	}

	QString dbType(Isds::dbType2Str(dbOwnerInfo.dbType()));
	bool dbEffectiveOVM = (dbOwnerInfo.dbEffectiveOVM() == Isds::Type::BOOL_TRUE);
	bool dbOpenAddressing = (dbOwnerInfo.dbOpenAddressing() == Isds::Type::BOOL_TRUE);

	showStatusTextWithTimeout(tr("Find data boxes from account '%1'.")
	    .arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()));

	DlgDsSearch::search(acntId, dbType, dbEffectiveOVM, dbOpenAddressing,
	    this);
}

/*!
 * @brief Calls a message authentication task.
 *
 * @param[in] acntId Account identifier.
 * @param[in] fileName ZFO file name.
 * @return TaskAuthenticateMessage::Result.
 */
static
enum TaskAuthenticateMessage::Result authenticateMessageZFO(
    const AcntId &acntId, const QString &fileName)
{
	debugFuncCall();

	Q_ASSERT(acntId.isValid());
	Q_ASSERT(!fileName.isEmpty());

	TaskAuthenticateMessage *task =
	    new (::std::nothrow) TaskAuthenticateMessage(acntId, fileName);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return TaskAuthenticateMessage::AUTH_ERR;
	}
	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->runSingle(task);

	enum TaskAuthenticateMessage::Result result = task->m_result;
	delete task; task = Q_NULLPTR;

	return result;
}

void MainWindow::viewAuthenticateMessageDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		return;
	}

	const QString fileName = QFileDialog::getOpenFileName(this,
	    tr("Add ZFO file"), QString(), tr("ZFO file (*.zfo)"));
	if (fileName.isEmpty()) {
		return;
	}

	showStatusTextPermanently(tr("Authenticating the ZFO file '%1'.")
	    .arg(fileName));

	switch (authenticateMessageZFO(acntId, fileName)) {
	case TaskAuthenticateMessage::AUTH_SUCCESS:
		showStatusTextWithTimeout(
		    tr("The ISDS server confirms that the message is authentic."));
		QMessageBox::information(this, tr("Message Authentic"),
		    tr("The message was <b>successfully authenticated</b> against data on the ISDS server.") +
		    "<br/><br/>" +
		    tr("This message has passed through the ISDS system and has not been tampered with since."),
		    QMessageBox::Ok);
		break;
	case TaskAuthenticateMessage::AUTH_NOT_EQUAL:
		showStatusTextWithTimeout(
		    tr("The ISDS server informs that the message is not authentic."));
		QMessageBox::critical(this, tr("Message Not Authentic"),
		    tr("The message was <b>not</b> authenticated by the ISDS.") +
		    "<br/><br/>" +
		    tr("It is either not a valid ZFO file or it was modified since it was obtained from ISDS."),
		    QMessageBox::Ok);
		break;
	case TaskAuthenticateMessage::AUTH_ISDS_ERROR:
		showStatusTextWithTimeout(tr("Message authentication failed."));
		QMessageBox::warning(this, tr("Authentication Failed"),
		    tr("Authentication of the message has been interrupted because the connection to ISDS failed.") +
		    "<br/><br/>" +
		    tr("Check your internet connection."),
		    QMessageBox::Ok);
		break;
	case TaskAuthenticateMessage::AUTH_DATA_ERROR:
		showStatusTextWithTimeout(tr("Message authentication failed."));
		QMessageBox::warning(this, tr("Authentication Failed"),
		    tr("Authentication of the message has been stopped because the message file has a wrong format."),
		    QMessageBox::Ok);
		break;
	case TaskAuthenticateMessage::AUTH_ERR:
	default:
		showStatusTextWithTimeout(tr("Message authentication failed."));
		QMessageBox::warning(this, tr("Authentication Failed"),
		    tr("An undefined error occurred.") + "<br/><br/>" +
		    tr("Try it again later."),
		    QMessageBox::Ok);
		break;
	}
}

void MainWindow::viewMessageFromZfoDlg(void)
{
	debugSlotCall();

	QString fileName = QFileDialog::getOpenFileName(this,
	    tr("Add ZFO file"),
	    PrefsSpecific::viewZfoDir(*GlobInstcs::prefsPtr),
	    tr("ZFO file (*.zfo)"));

	if (fileName.isEmpty()) {
		return;
	}

	PrefsSpecific::setViewZfoDir(*GlobInstcs::prefsPtr,
	    QFileInfo(fileName).absoluteDir().absolutePath());

	/* Generate dialog showing message content. */
	DlgViewZfo::view(fileName, this);
}

void MainWindow::viewExportCorrespondenceOverviewDlg(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const QString dbId(
	    GlobInstcs::accntDbPtr->dbId(AccountDb::keyFromLogin(acntId.username())));

	DlgCorrespondenceOverview::exportData(*dbSet, dbId, acntId,
	    *GlobInstcs::tagDbPtr, *GlobInstcs::prefsPtr, this);
}

void MainWindow::viewTimestampExpirationDlg(void)
{
	debugSlotCall();

	enum DlgTimestampExpir::Action action =
	    DlgTimestampExpir::askAction(this);
	if (action != DlgTimestampExpir::CHECK_NOTHING) {
		prepareMsgTmstmpExpir(action);
	}
}

void MainWindow::viewMessageSearchDlg(void)
{
	debugSlotCall();

	if (m_messageSearchDlg != Q_NULLPTR) {
		if (!m_messageSearchDlg->isMinimized()) {
			m_messageSearchDlg->show();
		} else {
			m_messageSearchDlg->showNormal();
		}
		m_messageSearchDlg->raise();
		m_messageSearchDlg->activateWindow();
		return;
	}

	if (m_accountModel.rowCount() == 0) {
		return;
	}

	/* Get pointers to database sets of all accounts. */
	const QList<AcntIdDb> acntIdDbList(
	    allAcntIdDbList(m_accountModel, this));

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	m_messageSearchDlg = new (::std::nothrow) DlgMsgSearch(acntIdDbList,
	    acntId, this, this, Qt::Window);
	connect(m_messageSearchDlg, SIGNAL(focusSelectedMsg(AcntId, qint64, QString, int)),
	    this, SLOT(messageItemFromSearchSelection(AcntId, qint64, QString, int)));
	connect(m_messageSearchDlg, SIGNAL(finished(int)),
	    this, SLOT(closeMessageSearchDlg(int)));
	m_messageSearchDlg->show();
}

void MainWindow::closeMessageSearchDlg(int result)
{
	Q_UNUSED(result);
	debugSlotCall();

	m_messageSearchDlg->setAttribute(Qt::WA_DeleteOnClose, true);
	m_messageSearchDlg->deleteLater();
	m_messageSearchDlg = Q_NULLPTR;
}

void MainWindow::viewTagDlg(void)
{
	debugSlotCall();

	modifyTags(m_selectionStatus.mwStatus().acntId(), QList<qint64>());
}

void MainWindow::viewLogDlg(void)
{
	debugSlotCall();

	if (m_logDlg != Q_NULLPTR) {
		if (!m_logDlg->isMinimized()) {
			m_logDlg->show();
		} else {
			m_logDlg->showNormal();
		}
		m_logDlg->raise();
		m_logDlg->activateWindow();
		return;
	}

	m_logDlg = new (::std::nothrow) DlgViewLog(this, Qt::Window);
	if (Q_UNLIKELY(m_logDlg == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}
	connect(m_logDlg, SIGNAL(finished(int)),
	    this, SLOT(closedLogDlg(int)));
	m_logDlg->show();
}

void MainWindow::closedLogDlg(int result)
{
	Q_UNUSED(result);
	debugSlotCall();

	m_logDlg->setAttribute(Qt::WA_DeleteOnClose, true);
	m_logDlg->deleteLater();
	m_logDlg = Q_NULLPTR;
}

void MainWindow::viewAllSettingsDlg(void)
{
	debugSlotCall();

	DlgPrefs::modify(*GlobInstcs::prefsPtr, this);

	/* Set actual download timer values from settings if enabled. */
	manageBackgroundTimer(m_timerSyncAccounts, TA_ENFORCE_SETUP);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_ENFORCE_SETUP);
}

void MainWindow::viewAboutAppDlg(void)
{
	DlgAbout::about(updateChecker, this);
}

void MainWindow::openHomepageUrl(void) const
{
	QDesktopServices::openUrl(QUrl(DATOVKA_HOMEPAGE_URL,
	    QUrl::TolerantMode));
}

void MainWindow::openHelpUrl(void) const
{
	QDesktopServices::openUrl(QUrl(DATOVKA_ONLINE_HELP_URL,
	    QUrl::TolerantMode));
}

void MainWindow::accountMarkRecentReceivedRead(void)
{
	debugSlotCall();

	accountMarkRecentReceivedLocallyRead(true);
}

void MainWindow::accountMarkRecentReceivedUnread(void)
{
	debugSlotCall();

	accountMarkRecentReceivedLocallyRead(false);
}

void MainWindow::accountMarkRecentReceivedUnsettled(void)
{
	debugSlotCall();

	accountMarkRecentReceivedProcessState(MessageDb::UNSETTLED);
}

void MainWindow::accountMarkRecentReceivedInProgress(void)
{
	debugSlotCall();

	accountMarkRecentReceivedProcessState(MessageDb::IN_PROGRESS);
}

void MainWindow::accountMarkRecentReceivedSettled(void)
{
	debugSlotCall();

	accountMarkRecentReceivedProcessState(MessageDb::SETTLED);
}

void MainWindow::accountMarkReceivedYearRead(void)
{
	debugSlotCall();

	accountMarkReceivedYearLocallyRead(true);
}

void MainWindow::accountMarkReceivedYearUnread(void)
{
	debugSlotCall();

	accountMarkReceivedYearLocallyRead(false);
}

void MainWindow::accountMarkReceivedYearUnsettled(void)
{
	debugSlotCall();

	accountMarkReceivedYearProcessState(MessageDb::UNSETTLED);
}

void MainWindow::accountMarkReceivedYearInProgress(void)
{
	debugSlotCall();

	accountMarkReceivedYearProcessState(MessageDb::IN_PROGRESS);
}

void MainWindow::accountMarkReceivedYearSettled(void)
{
	debugSlotCall();

	accountMarkReceivedYearProcessState(MessageDb::SETTLED);
}

void MainWindow::messageItemsSelectedMarkRead(void)
{
	debugSlotCall();

	messageItemsSetReadStatus(true);
}

void MainWindow::messageItemsSelectedMarkUnread(void)
{
	debugSlotCall();

	messageItemsSetReadStatus(false);
}

void MainWindow::messageItemsSelectedMarkUnsettled(void)
{
	debugSlotCall();

	messageItemsSetProcessStatus(MessageDb::UNSETTLED);
}

void MainWindow::messageItemsSelectedMarkInProgress(void)
{
	debugSlotCall();

	messageItemsSetProcessStatus(MessageDb::IN_PROGRESS);
}

void MainWindow::messageItemsSelectedMarkSettled(void)
{
	debugSlotCall();

	messageItemsSetProcessStatus(MessageDb::SETTLED);
}

void MainWindow::messageItemsSelectedAssignTags(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	QList<qint64> dmIdList;
	foreach (const MsgId &msgId, mwStatus.msgIds()) {
		dmIdList.append(msgId.dmId());
	}

	modifyTags(mwStatus.acntId(), dmIdList);
}

void MainWindow::updateActionActivation(void)
{
	debugSlotCall();

	const int acntCnt = m_accountModel.rowCount(); /* Used multiple times. */
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	const bool acntIdValid = acntId.isValid(); /* Used many times. */
	const int numSelMsg = mwStatus.msgIds().size(); /* Used many times. */

	/* File menu. */
	ui->actionSynchroniseAllAccounts->setEnabled(
	    !mwStatus.flagBlockIsds() && (acntCnt > 0));
	ui->actionAddAccount->setEnabled(!mwStatus.flagBlockIsds());
	ui->actionRemoveAccount->setEnabled(acntIdValid);
	ui->actionAccountSettings->setEnabled(acntCnt > 0); /* TODO -- Should be always enabled. */
	ui->actionImportAccountFromDatabase->setEnabled(!mwStatus.flagBlockIsds());
	/* ui->actionBackUp */
	/* ui->actionRestore */
	/* ui->actionProxySettings */
	/* ui->actionRecordsManagementSettings */
	ui->actionUpdateRecordsManagementInformation->setEnabled(
	    GlobInstcs::recMgmtSetPtr->isValid());
	/* ui->actionPreferences */
	/* ui->actionQuit */

	/* Data box menu. */
	ui->menuDatabox->setEnabled(acntIdValid);
	ui->actionSynchroniseAccount->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid);
	ui->menuSynchronisation->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid);
	ui->actionSynchroniseAccountReceived->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid);
	ui->actionSynchroniseAccountSent->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid);
	ui->actionSendMessage->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid);
	ui->actionSendEGovRequest->setEnabled(
	    !mwStatus.flagBlockIsds() && acntIdValid && !acntId.testing());
	ui->menuMarkAllReceivedAs->setEnabled(acntIdValid);
	ui->actionMarkAllReceivedRead->setEnabled(acntIdValid);
	ui->actionMarkAllReceivedUnread->setEnabled(acntIdValid);
	ui->actionMarkAllReceivedUnsettled->setEnabled(acntIdValid);
	ui->actionMarkAllReceivedInProgress->setEnabled(acntIdValid);
	ui->actionMarkAllReceivedSettled->setEnabled(acntIdValid);
	ui->actionChangePassword->setEnabled(acntIdValid);
	ui->actionAccountProperties->setEnabled(acntIdValid);
	ui->actionMoveAccountUp->setEnabled(acntIdValid);
	ui->actionMoveAccountDown->setEnabled(acntIdValid);
#ifdef PORTABLE_APPLICATION
	ui->actionChangeDataDirectory->setEnabled(false);
#else /* !PORTABLE_APPLICATION */
	ui->actionChangeDataDirectory->setEnabled(acntIdValid);
#endif /* PORTABLE_APPLICATION */
	ui->actionImportMessagesFromDatabase->setEnabled(
	    !mwStatus.flagBlockImports() && acntIdValid);
	ui->actionImportZfoIntoDatabase->setEnabled(
	    !mwStatus.flagBlockImports() && acntIdValid);
	ui->actionVacuumMessageDatabase->setEnabled(acntIdValid);
	{
		const MessageDbSet *dbSet = Q_NULLPTR;
		if (acntIdValid) {
			dbSet = accountDbSet(acntId, this);
		}
		ui->actionSplitDatabaseYears->setEnabled((dbSet != Q_NULLPTR) &&
		    (MessageDbSet::DO_YEARLY != dbSet->organisation()));
	}

	/* Message menu. */
	ui->menuMessage->setEnabled(numSelMsg > 0);
	ui->actionDownloadMessage->setEnabled(numSelMsg > 0);
	ui->actionReply->setEnabled(
	    AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType()) &&
	    (numSelMsg == 1));
	ui->actionForward->setEnabled(numSelMsg > 0);
	ui->actionUseAsTemplate->setEnabled(numSelMsg == 1);
	ui->actionSignatureDetail->setEnabled(numSelMsg == 1);
	ui->actionVerifyMessage->setEnabled(numSelMsg == 1);
	ui->actionOpenMessageExternal->setEnabled(numSelMsg == 1);
	ui->actionOpenAcceptanceInfoExternal->setEnabled(numSelMsg == 1);
	ui->actionSendToRecordsManagement->setEnabled((numSelMsg == 1) &&
	    GlobInstcs::recMgmtSetPtr->isValid());
	ui->actionExportMessageZfo->setEnabled(numSelMsg > 0);
	ui->actionExportAcceptanceInfoZfo->setEnabled(numSelMsg > 0);
	ui->actionExportAcceptanceInfoPdf->setEnabled(numSelMsg > 0);
	ui->actionExportEnvelopePdf->setEnabled(numSelMsg > 0);
	ui->actionExportEnvelopePdfAndAttachments->setEnabled(numSelMsg > 0);
	ui->actionEmailZfos->setEnabled(numSelMsg > 0);
	ui->actionEmailAllAttachments->setEnabled(numSelMsg > 0);
	ui->actionSaveAllAttachments->setEnabled(numSelMsg > 0);
	{
		const int numSelAtt = currentFrstColAttachmentIndexes().size();
		ui->actionSaveSelectedAttachments->setEnabled(
		    (numSelMsg == 1) && (numSelAtt > 0));
		ui->actionOpenAttachment->setEnabled(
		    (numSelMsg == 1) && (numSelAtt == 1));
	}
	ui->actionDeleteMessage->setEnabled(
	    (AccountModel::nodeRecentReceived != mwStatus.acntNodeType()) &&
	    (AccountModel::nodeRecentSent != mwStatus.acntNodeType()) &&
	    (numSelMsg > 0));

	/* Tools menu. */
	ui->actionFindDataBox->setEnabled(
	    !mwStatus.flagBlockIsds() && (acntCnt > 0));
	ui->actionAuthenticateMessageFile->setEnabled(
	    !mwStatus.flagBlockIsds() && (acntCnt > 0));
	/* ui->actionViewMessageFile */
	ui->actionExportCorrespondenceOverview->setEnabled(acntIdValid);
	ui->actionCheckMessageTimestampExpiration->setEnabled(acntCnt > 0);
	ui->actionMessageSearch->setEnabled(acntCnt > 0);
	ui->actionTagSettings->setEnabled(acntIdValid);
	/* ui->actionViewLog */
	/* ui->actionViewAllSettings */

	/* Help. */
	/* ui->actionAboutApplication */
	/* ui->actionHomepage */
	/* ui->actionHelp */

	/* Actions that are not shown in the top menu. */
	ui->actionEmail_selected_attachments->setEnabled(numSelMsg == 1);

	/* Non-actions. */
	ui->messageStateCombo->setEnabled(
	    AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType()) &&
	    (numSelMsg > 0));
}

void MainWindow::updateAccountSelection(const QModelIndex &current,
    const QModelIndex &previous)
{
	debugSlotCall();

	Q_UNUSED(previous);

	if (Q_UNLIKELY(!current.isValid())) {
		m_selectionStatus.setAccount(AcntId(),
		    AccountModel::nodeUnknown, QString());
	} else {
		enum AccountModel::NodeType nodeType =
		    AccountModel::nodeType(current);
		QString year;
		if ((nodeType == AccountModel::nodeReceivedYear) ||
		    (nodeType == AccountModel::nodeSentYear)) {
			year = current.data(AccountModel::ROLE_PLAIN_DISPLAY).toString();
		}
		m_selectionStatus.setAccount(m_accountModel.acntId(current),
		    nodeType, year);
	}
}

void MainWindow::updateMessageSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	debugSlotCall();

	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	/*
	 * Obtain indexes of message ids column an convert them to message
	 * identifiers.
	 */
	m_selectionStatus.setMsgIds(idxs2MsgIdSet(
	    ui->messageList->selectionModel()->selectedRows(
	        DbMsgsTblModel::DMID_COL)));
}

/*!
 * @brief Generate overall account information.
 *
 * @param[in] receivedCounts Numbers of received messages.
 * @param[in] sentCounts Numbers of send messages.
 * @return Formatted summary.
 */
QString createAccountInfoAllField(const QString &accountName,
    const QList< QPair<QString, int> > &receivedCounts,
    const QList< QPair<QString, int> > &sentCounts)
{
	QString html = indentDivStart;
	html.append ("<h3>" + accountName.toHtmlEscaped() + "</h3>");

	html.append(strongAccountInfoLine(
	    MainWindow::tr("Received messages"), QString()));
	html.append(indentDivStart);
	if (0 == receivedCounts.size()) {
		html.append(MainWindow::tr("none"));
	} else {
		for (int i = 0; i < receivedCounts.size(); ++i) {
			html.append(accountInfoLine(receivedCounts[i].first,
			    QString::number(receivedCounts[i].second)));
		}
	}
	html.append(divEnd);

	html.append("<br/>");

	html.append(strongAccountInfoLine(
	    MainWindow::tr("Sent messages"), QString()));
	html.append(indentDivStart);
	if (0 == sentCounts.size()) {
		html.append(MainWindow::tr("none"));
	} else {
		for (int i = 0; i < sentCounts.size(); ++i) {
			html.append(accountInfoLine(sentCounts[i].first,
			    QString::number(sentCounts[i].second)));
		}
	}
	html.append(divEnd);

	html.append(divEnd);
	return html;
}

void MainWindow::adaptToAccountSelection(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId(mwStatus.acntId());
	const enum AccountModel::NodeType nodeType = mwStatus.acntNodeType();
	const QString &year = mwStatus.year();

	if (!acntId.isValid()) {
		/* May occur on deleting last account. */

		/* Clear message model and show banner page. */
		m_messageTableModel.removeAllRows();
		ui->messageStackedWidget->setCurrentIndex(SW_BANNER);
		ui->accountTextInfo->setHtml(
		    appBanner(QCoreApplication::applicationVersion()));
		ui->accountTextInfo->setReadOnly(true);
		return;
	}

	const MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		/* May occur on deleting last account. */

		/* Get username and database location. */
		const AcntData itemSettings =
		    GlobInstcs::acntMapPtr->acntData(acntId);

		QString dbDir = itemSettings.dbDir();
		if (dbDir.isEmpty()) {
			/* Set default directory name. */
			dbDir = GlobInstcs::iniPrefsPtr->confDir();
		}

		/* Clear message model and show banner page. */
		m_messageTableModel.removeAllRows();
		ui->messageStackedWidget->setCurrentIndex(SW_BANNER);
		QString htmlMessage = "<div style=\"margin-left: 12px;\">"
		    "<h3>" + tr("Database Access Error") + "</h3>" "<br/>";
		htmlMessage += "<div>";
		htmlMessage += tr("Database files for account '%1' cannot be accessed in location '%2'."
		    ).arg(acntId.username().toHtmlEscaped()).arg(dbDir.toHtmlEscaped());
		htmlMessage += "<br/>";
		htmlMessage += tr("The file cannot be accessed or is corrupted. "
		    "Please fix the access privileges or remove or rename the file so that the application can create a new empty file.");
		htmlMessage += "<br/><br/>";
		htmlMessage += tr("Create a backup copy of the affected file. "
		    "This will help when trying to perform data recovery.");
		htmlMessage += "<br/><br/>";
		htmlMessage += tr("In general, it is recommended to create backup copies of the database files to prevent data loss.");
		htmlMessage += "</div>";
		htmlMessage += "</div>";
		ui->accountTextInfo->setHtml(htmlMessage);
		ui->accountTextInfo->setReadOnly(true);
		return;
	}

	/* Clicked account item. */

	QString html;
	/* Depending which account item was clicked show/hide elements. */
	enum AccountModel::NodeType msgViewType = AccountModel::nodeUnknown;

	/* Reading database data may take some time. */
	QApplication::setOverrideCursor(Qt::WaitCursor);

	switch (nodeType) {
	case AccountModel::nodeAccountTop:
		html = DlgCreateAccount::htmlAccountInfo(
		    GlobInstcs::acntMapPtr->acntData(acntId),
		    accountDbSet(acntId, this));
		break;
	case AccountModel::nodeRecentReceived:
		m_messageTableModel.assignData(
		    dbSet->msgsRcvdEntriesWithin90Days(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		break;
	case AccountModel::nodeRecentSent:
		m_messageTableModel.assignData(
		    dbSet->msgsSntEntriesWithin90Days(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		break;
	case AccountModel::nodeAll:
		html = createAccountInfoAllField(tr("All messages"),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_RECEIVED,
		        DESCENDING),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_SENT, DESCENDING));
		break;
	case AccountModel::nodeReceived:
#ifdef DISABLE_ALL_TABLE
		html = createAccountInfoMessagesCount(
		    tr("All received messages"),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_RECEIVED,
		        DESCENDING),
		    MessageDb::TYPE_RECEIVED);
#else /* !DISABLE_ALL_TABLE */
		m_messageTableModel.assignData(dbSet->msgsEntriesRcvd(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
#endif /* DISABLE_ALL_TABLE */
		break;
	case AccountModel::nodeSent:
#ifdef DISABLE_ALL_TABLE
		html = createAccountInfoMessagesCount(
		    tr("All sent messages"),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_SENT, DESCENDING),
		    MessageDb::TYPE_SENT);
#else /* !DISABLE_ALL_TABLE */
		m_messageTableModel.assignData(dbSet->msgsSntEntries(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
#endif /* DISABLE_ALL_TABLE */
		break;
	case AccountModel::nodeReceivedYear:
		m_messageTableModel.assignData(
		    dbSet->msgsRcvdEntriesInYear(year),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		/* TODO -- Parameter check. */
		break;
	case AccountModel::nodeSentYear:
		m_messageTableModel.assignData(
		    dbSet->msgsSntEntriesInYear(year),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		/* TODO -- Parameter check. */
		break;
	default:
		logErrorNL("%s", "Cannot determine account node type.");
//		Q_ASSERT(0);
		goto end;
		break;
	}

	{
		m_messageTableModel.setRecordsManagementIcon();
		m_messageTableModel.fillRecordsManagementColumn(
		    DbMsgsTblModel::RECMGMT_NEG_COL);
		m_messageTableModel.fillTagsColumn(acntId.username(),
		    DbMsgsTblModel::TAGS_NEG_COL);
		/* TODO -- Add some labels. */
	}

	switch (nodeType) {
	case AccountModel::nodeAccountTop:
	case AccountModel::nodeAll:
#ifdef DISABLE_ALL_TABLE
	case AccountModel::nodeReceived:
	case AccountModel::nodeSent:
#endif /* DISABLE_ALL_TABLE */
		ui->messageStackedWidget->setCurrentIndex(SW_BANNER);
		ui->accountTextInfo->setHtml(html);
		ui->accountTextInfo->setReadOnly(true);
		break;
	case AccountModel::nodeRecentReceived:
#ifndef DISABLE_ALL_TABLE
	case AccountModel::nodeReceived:
#endif /* !DISABLE_ALL_TABLE */
	case AccountModel::nodeReceivedYear:
		/* Set model. */
		showMessageColumnsAccordingToFunctionality(ui->messageList,
		    m_messageTableModel.type());
		/* Set specific column width. */
		setReceivedColumnWidths();
		msgViewType = AccountModel::nodeReceived;
		break;
	case AccountModel::nodeRecentSent:
#ifndef DISABLE_ALL_TABLE
	case AccountModel::nodeSent:
#endif /* !DISABLE_ALL_TABLE */
	case AccountModel::nodeSentYear:
		/* Set model. */
		showMessageColumnsAccordingToFunctionality(ui->messageList,
		    m_messageTableModel.type());
		/* Set specific column width. */
		setSentColumnWidths();
		msgViewType = AccountModel::nodeSent;
		break;
	default:
		logErrorNL("%s", "Cannot determine account node type.");
//		Q_ASSERT(0);
		goto end;
		break;
	}

	/* Set model. */
	if (msgViewType != AccountModel::nodeUnknown) {
		ui->messageStackedWidget->setCurrentIndex(SW_MESSAGES);
		/* Apply message filter. */
		filterMessages(mui_filterLine->text());
		/* Clear message info. */
		ui->messageInfo->clear();

		/* Select last message in list if there are some messages. */
		if (m_messageListProxyModel.rowCount() > 0) {
			messageItemRestoreSelectionOnModelChange();
		}
	}

	/* Set specific column width. */
	switch (msgViewType) {
	case AccountModel::nodeReceived:
		setReceivedColumnWidths();
		break;
	case AccountModel::nodeSent:
		setSentColumnWidths();
		break;
	default:
		break;
	}

end:
	QApplication::restoreOverrideCursor();
}

void MainWindow::adaptToMessageSelection(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const QSet<MsgId> &msgIds = mwStatus.msgIds();

	/* Clear attachment list model. */
	m_attachmentModel.removeAllRows();
	/* Clear message information. */
	ui->messageInfo->setHtml(QString());
	ui->messageInfo->setReadOnly(true);

	/* Stop the timer. */
	m_messageMarker.stop();

	if (msgIds.isEmpty()) {
		/* Nothing selected. */
		messageItemStoreSelection(-1);
		/* End if nothing selected. */
		return;
	}

	if (1 != msgIds.size()) {
		/* Multiple messages selected - stop here. */
		return;
	}

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const MsgId &msgId = firstMsgId(msgIds);

	/* Remember last selected message. */
	messageItemStoreSelection(msgId.dmId());

	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	qint64 markReadTimeout = 0;
	if (!GlobInstcs::prefsPtr->intVal(
	        "window.main.message_list.read.mark_read.timeout.ms",
	        markReadTimeout)) {
		logErrorNL("Missing '%s' value.",
		    "window.main.message_list.read.mark_read.timeout.ms");
	}

	/* Mark message locally read. */
	if (!messageDb->messageLocallyRead(msgId.dmId())) {
		if (markReadTimeout >= 0) {
			logDebugLv1NL(
			    "Starting timer to mark as read for message '%" PRId64 "'.",
			    UGLY_QINT64_CAST msgId.dmId());
			m_messageMarker.setSingleShot(true);
			m_messageMarker.start(markReadTimeout);
		}
	} else {
		m_messageMarker.stop();
	}

	/* Generate and show message information. */
	ui->messageInfo->setHtml(messageDb->descriptionHtml(msgId.dmId()));
	ui->messageInfo->setReadOnly(true);

	if (AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType())) {
		bool iOk = false;
		enum MessageDb::MessageProcessState msgState =
		    messageDb->getMessageProcessState(msgId.dmId(), &iOk);

		/* State is not set when message is not in database. */
		if (iOk) {
			ui->messageStateCombo->setCurrentIndex(msgState);
		} else {
			/* insert message state into database */
			messageDb->setMessagesProcessState(
			    QList<MsgId>() << msgId, MessageDb::UNSETTLED);
			ui->messageStateCombo->setCurrentIndex(
			    MessageDb::UNSETTLED);
		}
	} else {
		ui->messageStateCombo->setCurrentIndex(MessageDb::UNSETTLED);
	}

	/* Show attachments. */
	m_attachmentModel.appendData(messageDb->attachEntries(msgId.dmId()));

	ui->attachmentList->resizeColumnToContents(
	    AttachmentTblModel::FNAME_COL);
}

void MainWindow::adaptToAttachmentSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	debugSlotCall();

	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	updateActionActivation();
}

void MainWindow::viewAccountListContextMenu(const QPoint &point)
{
	debugSlotCall();

	/*
	 * The slot updateAccountSelection() is called before this one
	 * if the selection changes with the right click.
	 */

	QModelIndex acntIdx = ui->accountList->indexAt(point);
	QMenu *menu = new (::std::nothrow) QMenu(ui->accountList);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	if (acntIdx.isValid()) {
		bool received = AccountModel::nodeIsReceived(acntIdx);

		menu->addAction(ui->actionSynchroniseAccount);
		{
			QMenu *submenu = menu->addMenu(tr("Synchronisation"));

			submenu->addAction(ui->actionSynchroniseAccountReceived);
			submenu->addAction(ui->actionSynchroniseAccountSent);
		}
		menu->addAction(ui->actionSendMessage);
		menu->addAction(ui->actionSendEGovRequest);
		menu->addSeparator();
		if (received) {
			QMenu *submenu = menu->addMenu(tr("Mark"));
			menu->addSeparator();

			switch (AccountModel::nodeType(acntIdx)) {
			case AccountModel::nodeRecentReceived:
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Read"), this,
				    SLOT(accountMarkRecentReceivedRead()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREEN_BALL),
				    tr("As Unread"), this,
				    SLOT(accountMarkRecentReceivedUnread()));

				submenu->addSeparator();
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_RED_BALL),
				    tr("As Unsettled"), this,
				    SLOT(accountMarkRecentReceivedUnsettled()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_YELLOW_BALL),
				    tr("As in Progress"), this,
				    SLOT(accountMarkRecentReceivedInProgress()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Settled"), this,
				    SLOT(accountMarkRecentReceivedSettled()));
				break;
			case AccountModel::nodeReceived:
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Read"), this,
				    SLOT(accountMarkReceivedRead()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREEN_BALL),
				    tr("As Unread"), this,
				    SLOT(accountMarkReceivedUnread()));

				submenu->addSeparator();
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_RED_BALL),
				    tr("As Unsettled"), this,
				    SLOT(accountMarkReceivedUnsettled()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_YELLOW_BALL),
				    tr("As in Progress"), this,
				    SLOT(accountMarkReceivedInProgress()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Settled"), this,
				    SLOT(accountMarkReceivedSettled()));
				break;
			case AccountModel::nodeReceivedYear:
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Read"), this,
				    SLOT(accountMarkReceivedYearRead()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREEN_BALL),
				    tr("As Unread"), this,
				    SLOT(accountMarkReceivedYearUnread()));

				submenu->addSeparator();
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_RED_BALL),
				    tr("As Unsettled"), this,
				    SLOT(accountMarkReceivedYearUnsettled()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_YELLOW_BALL),
				    tr("As in Progress"), this,
				    SLOT(accountMarkReceivedYearInProgress()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_GREY_BALL),
				    tr("As Settled"), this,
				    SLOT(accountMarkReceivedYearSettled()));
				break;
			default:
				Q_ASSERT(0);
				break;
			}
		}
		menu->addAction(ui->actionChangePassword);
		menu->addSeparator();
		menu->addAction(ui->actionAccountProperties);
		menu->addAction(ui->actionRemoveAccount);
		menu->addSeparator();
		menu->addAction(ui->actionMoveAccountUp);
		menu->addAction(ui->actionMoveAccountDown);
		menu->addSeparator();
		menu->addAction(ui->actionChangeDataDirectory);

		menu->addSeparator();
		menu->addAction(ui->actionImportMessagesFromDatabase);
		menu->addAction(ui->actionImportZfoIntoDatabase);
	} else {
		menu->addAction(ui->actionAddAccount);
	}

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

void MainWindow::viewMessageListContextMenu(const QPoint &point)
{
	debugSlotCall();

	/*
	 * The slot updateMessageSelection() is called before this one
	 * if the selection changes with the right click.
	 */

	if (!ui->messageList->indexAt(point).isValid()) {
		/* Do nothing. */
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	bool singleSelected = (1 == mwStatus.msgIds().size());
	bool received = AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType());

	QMenu *menu = new (::std::nothrow) QMenu(ui->messageList);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	/*
	 * Remember last selected message. Pick the first from
	 * the current selection.
	 *
	 * TODO -- Save whole selection?
	 */
	if (!mwStatus.msgIds().isEmpty()) {
		messageItemStoreSelection(firstMsgId(mwStatus.msgIds()).dmId());
	}

	/* TODO use QAction::iconText() instead of direct strings here. */

	menu->addAction(ui->actionDownloadMessage);
	if (singleSelected) {
		menu->addAction(ui->actionReply);
	}
	menu->addAction(ui->actionForward);
	if (singleSelected) {
		menu->addAction(ui->actionUseAsTemplate);
	}
	menu->addSeparator();
	if (singleSelected) {
		menu->addAction(ui->actionSignatureDetail);
		menu->addAction(ui->actionVerifyMessage);
		menu->addSeparator();
		menu->addAction(ui->actionOpenMessageExternal);
		menu->addAction(ui->actionOpenAcceptanceInfoExternal);
		menu->addSeparator();
		menu->addAction(ui->actionSendToRecordsManagement);
		menu->addSeparator();
	}
	menu->addAction(ui->actionExportMessageZfo);
	menu->addAction(ui->actionExportAcceptanceInfoZfo);
	menu->addAction(ui->actionExportAcceptanceInfoPdf);
	menu->addAction(ui->actionExportEnvelopePdf);
	menu->addAction(ui->actionExportEnvelopePdfAndAttachments);
	menu->addSeparator();
	menu->addAction(ui->actionEmailZfos);
	menu->addAction(ui->actionEmailAllAttachments);
	menu->addSeparator();

	if (received) {
		QMenu *submenu = menu->addMenu(tr("Mark"));
		menu->addSeparator();

		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL),
		    tr("As Read"), this,
		    SLOT(messageItemsSelectedMarkRead()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_GREEN_BALL),
		    tr("As Unread"), this,
		    SLOT(messageItemsSelectedMarkUnread()));

		submenu->addSeparator();
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_RED_BALL),
		    tr("As Unsettled"), this,
		    SLOT(messageItemsSelectedMarkUnsettled()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_YELLOW_BALL),
		    tr("As in Progress"), this,
		    SLOT(messageItemsSelectedMarkInProgress()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL),
		    tr("As Settled"), this,
		    SLOT(messageItemsSelectedMarkSettled()));
	}
	menu->addAction(IconContainer::construcIcon(IconContainer::ICON_LABEL),
	    ui->actionTagSettings->text(), this,
	    SLOT(messageItemsSelectedAssignTags()));

	menu->addSeparator();

	menu->addAction(ui->actionDeleteMessage);

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

void MainWindow::viewAttachmentListContextMenu(const QPoint &point)
{
	debugSlotCall();

	if (!ui->attachmentList->indexAt(point).isValid()) {
		/* Do nothing. */
		return;
	}

	const int numSelAtt = currentFrstColAttachmentIndexes().size();
	if (Q_UNLIKELY(numSelAtt == 0)) {
		Q_ASSERT(0);
		return;
	}

	QMenu *menu = new (::std::nothrow) QMenu(ui->attachmentList);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	if (numSelAtt == 1) {
		menu->addAction(ui->actionOpenAttachment);
	}
	menu->addAction(ui->actionSaveSelectedAttachments);
	menu->addSeparator();
	menu->addAction(ui->actionEmail_selected_attachments);

	menu->exec(QCursor::pos());
	menu->deleteLater();
}

/*!
 * @brief Update numbers of unread messages in account model.
 *
 * @note Does not add/remove any nodes, just updates the counts.
 *
 * @param[in,out] accountModel Model to be updated.
 * @param[in]     acntId Account identifier.
 * @param[in]     parent Parent widget.
 * @return True on success.
 */
static
bool updateExistingAccountModelUnread(AccountModel &accountModel,
    const AcntId &acntId, QWidget *parent)
{
	/*
	 * Several nodes may be updated at once, because some messages may be
	 * referred from multiple nodes.
	 */

	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return false;
	}

	QList<QString> yearList;
	int unreadMsgs;

	/* Get database id. */
	MessageDbSet *dbSet = accountDbSet(acntId, parent);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return false;
	}

	/* Received. */
	unreadMsgs = dbSet->msgsUnreadWithin90Days(MessageDb::TYPE_RECEIVED);
	accountModel.updateRecentUnread(acntId,
	    AccountModel::nodeRecentReceived, unreadMsgs);
	yearList = dbSet->msgsYears(MessageDb::TYPE_RECEIVED, DESCENDING);
	for (int j = 0; j < yearList.size(); ++j) {
		unreadMsgs = dbSet->msgsUnreadInYear(MessageDb::TYPE_RECEIVED,
		    yearList.value(j));
		AccountModel::YearCounter yCounter(unreadMsgs != -2,
		    (unreadMsgs > 0) ? unreadMsgs : 0);
		accountModel.updateYear(acntId, AccountModel::nodeReceivedYear,
		    yearList.value(j), yCounter);
	}
	/* Sent. */
	//unreadMsgs = dbSet->msgsUnreadWithin90Days(MessageDb::TYPE_SENT);
	accountModel.updateRecentUnread(acntId, AccountModel::nodeRecentSent,
	    0);
	yearList = dbSet->msgsYears(MessageDb::TYPE_SENT, DESCENDING);
	for (int j = 0; j < yearList.size(); ++j) {
		unreadMsgs = dbSet->msgsUnreadInYear(MessageDb::TYPE_SENT,
		    yearList.value(j));
		AccountModel::YearCounter yCounter(unreadMsgs != -2, 0);
		accountModel.updateYear(acntId, AccountModel::nodeSentYear,
		    yearList.value(j), yCounter);
	}
	return true;
}

void MainWindow::messageItemClicked(const QModelIndex &index)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	if (!AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType())) {
		logDebugLv1NL("%s", "Not clicked received message.");
		return;
	}

	if (DbMsgsTblModel::READLOC_COL != index.column()) {
		logDebugLv1NL("%s", "Not clicked read locally.");
		return;
	}

	/* Stop the timer. */
	m_messageMarker.stop();

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const MsgId msgId(idx2MsgId(index));

	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	/* Get message state from database and toggle the value. */
	bool isRead = messageDb->messageLocallyRead(msgId.dmId());
	messageDb->setMessagesLocallyRead(QList<MsgId>() << msgId, !isRead);
}

void MainWindow::viewSelectedMessage(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		/*
		 * Can happen when navigating through control elements and
		 * enter is pressed.
		 */
		return;
	}
	MsgId msgId = firstMsgId(mwStatus.msgIds());
	Q_ASSERT(msgId.dmId() >= 0);

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	QByteArray msgRaw(messageDb->getCompleteMessageRaw(msgId.dmId()));
	if (msgRaw.isEmpty()) {

		if (!messageMissingOfferDownload(msgId,
		        tr("Missing Message Content"))) {
			return;
		}

		messageDb = dbSet->accessMessageDb(msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			logErrorNL(
			    "Could not access database of freshly downloaded message '%" PRId64 "'.",
			    UGLY_QINT64_CAST msgId.dmId());
			return;
		}

		msgRaw = messageDb->getCompleteMessageRaw(msgId.dmId());
		if (Q_UNLIKELY(msgRaw.isEmpty())) {
			Q_ASSERT(0);
			return;
		}
	}

	/* Generate dialogue showing message content. */
	DlgViewZfo::view(msgRaw, this);
}

void MainWindow::watchChangedLocallyRead(const AcntId &acntId,
    const QList<MsgId> &msgIds, bool read)
{
	debugSlotCall();

	if (Q_UNLIKELY((!acntId.isValid()) || msgIds.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &currentAcntId = mwStatus.acntId();
	if (Q_UNLIKELY(!currentAcntId.isValid())) {
		/* Current index may be empty when the account is changed. */
		return;
	}

	/* Do nothing if account has been changed. */
	if (Q_UNLIKELY(acntId != currentAcntId)) {
		return;
	}

	/* Mark message as read without reloading the whole model. */
	if (msgIds.size() == 1) {
		m_messageTableModel.overrideRead(msgIds.at(0).dmId(), read);
	} else {
		m_messageTableModel.overrideRead(msgIds, read);
	}

	/* Reload/update account model only for affected account. */
	updateExistingAccountModelUnread(m_accountModel, acntId, this);
}

void MainWindow::watchChangedProcessState(const AcntId &acntId,
    const QList<MsgId> &msgIds, enum MessageDb::MessageProcessState state)
{
	debugSlotCall();

	if (Q_UNLIKELY((!acntId.isValid()) || msgIds.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &currentAcntId = mwStatus.acntId();
	if (Q_UNLIKELY(!currentAcntId.isValid())) {
		/* Current index may be empty when the account is changed. */
		return;
	}

	/* Do nothing if account has been changed. */
	if (Q_UNLIKELY(acntId != currentAcntId)) {
		return;
	}

	/* Set new state without reloading the whole model. */
	if (msgIds.size() == 1) {
		m_messageTableModel.overrideProcessing(msgIds.at(0).dmId(),
		    state);
	} else {
		m_messageTableModel.overrideProcessing(msgIds, state);
	}

	/* No need to reload/update account model for affected account. */

	/* Update message state combo. */
	if (AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType())) {
		const QSet<MsgId> &currentMsgIds = mwStatus.msgIds();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
		const QSet<MsgId> changedMsgIds(msgIds.begin(), msgIds.end());
#else /* < Qt-5.14.0 */
		const QSet<MsgId> changedMsgIds = msgIds.toSet();
#endif /* >= Qt-5.14.0 */
		/* All currently selected have been updated. */
		if (changedMsgIds.contains(currentMsgIds)) {
			ui->messageStateCombo->setCurrentIndex(state);
		}
	}
}

void MainWindow::watchMessageInserted(const AcntId &acntId, const MsgId &msgId,
    int direction)
{
	debugSlotCall();

	Q_UNUSED(direction);

	if (Q_UNLIKELY((!acntId.isValid()) || (!msgId.isValid()))) {
		Q_ASSERT(0);
		return;
	}

	showStatusTextWithTimeout(
	    tr("Message '%1' has been downloaded from server.")
	        .arg(msgId.dmId()));

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &currentAcntId = mwStatus.acntId();
	if (Q_UNLIKELY(!currentAcntId.isValid())) {
		/* Current index may be empty when the account is changed. */
		return;
	}

	/* Do nothing if account has been changed. */
	if (Q_UNLIKELY(acntId != currentAcntId)) {
		return;
	}

	/*
	 * Mark message as having attachment downloaded without reloading
	 * the whole model.
	 */
	m_messageTableModel.overrideDownloaded(msgId.dmId(), true);

	if (mwStatus.msgIds().size() != 1) {
		/* There is no focus on a single message. */
		return;
	}

	const MsgId &currentMsgId = firstMsgId(mwStatus.msgIds());
	/* Do nothing if message has been changed. */
	if ((msgId.dmId() != currentMsgId.dmId())) {
		return;
	}

	/*
	 * TODO -- Create a separate function for reloading attachment
	 * contents. Similar code is used for handling message selection
	 * changes.
	 */

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	MessageDb *messageDb =
	    dbSet->accessMessageDb(currentMsgId.deliveryTime(), false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	/* Generate and show message information. */
	ui->messageInfo->setHtml(messageDb->descriptionHtml(msgId.dmId()));
	ui->messageInfo->setReadOnly(true);

	m_attachmentModel.removeAllRows();
	m_attachmentModel.appendData(messageDb->attachEntries(msgId.dmId()));

	ui->attachmentList->resizeColumnToContents(
	    AttachmentTblModel::FNAME_COL);
}

void MainWindow::watchMessageDataDeleted(const AcntId &acntId,
    const MsgId &msgId)
{
	debugSlotCall();

	/* Deleted message may have invalid delivery time. */
	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &currentAcntId = mwStatus.acntId();
	if (Q_UNLIKELY(!currentAcntId.isValid())) {
		/* Current index may be empty when the account is changed. */
		return;
	}

	/* Do nothing if account has been changed. */
	if (Q_UNLIKELY(acntId != currentAcntId)) {
		return;
	}

	/* Delete message entry from model. */
	m_messageTableModel.remove(msgId.dmId());

	/* Reload/update account model only for affected account. */
	updateExistingAccountModelUnread(m_accountModel, acntId, this);

	/*
	 * TODO -- Remove the year in account list if last message
	 * has been removed.
	 */
}

void MainWindow::watchTagUpdated(const QList<TagEntry> &entries)
{
	foreach (const TagEntry &entry, entries) {
		m_messageTableModel.updateTag(entry);
	}
}

void MainWindow::watchTagDeleted(const QList<int> &ids)
{
	m_messageTableModel.deleteTags(ids);
}

void MainWindow::watchTagAssignmentChanged(
    const QList<TagAssignment> &assignments)
{
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &currentAcntId = mwStatus.acntId();
	if (Q_UNLIKELY(!currentAcntId.isValid())) {
		/* Current index may be empty when the account is changed. */
		return;
	}

	foreach (const TagAssignment &assignment, assignments) {
		const AcntId acntId(assignment.username(), assignment.testing());
		/* Do nothing if account has been changed. */
		if (Q_UNLIKELY(acntId != currentAcntId)) {
			continue;
		}
		m_messageTableModel.updateTagAssignment(assignment.dmId(), assignment.entries());
	}
}

/*!
 * @brief Return index for message with given properties.
 *
 * @param[in] view Message table view.
 * @param[in] dmId Message identifier.
 * @return Index of given message.
 */
static inline
QModelIndex messageIndex(const QTableView *view, qint64 dmId)
{
	if (Q_UNLIKELY((Q_NULLPTR == view) || (dmId < 0))) {
		Q_ASSERT(0);
		return QModelIndex();
	}

	const QAbstractItemModel *model = view->model();
	if (Q_UNLIKELY(Q_NULLPTR == model)) {
		Q_ASSERT(0);
		return QModelIndex();
	}

	/* Find and select the message with the ID. */
	for (int row = 0; row < model->rowCount(); ++row) {
		/*
		 * TODO -- Search in a more resource-saving way.
		 * Eliminate index copying, use smarter search.
		 */
		if (model->index(row, 0).data().toLongLong() == dmId) {
			return model->index(row, 0);
		}
	}

	return QModelIndex();
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
    ui(new (::std::nothrow) Ui::MainWindow),
    m_selectionStatus(this),
    m_accountModel(this),
    m_messageTableModel(DbMsgsTblModel::RCVD_MODEL, this),
    m_messageListProxyModel(this),
    m_attachmentModel(false, this),
    m_messageMarker(this),
    m_lastSelectedMessageId(-1),
    m_lastStoredMessageId(-1),
    m_lastSelectedAccountNodeType(AccountModel::nodeUnknown),
    m_lastStoredAccountNodeType(AccountModel::nodeUnknown),
    m_messageSearchDlg(Q_NULLPTR),
    m_logDlg(Q_NULLPTR),
    m_colWidth(COL_WIDTH_COUNT),
    m_sortCol(DFLT_COL_SORT),
    m_sort_order(),
    m_geometry(),
    m_msgTblAppendedCols(),
    mui_filterLine(Q_NULLPTR),
    mui_statusBar(Q_NULLPTR),
    mui_statusDbMode(Q_NULLPTR),
    mui_statusOnlineLabel(Q_NULLPTR),
    mui_statusProgressBar(Q_NULLPTR),
    mui_dockMenu()
{
	m_colWidth[0] = m_colWidth[1] = m_colWidth[2] = DFLT_COL_WIDTH;

	setUpUi();

	m_msgTblAppendedCols.append(DbMsgsTblModel::AppendedCol(QString(),
	    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE_GREY),
	    tr("Uploaded to records management service")));
	m_msgTblAppendedCols.append(DbMsgsTblModel::AppendedCol(
	    tr("Tags"), QIcon(), tr("User-assigned tags")));

	connect(&mui_dockMenu, SIGNAL(aboutToShow()),
	    this, SLOT(dockMenuPopulate()));
	connect(&mui_dockMenu, SIGNAL(triggered(QAction *)),
	    this, SLOT(dockMenuActionTriggerred(QAction *)));
#ifdef Q_OS_OSX
	mui_dockMenu.setAsDockMenu();
#endif /* Q_OS_OSX */

	/* Single instance emitter. */
	connect(GlobInstcs::snglInstEmitterPtr,
	    SIGNAL(messageReceived(int, QString)), this,
	    SLOT(processSingleInstanceMessages(int, QString)));

	/* Worker-related processing signals. */
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(downloadMessageFinished(AcntId, qint64, QDateTime, int,
	        QString, int, QString)), this,
	    SLOT(collectDownloadMessageStatus(AcntId, qint64, QDateTime, int,
	        QString, int, QString)));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(downloadMessageListFinished(AcntIdDb, AcntId, int, int,
	        QString, bool, int, int, int, int)), this,
	    SLOT(collectDownloadMessageListStatus(AcntIdDb, AcntId, int, int,
	        QString, bool, int, int, int, int)));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(importZfoFinished(QString, int, QString)), this,
	    SLOT(collectImportZfoStatus(QString, int, QString)));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(importMessageFinished(QString, QStringList, int, int)), this,
	    SLOT(showImportMessageResults(QString, QStringList, int, int)));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(progressChange(QString, int)), this,
	    SLOT(updateProgressBar(QString, int)));
	connect(GlobInstcs::msgProcEmitterPtr, SIGNAL(statusBarChange(QString)),
	    this, SLOT(updateStatusBarText(QString)));
	connect(GlobInstcs::msgProcEmitterPtr,
	    SIGNAL(sendMessageFinished(AcntId, QString, int, QString,
	        QString, QString, bool, qint64, int, QString)), this,
	    SLOT(collectSendMessageStatus(AcntId, QString, int, QString,
	        QString, QString, bool, qint64, int, QString)));

	connect(GlobInstcs::workPoolPtr, SIGNAL(assignedFinished()),
	    this, SLOT(backgroundWorkersFinished()));

	/* Account list. */
	ui->accountList->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->accountList, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewAccountListContextMenu(QPoint)));
	ui->accountList->setSelectionMode(QAbstractItemView::SingleSelection);

	/* Message list. */
	ui->messageList->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->messageList, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewMessageListContextMenu(QPoint)));
	ui->messageList->setSelectionMode(QAbstractItemView::ExtendedSelection);
	ui->messageList->setSelectionBehavior(QAbstractItemView::SelectRows);
	ui->messageList->setFocusPolicy(Qt::StrongFocus);
	connect(ui->messageList, SIGNAL(doubleClicked(QModelIndex)), this,
	    SLOT(viewSelectedMessage()));
	ui->messageList->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(ui->messageList));
	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(ui->messageList);
		filter->registerAction(Qt::Key_Return,
		    &viewSelectedMessageViaFilter, this, true);
		filter->registerAction(Qt::Key_Enter, /* On keypad. */
		    &viewSelectedMessageViaFilter, this, true);
		ui->messageList->installEventFilter(filter);
	}
	ui->messageList->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(ui->messageList));
	ui->messageList->setItemDelegate(new (::std::nothrow) TagsDelegate(this));

	ui->accountList->setModel(&m_accountModel);
	m_messageListProxyModel.setSourceModel(&m_messageTableModel);
	m_messageListProxyModel.setSortRole(DbMsgsTblModel::ROLE_PROXYSORT);
	ui->messageList->setModel(&m_messageListProxyModel);
	ui->attachmentList->setModel(&m_attachmentModel);

	m_attachmentModel.setHeader();
	/* First three columns contain hidden data. */
	ui->attachmentList->setColumnHidden(AttachmentTblModel::ATTACHID_COL, true);
	ui->attachmentList->setColumnHidden(AttachmentTblModel::MSGID_COL, true);
	ui->attachmentList->setColumnHidden(AttachmentTblModel::BINARY_CONTENT_COL, true);
	ui->attachmentList->setColumnHidden(AttachmentTblModel::MIME_COL, true);
	ui->attachmentList->setColumnHidden(AttachmentTblModel::FPATH_COL, true);

#if 1
	/*
	 * If the 'opened' signal is connected before loadSettings() is called
	 * then an unnecessary series of model updates is called into action.
	 * However, the code must still be able to handle such order of action.
	 * So don't remove this comment or the code inside the following block
	 * which has been disabled using the preprocessor conditional because
	 * it may be used for testing.
	 */

	/* Load configuration file. */
	loadSettings();

	/* Handle opening of database files on demand. */
	connect(GlobInstcs::msgDbsPtr, SIGNAL(opened(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
#else
	connect(GlobInstcs::msgDbsPtr, SIGNAL(opened(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));

	loadSettings();
#endif

	/* Set toolbar buttons style from settings */
	{
		qint64 val = PrefsSpecific::TEXT_UNDER_ICON;
		if (GlobInstcs::prefsPtr->intVal(
		        "window.main.toolbar.button.style", val)) {
			ui->toolBar->setToolButtonStyle(
			    (Qt::ToolButtonStyle)PrefsSpecific::qtToolButtonStyle(val));
		}
	}

	connect(&m_selectionStatus, SIGNAL(changed()),
	    this, SLOT(updateActionActivation()));

	/* Account list must already be set in order to connect this signal. */
	connect(ui->accountList->selectionModel(),
	    SIGNAL(currentChanged(QModelIndex, QModelIndex)), this,
	    SLOT(updateAccountSelection(QModelIndex, QModelIndex)));
	connect(&m_selectionStatus, SIGNAL(accountChanged()),
	    this, SLOT(adaptToAccountSelection()));

	/* Message list, ui->messageList->model() refers to proxy model. */
	connect(ui->messageList, SIGNAL(clicked(QModelIndex)),
	    this, SLOT(messageItemClicked(QModelIndex)));
	connect(ui->messageList->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
	    SLOT(updateMessageSelection(QItemSelection, QItemSelection)));
	connect(&m_selectionStatus, SIGNAL(messageChanged()),
	    this, SLOT(adaptToMessageSelection()));
	connect(ui->messageList->model(), SIGNAL(layoutAboutToBeChanged()),
	    this, SLOT(messageItemStoreSelectionOnModelChange()));
	connect(ui->messageList->model(), SIGNAL(layoutChanged()),
	    this, SLOT(messageItemRestoreSelectionAfterLayoutChange()));
	/* TODO -- modelAboutToBeReset() modelReset() */

	/* Enable drag and drop on account list. */
	ui->accountList->setAcceptDrops(true);
	ui->accountList->setDragEnabled(true);
	ui->accountList->setDragDropOverwriteMode(false);
	ui->accountList->setDropIndicatorShown(true);
	ui->accountList->setDragDropMode(QAbstractItemView::InternalMove);
	ui->accountList->setDefaultDropAction(Qt::MoveAction);

	/* Enable sorting of message table items. */
	ui->messageList->setSortingEnabled(true);

	/* Set default column size. */
	/* TODO -- Check whether received or sent messages are shown? */
	setReceivedColumnWidths();

	/* Attachment list. */
	connect(ui->attachmentList->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this, SLOT(adaptToAttachmentSelection(QItemSelection,
	        QItemSelection)));
	ui->attachmentList->setContextMenuPolicy(Qt::CustomContextMenu);
	ui->attachmentList->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	ui->attachmentList->setSelectionBehavior(QAbstractItemView::SelectRows);
	connect(ui->attachmentList, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewAttachmentListContextMenu(QPoint)));
	connect(ui->attachmentList, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(openSelectedAttachment(QModelIndex)));
	ui->attachmentList->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(ui->attachmentList));
	ui->attachmentList->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(ui->attachmentList));

	/* It fires when any column was resized. */
	connect(ui->messageList->horizontalHeader(),
	    SIGNAL(sectionResized(int, int, int)),
	    this, SLOT(onTableColumnResized(int, int, int)));

	/* It fires when any column was clicked. */
	connect(ui->messageList->horizontalHeader(),
	    SIGNAL(sectionClicked(int)),
	    this, SLOT(onTableColumnHeaderSectionClicked(int)));

	/* Connect non-automatic menu actions. */
	topMenuConnectActions();
	messageBarAssignActions();
	databaseConnectActions();

	/* Message marking timer. */
	connect(&m_messageMarker, SIGNAL(timeout()),
	    this, SLOT(messageItemsSelectedMarkRead()));

	/* Initialisation of message download timers. */
	connect(&m_timerSyncAccounts, SIGNAL(timeout()), this,
	    SLOT(synchroniseAllAccounts()));
	manageBackgroundTimer(m_timerSyncAccounts, TA_ENFORCE_SETUP);
	connect(&m_timerShadowSyncAccounts, SIGNAL(timeout()), this,
	    SLOT(synchroniseAllShadowAccounts()));
	manageShadowTimer(m_timerShadowSyncAccounts, TA_ENFORCE_SETUP);

	/* Select last used account. */
	setDefaultAccount(*GlobInstcs::prefsPtr);

	if (updateChecker == Q_NULLPTR) {
		updateChecker = new (::std::nothrow) UpdateChecker(
		    *GlobInstcs::prefsPtr, *GlobInstcs::proxSetPtr, this);
	}
	QTimer::singleShot(RUN_FIRST_ACTION_MS, this,
	    SLOT(setWindowsAfterInit()));

	QString msgStrg(tr("disk"));
	QString acntStrg(tr("disk"));

	if (!PrefsSpecific::messageDbOnDisk(*GlobInstcs::prefsPtr)) {
		msgStrg = tr("memory");
	}

	if (!PrefsSpecific::accountDbOnDisk(*GlobInstcs::prefsPtr)) {
		acntStrg = tr("memory");
	}

	mui_statusDbMode->setText(tr("Storage:") + " " + msgStrg + " | "
	    + acntStrg + "   ");
}

void MainWindow::setWindowsAfterInit(void)
{
	debugSlotCall();

	/* Always send the request for new version check. */
	if (updateChecker != Q_NULLPTR) {
		if (PrefsSpecific::checkNewVersions(*GlobInstcs::prefsPtr)) {
			connect(updateChecker, SIGNAL(newerVersionFound(QString)),
			    this, SLOT(viewAboutAppDlg()));
		}
		updateChecker->startVersionCheck(
		    PrefsSpecific::checkNewVersionsCooldown(*GlobInstcs::prefsPtr));
	}

	if (m_accountModel.rowCount() <= 0) {
		viewAddAccountDlg();
	} else {
		if (PrefsSpecific::shadowSyncTimerMinutes(*GlobInstcs::prefsPtr) > 0) {
			m_timerShadowSyncAccounts.start(100); /* Run in 0.1 seconds. */
		}

		bool val = false;
		GlobInstcs::prefsPtr->boolVal(
		    "window.main.on.start.tie.action.sync_all_accounts", val);
		if (val) {
			/*
			 * Calling account synchronisation as slot bound to
			 * different events is repeatedly causing problems.
			 * Here we try to minimise the probability that the
			 * synchronisation  action is triggered for the second
			 * time after the application start-up.
			 *
			 * If the timer is running then shorten the interval.
			 * If it is not running then run once.
			 */
			m_timerSyncAccounts.start(3000); /* Run in 3 seconds. */
		}
	}
}

void MainWindow::showStatusTextWithTimeout(const QString &qStr)
{
	clearStatusBar();
	mui_statusBar->showMessage((qStr), TIMER_STATUS_TIMEOUT_MS);
}

void MainWindow::showStatusTextPermanently(const QString &qStr)
{
	clearStatusBar();
	mui_statusBar->showMessage((qStr), 0);
}

void MainWindow::clearProgressBar(void)
{
	mui_statusProgressBar->setFormat(PL_IDLE);
	mui_statusProgressBar->setValue(0);
}

void MainWindow::clearStatusBar(void)
{
	mui_statusBar->clearMessage();
}

void MainWindow::updateProgressBar(const QString &label, int value)
{
	if (value == -1) {
		mui_statusProgressBar->setMaximum(0);
		mui_statusProgressBar->setMinimum(0);
	} else {
		mui_statusProgressBar->setMaximum(100);
		mui_statusProgressBar->setMinimum(0);
		mui_statusProgressBar->setValue(value);
	}
	mui_statusProgressBar->setFormat(label);
	mui_statusProgressBar->repaint();
}

void MainWindow::updateStatusBarText(const QString &text)
{
	showStatusTextPermanently(text);
}

void MainWindow::messageItemStoreSelection(qint64 msgId)
{
	debugSlotCall();

	m_lastSelectedMessageId = msgId;
	logDebugLv1NL("Last selected message '%" PRId64 "'.",
	    UGLY_QINT64_CAST m_lastSelectedMessageId);
	if (-1 == msgId) {
		m_lastSelectedAccountNodeType = AccountModel::nodeUnknown;
		return;
	}

	/*
	 * If we selected a message from last received then store the
	 * selection to the model.
	 */
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	m_lastSelectedAccountNodeType = mwStatus.acntNodeType();
	if (AccountModel::nodeRecentReceived == m_lastSelectedAccountNodeType) {
		logDebugLv1NL(
		    "Storing recent received selection '%" PRId64 "' into the model.",
		    UGLY_QINT64_CAST msgId);

		const QString accountId = PrefsHelper::accountIdentifier(
		    acntId.username(), acntId.testing());
		GlobInstcs::prefsPtr->setIntVal(
		    "window.main.account." + accountId + ".last.selected.message_id",
		    msgId);
	}
}

/* ========================================================================= */
/*
 * Saves message selection when model changes.
 */
void MainWindow::messageItemStoreSelectionOnModelChange(void)
/* ========================================================================= */
{
	debugSlotCall();

	m_lastStoredMessageId = m_lastSelectedMessageId;
	m_lastStoredAccountNodeType = m_lastSelectedAccountNodeType;
	qDebug() << "Last stored position" << m_lastStoredMessageId;
}


/* ========================================================================= */
/*
 * Restores message selection.
 */
void MainWindow::messageItemRestoreSelectionOnModelChange(void)
/* ========================================================================= */
{
	debugSlotCall();

	QModelIndex msgIndex;

	const QAbstractItemModel *model = &m_messageListProxyModel;

	int rowCount = model->rowCount();
	int row = 0;

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	enum AccountModel::NodeType acntNodeType = mwStatus.acntNodeType();

	if (0 == rowCount) {
		/* Do nothing on empty model. */
		return;
	}

	/* Search for existing ID if account position did not change. */
	if ((-1 != m_lastStoredMessageId) &&
	    (m_lastStoredAccountNodeType == acntNodeType)) {

		/* Find and select the message with the ID. */
		for (row = 0; row < rowCount; ++row) {
			/*
			 * TODO -- Search in a more resource-saving way.
			 * Eliminate index copying, use smarter search.
			 */
			if (model->index(row, 0).data().toLongLong() ==
			    m_lastStoredMessageId) {
				msgIndex = model->index(row, 0);
				break;
			}
		}

		if (msgIndex.isValid()) { /*(row < rowCount)*/
			/* Message found. */
			ui->messageList->setCurrentIndex(msgIndex);
			ui->messageList->scrollTo(msgIndex);
			return;
		}
	}

	/*
	 * If we selected a message from last received then restore the
	 * selection according to the model.
	 */
	qint64 val = PrefsSpecific::SELECT_NOTHING;
	if (!GlobInstcs::prefsPtr->intVal(
	        "window.main.message_list.select.on.account_change", val)) {
		logErrorNL("Missing '%s' value.",
		    "window.main.message_list.select.on.account_change");
	}
	switch (val) {
	case PrefsSpecific::SELECT_NEWEST:
		/* Search for the message with the largest id. */
		{
			msgIndex = model->index(0, 0);
			qint64 largestSoFar = msgIndex.data().toLongLong();
			for (row = 1; row < rowCount; ++row) {
				/*
				 * TODO -- Search in a more resource-saving
				 * way. Eliminate index copying, use smarter
				 * search.
				 */
				if (largestSoFar < model->index(row, 0).data()
				        .toLongLong()) {
					msgIndex = model->index(row, 0);
					largestSoFar =
					    msgIndex.data().toLongLong();
				}
			}
			if (msgIndex.isValid()) {
				ui->messageList->setCurrentIndex(msgIndex);
				ui->messageList->scrollTo(msgIndex);
			}
		}
		break;
	case PrefsSpecific::SELECT_LAST_VISITED:
		{
			qint64 msgLastId = -1;
			if (AccountModel::nodeRecentReceived == acntNodeType) {
				const QString accountId = PrefsHelper::accountIdentifier(
				    acntId.username(), acntId.testing());

				qint64 val = 0;
				if (GlobInstcs::prefsPtr->intVal(
				        "window.main.account." + accountId + ".last.selected.message_id",
				        val)) {
					msgLastId = val;
				}
			} else {
				msgLastId = m_lastStoredMessageId;
			}
			if (0 <= msgLastId) {
				/* Find and select the message with the ID. */
				for (row = 0; row < rowCount; ++row) {
					/*
					 * TODO -- Search in a more
					 * resource-saving way.
					 * Eliminate index copying, use
					 * smarter search.
					 */
					if (model->index(row, 0)
					        .data().toLongLong()
					    == msgLastId) {
						msgIndex =
						    model->index(row, 0);
						break;
					}
				}
				if (msgIndex.isValid()) { /*(row < rowCount)*/
					ui->messageList->setCurrentIndex(
					    msgIndex);
					ui->messageList->scrollTo(msgIndex);
				}
			}
		}
		break;
	case PrefsSpecific::SELECT_NOTHING:
		/* Don't select anything. */
		break;
	default:
		Q_ASSERT(0);
		break;
	}
}


/* ========================================================================= */
/*
 * Restores message selection.
 */
void MainWindow::messageItemRestoreSelectionAfterLayoutChange(void)
/* ========================================================================= */
{
	debugSlotCall();

	QModelIndex msgIndex;

	const QAbstractItemModel *model = &m_messageListProxyModel;

	int rowCount = model->rowCount();
	int row = 0;

	if (0 == rowCount) {
		/* Do nothing on empty model. */
		return;
	}

	/* If the ID does not exist then don't search for it. */
	if (-1 == m_lastSelectedMessageId) {
		row = rowCount;
	}

	/* Find and select the message with the ID. */
	for (; row < rowCount; ++row) {
		/*
		 * TODO -- Search in a more resource-saving way.
		 * Eliminate index copying, use smarter search.
		 */
		if (model->index(row, 0).data().toLongLong() ==
		    m_lastSelectedMessageId) {
			msgIndex = model->index(row, 0);
			break;
		}
	}

	if (msgIndex.isValid()) { /*(row < rowCount)*/
		/* Message found. */
		ui->messageList->setCurrentIndex(msgIndex);
		ui->messageList->scrollTo(msgIndex);
	}
}

QModelIndex MainWindow::accountYearlyIndex(const AcntId &acntId,
    const QString &year, int msgType) const
{
	debugFuncCall();

	/* first step: search correspond account index from username */
	QModelIndex acntIdxTop = m_accountModel.topAcntIndex(acntId);

	if (Q_UNLIKELY(!acntIdxTop.isValid())) {
		return QModelIndex();
	}

	/* second step: obtain index of received or sent messages */
	QModelIndex typeIdx;
	switch (msgType) {
	case MessageDb::TYPE_RECEIVED:
		typeIdx = m_accountModel.index(2, 0, acntIdxTop);
		typeIdx = m_accountModel.index(0, 0, typeIdx); /* All received. */
		break;
	case MessageDb::TYPE_SENT:
		typeIdx = m_accountModel.index(2, 0, acntIdxTop);
		typeIdx = m_accountModel.index(1, 0, typeIdx); /* All sent. */
		break;
	default:
		Q_ASSERT(0);
		return QModelIndex();
		break;
	}

	/* third step: obtain index with given year */
	int childRow = 0;
	QModelIndex yearIdx = m_accountModel.index(childRow, 0, typeIdx);
	while (yearIdx.isValid() &&
	   (yearIdx.data(AccountModel::ROLE_PLAIN_DISPLAY).toString() != year)) {
		yearIdx = yearIdx.sibling(++childRow, 0);
	}

	if (!yearIdx.isValid()) {
		return QModelIndex();
	}

	return yearIdx;
}

void MainWindow::messageItemFromSearchSelection(const AcntId &acntId,
    qint64 msgId, const QString &deliveryYear, int msgType)
{
	debugSlotCall();

	/* If the ID does not exist then don't search for it. */
	if (-1 == msgId) {
		return;
	}

	/* first step: navigate year entry in account list */
	QModelIndex yearIdx(accountYearlyIndex(acntId, deliveryYear, msgType));
	if (Q_UNLIKELY(!yearIdx.isValid())) {
		return;
	}

	ui->accountList->selectionModel()->setCurrentIndex(yearIdx,
	    QItemSelectionModel::ClearAndSelect);

	/* second step: find and select message according to msgId */
	QModelIndex msgIdx(messageIndex(ui->messageList, msgId));
	if (msgIdx.isValid()) {
		ui->messageList->setCurrentIndex(msgIdx);
		ui->messageList->scrollTo(msgIdx);
	}
}

void MainWindow::saveAttachmentToFile(const AcntId &acntId, const MsgId &msgId,
    const QModelIndex &attIdx)
{
	if (Q_UNLIKELY(!attIdx.isValid())) {
		Q_ASSERT(0);
		return;
	}

	QModelIndex fileNameIndex(attIdx.sibling(attIdx.row(),
	    AttachmentTblModel::FNAME_COL));
	if(Q_UNLIKELY(!fileNameIndex.isValid())) {
		showStatusTextWithTimeout(
		    tr("Saving attachment of message '%1' into a file was not successful.").arg(msgId.dmId()));
		return;
	}
	QString filename(fileNameIndex.data().toString());

	QString saveAttachPath;
	if (PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
		saveAttachPath = PrefsSpecific::saveAttachDir(
		    *GlobInstcs::prefsPtr);
	} else {
		saveAttachPath = PrefsSpecific::acntSaveAttachDir(
		    *GlobInstcs::prefsPtr, acntId);
	}

	const QString dbId(
	    GlobInstcs::accntDbPtr->dbId(AccountDb::keyFromLogin(acntId.username())));

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const QString accountName(
	    GlobInstcs::acntMapPtr->acntData(acntId).accountName());

	QString fileName(Exports::attachmentSavePathWithFileName(*dbSet,
	    saveAttachPath, filename, dbId, acntId.username(), accountName, msgId, true));

	QString savedFileName(AttachmentInteraction::saveAttachmentToFile(this,
	    attIdx, fileName));
	if (!savedFileName.isEmpty()) {
		showStatusTextWithTimeout(
		    tr("Saving attachment of message '%1' into a file was successful.").arg(msgId.dmId()));
		if (!PrefsSpecific::useGlobalPaths(*GlobInstcs::prefsPtr)) {
			PrefsSpecific::setAcntSaveAttachDir(
			    *GlobInstcs::prefsPtr, acntId,
			    QFileInfo(savedFileName).absoluteDir().absolutePath());
		}
	} else {
		showStatusTextWithTimeout(
		    tr("Saving attachment of message '%1' into a file was not successful.").arg(msgId.dmId()));
	}
}

void MainWindow::processSingleInstanceMessages(int msgType,
    const QString &msgVal)
{
	debugSlotCall();

	logDebugLv0NL("Received message '%d>%s'.", msgType,
	    msgVal.toUtf8().constData());

	switch (msgType) {
	case SingleInstance::MTYPE_RAISE_MAIN_WIN:
		this->show();
		this->raise();
		this->activateWindow();
		break;
	case SingleInstance::MTYPE_COMPOSE:
		if (m_accountModel.rowCount() > 0) {
			showSendMessageDialog(DlgSendMessage::ACT_NEW, msgVal);
		} else {
			QMessageBox::information(this,
			    tr("Cannot create message"),
			    tr("Create an user account first before trying to create and send a message."),
			    QMessageBox::Ok, QMessageBox::Ok);
		}
		break;
	default:
		break;
	}
}

void MainWindow::backgroundWorkersFinished(void)
{
	debugSlotCall();

	int accountCount = m_accountModel.rowCount();
	if (accountCount > 0) {
		m_selectionStatus.setFlagBlockIsds(false);

		/* Activate import buttons. */
		m_selectionStatus.setFlagBlockImports(false);
	}
	/*
	 * Prepare counters for next action.
	 * TODO -- This must be moved to separate slot that waits for downloads.
	 */
	dataFromWorkerToStatusBarInfo(false, 0, 0, 0, 0);

	/* Restart timers. */
	manageBackgroundTimer(m_timerSyncAccounts, TA_RUN);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_RUN);
}

void MainWindow::aggregateDownloadMessageStatus(const AcntId &acntId,
    qint64 msgId, bool forceProcess)
{
	static AcntId aggAcntId;
	static QList<qint64> aggMsgIds;
	const int limit = 10;

	/*
	 * Process pending if limit is exceeded or if different account needs
	 * to be collected.
	 */
	bool processPending = (aggMsgIds.size() >= limit) ||
	   (aggAcntId.isValid() && acntId.isValid() && (aggAcntId != acntId));

	/* Suspend workers while processing the status. */
	if (processPending || forceProcess) {
		GlobInstcs::workPoolPtr->suspend();

		while (processPending || forceProcess) {
			/* Append last enforced message if account identifier matches. */
			if (forceProcess &&
			    ((!aggAcntId.isValid()) || (aggAcntId == acntId))) {
				if (0 <= msgId) {
					aggAcntId = acntId;
					aggMsgIds.append(msgId);
				}
				msgId = -1;
				forceProcess = false;
			}

			/* Refresh account list. */
			if (aggAcntId.isValid()) {
				refreshAccountList(aggAcntId);

				aggAcntId.clear();
				aggMsgIds.clear();
			}

			processPending = forceProcess;
		}

		GlobInstcs::workPoolPtr->resume();
	}

	if (!(acntId.isValid() && (0 <= msgId))) {
		/* Nothing to be appended. */
		return;
	}

	/*
	 * Aggregated account id must be invalid or the same as given account
	 * id.
	 */
	if (Q_UNLIKELY(aggAcntId.isValid() && (aggAcntId != acntId))) {
		Q_ASSERT(0);
		return;
	}

	if (0 <= msgId) {
		aggAcntId = acntId;
		aggMsgIds.append(msgId);
	}
}

void MainWindow::collectDownloadMessageStatus(const AcntId &acntId,
    qint64 msgId, const QDateTime &deliveryTime, int result,
    const QString &errDesc, int processFlags, const QString &recMgmtHierarchyId)
{
	debugSlotCall();

	Q_UNUSED(deliveryTime);

	if (TaskDownloadMessage::DM_SUCCESS == result) {
		/* Force refresh on last message. */
		aggregateDownloadMessageStatus(acntId, msgId,
		    processFlags & Task::PROC_LIST_SCHEDULED_LAST);
	} else {
		/* Notify the user. */
		if (!(processFlags & Task::PROC_LIST_SCHEDULED)) {
			showStatusTextWithTimeout(tr(
			    "It was not possible to download the complete message '%1' from the ISDS server.").arg(msgId));

			if (!(processFlags & Task::PROC_NO_ERROR_DLG)) {
				QString infoText(!errDesc.isEmpty() ?
				    tr("ISDS") + QLatin1String(": ") + errDesc :
				    tr("A connection error occurred or the message has already been deleted from the server."));
				DlgMsgBox::message(this, QMessageBox::Warning,
				    tr("Download message error"),
				    tr("It was not possible to download the complete message '%1' from the ISDS server.")
				        .arg(msgId),
				    infoText, QString(), QMessageBox::Ok,
				    QMessageBox::Ok);
			}
		} else {
			showStatusTextWithTimeout(
			    tr("Couldn't download message '%1'.").arg(msgId));
		}
	}

	if (TaskDownloadMessage::DM_SUCCESS == result) {
		if (processFlags & Task::PROC_IMM_RM_UPLOAD) {
			sendMessageToRecordsManagement(acntId,
			    MsgId(msgId, deliveryTime), recMgmtHierarchyId);
		} else if (processFlags & Task::PROC_IMM_RM_UPLOAD_AMBIG) {
			DlgMsgBoxNonModal::warning(this,
			    tr("Message '%1' could not be uploaded").arg(msgId),
			    tr("Message '%1' could not be uploaded into records management service.").arg(msgId) +
			    QStringLiteral("\n") +
			    tr("The hierarchy location could not be determined because multiple possible targets have been detected."),
			    QMessageBox::Ok);
		}
	}
}

void MainWindow::collectDownloadMessageListStatus(const AcntIdDb &rAcntIdDb,
    const AcntId &sAcntId, int direction, int result, const QString &errDesc,
    bool add, int rt, int rn, int st, int sn)
{
	debugSlotCall();

	/* Refresh account list. */
	refreshAccountList(rAcntIdDb);

	dataFromWorkerToStatusBarInfo(add, rt, rn, st, sn);

	if (TaskDownloadMessageList::DL_SUCCESS == result) {
		if ((MSG_SENT == direction) && /* Sent messages are processed after received. */
		    GlobInstcs::recMgmtSetPtr->isValid() &&
		    PrefsSpecific::recMgmtAutoAcntStatusUpload(*GlobInstcs::prefsPtr)) {

			/* Send status info into records management. */
			const QString boxId = GlobInstcs::accntDbPtr->getOwnerInfo(
			    AccountDb::keyFromLogin(rAcntIdDb.username())).dbID();
			TaskRecordsManagementUploadAccountStatus *task =
			    new (::std::nothrow) TaskRecordsManagementUploadAccountStatus(
			        GlobInstcs::recMgmtSetPtr->url(), GlobInstcs::recMgmtSetPtr->token(),
			        boxId, (!sAcntId.isValid()) ? rAcntIdDb : sAcntId,
			        QDateTime::currentDateTime(), rAcntIdDb.messageDbSet(),
			        GlobInstcs::recMgmtDbPtr,
			        GlobInstcs::acntMapPtr->acntData(rAcntIdDb).accountName());
			if (Q_UNLIKELY(task == Q_NULLPTR)) {
				Q_ASSERT(0);
				return;
			}
			task->setAutoDelete(false);
			GlobInstcs::workPoolPtr->runSingle(task);
			delete task;
		}
	} else {
		/* Notify the user. */
		QString errorMessage = (MSG_RECEIVED == direction) ?
		    tr("It was not possible to download the received message list from the server.") :
		    tr("It was not possible to download the sent message list from the server.");

		showStatusTextWithTimeout(errorMessage);

		QString infoText(!errDesc.isEmpty() ?
		    tr("Server") + QLatin1String(": ") + errDesc :
		    tr("A connection error occurred."));
		DlgMsgBox::message(this, QMessageBox::Warning,
		    tr("Download message list error"), errorMessage, infoText,
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	}
}

void MainWindow::collectImportZfoStatus(const QString &fileName, int result,
    const QString &resultDesc)
{
	debugSlotCall();

	logDebugLv0NL("Received import ZFO finished for file '%s' %d: '%s'",
	    fileName.toUtf8().constData(), result,
	    resultDesc.toUtf8().constData());

	ZFOImportCtx &importCtx(ZFOImportCtx::getInstance());

	switch (result) {
	case TaskImportZfo::IMP_SUCCESS:
		importCtx.importSucceeded.append(
		    QPair<QString, QString>(fileName, resultDesc));
		break;
	case TaskImportZfo::IMP_DB_EXISTS:
		importCtx.importExisted.append(
		    QPair<QString, QString>(fileName, resultDesc));
		break;
	default:
		importCtx.importFailed.append(
		    QPair<QString, QString>(fileName, resultDesc));
		break;
	}

	if (!importCtx.zfoFilesToImport.remove(fileName)) {
		logErrorNL(
		    "Processed ZFO file which '%s' the application has not been aware of.",
		    fileName.toUtf8().constData());
	}

	if (importCtx.zfoFilesToImport.isEmpty()) {
		DlgImportZFOResult::view(importCtx.numFilesToImport,
		    importCtx.importSucceeded, importCtx.importExisted,
		    importCtx.importFailed, this);

		importCtx.clear();

		/* Activate import buttons. */
		m_selectionStatus.setFlagBlockImports(false);
	}
}

void MainWindow::collectSendMessageStatus(const AcntId &acntId,
    const QString &transactId, int result, const QString &resultDesc,
    const QString &dbIDRecipient, const QString &recipientName,
    bool isPDZ, qint64 dmId, int processFlags,
    const QString &recMgmtHierarchyId)
{
	debugSlotCall();

	Q_UNUSED(transactId);
	Q_UNUSED(resultDesc);
	Q_UNUSED(isPDZ);

	if (TaskSendMessage::SM_SUCCESS == result) {
		showStatusTextWithTimeout(tr(
		    "Message from '%1' (%2) has been successfully sent to '%3' (%4).").
		    arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()).
		    arg(acntId.username()).arg(recipientName).arg(dbIDRecipient));

		/* Refresh account list. */
		refreshAccountList(acntId);
	} else {
		showStatusTextWithTimeout(tr(
		    "Error while sending message from '%1' (%2) to '%3' (%4).").
		    arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()).
		    arg(acntId.username()).arg(recipientName).arg(dbIDRecipient));
	}

	clearProgressBar();

	if ((TaskSendMessage::SM_SUCCESS == result) &&
	    (processFlags & Task::PROC_IMM_DOWNLOAD)) {

		/* Schedule sent complete message download */
		int ms =
		    PrefsSpecific::sentMsgDownloadWaitIntervalMs(*GlobInstcs::prefsPtr);

		if (ms > 0) {
			/* Disable waiting when having negative or zero timeout. */
			if (ms > 20000) {
				/* Trim to 20 seconds. */
				ms = 20000;
			}
			DlgWaitProgress::wait(ms,
			    tr("Waiting %1 seconds before downloading the sent message.").arg(ms / 1000),
			    this);
		}

		TaskDownloadMessage *task =
		    new (::std::nothrow) TaskDownloadMessage(
		        AcntIdDb(acntId, accountDbSet(acntId, this)),
		        MSG_SENT, MsgId(dmId, QDateTime()),
		        Task::PROC_LIST_SCHEDULED_LAST | (processFlags & ~Task::PROC_IMM_DOWNLOAD),
		        recMgmtHierarchyId);
		if (Q_UNLIKELY(task == Q_NULLPTR)) {
			Q_ASSERT(0);
			return;
		}
		task->setAutoDelete(true);
		GlobInstcs::workPoolPtr->assignHi(task, WorkerPool::PREPEND);
	}
}

void MainWindow::accountMarkReceivedLocallyRead(bool read)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	dbSet->smsgdtSetAllReceivedLocallyRead(read);

	/*
	 * Reload/update account model only for
	 * affected account.
	 */
	updateExistingAccountModelUnread(m_accountModel, acntId, this);

	/* Regenerate the model. */
	adaptToAccountSelection();
}

void MainWindow::accountMarkReceivedYearLocallyRead(bool read)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	dbSet->smsgdtSetReceivedYearLocallyRead(mwStatus.year(), read);

	/*
	 * Reload/update account model only for
	 * affected account.
	 */
	updateExistingAccountModelUnread(m_accountModel, acntId, this);

	/* Regenerate the model. */
	adaptToAccountSelection();
}

void MainWindow::accountMarkRecentReceivedLocallyRead(bool read)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	dbSet->smsgdtSetWithin90DaysReceivedLocallyRead(read);

	/*
	 * Reload/update account model only for
	 * affected account.
	 */
	updateExistingAccountModelUnread(m_accountModel, acntId, this);

	/* Regenerate the model. */
	adaptToAccountSelection();
}

void MainWindow::accountMarkReceivedProcessState(
    enum MessageDb::MessageProcessState state)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	dbSet->setReceivedMessagesProcessState(state);

	/*
	 * No need to reload account model.
	 */

	/* Regenerate the model. */
	adaptToAccountSelection();
}

void MainWindow::accountMarkReceivedYearProcessState(
    enum MessageDb::MessageProcessState state)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	/*
	 * Data cannot be read directly from index because to the overloaded
	 * model functions.
	 * TODO -- Parameter check.
	 */
	dbSet->smsgdtSetReceivedYearProcessState(mwStatus.year(), state);

	/*
	 * No need to reload account model.
	 */

	/* Regenerate the model. */
	adaptToAccountSelection();
}

void MainWindow::accountMarkRecentReceivedProcessState(
    enum MessageDb::MessageProcessState state)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	MessageDbSet *dbSet = accountDbSet(mwStatus.acntId(), this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	dbSet->smsgdtSetWithin90DaysReceivedProcessState(state);

	/*
	 * No need to reload account model.
	 */

	/* Regenerate the model. */
	adaptToAccountSelection();
}

bool MainWindow::eraseMessage(const AcntId &acntId,
    enum MessageDirection msgDirect, const MsgId &msgId, bool delFromIsds)
{
	debugFuncCall();

	Q_ASSERT(acntId.isValid());

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return false;
	}

	if (delFromIsds &&
	    !GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		logErrorNL(
		    "Couldn't connect to ISDS when erasing message '%" PRId64 "'.",
		    UGLY_QINT64_CAST msgId.dmId());
		return false;
	}

	QString errorStr, longErrorStr;
	TaskEraseMessage *task;

	task = new (::std::nothrow) TaskEraseMessage(AcntIdDb(acntId, dbSet),
	    msgId, msgDirect, delFromIsds);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->runSingle(task);

	TaskEraseMessage::Result result = task->m_result;
	errorStr = task->m_isdsError;
	longErrorStr = task->m_isdsLongError;
	delete task;

	switch (result) {
	case TaskEraseMessage::NOT_DELETED:
		logErrorNL("Message '%" PRId64 "' couldn't be deleted.",
		    UGLY_QINT64_CAST msgId.dmId());
		showStatusTextWithTimeout(tr("Message \"%1\" was not deleted.")
		    .arg(msgId.dmId()));
		return false;
		break;
	case TaskEraseMessage::DELETED_ISDS:
		logWarning("Message '%" PRId64 "' deleted only from ISDS.\n",
		    UGLY_QINT64_CAST msgId.dmId());
		showStatusTextWithTimeout(tr(
		    "Message \"%1\" was deleted only from ISDS.")
		    .arg(msgId.dmId()));
		return false;
		break;
	case TaskEraseMessage::DELETED_LOCAL:
		if (delFromIsds) {
			logWarning(
			    "Message '%" PRId64 "' deleted only from local database.\n",
			    UGLY_QINT64_CAST msgId.dmId());
			showStatusTextWithTimeout(tr(
			    "Message \"%1\" was deleted only from local database.")
			    .arg(msgId.dmId()));
		} else {
			logInfo(
			    "Message '%" PRId64 "' deleted from local database.\n",
			    UGLY_QINT64_CAST msgId.dmId());
			showStatusTextWithTimeout(tr(
			    "Message \"%1\" was deleted from local database.")
			    .arg(msgId.dmId()));
		}
		return true;
		break;
	case TaskEraseMessage::DELETED_ISDS_LOCAL:
		logInfo(
		    "Message '%" PRId64 "' deleted from ISDS and local database.\n",
		    UGLY_QINT64_CAST msgId.dmId());
		showStatusTextWithTimeout(tr(
		    "Message \"%1\" was deleted from ISDS and local database.")
		    .arg(msgId.dmId()));
		return true;
		break;
	default:
		Q_ASSERT(0);
		break;
	}

	return false;
}


/* ========================================================================= */
/*
* Set info status bar from worker.
*/
void MainWindow::dataFromWorkerToStatusBarInfo(bool add,
    int rt, int rn, int st, int sn)
/* ========================================================================= */
{
	debugFuncCall();

	static int s_rt = 0, s_rn = 0, s_st = 0, s_sn = 0;

	if (add) {
		s_rt += rt;
		s_rn += rn;
		s_st += st;
		s_sn += sn;
		showStatusTextWithTimeout(tr("Messages on the server") + ": " +
		    QString::number(s_rt) + " " + tr("received") +
		    " (" + QString::number(s_rn) + " " + tr("new") + "); " +
		    QString::number(s_st) + " " + tr("sent") +
		    " (" + QString::number(s_sn) + " " + tr("new") + ")");
	} else {
		s_rt = rt;
		s_rn = rn;
		s_st = st;
		s_sn = sn;
	}
}

void MainWindow::synchroniseAllShadowAccounts(void)
{
	debugSlotCall();

	synchroniseAccounts(true);
}

/* ========================================================================= */
/*
 * Generate overall account information only for sent or received messages.
 */
QString MainWindow::createAccountInfoMessagesCount(const QString &accountName,
    const QList< QPair<QString, int> > &counts,
    enum MessageDb::MessageType type) const
/* ========================================================================= */
{
	QString html = indentDivStart;
	html.append ("<h3>" + accountName.toHtmlEscaped() + "</h3>");

	if (type == MessageDb::TYPE_RECEIVED) {
		html.append(strongAccountInfoLine(tr("Received messages"), QString()));
	} else {
		html.append(strongAccountInfoLine(tr("Sent messages"), QString()));
	}
	html.append(indentDivStart);
	if (0 == counts.size()) {
		html.append(tr("none"));
	} else {
		for (int i = 0; i < counts.size(); ++i) {
			html.append(accountInfoLine(counts[i].first,
			    QString::number(counts[i].second)));
		}
	}
	html.append(divEnd);

	html.append(divEnd);
	return html;
}

void MainWindow::setDefaultAccount(const Prefs &prefs)
{
	debugFuncCall();

	QString accountId;
	if (prefs.strVal("window.main.account_tree.selected_account", accountId)) {
		QString username;
		bool testing = false;
		if (PrefsHelper::accountIdentifierData(accountId, username, testing)) {
			const AcntId acntId(username, testing);
			QModelIndex acntTopIdx =
			    m_accountModel.topAcntIndex(acntId);
			if (acntTopIdx.isValid()) {
				const QModelIndex index = m_accountModel.index(0, 0, acntTopIdx);
				ui->accountList->selectionModel()->setCurrentIndex(
				    index,
				    QItemSelectionModel::ClearAndSelect);
				updateAccountSelection(index);
				/*
				 * Following method must be called because
				 * slot is not connected yet.
				 */
				adaptToAccountSelection();
				updateActionActivation();
				return;
			}
		}
	}

	/* Something failed. */
	updateActionActivation();
}

void MainWindow::saveWindowGeometry(Prefs &prefs) const
{
	int value;

	/* Window geometry. */
	QRect geom(this->geometry());

	if (this->isMaximized() && m_geometry.isValid()) {
		geom = m_geometry;
	}

	value = geom.x();
	value = (value < 0) ? 0 : value;
	prefs.setIntVal("window.main.position.x", value);

	value = geom.y();
	value = (value < 0) ? 0 : value;
	prefs.setIntVal("window.main.position.y", value);

	prefs.setIntVal("window.main.position.width", geom.width());
	prefs.setIntVal("window.main.position.height", geom.height());

	/* False value should not be stored because of defaults. */
	prefs.setBoolVal("window.main.position.maximised", this->isMaximized());

	/* Splitter geometry. */
	prefs.setIntVal("window.main.pane.account_tree",
	    ui->hSplitterAccount->sizes()[0]);
	prefs.setIntVal("window.main.pane.message_list",
	    ui->vSplitterMessage->sizes()[0]);
	prefs.setIntVal("window.main.pane.message_info",
	    ui->hSplitterMessageInfo->sizes()[0]);
}

void MainWindow::saveAccountIndex(Prefs &prefs) const
{
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (acntId.isValid()) {
		const QString accountId = PrefsHelper::accountIdentifier(
		    acntId.username(), acntId.testing());
		prefs.setStrVal("window.main.account_tree.selected_account", accountId);
	}
}

bool MainWindow::regenerateAccountModelYears(const QModelIndex &index,
    bool prohibitYearRemoval)
{
	debugFuncCall();

	if (Q_UNLIKELY(!index.isValid())) {
		Q_ASSERT(0);
		return false;
	}

	/* Get database id. */
	const AcntId acntId(m_accountModel.acntId(index));
	Q_ASSERT(acntId.isValid());
	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return false;
	}

	QStringList yearList;
	int unreadMsgs;
	typedef QPair<QString, AccountModel::YearCounter> CounterPair;
	QList<CounterPair> yearlyUnreadList;

	/* Received. */
	unreadMsgs = dbSet->msgsUnreadWithin90Days(MessageDb::TYPE_RECEIVED);
	m_accountModel.updateRecentUnread(acntId,
	    AccountModel::nodeRecentReceived, unreadMsgs);
	yearList = dbSet->msgsYears(MessageDb::TYPE_RECEIVED, DESCENDING);
	foreach (const QString &year, yearList) {
		unreadMsgs = dbSet->msgsUnreadInYear(MessageDb::TYPE_RECEIVED,
		    year);
		AccountModel::YearCounter yCounter(unreadMsgs != -2,
		    (unreadMsgs > 0) ? unreadMsgs : 0);
		yearlyUnreadList.append(CounterPair(year, yCounter));
	}
	m_accountModel.updateYearNodes(acntId, AccountModel::nodeReceivedYear,
	    yearlyUnreadList, AccountModel::DESCENDING, prohibitYearRemoval);

	/* Sent. */
	//unreadMsgs = dbSet->msgsUnreadWithin90Days(MessageDb::TYPE_SENT);
	m_accountModel.updateRecentUnread(acntId,
	    AccountModel::nodeRecentSent, 0);
	yearList = dbSet->msgsYears(MessageDb::TYPE_SENT, DESCENDING);
	yearlyUnreadList.clear();
	foreach (const QString &year, yearList) {
		unreadMsgs = dbSet->msgsUnreadInYear(MessageDb::TYPE_SENT,
		    year);
		AccountModel::YearCounter yCounter(unreadMsgs != -2, 0);
		yearlyUnreadList.append(CounterPair(year, yCounter));
	}
	m_accountModel.updateYearNodes(acntId, AccountModel::nodeSentYear,
	    yearlyUnreadList, AccountModel::DESCENDING, prohibitYearRemoval);

	return true;
}

bool MainWindow::regenerateAllAccountModelYears(void)
{
	debugFuncCall();

	m_accountModel.removeAllYearNodes();

	for (int i = 0; i < m_accountModel.rowCount(); ++i) {
		regenerateAccountModelYears(m_accountModel.index(i, 0));
	}

	return true;
}

void MainWindow::showSendMessageDialog(int action,
    const QString &composeSerialised)
{
	debugFuncCall();

	QList<AcntIdDb> acntIdDbList;
	QList<MsgId> msgIds;

	/* get account identifier of selected account */
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	/* if not reply, get pointers to database for other accounts */
	if (DlgSendMessage::ACT_REPLY != action) {
		acntIdDbList = allAcntIdDbList(m_accountModel, this);
	} else {
		MessageDbSet *dbSet = accountDbSet(acntId, this);
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			return;
		}
		acntIdDbList.append(AcntIdDb(acntId, dbSet));
	}

	QList<MsgOrigin> originList = messageOriginList(mwStatus, this);

	switch (action) {
	case DlgSendMessage::ACT_NEW:
		break;
	case DlgSendMessage::ACT_REPLY:
	case DlgSendMessage::ACT_NEW_FROM_TMP:
		Q_ASSERT(originList.size() == 1);
		Q_FALLTHROUGH();
	case DlgSendMessage::ACT_FORWARD:
		Q_ASSERT(originList.size() > 0);
		{
			originList = DlgDownloadMessages::offerDownloadForMissing(
			    MsgOriginAndUpladTarget::fromMsgOriginList(originList),
			    this, this);
			if (Q_UNLIKELY(originList.isEmpty())) {
				return;
			}
			/*
			 * Messages are all present here and originate from
			 * a single account.
			 */
			foreach (const MsgOrigin &origin, originList) {
				msgIds.append(origin.msgId);
			}
		}
		break;
	default:
		Q_ASSERT(0);
		return;
		break;
	}

	QDialog *sendMsgDialog = new (::std::nothrow) DlgSendMessage(
	    acntIdDbList, (DlgSendMessage::Action)action, msgIds, acntId,
	    composeSerialised, this);
	if (Q_UNLIKELY(sendMsgDialog == Q_NULLPTR)) {
		return;
	}

	showStatusTextWithTimeout(tr("Create and send a message."));

	sendMsgDialog->setAttribute(Qt::WA_DeleteOnClose, true);
	sendMsgDialog->show();
}

AcntId MainWindow::shadowAcntId(const AcntId &rAcntId)
{
	if (Q_UNLIKELY(!rAcntId.isValid())) {
		return AcntId();
	}

	if (!ShadowHelper::hasListDownloadPrivileges(rAcntId)) {
		/*
		 * Don't use shadow account for regular account that doesn't
		 * have permissions to download message lists.
		 */
		return AcntId();
	}

	foreach (const AcntId sAcntId, ShadowHelper::usableBackgroundAccounts(
	    rAcntId, *GlobInstcs::acntMapPtr, *GlobInstcs::shadowAcntsPtr)) {
		/*
		 * Try connecting to ISDS, generate log-in dialogue,
		 * refresh account information.
		 */
		if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(sAcntId.username()) &&
		    !connectToIsds(sAcntId, true)) {
			continue;
		}
		/* Connected to ISDS. Check for privileges once more. */
		if (ShadowHelper::isUsableBackgroundAccount(sAcntId,
		    *GlobInstcs::shadowAcntsPtr)) {
			return sAcntId;
		}
	}

	return AcntId();
}

void MainWindow::synchroniseAccounts(bool shadow)
{
	manageBackgroundTimer(m_timerSyncAccounts, TA_PAUSE);
	manageShadowTimer(m_timerShadowSyncAccounts, TA_PAUSE);

	int accountCount = m_accountModel.rowCount();
	bool appended = false;

	for (int i = 0; i < accountCount; ++i) {

		QModelIndex index = m_accountModel.index(i, 0);

		const AcntId rAcntId(m_accountModel.acntId(index));
		Q_ASSERT(rAcntId.isValid());
		AcntId sAcntId;

		if (!shadow) {
			/* Skip those that should omitted. */
			if (!GlobInstcs::acntMapPtr->acntData(rAcntId).syncWithAll()) {
				continue;
			}

			/* Try connecting to ISDS, just to generate log-in dialogue. */
			if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(rAcntId.username()) &&
			    !connectToIsds(rAcntId)) {
				continue;
			}
		} else {
			sAcntId = shadowAcntId(rAcntId);
			/* Proceed only if shadow account found. */
			if (!sAcntId.isValid()) {
				continue;
			}
		}

		if (synchroniseAccount(rAcntId, sAcntId)) {
			appended = true;
		}
	}

	if (!appended) {
		showStatusTextWithTimeout(tr("No account synchronised."));
		/* Restart timers. */
		manageBackgroundTimer(m_timerSyncAccounts, TA_RUN);
		manageShadowTimer(m_timerShadowSyncAccounts, TA_RUN);
	}
}

bool MainWindow::synchroniseAccount(const AcntId &rAcntId,
    const AcntId &sAcntId, enum MessageDirection msgDirect)
{
	if (Q_UNLIKELY(!rAcntId.isValid())) {
		Q_ASSERT(0);
		return false;
	}

	MessageDbSet *dbSet = accountDbSet(rAcntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		return false;
	}

	{
		/*
		 * Disabling buttons is not as easy as it is handled by
		 * the event loop. The event loop is forced to process all
		 * pending events to minimise the probability that the actions
		 * remain enabled accidentally.
		 *
		 * TODO -- The checking for live ISDS connection must be
		 * guarded for simultaneous access.
		 */
		QCoreApplication::processEvents();
		m_selectionStatus.setFlagBlockIsds(true);
		QCoreApplication::processEvents();
	}

	const bool shadow = sAcntId.isValid();

	{
		const AcntId acntId((!shadow) ? rAcntId : sAcntId);

		/* Try connecting to ISDS, just to generate log-in dialogue. */
		if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
		    !connectToIsds(acntId, shadow)) {
			m_selectionStatus.setFlagBlockIsds(false);
			return false;
		}
	}

	/* Method connectToIsds() acquires account information. */

	bool downloadReceivedMessages = false;
	if (!shadow) {
		GlobInstcs::prefsPtr->boolVal(
		    "action.sync_account.tie.action.download_message",
		    downloadReceivedMessages);
	}
	bool downloadSentMessages = downloadReceivedMessages;
	if (downloadReceivedMessages) {
		/* Method connectToIsds() acquires account information. */
		const QString acntDbKey(AccountDb::keyFromLogin(rAcntId.username()));
		DbEntry userEntry = GlobInstcs::accntDbPtr->userEntry(acntDbKey);
		const QString key("userPrivils");
		if (userEntry.hasValue(key)) {
			int privils = userEntry.value(key).toInt();
			if (!(privils & (Isds::Type::PRIVIL_READ_NON_PERSONAL | Isds::Type::PRIVIL_READ_ALL))) {
				logInfoNL(
				    "User '%s' has no privileges to download received messages. Won't try downloading messages.",
				    rAcntId.username().toUtf8().constData());
				downloadReceivedMessages = false;
			}
		}
	}

	RecMgmt::AutomaticUploadTarget uploadTarget;
	if (GlobInstcs::recMgmtSetPtr->isValid() &&
	    PrefsSpecific::recMgmtAutoUpload(*GlobInstcs::prefsPtr)) {
		uploadTarget =
		    DlgRecordsManagementUpload::uploadHierarchyTargets(
		        *GlobInstcs::recMgmtSetPtr, this);
	}

	TaskDownloadMessageList *task = Q_NULLPTR;

	if (((MSG_ALL == msgDirect) || (MSG_RECEIVED == msgDirect)) &&
	    ((!shadow) || ShadowHelper::hasListDownloadPrivileges(rAcntId))) {
		/*
		 * Don't use shadow account for regular account that doesn't
		 * have permissions to download message lists.
		 */
		task = new (::std::nothrow) TaskDownloadMessageList(
		    AcntIdDb(rAcntId, dbSet), sAcntId, MSG_RECEIVED,
		    downloadReceivedMessages, MESSAGE_LIST_LIMIT,
		    Isds::Type::MFS_ANY, uploadTarget);
		if (task != Q_NULLPTR) {
			task->setAutoDelete(true);
			GlobInstcs::workPoolPtr->assignLo(task);
		}
	}

	if ((MSG_ALL == msgDirect) || (MSG_SENT == msgDirect)) {
		task = new (::std::nothrow) TaskDownloadMessageList(
		    AcntIdDb(rAcntId, dbSet), sAcntId, MSG_SENT, downloadSentMessages);
		if (task != Q_NULLPTR) {
			task->setAutoDelete(true);
			GlobInstcs::workPoolPtr->assignLo(task);
		}
	}

	return true;
}

void MainWindow::removeAccount(const AcntId acntId)
{
	if (Q_UNLIKELY(!acntId.isValid())) {
		Q_ASSERT(0);
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	const QString accountName(
	    GlobInstcs::acntMapPtr->acntData(acntId).accountName());

	int retVal = DlgYesNoCheckbox::NoUnchecked;
	{
		QString dlgTitleText = tr("Remove Account '%1'").arg(accountName);
		QString questionText = tr("Do you want to remove the account '%1' (%2)?")
		    .arg(accountName).arg(acntId.username());
		QString checkBoxText = tr("Delete also message database from storage.");
		QString detailText = tr("Warning:") + " " +
		    tr("If you delete the message database then all locally accessible messages that are not stored on the ISDS server will be lost.");

		DlgYesNoCheckbox questionDlg(dlgTitleText, questionText,
		    checkBoxText, detailText, this);
		retVal = questionDlg.exec();
	}

	if ((DlgYesNoCheckbox::YesChecked == retVal) ||
	    (DlgYesNoCheckbox::YesUnchecked == retVal)) {
		/* Delete account -- all connected models should adapt automatically. */
		GlobInstcs::acntMapPtr->removeAccount(acntId);
		GlobInstcs::accntDbPtr->deleteAccountInfo(
		    AccountDb::keyFromLogin(acntId.username()), true);
	}

	if (DlgYesNoCheckbox::YesChecked == retVal) {
		if (GlobInstcs::msgDbsPtr->deleteDbSet(dbSet)) {
			showStatusTextWithTimeout(tr(
			    "Account '%1' was deleted together with message database file.")
			    .arg(accountName));
		} else {
			showStatusTextWithTimeout(tr(
			    "Account '%1' was deleted but its message database was not deleted.")
			    .arg(accountName));
		}
	} else if (DlgYesNoCheckbox::YesUnchecked == retVal) {
		showStatusTextWithTimeout(
		    tr("Account '%1' was deleted.").arg(accountName));
	}

	/*
	 * Delete all tags from message_tags table related to the given account.
	 * Tags in the tag table are kept.
	 *
	 * TODO -- Deleting tag association with a message may
	 * become undesirable if the tag database should be
	 * shared between multiple application instances.
	 */
	GlobInstcs::tagDbPtr->removeAllMsgTagsFromAccount(acntId.username(),
	    acntId.testing());

	if ((DlgYesNoCheckbox::YesChecked == retVal) ||
	    (DlgYesNoCheckbox::YesUnchecked == retVal)) {
		saveSettings();
	}

	if (m_accountModel.rowCount() < 1) {
		updateAccountSelection(QModelIndex());
		updateActionActivation();
	}
}

void MainWindow::filterMessages(const QString &text)
{
	debugSlotCall();

	/* The model is always associated to the proxy model. */

	m_messageListProxyModel.setSortRole(DbMsgsTblModel::ROLE_PROXYSORT);

	m_messageListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
	/* Filter according to second and third column. */
	QList<int> columnList;
	columnList.append(DbMsgsTblModel::DMID_COL);
	columnList.append(DbMsgsTblModel::ANNOT_COL);
	columnList.append(DbMsgsTblModel::SENDER_COL);
	columnList.append(DbMsgsTblModel::RECIP_COL);
	if (Q_NULLPTR != GlobInstcs::tagDbPtr) {
		columnList.append(
		    m_messageListProxyModel.sourceModel()->columnCount() +
		    DbMsgsTblModel::TAGS_NEG_COL); /* Tags. */
	}
	m_messageListProxyModel.setFilterKeyColumns(columnList);

	/* Set filter field background colour. */
	if (text.isEmpty()) {
		mui_filterLine->setStyleSheet(
		    SortFilterProxyModel::blankFilterEditStyle);
	} else if (m_messageListProxyModel.rowCount() != 0) {
		mui_filterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		mui_filterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

void MainWindow::setReceivedColumnWidths(void)
{
	debugFuncCall();

	ui->messageList->resizeColumnToContents(DbMsgsTblModel::DMID_COL);

	/* Sender and recipient column must be set both here. */
	ui->messageList->setColumnWidth(DbMsgsTblModel::ANNOT_COL, m_colWidth[0]);
	ui->messageList->setColumnWidth(DbMsgsTblModel::SENDER_COL, m_colWidth[1]);
	ui->messageList->setColumnWidth(DbMsgsTblModel::RECIP_COL, m_colWidth[2]);

	ui->messageList->resizeColumnToContents(DbMsgsTblModel::DELIVERY_COL);
	ui->messageList->resizeColumnToContents(DbMsgsTblModel::ACCEPT_COL);

	/* These columns display an icon. */
	ui->messageList->setSquareColumnWidth(DbMsgsTblModel::PERSDELIV_COL);
	ui->messageList->setSquareColumnWidth(DbMsgsTblModel::READLOC_COL);
	ui->messageList->setSquareColumnWidth(DbMsgsTblModel::ATTDOWN_COL);
	ui->messageList->setSquareColumnWidth(DbMsgsTblModel::PROCSNG_COL);
	if (GlobInstcs::recMgmtSetPtr->isValid()) {
		ui->messageList->setSquareColumnWidth(
		    DbMsgsTblModel::MAX_COLNUM + DbMsgsTblModel::RECMGMT_NEG_COL);
	}

	if (m_sort_order == "SORT_ASCENDING") {
		ui->messageList->sortByColumn(m_sortCol, Qt::AscendingOrder);
	} else if (m_sort_order == "SORT_DESCENDING") {
		ui->messageList->sortByColumn(m_sortCol, Qt::DescendingOrder);
	}
}

void MainWindow::setSentColumnWidths(void)
{
	debugFuncCall();

	ui->messageList->resizeColumnToContents(DbMsgsTblModel::DMID_COL);

	/* Sender and recipient column must be set both here. */
	ui->messageList->setColumnWidth(DbMsgsTblModel::ANNOT_COL, m_colWidth[0]);
	ui->messageList->setColumnWidth(DbMsgsTblModel::SENDER_COL, m_colWidth[1]);
	ui->messageList->setColumnWidth(DbMsgsTblModel::RECIP_COL, m_colWidth[2]);

	ui->messageList->resizeColumnToContents(DbMsgsTblModel::DELIVERY_COL);
	ui->messageList->resizeColumnToContents(DbMsgsTblModel::ACCEPT_COL);
	ui->messageList->resizeColumnToContents(DbMsgsTblModel::MSGSTAT_COL);

	/* These columns display an icon. */
	ui->messageList->setSquareColumnWidth(DbMsgsTblModel::ATTDOWN_COL);
	if (GlobInstcs::recMgmtSetPtr->isValid()) {
		ui->messageList->setSquareColumnWidth(
		    DbMsgsTblModel::MAX_COLNUM + DbMsgsTblModel::RECMGMT_NEG_COL);
	}

	if (m_sort_order == "SORT_ASCENDING") {
		ui->messageList->sortByColumn(m_sortCol, Qt::AscendingOrder);
	} else if (m_sort_order == "SORT_DESCENDING") {
		ui->messageList->sortByColumn(m_sortCol, Qt::DescendingOrder);
	}
}

void MainWindow::onTableColumnResized(int index, int oldSize, int newSize)
{
	//debugSlotCall();

	Q_UNUSED(oldSize);
	const MWStatus mwStatus = m_selectionStatus.mwStatus();

	switch (mwStatus.acntNodeType()) {
	case AccountModel::nodeRecentReceived:
	case AccountModel::nodeReceived:
	case AccountModel::nodeReceivedYear:
		if (index == DbMsgsTblModel::ANNOT_COL) {
			m_colWidth[0] = newSize;
		} else if (index == DbMsgsTblModel::SENDER_COL) {
			m_colWidth[1] = newSize;
		}
		break;
	case AccountModel::nodeRecentSent:
	case AccountModel::nodeSent:
	case AccountModel::nodeSentYear:
		if (index == DbMsgsTblModel::ANNOT_COL) {
			m_colWidth[0] = newSize;
		} else if (index == DbMsgsTblModel::RECIP_COL) {
			m_colWidth[2] = newSize;
		}
		break;
	default:
		break;
	}
}

void MainWindow::onTableColumnHeaderSectionClicked(int column)
{
	debugSlotCall();

	m_sortCol = column;
	if (ui->messageList->horizontalHeader()->sortIndicatorOrder() ==
	    Qt::AscendingOrder) {
		m_sort_order = "SORT_ASCENDING";
	} else if (ui->messageList->horizontalHeader()->sortIndicatorOrder() ==
	           Qt::DescendingOrder) {
		m_sort_order = "SORT_DESCENDING";
	} else {
		m_sort_order = QString();
	}
}

void MainWindow::refreshAccountList(const AcntId &acntId)
{
	debugSlotCall();

	if (Q_UNLIKELY(!acntId.isValid())) {
		logWarningNL("%s",
		    "Cannot refresh account list on empty user name.");
		return;
	}

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &selectedAcntId = mwStatus.acntId();
	/* There may be no account selected. */

	const enum AccountModel::NodeType nodeType = mwStatus.acntNodeType();
	const QString &year = mwStatus.year();
	const enum MessageDb::MessageType msgType =
	    AccountModel::nodeTypeIsReceived(nodeType) ?
	        MessageDb::TYPE_RECEIVED : MessageDb::TYPE_SENT;
	qint64 dmId = -1;

	if (selectedAcntId == acntId) {
		/* Currently selected is the one being processed. */
		if (nodeType != AccountModel::nodeUnknown) {
			if (mwStatus.msgIds().size() == 1) {
				dmId = firstMsgId(mwStatus.msgIds()).dmId();
			}
		}
	}

	/* Redraw views' content. */
	const QModelIndex topAcntIdx(m_accountModel.topAcntIndex(acntId));
	if (topAcntIdx.isValid()) {
		bool prohibitYearRemoval =
		    (nodeType == AccountModel::nodeReceivedYear) ||
		    (nodeType == AccountModel::nodeSentYear);
		regenerateAccountModelYears(topAcntIdx, prohibitYearRemoval);
	}

	/*
	 * Force repaint.
	 * TODO -- A better solution?
	 */
	ui->accountList->repaint();
	if ((nodeType != AccountModel::nodeUnknown) && (dmId != -1)) {
		if (!year.isEmpty()) {
			QModelIndex yearIdx(accountYearlyIndex(acntId, year,
			    msgType));
			if (yearIdx.isValid()) {
				ui->accountList->selectionModel()->setCurrentIndex(
				    yearIdx, QItemSelectionModel::ClearAndSelect);
			}
		}

		adaptToAccountSelection();

		if (dmId != -1) {
			QModelIndex msgIdx(messageIndex(ui->messageList, dmId));
			if (msgIdx.isValid()) {
				ui->messageList->setCurrentIndex(msgIdx);
				ui->messageList->scrollTo(msgIdx);
			}
		}
	} else {
		/* Update message model. */
		adaptToAccountSelection();
	}
}

void MainWindow::sendMessageToRecordsManagement(const AcntId &acntId,
    MsgId msgId, const QString &recMgmtHierarchyId)
{
	if (Q_UNLIKELY(!acntId.isValid() || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}
	MessageDb *messageDb = dbSet->accessMessageDb(msgId.deliveryTime(),
	    false);
	if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
		Q_ASSERT(0);
		return;
	}

	QByteArray msgRaw(messageDb->getCompleteMessageRaw(msgId.dmId()));
	if (msgRaw.isEmpty()) {
		if (!messageMissingOfferDownload(msgId,
		        tr("Missing Message Content"))) {
			return;
		}

		messageDb = dbSet->accessMessageDb(msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			logErrorNL(
			    "Could not access database of freshly downloaded message '%" PRId64 "'.",
			    UGLY_QINT64_CAST msgId.dmId());
			return;
		}

		msgRaw = messageDb->getCompleteMessageRaw(msgId.dmId());
		if (Q_UNLIKELY(msgRaw.isEmpty())) {
			Q_ASSERT(0);
			return;
		}
	}

	/* Show send to records management dialogue. */
	DlgRecordsManagementUpload::uploadMessage(*GlobInstcs::recMgmtSetPtr,
	    msgId.dmId(),
	    QString("%1_%2.zfo").arg(dzPrefix(messageDb, msgId.dmId())).arg(msgId.dmId()),
	    msgRaw,
	    recMgmtHierarchyId.isEmpty() ? QStringList() : QStringList(recMgmtHierarchyId),
	    this);

	QList<qint64> msgIdList;
	msgIdList.append(msgId.dmId());

	m_messageTableModel.refillRecordsManagementColumn(msgIdList,
	    DbMsgsTblModel::RECMGMT_NEG_COL);
}

void MainWindow::viewSelectedMessageViaFilter(QObject *mwPtr)
{
	if (Q_NULLPTR == mwPtr) {
		return;
	}

	MainWindow *mw = dynamic_cast<MainWindow *>(mwPtr);
	if (Q_UNLIKELY(Q_NULLPTR == mw)) {
		Q_ASSERT(0);
		return;
	}

	mw->viewSelectedMessage();
}

bool MainWindow::downloadCompleteMessage(MsgId &msgId)
{
	debugFuncCall();

	/* selection().indexes() ? */

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	Q_ASSERT(acntId.isValid());

	enum MessageDirection msgDirect =
	    messageDirection(mwStatus.acntNodeType(), MSG_RECEIVED);

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return false;
	}

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(acntId.username()) &&
	    !connectToIsds(acntId)) {
		return false;
	}

	warnAboutPossibleDownloadFailure(msgDirect, msgId, dbSet, this);

	TaskDownloadMessage *task = new (::std::nothrow) TaskDownloadMessage(
	    AcntIdDb(acntId, dbSet), msgDirect, msgId,
	    Task::PROC_LIST_SCHEDULED_LAST);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}
	task->setAutoDelete(false);
	GlobInstcs::workPoolPtr->runSingle(task);
	bool ret = TaskDownloadMessage::DM_SUCCESS == task->m_result;
	if (ret) {
		msgId.setDeliveryTime(task->m_mId.deliveryTime());
	}

	delete task;

	return ret;
}

bool MainWindow::messageMissingOfferDownload(MsgId &msgId, const QString &title)
{
	debugFuncCall();

	int dlgRet = DlgMsgBox::message(this, QMessageBox::Warning, title,
	    tr("Complete message '%1' is missing.").arg(msgId.dmId()),
	    tr("First you must download the complete message to continue with the action.") +
	    "\n\n" +
	    tr("Do you want to download the complete message now?"),
	    QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);

	if ((QMessageBox::Yes == dlgRet) && downloadCompleteMessage(msgId)) {
		showStatusTextWithTimeout(
		    tr("Complete message '%1' has been downloaded.").
		    arg(msgId.dmId()));
		return true;
	} else {
		showStatusTextWithTimeout(
		    tr("Complete message '%1' has not been downloaded.").
		    arg(msgId.dmId()));
		return false;
	}
}

void MainWindow::sendAttachmentsEmail(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	if (Q_UNLIKELY(mwStatus.msgIds().size() != 1)) {
		Q_ASSERT(0);
		return;
	}
	const MsgId &msgId = firstMsgId(mwStatus.msgIds());

	QModelIndexList attachmentIndexes(currentFrstColAttachmentIndexes());

	QString emailMessage;
	const QString boundary = GuiMsgOps::emailBoundary();

	QString subject = ((1 == attachmentIndexes.size()) ?
	    tr("Attachment of message %1") : tr("Attachments of message %1")).
	    arg(msgId.dmId());

	createEmailMessage(emailMessage, subject, boundary);

	foreach (const QModelIndex &attachIdx, attachmentIndexes) {
		QString attachmentName(attachIdx.sibling(attachIdx.row(),
		    AttachmentTblModel::FNAME_COL).data().toString());
		const QByteArray binaryData(attachIdx.sibling(attachIdx.row(),
		    AttachmentTblModel::BINARY_CONTENT_COL).data().toByteArray());

		addAttachmentToEmailMessage(emailMessage, attachmentName,
		    binaryData.toBase64(), boundary);
	}

	finishEmailMessage(emailMessage, boundary);

	QString tmpEmailFile = writeTemporaryFile(
	    TMP_ATTACHMENT_PREFIX "mail.eml", emailMessage.toUtf8());

	if (!tmpEmailFile.isEmpty()) {
		QDesktopServices::openUrl(QUrl::fromLocalFile(tmpEmailFile));
	}
}

bool MainWindow::logInGUI(AcntData &acntSettings)
{
	Gui::LogInCycle login(ui->actionSynchroniseAllAccounts->text(), this);

	connect(&login, SIGNAL(logInStatusMessage(QString)),
	    this, SLOT(showStatusTextWithTimeout(QString)));

	bool ret = login.logIn(*GlobInstcs::isdsSessionsPtr, acntSettings);
	if (ret) {
		mui_statusOnlineLabel->setText(tr("Mode: online"));
	}

	login.disconnect(SIGNAL(logInStatusMessage(QString)),
	    this, SLOT(showStatusTextWithTimeout(QString)));

	return ret;
}

/*!
 * @brief Show dialogue that notifies the user about expiring password.
 *
 * @param[in] parent Parent widget.
 * @param[in] accountName Account name.
 * @param[in] acntId Account identifier.
 * @param[in] days Number of days before the password expires.
 * @param[in] dateTime Time of expiration.
 * @param[in] acntPwdChangeName Name of the action which can change the password.
 * @return Returns standard button value.
 */
static
int showDialogueAboutPwdExpir(QWidget *parent, const QString &accountName,
    const AcntId &acntId, qint64 days, const QDateTime &dateTime,
    const QString &acntPwdChangeName)
{
	debugFuncCall();

	int dlgRet = QMessageBox::No;

	if (days < 0) {
		DlgMsgBox::message(parent, QMessageBox::Information,
		    MainWindow::tr("Password expiration"),
		    MainWindow::tr("According to the last available information, "
		        "your password for account '%1' (login '%2') expired %3 days ago (%4).")
		        .arg(accountName).arg(acntId.username()).arg(days*(-1))
		        .arg(dateTime.toString("dd.MM.yyyy hh:mm:ss")),
		    MainWindow::tr("You have to change your password from the ISDS web interface. "
		        "Your new password will be valid for 90 days."),
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	} else {
		dlgRet = DlgMsgBox::message(parent, QMessageBox::Information,
		    MainWindow::tr("Password expiration"),
		    MainWindow::tr("According to the last available information, "
		        "your password for account '%1' (login '%2') will expire in %3 days (%4).")
		        .arg(accountName).arg(acntId.username()).arg(days)
		        .arg(dateTime.toString("dd.MM.yyyy hh:mm:ss")),
		    MainWindow::tr("You can change your password now or later using the '%1' command. "
		        "Your new password will be valid for 90 days.\n\n"
		        "Change password now?").arg(acntPwdChangeName),
		    QString(), QMessageBox::No | QMessageBox::Yes,
		    QMessageBox::No);
	}

	return dlgRet;
}

bool MainWindow::connectToIsds(const AcntId &acntId, bool shadow)
{
	AcntData settingsCopy((!shadow) ?
	    GlobInstcs::acntMapPtr->acntData(acntId) :
	    GlobInstcs::shadowAcntsPtr->acntData(acntId));

	if (!logInGUI(settingsCopy)) {
		return false;
	}
	/* Logged in. */

	if (!shadow) {
		GlobInstcs::acntMapPtr->updateAccount(settingsCopy);
	} else {
		GlobInstcs::shadowAcntsPtr->updateAccount(settingsCopy);
	}
	saveSettings();

	/* Get account information if possible. */
	if (!IsdsHelper::getOwnerInfoFromLogin(acntId)) {
		logWarningNL("Owner information for account '%s' (login %s) could not be acquired.",
		    settingsCopy.accountName().toUtf8().constData(),
		    acntId.username().toUtf8().constData());
	}
	if (!IsdsHelper::getUserInfoFromLogin(acntId)) {
		logWarningNL("User information for account '%s' (login %s) could not be acquired.",
		    settingsCopy.accountName().toUtf8().constData(),
		    acntId.username().toUtf8().constData());
	}
	if (!IsdsHelper::getPasswordInfoFromLogin(acntId)) {
		logWarningNL("Password information for account '%s' (login %s) could not be acquired.",
		    settingsCopy.accountName().toUtf8().constData(),
		    acntId.username().toUtf8().constData());
	}

	/* Check password expiration. */
	if (!settingsCopy._pwdExpirDlgShown()) {
		/* Notify only once. */
		settingsCopy._setPwdExpirDlgShown(true);

		const QString acntDbKey(AccountDb::keyFromLogin(acntId.username()));

		int daysTo = GlobInstcs::accntDbPtr->pwdExpiresInDays(acntDbKey,
		    PWD_EXPIRATION_NOTIFICATION_DAYS);

		if (daysTo >= 0) {
			logWarningNL(
			    "Password for user '%s' of account '%s' expires in %d days.",
			    acntId.username().toUtf8().constData(),
			    settingsCopy.accountName().toUtf8().constData(),
			    daysTo);

			/* Password change dialogue. */
			const QDateTime dbDateTime(
			    GlobInstcs::accntDbPtr->getPwdExpirFromDb(acntDbKey));
			if (QMessageBox::Yes == showDialogueAboutPwdExpir(this,
			        settingsCopy.accountName(), acntId, daysTo,
			        dbDateTime, ui->actionChangePassword->text())) {
				showStatusTextWithTimeout(tr(
				    "Change password of account '%1'.")
				    .arg(settingsCopy.accountName()));
				const QString dbId(
				    GlobInstcs::accntDbPtr->dbId(acntDbKey));
				/* TODO -- Make it work also for shadow account. */
				if (!shadow) {
					DlgChangePwd::changePassword(dbId, acntId,
					    this);
				}
			}
		}
	}

	/* Set longer time-out. */
	GlobInstcs::isdsSessionsPtr->setSessionTimeout(acntId.username(),
	    PrefsSpecific::isdsDownloadTimeoutMs(*GlobInstcs::prefsPtr));

	return true;
}

bool MainWindow::firstConnectToIsds(AcntData &accountInfo)
{
	debugFuncCall();

	if (!logInGUI(accountInfo)) {
		return false;
	}

	const AcntId acntId(accountInfo.userName(), accountInfo.isTestAccount());
	Q_ASSERT(acntId.isValid());

	if (!IsdsHelper::getOwnerInfoFromLogin(acntId)) {
		//TODO: return false;
	}
	if (!IsdsHelper::getUserInfoFromLogin(acntId)) {
		//TODO: return false;
	}
	if (!IsdsHelper::getPasswordInfoFromLogin(acntId)) {
		//TODO: return false;
	}

	return true;
}

void MainWindow::getAccountUserDataboxInfo(AcntData accountInfo)
{
	debugSlotCall();

	if (!GlobInstcs::isdsSessionsPtr->isConnectedToIsds(
	        accountInfo.userName())) {
		if (!firstConnectToIsds(accountInfo)) {
			QString msgBoxTitle = tr("New account error") +
			    ": " + accountInfo.accountName();
			QString msgBoxContent =
			    tr("It was not possible to get user info and "
			    "databox info from ISDS server for account")
			    + " \"" + accountInfo.accountName() + "\"."
			    + "<br><br><b>" +
			    tr("Connection to ISDS or user authentication failed!")
			    + "</b><br><br>" +
			    tr("Please check your internet connection and "
			    "try again or it is possible that your password "
			    "(certificate) has expired - in this case, you "
			    "need to use the official web interface of Datové "
			    "schránky to change it.")
			    + "<br><br><b>" +
			    tr("Account") + "<i>" + " \"" +
			    accountInfo.accountName() + "\" " + "("
			    + accountInfo.userName() + ") </i> " +
			    tr("was not created!") + "</b>";

			QMessageBox::critical(this,
			    msgBoxTitle,
			    msgBoxContent,
			    QMessageBox::Ok);

			return;
		}
	}

	/* Store account information. */
	int ret = GlobInstcs::acntMapPtr->addAccount(accountInfo);
	if (ret == 0) {
		const AcntId acntId(accountInfo.userName(), accountInfo.isTestAccount());
		refreshAccountList(acntId);
		QModelIndex index(m_accountModel.topAcntIndex(acntId));

		/* Change selection to newly added account. */
		if (index.isValid()) {
			logDebugLv0NL("Changing selection to index %d.", index.row());
			ui->accountList->selectionModel()->setCurrentIndex(index,
			    QItemSelectionModel::ClearAndSelect);
			/* Expand the tree. */
			ui->accountList->expand(index);
		}
	} else if (ret == -1) {
		QMessageBox::warning(this, tr("Adding new account failed"),
		    tr("Account could not be added because an error occurred."),
		    QMessageBox::Ok);
	} else if (ret == -2) {
		logErrorNL("Account with username '%s' already exists.",
		    accountInfo.userName().toUtf8().constData());
		QMessageBox::warning(this, tr("Adding new account failed"),
		    tr("Account could not be added because account already exists."),
		    QMessageBox::Ok);
	}
}

void MainWindow::msgSetSelectedMessageProcessState(int stateIndex)
{
	debugSlotCall();

	enum MessageDb::MessageProcessState procSt = MessageDb::UNSETTLED;
	switch (stateIndex) {
	case MessageDb::UNSETTLED:
		procSt = MessageDb::UNSETTLED;
		break;
	case MessageDb::IN_PROGRESS:
		procSt = MessageDb::IN_PROGRESS;
		break;
	case MessageDb::SETTLED:
		procSt = MessageDb::SETTLED;
		break;
	default:
		Q_ASSERT(0);
		return;
		break;
	}

	messageItemsSetProcessStatus(procSt);
}

void MainWindow::messageItemsSetReadStatus(bool read)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	const QSet<MsgId> &msgIds = mwStatus.msgIds();

	/* Works only for received messages. */
	if (!AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType())) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	if (msgIds.isEmpty()) {
		return;
	}

	/*
	 * Messages can be in multiple databases when using recently
	 * received/sent messages.
	 */
	QMap<MessageDb *, QList<MsgId> > msgIdMap;
	foreach (const MsgId &msgId, msgIds) {
		MessageDb *messageDb = dbSet->accessMessageDb(
		    msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}
		msgIdMap[messageDb].append(msgId);
	}

	foreach (MessageDb *messageDb, msgIdMap.keys()) {
		messageDb->setMessagesLocallyRead(msgIdMap[messageDb], read);
	}
}

void MainWindow::messageItemsSetProcessStatus(
    enum MessageDb::MessageProcessState state)
{
	debugFuncCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	const QSet<MsgId> &msgIds = mwStatus.msgIds();

	/* Works only for received messages. */
	if (!AccountModel::nodeTypeIsReceived(mwStatus.acntNodeType())) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	if (msgIds.isEmpty()) {
		return;
	}

	/*
	 * Messages can be in multiple databases when using recently
	 * received/sent messages.
	 */
	QMap<MessageDb *, QList<MsgId> > msgIdMap;
	foreach (const MsgId &msgId, msgIds) {
		MessageDb *messageDb = dbSet->accessMessageDb(
		    msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}
		msgIdMap[messageDb].append(msgId);
	}

	foreach (MessageDb *messageDb, msgIdMap.keys()) {
		messageDb->setMessagesProcessState(msgIdMap[messageDb], state);
	}
}

void MainWindow::prepareMsgTmstmpExpir(enum DlgTimestampExpir::Action action)
{
	debugFuncCall();

	AcntId acntId;
	bool includeSubdir = false;
	QString importDir;
	QStringList fileList, filePathList;
	QStringList nameFilter("*.zfo");
	QDir directory(QDir::home());
	fileList.clear();
	filePathList.clear();

	switch (action) {
	case DlgTimestampExpir::CHECK_SELECTED_ACNT:
		/* Process the selected account. */
		acntId = m_selectionStatus.mwStatus().acntId();
		Q_ASSERT(acntId.isValid());
		showStatusTextPermanently(tr("Checking time stamps in account '%1'...")
		    .arg(GlobInstcs::acntMapPtr->acntData(acntId).accountName()));
		checkMsgsTmstmpExpiration(acntId, QStringList());
		break;

	case DlgTimestampExpir::CHECK_ALL_ACNTS:
		for (int i = 0; i < m_accountModel.rowCount(); ++i) {
			QModelIndex index = m_accountModel.index(i, 0);
			acntId = m_accountModel.acntId(index);
			Q_ASSERT(acntId.isValid());
			showStatusTextPermanently(
			    tr("Checking time stamps in account '%1'...").arg(
			        GlobInstcs::acntMapPtr->acntData(acntId).accountName()));
			checkMsgsTmstmpExpiration(acntId, QStringList());
		}
		break;

	case DlgTimestampExpir::CHECK_DIR_SUB:
		includeSubdir = true;
		Q_FALLTHROUGH();
	case DlgTimestampExpir::CHECK_DIR:
		importDir = QFileDialog::getExistingDirectory(this,
		    tr("Select Directory"),
		    PrefsSpecific::importDbZfo(*GlobInstcs::prefsPtr),
		    QFileDialog::ShowDirsOnly |
		    QFileDialog::DontResolveSymlinks);

		if (importDir.isEmpty()) {
			return;
		}

		PrefsSpecific::setImportDbZfo(*GlobInstcs::prefsPtr, importDir);

		if (includeSubdir) {
			QDirIterator it(importDir, nameFilter, QDir::Files,
			    QDirIterator::Subdirectories);
			while (it.hasNext()) {
				filePathList.append(it.next());
			}
		} else {
			directory.setPath(importDir);
			fileList = directory.entryList(nameFilter);
			for (int i = 0; i < fileList.size(); ++i) {
				filePathList.append(
				    importDir + "/" + fileList.at(i));
			}
		}

		if (filePathList.isEmpty()) {
			qDebug() << "ZFO-IMPORT:" << "No *.zfo file(s) in the "
			    "selected directory";
			showStatusTextWithTimeout(tr("ZFO file(s) not found in "
			    "selected directory."));
			QMessageBox::warning(this,
			    tr("No ZFO file(s)"),
			    tr("ZFO file(s) not found in selected directory."),
			    QMessageBox::Ok);
			return;
		}

		checkMsgsTmstmpExpiration(AcntId(), filePathList);

		break;
	default:
		break;
	}

	clearStatusBar();
}

void MainWindow::checkMsgsTmstmpExpiration(const AcntId &acntId,
    const QStringList &filePathList)
{
	debugFuncCall();

	QList<MsgId> expirMsgIds;
	QStringList expirMsgFileNames;
	QList<MsgId> errorMsgIds;
	QStringList errorMsgFileNames;

	QByteArray tstData;
	int msgCnt = 0;
	QString dlgText;
	bool showExportOption = true;

	qint64 notifyBeforeDays = 0;
	if (Q_UNLIKELY(!GlobInstcs::prefsPtr->intVal(
	        "crypto.message.timestamp.expiration.notify_ahead.days",
	        notifyBeforeDays))) {
		Q_ASSERT(0);
	}

	if (!acntId.isValid()) {

		msgCnt = filePathList.count();

		for (int i = 0; i < msgCnt; ++i) {

			Isds::Message message(
			    Isds::messageFromFile(filePathList.at(i),
			        Isds::LT_MESSAGE));
			if (message.isNull() || message.envelope().isNull()) {
				errorMsgFileNames.append(filePathList.at(i));
				continue;
			}

			const QByteArray &tstData(message.envelope().dmQTimestamp());
			if (tstData.isEmpty()) {
				errorMsgFileNames.append(filePathList.at(i));
				continue;
			}

			if (DlgSignatureDetail::signingCertExpiresBefore(tstData,
			        notifyBeforeDays)) {
				expirMsgFileNames.append(filePathList.at(i));
			}
		}

		dlgText = tr("Time stamp expiration check of ZFO files finished with result:")
		    + "<br/><br/>" +
		    tr("Total of ZFO files: %1").arg(msgCnt)
		    + "<br/><b>" +
		    tr("ZFO files with time stamp expiring within %1 days: %2")
		        .arg(notifyBeforeDays).arg(expirMsgFileNames.count())
		    + "</b><br/>" +
		    tr("Unchecked ZFO files: %1").arg(errorMsgFileNames.count());

		showExportOption = false;

	} else {
		MessageDbSet *dbSet = accountDbSet(acntId, this);
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			return;
		}

		QList<MsgId> msgIdList(dbSet->getAllMessageIDsFromDB());
		msgCnt = msgIdList.count();

		foreach (const MsgId &mId, msgIdList) {
			MessageDb *messageDb = dbSet->accessMessageDb(
			    mId.deliveryTime(), false);
			if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
				Q_ASSERT(0);
				continue;
			}

			tstData = messageDb->getMessageTimestampRaw(mId.dmId());
			if (tstData.isEmpty()) {
				errorMsgIds.append(mId);
				continue;
			}
			if (DlgSignatureDetail::signingCertExpiresBefore(tstData,
			        notifyBeforeDays)) {
				expirMsgIds.append(mId);
			}
		}

		dlgText = tr("Time stamp expiration check "
		    "in account '%1' finished with result:").arg(
		        GlobInstcs::acntMapPtr->acntData(acntId).accountName())
		    + "<br/><br/>" +
		    tr("Total of messages in database: %1").arg(msgCnt)
		    + "<br/><b>" +
		    tr("Messages with time stamp expiring within %1 days: %2")
		        .arg(notifyBeforeDays).arg(expirMsgIds.count())
		    + "</b><br/>" +
		    tr("Unchecked messages: %1").arg(errorMsgIds.count());
	}

	QString infoText;
	QString detailText;
	QMessageBox::StandardButtons buttons = QMessageBox::Ok;
	enum QMessageBox::StandardButton dfltDutton = QMessageBox::Ok;

	if (!expirMsgIds.isEmpty() || !expirMsgFileNames.isEmpty() ||
	    !errorMsgIds.isEmpty() || !errorMsgFileNames.isEmpty()) {
		infoText = tr("See details for more info...") + "<br/><br/>";
		if (!expirMsgIds.isEmpty() && showExportOption) {
			infoText += "<b>" +
			    tr("Do you want to export the expiring "
			    "messages to ZFO?") + "</b><br/><br/>";
		}

		if (!expirMsgIds.isEmpty() || !errorMsgIds.isEmpty()) {
			for (int i = 0; i < expirMsgIds.count(); ++i) {
				detailText += tr("Time stamp of message %1 expires "
				    "within specified interval.").arg(expirMsgIds.at(i).dmId());
				if (((expirMsgIds.count() - 1) != i) ||
				    errorMsgIds.count()) {
					detailText += "\n";
				}
			}
			for (int i = 0; i < errorMsgIds.count(); ++i) {
				detailText += tr("Time stamp of message %1 "
				    "is not present.").arg(errorMsgIds.at(i).dmId());
				if ((expirMsgIds.count() - 1) != i) {
					detailText += "\n";
				}
			}
		} else {
			for (int i = 0; i < expirMsgFileNames.count(); ++i) {
				detailText += tr("Time stamp of message %1 expires "
				    "within specified interval.").arg(expirMsgFileNames.at(i));
				if (((expirMsgFileNames.count() - 1) != i) ||
				    errorMsgFileNames.count()) {
					detailText += "\n";
				}
			}
			for (int i = 0; i < errorMsgFileNames.count(); ++i) {
				detailText += tr("Time stamp of message %1 "
				    "is not present.").arg(errorMsgFileNames.at(i));
				if ((expirMsgFileNames.count() - 1) != i) {
					detailText += "\n";
				}
			}
		}

		if (!expirMsgIds.isEmpty() && showExportOption) {
			buttons = QMessageBox::No | QMessageBox::Yes;
			dfltDutton = QMessageBox::No;
		}
	}

	int dlgRet = DlgMsgBox::message(this, QMessageBox::Information,
	    tr("Time stamp expiration check results"), dlgText, infoText,
	    detailText, buttons, dfltDutton);

	if (QMessageBox::Yes == dlgRet) {
		if (acntId.isValid()) {
			exportExpirMessagesToZFO(acntId, expirMsgIds);
		}
	}
}

void MainWindow::exportExpirMessagesToZFO(const AcntId &acntId,
    const QList<MsgId> &expirMsgIds)
{
	QString newDir = QFileDialog::getExistingDirectory(this,
	    tr("Select Directory"),
	    PrefsSpecific::acntZfoDir(*GlobInstcs::prefsPtr, acntId),
	    QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

	if (newDir.isEmpty()) {
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	QString lastPath = newDir;
	QString errStr;
	const QString dbId(
	    GlobInstcs::accntDbPtr->dbId(AccountDb::keyFromLogin(acntId.username())));
	Exports::ExportError ret;

	const QString accountName(
	    GlobInstcs::acntMapPtr->acntData(acntId).accountName());

	foreach (MsgId mId, expirMsgIds) {
		ret = Exports::exportAs(this, *dbSet, Exports::ZFO_MESSAGE,
		    newDir, QString(), acntId.username(), accountName, dbId, mId, false,
		    lastPath, errStr);
		if (Exports::EXP_CANCELED == ret) {
			break;
		} else if (Exports::EXP_NOT_MSG_DATA == ret) {
			if (messageMissingOfferDownload(mId, errStr)) {
			    Exports::exportAs(this, *dbSet, Exports::ZFO_MESSAGE,
			    newDir, QString(), acntId.username(), accountName, dbId, mId,
			    false, lastPath, errStr);
			}
		}
		if (!lastPath.isEmpty()) {
			PrefsSpecific::setAcntZfoDir(*GlobInstcs::prefsPtr,
			    acntId, lastPath);
		}
	}
}

void MainWindow::modifyTags(const AcntId &acntId, QList<qint64> msgIdList)
{
	if (Q_UNLIKELY(GlobInstcs::tagDbPtr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	if (msgIdList.isEmpty()) {
		DlgTags::editAvailable(acntId, GlobInstcs::tagDbPtr, this);
	} else if ((acntId.isValid() && !msgIdList.isEmpty()) || acntId.isValid()) {
		DlgTags::editAssignment(acntId, GlobInstcs::tagDbPtr,
		    msgIdList, this);
	} else {
		Q_ASSERT(0);
		return;
	}

	/* Model content is updated through signals from tag database. */
}

void MainWindow::showImportMessageResults(const QString &userName,
    const QStringList &errImportList, int totalMsgs, int importedMsgs)
{
	showStatusTextPermanently(
	    tr("Import of messages to account %1 finished").arg(userName));

	QString detailText;
	if (errImportList.count() > 0) {
		for (int m = 0; m < errImportList.count(); ++ m) {
			detailText += errImportList.at(m) + "\n";
		}
	}

	DlgMsgBox::message(this, QMessageBox::Information,
	    tr("Messages import result"),
	    tr("Import of messages into account '%1' finished with result:")
	        .arg(userName),
	    tr("Total of messages in database: %1").arg(totalMsgs) + "<br/><b>" +
	        tr("Imported messages: %1").arg(importedMsgs) + "<br/>" +
	        tr("Non-imported messages: %1").arg(errImportList.count()) + "</b><br/>",
	    detailText, QMessageBox::Ok);
}

void MainWindow::dockMenuPopulate(void)
{
	static const QIcon winIco(
	    IconContainer::construcIcon(IconContainer::ICON_MACOS_WINDOW));

	mui_dockMenu.clear();
	QAction *action;

	/* Main window. */
	action = mui_dockMenu.addAction(this->windowTitle());
	action->setData(QVariant::fromValue(this));
	action->setCheckable(true);
	action->setChecked(this->isActiveWindow());
#ifdef Q_OS_OSX
	action->setIconVisibleInMenu(true);
	action->setIcon(winIco);
#endif /* Q_OS_OSX */

	foreach (QWidget *widget, QApplication::topLevelWidgets()) {
		QDialog *dialogue = qobject_cast<QDialog *>(widget);
		if (dialogue == Q_NULLPTR) {
			continue;
		}
		/* Remaining dialogue windows. */
		action = mui_dockMenu.addAction(dialogue->windowTitle());
		action->setData(QVariant::fromValue(dialogue));
		action->setCheckable(true);
		action->setChecked(dialogue->isActiveWindow());
#ifdef Q_OS_OSX
		action->setIconVisibleInMenu(true);
		action->setIcon(winIco);
#endif /* Q_OS_OSX */
	}
}

void MainWindow::dockMenuActionTriggerred(QAction *action)
{
	if (Q_UNLIKELY(action == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	QObject *object = Q_NULLPTR;
	{
		QVariant data(action->data());
		if (data.canConvert<QObject *>()) {
			object = qvariant_cast<QObject *>(data);
		}
	}
	if (Q_UNLIKELY(object == Q_NULLPTR)) {
		return;
	}
	QWidget *widget = qobject_cast<QWidget *>(object);
	if (Q_UNLIKELY(widget == Q_NULLPTR)) {
		return;
	}
	if (!widget->isMinimized()) {
		widget->show();
	} else {
		widget->showNormal();
	}
	widget->raise();
	widget->activateWindow();
}
