/*
 * Copyright (C) 2014-2024 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 <QComboBox>
#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 <QPropertyAnimation>
#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/compiler.h" /* ignoreRetVal, macroStdMove */
#include "src/datovka_shared/compat_qt/misc.h" /* Q_FALLTHROUGH */
#include "src/datovka_shared/html/html_export.h"
#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/utility/date_time.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_convert_import_from_mobile_app.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_email_content.h"
#include "src/gui/dlg_msg_box_detail.h"
#include "src/gui/dlg_msg_box_detail_nonmodal.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/dlg_tag_settings.h"
#include "src/gui/dlg_tool_bar.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/stats_reporter.h"
#include "src/io/tag_client.h"
#include "src/io/tag_container.h"
#include "src/io/tag_db.h"
#include "src/io/update_checker.h"
#include "src/json/tag_assignment_hash.h"
#include "src/json/tag_message_id.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_check_msgs.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/settings/tag_container_settings_apply.h"
#include "src/settings/tag_container_settings.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 MAIN_TOP_TOOLBAR_VISIBLE_KEY  "window.main.toolbar.visible"
#define MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY "window.main.top_panels.toolbar.visible"

#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))

static
volatile int req_close = 0; /*!< Set to 1 if application close requested. */

/* Used for detecting newest available update. */
static UpdateChecker *updateChecker = Q_NULLPTR;
/* Used to report application stats. */
static StatsReporter *statsReporter = Q_NULLPTR;

static const QString attachmentTableName("attachment_list");

/*!
 * @brief Add default values into preferences.
 *
 * @param[in,out] prefs Preferences.
 */
static
void addPrefsDefaults(Prefs &prefs)
{
	prefs.setDefault(Prefs::Entry(
	    MAIN_TOP_TOOLBAR_VISIBLE_KEY,
	    PrefsDb::VAL_BOOLEAN, false));
	prefs.setDefault(Prefs::Entry(
	    MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY,
	    PrefsDb::VAL_BOOLEAN, true));
}

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);
}

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 (GlobInstcs::req_halt || req_close) {
			GlobInstcs::workPoolPtr->stop();
			GlobInstcs::workPoolPtr->clear();
		} else {
			viewAskCloseDlg();
			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(this));
	QRect frameRect(frameGeometry());

	if (isMaximized() ||
	    ((frameRect.width() <= availRect.width()) &&
	    (frameRect.height() <= availRect.height()))) {
		/* Window fits to screen. */
		Dimensions::setTableColumnWidths(ui->attachmentList,
		    PrefsSpecific::mainWinColWidths(*GlobInstcs::prefsPtr,
		        attachmentTableName));
		Dimensions::setTableColumnSortOrder(ui->attachmentList,
		    PrefsSpecific::mainWinTblColSortOrder(*GlobInstcs::prefsPtr,
		    attachmentTableName));
		return;
	}

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

	Dimensions::setTableColumnWidths(ui->attachmentList,
	    PrefsSpecific::mainWinColWidths(*GlobInstcs::prefsPtr,
	        attachmentTableName));
	Dimensions::setTableColumnSortOrder(ui->attachmentList,
	    PrefsSpecific::mainWinTblColSortOrder(*GlobInstcs::prefsPtr,
	    attachmentTableName));
}

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

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

	/* Tool bar elements need to have focus enabled. */
	if (Q_NULLPTR != accountToolBar) {
		QWidget::setTabOrder(ui->toolBar, accountToolBar);
		QWidget::setTabOrder(accountToolBar, ui->accountList);
	} else {
		QWidget::setTabOrder(ui->toolBar, ui->accountList);
	}
	QWidget::setTabOrder(ui->accountList, ui->accountTextInfo);
	if (Q_NULLPTR != messageToolBar) {
		QWidget::setTabOrder(ui->accountTextInfo, messageToolBar);
		QWidget::setTabOrder(messageToolBar, ui->messageList);
	} else {
		QWidget::setTabOrder(ui->accountTextInfo, ui->messageList);
	}
	QWidget::setTabOrder(ui->messageList, ui->messageFilterLine);
	QWidget::setTabOrder(ui->messageFilterLine, ui->closeFilterButton);
	if (Q_NULLPTR != detailToolBar) {
		QWidget::setTabOrder(ui->closeFilterButton, detailToolBar);
		QWidget::setTabOrder(detailToolBar, ui->messageInfo);
	} else {
		QWidget::setTabOrder(ui->closeFilterButton, ui->messageInfo);
	}
	if (Q_NULLPTR != attachmentToolBar) {
		QWidget::setTabOrder(ui->messageInfo, attachmentToolBar);
		QWidget::setTabOrder(attachmentToolBar, ui->attachmentList);
	} else {
		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);
		ui->actionCheckUploadMessagesRecordsManagement->setIcon(ico);
	}
	    /* Separator. */
	ui->actionTagStorageSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_LABEL));
	    /* 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_NEW_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_DATABOX_SETTINGS));
	    /* 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_FORWARD));
	ui->actionUseAsTemplate->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_MESSAGE));
	    /* Separator. */
	ui->actionSignatureDetail->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATOVKA_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();
	ui->actionEmailContentSelection->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));
	    /* Separator. */
	ui->actionConvertImportFromMobileApp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE));

	/* 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();
	ui->actionFilterMessages->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_FILTER_MESSAGE));
}

/*!
 * @brief Constructs a list of default tool bar actions.
 *
 * @param[in] ui Main window user interface.
 * @return List of actions, null values represent separators.
 */
static
QList<QAction *> defaultTopToolBarActions(Ui::MainWindow *ui)
{
	QList<QAction *> actionList;

	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		return actionList;
	}

	actionList.append(ui->actionSynchroniseAllAccounts);
	actionList.append(ui->actionSynchroniseAccount);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionSendMessage);
	actionList.append(ui->actionReply);
	actionList.append(ui->actionVerifyMessage);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionMessageSearch);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionAccountProperties);
	actionList.append(ui->actionPreferences);

	return actionList;
}

/*!
 * @brief Constructs a list of default account list bar actions.
 *
 * @param[in] ui Main window user interface.
 * @return List of actions, null values represent separators.
 */
static
QList<QAction *> defaultAccountBarActions(Ui::MainWindow *ui)
{
	QList<QAction *> actionList;

	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		return actionList;
	}

	actionList.append(ui->actionMessageSearch);
	actionList.append(ui->actionSynchroniseAllAccounts);

	return actionList;
}

/*!
 * @brief Constructs a list of default message list bar actions.
 *
 * @param[in] ui Main window user interface.
 * @return List of actions, null values represent separators.
 */
static
QList<QAction *> defaultMessageBarActions(Ui::MainWindow *ui)
{
	QList<QAction *> actionList;

	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		return actionList;
	}

	actionList.append(ui->actionSynchroniseAccount);
	actionList.append(ui->actionSendMessage);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionAccountProperties);

	return actionList;
}

/*!
 * @brief Constructs a list of default message detail bar actions.
 *
 * @param[in] ui Main window user interface.
 * @return List of actions, null values represent separators.
 */
static
QList<QAction *> defaultDetailBarActions(Ui::MainWindow *ui)
{
	QList<QAction *> actionList;

	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		return actionList;
	}

	actionList.append(ui->actionForward);
	actionList.append(ui->actionReply);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionSignatureDetail);
	actionList.append(Q_NULLPTR);
	actionList.append(ui->actionVerifyMessage);

	return actionList;
}

/*!
 * @brief Constructs a list of default attachment list bar actions.
 *
 * @param[in] ui Main window user interface.
 * @return List of actions, null values represent separators.
 */
static
QList<QAction *> defaultAttachmentBarActions(Ui::MainWindow *ui)
{
	QList<QAction *> actionList;

	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		return actionList;
	}

	actionList.append(ui->actionDownloadMessage);
	actionList.append(ui->actionSaveAllAttachments);
	actionList.append(ui->actionSaveSelectedAttachments);
	actionList.append(ui->actionOpenAttachment);

	return actionList;
}

/*!
 * @brief Adds actions to top tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @param[in]     mw Main window.
 * @param[out]    firstUnconfigurableToolbarElement Pointer to first non-configurable element.
 */
static
void setUpTopToolBar(Ui::MainWindow *ui, QMainWindow *mw,
     QAction **firstUnconfigurableToolbarElement)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	/* Add actions to the top tool bar. */
	loadToolBarActions(mw, DlgToolBar::MAIN_TOP, ui->toolBar,
	    defaultTopToolBarActions(ui));

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

			if ((firstUnconfigurableToolbarElement != Q_NULLPTR) &&
			    (*firstUnconfigurableToolbarElement == Q_NULLPTR)) {
				*firstUnconfigurableToolbarElement = action;
			}
		}
	}

	insertToolBarActions(ui->toolBar, {ui->actionFilterMessages}, Q_NULLPTR);
}

/*!
 * @brief Adds actions to account list tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @param[in]     mw Main window.
 * @param[out]    lastUnconfigurableToolbarElement Pointer to last non-configurable element.
 */
static
QToolBar *setUpAccountToolBar(Ui::MainWindow *ui, QMainWindow *mw,
    QAction **lastUnconfigurableToolbarElement)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	QToolBar *accountToolBar = new (::std::nothrow) QToolBar(mw);
	if (Q_UNLIKELY(Q_NULLPTR == accountToolBar)) {
		return Q_NULLPTR;
	}

	ui->accountsVerticalLayout->setMenuBar(accountToolBar);

	accountToolBar->setToolButtonStyle(toolBarButtonStyle(DlgToolBar::ACCOUNT));

	{
		QWidget *spacer = new (::std::nothrow) QWidget(mw);
		if (Q_NULLPTR != spacer) {
			spacer->setSizePolicy(QSizePolicy::Expanding,
			    QSizePolicy::Expanding);
			QAction *action = accountToolBar->addWidget(spacer);

			if ((lastUnconfigurableToolbarElement != Q_NULLPTR) &&
			    (*lastUnconfigurableToolbarElement == Q_NULLPTR)) {
				*lastUnconfigurableToolbarElement = action;
			}
		}
	}

	/* Add actions to the top tool bar. */
	loadToolBarActions(mw, DlgToolBar::ACCOUNT, accountToolBar,
	    defaultAccountBarActions(ui));

	return accountToolBar;
}

/*!
 * @brief Adds actions to message list tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @param[in]     mw Main window.
 * @param[out]    lastUnconfigurableToolbarElement Pointer to last non-configurable element.
 */
static
QToolBar *setUpMessageToolBar(Ui::MainWindow *ui, QMainWindow *mw,
    QAction **lastUnconfigurableToolbarElement,
    QAction **firstUnconfigurableToolbarElement)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	QToolBar *messageToolBar = new (::std::nothrow) QToolBar(mw);
	if (Q_UNLIKELY(Q_NULLPTR == messageToolBar)) {
		return Q_NULLPTR;
	}

	ui->messagesVerticalLayout->setMenuBar(messageToolBar);

	messageToolBar->setToolButtonStyle(toolBarButtonStyle(DlgToolBar::MESSAGE));

	{
		QWidget *spacer = new (::std::nothrow) QWidget(mw);
		if (Q_NULLPTR != spacer) {
			spacer->setSizePolicy(QSizePolicy::Expanding,
			    QSizePolicy::Expanding);
			QAction *action = messageToolBar->addWidget(spacer);

			if ((lastUnconfigurableToolbarElement != Q_NULLPTR) &&
			    (*lastUnconfigurableToolbarElement == Q_NULLPTR)) {
				*lastUnconfigurableToolbarElement = action;
			}
		}
	}

	/* Add actions to the top tool bar. */
	loadToolBarActions(mw, DlgToolBar::MESSAGE, messageToolBar,
	    defaultMessageBarActions(ui));

	{
		QAction *action = messageToolBar->insertSeparator(Q_NULLPTR);

		if ((firstUnconfigurableToolbarElement != Q_NULLPTR) &&
		    (*firstUnconfigurableToolbarElement == Q_NULLPTR)) {
			*firstUnconfigurableToolbarElement = action;
		}
	}

	insertToolBarActions(messageToolBar, {ui->actionFilterMessages}, Q_NULLPTR);
	if ((firstUnconfigurableToolbarElement != Q_NULLPTR) &&
	    (*firstUnconfigurableToolbarElement == Q_NULLPTR)) {
		*firstUnconfigurableToolbarElement = ui->actionFilterMessages;
	}

	return messageToolBar;
}

/*!
 * @brief Adds actions to message detail tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @param[in]     mw Main window.
 * @param[out]    lastUnconfigurableToolbarElement Pointer to last non-configurable element.
 */
static
QToolBar *setUpDetailToolBar(Ui::MainWindow *ui, QMainWindow *mw,
    QAction **lastUnconfigurableToolbarElement, QComboBox **messageStateCombo)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	QToolBar *detailToolBar = new (::std::nothrow) QToolBar(mw);
	if (Q_UNLIKELY(Q_NULLPTR == detailToolBar)) {
		return Q_NULLPTR;
	}

	ui->detailVerticalLayout->setMenuBar(detailToolBar);

	detailToolBar->setToolButtonStyle(toolBarButtonStyle(DlgToolBar::DETAIL));

	{
		QComboBox *comboBox = new (::std::nothrow) QComboBox(mw);
		if (Q_NULLPTR != comboBox) {
			comboBox->setToolTip(
			    "For the current message you can locally set""\n"
			    "whether the message is not processed, whether the""\n"
			    "message is in progress or whether there was already""\n"
			    "sent a reply to this message.");

			detailToolBar->addWidget(comboBox);

			if ((Q_NULLPTR != messageStateCombo)
			    && (Q_NULLPTR == *messageStateCombo)) {
				*messageStateCombo = comboBox;
			}
		}

		QWidget *spacer = new (::std::nothrow) QWidget(mw);
		if (Q_NULLPTR != spacer) {
			spacer->setSizePolicy(QSizePolicy::Expanding,
			    QSizePolicy::Expanding);
			QAction *action = detailToolBar->addWidget(spacer);

			if ((lastUnconfigurableToolbarElement != Q_NULLPTR) &&
			    (*lastUnconfigurableToolbarElement == Q_NULLPTR)) {
				*lastUnconfigurableToolbarElement = action;
			}
		}
	}

	/* Add actions to the top tool bar. */
	loadToolBarActions(mw, DlgToolBar::DETAIL, detailToolBar,
	    defaultDetailBarActions(ui));

	return detailToolBar;
}

/*!
 * @brief Adds actions to attachment list tool bar.
 *
 * @param[in,out] ui Main window user interface.
 * @param[in]     mw Main window.
 * @param[out]    lastUnconfigurableToolbarElement Pointer to last non-configurable element.
 * @return Pointer to created attachment tool bar.
 */
static
QToolBar *setUpAttachmentsToolBar(Ui::MainWindow *ui, QMainWindow *mw,
    QAction **lastUnconfigurableToolbarElement)
{
	if (Q_UNLIKELY(ui == Q_NULLPTR)) {
		Q_ASSERT(0);
		return Q_NULLPTR;
	}

	QToolBar *attachmentsToolBar = new (::std::nothrow) QToolBar(mw);
	if (Q_UNLIKELY(Q_NULLPTR == attachmentsToolBar)) {
		return Q_NULLPTR;
	}

	ui->attachmentsVerticalLayout->setMenuBar(attachmentsToolBar);

	attachmentsToolBar->setToolButtonStyle(toolBarButtonStyle(DlgToolBar::ATTACHMENT));

	{
		QWidget *spacer = new (::std::nothrow) QWidget(mw);
		if (Q_NULLPTR != spacer) {
			spacer->setSizePolicy(QSizePolicy::Expanding,
			    QSizePolicy::Expanding);
			QAction *action = attachmentsToolBar->addWidget(spacer);

			if ((lastUnconfigurableToolbarElement != Q_NULLPTR) &&
			    (*lastUnconfigurableToolbarElement == Q_NULLPTR)) {
				*lastUnconfigurableToolbarElement = action;
			}
		}
	}

	/* Add actions to the top tool bar. */
	loadToolBarActions(mw, DlgToolBar::ATTACHMENT, attachmentsToolBar,
	    defaultAttachmentBarActions(ui));

	return attachmentsToolBar;
}

/*!
 * @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 += strongInfoLine(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)
{
	addPrefsDefaults(*GlobInstcs::prefsPtr);

	ui->setupUi(this);
	/*
	 * Setting menu roles because of macOS.
	 * It cannot be set in the UI file.
	 */
	ui->menuRecordsManagement->menuAction()->setMenuRole(QAction::NoRole);
	ui->menuMarkAllReceivedAs->menuAction()->setMenuRole(QAction::NoRole);
	ui->menuSynchronisation->menuAction()->setMenuRole(QAction::NoRole);
	ui->menuMarkAllReceivedAs->menuAction()->setMenuRole(QAction::NoRole);
	ui->menuEmailWith->menuAction()->setMenuRole(QAction::NoRole);
	ui->menuToolBars->menuAction()->setMenuRole(QAction::NoRole);

	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);

	setUpTopToolBar(ui, this, &mui_firstUnconfigurableToolbarElement);

	mui_accountToolBar = setUpAccountToolBar(ui, this,
	    &mui_lastUnconfigurableAccountToolBarElement);

	mui_messageToolBar = setUpMessageToolBar(ui, this,
	    &mui_lastUnconfigurableMessageToolBarElement,
	    &mui_firstUnconfigurableMessageToolBarElement);

	mui_detailToolBar = setUpDetailToolBar(ui, this,
	    &mui_lastUnconfigurableDetailToolBarElement, &mui_messageStateCombo);

	mui_attachmentToolBar = setUpAttachmentsToolBar(ui, this,
	    &mui_lastUnconfigurableAttachmentToolBarElement);

	{
		bool val = false;
		val = GlobInstcs::prefsPtr->boolVal(MAIN_TOP_TOOLBAR_VISIBLE_KEY, val) && val;
		ui->toolBar->setVisible(val);
		ui->actionCustomiseToolBar->setEnabled(val);
	}
	{
		bool val = false;
		val = GlobInstcs::prefsPtr->boolVal(MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY, val) && val;
		if (Q_NULLPTR != mui_accountToolBar) {
			mui_accountToolBar->setVisible(val);
		}
		if (Q_NULLPTR != mui_messageToolBar) {
			mui_messageToolBar->setVisible(val);
		}
		ui->actionCustomiseAccountBar->setEnabled(val);
		ui->actionCustomiseMessageBar->setEnabled(val);
	}

	setUpTabOrder(ui, mui_accountToolBar, mui_messageToolBar,
	    mui_detailToolBar, mui_attachmentToolBar);

	ui->messagesFilterHorizontalLayout->setVisible(false);
	connect(ui->messageFilterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterMessages(QString)));
	ui->closeFilterButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton));
	connect(ui->closeFilterButton, SIGNAL(clicked()),
	    this, SLOT(hideMessageFilter()));

	/* 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 label showing tag storage mode. */
	mui_statusTagMode = new (::std::nothrow) QLabel;
	if (mui_statusTagMode != Q_NULLPTR) {
		mui_statusTagMode->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
		ui->statusBar->addWidget(mui_statusTagMode, 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. */
	mui_messageStateCombo->setInsertPolicy(QComboBox::InsertAtBottom);
	mui_messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_RED_BALL),
	    tr("Unsettled"));
	mui_messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_YELLOW_BALL),
	    tr("In Progress"));
	mui_messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_GREY_BALL),
	    tr("Settled"));

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

/*!
 * @brief Acquire account proxy model from account list tree view.
 *
 * @param[in] accountList Account tree view.
 * @return Account proxy model.
 */
static
CollapsibleAccountProxyModel *getAccountProxyModel(const QTreeView &accountList)
{
	return qobject_cast<CollapsibleAccountProxyModel *>(accountList.model());
}

/*!
 * @brief Acquire account source model from account list tree view.
 *
 * @param[in] accountList Account tree view.
 * @return Account source model.
 */
static
AccountModel *getAccountModel(const QTreeView &accountList)
{
	CollapsibleAccountProxyModel *proxyModel =
	    qobject_cast<CollapsibleAccountProxyModel *>(accountList.model());
	if (Q_UNLIKELY(Q_NULLPTR == proxyModel)) {
		return Q_NULLPTR;
	}
	return qobject_cast<AccountModel *>(proxyModel->sourceModel());
}

/*!
 * @brief Convert proxy model (view) index to source model index.
 *
 * @param[in] proxyModelPtr Pointer to proxy model.
 * @param[in] proxyIndex Proxy model (view) index.
 * @return Source model index.
 */
#define proxy2sourceModelIndex(proxyModelPtr, proxyIndex) \
	(proxyModelPtr)->mapToSource(proxyIndex)

/*!
 * @brief Convert source model index to proxy model (view) index.
 *
 * @param[in] proxyModelPtr Pointer to proxy model.
 * @param[in] sourceIndex Source model index.
 * @return Proxy model (view) index.
 */
#define source2proxyModelIndex(proxyModelPtr, sourceIndex) \
	(proxyModelPtr)->mapFromSource(sourceIndex)

