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

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