/*
 * Copyright (C) 2014-2024 CZ.NIC
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <QColor>

#include "src/datovka_shared/compat/compiler.h" /* macroStdMove */
#include "src/datovka_shared/compat_qt/misc.h" /* Q_FALLTHROUGH */
#include "src/datovka_shared/isds/type_conversion.h"
#include "src/datovka_shared/isds/type_description.h"
#include "src/isds/to_text_conversion.h"
#include "src/models/helper.h"
#include "src/models/data_box_contacts_model.h"

BoxContactsModel::BoxContactsModel(QObject *parent)
    : TblModel(parent),
    m_colouring(false),
    m_senderEffectiveOVM(false)
{
	/* Fixed column count. */
	m_columnCount = MAX_COL;
}

/*!
 * @brief Convert NilBool value into user understandable string.
 *
 * @param[in] val NilBool value.
 * @return String that can be shown to the user.
 */
static
QString nilBoolString(enum Isds::Type::NilBool val)
{
	switch (val) {
	case Isds::Type::BOOL_NULL:
		return BoxContactsModel::tr("unknown");
		break;
	case Isds::Type::BOOL_FALSE:
		return BoxContactsModel::tr("no");
		break;
	case Isds::Type::BOOL_TRUE:
		return BoxContactsModel::tr("yes");
		break;
	default:
		return QString();
		break;
	}
}

