File indexing completed on 2024-05-12 16:42:19

0001 /*
0002     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "securitiesmodel.h"
0007 
0008 // ----------------------------------------------------------------------------
0009 // QT Includes
0010 
0011 #include <QMenu>
0012 
0013 // ----------------------------------------------------------------------------
0014 // KDE Includes
0015 
0016 #include <KLocalizedString>
0017 
0018 // ----------------------------------------------------------------------------
0019 // Project Includes
0020 
0021 #include "mymoneyfile.h"
0022 #include "mymoneysecurity.h"
0023 #include "mymoneyenums.h"
0024 
0025 class SecuritiesModel::Private
0026 {
0027 public:
0028     Private() :
0029         m_file(MyMoneyFile::instance()),
0030         m_ndCurrencies(nullptr),
0031         m_ndSecurities(nullptr)
0032     {
0033         QVector<Column> columns {
0034             Column::Security, Column::Symbol, Column::Type,
0035             Column::Market, Column::Currency, Column::Fraction
0036         };
0037         foreach (auto const column, columns)
0038             m_columns.append(column);
0039     }
0040 
0041     ~Private() {}
0042 
0043     void loadSecurity(QStandardItem *node, const MyMoneySecurity &sec)
0044     {
0045         auto itSec = new QStandardItem(sec.name());
0046         node->appendRow(itSec);
0047         itSec->setEditable(false);
0048         setSecurityData(node, itSec->row(), sec, m_columns);
0049     }
0050 
0051     void setSecurityData(QStandardItem *node, const int row, const MyMoneySecurity &security, const QList<Column> &columns)
0052     {
0053         QStandardItem *cell;
0054 
0055         auto getCell = [&, row](const auto column) {
0056             cell = node->child(row, column);      // try to get QStandardItem
0057             if (!cell) {                          // it may be uninitialized
0058                 cell = new QStandardItem;           // so create one
0059                 node->setChild(row, column, cell);  // and add it under the node
0060                 cell->setEditable(false);           // and don't forget that it's non-editable
0061             }
0062         };
0063 
0064         auto colNum = m_columns.indexOf(Column::Security);
0065         if (colNum == -1)
0066             return;
0067 
0068         // Security
0069         getCell(colNum);
0070         if (columns.contains(Column::Security)) {
0071             cell->setData(security.name(), Qt::DisplayRole);
0072             cell->setData(security.id(), Qt::UserRole);
0073         }
0074 
0075         // Symbol
0076         if (columns.contains(Column::Symbol)) {
0077             colNum = m_columns.indexOf(Column::Symbol);
0078             if (colNum != -1) {
0079                 getCell(colNum);
0080                 cell->setData(security.tradingSymbol(), Qt::DisplayRole);
0081             }
0082         }
0083 
0084         // Type
0085         if (columns.contains(Column::Type)) {
0086             colNum = m_columns.indexOf(Column::Type);
0087             if (colNum != -1) {
0088                 getCell(colNum);
0089                 cell->setData(security.securityTypeToString(security.securityType()), Qt::DisplayRole);
0090             }
0091         }
0092 
0093         // Market
0094         if (columns.contains(Column::Market)) {
0095             colNum = m_columns.indexOf(Column::Market);
0096             if (colNum != -1) {
0097                 getCell(colNum);
0098                 QString market;
0099                 if (security.isCurrency())
0100                     market = QLatin1String("ISO 4217");
0101                 else
0102                     market = security.tradingMarket();
0103                 cell->setData(market, Qt::DisplayRole);
0104             }
0105         }
0106 
0107         // Currency
0108         if (columns.contains(Column::Currency)) {
0109             colNum = m_columns.indexOf(Column::Currency);
0110             if (colNum != -1) {
0111                 getCell(colNum);
0112                 MyMoneySecurity tradingCurrency;
0113                 if (!security.isCurrency())
0114                     tradingCurrency = m_file->security(security.tradingCurrency());
0115                 cell->setData(tradingCurrency.tradingSymbol(), Qt::DisplayRole);
0116             }
0117         }
0118 
0119         // Fraction
0120         if (columns.contains(Column::Fraction)) {
0121             colNum = m_columns.indexOf(Column::Fraction);
0122             if (colNum != -1) {
0123                 getCell(colNum);
0124                 cell->setData(QString::number(security.smallestAccountFraction()), Qt::DisplayRole);
0125             }
0126         }
0127     }
0128 
0129     QStandardItem *itemFromSecurityId(QStandardItemModel *model, const QString &securityId)
0130     {
0131         const auto itemList = model->match(model->index(0, 0), Qt::UserRole, QVariant(securityId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive));
0132         if (!itemList.isEmpty())
0133             return model->itemFromIndex(itemList.first());
0134         return nullptr;
0135     }
0136 
0137     MyMoneyFile *m_file;
0138     QList<SecuritiesModel::Column> m_columns;
0139     QStandardItem *m_ndCurrencies;
0140     QStandardItem *m_ndSecurities;
0141 };
0142 
0143 SecuritiesModel::SecuritiesModel(QObject *parent)
0144     : QStandardItemModel(parent), d(new Private)
0145 {
0146     init();
0147 }
0148 
0149 SecuritiesModel::~SecuritiesModel()
0150 {
0151     delete d;
0152 }
0153 
0154 void SecuritiesModel::init()
0155 {
0156     QStringList headerLabels;
0157     foreach (const auto column, d->m_columns)
0158         headerLabels.append(getHeaderName(column));
0159     setHorizontalHeaderLabels(headerLabels);
0160 }
0161 
0162 void SecuritiesModel::load()
0163 {
0164     this->blockSignals(true);
0165 
0166     auto rootItem = invisibleRootItem();
0167     auto secList = d->m_file->securityList();   // get all available securities
0168     d->m_ndSecurities = new QStandardItem(QStringLiteral("Securities"));
0169     d->m_ndSecurities->setEditable(false);
0170     rootItem->appendRow(d->m_ndSecurities);
0171 
0172     foreach (const auto sec, secList)
0173         d->loadSecurity(d->m_ndSecurities, sec);
0174 
0175     secList = d->m_file->currencyList();   // get all available currencies
0176     d->m_ndCurrencies = new QStandardItem(QStringLiteral("Currencies"));
0177     d->m_ndCurrencies->setEditable(false);
0178     rootItem->appendRow(d->m_ndCurrencies);
0179     foreach (const auto sec, secList)
0180         d->loadSecurity(d->m_ndCurrencies, sec);
0181 
0182     this->blockSignals(false);
0183 }
0184 
0185 /**
0186   * Notify the model that an object has been added. An action is performed only if the object is a security.
0187   *
0188   */
0189 void SecuritiesModel::slotObjectAdded(eMyMoney::File::Object objType, const QString& id)
0190 {
0191     // check whether change is about security
0192     if (objType != eMyMoney::File::Object::Security)
0193         return;
0194 
0195     // check that we're about to add security
0196     auto sec = MyMoneyFile::instance()->security(id);
0197 
0198     auto itSec = d->itemFromSecurityId(this, id);
0199 
0200     QStandardItem *node;
0201     if (sec.isCurrency())
0202         node = d->m_ndCurrencies;
0203     else
0204         node = d->m_ndSecurities;
0205 
0206     // if security doesn't exist in model then add it
0207     if (!itSec) {
0208         itSec = new QStandardItem(sec.name());
0209         node->appendRow(itSec);
0210         itSec->setEditable(false);
0211     }
0212 
0213     d->setSecurityData(node, itSec->row(), sec, d->m_columns);
0214 }
0215 
0216 /**
0217   * Notify the model that an object has been modified. An action is performed only if the object is a security.
0218   *
0219   */
0220 void SecuritiesModel::slotObjectModified(eMyMoney::File::Object objType, const QString& id)
0221 {
0222     if (objType != eMyMoney::File::Object::Security)
0223         return;
0224 
0225     // check that we're about to modify security
0226     auto sec = MyMoneyFile::instance()->security(id);
0227 
0228     auto itSec = d->itemFromSecurityId(this, id);
0229 
0230     QStandardItem *node;
0231     if (sec.isCurrency())
0232         node = d->m_ndCurrencies;
0233     else
0234         node = d->m_ndSecurities;
0235     d->setSecurityData(node, itSec->row(), sec, d->m_columns);
0236 }
0237 
0238 /**
0239   * Notify the model that an object has been removed. An action is performed only if the object is an account.
0240   *
0241   */
0242 void SecuritiesModel::slotObjectRemoved(eMyMoney::File::Object objType, const QString& id)
0243 {
0244     if (objType != eMyMoney::File::Object::Security)
0245         return;
0246 
0247     const auto indexList = match(index(0, 0), Qt::UserRole, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
0248     foreach (const auto index, indexList)
0249         removeRow(index.row(), index.parent());
0250 }
0251 
0252 auto SecuritiesModel::getColumns()
0253 {
0254     return &d->m_columns;
0255 }
0256 
0257 QString SecuritiesModel::getHeaderName(const Column column)
0258 {
0259     switch(column) {
0260     case Security:
0261         return i18n("Security");
0262     case Symbol:
0263         return i18nc("@title stock symbol column", "Symbol");
0264     case Type:
0265         return i18n("Type");
0266     case Market:
0267         return i18n("Market");
0268     case Currency:
0269         return i18n("Currency");
0270     case Fraction:
0271         return i18n("Fraction");
0272     default:
0273         return QString();
0274     }
0275 }
0276 
0277 class SecuritiesFilterProxyModel::Private
0278 {
0279 public:
0280     Private() :
0281         m_mdlColumns(nullptr),
0282         m_file(MyMoneyFile::instance())
0283     {}
0284 
0285     ~Private() {}
0286 
0287     QList<SecuritiesModel::Column> *m_mdlColumns;
0288     QList<SecuritiesModel::Column> m_visColumns;
0289 
0290     MyMoneyFile *m_file;
0291 };
0292 
0293 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
0294 #define QSortFilterProxyModel KRecursiveFilterProxyModel
0295 #endif
0296 SecuritiesFilterProxyModel::SecuritiesFilterProxyModel(QObject *parent, SecuritiesModel *model, const QList<SecuritiesModel::Column> &columns)
0297     : QSortFilterProxyModel(parent), d(new Private)
0298 {
0299     setRecursiveFilteringEnabled(true);
0300     setDynamicSortFilter(true);
0301     setFilterKeyColumn(-1);
0302     setSortLocaleAware(true);
0303     setFilterCaseSensitivity(Qt::CaseInsensitive);
0304     setSourceModel(model);
0305     d->m_mdlColumns = model->getColumns();
0306     d->m_visColumns.append(columns);
0307 }
0308 #undef QSortFilterProxyModel
0309 
0310 SecuritiesFilterProxyModel::~SecuritiesFilterProxyModel()
0311 {
0312     delete d;
0313 }
0314 
0315 bool SecuritiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
0316 {
0317     Q_UNUSED(source_parent)
0318     if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column)))
0319         return true;
0320     return false;
0321 }
0322 
0323 QList<SecuritiesModel::Column> &SecuritiesFilterProxyModel::getVisibleColumns()
0324 {
0325     return d->m_visColumns;
0326 }
0327 
0328 void SecuritiesFilterProxyModel::slotColumnsMenu(const QPoint)
0329 {
0330     // construct all hideable columns list
0331     const QList<SecuritiesModel::Column> idColumns {
0332         SecuritiesModel::Symbol, SecuritiesModel::Type,
0333         SecuritiesModel::Market, SecuritiesModel::Currency,
0334         SecuritiesModel::Fraction
0335     };
0336 
0337     // create menu
0338     QMenu menu(i18n("Displayed columns"));
0339     QList<QAction *> actions;
0340     foreach (const auto idColumn, idColumns) {
0341         auto a = new QAction(nullptr);
0342         a->setObjectName(QString::number(idColumn));
0343         a->setText(SecuritiesModel::getHeaderName(idColumn));
0344         a->setCheckable(true);
0345         a->setChecked(d->m_visColumns.contains(idColumn));
0346         actions.append(a);
0347     }
0348     menu.addActions(actions);
0349 
0350     // execute menu and get result
0351     const auto retAction = menu.exec(QCursor::pos());
0352     if (retAction) {
0353         const auto idColumn = static_cast<SecuritiesModel::Column>(retAction->objectName().toInt());
0354         const auto isChecked = retAction->isChecked();
0355         const auto contains = d->m_visColumns.contains(idColumn);
0356         if (isChecked && !contains) {           // column has just been enabled
0357             d->m_visColumns.append(idColumn);     // change filtering variable
0358             emit columnToggled(idColumn, true);   // emit signal for method to add column to model
0359             invalidate();                         // refresh model to reflect recent changes
0360         } else if (!isChecked && contains) {    // column has just been disabled
0361             d->m_visColumns.removeOne(idColumn);
0362             emit columnToggled(idColumn, false);
0363             invalidate();
0364         }
0365     }
0366 }