/*
 * 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 <QApplication>
#include <QClipboard>
#include <QMenu>

#include "src/datovka_shared/html/html_export.h"
#include "src/datovka_shared/io/records_management_db.h"
#include "src/datovka_shared/json/basic.h"
#include "src/datovka_shared/log/log.h"
#include "src/dimensions/dimensions.h"
#include "src/global.h"
#include "src/gui/dlg_download_messages.h"
#include "src/gui/dlg_email_content.h"
#include "src/gui/dlg_msg_search.h"
#include "src/gui/icon_container.h"
#include "src/gui/message_operations.h"
#include "src/gui/styles.h"
#include "src/io/message_db_set_container.h"
#include "src/io/message_db_set.h"
#include "src/io/tag_container.h"
#include "src/json/tag_text_search_request.h"
#include "src/json/tag_text_search_request_hash.h"
#include "src/model_interaction/view_interaction.h"
#include "src/settings/accounts.h"
#include "src/settings/prefs_specific.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 "ui_dlg_msg_search.h"

/* Tooltip is generated for every item in the search result table. */
#define ENABLE_TOOLTIP true

#define BOX_ID_LEN 7

static const QString dlgName("msg_search");
static const QString msgTableName("message_list");

DlgMsgSearch::DlgMsgSearch(const QList<AcntIdDb> &acntIdDbList,
    const AcntId &acntId, MainWindow *mw, QWidget *parent,
    Qt::WindowFlags flags)
    : QDialog(parent, flags),
    m_ui(new (::std::nothrow) Ui::DlgMsgSearch),
    m_actionDownloadMessage(Q_NULLPTR),
    m_actionExportMessageZfo(Q_NULLPTR),
    m_actionExportAcceptanceInfoZfo(Q_NULLPTR),
    m_actionExportAcceptanceInfoPdf(Q_NULLPTR),
    m_actionExportEnvelopePdf(Q_NULLPTR),
    m_actionExportEnvelopePdfAndAttachments(Q_NULLPTR),
    m_actionEmailZfos(Q_NULLPTR),
    m_actionEmailAllAttachments(Q_NULLPTR),
    m_actionEmailContentSelection(Q_NULLPTR),
    m_mw(mw),
    m_modelRowSelection(),
    m_acntIdDbList(acntIdDbList),
    m_foundListProxyModel(this),
     m_dfltFilerLineStyleSheet(SortFilterProxyModel::invalidFilterEditStyle),
    m_foundTableModel(this),
    m_dfltSize()
{
	m_ui->setupUi(this);
	/* Tab order is defined in UI file. */

	m_ui->filterLine->setToolTip(tr("Enter sought expression"));
	m_ui->filterLine->setClearButtonEnabled(true);

	/* Set default line height for table views/widgets. */
	m_ui->resultsTableView->setNarrowedLineHeight();
	m_ui->resultsTableView->horizontalHeader()->setDefaultAlignment(
	    Qt::AlignVCenter | Qt::AlignLeft);

	m_ui->resultsTableView->setSelectionMode(
	    QAbstractItemView::ExtendedSelection);
	m_ui->resultsTableView->setSelectionBehavior(
	    QAbstractItemView::SelectRows);

	m_foundListProxyModel.setSortRole(MsgFoundModel::ROLE_PROXYSORT);
	m_foundListProxyModel.setSourceModel(&m_foundTableModel);
	{
		QList<int> columnList;
		columnList.append(MsgFoundModel::COL_ACCOUNT_ID);
		columnList.append(MsgFoundModel::COL_MESSAGE_ID);
		columnList.append(MsgFoundModel::COL_ANNOTATION);
		columnList.append(MsgFoundModel::COL_SENDER);
		columnList.append(MsgFoundModel::COL_RECIPIENT);
		columnList.append(MsgFoundModel::COL_DELIVERY_TIME);
		m_foundListProxyModel.setFilterKeyColumns(columnList);
	}
	m_ui->resultsTableView->setModel(&m_foundListProxyModel);

	m_ui->resultsTableView->setSortingEnabled(true);
	m_ui->resultsTableView->sortByColumn(MsgFoundModel::COL_ACCOUNT_ID,
	    Qt::AscendingOrder);

	m_ui->resultsTableView->setSquareColumnWidth(
	    MsgFoundModel::COL_ATTACH_DOWNLOADED);

	m_ui->resultsTableView->setEnabled(false);

	/* Enable drag on found message list. */
	m_ui->resultsTableView->setDragEnabled(true);
	m_ui->resultsTableView->setDragDropMode(QAbstractItemView::DragOnly);
	m_ui->resultsTableView->setDefaultDropAction(Qt::CopyAction);

	m_dfltSize = this->size();
	{
		const QSize newSize = Dimensions::dialogueSize(this,
		    PrefsSpecific::dlgSize(*GlobInstcs::prefsPtr, dlgName),
		    m_dfltSize);
		if (newSize.isValid()) {
			this->resize(newSize);
		}
	}

	GuiStyles::setCalendarStyle(m_ui->fromDateEdit->calendarWidget());
	GuiStyles::setCalendarStyle(m_ui->toDateEdit->calendarWidget());

	setupActions();
	initSearchWindow(acntId);
}

