/*
 * Copyright (C) 2014-2026 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 <QApplication>
#include <QClipboard>
#include <QCloseEvent>
#include <QComboBox>
#include <QDesktopServices>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QLineEdit>
#include <QMessageBox>
#include <QMetaMethod>
#include <QPrinter>
#include <QProgressBar>
#include <QPropertyAnimation>
#include <QSet>
#include <QSettings>
#include <QStackedWidget>
#include <QTableView>
#include <QUrl>

#include "datovka.h"
#include "src/about.h"
#include "src/datovka_shared/compat/compiler.h" /* ignoreRetVal, macroStdMove */
#include "src/datovka_shared/compat_qt/misc.h" /* Q_FALLTHROUGH */
#include "src/datovka_shared/crypto/crypto_funcs.h"
#include "src/datovka_shared/crypto/crypto_wrapped.h"
#include "src/datovka_shared/html/html_export.h"
#include "src/datovka_shared/io/db_time.h"
#include "src/datovka_shared/io/filesystem.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/json/db_info.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/db_repair/gui/mw_db_repair.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_export_for_mobile_app.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_message_check.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_survey.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_timestamp_renewal.h"
#include "src/gui/dlg_view_changelog.h"
#include "src/gui/dlg_wait_progress.h"
#include "src/gui/dlg_yes_no_checkbox.h"
#include "src/gui/dlg_tag_assignment.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/initialisation_gui.h" /* loadAppUiThemeFromIndex() */
#include "src/io/account_shadow_helper.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/session_keeper.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/timestamp_db.h"
#include "src/io/update_checker.h"
#include "src/json/message_id.h"
#include "src/json/tag_assignment_hash.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/on_widget_hidden_filter.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/widgets/account_status_widget.h"
#include "src/widgets/db_status_widget.h"
#include "src/widgets/tags_label_control_widget.h"
#include "src/widgets/tags_popup_control_widget.h"
#include "src/widgets/timestamp_tracking_widget.h"
#include "src/worker/message_emitter.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 "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. */
#define SW_DRAFTS 2 /* Stacked widget page with draft 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;
/* Keeps connection alive. */
static SessionKeeper *sessionKeeper = Q_NULLPTR;

//static const QString messageTableName("message_list");
static const QString draftTableName("draft_list");
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)
{
	delete sessionKeeper; sessionKeeper = Q_NULLPTR;

	/*
	 * No saveSettings().
	 * Only save prefs.
	 */
	savePrefsSettings();
	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->draftList->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->draftList,
		    PrefsSpecific::mainWinColWidths(*GlobInstcs::prefsPtr,
		        draftTableName));
		Dimensions::setTableColumnSortOrder(ui->draftList,
		    PrefsSpecific::mainWinTblColSortOrder(*GlobInstcs::prefsPtr,
		    draftTableName));
		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->draftList,
	    PrefsSpecific::mainWinColWidths(*GlobInstcs::prefsPtr,
	        draftTableName));
	Dimensions::setTableColumnSortOrder(ui->draftList,
	    PrefsSpecific::mainWinTblColSortOrder(*GlobInstcs::prefsPtr,
	    draftTableName));

	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->messageFilterCloseButton);
	if (Q_NULLPTR != detailToolBar) {
		QWidget::setTabOrder(ui->messageFilterCloseButton, detailToolBar);
		QWidget::setTabOrder(detailToolBar, ui->messageInfo);
	} else {
		QWidget::setTabOrder(ui->messageFilterCloseButton, ui->messageInfo);
	}
	if (Q_NULLPTR != attachmentToolBar) {
		QWidget::setTabOrder(ui->messageInfo, attachmentToolBar);
		QWidget::setTabOrder(attachmentToolBar, ui->attachmentList);
	} else {
		QWidget::setTabOrder(ui->messageInfo, ui->attachmentList);
	}
	QWidget::setTabOrder(ui->attachmentList, ui->draftList);
	QWidget::setTabOrder(ui->draftList, ui->draftFilterLine);
	QWidget::setTabOrder(ui->draftFilterLine, ui->draftFilterCloseButton);
}

/*!
 * @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_DATABOX_SYNC_ALL));
	    /* Separator. */
	ui->actionAddAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_ADD));
	ui->actionRemoveAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_DELETE));
	ui->actionAccountSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SETTINGS));
	    /* Separator. */
	ui->actionImportAccountFromDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_IMPORT));
	ui->actionBackUp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_BACKUP));
	ui->actionRestore->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_RESTORE));
	    /* Separator. */
	ui->actionProxySettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_PROXY));
	    /* Separator. */
	ui->menuRecordsManagement->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE));
	{
		ui->actionRecordsManagementSettings->setIcon(
		    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE_SETTINGS));
		ui->actionUpdateRecordsManagementInformation->setIcon(
		    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE_SYNC));
		ui->actionCheckUploadMessagesRecordsManagement->setIcon(
		    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE_VERIFY));
	}
	    /* Separator. */
	ui->actionTagStorageSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_TAG_EDIT));
	    /* Separator. */
	ui->actionPreferences->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_SETTINGS));
	/* actionQuit -- connected in ui file. */
	ui->actionQuit->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_DELETE));

	/* Data box menu. */
	ui->actionSynchroniseAccount->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SYNC));
	ui->menuSynchronisation->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SYNC));
	ui->actionSynchroniseAccountReceived->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SYNC_RECEIVED));
	ui->actionSynchroniseAccountSent->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SYNC_SENT));
	ui->actionSendMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_ADD));
	    /* Separator. */
	ui->actionSendEGovRequest->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_UPLOAD));
	    /* Separator. */
	         /* Mark All received as menu. */
	ui->actionMarkAllReceivedRead->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_GREY));
	ui->actionMarkAllReceivedUnread->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_GREEN));
	        /* Separator. */
	ui->actionMarkAllReceivedUnsettled->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_RED));
	ui->actionMarkAllReceivedInProgress->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_YELLOW));
	ui->actionMarkAllReceivedSettled->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_GREY));
	    /* Separator. */
	ui->actionChangePassword->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_USER_KEY));
	    /* Separator. */
	ui->actionAccountProperties->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABOX_SETTINGS));
	    /* Separator. */
	ui->actionMoveAccountUp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_ARROW_UP));
	ui->actionMoveAccountDown->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_ARROW_DOWN));
	    /* Separator. */
	ui->actionChangeDataDirectory->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_FOLDER));
	    /* Separator. */
	ui->actionImportZfoIntoDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_IMPORT_ZFO));
	ui->actionImportMessagesFromDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_IMPORT));
	    /* Separator. */
	ui->actionVacuumMessageDatabase->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_RESTORE));
	ui->actionSplitDatabaseYears->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_STATISTIC));

	/* Message menu. */
	ui->actionDownloadMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_DOWNLOAD));
	ui->actionReply->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_REPLY));
	ui->actionForward->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_FORWARD));
	ui->actionUseAsTemplate->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE));
	    /* Separator. */
	ui->actionSignatureDetail->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_SIGNATURE));
	ui->actionAuthenticateMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_VERIFY));
	    /* Separator. */
	ui->actionOpenMessageExternal->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_OPEN));
	ui->actionOpenMessageExternal->isEnabled();
	ui->actionOpenAcceptanceInfoExternal->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELIVERY_INFO_OPEN));
	ui->actionOpenAcceptanceInfoExternal->isEnabled();
	    /* Separator. */
	ui->actionSendToRecordsManagement->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE_UPLOAD));
	    /* Separator. */
	ui->actionExportMessageZfo->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_EXPORT_ZFO));
	ui->actionExportMessageZfo->isEnabled();
	ui->actionExportAcceptanceInfoZfo->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELIVERY_INFO_EXPORT_ZFO));
	ui->actionExportAcceptanceInfoZfo->isEnabled();
	ui->actionExportAcceptanceInfoPdf->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DELIVERY_INFO_EXPORT_PDF));
	ui->actionExportAcceptanceInfoPdf->isEnabled();
	ui->actionExportEnvelopePdf->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_EXPORT_PDF));
	ui->actionExportEnvelopePdf->isEnabled();
	ui->actionExportEnvelopePdfAndAttachments->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_EXPORT_ALL));
	ui->actionExportEnvelopePdfAndAttachments->isEnabled();
	    /* Separator. */
	ui->menuEmailWith->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_EMAIL));
	{
		ui->actionEmailZfos->isEnabled();
		ui->actionEmailAllAttachments->isEnabled();
		ui->actionEmailContentSelection->isEnabled();
	}
	    /* Separator. */
	ui->actionSaveAllAttachments->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_ATTACHMENT_SAVE_ALL));
	ui->actionSaveSelectedAttachments->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_ATTACHMENT_SAVE));
	ui->actionOpenAttachment->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_ATTACHMENT_OPEN));
	    /* Separator. */
	ui->actionEditTagAssignment->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_TAG_EDIT));
	    /* Separator. */
	//ui->actionTrackTimestampExpiration;
	    /* Separator. */
	ui->actionDeleteMessage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_DELETE));

	/* Tools menu. */
	ui->actionFindDataBox->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_SEARCH));
	    /* Separator. */
	ui->actionAuthenticateMessageFile->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_VERIFY));
	ui->actionViewMessageFile->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_VIEW_ZFO));
	ui->actionExportCorrespondenceOverview->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_EXPORT_CORRESPONDENCE));
	ui->actionCheckMessageTimestampExpiration->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_SHIELD));
	ui->actionTimeStampRenewal->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_SHIELD));
	    /* Separator. */
	ui->actionMessageSearch->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_FIND));
	    /* Separator. */
	ui->actionEditAvailableTags->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_TAG_EDIT));
	    /* Separator. */
	ui->actionConvertImportFromMobileApp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_CONVERT));
	ui->actionConvertExportForMobileApp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_CONVERT));
	    /* Separator. */
	ui->actionRepairMessageDatabaseFiles->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_DATABASE_REPAIR));
	    /* Separator. */
	ui->menuToolBars->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_TOOLBAR_MAIN));
	{
		ui->actionCustomiseToolBar->setIcon(
		    IconContainer::construcIcon(
		    IconContainer::ICON_TOOLBAR_MAIN));
		ui->actionCustomiseAccountBar->setIcon(
		    IconContainer::construcIcon(
		    IconContainer::ICON_TOOLBAR_DATABOX));
		ui->actionCustomiseAttachmentBar->setIcon(
		    IconContainer::construcIcon(
		    IconContainer::ICON_TOOLBAR_ATTACHMENT));
		ui->actionCustomiseDetailBar->setIcon(
		    IconContainer::construcIcon(
		    IconContainer::ICON_TOOLBAR_MESSAGE_DETAIL));
		ui->actionCustomiseMessageBar->setIcon(
		    IconContainer::construcIcon(
		    IconContainer::ICON_TOOLBAR_MESSAGE));
	}
	ui->actionViewLog->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_VIEW_LOG));
	ui->actionViewAllSettings->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_PREFS));

	/* Help. */
	ui->actionAboutApplication->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_INFO));
	ui->actionHomepage->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_HOME));
	ui->actionHelp->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_APP_HELP));

	/* Actions that are not shown in the top menu. */
	ui->actionEmail_selected_attachments->isEnabled();
	ui->actionFilterMessages->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_FILTER));
	//ui->actionEditDraft;
	ui->actionDeleteDraft->setIcon(
	    IconContainer::construcIcon(IconContainer::ICON_MESSAGE_DELETE));
}

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

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

	/*
	 * TODO -- Remove this action and move the functionality into the
	 * timestamp dialogue.
	 */
	ui->actionCheckMessageTimestampExpiration->setVisible(false);

	/*
	 * 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();
	ui->draftList->setNarrowedLineHeight();

	/* Header content alignment. */
	ui->accountList->header()->setDefaultAlignment(Qt::AlignVCenter | Qt::AlignLeft);
	ui->messageList->horizontalHeader()->setDefaultAlignment(Qt::AlignVCenter | Qt::AlignLeft);
	ui->attachmentList->horizontalHeader()->setDefaultAlignment(Qt::AlignVCenter | Qt::AlignLeft);
	ui->draftList->horizontalHeader()->setDefaultAlignment(Qt::AlignVCenter | 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->messageFilterCloseButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton));
	connect(ui->messageFilterCloseButton, SIGNAL(clicked()),
	    this, SLOT(hideMessageFilter()));

	ui->draftsFilterHorizontalLayout->setVisible(false);
	connect(ui->draftFilterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterDrafts(QString)));
	ui->draftFilterCloseButton->setIcon(style()->standardIcon(QStyle::SP_DockWidgetCloseButton));
	connect(ui->draftFilterCloseButton, SIGNAL(clicked()),
	    this, SLOT(hideDraftFilter()));

	/* 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 database mode (memory/disk) control. */
	mui_dbStatusMode = new (::std::nothrow) DbStatusControl(
	    GlobInstcs::acntMapPtr, GlobInstcs::msgDbsPtr, this);
	if (Q_NULLPTR != mui_dbStatusMode) {
		ui->statusBar->addWidget(mui_dbStatusMode, 0);
	}

	mui_statusLabelTimestamps = new (::std::nothrow) TimestampTrackingControl(
	    GlobInstcs::timestampDbPtr, GlobInstcs::msgDbsPtr,
	    ui->actionTimeStampRenewal, GlobInstcs::prefsPtr, this);
	if (Q_NULLPTR != mui_statusLabelTimestamps) {
		ui->statusBar->addWidget(mui_statusLabelTimestamps, 0);
	}

	/* Create status bar tag control. */
	mui_statusLabelTags = new (::std::nothrow) TagsLabelControlWidget(
	    GlobInstcs::tagContPtr, this);
	if (Q_NULLPTR != mui_statusLabelTags) {
		ui->statusBar->addWidget(mui_statusLabelTags, 0);
		connect(mui_statusLabelTags, SIGNAL(clicked()),
		    this, SLOT(viewTagAssignmentPopup()));
	}

	/* Create status bar online/offline control. */
	mui_statusAccounts = new (::std::nothrow) AccountStatusControl(
	    GlobInstcs::acntMapPtr, GlobInstcs::shadowAcntsPtr,
	    GlobInstcs::isdsSessionsPtr, this);
	if (Q_NULLPTR != mui_statusAccounts) {
		ui->statusBar->addWidget(mui_statusAccounts, 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_BALL_RED),
	    tr("Unsettled"));
	mui_messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_YELLOW),
	    tr("In Progress"));
	mui_messageStateCombo->addItem(
	    IconContainer::construcIcon(IconContainer::ICON_BALL_GREY),
	    tr("Settled"));

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

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

#define ACNT_CKEY_PARTS 6 /* Account node colapse key has 6 parts. */
#define ALL_CKEY_PARTS 7 /* All node collapse key has 7 parts. */
#define RS_CKEY_PARTS 8 /* Received/sent node collapse keys have 8 parts. */

/*!
 * @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 ACNT_CKEY_PARTS: /* Account name node. */
			accountList.setExpanded(source2proxyModelIndex(accountProxyModel, amIndex), !collapsed);
			break;
		case ALL_CKEY_PARTS: /* All node. */
			{
				bool iOk = false;
				const int pos1 = nameSplit[6].toInt(&iOk);
				if (iOk && (pos1 == AccountModel::CHROW_ALL)) {
					amIndex = accountModel->index(pos1, 0, amIndex);
					accountList.setExpanded(source2proxyModelIndex(accountProxyModel, amIndex), !collapsed);
				}
			}
			break;
		case RS_CKEY_PARTS:
			{
				bool iOk = false;
				const int pos1 = nameSplit[6].toInt(&iOk);
				if (iOk && (pos1 == AccountModel::CHROW_ALL)) {
					const int pos2 = nameSplit[7].toInt(&iOk);
					if (iOk &&
					    ((pos2 == AccountModel::CHROW_RCVD) ||
					     (pos2 == AccountModel::CHROW_SENT))) {
						amIndex = accountModel->index(pos1, 0, amIndex);
						amIndex = accountModel->index(pos2, 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));
		/* ACNT_CKEY_PARTS */
		prefs.setBoolVal(
		    QString("window.main.account_tree.account.%1.collapsed")
		        .arg(accountId),
		    !topLevelExp);
		if (topLevelExp) {
			/* Here index points to nodeAccountTop. */
			const QModelIndex amIndexAll = accountModel->index(AccountModel::CHROW_ALL, 0, amIndex);
			bool allExp = accountList.isExpanded(source2proxyModelIndex(accountProxyModel, amIndexAll));
			/* ALL_CKEY_PARTS */
			prefs.setBoolVal(
			    QString("window.main.account_tree.account.%1.collapsed.%2")
			        .arg(accountId)
			        .arg(AccountModel::CHROW_ALL),
			    !allExp);
			if (allExp) {
				amIndex = accountModel->index(AccountModel::CHROW_RCVD, 0, amIndexAll);
				/* RS_CKEY_PARTS */
				prefs.setBoolVal(
				    QString("window.main.account_tree.account.%1.collapsed.%2.%3")
				        .arg(accountId)
				        .arg(AccountModel::CHROW_ALL)
				        .arg(AccountModel::CHROW_RCVD),
				    !accountList.isExpanded(source2proxyModelIndex(accountProxyModel, amIndex)));
				amIndex = accountModel->index(AccountModel::CHROW_SENT, 0, amIndexAll);
				/* RS_CKEY_PARTS */
				prefs.setBoolVal(
				    QString("window.main.account_tree.account.%1.collapsed.%2.%3")
				        .arg(accountId)
				        .arg(AccountModel::CHROW_ALL)
				        .arg(AccountModel::CHROW_SENT),
				    !accountList.isExpanded(source2proxyModelIndex(accountProxyModel, amIndex)));
			}
		}
	}
}

#undef ACNT_CKEY_PARTS
#undef ALL_CKEY_PARTS
#undef RS_CKEY_PARTS

/*!
 * @brief Load configuration from INI file.
 *
 * @param[in] filePath Path to file.
 */