QVariant BoxContactsModel::data(const QModelIndex &index, int role) const
{
	switch (role) {
	case Qt::DisplayRole:
		switch (index.column()) {
		case CHECKBOX_COL:
			return QVariant();
			break;
		case BOX_TYPE_COL:
			{
				QVariant entry(_data(index, role));
				if (!entry.isNull()) {
					return Isds::dbType2Str(Isds::intVariant2DbType(entry));
				} else {
					return entry;
				}
			}
			break;
		case PUBLIC_COL:
			Q_FALLTHROUGH();
		case PDZ_COL:
			switch (Isds::variant2NilBool(_data(index, role))) {
			case Isds::Type::BOOL_NULL:
				return QString();
				break;
			case Isds::Type::BOOL_FALSE:
				return tr("no");
				break;
			case Isds::Type::BOOL_TRUE:
				return tr("yes");
				break;
			default:
				return QString();
				break;
			}
			break;
		case PAYMENT_COL:
			{
				QList<QVariant> entries =
				    _data(index.sibling(index.row(), PAYMENTS_COL), Qt::DisplayRole).toList();
				if (entries.size() > 1) {
					/*
					 * Display permanent editor in the view rather than the value.
					 * The value must be hidden when permanent editor is visible
					 * because of some broken styles on macOS.
					 * */
					return QString();
				}
			}
			{
				QVariant entry(_data(index, role));
				if (!entry.isNull()) {
					return descrDmType(entry.toInt());
				} else {
					return QString();
				}
			}
			break;
		default:
			return _data(index, role);
			break;
		}
		break;
	case Qt::EditRole:
		if (index.column() == PAYMENT_COL) {
			return _data(index, Qt::DisplayRole);
		} else {
			return QVariant();
		}
		break;
	case Qt::ToolTipRole:
		switch (index.column()) {
		case BOX_ID_COL:
			if (m_colouring && (index.column() == BOX_ID_COL)) {
				enum Isds::Type::NilBool publ = Isds::variant2NilBool(
				    _data(index.sibling(index.row(), PUBLIC_COL), Qt::DisplayRole));
				enum Isds::Type::NilBool pdz = Isds::variant2NilBool(
				    _data(index.sibling(index.row(), PDZ_COL), Qt::DisplayRole));
				QString toolTipStr;
				if (!m_senderEffectiveOVM) {
					if ((publ == Isds::Type::BOOL_NULL) && (pdz == Isds::Type::BOOL_NULL)) {
						toolTipStr = tr("It's unknown whether public or commercial messages can be sent to this recipient.") +
						    QStringLiteral("\n");
					} else if ((publ == Isds::Type::BOOL_NULL) && (pdz == Isds::Type::BOOL_FALSE)) {
						toolTipStr = tr("It's unknown whether a public messages can be sent to this recipient.") +
						    QStringLiteral("\n");
					} else if ((publ == Isds::Type::BOOL_FALSE) && (pdz == Isds::Type::BOOL_NULL)) {
						toolTipStr = tr("It's unknown whether a commercial messages can be sent to this recipient.") +
						    QStringLiteral("\n");
					} else if ((publ == Isds::Type::BOOL_FALSE) && (pdz == Isds::Type::BOOL_FALSE)) {
						toolTipStr = tr("Receiving of PDZs has been disabled in the recipient data box or there are no available payment methods for PDZs.") +
						    QStringLiteral("\n");
					}

					toolTipStr += headerData(PUBLIC_COL, Qt::Horizontal, Qt::DisplayRole).toString() +
					    QStringLiteral(": ") + nilBoolString(publ) + QStringLiteral("\n");
					toolTipStr += headerData(PDZ_COL, Qt::Horizontal, Qt::DisplayRole).toString() +
					    QStringLiteral(": ") + nilBoolString(pdz);

					return toolTipStr;
				} else {
					return tr("Sender has the effective OVM flag set.");
				}
			}
			return QVariant();
			break;
		case BOX_TYPE_COL:
			{
				QVariant entry(_data(index, Qt::DisplayRole));
				if (!entry.isNull()) {
					return Isds::Description::descrDbType(
					    Isds::intVariant2DbType(entry));
				} else {
					return entry;
				}
			}
			break;
		case PUBLIC_COL:
			switch (Isds::variant2NilBool(_data(index, Qt::DisplayRole))) {
			case Isds::Type::BOOL_NULL:
				return QString("Cannot determine whether a public message can be sent.");
				break;
			case Isds::Type::BOOL_FALSE:
				return tr("Cannot send a public data message.");
				break;
			case Isds::Type::BOOL_TRUE:
				return tr("Can send a public data message.");
				break;
			default:
				return QString();
				break;
			}
			break;
		case PDZ_COL:
			switch (Isds::variant2NilBool(_data(index, Qt::DisplayRole))) {
			case Isds::Type::BOOL_NULL:
				return QString("Cannot determine whether a commercial message can be sent.");
				break;
			case Isds::Type::BOOL_FALSE:
				return tr("Cannot send a commercial data message.");
				break;
			case Isds::Type::BOOL_TRUE:
				return tr("Can send a commercial data message.");
				break;
			default:
				return QString();
				break;
			}
			break;
		case PAYMENT_COL:
			{
				QVariant entry(_data(index, Qt::DisplayRole));
				if (!entry.isNull()) {
					return dmTypeToolTipDescr(entry.toInt());
				} else {
					return QVariant();
				}
			}
			break;
		default:
			return QVariant();
			break;
		}
		break;
	case Qt::ForegroundRole:
		if (m_colouring && (index.column() == BOX_ID_COL)) {
			enum Isds::Type::NilBool publ = Isds::variant2NilBool(
			    _data(index.sibling(index.row(), PUBLIC_COL), Qt::DisplayRole));
			enum Isds::Type::NilBool pdz = Isds::variant2NilBool(
			    _data(index.sibling(index.row(), PDZ_COL), Qt::DisplayRole));
			if (!m_senderEffectiveOVM) {
				if ((publ == Isds::Type::BOOL_NULL) && (pdz == Isds::Type::BOOL_NULL)) {
					return QColor(Qt::darkGray);
				} else if ((publ == Isds::Type::BOOL_NULL) && (pdz == Isds::Type::BOOL_FALSE)) {
					return QColor(Qt::darkGray);
				} else if ((publ == Isds::Type::BOOL_FALSE) && (pdz == Isds::Type::BOOL_NULL)) {
					return QColor(Qt::darkGray);
				} else if ((publ == Isds::Type::BOOL_FALSE) && (pdz == Isds::Type::BOOL_FALSE)) {
					return QColor(Qt::darkRed);
				}
			}
			return QVariant();
		}
		return QVariant();
		break;
	case Qt::CheckStateRole:
		if (index.column() == CHECKBOX_COL) {
			return _data(index, Qt::DisplayRole).toBool() ?
			    Qt::Checked : Qt::Unchecked;
		} else {
			return QVariant();
		}
		break;
	case Qt::AccessibleTextRole:
		/*
		 * Most of the accessibility data are constructed using
		 * the header data followed by the cell content.
		 */
		switch (index.column()) {
		case CHECKBOX_COL:
			return _data(index, Qt::DisplayRole).toBool() ?
			    tr("selected") : tr("not selected");
			break;
		case BOX_ID_COL:
			return tr("box identifier") + QLatin1String(" ") +
			    data(index).toString();
			break;
		case BOX_TYPE_COL:
			return headerData(index.column(), Qt::Horizontal).toString() +
			    QLatin1String(" ") +
			    data(index, Qt::ToolTipRole).toString();
			break;
		case BOX_NAME_COL:
		case ADDRESS_COL:
		case POST_CODE_COL:
		case PUBLIC_COL:
		case PDZ_COL:
		case PAYMENT_COL:
			return headerData(index.column(), Qt::Horizontal).toString() +
			    QLatin1String(" ") +
			    data(index).toString();
			break;
		default:
			return QVariant();
			break;
		}
		break;
	case ROLE_PROXYSORT:
		switch (index.column()) {
		case CHECKBOX_COL:
		case PUBLIC_COL:
		case PDZ_COL:
			{
				QString rankKey("2");
				switch (Isds::variant2NilBool(_data(index, Qt::DisplayRole))) {
				case Isds::Type::BOOL_NULL:
					rankKey = "0";
					break;
				case Isds::Type::BOOL_FALSE:
					rankKey = "1";
					break;
				case Isds::Type::BOOL_TRUE:
					rankKey = "2";
					break;
				default:
					break;
				}
				return ModelHelper::sortRank(rankKey, getBoxId(index));
			}
			break;
		case BOX_TYPE_COL:
			return ModelHelper::sortRank(
			    data(index, Qt::DisplayRole).toString(),
			    getBoxId(index));
			break;
		default:
			return _data(index, Qt::DisplayRole);
			break;
		}
		break;
	default:
		return _data(index, role);
		break;
	}
}