DlgMsgSearch::~DlgMsgSearch(void)
{
	PrefsSpecific::setDlgSize(*GlobInstcs::prefsPtr,
	    dlgName, this->size(), m_dfltSize);

	PrefsSpecific::setDlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, msgTableName,
	    Dimensions::relativeTableColumnWidths(m_ui->resultsTableView));
	PrefsSpecific::setDlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, msgTableName,
	    Dimensions::tableColumnSortOrder(m_ui->resultsTableView));

	delete m_ui;
}

void DlgMsgSearch::showEvent(QShowEvent *event)
{
	QDialog::showEvent(event);

	Dimensions::setRelativeTableColumnWidths(m_ui->resultsTableView,
	    PrefsSpecific::dlgTblRelColWidths(*GlobInstcs::prefsPtr,
	    dlgName, msgTableName));
	Dimensions::setTableColumnSortOrder(m_ui->resultsTableView,
	    PrefsSpecific::dlgTblColSortOrder(*GlobInstcs::prefsPtr,
	    dlgName, msgTableName));
}

void DlgMsgSearch::checkInputFields(void)
{
	bool isAnyMsgTypeChecked = true;

	m_ui->searchPushButton->setEnabled(false);
	m_ui->tooManyFields->hide();

	/* is any message type checked? */
	if (!m_ui->searchRcvdMsgCheckBox->isChecked() &&
	    !m_ui->searchSntMsgCheckBox->isChecked()) {
		isAnyMsgTypeChecked = false;
	}

	const bool msgIdMissing = m_ui->msgIdLine->text().isEmpty();

	m_ui->subjectLine->setEnabled(msgIdMissing);
	m_ui->sndrBoxIdLine->setEnabled(msgIdMissing);
	m_ui->sndrNameLine->setEnabled(msgIdMissing);
	m_ui->sndrRefNumLine->setEnabled(msgIdMissing);
	m_ui->sndrFileMarkLine->setEnabled(msgIdMissing);
	m_ui->rcpntBoxIdLine->setEnabled(msgIdMissing);
	m_ui->rcpntNameLine->setEnabled(msgIdMissing);
	m_ui->rcpntRefNumLine->setEnabled(msgIdMissing);
	m_ui->rcpntFileMarkLine->setEnabled(msgIdMissing);
	m_ui->addressLine->setEnabled(msgIdMissing);
	m_ui->toHandsLine->setEnabled(msgIdMissing);
	/* Always leave from and to date input fields enabled. */
	m_ui->tagLine->setEnabled(msgIdMissing);
	m_ui->attachNameLine->setEnabled(msgIdMissing);

	if (!msgIdMissing) {
		/* Search via message ID. */
		m_ui->tagLine->clear();
	} else {
		/* Use either sender box ID or sender name for searching. */
		m_ui->sndrBoxIdLine->setEnabled(m_ui->sndrNameLine->text().isEmpty());
		m_ui->sndrNameLine->setEnabled(m_ui->sndrBoxIdLine->text().isEmpty());

		/* Use either recipient box ID or recipient name for searching. */
		m_ui->rcpntBoxIdLine->setEnabled(m_ui->rcpntNameLine->text().isEmpty());
		m_ui->rcpntNameLine->setEnabled(m_ui->rcpntBoxIdLine->text().isEmpty());
	}

	/* Search using message ID only. */
	if (!msgIdMissing) {
		/*
		 * Test if message ID to be a number.
		 * Exact match. A digit (\d), zero or more times (*).
		 */
		const QRegularExpression re("^\\d*$");
		const QString text = m_ui->msgIdLine->text();
		/* Test whether message ID is at least 3 characters. */
		m_ui->searchPushButton->setEnabled(isAnyMsgTypeChecked &&
		    (text.length() > 2) &&
		    (re.match(text).capturedLength() == text.length()));
		return;
	}

	/* Search according to supplied tag text. */
	const bool tagCorrect = m_ui->tagLine->text().isEmpty() ||
	    (m_ui->tagLine->text().length() > 2);

	/* Data-box ID must have 7 characters. */
	const bool boxIdCorrect =
	    (m_ui->sndrBoxIdLine->text().isEmpty() ||
	     (m_ui->sndrBoxIdLine->text().size() == BOX_ID_LEN)) &&
	    (m_ui->rcpntBoxIdLine->text().isEmpty() ||
	     (m_ui->rcpntBoxIdLine->text().size() == BOX_ID_LEN));

	/* Only 3 fields can be set together (+ optional date restriction). */
	bool adequateNumberOfFields = true;

	const int itemsWithoutTag = filledInExceptTags();
	if (itemsWithoutTag > 3) {
		/* Too many fields. */
		adequateNumberOfFields = false;
		m_ui->tooManyFields->show();
	} else if ((itemsWithoutTag < 1) && m_ui->tagLine->text().isEmpty()) {
		/* Not enough if no date specified. */
		adequateNumberOfFields =
		    m_ui->fromDateEdit->isEnabled() || m_ui->toDateEdit->isEnabled();
	}

	m_ui->searchPushButton->setEnabled(
	    (isAnyMsgTypeChecked && adequateNumberOfFields) &&
	    (boxIdCorrect && tagCorrect));
}

void DlgMsgSearch::fromDateEnable(bool enable)
{
	m_ui->fromDateEdit->setEnabled(enable);
}

void DlgMsgSearch::toDateEnable(bool enable)
{
	m_ui->toDateEdit->setEnabled(enable);
}