static
void loadINISettings(const QString &filePath)
{
	QSettings settings(filePath, 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 */

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

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

	/* Tag container settings. */
	GlobInstcs::tagContSetPtr->loadFromSettings(settings);
	GlobInstcs::tagContSetPtr->decryptPassword(GlobInstcs::pinSetPtr->pinValue());

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

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

	loadINISettings(GlobInstcs::iniPrefsPtr->loadConfPath());

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

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

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

	/* Tag container settings. */
	applyCompleteTagContainerSettings(*GlobInstcs::tagContSetPtr,
	    *GlobInstcs::tagContPtr);

	/* Accounts. */
	m_accountModel.loadContent(GlobInstcs::acntMapPtr);

	/* Convert local tag database from old format. */
	{
		TagDb *tagDbPtr = GlobInstcs::tagContPtr->backendDb();
		if (tagDbPtr != Q_NULLPTR) {
			Json::DbInfo 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 preferences. */
	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();
}

/*!
 * @brief Store configuration into INI file.
 *
 * @param[in] filePath Path to file.
 * @return True is configuration was written.
 */
static
bool saveINISettings(const QString &filePath)
{
	{
		/*
		 * TODO -- Target file name differs from source for testing purposes.
		 */
		QSettings settings(filePath, 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 */

		if (Q_UNLIKELY(QSettings::NoError != settings.status())) {
			return false;
		}

		settings.clear();

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

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

		/* Tag container settings. */
		GlobInstcs::tagContSetPtr->saveToSettings(
		    GlobInstcs::pinSetPtr->pinValue(), settings);

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

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

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

		settings.sync();

		if (Q_UNLIKELY(QSettings::NoError != settings.status())) {
			return false;
		}
	}
	/* Remove '"' symbols from passwords in INI file. */
	confFileRemovePwdQuotes(filePath, false); /* false -> in place */

	return true;
}

/*!
 * @brief Store configuration into INI file.
 */
static
bool saveINIFile(void)
{
	const QString filePath = GlobInstcs::iniPrefsPtr->saveConfPath();
	const QString tmpFilePath = auxConfFilePath(filePath);

	return QFileInfo(filePath).isWritable()
	   && saveINISettings(tmpFilePath)
	   && QFile::remove(filePath)
	   && QFile::rename(tmpFilePath, filePath);
}

void MainWindow::savePrefsSettings(void) const
{
	/* Store last-used account. */
	saveAccountIndex(*GlobInstcs::prefsPtr);

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

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

	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.
	 * Proxy settings are saved after being modified.
	 */
	//GlobInstcs::proxSetPtr->saveToPrefs(*GlobInstcs::prefsPtr);
}

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

	saveINIFile();

	savePrefsSettings();
}

#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->actionAuthenticateMessage, SIGNAL(triggered()),
	    this, SLOT(viewAuthenticateMessageDlg()));
	    /* 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->actionEditTagAssignment, SIGNAL(triggered()),
	    this, SLOT(viewTagAssignmentInterface()));
	    /* Separator. */
	connect(ui->actionTrackTimestampExpiration, SIGNAL(triggered(bool)),
	    this, SLOT(toggleTimestampTracking(bool)));
	    /* 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(viewAuthenticateMessageFileDlg()));
	connect(ui->actionViewMessageFile, SIGNAL(triggered()),
	    this, SLOT(viewMessageFromZfoDlg()));
	connect(ui->actionExportCorrespondenceOverview, SIGNAL(triggered()),
	    this, SLOT(viewExportCorrespondenceOverviewDlg()));
	connect(ui->actionCheckMessageTimestampExpiration, SIGNAL(triggered()),
	    this, SLOT(viewTimestampExpirationDlg()));
	connect(ui->actionTimeStampRenewal, SIGNAL(triggered()),
	     this, SLOT(viewTimestampRenewalDlg()));
	    /* Separator. */
	connect(ui->actionMessageSearch, SIGNAL(triggered()),
	    this, SLOT(viewMessageSearchDlg()));
	    /* Separator. */
	connect(ui->actionEditAvailableTags, SIGNAL(triggered()),
	    this, SLOT(viewAvailableTagsDlg()));
	    /* Separator. */
	connect(ui->actionConvertImportFromMobileApp, SIGNAL(triggered()),
	    this, SLOT(viewConvertImportFromMobileAppDlg()));
	connect(ui->actionConvertExportForMobileApp, SIGNAL(triggered()),
	    this, SLOT(viewConvertDataForMobileAppDlg()));
	    /* Separator. */
	connect(ui->actionRepairMessageDatabaseFiles, SIGNAL(triggered()),
	    this, SLOT(repairDatabaseFiles()));
	    /* 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(showFilter()));
	connect(ui->actionEditDraft, SIGNAL(triggered()),
	    this, SLOT(openSelectedDraft()));
	connect(ui->actionDeleteDraft, SIGNAL(triggered()),
	    this, SLOT(deleteSelectedDrafts()));
}

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::settingsConnectActions(void)
{
	connect(GlobInstcs::pinSetPtr, SIGNAL(contentChanged()),
	    this, SLOT(watchSettingsChange()));

	connect(GlobInstcs::tagContSetPtr, SIGNAL(contentChanged()),
	    this, SLOT(watchSettingsChange()));

	connect(GlobInstcs::acntMapPtr, SIGNAL(accountAdded(AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::acntMapPtr, SIGNAL(accountRemoved(AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::acntMapPtr, SIGNAL(accountsMoved(int, int, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::acntMapPtr, SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::acntMapPtr, SIGNAL(accountDataChanged(AcntId)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::acntMapPtr, SIGNAL(contentChanged()), /* Cannot use contentReset() here. */
	    this, SLOT(watchSettingsChange()));

	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(accountAdded(AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(accountRemoved(AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(accountsMoved(int, int, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(accountUsernameChanged(AcntId, AcntId, int)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(accountDataChanged(AcntId)),
	    this, SLOT(watchSettingsChange()));
	connect(GlobInstcs::shadowAcntsPtr, SIGNAL(contentChanged()), /* Cannot use contentReset() here. */
	    this, SLOT(watchSettingsChange()));

	connect(GlobInstcs::recMgmtSetPtr, SIGNAL(contentChanged()),
	    this, SLOT(watchSettingsChange()));
}

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::timestampDbPtr, SIGNAL(identificationsInserted(Json::MsgId2List, AcntId)),
	    this, SLOT(watchIdentificationsInserted(Json::MsgId2List, AcntId)));
	connect(GlobInstcs::timestampDbPtr, SIGNAL(identificationsDeleted(Json::MsgId2List)),
	    this, SLOT(watchIdentificationsDeleted(Json::MsgId2List)));

	connect(GlobInstcs::draftDbPtr, SIGNAL(draftsInserted(DraftEntryList)),
	    this, SLOT(watchDraftsInserted(DraftEntryList)));
	connect(GlobInstcs::draftDbPtr, SIGNAL(draftsUpdated(DraftEntryList)),
	    this, SLOT(watchDraftsUpdated(DraftEntryList)));
	connect(GlobInstcs::draftDbPtr, SIGNAL(draftsDeleted(Json::Int64StringList)),
	    this, SLOT(watchDraftsDeleted(Json::Int64StringList)));
	connect(GlobInstcs::draftDbPtr, SIGNAL(draftCountChanged(DraftCounts)),
	    this, SLOT(watchDraftCountChanged(DraftCounts)));

	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();
		/*
		 * No saveSettings().
		 * Settings are saved using the accountAdded() signal.
		 */
	}
}

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

	/*
	 * No saveSettings().
	 * Settings are saved using the contentChanged() signal.
	 */

	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);
		/*
		 * No saveSettings().
		 * Settings are saved using the accountAdded() signal.
		 */
	}

	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(bool automaticNotification)
{
	/*
	 * 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),
	    automaticNotification, 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 proxy settings. */
		GlobInstcs::proxSetPtr->saveToPrefs(*GlobInstcs::prefsPtr);
	}
}

