diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -2860,26 +2860,27 @@ void KMyMoneyApp::slotInvestmentNew() { - KNewInvestmentWizard dlg; - if (dlg.exec() == QDialog::Accepted) { - dlg.createObjects(d->m_selectedAccount.id()); - } + QPointer dlg = new KNewInvestmentWizard(this); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(d->m_selectedAccount.id()); + delete dlg; } void KMyMoneyApp::slotInvestmentEdit() { - KNewInvestmentWizard dlg(d->m_selectedInvestment); - if (dlg.exec() == QDialog::Accepted) { - dlg.createObjects(d->m_selectedAccount.id()); - } + QPointer dlg = new KNewInvestmentWizard(d->m_selectedInvestment); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(d->m_selectedAccount.id()); + delete dlg; } void KMyMoneyApp::slotInvestmentDelete() { if (KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the investment %1?

", d->m_selectedInvestment.name()), i18n("Delete investment"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteInvestment") == KMessageBox::Yes) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { + d->m_selectedAccount = MyMoneyAccount(); // CAUTION: deleting equity from investments view needs this, if ID of the equity to be deleted is the smallest from all file->removeAccount(d->m_selectedInvestment); ft.commit(); } catch (const MyMoneyException &e) { diff --git a/kmymoney/models/CMakeLists.txt b/kmymoney/models/CMakeLists.txt --- a/kmymoney/models/CMakeLists.txt +++ b/kmymoney/models/CMakeLists.txt @@ -8,6 +8,8 @@ models.cpp payeeidentifiercontainermodel.cpp onlinejobmessagesmodel.cpp + equitiesmodel.cpp + securitiesmodel.cpp ) if (USE_MODELTEST) diff --git a/kmymoney/models/equitiesmodel.h b/kmymoney/models/equitiesmodel.h new file mode 100644 --- /dev/null +++ b/kmymoney/models/equitiesmodel.h @@ -0,0 +1,94 @@ +/*************************************************************************** + equitiesmodel.h + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef EQUITIESMODEL_H +#define EQUITIESMODEL_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include + +class EquitiesModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Column { Equity = 0, Symbol, Value, Quantity, Price }; + enum Role { InvestmentID = Qt::UserRole, EquityID = Qt::UserRole, SecurityID = Qt::UserRole + 1 }; + + ~EquitiesModel(); + + auto getColumns(); + static QString getHeaderName(const Column column); + +public slots: + void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id); + void slotBalanceOrValueChanged(const MyMoneyAccount &account); + +private: + EquitiesModel(QObject *parent = nullptr); + EquitiesModel(const EquitiesModel&); + EquitiesModel& operator=(EquitiesModel&); + friend class Models; // only this class can create EquitiesModel + + void init(); + void load(); + +protected: + class Private; + Private* const d; +}; + +class EquitiesFilterProxyModel : public KRecursiveFilterProxyModel +{ + Q_OBJECT + +public: + EquitiesFilterProxyModel(QObject *parent , EquitiesModel *model, const QList &columns = QList()); + ~EquitiesFilterProxyModel(); + + QList &getVisibleColumns(); + void setHideClosedAccounts(const bool hideClosedAccounts); + void setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts); + +signals: + void columnToggled(const EquitiesModel::Column column, const bool show); + +public slots: + void slotColumnsMenu(const QPoint); + +protected: + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + class Private; + Private* const d; +}; + +#endif // EQUITIESMODEL_H diff --git a/kmymoney/models/equitiesmodel.cpp b/kmymoney/models/equitiesmodel.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/models/equitiesmodel.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + equitiesmodel.cpp + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "equitiesmodel.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +class EquitiesModel::Private +{ +public: + Private() : m_file(MyMoneyFile::instance()) + { + QVector columns {Column::Equity, Column::Symbol, Column::Value, + Column::Quantity, Column::Price}; + foreach (auto const column, columns) + m_columns.append(column); + } + + ~Private() {} + + void loadInvestmentAccount(QStandardItem *node, const MyMoneyAccount &invAcc) + { + auto itInvAcc = new QStandardItem(invAcc.name()); + node->appendRow(itInvAcc); // investment account is meant to be added under root item + itInvAcc->setEditable(false); + itInvAcc->setColumnCount(m_columns.count()); + setAccountData(node, itInvAcc->row(), invAcc, m_columns); + + const auto strStkAccList = invAcc.accountList(); // only stock or bond accounts are expected here + foreach (const auto strStkAcc, strStkAccList) { + auto stkAcc = m_file->account(strStkAcc); + auto itStkAcc = new QStandardItem(strStkAcc); + itStkAcc->setEditable(false); + itInvAcc->appendRow(itStkAcc); + setAccountData(itInvAcc, itStkAcc->row(), stkAcc, m_columns); + } + } + + void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Equity); + if (colNum == -1) + return; + + // Equity + getCell(colNum); + if (columns.contains(Column::Equity)) { + cell->setData(account.name(), Qt::DisplayRole); + cell->setData(account.id(), Role::EquityID); + cell->setData(account.currencyId(), Role::SecurityID); + } + + if (account.accountType() == MyMoneyAccount::Investment) // investments accounts are not meant to be displayed, so stop here + return; + + // Symbol + if (columns.contains(Column::Symbol)) { + colNum = m_columns.indexOf(Column::Symbol); + if (colNum != -1) { + auto security = m_file->security(account.currencyId()); + getCell(colNum); + cell->setData(security.tradingSymbol(), Qt::DisplayRole); + } + } + + setAccountBalanceAndValue(node, row, account, columns); + } + + void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Equity); + if (colNum == -1) + return; + + auto balance = m_file->balance(account.id()); + auto security = m_file->security(account.currencyId()); + auto tradingCurrency = m_file->security(security.tradingCurrency()); + auto price = m_file->price(account.currencyId(), tradingCurrency.id()); + + // Value + if (columns.contains(Column::Value)) { + colNum = m_columns.indexOf(Column::Value); + if (colNum != -1) { + getCell(colNum); + if (price.isValid()) { + auto prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction()); + auto value = balance * price.rate(tradingCurrency.id()); + auto strValue = QVariant(value.formatMoney(tradingCurrency.tradingSymbol(), prec)); + cell->setData(strValue, Qt::DisplayRole); + } else { + cell->setData(QVariant("---"), Qt::DisplayRole); + } + cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); + } + } + + // Quantity + if (columns.contains(Column::Quantity)) { + colNum = m_columns.indexOf(Column::Quantity); + if (colNum != -1) { + getCell(colNum); + auto prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); + auto strQuantity = QVariant(balance.formatMoney(QString(), prec)); + cell->setData(strQuantity, Qt::DisplayRole); + } + } + + // Price + if (columns.contains(Column::Price)) { + colNum = m_columns.indexOf(Column::Price); + if (colNum != -1) { + getCell(colNum); + if (price.isValid()) { + auto prec = security.pricePrecision(); + auto strPrice = QVariant(price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec)); + cell->setData(strPrice, Qt::DisplayRole); + } else { + cell->setData(QVariant("---"), Qt::DisplayRole); + } + cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); + } + } + } + + QStandardItem *itemFromId(QStandardItemModel *model, const QString &id, const Role role) + { + const auto itemList = model->match(model->index(0, 0), role, QVariant(id), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); + if (!itemList.isEmpty()) + return model->itemFromIndex(itemList.first()); + return nullptr; + } + + MyMoneyFile *m_file; + QList m_columns; +}; + +EquitiesModel::EquitiesModel(QObject *parent) + : QStandardItemModel(parent), d(new Private) +{ + init(); +} + +EquitiesModel::~EquitiesModel() +{ + delete d; +} + +void EquitiesModel::init() +{ + QStringList headerLabels; + foreach (const auto column, d->m_columns) + headerLabels.append(getHeaderName(column)); + setHorizontalHeaderLabels(headerLabels); +} + +void EquitiesModel::load() +{ + this->blockSignals(true); + + auto rootItem = invisibleRootItem(); + QList accList; + d->m_file->accountList(accList); // get all available accounts + foreach (const auto acc, accList) + if (acc.accountType() == MyMoneyAccount::Investment) // but add only investment accounts (and its children) to the model + d->loadInvestmentAccount(rootItem, acc); + + this->blockSignals(false); +} + +/** + * Notify the model that an object has been added. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + // check whether change is about accounts + if (objType != MyMoneyFile::notifyAccount) + return; + + // check whether change is about either investment or stock account + const auto acc = dynamic_cast(obj); + if (!acc || + (acc->accountType() != MyMoneyAccount::Investment && + acc->accountType() != MyMoneyAccount::Stock)) + return; + auto itAcc = d->itemFromId(this, acc->id(), Role::EquityID); + + QStandardItem *itParentAcc; + if (acc->accountType() == MyMoneyAccount::Investment) // if it's investment account then its parent is root item + itParentAcc = invisibleRootItem(); + else // otherwise it's stock account and its parent is investment account + itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID); + + // if account doesn't exist in model then add it + if (!itAcc) { + itAcc = new QStandardItem(acc->name()); + itParentAcc->appendRow(itAcc); + itAcc->setEditable(false); + } + + d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns); +} + +/** + * Notify the model that an object has been modified. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + const MyMoneyAccount *acc; + QStandardItem *itAcc; + switch (objType) { + case MyMoneyFile::notifyAccount: + { + auto tmpAcc = dynamic_cast(obj); + if (!tmpAcc || tmpAcc->accountType() != MyMoneyAccount::Stock) + return; + acc = tmpAcc; + itAcc = d->itemFromId(this, acc->id(), Role::EquityID); + break; + } + case MyMoneyFile::notifySecurity: + { + auto sec = dynamic_cast(obj); + itAcc = d->itemFromId(this, sec->id(), Role::SecurityID); + if (!itAcc) + return; + const auto idAcc = itAcc->data(Role::EquityID).toString(); + acc = &d->m_file->account(idAcc); + break; + } + default: + return; + } + + auto itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID); + + auto modelID = itParentAcc->data(Role::InvestmentID).toString(); // get parent account from model + if (modelID == acc->parentAccountId()) { // and if it matches with those from file then modify only + d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns); + } else { // and if not then reparent + slotObjectRemoved(MyMoneyFile::notifyAccount, acc->id()); + slotObjectAdded(MyMoneyFile::notifyAccount, obj); + } +} + +/** + * Notify the model that an object has been removed. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id) +{ + if (objType != MyMoneyFile::notifyAccount) + return; + + const auto indexList = match(index(0, 0), Role::EquityID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); + foreach (const auto index, indexList) + removeRow(index.row(), index.parent()); +} + +/** + * Notify the model that the account balance has been changed. + */ +void EquitiesModel::slotBalanceOrValueChanged(const MyMoneyAccount &account) +{ + if (account.accountType() != MyMoneyAccount::Stock) + return; + + const auto itAcc = d->itemFromId(this, account.id(), Role::EquityID); + if (!itAcc) + return; + d->setAccountBalanceAndValue(itAcc->parent(), itAcc->row(), account, d->m_columns); +} + +auto EquitiesModel::getColumns() +{ + return &d->m_columns; +} + +QString EquitiesModel::getHeaderName(const Column column) +{ + switch(column) { + case Equity: + return i18n("Equity"); + case Symbol: + return i18n("Symbol"); + case Value: + return i18n("Value"); + case Quantity: + return i18n("Quantity"); + case Price: + return i18n("Price"); + default: + return QString(); + } +} + +class EquitiesFilterProxyModel::Private +{ +public: + Private() : + m_mdlColumns(nullptr), + m_file(MyMoneyFile::instance()), + m_hideClosedAccounts(false), + m_hideZeroBalanceAccounts(false) + {} + + ~Private() {} + + QList *m_mdlColumns; + QList m_visColumns; + + MyMoneyFile *m_file; + + bool m_hideClosedAccounts; + bool m_hideZeroBalanceAccounts; +}; + +EquitiesFilterProxyModel::EquitiesFilterProxyModel(QObject *parent, EquitiesModel *model, const QList &columns) + : KRecursiveFilterProxyModel(parent), d(new Private) +{ + setDynamicSortFilter(true); + setFilterKeyColumn(-1); + setSortLocaleAware(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSourceModel(model); + d->m_mdlColumns = model->getColumns(); + d->m_visColumns.append(columns); +} + +EquitiesFilterProxyModel::~EquitiesFilterProxyModel() +{ + delete d; +} + +/** + * Set if closed accounts should be hidden or not. + * @param hideClosedAccounts + */ +void EquitiesFilterProxyModel::setHideClosedAccounts(const bool hideClosedAccounts) +{ + d->m_hideClosedAccounts = hideClosedAccounts; +} + +/** + * Set if zero balance accounts should be hidden or not. + * @param hideZeroBalanceAccounts + */ +void EquitiesFilterProxyModel::setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts) +{ + d->m_hideZeroBalanceAccounts = hideZeroBalanceAccounts; +} + +bool EquitiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) + return true; + return false; +} + +bool EquitiesFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (d->m_hideClosedAccounts || d->m_hideZeroBalanceAccounts) { + const auto ixRow = sourceModel()->index(source_row, EquitiesModel::Equity, source_parent); + const auto idAcc = sourceModel()->data(ixRow, EquitiesModel::EquityID).toString(); + const auto acc = d->m_file->account(idAcc); + + if (d->m_hideClosedAccounts && + acc.isClosed()) + return false; + if (d->m_hideZeroBalanceAccounts && + acc.accountType() != MyMoneyAccount::Investment && acc.balance().isZero()) // we should never hide investment account because all underlaying stocks will be hidden as well + return false; + } + return true; +} + +QList &EquitiesFilterProxyModel::getVisibleColumns() +{ + return d->m_visColumns; +} + +void EquitiesFilterProxyModel::slotColumnsMenu(const QPoint) +{ + // construct all hideable columns list + const QList idColumns { + EquitiesModel::Symbol, EquitiesModel::Value, + EquitiesModel::Quantity, EquitiesModel::Price + }; + + // create menu + QMenu menu(i18n("Displayed columns")); + QList actions; + foreach (const auto idColumn, idColumns) { + auto a = new QAction(nullptr); + a->setObjectName(QString::number(idColumn)); + a->setText(EquitiesModel::getHeaderName(idColumn)); + a->setCheckable(true); + a->setChecked(d->m_visColumns.contains(idColumn)); + actions.append(a); + } + menu.addActions(actions); + + // execute menu and get result + const auto retAction = menu.exec(QCursor::pos()); + if (retAction) { + const auto idColumn = static_cast(retAction->objectName().toInt()); + const auto isChecked = retAction->isChecked(); + const auto contains = d->m_visColumns.contains(idColumn); + if (isChecked && !contains) { // column has just been enabled + d->m_visColumns.append(idColumn); // change filtering variable + emit columnToggled(idColumn, true); // emit signal for method to add column to model + invalidate(); // refresh model to reflect recent changes + } else if (!isChecked && contains) { // column has just been disabled + d->m_visColumns.removeOne(idColumn); + emit columnToggled(idColumn, false); + invalidate(); + } + } +} diff --git a/kmymoney/models/models.h b/kmymoney/models/models.h --- a/kmymoney/models/models.h +++ b/kmymoney/models/models.h @@ -41,6 +41,8 @@ class LedgerModel; class CostCenterModel; class PayeesModel; +class EquitiesModel; +class SecuritiesModel; /** * This object is the owner and maintainer of all the core models of KMyMoney. @@ -78,6 +80,8 @@ LedgerModel* ledgerModel(); CostCenterModel* costCenterModel(); PayeesModel* payeesModel(); + EquitiesModel* equitiesModel(); + SecuritiesModel* securitiesModel(); /** * returns the index of an item the @a model based on the @a id of role @a role. diff --git a/kmymoney/models/models.cpp b/kmymoney/models/models.cpp --- a/kmymoney/models/models.cpp +++ b/kmymoney/models/models.cpp @@ -36,6 +36,8 @@ #include "ledgermodel.h" #include "costcentermodel.h" #include "payeesmodel.h" +#include "equitiesmodel.h" +#include "securitiesmodel.h" #ifdef KMM_MODELTEST #include "modeltest.h" @@ -51,14 +53,18 @@ , m_ledgerModel(0) , m_costCenterModel(0) , m_payeesModel(0) + , m_equitiesModel(0) + , m_securitiesModel(0) {} AccountsModel *m_accountsModel; InstitutionsModel *m_institutionsModel; onlineJobModel *m_onlineJobModel; LedgerModel *m_ledgerModel; CostCenterModel *m_costCenterModel; PayeesModel *m_payeesModel; + EquitiesModel *m_equitiesModel; + SecuritiesModel *m_securitiesModel; }; @@ -172,6 +178,37 @@ return d->m_payeesModel; } +/** + * This is the function to get a reference to the core @ref EquitiesModel. + * The returned object is owned by this object so don't delete it. It creates the + * model on the first access to it. + */ +EquitiesModel* Models::equitiesModel() +{ + if (!d->m_equitiesModel) { + d->m_equitiesModel = new EquitiesModel(this); + #ifdef KMM_MODELTEST + new ModelTest(d->m_equitiesModel, Models::instance()); + #endif + } + return d->m_equitiesModel; +} + +/** + * This is the function to get a reference to the core @ref SecuritiesModel. + * The returned object is owned by this object so don't delete it. It creates the + * model on the first access to it. + */ +SecuritiesModel* Models::securitiesModel() +{ + if (!d->m_securitiesModel) { + d->m_securitiesModel = new SecuritiesModel(this); + #ifdef KMM_MODELTEST + new ModelTest(d->m_securitiesModel, Models::instance()); + #endif + } + return d->m_securitiesModel; +} QModelIndex Models::indexById(QAbstractItemModel* model, int role, const QString& id) { @@ -196,6 +233,8 @@ costCenterModel()->load(); ledgerModel()->load(); payeesModel()->load(); + equitiesModel()->load(); + securitiesModel()->load(); emit modelsLoaded(); } @@ -210,4 +249,6 @@ ledgerModel()->unload(); costCenterModel()->unload(); payeesModel()->unload(); + equitiesModel()->removeRows(0, equitiesModel()->rowCount()); + securitiesModel()->removeRows(0, securitiesModel()->rowCount()); } diff --git a/kmymoney/models/securitiesmodel.h b/kmymoney/models/securitiesmodel.h new file mode 100644 --- /dev/null +++ b/kmymoney/models/securitiesmodel.h @@ -0,0 +1,89 @@ +/*************************************************************************** + securitiesmodel.h + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SECURITIESMODEL_H +#define SECURITIESMODEL_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include + +class SecuritiesModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Column { Security = 0, Symbol, Type, Market, Currency, Fraction }; + + ~SecuritiesModel(); + + auto getColumns(); + static QString getHeaderName(const Column column); + +public slots: + void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id); + +private: + SecuritiesModel(QObject *parent = nullptr); + SecuritiesModel(const SecuritiesModel&); + SecuritiesModel& operator=(SecuritiesModel&); + friend class Models; // only this class can create SecuritiesModel + + void init(); + void load(); + +protected: + class Private; + Private* const d; +}; + +class SecuritiesFilterProxyModel : public KRecursiveFilterProxyModel +{ + Q_OBJECT + +public: + SecuritiesFilterProxyModel(QObject *parent , SecuritiesModel *model, const QList &columns = QList()); + ~SecuritiesFilterProxyModel(); + + QList &getVisibleColumns(); + +signals: + void columnToggled(const SecuritiesModel::Column column, const bool show); + +public slots: + void slotColumnsMenu(const QPoint); + +protected: + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + +private: + class Private; + Private* const d; +}; + +#endif // SECURITIESMODEL_H diff --git a/kmymoney/models/securitiesmodel.cpp b/kmymoney/models/securitiesmodel.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/models/securitiesmodel.cpp @@ -0,0 +1,368 @@ +/*************************************************************************** + securitiesmodel.cpp + ------------------- + copyright : (C) 2017 by Łukasz Wojniłowicz + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "securitiesmodel.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +class SecuritiesModel::Private +{ +public: + Private() : m_file(MyMoneyFile::instance()) + { + QVector columns { + Column::Security, Column::Symbol, Column::Type, + Column::Market, Column::Currency, Column::Fraction + }; + foreach (auto const column, columns) + m_columns.append(column); + } + + ~Private() {} + + void loadSecurity(QStandardItem *node, const MyMoneySecurity &sec) + { + auto itSec = new QStandardItem(sec.name()); + node->appendRow(itSec); + itSec->setEditable(false); + setSecurityData(node, itSec->row(), sec, m_columns); + } + + void setSecurityData(QStandardItem *node, const int row, const MyMoneySecurity &security, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Security); + if (colNum == -1) + return; + + // Security + getCell(colNum); + if (columns.contains(Column::Security)) { + cell->setData(security.name(), Qt::DisplayRole); + cell->setData(security.id(), Qt::UserRole); + } + + // Symbol + if (columns.contains(Column::Symbol)) { + colNum = m_columns.indexOf(Column::Symbol); + if (colNum != -1) { + getCell(colNum); + cell->setData(security.tradingSymbol(), Qt::DisplayRole); + } + } + + // Type + if (columns.contains(Column::Type)) { + colNum = m_columns.indexOf(Column::Type); + if (colNum != -1) { + getCell(colNum); + cell->setData(security.securityTypeToString(security.securityType()), Qt::DisplayRole); + } + } + + // Market + if (columns.contains(Column::Market)) { + colNum = m_columns.indexOf(Column::Market); + if (colNum != -1) { + getCell(colNum); + QString market; + if (security.isCurrency()) + market = QLatin1String("ISO 4217"); + else + market = security.tradingMarket(); + cell->setData(market, Qt::DisplayRole); + } + } + + // Currency + if (columns.contains(Column::Currency)) { + colNum = m_columns.indexOf(Column::Currency); + if (colNum != -1) { + getCell(colNum); + MyMoneySecurity tradingCurrency; + if (!security.isCurrency()) + tradingCurrency = m_file->security(security.tradingCurrency()); + cell->setData(tradingCurrency.tradingSymbol(), Qt::DisplayRole); + } + } + + // Fraction + if (columns.contains(Column::Fraction)) { + colNum = m_columns.indexOf(Column::Fraction); + if (colNum != -1) { + getCell(colNum); + cell->setData(QString::number(security.smallestAccountFraction()), Qt::DisplayRole); + } + } + } + + QStandardItem *itemFromSecurityId(QStandardItemModel *model, const QString &securityId) + { + const auto itemList = model->match(model->index(0, 0), Qt::UserRole, QVariant(securityId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); + if (!itemList.isEmpty()) + return model->itemFromIndex(itemList.first()); + return nullptr; + } + + MyMoneyFile *m_file; + QList m_columns; + QStandardItem *m_ndCurrencies; + QStandardItem *m_ndSecurities; +}; + +SecuritiesModel::SecuritiesModel(QObject *parent) + : QStandardItemModel(parent), d(new Private) +{ + init(); +} + +SecuritiesModel::~SecuritiesModel() +{ + delete d; +} + +void SecuritiesModel::init() +{ + QStringList headerLabels; + foreach (const auto column, d->m_columns) + headerLabels.append(getHeaderName(column)); + setHorizontalHeaderLabels(headerLabels); +} + +void SecuritiesModel::load() +{ + this->blockSignals(true); + + auto rootItem = invisibleRootItem(); + auto secList = d->m_file->securityList(); // get all available securities + d->m_ndSecurities = new QStandardItem(QStringLiteral("Securities")); + d->m_ndSecurities->setEditable(false); + rootItem->appendRow(d->m_ndSecurities); + + foreach (const auto sec, secList) + d->loadSecurity(d->m_ndSecurities, sec); + + secList = d->m_file->currencyList(); // get all available currencies + d->m_ndCurrencies = new QStandardItem(QStringLiteral("Currencies")); + d->m_ndCurrencies->setEditable(false); + rootItem->appendRow(d->m_ndCurrencies); + foreach (const auto sec, secList) + d->loadSecurity(d->m_ndCurrencies, sec); + + this->blockSignals(false); +} + +/** + * Notify the model that an object has been added. An action is performed only if the object is a security. + * + */ +void SecuritiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + // check whether change is about security + if (objType != MyMoneyFile::notifySecurity) + return; + + // check that we're about to add security + auto sec = dynamic_cast(obj); + if (!sec) + return; + + auto itSec = d->itemFromSecurityId(this, sec->id()); + + QStandardItem *node; + if (sec->isCurrency()) + node = d->m_ndCurrencies; + else + node = d->m_ndSecurities; + + // if security doesn't exist in model then add it + if (!itSec) { + itSec = new QStandardItem(sec->name()); + node->appendRow(itSec); + itSec->setEditable(false); + } + + d->setSecurityData(node, itSec->row(), *sec, d->m_columns); +} + +/** + * Notify the model that an object has been modified. An action is performed only if the object is a security. + * + */ +void SecuritiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + if (objType != MyMoneyFile::notifySecurity) + return; + + // check that we're about to modify security + auto sec = dynamic_cast(obj); + if (!sec) + return; + + auto itSec = d->itemFromSecurityId(this, sec->id()); + + QStandardItem *node; + if (sec->isCurrency()) + node = d->m_ndCurrencies; + else + node = d->m_ndSecurities; + d->setSecurityData(node, itSec->row(), *sec, d->m_columns); +} + +/** + * Notify the model that an object has been removed. An action is performed only if the object is an account. + * + */ +void SecuritiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id) +{ + if (objType != MyMoneyFile::notifySecurity) + return; + + const auto indexList = match(index(0, 0), Qt::UserRole, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); + foreach (const auto index, indexList) + removeRow(index.row(), index.parent()); +} + +auto SecuritiesModel::getColumns() +{ + return &d->m_columns; +} + +QString SecuritiesModel::getHeaderName(const Column column) +{ + switch(column) { + case Security: + return i18n("Security"); + case Symbol: + return i18n("Symbol"); + case Type: + return i18n("Type"); + case Market: + return i18n("Market"); + case Currency: + return i18n("Currency"); + case Fraction: + return i18n("Fraction"); + default: + return QString(); + } +} + +class SecuritiesFilterProxyModel::Private +{ +public: + Private() : + m_mdlColumns(nullptr), + m_file(MyMoneyFile::instance()) + {} + + ~Private() {} + + QList *m_mdlColumns; + QList m_visColumns; + + MyMoneyFile *m_file; +}; + +SecuritiesFilterProxyModel::SecuritiesFilterProxyModel(QObject *parent, SecuritiesModel *model, const QList &columns) + : KRecursiveFilterProxyModel(parent), d(new Private) +{ + setDynamicSortFilter(true); + setFilterKeyColumn(-1); + setSortLocaleAware(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSourceModel(model); + d->m_mdlColumns = model->getColumns(); + d->m_visColumns.append(columns); +} + +SecuritiesFilterProxyModel::~SecuritiesFilterProxyModel() +{ + delete d; +} + +bool SecuritiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) + return true; + return false; +} + +QList &SecuritiesFilterProxyModel::getVisibleColumns() +{ + return d->m_visColumns; +} + +void SecuritiesFilterProxyModel::slotColumnsMenu(const QPoint) +{ + // construct all hideable columns list + const QList idColumns { + SecuritiesModel::Symbol, SecuritiesModel::Type, + SecuritiesModel::Market, SecuritiesModel::Currency, + SecuritiesModel::Fraction + }; + + // create menu + QMenu menu(i18n("Displayed columns")); + QList actions; + foreach (const auto idColumn, idColumns) { + auto a = new QAction(nullptr); + a->setObjectName(QString::number(idColumn)); + a->setText(SecuritiesModel::getHeaderName(idColumn)); + a->setCheckable(true); + a->setChecked(d->m_visColumns.contains(idColumn)); + actions.append(a); + } + menu.addActions(actions); + + // execute menu and get result + const auto retAction = menu.exec(QCursor::pos()); + if (retAction) { + const auto idColumn = static_cast(retAction->objectName().toInt()); + const auto isChecked = retAction->isChecked(); + const auto contains = d->m_visColumns.contains(idColumn); + if (isChecked && !contains) { // column has just been enabled + d->m_visColumns.append(idColumn); // change filtering variable + emit columnToggled(idColumn, true); // emit signal for method to add column to model + invalidate(); // refresh model to reflect recent changes + } else if (!isChecked && contains) { // column has just been disabled + d->m_visColumns.removeOne(idColumn); + emit columnToggled(idColumn, false); + invalidate(); + } + } +} diff --git a/kmymoney/mymoney/mymoneysecurity.cpp b/kmymoney/mymoney/mymoneysecurity.cpp --- a/kmymoney/mymoney/mymoneysecurity.cpp +++ b/kmymoney/mymoney/mymoneysecurity.cpp @@ -173,29 +173,20 @@ QString MyMoneySecurity::securityTypeToString(const eSECURITYTYPE securityType) { - QString returnString; - switch (securityType) { case MyMoneySecurity::SECURITY_STOCK: - returnString = I18N_NOOP("Stock"); - break; + return i18nc("Security type", "Stock"); case MyMoneySecurity::SECURITY_MUTUALFUND: - returnString = I18N_NOOP("Mutual Fund"); - break; + return i18nc("Security type", "Mutual Fund"); case MyMoneySecurity::SECURITY_BOND: - returnString = I18N_NOOP("Bond"); - break; + return i18nc("Security type", "Bond"); case MyMoneySecurity::SECURITY_CURRENCY: - returnString = I18N_NOOP("Currency"); - break; + return i18nc("Security type", "Currency"); case MyMoneySecurity::SECURITY_NONE: - returnString = I18N_NOOP("None"); - break; + return i18nc("Security type", "None"); default: - returnString = I18N_NOOP("Unknown"); + return i18nc("Security type", "Unknown"); } - - return returnString; } QString MyMoneySecurity::roundingMethodToString(const AlkValue::RoundingMethod roundingMethod) diff --git a/kmymoney/views/CMakeLists.txt b/kmymoney/views/CMakeLists.txt --- a/kmymoney/views/CMakeLists.txt +++ b/kmymoney/views/CMakeLists.txt @@ -44,7 +44,7 @@ kcategoriesviewdecl.ui kforecastviewdecl.ui kinstitutionsviewdecl.ui - kinvestmentviewdecl.ui + kinvestmentview.ui kpayeesviewdecl.ui kscheduledviewdecl.ui ktagsviewdecl.ui diff --git a/kmymoney/views/kinvestmentview.h b/kmymoney/views/kinvestmentview.h --- a/kmymoney/views/kinvestmentview.h +++ b/kmymoney/views/kinvestmentview.h @@ -9,6 +9,7 @@ John C Thomas Baumgart Kevin Tambascio + (C) 2017 Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** @@ -29,155 +30,108 @@ // ---------------------------------------------------------------------------- // KDE Includes -#include - // ---------------------------------------------------------------------------- // Project Includes -#include -#include -#include "ui_kinvestmentviewdecl.h" - -enum eInvestmentColumn { eInvestmentNameColumn, eInvestmentSymbolColumn, eValueColumn, eQuantityColumn, ePriceColumn }; +#include "ui_kinvestmentview.h" -enum eSecurityColum { eIdColumn, eTypeColumn, eSecurityNameColumn, eSecuritySymbolColumn, eMarketColumn, eCurrencyColumn, eAcctFractionColumn, eCashFractionColumn }; +class KMyMoneyApp; +class KMyMoneyView; +class MyMoneySecurity; /** * @author Kevin Tambascio + * @author Łukasz Wojniłowicz */ -class KInvestmentView : public QWidget, private Ui::KInvestmentViewDecl +class KInvestmentView : public QWidget, private Ui::KInvestmentView { Q_OBJECT public: - KInvestmentView(QWidget *parent = 0); + explicit KInvestmentView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview); ~KInvestmentView(); - /** - * Start reconciliation for the account in the current view - */ - void reconcileAccount(); - public slots: /** * This slot is used to reload all data from the MyMoneyFile engine. - * All existing data in the view will be discarded. + * All existing data in the view will be invalidated. * Call this e.g. if a new file has been loaded. */ void slotLoadView(); /** - * This slot is used to select the correct ledger view type for - * the account specified by @p id. If @p transactionId is not - * empty, then the respective transaction will be selected. - * - * @param accountId Internal id used for the account to show - * @param transactionId Internal id used for the transaction to select - * @param reconciliation if true, the account will be selected in - * reconciliation mode. If false, it will - * be selected in regular ledger mode. - * - * @retval true selection of account referenced by @p id succeeded - * @retval false selection of account failed + * This slot is used to preselect investment account from ledger view */ - bool slotSelectAccount(const QString& accountId, const QString& transactionId = QString(), const bool reconciliation = false); - - /** - * This method is provided for convenience and acts as the method above. - */ - bool slotSelectAccount(const MyMoneyObject& acc); + void slotSelectAccount(const MyMoneyObject &obj); void showEvent(QShowEvent* event); -protected: - - typedef enum { - EquitiesTab = 0, - SecuritiesTab, - // insert new values above this line - MaxViewTabs - } InvestmentsViewTab; - - /** - * This method loads the investments and securities for the respective tab. - * - * @param tab which tab should be loaded - */ - void loadView(InvestmentsViewTab tab); - - /** - * This method reloads the account selection combo box of the - * view with all asset and liability accounts from the engine. - * If the account id of the current account held in @p m_accountId is - * empty or if the referenced account does not exist in the engine, - * the first account found in the list will be made the current account. - */ - void loadAccounts(); - - /** - * clear the view - */ - void clear(); - - void loadInvestmentTab(); - - void loadInvestmentItem(const MyMoneyAccount& account); - - void loadSecuritiesList(); - - void loadSecurityItem(QTreeWidgetItem* item, const MyMoneySecurity& security); - -protected slots: - void slotTabCurrentChanged(int index); +private slots: /** - * This slot receives the signal from the listview @c lv control that the context menu - * was requested for @c item at @c point. + * This slot is used to reload (filters + equities account) specific tab */ - void slotInvestmentContextMenu(const QPoint& point); + void slotLoadTab(int index); - void slotInvestmentSelectionChanged(); + void slotEquitySelected(const QModelIndex ¤t, const QModelIndex &previous); + void slotEquityRightClicked(const QPoint& point); + void slotEquityDoubleClicked(); + void slotSecuritySelected(const QModelIndex ¤t, const QModelIndex &previous); - void slotUpdateSecuritiesButtons(); void slotEditSecurity(); void slotDeleteSecurity(); + /** + * This slot is used to programatically preselect account in investment view + */ + void slotSelectAccount(const QString &id); -signals: /** - * This signal is emitted, if an account has been selected - * which cannot be handled by this view. + * This slot is used to load investment account into tree view */ - void accountSelected(const QString& accountId, const QString& transactionId); + void slotLoadAccount(const QString &id); +signals: void accountSelected(const MyMoneyObject&); - void investmentRightMouseClick(); + void equityRightClicked(); /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); private: - /// \internal d-pointer class. class Private; - /// \internal d-pointer instance. Private* const d; - const QString m_currencyMarket; + KMyMoneyApp *m_kmymoney; + KMyMoneyView *m_kmymoneyview; + + /** Initializes page and sets its load status to initialized + */ + void init(); + /** - * Search widget for the securities list + * This slot is used to programatically preselect default account in investment view */ - KTreeWidgetSearchLineWidget* m_searchSecuritiesWidget; + void selectDefaultInvestmentAccount(); + enum Tab { Equities = 0, Securities }; + + /** + * This slots are used to reload tabs + */ + void loadInvestmentTab(); + void loadSecuritiesTab(); + + /** + * This slots returns security currently selected in tree view + */ + MyMoneySecurity currentSecurity(); /** * This member holds the load state of page */ bool m_needLoad; - - /** Initializes page and sets its load status to initialized - */ - void init(); }; #endif diff --git a/kmymoney/views/kinvestmentview.cpp b/kmymoney/views/kinvestmentview.cpp --- a/kmymoney/views/kinvestmentview.cpp +++ b/kmymoney/views/kinvestmentview.cpp @@ -4,6 +4,7 @@ begin : Mon Mar 12 2007 copyright : (C) 2007 by Thomas Baumgart email : Thomas Baumgart + (C) 2017 Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** @@ -28,7 +29,6 @@ // KDE Includes #include -#include #include #include @@ -38,79 +38,70 @@ #include #include #include -#include -#include #include #include #include -#include #include -#include "kmymoney.h" -#include "models.h" +#include +#include +#include +#include +#include #include using namespace Icons; -/** - * This class is only needed to implement proper sorting. - */ -class InvestmentItem : public QTreeWidgetItem -{ -public: - InvestmentItem(QTreeWidget *view) : QTreeWidgetItem(view) {} - virtual bool operator<(const QTreeWidgetItem &other) const; -}; - -bool InvestmentItem::operator<(const QTreeWidgetItem &other) const -{ - const int sortColumn = treeWidget()->sortColumn(); - if (sortColumn > eInvestmentSymbolColumn) { - // these columns have a MyMoneyMoney value in the Qt::UserRole role - const MyMoneyMoney &money = data(sortColumn, Qt::UserRole).value(); - const MyMoneyMoney &otherMoney = other.data(sortColumn, Qt::UserRole).value(); - return money < otherMoney; - } - return QTreeWidgetItem::operator<(other); -} - class KInvestmentView::Private { public: Private() : - m_newAccountLoaded(false), - m_recursion(false), - m_precision(2), - m_filterProxyModel(0) {} - - MyMoneyAccount m_account; - bool m_needReload[MaxViewTabs]; - bool m_newAccountLoaded; - bool m_recursion; - int m_precision; - AccountNamesFilterProxyModel *m_filterProxyModel; + m_idInvAcc(QString()), + m_accountsProxyModel(nullptr), + m_equitiesProxyModel(nullptr), + m_securitiesProxyModel(nullptr) {} + QString m_idInvAcc; + + bool m_needReload[2]; + AccountNamesFilterProxyModel *m_accountsProxyModel; + EquitiesFilterProxyModel *m_equitiesProxyModel; + SecuritiesFilterProxyModel *m_securitiesProxyModel; }; -KInvestmentView::KInvestmentView(QWidget *parent) : - QWidget(parent), +KInvestmentView::KInvestmentView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview) : + QWidget(nullptr), d(new Private), - m_currencyMarket("ISO 4217"), + m_kmymoney(kmymoney), + m_kmymoneyview(kmymoneyview), m_needLoad(true) { } KInvestmentView::~KInvestmentView() { if (!m_needLoad) { // save the header state of the equities list - KConfigGroup grp = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); - QByteArray columns = m_investmentsList->header()->saveState(); - grp.writeEntry("HeaderState", columns); + auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); + auto cfgHeader = m_equitiesTree->header()->saveState(); + auto visEColumns = d->m_equitiesProxyModel->getVisibleColumns(); + + QList cfgColumns; + foreach (const auto visColumn, visEColumns) + cfgColumns.append(static_cast(visColumn)); + + cfgGroup.writeEntry("HeaderState", cfgHeader); + cfgGroup.writeEntry("ColumnsSelection", cfgColumns); // save the header state of the securities list - grp = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); - columns = m_securitiesList->header()->saveState(); - grp.writeEntry("HeaderState", columns); + cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); + cfgHeader = m_securitiesTree->header()->saveState(); + auto visSColumns = d->m_securitiesProxyModel->getVisibleColumns(); + cfgColumns.clear(); + foreach (const auto visColumn, visSColumns) + cfgColumns.append(static_cast(visColumn)); + + cfgGroup.writeEntry("HeaderState", cfgHeader); + cfgGroup.writeEntry("ColumnsSelection", cfgColumns); } delete d; @@ -121,350 +112,205 @@ m_needLoad = false; setupUi(this); - // load the header state of the equities list - KConfigGroup grp = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); - QByteArray columns; - columns = grp.readEntry("HeaderState", columns); - m_investmentsList->header()->restoreState(columns); - - // load the header state of the securities list - grp = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); - columns.clear(); - columns = grp.readEntry("HeaderState", columns); - m_securitiesList->header()->restoreState(columns); - - //first set up everything for the equities tab - d->m_filterProxyModel = new AccountNamesFilterProxyModel(this); - d->m_filterProxyModel->addAccountType(MyMoneyAccount::Investment); - d->m_filterProxyModel->setHideEquityAccounts(false); - auto const model = Models::instance()->accountsModel(); - d->m_filterProxyModel->init(model, model->getColumns()); - d->m_filterProxyModel->sort(AccountsModel::Account); - m_accountComboBox->setModel(d->m_filterProxyModel); - - m_investmentsList->setContextMenuPolicy(Qt::CustomContextMenu); - m_investmentsList->setSortingEnabled(true); - - for (auto i = 0; i < MaxViewTabs; ++i) - d->m_needReload[i] = false; - - connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(slotTabCurrentChanged(int))); - - connect(m_investmentsList, SIGNAL(customContextMenuRequested(QPoint)), - this, SLOT(slotInvestmentContextMenu(QPoint))); - connect(m_investmentsList, SIGNAL(itemSelectionChanged()), this, SLOT(slotInvestmentSelectionChanged())); + // Equities tab + d->m_accountsProxyModel = new AccountNamesFilterProxyModel(this); + d->m_accountsProxyModel->addAccountType(MyMoneyAccount::Investment); + d->m_accountsProxyModel->setHideEquityAccounts(false); + d->m_accountsProxyModel->init(Models::instance()->accountsModel()); + d->m_accountsProxyModel->sort(AccountsModel::Account); + m_accountComboBox->setModel(d->m_accountsProxyModel); + m_accountComboBox->expandAll(); + + auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); + auto cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray()); + auto cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList()); + QList visEColumns {EquitiesModel::Equity}; + foreach (const auto cfgColumn, cfgColumns) { + const auto visColumn = static_cast(cfgColumn); + if (!visEColumns.contains(visColumn)) + visEColumns.append(visColumn); + } + + d->m_equitiesProxyModel = new EquitiesFilterProxyModel(this, Models::instance()->equitiesModel(), visEColumns); + m_equitiesTree->setModel(d->m_equitiesProxyModel); + m_equitiesTree->header()->restoreState(cfgHeader); + + connect(m_equitiesTree, &QWidget::customContextMenuRequested, this, &KInvestmentView::slotEquityRightClicked); +// connect(m_equitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotEquitySelected); + connect(m_equitiesTree, &QTreeView::doubleClicked, this, &KInvestmentView::slotEquityDoubleClicked); + m_equitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_equitiesTree->header(), &QWidget::customContextMenuRequested, d->m_equitiesProxyModel, &EquitiesFilterProxyModel::slotColumnsMenu); connect(m_accountComboBox, SIGNAL(accountSelected(QString)), - this, SLOT(slotSelectAccount(QString))); - connect(m_investmentsList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::InvestmentEdit]), SLOT(trigger())); - connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); - - // create the searchline widget - // and insert it into the existing layout - m_searchSecuritiesWidget = new KTreeWidgetSearchLineWidget(this, m_securitiesList); - m_searchSecuritiesWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); - m_securitiesLayout->insertWidget(0, m_searchSecuritiesWidget); - - KGuiItem removeButtonItem(i18n("&Delete"), - QIcon::fromTheme(g_Icons[Icon::EditDelete]), - i18n("Delete this entry"), - i18n("Remove this security item from the file")); - KGuiItem::assign(m_deleteSecurityButton, removeButtonItem); - - KGuiItem editButtonItem(i18n("&Edit"), - QIcon::fromTheme(g_Icons[Icon::DocumentEdit]), - i18n("Modify the selected entry"), - i18n("Change the security information of the selected entry.")); - KGuiItem::assign(m_editSecurityButton, editButtonItem); - - connect(m_securitiesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotUpdateSecuritiesButtons())); - connect(m_editSecurityButton, SIGNAL(clicked()), this, SLOT(slotEditSecurity())); - connect(m_deleteSecurityButton, SIGNAL(clicked()), this, SLOT(slotDeleteSecurity())); + this, SLOT(slotLoadAccount(QString))); + + // Securities tab + cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); + cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray()); + cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList()); + QList visSColumns {SecuritiesModel::Security}; + foreach (const auto cfgColumn, cfgColumns) { + const auto visColumn = static_cast(cfgColumn); + if (!visSColumns.contains(visColumn)) + visSColumns.append(visColumn); + } + + d->m_securitiesProxyModel = new SecuritiesFilterProxyModel(this, Models::instance()->securitiesModel(), visSColumns); + m_securitiesTree->setModel(d->m_securitiesProxyModel); + m_securitiesTree->header()->restoreState(cfgHeader); + + m_searchSecurities->setProxy(d->m_securitiesProxyModel); + m_deleteSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); + m_editSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); + + connect(m_securitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotSecuritySelected); + connect(m_editSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotEditSecurity); + connect(m_deleteSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotDeleteSecurity); + m_securitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_securitiesTree->header(), &QWidget::customContextMenuRequested, d->m_securitiesProxyModel, &SecuritiesFilterProxyModel::slotColumnsMenu); + + // Investment Page + d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true; + connect(m_tab, &QTabWidget::currentChanged, this, &KInvestmentView::slotLoadTab); + connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KInvestmentView::slotLoadView); + + connect(this, SIGNAL(accountSelected(MyMoneyObject)), m_kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); + connect(this, &KInvestmentView::equityRightClicked, m_kmymoney, &KMyMoneyApp::slotShowInvestmentContextMenu); + connect(this, &KInvestmentView::aboutToShow, m_kmymoneyview, &KMyMoneyView::aboutToChangeView); } -void KInvestmentView::loadView(InvestmentsViewTab tab) +void KInvestmentView::slotLoadTab(int index) { + auto tab = static_cast(index); if (d->m_needReload[tab]) { switch (tab) { - case EquitiesTab: + case Tab::Equities: loadInvestmentTab(); - // force a new account if the current one is empty - d->m_newAccountLoaded = d->m_account.id().isEmpty(); break; - case SecuritiesTab: - loadSecuritiesList(); - break; - default: + case Tab::Securities: + loadSecuritiesTab(); break; } d->m_needReload[tab] = false; } } -void KInvestmentView::slotInvestmentSelectionChanged() +void KInvestmentView::slotEquitySelected(const QModelIndex ¤t, const QModelIndex &previous) { - kmymoney->slotSelectInvestment(); - - QTreeWidgetItem *item = m_investmentsList->currentItem(); - if (item) { - try { - MyMoneyAccount account = MyMoneyFile::instance()->account(item->data(0, Qt::UserRole).value().id()); - kmymoney->slotSelectInvestment(account); - - } catch (const MyMoneyException &) { - } + Q_UNUSED(current); + Q_UNUSED(previous); + MyMoneyAccount acc; + + auto treeItem = m_equitiesTree->currentIndex(); + if (treeItem.isValid()) { + auto mdlItem = d->m_equitiesProxyModel->index(treeItem.row(), EquitiesModel::Equity, treeItem.parent()); + acc = MyMoneyFile::instance()->account(mdlItem.data(EquitiesModel::EquityID).toString()); } + m_kmymoney->slotSelectInvestment(acc); } -void KInvestmentView::slotInvestmentContextMenu(const QPoint& /*point*/) +void KInvestmentView::slotEquityRightClicked(const QPoint&) { - kmymoney->slotSelectInvestment(); - QTreeWidgetItem *item = m_investmentsList->currentItem(); - if (item) { - kmymoney->slotSelectInvestment(MyMoneyFile::instance()->account(item->data(0, Qt::UserRole).value().id())); - } - emit investmentRightMouseClick(); + slotEquitySelected(QModelIndex(), QModelIndex()); + emit equityRightClicked(); } -void KInvestmentView::slotLoadView() +void KInvestmentView::slotEquityDoubleClicked() { - d->m_needReload[EquitiesTab] = true; - d->m_needReload[SecuritiesTab] = true; - if (isVisible()) - slotTabCurrentChanged(m_tab->currentIndex()); + slotEquitySelected(QModelIndex(), QModelIndex()); + m_kmymoney->actionCollection()->action(m_kmymoney->s_Actions[Action::InvestmentEdit])->trigger(); } -void KInvestmentView::slotTabCurrentChanged(int index) +void KInvestmentView::slotSecuritySelected(const QModelIndex ¤t, const QModelIndex &previous) { - InvestmentsViewTab tab = static_cast(index); - - loadView(tab); -} - -void KInvestmentView::loadAccounts() -{ - MyMoneyFile* file = MyMoneyFile::instance(); - - // check if the current account still exists and make it the - // current account - if (!d->m_account.id().isEmpty()) { - try { - d->m_account = file->account(d->m_account.id()); - } catch (const MyMoneyException &) { - d->m_account = MyMoneyAccount(); - } - } - - d->m_filterProxyModel->invalidate(); - m_accountComboBox->expandAll(); - - if (d->m_account.id().isEmpty()) { - // there are no favorite accounts find any account - QModelIndexList list = d->m_filterProxyModel->match(d->m_filterProxyModel->index(0, 0), - Qt::DisplayRole, - QVariant(QString("*")), - -1, - Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive)); - for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { - if (!it->parent().isValid()) - continue; // skip the top level accounts - QVariant accountId = (*it).data(AccountsModel::AccountIdRole); - if (accountId.isValid()) { - MyMoneyAccount a = file->account(accountId.toString()); - if (a.value("PreferredAccount") == "Yes") { - d->m_account = a; - break; - } else if (d->m_account.id().isEmpty()) { - d->m_account = a; - } - } - } - } - - if (!d->m_account.id().isEmpty()) { - m_accountComboBox->setSelected(d->m_account.id()); - try { - d->m_precision = MyMoneyMoney::denomToPrec(d->m_account.fraction()); - } catch (const MyMoneyException &) { - qDebug("Security %s for account %s not found", qPrintable(d->m_account.currencyId()), qPrintable(d->m_account.name())); - d->m_precision = 2; - } + Q_UNUSED(current); + Q_UNUSED(previous); + const auto sec = currentSecurity(); + if (!sec.id().isEmpty()) { + MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); + skip.fill(false); + skip.setBit(IMyMoneyStorage::RefCheckPrice); + m_editSecurityButton->setEnabled(true); + m_deleteSecurityButton->setEnabled(!MyMoneyFile::instance()->isReferenced(sec, skip)); + } else { + m_editSecurityButton->setEnabled(false); + m_deleteSecurityButton->setEnabled(false); } } - -bool KInvestmentView::slotSelectAccount(const MyMoneyObject& obj) +void KInvestmentView::slotLoadView() { - if (typeid(obj) != typeid(MyMoneyAccount)) - return false; - - if (d->m_recursion) - return false; - - d->m_recursion = true; - const MyMoneyAccount& acc = dynamic_cast(obj); - bool rc = slotSelectAccount(acc.id()); - d->m_recursion = false; - return rc; + d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true; + if (isVisible()) + slotLoadTab(m_tab->currentIndex()); } -bool KInvestmentView::slotSelectAccount(const QString& id, const QString& transactionId, const bool /* reconciliation*/) +void KInvestmentView::selectDefaultInvestmentAccount() { - bool rc = true; - - if (!id.isEmpty()) { - // if the account id differs, then we have to do something - if (d->m_account.id() != id) { - try { - d->m_account = MyMoneyFile::instance()->account(id); - // if a stock account is selected, we show the - // the corresponding parent (investment) account - if (d->m_account.isInvest()) { - d->m_account = MyMoneyFile::instance()->account(d->m_account.parentAccountId()); - } - // TODO if we don't have an investment account, then we should switch to the ledger view - d->m_newAccountLoaded = true; - if (d->m_account.accountType() == MyMoneyAccount::Investment) { - slotLoadView(); - } else { - emit accountSelected(id, transactionId); - d->m_account = MyMoneyAccount(); - d->m_needReload[EquitiesTab] = true; - rc = false; - } - - } catch (const MyMoneyException &) { - qDebug("Unable to retrieve account %s", qPrintable(id)); - rc = false; - } - } else { - emit accountSelected(d->m_account); + if (d->m_accountsProxyModel->rowCount() > 0) { + auto firsitem = d->m_accountsProxyModel->index(0, 0, QModelIndex()); + if (d->m_accountsProxyModel->hasChildren(firsitem)) { + auto seconditem = d->m_accountsProxyModel->index(0, 0, firsitem); + slotSelectAccount(seconditem.data(EquitiesModel::EquityID).toString()); } } - - return rc; } -void KInvestmentView::clear() +void KInvestmentView::slotSelectAccount(const QString &id) { - // setup header font - QFont font = KMyMoneyGlobalSettings::listHeaderFont(); - QFontMetrics fm(font); - int height = fm.lineSpacing() + 6; - m_investmentsList->header()->setMinimumHeight(height); - m_investmentsList->header()->setMaximumHeight(height); - m_investmentsList->header()->setFont(font); - - // setup cell font - font = KMyMoneyGlobalSettings::listCellFont(); - m_investmentsList->setFont(font); - - // clear the table - m_investmentsList->clear(); - - // and the selected account in the combo box - m_accountComboBox->setSelected(QString()); - - // right align col headers for quantity, price and value - for (int i = 2; i < 5; ++i) { - m_investmentsList->headerItem()->setTextAlignment(i, Qt::AlignRight | Qt::AlignVCenter); + if (!id.isEmpty()) { + d->m_idInvAcc = id; + if (isVisible()) + m_accountComboBox->setSelected(id); } } -void KInvestmentView::loadInvestmentTab() +void KInvestmentView::slotSelectAccount(const MyMoneyObject &obj) { - // no account selected - emit accountSelected(MyMoneyAccount()); - - // clear the current contents ... - clear(); + if (typeid(obj) != typeid(MyMoneyAccount)) + return; + const auto acc = dynamic_cast(obj); - // ... load the combobox widget and select current account ... - loadAccounts(); + if (acc.accountType() == MyMoneyAccount::Investment) + slotSelectAccount(acc.id()); +} - if (d->m_account.id().isEmpty()) { - // if we don't have an account we bail out - setEnabled(false); - return; - } - setEnabled(true); - - MyMoneyFile* file = MyMoneyFile::instance(); - bool showClosedAccounts = kmymoney->isActionToggled(Action::ViewShowAll) - || !KMyMoneyGlobalSettings::hideClosedAccounts(); - const bool hideZeroBalance = KMyMoneyGlobalSettings::hideZeroBalanceEquities(); - - try { - d->m_account = file->account(d->m_account.id()); - QStringList securities = d->m_account.accountList(); - - for (QStringList::ConstIterator it = securities.constBegin(); it != securities.constEnd(); ++it) { - MyMoneyAccount acc = file->account(*it); - bool displayThisBalance = true; - if (hideZeroBalance) { - const MyMoneyMoney &balance = file->balance(acc.id()); - displayThisBalance = !balance.isZero(); - } - if ((!acc.isClosed() || showClosedAccounts) && displayThisBalance) - loadInvestmentItem(acc); - } - } catch (const MyMoneyException &) { - qDebug("KInvestmentView::loadView() - selected account does not exist anymore"); - d->m_account = MyMoneyAccount(); +void KInvestmentView::slotLoadAccount(const QString &id) +{ + const auto indexList = d->m_equitiesProxyModel->match(d->m_equitiesProxyModel->index(0,0), EquitiesModel::InvestmentID, id, 1, + Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap)); + if (!indexList.isEmpty()) { + m_equitiesTree->setRootIndex(indexList.first()); + d->m_idInvAcc = id; + if (isVisible()) + emit accountSelected(MyMoneyFile::instance()->account(id)); } - - // and tell everyone what's selected - emit accountSelected(d->m_account); } -void KInvestmentView::loadInvestmentItem(const MyMoneyAccount& account) +void KInvestmentView::loadInvestmentTab() { - QTreeWidgetItem* item = new InvestmentItem(m_investmentsList); - MyMoneySecurity security; - MyMoneyFile* file = MyMoneyFile::instance(); - - security = file->security(account.currencyId()); - MyMoneySecurity tradingCurrency = file->security(security.tradingCurrency()); - - int prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction()); - - //column 0 (COLUMN_NAME_INDEX) is the name of the stock - item->setText(eInvestmentNameColumn, account.name()); - item->setData(eInvestmentNameColumn, Qt::UserRole, QVariant::fromValue(account)); - - //column 1 (COLUMN_SYMBOL_INDEX) is the ticker symbol - item->setText(eInvestmentSymbolColumn, security.tradingSymbol()); - - //column 2 is the net value (price * quantity owned) - const MyMoneyPrice &price = file->price(account.currencyId(), tradingCurrency.id()); - const MyMoneyMoney &balance = file->balance(account.id()); - if (price.isValid()) { - const MyMoneyMoney &value = balance * price.rate(tradingCurrency.id()); - item->setText(eValueColumn, value.formatMoney(tradingCurrency.tradingSymbol(), prec)); - item->setData(eValueColumn, Qt::UserRole, QVariant::fromValue(value)); - } else { - item->setText(eValueColumn, "---"); + d->m_equitiesProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll)); + d->m_equitiesProxyModel->setHideZeroBalanceAccounts(KMyMoneyGlobalSettings::hideZeroBalanceEquities()); + d->m_equitiesProxyModel->invalidate(); + + d->m_accountsProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll)); + d->m_accountsProxyModel->invalidate(); + + if (!d->m_idInvAcc.isEmpty()) { // check if account to be selected exist + try { // it could not exist anymore (e.g. another file has been opened) + const auto acc = MyMoneyFile::instance()->account(d->m_idInvAcc); // then this should throw an exception + if (acc.accountType() == MyMoneyAccount::Investment) // it could be that id exists but account in new file isn't investment account anymore + slotSelectAccount(d->m_idInvAcc); // otherwise select preset account + else + d->m_idInvAcc.clear(); + } catch (const MyMoneyException &) { + d->m_idInvAcc.clear(); // account is invalid + } } - item->setTextAlignment(eValueColumn, Qt::AlignRight | Qt::AlignVCenter); - - //column 3 (COLUMN_QUANTITY_INDEX) is the quantity of shares owned - prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); - item->setText(eQuantityColumn, balance.formatMoney("", prec)); - item->setTextAlignment(eQuantityColumn, Qt::AlignRight | Qt::AlignVCenter); - item->setData(eQuantityColumn, Qt::UserRole, QVariant::fromValue(balance)); + if (d->m_idInvAcc.isEmpty()) // if account is invalid select default one + selectDefaultInvestmentAccount(); - //column 4 is the current price - // Get the price precision from the configuration - prec = security.pricePrecision(); - - // prec = MyMoneyMoney::denomToPrec(m_tradingCurrency.smallestAccountFraction()); - if (price.isValid()) { - item->setText(ePriceColumn, price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec)); - item->setData(ePriceColumn, Qt::UserRole, QVariant::fromValue(price.rate(tradingCurrency.id()))); - } else { - item->setText(ePriceColumn, "---"); - } - item->setTextAlignment(ePriceColumn, Qt::AlignRight | Qt::AlignVCenter); + m_accountComboBox->expandAll(); } void KInvestmentView::showEvent(QShowEvent* event) @@ -474,110 +320,56 @@ emit aboutToShow(); - /*if (d->m_needReload) { - loadInvestmentTab(); - d->m_needReload = false; - d->m_newAccountLoaded = false; - - } else { - emit accountSelected(d->m_account); - }*/ - - slotTabCurrentChanged(m_tab->currentIndex()); + d->m_needReload[Tab::Equities] = true; // ensure tree view will be reloaded after selecting account in ledger view + slotLoadTab(m_tab->currentIndex()); // don't forget base class implementation QWidget::showEvent(event); } -void KInvestmentView::loadSecuritiesList() -{ - m_securitiesList->setColumnWidth(eIdColumn, 0); - m_securitiesList->setSortingEnabled(false); - m_securitiesList->clear(); - - QList list = MyMoneyFile::instance()->securityList(); - QList::ConstIterator it; - for (it = list.constBegin(); it != list.constEnd(); ++it) { - QTreeWidgetItem* newItem = new QTreeWidgetItem(m_securitiesList); - loadSecurityItem(newItem, *it); - - } - m_securitiesList->setSortingEnabled(true); - - slotUpdateSecuritiesButtons(); -} - -void KInvestmentView::loadSecurityItem(QTreeWidgetItem* item, const MyMoneySecurity& security) +void KInvestmentView::loadSecuritiesTab() { - QString market = security.tradingMarket(); - MyMoneySecurity tradingCurrency; - if (security.isCurrency()) - market = m_currencyMarket; - else - tradingCurrency = MyMoneyFile::instance()->security(security.tradingCurrency()); - - item->setText(eIdColumn, security.id()); - item->setText(eTypeColumn, KMyMoneyUtils::securityTypeToString(security.securityType())); - item->setText(eSecurityNameColumn, security.name()); - item->setText(eSecuritySymbolColumn, security.tradingSymbol()); - item->setText(eMarketColumn, market); - item->setText(eCurrencyColumn, tradingCurrency.tradingSymbol()); - item->setTextAlignment(eCurrencyColumn, Qt::AlignHCenter); - item->setText(eAcctFractionColumn, QString::number(security.smallestAccountFraction())); - - // smallestCashFraction is only applicable for currencies - if (security.isCurrency()) - item->setText(eCashFractionColumn, QString::number(security.smallestCashFraction())); + m_deleteSecurityButton->setEnabled(false); + m_editSecurityButton->setEnabled(false); + + d->m_securitiesProxyModel->invalidate(); + // securities model contains both securities and currencies, so... + // ...search here for securities node and show only this + const auto indexList = d->m_securitiesProxyModel->match(d->m_securitiesProxyModel->index(0, 0), Qt::DisplayRole, QLatin1String("Securities"), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)); + if (!indexList.isEmpty()) + m_securitiesTree->setRootIndex(indexList.first()); } -void KInvestmentView::slotUpdateSecuritiesButtons() +void KInvestmentView::slotEditSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - - if (item) { - MyMoneySecurity security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); - m_editSecurityButton->setEnabled(item->text(eMarketColumn) != m_currencyMarket); - MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); - skip.fill(false); - skip.setBit(IMyMoneyStorage::RefCheckPrice); - m_deleteSecurityButton->setEnabled(!MyMoneyFile::instance()->isReferenced(security, skip)); + auto sec = currentSecurity(); - } else { - m_editSecurityButton->setEnabled(false); - m_deleteSecurityButton->setEnabled(false); + if (!sec.id().isEmpty()) { + QPointer dlg = new KNewInvestmentWizard(sec, this); + dlg->setObjectName("KNewInvestmentWizard"); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(QString()); + delete dlg; } } -void KInvestmentView::slotEditSecurity() +MyMoneySecurity KInvestmentView::currentSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - if (item) { - MyMoneySecurity security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); + MyMoneySecurity sec; - QPointer dlg = new KNewInvestmentWizard(security, this); - dlg->setObjectName("KNewInvestmentWizard"); - if (dlg->exec() == QDialog::Accepted) { - dlg->createObjects(QString()); - try { - // For some reason, the item gets deselected, and the pointer - // invalidated. So fix it here before continuing. - item = m_securitiesList->findItems(security.id(), Qt::MatchExactly).at(0); - m_securitiesList->setCurrentItem(item); - if (item) { - security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); - loadSecurityItem(item, security); - } - } catch (const MyMoneyException &e) { - KMessageBox::error(this, i18n("Failed to edit security: %1", e.what())); - } - } - delete dlg; + auto treeItem = m_securitiesTree->currentIndex(); + if (treeItem.isValid()) { + auto mdlItem = d->m_securitiesProxyModel->index(treeItem.row(), SecuritiesModel::Security, treeItem.parent()); + try { + sec = MyMoneyFile::instance()->security(mdlItem.data(Qt::UserRole).toString()); + } catch (const MyMoneyException &) {} } + return sec; } void KInvestmentView::slotDeleteSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - if (item) - KMyMoneyUtils::deleteSecurity(MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()), this); + auto sec = currentSecurity(); + if (!sec.id().isEmpty()) + KMyMoneyUtils::deleteSecurity(sec, this); } diff --git a/kmymoney/views/kinvestmentviewdecl.ui b/kmymoney/views/kinvestmentview.ui rename from kmymoney/views/kinvestmentviewdecl.ui rename to kmymoney/views/kinvestmentview.ui --- a/kmymoney/views/kinvestmentviewdecl.ui +++ b/kmymoney/views/kinvestmentview.ui @@ -1,13 +1,13 @@ - KInvestmentViewDecl - + KInvestmentView + 0 0 439 - 164 + 274 @@ -72,56 +72,37 @@ - - - - 1 - 1 - - - - + + + Qt::CustomContextMenu Summary of the equities contained in this account, showing your holdings and their most recent price. + + QAbstractItemView::DoubleClicked + + + false + true - - QAbstractItemView::SingleSelection + + 0 false + + false + + + true + true - - - Name - - - - - Symbol - - - - - Value - - - - - Quantity - - - - - Price - - @@ -136,75 +117,64 @@ - - - - 1 - 1 - + + + + + + QAbstractItemView::DoubleClicked + + + false true - - QAbstractItemView::SingleSelection + + 0 false + + false + + + true + true - - - ID - - - - - Type - - - - - Name - - - - - Symbol - - - - - Market - - - - - Currency - - - - - Fraction - - + + false + + + Modify the selected entry + + + Change the security information of the selected entry. + Edit... + + Delete this entry + + + Remove this security item from the file + Delete... @@ -252,6 +222,11 @@ + + KFilterProxySearchLine + QWidget +
kfilterproxysearchline.h
+
KComboBox QComboBox diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -98,7 +98,9 @@ #include "konlinejoboutbox.h" #include "kmymoney.h" #include "kmymoneyutils.h" -#include "models.h" +#include +#include +#include #include using namespace Icons; @@ -231,16 +233,10 @@ connect(m_ledgerView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 8 - m_investmentView = new KInvestmentView(); + m_investmentView = new KInvestmentView(kmymoney, this); viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments")); viewFrames[View::Investments]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInvestment])); - connect(m_investmentView, SIGNAL(accountSelected(QString,QString)), - this, SLOT(slotLedgerSelected(QString,QString))); - connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); - connect(m_investmentView, SIGNAL(investmentRightMouseClick()), kmymoney, SLOT(slotShowInvestmentContextMenu())); - connect(m_investmentView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); - // Page 9 m_reportsView = new KReportsView(); viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports")); @@ -676,29 +672,47 @@ m_reportsView->slotCloseAll(); // disconnect the signals - disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), - Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); - disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), - Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), - Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - - disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), - Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); - disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), - Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), - Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - - disconnect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::slotLoadView); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); @@ -1153,34 +1167,52 @@ Models::instance()->fileOpened(); // connect the needed signals - connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), - Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); - connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), - Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), - Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - - connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), - Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); - connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), - Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); - connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), - Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); - connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), - Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); // inform everyone about new data MyMoneyFile::instance()->preloadCache(); MyMoneyFile::instance()->forceDataChanged(); // views can wait since they are going to be refresed in slotRefreshViews - connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); + connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::slotLoadView); // if we currently see a different page, then select the right one if (page != currentPage()) {