void DlgMsgSearch::reflectDateChange(void)
{
	m_ui->toDateEdit->setMinimumDate(m_ui->fromDateEdit->date());
	checkInputFields();
}

/*!
 * @brief Computes intersection between tag and envelope search result data.
 *
 * @param[in] envelData Envelope search result data.
 * @param[in] tagData Tag search data.
 * @param[in] messageDbSet Message database set.
 * @return Intersection data.
 */
static
QList<MessageDb::SoughtMsg> dataIntersect(
    const QList<MessageDb::SoughtMsg> &envelData, const QList<qint64> &tagData,
    MessageDbSet *messageDbSet)
{
	if (Q_UNLIKELY(messageDbSet == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QList<MessageDb::SoughtMsg>();
	}

	QList<MessageDb::SoughtMsg> result;

	for (const qint64 msgId : tagData) {
		for (const MessageDb::SoughtMsg &msg : envelData) {
			if (msg.mId.dmId() == msgId) {
				MessageDb::SoughtMsg msgData(
				    messageDbSet->msgsGetMsgDataFromId(msgId));
				if (msgData.mId.dmId() != -1) {
					result.append(msgData);
				}
			}
		}
	}

	return result;
}

/*!
 * @brief Obtains data from databases that match found tag data.
 *
 * @param[in] tagData Tag search data.
 * @param[in] messageDbSet Message database set.
 * @return Message data.
 */
static
QList<MessageDb::SoughtMsg> displayableTagData(const QList<qint64> &tagData,
    MessageDbSet *messageDbSet)
{
	if (Q_UNLIKELY(messageDbSet == Q_NULLPTR)) {
		Q_ASSERT(0);
		return QList<MessageDb::SoughtMsg>();
	}

	QList<MessageDb::SoughtMsg> result;

	for (const qint64 msgId : tagData) {
		MessageDb::SoughtMsg msgData(
		    messageDbSet->msgsGetMsgDataFromId(msgId));

		if (msgData.mId.dmId() != -1) {
			result.append(msgData);
		}
	}

	return result;
}

/*!
 * @brief Insert message detail data into model.
 *
 * @param[in,out] foundTableModel Table model to put message detail entries into.
 * @param[in]     accountDescr Username and related database set.
 * @param[in]     msgDataList Data list to serve as list of account identifiers.
 */
static
void setMessageDetails(MsgFoundModel &foundTableModel,
    const AcntIdDb &accountDescr,
    const QList<MessageDb::SoughtMsg> &msgDataList)
{
	for (const MessageDb::SoughtMsg &msgData : msgDataList) {
		MessageDb *messageDb =
		    accountDescr.messageDbSet()->constAccessMessageDb(
		        msgData.mId.deliveryTime());
		if (Q_UNLIKELY(Q_NULLPTR == messageDb)) {
			Q_ASSERT(0);
			continue;
		}
		foundTableModel.setMessageDetail(accountDescr, msgData.mId,
		    htmlMarginsApp("",
		    Html::Export::htmlMessageInfoApp(
		    messageDb->getMessageEnvelope(msgData.mId.dmId()),
		    messageDb->messageAuthorJsonStr(msgData.mId.dmId()),
		    GlobInstcs::recMgmtDbPtr->storedMsgLocations(msgData.mId.dmId()),
		    true, false, true)));
	}
}

/*!
 * @brief Append message list to result table.
 *
 * @param[in,out] resultsTableView Table view.
 * @param[in,out] foundTableModel Table model.
 * @param[in]     accountDescr Username and related database set.
 * @param[in]     msgDataList Message data list.
 */
static
void appendMsgsToTable(QTableView &resultsTableView,
    MsgFoundModel &foundTableModel, const AcntIdDb &accountDescr,
    const QList<MessageDb::SoughtMsg> &msgDataList)
{
	resultsTableView.setEnabled(true);

	foundTableModel.appendData(accountDescr, msgDataList);
	if (ENABLE_TOOLTIP) {
		setMessageDetails(foundTableModel, accountDescr, msgDataList);
	}

	/*
	 * Use table column widths from preferences.
	 *
	 * resultsTableView.resizeColumnsToContents();
	 */

	resultsTableView.horizontalHeader()->setStretchLastSection(true);
}

void DlgMsgSearch::searchMessages(void)
{
	debugSlotCall();

	m_foundTableModel.removeAllRows();
	m_ui->resultsTableView->setEnabled(false);

	/* Message envelope search results. */
	QList<MessageDb::SoughtMsg> envelResults;

	/* Tag search result. */
	Json::Int64StringList tagResults;

	/* Displayed results. */
	QList<MessageDb::SoughtMsg> resultsToBeDisplayed;

	/* Types of messages to search for. */
	enum MessageDirection msgType = MSG_ALL;
	if (m_ui->searchRcvdMsgCheckBox->isChecked() &&
	    m_ui->searchSntMsgCheckBox->isChecked()) {
		msgType = MSG_ALL;
	} else if (m_ui->searchRcvdMsgCheckBox->isChecked()) {
		msgType = MSG_RECEIVED;
	} else if (m_ui->searchSntMsgCheckBox->isChecked()) {
		msgType = MSG_SENT;
	}

	/* If tag data were supplied, get message ids from tag table. */
	const bool searchTags = !m_ui->tagLine->text().isEmpty();
	if (searchTags) {
		const Json::TagTextSearchRequest request(m_ui->tagLine->text(),
		        Json::TagTextSearchRequest::ST_SUBSTRING);
		Json::TagTextSearchRequestToIdenfifierHash results;
		GlobInstcs::tagContPtr->getMsgIdsContainSearchTagText(
		    Json::TagTextSearchRequestList({request}),
		    results);
		tagResults = results[request];
	}

	/* Fill search envelope items. */
	Isds::Envelope searchEnvelope;
	searchEnvelope.setDmId(m_ui->msgIdLine->text().isEmpty() ? -1 :
	    m_ui->msgIdLine->text().toLongLong());
	searchEnvelope.setDmAnnotation(m_ui->subjectLine->text());
	searchEnvelope.setDbIDSender(m_ui->sndrBoxIdLine->text());
	searchEnvelope.setDmSender(m_ui->sndrNameLine->text());
	searchEnvelope.setDmSenderAddress(m_ui->addressLine->text());
	searchEnvelope.setDbIDRecipient(m_ui->rcpntBoxIdLine->text());
	searchEnvelope.setDmRecipient(m_ui->rcpntNameLine->text());
	searchEnvelope.setDmSenderRefNumber(m_ui->sndrRefNumLine->text());
	searchEnvelope.setDmSenderIdent(m_ui->sndrFileMarkLine->text());
	searchEnvelope.setDmRecipientRefNumber(m_ui->rcpntRefNumLine->text());
	searchEnvelope.setDmRecipientIdent(m_ui->rcpntFileMarkLine->text());
	searchEnvelope.setDmToHands(m_ui->toHandsLine->text());

	/* Number of accounts in which to search for messages in. */
	const int dbCount = m_ui->searchAllAcntCheckBox->isChecked() ?
	    m_acntIdDbList.count() : 1;

	/* Search in accounts. */
	for (int i = 0; i < dbCount; ++i) {

		/* Get currently selected account if processing only one account. */
		const AcntIdDb &acntIdDb((dbCount == 1) ?
		    m_acntIdDbList.at(m_ui->accountComboBox->currentIndex()) :
		    m_acntIdDbList.at(i));

		envelResults.clear();
		resultsToBeDisplayed.clear();

		/* Search in envelope envelope data. */
		envelResults = acntIdDb.messageDbSet()->msgsSearch(
		    searchEnvelope, msgType,
		    m_ui->attachNameLine->text(),
		    m_ui->logicalAndRelationCheckBox->isChecked(),
		    m_ui->fromDateEdit->isEnabled() ? m_ui->fromDateEdit->date() : QDate(),
		    m_ui->toDateEdit->isEnabled() ? m_ui->toDateEdit->date() : QDate());

		if (searchTags && !tagResults.isEmpty() && !envelResults.isEmpty()) {
			/*
			 * Tag data were supplied and some envelope data also.
			 * Intersection of tag and envelope search results is
			 * needed.
			 */
			resultsToBeDisplayed = dataIntersect(envelResults,
			    tagResults, acntIdDb.messageDbSet());
		} else if (!searchTags && !envelResults.isEmpty()) {
			/*
			 * No tag data were supplied. Envelope were supplied.
			 * Use envelope search results only.
			 */
			resultsToBeDisplayed = envelResults;
		} else if (searchTags && !tagResults.isEmpty()) {
			/*
			 * Only tag tag data were supplied.
			 * Convert tag results into displayable form.
			 */
			resultsToBeDisplayed = displayableTagData(tagResults,
			    acntIdDb.messageDbSet());
		}

		if (!resultsToBeDisplayed.isEmpty()) {
			appendMsgsToTable(*(m_ui->resultsTableView),
			    m_foundTableModel, acntIdDb, resultsToBeDisplayed);
		}
	}
}

void DlgMsgSearch::filterFound(const QString &text)
{
	/* Store style at first invocation. */
	if (m_dfltFilerLineStyleSheet == SortFilterProxyModel::invalidFilterEditStyle) {
		m_dfltFilerLineStyleSheet = m_ui->filterLine->styleSheet();
	}

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
	m_foundListProxyModel.setFilterRegularExpression(
	    QRegularExpression(QRegularExpression::escape(text),
	        QRegularExpression::CaseInsensitiveOption));
#else /* < Qt-5.15 */
	m_foundListProxyModel.setFilterRegExp(QRegExp(text,
	    Qt::CaseInsensitive, QRegExp::FixedString));
#endif /* >= Qt-5.15 */
	/* Set filter field background colour. */
	if (text.isEmpty()) {
		m_ui->filterLine->setStyleSheet(m_dfltFilerLineStyleSheet);
	} else if (m_foundListProxyModel.rowCount() != 0) {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::foundFilterEditStyle);
	} else {
		m_ui->filterLine->setStyleSheet(
		    SortFilterProxyModel::notFoundFilterEditStyle);
	}
}