Qt::ItemFlags BoxContactsModel::flags(const QModelIndex &index) const
{
	Qt::ItemFlags defaultFlags = TblModel::flags(index);

	switch (index.column()) {
	case CHECKBOX_COL:
		defaultFlags |= Qt::ItemIsUserCheckable;
		break;
	case PAYMENT_COL:
		{
			/* Set editable only when there are several choices. */
			QList<QVariant> entries =
			    _data(index.sibling(index.row(), PAYMENTS_COL), Qt::DisplayRole).toList();
			if (entries.size() > 1) {
				defaultFlags |= Qt::ItemIsEditable;
			}
		}
		break;
	default:
		break;
	}

	return defaultFlags;
}

bool BoxContactsModel::setData(const QModelIndex &index, const QVariant &value,
    int role)
{
	if (Q_UNLIKELY(!index.isValid())) {
		return false;
	}

	int row = index.row();
	if (Q_UNLIKELY((row < 0) || (row >= m_rowCount))) {
		return false;
	}

	if ((index.column() == CHECKBOX_COL) && (role == Qt::CheckStateRole)) {
		bool newVal = (value == Qt::Checked);
		if (newVal != _data(index, Qt::DisplayRole).toBool()) {
			m_data[row][CHECKBOX_COL] = newVal;
			emit dataChanged(index, index);
			return true;
		}
	} else if ((index.column() == PAYMENT_COL) && (role == Qt::EditRole)) {
		if (value != _data(index, Qt::DisplayRole).toInt()) {
			m_data[row][PAYMENT_COL] = value;
			emit dataChanged(index, index);
			return true;
		}
	} else {
		return TblModel::setData(index, value, role);
	}

	return false;
}