/*!
 * @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)) {
		/*
		 * No saveSettings().
		 * Settings are saved using the contentChanged() signal.
		 */
	}

	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)));
		GlobInstcs::msgDbsPtr->disconnect(SIGNAL(openingFailed(AcntId, QString)),
		    this, SLOT(watchOpeningFailed(AcntId, QString)));

		/* 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()));
	/*
	 * No saveSettings().
	 * Settings are saved using the accountUsernameChanged()
	 * and the accountDataChanged() signal.
	 */

	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)) {
		/*
		 * No saveSettings().
		 * Settings are saved using the accountDataChanged() signal.
		 */
		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 Construct exhaustive message selection list for supplied account and
 *     message identifiers.
 *
 * @param[in] acntId Account identifier.
 * @param[in] msgIds Message identifiers.
 * @param[in] parent Parent widget.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginListFromIds(const AcntId &acntId,
    const QList<MsgId> &msgIds, QWidget *parent)
{
	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>();
	}
	if (Q_UNLIKELY(msgIds.isEmpty())) {
		/* No message selected. */
		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>();
		}
		enum MessageDirection msgDirect = MSG_ALL;
		switch (messageDb->getMessageType(msgId.dmId())) {
		case MessageDb::TYPE_RECEIVED:
			msgDirect = MSG_RECEIVED;
			break;
		case MessageDb::TYPE_SENT:
			msgDirect = MSG_SENT;
			break;
		default:
			continue;
			break;
		}
		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 Construct exhaustive message selection list for supplied account and
 *     message identifiers.
 *
 * @param[in] acntMsgIds List of message identifiers for respective accounts.
 * @param[in] parent Parent widget.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginListFromAcntMsgIds(
    const AcntIdToMsgIdListHash &acntMsgIds, QWidget *parent)
{
	if (Q_UNLIKELY(acntMsgIds.isEmpty())) {
		/* No message selected. */
		return QList<MsgOrigin>();
	}

	QList<MsgOrigin> originList;

	for (AcntIdToMsgIdListHash::const_iterator cit = acntMsgIds.constBegin();
	     cit != acntMsgIds.constEnd(); ++cit) {
		originList.append(
		    messageOriginListFromIds(cit.key(), cit.value(), parent));
	}

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

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

/*!
 * @brief Return first draft identifier from the set.
 *
 * @note The set mustn't be empty.
 */
#define firstDraftId(draftIds) \
	(*(draftIds).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;
	}

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

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 (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 QByteArray data = messageDb->getCompleteMessageRaw(msgId.dmId());
	if (Q_UNLIKELY(data.isEmpty())) {
		DlgMsgBoxDetail::message(this, QMessageBox::Warning,
		    tr("Missing Message Content"),
		    tr("Complete message '%1' is missing.").arg(msgId.dmId()),
		    tr("First you must download the complete message."),
		    QString());
		return;
	}

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

	DlgMessageCheck::authenticateMessage(acntId, data,
	    PrefsSpecific::enableVodzSending(*GlobInstcs::prefsPtr) ?
	        TaskAuthenticateMessage::MSG_VODZ_SIZE_DEPENDENT :
	        TaskAuthenticateMessage::MSG_BASIC,
	    this);
}

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(QDir::toNativeSeparators(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(QDir::toNativeSeparators(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(QDir::toNativeSeparators(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(QDir::toNativeSeparators(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);

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

void MainWindow::viewAuthenticateMessageFileDlg(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 (Q_UNLIKELY(fileName.isEmpty())) {
		return;
	}

	DlgMessageCheck::authenticateMessage(acntId, fileName,
	    PrefsSpecific::enableVodzSending(*GlobInstcs::prefsPtr) ?
	        TaskAuthenticateMessage::MSG_VODZ_SIZE_DEPENDENT :
	        TaskAuthenticateMessage::MSG_BASIC,
	    this);
}

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::viewTimestampRenewalDlg(void)
{
	debugSlotCall();

	if (raiseWidget(m_timestampRenewalDlg)) {
		return;
	}

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

	m_timestampRenewalDlg = new (::std::nothrow) DlgTimestampRenewal(
	    acntIdDbList,
	    GlobInstcs::timestampDbPtr, GlobInstcs::msgDbsPtr,
	    GlobInstcs::isdsSessionsPtr, this, this, Qt::Window);
	connect(m_timestampRenewalDlg, SIGNAL(focusSelectedMsg(AcntId, qint64, QString, int)),
	    this, SLOT(messageItemFromSearchSelection(AcntId, qint64, QString, int)));
	connect(m_timestampRenewalDlg, SIGNAL(finished(int)),
	    this, SLOT(closeTimestampRenewalDlg(int)));
	m_timestampRenewalDlg->show();
}

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

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

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::viewAvailableTagsDlg(void)
{
	debugSlotCall();

	modifyTags(AcntId(), QList<qint64>());
}

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

	DlgConvertImportFromMobileApp::view(this, this);
}

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

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

	DlgConvertExportForMobileApp::transfer(allAcntIdDbList(m_accountModel, this),
	    this);

	GlobInstcs::workPoolPtr->start();

	updateActionActivation();
}

void MainWindow::repairDatabaseFiles(void)
{
	repairDatabaseFile(QUrl());
}

void MainWindow::repairDatabaseFile(const QUrl &link)
{
	QString localFile;
	if (link.isLocalFile()) {
		localFile = link.toLocalFile();
		const QFileInfo fileInfo(link.toLocalFile());
		if (Q_UNLIKELY(fileInfo.isDir())) {
			return;
		}
	}

	if (QMessageBox::Yes != QMessageBox::question(this,
	        tr("Quit the Application?"),
		localFile.isEmpty()
		    ? tr("Do you want to quit the application and launch the database file repair tool?")
		    : tr("Do you want to quit the application and launch the database file repair tool on the file '%1'?")
		        .arg(localFile),
		QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
		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();

	MWDbRepair::runDatabaseRepair = true;
	{
		const AcntId acntId = m_selectionStatus.mwStatus().acntId();
		if (acntId.isValid()) {
			QString dbDir = GlobInstcs::acntMapPtr->acntData(acntId).dbDir();
			if (dbDir.isEmpty()) {
				/* Set default directory name. */
				dbDir = GlobInstcs::iniPrefsPtr->confDir();
			}

			MWDbRepair::passedMessageDbDir = dbDir;
		}
	}
	MWDbRepair::passedMessageDbFile = localFile;

	close(); /* Close this window. */
}

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

/*!
 * @brief Construct a list of actions not-listed in main window top menu.
 *
 * @return List of actions.
 */
static
QList<QAction *> additionalActions(const Ui::MainWindow *ui)
{
	if (Q_NULLPTR != ui) {
		return QList<QAction *>({ui->actionEditTagAssignment});
	}
	return QList<QAction *>();
}

/*!
 * @brief Construct a list of actions to be excluded from listing.
 *
 * @return List of actions.
 */
static
QSet<QAction *> excludedActions(const Ui::MainWindow *ui)
{
	if (Q_NULLPTR != ui) {
		return QSet<QAction *>({
		    ui->actionCheckMessageTimestampExpiration,
		    ui->actionMarkAllReceivedRead,
		    ui->actionMarkAllReceivedUnread,
		    ui->actionMarkAllReceivedUnsettled,
		    ui->actionMarkAllReceivedInProgress,
		    ui->actionMarkAllReceivedSettled,
		    ui->actionToolBar,
		    ui->actionTopPanelBars,
		    ui->actionTrackTimestampExpiration});
	}
	return QSet<QAction *>();
}

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

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

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

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

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

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

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

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

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

	DlgToolBar::configureToolBar(ui->menubar, additionalActions(ui),
	    excludedActions(ui), 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::deleteSelectedDrafts(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const QSet<qint64> &draftIds = mwStatus.draftIds();
	if (Q_UNLIKELY(draftIds.isEmpty())) {
		Q_ASSERT(0);
		return;
	}

	if (QMessageBox::Yes != QMessageBox::question(this,
	        tr("Delete Selected Drafts?"),
		tr("Do you want to delete the selected drafts?"),
		QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
		return;
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
	const QList<qint64> idList(draftIds.begin(), draftIds.end());
#else /* < Qt-5.14.0 */
	const QList<qint64> idList = draftIds.toList();
#endif /* >= Qt-5.14.0 */
	GlobInstcs::draftDbPtr->deleteDrafts(idList);
}

void MainWindow::showFilter(void)
{
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	switch (mwStatus.acntNodeType()) {
	case AccountModel::nodeRecentReceived:
	case AccountModel::nodeRecentSent:
	case AccountModel::nodeReceivedYear:
	case AccountModel::nodeSentYear:
		showMessageFilter();
		break;
	case AccountModel::nodeDrafts:
		showDraftFilter();
		break;
	default:
		/* DO nothing. */
		break;
	}
}

#define ANIMATION_DURATION 250

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(ANIMATION_DURATION);
			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(ANIMATION_DURATION);
			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::showDraftFilter(void)
{
	if (!ui->draftsFilterHorizontalLayout->isVisible()) {
		ui->draftsFilterHorizontalLayout->setMaximumHeight(0);
		ui->draftsFilterHorizontalLayout->setVisible(true);

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

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

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

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

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

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

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

#undef ANIMATION_DURATION

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. */
	const int numSelDrafts = mwStatus.draftIds().size();

	/* 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->actionAuthenticateMessage->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->actionEditTagAssignment->setEnabled(numSelMsg > 0);
	ui->actionTrackTimestampExpiration->setEnabled(numSelMsg > 0);
	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->actionTimeStampRenewal */
	ui->actionMessageSearch->setEnabled(acntCnt > 0);
	/* ui->actionEditAvailableTags */
	/* 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);
	ui->actionEditDraft->setEnabled(numSelDrafts == 1);
	ui->actionDeleteDraft->setEnabled(numSelDrafts > 0);

	/* 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 Returns set of draft identifiers.
 *
 * @param[in] draftIdxs List of indexes.
 * @return Set of draft identifiers.
 */
static inline
QSet<qint64> idxs2DraftIdSet(const QModelIndexList &draftIdxs)
{
	QSet<qint64> draftIds;
	for (const QModelIndex &idx : draftIdxs) {
		if (Q_UNLIKELY(!idx.isValid())) {
			Q_ASSERT(0);
			return QSet<qint64>();
		}

		qint64 draftId = -1;
		bool iOk = false;
		if (idx.column() == DraftTableModel::COL_DRAFT_ID) {
			draftId = idx.data().toLongLong(&iOk);
		} else {
			draftId = idx.sibling(idx.row(), DraftTableModel::COL_DRAFT_ID)
			    .data().toLongLong(&iOk);
		}

		if (Q_UNLIKELY((draftId < 0) || (!iOk))) {
			Q_ASSERT(0);
			return QSet<qint64>();
		}
		draftIds.insert(draftId);
	}
	return draftIds;
}

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

	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	/*
	 * Obtain indexes of draft ids column an convert them to draft
	 * identifiers.
	 */
	m_selectionStatus.setDraftIds(idxs2DraftIdSet(
	    ui->draftList->selectionModel()->selectedRows(
	        DraftTableModel::COL_DRAFT_ID)));
}

/*!
 * @brief Fills the message model with timstamp tracking information.
 *
 * @param[in] testEnv testing environment.
 * @param[in,out] messageTableModel Message model.
 */
static inline
void fillTimestampColumn(bool testEnv, DbMsgsTblModel &messageTableModel)
{
	TstValidityHash tstValues;
	{
		const Json::MsgId1List msgIds =
		    messageTableModel.msgIdList(testEnv);
		GlobInstcs::timestampDbPtr->getMsgTimestampData(msgIds,
		    tstValues);
	}
	messageTableModel.setTimestampTrackings(testEnv,
	    DbMsgsTblModel::TST_TRAC_NEG_COL, tstValues);
}

/*!
 * @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::MsgId1List 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);
		ui->accountTextInfo->setOpenLinks(false);
		return;
	}

	MessageDbSet *dbSet = accountDbSet(acntId, this); /* Non-const because of AcntIdDb. */
	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);
		ui->accountTextInfo->setOpenLinks(false);
		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(AcntIdDb(acntId, dbSet),
		    dbSet->msgsRcvdEntriesWithin90Days(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		break;
	case AccountModel::nodeRecentSent:
		m_messageTableModel.assignData(AcntIdDb(acntId, dbSet),
		    dbSet->msgsSntEntriesWithin90Days(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		break;
	case AccountModel::nodeDrafts:
		m_draftModel.removeAllRows();
		{
			QList<DraftEntry> draftEntries;
			if (GlobInstcs::draftDbPtr->getDraftListing(
			    {acntId}, draftEntries)) {
				m_draftModel.appendData(
				    macroStdMove(draftEntries));
			}
		}
		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(AcntIdDb(acntId, dbSet),
		    dbSet->msgsRcvdEntries(),
		    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(AcntIdDb(acntId, dbSet),
		    dbSet->msgsSntEntries(),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
#endif /* DISABLE_ALL_TABLE */
		break;
	case AccountModel::nodeReceivedYear:
		m_messageTableModel.assignData(AcntIdDb(acntId, dbSet),
		    dbSet->msgsRcvdEntriesInYear(year),
		    m_msgTblAppendedCols.size());
		m_messageTableModel.setHeader(m_msgTblAppendedCols);
		/* TODO -- Parameter check. */
		break;
	case AccountModel::nodeSentYear:
		m_messageTableModel.assignData(AcntIdDb(acntId, dbSet),
		    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);
		fillTimestampColumn(acntId.testing(), m_messageTableModel);
		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);
		ui->accountTextInfo->setOpenLinks(false);
		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;
	case AccountModel::nodeDrafts:
		ui->messageStackedWidget->setCurrentIndex(SW_DRAFTS);
		/* Draft list doesn't change models. */
		/* Column width doesn't need to adapt to model changes. */
		msgViewType = AccountModel::nodeDrafts;
		break;
	default:
		logErrorNL("%s", "Cannot determine account node type.");
//		Q_ASSERT(0);
		goto end;
		break;
	}

	/* Set message model. */
	if ((msgViewType != AccountModel::nodeUnknown)
	    && (msgViewType != AccountModel::nodeDrafts)) {
		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;
	case AccountModel::nodeDrafts:
		/* Column width doesn't need to adapt to model changes. */
		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));

	/* Load information whether timestamps are tracked.  */
	if (msgIds.size() == 0) {
		ui->actionTrackTimestampExpiration->setChecked(false);
	} else {
		const AcntId &acntId = mwStatus.acntId();
		Json::MsgId1List msgId1List;
		for (const MsgId &msgId : msgIds) {
			msgId1List.append(
			    Json::MsgId1(
			        acntId.testing() ? Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE,
			        msgId.dmId()));
		}
		TstValidityHash tstValues;
		GlobInstcs::timestampDbPtr->getMsgTimestampData(msgId1List,
		    tstValues);
		/* Leave unchecked if some values untracked. */
		ui->actionTrackTimestampExpiration->setChecked(msgId1List.size() == tstValues.size());
	}

	/* 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::viewTagAssignmentInterface(void)
{
	bool usePopup = true;
	{
		bool val = false;
		if (GlobInstcs::prefsPtr->boolVal(
		        "action.edit_tag_assignment.tie.simplified_tag_assignment.enabled", val)) {
			usePopup = val;
		}
	}
	if (usePopup) {
		viewTagAssignmentPopup();
	} else {
		viewTagAssignmentDialogue();
	}
}

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

	const MWStatus mwStatus = m_selectionStatus.mwStatus();

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

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

void MainWindow::viewTagAssignmentPopup(void)
{
	TagsLabelControlWidget *labelWidget =
	    qobject_cast<TagsLabelControlWidget *>(QObject::sender());

	QWidget *tagsPopup = Q_NULLPTR;
	TagsPopupControlWidget *tagAssignmentWidget = Q_NULLPTR;
	DlgTagAssignment *tagAssignmentDlg = Q_NULLPTR;
	bool asDialogue = false;
	if (Q_NULLPTR == labelWidget) {
		/* Enable window decorations only when called outside of tag label. */
		bool val = false;
		if (GlobInstcs::prefsPtr->boolVal(
		        "action.edit_tag_assignment.simplified_tag_assignment.window_decorations.enabled", val)) {
			asDialogue = val;
		}
	}

	if (!asDialogue) {
		tagAssignmentWidget = new (::std::nothrow) TagsPopupControlWidget(
		    GlobInstcs::tagContPtr, ui->actionEditAvailableTags, this);
		tagsPopup = tagAssignmentWidget;
	} else {
		tagAssignmentDlg = new (::std::nothrow) DlgTagAssignment(
		    GlobInstcs::tagContPtr, ui->actionEditAvailableTags, this);
		tagsPopup = tagAssignmentDlg;
	}
	if (Q_NULLPTR != tagsPopup) {
		tagsPopup->setAttribute(Qt::WA_DeleteOnClose, true);

		{
			const MWStatus mwStatus = m_selectionStatus.mwStatus();

			if (Q_NULLPTR != tagAssignmentWidget) {
				tagAssignmentWidget->setMsgIds(mwStatus.acntId(),
				    mwStatus.msgIds());
			} else if (Q_NULLPTR != tagAssignmentDlg) {
				tagAssignmentDlg->setMsgIds(mwStatus.acntId(),
				    mwStatus.msgIds());
			}
		}

		if (Q_NULLPTR != labelWidget) {
			/*
			 * Set position above status bar label if invoked from there.
			 * Cannot be a dialogue.
			 */
			const QPoint parentTopLeft = labelWidget->mapToGlobal(QPoint(0, 0));
			const QSize sizeHint = tagsPopup->sizeHint();
			const int width = sizeHint.width();
			const int height = sizeHint.height();

			tagsPopup->setGeometry(
			    parentTopLeft.x() - width + labelWidget->width(),
			    parentTopLeft.y() - height, width, height);
		} else {
			if (Q_NULLPTR != tagAssignmentWidget) {
				/*
				 * Set position in the centre of the main window.
				 * May be a widget or a dialogue.
				 */
				const QPoint parentTopLeft = this->mapToGlobal(QPoint(0, 0));
				const QSize sizeHint = tagsPopup->sizeHint();
				const int width = sizeHint.width();
				const int height = sizeHint.height();

				tagsPopup->setGeometry(
				    parentTopLeft.x() + (this->width() / 2) - (width / 2),
				    parentTopLeft.y() + (this->height() / 2) - (height / 2), width, height);
			}
		}

		if (Q_NULLPTR != tagAssignmentWidget) {
			tagsPopup->show();
			tagsPopup->raise();
		} else if (Q_NULLPTR != tagAssignmentDlg) {
			tagAssignmentDlg->exec();
		}
	}
}

/*!
 * @brief Load timestamps from message database and store expiration dates
 *     into timestamp tracking database.
 *
 * @param[in] acntId Account identifier.
 * @param[in] msgId2List List of message identifiers.
 * @param[in] parent Parent widget.
 */
static
void updateTimestampData(const AcntId &acntId,
    const Json::MsgId2List &msgId2List, QWidget *parent)
{
	MessageDbSet *dbSet = accountDbSet(acntId, parent);
	if (Q_UNLIKELY(Q_NULLPTR == dbSet)) {
		Q_ASSERT(0);
		return;
	}

	TstValidityHash expirationValues;

	/* Read timestamp expiration times from message database. */
	for (const Json::MsgId2 &msgId : msgId2List) {
		MessageDb *messageDb = dbSet->constAccessMessageDb(msgId.deliveryTime());
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			logErrorNL("Couldn't access message database for username '%s' and message '%" PRId64 "'.",
			    acntId.username().toUtf8().constData(),
			    UGLY_QINT64_CAST msgId.dmId());
			continue;
		}

		QByteArray msgData = messageDb->getCompleteMessageRaw(msgId.dmId());
		if (Q_UNLIKELY(msgData.isEmpty())) {
			logErrorNL("Couldn't access data for message '%" PRId64 "'.",
			    UGLY_QINT64_CAST msgId.dmId());
			continue;
		}

		QDateTime expir;
		{
			QDateTime incep;
			if (Q_UNLIKELY(!TstEntries::latestTstSigningCertTimes(
			        msgData, incep, expir))) {
				logErrorNL("Couldn't read timestamp expiration data for message '%" PRId64 "'.",
				    UGLY_QINT64_CAST msgId.dmId());
				continue;
			}
		}

		expirationValues[Json::MsgId1(msgId.testEnv(), msgId.dmId())] = TstValidity(true, expir);
	}

	/* Store timestamp expiration times into timestamp database. */
	if (!expirationValues.isEmpty()) {
		GlobInstcs::timestampDbPtr->updateMsgTimestampData(expirationValues);
	}
}

void MainWindow::toggleTimestampTracking(bool enable)
{
	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	const AcntId &acntId = mwStatus.acntId();
	const QSet<MsgId> &msgIds = mwStatus.msgIds();

	Json::MsgId2List msgId2List;

	if (enable) {
		/* Offer download for missing messages. */
		QList<MsgOrigin> originList = messageOriginList(mwStatus, this);
		originList = DlgDownloadMessages::offerDownloadForMissing(
		    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, this);

		/* Leave action unchecked if no all selected messages were downloaded. */
		if (msgIds.size() != originList.size()) {
			ui->actionTrackTimestampExpiration->setChecked(false);
		}

		/* Insert only complete messages. */
		for (const MsgOrigin &msgOrigin : originList) {
			msgId2List.append(
			    Json::MsgId2(
			        acntId.testing() ? Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE,
			        msgOrigin.msgId.dmId(), msgOrigin.msgId.deliveryTime()));
		}

		GlobInstcs::timestampDbPtr->insertIdentifications(msgId2List, acntId);

		/*
		 * Set timestamps here.
		 *
		 * The expiration times cannot be set in a slot that is called
		 * when identifications are inserted. This because it may cause
		 * the signals to be handled in wrong order.
		 */
		updateTimestampData(acntId, msgId2List, this);
	} else {
		/* Remove all selected messages. */
		for (const MsgId &msgId : msgIds) {
			msgId2List.append(
			    Json::MsgId2(
			        acntId.testing() ? Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE,
			        msgId.dmId(), msgId.deliveryTime()));
		}

		GlobInstcs::timestampDbPtr->deleteIdentifications(msgId2List);
	}
}

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_BALL_GREY),
				    tr("As Read"), this,
				    SLOT(accountMarkRecentReceivedRead()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_BALL_GREEN),
				    tr("As Unread"), this,
				    SLOT(accountMarkRecentReceivedUnread()));

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

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

				submenu->addSeparator();
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_BALL_RED),
				    tr("As Unsettled"), this,
				    SLOT(accountMarkReceivedYearUnsettled()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_BALL_YELLOW),
				    tr("As in Progress"), this,
				    SLOT(accountMarkReceivedYearInProgress()));
				submenu->addAction(IconContainer::construcIcon(
				        IconContainer::ICON_BALL_GREY),
				    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::viewAccountTextInfoContextMenu(const QPoint &point)
{
	QMenu *menu = ui->accountTextInfo->createStandardContextMenu();
	if (Q_UNLIKELY(menu == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	const QString anchorReference = ui->accountTextInfo->anchorAt(point);
	if (!anchorReference.isEmpty()) {
		QAction *firstDefaultAction = Q_NULLPTR;
		{
			QList<QAction *> actions = menu->actions();
			if (!actions.isEmpty()) {
				firstDefaultAction = actions.first();
			}
		}

		/*
		 * Method addAction() has been oveloaded since Qt-5.6
		 * https://stackoverflow.com/questions/40684354/connect-qmenuaddaction-directly-to-lambda-function-signature-mismatch
		 */

		QAction *act = Q_NULLPTR;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
		act = menu->addAction(tr("Open File Location"),
		    [=]() { this->viewAccountTextInfoAnchorClicked(QUrl(anchorReference)); });
#else /* < Qt-5.6.0 */
		act = menu->addAction(tr("Open File Location"));
		if (Q_NULLPTR != act) {
			connect(act, &QAction::triggered,
			    [=]() { this->viewAccountTextInfoAnchorClicked(QUrl(anchorReference)); });
		}
#endif /* >= Qt-5.6.0 */
		/* Reinsert action before first default. */
		menu->insertAction(firstDefaultAction, act);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
		act = menu->addAction(tr("Repair Message Database File"),
		    [=]() { this->repairDatabaseFile(QUrl(anchorReference)); });
#else /* < Qt-5.6.0 */
		act = menu->addAction(tr("Repair Message Database File"));
		if (Q_NULLPTR != act) {
			connect(act, &QAction::triggered,
			    [=]() { this->repairDatabaseFile(QUrl(anchorReference)); });
		}
#endif /* >= Qt-5.6.0 */
		menu->insertAction(firstDefaultAction, act);

		menu->insertSeparator(firstDefaultAction);
	}

	menu->exec(ui->accountTextInfo->mapToGlobal(point));
	menu->deleteLater();
}

void MainWindow::viewAccountTextInfoAnchorClicked(const QUrl &link)
{
	if (link.isLocalFile()) {
		QString localFile = link.toLocalFile();
		const QFileInfo fileInfo(link.toLocalFile());
		if (fileInfo.isDir()) {
			/* Open the directory link. */
			QDesktopServices::openUrl(link);
		} else {
			/* Open parent directory of the file. */
			QDesktopServices::openUrl(QUrl::fromLocalFile(
			    fileInfo.absoluteDir().absolutePath()));
		}
	} else {
		QDesktopServices::openUrl(link);
	}
}

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->actionAuthenticateMessage);
		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_BALL_GREY),
		    tr("As Read"), this,
		    SLOT(messageItemsSelectedMarkRead()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_BALL_GREEN),
		    tr("As Unread"), this,
		    SLOT(messageItemsSelectedMarkUnread()));

		submenu->addSeparator();
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_BALL_RED),
		    tr("As Unsettled"), this,
		    SLOT(messageItemsSelectedMarkUnsettled()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_BALL_YELLOW),
		    tr("As in Progress"), this,
		    SLOT(messageItemsSelectedMarkInProgress()));
		submenu->addAction(
		    IconContainer::construcIcon(IconContainer::ICON_BALL_GREY),
		    tr("As Settled"), this,
		    SLOT(messageItemsSelectedMarkSettled()));
	}
	menu->addAction(ui->actionEditTagAssignment);

	menu->addSeparator();

	menu->addAction(ui->actionTrackTimestampExpiration);

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

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

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

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

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

	menu->addAction(ui->actionEditDraft);
	menu->addSeparator();
	menu->addAction(ui->actionDeleteDraft);

	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::openSelectedDraft(void)
{
	debugSlotCall();

	const MWStatus mwStatus = m_selectionStatus.mwStatus();
	if (Q_UNLIKELY(mwStatus.draftIds().size() != 1)) {
		/*
		 * Can happen when navigating through control elements and
		 * enter is pressed.
		 */
		return;
	}

	const qint64 draftId = firstDraftId(mwStatus.draftIds());
	DraftEntry draftEntry;
	if (Q_UNLIKELY(!m_draftModel.entryForId(draftId, draftEntry))) {
		logErrorNL(
		    "Draft model doesn't hold data for draft '%" PRId64 "'.",
		    UGLY_QINT64_CAST draftId);
		return;
	}

	showSendMessageDialog(DlgSendMessage::ACT_NEW_FROM_DRAFT, QString(),
	    draftEntry);
}

void MainWindow::watchSettingsChange(void)
{
	if (meetsDebugLv1()) {
		const QObject *senderObj = QObject::sender();
		const int signalIndex = QObject::senderSignalIndex();
		const QMetaObject *metaObj = Q_NULLPTR;
		if (Q_NULLPTR != senderObj) {
			metaObj = senderObj->metaObject();
		}
		if (Q_NULLPTR != metaObj) {
			logDebugLv1NL("<SLOT> %s() called by signal %s()",
			    __func__,
			    metaObj->method(signalIndex).name().constData());
		}
	}
	if (saveINIFile()) {
		logDebugLv0NL("%s", "Saved INI settings.");
	} else {
		QMessageBox::warning(this, tr("Cannot Write Configuration File"),
		    tr("An error occurred while attempting to save the configuration.")
		    + "\n\n"
		    + tr("Cannot write file '%1'.")
		        .arg(QDir::toNativeSeparators(GlobInstcs::iniPrefsPtr->saveConfPath())),
		    QMessageBox::Ok);
	}
}

void MainWindow::offerDownloadForMessages(
    const AcntIdToMsgIdListHash &acntMsgIds)
{
	/*
	 * Generate origins from account and message identifiers.
	 * Don't use window state here.
	 */
	QList<MsgOrigin> originList = messageOriginListFromAcntMsgIds(
	    acntMsgIds, this);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), this, 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();

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

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

	/* Complete message has been inserted. */
	{
		bool val = false;
		if (MSG_RECEIVED == direction) {
			val = GlobInstcs::prefsPtr->boolVal(
			    "action.download_message.received.tie.action.track_timestamp_expiration.enabled",
			    val) && val;
		} else if (MSG_SENT == direction) {
			val = GlobInstcs::prefsPtr->boolVal(
			    "action.download_message.sent.tie.action.track_timestamp_expiration.enabled",
			    val) && val;
		}
		/* Add to timestamp tracking if automatic adding enabled. */
		if (val) {
			Json::MsgId2List msgId2List;

			msgId2List.append(
			    Json::MsgId2(
			        acntId.testing() ? Isds::Type::BOOL_TRUE : Isds::Type::BOOL_FALSE,
			        msgId.dmId(), msgId.deliveryTime()));

			GlobInstcs::timestampDbPtr->insertIdentifications(msgId2List, acntId);

			updateTimestampData(acntId, msgId2List, this);
		}
	}

	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)
{
	/* Do nothing. Reset is sent on profile selection. */
}

void MainWindow::watchTagContDisconnected(void)
{
	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)
{
	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:
		fillTimestampColumn(acntId.testing(), m_messageTableModel);
		fillTagsColumn(acntId.testing(), m_messageTableModel);
		break;
	default:
		/* Do nothing. */
		break;
	}
}

void MainWindow::watchIdentificationsInserted(const Json::MsgId2List &msgIds,
    const AcntId &acntId)
{
	Q_UNUSED(acntId);

	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::MsgId2 &msgId : msgIds) {
		/* Do nothing if account has been changed. */
		if (Q_UNLIKELY(msgId.testEnv() != currentAcntId.testing())) {
			continue;
		}
		m_messageTableModel.updateTimestampTracking(msgId.dmId(), TstValidity(true, QDateTime()));
	}
}

void MainWindow::watchIdentificationsDeleted(const Json::MsgId2List &msgIds)
{
	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::MsgId2 &msgId : msgIds) {
		/* Do nothing if account has been changed. */
		if (Q_UNLIKELY(msgId.testEnv() != currentAcntId.testing())) {
			continue;
		}
		m_messageTableModel.updateTimestampTracking(msgId.dmId(), TstValidity());
	}
}

void MainWindow::watchDraftsInserted(const DraftEntryList &entries)
{
	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;
	}

	DraftEntryList matchingEntries;
	for (const DraftEntry &entry : entries) {
		if (currentAcntId == entry.acntId) {
			matchingEntries.append(entry);
		}
	}

	if (!matchingEntries.isEmpty()) {
		m_draftModel.appendData(matchingEntries);
	}
}