void DlgMsgSearch::updateMessageSelection(const QItemSelection &selected,
    const QItemSelection &deselected)
{
	Q_UNUSED(selected);
	Q_UNUSED(deselected);

	m_modelRowSelection = ViewInteraction::selectedSrcRowNums(
	    *(m_ui->resultsTableView), m_foundListProxyModel,
	    MsgFoundModel::COL_ACCOUNT_ID);

	updateActionActivation();
}

void DlgMsgSearch::messageItemDoubleClicked(const QModelIndex &index)
{
	if (Q_UNLIKELY(!index.isValid())) {
		return;
	}

	const QModelIndex srcIdx = m_foundListProxyModel.mapToSource(index);
	if (Q_UNLIKELY(!srcIdx.isValid())) {
		Q_ASSERT(0);
		return;
	}

	const MsgFoundModel::MsgIdentification &identif =
	    m_foundTableModel.msgIdentification(srcIdx.row());
	if (Q_UNLIKELY(!identif.acntIdDb.isValid())) {
		Q_ASSERT(0);
		return;
	}

	emit focusSelectedMsg(identif.acntIdDb, identif.msgId.dmId(),
	    MessageDbSet::yearFromDateTime(identif.msgId.deliveryTime()),
	    (int)identif.msgType);
	/* Don't close the dialogue. */
}

