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