void MainWindow::watchDraftsUpdated(const DraftEntryList &entries)
{
	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;
	}

	DraftEntryList updatedEntries;
	Json::Int64StringList removedIds;
	for (const DraftEntry &entry : entries) {
		if (currentAcntId == entry.acntId) {
			updatedEntries.append(entry);
		} else {
			removedIds.append(entry.id);
		}
	}

	if (!updatedEntries.isEmpty()) {
		m_draftModel.updateOrAppendData(updatedEntries);
	}

	if (!removedIds.isEmpty()) {
		m_draftModel.remove(removedIds);
	}
}

void MainWindow::watchDraftsDeleted(const Json::Int64StringList &ids)
{
	m_draftModel.remove(ids);
}

void MainWindow::watchDraftCountChanged(const DraftCounts &draftCounts)
{
	for (const AcntId &acntId : draftCounts.keys()) {
		m_accountModel.updateDraftCount(acntId,
		    draftCounts[acntId]);
	}
}

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

/*!
 * @brief Set or unset session keeper flag with specific value.
 *
 * @param[in] flag Flag value.
 * @param[in] enabled True to set the flag, false to unset the flag.
 */
static
void sessionKeeperSetFlag(int flag, bool enable)
{
	if (Q_UNLIKELY(Q_NULLPTR == sessionKeeper)) {
		return;
	}

	sessionKeeper->setProcessedTypeFlags(enable
	    ? (sessionKeeper->processedTypeFlags() | flag)
	    : (sessionKeeper->processedTypeFlags() & ~flag));
}

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 == "network.isds.keep_alive.on.fail.quit_session") {
			const bool val = value.toBool();
			if (sessionKeeper != Q_NULLPTR) {
				sessionKeeper->setQuitInactive(val);
			}
		} else if (name == "network.isds.keep_alive.accounts.uname_pwd.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_PWD, val);
		} else if (name == "network.isds.keep_alive.accounts.uname_crt.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_CRT, val);
		} else if (name == "network.isds.keep_alive.accounts.uname_pwd_crt.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_PWD_CRT, val);
		} else if (name == "network.isds.keep_alive.accounts.uname_pwd_hotp.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_PWD_HOTP, val);
		} else if (name == "network.isds.keep_alive.accounts.uname_pwd_totp.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_PWD_TOTP, val);
		} else if (name == "network.isds.keep_alive.accounts.uname_mep.enabled") {
			const bool val = value.toBool();
			sessionKeeperSetFlag(SessionKeeper::LT_UNAME_MEP, val);
		} else  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 == "application.ui.theme") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			changeTheme(val);
		} if (name == "application.ui.theme.custom.font_scale.percent") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			/* Just scale the icons. */
			loadAppUiThemeFromValues(PrefsSpecific::appUiTheme(*GlobInstcs::prefsPtr),
			    val);
		} else 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 == "network.isds.keep_alive.timer.period.seconds") {
			bool ok = false;
			qint64 val = value.toLongLong(&ok);
			if (Q_UNLIKELY(!ok)) {
				logErrorNL("%s", "Cannot convert value.");
				return;
			}
			if (sessionKeeper != Q_NULLPTR) {
				sessionKeeper->setCoolDown(val * 1000);
			}
		} else 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();
}

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_attachmentModel(false, this),
    m_draftModel(this),
    m_draftListProxyModel(this),
    m_dfltFilerLineStyleSheet(SortFilterProxyModel::invalidFilterEditStyle),
    m_messageMarker(this),
    m_lastSelectedMessageId(-1),
    m_lastStoredMessageId(-1),
    m_lastSelectedAccountNodeType(AccountModel::nodeUnknown),
    m_lastStoredAccountNodeType(AccountModel::nodeUnknown),
    m_timestampRenewalDlg(Q_NULLPTR),
    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_dbStatusMode(Q_NULLPTR),
    mui_statusLabelTimestamps(Q_NULLPTR),
    mui_statusLabelTags(Q_NULLPTR),
    mui_statusAccounts(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_CLOCK),
	    tr("Timestamps tracked")));
	m_msgTblAppendedCols.append(DbMsgsTblModel::AppendedCol(QString(),
	    IconContainer::construcIcon(IconContainer::ICON_BRIEFCASE),
	    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);

	/* Account text info. */
	ui->accountTextInfo->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->accountTextInfo, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewAccountTextInfoContextMenu(QPoint)));
	//connect(ui->accountTextInfo, SIGNAL(anchorClicked(QUrl)),
	//    this, SLOT(viewAccountTextInfoAnchorClicked(QUrl)));

	/* 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,
			    showMessageFilterLineViaFilter, this, true);
#ifdef Q_OS_OSX
			filter->registerAction(Qt::CTRL | Qt::Key_Backspace,
			    deleteSelectedMessageViaFilter, this, true);
#endif /* Q_OS_OSX */
			filter->registerAction(Qt::Key_Delete,
			    deleteSelectedMessageViaFilter, this, true);
			filter->registerAction(Qt::CTRL | Qt::Key_C,
			    copySelectedMessageViaFilter, ui->messageList, 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);
	ui->attachmentList->setColumnHidden(AttachmentTblModel::CREATED_FROM_TEXT_COL, true);

	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(ui->attachmentList);
		if (filter != Q_NULLPTR) {
			filter->registerAction(Qt::CTRL | Qt::Key_C,
			    copyAttachmentViaFilter, ui->attachmentList, true);
			ui->attachmentList->installEventFilter(filter);
		}
	}

	/* Draft list. */
	ui->draftList->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(ui->draftList, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewDraftListContextMenu(QPoint)));
	ui->draftList->setSelectionMode(QAbstractItemView::ExtendedSelection);
	ui->draftList->setSelectionBehavior(QAbstractItemView::SelectRows);
	connect(ui->draftList, SIGNAL(doubleClicked(QModelIndex)), this,
	    SLOT(openSelectedDraft()));
	ui->draftList->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(ui->draftList));
	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(ui->draftList);
		if (filter != Q_NULLPTR) {
			filter->registerAction(Qt::Key_Return,
			    openSelectedDraftViaFilter, this, true);
			filter->registerAction(Qt::Key_Enter, /* On keypad. */
			    openSelectedDraftViaFilter, this, true);
			filter->registerAction(Qt::CTRL | Qt::Key_F,
			    showDraftFilterLineViaFilter, this, true);
#ifdef Q_OS_OSX
			filter->registerAction(Qt::CTRL | Qt::Key_Backspace,
			    deleteSelectedDraftViaFilter, this, true);
#endif /* Q_OS_OSX */
			filter->registerAction(Qt::Key_Delete,
			    deleteSelectedDraftViaFilter, this, true);
			ui->draftList->installEventFilter(filter);
		}
	}
	ui->draftList->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(ui->draftList));

	m_draftListProxyModel.setSourceModel(&m_draftModel);
	m_draftListProxyModel.setSortRole(DraftTableModel::ROLE_PROXYSORT);
	ui->draftList->setModel(&m_draftListProxyModel);

	ui->draftList->setColumnHidden(DraftTableModel::COL_DRAFT_ID, true);
	ui->draftList->setColumnHidden(DraftTableModel::COL_ACNT_ID, 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)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(openingFailed(AcntId, QString)),
	    this, SLOT(watchOpeningFailed(AcntId, QString)));