void DlgMsgSearch::viewMessageListContextMenu(const QPoint &point)
{
	/*
	 * The slot updateMessageSelection() is called before this one
	 * if the selection changes with the right click.
	 */

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

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

	menu->addAction(m_actionDownloadMessage);
	menu->addSeparator();
	menu->addAction(m_actionExportMessageZfo);
	menu->addAction(m_actionExportAcceptanceInfoZfo);
	menu->addAction(m_actionExportAcceptanceInfoPdf);
	menu->addAction(m_actionExportEnvelopePdf);
	menu->addAction(m_actionExportEnvelopePdfAndAttachments);
	menu->addSeparator();
	{
		QMenu *submenu = menu->addMenu(tr("E-mail with"));

		submenu->addAction(m_actionEmailZfos);
		submenu->addAction(m_actionEmailAllAttachments);
		submenu->addAction(m_actionEmailContentSelection);
	}

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

/*!
 * @brief Convert enum MessageDb::MessageType to enum MessageDirection.
 *
 * @param[in]  type Message type.
 * @param[out] ok Set to true on success.
 * @return Message direction.
 */
static
enum MessageDirection messageType2messageDirection(
    enum MessageDb::MessageType type, bool *ok = Q_NULLPTR)
{
	enum MessageDirection direct = MSG_ALL;

	switch (type) {
	case MessageDb::TYPE_RECEIVED:
		direct = MSG_RECEIVED;
		break;
	case MessageDb::TYPE_SENT:
		direct = MSG_SENT;
		break;
	default:
		Q_ASSERT(0);
		if (ok != Q_NULLPTR) {
			*ok = false;
		}
		return direct;
		break;
	}

	if (ok != Q_NULLPTR) {
		*ok = true;
	}
	return direct;
}

/*!
 * @brief Construct exhaustive message selection list.
 *
 * @param[in] msgSelection Message selection status.
 * @param[in] model Found table model.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginList(const QList<int> &modelRowSelection,
    const MsgFoundModel &model)
{
	QList<MsgOrigin> originList;

	for (int row : modelRowSelection) {
		if (Q_UNLIKELY(row < 0)) {
			Q_ASSERT(0);
			continue;
		}
		const MsgFoundModel::MsgIdentification &identif =
		    model.msgIdentification(row);

		originList.append(MsgOrigin(identif.acntIdDb, identif.msgId,
		    identif.isVodz, messageType2messageDirection(identif.msgType),
		    identif.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] model Found table model.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginListFromIds(const AcntId &acntId,
    const QList<MsgId> &msgIds, const MsgFoundModel &model)
{
	if (Q_UNLIKELY(!acntId.isValid())) {
		/* No account selected. */
		return QList<MsgOrigin>();
	}
	if (Q_UNLIKELY(msgIds.isEmpty())) {
		/* No message selected. */
		return QList<MsgOrigin>();
	}

	QList<MsgOrigin> originList;

	for (const MsgId &msgId : msgIds) {
		bool iOk = false;
		const MsgFoundModel::MsgIdentification &identif =
		    model.msgIdentification(acntId, msgId, &iOk);
		if (Q_UNLIKELY(!iOk)) {
			continue;
		}

		originList.append(MsgOrigin(identif.acntIdDb, identif.msgId,
		    identif.isVodz, messageType2messageDirection(identif.msgType),
		    identif.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] model Found table model.
 * @return Origin list of selected messages.
 */
static
QList<MsgOrigin> messageOriginListFromAcntMsgIds(
    const AcntIdToMsgIdListHash &acntMsgIds, const MsgFoundModel &model)
{
	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(), model));
	}

	return originList;
}

void DlgMsgSearch::offerDownloadForMessages(
    const AcntIdToMsgIdListHash &acntMsgIds)
{
	/*
	 * Generate origins from account and message identifiers.
	 */
	QList<MsgOrigin> originList = messageOriginListFromAcntMsgIds(
	    acntMsgIds, m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);
}

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

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

	m_foundTableModel.overrideDownloaded(acntId, msgId, true);
}

void DlgMsgSearch::watchMessageDataDeleted(const AcntId &acntId,
    const MsgId &msgId)
{
	if (Q_UNLIKELY((!acntId.isValid()) || (msgId.dmId() < 0))) {
		Q_ASSERT(0);
		return;
	}

	m_foundTableModel.remove(acntId, msgId);
}

void DlgMsgSearch::downloadSelectedMessageAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	DlgDownloadMessages::downloadAll(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);
}

