1 /*
2 * Copyright 2004-2017 Thomas Baumgart <tbaumgart@kde.org>
3 * Copyright 2004 Kevin Tambascio <ktambascio@users.sourceforge.net>
4 * Copyright 2004-2006 Ace Jones <acejones@users.sourceforge.net>
5 * Copyright 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "kequitypriceupdatedlg.h"
22
23 // ----------------------------------------------------------------------------
24 // QT Includes
25
26 #include <QPushButton>
27 #include <QTimer>
28 #include <QList>
29 #include <QPointer>
30
31 // ----------------------------------------------------------------------------
32 // KDE Includes
33
34 #include <KMessageBox>
35 #include <KTextEdit>
36 #include <KConfig>
37 #include <KConfigGroup>
38 #include <KSharedConfig>
39 #include <KStandardGuiItem>
40 #include <KLocalizedString>
41 #include <KColorScheme>
42
43 // ----------------------------------------------------------------------------
44 // Project Includes
45
46 #include "ui_kequitypriceupdatedlg.h"
47
48 #include "mymoneyfile.h"
49 #include "mymoneyaccount.h"
50 #include "mymoneysecurity.h"
51 #include "mymoneyprice.h"
52 #include "webpricequote.h"
53 #include "kequitypriceupdateconfdlg.h"
54 #include "kmymoneyutils.h"
55 #include "mymoneyexception.h"
56 #include "dialogenums.h"
57
58 #define WEBID_COL 0
59 #define NAME_COL 1
60 #define PRICE_COL 2
61 #define DATE_COL 3
62 #define KMMID_COL 4
63 #define SOURCE_COL 5
64
65 class KEquityPriceUpdateDlgPrivate
66 {
67 Q_DISABLE_COPY(KEquityPriceUpdateDlgPrivate)
68 Q_DECLARE_PUBLIC(KEquityPriceUpdateDlg)
69
70 public:
KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg * qq)71 explicit KEquityPriceUpdateDlgPrivate(KEquityPriceUpdateDlg *qq) :
72 q_ptr(qq),
73 ui(new Ui::KEquityPriceUpdateDlg),
74 m_fUpdateAll(false),
75 m_updatingPricePolicy(eDialogs::UpdatePrice::All)
76 {
77 }
78
~KEquityPriceUpdateDlgPrivate()79 ~KEquityPriceUpdateDlgPrivate()
80 {
81 delete ui;
82 }
83
init(const QString & securityId)84 void init(const QString& securityId)
85 {
86 Q_Q(KEquityPriceUpdateDlg);
87 ui->setupUi(q);
88 m_fUpdateAll = false;
89 QStringList headerList;
90 headerList << i18n("ID") << i18nc("Equity name", "Name")
91 << i18n("Price") << i18n("Date");
92
93 ui->lvEquityList->header()->setSortIndicator(0, Qt::AscendingOrder);
94 ui->lvEquityList->setColumnWidth(NAME_COL, 125);
95
96 // This is a "get it up and running" hack. Will replace this in the future.
97 headerList << i18nc("Internal identifier", "Internal ID")
98 << i18nc("Online quote source", "Source");
99 ui->lvEquityList->setColumnWidth(KMMID_COL, 0);
100
101 ui->lvEquityList->setHeaderLabels(headerList);
102
103 ui->lvEquityList->setSelectionMode(QAbstractItemView::MultiSelection);
104 ui->lvEquityList->setAllColumnsShowFocus(true);
105
106 ui->btnUpdateAll->setEnabled(false);
107
108 auto file = MyMoneyFile::instance();
109
110 //
111 // Add each price pair that we know about
112 //
113
114 // send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY.
115 // for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch()
116 QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
117 MyMoneySecurityPair currencyIds;
118 if (splitrx.indexIn(securityId) != -1) {
119 currencyIds = MyMoneySecurityPair(splitrx.cap(1), splitrx.cap(2));
120 }
121
122 MyMoneyPriceList prices = file->priceList();
123 for (MyMoneyPriceList::ConstIterator it_price = prices.constBegin(); it_price != prices.constEnd(); ++it_price) {
124 const MyMoneySecurityPair& pair = it_price.key();
125 if (file->security(pair.first).isCurrency() && (securityId.isEmpty() || (pair == currencyIds))) {
126 const MyMoneyPriceEntries& entries = (*it_price);
127 if (entries.count() > 0 && entries.begin().key() <= QDate::currentDate()) {
128 addPricePair(pair, false);
129 ui->btnUpdateAll->setEnabled(true);
130 }
131 }
132 }
133
134 //
135 // Add each investment
136 //
137
138 QList<MyMoneySecurity> securities = file->securityList();
139 for (QList<MyMoneySecurity>::const_iterator it = securities.constBegin(); it != securities.constEnd(); ++it) {
140 if (!(*it).isCurrency()
141 && (securityId.isEmpty() || ((*it).id() == securityId))
142 && !(*it).value("kmm-online-source").isEmpty()
143 ) {
144 addInvestment(*it);
145 ui->btnUpdateAll->setEnabled(true);
146 }
147 }
148
149 // if list is empty and a price pair has been requested, add it
150 if (ui->lvEquityList->invisibleRootItem()->childCount() == 0 && !securityId.isEmpty()) {
151 addPricePair(currencyIds, true);
152 }
153
154 q->connect(ui->btnUpdateSelected, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateSelectedClicked);
155 q->connect(ui->btnUpdateAll, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotUpdateAllClicked);
156
157 q->connect(ui->m_fromDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
158 q->connect(ui->m_toDate, &KMyMoneyDateInput::dateChanged, q, &KEquityPriceUpdateDlg::slotDateChanged);
159
160 q->connect(&m_webQuote, &WebPriceQuote::csvquote,
161 q, &KEquityPriceUpdateDlg::slotReceivedCSVQuote);
162 q->connect(&m_webQuote, &WebPriceQuote::quote,
163 q, &KEquityPriceUpdateDlg::slotReceivedQuote);
164 q->connect(&m_webQuote, &WebPriceQuote::failed,
165 q, &KEquityPriceUpdateDlg::slotQuoteFailed);
166 q->connect(&m_webQuote, &WebPriceQuote::status,
167 q, &KEquityPriceUpdateDlg::logStatusMessage);
168 q->connect(&m_webQuote, &WebPriceQuote::error,
169 q, &KEquityPriceUpdateDlg::logErrorMessage);
170
171 q->connect(ui->lvEquityList, &QTreeWidget::itemSelectionChanged, q, &KEquityPriceUpdateDlg::slotUpdateSelection);
172
173 q->connect(ui->btnConfigure, &QAbstractButton::clicked, q, &KEquityPriceUpdateDlg::slotConfigureClicked);
174
175 if (!securityId.isEmpty()) {
176 ui->btnUpdateSelected->hide();
177 ui->btnUpdateAll->hide();
178 // delete layout1;
179
180 QTimer::singleShot(100, q, SLOT(slotUpdateAllClicked()));
181 }
182
183 // Hide OK button until we have received the first update
184 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
185 if (ui->lvEquityList->invisibleRootItem()->childCount() == 0) {
186 ui->btnUpdateAll->setEnabled(false);
187 }
188 q->slotUpdateSelection();
189
190 // previous versions of this dialog allowed to store a "Don't ask again" switch.
191 // Since we don't support it anymore, we just get rid of it
192 KSharedConfigPtr config = KSharedConfig::openConfig();
193 KConfigGroup grp = config->group("Notification Messages");
194 grp.deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed");
195 grp.sync();
196 grp = config->group("Equity Price Update");
197 int policyValue = grp.readEntry("PriceUpdatingPolicy", (int)eDialogs::UpdatePrice::Missing);
198 if (policyValue > (int)eDialogs::UpdatePrice::Ask || policyValue < (int)eDialogs::UpdatePrice::All)
199 m_updatingPricePolicy = eDialogs::UpdatePrice::Missing;
200 else
201 m_updatingPricePolicy = static_cast<eDialogs::UpdatePrice>(policyValue);
202 }
203
addPricePair(const MyMoneySecurityPair & pair,bool dontCheckExistance)204 void addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance)
205 {
206 auto file = MyMoneyFile::instance();
207
208 const auto symbol = QString::fromLatin1("%1 > %2").arg(pair.first, pair.second);
209 const auto id = QString::fromLatin1("%1 %2").arg(pair.first, pair.second);
210 // Check that the pair does not already exist
211 if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
212 const MyMoneyPrice &pr = file->price(pair.first, pair.second);
213 if (pr.source() != QLatin1String("KMyMoney")) {
214 bool keep = true;
215 if ((pair.first == file->baseCurrency().id())
216 || (pair.second == file->baseCurrency().id())) {
217 const QString& foreignCurrency = file->foreignCurrency(pair.first, pair.second);
218 // check that the foreign currency is still in use
219 QList<MyMoneyAccount>::const_iterator it_a;
220 QList<MyMoneyAccount> list;
221 file->accountList(list);
222 for (it_a = list.constBegin(); !dontCheckExistance && it_a != list.constEnd(); ++it_a) {
223 // if it's an account denominated in the foreign currency
224 // keep it
225 if (((*it_a).currencyId() == foreignCurrency)
226 && !(*it_a).isClosed())
227 break;
228 // if it's an investment traded in the foreign currency
229 // keep it
230 if ((*it_a).isInvest() && !(*it_a).isClosed()) {
231 MyMoneySecurity sec = file->security((*it_a).currencyId());
232 if (sec.tradingCurrency() == foreignCurrency)
233 break;
234 }
235 }
236 // if it is in use, it_a is not equal to list.end()
237 if (it_a == list.constEnd() && !dontCheckExistance)
238 keep = false;
239 }
240
241 if (keep) {
242 auto item = new QTreeWidgetItem();
243 item->setText(WEBID_COL, symbol);
244 item->setText(NAME_COL, i18n("%1 units in %2", pair.first, pair.second));
245 if (pr.isValid()) {
246 MyMoneySecurity fromCurrency = file->currency(pair.second);
247 MyMoneySecurity toCurrency = file->currency(pair.first);
248 item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
249 item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
250 }
251 item->setText(KMMID_COL, id);
252 item->setText(SOURCE_COL, "KMyMoney Currency"); // This string value should not be localized
253 ui->lvEquityList->invisibleRootItem()->addChild(item);
254 }
255 }
256 }
257 }
258
addInvestment(const MyMoneySecurity & inv)259 void addInvestment(const MyMoneySecurity& inv)
260 {
261 const auto id = inv.id();
262 // Check that the pair does not already exist
263 if (ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL).empty()) {
264 auto file = MyMoneyFile::instance();
265 // check that the security is still in use
266 QList<MyMoneyAccount>::const_iterator it_a;
267 QList<MyMoneyAccount> list;
268 file->accountList(list);
269 for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) {
270 if ((*it_a).isInvest()
271 && ((*it_a).currencyId() == inv.id())
272 && !(*it_a).isClosed())
273 break;
274 }
275 // if it is in use, it_a is not equal to list.end()
276 if (it_a != list.constEnd()) {
277 QString webID;
278 WebPriceQuoteSource onlineSource(inv.value("kmm-online-source"));
279 if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::IdentificationNumber)
280 webID = inv.value("kmm-security-id"); // insert ISIN number...
281 else if (onlineSource.m_webIDBy == WebPriceQuoteSource::identifyBy::Name)
282 webID = inv.name(); // ...or name...
283 else
284 webID = inv.tradingSymbol(); // ...or symbol
285
286 QTreeWidgetItem* item = new QTreeWidgetItem();
287 item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText));
288 if (webID.isEmpty()) {
289 webID = i18n("[No identifier]");
290 item->setForeground(WEBID_COL, KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText));
291 }
292 item->setText(WEBID_COL, webID);
293 item->setText(NAME_COL, inv.name());
294 MyMoneySecurity currency = file->currency(inv.tradingCurrency());
295 const MyMoneyPrice &pr = file->price(id.toUtf8(), inv.tradingCurrency());
296 if (pr.isValid()) {
297 item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), inv.pricePrecision()));
298 item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
299 }
300 item->setText(KMMID_COL, id);
301 if (inv.value("kmm-online-quote-system") == "Finance::Quote")
302 item->setText(SOURCE_COL, QString("Finance::Quote %1").arg(inv.value("kmm-online-source")));
303 else
304 item->setText(SOURCE_COL, inv.value("kmm-online-source"));
305
306 ui->lvEquityList->invisibleRootItem()->addChild(item);
307
308 // If this investment is denominated in a foreign currency, ensure that
309 // the appropriate price pair is also on the list
310
311 if (currency.id() != file->baseCurrency().id()) {
312 addPricePair(MyMoneySecurityPair(currency.id(), file->baseCurrency().id()), false);
313 }
314 }
315 }
316 }
317
318 KEquityPriceUpdateDlg *q_ptr;
319 Ui::KEquityPriceUpdateDlg *ui;
320 bool m_fUpdateAll;
321 eDialogs::UpdatePrice m_updatingPricePolicy;
322 WebPriceQuote m_webQuote;
323 };
324
KEquityPriceUpdateDlg(QWidget * parent,const QString & securityId)325 KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(QWidget *parent, const QString& securityId) :
326 QDialog(parent),
327 d_ptr(new KEquityPriceUpdateDlgPrivate(this))
328 {
329 Q_D(KEquityPriceUpdateDlg);
330 d->init(securityId);
331 }
332
~KEquityPriceUpdateDlg()333 KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg()
334 {
335 Q_D(KEquityPriceUpdateDlg);
336 auto config = KSharedConfig::openConfig();
337 auto grp = config->group("Equity Price Update");
338 grp.writeEntry("PriceUpdatingPolicy", static_cast<int>(d->m_updatingPricePolicy));
339 grp.sync();
340 delete d;
341 }
342
logErrorMessage(const QString & message)343 void KEquityPriceUpdateDlg::logErrorMessage(const QString& message)
344 {
345 logStatusMessage(QString("<font color=\"red\"><b>") + message + QString("</b></font>"));
346 }
347
logStatusMessage(const QString & message)348 void KEquityPriceUpdateDlg::logStatusMessage(const QString& message)
349 {
350 Q_D(KEquityPriceUpdateDlg);
351 d->ui->lbStatus->append(message);
352 }
353
price(const QString & id) const354 MyMoneyPrice KEquityPriceUpdateDlg::price(const QString& id) const
355 {
356 Q_D(const KEquityPriceUpdateDlg);
357 MyMoneyPrice price;
358 QTreeWidgetItem* item = nullptr;
359 QList<QTreeWidgetItem*> foundItems = d->ui->lvEquityList->findItems(id, Qt::MatchExactly, KMMID_COL);
360
361 if (! foundItems.empty())
362 item = foundItems.at(0);
363
364 if (item) {
365 MyMoneyMoney rate(item->text(PRICE_COL));
366 if (!rate.isZero()) {
367 QString kmm_id = item->text(KMMID_COL).toUtf8();
368
369 // if the ID has a space, then this is TWO ID's, so it's a currency quote
370 if (kmm_id.contains(" ")) {
371 QStringList ids = kmm_id.split(' ', QString::SkipEmptyParts);
372 QString fromid = ids[0].toUtf8();
373 QString toid = ids[1].toUtf8();
374 price = MyMoneyPrice(fromid, toid, QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
375 } else
376 // otherwise, it's a security quote
377 {
378 MyMoneySecurity security = MyMoneyFile::instance()->security(kmm_id);
379 price = MyMoneyPrice(kmm_id, security.tradingCurrency(), QDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
380 }
381 }
382 }
383 return price;
384 }
385
storePrices()386 void KEquityPriceUpdateDlg::storePrices()
387 {
388 Q_D(KEquityPriceUpdateDlg);
389 // update the new prices into the equities
390
391 auto file = MyMoneyFile::instance();
392 QString name;
393
394 MyMoneyFileTransaction ft;
395 try {
396 for (auto i = 0; i < d->ui->lvEquityList->invisibleRootItem()->childCount(); ++i) {
397 QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(i);
398 // turn on signals before we modify the last entry in the list
399 file->blockSignals(i < d->ui->lvEquityList->invisibleRootItem()->childCount() - 1);
400
401 MyMoneyMoney rate(item->text(PRICE_COL));
402 if (!rate.isZero()) {
403 QString id = item->text(KMMID_COL);
404 QString fromid;
405 QString toid;
406
407 // if the ID has a space, then this is TWO ID's, so it's a currency quote
408 if (id.contains(QLatin1Char(' '))) {
409 QStringList ids = id.split(QLatin1Char(' '), QString::SkipEmptyParts);
410 fromid = ids.at(0);
411 toid = ids.at(1);
412 name = QString::fromLatin1("%1 --> %2").arg(fromid, toid);
413 } else { // otherwise, it's a security quote
414 MyMoneySecurity security = file->security(id);
415 name = security.name();
416 fromid = id;
417 toid = security.tradingCurrency();
418 }
419 // TODO (Ace) Better handling of the case where there is already a price
420 // for this date. Currently, it just overrides the old value. Really it
421 // should check to see if the price is the same and prompt the user.
422 file->addPrice(MyMoneyPrice(fromid, toid, QDate::fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL)));
423 }
424 }
425 ft.commit();
426
427 } catch (const MyMoneyException &) {
428 qDebug("Unable to add price information for %s", qPrintable(name));
429 }
430 }
431
slotConfigureClicked()432 void KEquityPriceUpdateDlg::slotConfigureClicked()
433 {
434 Q_D(KEquityPriceUpdateDlg);
435 QPointer<EquityPriceUpdateConfDlg> dlg = new EquityPriceUpdateConfDlg(d->m_updatingPricePolicy);
436 if (dlg->exec() == QDialog::Accepted)
437 d->m_updatingPricePolicy = dlg->policy();
438 delete dlg;
439 }
440
slotUpdateSelection()441 void KEquityPriceUpdateDlg::slotUpdateSelection()
442 {
443 Q_D(KEquityPriceUpdateDlg);
444 // Only enable the update button if there is a selection
445 d->ui->btnUpdateSelected->setEnabled(false);
446
447 if (! d->ui->lvEquityList->selectedItems().empty())
448 d->ui->btnUpdateSelected->setEnabled(true);
449 }
450
slotUpdateSelectedClicked()451 void KEquityPriceUpdateDlg::slotUpdateSelectedClicked()
452 {
453 Q_D(KEquityPriceUpdateDlg);
454 // disable sorting while the update is running to maintain the current order of items on which
455 // the update process depends and which could be changed with sorting enabled due to the updated values
456 d->ui->lvEquityList->setSortingEnabled(false);
457 auto item = d->ui->lvEquityList->invisibleRootItem()->child(0);
458 auto skipCnt = 1;
459 while (item && !item->isSelected()) {
460 item = d->ui->lvEquityList->invisibleRootItem()->child(skipCnt);
461 ++skipCnt;
462 }
463 d->m_webQuote.setDate(d->ui->m_fromDate->date(), d->ui->m_toDate->date());
464 if (item) {
465 d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
466 d->ui->prgOnlineProgress->setValue(skipCnt);
467 d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
468
469 } else {
470
471 logErrorMessage("No security selected.");
472 }
473 }
474
slotUpdateAllClicked()475 void KEquityPriceUpdateDlg::slotUpdateAllClicked()
476 {
477 Q_D(KEquityPriceUpdateDlg);
478 // disable sorting while the update is running to maintain the current order of items on which
479 // the update process depends and which could be changed with sorting enabled due to the updated values
480 d->ui->lvEquityList->setSortingEnabled(false);
481 QTreeWidgetItem* item = d->ui->lvEquityList->invisibleRootItem()->child(0);
482 if (item) {
483 d->ui->prgOnlineProgress->setMaximum(1 + d->ui->lvEquityList->invisibleRootItem()->childCount());
484 d->ui->prgOnlineProgress->setValue(1);
485 d->m_fUpdateAll = true;
486 d->m_webQuote.launch(item->text(WEBID_COL), item->text(KMMID_COL), item->text(SOURCE_COL));
487
488 } else {
489 logErrorMessage("Security list is empty.");
490 }
491 }
492
slotDateChanged()493 void KEquityPriceUpdateDlg::slotDateChanged()
494 {
495 Q_D(KEquityPriceUpdateDlg);
496 d->ui->m_fromDate->blockSignals(true);
497 d->ui->m_toDate->blockSignals(true);
498 if (d->ui->m_toDate->date() > QDate::currentDate())
499 d->ui->m_toDate->setDate(QDate::currentDate());
500 if (d->ui->m_fromDate->date() > d->ui->m_toDate->date())
501 d->ui->m_fromDate->setDate(d->ui->m_toDate->date());
502 d->ui->m_fromDate->blockSignals(false);
503 d->ui->m_toDate->blockSignals(false);
504 }
505
slotQuoteFailed(const QString & _kmmID,const QString & _webID)506 void KEquityPriceUpdateDlg::slotQuoteFailed(const QString& _kmmID, const QString& _webID)
507 {
508 Q_D(KEquityPriceUpdateDlg);
509 auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
510 QTreeWidgetItem* item = nullptr;
511
512 if (! foundItems.empty())
513 item = foundItems.at(0);
514
515 // Give the user some options
516 int result;
517 if (_kmmID.contains(" ")) {
518 if (item)
519 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"));
520 else
521 return;
522 } else if (!item) {
523 return;
524 } else {
525 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());
526 }
527
528
529 if (result == KMessageBox::No) {
530 // Disable price updates for this security
531
532 MyMoneyFileTransaction ft;
533 try {
534 // Get this security (by ID)
535 MyMoneySecurity security = MyMoneyFile::instance()->security(_kmmID.toUtf8());
536
537 // Set the quote source to blank
538 security.setValue("kmm-online-source", QString());
539 security.setValue("kmm-online-quote-system", QString());
540
541 // Re-commit the security
542 MyMoneyFile::instance()->modifySecurity(security);
543 ft.commit();
544 } catch (const MyMoneyException &e) {
545 KMessageBox::error(this, QString("<qt>") + i18n("Cannot update security <b>%1</b>: %2", _webID, QString::fromLatin1(e.what())) + QString("</qt>"), i18n("Price Update Failed"));
546 }
547 }
548
549 // As long as the user doesn't want to cancel, move on!
550 if (result != KMessageBox::Cancel) {
551 QTreeWidgetItem* next = nullptr;
552 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
553 item->setSelected(false);
554
555 // launch the NEXT one ... in case of m_fUpdateAll == false, we
556 // need to parse the list to find the next selected one
557 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
558 if (!d->m_fUpdateAll) {
559 while (next && !next->isSelected()) {
560 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
561 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
562 }
563 }
564 if (next) {
565 d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
566 } else {
567 finishUpdate();
568 }
569 } else {
570 finishUpdate();
571 }
572 }
573
slotReceivedCSVQuote(const QString & _kmmID,const QString & _webID,MyMoneyStatement & st)574 void KEquityPriceUpdateDlg::slotReceivedCSVQuote(const QString& _kmmID, const QString& _webID, MyMoneyStatement& st)
575 {
576 Q_D(KEquityPriceUpdateDlg);
577 auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
578 QTreeWidgetItem* item = nullptr;
579
580 if (! foundItems.empty())
581 item = foundItems.at(0);
582
583 QTreeWidgetItem* next = nullptr;
584
585 if (item) {
586 auto file = MyMoneyFile::instance();
587 MyMoneySecurity fromCurrency, toCurrency;
588
589 if (!_kmmID.contains(QLatin1Char(' '))) {
590 try {
591 toCurrency = MyMoneyFile::instance()->security(_kmmID);
592 fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
593 } catch (const MyMoneyException &) {
594 fromCurrency = toCurrency = MyMoneySecurity();
595 }
596
597 } else {
598 QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
599 if (splitrx.indexIn(_kmmID) != -1) {
600 try {
601 fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8());
602 toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8());
603 } catch (const MyMoneyException &) {
604 fromCurrency = toCurrency = MyMoneySecurity();
605 }
606 }
607 }
608
609 if (d->m_updatingPricePolicy != eDialogs::UpdatePrice::All) {
610 QStringList qSources = WebPriceQuote::quoteSources();
611 for (auto it = st.m_listPrices.begin(); it != st.m_listPrices.end();) {
612 MyMoneyPrice storedPrice = file->price(toCurrency.id(), fromCurrency.id(), (*it).m_date, true);
613 bool priceValid = storedPrice.isValid();
614 if (!priceValid)
615 ++it;
616 else {
617 switch(d->m_updatingPricePolicy) {
618 case eDialogs::UpdatePrice::Missing:
619 it = st.m_listPrices.erase(it);
620 break;
621 case eDialogs::UpdatePrice::Downloaded:
622 if (!qSources.contains(storedPrice.source()))
623 it = st.m_listPrices.erase(it);
624 else
625 ++it;
626 break;
627 case eDialogs::UpdatePrice::SameSource:
628 if (storedPrice.source().compare((*it).m_sourceName) != 0)
629 it = st.m_listPrices.erase(it);
630 else
631 ++it;
632 break;
633 case eDialogs::UpdatePrice::Ask:
634 {
635 auto result = KMessageBox::questionYesNoCancel(this,
636 i18n("For <b>%1</b> on <b>%2</b> price <b>%3</b> already exists.<br>"
637 "Do you want to replace it with <b>%4</b>?",
638 storedPrice.from(), storedPrice.date().toString(Qt::ISODate),
639 QString().setNum(storedPrice.rate(storedPrice.to()).toDouble(), 'g', 10),
640 QString().setNum((*it).m_amount.toDouble(), 'g', 10)),
641 i18n("Price Already Exists"));
642 switch(result) {
643 case KMessageBox::ButtonCode::Yes:
644 ++it;
645 break;
646 case KMessageBox::ButtonCode::No:
647 it = st.m_listPrices.erase(it);
648 break;
649 default:
650 case KMessageBox::ButtonCode::Cancel:
651 finishUpdate();
652 return;
653 break;
654 }
655 break;
656 }
657 default:
658 ++it;
659 break;
660 }
661 }
662 }
663 }
664
665 if (!st.m_listPrices.isEmpty()) {
666 MyMoneyFileTransaction ft;
667 KMyMoneyUtils::processPriceList(st);
668 ft.commit();
669
670 // latest price could be in the last or in the first row
671 MyMoneyStatement::Price priceClass;
672 if (st.m_listPrices.first().m_date > st.m_listPrices.last().m_date)
673 priceClass = st.m_listPrices.first();
674 else
675 priceClass = st.m_listPrices.last();
676
677 // update latest price in dialog if applicable
678 auto latestDate = QDate::fromString(item->text(DATE_COL),Qt::ISODate);
679 if (latestDate <= priceClass.m_date && priceClass.m_amount.isPositive()) {
680 item->setText(PRICE_COL, priceClass.m_amount.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
681 item->setText(DATE_COL, priceClass.m_date.toString(Qt::ISODate));
682 item->setText(SOURCE_COL, priceClass.m_sourceName);
683 }
684 logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID));
685 // make sure to make OK button available
686 }
687 d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
688
689 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
690 item->setSelected(false);
691
692 // launch the NEXT one ... in case of m_fUpdateAll == false, we
693 // need to parse the list to find the next selected one
694 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
695 if (!d->m_fUpdateAll) {
696 while (next && !next->isSelected()) {
697 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
698 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
699 }
700 }
701 } else {
702 logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
703 }
704
705 if (next) {
706 d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
707 } else {
708 finishUpdate();
709 }
710 }
711
slotReceivedQuote(const QString & _kmmID,const QString & _webID,const QDate & _date,const double & _price)712 void KEquityPriceUpdateDlg::slotReceivedQuote(const QString& _kmmID, const QString& _webID, const QDate& _date, const double& _price)
713 {
714 Q_D(KEquityPriceUpdateDlg);
715 auto foundItems = d->ui->lvEquityList->findItems(_kmmID, Qt::MatchExactly, KMMID_COL);
716 QTreeWidgetItem* item = nullptr;
717
718 if (! foundItems.empty())
719 item = foundItems.at(0);
720
721 QTreeWidgetItem* next = 0;
722
723 if (item) {
724 if (_price > 0.0f && _date.isValid()) {
725 QDate date = _date;
726 if (date > QDate::currentDate())
727 date = QDate::currentDate();
728
729 MyMoneyMoney price = MyMoneyMoney::ONE;
730 QString id = _kmmID.toUtf8();
731 MyMoneySecurity fromCurrency, toCurrency;
732 if (_kmmID.contains(" ") == 0) {
733 MyMoneySecurity security = MyMoneyFile::instance()->security(id);
734 QString factor = security.value("kmm-online-factor");
735 if (!factor.isEmpty()) {
736 price = price * MyMoneyMoney(factor);
737 }
738 try {
739 toCurrency = MyMoneyFile::instance()->security(id);
740 fromCurrency = MyMoneyFile::instance()->security(toCurrency.tradingCurrency());
741 } catch (const MyMoneyException &) {
742 fromCurrency = toCurrency = MyMoneySecurity();
743 }
744
745 } else {
746 QRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)", Qt::CaseInsensitive);
747 if (splitrx.indexIn(_kmmID) != -1) {
748 try {
749 fromCurrency = MyMoneyFile::instance()->security(splitrx.cap(2).toUtf8());
750 toCurrency = MyMoneyFile::instance()->security(splitrx.cap(1).toUtf8());
751 } catch (const MyMoneyException &) {
752 fromCurrency = toCurrency = MyMoneySecurity();
753 }
754 }
755 }
756 price *= MyMoneyMoney(_price, MyMoneyMoney::precToDenom(toCurrency.pricePrecision()));
757
758 item->setText(PRICE_COL, price.formatMoney(fromCurrency.tradingSymbol(), toCurrency.pricePrecision()));
759 item->setText(DATE_COL, date.toString(Qt::ISODate));
760 logStatusMessage(i18n("Price for %1 updated (id %2)", _webID, _kmmID));
761 // make sure to make OK button available
762 d->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
763 } else {
764 logErrorMessage(i18n("Received an invalid price for %1, unable to update.", _webID));
765 }
766
767 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
768 item->setSelected(false);
769
770 // launch the NEXT one ... in case of m_fUpdateAll == false, we
771 // need to parse the list to find the next selected one
772 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(item) + 1);
773 if (!d->m_fUpdateAll) {
774 while (next && !next->isSelected()) {
775 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->value() + 1);
776 next = d->ui->lvEquityList->invisibleRootItem()->child(d->ui->lvEquityList->invisibleRootItem()->indexOfChild(next) + 1);
777 }
778 }
779 } else {
780 logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list. Aborting entire update.", _webID, _kmmID));
781 }
782
783 if (next) {
784 d->m_webQuote.launch(next->text(WEBID_COL), next->text(KMMID_COL), next->text(SOURCE_COL));
785 } else {
786 finishUpdate();
787 }
788 }
789
finishUpdate()790 void KEquityPriceUpdateDlg::finishUpdate()
791 {
792 Q_D(KEquityPriceUpdateDlg);
793 // we've run past the end, reset to the default value.
794 d->m_fUpdateAll = false;
795 // force progress bar to show 100%
796 d->ui->prgOnlineProgress->setValue(d->ui->prgOnlineProgress->maximum());
797 // re-enable the sorting that was disabled during the update process
798 d->ui->lvEquityList->setSortingEnabled(true);
799 }
800
801 // Make sure, that these definitions are only used within this file
802 // this does not seem to be necessary, but when building RPMs the
803 // build option 'final' is used and all CPP files are concatenated.
804 // So it could well be, that in another CPP file these definitions
805 // are also used.
806 #undef WEBID_COL
807 #undef NAME_COL
808 #undef PRICE_COL
809 #undef DATE_COL
810 #undef KMMID_COL
811 #undef SOURCE_COL
812