#else
	connect(GlobInstcs::msgDbsPtr, SIGNAL(madeAvailable(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(opened(AcntId)),
	    this, SLOT(refreshAccountList(AcntId)));
	connect(GlobInstcs::msgDbsPtr, SIGNAL(openingFailed(AcntId, QString)),
	    this, SLOT(watchOpeningFailed(AcntId, QString)));

	loadSettings();
#endif

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

	/* Filter lines. */
	/* Clear message filter line when line edit is hidden. */
	ui->messagesFilterHorizontalLayout->installEventFilter(
	    new (::std::nothrow) OnWidgetHiddenFilter(
	        &clearMessageFilterLineViaFilter, 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,
			    hideMessageFilterLineViaFilter, this, true);
			ui->messageFilterLine->installEventFilter(filter);
		}
	}
	/* Clear draft filter line when line edit is hidden. */
	ui->draftsFilterHorizontalLayout->installEventFilter(
	    new (::std::nothrow) OnWidgetHiddenFilter(
	        &clearDraftFilterLineViaFilter, this, ui->draftFilterLine));
	/* Hide draft filter line when escape pressed in line edit. */
	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(ui->draftFilterLine);
		if (Q_NULLPTR != filter) {
			filter->registerAction(Qt::Key_Escape,
			    hideDraftFilterLineViaFilter, this, true);
			ui->draftFilterLine->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() */

	/* Draft list, ui->draftList->model() refers to proxy model. */
	connect(ui->draftList->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this,
	    SLOT(updateDraftSelection(QItemSelection, QItemSelection)));

	/* 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 drag on message list. */
	ui->messageList->setDragEnabled(true);
	ui->messageList->setDragDropMode(QAbstractItemView::DragOnly);
	ui->messageList->setDefaultDropAction(Qt::CopyAction);

	/* Enable sorting of message table items. */
	ui->messageList->setSortingEnabled(true);
	ui->draftList->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();
	settingsConnectActions();
	connect(&m_messageTableModel, SIGNAL(missingCompleteMessages(AcntIdToMsgIdListHash)),
	    this, SLOT(offerDownloadForMessages(AcntIdToMsgIdListHash)));
	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;
	}
	if (sessionKeeper == Q_NULLPTR) {
		sessionKeeper = new (::std::nothrow) SessionKeeper(
		    GlobInstcs::acntMapPtr, GlobInstcs::isdsSessionsPtr, this);
	}
	QTimer::singleShot(RUN_FIRST_ACTION_MS, this,
	    SLOT(setWindowsAfterInit()));

	DbStatusControl *dbStatusControl = qobject_cast<DbStatusControl *>(
	    mui_dbStatusMode);
	if (Q_NULLPTR != dbStatusControl) {
		enum DbStatusControl::DbLocation msgDbLoc =
		    PrefsSpecific::messageDbOnDisk(*GlobInstcs::prefsPtr)
		        ? DbStatusControl::LOC_DISK
		        : DbStatusControl::LOC_MEMORY;

		enum DbStatusControl::DbLocation acntDbLoc =
		    PrefsSpecific::accountDbOnDisk(*GlobInstcs::prefsPtr)
		        ? DbStatusControl::LOC_DISK
		        : DbStatusControl::LOC_MEMORY;

		dbStatusControl->setStorageLocations(msgDbLoc, acntDbLoc);
	}
}

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

	/* Disable survey, but leave code for potential future use. */
	bool hideSurvey = true;

	/* Show changelog if it is first time run new version */
	if (PrefsSpecific::isNewVersion(*GlobInstcs::prefsPtr)) {
		DlgViewChangeLog::view(this);
		/* Don't bother with survey if news shown. */
		hideSurvey = true;
	}

	if (!hideSurvey) {
		ignoreRetVal(GlobInstcs::prefsPtr->boolVal(
		    "window.main.on.start.survey.202603.do_dot_show",
		    hideSurvey));
	}

	/*
	 * If user has more than 6 databoxes, show dialog asking for filling
	 * out survey.
	 */
	if((!hideSurvey) && (QDate::currentDate() < QDate(2026, 7, 1)) && (GlobInstcs::acntMapPtr->size() > SURVEY_BOX_LOWER_LIMIT)) {
		DlgSurvey::view(this);
	}

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

	/* Keep sessions alive. */
	if (sessionKeeper != Q_NULLPTR) {
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.on.fail.quit_session", val)) {
				sessionKeeper->setQuitInactive(val);
			}
		}
		{
			qint64 val = 0;
			if (GlobInstcs::prefsPtr->intVal(
			        "network.isds.keep_alive.timer.period.seconds", val)) {
				sessionKeeper->setCoolDown(val * 1000);
			}
		}
		int processedTypeFlags = 0;
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_pwd.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_PWD;
				}
			}
		}
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_crt.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_CRT;
				}
			}
		}
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_pwd_crt.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_PWD_CRT;
				}
			}
		}
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_pwd_hotp.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_PWD_HOTP;
				}
			}
		}
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_pwd_totp.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_PWD_TOTP;
				}
			}
		}
		{
			bool val = false;
			if (GlobInstcs::prefsPtr->boolVal(
			        "network.isds.keep_alive.accounts.uname_mep.enabled", val)) {
				if (val) {
					processedTypeFlags |= SessionKeeper::LT_UNAME_MEP;
				}
			}
		}
		sessionKeeper->setProcessedTypeFlags(processedTypeFlags);
		sessionKeeper->startKeepAlive(1000); /* Fire every second. */
	}

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

		if (PrefsSpecific::autoBackupEnabled(*GlobInstcs::prefsPtr)) {
			if (PrefsSpecific::isTimeForAutoBackup(*GlobInstcs::prefsPtr)) {
				/* Show backup dialogue. */
				viewDataBackupDlg(true);
			}
		} else 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;
		val = GlobInstcs::prefsPtr->boolVal(
		    "window.main.on.start.tie.action.sync_all_accounts", val) && 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(AccountModel::CHROW_ALL, 0, amIndexTop);
		amIndexType = m_accountModel.index(AccountModel::CHROW_RCVD, 0, amIndexType); /* All received. */
		break;
	case MessageDb::TYPE_SENT:
		amIndexType = m_accountModel.index(AccountModel::CHROW_ALL, 0, amIndexTop);
		amIndexType = m_accountModel.index(AccountModel::CHROW_SENT, 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)), QString(),
		        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(AccountModel::CHROW_RECENT_RCVD, 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();

	QList<AcntId> acntIds;
	for (int i = 0; i < m_accountModel.rowCount(); ++i) {
		const QModelIndex amIndex(m_accountModel.index(i, 0));
		AcntId acntId = m_accountModel.acntId(amIndex);
		if (acntId.isValid()) {
			acntIds.append(acntId);
		} else {
			Q_ASSERT(0);
		}

		regenerateAccountModelYears(amIndex);
	}

	/* Drafts. */
	{
		DraftCounts draftCounts;
		if (GlobInstcs::draftDbPtr->getDraftCounts(acntIds, draftCounts)) {
			for (const AcntId &acntId : draftCounts.keys()) {
				m_accountModel.updateDraftCount(acntId,
				    draftCounts[acntId]);
			}
		} else {
			logErrorNL("%s", "Cannot obtain draft counts.");
		}
	}

	return true;
}

