File indexing completed on 2024-05-12 05:06:09

0001 /*
0002     SPDX-FileCopyrightText: 2004-2017 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "kmymoneypricedlg.h"
0008 
0009 // ----------------------------------------------------------------------------
0010 // QT Includes
0011 
0012 #include <QCheckBox>
0013 #include <QPushButton>
0014 #include <QIcon>
0015 #include <QVBoxLayout>
0016 #include <QMenu>
0017 #include <QVector>
0018 
0019 // ----------------------------------------------------------------------------
0020 // KDE Includes
0021 
0022 #include <KMessageBox>
0023 #include <KLocalizedString>
0024 #include <KTreeWidgetSearchLine>
0025 #include <KTreeWidgetSearchLineWidget>
0026 
0027 // ----------------------------------------------------------------------------
0028 // Project Includes
0029 
0030 #include "ui_kmymoneypricedlg.h"
0031 #include "ui_kupdatestockpricedlg.h"
0032 
0033 #include "icons.h"
0034 #include "kcurrencycalculator.h"
0035 #include "kequitypriceupdatedlg.h"
0036 #include "kmymoneycurrencyselector.h"
0037 #include "kmymoneyutils.h"
0038 #include "kpricetreeitem.h"
0039 #include "kupdatestockpricedlg.h"
0040 #include "mymoneyaccount.h"
0041 #include "mymoneyexception.h"
0042 #include "mymoneyfile.h"
0043 #include "mymoneymoney.h"
0044 #include "mymoneyprice.h"
0045 #include "mymoneysecurity.h"
0046 #include "mymoneyutils.h"
0047 
0048 #include "kmmyesno.h"
0049 
0050 using namespace Icons;
0051 
0052 // duplicated eMenu namespace from menuenums.h for consistency
0053 // there shouldn't be any clash, because we don't need menuenums.h here
0054 namespace eMenu {
0055 enum class Action {
0056     // *************
0057     // The price menu
0058     // *************
0059     NewPrice, DeletePrice,
0060     UpdatePrice, EditPrice,
0061 };
0062 inline qHashSeedType qHash(const Action key, qHashSeedType seed)
0063 {
0064     return ::qHash(static_cast<uint>(key), seed);
0065 }
0066 }
0067 
0068 class KMyMoneyPriceDlgPrivate
0069 {
0070     Q_DISABLE_COPY(KMyMoneyPriceDlgPrivate)
0071     Q_DECLARE_PUBLIC(KMyMoneyPriceDlg)
0072 
0073 public:
0074     explicit KMyMoneyPriceDlgPrivate(KMyMoneyPriceDlg *qq) :
0075         q_ptr(qq),
0076         ui(new Ui::KMyMoneyPriceDlg),
0077         m_currentItem(nullptr),
0078         m_searchWidget(nullptr)
0079     {
0080     }
0081 
0082     ~KMyMoneyPriceDlgPrivate()
0083     {
0084         delete ui;
0085     }
0086 
0087     int editPrice()
0088     {
0089         Q_Q(KMyMoneyPriceDlg);
0090         int rc = QDialog::Rejected;
0091         auto item = ui->m_priceList->currentItem();
0092         if (item) {
0093             MyMoneySecurity from(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().from()));
0094             MyMoneySecurity to(MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().to()));
0095             signed64 fract = MyMoneyMoney::precToDenom(from.pricePrecision());
0096 
0097             QPointer<KCurrencyCalculator> calc =
0098                 new KCurrencyCalculator(from,
0099                                         to,
0100                                         MyMoneyMoney::ONE,
0101                                         item->data(0, Qt::UserRole).value<MyMoneyPrice>().rate(to.id()),
0102                                         item->data(0, Qt::UserRole).value<MyMoneyPrice>().date(),
0103                                         fract,
0104                                         q);
0105             calc->setupPriceEditor();
0106 
0107             rc = calc->exec();
0108             delete calc;
0109         }
0110         return rc;
0111     }
0112 
0113     KMyMoneyPriceDlg      *q_ptr;
0114     Ui::KMyMoneyPriceDlg  *ui;
0115     QTreeWidgetItem*       m_currentItem;
0116     /**
0117       * Search widget for the list
0118       */
0119     KTreeWidgetSearchLineWidget*  m_searchWidget;
0120     QMap<QString, QString>        m_stockNameMap;
0121 };
0122 
0123 KMyMoneyPriceDlg::KMyMoneyPriceDlg(QWidget* parent) :
0124     QDialog(parent),
0125     d_ptr(new KMyMoneyPriceDlgPrivate(this))
0126 {
0127     Q_D(KMyMoneyPriceDlg);
0128     d->ui->setupUi(this);
0129 
0130     // create the searchline widget
0131     // and insert it into the existing layout
0132     d->m_searchWidget = new KTreeWidgetSearchLineWidget(this, d->ui->m_priceList);
0133     d->m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
0134 
0135     d->ui->m_listLayout->insertWidget(0, d->m_searchWidget);
0136 
0137     d->ui->m_priceList->header()->setSortIndicator(0, Qt::AscendingOrder);
0138     d->ui->m_priceList->header()->setStretchLastSection(true);
0139     d->ui->m_priceList->setContextMenuPolicy(Qt::CustomContextMenu);
0140 
0141     d->ui->m_deleteButton->setIcon(Icons::get(Icon::EditRemove));
0142     d->ui->m_newButton->setIcon(Icons::get(Icon::DocumentNew));
0143     d->ui->m_editButton->setIcon(Icons::get(Icon::DocumentEdit));
0144 
0145     d->ui->m_onlineQuoteButton->setIcon(Icons::get(Icon::OnlinePriceUpdate));
0146 
0147     connect(d->ui->m_editButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotEditPrice);
0148     connect(d->ui->m_deleteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotDeletePrice);
0149     connect(d->ui->m_newButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotNewPrice);
0150     connect(d->ui->m_priceList, &QTreeWidget::itemSelectionChanged, this, &KMyMoneyPriceDlg::slotSelectPrice);
0151     connect(d->ui->m_onlineQuoteButton, &QAbstractButton::clicked, this, &KMyMoneyPriceDlg::slotOnlinePriceUpdate);
0152     connect(d->ui->m_priceList, &QWidget::customContextMenuRequested, this, &KMyMoneyPriceDlg::slotShowPriceMenu);
0153 
0154     connect(d->ui->m_showAllPrices, &QAbstractButton::toggled, this, &KMyMoneyPriceDlg::slotLoadWidgets);
0155     connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KMyMoneyPriceDlg::slotLoadWidgets);
0156 
0157     slotLoadWidgets();
0158     slotSelectPrice();
0159 }
0160 
0161 KMyMoneyPriceDlg::~KMyMoneyPriceDlg()
0162 {
0163     Q_D(KMyMoneyPriceDlg);
0164     delete d;
0165 }
0166 
0167 void KMyMoneyPriceDlg::slotLoadWidgets()
0168 {
0169     Q_D(KMyMoneyPriceDlg);
0170     auto file = MyMoneyFile::instance();
0171 
0172     //clear the list and disable the sorting while it loads the widgets, for performance
0173     d->ui->m_priceList->setSortingEnabled(false);
0174     d->ui->m_priceList->clear();
0175     d->m_stockNameMap.clear();
0176 
0177     //load the currencies for investments, which we'll need later
0178     QList<MyMoneyAccount> accList;
0179     file->accountList(accList);
0180     QList<MyMoneyAccount>::const_iterator acc_it;
0181     for (acc_it = accList.constBegin(); acc_it != accList.constEnd(); ++acc_it) {
0182         if ((*acc_it).isInvest()) {
0183             if (d->m_stockNameMap.contains((*acc_it).currencyId())) {
0184                 d->m_stockNameMap[(*acc_it).currencyId()] = QString(d->m_stockNameMap.value((*acc_it).currencyId()) + ", " + (*acc_it).name());
0185             } else {
0186                 d->m_stockNameMap[(*acc_it).currencyId()] = (*acc_it).name();
0187             }
0188         }
0189     }
0190 
0191     //get the price list
0192     MyMoneyPriceList list = file->priceList();
0193     MyMoneyPriceList::ConstIterator it_allPrices;
0194     for (it_allPrices = list.constBegin(); it_allPrices != list.constEnd(); ++it_allPrices) {
0195         const auto& pair = it_allPrices.key();
0196         try {
0197             if (file->security(pair.first).isCurrency() && !file->security(pair.second).isCurrency()) {
0198                 qDebug() << "A currency pair" << pair << "is invalid (from currency to equity). Omitting from listing.";
0199                 continue;
0200             }
0201         } catch (MyMoneyException&) {
0202             qDebug() << "A currency pair" << pair << "is invalid. Omitting from listing.";
0203             continue;
0204         }
0205 
0206         MyMoneyPriceEntries::ConstIterator it_priceItem;
0207         if (d->ui->m_showAllPrices->isChecked()) {
0208             for (it_priceItem = (*it_allPrices).constBegin(); it_priceItem != (*it_allPrices).constEnd(); ++it_priceItem) {
0209                 loadPriceItem(*it_priceItem);
0210             }
0211         } else {
0212             //if it doesn't show all prices, it only shows the most recent occurrence for each price
0213             if ((*it_allPrices).count() > 0) {
0214                 //the prices for each currency are ordered by date in ascending order
0215                 //it gets the last item of the item, which is supposed to be the most recent price
0216                 it_priceItem = (*it_allPrices).constEnd();
0217                 --it_priceItem;
0218                 loadPriceItem(*it_priceItem);
0219             }
0220         }
0221     }
0222     //reenable sorting and sort by the commodity column
0223     d->ui->m_priceList->setSortingEnabled(true);
0224     d->ui->m_priceList->sortByColumn(KPriceTreeItem::ePriceCommodity, Qt::SortOrder::AscendingOrder);
0225 
0226     //update the search widget so the list gets refreshed correctly if it was being filtered
0227     if (!d->m_searchWidget->searchLine()->text().isEmpty())
0228         d->m_searchWidget->searchLine()->updateSearch(d->m_searchWidget->searchLine()->text());
0229 }
0230 
0231 QTreeWidgetItem* KMyMoneyPriceDlg::loadPriceItem(const MyMoneyPrice& basePrice)
0232 {
0233     Q_D(KMyMoneyPriceDlg);
0234     MyMoneySecurity from, to;
0235     auto price = MyMoneyPrice(basePrice);
0236 
0237     auto priceTreeItem = new KPriceTreeItem(d->ui->m_priceList);
0238 
0239     if (!price.isValid())
0240         price = MyMoneyFile::instance()->price(price.from(), price.to(), price.date());
0241 
0242     if (price.isValid()) {
0243         QString priceBase = price.to();
0244         from = MyMoneyFile::instance()->security(price.from());
0245         to = MyMoneyFile::instance()->security(price.to());
0246         if (!to.isCurrency()) {
0247             from = MyMoneyFile::instance()->security(price.to());
0248             to = MyMoneyFile::instance()->security(price.from());
0249             priceBase = price.from();
0250         }
0251 
0252         priceTreeItem->setData(KPriceTreeItem::ePriceCommodity, Qt::UserRole, QVariant::fromValue(price));
0253         priceTreeItem->setText(KPriceTreeItem::ePriceCommodity, (from.isCurrency()) ? from.id() : from.tradingSymbol());
0254         priceTreeItem->setText(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id()));
0255         priceTreeItem->setToolTip(KPriceTreeItem::ePriceStockName, (from.isCurrency()) ? QString() : d->m_stockNameMap.value(from.id()));
0256         priceTreeItem->setText(KPriceTreeItem::ePriceCurrency, to.id());
0257         priceTreeItem->setText(KPriceTreeItem::ePriceDate, MyMoneyUtils::formatDate(price.date()));
0258         priceTreeItem->setData(KPriceTreeItem::ePriceDate, KPriceTreeItem::OrderRole, QVariant(price.date()));
0259         priceTreeItem->setText(KPriceTreeItem::ePricePrice, price.rate(priceBase).formatMoney("", from.pricePrecision()));
0260         priceTreeItem->setTextAlignment(KPriceTreeItem::ePricePrice, Qt::AlignRight | Qt::AlignVCenter);
0261         priceTreeItem->setData(KPriceTreeItem::ePricePrice, KPriceTreeItem::OrderRole, QVariant::fromValue(price.rate(priceBase)));
0262         priceTreeItem->setText(KPriceTreeItem::ePriceSource, price.source());
0263     }
0264     return priceTreeItem;
0265 }
0266 
0267 void KMyMoneyPriceDlg::slotSelectPrice()
0268 {
0269     Q_D(KMyMoneyPriceDlg);
0270     QTreeWidgetItem* item = 0;
0271     if (d->ui->m_priceList->selectedItems().count() > 0) {
0272         item = d->ui->m_priceList->selectedItems().at(0);
0273     }
0274     d->m_currentItem = item;
0275     d->ui->m_editButton->setEnabled(item != 0);
0276     bool deleteEnabled = (item != 0);
0277 
0278     //if one of the selected entries is a default, then deleting is disabled
0279     QList<QTreeWidgetItem*> itemsList = d->ui->m_priceList->selectedItems();
0280     QList<QTreeWidgetItem*>::const_iterator item_it;
0281     for (item_it = itemsList.constBegin(); item_it != itemsList.constEnd(); ++item_it) {
0282         MyMoneyPrice price = (*item_it)->data(0, Qt::UserRole).value<MyMoneyPrice>();
0283         if (price.source() == "KMyMoney")
0284             deleteEnabled = false;
0285     }
0286     d->ui->m_deleteButton->setEnabled(deleteEnabled);
0287 
0288     // Modification of automatically added entries is not allowed
0289     // Multiple entries cannot be edited at once
0290     if (item) {
0291         MyMoneyPrice price = item->data(0, Qt::UserRole).value<MyMoneyPrice>();
0292         if (price.source() == "KMyMoney" || itemsList.count() > 1)
0293             d->ui->m_editButton->setEnabled(false);
0294 //    Q_EMIT selectObject(price);
0295     }
0296 }
0297 
0298 void KMyMoneyPriceDlg::slotNewPrice()
0299 {
0300     Q_D(KMyMoneyPriceDlg);
0301     QPointer<KUpdateStockPriceDlg> dlg = new KUpdateStockPriceDlg(this);
0302     try {
0303         auto item = d->ui->m_priceList->currentItem();
0304         if (item) {
0305             MyMoneySecurity security;
0306             security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().from());
0307             dlg->ui->m_security->setSecurity(security);
0308             security = MyMoneyFile::instance()->security(item->data(0, Qt::UserRole).value<MyMoneyPrice>().to());
0309             dlg->ui->m_currency->setSecurity(security);
0310         }
0311 
0312         if (dlg->exec()) {
0313             MyMoneyPrice price(dlg->ui->m_security->security().id(), dlg->ui->m_currency->security().id(), dlg->date(), MyMoneyMoney::ONE, QString());
0314             QTreeWidgetItem* p = loadPriceItem(price);
0315             d->ui->m_priceList->setCurrentItem(p, true);
0316             // If the user cancels the following operation, we delete the new item
0317             // and re-select any previously selected one
0318             if (d->editPrice() == Rejected) {
0319                 delete p;
0320                 if (item)
0321                     d->ui->m_priceList->setCurrentItem(item, true);
0322             }
0323         }
0324     } catch (...) {
0325         delete dlg;
0326         throw;
0327     }
0328     delete dlg;
0329 }
0330 
0331 void KMyMoneyPriceDlg::slotEditPrice()
0332 {
0333     Q_D(KMyMoneyPriceDlg);
0334     d->editPrice();
0335 }
0336 
0337 void KMyMoneyPriceDlg::slotDeletePrice()
0338 {
0339     Q_D(KMyMoneyPriceDlg);
0340     QList<QTreeWidgetItem*> listItems = d->ui->m_priceList->selectedItems();
0341     if (listItems.count() > 0) {
0342         if (KMessageBox::questionTwoActions(
0343                 this,
0344                 i18np("Do you really want to delete the selected price entry?", "Do you really want to delete the selected price entries?", listItems.count()),
0345                 i18n("Delete price information"),
0346                 KMMYesNo::yes(),
0347                 KMMYesNo::no(),
0348                 "DeletePrice")
0349             == KMessageBox::PrimaryAction) {
0350             MyMoneyFileTransaction ft;
0351             try {
0352                 QList<QTreeWidgetItem*>::const_iterator price_it;
0353                 for (price_it = listItems.constBegin(); price_it != listItems.constEnd(); ++price_it) {
0354                     MyMoneyFile::instance()->removePrice((*price_it)->data(0, Qt::UserRole).value<MyMoneyPrice>());
0355                 }
0356                 ft.commit();
0357             } catch (const MyMoneyException &) {
0358                 qDebug("Cannot delete price");
0359             }
0360         }
0361     }
0362 }
0363 
0364 void KMyMoneyPriceDlg::slotOnlinePriceUpdate()
0365 {
0366     QPointer<KEquityPriceUpdateDlg> dlg = new KEquityPriceUpdateDlg(this);
0367     if (dlg->exec() == Accepted && dlg)
0368         dlg->storePrices();
0369     delete dlg;
0370 }
0371 
0372 void KMyMoneyPriceDlg::slotShowPriceMenu(const QPoint& p)
0373 {
0374     Q_D(KMyMoneyPriceDlg);
0375     auto item = d->ui->m_priceList->itemAt(p);
0376     if (item) {
0377         d->ui->m_priceList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
0378         const auto price = item->data(0, Qt::UserRole).value<MyMoneyPrice>();
0379         const auto cond1 = !price.from().isEmpty() && price.source() != QLatin1String("KMyMoney");
0380         const auto cond2 = cond1 && MyMoneyFile::instance()->security(price.from()).isCurrency();
0381         auto menu = new QMenu;
0382         typedef void(KMyMoneyPriceDlg::*KMyMoneyPriceDlgFunc)();
0383         struct actionInfo {
0384             eMenu::Action        action;
0385             KMyMoneyPriceDlgFunc callback;
0386             QString              text;
0387             Icon                 icon;
0388             bool                 enabled;
0389         };
0390 
0391         const QVector<actionInfo> actionInfos {
0392             {eMenu::Action::NewPrice,    &KMyMoneyPriceDlg::slotNewPrice,          i18n("New price..."),           Icon::DocumentNew,       true},
0393             {eMenu::Action::EditPrice,   &KMyMoneyPriceDlg::slotEditPrice,         i18n("Edit price..."),          Icon::DocumentEdit,      cond1},
0394             {eMenu::Action::UpdatePrice, &KMyMoneyPriceDlg::slotOnlinePriceUpdate, i18n("Online Price Update..."), Icon::OnlinePriceUpdate, cond2},
0395             {eMenu::Action::DeletePrice, &KMyMoneyPriceDlg::slotDeletePrice,       i18n("Delete price..."), Icon::EditRemove,        cond1},
0396         };
0397 
0398         QList<QAction*> LUTActions;
0399         for (const auto& info : actionInfos) {
0400             auto a = new QAction(Icons::get(info.icon), info.text, nullptr); // WARNING: no empty Icon::Empty here
0401             a->setEnabled(info.enabled);
0402             connect(a, &QAction::triggered, this, info.callback);
0403             LUTActions.append(a);
0404         }
0405         menu->addSection(i18nc("@title:menu", "Price options"));
0406         menu->addActions(LUTActions);
0407         menu->exec(QCursor::pos());
0408     }
0409 }