/*!
 * @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();

	CollapsibleAccountProxyModel *accountProxyModel = getAccountProxyModel(accountList);
	if (Q_UNLIKELY(Q_NULLPTR == accountProxyModel)) {
		Q_ASSERT(0);
		return;
	}
	AccountModel *accountModel = getAccountModel(accountList);
	if (Q_UNLIKELY(accountModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	for (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 amIndex = accountModel->topAcntIndex(
		    AcntId(username, testing));
		if (Q_UNLIKELY(!amIndex.isValid())) {
			continue;
		}

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

		switch (nameSplit.size()) {
		case 6: /* Account name item. */
			accountList.setExpanded(source2proxyModelIndex(accountProxyModel, amIndex), !collapsed);
			break;
		case 7: /* All item. */
			amIndex = accountModel->index(nameSplit[6].toInt(), 0, amIndex);
			accountList.setExpanded(source2proxyModelIndex(accountProxyModel, amIndex), !collapsed);
			break;
		case 8:
			amIndex = accountModel->index(nameSplit[6].toInt(), 0, amIndex);
			amIndex = accountModel->index(nameSplit[7].toInt(), 0, amIndex);
			accountList.setExpanded(source2proxyModelIndex(accountProxyModel, amIndex), !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 CollapsibleAccountProxyModel *accountProxyModel = getAccountProxyModel(accountList);
	if (Q_UNLIKELY(Q_NULLPTR == accountProxyModel)) {
		Q_ASSERT(0);
		return;
	}
	const AccountModel *accountModel = getAccountModel(accountList);
	if (Q_UNLIKELY(accountModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

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

/*!
 * @brief Set status label with tag container status description.
 *
 * @param[out] label Label whose test has to be set.
 * @param[in] tagCont Tag container whose status is read.
 */
static
void setTagStorageLabel(QLabel &label, TagContainer &tagCont)
{
	QString labelText;

	switch (tagCont.backend()) {
	case TagContainer::BACK_DB:
		labelText = MainWindow::tr("Tags: %1")
		    .arg(MainWindow::tr("local storage"));
		break;
	case TagContainer::BACK_CLIENT:
		{
			TagClient *client = tagCont.backendClient();
			if (Q_UNLIKELY(client == Q_NULLPTR)) {
				Q_ASSERT(0);
				labelText = MainWindow::tr("Tags: %1")
				    .arg(MainWindow::tr("error"));
			}
			labelText = MainWindow::tr("Tags: %1")
			    .arg(client->isConnected() ?
			        MainWindow::tr("connected") :
			        MainWindow::tr("disconnected"));
		}
		break;
	default:
		Q_ASSERT(0);
		labelText = MainWindow::tr("Tags: %1")
		    .arg(MainWindow::tr("error"));
		break;
	}

	label.setText(labelText + "   ");
}

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

	QSettings settings(GlobInstcs::iniPrefsPtr->loadConfPath(),
	    QSettings::IniFormat);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
	/*
	 * Qt-6 does not provide a replacement of QSettings::setIniCodec().
	 * The documentation claims that QSettings assumes the INI file
	 * is UTF-8 encoded.
	 * TODO - It would be great to make really sure the INI is UTF-8 encoded
	 * instead of relying on the developers and documentation.
	 */
#else /* < Qt-6.0 */
	settings.setIniCodec("UTF-8");
#endif /* >= Qt-6.0 */

	/* 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);

	/* Tag container settings. */
	GlobInstcs::tagContSetPtr->loadFromSettings(settings);
	GlobInstcs::tagContSetPtr->decryptPassword(GlobInstcs::pinSetPtr->_pinVal);
	applyCompleteTagContainerSettings(*GlobInstcs::tagContSetPtr,
	    *GlobInstcs::tagContPtr);

	setTagStorageLabel(*mui_statusTagMode, *GlobInstcs::tagContPtr);

	/* 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);

	/* Convert local tag database from old format. */
	{
		TagDb *tagDbPtr = GlobInstcs::tagContPtr->backendDb();
		if (tagDbPtr != Q_NULLPTR) {
			Json::TagDbInfo info;
			tagDbPtr->getDbInfo(info);
			if (info.formatVersionMajor() < 1) {
				tagDbPtr->convertAccountIdentifiersToTestEnvFlag(
				    GlobInstcs::acntMapPtr->acntIds());
			}
		}
	}

	/* 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);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
	/*
	 * Qt-6 does not provide a replacement of QSettings::setIniCodec().
	 * The documentation claims that QSettings assumes the INI file
	 * is UTF-8 encoded.
	 * TODO - It would be great to make really sure the INI is UTF-8 encoded
	 * instead of relying on the developers and documentation.
	 */
#else /* < Qt-6.0 */
	settings.setIniCodec("UTF-8");
#endif /* >= Qt-6.0 */

	settings.clear();

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

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

	/* Tag container settings. */
	GlobInstcs::tagContSetPtr->saveToSettings(
	    GlobInstcs::pinSetPtr->_pinVal, 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);

	PrefsSpecific::setMainWinTblColWidths(*GlobInstcs::prefsPtr,
	    attachmentTableName, Dimensions::tableColumnWidths(ui->attachmentList));
	PrefsSpecific::setMainWinTblColSortOrder(*GlobInstcs::prefsPtr,
	    attachmentTableName, Dimensions::tableColumnSortOrder(ui->attachmentList));

	/* 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()));
	connect(ui->actionCheckUploadMessagesRecordsManagement, SIGNAL(triggered()),
	    this, SLOT(checkUploadMessagesIntoRecordsManagement()));
	    /* Separator. */
#if defined(HAVE_WEBSOCKETS)
	connect(ui->actionTagStorageSettings, SIGNAL(triggered()),
	    this, SLOT(editTagContSettings()));
	{
		bool val = false;
		if (GlobInstcs::prefsPtr->boolVal(
		        "storage.client.tags.enabled", val)) {
			ui->actionTagStorageSettings->setVisible(val);
			if (!val) {
				/* Fall back to local storage when client disabled. */
				GlobInstcs::tagContPtr->selectBackend(TagContainer::BACK_DB);
			}
		}
	}
	{
		qint64 val = 0;
		if (GlobInstcs::prefsPtr->intVal(
		        "storage.client.tags.communication.timeout.seconds", val)) {
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setCommunicationTimeout(val * 1000);
			}
		}
		val = 0;
		if (GlobInstcs::prefsPtr->intVal(
		        "storage.client.tags.keep_alive.timer.period.seconds", val)) {
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setKeepAlivePeriod(val * 1000);
			}
		}
		val = 0;
		if (GlobInstcs::prefsPtr->intVal(
		        "storage.client.tags.reconnect.timer.delay.seconds", val)) {
		        TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setReconnectDelay(val * 1000);
			}
		}
		val = 0;
		if (GlobInstcs::prefsPtr->intVal(
		        "storage.client.tags.reconnect.timer.period.seconds", val)) {
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setReconnectPeriod(val * 1000);
			}
		}
	}
#else /* !defined(HAVE_WEBSOCKETS) */
	ui->actionTagStorageSettings->setVisible(false);
#endif /* defined(HAVE_WEBSOCKETS) */
	    /* 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()));
	connect(ui->actionEmailContentSelection, SIGNAL(triggered()),
	    this, SLOT(createEmailContentSelection()));
	    /* 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->actionConvertImportFromMobileApp, SIGNAL(triggered()),
	    this, SLOT(viewConvertImportFromMobileAppDlg()));
	    /* Separator. */
	        /* Tool Bars menu. */
	{
		bool val = false;
		ui->actionToolBar->setChecked(
		    GlobInstcs::prefsPtr->boolVal(MAIN_TOP_TOOLBAR_VISIBLE_KEY, val) && val);
	}
	connect(ui->actionToolBar, SIGNAL(toggled(bool)),
	    this, SLOT(toolBarSettingsToggled(bool)));
	{
		bool val = false;
		ui->actionTopPanelBars->setChecked(
		    GlobInstcs::prefsPtr->boolVal(MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY, val) && val);
	}
	connect(ui->actionTopPanelBars, SIGNAL(toggled(bool)),
	    this, SLOT(topPanelBarsToggled(bool)));
	connect(ui->actionCustomiseToolBar, SIGNAL(triggered()),
	    this, SLOT(viewCustomiseToolBar()));
	connect(ui->actionCustomiseAccountBar, SIGNAL(triggered()),
	    this, SLOT(viewCustomiseAccountBar()));
	connect(ui->actionCustomiseMessageBar, SIGNAL(triggered()),
	    this, SLOT(viewCustomiseMessageBar()));
	connect(ui->actionCustomiseDetailBar, SIGNAL(triggered()),
	    this, SLOT(viewCustomiseDetailBar()));
	connect(ui->actionCustomiseAttachmentBar, SIGNAL(triggered()),
	    this, SLOT(viewCustomiseAttachmentBar()));
	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()));
	connect(ui->actionFilterMessages, SIGNAL(triggered()),
	    this, SLOT(showMessageFilter()));
}

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

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

	/* Sets message processing state. */
	connect(mui_messageStateCombo, SIGNAL(activated(int)),
	    this, SLOT(msgSetSelectedMessageProcessState(int)));
}

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::tagContPtr, SIGNAL(tagsUpdated(Json::TagEntryList)),
	    this, SLOT(watchTagsUpdated(Json::TagEntryList)));
	connect(GlobInstcs::tagContPtr, SIGNAL(tagsDeleted(Json::Int64StringList)),
	    this, SLOT(watchTagsDeleted(Json::Int64StringList)));
	connect(GlobInstcs::tagContPtr, SIGNAL(tagAssignmentChanged(Json::TagAssignmentList)),
	    this, SLOT(watchTagAssignmentChanged(Json::TagAssignmentList)));
	connect(GlobInstcs::tagContPtr, SIGNAL(connected()),
	    this, SLOT(watchTagContConnected()));
	connect(GlobInstcs::tagContPtr, SIGNAL(disconnected()),
	    this, SLOT(watchTagContDisconnected()));
	connect(GlobInstcs::tagContPtr, SIGNAL(reset()),
	    this, SLOT(watchTagContReset()));

	connect(GlobInstcs::recMgmtDbPtr, SIGNAL(msgEntriesDeleted(QList<qint64>)),
	    this, SLOT(watchRmMsgEntriesDeleted(QList<qint64>)));
	connect(GlobInstcs::recMgmtDbPtr, SIGNAL(msgEntryUpdated(qint64, QStringList)),
	    this, SLOT(watchRmMsgEntryUpdated(qint64, QStringList)));

	connect(GlobInstcs::prefsPtr, SIGNAL(entrySet(int, QString, QVariant)),
	    this, SLOT(watchPrefsModification(int, QString, QVariant)));
}

void MainWindow::haltRequested(void)
{
	if (Q_UNLIKELY(GlobInstcs::req_halt)) {
		logInfoNL("%s", "Requested halt.");
		GlobInstcs::workPoolPtr->stop();
		GlobInstcs::workPoolPtr->clear();
		close(); /* Close this window. */
	}
}

/*!
 * @brief Raise a widget.
 *
 * @param[in,out] widget Widget that should be de-minimised and shown.
 * @return True on success.
 */