void MainWindow::showSendMessageDialog(int action,
    const QString &composeSerialised, const DraftEntry &draftEntry)
{
	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_TMPLT:
		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.
			 */
			for (const MsgOrigin &origin : originList) {
				msgIds.append(origin.msgId);
			}
		}
		break;
	case DlgSendMessage::ACT_NEW_FROM_DRAFT:
		break;
	default:
		Q_ASSERT(0);
		return;
		break;
	}

	QDialog *sendMsgDialog = new (::std::nothrow) DlgSendMessage(
	    acntIdDbList, (DlgSendMessage::Action)action, msgIds, acntId,
	    composeSerialised, draftEntry, 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) {
		ignoreRetVal(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;
	ignoreRetVal(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());

		/* Delete related drafts. */
		GlobInstcs::draftDbPtr->deleteDrafts({acntId});
	}

	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)) {
		/*
		 * No saveSettings().
		 * Settings are saved using the accountRemoved() signal.
		 */
	}

	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 specified columns. */
	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::filterDrafts(const QString &text)
{
	debugSlotCall();

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

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

	m_draftListProxyModel.setSortRole(DraftTableModel::ROLE_PROXYSORT);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_draftListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_draftListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Filter according to specified columns. */
	QList<int> columnList;
	columnList.append(DraftTableModel::COL_ACNT_ID);
	columnList.append(DraftTableModel::COL_CREAT_TIME);
	columnList.append(DraftTableModel::COL_MOD_TIME);
	columnList.append(DraftTableModel::COL_ANNOTATION);
	columnList.append(DraftTableModel::COL_RECIPIENTS);
	m_draftListProxyModel.setFilterKeyColumns(columnList);

	/* Set filter field background colour. */
	if (text.isEmpty()) {
		ui->draftFilterLine->setStyleSheet(m_dfltFilerLineStyleSheet);
	} else if (m_draftListProxyModel.rowCount() != 0) {
		ui->draftFilterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		ui->draftFilterLine->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);
	ui->messageList->setSquareColumnWidth(
	    DbMsgsTblModel::MAX_COLNUM + DbMsgsTblModel::TST_TRAC_NEG_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);
	ui->messageList->setSquareColumnWidth(
	    DbMsgsTblModel::MAX_COLNUM + DbMsgsTblModel::TST_TRAC_NEG_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::changeTheme(int index)
{
	/* Load theme. Use settings from preferences if invalid index received. */
	if (index >= 0) {
		loadAppUiThemeFromValues(index,
		    PrefsSpecific::appUiThemeFontScalePercent(*GlobInstcs::prefsPtr));
	} else {
		loadAppUiTheme(*GlobInstcs::prefsPtr);
	}
	/* Reload icon sets. */
	setUpMenuActionIcons(ui);
	m_accountModel.reloadIcons();
}

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::watchOpeningFailed(const AcntId &acntId,
    const QString &fileName)
{
	Q_UNUSED(acntId);

	showStatusTextWithTimeout(
	    tr("Cannot open database file '%1'.")
	        .arg(QDir::toNativeSeparators(fileName)));
}

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::copyAttachmentViaFilter(QObject *alPtr)
{
	if (Q_UNLIKELY(alPtr == Q_NULLPTR)) {
		return;
	}

	const AttachmentTableView *attachTableView =
	    qobject_cast<AttachmentTableView *>(alPtr);
	if (Q_UNLIKELY(attachTableView == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	/* m_attachmentModel */
	const AttachmentTblModel *attachTblModel =
	    qobject_cast<AttachmentTblModel *>(attachTableView->model());
	if (Q_UNLIKELY(attachTblModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	const QModelIndexList indexes =
	    attachTableView->selectionModel()->selectedRows(
	        AttachmentTblModel::ATTACHID_COL);
	if (Q_UNLIKELY(indexes.isEmpty())) {
		return;
	}

	QMimeData *md = attachTblModel->mimeData(indexes);
	if (Q_UNLIKELY(md == Q_NULLPTR)) {
		return;
	}

	QApplication::clipboard()->setMimeData(md);
}

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

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

	mw->viewSelectedMessage();
}

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

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

	/*
	 * No need to check the selection.
	 * The actionDeleteMessage is already enabled/disabled according to
	 * account and message selection.
	 */
	if (mw->ui->actionDeleteMessage->isEnabled()) {
		mw->ui->actionDeleteMessage->trigger();
	}
}

void MainWindow::copySelectedMessageViaFilter(QObject *mlPtr)
{
	if (Q_UNLIKELY(Q_NULLPTR == mlPtr)) {
		return;
	}

	const LoweredTableView *messageList =
	    qobject_cast<LoweredTableView *>(mlPtr);
	if (Q_UNLIKELY(Q_NULLPTR == messageList)) {
		Q_ASSERT(0);
		return;
	}

	/* m_messageListProxyModel */
	const SortFilterProxyModel *proxyModel =
	    qobject_cast<SortFilterProxyModel *>(messageList->model());
	if (Q_UNLIKELY(proxyModel == Q_NULLPTR)) {
		Q_ASSERT(0);
		return;
	}

	/* Use the proxymodel's implementation if mimeData(). */
	QModelIndexList proxyIndexes =
	    messageList->selectionModel()->selectedRows(
	        DbMsgsTblModel::DMID_COL);

	if (Q_UNLIKELY(proxyIndexes.isEmpty())) {
		return;
	}

	QMimeData *md = proxyModel->mimeData(proxyIndexes);
	if (Q_UNLIKELY(md == Q_NULLPTR)) {
		return;
	}

	QApplication::clipboard()->setMimeData(md);
}

void MainWindow::clearMessageFilterLineViaFilter(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::showMessageFilterLineViaFilter(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::hideMessageFilterLineViaFilter(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();
	}
}

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

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

	mw->openSelectedDraft();
}

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

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

	/*
	 * No need to check the selection.
	 * The actionDeleteDraft is already enabled/disabled according to
	 * draft selection.
	 */
	if (mw->ui->actionDeleteDraft->isEnabled()) {
		mw->ui->actionDeleteDraft->trigger();
	}
}

void MainWindow::clearDraftFilterLineViaFilter(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->draftFilterLine != Q_NULLPTR) {
		mw->ui->draftFilterLine->clear();
	}
}

void MainWindow::showDraftFilterLineViaFilter(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->draftFilterLine != Q_NULLPTR) {
		mw->showDraftFilter();
	}
}

void MainWindow::hideDraftFilterLineViaFilter(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->draftFilterLine != Q_NULLPTR) {
		mw->hideDraftFilter();
	}
}

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), QString(),
	    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();

	AttachOrigin attachOrigin;

	{
		const MWStatus mwStatus = m_selectionStatus.mwStatus();
		attachOrigin.msgId = firstMsgId(mwStatus.msgIds());

		const AcntId &acntId = mwStatus.acntId();

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

		attachOrigin.isVodz = messageDb->isVodz(attachOrigin.msgId.dmId());

		/* Taken from main window, all messages have same direction. */
		attachOrigin.direction =
		    messageDirection(mwStatus.acntNodeType(), MSG_ALL);
		if (Q_UNLIKELY(attachOrigin.direction == MSG_ALL)) {
			Q_ASSERT(0);
			return;
		}

		QModelIndexList attachmentIndexes = currentFrstColAttachmentIndexes();

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

			attachOrigin.attachData += QPair<QString, QByteArray>(
			    attachIdx.sibling(attachIdx.row(),
			        AttachmentTblModel::FNAME_COL).data().toString(),
			    attachIdx.sibling(attachIdx.row(),
			        AttachmentTblModel::BINARY_CONTENT_COL).data().toByteArray());
		}
	}

	GuiMsgOps::createEmailAttachments(attachOrigin);
}

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

	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);
	}
	/*
	 * No saveSettings().
	 * Settings are saved using the accountDataChanged() signal.
	 */

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

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

			qint64 fSize = 0;
			const char *fData = NULL;

			QFile file(filePathList.at(i));
			if (Q_UNLIKELY(!file.open(QIODevice::ReadOnly))) {
				errorMsgFileNames.append(filePathList.at(i));
				continue;
			}
			fSize = file.size();
			fData = (char *)file.map(0, fSize);
			if (Q_UNLIKELY(fData == Q_NULLPTR)) {
				errorMsgFileNames.append(filePathList.at(i));
				continue;
			}

			if (TstEntries::latestTstSigningCertExpiresBefore(
			        QByteArray::fromRawData(fData, fSize),
			        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;
			}

			QByteArray msgData = messageDb->getCompleteMessageRaw(mId.dmId());
			if (Q_UNLIKELY(msgData.isEmpty())) {
				errorMsgIds.append(mId);
				continue;
			}
			if (TstEntries::latestTstSigningCertExpiresBefore(
			        msgData,
			        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(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);
	ignoreRetVal(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));
		}
	}
}