void DlgMsgSearch::exportSelectedMessageZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

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

void DlgMsgSearch::exportSelectedAcceptanceInfoZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

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

void DlgMsgSearch::exportSelectedAcceptanceInfoPdfs(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

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

void DlgMsgSearch::exportSelectedEnvelopePdfs(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

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

void DlgMsgSearch::exportSelectedEnvelopeAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::exportSelectedEnvelopeAndAttachments(originList, this);
}

void DlgMsgSearch::createEmailWithZfos(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::createEmail(originList, GuiMsgOps::ADD_ZFO_MESSAGE);
}

void DlgMsgSearch::createEmailWithAllAttachments(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	GuiMsgOps::createEmail(originList, GuiMsgOps::ADD_ATTACHMENTS);
}

void DlgMsgSearch::createEmailContentSelection(void)
{
	QList<MsgOrigin> originList = messageOriginList(m_modelRowSelection,
	    m_foundTableModel);
	originList = DlgDownloadMessages::offerDownloadForMissing(
	    MsgOriginAndUpladTarget::fromMsgOriginList(originList), m_mw, this);

	DlgEmailContent::createEmail(originList, this);
}

void DlgMsgSearch::setupActions(void)
{
	m_actionDownloadMessage = new (::std::nothrow) QAction(this);
	if (m_actionDownloadMessage != Q_NULLPTR) {
		m_actionDownloadMessage->setObjectName(QString::fromUtf8("actionDownloadMessage"));
		m_actionDownloadMessage->setIcon(IconContainer::construcIcon(IconContainer::ICON_MESSAGE_DOWNLOAD));
		m_actionDownloadMessage->setMenuRole(QAction::NoRole);
		m_actionDownloadMessage->setText(tr("Download Signed Message"));
		m_actionDownloadMessage->setToolTip(tr("Download the complete message, i.e. including attachments, and verify its signature."));
	}

	m_actionExportMessageZfo = new (::std::nothrow) QAction(this);
	if (m_actionExportMessageZfo != Q_NULLPTR) {
		m_actionExportMessageZfo->setObjectName(QString::fromUtf8("actionExportMessageZfo"));
		//m_actionExportMessageZfo->setIcon();
		m_actionExportMessageZfo->setMenuRole(QAction::NoRole);
		m_actionExportMessageZfo->setText(tr("Export Message as ZFO"));
		m_actionExportMessageZfo->setToolTip(tr("Export the selected message as a ZFO file."));
	}

	m_actionExportAcceptanceInfoZfo = new (::std::nothrow) QAction(this);
	if (m_actionExportAcceptanceInfoZfo != Q_NULLPTR) {
		m_actionExportAcceptanceInfoZfo->setObjectName(QString::fromUtf8("actionExportAcceptanceInfoZfo"));
		//m_actionExportAcceptanceInfoZfo->setIcon();
		m_actionExportAcceptanceInfoZfo->setMenuRole(QAction::NoRole);
		m_actionExportAcceptanceInfoZfo->setText(tr("Export Acceptance Info as ZFO"));
		m_actionExportAcceptanceInfoZfo->setToolTip(tr("Export the acceptance information of the selected message as a ZFO file."));
	}

	m_actionExportAcceptanceInfoPdf = new (::std::nothrow) QAction(this);
	if (m_actionExportAcceptanceInfoPdf != Q_NULLPTR) {
		m_actionExportAcceptanceInfoPdf->setObjectName(QString::fromUtf8("actionExportAcceptanceInfoPdf"));
		//m_actionExportAcceptanceInfoPdf->setIcon();
		m_actionExportAcceptanceInfoPdf->setMenuRole(QAction::NoRole);
		m_actionExportAcceptanceInfoPdf->setText(tr("Export Acceptance Info as PDF"));
		m_actionExportAcceptanceInfoPdf->setToolTip(tr("Export the content of the acceptance information\n"
		    "of the selected message into a PDF file."));
	}

	m_actionExportEnvelopePdf = new (::std::nothrow) QAction(this);
	if (m_actionExportEnvelopePdf != Q_NULLPTR) {
		m_actionExportEnvelopePdf->setObjectName(QString::fromUtf8("actionExportEnvelopePdf"));
		//m_actionExportEnvelopePdf->setIcon();
		m_actionExportEnvelopePdf->setMenuRole(QAction::NoRole);
		m_actionExportEnvelopePdf->setText(tr("Export Message Envelope as PDF"));
		m_actionExportEnvelopePdf->setToolTip(tr("Export content of the envelope\n"
		    "of the selected message as a PDF file."));
	}

	m_actionExportEnvelopePdfAndAttachments = new (::std::nothrow) QAction(this);
	if (m_actionExportEnvelopePdfAndAttachments != Q_NULLPTR) {
		m_actionExportEnvelopePdfAndAttachments->setObjectName(QString::fromUtf8("actionExportEnvelopePdfAndAttachments"));
		//m_actionExportEnvelopePdfAndAttachments->setIcon();
		m_actionExportEnvelopePdfAndAttachments->setMenuRole(QAction::NoRole);
		m_actionExportEnvelopePdfAndAttachments->setText(tr("Export Envelope PDF with Attachments"));
		m_actionExportEnvelopePdfAndAttachments->setToolTip(tr("Export the content of the envelope\n"
		    "of the selected message to a PDF file.\n"
		    "Also export message attachments."));
	}

	m_actionEmailZfos = new (::std::nothrow) QAction(this);
	if (m_actionEmailZfos != Q_NULLPTR) {
		m_actionEmailZfos->setObjectName(QString::fromUtf8("actionEmailZfos"));
		//m_actionEmailZfos->setIcon();
		m_actionEmailZfos->setMenuRole(QAction::NoRole);
		m_actionEmailZfos->setText(tr("Message ZFOs"));
		m_actionEmailZfos->setToolTip(tr("Creates an e-mail containing ZFOs of selected messages."));
	}

	m_actionEmailAllAttachments = new (::std::nothrow) QAction(this);
	if (m_actionEmailAllAttachments != Q_NULLPTR) {
		m_actionEmailAllAttachments->setObjectName(QString::fromUtf8("actionEmailAllAttachments"));
		//m_actionEmailAllAttachments->setIcon();
		m_actionEmailAllAttachments->setMenuRole(QAction::NoRole);
		m_actionEmailAllAttachments->setText(tr("All Attachments"));
		m_actionEmailAllAttachments->setToolTip(tr("Creates an e-mail containing all attachments of selected messages."));
	}

	m_actionEmailContentSelection = new (::std::nothrow) QAction(this);
	if (m_actionEmailContentSelection != Q_NULLPTR) {
		m_actionEmailContentSelection->setObjectName(QString::fromUtf8("actionEmailContentSelection"));
		//m_actionEmailContentSelection->setIcon();
		m_actionEmailContentSelection->setMenuRole(QAction::NoRole);
		m_actionEmailContentSelection->setText(tr("Selected Content"));
		m_actionEmailContentSelection->setToolTip(tr("Creates an e-mail containing selected content."));
	}

	connect(m_actionDownloadMessage, SIGNAL(triggered()),
	    this, SLOT(downloadSelectedMessageAttachments()));
	    /* Separator. */
	connect(m_actionExportMessageZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedMessageZfos()));
	connect(m_actionExportAcceptanceInfoZfo, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoZfos()));
	connect(m_actionExportAcceptanceInfoPdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedAcceptanceInfoPdfs()));
	connect(m_actionExportEnvelopePdf, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopePdfs()));
	connect(m_actionExportEnvelopePdfAndAttachments, SIGNAL(triggered()),
	    this, SLOT(exportSelectedEnvelopeAttachments()));
	    /* Separator. */
	connect(m_actionEmailZfos, SIGNAL(triggered()),
	    this, SLOT(createEmailWithZfos()));
	connect(m_actionEmailAllAttachments, SIGNAL(triggered()),
	    this, SLOT(createEmailWithAllAttachments()));
	connect(m_actionEmailContentSelection, SIGNAL(triggered()),
	    this, SLOT(createEmailContentSelection()));
}