static inline
bool raiseWidget(QWidget *widget)
{
	if (Q_UNLIKELY(Q_NULLPTR == widget)) {
		return false;
	}
	if (!widget->isMinimized()) {
		widget->show();
	} else {
		widget->showNormal();
	}
	widget->raise();
	widget->activateWindow();
	return true;
}

/*!
 * @brief Ask the user whether to abort all pending tasks.
 *
 * @note This generates a blocking window. Don't call this from an input event
 *     handler (such as closeEvent()) as this will block the event loop.
 *
 * @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 = DlgMsgBoxDetail::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::viewAskCloseDlg(void)
{
	debugSlotCall();

	if (raiseWidget(m_askCloseDlg)) {
		return;
	}

	m_askCloseDlg = new (::std::nothrow) DlgMsgBoxDetail(
	    QMessageBox::Question, tr("Pending Tasks"),
	    tr("The application is currently processing some tasks."),
	    tr("Do you want to abort all pending actions and close the application?"),
	    QString(), QMessageBox::No | QMessageBox::Yes, QMessageBox::No, this);
	if (Q_UNLIKELY(m_askCloseDlg == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}
	connect(m_askCloseDlg, SIGNAL(finished(int)),
	    this, SLOT(closedAskCloseDlg(int)));
	m_askCloseDlg->show();
}

void MainWindow::closedAskCloseDlg(int result)
{
	debugSlotCall();

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

	if (result == QMessageBox::Yes) {
		req_close = 1;
		close();
	}
}

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

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

	synchroniseAccounts(false);
}

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

	showStatusTextWithTimeout(tr("Add a new data box."));

	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 data box '%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 data box '%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 data box '%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 data box '%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 data box '%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 data box '%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 data box '%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;
	for (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::checkUploadMessagesIntoRecordsManagement(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<DlgRecordsManagementCheckMsgs::LocAcntData> accounts;
	for (const AcntId &acntId : acntIds) {
		MessageDbSet *dbSet = accountDbSet(acntId, this);
		if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
			Q_ASSERT(0);
			return;
		}
		accounts.append(DlgRecordsManagementCheckMsgs::LocAcntData(
		    GlobInstcs::acntMapPtr->acntData(acntId).accountName(),
		    acntId, dbSet));
	}

	DlgRecordsManagementCheckMsgs::openDlg(*GlobInstcs::recMgmtSetPtr,
	    accounts, this, this);
}

void MainWindow::editTagContSettings(void)
{
	DlgTagSettings::editTagContSettings(*GlobInstcs::tagContSetPtr,
	    *GlobInstcs::tagContPtr, this);
}

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

	if (!DlgPreferences::modify(*GlobInstcs::prefsPtr,
	        *GlobInstcs::pinSetPtr, 0, 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 data box '%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 data box '%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("A data box '%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 = DlgMsgBoxDetail::message(this,
		    QMessageBox::Question, tr("Username Change"),
		    tr("Do you want to change the username from '%1' to '%2' for the data box '%3'?")
		        .arg(acntId.username())
		        .arg(newSettings.userName())
		        .arg(newSettings.accountName()),
		    tr("Note: This will also change all related local database names and stored data-box 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());
			DlgMsgBoxDetail::message(this, QMessageBox::Warning,
			    tr("Data Box Mismatch"),
			    tr("Cannot change the username '%1' to '%2' for the data box '%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(madeAvailable(AcntId)),
		    this, SLOT(refreshAccountList(AcntId)));
		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 = macroStdMove(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("Data box '%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("Data box 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("Data box 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 data box '%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 = DlgMsgBoxDetail::message(this,
		    QMessageBox::Question, tr("Import Messages from Database"),
		    tr("Here you can import messages from selected database files into the current data box.") + " " +
		    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 = macroStdMove(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 %" PRId64 " ZFO files.",
	    UGLY_QINT64_CAST 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 data box 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."));

		DlgMsgBoxDetail::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 = DlgMsgBoxDetail::message(this,
		    QMessageBox::Question, tr("Clean Message Database"),
		    tr("Performs a message database clean-up for the selected data box. "
		        "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. */
	bool poolRet = GlobInstcs::workPoolPtr->runSingle(task);

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

	if (poolRet) {
		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; task = Q_NULLPTR;
}

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

	{
		int dlgRet = DlgMsgBoxDetail::message(this,
		    QMessageBox::Question, tr("Split Database"),
		    tr("The following operation splits the message database of the selected data box 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."));

			DlgMsgBoxDetail::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. */
	bool poolRet = GlobInstcs::workPoolPtr->runSingle(task);

	QApplication::restoreOverrideCursor();

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

			DlgMsgBoxDetail::message(this, QMessageBox::Information,
			    tr("Database Split Result"),
			    tr("Message database for data box '%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 {
			DlgMsgBoxDetail::message(this, QMessageBox::Critical,
			    tr("Database Split Result"),
			    tr("Message database splitting for data box '%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; task = Q_NULLPTR;
	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 data box 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;

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

		originList.append(MsgOrigin(AcntIdDb(acntId, dbSet),
		    msgId, isVodz, 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())

/*!
 * @brief Generate HTML info about the message being deleted from ISDS.
 *
 * @param[in] acntId Account identifier.
 * @param[in] remainDays Remaining days to supposed deletion from ISDS.
 * @return Deletion info text.
 */
static
QString msgIsdsDeletionInfo(const AcntId &acntId, int remainDays)
{
	qint64 msgDelThreshold = 0;
	ignoreRetVal(GlobInstcs::prefsPtr->intVal(
	    "window.main.message.isds_deletion.notify_ahead.days",
	    msgDelThreshold));
	QString msgDelColour;
	GlobInstcs::prefsPtr->colourVal(
	    "window.main.message.isds_deletion.notify_ahead.colour",
	    msgDelColour);

	QString message;

	if ((remainDays > 0) && (remainDays <= msgDelThreshold)) {

		const QString dbId(GlobInstcs::accntDbPtr->dbId(
		    AccountDb::keyFromLogin(acntId.username())));
		Isds::DTInfoOutput dtInfo =
		    GlobInstcs::accntDbPtr->getDTInfo(acntId.testing(), dbId);

		/*
		 * type <= Isds::Type::DTT_INACTIVE - Long term storage
		 * isn't active or its state is unknown.
		 */
		if (dtInfo.actDTType() <= Isds::Type::DTT_INACTIVE) {
			message = MainWindow::tr(
			    "The message will be deleted from ISDS in approximately %n day(s).",
			    "", remainDays);
		} else {
			if (dtInfo.actDTCapacity() <= dtInfo.actDTCapUsed()) {
				message = MainWindow::tr(
				    "The message will be deleted from ISDS in approximately %n day(s) because the long term storage is full.",
				    "", remainDays);
			} else {
				message = MainWindow::tr(
				    "The message will be moved to the long term storage in approximately %n day(s) if the storage is not full.",
				    "", remainDays) + QStringLiteral(" ") +
				    MainWindow::tr("The message will be deleted from the ISDS server if the storage is full.");
			}
		}
	} else if (remainDays == 0) {
		message = MainWindow::tr(
		    "The message has already been deleted from the ISDS.");
	}

	if (!message.isEmpty()) {
		message = QStringLiteral("<b>") + message + QStringLiteral("</b>");
		if (!msgDelColour.isEmpty()) {
			message = QStringLiteral("<font color=\"#") + msgDelColour + QStringLiteral("\">") + message
			    + QStringLiteral("</font>");
		}
	}

	return message;
}

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;
	}

	/*
	 * Reload message description with signature and timestamp information.
	 */
	{
		MessageDb *messageDb = dbSet->accessMessageDb(
		    msgId.deliveryTime(), false);
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			return;
		}

		/* Generate and show message information. */
		int days = MessageDb::daysRemainingSinceAcceptance(
		    messageDb->messageAcceptanceTime(msgId.dmId()));
		QString delInfo = msgIsdsDeletionInfo(mwStatus.acntId(), days);
		ui->messageInfo->setHtml(htmlMarginsApp(delInfo,
		    Html::Export::htmlMessageInfoApp(
		    messageDb->getMessageEnvelope(msgId.dmId()),
		    messageDb->messageAuthorJsonStr(msgId.dmId()),
		    GlobInstcs::recMgmtDbPtr->storedMsgLocations(msgId.dmId()),
		    true, false, true) +
		    messageDb->verifySignatureHtml(msgId.dmId())));
	}

	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;
	}

	enum TaskVerifyMessage::Result result = TaskVerifyMessage::VERIFY_ERR;

	task->setAutoDelete(false);
	if (GlobInstcs::workPoolPtr->runSingle(task)) {
		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;
	}

	const QByteArray data = messageDb->getCompleteMessageRaw(msgId.dmId());
	if (Q_UNLIKELY(data.isEmpty())) {
		DlgMsgBoxDetail::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(Exports::dmTypePrefix(messageDb, msgId.dmId()))
	    .arg(msgId.dmId()));
	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	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;
	}

	const QByteArray data = messageDb->getDeliveryInfoRaw(msgId.dmId());
	if (Q_UNLIKELY(data.isEmpty())) {
		DlgMsgBoxDetail::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(Exports::dmTypePrefix(messageDb, msgId.dmId()))
	    .arg(msgId.dmId()));
	if (Q_UNLIKELY(fileName.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	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::createEmail(originList, GuiMsgOps::ADD_ZFO_MESSAGE);
}

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::createEmail(originList, GuiMsgOps::ADD_ATTACHMENTS);
}

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

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

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

	DlgEmailContent::createEmail(originList, this);
}

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 = DlgMsgBoxDetail::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.
			 *
			 * Deleting tag association with a message is
			 * undesirable if the tag database is
			 * between multiple application instances.
			 *
			 * GlobInstcs::tagContPtr->removeAllTagsFromMsgs();
			 */
		}
	}

	/* 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 data box '%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,
	        PrefsSpecific::enableVodzSending(*GlobInstcs::prefsPtr) ?
	            TaskAuthenticateMessage::MSG_VODZ_SIZE_DEPENDENT :
	            TaskAuthenticateMessage::MSG_BASIC);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return TaskAuthenticateMessage::AUTH_ERR;
	}

	enum TaskAuthenticateMessage::Result result = TaskAuthenticateMessage::AUTH_ERR;

	task->setAutoDelete(false);
	if (GlobInstcs::workPoolPtr->runSingle(task)) {
		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::tagContPtr, *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 (raiseWidget(m_messageSearchDlg)) {
		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::viewConvertImportFromMobileAppDlg(void)
{
	debugSlotCall();

	DlgConvertImportFromMobileApp::view(this);
}

void MainWindow::toolBarSettingsToggled(bool checked)
{
	GlobInstcs::prefsPtr->setBoolVal(MAIN_TOP_TOOLBAR_VISIBLE_KEY, checked);
}

void MainWindow::topPanelBarsToggled(bool checked)
{
	GlobInstcs::prefsPtr->setBoolVal(MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY, checked);
}

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

	DlgToolBar::configureToolBar(ui->menubar, DlgToolBar::MAIN_TOP,
	    ui->toolBar, Q_NULLPTR, mui_firstUnconfigurableToolbarElement,
	    defaultTopToolBarActions(ui), this);
}

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

	DlgToolBar::configureToolBar(ui->menubar, DlgToolBar::ACCOUNT,
	    mui_accountToolBar,
	    mui_lastUnconfigurableAccountToolBarElement, Q_NULLPTR,
	    defaultAccountBarActions(ui), this);
}

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

	DlgToolBar::configureToolBar(ui->menubar, DlgToolBar::MESSAGE,
	    mui_messageToolBar,
	    mui_lastUnconfigurableMessageToolBarElement,
	    mui_firstUnconfigurableMessageToolBarElement,
	    defaultMessageBarActions(ui), this);
}

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

	DlgToolBar::configureToolBar(ui->menubar, DlgToolBar::DETAIL,
	    mui_detailToolBar,
	    mui_lastUnconfigurableDetailToolBarElement, Q_NULLPTR,
	    defaultDetailBarActions(ui), this);
}

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

	DlgToolBar::configureToolBar(ui->menubar, DlgToolBar::ATTACHMENT,
	    mui_attachmentToolBar,
	    mui_lastUnconfigurableAttachmentToolBarElement, Q_NULLPTR,
	    defaultAttachmentBarActions(ui), this);
}

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

	if (raiseWidget(m_logDlg)) {
		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, DlgAbout::TAB_0_ABOUT, 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::showMessageFilter(void)
{
	if (!ui->messagesFilterHorizontalLayout->isVisible()) {
		ui->messagesFilterHorizontalLayout->setMaximumHeight(0);
		ui->messagesFilterHorizontalLayout->setVisible(true);

		QPropertyAnimation *animation =
		    new (::std::nothrow) QPropertyAnimation(
		        ui->messagesFilterHorizontalLayout, "maximumHeight");
		if (Q_NULLPTR != animation) {
			animation->setDuration(250);
			animation->setStartValue(0);
			animation->setEndValue(
			    ui->messagesFilterHorizontalLayout->sizeHint().height());

			animation->start(QAbstractAnimation::DeleteWhenStopped);
		}
	}

	ui->messageFilterLine->setFocus(Qt::OtherFocusReason);
}

void MainWindow::hideMessageFilter(void)
{
	{
		QPropertyAnimation *animation =
		    new (::std::nothrow) QPropertyAnimation(
		        ui->messagesFilterHorizontalLayout, "maximumHeight");
		if (Q_NULLPTR != animation) {
			animation->setDuration(250);
			animation->setStartValue(
			    ui->messagesFilterHorizontalLayout->height());
			animation->setEndValue(0);

			/* Hide line after animation finishes. */
			connect(animation, SIGNAL(finished()),
			    ui->messagesFilterHorizontalLayout, SLOT(hide()));

			animation->start(QAbstractAnimation::DeleteWhenStopped);
		}
	}
	ui->messagesFilterHorizontalLayout->setMaximumHeight(QWIDGETSIZE_MAX);

	ui->messageList->setFocus(Qt::OtherFocusReason);
}

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->actionCheckUploadMessagesRecordsManagement->setEnabled(
	    GlobInstcs::recMgmtSetPtr->isValid());
	/* ui->actionTagStorageSettings */
	/* 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->menuEmailWith->setEnabled(numSelMsg > 0);
	ui->actionEmailZfos->setEnabled(numSelMsg > 0);
	ui->actionEmailAllAttachments->setEnabled(numSelMsg > 0);
	ui->actionEmailContentSelection->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. */
	mui_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 {
		const QModelIndex amIndexCurrent =
		    proxy2sourceModelIndex(&m_accountProxyModel, current);

		enum AccountModel::NodeType nodeType =
		    AccountModel::nodeType(amIndexCurrent);
		QString year;
		if ((nodeType == AccountModel::nodeReceivedYear) ||
		    (nodeType == AccountModel::nodeSentYear)) {
			year = amIndexCurrent.data(AccountModel::ROLE_PLAIN_DISPLAY).toString();
		}
		m_selectionStatus.setAccount(m_accountModel.acntId(amIndexCurrent),
		    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 Fills the message model with tag information.
 *
 * @param[in]     testEnv testing environment.
 * @param[in,out] messageTableModel Message model.
 */
static inline
void fillTagsColumn(bool testEnv, DbMsgsTblModel &messageTableModel)
{
	Json::TagAssignmentHash assignments;
	{
		const Json::TagMsgIdList msgIds =
		    messageTableModel.msgIdList(testEnv);
		GlobInstcs::tagContPtr->getMessageTags(msgIds, assignments);
	}
	messageTableModel.setTagAssignments(testEnv,
	    DbMsgsTblModel::TAGS_NEG_COL, assignments);
}

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 data box '%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 = htmlMarginsApp("",
		    Html::Export::htmlAllMessagesInfoApp(
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_RECEIVED,
		        DESCENDING),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_SENT, DESCENDING)));
		break;
	case AccountModel::nodeReceived:
#ifdef DISABLE_ALL_TABLE
		html = htmlMarginsApp("",
		    Html::Export::htmlMessageInfoApp(
		    tr("All Received Messages"),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_RECEIVED,
		        DESCENDING)));
#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 = htmlMarginsApp("",
		    Html::Export::htmlMessageInfoApp(
		    tr("All Sent Messages"),
		    dbSet->msgsYearlyCounts(MessageDb::TYPE_SENT, DESCENDING)));
#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);
		fillTagsColumn(acntId.testing(), m_messageTableModel);
		/* 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(ui->messageFilterLine->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. */
	int days = MessageDb::daysRemainingSinceAcceptance(
	    messageDb->messageAcceptanceTime(msgId.dmId()));
	QString delInfo = msgIsdsDeletionInfo(mwStatus.acntId(), days);
	ui->messageInfo->setHtml(htmlMarginsApp(delInfo,
	    Html::Export::htmlMessageInfoApp(
	    messageDb->getMessageEnvelope(msgId.dmId()),
	    messageDb->messageAuthorJsonStr(msgId.dmId()),
	    GlobInstcs::recMgmtDbPtr->storedMsgLocations(msgId.dmId()),
	    true, false, true)));
	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) {
			mui_messageStateCombo->setCurrentIndex(msgState);
		} else {
			/* insert message state into database */
			messageDb->setMessagesProcessState(
			    QList<MsgId>() << msgId, MessageDb::UNSETTLED);
			mui_messageStateCombo->setCurrentIndex(
			    MessageDb::UNSETTLED);
		}
	} else {
		mui_messageStateCombo->setCurrentIndex(MessageDb::UNSETTLED);
	}

	/* Load and show attachments without file binary content. */
	m_attachmentModel.appendData(messageDb->attachEntries(msgId.dmId(), false));

	/* Use column widths from preferences.
	 *
	 * ui->attachmentList->resizeColumnToContents(
	 *    AttachmentTblModel::FNAME_COL);
	 */
}

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

	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	ensureSelectedAttachmentsPresence();

	updateActionActivation();
}

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionToolBar);
	menu->addAction(ui->actionTopPanelBars);
	menu->addAction(ui->actionCustomiseToolBar);
	menu->addAction(ui->actionCustomiseAccountBar);
	menu->addAction(ui->actionCustomiseMessageBar);

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

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionToolBar);
	menu->addAction(ui->actionCustomiseToolBar);

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

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionTopPanelBars);
	menu->addAction(ui->actionCustomiseAccountBar);

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

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionTopPanelBars);
	menu->addAction(ui->actionCustomiseMessageBar);

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

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionCustomiseDetailBar);

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

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

	Q_UNUSED(point);

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

	menu->addAction(ui->actionCustomiseAttachmentBar);

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

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

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

	const QModelIndex amIndex = proxy2sourceModelIndex(&m_accountProxyModel,
	    ui->accountList->indexAt(point));
	QMenu *menu = new (::std::nothrow) QMenu(ui->accountList);
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

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

		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(amIndex)) {
			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();
	{
		QMenu *submenu = menu->addMenu(tr("E-mail with"));

		submenu->addAction(ui->actionEmailZfos);
		submenu->addAction(ui->actionEmailAllAttachments);
		submenu->addAction(ui->actionEmailContentSelection);
	}
	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)) {
			mui_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. */
	int days = MessageDb::daysRemainingSinceAcceptance(
	    messageDb->messageAcceptanceTime(msgId.dmId()));
	QString delInfo = msgIsdsDeletionInfo(mwStatus.acntId(), days);
	ui->messageInfo->setHtml(htmlMarginsApp(delInfo,
	    Html::Export::htmlMessageInfoApp(
	    messageDb->getMessageEnvelope(msgId.dmId()),
	    messageDb->messageAuthorJsonStr(msgId.dmId()),
	    GlobInstcs::recMgmtDbPtr->storedMsgLocations(msgId.dmId()),
	    true, false, true)));
	ui->messageInfo->setReadOnly(true);

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

	/* Use column widths from preferences.
	 *
	 * 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::watchTagsUpdated(const Json::TagEntryList &entries)
{
	for (const Json::TagEntry &entry : entries) {
		m_messageTableModel.updateTag(entry);
	}
}

void MainWindow::watchTagsDeleted(const Json::Int64StringList &ids)
{
	m_messageTableModel.deleteTags(ids);
}

void MainWindow::watchTagAssignmentChanged(
    const Json::TagAssignmentList &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;
	}

	for (const Json::TagAssignment &assignment : assignments) {
		/* Do nothing if account has been changed. */
		if (Q_UNLIKELY(assignment.testEnv() != currentAcntId.testing())) {
			continue;
		}
		m_messageTableModel.updateTagAssignment(assignment.dmId(), assignment.entries());
	}
}

void MainWindow::watchTagContConnected(void)
{
	setTagStorageLabel(*mui_statusTagMode, *GlobInstcs::tagContPtr);
	/* Do nothing. Reset is sent on profile selection. */
}

void MainWindow::watchTagContDisconnected(void)
{
	setTagStorageLabel(*mui_statusTagMode, *GlobInstcs::tagContPtr);

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	switch (mwStatus.acntNodeType()) {
	case AccountModel::nodeRecentReceived:
	case AccountModel::nodeRecentSent:
	case AccountModel::nodeReceivedYear:
	case AccountModel::nodeSentYear:
		/* Clear all tags. */
		{
			Json::TagAssignmentHash assignments;
			m_messageTableModel.setTagAssignments(acntId.testing(),
			    DbMsgsTblModel::TAGS_NEG_COL, assignments);
		}
		break;
	default:
		/* Do nothing. */
		break;
	}
}

void MainWindow::watchTagContReset(void)
{
	setTagStorageLabel(*mui_statusTagMode, *GlobInstcs::tagContPtr);

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	switch (mwStatus.acntNodeType()) {
	case AccountModel::nodeRecentReceived:
	case AccountModel::nodeRecentSent:
	case AccountModel::nodeReceivedYear:
	case AccountModel::nodeSentYear:
		fillTagsColumn(acntId.testing(), m_messageTableModel);
		break;
	default:
		/* Do nothing. */
		break;
	}
}

void MainWindow::watchRmMsgEntriesDeleted(const QList<qint64> &dmIds)
{
	for (qint64 dmId : dmIds) {
		m_messageTableModel.updateRecordsManagementColumn(dmId,
		    QStringList(), DbMsgsTblModel::RECMGMT_NEG_COL);
	}
}

void MainWindow::watchRmMsgEntryUpdated(qint64 dmId, const QStringList &locations)
{
	m_messageTableModel.updateRecordsManagementColumn(dmId, locations,
	    DbMsgsTblModel::RECMGMT_NEG_COL);
}

void MainWindow::watchPrefsModification(int valueType, const QString &name,
    const QVariant &value)
{
	switch (valueType) {
	case PrefsDb::VAL_BOOLEAN:
#if defined(HAVE_WEBSOCKETS)
		if (name == "storage.client.tags.enabled") {
			bool val = value.toBool();
			ui->actionTagStorageSettings->setVisible(val);
			if (!val) {
				/* Fall back to local storage when client disabled. */
				GlobInstcs::tagContPtr->selectBackend(TagContainer::BACK_DB);
			}
		}
#endif /* defined(HAVE_WEBSOCKETS) */
		if (name == MAIN_TOP_TOOLBAR_VISIBLE_KEY) {
			const bool val = value.toBool();
			ui->toolBar->setVisible(val);
			ui->actionToolBar->setChecked(val);
			ui->actionCustomiseToolBar->setEnabled(val);
		} else if (name == MAIN_TOP_PANELS_TOOLBAR_VISIBLE_KEY) {
			const bool val = value.toBool();
			if (Q_NULLPTR != mui_accountToolBar) {
				mui_accountToolBar->setVisible(val);
			}
			if (Q_NULLPTR != mui_messageToolBar) {
				mui_messageToolBar->setVisible(val);
			}
			ui->actionTopPanelBars->setChecked(val);
			ui->actionCustomiseAccountBar->setEnabled(val);
			ui->actionCustomiseMessageBar->setEnabled(val);
		}
		break;
	case PrefsDb::VAL_INTEGER:
#if defined(HAVE_WEBSOCKETS)
		if (name == "storage.client.tags.communication.timeout.seconds") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setCommunicationTimeout(val * 1000);
			}
		} else if (name == "storage.client.tags.keep_alive.timer.period.seconds") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setKeepAlivePeriod(val * 1000);
			}
		} else if (name == "storage.client.tags.reconnect.timer.delay.seconds") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setReconnectDelay(val * 1000);
			}
		} else if (name == "storage.client.tags.reconnect.timer.period.seconds") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			TagClient *client = GlobInstcs::tagContPtr->backendClient();
			if (client != Q_NULLPTR) {
				client->setReconnectPeriod(val * 1000);
			}
		}
#endif /* defined(HAVE_WEBSOCKETS) */
		if (name == "window.main.message.isds_deletion.notify_ahead.days") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			m_messageTableModel.setMsgDeletionNotifyAheadDays(val);
		}
		break;
	case PrefsDb::VAL_COLOUR:
		if (name == "window.main.message.isds_deletion.notify_ahead.colour") {
			QString val = value.toString();
			m_messageTableModel.setMsgDeletionNotifyAheadColour(
			    QColor("#" + val));
		}
		break;
	default:
		/* Do nothing. */
		break;
	}
}

/*!
 * @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();
}

/*!
 * @brief This object is used to call operations when a widget is hidden.
 */
class OnWidgetHiddenFilter : public QObject {
public:
	/*!
	 * @brief Constructor.
	 *
	 * @param[in] func Function to call when hide event is encountered.
	 * @param[in] obj Used as argument of \a func.
	 * @param[in] parent Parent object.
	 */
	OnWidgetHiddenFilter(void (*func)(QObject *), QObject *obj,
	    QObject *parent)
	    : QObject(parent),
	    actionFuncPtr(func),
	    actionObj(obj)
	{ }

	/*!
	 * @brief Event filter function.
	 *
	 * @note The function catches the given keys and calls given action
	 *       functions.
	 *
	 * @param[in,out] object View object.
	 * @param[in] event Caught event.
	 * @return True if further processing of event should be blocked.
	 */
	virtual
	bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE
	{
		Q_UNUSED(object);

		if ((event->type() == QEvent::Hide)
		        && (actionFuncPtr != Q_NULLPTR)) {
			actionFuncPtr(actionObj);
		}

		return QObject::eventFilter(object, event);
	}

private:
	void (*actionFuncPtr)(QObject *); /*!< Pointer to a function. */
	QObject *actionObj; /*!< Parameter of the action function. */
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent),
    ui(new (::std::nothrow) Ui::MainWindow),
    m_selectionStatus(this),
    m_timerCheckReqHalt(),
    m_timerSyncAccounts(),
    m_timerShadowSyncAccounts(),
    m_confDirName(),
    m_confFileName(),
    m_accountModel(this),
    m_accountProxyModel(this),
    m_messageTableModel(DbMsgsTblModel::RCVD_MODEL, this),
    m_messageListProxyModel(this),
    m_dfltFilerLineStyleSheet(SortFilterProxyModel::invalidFilterEditStyle),
    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_askCloseDlg(Q_NULLPTR),
    m_colWidth(COL_WIDTH_COUNT),
    m_sortCol(DFLT_COL_SORT),
    m_sort_order(),
    m_geometry(),
    m_msgTblAppendedCols(),
    mui_firstUnconfigurableToolbarElement(Q_NULLPTR),
    mui_lastUnconfigurableAccountToolBarElement(Q_NULLPTR),
    mui_accountToolBar(Q_NULLPTR),
    mui_lastUnconfigurableMessageToolBarElement(Q_NULLPTR),
    mui_firstUnconfigurableMessageToolBarElement(Q_NULLPTR),
    mui_messageToolBar(Q_NULLPTR),
    mui_lastUnconfigurableDetailToolBarElement(Q_NULLPTR),
    mui_messageStateCombo(Q_NULLPTR),
    mui_detailToolBar(Q_NULLPTR),
    mui_lastUnconfigurableAttachmentToolBarElement(Q_NULLPTR),
    mui_attachmentToolBar(Q_NULLPTR),
    mui_statusBar(Q_NULLPTR),
    mui_statusDbMode(Q_NULLPTR),
    mui_statusTagMode(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();

	/* Set custom menu ant tool bar context menus. */
	ui->menubar->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->menubar, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewMenuBarContextMenu(QPoint)));
	ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewToolBarContextMenu(QPoint)));
	if (Q_NULLPTR != mui_accountToolBar) {
		mui_accountToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(mui_accountToolBar, SIGNAL(customContextMenuRequested(QPoint)),
		    this, SLOT(viewAccountToolBarContextMenu(QPoint)));
	}
	if (Q_NULLPTR != mui_messageToolBar) {
		mui_messageToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(mui_messageToolBar, SIGNAL(customContextMenuRequested(QPoint)),
		    this, SLOT(viewMessageToolBarContextMenu(QPoint)));
	}
	if (Q_NULLPTR != mui_detailToolBar) {
		mui_detailToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(mui_detailToolBar, SIGNAL(customContextMenuRequested(QPoint)),
		    this, SLOT(viewDetailToolBarContextMenu(QPoint)));
	}
	if (Q_NULLPTR != mui_attachmentToolBar) {
		mui_attachmentToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
		connect(mui_attachmentToolBar, SIGNAL(customContextMenuRequested(QPoint)),
		    this, SLOT(viewAttachmentToolBarContextMenu(QPoint)));
	}

	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(foundDownloadedUnlistedMessages(AcntIdDb, int,
	        QList<MsgId>)), this,
	    SLOT(collectDownloadedUnlistedMessages(AcntIdDb, int, QList<MsgId>)));
	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, bool, qint64, int, QString)), this,
	    SLOT(collectSendMessageStatus(AcntId, QString, int, QString,
	        QString, QString, bool, 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);
		if (filter != Q_NULLPTR) {
			filter->registerAction(Qt::Key_Return,
			    &viewSelectedMessageViaFilter, this, true);
			filter->registerAction(Qt::Key_Enter, /* On keypad. */
			    &viewSelectedMessageViaFilter, this, true);
			filter->registerAction(Qt::CTRL | Qt::Key_F,
			    &showFilterLineViaFilter, this, true);
			ui->messageList->installEventFilter(filter);
		}
	}
	ui->messageList->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(ui->messageList));
	ui->messageList->setItemDelegate(new (::std::nothrow) TagsDelegate(this));

	m_accountProxyModel.setSourceModel(&m_accountModel);
	m_accountProxyModel.setWatchedTreeView(ui->accountList);
	m_accountProxyModel.setCollapsedRoleBit(AccountModel::ROLE_COLLAPSED_BIT);
	ui->accountList->setModel(&m_accountProxyModel);
	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(madeAvailable(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(opened(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
#else
	connect(GlobInstcs::msgDbsPtr, SIGNAL(madeAvailable(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(opened(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));

	loadSettings();
#endif

	/* Tool bar. */
	/* Clear message filter line when tool bar is hidden. */
	ui->toolBar->installEventFilter(
	    new (::std::nothrow) OnWidgetHiddenFilter(
	        &clearFilterLineViaFilter, this, ui->toolBar));
	/* Set toolbar buttons style from settings */
	ui->toolBar->setToolButtonStyle(toolBarButtonStyle(DlgToolBar::MAIN_TOP));

	/* Filter line. */
	/* Clear message filter line when line edit is hidden. */
	ui->messagesFilterHorizontalLayout->installEventFilter(
	    new (::std::nothrow) OnWidgetHiddenFilter(
	        &clearFilterLineViaFilter, this, ui->messageFilterLine));
	/* Hide message filter line when escape pressed in line edit. */
	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(ui->messageFilterLine);
		if (Q_NULLPTR != filter) {
			filter->registerAction(Qt::Key_Escape,
			    &hideFilterLineViaFilter, this, true);
			ui->messageFilterLine->installEventFilter(filter);
		}
	}

	/*
	 * Set amount of days to show message deletion notification
	 * into message model.
	 */
	{
		qint64 intVal = 0; /* Disable by default. */
		QString colVal;
		if (GlobInstcs::prefsPtr->intVal(
		        "window.main.message.isds_deletion.notify_ahead.days", intVal)) {
			m_messageTableModel.setMsgDeletionNotifyAheadDays(intVal);
		}
		if (GlobInstcs::prefsPtr->colourVal(
		        "window.main.message.isds_deletion.notify_ahead.colour", colVal)) {
			m_messageTableModel.setMsgDeletionNotifyAheadColour(
			    QColor("#" + colVal));
		}
	}

	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 requested halt checker timer. */
	connect(&m_timerCheckReqHalt, SIGNAL(timeout()),
	    this, SLOT(haltRequested()));
	m_timerCheckReqHalt.start(200);

	/* 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);
	}
	if (statsReporter == Q_NULLPTR) {
		statsReporter = new (::std::nothrow) StatsReporter;
	}
	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 + "   ");
}

/*!
 * @brief Collect usage report.
 *
 * @param[out] usageReport Collected usage report data.
 */
static
void collectUsageData(Json::UsageReport &usageReport, QWidget *parent)
{
	{
		int nBoxProd = 0;
		int nBoxTest = 0;
		for (const AcntId &acntId : GlobInstcs::acntMapPtr->acntIds()) {
			if (!acntId.testing()) {
				++nBoxProd;
			} else {
				++nBoxTest;
			}
		}
		usageReport.setNumBoxesProd(nBoxProd);
		usageReport.setNumBoxesTesting(nBoxTest);
	}

	{
		/* Collect data estimates without accessing database content. */
		qint64 dataSizeProd = 0;
		qint64 dataSizeTest = 0;
		for (const AcntId &acntId : GlobInstcs::acntMapPtr->acntIds()) {
			MessageDbSet *dbSet = accountDbSet(acntId, parent);
			if (dbSet != Q_NULLPTR) {
				const qint64 dbSize = dbSet->dbSize();
				if (!acntId.testing()) {
					dataSizeProd += dbSize;
				} else {
					dataSizeTest += dbSize;
				}
			}
		}
		usageReport.setMessageDataSizeProd(dataSizeProd);
		usageReport.setMessageDataSizeTesting(dataSizeTest);
	}

	{
		TagDb *tagDbPtr = GlobInstcs::tagContPtr->backendDb();
		if (tagDbPtr != Q_NULLPTR) {
			int numTags = 0;
			{
				Json::TagEntryList entries;
				tagDbPtr->getAllTags(entries);
				numTags = entries.size();
			}
			usageReport.setNumDefinedTags(numTags);
		}
	}

	usageReport.setUsesDatabasesInMemory(
	    !PrefsSpecific::accountDbOnDisk(*GlobInstcs::prefsPtr));
	usageReport.setUsesPin(GlobInstcs::pinSetPtr->pinConfigured());
	usageReport.setUsesRecordsManagement(
	    GlobInstcs::recMgmtSetPtr->isValid());

}

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

	/* Remember first application startup time and other info. */
	if (Q_UNLIKELY(!PrefsSpecific::appFirstLaunchTime(
	        *GlobInstcs::prefsPtr).isValid())) {
		PrefsSpecific::storeAppFirstLaunchTime(*GlobInstcs::prefsPtr);
	}
	PrefsSpecific::incrementLaunchCount(*GlobInstcs::prefsPtr);
	PrefsSpecific::storeAppLastLaunchTime(*GlobInstcs::prefsPtr);

	/* 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));
	}

	/* Always report stats. */
	if (statsReporter != Q_NULLPTR) {
		Json::UsageReport usageReport;
		if (PrefsSpecific::statsReportUsage(*GlobInstcs::prefsPtr)) {
			/* Collect usage report data. */
			collectUsageData(usageReport, this);
		}
		QString uniqueId = PrefsSpecific::statsReportUniqueId(
		    *GlobInstcs::prefsPtr);
		if (uniqueId.isEmpty()) {
			/* Construct and store new unique identifier. */
			uniqueId = StatsReporter::constructUniqueId();
			PrefsSpecific::setStatsReportUniqueId(
			    *GlobInstcs::prefsPtr, uniqueId);
		}
		statsReporter->reportVisit(Json::Report::AT_DESKTOP, uniqueId,
		    StatsReporter::constructVisitPath(Json::Report::AT_DESKTOP),
		    usageReport);
	}

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

		if (PrefsSpecific::isTimeToShowDonationDlg(*GlobInstcs::prefsPtr)) {
			/* Show donate dialogue and plan its next showing. */
			PrefsSpecific::planNextDonationViewTime(*GlobInstcs::prefsPtr, false);
			DlgAbout::about(updateChecker, DlgAbout::TAB_3_DONATE, this);
		} else {
			/* Ask for consent for user data collection. */
			const QDateTime consentDate =
			   PrefsSpecific::statsReportLastConsentRequest(*GlobInstcs::prefsPtr);
			const bool consentUserData = PrefsSpecific::statsReportUsage(*GlobInstcs::prefsPtr);
			/* Ask if no consent given or after half year if incomplete data selected. */
			if ((!consentDate.isValid()) ||
			    ((!consentUserData) && (consentDate.addDays(150) < QDateTime::currentDateTimeUtc()))) {
				DlgPreferences::modify(*GlobInstcs::prefsPtr,
				    *GlobInstcs::pinSetPtr,
				    DlgPreferences::TAB_DATA_COLLECTION, this);
				PrefsSpecific::setStatsReportLastConsentRequest(
				    *GlobInstcs::prefsPtr,
				    QDateTime::currentDateTimeUtc());
			}
		}

		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 */
	const QModelIndex amIndexTop = m_accountModel.topAcntIndex(acntId);

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

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

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

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

	return amIndexYear;
}

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 */
	const QModelIndex amIndexYear =
	    accountYearlyIndex(acntId, deliveryYear, msgType);
	if (Q_UNLIKELY(!amIndexYear.isValid())) {
		return;
	}

	ui->accountList->selectionModel()->setCurrentIndex(
	    source2proxyModelIndex(&m_accountProxyModel, amIndexYear),
	    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 a data box 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."));
				DlgMsgBoxDetail::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);
			ignoreRetVal(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."));
		DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Download message list error"), errorMessage, infoText,
		    QString(), QMessageBox::Ok, QMessageBox::Ok);
	}
}

/*!
 * @brief Generate a string containing a list of message IDs and delivery times.
 *
 * @param[in] msgIds List of message identifiers.
 * @return String with IDs and delivery times.
 */
static
QString dmIdListing(const QList<MsgId> &msgIds)
{
	QString listing;
	for (const MsgId &msgId : msgIds) {
		listing += QStringLiteral("\n") +
		    MainWindow::tr("%1 delivered at %2")
		        .arg(QString::number(msgId.dmId()))
		        .arg(msgId.deliveryTime().toString(Utility::dateTimeDisplayFormat));
	}
	return listing;
}

void MainWindow::collectDownloadedUnlistedMessages(const AcntIdDb &rAcntIdDb,
    int direction, const QList<MsgId> &unlistedMsgIds)
{
	const QString accountName = GlobInstcs::acntMapPtr->acntData(rAcntIdDb).accountName();

	QString message;
	switch (direction) {
	case MSG_RECEIVED:
		message = tr("Received messages that are not being listed have been detected in the data box '%1'.").arg(accountName);
		break;
	case MSG_SENT:
		message = tr("Sent messages that are not being listed have been detected in the data box '%1'.").arg(accountName);
		break;
	default:
		message = tr("Messages that are not being listed have been detected in the data box '%1'.").arg(accountName);
		break;
	}

	QString infoText;
	/* Get list of relevant database files. */
	{
		infoText = tr("The following database files seem to have problems recalling written data:");

		const MessageDbSet *dbSet = accountDbSet(rAcntIdDb, this);
		if (Q_NULLPTR != dbSet) {
			QStringList fileList;
			{
				QSet<QString> fileSet;
				for (const MsgId &msgId : unlistedMsgIds) {
					MessageDb *msgDb =
					    dbSet->constAccessMessageDb(
					        msgId.deliveryTime());
					if (Q_NULLPTR != msgDb) {
						fileSet.insert(
						    QDir::toNativeSeparators(msgDb->fileName()));
					}
				}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
				fileList = QStringList(fileSet.begin(), fileSet.end());
#else /* < Qt-5.14.0 */
				fileList = fileSet.toList();
#endif /* >= Qt-5.14.0 */
			}

			fileList.sort();
			infoText += QStringLiteral("\n\n") + fileList.join("\n");
		}
	}

	/* Get message list. */
	QString detailText =
	    tr("These messages are not being recalled from the databases:") +
	    QStringLiteral("\n");
	detailText += dmIdListing(unlistedMsgIds);

	DlgMsgBoxDetailNonModal::warning(this, tr("Unlisted Messages"),
	    message, infoText, detailText, QMessageBox::Close);
}

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, bool isVodz, qint64 dmId, int processFlags,
    const QString &recMgmtHierarchyId)
{
#define STORE_RAW_INTO_DB (!isVodz) /* Set to false if to store as files -- determined according to VoDZ. */
	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()), STORE_RAW_INTO_DB,
		        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);
	}
#undef STORE_RAW_INTO_DB
}

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);
	if (Q_UNLIKELY(!GlobInstcs::workPoolPtr->runSingle(task))) {
		delete task; task = Q_NULLPTR;
		return false;
	}

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

	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);
}

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);
			const QModelIndex amIndexTop =
			    m_accountModel.topAcntIndex(acntId);
			if (amIndexTop.isValid()) {
				const QModelIndex apIndex =
				    source2proxyModelIndex(&m_accountProxyModel,
				        m_accountModel.index(0, 0, amIndexTop));
				ui->accountList->selectionModel()->setCurrentIndex(
				    apIndex,
				    QItemSelectionModel::ClearAndSelect);
				updateAccountSelection(apIndex);
				/*
				 * 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 &amIndex,
    bool prohibitYearRemoval)
{
	debugFuncCall();

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

	/* Get database id. */
	const AcntId acntId(m_accountModel.acntId(amIndex));
	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, 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) {

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

		const AcntId rAcntId(m_accountModel.acntId(amIndex));
		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 data box 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;
			}
		}
	}
	bool checkDownloadedList = true;
	GlobInstcs::prefsPtr->boolVal(
	    "action.sync_account.tie.action.check_downloaded_message_list",
	    checkDownloadedList);

	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, checkDownloadedList,
		    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, checkDownloadedList);
		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 Data Box '%1'").arg(accountName);
		QString questionText = tr("Do you want to remove the data box '%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);
		GlobInstcs::isdsSessionsPtr->quitSession(acntId.username());
	}

	if (DlgYesNoCheckbox::YesChecked == retVal) {
		if (GlobInstcs::msgDbsPtr->deleteDbSet(dbSet)) {
			showStatusTextWithTimeout(tr(
			    "Data box '%1' was deleted together with message database file.")
			    .arg(accountName));
		} else {
			showStatusTextWithTimeout(tr(
			    "Data box '%1' was deleted but its message database was not deleted.")
			    .arg(accountName));
		}
	} else if (DlgYesNoCheckbox::YesUnchecked == retVal) {
		showStatusTextWithTimeout(
		    tr("Data box '%1' was deleted.").arg(accountName));
	}

	/*
	 * Delete all tags from message_tags table related to the given account.
	 * Tags in the tag table are kept.
	 *
	 * Deleting tag association with a message is
	 * undesirable if the tag database is
	 * shared between multiple application instances.
	 *
	 * GlobInstcs::tagContPtr->removeAllMsgTagsFromAccount();
	 */

	if ((DlgYesNoCheckbox::YesChecked == retVal) ||
	    (DlgYesNoCheckbox::YesUnchecked == retVal)) {
		saveSettings();
	}

	if (m_accountModel.rowCount() < 1) {
		updateAccountSelection(QModelIndex());
		updateActionActivation();
	}
}

