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