void DlgMsgSearch::initSearchWindow(const AcntId &acntId)
{
	m_ui->infoTextLabel->setText(tr(
	    "Double clicking on a found message will change focus of the selected message in the main application window. "
	    "Note: You can view additional information when hovering the mouse cursor over the message ID."));

	Q_ASSERT(acntId.isValid());

	/* Set accounts into combobox - account name and user name. */
	for (const AcntIdDb &acntIdDb : m_acntIdDbList) {
		const AcntId aId(acntIdDb);
		const QString accountName =
		    GlobInstcs::acntMapPtr->acntData(aId).accountName() +
		    " (" + acntIdDb.username() + ")";
		m_ui->accountComboBox->addItem(accountName, QVariant::fromValue(aId));
		if (acntId == aId) {
			int i = m_ui->accountComboBox->count() - 1;
			Q_ASSERT(0 <= i);
			m_ui->accountComboBox->setCurrentIndex(i);
		}
	}

	/* Only one account available. */
	if (m_acntIdDbList.count() <= 1) {
		m_ui->searchAllAcntCheckBox->setEnabled(false);
	}

	m_ui->tooManyFields->setStyleSheet("QLabel { color: red }");
	m_ui->tooManyFields->hide();

	m_ui->fromDateEdit->setEnabled(false);
	m_ui->toDateEdit->setEnabled(false);

	connect(m_ui->searchRcvdMsgCheckBox, SIGNAL(clicked()),
	    this, SLOT(checkInputFields()));
	connect(m_ui->searchSntMsgCheckBox, SIGNAL(clicked()),
	    this, SLOT(checkInputFields()));

	connect(m_ui->msgIdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->subjectLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(m_ui->sndrBoxIdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrRefNumLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->sndrFileMarkLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntBoxIdLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntRefNumLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->rcpntFileMarkLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(m_ui->addressLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->toHandsLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(m_ui->fromDateCheckBox, SIGNAL(toggled(bool)),
	    this, SLOT(fromDateEnable(bool)));
	connect(m_ui->fromDateCheckBox, SIGNAL(toggled(bool)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->toDateCheckBox, SIGNAL(toggled(bool)),
	    this, SLOT(toDateEnable(bool)));
	connect(m_ui->toDateCheckBox, SIGNAL(toggled(bool)),
	    this, SLOT(checkInputFields()));
	connect(m_ui->fromDateEdit, SIGNAL(dateChanged(QDate)),
	    this, SLOT(reflectDateChange()));
	connect(m_ui->toDateEdit, SIGNAL(dateChanged(QDate)),
	    this, SLOT(reflectDateChange()));

	connect(m_ui->tagLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	connect(m_ui->attachNameLine, SIGNAL(textChanged(QString)),
	    this, SLOT(checkInputFields()));

	m_ui->resultsTableView->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(m_ui->resultsTableView, SIGNAL(customContextMenuRequested(QPoint)),
	    this, SLOT(viewMessageListContextMenu(QPoint)));
	connect(m_ui->resultsTableView, SIGNAL(doubleClicked(QModelIndex)),
	    this, SLOT(messageItemDoubleClicked(QModelIndex)));
	connect(m_ui->resultsTableView->selectionModel(),
	    SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
	    this,
	    SLOT(updateMessageSelection(QItemSelection, QItemSelection)));

	{
		TableKeyPressFilter *filter =
		    new (::std::nothrow) TableKeyPressFilter(m_ui->resultsTableView);
		if (Q_NULLPTR != filter) {
			filter->registerAction(Qt::Key_Return,
			    focusOnMessageViaFilter, this, true);
			filter->registerAction(Qt::Key_Enter, /* On keypad. */
			    focusOnMessageViaFilter, this, true);
			filter->registerAction(Qt::CTRL | Qt::Key_C,
			    copyMessageViaFilter, this, true);

			m_ui->resultsTableView->installEventFilter(filter);
		}
	}

	connect(m_ui->searchPushButton, SIGNAL(clicked()),
	    this, SLOT(searchMessages()));

	connect(m_ui->filterLine, SIGNAL(textChanged(QString)),
	    this, SLOT(filterFound(QString)));

	m_ui->resultsTableView->installEventFilter(
	    new (::std::nothrow) TableHomeEndFilter(m_ui->resultsTableView));
	m_ui->resultsTableView->installEventFilter(
	    new (::std::nothrow) TableTabIgnoreFilter(m_ui->resultsTableView));

	m_ui->toDateEdit->setDate(QDate::currentDate());

	connect(&m_foundTableModel, SIGNAL(missingCompleteMessages(AcntIdToMsgIdListHash)),
	    this, SLOT(offerDownloadForMessages(AcntIdToMsgIdListHash)));

	databaseConnectActions();
}

void DlgMsgSearch::databaseConnectActions(void)
{
	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)));
}

int DlgMsgSearch::filledInExceptTags(void) const
{
	int cnt = 0;

	if (!m_ui->msgIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->subjectLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrBoxIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrNameLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrRefNumLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->sndrFileMarkLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntBoxIdLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntNameLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntRefNumLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->rcpntFileMarkLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->addressLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->toHandsLine->text().isEmpty()) { ++cnt; }
	if (!m_ui->attachNameLine->text().isEmpty()) { ++cnt; }

	return cnt;
}

void DlgMsgSearch::updateActionActivation(void)
{
	int numSelected = m_modelRowSelection.size();

	m_actionDownloadMessage->setEnabled(numSelected > 0);

	m_actionExportMessageZfo->setEnabled(numSelected > 0);
	m_actionExportAcceptanceInfoZfo->setEnabled(numSelected > 0);
	m_actionExportAcceptanceInfoPdf->setEnabled(numSelected > 0);
	m_actionExportEnvelopePdf->setEnabled(numSelected > 0);
	m_actionExportEnvelopePdfAndAttachments->setEnabled(numSelected > 0);

	m_actionEmailZfos->setEnabled(numSelected > 0);
	m_actionEmailAllAttachments->setEnabled(numSelected > 0);
	m_actionEmailContentSelection->setEnabled(numSelected > 0);
}

void DlgMsgSearch::focusOnMessageViaFilter(QObject *dlgPtr)
{
	if (Q_UNLIKELY(Q_NULLPTR == dlgPtr)) {
		return;
	}

	DlgMsgSearch *dlg = qobject_cast<DlgMsgSearch *>(dlgPtr);
	if (Q_UNLIKELY(Q_NULLPTR == dlg)) {
		Q_ASSERT(0);
		return;
	}

	LoweredTableView *view = dlg->m_ui->resultsTableView;
	if (Q_UNLIKELY(Q_NULLPTR == view)) {
		Q_ASSERT(0);
		return;
	}

	const QModelIndexList indexes =
	    view->selectionModel()->selectedRows(MsgFoundModel::COL_ACCOUNT_ID);
	/* Focus on message only when one message selected. */
	if (indexes.size() != 1) {
		return;
	}

	dlg->messageItemDoubleClicked(indexes.first());
}

void DlgMsgSearch::copyMessageViaFilter(QObject *dlgPtr)
{
	if (Q_UNLIKELY(Q_NULLPTR == dlgPtr)) {
		return;
	}

	const DlgMsgSearch *dlg = qobject_cast<DlgMsgSearch *>(dlgPtr);
	if (Q_UNLIKELY(Q_NULLPTR == dlg)) {
		Q_ASSERT(0);
		return;
	}

	const LoweredTableView *view = dlg->m_ui->resultsTableView;
	if (Q_UNLIKELY(Q_NULLPTR == view)) {
		Q_ASSERT(0);
		return;
	}

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

	const QModelIndexList indexes =
	    view->selectionModel()->selectedRows(MsgFoundModel::COL_ACCOUNT_ID);
	if (indexes.isEmpty() || indexes.size() < 1) {
		return;
	}

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

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