void MainWindow::filterMessages(const QString &text)
{
	debugSlotCall();

	/* Store style at first invocation. */
	if (m_dfltFilerLineStyleSheet == SortFilterProxyModel::invalidFilterEditStyle) {
		m_dfltFilerLineStyleSheet = ui->messageFilterLine->styleSheet();
	}

	/* The model is always associated to the proxy model. */

	m_messageListProxyModel.setSortRole(DbMsgsTblModel::ROLE_PROXYSORT);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_messageListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_messageListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* 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::tagContPtr) {
		columnList.append(
		    m_messageListProxyModel.sourceModel()->columnCount() +
		    DbMsgsTblModel::TAGS_NEG_COL); /* Tags. */
	}
	m_messageListProxyModel.setFilterKeyColumns(columnList);

	/* Set filter field background colour. */
	if (text.isEmpty()) {
		ui->messageFilterLine->setStyleSheet(m_dfltFilerLineStyleSheet);
	} else if (m_messageListProxyModel.rowCount() != 0) {
		ui->messageFilterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		ui->messageFilterLine->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 amIndexTop = m_accountModel.topAcntIndex(acntId);
	if (amIndexTop.isValid()) {
		bool prohibitYearRemoval =
		    (nodeType == AccountModel::nodeReceivedYear) ||
		    (nodeType == AccountModel::nodeSentYear);
		regenerateAccountModelYears(amIndexTop, prohibitYearRemoval);
	}

	/*
	 * Force repaint.
	 * TODO -- A better solution?
	 */
	ui->accountList->repaint();
	if ((nodeType != AccountModel::nodeUnknown) && (dmId != -1)) {
		if (!year.isEmpty()) {
			const QModelIndex amIndexYear =
			    accountYearlyIndex(acntId, year, msgType);
			if (amIndexYear.isValid()) {
				ui->accountList->selectionModel()->setCurrentIndex(
				    source2proxyModelIndex(&m_accountProxyModel, amIndexYear),
				    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(Exports::dmTypePrefix(messageDb, msgId.dmId()))
	        .arg(msgId.dmId()),
	    msgRaw,
	    recMgmtHierarchyId.isEmpty() ? QStringList() : QStringList(recMgmtHierarchyId),
	    this);
}

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();
}

void MainWindow::clearFilterLineViaFilter(QObject *mwPtr)
{
	if (Q_NULLPTR == mwPtr) {
		return;
	}

	MainWindow *mw = qobject_cast<MainWindow *>(mwPtr);
	if (Q_UNLIKELY(Q_NULLPTR == mw)) {
		Q_ASSERT(0);
		return;
	}

	if (mw->ui->messageFilterLine != Q_NULLPTR) {
		mw->ui->messageFilterLine->clear();
	}
}

void MainWindow::showFilterLineViaFilter(QObject *mwPtr)
{
	if (Q_NULLPTR == mwPtr) {
		return;
	}

	MainWindow *mw = qobject_cast<MainWindow *>(mwPtr);
	if (Q_UNLIKELY(Q_NULLPTR == mw)) {
		Q_ASSERT(0);
		return;
	}
	if (mw->ui->messageFilterLine != Q_NULLPTR) {
		mw->showMessageFilter();
	}
}

void MainWindow::hideFilterLineViaFilter(QObject *mwPtr)
{
	if (Q_NULLPTR == mwPtr) {
		return;
	}

	MainWindow *mw = qobject_cast<MainWindow *>(mwPtr);
	if (Q_UNLIKELY(Q_NULLPTR == mw)) {
		Q_ASSERT(0);
		return;
	}
	if (mw->ui->messageFilterLine != Q_NULLPTR) {
		mw->hideMessageFilter();
	}
}

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);

	bool isVodz = false;
	{
		bool iOk = false;
		bool ret = dbSet->isVodz(msgId.dmId(), &iOk);
		if (iOk) {
			isVodz = ret;
		}
	}

#define STORE_RAW_INTO_DB (!isVodz) /* Set to false if to store as files -- determined according to VoDZ. */
	TaskDownloadMessage *task = new (::std::nothrow) TaskDownloadMessage(
	    AcntIdDb(acntId, dbSet), msgDirect, msgId, STORE_RAW_INTO_DB,
	    Task::PROC_LIST_SCHEDULED_LAST);
	if (Q_UNLIKELY(task == Q_NULLPTR)) {
		Q_ASSERT(0);
		return false;
	}

	bool ret = false;

	task->setAutoDelete(false);
	if (GlobInstcs::workPoolPtr->runSingle(task)) {
		ret = TaskDownloadMessage::DM_SUCCESS == task->m_result;
	}
	if (ret) {
		msgId.setDeliveryTime(task->m_mId.deliveryTime());
	}

	delete task; task = Q_NULLPTR;

	return ret;
#undef STORE_RAW_INTO_DB
}

bool MainWindow::messageMissingOfferDownload(MsgId &msgId, const QString &title)
{
	debugFuncCall();

	int dlgRet = DlgMsgBoxDetail::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) {
		DlgMsgBoxDetail::message(parent, QMessageBox::Information,
		    MainWindow::tr("Password expiration"),
		    MainWindow::tr("According to the last available information, "
		        "your password for data box '%1' (login '%2') expired %3 day(s) ago (%4).")
		        .arg(accountName).arg(acntId.username()).arg(days*(-1))
		        .arg(dateTime.toString(Utility::dateTimeDisplayFormat)),
		    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 = DlgMsgBoxDetail::message(parent, QMessageBox::Information,
		    MainWindow::tr("Password expiration"),
		    MainWindow::tr("According to the last available information, "
		        "your password for data box '%1' (login '%2') will expire in %3 day(s) (%4).")
		        .arg(accountName).arg(acntId.username()).arg(days)
		        .arg(dateTime.toString(Utility::dateTimeDisplayFormat)),
		    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());
	}
	const QString dbID = GlobInstcs::accntDbPtr->dbId(
	    AccountDb::keyFromLogin(acntId.username()));
	if (!IsdsHelper::getDtInfo(acntId, dbID)) {
		logWarningNL("Long term storage 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 data box '%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;
	}
	const QString dbID = GlobInstcs::accntDbPtr->dbId(
	    AccountDb::keyFromLogin(acntId.username()));
	if (!IsdsHelper::getDtInfo(acntId, dbID)) {
		//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 data box error") +
			    ": " + accountInfo.accountName();
			QString msgBoxContent =
			    tr("It was not possible to get user info and "
			    "databox info from ISDS server for data box")
			    + " \"" + 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("Data box") + "<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);
		const QModelIndex amIndex = m_accountModel.topAcntIndex(acntId);

		/* Change selection to newly added account. */
		if (amIndex.isValid()) {
			const QModelIndex apIndex = source2proxyModelIndex(&m_accountProxyModel, amIndex);
			logDebugLv0NL("Changing selection to index %d.", apIndex.row());
			ui->accountList->selectionModel()->setCurrentIndex(apIndex,
			    QItemSelectionModel::ClearAndSelect);
			/* Expand the tree. */
			ui->accountList->expand(apIndex);
		}
	} else if (ret == -1) {
		QMessageBox::warning(this, tr("Adding new data box failed"),
		    tr("Data box could not be added because an error occurred."),
		    QMessageBox::Ok);
	} else if (ret == -2) {
		logErrorNL("Data box with username '%s' already exists.",
		    accountInfo.userName().toUtf8().constData());
		QMessageBox::warning(this, tr("Adding new data box failed"),
		    tr("Data box could not be added because data box 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 data box '%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) {
			const QModelIndex amIndex = m_accountModel.index(i, 0);
			acntId = m_accountModel.acntId(amIndex);
			Q_ASSERT(acntId.isValid());
			showStatusTextPermanently(
			    tr("Checking time stamps in data box '%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;
		}

		const QSet<MsgId> msgIdSet(dbSet->getAllMsgIds());
		msgCnt = msgIdSet.count();

		for (const MsgId &mId : msgIdSet) {
			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 data box '%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 = DlgMsgBoxDetail::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::exportAsGUI(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::exportAsGUI(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::tagContPtr == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	if (msgIdList.isEmpty()) {
		DlgTags::editAvailable(acntId, GlobInstcs::tagContPtr, this);
	} else if ((acntId.isValid() && !msgIdList.isEmpty()) || acntId.isValid()) {
		DlgTags::editAssignment(acntId, GlobInstcs::tagContPtr,
		    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 data box '%1' finished").arg(userName));

	QString detailText;
	if (errImportList.count() > 0) {
		for (int m = 0; m < errImportList.count(); ++ m) {
			detailText += errImportList.at(m) + "\n";
		}
	}

	DlgMsgBoxDetail::message(this, QMessageBox::Information,
	    tr("Messages import result"),
	    tr("Import of messages into data box '%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);
	raiseWidget(widget);
}

void MainWindow::ensureSelectedAttachmentsPresence(void)
{
	QSet<qint64> missingAttachIds;
	{
		const QModelIndexList idxs =
		    ui->attachmentList->selectionModel()->selectedRows(
		        AttachmentTblModel::BINARY_CONTENT_COL);
		for (const QModelIndex &idx : idxs) {
			int row = idx.row();
			if (!m_attachmentModel.holdsAttachmentContent(row)) {
				missingAttachIds.insert(m_attachmentModel.attachId(row));
			}
		}
	}

	if (!missingAttachIds.isEmpty()) {
		const MWStatus mwStatus = m_selectionStatus.mwStatus();
		const QSet<MsgId> &msgIds = mwStatus.msgIds();
		if (Q_UNLIKELY(1 != msgIds.size())) {
			return;
		}

		const MsgId &msgId = firstMsgId(msgIds);
		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;
		}

		for (qint64 attachId : missingAttachIds) {
			m_attachmentModel.overrideAttachmentContent(attachId,
			    messageDb->getAttachmentContent(attachId));
		}
	}
}