void BoxContactsModel::setHeader(void)
{
	setHeaderData(BOX_ID_COL, Qt::Horizontal, tr("ID"), Qt::DisplayRole);
	setHeaderData(BOX_TYPE_COL, Qt::Horizontal, tr("Type"),
	    Qt::DisplayRole);
	setHeaderData(BOX_NAME_COL, Qt::Horizontal, tr("Name"),
	    Qt::DisplayRole);
	setHeaderData(ADDRESS_COL, Qt::Horizontal, tr("Address"),
	    Qt::DisplayRole);
	setHeaderData(POST_CODE_COL, Qt::Horizontal, tr("Postal Code"),
	    Qt::DisplayRole);
	setHeaderData(PUBLIC_COL, Qt::Horizontal, tr("Public Message"),
	    Qt::DisplayRole);
	setHeaderData(PDZ_COL, Qt::Horizontal, tr("PDZ"), Qt::DisplayRole);
	setHeaderData(PAYMENT_COL, Qt::Horizontal, tr("PDZ Payment"),
	    Qt::DisplayRole);
	setHeaderData(PAYMENT_COL, Qt::Horizontal,
	    tr("Determines the type of the commercial message and the means of payment."),
	    Qt::ToolTipRole);
}

void BoxContactsModel::appendData(
    const QList<Isds::DbOwnerInfo> &entryList)
{
	if (Q_UNLIKELY(entryList.isEmpty())) {
		return;
	}

	beginInsertRows(QModelIndex(), rowCount(),
	    rowCount() + entryList.size() - 1);

	foreach (const Isds::DbOwnerInfo &entry, entryList) {

		reserveSpace();

		QVector<QVariant> row(m_columnCount);

		row[CHECKBOX_COL] = false;
		row[BOX_ID_COL] = entry.dbID();
		row[BOX_TYPE_COL] = Isds::dbType2IntVariant(entry.dbType());
		row[BOX_NAME_COL] = Isds::textOwnerName(entry);
		row[ADDRESS_COL] = Isds::textAddressWithoutIc(entry.address());
		row[POST_CODE_COL] = entry.address().zipCode();
		row[PUBLIC_COL] = Isds::nilBool2Variant(entry.dbEffectiveOVM());
		row[PDZ_COL] = Isds::nilBool2Variant(
		    (entry.dbEffectiveOVM() == Isds::Type::BOOL_NULL) ? Isds::Type::BOOL_NULL :
		        (entry.dbEffectiveOVM() == Isds::Type::BOOL_TRUE ? Isds::Type::BOOL_FALSE : Isds::Type::BOOL_TRUE));
		//row[PAYMENT_COL];
		//row[PAYMENTS_COL];

		m_data[m_rowCount++] = macroStdMove(row);
	}

	endInsertRows();
}

void BoxContactsModel::appendData(const QList<Isds::FulltextResult> &entryList)
{
	if (Q_UNLIKELY(entryList.isEmpty())) {
		return;
	}

	beginInsertRows(QModelIndex(), rowCount(),
	    rowCount() + entryList.size() - 1);

	foreach (const Isds::FulltextResult &entry, entryList) {

		reserveSpace();

		QVector<QVariant> row(m_columnCount);

		row[CHECKBOX_COL] = false;
		row[BOX_ID_COL] = entry.dbID();
		row[BOX_TYPE_COL] = Isds::dbType2IntVariant(entry.dbType());
		row[BOX_NAME_COL] = entry.dbName();
		row[ADDRESS_COL] = entry.dbAddress();
		//row[POST_CODE_COL];
		row[PUBLIC_COL] = entry.publicSending();
		row[PDZ_COL] = entry.commercialSending();
		//row[PAYMENT_COL];
		//row[PAYMENTS_COL];

		m_data[m_rowCount++] = macroStdMove(row);
	}

	endInsertRows();
}

void BoxContactsModel::appendData(
    const QList<MessageDb::ContactEntry> &entryList)
{
	if (Q_UNLIKELY(entryList.isEmpty())) {
		return;
	}

	beginInsertRows(QModelIndex(), rowCount(),
	    rowCount() + entryList.size() - 1);

	foreach (const MessageDb::ContactEntry &entry, entryList) {

		reserveSpace();

		QVector<QVariant> row(m_columnCount);

		row[CHECKBOX_COL] = false;
		row[BOX_ID_COL] = entry.boxId;
		//row[BOX_TYPE_COL];
		row[BOX_NAME_COL] = entry.name;
		row[ADDRESS_COL] = entry.address;
		//row[POST_CODE_COL];
		//row[PUBLIC_COL];
		//row[PDZ_COL];
		//row[PAYMENT_COL];
		//row[PAYMENTS_COL];

		m_data[m_rowCount++] = macroStdMove(row);
	}

	endInsertRows();
}

