You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmymoney/kmymoney2/dialogs/kequitypriceupdatedlg.cpp

589 lines
20 KiB

/***************************************************************************
kequitypriceupdatedlg.cpp - description
-------------------
begin : Mon Sep 1 2003
copyright : (C) 2000-2003 by Michael Edwardes
email : mte@users.sourceforge.net
Javier Campos Morales <javi_c@users.sourceforge.net>
Felix Rodriguez <frodriguez@users.sourceforge.net>
John C <thetacoturtle@users.sourceforge.net>
Thomas Baumgart <ipwizard@users.sourceforge.net>
Kevin Tambascio <ktambascio@users.sourceforge.net>
Ace Jones <acejones@users.sourceforge.net>
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
// ----------------------------------------------------------------------------
// QT Includes
#include <tqlabel.h>
#include <tqpushbutton.h>
#include <tqradiobutton.h>
#include <tqfile.h>
#include <tqtextstream.h>
#include <tqtimer.h>
#include <tqlayout.h>
// ----------------------------------------------------------------------------
// KDE Includes
#include <klocale.h>
#include <kmessagebox.h>
#include <kpushbutton.h>
#include <ktextedit.h>
#include <klistview.h>
#include <kdebug.h>
#include <kprogress.h>
#include <kglobal.h>
#include <kconfig.h>
// ----------------------------------------------------------------------------
// Project Includes
#include "kequitypriceupdatedlg.h"
#include "../mymoney/mymoneyfile.h"
#include "../mymoney/mymoneysecurity.h"
#include "../mymoney/mymoneyprice.h"
#include "../kmymoneyglobalsettings.h"
#define SYMBOL_COL 0
#define NAME_COL 1
#define PRICE_COL 2
#define DATE_COL 3
#define ID_COL 4
#define SOURCE_COL 5
KEquityPriceUpdateDlg::KEquityPriceUpdateDlg(TQWidget *tqparent, const TQString& securityId) :
KEquityPriceUpdateDlgDecl(tqparent),
m_fUpdateAll(false)
{
lvEquityList->setRootIsDecorated(false);
lvEquityList->setColumnText(0, i18n("Symbol"));
lvEquityList->addColumn(i18n("Symbol"));
lvEquityList->addColumn(i18n("Name"),125);
lvEquityList->addColumn(i18n("Price"));
lvEquityList->addColumn(i18n("Date"));
// This is a "get it up and running" hack. Will replace this in the future.
lvEquityList->addColumn("ID");
lvEquityList->addColumn("Source");
lvEquityList->setColumnWidth(ID_COL, 0);
lvEquityList->setMultiSelection(true);
lvEquityList->setColumnWidthMode(SYMBOL_COL, TQListView::Maximum);
lvEquityList->setColumnWidthMode(ID_COL, TQListView::Manual);
lvEquityList->setAllColumnsShowFocus(true);
btnUpdateAll->setEnabled(false);
MyMoneyFile* file = MyMoneyFile::instance();
//
// Add each price pair that we know about
//
// send in securityId == "XXX YYY" to get a single-shot update for XXX to YYY.
// for consistency reasons, this accepts the same delimiters as WebPriceQuote::launch()
TQRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)",false /*case sensitive*/);
MyMoneySecurityPair currencyIds;
if ( splitrx.search(securityId) != -1 )
currencyIds = MyMoneySecurityPair(splitrx.cap(1).utf8(),splitrx.cap(2).utf8());
MyMoneyPriceList prices = file->priceList();
for(MyMoneyPriceList::ConstIterator it_price = prices.begin(); it_price != prices.end(); ++it_price)
{
const MyMoneySecurityPair& pair = it_price.key();
if ( file->security( pair.first ).isCurrency() && ( securityId.isEmpty() || ( pair == currencyIds ) ) )
{
const MyMoneyPriceEntries& entries = (*it_price);
if(entries.count() > 0 && entries.begin().key() <= TQDate::tqcurrentDate()) {
addPricePair(pair);
btnUpdateAll->setEnabled(true);
}
}
}
//
// Add each investment
//
TQValueList<MyMoneySecurity> securities = file->securityList();
for(TQValueList<MyMoneySecurity>::ConstIterator it = securities.begin(); it != securities.end(); ++it)
{
if ( !(*it).isCurrency()
&& ( securityId.isEmpty() || ( (*it).id() == securityId ) )
&& !(*it).value("kmm-online-source").isEmpty()
)
{
addInvestment(*it);
btnUpdateAll->setEnabled(true);
}
}
// if list is empty, add the request price pair
if(lvEquityList->firstChild() == 0) {
addPricePair(currencyIds, true);
}
connect(btnOK, TQT_SIGNAL(clicked()), this, TQT_SLOT(accept()));
connect(btnCancel, TQT_SIGNAL(clicked()), this, TQT_SLOT(reject()));
connect(btnUpdateSelected, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotUpdateSelectedClicked()));
connect(btnUpdateAll, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotUpdateAllClicked()));
connect(&m_webQuote,TQT_SIGNAL(quote(const TQString&, const TQString&,const TQDate&, const double&)),
this,TQT_SLOT(slotReceivedQuote(const TQString&, const TQString&,const TQDate&, const double&)));
connect(&m_webQuote,TQT_SIGNAL(failed(const TQString&, const TQString&)),
this,TQT_SLOT(slotQuoteFailed(const TQString&, const TQString&)));
connect(&m_webQuote,TQT_SIGNAL(status(const TQString&)),
this,TQT_SLOT(logStatusMessage(const TQString&)));
connect(&m_webQuote,TQT_SIGNAL(error(const TQString&)),
this,TQT_SLOT(logErrorMessage(const TQString&)));
connect(lvEquityList, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotUpdateSelection()));
// Not implemented yet.
btnConfigure->hide();
//connect(btnConfigure, TQT_SIGNAL(clicked()), this, TQT_SLOT(slotConfigureClicked()));
if ( !securityId.isEmpty() )
{
btnUpdateSelected->hide();
btnUpdateAll->hide();
// delete tqlayout1;
TQTimer::singleShot(100,this,TQT_SLOT(slotUpdateAllClicked()));
}
// Hide OK button until we have received the first update
btnOK->setEnabled(false);
slotUpdateSelection();
// previous versions of this dialog allowed to store a "Don't ask again" switch.
// Since we don't support it anymore, we just get rid of it
KConfig* config = KGlobal::config();
config->setGroup("Notification Messages");
config->deleteEntry("KEquityPriceUpdateDlg::slotQuoteFailed::Price Update Failed");
}
KEquityPriceUpdateDlg::~KEquityPriceUpdateDlg()
{
}
void KEquityPriceUpdateDlg::addPricePair(const MyMoneySecurityPair& pair, bool dontCheckExistance)
{
MyMoneyFile* file = MyMoneyFile::instance();
TQString symbol = TQString("%1 > %2").tqarg(pair.first,pair.second);
TQString id = TQString("%1 %2").tqarg(pair.first,pair.second);
if ( ! lvEquityList->tqfindItem(id,ID_COL,TQt::ExactMatch) )
{
MyMoneyPrice pr = file->price(pair.first,pair.second);
if(pr.source() != "KMyMoney") {
bool keep = true;
if((pair.first == file->baseCurrency().id())
|| (pair.second == file->baseCurrency().id())) {
const TQString& foreignCurrency = file->foreignCurrency(pair.first, pair.second);
// check that the foreign currency is still in use
TQValueList<MyMoneyAccount>::const_iterator it_a;
TQValueList<MyMoneyAccount> list;
file->accountList(list);
for(it_a = list.begin(); !dontCheckExistance && it_a != list.end(); ++it_a) {
// if it's an account denominated in the foreign currency
// keep it
if(((*it_a).currencyId() == foreignCurrency)
&& !(*it_a).isClosed())
break;
// if it's an investment traded in the foreign currency
// keep it
if((*it_a).isInvest() && !(*it_a).isClosed()) {
MyMoneySecurity sec = file->security((*it_a).currencyId());
if(sec.tradingCurrency() == foreignCurrency)
break;
}
}
// if it is in use, it_a is not equal to list.end()
if(it_a == list.end() && !dontCheckExistance)
keep = false;
}
if(keep) {
KListViewItem* item = new KListViewItem(lvEquityList,
symbol,
i18n("%1 units in %2").tqarg(pair.first,pair.second));
if(pr.isValid()) {
item->setText(PRICE_COL, pr.rate(pair.second).formatMoney(file->currency(pair.second).tradingSymbol(), KMyMoneyGlobalSettings::pricePrecision()));
item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
}
item->setText(ID_COL,id);
item->setText(SOURCE_COL, "Yahoo Currency"); // This string value should not be localized
}
}
}
}
void KEquityPriceUpdateDlg::addInvestment(const MyMoneySecurity& inv)
{
MyMoneyFile* file = MyMoneyFile::instance();
TQString symbol = inv.tradingSymbol();
TQString id = inv.id();
if ( ! lvEquityList->tqfindItem(id, ID_COL, TQt::ExactMatch) )
{
// check that the security is still in use
TQValueList<MyMoneyAccount>::const_iterator it_a;
TQValueList<MyMoneyAccount> list;
file->accountList(list);
for(it_a = list.begin(); it_a != list.end(); ++it_a) {
if((*it_a).isInvest()
&& ((*it_a).currencyId() == inv.id())
&& !(*it_a).isClosed())
break;
}
// if it is in use, it_a is not equal to list.end()
if(it_a != list.end()) {
KListViewItem* item = new KListViewItem(lvEquityList, symbol, inv.name());
MyMoneySecurity currency = file->currency(inv.tradingCurrency());
MyMoneyPrice pr = file->price(id.utf8(), inv.tradingCurrency());
if(pr.isValid()) {
item->setText(PRICE_COL, pr.rate(currency.id()).formatMoney(currency.tradingSymbol(), KMyMoneyGlobalSettings::pricePrecision()));
item->setText(DATE_COL, pr.date().toString(Qt::ISODate));
}
item->setText(ID_COL,id);
if (inv.value("kmm-online-quote-system") == "Finance::Quote")
item->setText(SOURCE_COL, TQString("Finance::Quote %1").tqarg( inv.value("kmm-online-source")));
else
item->setText(SOURCE_COL, inv.value("kmm-online-source"));
// If this investment is denominated in a foreign currency, ensure that
// the appropriate price pair is also on the list
if ( currency.id() != file->baseCurrency().id() )
{
addPricePair(MyMoneySecurityPair(currency.id(),file->baseCurrency().id()));
}
}
}
}
void KEquityPriceUpdateDlg::logErrorMessage(const TQString& message)
{
logStatusMessage(TQString("<font color=\"red\"><b>") + message + TQString("</b></font>"));
}
void KEquityPriceUpdateDlg::logStatusMessage(const TQString& message)
{
lbtqStatus->append(message);
}
MyMoneyPrice KEquityPriceUpdateDlg::price(const TQString& id) const
{
MyMoneyPrice price;
TQListViewItem* item;
if((item = lvEquityList->tqfindItem(id, ID_COL, TQt::ExactMatch)) != 0) {
MyMoneyMoney rate(item->text(PRICE_COL));
if ( !rate.isZero() )
{
TQString id = item->text(ID_COL).utf8();
// if the ID has a space, then this is TWO ID's, so it's a currency quote
if ( TQString(id).tqcontains(" ") )
{
TQStringList ids = TQStringList::split(" ",TQString(id));
TQString fromid = ids[0].utf8();
TQString toid = ids[1].utf8();
price = MyMoneyPrice(fromid,toid,TQDate().fromString(item->text(DATE_COL), Qt::ISODate),rate,item->text(SOURCE_COL));
}
else
// otherwise, it's a security quote
{
MyMoneySecurity security = MyMoneyFile::instance()->security(id);
price = MyMoneyPrice(id, security.tradingCurrency(), TQDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
}
}
}
return price;
}
void KEquityPriceUpdateDlg::storePrices(void)
{
// update the new prices into the equities
MyMoneyFile* file = MyMoneyFile::instance();
TQValueList<MyMoneySecurity> equities = file->securityList();
TQListViewItem* item = lvEquityList->firstChild();
MyMoneyFileTransaction ft;
TQString name;
try {
while ( item )
{
// turn on signals before we modify the last entry in the list
MyMoneyFile::instance()->blockSignals(item->nextSibling() != 0);
MyMoneyMoney rate(item->text(PRICE_COL));
if ( !rate.isZero() )
{
TQString id = item->text(ID_COL).utf8();
// if the ID has a space, then this is TWO ID's, so it's a currency quote
if ( TQString(id).tqcontains(" ") )
{
TQStringList ids = TQStringList::split(" ",TQString(id));
TQString fromid = ids[0].utf8();
TQString toid = ids[1].utf8();
name = TQString("%1 --> %2").tqarg(fromid).tqarg(toid);
MyMoneyPrice price(fromid,toid,TQDate().fromString(item->text(DATE_COL), Qt::ISODate),rate,item->text(SOURCE_COL));
file->addPrice(price);
}
else
// otherwise, it's a security quote
{
MyMoneySecurity security = MyMoneyFile::instance()->security(id);
name = security.name();
MyMoneyPrice price(id, security.tradingCurrency(), TQDate().fromString(item->text(DATE_COL), Qt::ISODate), rate, item->text(SOURCE_COL));
// TODO (Ace) Better handling of the case where there is already a price
// for this date. Currently, it just overrides the old value. Really it
// should check to see if the price is the same and prompt the user.
MyMoneyFile::instance()->addPrice(price);
}
}
item = item->nextSibling();
}
ft.commit();
} catch(MyMoneyException *e) {
qDebug("Unable to add price information for %s", name.data());
delete e;
}
}
void KEquityPriceUpdateDlg::slotUpdateSelection(void)
{
btnUpdateSelected->setEnabled(false);
TQListViewItem* item = lvEquityList->firstChild();
while ( item && !item->isSelected())
item = item->nextSibling();
if(item)
btnUpdateSelected->setEnabled(true);
}
void KEquityPriceUpdateDlg::slotUpdateSelectedClicked(void)
{
TQListViewItem* item = lvEquityList->firstChild();
int skipCnt = 1;
while ( item && !item->isSelected())
{
skipCnt++;
item = item->nextSibling();
}
if(item) {
prgOnlineProgress->setTotalSteps(1+lvEquityList->childCount());
prgOnlineProgress->setProgress(skipCnt);
m_webQuote.launch(item->text(SYMBOL_COL),item->text(ID_COL),item->text(SOURCE_COL));
}
else
logErrorMessage("No security selected.");
}
void KEquityPriceUpdateDlg::slotUpdateAllClicked(void)
{
TQListViewItem* item = lvEquityList->firstChild();
if ( item )
{
prgOnlineProgress->setTotalSteps(1+lvEquityList->childCount());
prgOnlineProgress->setProgress(1);
m_fUpdateAll = true;
m_webQuote.launch(item->text(SYMBOL_COL),item->text(ID_COL),item->text(SOURCE_COL));
}
else
logErrorMessage("Security list is empty.");
}
void KEquityPriceUpdateDlg::slotQuoteFailed(const TQString& _id, const TQString& _symbol)
{
TQListViewItem* item = lvEquityList->tqfindItem(_id,ID_COL,TQt::ExactMatch);
// Give the user some options
int result;
if(_id.tqcontains(" ")) {
result = KMessageBox::warningContinueCancel(this, i18n("Failed to retrieve an exchange rate for %1 from %2. It will be skipped this time.").tqarg(_symbol, item->text(SOURCE_COL)), i18n("Price Update Failed"));
} else {
result = KMessageBox::questionYesNoCancel(this, TQString("<qt>%1</qt>").tqarg(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.").tqarg(_symbol, item->text(SOURCE_COL))), i18n("Price Update Failed"), KStdGuiItem::yes(), KStdGuiItem::no());
}
if ( result == KMessageBox::No )
{
// Disable price updates for this security
MyMoneyFileTransaction ft;
try {
// Get this security (by ID)
MyMoneySecurity security = MyMoneyFile::instance()->security(_id.utf8());
// Set the quote source to blank
security.setValue("kmm-online-source",TQString());
security.setValue("kmm-online-quote-system",TQString());
// Re-commit the security
MyMoneyFile::instance()->modifySecurity(security);
ft.commit();
} catch(MyMoneyException* e) {
KMessageBox::error(this, TQString("<qt>")+i18n("Cannot update security <b>%1</b>: %2").tqarg(_symbol, e->what())+TQString("</qt>"), i18n("Price Update Failed"));
delete e;
}
}
// As long as the user doesn't want to cancel, move on!
if ( result != KMessageBox::Cancel )
{
TQListViewItem* next = NULL;
prgOnlineProgress->advance(1);
item->listView()->setSelected(item, false);
// launch the NEXT one ... in case of m_fUpdateAll == false, we
// need to parse the list to find the next selected one
next = item->nextSibling();
if ( !m_fUpdateAll )
{
while(next && !next->isSelected())
{
prgOnlineProgress->advance(1);
next = next->nextSibling();
}
}
if (next)
{
m_webQuote.launch(next->text(SYMBOL_COL),next->text(ID_COL),next->text(SOURCE_COL));
}
else
{
finishUpdate();
}
}
else
{
finishUpdate();
}
}
void KEquityPriceUpdateDlg::slotReceivedQuote(const TQString& _id, const TQString& _symbol,const TQDate& _date, const double& _price)
{
TQListViewItem* item = lvEquityList->tqfindItem(_id,ID_COL,TQt::ExactMatch);
TQListViewItem* next = NULL;
if ( item )
{
if ( _price > 0.0f && _date.isValid() )
{
TQDate date = _date;
if ( date > TQDate::tqcurrentDate() )
date = TQDate::tqcurrentDate();
double price = _price;
TQString id = _id.utf8();
MyMoneySecurity sec;
if ( _id.tqcontains(" ") == 0) {
MyMoneySecurity security = MyMoneyFile::instance()->security(id);
TQString factor = security.value("kmm-online-factor");
if(!factor.isEmpty()) {
price *= MyMoneyMoney(factor).toDouble();
}
try {
sec = MyMoneyFile::instance()->security(id);
sec = MyMoneyFile::instance()->security(sec.tradingCurrency());
} catch(MyMoneyException *e) {
sec = MyMoneySecurity();
delete e;
}
} else {
TQRegExp splitrx("([0-9a-z\\.]+)[^a-z0-9]+([0-9a-z\\.]+)",false /*case sensitive*/);
if ( splitrx.search(_id) != -1 ) {
try {
sec = MyMoneyFile::instance()->security(splitrx.cap(2).utf8());
} catch(MyMoneyException *e) {
sec = MyMoneySecurity();
delete e;
}
}
}
item->setText(PRICE_COL, KGlobal::locale()->formatMoney(price, sec.tradingSymbol(), KMyMoneyGlobalSettings::pricePrecision()));
item->setText(DATE_COL, date.toString(Qt::ISODate));
logStatusMessage(i18n("Price for %1 updated (id %2)").tqarg(_symbol,_id));
// make sure to make OK button available
btnOK->setEnabled(true);
}
else
{
logErrorMessage(i18n("Received an invalid price for %1, unable to update.").tqarg(_symbol));
}
prgOnlineProgress->advance(1);
item->listView()->setSelected(item, false);
// launch the NEXT one ... in case of m_fUpdateAll == false, we
// need to parse the list to find the next selected one
next = item->nextSibling();
if ( !m_fUpdateAll )
{
while(next && !next->isSelected())
{
prgOnlineProgress->advance(1);
next = next->nextSibling();
}
}
}
else
{
logErrorMessage(i18n("Received a price for %1 (id %2), but this symbol is not on the list! Aborting entire update.").tqarg(_symbol,_id));
}
if (next)
{
m_webQuote.launch(next->text(SYMBOL_COL),next->text(ID_COL),next->text(SOURCE_COL));
}
else
{
finishUpdate();
}
}
void KEquityPriceUpdateDlg::finishUpdate(void)
{
// we've run past the end, reset to the default value.
m_fUpdateAll = false;
// force progress bar to show 100%
prgOnlineProgress->setProgress(prgOnlineProgress->totalSteps());
}
// Make sure, that these definitions are only used within this file
// this does not seem to be necessary, but when building RPMs the
// build option 'final' is used and all CPP files are concatenated.
// So it could well be, that in another CPP file these definitions
// are also used.
#undef SYMBOL_COL
#undef NAME_COL
#undef PRICE_COL
#undef DATE_COL
#undef ID_COL
#undef SOURCE_COL
#include "kequitypriceupdatedlg.moc"
// vim:cin:si:ai:et:ts=2:sw=2: