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 }