void BoxContactsModel::appendData(const QString &id,
    enum Isds::Type::DbType type, const QString &name, const QString &addr,
    const QString &postCode, enum Isds::Type::NilBool publ,
    enum Isds::Type::NilBool pdz, const QList<enum Isds::Type::DmType> &dmTypes)
{
	/*
	 * We've already encountered some data boxes without any address.
	 * Don't know whether it's a bug in ISDS or just sloppiness.
	 * See issue #471.
	 */
	if (Q_UNLIKELY(id.isEmpty())) {
		return;
	}

	beginInsertRows(QModelIndex(), rowCount(), rowCount());

	{
		reserveSpace();

		QVector<QVariant> row(m_columnCount);

		row[CHECKBOX_COL] = false;
		row[BOX_ID_COL] = id;
		if (type != Isds::Type::BT_NULL) {
			row[BOX_TYPE_COL] = Isds::dbType2IntVariant(type);
		}
		row[BOX_NAME_COL] = name;
		row[ADDRESS_COL] = addr;
		row[POST_CODE_COL] = postCode;
		row[PUBLIC_COL] = Isds::nilBool2Variant(publ);
		row[PDZ_COL] = Isds::nilBool2Variant(pdz);

		QList<QVariant> dmTypeVariantList;
		switch (pdz) {
		case Isds::Type::BOOL_NULL: /* Undefined value. */
			row[PAYMENT_COL] = Isds::Type::MT_UNKNOWN;
			dmTypeVariantList.append(Isds::Type::MT_UNKNOWN);
			break;
		case Isds::Type::BOOL_FALSE:
			row[PAYMENT_COL] = Isds::Type::MT_V;
			dmTypeVariantList.append(Isds::Type::MT_V);
			break;
		case Isds::Type::BOOL_TRUE:
			foreach (enum Isds::Type::DmType dmType, dmTypes) {
				dmTypeVariantList.append(dmType);
			}
			if (dmTypeVariantList.count() > 0) {
				row[PAYMENT_COL] = dmTypeVariantList.at(0);
			} else {
				row[PAYMENT_COL] = Isds::Type::MT_UNKNOWN;
			}
			break;
		default:
			Q_ASSERT(0);
			break;
		}

		row[PAYMENTS_COL] = dmTypeVariantList;

		m_data[m_rowCount++] = macroStdMove(row);
	}

	endInsertRows();
}

bool BoxContactsModel::somethingChecked(void) const
{
	for (int row = 0; row < m_rowCount; ++row) {
		if (m_data[row][CHECKBOX_COL].toBool()) {
			return true;
		}
	}

	return false;
}

bool BoxContactsModel::containsBoxId(const QString &boxId) const
{
	for (int row = 0; row < m_rowCount; ++row) {
		if (m_data[row][BOX_ID_COL].toString() == boxId) {
			return true;
		}
	}

	return false;
}

/*!
 * @brief Compares the check state according to required criteria.
 *
 * @param[in] checked Check state from the model.
 * @param[in] entryState User required check state.
 * @return True if state matches the criteria.
 */
static inline
bool checkStateMatch(bool checked, enum BoxContactsModel::EntryState entryState)
{
	return (!checked && (entryState == BoxContactsModel::UNCHECKED)) ||
	    (checked && (entryState == BoxContactsModel::CHECKED)) ||
	    (entryState == BoxContactsModel::ANY);
}

QStringList BoxContactsModel::boxIdentifiers(enum EntryState entryState) const
{
	QStringList ids;

	for (int row = 0; row < m_rowCount; ++row) {
		if (checkStateMatch(m_data[row][CHECKBOX_COL].toBool(),
		        entryState)) {
			ids.append(m_data[row][BOX_ID_COL].toString());
		}
	}

	return ids;
}

QList<BoxContactsModel::PartialEntry> BoxContactsModel::partialBoxEntries(
    enum EntryState entryState) const
{
	QList<PartialEntry> entries;

