File indexing completed on 2024-05-19 05:06:51

0001 /*
0002     SPDX-FileCopyrightText: 2004-2017 Thomas Baumgart <tbaumgart@kde.org>
0003     SPDX-FileCopyrightText: 2004 Kevin Tambascio <ktambascio@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2006 Ace Jones <acejones@users.sourceforge.net>
0005     SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "kequitypriceupdatedlg.h"
0010 
0011 // ----------------------------------------------------------------------------
0012 // QT Includes
0013 
0014 #include <QList>
0015 #include <QPointer>
0016 #include <QPushButton>
0017 #include <QTimer>
0018 #include <QToolButton>
0019 
0020 // ----------------------------------------------------------------------------
0021 // KDE Includes
0022 
0023 #include <KMessageBox>
0024 #include <KTextEdit>
0025 #include <KConfig>
0026 #include <KConfigGroup>
0027 #include <KSharedConfig>
0028 #include <KStandardGuiItem>
0029 #include <KLocalizedString>
0030 #include <KColorScheme>
0031 
0032 // ----------------------------------------------------------------------------
0033 // Project Includes
0034 
0035 #include "ui_kequitypriceupdatedlg.h"
0036 
0037 #include "dialogenums.h"
0038 #include "icons.h"
0039 #include "kequitypriceupdateconfdlg.h"
0040 #include "kmymoneyutils.h"
0041 #include "mymoneyaccount.h"
0042 #include "mymoneyexception.h"
0043 #include "mymoneyfile.h"
0044 #include "mymoneymoney.h"
0045 #include "mymoneyprice.h"
0046 #include "mymoneysecurity.h"
0047 #include "mymoneystatement.h"
0048 /// @todo AlkOnlineQuote remove following include when ported to Alkimia
0049 #include "webpricequote.h"
0050 #include "widgethintframe.h"
0051 
0052 #include "kmmyesno.h"
0053 
0054 #define WEBID_COL       0
0055 #define NAME_COL        1
0056 #define PRICE_COL       2
0057 #define DATE_COL        3
0058 #define KMMID_COL       4
0059 #define SOURCE_COL      5
0060 
0061 class KEquityPriceUpdateDlgPrivate
0062 {
0063     Q_DISABLE_COPY(KEquityPriceUpdateDlgPrivate)
0064     Q_DECLARE_PUBLIC(KEquityPriceUpdateDlg)
0065 
0066 public:
0067     explicit KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg* qq)
0068         : q_ptr(qq)
0069         , ui(new Ui::KEquityPriceUpdateDlg)
0070         , m_fUpdateAll(false)
0071         , m_updatingPricePolicy(eDialogs::UpdatePrice::All)
0072         , m_splitRegex("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", QRegularExpression::CaseInsensitiveOption)
0073     {
0074     }
0075 
0076     ~KEquityPriceUpdateDlgPrivate()
0077     {
0078         delete ui;
0079     }
0080 
0081     void init(const QString& securityId)
0082     {
0083         Q_Q(KEquityPriceUpdateDlg);
0084         ui->setupUi(q);
0085         m_fUpdateAll = false;
0086         QStringList headerList;
0087         headerList << i18n("ID") << i18nc("Equity name", "Name")
0088                    << i18n("Price") << i18n("Date");
0089 
0090         ui->lvEquityList->header()->setSortIndicator(0, Qt::AscendingOrder);
0091         ui->lvEquityList->setColumnWidth(NAME_COL, 125);
0092 
0093         // This is a "get it up and running" hack.  Will replace this in the future.
0094         headerList << i18nc("Internal identifier", "Internal ID")
0095                    << i18nc("Online quote source", "Source");
0096         ui->lvEquityList->setColumnWidth(KMMID_COL, 0);
0097 
0098         ui->lvEquityList->setHeaderLabels(headerList);
0099 
0100         ui->lvEquityList->setSelectionMode(QAbstractItemView::MultiSelection);
0101         ui->lvEquityList->setAllColumnsShowFocus(true);
0102 
0103         ui->closeStatusButton->setIcon(Icons::get(Icons::Icon::DialogClose));
0104         q->connect(ui->closeStatusButton, &QToolButton::clicked, ui->statusContainer, &QWidget::hide);
0105 
0106         // hide the status widgets until there is activity
0107         ui->statusContainer->hide();
0108 
0109         auto file = MyMoneyFile::instance();
0110 
0111         //
0112         // Add each price pair that we know about
0113         //
0114 
0115         // send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY.
0116         // for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch()
0117         QRegularExpressionMatch match(m_splitRegex.match(securityId));
0118         MyMoneySecurityPair currencyIds;
0119         if (match.hasMatch()) {
0120             currencyIds = MyMoneySecurityPair(match.captured(1), match.captured(2));
0121         }
0122 
0123         MyMoneyPriceList prices = file->priceList();
0124         for (MyMoneyPriceList::ConstIterator it_price = prices.constBegin(); it_price != prices.constEnd(); ++it_price) {
0125             const MyMoneySecurityPair& pair = it_price.key();
0126             if (file->security(pair.first).isCurrency() && (securityId.isEmpty() || (pair == currencyIds))) {
0127                 if (pair.first.trimmed().isEmpty() || pair.second.trimmed().isEmpty()) {
0128                     qDebug() << "A currency pair" << pair << "has one of its elements present, while the other one is empty. Omitting.";
0129                     continue;
0130                 }
0131                 if (!file->security(pair.second).isCurrency()) {
0132                     qDebug() << "A currency pair" << pair << "is invalid (from currency to equity). Omitting.";
0133                     continue;
0134                 }
0135                 const MyMoneyPriceEntries& entries = (*it_price);
0136                 if (entries.count() > 0 && entries.begin().key() <= QDate::currentDate()) {
0137                     addPricePair(pair, false);
0138                 }
0139             }
0140         }
0141 
0142         //
0143         // Add each investment
0144         //
0145 
0146         QList<MyMoneySecurity> securities = file->securityList();
0147         for (QList<MyMoneySecurity>::const_iterator it = securities.constBegin(); it != securities.constEnd(); ++it) {
0148             if (!(*it).isCurrency() //
0149                     && (securityId.isEmpty() || ((*it).id() == securityId)) //
0150                     && !(*it).value("kmm-online-source").isEmpty()
0151                ) {
0152                 addInvestment(*it);
0153             }
0154         }
0155 
0156         // if list is empty and a price pair has been requested, add it
0157         if (ui->lvEquityList->invisibleRootItem()->childCount() == 0 && !securityId.isEmpty()) {
0158             addPricePair(currencyIds, true);
0159         }
0160 
0161         q->connect(ui->btnUpdateSelected, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateSelectedClicked);
0162         q->connect(ui->btnUpdateAll, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateAllClicked);
0163 
0164         q->connect(ui->m_fromDate, &KMyMoneyDateEdit::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
0165         q->connect(ui->m_toDate, &KMyMoneyDateEdit::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
0166 
0167         q->connect(&m_webQuote, &WebPriceQuote::csvquote, q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote);
0168         q->connect(&m_webQuote, &WebPriceQuote::quote, q, &KEquityPriceUpdateDlg::slotReceivedQuote);
0169         q->connect(&m_webQuote, &WebPriceQuote::failed, q, &KEquityPriceUpdateDlg::slotQuoteFailed);
0170         q->connect(&m_webQuote, &WebPriceQuote::status, q, &KEquityPriceUpdateDlg::logStatusMessage);
0171         q->connect(&m_webQuote, &WebPriceQuote::error, q, &KEquityPriceUpdateDlg::logErrorMessage);
0172 
0173         q->connect(ui->lvEquityList, &QTreeWidget::itemSelectionChanged, q, &KEquityPriceUpdateDlg::slotUpdateSelection);
0174 
0175         ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(i18nc("@action:button Configuration of price update", "Configure"));
0176         q->connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotConfigureClicked);
0177 
0178         if (!securityId.isEmpty()) {
0179             ui->btnUpdateSelected->hide();
0180             ui->btnUpdateAll->hide();
0181             // delete layout1;
0182 
0183             QTimer::singleShot(100, q, SLOT(slotUpdateAllClicked()));
0184         }
0185 
0186         // Hide OK button until we have received the first update
0187         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0188         updateButtonState();
0189 
0190         // previous versions of this dialog allowed to store a "Don't ask again" switch.
0191         // Since we don't support it anymore, we just get rid of it
0192         KSharedConfigPtr config = KSharedConfig::openConfig();
0193         KConfigGroup grp = config->group("Notification Messages");
0194         grp.deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed");
0195         grp.sync();
0196         grp = config->group("Equity Price Update");
0197         int policyValue = grp.readEntry("PriceUpdatingPolicy", (int)eDialogs::UpdatePrice::Missing);
0198         if (policyValue > (int)eDialogs::UpdatePrice::Ask || policyValue < (int)eDialogs::UpdatePrice::All)
0199             m_updatingPricePolicy = eDialogs::UpdatePrice::Missing;
0200         else
0201             m_updatingPricePolicy = static_cast<eDialogs::UpdatePrice>(policyValue);
0202     }
0203 
0204     void updateButtonState()
0205     {
0206         if (ui->m_fromDate->date().isValid() && ui->m_toDate->date().isValid()) {
0207             // Only enable the update all button if there is an item in the list
0208             ui->btnUpdateAll->setEnabled(ui->lvEquityList->invisibleRootItem()->childCount() > 0);
0209             // Only enable the update button if there is a selection
0210             ui->btnUpdateSelected->setEnabled(!ui->lvEquityList->selectedItems().empty());
0211         } else {
0212             // upon invalid date, disable all buttons
0213             ui->btnUpdateAll->setEnabled(false);
0214             ui->btnUpdateSelected->setEnabled(false);
0215         }
0216     }
0217 
0218     void addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance)
0219     {
0220         Q_ASSERT(!pair.first.trimmed().isEmpty());
0221         Q_ASSERT(!pair.second.trimmed().isEmpty());
0222 
0223         auto file = MyMoneyFile::instance();
0224 
0225         const auto symbol = QString::fromLatin1("%1 > %2").arg(pair.first, pair.second);
0226         const auto id = QString::fromLatin1("%1 %2").arg(pair.first, pair.second);
0227         // Check that the pair does not already exist
0228         if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
0229             const MyMoneyPrice &pr = file->price(pair.first, pair.second);
0230             if (pr.source() != QLatin1String("KMyMoney")) {
0231                 bool keep = true;
0232                 if ((pair.first == file->baseCurrency().id())
0233                         || (pair.second == file->baseCurrency().id())) {
0234                     const QString& foreignCurrency = file->foreignCurrency(pair.first, pair.second);
0235                     // check that the foreign currency is still in use
0236                     QList<MyMoneyAccount>::const_iterator it_a;
0237                     QList<MyMoneyAccount> list;
0238                     file->accountList(list);
0239                     for (it_a = list.constBegin(); !dontCheckExistance && it_a != list.constEnd(); ++it_a) {
0240                         // if it's an account denominated in the foreign currency
0241                         // keep it
0242                         if (((*it_a).currencyId() == foreignCurrency)
0243                                 && !(*it_a).isClosed())
0244                             break;
0245                         // if it's an investment traded in the foreign currency
0246                         // keep it
0247                         if ((*it_a).isInvest() && !(*it_a).isClosed()) {
0248                             MyMoneySecurity sec = file->security((*it_a).currencyId());
0249                             if (sec.tradingCurrency() == foreignCurrency)
0250                                 break;
0251                         }
0252                     }
0253                     // if it is in use, it_a is not equal to list.end()
0254                     if (it_a == list.constEnd() && !dontCheckExistance)
0255                         keep = false;
0256                 }
0257 
0258                 if (keep) {
0259                     auto item = new QTreeWidgetItem();
0260                     item->setText(WEBID_COL, symbol);
0261                     item->setText(NAME_COL, i18n("%1 units in %2", pair.first, pair.second));
0262                     if (pr.isValid()) {
0263                         MyMoneySecurity fromCurrency = file->currency(pair.second);
0264                         MyMoneySecurity toCurrency = file->currency(pair.first);
0265                         item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0266                         item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
0267                     }
0268                     item->setText(KMMID_COL, id);
0269                     item->setText(SOURCE_COL, "KMyMoney Currency");  // This string value should not be localized
0270                     ui->lvEquityList->invisibleRootItem()->addChild(item);
0271                 }
0272             }
0273         }
0274     }
0275 
0276     void addInvestment(const MyMoneySecurity& inv)
0277     {
0278         const auto id = inv.id();
0279         // Check that the pair does not already exist
0280         if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
0281             auto file = MyMoneyFile::instance();
0282             // check that the security is still in use
0283             QList<MyMoneyAccount>::const_iterator it_a;
0284             QList<MyMoneyAccount> list;
0285             file->accountList(list);
0286             for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
0287                 if ((*it_a).isInvest()
0288                         && ((*it_a).currencyId() == inv.id())
0289                         && !(*it_a).isClosed())
0290                     break;
0291             }
0292             // if it is in use, it_a is not equal to list.end()
0293             if (it_a != list.constEnd()) {
0294                 QString webID;
0295                 WebPriceQuoteSource onlineSource(inv.value("kmm-online-source"));
0296                 if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::IdentificationNumber)
0297                     webID = inv.value("kmm-security-id");   // insert ISIN number...
0298                 else if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::Name)
0299                     webID = inv.name();                     // ...or name...
0300                 else
0301                     webID = inv.tradingSymbol();            // ...or symbol
0302 
0303                 QTreeWidgetItem* item = new QTreeWidgetItem();
0304                 item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText));
0305                 if (webID.isEmpty()) {
0306                     webID = i18n("[No identifier]");
0307                     item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText));
0308                 }
0309                 item->setText(WEBID_COL, webID);
0310                 item->setText(NAME_COL, inv.name());
0311                 MyMoneySecurity currency = file->currency(inv.tradingCurrency());
0312                 const MyMoneyPrice &pr = file->price(id.toUtf8(), inv.tradingCurrency());
0313                 if (pr.isValid()) {
0314                     item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), inv.pricePrecision()));
0315                     item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
0316                 }
0317                 item->setText(KMMID_COL, id);
0318                 if (inv.value("kmm-online-quote-system") == "Finance::Quote")
0319                     item->setText(SOURCE_COL, QString("Finance::Quote %1").arg(inv.value("kmm-online-source")));
0320                 else
0321                     item->setText(SOURCE_COL, inv.value("kmm-online-source"));
0322 
0323                 ui->lvEquityList->invisibleRootItem()->addChild(item);
0324 
0325                 // If this investment is denominated in a foreign currency, ensure that
0326                 // the appropriate price pair is also on the list
0327 
0328                 if (currency.id() != file->baseCurrency().id()) {
0329                     addPricePair(MyMoneySecurityPair(currency.id(), file->baseCurrency().id()), false);
0330                 }
0331             }
0332         }
0333     }
0334 
0335     KEquityPriceUpdateDlg* q_ptr;
0336     Ui::KEquityPriceUpdateDlg* ui;
0337     bool m_fUpdateAll;
0338     eDialogs::UpdatePrice m_updatingPricePolicy;
0339     WebPriceQuote m_webQuote;
0340     QRegularExpression m_splitRegex;
0341 };
0342 
0343 KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent, const QString& securityId) :
0344     QDialog(parent),
0345     d_ptr(new KEquityPriceUpdateDlgPrivate(this))
0346 {
0347     Q_D(KEquityPriceUpdateDlg);
0348     d->init(securityId);
0349 
0350     auto decorateEditWidget = [&](const QDate& date, QWidget* widget) {
0351         if (widget) {
0352             WidgetHintFrame::hide(widget, QString());
0353             if (!date.isValid()) {
0354                 WidgetHintFrame::show(widget, i18nc("@info:tooltip", "The date is invalid."));
0355             }
0356         }
0357     };
0358 
0359     auto frameCollection = new WidgetHintFrameCollection(this);
0360     frameCollection->addFrame(new WidgetHintFrame(d->ui->m_fromDate));
0361     frameCollection->addFrame(new WidgetHintFrame(d->ui->m_toDate));
0362 
0363     connect(d->ui->m_fromDate, &KMyMoneyDateEdit::dateValidityChanged, this, [&](const QDate& date) {
0364         Q_D(KEquityPriceUpdateDlg);
0365         decorateEditWidget(date, qobject_cast<QWidget*>(sender()));
0366         d->updateButtonState();
0367     });
0368     connect(d->ui->m_toDate, &KMyMoneyDateEdit::dateValidityChanged, this, [&](const QDate& date) {
0369         Q_D(KEquityPriceUpdateDlg);
0370         decorateEditWidget(date, qobject_cast<QWidget*>(sender()));
0371         d->updateButtonState();
0372     });
0373 }
0374 
0375 KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg()
0376 {
0377     Q_D(KEquityPriceUpdateDlg);
0378     auto config = KSharedConfig::openConfig();
0379     auto grp = config->group("Equity Price Update");
0380     grp.writeEntry("PriceUpdatingPolicy", static_cast<int>(d->m_updatingPricePolicy));
0381     grp.sync();
0382     delete d;
0383 }
0384 
0385 void KEquityPriceUpdateDlg::logErrorMessage(const QString& message)
0386 {
0387     logStatusMessage(QString("<font color=\"red\"><b>") + message + QString("</b></font>"));
0388 }
0389 
0390 void KEquityPriceUpdateDlg::logStatusMessage(const QString& message)
0391 {
0392     Q_D(KEquityPriceUpdateDlg);
0393     d->ui->lbStatus->append(message);
0394 }
0395 
0396 MyMoneyPrice KEquityPriceUpdateDlg::price(const QString& id) const
0397 {
0398     Q_D(const KEquityPriceUpdateDlg);
0399     MyMoneyPrice price;
0400     QTreeWidgetItem* item = nullptr;
0401     QList<QTreeWidgetItem*> foundItems = d->ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL);
0402 
0403     if (! foundItems.empty())
0404         item = foundItems.at(0);
0405 
0406     if (item) {
0407         MyMoneyMoney rate(item->text(PRICE_COL));
0408         if (!rate.isZero()) {
0409             QString kmm_id = item->text(KMMID_COL).toUtf8();
0410 
0411             // if the ID has a space, then this is TWO ID's, so it's a currency quote
0412             if (kmm_id.contains(" ")) {
0413                 QStringList ids = kmm_id.split(' ', Qt::SkipEmptyParts);
0414                 QString fromid = ids[0].toUtf8();
0415                 QString toid = ids[1].toUtf8();
0416                 price = MyMoneyPrice(fromid, toid, QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
0417             } else
0418                 // otherwise, it's a security quote
0419             {
0420                 MyMoneySecurity security = MyMoneyFile::instance()->security(kmm_id);
0421                 price = MyMoneyPrice(kmm_id, security.tradingCurrency(), QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
0422             }
0423         }
0424     }
0425     return price;
0426 }
0427 
0428 void KEquityPriceUpdateDlg::storePrices()
0429 {
0430     Q_D(KEquityPriceUpdateDlg);
0431     // update the new prices into the equities
0432 
0433     auto file = MyMoneyFile::instance();
0434     QString name;
0435 
0436     MyMoneyFileTransaction ft;
0437     try {
0438         for (auto i = 0; i < d->ui->lvEquityList->invisibleRootItem()->childCount(); ++i) {
0439             QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(i);
0440             // turn on signals before we modify the last entry in the list
0441             file->blockSignals(i < d->ui->lvEquityList->invisibleRootItem()->childCount() - 1);
0442 
0443             MyMoneyMoney rate(item->text(PRICE_COL));
0444             if (!rate.isZero()) {
0445                 QString id = item->text(KMMID_COL);
0446                 QString fromid;
0447                 QString toid;
0448 
0449                 // if the ID has a space, then this is TWO ID's, so it's a currency quote
0450                 if (id.contains(QLatin1Char(' '))) {
0451                     QStringList ids = id.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0452                     fromid = ids.at(0);
0453                     toid = ids.at(1);
0454                     name = QString::fromLatin1("%1 --> %2").arg(fromid, toid);
0455                 } else { // otherwise, it's a security quote
0456                     MyMoneySecurity security = file->security(id);
0457                     name = security.name();
0458                     fromid = id;
0459                     toid = security.tradingCurrency();
0460                 }
0461                 // TODO (Ace) Better handling of the case where there is already a price
0462                 // for this date.  Currently, it just overrides the old value.  Really it
0463                 // should check to see if the price is the same and prompt the user.
0464                 file->addPrice(MyMoneyPrice(fromid, toid, QDate::fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)));
0465             }
0466         }
0467         ft.commit();
0468 
0469     } catch (const MyMoneyException &) {
0470         qDebug("Unable to add price information for %s", qPrintable(name));
0471     }
0472 }
0473 
0474 void KEquityPriceUpdateDlg::slotConfigureClicked()
0475 {
0476     Q_D(KEquityPriceUpdateDlg);
0477     QPointer<EquityPriceUpdateConfDlg> dlg = new EquityPriceUpdateConfDlg(d->m_updatingPricePolicy);
0478     if (dlg->exec() == QDialog::Accepted)
0479         d->m_updatingPricePolicy = dlg->policy();
0480     delete dlg;
0481 }
0482 
0483 void KEquityPriceUpdateDlg::slotUpdateSelection()
0484 {
0485     Q_D(KEquityPriceUpdateDlg);
0486     d->updateButtonState();
0487 }
0488 
0489 void KEquityPriceUpdateDlg::slotUpdateSelectedClicked()
0490 {
0491     Q_D(KEquityPriceUpdateDlg);
0492     // disable sorting while the update is running to maintain the current order of items on which
0493     // the update process depends and which could be changed with sorting enabled due to the updated values
0494     d->ui->lvEquityList->setSortingEnabled(false);
0495 
0496     // show the status widgets
0497     d->ui->statusContainer->show();
0498     d->ui->prgOnlineProgress->show();
0499 
0500     auto item = d->ui->lvEquityList->invisibleRootItem()->child(0);
0501     auto skipCnt = 1;
0502     while (item && !item->isSelected()) {
0503         item = d->ui->lvEquityList->invisibleRootItem()->child(skipCnt);
0504         ++skipCnt;
0505     }
0506     d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date());
0507     if (item) {
0508         d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
0509         d->ui->prgOnlineProgress->setValue(skipCnt);
0510         d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
0511 
0512     } else {
0513 
0514         logErrorMessage("No security selected.");
0515     }
0516 }
0517 
0518 void KEquityPriceUpdateDlg::slotUpdateAllClicked()
0519 {
0520     Q_D(KEquityPriceUpdateDlg);
0521     // disable sorting while the update is running to maintain the current order of items on which
0522     // the update process depends and which could be changed with sorting enabled due to the updated values
0523     d->ui->lvEquityList->setSortingEnabled(false);
0524 
0525     // show the status widgets
0526     d->ui->statusContainer->show();
0527     d->ui->prgOnlineProgress->show();
0528 
0529     QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(0);
0530     if (item) {
0531         d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
0532         d->ui->prgOnlineProgress->setValue(1);
0533         d->m_fUpdateAll = true;
0534         d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
0535 
0536     } else {
0537         logErrorMessage("Security list is empty.");
0538     }
0539 }
0540 
0541 void KEquityPriceUpdateDlg::slotDateChanged()
0542 {
0543     Q_D(KEquityPriceUpdateDlg);
0544     QSignalBlocker blockFrom(d->ui->m_fromDate);
0545     QSignalBlocker blockTo(d->ui->m_toDate);
0546     if (d->ui->m_toDate->date() > QDate::currentDate())
0547         d->ui->m_toDate->setDate(QDate::currentDate());
0548     if (d->ui->m_fromDate->date() > d->ui->m_toDate->date())
0549         d->ui->m_fromDate->setDate(d->ui->m_toDate->date());
0550 }
0551 
0552 void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _kmmID, const QString& _webID)
0553 {
0554     Q_D(KEquityPriceUpdateDlg);
0555     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0556     QTreeWidgetItem* item = nullptr;
0557 
0558     if (! foundItems.empty())
0559         item = foundItems.at(0);
0560 
0561     // Give the user some options
0562     int result;
0563     if (_kmmID.contains(" ")) {
0564         if (item)
0565             result = KMessageBox::warningContinueCancel(this, i18n("Failed to retrieve an exchange rate for %1 from %2. It will be skipped this time.", _webID, item->text(SOURCE_COL)), i18n("Price Update Failed"));
0566         else
0567             return;
0568     } else if (!item) {
0569         return;
0570     } else {
0571         result = KMessageBox::questionTwoActionsCancel(
0572             this,
0573             QString::fromLatin1("<qt>%1</qt>")
0574                 .arg(i18n("Failed to retrieve a quote for %1 from %2.  Press <b>No</b> to remove the online price source from this security permanently, "
0575                           "<b>Yes</b> to continue updating this security during future price updates or <b>Cancel</b> to stop the current update operation.",
0576                           _webID,
0577                           item->text(SOURCE_COL))),
0578             i18n("Price Update Failed"),
0579             KMMYesNo::yes(),
0580             KMMYesNo::no());
0581     }
0582 
0583     if (result == KMessageBox::SecondaryAction) {
0584         // Disable price updates for this security
0585 
0586         MyMoneyFileTransaction ft;
0587         try {
0588             // Get this security (by ID)
0589             MyMoneySecurity security = MyMoneyFile::instance()->security(_kmmID.toUtf8());
0590 
0591             // Set the quote source to blank
0592             security.setValue("kmm-online-source", QString());
0593             security.setValue("kmm-online-quote-system", QString());
0594 
0595             // Re-commit the security
0596             MyMoneyFile::instance()->modifySecurity(security);
0597             ft.commit();
0598         } catch (const MyMoneyException &e) {
0599             KMessageBox::error(this, QString("<qt>") + i18n("Cannot update security <b>%1</b>: %2", _webID, QString::fromLatin1(e.what())) + QString("</qt>"), i18n("Price Update Failed"));
0600         }
0601     }
0602 
0603     // As long as the user doesn't want to cancel, move on!
0604     if (result != KMessageBox::Cancel) {
0605         QTreeWidgetItem* next = nullptr;
0606         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0607         item->setSelected(false);
0608 
0609         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0610         // need to parse the list to find the next selected one
0611         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0612         if (!d->m_fUpdateAll) {
0613             while (next && !next->isSelected()) {
0614                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0615                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0616             }
0617         }
0618         if (next) {
0619             d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0620         } else {
0621             finishUpdate();
0622         }
0623     } else {
0624         finishUpdate();
0625     }
0626 }
0627 
0628 void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _kmmID, const QString& _webID, MyMoneyStatement& st)
0629 {
0630     Q_D(KEquityPriceUpdateDlg);
0631     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0632     QTreeWidgetItem* item = nullptr;
0633 
0634     if (! foundItems.empty())
0635         item = foundItems.at(0);
0636 
0637     QTreeWidgetItem* next = nullptr;
0638 
0639     if (item) {
0640         auto file = MyMoneyFile::instance();
0641         MyMoneySecurity fromCurrency, toCurrency;
0642 
0643         if (!_kmmID.contains(QLatin1Char(' '))) {
0644             try {
0645                 toCurrency = MyMoneyFile::instance()->security(_kmmID);
0646                 fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
0647             } catch (const MyMoneyException &) {
0648                 fromCurrency = toCurrency = MyMoneySecurity();
0649             }
0650 
0651         } else {
0652             QRegularExpressionMatch match(d->m_splitRegex.match(_kmmID));
0653             if (match.hasMatch()) {
0654                 try {
0655                     fromCurrency = MyMoneyFile::instance()->security(match.captured(2).toUtf8());
0656                     toCurrency = MyMoneyFile::instance()->security(match.captured(1).toUtf8());
0657                 } catch (const MyMoneyException &) {
0658                     fromCurrency = toCurrency = MyMoneySecurity();
0659                 }
0660             }
0661         }
0662 
0663         if (d->m_updatingPricePolicy != eDialogs::UpdatePrice::All) {
0664             QStringList qSources = WebPriceQuote::quoteSources();
0665             for (auto it = st.m_listPrices.begin(); it != st.m_listPrices.end();) {
0666                 MyMoneyPrice storedPrice = file->price(toCurrency.id(), fromCurrency.id(), (*it).m_date, true);
0667                 bool priceValid = storedPrice.isValid();
0668                 if (!priceValid)
0669                     ++it;
0670                 else {
0671                     switch(d->m_updatingPricePolicy) {
0672                     case eDialogs::UpdatePrice::Missing:
0673                         it = st.m_listPrices.erase(it);
0674                         break;
0675                     case eDialogs::UpdatePrice::Downloaded:
0676                         if (!qSources.contains(storedPrice.source()))
0677                             it = st.m_listPrices.erase(it);
0678                         else
0679                             ++it;
0680                         break;
0681                     case eDialogs::UpdatePrice::SameSource:
0682                         if (storedPrice.source().compare((*it).m_sourceName) != 0)
0683                             it = st.m_listPrices.erase(it);
0684                         else
0685                             ++it;
0686                         break;
0687                     case eDialogs::UpdatePrice::Ask:
0688                     {
0689                         auto result = KMessageBox::questionTwoActionsCancel(this,
0690                                                                             i18n("For <b>%1</b> on <b>%2</b> price <b>%3</b> already exists.<br>"
0691                                                                                  "Do you want to replace it with <b>%4</b>?",
0692                                                                                  storedPrice.from(),
0693                                                                                  storedPrice.date().toString(Qt::ISODate),
0694                                                                                  QString().setNum(storedPrice.rate(storedPrice.to()).toDouble(), 'g', 10),
0695                                                                                  QString().setNum((*it).m_amount.toDouble(), 'g', 10)),
0696                                                                             i18n("Price Already Exists"),
0697                                                                             KMMYesNo::yes(),
0698                                                                             KMMYesNo::no());
0699                         switch(result) {
0700                         case KMessageBox::ButtonCode::PrimaryAction:
0701                             ++it;
0702                             break;
0703                         case KMessageBox::ButtonCode::SecondaryAction:
0704                             it = st.m_listPrices.erase(it);
0705                             break;
0706                         default:
0707                         case KMessageBox::ButtonCode::Cancel:
0708                             finishUpdate();
0709                             return;
0710                             break;
0711                         }
0712                         break;
0713                     }
0714                     default:
0715                         ++it;
0716                         break;
0717                     }
0718                 }
0719             }
0720         }
0721 
0722         if (!st.m_listPrices.isEmpty()) {
0723             MyMoneyFileTransaction ft;
0724             KMyMoneyUtils::processPriceList(st);
0725             ft.commit();
0726 
0727             // latest price could be in the last or in the first row
0728             MyMoneyStatement::Price priceClass;
0729             QDate firstEntry;
0730             QDate lastEntry;
0731             if (st.m_listPrices.first().m_date > st.m_listPrices.last().m_date) {
0732                 priceClass = st.m_listPrices.first();
0733                 firstEntry = st.m_listPrices.last().m_date;
0734             } else {
0735                 priceClass = st.m_listPrices.last();
0736                 firstEntry = st.m_listPrices.first().m_date;
0737             }
0738             lastEntry = priceClass.m_date;
0739             // update latest price in dialog if applicable
0740             auto latestDate = QDate::fromString(item->text(DATE_COL),Qt::ISODate);
0741             if (latestDate <= priceClass.m_date && priceClass.m_amount.isPositive()) {
0742                 item->setText(PRICE_COL, priceClass.m_amount.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0743                 item->setText(DATE_COL, priceClass.m_date.toString(Qt::ISODate));
0744                 item->setText(SOURCE_COL, priceClass.m_sourceName);
0745             }
0746             logStatusMessage(i18nc("Log message e.g. 'Prices for EUR > USD updated between 2001-01-01 and 2001-02-01 (id EUR USD)'",
0747                                    "Prices for %1 updated between %3 and %4 (id %2)",
0748                                    _webID,
0749                                    _kmmID,
0750                                    firstEntry.toString(Qt::ISODate),
0751                                    lastEntry.toString(Qt::ISODate)));
0752             // make sure to make OK button available
0753         }
0754         d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
0755 
0756         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0757         item->setSelected(false);
0758 
0759         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0760         // need to parse the list to find the next selected one
0761         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0762         if (!d->m_fUpdateAll) {
0763             while (next && !next->isSelected()) {
0764                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0765                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0766             }
0767         }
0768     } else {
0769         logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
0770     }
0771 
0772     if (next) {
0773         d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0774     } else {
0775         finishUpdate();
0776     }
0777 }
0778 
0779 void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _kmmID, const QString& _webID, const QDate& _date, const double& _price)
0780 {
0781     Q_D(KEquityPriceUpdateDlg);
0782     auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
0783     QTreeWidgetItem* item = nullptr;
0784 
0785     if (! foundItems.empty())
0786         item = foundItems.at(0);
0787 
0788     QTreeWidgetItem* next = 0;
0789 
0790     if (item) {
0791         if (_price > 0.0f && _date.isValid()) {
0792             QDate date = _date;
0793             if (date > QDate::currentDate())
0794                 date = QDate::currentDate();
0795 
0796             MyMoneyMoney calculatedPrice = MyMoneyMoney::ONE;
0797             QString id = _kmmID.toUtf8();
0798             MyMoneySecurity fromCurrency, toCurrency;
0799             if (_kmmID.contains(" ") == 0) {
0800                 MyMoneySecurity security = MyMoneyFile::instance()->security(id);
0801                 QString factor = security.value("kmm-online-factor");
0802                 if (!factor.isEmpty()) {
0803                     calculatedPrice = calculatedPrice * MyMoneyMoney(factor);
0804                 }
0805                 try {
0806                     toCurrency = MyMoneyFile::instance()->security(id);
0807                     fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
0808                 } catch (const MyMoneyException &) {
0809                     fromCurrency = toCurrency = MyMoneySecurity();
0810                 }
0811 
0812             } else {
0813                 QRegularExpressionMatch match(d->m_splitRegex.match(_kmmID));
0814                 if (match.hasMatch()) {
0815                     try {
0816                         fromCurrency = MyMoneyFile::instance()->security(match.captured(2).toUtf8());
0817                         toCurrency = MyMoneyFile::instance()->security(match.captured(1).toUtf8());
0818                     } catch (const MyMoneyException &) {
0819                         fromCurrency = toCurrency = MyMoneySecurity();
0820                     }
0821                 }
0822             }
0823             calculatedPrice *= MyMoneyMoney(_price, MyMoneyMoney::precToDenom(toCurrency.pricePrecision()));
0824 
0825             item->setText(PRICE_COL, calculatedPrice.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
0826             item->setText(DATE_COL, date.toString(Qt::ISODate));
0827             logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID));
0828             // make sure to make OK button available
0829             d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
0830         } else {
0831             logErrorMessage(i18n("Received an invalid price for %1, unable to update.", _webID));
0832         }
0833 
0834         d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0835         item->setSelected(false);
0836 
0837         // launch the NEXT one ... in case of m_fUpdateAll == false, we
0838         // need to parse the list to find the next selected one
0839         next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
0840         if (!d->m_fUpdateAll) {
0841             while (next && !next->isSelected()) {
0842                 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
0843                 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
0844             }
0845         }
0846     } else {
0847         logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
0848     }
0849 
0850     if (next) {
0851         d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
0852     } else {
0853         finishUpdate();
0854     }
0855 }
0856 
0857 void KEquityPriceUpdateDlg::finishUpdate()
0858 {
0859     Q_D(KEquityPriceUpdateDlg);
0860     // we've run past the end, reset to the default value.
0861     d->m_fUpdateAll = false;
0862 
0863     // force progress bar to show 100% and hide it after a while
0864     d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->maximum());
0865     QTimer::singleShot(500, d->ui->prgOnlineProgress, &QProgressBar::hide);
0866 
0867     // re-enable the sorting that was disabled during the update process
0868     d->ui->lvEquityList->setSortingEnabled(true);
0869 }
0870 
0871 // Make sure, that these definitions are only used within this file
0872 // this does not seem to be necessary, but when building RPMs the
0873 // build option 'final' is used and all CPP files are concatenated.
0874 // So it could well be, that in another CPP file these definitions
0875 // are also used.
0876 #undef WEBID_COL
0877 #undef NAME_COL
0878 #undef PRICE_COL
0879 #undef DATE_COL
0880 #undef KMMID_COL
0881 #undef SOURCE_COL