	for (int row = 0; row < m_rowCount; ++row) {
		if (checkStateMatch(m_data[row][CHECKBOX_COL].toBool(),
		        entryState)) {
			PartialEntry entry;

			entry.id = m_data[row][BOX_ID_COL].toString();
			entry.name = m_data[row][BOX_NAME_COL].toString();
			entry.address = m_data[row][ADDRESS_COL].toString();
			entry.publ = Isds::variant2NilBool(m_data[row][PUBLIC_COL]);
			entry.pdz = Isds::variant2NilBool(m_data[row][PDZ_COL]);
			switch (entry.pdz) {
			case Isds::Type::BOOL_NULL: /* Undefined value. */
				break;
			case Isds::Type::BOOL_FALSE:
				entry.dmType = Isds::Type::MT_V;
				break;
			case Isds::Type::BOOL_TRUE:
				entry.dmType =
				    (enum Isds::Type::DmType)m_data[row][PAYMENT_COL].toInt();
				break;
			default:
				Q_ASSERT(0);
				break;
			}
			if (entry.pdz) {
			} else {
				entry.dmType = Isds::Type::MT_V;
			}
			entries.append(entry);
		}
	}

	return entries;
}

void BoxContactsModel::setColouring(bool colouring)
{
	if (m_colouring != colouring) {
		beginResetModel();
		m_colouring = colouring;
		endResetModel();
	}
}

void BoxContactsModel::setSenderEffectiveOVM(bool senderEffectiveOVM)
{
	const bool colouring = m_colouring;

	if (m_senderEffectiveOVM != senderEffectiveOVM) {
		/* Inly emit signals when colouring enabled. */
		if (colouring) {
			beginResetModel();
		}
		m_senderEffectiveOVM = senderEffectiveOVM;
		if (colouring) {
			endResetModel();
		}
	}
}

QString BoxContactsModel::descrDmType(int type)
{
	switch (type) {
	case Isds::Type::MT_UNKNOWN:
		return QString();
		break;
	case Isds::Type::MT_V:
		return tr("Public");
		break;
	case Isds::Type::MT_O:
		return tr("Response to initiatory");
		break;
	case Isds::Type::MT_G:
		return tr("Subsidised");
		break;
	case Isds::Type::MT_K:
		return tr("Contractual");
		break;
	case Isds::Type::MT_I:
		return tr("Initiatory");
		break;
	case Isds::Type::MT_E:
		return tr("Prepaid credit");
		break;
	default:
		return QString();
		break;
	}
}

QString BoxContactsModel::dmTypeToolTipDescr(int type)
{
	switch (type) {
	case Isds::Type::MT_UNKNOWN:
		return tr("Type of the message is not determined.");
		break;
	case Isds::Type::MT_V:
		return tr("A free-of-charge public data message will be sent to the recipient.");
		break;
	case Isds::Type::MT_O:
		/* The recipient offers pre-paid response commercial message to be sent to him. */
		return tr(
		    "The recipient offers a pre-paid commercial message response to be sent to him.\n"
		    "This setting allows you to use the offered payment.\n"
		    "The transfer charges are going to be paid by the recipient.");
		break;
	case Isds::Type::MT_G:
		return tr("Send a subsidised commercial data message.\n"
		    "Another data box is paying for the transfer charges.");
		break;
	case Isds::Type::MT_K:
		return tr("Send a contractual commercial data message.\n"
		    "The charges for sending this message are going to be billed by the Czech Post.\n"
		    "An active contract with the Czech Post is needed.");
		break;
	case Isds::Type::MT_I:
		return tr("Send an initiatory commercial data message.\n"
		    "This type of message enables the recipient to send a response on behalf of your data box.\n"
		    "A sender reference number must be filled in in order to send this type of message.\n"
		    "An active contract with the Czech Post or a subsidising data box is needed.");
		break;
	case Isds::Type::MT_E:
		return tr("Send a commercial data message.\n"
		    "Pay charges from a prepaid credit.");
		break;
	default:
		return QString();
		break;
	}
}

QString BoxContactsModel::getBoxId(const QModelIndex &index) const
{
	return _data(index.sibling(index.row(), BOX_ID_COL),
	    Qt::DisplayRole).toString();
}
