diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2011-07-04 22:38:03 +0000 |
commit | dadc34655c3ab961b0b0b94a10eaaba710f0b5e8 (patch) | |
tree | 99e72842fe687baea16376a147619b6048d7e441 /kmymoney2/views | |
download | kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.tar.gz kmymoney-dadc34655c3ab961b0b0b94a10eaaba710f0b5e8.zip |
Added kmymoney
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kmymoney@1239792 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmymoney2/views')
41 files changed, 18067 insertions, 0 deletions
diff --git a/kmymoney2/views/Makefile.am b/kmymoney2/views/Makefile.am new file mode 100644 index 0000000..693d075 --- /dev/null +++ b/kmymoney2/views/Makefile.am @@ -0,0 +1,25 @@ +KDE_OPTIONS = noautodist + +INCLUDES = -I$(top_srcdir) $(all_includes) -I$(top_srcdir)/libkdchart + +noinst_LIBRARIES = libviews.a +libviews_a_METASOURCES = AUTO + +libviews_a_SOURCES = kreportsview.cpp kmymoneytransaction.cpp kgloballedgerview.cpp kmymoneyfile.cpp kinvestmentlistitem.cpp kinvestmentviewdecl.ui kinvestmentview.cpp kscheduledlistitem.cpp kscheduledviewdecl.ui kscheduledview.cpp kpayeesview.cpp kpayeesviewdecl.ui khomeview.cpp kcategoriesviewdecl.ui kcategoriesview.cpp kmymoneyview.cpp kaccountsviewdecl.ui kaccountsview.cpp kinstitutionsviewdecl.ui kinstitutionsview.cpp kbudgetview.cpp kbudgetviewdecl.ui kforecastview.cpp kforecastviewdecl.ui + +# oldviews +# kledgerview.cpp kledgerviewinvestments.cpp kledgerviewliability.cpp kledgerviewloan.cpp kledgerviewcash.cpp kledgerviewasset.cpp kledgerviewcreditcard.cpp kledgerviewsavings.cpp kledgerviewcheckings.cpp + +DISTCLEANFILES= kaccountsviewdecl.cpp kcategoriesviewdecl.cpp kinvestmentviewdecl.cpp kpayeesviewdecl.cpp kscheduledviewdecl.cpp kaccountsviewdecl.h kcategoriesviewdecl.h kinvestmentviewdecl.h kpayeesviewdecl.h kscheduledviewdecl.h kinstitutionsviewdecl.cpp kinstitutionsviewdecl.h kbudgetviewdecl.cpp kbudgetviewdecl.h kforecastviewdecl.cpp kforecastviewdecl.h + +EXTRA_DIST = kaccountsviewdecl.ui kcategoriesviewdecl.ui kinvestmentviewdecl.ui kpayeesviewdecl.ui kscheduledviewdecl.ui kinstitutionsviewdecl.ui kbudgetviewdecl.ui kforecastviewdecl.ui + +instdir=$(includedir)/kmymoney +inst_HEADERS = kmymoneyview.h + +noinst_HEADERS = kaccountsview.h kcategoriesview.h kgloballedgerview.h khomeview.h kinvestmentlistitem.h kinvestmentview.h kmymoneyfile.h kmymoneytransaction.h kpayeesview.h kreportsview.h kscheduledlistitem.h kscheduledview.h kinstitutionsview.h kbudgetview.h kforecastview.h + +# old noinst_HEADERS +# kledgerviewasset.h kledgerviewcash.h kledgerviewcheckings.h kledgerviewcreditcard.h kledgerviewliability.h kledgerviewloan.h kledgerviewsavings.h kledgerview.h kledgerviewinvestments.h + +messages: rc.cpp diff --git a/kmymoney2/views/kaccountsview.cpp b/kmymoney2/views/kaccountsview.cpp new file mode 100644 index 0000000..b0800cd --- /dev/null +++ b/kmymoney2/views/kaccountsview.cpp @@ -0,0 +1,625 @@ +/*************************************************************************** + kaccountsview.cpp + ------------------- + copyright : (C) 2005 by Thomas Baumgart + email : ipwizard@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 <qlabel.h> +#include <qtabwidget.h> +#include <qpixmap.h> +#include <qlayout.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kdebug.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kiconview.h> +#include <kguiitem.h> +#include <kpushbutton.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include "kaccountsview.h" +#include "kmymoneyview.h" +#include "../widgets/klistviewsearchline.h" +#include "../kmymoneyglobalsettings.h" +#include "../kmymoney2.h" + + +KMyMoneyAccountIconItem::KMyMoneyAccountIconItem(QIconView *parent, const MyMoneyAccount& account) : + KIconViewItem(parent, account.name()), + m_account(account), + m_reconcileFlag(false) +{ + updateAccount(account); +} + +KMyMoneyAccountIconItem::~KMyMoneyAccountIconItem() +{ +} + +void KMyMoneyAccountIconItem::setReconciliation(bool on) +{ + if(m_reconcileFlag == on) + return; + m_reconcileFlag = on; + updateAccount(m_account); +} + +void KMyMoneyAccountIconItem::updateAccount(const MyMoneyAccount& account) +{ + setPixmap(account.accountPixmap(m_reconcileFlag)); +} + +KAccountsView::KAccountsView(QWidget *parent, const char *name) : + KAccountsViewDecl(parent,name), + m_assetItem(0), + m_liabilityItem(0) +{ + // create the searchline widget + // and insert it into the existing layout + m_searchWidget = new KListViewSearchLineWidget(m_accountTree, m_accountTree->parentWidget()); + QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(m_accountTree->parentWidget()->layout()); + if(layout) { + layout->insertWidget(0, m_searchWidget); + } + + // setup icons for collapse and expand button + KIconLoader *ic = KGlobal::iconLoader(); + KGuiItem collapseGuiItem("", + QIconSet(ic->loadIcon("viewmag-", KIcon::Small, KIcon::SizeSmall)), + QString(), + QString()); + KGuiItem expandGuiItem("", + QIconSet(ic->loadIcon("viewmag+", KIcon::Small, KIcon::SizeSmall)), + QString(), + QString()); + m_collapseButton->setGuiItem(collapseGuiItem); + m_expandButton->setGuiItem(expandGuiItem); + + for(int i=0; i < MaxViewTabs; ++i) + m_needReload[i] = false; + + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + m_tab->setCurrentPage(config->readNumEntry("KAccountsView_LastType", 0)); + + connect(m_tab, SIGNAL(currentChanged(QWidget*)), this, SLOT(slotTabChanged(QWidget*))); + + connect(m_accountTree, SIGNAL(selectObject(const MyMoneyObject&)), this, SIGNAL(selectObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(openContextMenu(const MyMoneyObject&)), this, SIGNAL(openContextMenu(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(valueChanged(void)), this, SLOT(slotUpdateNetWorth(void))); + connect(m_accountTree, SIGNAL(openObject(const MyMoneyObject&)), this, SIGNAL(openObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&)), this, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&))); + + connect(m_accountIcons, SIGNAL(selectionChanged(QIconViewItem*)), this, SLOT(slotSelectIcon(QIconViewItem*))); + connect(m_accountIcons, SIGNAL(rightButtonClicked(QIconViewItem*, const QPoint&)), this, SLOT(slotOpenContext(QIconViewItem*))); + connect(m_accountIcons, SIGNAL(executed(QIconViewItem*)), this, SLOT(slotOpenObject(QIconViewItem*))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); + connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); + connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); +} + +KAccountsView::~KAccountsView() +{ +} + +void KAccountsView::slotExpandCollapse(void) +{ + if(sender()) { + KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); + } +} + +void KAccountsView::slotLoadAccounts(void) +{ + m_needReload[ListView] = true; + m_needReload[IconView] = true; + if(isVisible()) + slotTabChanged(m_tab->currentPage()); +} + +void KAccountsView::slotTabChanged(QWidget* _tab) +{ + AccountsViewTab tab = static_cast<AccountsViewTab>(m_tab->indexOf(_tab)); + + // remember this setting for startup + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + config->writeEntry("KAccountsView_LastType", tab); + + loadAccounts(tab); + + switch(tab) { + case ListView: + // update the hint if categories are hidden + m_hiddenCategories->setShown(m_haveUnusedCategories); + break; + + case IconView: + m_hiddenCategories->hide(); + break; + + default: + break; + } + + KMyMoneyAccountTreeBaseItem* treeItem = m_accountTree->selectedItem(); + KMyMoneyAccountIconItem* iconItem = selectedIcon(); + + emit selectObject(MyMoneyAccount()); + switch(static_cast<AccountsViewTab>(m_tab->indexOf(m_tab->currentPage()))) { + case ListView: + // if we have a selected account, let the application know about it + if(treeItem) { + emit selectObject(treeItem->itemObject()); + } + break; + + case IconView: + if(iconItem) { + emit selectObject(iconItem->itemObject()); + } + break; + + default: + break; + } +} + +void KAccountsView::show(void) +{ + // don't forget base class implementation + KAccountsViewDecl::show(); + slotTabChanged(m_tab->currentPage()); +} + +void KAccountsView::polish(void) +{ + // don't forget base class implementation + KAccountsViewDecl::polish(); + m_accountTree->setResizeMode(QListView::LastColumn); + m_accountTree->restoreLayout("Account View Settings"); +} + +void KAccountsView::loadAccounts(AccountsViewTab tab) +{ + if(m_needReload[tab]) { + switch(tab) { + case ListView: + loadListView(); + break; + case IconView: + loadIconView(); + break; + default: + break; + } + m_needReload[tab] = false; + } +} + +void KAccountsView::loadIconView(void) +{ + ::timetrace("start load accounts icon view"); + + // remember the positions of the icons + QMap<QString, QPoint> posMap; + KMyMoneyAccountIconItem* p = dynamic_cast<KMyMoneyAccountIconItem*>(m_accountIcons->firstItem()); + for(;p; p = dynamic_cast<KMyMoneyAccountIconItem*>(p->nextItem())) + posMap[p->itemObject().id()] = p->pos(); + + // turn off updates to avoid flickering during reload + m_accountIcons->setAutoArrange(true); + + // clear the current contents and recreate it + m_accountIcons->clear(); + QMap<QString, MyMoneyAccount> accountMap; + + MyMoneyFile* file = MyMoneyFile::instance(); + + // get account list and sort by name + QValueList<MyMoneyAccount> alist; + file->accountList(alist); + QValueList<MyMoneyAccount>::const_iterator it_a; + for(it_a = alist.begin(); it_a != alist.end(); ++it_a) { + accountMap[QString("%1-%2").arg((*it_a).name()).arg((*it_a).id())] = *it_a; + } + + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked() + || !KMyMoneyGlobalSettings::hideClosedAccounts(); + bool existNewIcons = false; + + // parse list and add all asset and liability accounts + QMap<QString, MyMoneyAccount>::const_iterator it; + for(it = accountMap.begin(); it != accountMap.end(); ++it) { + QPoint loc; + if((*it).isClosed() && !showClosedAccounts) + continue; + const QString& pos = (*it).value("kmm-iconpos"); + KMyMoneyAccountIconItem* item; + switch((*it).accountGroup()) { + case MyMoneyAccount::Equity: + if(!KMyMoneyGlobalSettings::expertMode()) + continue; + // tricky fall through here + + case MyMoneyAccount::Asset: + case MyMoneyAccount::Liability: + // don't show stock accounts + if((*it).isInvest()) + continue; + + // if we have a position stored with the object and no other + // idea of it's current position, then take the one + // stored inside the object. Also, turn off auto arrangement + if(!pos.isEmpty() && posMap[(*it).id()] == QPoint()) { + posMap[(*it).id()] = point(pos); + } + + loc = posMap[(*it).id()]; + if(loc == QPoint()) { + existNewIcons = true; + } else { + m_accountIcons->setAutoArrange(false); + } + + item = new KMyMoneyAccountIconItem(m_accountIcons, *it); + if((*it).id() == m_reconciliationAccount.id()) + item->setReconciliation(true); + + if(loc != QPoint()) { + item->move(loc); + } + break; + + default: + break; + } + } + + // clear the current contents + m_securityMap.clear(); + m_transactionCountMap.clear(); + + if(existNewIcons) { + m_accountIcons->arrangeItemsInGrid(true); + } + + m_accountIcons->setAutoArrange(false); + + ::timetrace("done load accounts icon view"); +} + +void KAccountsView::loadListView(void) +{ + QMap<QString, bool> isOpen; + + ::timetrace("start load accounts list view"); + // remember the id of the current selected item + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + QString selectedItemId = (item) ? item->id() : QString(); + + // keep a map of all 'expanded' accounts + QListViewItemIterator it_lvi(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item && item->isOpen()) { + isOpen[item->id()] = true; + } + ++it_lvi; + } + + // remember the upper left corner of the viewport + QPoint startPoint = m_accountTree->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_accountTree->setUpdatesEnabled(false); + + // clear the current contents and recreate it + m_accountTree->clear(); + m_securityMap.clear(); + m_transactionCountMap.clear(); + + // make sure, the pointers are not pointing to some deleted object + m_assetItem = m_liabilityItem = 0; + + MyMoneyFile* file = MyMoneyFile::instance(); + + QValueList<MyMoneySecurity> slist = file->currencyList(); + slist += file->securityList(); + QValueList<MyMoneySecurity>::const_iterator it_s; + for(it_s = slist.begin(); it_s != slist.end(); ++it_s) { + m_securityMap[(*it_s).id()] = *it_s; + } + m_transactionCountMap = file->transactionCountMap(); + + m_haveUnusedCategories = false; + + // create the items + try { + const MyMoneySecurity security = file->baseCurrency(); + m_accountTree->setBaseCurrency(security); + + const MyMoneyAccount& asset = file->asset(); + m_assetItem = new KMyMoneyAccountTreeItem(m_accountTree, asset, security, i18n("Asset")); + loadSubAccounts(m_assetItem, asset.accountList()); + + const MyMoneyAccount& liability = file->liability(); + m_liabilityItem = new KMyMoneyAccountTreeItem(m_accountTree, liability, security, i18n("Liability")); + loadSubAccounts(m_liabilityItem, liability.accountList()); + + const MyMoneyAccount& income = file->income(); + KMyMoneyAccountTreeItem *incomeItem = new KMyMoneyAccountTreeItem(m_accountTree, income, security, i18n("Income")); + m_haveUnusedCategories |= loadSubAccounts(incomeItem, income.accountList()); + + const MyMoneyAccount& expense = file->expense(); + KMyMoneyAccountTreeItem *expenseItem = new KMyMoneyAccountTreeItem(m_accountTree, expense, security, i18n("Expense")); + m_haveUnusedCategories |= loadSubAccounts(expenseItem, expense.accountList()); + + if(KMyMoneyGlobalSettings::expertMode()) { + const MyMoneyAccount equity = file->equity(); + KMyMoneyAccountTreeItem *equityItem = new KMyMoneyAccountTreeItem(m_accountTree, equity, security, i18n("Equity")); + loadSubAccounts(equityItem, equity.accountList()); + } + + } catch(MyMoneyException *e) { + kdDebug(2) << "Problem in accounts list view: " << e->what(); + delete e; + } + + // scan through the list of accounts and re-expand those that were + // expanded and re-select the one that was probably selected before + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item) { + if(item->id() == selectedItemId) + m_accountTree->setSelected(item, true); + if(isOpen.find(item->id()) != isOpen.end()) + item->setOpen(true); + } + ++it_lvi; + } + + // reposition viewport + m_accountTree->setContentsPos(startPoint.x(), startPoint.y()); + + m_searchWidget->searchLine()->updateSearch(QString::null); + + // turn updates back on + m_accountTree->setUpdatesEnabled(true); + m_accountTree->repaintContents(); + + // and in case we need to show things expanded, we'll do so + if(KMyMoneyGlobalSettings::showAccountsExpanded()) + m_accountTree->slotExpandAll(); + + // clear the current contents + m_securityMap.clear(); + m_transactionCountMap.clear(); + ::timetrace("done load accounts list view"); +} + +bool KAccountsView::loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QStringList& accountList) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + bool unused = false; + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked() + || !KMyMoneyGlobalSettings::hideClosedAccounts(); + + QStringList::const_iterator it_a; + for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { + const MyMoneyAccount& acc = file->account(*it_a); + QValueList<MyMoneyPrice> prices; + MyMoneySecurity security = file->baseCurrency(); + try { + if(acc.isInvest()) { + security = m_securityMap[acc.currencyId()]; + prices += file->price(acc.currencyId(), security.tradingCurrency()); + if(security.tradingCurrency() != file->baseCurrency().id()) { + MyMoneySecurity sec = m_securityMap[security.tradingCurrency()]; + prices += file->price(sec.id(), file->baseCurrency().id()); + } + } else if(acc.currencyId() != file->baseCurrency().id()) { + if(acc.currencyId() != file->baseCurrency().id()) { + security = m_securityMap[acc.currencyId()]; + prices += file->price(acc.currencyId(), file->baseCurrency().id()); + } + } + + } catch(MyMoneyException *e) { + kdDebug(2) << __PRETTY_FUNCTION__ << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e->what(); + delete e; + } + + KMyMoneyAccountTreeItem* item = new KMyMoneyAccountTreeItem(parent, acc, prices, security); + if(acc.id() == m_reconciliationAccount.id()) + item->setReconciliation(true); + + unused |= loadSubAccounts(item, acc.accountList()); + + // no child accounts and no transactions in this account means 'unused' + bool thisUnused = (!item->firstChild()) && (m_transactionCountMap[acc.id()] == 0); + + // In case of a category which is unused and we are requested to suppress + // the display of those, + if(acc.isIncomeExpense()) { + if(KMyMoneyGlobalSettings::hideUnusedCategory() && thisUnused) { + unused = true; + delete item; + } + } + + // if the account is closed and we should not show it, we delete the item + if(acc.isClosed() && !showClosedAccounts) { + delete item; + } + } + return unused; +} + +void KAccountsView::slotReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) +{ + Q_UNUSED(reconciliationDate); + Q_UNUSED(endingBalance); + + // scan through the list of accounts and mark all non + // expanded and re-select the one that was probably selected before + QListViewItemIterator it_lvi(m_accountTree); + KMyMoneyAccountTreeItem* item; + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item) { + item->setReconciliation(false); + } + ++it_lvi; + } + + // scan trough the icon list and do the same thing + KMyMoneyAccountIconItem* icon = dynamic_cast<KMyMoneyAccountIconItem*>(m_accountIcons->firstItem()); + for(;icon; icon = dynamic_cast<KMyMoneyAccountIconItem*>(icon->nextItem())) { + icon->setReconciliation(false); + } + + m_reconciliationAccount = acc; + + if(!acc.id().isEmpty()) { + // scan through the list of accounts and mark + // the one that is currently reconciled + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item && item->itemObject().id() == acc.id()) { + item->setReconciliation(true); + break; + } + ++it_lvi; + } + + // scan trough the icon list and do the same thing + icon = dynamic_cast<KMyMoneyAccountIconItem*>(m_accountIcons->firstItem()); + for(;icon; icon = dynamic_cast<KMyMoneyAccountIconItem*>(icon->nextItem())) { + if(icon->itemObject().id() == acc.id()) { + icon->setReconciliation(true); + break; + } + } + } +} + +void KAccountsView::slotUpdateNetWorth(void) +{ + if(!m_assetItem || !m_liabilityItem) + return; + + MyMoneyMoney netWorth = m_assetItem->totalValue() - m_liabilityItem->totalValue(); + + QString s(i18n("Net Worth: ")); + + // FIXME figure out how to deal with the approximate + // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) + // s += "~ "; + + s.replace(QString(" "), QString(" ")); + if(netWorth.isNegative()) { + s += "<b><font color=\"red\">"; + } + const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); + QString v(netWorth.formatMoney(sec)); + s += v.replace(QString(" "), QString(" ")); + if(netWorth.isNegative()) { + s += "</font></b>"; + } + + m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); + m_totalProfitsLabel->setText(s); +} + +KMyMoneyAccountIconItem* KAccountsView::selectedIcon(void) const +{ + return dynamic_cast<KMyMoneyAccountIconItem*>(m_accountIcons->currentItem()); +} + +void KAccountsView::slotSelectIcon(QIconViewItem* item) +{ + KMyMoneyAccountIconItem* p = dynamic_cast<KMyMoneyAccountIconItem*>(item); + if(p) + emit selectObject(p->itemObject()); +} + +void KAccountsView::slotOpenContext(QIconViewItem* item) +{ + KMyMoneyAccountIconItem* p = dynamic_cast<KMyMoneyAccountIconItem*>(item); + if(p) + emit openContextMenu(p->itemObject()); +} + +void KAccountsView::slotOpenObject(QIconViewItem* item) +{ + KMyMoneyAccountIconItem* p = dynamic_cast<KMyMoneyAccountIconItem*>(item); + if(p) + emit openObject(p->itemObject()); +} + +QString KAccountsView::point(const QPoint& val) const +{ + return QString("%1;%2").arg(val.x()).arg(val.y()); +} + +QPoint KAccountsView::point(const QString& val) const +{ + QRegExp exp("(\\d+);(\\d+)"); + int x = 0; + int y = 0; + if(exp.search(val) != -1) { + x = exp.cap(1).toInt(); + y = exp.cap(2).toInt(); + } + return QPoint(x, y); +} + +void KAccountsView::slotUpdateIconPos(unsigned int action) +{ + if(action != KMyMoneyView::preSave) + return; + + MyMoneyFileTransaction ft; + KMyMoneyAccountIconItem* p = dynamic_cast<KMyMoneyAccountIconItem*>(m_accountIcons->firstItem()); + for(;p; p = dynamic_cast<KMyMoneyAccountIconItem*>(p->nextItem())) { + const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(p->itemObject()); + if(acc.value("kmm-iconpos") != point(p->pos())) { + MyMoneyAccount a(acc); + a.setValue("kmm-iconpos", point(p->pos())); + try { + MyMoneyFile::instance()->modifyAccount(a); + } catch(MyMoneyException* e) { + kdDebug(2) << "Unable to update icon pos: " << e->what(); + delete e; + } + } + } + ft.commit(); +} + + +#include "kaccountsview.moc" diff --git a/kmymoney2/views/kaccountsview.h b/kmymoney2/views/kaccountsview.h new file mode 100644 index 0000000..ed30ac5 --- /dev/null +++ b/kmymoney2/views/kaccountsview.h @@ -0,0 +1,209 @@ +/*************************************************************************** + kaccountssview.h + ------------------- + copyright : (C) 2005 by Thomas Baumgart + email : ipwizard@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. * + * * + ***************************************************************************/ + +#ifndef KACCOUNTSSVIEW_H +#define KACCOUNTSSVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kiconview.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/kmymoneyaccounttree.h> +#include <kmymoney/mymoneyutils.h> +class KListViewSearchLineWidget; + +#include "../views/kaccountsviewdecl.h" + +/** + * @author Thomas Baumgart + */ + +/** + * This class represents an item in the account icon view. It is used + * by the KAccountsView to select between the accounts using icons. + */ +class KMyMoneyAccountIconItem : public KIconViewItem +{ +public: + /** + * Constructor to be used to construct an account icon object. + * + * @param parent pointer to the KIconView object this entry should be + * added to. + * @param account const reference to MyMoneyAccount for which + * the KIconView entry is constructed + */ + KMyMoneyAccountIconItem(QIconView *parent, const MyMoneyAccount& account); + ~KMyMoneyAccountIconItem(); + + /** + * This method is loads new information into the item and updates the fields + * + * @param account the account data for the object to be updated + * + * @note if account.id() is not equal to the current account id + * then this method returns immediately + */ + void updateAccount(const MyMoneyAccount& account); + + const MyMoneyObject& itemObject(void) const { return m_account; }; + + void setReconciliation(bool); + +protected: + +private: + MyMoneyAccount m_account; + bool m_reconcileFlag; +}; + + + + +/** + * This class implements the accounts hierarchical and iconic 'view'. + */ +class KAccountsView : public KAccountsViewDecl +{ + Q_OBJECT +private: + +public: + KAccountsView(QWidget *parent=0, const char *name=0); + virtual ~KAccountsView(); + +public slots: + void slotLoadAccounts(void); + + /** + * Override the base class behaviour to include all updates that + * happened in the meantime. + */ + void show(void); + + /** + * Override the base class behaviour to restore the layout. Do not + * do this in show() because show() itself may change the layout + * in undesired ways. + */ + void polish(void); + + /** + * update the account objects if their icon position has changed since + * the last time. + * + * @param action must be KMyMoneyView::preSave, otherwise this slot is a NOP. + */ + void slotUpdateIconPos(unsigned int action); + + void slotReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); + +protected: + typedef enum { + ListView = 0, + IconView, + // insert new values above this line + MaxViewTabs + } AccountsViewTab; + + /** + * This method loads the accounts for the respective tab. + * + * @param tab which tab should be loaded + */ + void loadAccounts(AccountsViewTab tab); + void loadListView(void); + void loadIconView(void); + + bool loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QStringList& accountList); + + /** + * This method returns a pointer to the currently selected + * account icon or 0 if no icon is selected. + */ + KMyMoneyAccountIconItem* selectedIcon(void) const; + + QPoint point(const QString& str) const; + QString point(const QPoint& val) const; + +protected slots: + void slotUpdateNetWorth(void); + void slotTabChanged(QWidget*); + void slotSelectIcon(QIconViewItem* item); + void slotOpenContext(QIconViewItem* item); + void slotOpenObject(QIconViewItem* item); + void slotExpandCollapse(void); + +signals: + /** + * This signal serves as proxy for KMyMoneyAccountTree::selectObject() + * + * @param obj const reference to object + */ + void selectObject(const MyMoneyObject& obj); + + /** + * This signal serves as proxy for + * KMyMoneyAccountTree::openContextMenu(const MyMoneyObject&) + * + * @param obj const reference to object + */ + void openContextMenu(const MyMoneyObject& obj); + + /** + * This signal will be emitted when the left mouse button is double + * clicked (actually the KDE executed setting is used) on an object. + * + * @param obj const reference to object + */ + void openObject(const MyMoneyObject& obj); + + /** + * This signal is emitted, when the user selected to reparent the + * account @p acc to be a subordinate account of @p parent. + * + * @param acc const reference to account to be reparented + * @param parent const reference to new parent account + */ + void reparent(const MyMoneyAccount& acc, const MyMoneyAccount& parent); + +private: + MyMoneyAccount m_reconciliationAccount; + QMap<QString, MyMoneySecurity> m_securityMap; + QMap<QString, unsigned long> m_transactionCountMap; + + KMyMoneyAccountTreeItem* m_assetItem; + KMyMoneyAccountTreeItem* m_liabilityItem; + + /** + * Search widget for the list + */ + KListViewSearchLineWidget* m_searchWidget; + + /// set if a view needs to be reloaded during show() + bool m_needReload[MaxViewTabs]; + bool m_haveUnusedCategories; +}; + +#endif diff --git a/kmymoney2/views/kaccountsviewdecl.ui b/kmymoney2/views/kaccountsviewdecl.ui new file mode 100644 index 0000000..c8bb5ac --- /dev/null +++ b/kmymoney2/views/kaccountsviewdecl.ui @@ -0,0 +1,204 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KAccountsViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KAccountsViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>697</width> + <height>426</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QTabWidget"> + <property name="name"> + <cstring>m_tab</cstring> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>List</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>2</number> + </property> + <widget class="KMyMoneyAccountTree"> + <property name="name"> + <cstring>m_accountTree</cstring> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_collapseButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Collapse all accounts in the list</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_expandButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Expand all accounts in the list</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>515</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Icons</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KIconView"> + <property name="name"> + <cstring>m_accountIcons</cstring> + </property> + <property name="arrangement"> + <enum>LeftToRight</enum> + </property> + <property name="maxItemTextLength"> + <number>30</number> + </property> + <property name="autoArrange"> + <bool>true</bool> + </property> + <property name="wordWrapIconText"> + <bool>false</bool> + </property> + </widget> + </vbox> + </widget> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>m_hiddenCategories</cstring> + </property> + <property name="text"> + <string>Note: Unused categories are not shown as selected by settings.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>Spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>367</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>m_totalProfitsLabel</cstring> + </property> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Total Profits:</string> + </property> + <property name="textFormat"> + <enum>RichText</enum> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignRight</set> + </property> + <property name="hAlign" stdset="0"> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<connections> + <connection> + <sender>m_expandButton</sender> + <signal>clicked()</signal> + <receiver>m_accountTree</receiver> + <slot>slotExpandAll()</slot> + </connection> + <connection> + <sender>m_collapseButton</sender> + <signal>clicked()</signal> + <receiver>m_accountTree</receiver> + <slot>slotCollapseAll()</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kbudgetview.cpp b/kmymoney2/views/kbudgetview.cpp new file mode 100644 index 0000000..a8d5bc1 --- /dev/null +++ b/kmymoney2/views/kbudgetview.cpp @@ -0,0 +1,767 @@ +/*************************************************************************** + kbudgetview.cpp + --------------- + begin : Thu Jan 10 2006 + copyright : (C) 2006 by Darren Gould + email : darren_gould@gmx.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qpushbutton.h> +#include <qcombobox.h> +#include <qlineedit.h> +#include <qlabel.h> +#include <qmultilineedit.h> +#include <qpixmap.h> +#include <qtabwidget.h> +#include <qlistbox.h> +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qtooltip.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kglobal.h> +#include <klocale.h> +#include <kconfig.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <kguiitem.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <kcalendarsystem.h> +#include <kcombobox.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> + +#include <kmymoney/kmymoneyglobalsettings.h> +#include <kmymoney/kmymoneytitlelabel.h> +#include <kmymoney/kmymoneyedit.h> +#include <kmymoney/kbudgetvalues.h> +#include "../widgets/kmymoneyaccounttreebudget.h" +#include "kbudgetview.h" +#include "../dialogs/knewbudgetdlg.h" +#include "../kmymoney2.h" + +// *** KBudgetListItem Implementation *** +KBudgetListItem::KBudgetListItem(KListView *parent, const MyMoneyBudget& budget) : + KListViewItem(parent), + m_budget(budget) +{ + setText(0, budget.name()); + setText(1, QString("%1").arg(budget.budgetStart().year())); + + // allow in column rename + setRenameEnabled(0, true); +} + +KBudgetListItem::~KBudgetListItem() +{ +} + +void KBudgetListItem::paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align) +{ + p->setFont(KMyMoneyGlobalSettings::listCellFont()); + + QColorGroup cg2(cg); + + if (isAlternate()) + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listColor()); + else + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listBGColor()); + + QListViewItem::paintCell(p, cg2, column, width, align); +} + + +// *** KBudgetView Implementation *** +const int KBudgetView::m_iBudgetYearsAhead = 5; +const int KBudgetView::m_iBudgetYearsBack = 3; + +KBudgetView::KBudgetView(QWidget *parent, const char *name ) : + KBudgetViewDecl(parent,name), + m_needReload(false), + m_inSelection(false) +{ + m_accountTree->setSorting(-1); + m_budgetList->setSorting(0); + + KIconLoader* il = KGlobal::iconLoader(); + KGuiItem newButtenItem( QString(""), + QIconSet(il->loadIcon("file_new", KIcon::Small, KIcon::SizeSmall)), + i18n("Creates a new budget"), + i18n("Use this to create a new empty budget.")); + m_newButton->setGuiItem(newButtenItem); + QToolTip::add(m_newButton, newButtenItem.toolTip()); + + KGuiItem renameButtenItem( QString(""), + QIconSet(il->loadIcon("editpaste", KIcon::Small, KIcon::SizeSmall)), + i18n("Rename the current selected budget"), + i18n("Use this to start renaming the selected budget.")); + m_renameButton->setGuiItem(renameButtenItem); + QToolTip::add(m_renameButton, renameButtenItem.toolTip()); + + KGuiItem deleteButtenItem( QString(""), + QIconSet(il->loadIcon("editdelete", KIcon::Small, KIcon::SizeSmall)), + i18n("Delete the current selected budget"), + i18n("Use this to delete the selected budget.")); + m_deleteButton->setGuiItem(deleteButtenItem); + QToolTip::add(m_deleteButton, deleteButtenItem.toolTip()); + + KGuiItem updateButtenItem( QString(""), + QIconSet(il->loadIcon("button_ok", KIcon::Small, KIcon::SizeSmall)), + i18n("Accepts the entered values and stores the budget"), + i18n("Use this to store the modified data.")); + m_updateButton->setGuiItem(updateButtenItem); + QToolTip::add(m_updateButton, updateButtenItem.toolTip()); + + KGuiItem resetButtenItem( QString(""), + QIconSet(il->loadIcon("undo", KIcon::Small, KIcon::SizeSmall)), + i18n("Revert budget to last saved state"), + i18n("Use this to discard the modified data.")); + m_resetButton->setGuiItem(resetButtenItem); + QToolTip::add(m_resetButton, resetButtenItem.toolTip()); + + connect(m_budgetList, SIGNAL(contextMenu(KListView*, QListViewItem* , const QPoint&)), + this, SLOT(slotOpenContextMenu(KListView*, QListViewItem*, const QPoint&))); + connect(m_budgetList, SIGNAL(itemRenamed(QListViewItem*,int,const QString&)), this, SLOT(slotRenameBudget(QListViewItem*,int,const QString&))); + connect(m_budgetList, SIGNAL(selectionChanged()), this, SLOT(slotSelectBudget())); + + connect(m_cbBudgetSubaccounts, SIGNAL(clicked()), this, SLOT(cb_includesSubaccounts_clicked())); + + connect(m_accountTree, SIGNAL(selectionChanged(QListViewItem*)), this, SLOT(slotSelectAccount(QListViewItem*))); + connect(m_accountTree, SIGNAL(valueChanged()), this, SLOT(slotRefreshHideUnusedButton())); + + // connect the buttons to the actions. Make sure the enabled state + // of the actions is reflected by the buttons + connect(kmymoney2->action("budget_new"), SIGNAL(enabled(bool)), m_newButton, SLOT(setEnabled(bool))); + connect(m_renameButton, SIGNAL(clicked()), kmymoney2->action("budget_rename"), SLOT(activate())); + connect(kmymoney2->action("budget_rename"), SIGNAL(enabled(bool)), m_renameButton, SLOT(setEnabled(bool))); + connect(m_deleteButton, SIGNAL(clicked()), kmymoney2->action("budget_delete"), SLOT(activate())); + connect(kmymoney2->action("budget_delete"), SIGNAL(enabled(bool)), m_deleteButton, SLOT(setEnabled(bool))); + + connect(m_budgetValue, SIGNAL(valuesChanged()), this, SLOT(slotBudgetedAmountChanged())); + + connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNewBudget())); + connect(m_updateButton, SIGNAL(pressed()), this, SLOT(slotUpdateBudget())); + connect(m_resetButton, SIGNAL(pressed()), this, SLOT(slotResetBudget())); + + connect(m_hideUnusedButton, SIGNAL(toggled(bool)), this, SLOT(slotHideUnused(bool))); + + // setup initial state + m_newButton->setEnabled(kmymoney2->action("budget_new")->isEnabled()); + m_renameButton->setEnabled(kmymoney2->action("budget_rename")->isEnabled()); + m_deleteButton->setEnabled(kmymoney2->action("budget_delete")->isEnabled()); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotRefreshView())); +} + +KBudgetView::~KBudgetView() +{ +} + +void KBudgetView::show() +{ + QTimer::singleShot(50, this, SLOT(slotRearrange())); + QWidget::show(); + if(m_needReload) { + slotRefreshView(); + } +} + +void KBudgetView::polish() +{ + KBudgetViewDecl::polish(); + m_accountTree->restoreLayout("Budget Account View Settings"); +} + +void KBudgetView::slotRearrange(void) +{ + resizeEvent(0); +} + +void KBudgetView::resizeEvent(QResizeEvent* ev) +{ + // resize the register + KBudgetViewDecl::resizeEvent(ev); +} + +void KBudgetView::slotReloadView(void) +{ + ::timetrace("Start KBudgetView::slotReloadView"); + slotRearrange(); + ::timetrace("Done KBudgetView::slotReloadView"); +} + +void KBudgetView::loadBudgets(void) +{ + QString id; + + ::timetrace("Start KBudgetView::loadBudgets"); + + // remember which item is currently selected + id = m_budget.id(); + + // remember the upper left corner of the viewport + QPoint startPoint = m_budgetList->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_budgetList->setUpdatesEnabled(false); + + // clear the budget list + m_budgetList->clear(); + m_budgetValue->clear(); + + // add the correct years to the drop down list + QDate date = QDate::currentDate(Qt::LocalTime); + int iStartYear = date.year() - m_iBudgetYearsBack; + + m_yearList.clear(); + for (int i=0; i<m_iBudgetYearsAhead + m_iBudgetYearsBack; i++) + m_yearList += QString::number(iStartYear+i); + + KBudgetListItem* currentItem = 0; + + QValueList<MyMoneyBudget> list = MyMoneyFile::instance()->budgetList(); + QValueList<MyMoneyBudget>::ConstIterator it; + for (it = list.begin(); it != list.end(); ++it) + { + KBudgetListItem* item = new KBudgetListItem(m_budgetList, *it); + + // create a list of unique years + if (m_yearList.findIndex(QString::number((*it).budgetStart().year())) == -1) + m_yearList += QString::number((*it).budgetStart().year()); + + if(item->budget().id() == id) { + m_budget = (*it); + currentItem = item; + item->setSelected(true); + } + } + m_yearList.sort(); + + if (currentItem) { + m_budgetList->setCurrentItem(currentItem); + } + + // reposition viewport + m_budgetList->setContentsPos(startPoint.x(), startPoint.y()); + + // turn updates back on + m_budgetList->setUpdatesEnabled(true); + m_budgetList->repaintContents(); + + // reset the status of the buttons + m_updateButton->setEnabled(false); + m_resetButton->setEnabled(false); + + // make sure the world around us knows what we have selected + slotSelectBudget(); + + ::timetrace("End KBudgetView::loadBudgets"); +} + +void KBudgetView::ensureBudgetVisible(const QString& id) +{ + for (QListViewItem * item = m_budgetList->firstChild(); item; item = item->itemBelow()) { + KBudgetListItem* p = dynamic_cast<KBudgetListItem*>(item); + if(p && p->budget().id() == id) { + if(p->itemAbove()) + m_budgetList->ensureItemVisible(p->itemAbove()); + if(p->itemBelow()) + m_budgetList->ensureItemVisible(p->itemBelow()); + + m_budgetList->setCurrentItem(p); // active item and deselect all others + m_budgetList->setSelected(p, true); // and select it + m_budgetList->ensureItemVisible(p); + break; + } + } +} + +void KBudgetView::slotRefreshView(void) +{ + if(isVisible()) { + if(m_inSelection) + QTimer::singleShot(0, this, SLOT(slotRefreshView())); + else { + loadBudgets(); + m_needReload = false; + } + } else { + m_needReload = true; + } +} + +void KBudgetView::loadAccounts(void) +{ + QMap<QString, bool> isOpen; + + ::timetrace("start load budget account view"); + + // if no budgets are selected, don't load the accounts + // and clear out the previously shown list + if (m_budget.id().isEmpty()) { + m_accountTree->clear(); + m_budgetValue->clear(); + m_updateButton->setEnabled(false); + m_resetButton->setEnabled(false); + ::timetrace("done load budgets view"); + return; + } + + // remember the id of the current selected item + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + QString selectedItemId = (item) ? item->id() : QString(); + + // keep a map of all 'expanded' accounts + QListViewItemIterator it_lvi(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeBaseItem*>(it_lvi.current()); + if(item && item->isOpen()) { + isOpen[item->id()] = true; + } + ++it_lvi; + } + + // remember the upper left corner of the viewport + QPoint startPoint = m_accountTree->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_accountTree->setUpdatesEnabled(false); + + // clear the current contents and recreate it + m_accountTree->clear(); + m_transactionCountMap.clear(); + + // make sure, the pointers are not pointing to some deleted object + m_incomeItem = m_expenseItem = 0; + + MyMoneyFile* file = MyMoneyFile::instance(); + + m_transactionCountMap = file->transactionCountMap(); + + m_accountTree->setBaseCurrency(file->baseCurrency()); + + bool haveUnusedBudgets = false; + + // create the items + try { + const MyMoneySecurity& security = file->baseCurrency(); + m_accountTree->setBaseCurrency(security); + + const MyMoneyAccount& income = file->income(); + QStringList incSubAcctList = income.accountList(); + m_incomeItem = new KMyMoneyAccountTreeBudgetItem(m_accountTree, income, m_budget, security, i18n("Income")); + haveUnusedBudgets |= loadSubAccounts(m_incomeItem, incSubAcctList, m_budget); + m_incomeItem->setSelectable(false); + + const MyMoneyAccount& expense = file->expense(); + QStringList expSubAcctList = expense.accountList(); + m_expenseItem = new KMyMoneyAccountTreeBudgetItem(m_accountTree, expense, m_budget, security, i18n("Expense")); + haveUnusedBudgets |= loadSubAccounts(m_expenseItem, expSubAcctList, m_budget); + m_expenseItem->setSelectable(false); + + } catch(MyMoneyException *e) { + kdDebug(2) << "Problem in budgetview: " << e->what(); + delete e; + } + + // scan through the list of accounts and re-expand those that were + // expanded and re-select the one that was probably selected before + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeBaseItem*>(it_lvi.current()); + if(item) { + if(item->id() == selectedItemId) + m_accountTree->setSelected(item, true); + if(isOpen.find(item->id()) != isOpen.end()) + item->setOpen(true); + } + ++it_lvi; + } + + // reposition viewport + m_accountTree->setContentsPos(startPoint.x(), startPoint.y()); + + // turn updates back on + m_accountTree->setUpdatesEnabled(true); + m_accountTree->repaintContents(); + + m_updateButton->setEnabled(!(selectedBudget() == m_budget)); + m_resetButton->setEnabled(!(selectedBudget() == m_budget)); + + ::timetrace("done load budgets view"); +} + + +bool KBudgetView::loadSubAccounts(KMyMoneyAccountTreeBudgetItem* parent, QStringList& accountList, const MyMoneyBudget& budget) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + bool unused = false; + + //sort the subaccount list + //FIXME this is just a hack to order the accounts + if ( !accountList.isEmpty() ) { + QMap<QString, MyMoneyAccount> accountMap; + QValueList<MyMoneyAccount> alist; + file->accountList ( alist, accountList ); + accountList.clear(); + QValueList<MyMoneyAccount>::const_iterator it_ac; + for ( it_ac = alist.begin(); it_ac != alist.end(); ++it_ac ) { + accountMap[(*it_ac).name()] = *it_ac; + } + QMap<QString, MyMoneyAccount>::const_iterator it_am; + for ( it_am = accountMap.begin(); it_am != accountMap.end(); ++it_am ) { + accountList.prepend((*it_am).id()); //use prepend instead of append otherwise account show up in ascending order + } + } + + QStringList::const_iterator it_a; + for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { + const MyMoneyAccount& acc = file->account(*it_a); + QValueList<MyMoneyPrice> prices; + MyMoneySecurity security = file->baseCurrency(); + try { + if(acc.isInvest()) { + security = file->security(acc.currencyId()); + prices += file->price(acc.currencyId(), security.tradingCurrency()); + if(security.tradingCurrency() != file->baseCurrency().id()) { + MyMoneySecurity sec = file->security(security.tradingCurrency()); + prices += file->price(sec.id(), file->baseCurrency().id()); + } + } else if(acc.currencyId() != file->baseCurrency().id()) { + if(acc.currencyId() != file->baseCurrency().id()) { + security = file->security(acc.currencyId()); + prices += file->price(acc.currencyId(), file->baseCurrency().id()); + } + } + + } catch(MyMoneyException *e) { + kdDebug(2) << __PRETTY_FUNCTION__ << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e->what(); + delete e; + } + + QStringList subAcctList = acc.accountList(); + KMyMoneyAccountTreeBudgetItem *item = new KMyMoneyAccountTreeBudgetItem(parent, acc, budget, prices, security); + unused |= loadSubAccounts(item, subAcctList, budget); + + // no child accounts and no value assigned to this account + bool thisUnused = (!item->firstChild()) && (!budget.contains(acc.id())); + + // In case of a budget which is unused and we are requested to suppress + // the display of those, + if(acc.accountGroup() == MyMoneyAccount::Income + || acc.accountGroup() == MyMoneyAccount::Expense) { + if(m_hideUnusedButton->isEnabled() && m_hideUnusedButton->isChecked() && thisUnused) { + unused = true; + delete item; + } + } + } + return unused; +} + +void KBudgetView::askSave(void) +{ + // check if the content of a currently selected budget was modified + // and ask to store the data + if (m_updateButton->isEnabled()) { + if (KMessageBox::questionYesNo(this, QString("<qt>%1</qt>").arg( + i18n("Do you want to save the changes for <b>%1</b>").arg(m_budget.name())), + i18n("Save changes")) == KMessageBox::Yes) { + m_inSelection = true; + slotUpdateBudget(); + m_inSelection = false; + } + } +} + +void KBudgetView::slotRefreshHideUnusedButton(void) +{ + m_hideUnusedButton->setDisabled(m_budget.getaccounts().isEmpty()); +} + +void KBudgetView::slotSelectBudget(void) +{ + askSave(); + KBudgetListItem* item; + if (m_budget.id().isEmpty()) { + item = dynamic_cast<KBudgetListItem*>(m_budgetList->firstChild()); + if(item) { + m_budgetList->blockSignals(true); + m_budgetList->setSelected(item, true); // WRTODO das auch beim NewBudget machen + m_budgetList->blockSignals(false); + } + } + + m_accountTree->setEnabled(false); + m_assignmentBox->setEnabled(false); + m_budget = MyMoneyBudget(); + + QListViewItemIterator it_l(m_budgetList, QListViewItemIterator::Selected); + item = dynamic_cast<KBudgetListItem*>(it_l.current()); + if(item) { + m_budget = item->budget(); + m_accountTree->setEnabled(true); + } + + slotRefreshHideUnusedButton(); + loadAccounts(); + + QValueList<MyMoneyBudget> budgetList; + if(!m_budget.id().isEmpty()) + budgetList << m_budget; + emit selectObjects(budgetList); +} + +void KBudgetView::slotHideUnused(bool toggled) +{ + // make sure we show all items for an empty budget + bool prevState=!toggled; + slotRefreshHideUnusedButton(); + if (prevState!=m_hideUnusedButton->isChecked()) + loadAccounts(); +} + +const MyMoneyBudget& KBudgetView::selectedBudget(void) const +{ + static MyMoneyBudget nullBudget; + + QListViewItemIterator it_l(m_budgetList, QListViewItemIterator::Selected); + KBudgetListItem* item = dynamic_cast<KBudgetListItem*>(it_l.current()); + if(item) { + return item->budget(); + } + + return nullBudget; +} + +KMyMoneyAccountTreeBudgetItem* KBudgetView::selectedAccount(void) const +{ + QListViewItemIterator it_l(m_accountTree, QListViewItemIterator::Selected); + KMyMoneyAccountTreeBudgetItem* item = dynamic_cast<KMyMoneyAccountTreeBudgetItem*>(it_l.current()); + return item; +} + +void KBudgetView::slotOpenContextMenu(KListView* lv, QListViewItem* i, const QPoint& p) +{ + Q_UNUSED(lv); + Q_UNUSED(p); + + m_accountTree->setUpdatesEnabled(false); + + KBudgetListItem* item = dynamic_cast<KBudgetListItem*>(i); + if (item) + emit openContextMenu(item->budget()); + else + emit openContextMenu(MyMoneyBudget()); + + m_accountTree->setUpdatesEnabled(true); +} + +void KBudgetView::slotStartRename(void) +{ + QListViewItemIterator it_l(m_budgetList, QListViewItemIterator::Selected); + QListViewItem* it_v; + if((it_v = it_l.current()) != 0) { + it_v->startRename(0); + } +} + +// This variant is only called when a single budget is selected and renamed. +void KBudgetView::slotRenameBudget(QListViewItem* p , int /*col*/, const QString& txt) +{ + KBudgetListItem *pBudget = dynamic_cast<KBudgetListItem*> (p); + if (!pBudget) + return; + + //kdDebug() << "[KPayeesView::slotRenamePayee]" << endl; + // create a copy of the new name without appended whitespaces + QString new_name = txt.stripWhiteSpace(); + if (pBudget->budget().name() != new_name) { + MyMoneyFileTransaction ft; + try { + // check if we already have a budget with the new name + try { + // this function call will throw an exception, if the budget + // hasn't been found. + MyMoneyFile::instance()->budgetByName(new_name); + // the name already exists, ask the user whether he's sure to keep the name + if (KMessageBox::questionYesNo(this, + i18n("A budget with the name '%1' already exists. It is not advisable to have " + "multiple budgets with the same identification name. Are you sure you would like " + "to rename the budget?").arg(new_name)) != KMessageBox::Yes) + { + p->setText(0,pBudget->budget().name()); + return; + } + } catch(MyMoneyException *e) { + // all ok, the name is unique + delete e; + } + + MyMoneyBudget b = pBudget->budget(); + b.setName(new_name); + // don't use pBudget beyond this point as it will change due to call to modifyBudget + pBudget = 0; + + MyMoneyFile::instance()->modifyBudget(b); + + // the above call to modifyBudget will reload the view so + // all references and pointers to the view have to be + // re-established. You cannot use pBudget beyond this point!!! + ft.commit(); + + } catch(MyMoneyException *e) { + KMessageBox::detailedSorry(0, i18n("Unable to modify budget"), + (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line())); + delete e; + } + } + else { + pBudget->setText(0, new_name); + } +} + +void KBudgetView::slotSelectAccount(QListViewItem* item) +{ + if(item->listView() == m_accountTree) { + m_assignmentBox->setEnabled(false); + KMyMoneyAccountTreeBudgetItem *account = selectedAccount(); + m_assignmentBox->setEnabled(account != 0); + + if(account) { + if (m_budget.id().isEmpty() ) + return; + + QString id = account->id(); + m_leAccounts->setText(MyMoneyFile::instance()->accountToCategory(id)); + m_cbBudgetSubaccounts->setChecked(m_budget.account(id).budgetSubaccounts()); + m_accountTotal->setValue(m_budget.account(id).totalBalance()); + + MyMoneyBudget::AccountGroup budgetAccount = m_budget.account( id ); + if ( id != budgetAccount.id() ) { + budgetAccount.setBudgetLevel(MyMoneyBudget::AccountGroup::eMonthly); + } + m_budgetValue->setBudgetValues(m_budget, budgetAccount); + } + } +} + +void KBudgetView::slotBudgetedAmountChanged(void) +{ + if (m_budget.id().isEmpty()) + return; + + KMyMoneyAccountTreeBudgetItem *account; + if ((account=selectedAccount()) == NULL) + return; + + MyMoneyBudget::AccountGroup accountGroup = m_budget.account(account->id()); + accountGroup.setId( account->id() ); + m_budgetValue->budgetValues(m_budget, accountGroup); + m_budget.setAccount(accountGroup, account->id()); + + account->setBudget(m_budget); + m_accountTotal->setValue(accountGroup.totalBalance()); + + m_updateButton->setEnabled(!(selectedBudget() == m_budget)); + m_resetButton->setEnabled(!(selectedBudget() == m_budget)); +} + +void KBudgetView::AccountEnter() +{ + if (m_budget.id().isEmpty()) + return; + + //(ace) kCategoryWidget not currently defined + KMyMoneyAccountTreeBudgetItem *item = NULL; //dynamic_cast<KMyMoneyAccountTreeBudgetItem*> (m_accountTree->findItem(m_leAccounts->selectedAccountId())); + if (item) + { + m_accountTree->setCurrentItem(item); + m_accountTree->setOpen(item, true); + } +} + + +void KBudgetView::cb_includesSubaccounts_clicked() +{ + if (m_budget.id().isEmpty()) + return; + + if(selectedAccount() != 0) { + QString accountID = selectedAccount()->id(); + // now, we get a reference to the accountgroup, to mofify its atribute, + // and then put the resulting account group instead of the original + + MyMoneyBudget::AccountGroup auxAccount = m_budget.account(accountID); + auxAccount.setBudgetSubaccounts( m_cbBudgetSubaccounts->isChecked()); + m_budget.setAccount( auxAccount, accountID); + + loadAccounts(); + } +} + +void KBudgetView::slotNewBudget(void) +{ + askSave(); + kmymoney2->action("budget_new")->activate(); +} + +void KBudgetView::slotResetBudget(void) +{ + try { + m_budget = MyMoneyFile::instance()->budget(m_budget.id()); + loadAccounts(); + } catch(MyMoneyException *e) { + KMessageBox::detailedSorry(0, i18n("Unable to reset budget"), + (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line())); + delete e; + } +} + +void KBudgetView::slotUpdateBudget(void) +{ + MyMoneyFileTransaction ft; + try { + MyMoneyFile::instance()->modifyBudget(m_budget); + ft.commit(); + slotRefreshHideUnusedButton(); + } catch(MyMoneyException *e) { + KMessageBox::detailedSorry(0, i18n("Unable to modify budget"), + (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line())); + delete e; + } +} + +void KBudgetView::languageChange(void) +{ + KBudgetViewDecl::languageChange(); + + m_newButton->setText(QString()); + m_renameButton->setText(QString()); + m_deleteButton->setText(QString()); + m_updateButton->setText(QString()); + m_resetButton->setText(QString()); +} + +#include "kbudgetview.moc" diff --git a/kmymoney2/views/kbudgetview.h b/kmymoney2/views/kbudgetview.h new file mode 100644 index 0000000..f857a15 --- /dev/null +++ b/kmymoney2/views/kbudgetview.h @@ -0,0 +1,221 @@ +/*************************************************************************** + kbudgetview.h + ------------- + begin : Thu Jan 24 2002 + copyright : (C) 2006 by Darren Gould + email : darren_gould@gmx.de +***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KBUDGETVIEW_H +#define KBUDGETVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qwidget.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klistview.h> +#include <kpopupmenu.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kbudgetviewdecl.h" +#include "../mymoney/mymoneybudget.h" +#include "../mymoney/mymoneysecurity.h" +class KMyMoneyAccountTreeBudgetItem; + +/** + * @author Darren Gould + * @author Thomas Baumgart + * + * + * This class represents an item in the budgets list view. + */ +class KBudgetListItem : public KListViewItem +{ +public: + /** + * Constructor to be used to construct a budget entry object. + * + * @param parent pointer to the KListView object this entry should be + * added to. + * @param budget const reference to MyMoneyBudget for which + * the KListView entry is constructed + */ + KBudgetListItem(KListView *parent, const MyMoneyBudget& budget); + ~KBudgetListItem(); + + /** + * This method is re-implemented from QListViewItem::paintCell(). + * Besides the standard implementation, the QPainter is set + * according to the applications settings. + */ + void paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align); + + const MyMoneyBudget& budget(void) { return m_budget; }; + void setBudget(const MyMoneyBudget& budget) { m_budget = budget; } + +private: + MyMoneyBudget m_budget; +}; + +/** + * @author Darren Gould + * @author Thomas Baumgart + */ +class KBudgetView : public KBudgetViewDecl +{ + Q_OBJECT +public: + KBudgetView(QWidget *parent=0, const char *name=0); + ~KBudgetView(); + void show(); + + /** + * Override the base class behaviour to restore the layout. Do not + * do this in show() because show() itself may change the layout + * in undesired ways. + */ + void polish(void); + + /** + * This method is used to suppress updates for specific times + * (e.g. during creation of a new MyMoneyFile object when the + * default accounts are loaded). The behaviour of update() is + * controlled with the parameter. + * + * @param suspend Suspend updates or not. Possible values are + * + * @li true updates are suspended + * @li false updates will be performed immediately + * + * When a true/false transition of the parameter between + * calls to this method is detected, + * refresh() will be invoked once automatically. + */ + void suspendUpdate(const bool suspend); + +public slots: + void slotReloadView(void); + void slotRefreshView(void); + void slotSelectBudget(void); + void slotHideUnused(bool); + void slotRefreshHideUnusedButton(); + void slotStartRename(void); + + /** + *This is to update the information about the checkbox "budget amount integrates subaccounts" into the file, when the user clicks the check box + */ + void cb_includesSubaccounts_clicked(); + + +protected: + void resizeEvent(QResizeEvent*); + void loadAccounts(void); + bool loadSubAccounts(KMyMoneyAccountTreeBudgetItem* parent, QStringList& accountList, const MyMoneyBudget& budget); + + /** + * This method loads all available budgets into the budget list widget. If a budget is + * currently selected it remains selected if it is still present. + */ + void loadBudgets(void); + void ensureBudgetVisible(const QString& id); + const MyMoneyBudget& selectedBudget(void) const; + KMyMoneyAccountTreeBudgetItem* selectedAccount(void) const; + void setTimeSpan(KMyMoneyAccountTreeBudgetItem *account, MyMoneyBudget::AccountGroup& accountGroup, int iTimeSpan); + void askSave(void); + +protected slots: + + /** + * This slot is called when the name of a budget is changed inside + * the budget list view and only a single budget is selected. + * + * @param p The listviewitem containing the budget name + * @param col The column where the name is located + * @param txt The text of the new name + */ + void slotRenameBudget(QListViewItem *p, int col, const QString& txt); + + /** + * This slot is called when the amount of a budget is changed. It + * updates the budget and stores it in the engine + */ + void slotBudgetedAmountChanged(void); + + /** + */ + void slotSelectAccount(QListViewItem*); + + void AccountEnter(); + + void slotUpdateBudget(void); + + void slotResetBudget(void); + + void slotNewBudget(void); + + void languageChange(void); + +private slots: + void slotRearrange(void); + + /** + * This slot receives the signal from the listview control that an item was right-clicked, + * If @p item points to a real budget item, emits openContextMenu(). + * + * @param lv pointer to the listview + * @param i the item on which the cursor resides + * @param p position of the pointing device + */ + void slotOpenContextMenu(KListView* lv, QListViewItem* i, const QPoint& p); + +signals: + /** + * This signal serves as proxy for KMyMoneyBudgetList::selectObject() + */ + void openContextMenu(const MyMoneyObject& obj); + void selectObjects(const QValueList<MyMoneyBudget>& budget); + +private: + typedef enum { + eNone=-1, + eYearly=0, + eMonthly=1, + eMonthByMonth=2 + } eTimePeriodColumn; + + MyMoneyBudget m_budget; + + QMap<QString, unsigned long> m_transactionCountMap; + QStringList m_yearList; + + KMyMoneyAccountTreeBudgetItem* m_incomeItem; + KMyMoneyAccountTreeBudgetItem* m_expenseItem; + + /// set if a view needs to be reloaded during show() + bool m_needReload; + + // set if we are in the selection of a different budget + bool m_inSelection; + + void adaptHideUnusedButton(void); + + static const int m_iBudgetYearsAhead; + static const int m_iBudgetYearsBack; +}; + +#endif diff --git a/kmymoney2/views/kbudgetviewdecl.ui b/kmymoney2/views/kbudgetviewdecl.ui new file mode 100644 index 0000000..f3fb5be --- /dev/null +++ b/kmymoney2/views/kbudgetviewdecl.ui @@ -0,0 +1,348 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KBudgetViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KBudgetViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>957</width> + <height>610</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>TabFocus</enum> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Your budgets</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_newButton</cstring> + </property> + <property name="text"> + <string>New</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_renameButton</cstring> + </property> + <property name="text"> + <string>Ren</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_deleteButton</cstring> + </property> + <property name="text"> + <string>Del</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_resetButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Res</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_updateButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Upd</string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="KListView"> + <column> + <property name="text"> + <string>Name</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Year</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>false</bool> + </property> + </column> + <property name="name"> + <cstring>m_budgetList</cstring> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="showSortIndicator"> + <bool>true</bool> + </property> + <property name="fullWidth"> + <bool>false</bool> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KMyMoneyAccountTreeBudget"> + <property name="name"> + <cstring>m_accountTree</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>5</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_hideUnusedButton</cstring> + </property> + <property name="text"> + <string>Hide unused budget categories</string> + </property> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>m_assignmentBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>5</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Assignments</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Account</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_leAccounts</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>NoFocus</enum> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Total</string> + </property> + </widget> + <widget class="kMyMoneyEdit"> + <property name="name"> + <cstring>m_accountTotal</cstring> + </property> + <property name="focusPolicy"> + <enum>NoFocus</enum> + </property> + <property name="calculatorButtonVisibility" stdset="0"> + <bool>false</bool> + </property> + <property name="resetButtonVisibility" stdset="0"> + <bool>false</bool> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_cbBudgetSubaccounts</cstring> + </property> + <property name="text"> + <string>Include subaccounts</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KBudgetValues"> + <property name="name"> + <cstring>m_budgetValue</cstring> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + </vbox> + </widget> + </vbox> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<customwidgets> + <customwidget> + <class>KMyMoneyAccountTreeBudget</class> + <header location="local">../widgets/kmymoneyaccounttreebudget.h</header> + <sizehint> + <width>-1</width> + <height>-1</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>5</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="PNG" length="870">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000032d49444154388db59531681c4714863f892dde8004b320c32e3870020b74458a6b0f5458e581c016a809a8496b42208454098604d2c4904a6954a410511ae752085b858c1208dc35221244b00119f60ac12ee860062c980719708a95a248f129459cd7cceeccf0edb76fff61a706830197d5ed765ff3166a30184c4d5d829fed3c7bdd5e6ce3bda71a5738ef301802e1d6d179477152303c1842bc822797a64fbf7b4a9a43be00ada817cb0e12011c2611205ccd73755f9c087c6b19bef0d7c100f5b8267d07caf10fe8ab9210156320fc01be16aa5a11043307f30b20a21041019985f48ef2f7fa0becc68e80475fd584e831b396f210f67795c3831a4940a3228925bb27f4d652ba4b01a199b73342f3981be0ca57745042ac30c632d853b6373d44b056c8ef0922508d94d14be59b2f4aeaf58cd5751069e06f3436890114332b9487d0bf80f61e64dc5f813c3790045453f67703fd4d4f7f6b4496b5597e689044af194f5f5e841800210478bee3d1a8f41e64acbe0f69ae6852e1cf0ccf7f74f4d652defbc042226c6f55e8f89f91bb6e9c387c9d521c9558db988a3416fe3c67e32b4779ec7167f0e8939ce19ea7fc5d298a80c875f03563930855ed2081bc05e91d5014ef53363eaf288e3d6285ee520a338e76c7a251a94e41e30470d3631004a262672e3eca59cec6978ef2b889979d11f2bb904af3be92081a416e28dfe831983920b1142345d5b0ff2234a6334276d7321ad53c795c511ca654a5a251996f19b83d158ef602b45a423d52f67703abeb29ee4ce9de4fc93378f218462f6b3efdb042cf3d59666977a0aa6fe9310888d25b13342afd4dcffeaee3d147399da540ab13f8f8b39c2cb3f8710d11ba2b96f9c57fcd7180287497a03ecde86f8dd8fe1a867b9ef6bb1612a84a871f6bd35b94e217a53832589970f2dcd85d9c7d4580d57521cbdaf4bfaf288e95e268d4ec8e60e72ccb0f2dbffea454e71e8d29f57882717152509482a48d8924b0bc12e82ee51445a03a6da079cbd0eec0fc22142b06620e89a3fc8d3783870743d814d2bc8994aa6ff286472e764902e5a96f72bbd3b4c37b280e95aa9e604c84e1cf978b37c74935797d7ae2ca7fac6968fe51ff0bf86dc30783c1d49f0baa9bb819e612310000000049454e44ae426082</data> + </image> +</images> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kcategoriesview.cpp b/kmymoney2/views/kcategoriesview.cpp new file mode 100644 index 0000000..e01d62b --- /dev/null +++ b/kmymoney2/views/kcategoriesview.cpp @@ -0,0 +1,313 @@ +/*************************************************************************** + kcategoriesview.cpp - description + ------------------- + begin : Sun Jan 20 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qlabel.h> +#include <qlayout.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klocale.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kguiitem.h> +#include <kpushbutton.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/kmymoneyaccounttree.h> +#include "kcategoriesview.h" +#include "../widgets/klistviewsearchline.h" +#include "../kmymoneyglobalsettings.h" +#include "../kmymoney2.h" + + +KCategoriesView::KCategoriesView(QWidget *parent, const char *name ) : + KCategoriesViewDecl(parent, name), + m_incomeItem(0), + m_expenseItem(0), + m_needReload(false) +{ + // create the searchline widget + // and insert it into the existing layout + m_searchWidget = new KListViewSearchLineWidget(m_accountTree, m_accountTree->parentWidget()); + QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(m_accountTree->parentWidget()->layout()); + if(layout) { + layout->insertWidget(0, m_searchWidget); + } + + // setup icons for collapse and expand button + KIconLoader *ic = KGlobal::iconLoader(); + KGuiItem collapseGuiItem("", + QIconSet(ic->loadIcon("viewmag-", KIcon::Small, KIcon::SizeSmall)), + QString(), + QString()); + KGuiItem expandGuiItem("", + QIconSet(ic->loadIcon("viewmag+", KIcon::Small, KIcon::SizeSmall)), + QString(), + QString()); + m_collapseButton->setGuiItem(collapseGuiItem); + m_expandButton->setGuiItem(expandGuiItem); + + m_accountTree->setSectionHeader(i18n("Category")); + + connect(m_accountTree, SIGNAL(selectObject(const MyMoneyObject&)), this, SIGNAL(selectObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(openContextMenu(const MyMoneyObject&)), this, SIGNAL(openContextMenu(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(valueChanged(void)), this, SLOT(slotUpdateProfit(void))); + connect(m_accountTree, SIGNAL(openObject(const MyMoneyObject&)), this, SIGNAL(openObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&)), this, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); + connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); + connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); +} + +KCategoriesView::~KCategoriesView() +{ +} + +void KCategoriesView::slotExpandCollapse(void) +{ + if(sender()) { + KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); + } +} + + +void KCategoriesView::show(void) +{ + if(m_needReload) { + loadAccounts(); + m_needReload = false; + } + + // don't forget base class implementation + KCategoriesViewDecl::show(); + + // if we have a selected account, let the application know about it + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + if(item) { + emit selectObject(item->itemObject()); + } +} + +void KCategoriesView::polish(void) +{ + KCategoriesViewDecl::polish(); + m_accountTree->setResizeMode(QListView::LastColumn); + m_accountTree->restoreLayout("Category View Settings"); + +} + +void KCategoriesView::slotLoadAccounts(void) +{ + if(isVisible()) { + loadAccounts(); + } else { + m_needReload = true; + } +} + +void KCategoriesView::loadAccounts(void) +{ + QMap<QString, bool> isOpen; + + ::timetrace("start load categories view"); + // remember the id of the current selected item + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + QString selectedItemId = (item) ? item->id() : QString(); + + // keep a map of all 'expanded' accounts + QListViewItemIterator it_lvi(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item && item->isOpen()) { + isOpen[item->id()] = true; + } + ++it_lvi; + } + + // remember the upper left corner of the viewport + QPoint startPoint = m_accountTree->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_accountTree->setUpdatesEnabled(false); + + // clear the current contents and recreate it + m_accountTree->clear(); + m_securityMap.clear(); + m_transactionCountMap.clear(); + + // make sure, the pointers are not pointing to some deleted object + m_incomeItem = m_expenseItem = 0; + + MyMoneyFile* file = MyMoneyFile::instance(); + + QValueList<MyMoneySecurity> slist = file->currencyList(); + slist += file->securityList(); + QValueList<MyMoneySecurity>::const_iterator it_s; + for(it_s = slist.begin(); it_s != slist.end(); ++it_s) { + m_securityMap[(*it_s).id()] = *it_s; + } + + m_transactionCountMap = file->transactionCountMap(); + + bool haveUnusedCategories = false; + + // create the items + try { + const MyMoneySecurity& security = file->baseCurrency(); + m_accountTree->setBaseCurrency(security); + + const MyMoneyAccount& income = file->income(); + m_incomeItem = new KMyMoneyAccountTreeItem(m_accountTree, income, security, i18n("Income")); + haveUnusedCategories |= loadSubAccounts(m_incomeItem, income.accountList()); + + const MyMoneyAccount& expense = file->expense(); + m_expenseItem = new KMyMoneyAccountTreeItem(m_accountTree, expense, security, i18n("Expense")); + haveUnusedCategories |= loadSubAccounts(m_expenseItem, expense.accountList()); + + } catch(MyMoneyException *e) { + kdDebug(2) << "Problem in categoriesview: " << e->what() << endl; + delete e; + } + + // scan through the list of accounts and re-expand those that were + // expanded and re-select the one that was probably selected before + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item) { + if(item->id() == selectedItemId) + m_accountTree->setSelected(item, true); + if(isOpen.find(item->id()) != isOpen.end()) + item->setOpen(true); + } + ++it_lvi; + } + + // reposition viewport + m_accountTree->setContentsPos(startPoint.x(), startPoint.y()); + + m_searchWidget->searchLine()->updateSearch(QString::null); + + // turn updates back on + m_accountTree->setUpdatesEnabled(true); + m_accountTree->repaintContents(); + + // and in case we need to show things expanded, we'll do so + if(KMyMoneyGlobalSettings::showAccountsExpanded()) + m_accountTree->slotExpandAll(); + + // update the hint if categories are hidden + m_hiddenCategories->setShown(haveUnusedCategories); + + ::timetrace("done load categories view"); +} + +bool KCategoriesView::loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QStringList& accountList) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + bool unused = false; + + QStringList::const_iterator it_a; + for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { + const MyMoneyAccount& acc = file->account(*it_a); + QValueList<MyMoneyPrice> prices; + MyMoneySecurity security = file->baseCurrency(); + try { + if(acc.isInvest()) { + security = m_securityMap[acc.currencyId()]; + prices += file->price(acc.currencyId(), security.tradingCurrency()); + if(security.tradingCurrency() != file->baseCurrency().id()) { + MyMoneySecurity sec = m_securityMap[security.tradingCurrency()]; + prices += file->price(sec.id(), file->baseCurrency().id()); + } + } else if(acc.currencyId() != file->baseCurrency().id()) { + if(acc.currencyId() != file->baseCurrency().id()) { + security = m_securityMap[acc.currencyId()]; + prices += file->price(acc.currencyId(), file->baseCurrency().id()); + } + } + + } catch(MyMoneyException *e) { + kdDebug(2) << __PRETTY_FUNCTION__ << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e->what(); + delete e; + } + + KMyMoneyAccountTreeItem* item = new KMyMoneyAccountTreeItem(parent, acc, prices, security); + unused |= loadSubAccounts(item, acc.accountList()); + + // no child accounts and not transactions in this account means 'unused' + bool thisUnused = (!item->firstChild()) && (m_transactionCountMap[acc.id()] == 0); + + // In case of a category which is unused and we are requested to suppress + // the display of those, + if(acc.accountGroup() == MyMoneyAccount::Income + || acc.accountGroup() == MyMoneyAccount::Expense) { + if(KMyMoneyGlobalSettings::hideUnusedCategory() && thisUnused) { + unused = true; + delete item; + } + } + } + return unused; +} + +void KCategoriesView::slotUpdateProfit(void) +{ + if(!m_incomeItem || !m_expenseItem) + return; + + MyMoneyMoney profit = m_incomeItem->totalValue() - m_expenseItem->totalValue(); + + QString s(i18n("Profit: ")); + if(profit.isNegative()) + s = i18n("Loss: "); + + // FIXME figure out how to deal with the approximate + // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) + // s += "~ "; + + s.replace(QString(" "), QString(" ")); + if(profit.isNegative()) { + s += "<b><font color=\"red\">"; + } + const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); + QString v(profit.abs().formatMoney(sec)); + s += v.replace(QString(" "), QString(" ")); + if(profit.isNegative()) { + s += "</font></b>"; + } + + m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); + m_totalProfitsLabel->setText(s); +} + +#include "kcategoriesview.moc" + diff --git a/kmymoney2/views/kcategoriesview.h b/kmymoney2/views/kcategoriesview.h new file mode 100644 index 0000000..4a6ad3c --- /dev/null +++ b/kmymoney2/views/kcategoriesview.h @@ -0,0 +1,147 @@ +/*************************************************************************** + kcategoriesview.h - description + ------------------- + begin : Sun Jan 20 2002 + copyright : (C) 2000-2002 by Michael Edwardes + (C) 2005 by Thomas Baumgart + 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KCATEGORIESVIEW_H +#define KCATEGORIESVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/kmymoneyaccounttree.h> +#include <kmymoney/mymoneyutils.h> +class KListViewSearchLineWidget; + +#include "kcategoriesviewdecl.h" + +/** + * @brief This class contains the implementation of the categories view. + * @author Michael Edwardes, Thomas Baumgart + * + * While named "categories view", this view actually displays all accounts + * that are children of the global "Income" and "Expense" accounts. Even though + * categories are internally just accounts as well, the distinction between + * categories and accounts in the user interface is done for better + * usability and clarity. + * + * The main functionality in the categories view is actually implemented + * in the KMyMoneyAccountTree. Signals from user actions are connect to + * other signals/slots in KCategoriesView and relayed to KMyMoneyView. + * A typical example is the selectObject() signal that eventually results + * in enabling/disabling the user actions for the categories view. + * + * For the categories view three user actions are important (all created in + * kmymoney2.cpp): category_new, category_edit and category_delete. They are + * accessible from either the main menu or the context menu. + */ +class KCategoriesView : public KCategoriesViewDecl +{ + Q_OBJECT +public: + KCategoriesView(QWidget *parent=0, const char *name=0); + virtual ~KCategoriesView(); + + +public slots: + void slotLoadAccounts(void); + + /** + * Override the base class behaviour to include all updates that + * happened in the meantime. + */ + void show(void); + + /** + * Override the base class behaviour to restore the layout. Do not + * do this in show() because show() itself may change the layout + * in undesired ways. + */ + void polish(void); + +protected: + void loadAccounts(void); + bool loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QStringList& accountList); + +protected slots: + void slotUpdateProfit(void); + void slotExpandCollapse(void); + +private: + /** + * This method returns an icon according to the account type + * passed in the argument @p type. + * + * @param type account type as defined in MyMoneyAccount::accountTypeE + */ + const QPixmap accountImage(const MyMoneyAccount::accountTypeE type) const; + +signals: + /** + * This signal serves as proxy for KMyMoneyAccountTree::selectObject() + */ + void selectObject(const MyMoneyObject&); + + /** + * This signal serves as proxy for + * KMyMoneyAccountTree::openContextMenu(const MyMoneyObject&) + */ + void openContextMenu(const MyMoneyObject& obj); + + /** + * This signal will be emitted when the left mouse button is double + * clicked (actually the KDE executed setting is used) on an account. + */ + void openObject(const MyMoneyObject& obj); + + /** + * This signal is emitted, when the user selected to reparent the + * account @p acc to be a subordinate account of @p parent. + * + * @param acc const reference to account to be reparented + * @param parent const reference to new parent account + */ + void reparent(const MyMoneyAccount& acc, const MyMoneyAccount& parent); + +private: + QMap<QString, MyMoneySecurity> m_securityMap; + QMap<QString, unsigned long> m_transactionCountMap; + + KMyMoneyAccountTreeItem* m_incomeItem; + KMyMoneyAccountTreeItem* m_expenseItem; + + /** + * Search widget for the list + */ + KListViewSearchLineWidget* m_searchWidget; + + /// set if a view needs to be reloaded during show() + bool m_needReload; +}; + +#endif diff --git a/kmymoney2/views/kcategoriesviewdecl.ui b/kmymoney2/views/kcategoriesviewdecl.ui new file mode 100644 index 0000000..858cc54 --- /dev/null +++ b/kmymoney2/views/kcategoriesviewdecl.ui @@ -0,0 +1,153 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KCategoriesViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KCategoriesViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>698</width> + <height>430</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KMyMoneyAccountTree"> + <property name="name"> + <cstring>m_accountTree</cstring> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_collapseButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Collapse all accounts in the list</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_expandButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Expand all accounts in the list</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>515</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>m_hiddenCategories</cstring> + </property> + <property name="text"> + <string>Note: Unused categories are not shown as selected by settings.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>Spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>310</width> + <height>16</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>m_totalProfitsLabel</cstring> + </property> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Total Profits:</string> + </property> + <property name="textFormat"> + <enum>RichText</enum> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignRight</set> + </property> + <property name="hAlign" stdset="0"> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<connections> + <connection> + <sender>m_expandButton</sender> + <signal>clicked()</signal> + <receiver>m_accountTree</receiver> + <slot>slotExpandAll()</slot> + </connection> + <connection> + <sender>m_collapseButton</sender> + <signal>clicked()</signal> + <receiver>m_accountTree</receiver> + <slot>slotCollapseAll()</slot> + </connection> +</connections> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kforecastview.cpp b/kmymoney2/views/kforecastview.cpp new file mode 100644 index 0000000..c7bcb0a --- /dev/null +++ b/kmymoney2/views/kforecastview.cpp @@ -0,0 +1,673 @@ +/*************************************************************************** + kforecastview.cpp + ------------------- + copyright : (C) 2007 by Alvaro Soliverez + email : asoliverez@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qtabwidget.h> +#include <qspinbox.h> +#include <qlabel.h> +#include <qbuttongroup.h> +#include <qtextedit.h> +#include <qlayout.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kdebug.h> +#include <klocale.h> +#include <klistview.h> +#include <kpushbutton.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include "kforecastview.h" +#include "../kmymoneyglobalsettings.h" +#include "../kmymoney2.h" +#include "../kmymoneyutils.h" +#include "../mymoney/mymoneyforecast.h" +#include "../widgets/kmymoneyforecastlistviewitem.h" +#include "../widgets/kmymoneyaccounttreeforecast.h" +#include "../reports/pivottable.h" +#include "../reports/pivotgrid.h" + +KForecastView::KForecastView(QWidget *parent, const char *name) : + KForecastViewDecl(parent,name) +{ + for(int i=0; i < MaxViewTabs; ++i) + m_needReload[i] = false; + + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + m_tab->setCurrentPage(config->readNumEntry("KForecastView_LastType", 0)); + + connect(m_tab, SIGNAL(currentChanged(QWidget*)), this, SLOT(slotTabChanged(QWidget*))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadForecast())); + + connect(m_forecastButton, SIGNAL(clicked()), this, SLOT(slotManualForecast())); + + m_forecastList->setAllColumnsShowFocus(true); + m_summaryList->setAllColumnsShowFocus(true); + //m_adviceList->setAllColumnsShowFocus(true); + m_advancedList->setAllColumnsShowFocus(true); + + m_forecastChart = new KReportChartView(m_tabChart, "forecastChart" ); + m_forecastChart->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + + loadForecastSettings(); + +} + +KForecastView::~KForecastView() +{ +} + +void KForecastView::slotTabChanged(QWidget* _tab) +{ + ForecastViewTab tab = static_cast<ForecastViewTab>(m_tab->indexOf(_tab)); + + // remember this setting for startup + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + config->writeEntry("KForecastView_LastType", tab); + + loadForecast(tab); + +} + +void KForecastView::loadForecast(ForecastViewTab tab) +{ + if(m_needReload[tab]) { + switch(tab) { + case ListView: + loadListView(); + break; + case SummaryView: + loadSummaryView(); + break; + case AdvancedView: + loadAdvancedView(); + break; + case BudgetView: + loadBudgetView(); + break; + case ChartView: + loadChartView(); + break; + default: + break; + } + m_needReload[tab] = false; + } +} + +void KForecastView::show(void) +{ + // don't forget base class implementation + KForecastViewDecl::show(); + slotTabChanged(m_tab->currentPage()); +} + +void KForecastView::slotLoadForecast(void) +{ + m_needReload[SummaryView] = true; + m_needReload[ListView] = true; + m_needReload[AdvancedView] = true; + m_needReload[BudgetView] = true; + m_needReload[ChartView] = true; + + //refresh settings + loadForecastSettings(); + + if(isVisible()) + slotTabChanged(m_tab->currentPage()); +} + +void KForecastView::slotManualForecast(void) +{ + m_needReload[SummaryView] = true; + m_needReload[ListView] = true; + m_needReload[AdvancedView] = true; + m_needReload[BudgetView] = true; + m_needReload[ChartView] = true; + + if(isVisible()) + slotTabChanged(m_tab->currentPage()); +} + +void KForecastView::loadForecastSettings(void) +{ + //fill the settings controls + m_forecastDays->setValue(KMyMoneyGlobalSettings::forecastDays()); + m_accountsCycle->setValue(KMyMoneyGlobalSettings::forecastAccountCycle()); + m_beginDay->setValue(KMyMoneyGlobalSettings::beginForecastDay()); + m_forecastCycles->setValue(KMyMoneyGlobalSettings::forecastCycles()); + m_historyMethod->setButton(KMyMoneyGlobalSettings::historyMethod()); + switch(KMyMoneyGlobalSettings::forecastMethod()) { + case 0: + m_forecastMethod->setText(i18n("Scheduled")); + m_forecastCycles->setDisabled(true); + m_historyMethod->setDisabled(true); + break; + case 1: + m_forecastMethod->setText(i18n("History")); + m_forecastCycles->setEnabled(true); + m_historyMethod->setEnabled(true); + break; + default: + m_forecastMethod->setText(i18n("Unknown")); + break; + } +} + +void KForecastView::loadListView(void) +{ + MyMoneyForecast forecast; + MyMoneyFile* file = MyMoneyFile::instance(); + + m_forecastList->setBaseCurrency(file->baseCurrency()); + + //get the settings from current page + forecast.setForecastDays(m_forecastDays->value()); + forecast.setAccountsCycle(m_accountsCycle->value()); + forecast.setBeginForecastDay(m_beginDay->value()); + forecast.setForecastCycles(m_forecastCycles->value()); + forecast.setHistoryMethod(m_historyMethod->selectedId()); + forecast.doForecast(); + + //clear the list, including columns + m_forecastList->clearColumns(); + + //add columns + m_forecastList->showAccount(); + m_forecastList->showDetailed(forecast); + + //add default rows + addTotalRow(m_forecastList, forecast); + addAssetLiabilityRows(forecast); + + //load asset and liability forecast accounts + loadAccounts(forecast, file->asset(), m_assetItem, KMyMoneyAccountTreeForecastItem::eDetailed); + loadAccounts(forecast, file->liability(), m_liabilityItem, KMyMoneyAccountTreeForecastItem::eDetailed); + + m_forecastList->show(); +} + +void KForecastView::loadSummaryView(void) +{ + MyMoneyForecast forecast; + QValueList<MyMoneyAccount> accList; + int dropMinimum; + int dropZero; + + MyMoneyFile* file = MyMoneyFile::instance(); + + m_summaryList->setBaseCurrency(file->baseCurrency()); + + //get the settings from current page + forecast.setForecastDays(m_forecastDays->value()); + forecast.setAccountsCycle(m_accountsCycle->value()); + forecast.setBeginForecastDay(m_beginDay->value()); + forecast.setForecastCycles(m_forecastCycles->value()); + forecast.setHistoryMethod(m_historyMethod->selectedId()); + forecast.doForecast(); + + //clear the list, including columns + m_summaryList->clearColumns(); + + //add columns + m_summaryList->showAccount(); + m_summaryList->showSummary(forecast); + + //add default rows + addTotalRow(m_summaryList, forecast); + addAssetLiabilityRows(forecast); + + loadAccounts(forecast, file->asset(), m_assetItem, KMyMoneyAccountTreeForecastItem::eSummary); + loadAccounts(forecast, file->liability(), m_liabilityItem, KMyMoneyAccountTreeForecastItem::eSummary); + + //Add comments to the advice list + //Get all accounts of the right type to calculate forecast + m_nameIdx.clear(); + accList = forecast.accountList(); + QValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin(); + for(; accList_t != accList.end(); ++accList_t ) { + MyMoneyAccount acc = *accList_t; + if(m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there + m_nameIdx[acc.id()] = acc.id(); + } + } + + QMap<QString, QString>::ConstIterator it_nc; + for(it_nc = m_nameIdx.begin(); it_nc != m_nameIdx.end(); ++it_nc) { + + const MyMoneyAccount& acc = file->account(*it_nc); + MyMoneySecurity currency; + + //change currency to deep currency if account is an investment + if(acc.isInvest()) { + MyMoneySecurity underSecurity = file->security(acc.currencyId()); + currency = file->security(underSecurity.tradingCurrency()); + } else { + currency = file->security(acc.currencyId()); + } + + //Check if the account is going to be below zero or below the minimal balance in the forecast period + QString minimumBalance = acc.value("minimumBalance"); + MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); + + //Check if the account is going to be below minimal balance + dropMinimum = forecast.daysToMinimumBalance(acc); + + //Check if the account is going to be below zero in the future + dropZero = forecast.daysToZeroBalance(acc); + + // spit out possible warnings + QString msg; + + // if a minimum balance has been specified, an appropriate warning will + // only be shown, if the drop below 0 is on a different day or not present + + if(dropMinimum != -1 + && !minBalance.isZero() + && (dropMinimum < dropZero + || dropZero == -1)) { + switch(dropMinimum) { + case -1: + break; + case 0: + msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name()); + msg += i18n("The balance of %2 is below the minimum balance %3 today.").arg(acc.name()).arg(minBalance.formatMoney(acc, currency)); + msg += QString("</font>"); + break; + default: + msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name()); + msg += i18n("The balance of %1 will drop below the minimum balance %2 in %3 days.").arg(acc.name()).arg(minBalance.formatMoney(acc, currency)).arg(dropMinimum-1); + msg += QString("</font>"); + } + + if(!msg.isEmpty()) { + m_adviceText->append(msg); + } + } + + // a drop below zero is always shown + msg = QString(); + switch(dropZero) { + case -1: + break; + case 0: + if(acc.accountGroup() == MyMoneyAccount::Asset) { + msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name()); + msg += i18n("The balance of %1 is below %2 today.").arg(acc.name()).arg(MyMoneyMoney().formatMoney(acc, currency)); + msg += QString("</font>"); + break; + } + if(acc.accountGroup() == MyMoneyAccount::Liability) { + msg = i18n("The balance of %1 is above %2 today.").arg(acc.name()).arg(MyMoneyMoney().formatMoney(acc, currency)); + break; + } + break; + default: + if(acc.accountGroup() == MyMoneyAccount::Asset) { + msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name()); + msg += i18n("The balance of %1 will drop below %2 in %3 days.").arg(acc.name()).arg(MyMoneyMoney().formatMoney(acc, currency)).arg(dropZero); + msg += QString("</font>"); + break; + } + if(acc.accountGroup() == MyMoneyAccount::Liability) { + msg = i18n("The balance of %1 will raise above %2 in %3 days.").arg(acc.name()).arg(MyMoneyMoney().formatMoney(acc, currency)).arg(dropZero); + break; + } + } + if(!msg.isEmpty()) { + m_adviceText->append(msg); + } + + //advice about trends + msg = QString(); + MyMoneyMoney accCycleVariation = forecast.accountCycleVariation(acc); + if (accCycleVariation < MyMoneyMoney(0, 1)) { + msg = QString("<font color=\"%1\">").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name()); + msg += i18n("The account %1 is decreasing %2 per cycle.").arg(acc.name()).arg(accCycleVariation.formatMoney(acc, currency)); + msg += QString("</font>"); + } + + if(!msg.isEmpty()) { + m_adviceText->append(msg); + } + } + m_summaryList->show(); + m_adviceText->show(); +} + +void KForecastView::loadAdvancedView(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneyAccount> accList; + MyMoneySecurity baseCurrency = file->baseCurrency(); + MyMoneyForecast forecast; + int daysToBeginDay; + + //get the settings from current page + forecast.setForecastDays(m_forecastDays->value()); + forecast.setAccountsCycle(m_accountsCycle->value()); + forecast.setBeginForecastDay(m_beginDay->value()); + forecast.setForecastCycles(m_forecastCycles->value()); + forecast.setHistoryMethod(m_historyMethod->selectedId()); + forecast.doForecast(); + + //Get all accounts of the right type to calculate forecast + m_nameIdx.clear(); + accList = forecast.accountList(); + QValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin(); + for(; accList_t != accList.end(); ++accList_t ) { + MyMoneyAccount acc = *accList_t; + if(m_nameIdx[acc.id()] != acc.id()) { //Check if the account is there + m_nameIdx[acc.id()] = acc.id(); + } + } + //clear the list, including columns + m_advancedList->clear(); + for(;m_advancedList->columns() > 0;) { + m_advancedList->removeColumn(0); + } + + //add first column of both lists + int accountColumn = m_advancedList->addColumn(i18n("Account"), -1); + + //if beginning of forecast is today, set the begin day to next cycle to avoid repeating the first cycle + if(QDate::currentDate() < forecast.beginForecastDate()) { + daysToBeginDay = QDate::currentDate().daysTo(forecast.beginForecastDate()); + } else { + daysToBeginDay = forecast.accountsCycle(); + } + + //add columns + for(int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { + int col = m_advancedList->addColumn(i18n("Min Bal %1").arg(i), -1); + m_advancedList->setColumnAlignment(col, Qt::AlignRight); + m_advancedList->addColumn(i18n("Min Date %1").arg(i), -1); + } + for(int i = 1; ((i * forecast.accountsCycle()) + daysToBeginDay) <= forecast.forecastDays(); ++i) { + int col = m_advancedList->addColumn(i18n("Max Bal %1").arg(i), -1); + m_advancedList->setColumnAlignment(col, Qt::AlignRight); + m_advancedList->addColumn(i18n("Max Date %1").arg(i), -1); + } + int col = m_advancedList->addColumn(i18n("Average"), -1); + m_advancedList->setColumnAlignment(col, Qt::AlignRight); + m_advancedList->setSorting(-1); + KMyMoneyForecastListViewItem *advancedItem = 0; + + QMap<QString, QString>::ConstIterator it_nc; + for(it_nc = m_nameIdx.begin(); it_nc != m_nameIdx.end(); ++it_nc) { + const MyMoneyAccount& acc = file->account(*it_nc); + QString amount; + MyMoneyMoney amountMM; + MyMoneySecurity currency; + + //change currency to deep currency if account is an investment + if(acc.isInvest()) { + MyMoneySecurity underSecurity = file->security(acc.currencyId()); + currency = file->security(underSecurity.tradingCurrency()); + } else { + currency = file->security(acc.currencyId()); + } + + + advancedItem = new KMyMoneyForecastListViewItem(m_advancedList, advancedItem, false); + advancedItem->setText(accountColumn, acc.name()); + int it_c = 1; // iterator for the columns of the listview + + //get minimum balance list + QValueList<QDate> minBalanceList = forecast.accountMinimumBalanceDateList(acc); + QValueList<QDate>::Iterator t_min; + for(t_min = minBalanceList.begin(); t_min != minBalanceList.end() ; ++t_min) + { + QDate minDate = *t_min; + amountMM = forecast.forecastBalance(acc, minDate); + + amount = amountMM.formatMoney(acc, currency); + advancedItem->setText(it_c, amount, amountMM.isNegative()); + it_c++; + + QString dateString = KGlobal::locale()->formatDate(minDate, true); + advancedItem->setText(it_c, dateString, amountMM.isNegative()); + it_c++; + } + + //get maximum balance list + QValueList<QDate> maxBalanceList = forecast.accountMaximumBalanceDateList(acc); + QValueList<QDate>::Iterator t_max; + for(t_max = maxBalanceList.begin(); t_max != maxBalanceList.end() ; ++t_max) + { + QDate maxDate = *t_max; + amountMM = forecast.forecastBalance(acc, maxDate); + + amount = amountMM.formatMoney(acc, currency); + advancedItem->setText(it_c, amount, amountMM.isNegative()); + it_c++; + + QString dateString = KGlobal::locale()->formatDate(maxDate, true); + advancedItem->setText(it_c, dateString, amountMM.isNegative()); + it_c++; + } + //get average balance + amountMM = forecast.accountAverageBalance(acc); + amount = amountMM.formatMoney(acc, currency); + advancedItem->setText(it_c, amount, amountMM.isNegative()); + it_c++; + } + m_advancedList->show(); +} + +void KForecastView::loadBudgetView(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyForecast forecast; +// QValueList<MyMoneyAccount> accList; + + m_budgetList->setBaseCurrency(file->baseCurrency()); + + //get the settings from current page and calculate this year based on last year + QDate historyEndDate = QDate(QDate::currentDate().year()-1, 12, 31); + QDate historyStartDate = historyEndDate.addDays(-m_accountsCycle->value() * m_forecastCycles->value()); + QDate forecastStartDate = QDate(QDate::currentDate().year(), 1, 1); + QDate forecastEndDate = QDate::currentDate().addDays(m_forecastDays->value()); + forecast.setHistoryMethod(m_historyMethod->selectedId()); + + MyMoneyBudget budget; + forecast.createBudget(budget, historyStartDate, historyEndDate, forecastStartDate, forecastEndDate, false); + + //clear the list, including columns + m_budgetList->clearColumns(); + + //add columns + m_budgetList->showAccount(); + m_budgetList->showBudget(forecast); + + //add default rows + addTotalRow(m_budgetList, forecast); + addIncomeExpenseRows(forecast); + + //load income and expense budget accounts + loadAccounts(forecast, file->income(), m_incomeItem, KMyMoneyAccountTreeForecastItem::eBudget); + loadAccounts(forecast, file->expense(), m_expenseItem, KMyMoneyAccountTreeForecastItem::eBudget); + + m_budgetList->show(); +} + +QValueList<MyMoneyPrice> KForecastView::getAccountPrices(const MyMoneyAccount& acc) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneyPrice> prices; + MyMoneySecurity security = file->baseCurrency(); + try { + if(acc.isInvest()) { + security = file->security(acc.currencyId()); + if(security.tradingCurrency() != file->baseCurrency().id()) { + MyMoneySecurity sec = file->security(security.tradingCurrency()); + prices += file->price(sec.id(), file->baseCurrency().id()); + } + } else if(acc.currencyId() != file->baseCurrency().id()) { + if(acc.currencyId() != file->baseCurrency().id()) { + security = file->security(acc.currencyId()); + prices += file->price(acc.currencyId(), file->baseCurrency().id()); + } + } + + } catch(MyMoneyException *e) { + kdDebug(2) << __PRETTY_FUNCTION__ << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e->what(); + delete e; + } + return prices; +} + +void KForecastView::addAssetLiabilityRows(const MyMoneyForecast& forecast) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + QValueList<MyMoneyPrice> basePrices; + m_assetItem = new KMyMoneyAccountTreeForecastItem( m_totalItem, file->asset(), forecast, basePrices, file->baseCurrency() ); + m_assetItem->setOpen(true); + m_liabilityItem = new KMyMoneyAccountTreeForecastItem( m_totalItem, file->liability(), forecast, basePrices, file->baseCurrency()); + m_liabilityItem->setOpen(true); +} + +void KForecastView::addIncomeExpenseRows(const MyMoneyForecast& forecast) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + QValueList<MyMoneyPrice> basePrices; + m_incomeItem = new KMyMoneyAccountTreeForecastItem( m_totalItem, file->income(), forecast, basePrices, file->baseCurrency() ); + m_incomeItem->setOpen(true); + m_expenseItem = new KMyMoneyAccountTreeForecastItem( m_totalItem, file->expense(), forecast, basePrices, file->baseCurrency()); + m_expenseItem->setOpen(true); +} + +void KForecastView::addTotalRow(KMyMoneyAccountTreeForecast* forecastList, const MyMoneyForecast& forecast) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + m_totalItem = new KMyMoneyAccountTreeForecastItem( forecastList, file->asset(), forecast, file->baseCurrency(), i18n("Total") ); + m_totalItem->setOpen(true); +} + +bool KForecastView::includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + if( forecast.isForecastAccount(acc) ) + return true; + + QStringList accounts = acc.accountList(); + + if(accounts.size() > 0) { + QStringList::ConstIterator it_acc; + for(it_acc = accounts.begin(); it_acc != accounts.end(); ++it_acc) { + MyMoneyAccount account = file->account(*it_acc); + if( includeAccount(forecast, account) ) + return true; + } + } + return false; +} + +void KForecastView::loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, KMyMoneyAccountTreeForecastItem* parentItem, int forecastType ) +{ + QMap<QString, QString> nameIdx; + QStringList accList; + MyMoneyFile* file = MyMoneyFile::instance(); + KMyMoneyAccountTreeForecastItem *forecastItem = 0; + + //Get all accounts of the right type to calculate forecast + accList = account.accountList(); + + if(accList.size() == 0) + return; + + QStringList::ConstIterator accList_t; + for(accList_t = accList.begin(); accList_t != accList.end(); ++accList_t ) { + MyMoneyAccount subAccount = file->account(*accList_t); + //only add the account if it is a forecast account or the parent of a forecast account + if(includeAccount(forecast, subAccount)) { + nameIdx[subAccount.id()] = subAccount.id(); + } + } + + QMap<QString, QString>::ConstIterator it_nc; + for(it_nc = nameIdx.begin(); it_nc != nameIdx.end(); ++it_nc) { + + const MyMoneyAccount subAccount = file->account(*it_nc); + MyMoneySecurity currency; + if(subAccount.isInvest()) { + MyMoneySecurity underSecurity = file->security(subAccount.currencyId()); + currency = file->security(underSecurity.tradingCurrency()); + } else { + currency = file->security(subAccount.currencyId()); + } + + QString amount; + QString vAmount; + MyMoneyMoney vAmountMM; + + //get prices + QValueList<MyMoneyPrice> prices = getAccountPrices(subAccount); + + forecastItem = new KMyMoneyAccountTreeForecastItem( parentItem, subAccount, forecast, prices, currency, static_cast<KMyMoneyAccountTreeForecastItem::EForecastViewType>(forecastType) ); + forecastItem->setOpen(true); + + loadAccounts(forecast, subAccount, forecastItem, forecastType); + } +} + +void KForecastView::loadChartView(void) +{ + MyMoneyReport::EDetailLevel detailLevel[4] = { MyMoneyReport::eDetailAll, MyMoneyReport::eDetailTop, MyMoneyReport::eDetailGroup, MyMoneyReport::eDetailTotal }; + + MyMoneyReport reportCfg = MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below + detailLevel[m_comboDetail->currentItem()], + i18n("Networth Forecast"), + i18n("Generated Report")); + + reportCfg.setChartByDefault(true); + reportCfg.setChartGridLines(false); + reportCfg.setChartType(MyMoneyReport::eChartLine); + reportCfg.setIncludingSchedules( false ); + reportCfg.addAccountGroup(MyMoneyAccount::Asset); + reportCfg.addAccountGroup(MyMoneyAccount::Liability); + reportCfg.setColumnsAreDays( true ); + reportCfg.setConvertCurrency( true ); + reportCfg.setIncludingForecast( true ); + reportCfg.setDateFilter(QDate::currentDate(),QDate::currentDate().addDays(m_forecastDays->value())); + + reports::PivotTable table(reportCfg); + + table.drawChart(*m_forecastChart); + + // Adjust the size + m_forecastChart->resize(m_tab->width()-30, m_tab->height()-60); + + m_forecastChart->update(); +} + +#include "kforecastview.moc" diff --git a/kmymoney2/views/kforecastview.h b/kmymoney2/views/kforecastview.h new file mode 100644 index 0000000..8beb24a --- /dev/null +++ b/kmymoney2/views/kforecastview.h @@ -0,0 +1,138 @@ +/*************************************************************************** + kforecastview.h + ------------------- + copyright : (C) 2007 by Alvaro Soliverez + email : asoliverez@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KFORECASTVIEW_H +#define KFORECASTVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/mymoneyutils.h> + +#include "../views/kforecastviewdecl.h" +#include "../widgets/kmymoneyaccounttreeforecast.h" +#include "../reports/kreportchartview.h" + +using namespace reports; + +/** + * @author Alvaro Soliverez + */ + +/** + * This class implements the forecast 'view'. + */ +class KForecastView : public KForecastViewDecl +{ + Q_OBJECT +private: + +public: + KForecastView(QWidget *parent=0, const char *name=0); + virtual ~KForecastView(); + + void show(void); + +public slots: + void slotLoadForecast(void); + void slotManualForecast(void); + +protected: + typedef enum { + SummaryView = 0, + ListView, + AdvancedView, + BudgetView, + ChartView, + // insert new values above this line + MaxViewTabs + } ForecastViewTab; + + QMap<QString, QString> m_nameIdx; + + + /** + * This method loads the forecast view. + */ + void loadForecast(ForecastViewTab tab); + + /** + * This method loads the detailed view + */ + void loadListView(void); + + /** + * This method loads the summary view + */ + void loadSummaryView(void); + + /** + * This method loads the advanced view + */ + void loadAdvancedView(void); + + /** + * This method loads the budget view + */ + void loadBudgetView(void); + + /** + * This method loads the budget view + */ + void loadChartView(void); + + /** + * This method loads the settings from user configuration + */ + void loadForecastSettings(void); + +protected slots: + void slotTabChanged(QWidget*); + + /** + * Get the list of prices for an account + * This is used later to create an instance of KMyMoneyAccountTreeForecastItem + * + */ + QValueList<MyMoneyPrice> getAccountPrices(const MyMoneyAccount& acc); + +private: + void addAssetLiabilityRows(const MyMoneyForecast& forecast); + void addIncomeExpenseRows(const MyMoneyForecast& forecast); + void addTotalRow(KMyMoneyAccountTreeForecast* forecastList, const MyMoneyForecast& forecast); + bool includeAccount(MyMoneyForecast& forecast, const MyMoneyAccount& acc); + void loadAccounts(MyMoneyForecast& forecast, const MyMoneyAccount& account, KMyMoneyAccountTreeForecastItem* parentItem, int forecastType); + + bool m_needReload[MaxViewTabs]; + KMyMoneyAccountTreeForecastItem* m_totalItem; + KMyMoneyAccountTreeForecastItem* m_assetItem; + KMyMoneyAccountTreeForecastItem* m_liabilityItem; + KMyMoneyAccountTreeForecastItem* m_incomeItem; + KMyMoneyAccountTreeForecastItem* m_expenseItem; + + KReportChartView* m_forecastChart; + +}; + +#endif diff --git a/kmymoney2/views/kforecastviewdecl.ui b/kmymoney2/views/kforecastviewdecl.ui new file mode 100644 index 0000000..6d2bf3b --- /dev/null +++ b/kmymoney2/views/kforecastviewdecl.ui @@ -0,0 +1,614 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KForecastViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KForecastViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>909</width> + <height>771</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox3</cstring> + </property> + <property name="title"> + <string>Forecast Settings</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout16</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Days to Forecast:</string> + </property> + </widget> + <widget class="QSpinBox" row="0" column="1"> + <property name="name"> + <cstring>m_forecastDays</cstring> + </property> + <property name="maxValue"> + <number>999</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Days of Accounts Cycle:</string> + </property> + </widget> + <widget class="QSpinBox" row="1" column="1"> + <property name="name"> + <cstring>m_accountsCycle</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maxValue"> + <number>999</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>Day of Month to start Forecast:</string> + </property> + </widget> + <widget class="QSpinBox" row="2" column="1"> + <property name="name"> + <cstring>m_beginDay</cstring> + </property> + <property name="maxValue"> + <number>31</number> + </property> + <property name="minValue"> + <number>0</number> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>Historic Cycles:</string> + </property> + </widget> + <widget class="QSpinBox" row="3" column="1"> + <property name="name"> + <cstring>m_forecastCycles</cstring> + </property> + <property name="maxValue"> + <number>999</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="text"> + <string>Chart Detail:</string> + </property> + </widget> + <widget class="QComboBox" row="4" column="1"> + <item> + <property name="text"> + <string>All</string> + </property> + </item> + <item> + <property name="text"> + <string>Top-Level</string> + </property> + </item> + <item> + <property name="text"> + <string>Groups</string> + </property> + </item> + <item> + <property name="text"> + <string>Totals</string> + </property> + </item> + <property name="name"> + <cstring>m_comboDetail</cstring> + </property> + <property name="currentItem"> + <number>2</number> + </property> + <property name="toolTip" stdset="0"> + <string><p>Choose what level of detail to show on the chart.</p></string> + </property> + </widget> + </grid> + </widget> + <spacer> + <property name="name"> + <cstring>spacer23</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>31</height> + </size> + </property> + </spacer> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout76</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Current Forecast Method:</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>m_forecastMethod</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>2</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>method</string> + </property> + </widget> + </hbox> + <widget class="QButtonGroup" row="2" column="0"> + <property name="name"> + <cstring>m_historyMethod</cstring> + </property> + <property name="title"> + <string>History Forecast Method</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioButton11</cstring> + </property> + <property name="text"> + <string>Simple Moving Average</string> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioButton12</cstring> + </property> + <property name="text"> + <string>Weighted Moving Average</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioButton12</cstring> + </property> + <property name="text"> + <string>Linear Regression</string> + </property> + <property name="buttonGroupId"> + <number>2</number> + </property> + </widget> + </vbox> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>6</height> + </size> + </property> + </spacer> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout75</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_forecastButton</cstring> + </property> + <property name="text"> + <string>Forecast</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer22</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>31</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_saveButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Save</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QTabWidget"> + <property name="name"> + <cstring>m_tab</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>summary</cstring> + </property> + <attribute name="title"> + <string>Summary</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KMyMoneyAccountTreeForecast"> + <column> + <property name="text"> + <string>Account</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>0 days</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>30 days</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>60 days</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>90 days</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>m_summaryList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>2</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="midLineWidth"> + <number>1</number> + </property> + <property name="resizePolicy"> + <enum>AutoOneFit</enum> + </property> + </widget> + <widget class="QTextEdit"> + <item> + <property name="text"> + <string>New Item</string> + </property> + <property name="pixmap"> + <pixmap></pixmap> + </property> + </item> + <property name="name"> + <cstring>m_adviceText</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="resizePolicy"> + <enum>AutoOneFit</enum> + </property> + </widget> + </hbox> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + </hbox> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>detail</cstring> + </property> + <attribute name="title"> + <string>Detail</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KMyMoneyAccountTreeForecast"> + <property name="name"> + <cstring>m_forecastList</cstring> + </property> + <property name="resizePolicy"> + <enum>AutoOneFit</enum> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>advanced</cstring> + </property> + <attribute name="title"> + <string>Advanced</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KListView"> + <property name="name"> + <cstring>m_advancedList</cstring> + </property> + <property name="resizePolicy"> + <enum>AutoOneFit</enum> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>budget</cstring> + </property> + <attribute name="title"> + <string>Budget Forecast</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KMyMoneyAccountTreeForecast"> + <property name="name"> + <cstring>m_budgetList</cstring> + </property> + <property name="resizePolicy"> + <enum>AutoOneFit</enum> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>m_tabChart</cstring> + </property> + <attribute name="title"> + <string>Chart</string> + </attribute> + <vbox> + <property name="name"> + <cstring>m_layoutChart</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + </vbox> + </widget> + </widget> + </vbox> +</widget> +<customwidgets> + <customwidget> + <class>KMyMoneyAccountTreeforecast</class> + <header location="local">../widgets/kmymoneyaccounttreeforecast.h</header> + <sizehint> + <width>-1</width> + <height>-1</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>5</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </customwidget> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kgloballedgerview.cpp b/kmymoney2/views/kgloballedgerview.cpp new file mode 100644 index 0000000..bf95e50 --- /dev/null +++ b/kmymoney2/views/kgloballedgerview.cpp @@ -0,0 +1,1461 @@ +/*************************************************************************** + kgloballedgerview.cpp - description + ------------------- + begin : Wed Jul 26 2006 + copyright : (C) 2006 by Thomas Baumgart + email : Thomas Baumgart <ipwizard@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. * + * * + ***************************************************************************/ + +#include <typeinfo> + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qframe.h> +#include <qlayout.h> +#include <qtimer.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include "kdecompat.h" +#include <klocale.h> +#include <kcombobox.h> +#include <kpushbutton.h> +#include <kmessagebox.h> +#include <kiconloader.h> +#include <ktoolbar.h> +#include <ktoolbarbutton.h> +#include <kpassivepopup.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kgloballedgerview.h" + +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/kmymoneyaccountcombo.h> +#include <kmymoney/kmymoneytitlelabel.h> +#include <kmymoney/register.h> +#include <kmymoney/transactioneditor.h> +#include <kmymoney/selectedtransaction.h> + +#include <kmymoney/kmymoneyglobalsettings.h> + +#include "../widgets/registersearchline.h" +#include "../dialogs/ksortoptiondlg.h" +#include "../kmymoney2.h" + +#include "../widgets/scheduledtransaction.h" + +class KGlobalLedgerView::Private +{ +public: + Private(); + + MousePressFilter* m_mousePressFilter; + KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine; + QPoint m_startPoint; + QString m_reconciliationAccount; + QDate m_reconciliationDate; + MyMoneyMoney m_endingBalance; + int m_precision; + bool m_inLoading; + bool m_recursion; + bool m_showDetails; + KMyMoneyRegister::Action m_action; + QTimer m_viewPosTimer; +}; + +MousePressFilter::MousePressFilter(QWidget* parent, const char* name) : + QObject(parent, name), + m_lastMousePressEvent(0), + m_filterActive(true) +{ +} + +void MousePressFilter::addWidget(QWidget* w) +{ + m_parents.append(w); +} + +void MousePressFilter::setFilterActive(bool state) +{ + m_filterActive = state; +} + +bool MousePressFilter::isChildOf( QWidget* child, QWidget *parent ) +{ + while(child) { + if(child == parent) + return true; + // If one of the ancestors is a KPassivePopup then it's as + // if it is a child of our own + if(dynamic_cast<KPassivePopup*>(child)) + return true; + child = child->parentWidget(); + } + return false; +} + +bool MousePressFilter::eventFilter(QObject* o, QEvent* e) +{ + if(m_filterActive) { + if(e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) { + QValueList<QWidget*>::const_iterator it_w; + for(it_w = m_parents.begin(); it_w != m_parents.end(); ++it_w) { + if(isChildOf((QWidget*)o, (*it_w))) { + m_lastMousePressEvent = e; + break; + } + } + if(it_w == m_parents.end()) { + m_lastMousePressEvent = e; + bool rc = false; + emit mousePressedOnExternalWidget(rc); + } + } + + if(e->type() != QEvent::MouseButtonPress) { + m_lastMousePressEvent = 0; + } + } + return false; +} + + +KGlobalLedgerView::Private::Private() : + m_mousePressFilter(0), + m_registerSearchLine(0), + m_inLoading(false), + m_recursion(false), + m_showDetails(false) +{ +} + +QDate KGlobalLedgerView::m_lastPostDate; + +KGlobalLedgerView::KGlobalLedgerView(QWidget *parent, const char *name ) + : KMyMoneyViewBase(parent, name, i18n("Ledgers")), + d(new Private), + m_needReload(false), + m_newAccountLoaded(true), + m_inEditMode(false) +{ + d->m_mousePressFilter = new MousePressFilter((QWidget*)this); + d->m_action = KMyMoneyRegister::ActionNone;; + + // create the toolbar frame at the top of the view + m_toolbarFrame = new QFrame(this); + QVBoxLayout* toolbarLayout = new QVBoxLayout(m_toolbarFrame, 0, 0); + + m_toolbar = new KToolBar(m_toolbarFrame, 0, true); + toolbarLayout->addWidget(m_toolbar); + m_toolbar->setIconText(KToolBar::IconTextRight); + + m_accountComboBox = new KMyMoneyAccountCombo(m_toolbar, "AccountCombo"); + m_toolbar->insertWidget(1, 100, m_accountComboBox); + +#if 0 + // the account button at the right of the toolbar + // I leave the code commented here for a while, so that I see + // how I can add other widgets at this point + KIconLoader *il = KGlobal::iconLoader(); + m_toolbar->insertButton(il->loadIcon("document", KIcon::Small, KIcon::SizeSmall), + 1,true,i18n("Account")); + //m_toolbar->setMaximumSize(50,20); + m_toolbar->alignItemRight(1); +#endif + m_toolbar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + layout()->addWidget(m_toolbarFrame); + + // create the register frame + m_registerFrame = new QFrame(this); + QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame, 0, 0); + layout()->addWidget(m_registerFrame); + layout()->setStretchFactor(m_registerFrame, 2); + m_register = new KMyMoneyRegister::Register(m_registerFrame); + registerFrameLayout->addWidget(m_register); + m_register->installEventFilter(this); + connect(m_register, SIGNAL(openContextMenu()), this, SIGNAL(openContextMenu())); + connect(m_register, SIGNAL(headerClicked()), this, SLOT(slotSortOptions())); + connect(m_register, SIGNAL(reconcileStateColumnClicked(KMyMoneyRegister::Transaction*)), this, SLOT(slotToggleTransactionMark(KMyMoneyRegister::Transaction*))); + connect(&d->m_viewPosTimer, SIGNAL(timeout()), this, SLOT(slotUpdateViewPos())); + + // insert search line widget + + d->m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbar); + m_toolbar->setStretchableWidget(d->m_registerSearchLine); + + // create the summary frame + m_summaryFrame = new QFrame(this); + QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame, 0, 0); + m_leftSummaryLabel = new QLabel(m_summaryFrame); + m_centerSummaryLabel = new QLabel(m_summaryFrame); + m_rightSummaryLabel = new QLabel(m_summaryFrame); + summaryFrameLayout->addWidget(m_leftSummaryLabel); + QSpacerItem* spacer = new QSpacerItem( 20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); + summaryFrameLayout->addItem(spacer); + summaryFrameLayout->addWidget(m_centerSummaryLabel); + spacer = new QSpacerItem( 20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum ); + summaryFrameLayout->addItem(spacer); + summaryFrameLayout->addWidget(m_rightSummaryLabel); + layout()->addWidget(m_summaryFrame); + + // create the button frame + m_buttonFrame = new QFrame(this); + QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame, 0, 0); + layout()->addWidget(m_buttonFrame); + m_buttonbar = new KToolBar(m_buttonFrame, 0, true); + m_buttonbar->setIconText(KToolBar::IconTextRight); + buttonLayout->addWidget(m_buttonbar); + + kmymoney2->action("transaction_new")->plug(m_buttonbar); + kmymoney2->action("transaction_delete")->plug(m_buttonbar); + kmymoney2->action("transaction_edit")->plug(m_buttonbar); + kmymoney2->action("transaction_enter")->plug(m_buttonbar); + kmymoney2->action("transaction_cancel")->plug(m_buttonbar); + kmymoney2->action("transaction_accept")->plug(m_buttonbar); + kmymoney2->action("transaction_match")->plug(m_buttonbar); + + // create the transaction form frame + m_formFrame = new QFrame(this); + QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame, 5, 0); + m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame); + frameLayout->addWidget(m_form->tabBar(m_formFrame)); + frameLayout->addWidget(m_form); + m_formFrame->setFrameShape( QFrame::Panel ); + m_formFrame->setFrameShadow( QFrame::Raised ); + layout()->addWidget(m_formFrame); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); + connect(m_register, SIGNAL(focusChanged(KMyMoneyRegister::Transaction*)), m_form, SLOT(slotSetTransaction(KMyMoneyRegister::Transaction*))); + connect(m_register, SIGNAL(focusChanged()), kmymoney2, SLOT(slotUpdateActions())); + connect(m_accountComboBox, SIGNAL(accountSelected(const QString&)), this, SLOT(slotSelectAccount(const QString&))); + connect(m_register, SIGNAL(selectionChanged(const KMyMoneyRegister::SelectedTransactions&)), this, SIGNAL(transactionsSelected(const KMyMoneyRegister::SelectedTransactions&))); + connect(m_register, SIGNAL(editTransaction()), this, SIGNAL(startEdit())); + connect(m_register, SIGNAL(emptyItemSelected()), this, SLOT(slotNewTransaction())); + connect(m_register, SIGNAL(aboutToSelectItem(KMyMoneyRegister::RegisterItem*, bool&)), this, SLOT(slotAboutToSelectItem(KMyMoneyRegister::RegisterItem*, bool&))); + connect(d->m_mousePressFilter, SIGNAL(mousePressedOnExternalWidget(bool&)), this, SIGNAL(cancelOrEndEdit(bool&))); + + connect(m_form, SIGNAL(newTransaction(KMyMoneyRegister::Action)), this, SLOT(slotNewTransaction(KMyMoneyRegister::Action))); + + // setup mouse press filter + d->m_mousePressFilter->addWidget(m_formFrame); + d->m_mousePressFilter->addWidget(m_buttonFrame); + d->m_mousePressFilter->addWidget(m_summaryFrame); + d->m_mousePressFilter->addWidget(m_registerFrame); +} + +KGlobalLedgerView::~KGlobalLedgerView() +{ + delete d; +} + +void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect) +{ + Q_UNUSED(item); + emit cancelOrEndEdit(okToSelect); +} + +void KGlobalLedgerView::slotLoadView(void) +{ + m_needReload = true; + if(isVisible()) { + if(!m_inEditMode) { + loadView(); + m_needReload = false; + // force a new account if the current one is empty + m_newAccountLoaded = m_account.id().isEmpty(); + } + } +} + +void KGlobalLedgerView::clear(void) +{ + // clear current register contents + m_register->clear(); + + // setup header font + QFont font = KMyMoneyGlobalSettings::listHeaderFont(); + QFontMetrics fm( font ); + int height = fm.lineSpacing()+6; + m_register->horizontalHeader()->setMinimumHeight(height); + m_register->horizontalHeader()->setMaximumHeight(height); + m_register->horizontalHeader()->setFont(font); + + // setup cell font + font = KMyMoneyGlobalSettings::listCellFont(); + m_register->setFont(font); + + // clear the form + m_form->clear(); + + // the selected transactions list + m_transactionList.clear(); + + // and the selected account in the combo box + m_accountComboBox->setSelected(QString()); + + // fraction defaults to two digits + d->m_precision = 2; +} + +void KGlobalLedgerView::loadView(void) +{ + MYMONEYTRACER(tracer); + + // setup form visibility + m_formFrame->setShown(KMyMoneyGlobalSettings::transactionForm()); + + // no account selected + emit accountSelected(MyMoneyAccount()); + // no transaction selected + KMyMoneyRegister::SelectedTransactions list; + emit transactionsSelected(list); + + QMap<QString, bool> isSelected; + QString focusItemId; + QString anchorItemId; + + if(!d->m_inLoading) + d->m_startPoint = QPoint(-1, -1); + + if(!m_newAccountLoaded) { + // remember the current selected transactions + KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); + for(; item; item = item->nextItem()) { + if(item->isSelected()) { + isSelected[item->id()] = true; + } + } + // remember the item that has the focus + if(m_register->focusItem()) + focusItemId = m_register->focusItem()->id(); + // and the one that has the selection anchor + if(m_register->anchorItem()) + anchorItemId = m_register->anchorItem()->id(); + + // remember the upper left corner of the viewport + if(!d->m_inLoading && d->m_showDetails == KMyMoneyGlobalSettings::showRegisterDetailed()) + d->m_startPoint = QPoint(m_register->contentsX(), m_register->contentsY()); + } else { + if(d->m_viewPosTimer.isActive()) + d->m_viewPosTimer.stop(); + d->m_startPoint = QPoint(-1, -1); + d->m_inLoading = false; + d->m_registerSearchLine->searchLine()->reset(); + } + + // clear the current contents ... + clear(); + + // ... load the combobox widget and select current account ... + loadAccounts(); + + // ... setup the register columns ... + m_register->setupRegister(m_account); + + // ... setup the form ... + m_form->setupForm(m_account); + + if(m_account.id().isEmpty()) { + // if we don't have an account we bail out + setEnabled(false); + return; + } + setEnabled(true); + + m_register->setUpdatesEnabled(false); + + // ... and recreate it + KMyMoneyRegister::RegisterItem* focusItem = 0; + KMyMoneyRegister::RegisterItem* anchorItem = 0; + QMap<QString, MyMoneyMoney> actBalance, clearedBalance, futureBalance; + QMap<QString, MyMoneyMoney>::iterator it_b; + try { + // setup the filter to select the transactions we want to display + // and update the sort order + QString sortOrder; + QString key; + QDate reconciliationDate = d->m_reconciliationDate; + + MyMoneyTransactionFilter filter(m_account.id()); + // if it's an investment account, we also take care of + // the sub-accounts (stock accounts) + if(m_account.accountType() == MyMoneyAccount::Investment) + filter.addAccount(m_account.accountList()); + + if(isReconciliationAccount()) { + key = "kmm-sort-reconcile"; + sortOrder = KMyMoneyGlobalSettings::sortReconcileView(); + filter.addState(MyMoneyTransactionFilter::notReconciled); + filter.addState(MyMoneyTransactionFilter::cleared); + } else { + filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); + key = "kmm-sort-std"; + sortOrder = KMyMoneyGlobalSettings::sortNormalView(); + if (KMyMoneyGlobalSettings::hideReconciledTransactions() + && !m_account.isIncomeExpense()) { + filter.addState(MyMoneyTransactionFilter::notReconciled); + filter.addState(MyMoneyTransactionFilter::cleared); + } + } + filter.setReportAllSplits(true); + + // check if we have an account override of the sort order + if(!m_account.value(key).isEmpty()) + sortOrder = m_account.value(key); + + // setup sort order + m_register->setSortOrder(sortOrder); + + // retrieve the list from the engine + MyMoneyFile::instance()->transactionList(m_transactionList, filter); + + kmymoney2->slotStatusProgressBar(0, m_transactionList.count()); + + // create the elements for the register + QValueList<QPair<MyMoneyTransaction, MyMoneySplit> >::const_iterator it; + QMap<QString, int>uniqueMap; + int i = 0; + for(it = m_transactionList.begin(); it != m_transactionList.end(); ++it) { + uniqueMap[(*it).first.id()]++; + KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); + actBalance[t->split().accountId()] = MyMoneyMoney(0,1); + kmymoney2->slotStatusProgressBar(++i, 0); + // if we're in reconciliation and the state is cleared, we + // force the item to show in dimmed intensity to get a visual focus + // on those items, that we need to work on + if(isReconciliationAccount() && (*it).second.reconcileFlag() == MyMoneySplit::Cleared) { + t->setReducedIntensity(true); + } + } + + // create dummy entries for the scheduled transactions if sorted by postdate + int period = KMyMoneyGlobalSettings::schedulePreview(); + if(m_register->primarySortKey() == KMyMoneyRegister::PostDateSort) { + // show scheduled transactions which have a scheduled postdate + // within the next 'period' days. In reconciliation mode, the + // period starts on the statement date. + QDate endDate = QDate::currentDate().addDays(period); + if(isReconciliationAccount()) + endDate = reconciliationDate.addDays(period); + QValueList<MyMoneySchedule> scheduleList = MyMoneyFile::instance()->scheduleList(m_account.id()); + while(scheduleList.count() > 0){ + MyMoneySchedule& s = scheduleList.first(); + for(;;) { + if(s.isFinished() || s.adjustedNextDueDate() > endDate) { + break; + } + + MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s)); + // if the transaction is scheduled and overdue, it can't + // certainly be posted in the past. So we take todays date + // as the alternative + if(s.isOverdue()) + t.setPostDate(QDate::currentDate()); + else + t.setPostDate(s.adjustedNextDueDate()); + const QValueList<MyMoneySplit>& splits = t.splits(); + QValueList<MyMoneySplit>::const_iterator it_s; + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + if((*it_s).accountId() == m_account.id()) { + new KMyMoneyRegister::StdTransactionScheduled(m_register, t, *it_s, uniqueMap[t.id()]); + } + } + // keep track of this payment locally (not in the engine) + if(s.isOverdue()) + s.setLastPayment(QDate::currentDate()); + else + s.setLastPayment(s.nextDueDate()); + + // if this is a one time schedule, we can bail out here as we're done + if(s.occurence() == MyMoneySchedule::OCCUR_ONCE) + break; + + // for all others, we check if the next payment date is still 'in range' + s.setNextDueDate(s.nextPayment(s.nextDueDate())); + } + scheduleList.pop_front(); + } + } + + // add the group markers + m_register->addGroupMarkers(); + + // sort the transactions according to the sort setting + m_register->sortItems(); + + // remove trailing and adjacent markers + m_register->removeUnwantedGroupMarkers(); + + // add special markers for reconciliation now so that they do not get + // removed by m_register->removeUnwantedGroupMarkers(). Needs resorting + // of items but that's ok. + + KMyMoneyRegister::StatementGroupMarker* statement = 0; + KMyMoneyRegister::StatementGroupMarker* dStatement = 0; + KMyMoneyRegister::StatementGroupMarker* pStatement = 0; + + if(isReconciliationAccount()) { + switch(m_register->primarySortKey()) { + case KMyMoneyRegister::PostDateSort: + statement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Details")); + m_register->sortItems(); + break; + case KMyMoneyRegister::TypeSort: + dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Deposit Details")); + pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Payment, reconciliationDate, i18n("Statement Payment Details")); + m_register->sortItems(); + break; + default: + break; + } + } + + // we need at least the balance for the account we currently show + actBalance[m_account.id()] = MyMoneyMoney(); + + if(m_account.accountType() == MyMoneyAccount::Investment) { + QValueList<QString>::const_iterator it_a; + for(it_a = m_account.accountList().begin(); it_a != m_account.accountList().end(); ++it_a) { + actBalance[*it_a] = MyMoneyMoney(); + } + } + + // determine balances (actual, cleared). We do this by getting the actual + // balance of all entered transactions from the engine and walk the list + // of transactions backward. Also re-select a transaction if it was + // selected before and setup the focus item. + + MyMoneyMoney factor(1,1); + if(m_account.accountGroup() == MyMoneyAccount::Liability + || m_account.accountGroup() == MyMoneyAccount::Equity) + factor = -factor; + + QMap<QString, int> deposits; + QMap<QString, int> payments; + QMap<QString, MyMoneyMoney> depositAmount; + QMap<QString, MyMoneyMoney> paymentAmount; + for(it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { + MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key()); + balance = balance * factor; + clearedBalance[it_b.key()] = + futureBalance[it_b.key()] = + (*it_b) = balance; + deposits[it_b.key()] = payments[it_b.key()] = 0; + depositAmount[it_b.key()] = MyMoneyMoney(); + paymentAmount[it_b.key()] = MyMoneyMoney(); + } + + tracer.printf("total balance of %s = %s", m_account.name().data(), actBalance[m_account.id()].formatMoney("", 2).data()); + tracer.printf("future balance of %s = %s", m_account.name().data(), futureBalance[m_account.id()].formatMoney("", 2).data()); + tracer.printf("cleared balance of %s = %s", m_account.name().data(), clearedBalance[m_account.id()].formatMoney("", 2).data()); + + KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); + focusItem = 0; + + // take care of possibly trailing scheduled transactions (bump up the future balance) + while(p) { + if(p->isSelectable()) { + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t && t->isScheduled()) { + MyMoneyMoney balance = futureBalance[t->split().accountId()]; + const MyMoneySplit& split = t->split(); + // if this split is a stock split, we can't just add the amount of shares + if(t->transaction().isStockSplit()) { + balance = balance * split.shares(); + } else { + balance += split.shares() * factor; + } + futureBalance[split.accountId()] = balance; + } else if(t && !focusItem) + focusItem = p; + } + p = p->prevItem(); + } + + p = m_register->lastItem(); + while(p) { + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t) { + if(isSelected.contains(t->id())) + t->setSelected(true); + + if(t->id() == focusItemId) + focusItem = t; + if(t->id() == anchorItemId) + anchorItem = t; + + const MyMoneySplit& split = t->split(); + MyMoneyMoney balance = futureBalance[split.accountId()]; + t->setBalance(balance); + + // if this split is a stock split, we can't just add the amount of shares + if(t->transaction().isStockSplit()) { + balance /= split.shares(); + } else { + balance -= split.shares() * factor; + } + + if(!t->isScheduled()) { + if(split.reconcileFlag() == MyMoneySplit::NotReconciled) { + tracer.printf("Reducing cleared balance by %s because %s/%s(%s) is not reconciled", (split.shares() * factor).formatMoney("", 2).data(), t->transaction().id().data(), split.id().data(), t->transaction().postDate().toString(Qt::ISODate).data()); + clearedBalance[split.accountId()] -= split.shares() * factor; + } + if(isReconciliationAccount() && t->transaction().postDate() > reconciliationDate && split.reconcileFlag() == MyMoneySplit::Cleared) { + tracer.printf("Reducing cleared balance by %s because we are in reconciliation, %s/%s(%s)'s date is after or on reconciliation date (%s) and is cleared", (split.shares() * factor).formatMoney("", 2).data(), t->transaction().id().data(), split.id().data(), t->transaction().postDate().toString(Qt::ISODate).data(), reconciliationDate.toString(Qt::ISODate).data()); + + clearedBalance[split.accountId()] -= split.shares() * factor; + } + if(isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == MyMoneySplit::Cleared) { + if(split.shares().isNegative()) { + payments[split.accountId()]++; + paymentAmount[split.accountId()] += split.shares(); + } else { + deposits[split.accountId()]++; + depositAmount[split.accountId()] += split.shares(); + } + } + + if(t->transaction().postDate() > QDate::currentDate()) { + tracer.printf("Reducing actual balance by %s because %s/%s(%s) is in the future", (split.shares() * factor).formatMoney("", 2).data(), t->transaction().id().data(), split.id().data(), t->transaction().postDate().toString(Qt::ISODate).data()); + actBalance[split.accountId()] -= split.shares() * factor; + } + } + futureBalance[split.accountId()] = balance; + } + p = p->prevItem(); + } + + tracer.printf("total balance of %s = %s", m_account.name().data(), actBalance[m_account.id()].formatMoney("", 2).data()); + tracer.printf("future balance of %s = %s", m_account.name().data(), futureBalance[m_account.id()].formatMoney("", 2).data()); + tracer.printf("cleared balance of %s = %s", m_account.name().data(), clearedBalance[m_account.id()].formatMoney("", 2).data()); + + // update statement information + if(statement) { + statement->setText(i18n("%1 deposits (%3), %2 payments (%4)"). + arg(deposits[m_account.id()]). + arg(payments[m_account.id()]). + arg(depositAmount[m_account.id()].abs().formatMoney(m_account.fraction())). + arg(paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction())) ); + } + if(pStatement) { + pStatement->setText(i18n("%1 payments (%2)").arg(payments[m_account.id()]). + arg(paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction())) ); + } + if(dStatement) { + dStatement->setText(i18n("%1 deposits (%2)").arg(deposits[m_account.id()]). + arg(depositAmount[m_account.id()].abs().formatMoney(m_account.fraction())) ); + } + + // add a last empty entry for new transactions + // leave some information about the current account + MyMoneySplit split; + split.setReconcileFlag(MyMoneySplit::NotReconciled); + // make sure to use the value specified in the option during reconciliation + if(isReconciliationAccount()) + split.setReconcileFlag(static_cast<MyMoneySplit::reconcileFlagE>(KMyMoneyGlobalSettings::defaultReconciliationState())); + KMyMoneyRegister::Register::transactionFactory(m_register, MyMoneyTransaction(), split, 0); + + m_register->updateRegister(true); + + if(focusItem) { + // in case we have some selected items we just set the focus item + // in other cases, we make the focusitem also the selected item + if(isSelected.count() > 1) { + m_register->setFocusItem(focusItem); + m_register->setAnchorItem(anchorItem); + } else + m_register->selectItem(focusItem, true); + } else { + // just use the empty line at the end if nothing else exists in the ledger + p = m_register->lastItem(); + m_register->setFocusItem(p); + m_register->selectItem(p); + focusItem = p; + } + + updateSummaryLine(actBalance, clearedBalance); + kmymoney2->slotStatusProgressBar(-1, -1); + + } catch(MyMoneyException *e) { + delete e; + m_account = MyMoneyAccount(); + clear(); + } + + // (re-)position viewport + if(m_newAccountLoaded) { + if(focusItem) { + d->m_startPoint = QPoint(-1, -1); + } else { + d->m_startPoint = QPoint(0, 0); + } + } + if(!d->m_inLoading) { + d->m_viewPosTimer.start(30, true); + d->m_inLoading = true; + } + + d->m_showDetails = KMyMoneyGlobalSettings::showRegisterDetailed(); + + // and tell everyone what's selected + emit accountSelected(m_account); +} + +void KGlobalLedgerView::updateSummaryLine(const QMap<QString, MyMoneyMoney>& actBalance, const QMap<QString, MyMoneyMoney>& clearedBalance) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + m_leftSummaryLabel->show(); + m_centerSummaryLabel->show(); + m_rightSummaryLabel->show(); + + if(isReconciliationAccount()) { + if(m_account.accountType() != MyMoneyAccount::Investment) { + m_leftSummaryLabel->setText(i18n("Statement: %1").arg(d->m_endingBalance.formatMoney("", d->m_precision))); + m_centerSummaryLabel->setText(i18n("Cleared: %1").arg(clearedBalance[m_account.id()].formatMoney("", d->m_precision))); + m_rightSummaryLabel->setText(i18n("Difference: %1").arg((clearedBalance[m_account.id()] - d->m_endingBalance).formatMoney("", d->m_precision))); + } + } else { + // update summary line in normal mode + QDate reconcileDate = m_account.lastReconciliationDate(); + + if(reconcileDate.isValid()) { + m_leftSummaryLabel->setText(i18n("Last reconciled: %1").arg(KGlobal::locale()->formatDate(reconcileDate, true))); + } else { + m_leftSummaryLabel->setText(i18n("Never reconciled")); + } + + m_rightSummaryLabel->setPaletteForegroundColor(m_leftSummaryLabel->paletteForegroundColor()); + if(m_account.accountType() != MyMoneyAccount::Investment) { + m_centerSummaryLabel->setText(i18n("Cleared: %1").arg(clearedBalance[m_account.id()].formatMoney("", d->m_precision))); + m_rightSummaryLabel->setText(i18n("Balance: %1").arg(actBalance[m_account.id()].formatMoney("", d->m_precision))); + bool showNegative = actBalance[m_account.id()].isNegative(); + if(m_account.accountGroup() == MyMoneyAccount::Liability && !actBalance[m_account.id()].isZero()) + showNegative = !showNegative; + if(showNegative) { + m_rightSummaryLabel->setPaletteForegroundColor(KMyMoneyGlobalSettings::listNegativeValueColor()); + } + } else { + m_centerSummaryLabel->hide(); + MyMoneyMoney balance; + MyMoneySecurity base = file->baseCurrency(); + QMap<QString, MyMoneyMoney>::const_iterator it_b; + bool approx = false; + for(it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { + MyMoneyAccount stock = file->account(it_b.key()); + QString currencyId = stock.currencyId(); + MyMoneySecurity sec = file->security(currencyId); + MyMoneyPrice priceInfo; + MyMoneyMoney rate(1,1); + + if(stock.isInvest()) { + currencyId = sec.tradingCurrency(); + priceInfo = file->price(sec.id(), currencyId); + approx |= !priceInfo.isValid(); + rate = priceInfo.rate(sec.tradingCurrency()); + } + + if(currencyId != base.id()) { + priceInfo = file->price(sec.tradingCurrency(), base.id()); + approx |= !priceInfo.isValid(); + rate = (rate * priceInfo.rate(base.id())).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())); + } + balance += ((*it_b) * rate).convert(base.smallestAccountFraction()); + } + m_rightSummaryLabel->setText(i18n("Investment value: %1%2").arg(approx ? "~" : "").arg(balance.formatMoney(base.tradingSymbol(), d->m_precision))); + } + } +} + +void KGlobalLedgerView::slotUpdateViewPos(void) +{ + m_register->setUpdatesEnabled(true); + + if(d->m_startPoint == QPoint(-1, -1)) { + m_register->ensureItemVisible(m_register->focusItem()); + m_register->updateContents(); + } else { + m_register->setContentsPos(d->m_startPoint.x(), d->m_startPoint.y()); + m_register->repaintContents(); + } + d->m_inLoading = false; +} + + +void KGlobalLedgerView::resizeEvent(QResizeEvent* ev) +{ + m_register->resize(KMyMoneyRegister::DetailColumn); + m_form->resize(KMyMoneyTransactionForm::ValueColumn1); + KMyMoneyViewBase::resizeEvent(ev); +} + +void KGlobalLedgerView::loadAccounts(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + // check if the current account still exists and make it the + // current account + if(!m_account.id().isEmpty()) { + try { + m_account = file->account(m_account.id()); + } catch(MyMoneyException *e) { + delete e; + m_account = MyMoneyAccount(); + } + } + + m_accountComboBox->loadList((KMyMoneyUtils::categoryTypeE)(KMyMoneyUtils::asset | KMyMoneyUtils::liability)); + + if(m_account.id().isEmpty()) { + QStringList list = m_accountComboBox->accountList(); + if(list.count()) { + QStringList::Iterator it; + for(it = list.begin(); it != list.end(); ++it) { + MyMoneyAccount a = file->account(*it); + if(!a.isInvest()) { + if(a.value("PreferredAccount") == "Yes") { + m_account = a; + break; + } else if(m_account.id().isEmpty()) { + m_account = a; + } + } + } + } + } + + if(!m_account.id().isEmpty()) { + m_accountComboBox->setSelected(m_account); + try { + d->m_precision = MyMoneyMoney::denomToPrec(m_account.fraction()); + } catch(MyMoneyException *e) { + qDebug("Security %s for account %s not found", m_account.currencyId().data(), m_account.name().data()); + delete e; + d->m_precision = 2; + } + } +} + +void KGlobalLedgerView::selectTransaction(const QString& id) +{ + if(!id.isEmpty()) { + KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); + while(p) { + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t) { + if(t->transaction().id() == id) { + m_register->selectItem(t); + m_register->ensureItemVisible(t); + break; + } + } + p = p->prevItem(); + } + } +} + +void KGlobalLedgerView::slotSelectAllTransactions(void) +{ + KMyMoneyRegister::RegisterItem* p = m_register->firstItem(); + while(p) { + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t) { + if(t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) { + t->setSelected(true); + } + } + p = p->nextItem(); + } + m_register->repaintItems(); + + // inform everyone else about the selected items + KMyMoneyRegister::SelectedTransactions list(m_register); + emit transactionsSelected(list); +} + +void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) +{ + if(d->m_reconciliationAccount != acc.id()) { + // make sure the account is selected + if(!acc.id().isEmpty()) + slotSelectAccount(acc.id()); + + d->m_reconciliationAccount = acc.id(); + d->m_reconciliationDate = reconciliationDate; + d->m_endingBalance = endingBalance; + if(acc.accountGroup() == MyMoneyAccount::Liability) + d->m_endingBalance = -endingBalance; + + m_newAccountLoaded = true; + if(acc.id().isEmpty()) { + kmymoney2->action("account_reconcile_postpone")->unplug(m_buttonbar); + kmymoney2->action("account_reconcile_finish")->unplug(m_buttonbar); + } else { + kmymoney2->action("account_reconcile_postpone")->plug(m_buttonbar); + kmymoney2->action("account_reconcile_finish")->plug(m_buttonbar); + // when we start reconciliation, we need to reload the view + // because no data has been changed. When postponing or finishing + // reconciliation, the data change in the engine takes care of updateing + // the view. + slotLoadView(); + } + } +} + +bool KGlobalLedgerView::isReconciliationAccount(void) const +{ + return m_account.id() == d->m_reconciliationAccount; +} + +bool KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj) +{ + if(typeid(obj) != typeid(MyMoneyAccount)) + return false; + + if(d->m_recursion) + return false; + + d->m_recursion = true; + const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj); + bool rc = slotSelectAccount(acc.id()); + d->m_recursion = false; + return rc; +} + +bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId) +{ + bool rc = true; + + if(!id.isEmpty()) { + if(m_account.id() != id) { + try { + m_account = MyMoneyFile::instance()->account(id); + // if a stock account is selected, we show the + // the corresponding parent (investment) account + if(m_account.isInvest()) { + m_account = MyMoneyFile::instance()->account(m_account.parentAccountId()); + } + m_newAccountLoaded = true; + slotLoadView(); + } catch(MyMoneyException* e) { + qDebug("Unable to retrieve account %s", id.data()); + delete e; + rc = false; + } + } else { + // we need to refresh m_account.m_accountList, a child could have been deleted + m_account = MyMoneyFile::instance()->account(id); + emit accountSelected(m_account); + } + selectTransaction(transactionId); + } + return rc; +} + +void KGlobalLedgerView::slotNewTransaction(KMyMoneyRegister::Action id) +{ + if(!m_inEditMode) { + d->m_action = id; + emit newTransaction(); + } +} + +void KGlobalLedgerView::slotNewTransaction(void) +{ + slotNewTransaction(KMyMoneyRegister::ActionNone); +} + +void KGlobalLedgerView::setupDefaultAction(void) +{ + switch(m_account.accountType()) { + // TODO if we want a different action for different account types + // we can add cases here + default: + d->m_action = KMyMoneyRegister::ActionWithdrawal; + break; + } +} + +bool KGlobalLedgerView::selectEmptyTransaction(void) +{ + bool rc = false; + + if(!m_inEditMode) { + // in case we don't know the type of transaction to be created, + // have at least one selected transaction and the id of + // this transaction is not empty, we take it as template for the + // transaction to be created + KMyMoneyRegister::SelectedTransactions list(m_register); + if((d->m_action == KMyMoneyRegister::ActionNone) && (list.count() > 0) && (!list[0].transaction().id().isEmpty())) { + // the new transaction to be created will have the same type + // as the one that currently has the focus + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(m_register->focusItem()); + if(t) + d->m_action = t->actionType(); + m_register->clearSelection(); + } + + // if we still don't have an idea which type of transaction + // to create, we use the default. + if(d->m_action == KMyMoneyRegister::ActionNone) { + setupDefaultAction(); + } + + m_register->selectItem(m_register->lastItem()); + m_register->updateRegister(); + rc = true; + } + return rc; +} + +TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) +{ + // we use the warnlevel to keep track, if we have to warn the + // user that some or all splits have been reconciled or if the + // user cannot modify the transaction if at least one split + // has the status frozen. The following value are used: + // + // 0 - no sweat, user can modify + // 1 - user should be warned that at least one split has been reconciled + // already + // 2 - user will be informed, that this transaction cannot be changed anymore + + int warnLevel = list.warnLevel(); + Q_ASSERT(warnLevel<2); // otherwise the edit action should not be enabled + + switch(warnLevel) { + case 0: + break; + + case 1: + if(KMessageBox::warningContinueCancel(0, + i18n( + "At least one split of the selected transactions has been reconciled. " + "Do you wish to continue to edit the transactions anyway?" + ), + i18n("Transaction already reconciled"), KStdGuiItem::cont(), + "EditReconciledTransaction") == KMessageBox::Cancel) { + warnLevel = 2; + } + break; + + case 2: + KMessageBox::sorry(0, + i18n("At least one split of the selected transactions has been frozen. " + "Editing the transactions is therefore prohibited."), + i18n("Transaction already frozen")); + break; + + case 3: + KMessageBox::sorry(0, + i18n("At least one split of the selected transaction references an account that has been closed. " + "Editing the transactions is therefore prohibited."), + i18n("Account closed")); + break; + } + + if(warnLevel > 1) + return 0; + + + TransactionEditor* editor = 0; + KMyMoneyRegister::Transaction* item = dynamic_cast<KMyMoneyRegister::Transaction*>(m_register->focusItem()); + + if(item) { + // in case the current focus item is not selected, we move the focus to the first selected transaction + if(!item->isSelected()) { + KMyMoneyRegister::RegisterItem* p; + for(p = m_register->firstItem(); p; p = p->nextItem()) { + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t && t->isSelected()) { + m_register->setFocusItem(t); + item = t; + break; + } + } + } + + // decide, if we edit in the register or in the form + TransactionEditorContainer* parent; + if(m_formFrame->isVisible()) + parent = m_form; + else { + parent = m_register; + } + + editor = item->createEditor(parent, list, m_lastPostDate); + + // check that we use the same transaction commodity in all selected transactions + // if not, we need to update this in the editor's list. The user can also bail out + // of this operation which means that we have to stop editing here. + if(editor) { + if(!editor->fixTransactionCommodity(m_account)) { + // if the user wants to quit, we need to destroy the editor + // and bail out + delete editor; + editor = 0; + } + } + + if(editor) { + if(parent == m_register) { + // make sure, the height of the table is correct + m_register->updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm()); + } + + m_inEditMode = true; + connect(editor, SIGNAL(transactionDataSufficient(bool)), kmymoney2->action("transaction_enter"), SLOT(setEnabled(bool))); + connect(editor, SIGNAL(returnPressed()), kmymoney2->action("transaction_enter"), SLOT(activate())); + connect(editor, SIGNAL(escapePressed()), kmymoney2->action("transaction_cancel"), SLOT(activate())); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets())); + connect(editor, SIGNAL(finishEdit(const KMyMoneyRegister::SelectedTransactions&)), this, SLOT(slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions&))); + + connect(editor, SIGNAL(objectCreation(bool)), d->m_mousePressFilter, SLOT(setFilterDeactive(bool))); + connect(editor, SIGNAL(createPayee(const QString&, QString&)), kmymoney2, SLOT(slotPayeeNew(const QString&, QString&))); + connect(editor, SIGNAL(createCategory(MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, SLOT(slotCategoryNew(MyMoneyAccount&, const MyMoneyAccount&))); + connect(editor, SIGNAL(createSecurity(MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, SLOT(slotInvestmentNew(MyMoneyAccount&, const MyMoneyAccount&))); + connect(editor, SIGNAL(assignNumber(void)), kmymoney2, SLOT(slotTransactionAssignNumber())); + connect(editor, SIGNAL(lastPostDateUsed(const QDate&)), this, SLOT(slotKeepPostDate(const QDate&))); + + // create the widgets, place them in the parent and load them with data + // setup tab order + m_tabOrderWidgets.clear(); + editor->setup(m_tabOrderWidgets, m_account, d->m_action); + + Q_ASSERT(!m_tabOrderWidgets.isEmpty()); + + // install event filter in all taborder widgets + for(QWidget* w = m_tabOrderWidgets.first(); w; w = m_tabOrderWidgets.next()) { + w->installEventFilter(this); + } + + // Install a filter that checks if a mouse press happened outside + // of one of our own widgets. + qApp->installEventFilter(d->m_mousePressFilter); + + // Check if the editor has some preference on where to set the focus + // If not, set the focus to the first widget in the tab order + QWidget* focusWidget = editor->firstWidget(); + if(!focusWidget) + focusWidget = m_tabOrderWidgets.first(); + + // for some reason, this only works reliably if delayed a bit + QTimer::singleShot(10, focusWidget, SLOT(setFocus())); + + // preset to 'I have no idea which type to create' for the next round. + d->m_action = KMyMoneyRegister::ActionNone; + } + } + return editor; +} + +void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list) +{ + m_inEditMode = false; + qApp->removeEventFilter(d->m_mousePressFilter); + + // a possible focusOut event may have removed the focus, so we + // install it back again. + m_register->focusItem()->setFocus(true); + + // if we come back from editing a new item, we make sure that + // we always select the very last known transaction entry no + // matter if the transaction has been created or not. + + if(list.count() && list[0].transaction().id().isEmpty()) { + // block signals to prevent some infinite loops that might occur here. + m_register->blockSignals(true); + m_register->clearSelection(); + KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); + if(p && p->prevItem()) + p = p->prevItem(); + m_register->selectItem(p); + m_register->updateRegister(true); + m_register->blockSignals(false); + // we need to update the form manually as sending signals was blocked + KMyMoneyRegister::Transaction* t = dynamic_cast<KMyMoneyRegister::Transaction*>(p); + if(t) + m_form->slotSetTransaction(t); + } + + if(m_needReload) + slotLoadView(); + + m_register->setFocus(); +} + +bool KGlobalLedgerView::focusNextPrevChild(bool next) +{ + bool rc = false; + + // qDebug("KGlobalLedgerView::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false"); + if(m_inEditMode) { + QWidget *w = 0; + QWidget *currentWidget; + + w = qApp->focusWidget(); + while(w && m_tabOrderWidgets.find(w) == -1) { + // qDebug("'%s' not in list, use parent", w->className()); + w = w->parentWidget(); + } + // if(w) qDebug("tab order is at '%s'", w->className()); + currentWidget = m_tabOrderWidgets.current(); + w = next ? m_tabOrderWidgets.next() : m_tabOrderWidgets.prev(); + + do { + if(!w) { + w = next ? m_tabOrderWidgets.first() : m_tabOrderWidgets.last(); + } + + if(w != currentWidget + && ((w->focusPolicy() & TabFocus) == TabFocus) + && w->isVisible() && w->isEnabled()) { + // qDebug("Selecting '%s' as focus", w->className()); + w->setFocus(); + rc = true; + break; + } + w = next ? m_tabOrderWidgets.next() : m_tabOrderWidgets.prev(); + } while(w != currentWidget); + + } else + rc = KMyMoneyViewBase::focusNextPrevChild(next); + + return rc; +} + + +void KGlobalLedgerView::show(void) +{ + if(m_needReload) { + if(!m_inEditMode) { + loadView(); + m_needReload = false; + m_newAccountLoaded = false; + } + + } else { + emit accountSelected(m_account); + KMyMoneyRegister::SelectedTransactions list(m_register); + emit transactionsSelected(list); + } + + // don't forget base class implementation + KMyMoneyViewBase::show(); +} + +bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e) +{ + bool rc = false; + + if(e->type() == QEvent::KeyPress) { + QKeyEvent *k = static_cast<QKeyEvent*>(e); + if(m_inEditMode) { + // qDebug("object = %s, key = %d", o->className(), k->key()); + if(o == m_register) { + // we hide all key press events from the register + // while editing a transaction + rc = true; + } + } else { + // qDebug("object = %s, key = %d", o->className(), k->key()); + if(o == m_register) { + if((k->state() & Qt::KeyButtonMask) == 0) { + switch(k->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + kmymoney2->action("transaction_edit")->activate(); + rc = true; + break; + } + } + } + } + } + + if(!rc) + rc = KMyMoneyViewBase::eventFilter(o, e); + + return rc; +} + +void KGlobalLedgerView::slotSortOptions(void) +{ + KSortOptionDlg* dlg = new KSortOptionDlg(this); + + QString key; + QString sortOrder, def; + if(isReconciliationAccount()) { + key = "kmm-sort-reconcile"; + def = KMyMoneyGlobalSettings::sortReconcileView(); + } else { + key = "kmm-sort-std"; + def = KMyMoneyGlobalSettings::sortNormalView(); + } + + // check if we have an account override of the sort order + if(!m_account.value(key).isEmpty()) + sortOrder = m_account.value(key); + + QString oldOrder = sortOrder; + + dlg->setSortOption(sortOrder, def); + + if(dlg->exec() == QDialog::Accepted) { + sortOrder = dlg->sortOption(); + if(sortOrder != oldOrder) { + if(sortOrder.isEmpty()) { + m_account.deletePair(key); + } else { + m_account.setValue(key, sortOrder); + } + MyMoneyFileTransaction ft; + try { + MyMoneyFile::instance()->modifyAccount(m_account); + ft.commit(); + } catch(MyMoneyException* e) { + qDebug("Unable to update sort order for account '%s': %s", m_account.name().latin1(), e->what().latin1()); + delete e; + } + } + } + delete dlg; +} + +void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */) +{ + if(!m_inEditMode) { + emit toggleReconciliationFlag(); + } +} + +void KGlobalLedgerView::slotKeepPostDate(const QDate& date) +{ + m_lastPostDate = date; +} + +bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const +{ + bool rc = true; + if(m_account.id().isEmpty()) { + tooltip = i18n("Cannot create transactions when no account is selected."); + rc = false; + } + if(m_account.accountGroup() == MyMoneyAccount::Income + || m_account.accountGroup() == MyMoneyAccount::Expense) { + tooltip = i18n("Cannot create transactions in the context of a category."); + rc = false; + } + if(m_account.isClosed()) { + tooltip = i18n("Cannot create transactions in a closed account."); + rc = false; + } + return rc; +} + +bool KGlobalLedgerView::canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + if(m_register->focusItem() == 0) + return false; + + if(!m_register->focusItem()->isSelected()) { + tooltip = i18n("Cannot process transaction with focus if it is not selected."); + return false; + } + return list.count() > 0; +} + +bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + return canProcessTransactions(list,tooltip) && list.canModify(); +} + +bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + return canProcessTransactions(list,tooltip) && list.canDuplicate(); +} + +bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + // check if we can edit the list of transactions. We can edit, if + // + // a) no mix of standard and investment transactions exist + // b) if a split transaction is selected, this is the only selection + // c) none of the splits is frozen + // d) the transaction having the current focus is selected + + // check for d) + if(!canProcessTransactions(list, tooltip)) + return false; + // check for c) + if (list.warnLevel() == 2) { + tooltip = i18n("Cannot edit transactions with frozen splits."); + return false; + } + + + bool rc = true; + int investmentTransactions = 0; + int normalTransactions = 0; + + if(m_account.accountGroup() == MyMoneyAccount::Income + || m_account.accountGroup() == MyMoneyAccount::Expense) { + tooltip = i18n("Cannot edit transactions in the context of a category."); + rc = false; + } + + KMyMoneyRegister::SelectedTransactions::const_iterator it_t; + for(it_t = list.begin(); rc && it_t != list.end(); ++it_t) { + if((*it_t).transaction().id().isEmpty()) { + tooltip = QString(); + rc = false; + continue; + } + + if(KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) + ++investmentTransactions; + else + ++normalTransactions; + + // check for a) + if(investmentTransactions != 0 && normalTransactions != 0) { + tooltip = i18n("Cannot edit investment transactions and non-investment transactions together."); + rc = false; + break; + } + + // check for b) but only for normalTransactions + if((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) { + if(list.count() > 1) { + tooltip = i18n("Cannot edit multiple split transactions at once."); + rc = false; + break; + } + } + } + + // now check that we have the correct account type for investment transactions + if(rc == true && investmentTransactions != 0) { + if(m_account.accountType() != MyMoneyAccount::Investment) { + tooltip = i18n("Cannot edit investment transactions in the context of this account."); + rc = false; + } + } + return rc; +} + +#include "kgloballedgerview.moc" diff --git a/kmymoney2/views/kgloballedgerview.h b/kmymoney2/views/kgloballedgerview.h new file mode 100644 index 0000000..861bd0e --- /dev/null +++ b/kmymoney2/views/kgloballedgerview.h @@ -0,0 +1,403 @@ +/*************************************************************************** + kgloballedgerview.h - description + ------------------- + begin : Sat Jul 13 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KACCOUNTVIEW_H +#define KACCOUNTVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qwidget.h> +#include <qwidgetlist.h> +#include <qstring.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyutils.h> +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/kmymoneyview.h> +#include <kmymoney/register.h> +#include <kmymoney/transactionform.h> + +class QVBoxLayout; +class QHBoxLayout; +class QGridLayout; +class QPopupMenu; +class QFrame; +class QLabel; + +class KMyMoneyAccountCombo; +class KToolBar; +class KToolBarButton; +class MyMoneyReport; +class TransactionEditor; + +/** + * helper class implementing an event filter to detect mouse button press + * events on widgets outside a given set of widgets. This is used internally + * to detect when to leave the edit mode. + */ +class MousePressFilter : public QObject +{ + Q_OBJECT +public: + MousePressFilter(QWidget* parent = 0, const char* name = 0); + + /** + * Add widget @p w to the list of possible parent objects. See eventFilter() how + * they will be used. + */ + void addWidget(QWidget* w); + +public slots: + /** + * This slot allows to activate/deactive the filter. By default the + * filter is active. + * + * @param state Allows to activate (@a true) or deactivate (@a false) the filter + */ + void setFilterActive(bool state = true); + + /** + * This slot allows to activate/deactive the filter. By default the + * filter is active. + * + * @param state Allows to deactivate (@a true) or activate (@a false) the filter + */ + void setFilterDeactive(bool state = false) { setFilterActive(!state); } + +protected: + /** + * This method checks if the widget @p child is a child of + * the widget @p parent and returns either @a true or @a false. + * + * @param child pointer to child widget + * @param parent pointer to parent widget + * @retval true @p child points to widget which has @p parent as parent or grand-parent + * @retval false @p child points to a widget which is not related to @p parent + */ + bool isChildOf(QWidget* child, QWidget* parent); + + /** + * Reimplemented from base class. Sends out the mousePressedOnExternalWidget() signal + * if object @p o points to an object which is not a child widget of any added previously + * using the addWidget() method. The signal is sent out only once for each event @p e. + * + * @param o pointer to QObject + * @param e pointer to QEvent + * @return always returns @a false + */ + bool eventFilter(QObject* o, QEvent* e); + +signals: + void mousePressedOnExternalWidget(bool&); + +private: + QValueList<QWidget*> m_parents; + QEvent* m_lastMousePressEvent; + bool m_filterActive; +}; + +/** + * @author Thomas Baumgart + */ +class KGlobalLedgerView : public KMyMoneyViewBase +{ + Q_OBJECT +public: + KGlobalLedgerView(QWidget *parent=0, const char *name=0); + ~KGlobalLedgerView(); + + /** + * This method returns the id of the currently selected account + * or QString() if none is selected. + */ + const QString accountId(void) const { return m_account.id(); } + + /** + * Checks if new transactions can be created in the current context + * + * @param tooltip reference to string receiving the tooltip text + * which explains why the modify function is not available (in case + * of returning @c false) + * + * @retval true Yes, view allows to create transactions (tooltip is not changed) + * @retval false No, view does not support creation of transactions (tooltip is updated with message) + */ + bool canCreateTransactions(QString& tooltip) const; + + /** + * Checks if a list of transactions can be modified (edit/delete) in the current context + * + * @param list list of selected transactions + * @param tooltip reference to string receiving the tooltip text + * which explains why the modify function is not available (in case + * of returning @c false) + * + * @retval true Yes, view allows to edit/delete transactions (tooltip is not changed) + * @retval false No, view cannot edit/delete transactions (tooltip is updated with message) + */ + bool canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + bool canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + /** + * Checks if the list of transactions can be edited in the current context + * + * @param list list of selected transactions + * @param tooltip reference to string receiving the tooltip text + * which explains why the edit function is not available (in case + * of returning @c false) + * + * @return @c true if edit operation is possible, @c false if not + */ + bool canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions& list); + + /** + * Method to prepare the ledger view to create a new transaction. + * Returns if successful or not. + * + * retval true Emtpy transaction selected. + * retval false Not successful (e.g. already editing a transaction) + */ + bool selectEmptyTransaction(void); + +public slots: + void show(void); + + /** + * This method loads the view with data from the MyMoney engine. + */ + void slotLoadView(void); + + /** + * This slot is used to select the correct ledger view type for + * the account specified by @p id in a specific mode. + * + * @param accountId Internal id used for the account to show + * @param transactionId Internal id used for the transaction to select. + * Default is QString() which will select the last + * transaction in the ledger if not the same account + * + * @retval true selection of account referenced by @p id succeeded + * @retval false selection of account failed + */ + bool slotSelectAccount(const QString& accountId, const QString& transactionId = QString()); + + /** + * This method is provided for convenience and acts as the method above. + */ + bool slotSelectAccount(const MyMoneyObject& acc); + + /** + * Switch to reconciliation mode for account @a account. + * If @a account is MyMoneyAccount() (the default), reconciliation mode + * is turned off. + * + * @param account account for which reconciliation mode is activated. + * Default is MyMoneyAccount(). + * @param reconciliationDate date of statment + * @param endingBalance The calculated ending balance for the statement + * Default ist 0. + */ + void slotSetReconcileAccount(const MyMoneyAccount& account = MyMoneyAccount(), const QDate& reconciliationDate = QDate(), const MyMoneyMoney& endingBalance = MyMoneyMoney()); + + /** + * Select all transactions in the ledger that are not hidden. + */ + void slotSelectAllTransactions(void); + +protected: + /** + * This method reloads the account selection combo box of the + * view with all asset and liability accounts from the engine. + * If the account id of the current account held in @p m_accountId is + * empty or if the referenced account does not exist in the engine, + * the first account found in the list will be made the current account. + */ + void loadAccounts(void); + + /** + * This method clears the register, form, transaction list. See @sa m_register, + * @sa m_transactionList + */ + void clear(void); + + void loadView(void); + + void resizeEvent(QResizeEvent*); + + void selectTransaction(const QString& id); + + /** + * This method handles the focus of the keyboard. When in edit mode + * (m_inEditMode is true) the keyboard focus is handled + * according to the widgets that are referenced in m_tabOrderWidgets. + * If not in edit mode, the base class functionality is provided. + * + * @param next true if forward-tab, false if backward-tab was + * pressed by the user + */ + bool focusNextPrevChild(bool next); + + bool eventFilter(QObject* o, QEvent* e); + + /** + * Returns @a true if setReconciliationAccount() has been called for + * the current loaded account. + * + * @retval true current account is in reconciliation mode + * @retval false current account is not in reconciliation mode + */ + bool isReconciliationAccount(void) const; + + /** + * Updates the values on the summary line beneath the register with + * the given values. The contents shown differs between reconciliation + * mode and normal mode. + * + * @param actBalance map of account indexed values to be used as actual balance + * @param clearedBalance map of account indexed values to be used as cleared balance + */ + void updateSummaryLine(const QMap<QString, MyMoneyMoney>& actBalance, const QMap<QString, MyMoneyMoney>& clearedBalance); + + /** + * setup the default action according to the current account type + */ + void setupDefaultAction(void); + +protected slots: + void slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list); + void slotNewTransaction(void); + void slotNewTransaction(KMyMoneyRegister::Action); + + /** + * Sets the contentsPos of the register to d->m_startPoint or makes + * the focus item visible if d->m_startPoint equals QPoint(-1, -1). + */ + void slotUpdateViewPos(void); + void slotSortOptions(void); + void slotToggleTransactionMark(KMyMoneyRegister::Transaction* t); + + void slotKeepPostDate(const QDate&); + + void slotAboutToSelectItem(KMyMoneyRegister::RegisterItem*, bool&); + +protected: + /** + * This member keeps the date that was used as the last posting date. + * It will be updated whenever the user modifies the post date + * and is used to preset the posting date when new transactions are created. + * This member is initialised to the current date when the program is started. + */ + static QDate m_lastPostDate; + +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + + // frames + QFrame* m_toolbarFrame; + QFrame* m_registerFrame; + QFrame* m_buttonFrame; + QFrame* m_formFrame; + QFrame* m_summaryFrame; + + // widgets + KMyMoneyAccountCombo* m_accountComboBox; + KMyMoneyRegister::Register* m_register; + KToolBar* m_toolbar; + KToolBar* m_buttonbar; + + /** + * This member holds the currently selected account + */ + MyMoneyAccount m_account; + + /** + * This member holds the transaction list + */ + QValueList<QPair<MyMoneyTransaction, MyMoneySplit> > m_transactionList; + + QLabel* m_leftSummaryLabel; + QLabel* m_centerSummaryLabel; + QLabel* m_rightSummaryLabel; + + KMyMoneyTransactionForm::TransactionForm* m_form; + + bool m_needReload; + bool m_newAccountLoaded; + bool m_inEditMode; + + QWidgetList m_tabOrderWidgets; + +signals: + void accountSelected(const MyMoneyObject&); + void transactionsSelected(const KMyMoneyRegister::SelectedTransactions&); + void newTransaction(void); + void startEdit(void); + void endEdit(void); + void cancelOrEndEdit(bool&); + + /** + * This signal is emitted, when a new report has been generated. A + * 'generated' report is halfway between a default report and a custom + * report. It's created by the system in response to the user's + * request, and it's usually filtered to be a little more specific + * than the usual default reports. + * + * The proper behaviour when getting this signal is to switch to the + * reports view and display the report. But it should NOT be added + * to the data file, unless the user customizes it further. That's + * because the user can always come back to the ledger UI to generate + * the report again. + * + * @param report reference to MyMoneyReport object that contains the report + * details + */ + void reportGenerated(const MyMoneyReport& report); + + void openContextMenu(void); + + /** + * This signal is sent out, when the current selected transaction should + * be marked different + */ + void toggleReconciliationFlag(void); + +private: + bool canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; +}; + +#endif +// vim:cin:si:ai:et:ts=2:sw=2: diff --git a/kmymoney2/views/khomeview.cpp b/kmymoney2/views/khomeview.cpp new file mode 100644 index 0000000..95b7323 --- /dev/null +++ b/kmymoney2/views/khomeview.cpp @@ -0,0 +1,1940 @@ +/*************************************************************************** + khomeview.cpp - description + ------------------- + begin : Tue Jan 22 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qlayout.h> +#include <qdatetime.h> +#include <qapplication.h> +#include <dom/dom_element.h> +#include <dom/dom_doc.h> +#include <dom/dom_text.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qtimer.h> +#include <qbuffer.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kglobal.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <khtmlview.h> +#include <kconfig.h> +#include <kstdaction.h> +#include <kmainwindow.h> +#include <kactioncollection.h> +#include <kapplication.h> +#include <kmessagebox.h> +#include <kdebug.h> +#include <kmdcodec.h> +#include <kglobalsettings.h> +#include <kiconloader.h> + +// ---------------------------------------------------------------------------- +// Project Includes +#include "khomeview.h" +#include "../kmymoneyutils.h" +#include "../kmymoneyglobalsettings.h" +#include "../mymoney/mymoneyfile.h" +#include "../mymoney/mymoneyforecast.h" +#include "../kmymoney2.h" +#include "../reports/kreportchartview.h" +#include "../reports/pivottable.h" +#include "../reports/pivotgrid.h" +#include "../reports/reportaccount.h" +#include "../kmymoneyglobalsettings.h" + + +#define VIEW_LEDGER "ledger" +#define VIEW_SCHEDULE "schedule" +#define VIEW_WELCOME "welcome" +#define VIEW_HOME "home" +#define VIEW_REPORTS "reports" + +// in KOffice version < 1.5 KDCHART_PROPSET_NORMAL_DATA was a static const +// but in 1.5 this has been changed into a #define'd value. So we have to +// make sure, we use the right one. +#ifndef KDCHART_PROPSET_NORMAL_DATA +#define KMM_KDCHART_PROPSET_NORMAL_DATA KDChartParams::KDCHART_PROPSET_NORMAL_DATA +#else +#define KMM_KDCHART_PROPSET_NORMAL_DATA KDCHART_PROPSET_NORMAL_DATA +#endif + +using namespace reports; + +class KHomeView::Private +{ + public: + Private() {} + void addNameIndex(QMap<QString, MyMoneyAccount> &idx, const MyMoneyAccount& account); +}; + +void KHomeView::Private::addNameIndex(QMap<QString, MyMoneyAccount> &idx, const MyMoneyAccount& account) +{ + QString key = account.name(); + + if(idx[key].id().isEmpty()) { + idx[key] = account; + //take care of accounts with duplicate names + } else if(idx[key].id() != account.id()) { + key = account.name() + "[%1]"; + int dup = 2; + while(!idx[key.arg(dup)].id().isEmpty() + && idx[key.arg(dup)].id() != account.id()) + ++dup; + idx[key.arg(dup)] = account; + } +} + +KHomeView::KHomeView(QWidget *parent, const char *name ) : + KMyMoneyViewBase(parent, name, i18n("Home")), + d(new Private), + m_showAllSchedules(false), + m_needReload(true) +{ + m_part = new KHTMLPart(this, "htmlpart_km2"); + addWidget(m_part->view()); + + m_filename = KMyMoneyUtils::findResource("appdata", QString("html/home%1.html")); + +// m_part->openURL(m_filename); + connect(m_part->browserExtension(), SIGNAL(openURLRequest(const KURL&, const KParts::URLArgs&)), + this, SLOT(slotOpenURL(const KURL&, const KParts::URLArgs&))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); +} + +KHomeView::~KHomeView() +{ + // if user wants to remember the font size, store it here + if (KMyMoneyGlobalSettings::rememberFontSize()) + { + KMyMoneyGlobalSettings::setFontSizePercentage(m_part->zoomFactor()); + //kdDebug() << "Storing font size: " << m_part->zoomFactor() << endl; + KMyMoneyGlobalSettings::self()->writeConfig(); + } + delete d; +} + +void KHomeView::slotLoadView(void) +{ + m_needReload = true; + if(isVisible()) { + loadView(); + m_needReload = false; + } +} + +void KHomeView::show(void) +{ + if(m_needReload) { + loadView(); + m_needReload = false; + } + QWidget::show(); +} + +void KHomeView::slotPrintView(void) +{ + if(m_part && m_part->view()) + m_part->view()->print(); +} + +void KHomeView::loadView(void) +{ + m_part->setZoomFactor( KMyMoneyGlobalSettings::fontSizePercentage() ); + //kdDebug() << "Setting font size: " << m_part->zoomFactor() << endl; + + QValueList<MyMoneyAccount> list; + MyMoneyFile::instance()->accountList(list); + if(list.count() == 0) + { + m_part->openURL(m_filename); + +#if 0 + // (ace) I am experimenting with replacing links in the + // html depending on the state of the engine. It's not + // working. That's why it's #if0'd out. + + DOM::Element e = m_part->document().getElementById("test"); + if ( e.isNull() ) + { + qDebug("Element id=test not found"); + } + else + { + qDebug("Element id=test found!"); + QString tagname = e.tagName().string(); + qDebug("%s",tagname.latin1()); + qDebug("%s id=%s",e.tagName().string().latin1(),e.getAttribute("id").string().latin1()); + + // Find the character data node + DOM::Node n = e.firstChild(); + while (!n.isNull()) + { + qDebug("Child type %u",static_cast<unsigned>(n.nodeType())); + if ( n.nodeType() == DOM::Node::TEXT_NODE ) + { + DOM::Text t = n; + t.setData("Success!!"); + e.replaceChild(n,t); + m_part->document().setDesignMode(true); + m_part->document().importNode(e,true); + m_part->document().updateRendering(); + + qDebug("Data is now %s",t.data().string().latin1()); + } + n = n.nextSibling(); + } + } +#endif + } else { + //clear the forecast flag so it will be reloaded + m_forecast.setForecastDone(false); + + QString filename = KGlobal::dirs()->findResource("appdata", "html/kmymoney2.css"); + QString header = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"%1\">\n").arg(filename); + + header += KMyMoneyUtils::variableCSS(); + + header += "</head><body id=\"summaryview\">\n"; + + QString footer = "</body></html>\n"; + + m_part->begin(); + m_part->write(header); + + m_part->write(QString("<div id=\"summarytitle\">%1</div>").arg(i18n("Your Financial Summary"))); + + QStringList settings = KMyMoneyGlobalSettings::itemList(); + + QStringList::ConstIterator it; + + for(it = settings.begin(); it != settings.end(); ++it) { + int option = (*it).toInt(); + if(option > 0) { + switch(option) { + case 1: // payments + showPayments(); + break; + + case 2: // preferred accounts + showAccounts(Preferred, i18n("Preferred Accounts")); + break; + + case 3: // payment accounts + // Check if preferred accounts are shown separately + if(settings.find("2") == settings.end()) { + showAccounts(static_cast<paymentTypeE> (Payment | Preferred), + i18n("Payment Accounts")); + } else { + showAccounts(Payment, i18n("Payment Accounts")); + } + break; + case 4: // favorite reports + showFavoriteReports(); + break; + case 5: // forecast + showForecast(); + break; + case 6: // net worth graph over all accounts + showNetWorthGraph(); + break; + case 8: // assets and liabilities + showAssetsLiabilities(); + break; + case 9: // budget + showBudget(); + break; + case 10: // cash flow summary + showCashFlowSummary(); + break; + + + } + m_part->write("<div class=\"gap\"> </div>\n"); + } + } + + m_part->write("<div id=\"returnlink\">"); + m_part->write(link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend()); + m_part->write("</div>"); + m_part->write("<div id=\"vieweffect\"></div>"); + m_part->write(footer); + m_part->end(); + + } +} + +void KHomeView::showNetWorthGraph(void) +{ +#ifdef HAVE_KDCHART + m_part->write(QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\"> </div>\n").arg(i18n("Networth Forecast"))); + + MyMoneyReport reportCfg = MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below + MyMoneyReport::eDetailTotal, + i18n("Networth Forecast"), + i18n("Generated Report")); + + reportCfg.setChartByDefault(true); + reportCfg.setChartGridLines(false); + reportCfg.setChartDataLabels(false); + reportCfg.setChartType(MyMoneyReport::eChartLine); + reportCfg.setIncludingSchedules( false ); + reportCfg.addAccountGroup(MyMoneyAccount::Asset); + reportCfg.addAccountGroup(MyMoneyAccount::Liability); + reportCfg.setColumnsAreDays( true ); + reportCfg.setConvertCurrency( true ); + reportCfg.setIncludingForecast( true ); + reportCfg.setDateFilter(QDate::currentDate(),QDate::currentDate().addDays(+90)); + + reports::PivotTable table(reportCfg); + + reports::KReportChartView* chartWidget = new reports::KReportChartView(0, 0); + + table.drawChart(*chartWidget); + + chartWidget->params()->setLineMarker(false); + chartWidget->params()->setLegendPosition(KDChartParams::NoLegend); + chartWidget->params()->setLineWidth(2); + chartWidget->params()->setDataColor(0, KGlobalSettings::textColor()); + + // draw future values in a different line style + KDChartPropertySet propSetFutureValue("future value", KMM_KDCHART_PROPSET_NORMAL_DATA); + propSetFutureValue.setLineStyle(KDChartPropertySet::OwnID, Qt::DotLine); + const int idPropFutureValue = chartWidget->params()->registerProperties(propSetFutureValue); + + //KDChartPropertySet propSetLastValue("last value", idPropFutureValue); + //propSetLastValue.setExtraLinesAlign(KDChartPropertySet::OwnID, Qt::AlignLeft | Qt::AlignBottom); + //propSetLastValue.setExtraLinesWidth(KDChartPropertySet::OwnID, -4); + //propSetLastValue.setExtraLinesColor(KDChartPropertySet::OwnID, KMyMoneyGlobalSettings::listGridColor()); + // propSetLastValue.setShowMarker(KDChartPropertySet::OwnID, true); + // propSetLastValue.setMarkerStyle(KDChartPropertySet::OwnID, KDChartParams::LineMarkerDiamond); + + //const int idPropLastValue = chartWidget->params()->registerProperties(propSetLastValue); + for(int iCell = 0; iCell < 90; ++iCell) { + chartWidget->setProperty(0, iCell, idPropFutureValue); + } + //chartWidget->setProperty(0, 10, idPropLastValue); + + // Adjust the size + if(width() < chartWidget->width()) { + int nh; + nh = (width()*chartWidget->height() ) / chartWidget->width(); + chartWidget->resize(width()-80, nh); + } + + QPixmap pm(chartWidget->width(), chartWidget->height()); + pm.fill(KGlobalSettings::baseColor()); + QPainter p(&pm); + chartWidget->paintTo(p); + + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + pm.save( &buffer, "PNG" ); // writes pixmap into ba in PNG format + + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr>"); + m_part->write(QString("<td><center><IMG SRC=\"data:image/png;base64,%1\" ALT=\"Networth\"></center></td>").arg(KCodecs::base64Encode(ba))); + m_part->write("</tr>"); + m_part->write("</table></div></div>"); + + delete chartWidget; +#endif +} + +void KHomeView::showPayments(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneySchedule> overdues; + QValueList<MyMoneySchedule> schedule; + int i = 0; + + //if forecast has not been executed yet, do it. + if(!m_forecast.isForecastDone()) + doForecast(); + + schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, + MyMoneySchedule::OCCUR_ANY, + MyMoneySchedule::STYPE_ANY, + QDate::currentDate(), + QDate::currentDate().addMonths(1)); + overdues = file->scheduleList("", MyMoneySchedule::TYPE_ANY, + MyMoneySchedule::OCCUR_ANY, + MyMoneySchedule::STYPE_ANY, + QDate(), QDate(), true); + + if(schedule.empty() && overdues.empty()) + return; + + // HACK + // Remove the finished schedules + + QValueList<MyMoneySchedule>::Iterator d_it; + for (d_it=schedule.begin(); d_it!=schedule.end();) + { + // FIXME cleanup old code + // if ((*d_it).isFinished() || (*d_it).nextPayment((*d_it).lastPayment()) == QDate()) + if ((*d_it).isFinished()) + { + d_it = schedule.remove(d_it); + continue; + } + ++d_it; + } + + for (d_it=overdues.begin(); d_it!=overdues.end();) + { + // FIXME cleanup old code + // if ((*d_it).isFinished() || (*d_it).nextPayment((*d_it).lastPayment()) == QDate()) + if ((*d_it).isFinished()) + { + d_it = overdues.remove(d_it); + continue; + } + ++d_it; + } + + m_part->write("<div class=\"shadow\"><div class=\"displayblock\">"); + m_part->write(QString("<div class=\"summaryheader\">%1</div>\n").arg(i18n("Payments"))); + + if(overdues.count() > 0) { + m_part->write("<div class=\"gap\"> </div>\n"); + + qBubbleSort(overdues); + QValueList<MyMoneySchedule>::Iterator it; + QValueList<MyMoneySchedule>::Iterator it_f; + + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write(QString("<tr class=\"itemtitle warningtitle\" ><td colspan=\"5\">%1</td></tr>\n").arg(showColoredAmount(i18n("Overdue payments"), true))); + m_part->write("<tr class=\"item warning\">"); + m_part->write("<td class=\"left\" width=\"10%\">"); + m_part->write(i18n("Date")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"40%\">"); + m_part->write(i18n("Schedule")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"20%\">"); + m_part->write(i18n("Account")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Amount")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Balance after")); + m_part->write("</td>"); + m_part->write("</tr>"); + + for(it = overdues.begin(); it != overdues.end(); ++it) { + // determine number of overdue payments + QDate nextDate = (*it).adjustedNextDueDate(); + int cnt = 0; + while(nextDate.isValid() && nextDate < QDate::currentDate()) { + ++cnt; + nextDate = (*it).nextPayment(nextDate); + // for single occurence nextDate will not change, so we + // better get out of here. + if((*it).occurence() == MyMoneySchedule::OCCUR_ONCE) + break; + } + + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + showPaymentEntry(*it, cnt); + m_part->write("</tr>"); + // make sure to not repeat overdues later again + for(it_f = schedule.begin(); it_f != schedule.end();) { + if((*it).id() == (*it_f).id()) { + it_f = schedule.remove(it_f); + continue; + } + ++it_f; + } + } + m_part->write("</table>"); + } + + if(schedule.count() > 0) { + qBubbleSort(schedule); + + // Extract todays payments if any + QValueList<MyMoneySchedule> todays; + QValueList<MyMoneySchedule>::Iterator t_it; + for (t_it=schedule.begin(); t_it!=schedule.end();) { + if ((*t_it).nextDueDate() == QDate::currentDate()) { + todays.append(*t_it); + (*t_it).setNextDueDate((*t_it).nextPayment((*t_it).nextDueDate())); + + //if nextDueDate is still currentDate then remove it from scheduled payments + if ((*t_it).nextDueDate() == QDate::currentDate()) { + t_it = schedule.remove(t_it); + continue; + } + } + ++t_it; + } + + if (todays.count() > 0) { + m_part->write("<div class=\"gap\"> </div>\n"); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write(QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"5\">%1</td></tr>\n").arg(i18n("Today's payments"))); + m_part->write("<tr class=\"item\">"); + m_part->write("<td class=\"left\" width=\"10%\">"); + m_part->write(i18n("Date")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"40%\">"); + m_part->write(i18n("Schedule")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"20%\">"); + m_part->write(i18n("Account")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Amount")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Balance after")); + m_part->write("</td>"); + m_part->write("</tr>"); + + for(t_it = todays.begin(); t_it != todays.end(); ++t_it) { + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + showPaymentEntry(*t_it); + m_part->write("</tr>"); + } + m_part->write("</table>"); + } + + if (schedule.count() > 0) + { + m_part->write("<div class=\"gap\"> </div>\n"); + + QValueList<MyMoneySchedule>::Iterator it; + + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write(QString("<tr class=\"itemtitle\"><td class=\"left\" colspan=\"5\">%1</td></tr>\n").arg(i18n("Future payments"))); + m_part->write("<tr class=\"item\">"); + m_part->write("<td class=\"left\" width=\"10%\">"); + m_part->write(i18n("Date")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"40%\">"); + m_part->write(i18n("Schedule")); + m_part->write("</td>"); + m_part->write("<td class=\"left\" width=\"20%\">"); + m_part->write(i18n("Account")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Amount")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"15%\">"); + m_part->write(i18n("Balance after")); + m_part->write("</td>"); + m_part->write("</tr>"); + + // show all or the first 6 entries + int cnt; + cnt = (m_showAllSchedules) ? -1 : 6; + bool needMoreLess = m_showAllSchedules; + + QDate lastDate = QDate::currentDate().addMonths(1); + qBubbleSort(schedule); + do { + it = schedule.begin(); + if(it == schedule.end()) + break; + + // if the next due date is invalid (schedule is finished) + // we remove it from the list + QDate nextDate = (*it).nextDueDate(); + if(!nextDate.isValid()) { + schedule.remove(it); + continue; + } + + if (nextDate > lastDate) + break; + + if(cnt == 0) { + needMoreLess = true; + break; + } + if(cnt > 0) + --cnt; + + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + showPaymentEntry(*it); + m_part->write("</tr>"); + + // for single occurence we have reported everything so we + // better get out of here. + if((*it).occurence() == MyMoneySchedule::OCCUR_ONCE) { + schedule.remove(it); + continue; + } + + (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); + qBubbleSort(schedule); + } + while(1); + + if (needMoreLess) { + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + m_part->write("<td>"); + if(m_showAllSchedules) { + m_part->write(link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18n("Less...") + linkend()); + } else { + m_part->write(link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18n("More...") + linkend()); + } + m_part->write("</td><td></td><td></td><td></td><td></td>"); + m_part->write("</tr>"); + } + m_part->write("</table>"); + } + } + m_part->write("</div></div>"); +} + +void KHomeView::showPaymentEntry(const MyMoneySchedule& sched, int cnt) +{ + QString tmp; + MyMoneyFile* file = MyMoneyFile::instance(); + + try { + MyMoneyAccount acc = sched.account(); + if(acc.id()) { + MyMoneyTransaction t = sched.transaction(); + // only show the entry, if it is still active + // FIXME clean old code + // if(!sched.isFinished() && sched.nextPayment(sched.lastPayment()) != QDate()) { + if(!sched.isFinished()) { + MyMoneySplit sp = t.splitByAccount(acc.id(), true); + + QString pathEnter, pathSkip; + KGlobal::iconLoader()->loadIcon("key_enter", KIcon::Small, KIcon::SizeSmall, KIcon::DefaultState, &pathEnter); + KGlobal::iconLoader()->loadIcon("player_fwd", KIcon::Small, KIcon::SizeSmall, KIcon::DefaultState, &pathSkip); + + //show payment date + tmp = QString("<td>") + + KGlobal::locale()->formatDate(sched.adjustedNextDueDate(), true) + + "</td><td>"; + if(pathEnter.length() > 0) + tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("<img src=\"file://%1\" border=\"0\"></a>").arg(pathEnter) + linkend(); + if(pathSkip.length() > 0) + tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("<img src=\"file://%1\" border=\"0\"></a>").arg(pathSkip) + linkend(); + + tmp += QString(" "); + tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); + + //show quantity of payments overdue if any + if(cnt > 1) + tmp += i18n(" (%1 payments)").arg(cnt); + + //show account of the main split + tmp += "</td><td>"; + tmp += QString(file->account(acc.id()).name()); + + //show amount of the schedule + tmp += "</td><td align=\"right\">"; + + const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); + QString amount = (sp.value()*cnt).formatMoney(acc, currency); + amount.replace(" "," "); + tmp += showColoredAmount(amount, (sp.value()*cnt).isNegative()) ; + tmp += "</td>"; + //show balance after payments + tmp += "<td align=\"right\">"; + MyMoneyMoney payment = MyMoneyMoney((sp.value()*cnt)); + QDate paymentDate = QDate(sched.nextDueDate()); + MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); + QString balance = balanceAfter.formatMoney(acc, currency); + balance.replace(" "," "); + tmp += showColoredAmount(balance, balanceAfter.isNegative()); + tmp += "</td>"; + + // qDebug("paymentEntry = '%s'", tmp.latin1()); + m_part->write(tmp); + } + } + } catch(MyMoneyException* e) { + qDebug("Unable to display schedule entry: %s", e->what().data()); + delete e; + } +} + +void KHomeView::showAccounts(KHomeView::paymentTypeE type, const QString& header) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneyAccount> accounts; + QValueList<MyMoneyAccount>::Iterator it; + QValueList<MyMoneyAccount>::Iterator prevIt; + QMap<QString, MyMoneyAccount> nameIdx; + + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked(); + + // get list of all accounts + file->accountList(accounts); + for(it = accounts.begin(); it != accounts.end();) { + prevIt = it; + if(!(*it).isClosed() || showClosedAccounts) { + switch((*it).accountType()) { + case MyMoneyAccount::Expense: + case MyMoneyAccount::Income: + // never show a category account + // Note: This might be different in a future version when + // the homepage also shows category based information + it = accounts.remove(it); + break; + + // Asset and Liability accounts are only shown if they + // have the preferred flag set + case MyMoneyAccount::Asset: + case MyMoneyAccount::Liability: + case MyMoneyAccount::Investment: + // if preferred accounts are requested, then keep in list + if((*it).value("PreferredAccount") != "Yes" + || (type & Preferred) == 0) { + it = accounts.remove(it); + } + break; + + // Check payment accounts. If payment and preferred is selected, + // then always show them. If only payment is selected, then + // show only if preferred flag is not set. + case MyMoneyAccount::Checkings: + case MyMoneyAccount::Savings: + case MyMoneyAccount::Cash: + case MyMoneyAccount::CreditCard: + switch(type & (Payment | Preferred)) { + case Payment: + if((*it).value("PreferredAccount") == "Yes") + it = accounts.remove(it); + break; + + case Preferred: + if((*it).value("PreferredAccount") != "Yes") + it = accounts.remove(it); + break; + + case Payment | Preferred: + break; + + default: + it = accounts.remove(it); + break; + } + break; + + // filter all accounts that are not used on homepage views + default: + it = accounts.remove(it); + break; + } + + } else if((*it).isClosed() || (*it).isInvest()) { + // don't show if closed or a stock account + it = accounts.remove(it); + } + + // if we still point to the same account we keep it in the list and move on ;-) + if(prevIt == it) { + d->addNameIndex(nameIdx, *it); + ++it; + } + } + + if(accounts.count() > 0) { + QString tmp; + int i = 0; + tmp = "<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + header + "</div>\n<div class=\"gap\"> </div>\n"; + m_part->write(tmp); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr class=\"item\"><td class=\"left\" width=\"35%\">"); + m_part->write(i18n("Account")); + m_part->write("</td><td width=\"25%\" class=\"right\">"); + m_part->write(i18n("Current Balance")); + m_part->write("</td>"); + //only show limit info if user chose to do so + if(KMyMoneyGlobalSettings::showLimitInfo()) { + m_part->write("<td width=\"40%\" class=\"right\">"); + m_part->write(i18n("To Minimum Balance / Maximum Credit")); + m_part->write("</td>"); + } + m_part->write("</tr>"); + + + QMap<QString, MyMoneyAccount>::const_iterator it_m; + for(it_m = nameIdx.begin(); it_m != nameIdx.end(); ++it_m) { + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + showAccountEntry(*it_m); + m_part->write("</tr>"); + } + m_part->write("</table></div></div>"); + } +} + +void KHomeView::showAccountEntry(const MyMoneyAccount& acc) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneySecurity currency = file->currency(acc.currencyId()); + MyMoneyMoney value; + + bool showLimit = KMyMoneyGlobalSettings::showLimitInfo(); + + if(acc.accountType() == MyMoneyAccount::Investment) { + //investment accounts show the balances of all its subaccounts + value = investmentBalance(acc); + + //investment accounts have no minimum balance + showAccountEntry(acc, value, MyMoneyMoney(), showLimit); + } else { + //get balance for normal accounts + value = file->balance(acc.id(), QDate::currentDate()); + + //if credit card or checkings account, show maximum credit + if( acc.accountType() == MyMoneyAccount::CreditCard || + acc.accountType() == MyMoneyAccount::Checkings ) { + QString maximumCredit = acc.value("maxCreditAbsolute"); + MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); + showAccountEntry(acc, value, value - maxCredit, showLimit); + } else { + //otherwise use minimum balance + QString minimumBalance = acc.value("minBalanceAbsolute"); + MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); + showAccountEntry(acc, value, value - minBalance, showLimit); + } + } +} + +void KHomeView::showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QString tmp; + MyMoneySecurity currency = file->currency(acc.currencyId()); + QString amount; + QString amountToMinBal; + + //format amounts + amount = value.formatMoney(acc, currency); + amount.replace(" "," "); + if(showMinBal) { + amountToMinBal = valueToMinBal.formatMoney(acc, currency); + amountToMinBal.replace(" "," "); + } + + tmp = QString("<td>") + + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + "</td>"; + + //show account balance + tmp += QString("<td class=\"right\">%1</td>").arg(showColoredAmount(amount, value.isNegative())); + + //show minimum balance column if requested + if(showMinBal) { + //if it is an investment, show minimum balance empty + if(acc.accountType() == MyMoneyAccount::Investment) { + tmp += QString("<td class=\"right\"> </td>"); + } else { + //show minimum balance entry + tmp += QString("<td class=\"right\">%1</td>").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); + } + } + // qDebug("accountEntry = '%s'", tmp.latin1()); + m_part->write(tmp); +} + +MyMoneyMoney KHomeView::investmentBalance(const MyMoneyAccount& acc) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyMoney value; + + value = file->balance(acc.id()); + QValueList<QString>::const_iterator it_a; + for(it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) { + MyMoneyAccount stock = file->account(*it_a); + try { + MyMoneyMoney val; + MyMoneyMoney balance = file->balance(stock.id()); + MyMoneySecurity security = file->security(stock.currencyId()); + MyMoneyPrice price = file->price(stock.currencyId(), security.tradingCurrency()); + val = (balance * price.rate(security.tradingCurrency())).convert(MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision())); + // adjust value of security to the currency of the account + MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); + val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); + val = val.convert(acc.fraction()); + value += val; + } catch(MyMoneyException* e) { + qWarning("%s", (QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e->what())).data()); + delete e; + } + } + return value; +} + +void KHomeView::showFavoriteReports(void) +{ + QValueList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList(); + + if ( reports.count() > 0 ) + { + bool firstTime = 1; + int row = 0; + QValueList<MyMoneyReport>::const_iterator it_report = reports.begin(); + while( it_report != reports.end() ) + { + if ( (*it_report).isFavorite() ) { + if(firstTime) { + m_part->write(QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\"> </div>\n").arg(i18n("Favorite Reports"))); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr class=\"item\"><td class=\"left\" width=\"40%\">"); + m_part->write(i18n("Report")); + m_part->write("</td><td width=\"60%\" class=\"left\">"); + m_part->write(i18n("Comment")); + m_part->write("</td></tr>"); + firstTime = false; + } + + m_part->write(QString("<tr class=\"row-%1\"><td>%2%3%4</td><td align=\"left\">%5</td></tr>") + .arg(row++ & 0x01 ? "even" : "odd") + .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) + .arg((*it_report).name()) + .arg(linkend()) + .arg((*it_report).comment()) + ); + } + + ++it_report; + } + if(!firstTime) + m_part->write("</table></div></div>"); + } +} + +void KHomeView::showForecast(void) +{ + QMap<QString, MyMoneyAccount> nameIdx; + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneyAccount> accList; + + // if forecast has not been executed yet, do it. + if(!m_forecast.isForecastDone()) + doForecast(); + + accList = m_forecast.accountList(); + + // add it to a map to have it ordered by name + QValueList<MyMoneyAccount>::const_iterator accList_t = accList.begin(); + for ( ; accList_t != accList.end(); ++accList_t ) { + d->addNameIndex(nameIdx, *accList_t); + } + + if(nameIdx.count() > 0) { + int i = 0; + + int colspan = 1; + // get begin day + int beginDay = QDate::currentDate().daysTo(m_forecast.beginForecastDate()); + // if begin day is today skip to next cycle + if(beginDay == 0) + beginDay = m_forecast.accountsCycle(); + + // Now output header + m_part->write(QString("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">%1</div>\n<div class=\"gap\"> </div>\n").arg(i18n("%1 Day Forecast").arg(m_forecast.forecastDays()))); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr class=\"item\"><td class=\"left\" width=\"40%\">"); + m_part->write(i18n("Account")); + m_part->write("</td>"); + int colWidth = 55/ (m_forecast.forecastDays() / m_forecast.accountsCycle()); + for(i = 0; (i*m_forecast.accountsCycle() + beginDay) <= m_forecast.forecastDays(); ++i) { + m_part->write(QString("<td width=\"%1%\" class=\"right\">").arg(colWidth)); + + m_part->write(i18n("%1 days").arg(i*m_forecast.accountsCycle() + beginDay)); + m_part->write("</td>"); + colspan++; + } + m_part->write("</tr>"); + + // Now output entries + i = 0; + + QMap<QString, MyMoneyAccount>::ConstIterator it_account; + for(it_account = nameIdx.begin(); it_account != nameIdx.end(); ++it_account) { + //MyMoneyAccount acc = (*it_n); + + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + m_part->write(QString("<td width=\"40%\">") + + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "</td>"); + + int dropZero = -1; //account dropped below zero + int dropMinimum = -1; //account dropped below minimum balance + QString minimumBalance = (*it_account).value("minimumBalance"); + MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); + MyMoneySecurity currency; + MyMoneyMoney forecastBalance; + + //change account to deep currency if account is an investment + if((*it_account).isInvest()) { + MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); + currency = file->security(underSecurity.tradingCurrency()); + } else { + currency = file->security((*it_account).currencyId()); + } + + for (int f = beginDay; f <= m_forecast.forecastDays(); f += m_forecast.accountsCycle()) { + forecastBalance = m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); + QString amount; + amount = forecastBalance.formatMoney( *it_account, currency); + amount.replace(" "," "); + m_part->write(QString("<td width=\"%1%\" align=\"right\">").arg(colWidth)); + m_part->write(QString("%1</td>").arg(showColoredAmount(amount, forecastBalance.isNegative()))); + } + + m_part->write("</tr>"); + + //Check if the account is going to be below zero or below the minimal balance in the forecast period + + //Check if the account is going to be below minimal balance + dropMinimum = m_forecast.daysToMinimumBalance(*it_account); + + //Check if the account is going to be below zero in the future + dropZero = m_forecast.daysToZeroBalance(*it_account); + + + // spit out possible warnings + QString msg; + + // if a minimum balance has been specified, an appropriate warning will + // only be shown, if the drop below 0 is on a different day or not present + + if(dropMinimum != -1 + && !minBalance.isZero() + && (dropMinimum < dropZero + || dropZero == -1)) { + switch(dropMinimum) { + case -1: + break; + case 0: + msg = i18n("The balance of %1 is below the minimum balance %2 today.").arg((*it_account).name()).arg(minBalance.formatMoney(*it_account, currency)); + msg = showColoredAmount(msg, true); + break; + default: + msg = i18n("The balance of %1 will drop below the minimum balance %2 in %3 days.").arg((*it_account).name()).arg(minBalance.formatMoney(*it_account, currency)).arg(dropMinimum-1); + msg = showColoredAmount(msg, true); + break; + } + + if(!msg.isEmpty()) { + m_part->write(QString("<tr class=\"warning\" style=\"font-weight: normal;\" ><td colspan=%2 align=\"center\" >%1</td></tr>").arg(msg).arg(colspan)); + } + } + // a drop below zero is always shown + msg = QString(); + switch(dropZero) { + case -1: + break; + case 0: + if((*it_account).accountGroup() == MyMoneyAccount::Asset) { + msg = i18n("The balance of %1 is below %2 today.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)); + msg = showColoredAmount(msg, true); + break; + } + if((*it_account).accountGroup() == MyMoneyAccount::Liability) { + msg = i18n("The balance of %1 is above %2 today.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)); + break; + } + break; + default: + if((*it_account).accountGroup() == MyMoneyAccount::Asset) { + msg = i18n("The balance of %1 will drop below %2 in %3 days.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)).arg(dropZero); + msg = showColoredAmount(msg, true); + break; + } + if((*it_account).accountGroup() == MyMoneyAccount::Liability) { + msg = i18n("The balance of %1 will raise above %2 in %3 days.").arg((*it_account).name()).arg(MyMoneyMoney().formatMoney(*it_account, currency)).arg(dropZero); + break; + } + } + if(!msg.isEmpty()) { + m_part->write(QString("<tr class=\"warning\"><td colspan=%2 align=\"center\" ><b>%1</b></td></tr>").arg(msg).arg(colspan)); + } + } + m_part->write("</table></div></div>"); + + } +} + +const QString KHomeView::link(const QString& view, const QString& query, const QString& _title) const +{ + QString titlePart; + QString title(_title); + if(!title.isEmpty()) + titlePart = QString(" title=\"%1\"").arg(title.replace(" ", " ")); + + return QString("<a href=\"/%1%2\"%3>").arg(view, query, titlePart); +} + +const QString KHomeView::linkend(void) const +{ + return "</a>"; +} + +void KHomeView::slotOpenURL(const KURL &url, const KParts::URLArgs& /* args */) +{ + QString protocol = url.protocol(); + QString view = url.fileName(false); + QString id = url.queryItem("id").data(); + QString mode = url.queryItem("mode").data(); + + if ( protocol == "http" ) + { + KApplication::kApplication()->invokeBrowser(url.prettyURL()); + } + else if ( protocol == "mailto" ) + { + KApplication::kApplication()->invokeMailer(url); + } + else + { + if(view == VIEW_LEDGER) { + emit ledgerSelected(id, QString()); + + } else if(view == VIEW_SCHEDULE) { + if(mode == "enter") { + emit scheduleSelected(id); + KMainWindow* mw = dynamic_cast<KMainWindow*>(qApp->mainWidget()); + Q_CHECK_PTR(mw); + QTimer::singleShot(0, mw->actionCollection()->action("schedule_enter"), SLOT(activate())); + + } else if(mode == "edit") { + emit scheduleSelected(id); + KMainWindow* mw = dynamic_cast<KMainWindow*>(qApp->mainWidget()); + Q_CHECK_PTR(mw); + QTimer::singleShot(0, mw->actionCollection()->action("schedule_edit"), SLOT(activate())); + + } else if(mode == "skip") { + emit scheduleSelected(id); + KMainWindow* mw = dynamic_cast<KMainWindow*>(qApp->mainWidget()); + Q_CHECK_PTR(mw); + QTimer::singleShot(0, mw->actionCollection()->action("schedule_skip"), SLOT(activate())); + + } else if(mode == "full") { + m_showAllSchedules = true; + loadView(); + + } else if(mode == "reduced") { + m_showAllSchedules = false; + loadView(); + } + + } else if(view == VIEW_REPORTS) { + emit reportSelected(id); + + } else if(view == VIEW_WELCOME) { + KMainWindow* mw = dynamic_cast<KMainWindow*>(qApp->mainWidget()); + Q_CHECK_PTR(mw); + if ( mode == "whatsnew" ) + { + QString fname = KMyMoneyUtils::findResource("appdata",QString("html/whats_new%1.html")); + if(!fname.isEmpty()) + m_part->openURL(fname); + } + else + m_part->openURL(m_filename); + + } else if(view == "action") { + KMainWindow* mw = dynamic_cast<KMainWindow*>(qApp->mainWidget()); + Q_CHECK_PTR(mw); + QTimer::singleShot(0, mw->actionCollection()->action( id ), SLOT(activate())); + + } else if(view == VIEW_HOME) { + QValueList<MyMoneyAccount> list; + MyMoneyFile::instance()->accountList(list); + if(list.count() == 0) { + KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead.")); + } + loadView(); + + } else { + qDebug("Unknown view '%s' in KHomeView::slotOpenURL()", view.latin1()); + } + } +} + +void KHomeView::showAssetsLiabilities(void) +{ + QValueList<MyMoneyAccount> accounts; + QValueList<MyMoneyAccount>::Iterator it; + QMap<QString, MyMoneyAccount> nameAssetsIdx; + QMap<QString, MyMoneyAccount> nameLiabilitiesIdx; + MyMoneyMoney netAssets; + MyMoneyMoney netLiabilities; + QString fontStart, fontEnd; + + MyMoneyFile* file = MyMoneyFile::instance(); + int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); + int i = 0; + + + // get list of all accounts + file->accountList(accounts); + for(it = accounts.begin(); it != accounts.end();) { + if(!(*it).isClosed()) { + switch((*it).accountType()) { + // group all assets into one list but make sure that investment accounts always show up + case MyMoneyAccount::Investment: + d->addNameIndex(nameAssetsIdx, *it); + break; + + case MyMoneyAccount::Checkings: + case MyMoneyAccount::Savings: + case MyMoneyAccount::Cash: + case MyMoneyAccount::Asset: + case MyMoneyAccount::AssetLoan: + // list account if it's the last in the hierarchy or has transactions in it + if((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { + d->addNameIndex(nameAssetsIdx, *it); + } + break; + + // group the liabilities into the other + case MyMoneyAccount::CreditCard: + case MyMoneyAccount::Liability: + case MyMoneyAccount::Loan: + // list account if it's the last in the hierarchy or has transactions in it + if((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { + d->addNameIndex(nameLiabilitiesIdx, *it); + } + break; + + default: + break; + } + } + ++it; + } + + //only do it if we have assets or liabilities account + if(nameAssetsIdx.count() > 0 || nameLiabilitiesIdx.count() > 0) { + //print header + m_part->write("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Assets and Liabilities Summary") + "</div>\n<div class=\"gap\"> </div>\n"); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + //column titles + m_part->write("<tr class=\"item\"><td class=\"left\" width=\"30%\">"); + m_part->write(i18n("Asset Accounts")); + m_part->write("</td>"); + m_part->write("<td width=\"15%\" class=\"right\">"); + m_part->write(i18n("Current Balance")); + m_part->write("</td>"); + //intermediate row to separate both columns + m_part->write("<td width=\"10%\" class=\"setcolor\"></td>"); + m_part->write("<td class=\"left\" width=\"30%\">"); + m_part->write(i18n("Liability Accounts")); + m_part->write("</td>"); + m_part->write("<td width=\"15%\" class=\"right\">"); + m_part->write(i18n("Current Balance")); + m_part->write("</td></tr>"); + + //get asset and liability accounts + QMap<QString, MyMoneyAccount>::const_iterator asset_it = nameAssetsIdx.begin(); + QMap<QString,MyMoneyAccount>::const_iterator liabilities_it = nameLiabilitiesIdx.begin(); + for(; asset_it != nameAssetsIdx.end() || liabilities_it != nameLiabilitiesIdx.end();) { + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + //write an asset account if we still have any + if(asset_it != nameAssetsIdx.end()) { + MyMoneyMoney value; + //investment accounts consolidate the balance of its subaccounts + if( (*asset_it).accountType() == MyMoneyAccount::Investment) { + value = investmentBalance(*asset_it); + } else { + value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); + } + //calculate balance for foreign currency accounts + if((*asset_it).currencyId() != file->baseCurrency().id()) { + ReportAccount repAcc = ReportAccount((*asset_it).id()); + MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); + MyMoneyMoney baseValue = value * curPrice; + baseValue = baseValue.convert(10000); + netAssets += baseValue; + } else { + netAssets += value; + } + //show the account without minimum balance + showAccountEntry(*asset_it, value, MyMoneyMoney(), false); + ++asset_it; + } else { + //write a white space if we don't + m_part->write("<td></td><td></td>"); + } + + //leave the intermediate column empty + m_part->write("<td class=\"setcolor\"></td>"); + + //write a liability account + if(liabilities_it != nameLiabilitiesIdx.end()) { + MyMoneyMoney value; + value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); + //calculate balance if foreign currency + if((*liabilities_it).currencyId() != file->baseCurrency().id()) { + ReportAccount repAcc = ReportAccount((*liabilities_it).id()); + MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); + MyMoneyMoney baseValue = value * curPrice; + baseValue = baseValue.convert(10000); + netLiabilities += baseValue; + } else { + netLiabilities += value; + } + //show the account without minimum balance + showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); + ++liabilities_it; + } else { + //leave the space empty if we run out of liabilities + m_part->write("<td></td><td></td>"); + } + m_part->write("</tr>"); + } + //calculate net worth + MyMoneyMoney netWorth = netAssets+netLiabilities; + + //format assets, liabilities and net worth + QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); + amountAssets.replace(" "," "); + amountLiabilities.replace(" "," "); + amountNetWorth.replace(" "," "); + + m_part->write(QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd")); + + //print total for assets + m_part->write(QString("<td class=\"left\">%1</td><td align=\"right\">%2</td>").arg(i18n("Total Assets")).arg(showColoredAmount(amountAssets, netAssets.isNegative()))); + + //leave the intermediate column empty + m_part->write("<td class=\"setcolor\"></td>"); + + //print total liabilities + m_part->write(QString("<td class=\"left\">%1</td><td align=\"right\">%2</td>").arg(i18n("Total Liabilities")).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative()))); + m_part->write("</tr>"); + + //print net worth + m_part->write(QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd")); + + m_part->write("<td></td><td></td><td class=\"setcolor\"></td>"); + m_part->write(QString("<td class=\"left\">%1</td><td align=\"right\">%2</td>").arg(i18n("Net Worth")).arg(showColoredAmount(amountNetWorth, netWorth.isNegative() ))); + + m_part->write("</tr>"); + m_part->write("</table>"); + m_part->write("</div></div>"); + } +} + +void KHomeView::showBudget(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + if ( file->countBudgets() ) { + int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); + int i = 0; + + //config report just like "Monthly Budgeted vs Actual + MyMoneyReport reportCfg = MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentMonth, + MyMoneyReport::eDetailAll, + i18n("Monthly Budgeted vs. Actual"), + i18n("Generated Report")); + + reportCfg.setBudget("Any",true); + + reports::PivotTable table(reportCfg); + + PivotGrid grid = table.grid(); + + //div header + m_part->write("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Budget") + "</div>\n<div class=\"gap\"> </div>\n"); + + //display budget summary + m_part->write("<table width=\"75%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr class=\"itemtitle\">"); + m_part->write("<td class=\"left\" colspan=\"3\">"); + m_part->write(i18n("Current Month Summary")); + m_part->write("</td></tr>"); + m_part->write("<tr class=\"item\">"); + m_part->write("<td class=\"right\" width=\"33%\">"); + m_part->write(i18n("Budgeted")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"33%\">"); + m_part->write(i18n("Actual")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"33%\">"); + m_part->write(i18n("Difference")); + m_part->write("</td></tr>"); + + m_part->write(QString("<tr class=\"row-odd\">")); + + MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total; + MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total; + MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total; + + QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative()))); + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative()))); + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative()))); + m_part->write("</tr>"); + m_part->write("</table>"); + + //budget overrun + m_part->write("<div class=\"gap\"> </div>\n"); + m_part->write("<table width=\"75%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + m_part->write("<tr class=\"itemtitle\">"); + m_part->write("<td class=\"left\" colspan=\"4\">"); + m_part->write(i18n("Budget Overruns")); + m_part->write("</td></tr>"); + m_part->write("<tr class=\"item\">"); + m_part->write("<td class=\"left\" width=\"30%\">"); + m_part->write(i18n("Account")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"20%\">"); + m_part->write(i18n("Budgeted")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"20%\">"); + m_part->write(i18n("Actual")); + m_part->write("</td>"); + m_part->write("<td class=\"right\" width=\"20%\">"); + m_part->write(i18n("Difference")); + m_part->write("</td></tr>"); + + + PivotGrid::iterator it_outergroup = grid.begin(); + while ( it_outergroup != grid.end() ) + { + i = 0; + PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); + while ( it_innergroup != (*it_outergroup).end() ) + { + PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); + while ( it_row != (*it_innergroup).end() ) + { + //column number is 1 because the report includes only current month + if(it_row.data()[eBudgetDiff][1].isNegative()) { + //get report account to get the name later + ReportAccount rowname = it_row.key(); + + //write the outergroup if it is the first row of outergroup being shown + if(i == 0) { + m_part->write("<tr style=\"font-weight:bold;\">"); + m_part->write(QString("<td class=\"left\" colspan=\"4\">%1</td>").arg(KMyMoneyUtils::accountTypeToString( rowname.accountType()))); + m_part->write("</tr>"); + } + m_part->write(QString("<tr class=\"row-%1\">").arg(i++ & 0x01 ? "even" : "odd")); + + //get values from grid + MyMoneyMoney actualValue = it_row.data()[eActual][1]; + MyMoneyMoney budgetValue = it_row.data()[eBudget][1]; + MyMoneyMoney budgetDiffValue = it_row.data()[eBudgetDiff][1]; + + //format amounts + QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + + //account name + m_part->write(QString("<td>") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "</td>"); + + //show amounts + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(budgetAmount, budgetValue.isNegative()))); + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(actualAmount, actualValue.isNegative()))); + m_part->write(QString("<td align=\"right\">%1</td>").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative()))); + m_part->write("</tr>"); + } + ++it_row; + } + ++it_innergroup; + } + ++it_outergroup; + } + + //if no negative differences are found, then inform that + if(i == 0) { + m_part->write(QString("<tr class=\"row-%1\" style=\"font-weight:bold;\">").arg(i++ & 0x01 ? "even" : "odd")); + m_part->write(QString("<td class=\"center\" colspan=\"4\">%1</td>").arg(i18n("No Budget Categories have been overrun"))); + m_part->write("</tr>"); + } + m_part->write("</table></div></div>"); + } +} + +QString KHomeView::showColoredAmount(const QString& amount, bool isNegative) +{ + if(isNegative) { + //if negative, get the settings for negative numbers + return QString("<font color=\"%1\">%2</font>").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name(), amount); + } + + //if positive, return the same string + return amount; +} + +void KHomeView::doForecast(void) +{ + //clear m_accountList because forecast is about to changed + m_accountList.clear(); + + //reinitialize the object + m_forecast = MyMoneyForecast(); + + //If forecastDays lower than accountsCycle, adjust to the first cycle + if(m_forecast.accountsCycle() > m_forecast.forecastDays()) + m_forecast.setForecastDays(m_forecast.accountsCycle()); + + //Get all accounts of the right type to calculate forecast + m_forecast.doForecast(); +} + +MyMoneyMoney KHomeView::forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) +{ + //if paymentDate before or equal to currentDate set it to current date plus 1 + //so we get to accumulate forecast balance correctly + if(paymentDate <= QDate::currentDate()) + paymentDate = QDate::currentDate().addDays(1); + + //check if the account is already there + if(m_accountList.find(acc.id()) == m_accountList.end() + || m_accountList[acc.id()].find(paymentDate) == m_accountList[acc.id()].end()) + { + if(paymentDate == QDate::currentDate()) { + m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate); + } else { + m_accountList[acc.id()][paymentDate] = m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); + } + } + m_accountList[acc.id()][paymentDate] = m_accountList[acc.id()][paymentDate] + payment; + return m_accountList[acc.id()][paymentDate]; +} + +void KHomeView::showCashFlowSummary() +{ + MyMoneyTransactionFilter filter; + MyMoneyMoney incomeValue; + MyMoneyMoney expenseValue; + + MyMoneyFile* file = MyMoneyFile::instance(); + int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); + + //set start and end of month dates + QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); + QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); + + //Add total income and expenses for this month + //get transactions for current month + filter.setDateFilter(startOfMonth, endOfMonth); + filter.setReportAllSplits(false); + + QValueList<MyMoneyTransaction> transactions = file->transactionList(filter); + //if no transaction then skip and print total in zero + if(transactions.size() > 0) { + QValueList<MyMoneyTransaction>::const_iterator it_transaction; + + //get all transactions for this month + for(it_transaction = transactions.begin(); it_transaction != transactions.end(); ++it_transaction ) { + + //get the splits for each transaction + const QValueList<MyMoneySplit>& splits = (*it_transaction).splits(); + QValueList<MyMoneySplit>::const_iterator it_split; + for(it_split = splits.begin(); it_split != splits.end(); ++it_split) { + if(!(*it_split).shares().isZero()) { + ReportAccount repSplitAcc = ReportAccount((*it_split).accountId()); + + //only add if it is an income or expense + if(repSplitAcc.isIncomeExpense()) { + MyMoneyMoney value; + + //convert to base currency if necessary + if(repSplitAcc.currencyId() != file->baseCurrency().id()) { + MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice((*it_transaction).postDate()); + value = ((*it_split).shares() * MyMoneyMoney(-1, 1)) * curPrice; + value = value.convert(10000); + } else { + value = ((*it_split).shares() * MyMoneyMoney(-1, 1)); + } + + //store depending on account type + if(repSplitAcc.accountType() == MyMoneyAccount::Income) { + incomeValue += value; + } else { + expenseValue += value; + } + } + } + } + } + } + + //format income and expenses + QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + amountIncome.replace(" "," "); + amountExpense.replace(" "," "); + + //calculate schedules + + //Add all schedules for this month + MyMoneyMoney scheduledIncome; + MyMoneyMoney scheduledExpense; + MyMoneyMoney scheduledLiquidTransfer; + MyMoneyMoney scheduledOtherTransfer; + + //get overdues and schedules until the end of this month + QValueList<MyMoneySchedule> schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, + MyMoneySchedule::OCCUR_ANY, + MyMoneySchedule::STYPE_ANY, + QDate(), + endOfMonth); + + //Remove the finished schedules + QValueList<MyMoneySchedule>::Iterator finished_it; + for (finished_it=schedule.begin(); finished_it!=schedule.end();) { + if ((*finished_it).isFinished()) { + finished_it = schedule.remove(finished_it); + continue; + } + ++finished_it; + } + + //add income and expenses + QValueList<MyMoneySchedule>::Iterator sched_it; + for (sched_it=schedule.begin(); sched_it!=schedule.end();) { + QDate nextDate = (*sched_it).nextDueDate(); + int cnt = 0; + + while(nextDate.isValid() && nextDate <= endOfMonth) { + ++cnt; + nextDate = (*sched_it).nextPayment(nextDate); + // for single occurence nextDate will not change, so we + // better get out of here. + if((*sched_it).occurence() == MyMoneySchedule::OCCUR_ONCE) + break; + } + + MyMoneyAccount acc = (*sched_it).account(); + if(acc.id()) { + MyMoneyTransaction transaction = (*sched_it).transaction(); + // only show the entry, if it is still active + + MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); + + // take care of the autoCalc stuff + if((*sched_it).type() == MyMoneySchedule::TYPE_LOANPAYMENT) { + QDate nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); + + //make sure we have all 'starting balances' so that the autocalc works + QValueList<MyMoneySplit>::const_iterator it_s; + QMap<QString, MyMoneyMoney> balanceMap; + + for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s ) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + // collect all overdues on the first day + QDate schedDate = nextDate; + if(QDate::currentDate() >= nextDate) + schedDate = QDate::currentDate().addDays(1); + + balanceMap[acc.id()] += file->balance(acc.id()); + } + KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); + } + + //go through the splits and assign to liquid or other transfers + const QValueList<MyMoneySplit> splits = transaction.splits(); + QValueList<MyMoneySplit>::const_iterator split_it; + for (split_it = splits.begin(); split_it != splits.end(); ++split_it) { + if( (*split_it).accountId() != acc.id() ) { + ReportAccount repSplitAcc = ReportAccount((*split_it).accountId()); + + //get the shares and multiply by the quantity of occurences in the period + MyMoneyMoney value = (*split_it).shares() * cnt; + + //convert to foreign currency if needed + if(repSplitAcc.currencyId() != file->baseCurrency().id()) { + MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate()); + value = value * curPrice; + value = value.convert(10000); + } + + if(( repSplitAcc.isLiquidLiability() + || repSplitAcc.isLiquidAsset() ) + && acc.accountGroup() != repSplitAcc.accountGroup()) { + scheduledLiquidTransfer += value; + } else if(repSplitAcc.isAssetLiability() + && !repSplitAcc.isLiquidLiability() + && !repSplitAcc.isLiquidAsset() ) { + scheduledOtherTransfer += value; + } else if(repSplitAcc.isIncomeExpense()) { + //income and expenses are stored as negative values + if(repSplitAcc.accountType() == MyMoneyAccount::Income) + scheduledIncome -= value; + if(repSplitAcc.accountType() == MyMoneyAccount::Expense) + scheduledExpense -= value; + } + } + } + } + ++sched_it; + } + + //format the currency strings + QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); + + amountScheduledIncome.replace(" "," "); + amountScheduledExpense.replace(" "," "); + amountScheduledLiquidTransfer.replace(" "," "); + amountScheduledOtherTransfer.replace(" "," "); + + //get liquid assets and liabilities + QValueList<MyMoneyAccount> accounts; + QValueList<MyMoneyAccount>::const_iterator account_it; + MyMoneyMoney liquidAssets; + MyMoneyMoney liquidLiabilities; + + // get list of all accounts + file->accountList(accounts); + for(account_it = accounts.begin(); account_it != accounts.end();) { + if(!(*account_it).isClosed()) { + switch((*account_it).accountType()) { + //group all assets into one list + case MyMoneyAccount::Checkings: + case MyMoneyAccount::Savings: + case MyMoneyAccount::Cash: + { + MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); + //calculate balance for foreign currency accounts + if((*account_it).currencyId() != file->baseCurrency().id()) { + ReportAccount repAcc = ReportAccount((*account_it).id()); + MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); + MyMoneyMoney baseValue = value * curPrice; + liquidAssets += baseValue; + liquidAssets = liquidAssets.convert(10000); + } else { + liquidAssets += value; + } + break; + } + //group the liabilities into the other + case MyMoneyAccount::CreditCard: + { + MyMoneyMoney value; + value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); + //calculate balance if foreign currency + if((*account_it).currencyId() != file->baseCurrency().id()) { + ReportAccount repAcc = ReportAccount((*account_it).id()); + MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); + MyMoneyMoney baseValue = value * curPrice; + liquidLiabilities += baseValue; + liquidLiabilities = liquidLiabilities.convert(10000); + } else { + liquidLiabilities += value; + } + break; + } + default: + break; + } + } + ++account_it; + } + //calculate net worth + MyMoneyMoney liquidWorth = liquidAssets+liquidLiabilities; + + //format assets, liabilities and net worth + QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); + amountLiquidAssets.replace(" "," "); + amountLiquidLiabilities.replace(" "," "); + amountLiquidWorth.replace(" "," "); + + //show the summary + m_part->write("<div class=\"shadow\"><div class=\"displayblock\"><div class=\"summaryheader\">" + i18n("Cash Flow Summary") + "</div>\n<div class=\"gap\"> </div>\n"); + + //print header + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + //income and expense title + m_part->write("<tr class=\"itemtitle\">"); + m_part->write("<td class=\"left\" colspan=\"4\">"); + m_part->write(i18n("Income and Expenses of Current Month")); + m_part->write("</td></tr>"); + //column titles + m_part->write("<tr class=\"item\">"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Income")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Scheduled Income")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Expenses")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Scheduled Expenses")); + m_part->write("</td>"); + m_part->write("</tr>"); + + //add row with banding + m_part->write(QString("<tr class=\"row-even\" style=\"font-weight:bold;\">")); + + //print current income + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountIncome, incomeValue.isNegative()))); + + //print the scheduled income + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative()))); + + //print current expenses + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpense, expenseValue.isNegative()))); + + //print the scheduled expenses + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative()))); + m_part->write("</tr>"); + + m_part->write("</table>"); + + //print header of assets and liabilities + m_part->write("<div class=\"gap\"> </div>\n"); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + //assets and liabilities title + m_part->write("<tr class=\"itemtitle\">"); + m_part->write("<td class=\"left\" colspan=\"4\">"); + m_part->write(i18n("Liquid Assets and Liabilities")); + m_part->write("</td></tr>"); + //column titles + m_part->write("<tr class=\"item\">"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Liquid Assets")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Transfers to Liquid Liabilities")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Liquid Liabilities")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Other Transfers")); + m_part->write("</td>"); + m_part->write("</tr>"); + + //add row with banding + m_part->write(QString("<tr class=\"row-even\" style=\"font-weight:bold;\">")); + + //print current liquid assets + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative()))); + + //print the scheduled transfers + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative()))); + + //print current liabilities + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative()))); + + //print the scheduled transfers + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative()))); + + + m_part->write("</tr>"); + + m_part->write("</table>"); + + //final conclusion + MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; + MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; + MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; + + QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); + QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); + amountProfit.replace(" "," "); + amountExpectedAsset.replace(" "," "); + amountExpectedLiabilities.replace(" "," "); + + + + //print header of cash flow status + m_part->write("<div class=\"gap\"> </div>\n"); + m_part->write("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"2\" class=\"summarytable\" >"); + //income and expense title + m_part->write("<tr class=\"itemtitle\">"); + m_part->write("<td class=\"left\" colspan=\"4\">"); + m_part->write(i18n("Cash Flow Status")); + m_part->write("</td></tr>"); + //column titles + m_part->write("<tr class=\"item\">"); + m_part->write("<td> </td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Expected Liquid Assets")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Expected Liquid Liabilities")); + m_part->write("</td>"); + m_part->write("<td width=\"25%\" class=\"center\">"); + m_part->write(i18n("Expected Profit/Loss")); + m_part->write("</td>"); + m_part->write("</tr>"); + + //add row with banding + m_part->write(QString("<tr class=\"row-even\" style=\"font-weight:bold;\">")); + m_part->write("<td> </td>"); + + //print expected assets + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative()))); + + //print expected liabilities + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative()))); + + //print expected profit + m_part->write(QString("<td align=\"right\">%2</td>").arg(showColoredAmount(amountProfit, profitValue.isNegative()))); + + m_part->write("</tr>"); + + m_part->write("</table>"); + + m_part->write("</div></div>"); + + +} + +// 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 VIEW_LEDGER +#undef VIEW_SCHEDULE +#undef VIEW_WELCOME +#undef VIEW_HOME +#undef VIEW_REPORTS + +#include "khomeview.moc" diff --git a/kmymoney2/views/khomeview.h b/kmymoney2/views/khomeview.h new file mode 100644 index 0000000..213fd91 --- /dev/null +++ b/kmymoney2/views/khomeview.h @@ -0,0 +1,158 @@ +/*************************************************************************** + khomeview.h - description + ------------------- + begin : Tue Jan 22 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef KHOMEVIEW_H +#define KHOMEVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes +#include <qwidget.h> +class QVBoxLayout; +class QFrame; + +// ---------------------------------------------------------------------------- +// KDE Includes +#include <khtml_part.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "../mymoney/mymoneyscheduled.h" +#include "../mymoney/mymoneyaccount.h" +#include "../mymoney/mymoneyforecast.h" +#include "../views/kmymoneyview.h" + +/** + * Displays a 'home page' for the user. Similar to concepts used in + * quicken and m$-money. + * + * @author Michael Edwardes + * + * @short A view containing the home page for kmymoney2. +**/ +class KHomeView : public KMyMoneyViewBase +{ + Q_OBJECT +public: + /** + * Definition of bitmap used as argument for showAccounts(). + */ + enum paymentTypeE { + Preferred = 1, ///< show preferred accounts + Payment = 2 ///< show payment accounts + }; + + KHomeView(QWidget *parent=0, const char *name=0); + ~KHomeView(); + +protected: + void showPayments(void); + void showPaymentEntry(const MyMoneySchedule&, int cnt = 1); + void showAccounts(paymentTypeE type, const QString& hdr); + void showAccountEntry(const MyMoneyAccount&); + void showFavoriteReports(void); + void showForecast(void); + void showNetWorthGraph(void); + void showSummary(void); + void showAssetsLiabilities(void); + void showIncomeExpenseSummary(void); + void showSchedulesSummary(void); + void showBudget(void); + void showCashFlowSummary(void); + + const QString link(const QString& view, const QString& query, const QString& title = QString()) const; + const QString linkend(void) const; + void loadView(void); + +public slots: + /** + * Overridden so we can emit the activated signal. + * + * @return Nothing. + */ + void show(void); + + void slotOpenURL(const KURL &url, const KParts::URLArgs& args); + void slotLoadView(void); + + /** + * Print the current view + */ + void slotPrintView(void); + +signals: + void ledgerSelected(const QString& id, const QString& transaction); + void scheduleSelected(const QString& id); + void reportSelected(const QString& id); + +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + + /** + * daily balances of an account + */ + typedef QMap<QDate, MyMoneyMoney> dailyBalances; + + /** + * Print an account and its balance and limit + */ + void showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal); + + /** + * @param acc the investment account + * @return the balance in the currency of the investment account + */ + MyMoneyMoney investmentBalance(const MyMoneyAccount& acc); + + /** + * Print text in the color set for negative numbers, if @p amount is negative + * abd @p isNegative is true + */ + QString showColoredAmount(const QString& amount, bool isNegative); + + /** + * Run the forecast + */ + void doForecast(void); + + /** + * Calculate the forecast balance after a payment has been made + */ + MyMoneyMoney forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate); + + KHTMLPart* m_part; + QVBoxLayout* m_qvboxlayoutPage; + QString m_filename; + bool m_showAllSchedules; + bool m_needReload; + MyMoneyForecast m_forecast; + + /** + * daily forecast balance of accounts + */ + QMap<QString, dailyBalances> m_accountList; + +}; + +#endif diff --git a/kmymoney2/views/kinstitutionsview.cpp b/kmymoney2/views/kinstitutionsview.cpp new file mode 100644 index 0000000..2c3ac5d --- /dev/null +++ b/kmymoney2/views/kinstitutionsview.cpp @@ -0,0 +1,353 @@ +/*************************************************************************** + kinstitutionsview.cpp + ------------------- + copyright : (C) 2005 by Thomas Baumgart + email : ipwizard@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 <qheader.h> +#include <qlabel.h> +#include <qtabwidget.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kdebug.h> +#include <klocale.h> +#include <kstandarddirs.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include "kinstitutionsview.h" +#include "../kmymoneyglobalsettings.h" +#include "../kmymoney2.h" + +KInstitutionsView::KInstitutionsView(QWidget *parent, const char *name) : + KInstitutionsViewDecl(parent,name), + m_needReload(false) +{ + m_accountTree->header()->setLabel(0, i18n("Institution/Account")); + + connect(m_accountTree, SIGNAL(selectObject(const MyMoneyObject&)), this, SIGNAL(selectObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(openContextMenu(const MyMoneyObject&)), this, SIGNAL(openContextMenu(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(valueChanged(void)), this, SLOT(slotUpdateNetWorth(void))); + connect(m_accountTree, SIGNAL(openObject(const MyMoneyObject&)), this, SIGNAL(openObject(const MyMoneyObject&))); + connect(m_accountTree, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyInstitution&)), this, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyInstitution&))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); +} + +KInstitutionsView::~KInstitutionsView() +{ +} + +void KInstitutionsView::show(void) +{ + if(m_needReload) { + loadAccounts(); + m_needReload = false; + } + + // don't forget base class implementation + KInstitutionsViewDecl::show(); + + // if we have a selected account, let the application know about it + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + if(item) { + emit selectObject(item->itemObject()); + } +} + +void KInstitutionsView::polish(void) +{ + KInstitutionsViewDecl::polish(); + m_accountTree->setResizeMode(QListView::LastColumn); + m_accountTree->restoreLayout("Institution View Settings"); +} + +void KInstitutionsView::slotLoadAccounts(void) +{ + if(isVisible()) { + loadAccounts(); + } else { + m_needReload = true; + } +} + +void KInstitutionsView::loadAccounts(void) +{ + QMap<QString, bool> isOpen; + + ::timetrace("start load institutions view"); + // remember the id of the current selected item + KMyMoneyAccountTreeBaseItem *item = m_accountTree->selectedItem(); + QString selectedItemId = (item) ? item->id() : QString(); + + // keep a map of all 'expanded' accounts + QListViewItemIterator it_lvi(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item && item->isOpen()) { + isOpen[item->id()] = true; + } + ++it_lvi; + } + + // remember the upper left corner of the viewport + QPoint startPoint = m_accountTree->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_accountTree->setUpdatesEnabled(false); + + // clear the current contents and recreate it + m_accountTree->clear(); + m_accountMap.clear(); + m_securityMap.clear(); + m_transactionCountMap.clear(); + + MyMoneyFile* file = MyMoneyFile::instance(); + + QValueList<MyMoneyAccount> alist; + file->accountList(alist); + QValueList<MyMoneyAccount>::const_iterator it_a; + for(it_a = alist.begin(); it_a != alist.end(); ++it_a) { + m_accountMap[(*it_a).id()] = *it_a; + } + + // we need to make sure we show stock accounts + // under the right institution (the one of the parent account) + QMap<QString, MyMoneyAccount>::iterator it_am; + for(it_am = m_accountMap.begin(); it_am != m_accountMap.end(); ++it_am) { + if((*it_am).isInvest()) { + (*it_am).setInstitutionId(m_accountMap[(*it_am).parentAccountId()].institutionId()); + } + } + + QValueList<MyMoneySecurity> slist = file->currencyList(); + slist += file->securityList(); + QValueList<MyMoneySecurity>::const_iterator it_s; + for(it_s = slist.begin(); it_s != slist.end(); ++it_s) { + m_securityMap[(*it_s).id()] = *it_s; + } + + m_transactionCountMap = file->transactionCountMap(); + + m_accountTree->setBaseCurrency(file->baseCurrency()); + + // create the items + try { + const MyMoneySecurity& security = file->baseCurrency(); + m_accountTree->setBaseCurrency(security); + + MyMoneyInstitution none; + none.setName(i18n("Accounts with no institution assigned")); + KMyMoneyAccountTreeItem* noInstitutionItem = new KMyMoneyAccountTreeItem(m_accountTree, none); + noInstitutionItem->setPixmap(0,none.pixmap()); + loadSubAccounts(noInstitutionItem, QString()); + + // hide it, if unused + noInstitutionItem->setVisible(noInstitutionItem->childCount() != 0); + + QValueList<MyMoneyInstitution> list = file->institutionList(); + QValueList<MyMoneyInstitution>::const_iterator it_i; + for(it_i = list.begin(); it_i != list.end(); ++it_i) { + KMyMoneyAccountTreeItem* item = new KMyMoneyAccountTreeItem(m_accountTree, *it_i); + item->setPixmap(0, none.pixmap()); + loadSubAccounts(item, (*it_i).id()); + } + + } catch(MyMoneyException *e) { + kdDebug(2) << "Problem in institutions view: " << e->what(); + delete e; + } + + // scan through the list of accounts and re-expand those that were + // expanded and re-select the one that was probably selected before + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item) { + if(item->id() == selectedItemId) + m_accountTree->setSelected(item, true); + if(isOpen.find(item->id()) != isOpen.end()) + item->setOpen(true); + } + ++it_lvi; + } + + // reposition viewport + m_accountTree->setContentsPos(startPoint.x(), startPoint.y()); + + // turn updates back on + m_accountTree->setUpdatesEnabled(true); + m_accountTree->repaintContents(); + + ::timetrace("done load institutions view"); +} + +void KInstitutionsView::loadSubAccounts(KMyMoneyAccountTreeItem* parent) +{ + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked(); + const MyMoneyAccount& account = dynamic_cast<const MyMoneyAccount&>(parent->itemObject()); + QValueList<QString>::const_iterator it_a; + MyMoneyFile* file = MyMoneyFile::instance(); + for(it_a = account.accountList().begin(); it_a != account.accountList().end(); ++it_a) { + MyMoneyAccount acc = m_accountMap[(*it_a)]; + if(!acc.isInvest()) + continue; + if(acc.isClosed() && !showClosedAccounts) + continue; + const MyMoneySecurity& security = m_securityMap[acc.currencyId()]; + QValueList<MyMoneyPrice> prices; + prices += file->price(acc.currencyId(), security.tradingCurrency()); + if(security.tradingCurrency() != file->baseCurrency().id()) { + MyMoneySecurity sec = m_securityMap[security.tradingCurrency()]; + prices += file->price(sec.id(), file->baseCurrency().id()); + } + KMyMoneyAccountTreeItem* item = new KMyMoneyAccountTreeItem(parent, acc, prices, security); + if(acc.id() == m_reconciliationAccount.id()) + item->setReconciliation(true); + } +} + +void KInstitutionsView::loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QString& institutionId) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + QMap<QString, MyMoneyAccount>::const_iterator it_a; + MyMoneyMoney value; + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked(); + + for(it_a = m_accountMap.begin(); it_a != m_accountMap.end(); ++it_a) { + const MyMoneyAccount& acc = *it_a; + MyMoneyMoney factor(1,1); + switch(acc.accountGroup()) { + case MyMoneyAccount::Liability: + factor = MyMoneyMoney(-1,1); + // tricky fall through here + + case MyMoneyAccount::Asset: + if(acc.institutionId() == institutionId + && !acc.isInvest() + && (!acc.isClosed() || showClosedAccounts)) { + QValueList<MyMoneyPrice> prices; + MyMoneySecurity security = file->baseCurrency(); + try { + if(acc.currencyId() != file->baseCurrency().id()) { + security = m_securityMap[acc.currencyId()]; + prices += file->price(acc.currencyId(), file->baseCurrency().id()); + } + + } catch(MyMoneyException *e) { + kdDebug(2) << __PRETTY_FUNCTION__ << " caught exception while adding " << acc.name() << "[" << acc.id() << "]: " << e->what(); + delete e; + } + + KMyMoneyAccountTreeItem* item = new KMyMoneyAccountTreeItem(parent, acc, prices, security); + if(acc.id() == m_reconciliationAccount.id()) + item->setReconciliation(true); + + if(acc.accountType() == MyMoneyAccount::Investment) + loadSubAccounts(item); + value += (item->totalValue() * factor); + } + break; + + default: + break; + } + } + + // the calulated value for the institution is not correct as + // it does not take the negative sign for liability accounts + // into account. So we correct this here with the value we + // have calculated while filling the list + parent->adjustTotalValue(-parent->totalValue()); // load a 0 + parent->adjustTotalValue(value); // now store the new value + + // we need to call slotUpdateNetWorth() here manually, because + // KMyMoneyAccountTreeItem::adjustTotalValue() does not send out + // the valueChanged() signal + slotUpdateNetWorth(); +} + +void KInstitutionsView::slotUpdateNetWorth(void) +{ + MyMoneyMoney netWorth; + + // calculate by going through the account trees top items + // and summing up the total value shown there + KMyMoneyAccountTreeItem* item = dynamic_cast<KMyMoneyAccountTreeItem*>(m_accountTree->firstChild()); + while(item) { + netWorth += item->totalValue(); + item = dynamic_cast<KMyMoneyAccountTreeItem*>(item->nextSibling()); + } + + QString s(i18n("Net Worth: ")); + + // FIXME figure out how to deal with the approximate + // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) + // s += "~ "; + + s.replace(QString(" "), QString(" ")); + if(netWorth.isNegative()) { + s += "<b><font color=\"red\">"; + } + const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); + QString v(netWorth.formatMoney(sec)); + s += v.replace(QString(" "), QString(" ")); + if(netWorth.isNegative()) { + s += "</font></b>"; + } + + m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); + m_totalProfitsLabel->setText(s); +} + +void KInstitutionsView::slotReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) +{ + Q_UNUSED(reconciliationDate); + Q_UNUSED(endingBalance); + + // scan through the list of accounts and mark all non + // expanded and re-select the one that was probably selected before + QListViewItemIterator it_lvi(m_accountTree); + KMyMoneyAccountTreeItem* item; + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item) { + item->setReconciliation(false); + } + ++it_lvi; + } + + m_reconciliationAccount = acc; + if(!acc.id().isEmpty()) { + it_lvi = QListViewItemIterator(m_accountTree); + while(it_lvi.current()) { + item = dynamic_cast<KMyMoneyAccountTreeItem*>(it_lvi.current()); + if(item && item->itemObject().id() == acc.id()) { + item->setReconciliation(true); + break; + } + ++it_lvi; + } + } +} + + +#include "kinstitutionsview.moc" diff --git a/kmymoney2/views/kinstitutionsview.h b/kmymoney2/views/kinstitutionsview.h new file mode 100644 index 0000000..3a35713 --- /dev/null +++ b/kmymoney2/views/kinstitutionsview.h @@ -0,0 +1,128 @@ +/*************************************************************************** + kinstitutionssview.h + ------------------- + copyright : (C) 2005 by Thomas Baumgart + email : ipwizard@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. * + * * + ***************************************************************************/ + +#ifndef KINSTITUTIONSVIEW_H +#define KINSTITUTIONSVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyinstitution.h> +#include <kmymoney/kmymoneyaccounttree.h> +#include <kmymoney/mymoneyutils.h> + +#include "../views/kinstitutionsviewdecl.h" + +/** + * @author Thomas Baumgart + */ + +/** + * This class implements the institutions hierarchical 'view'. + */ +class KInstitutionsView : public KInstitutionsViewDecl +{ + Q_OBJECT +private: + +public: + KInstitutionsView(QWidget *parent=0, const char *name=0); + virtual ~KInstitutionsView(); + +public slots: + void slotLoadAccounts(void); + + /** + * Override the base class behaviour to include all updates that + * happened in the meantime. + */ + void show(void); + + /** + * Override the base class behaviour to restore the layout. Do not + * do this in show() because show() itself may change the layout + * in undesired ways. + */ + void polish(void); + + void slotReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); + +protected: + void loadAccounts(void); + + // load accounts that are kept at a specific institution + void loadSubAccounts(KMyMoneyAccountTreeItem* parent, const QString& institutionId); + + // load stock accounts under the investment account (parent) + void loadSubAccounts(KMyMoneyAccountTreeItem* parent); + +protected slots: + void slotUpdateNetWorth(void); + +private: + /** + * This method returns an icon according to the account type + * passed in the argument @p type. + * + * @param type account type as defined in MyMoneyAccount::accountTypeE + */ + const QPixmap accountImage(const MyMoneyAccount::accountTypeE type) const; + +signals: + /** + * This signal serves as proxy for KMyMoneyAccountTree::selectObject() + */ + void selectObject(const MyMoneyObject&); + + /** + * This signal serves as proxy for + * KMyMoneyAccountTree::openContextMenu(const MyMoneyObject&) + */ + void openContextMenu(const MyMoneyObject& obj); + + /** + * This signal will be emitted when the left mouse button is double + * clicked (actually the KDE executed setting is used) on an account + * or institution. + */ + void openObject(const MyMoneyObject& obj); + + /** + * This signal is emitted, when the user selected to reparent the + * account @p acc to be a subordinate account of @p institution. + * + * @param acc const reference to account to be reparented + * @param institution const reference to new institution + */ + void reparent(const MyMoneyAccount& acc, const MyMoneyInstitution& institution); + +private: + MyMoneyAccount m_reconciliationAccount; + QMap<QString, MyMoneyAccount> m_accountMap; + QMap<QString, MyMoneySecurity> m_securityMap; + QMap<QString, unsigned long> m_transactionCountMap; + + /// set if a view needs to be reloaded during show() + bool m_needReload; +}; + +#endif diff --git a/kmymoney2/views/kinstitutionsviewdecl.ui b/kmymoney2/views/kinstitutionsviewdecl.ui new file mode 100644 index 0000000..3a0a740 --- /dev/null +++ b/kmymoney2/views/kinstitutionsviewdecl.ui @@ -0,0 +1,82 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KInstitutionsViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KInstitutionsViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>610</width> + <height>378</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KMyMoneyAccountTree"> + <property name="name"> + <cstring>m_accountTree</cstring> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>Spacer1_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>m_totalProfitsLabel</cstring> + </property> + <property name="minimumSize"> + <size> + <width>150</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Total Profits:</string> + </property> + <property name="textFormat"> + <enum>RichText</enum> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignRight</set> + </property> + <property name="hAlign" stdset="0"> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kinvestmentlistitem.cpp b/kmymoney2/views/kinvestmentlistitem.cpp new file mode 100644 index 0000000..c2c41d9 --- /dev/null +++ b/kmymoney2/views/kinvestmentlistitem.cpp @@ -0,0 +1,294 @@ +/*************************************************************************** + kinvestmentlistitem.cpp - description + ------------------- + begin : Wed Feb 6 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qpainter.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klistview.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/kmymoneyglobalsettings.h> + +#include "kinvestmentlistitem.h" + +#include <kmymoney/mymoneysecurity.h> +#include <kmymoney/mymoneyfile.h> + +KInvestmentListItem::KInvestmentListItem(KListView* parent, const MyMoneyAccount& account) + : KListViewItem(parent) +{ + bColumn5Negative = false; + bColumn6Negative = false; + bColumn7Negative = false; + bColumn8Negative = false; + bColumn9Negative = false; + + m_account = account; + m_listView = parent; + + MyMoneySecurity security; + MyMoneyFile* file = MyMoneyFile::instance(); + + security = file->security(m_account.currencyId()); + m_tradingCurrency = file->security(security.tradingCurrency()); + + int prec = MyMoneyMoney::denomToPrec(m_tradingCurrency.smallestAccountFraction()); + + QValueList<MyMoneyTransaction> transactionList; + // FIXME PRICE + // equity_price_history history = equity.priceHistory(); + + //column 0 (COLUMN_NAME_INDEX) is the name of the stock + setText(COLUMN_NAME_INDEX, m_account.name()); + + //column 1 (COLUMN_SYMBOL_INDEX) is the ticker symbol + setText(COLUMN_SYMBOL_INDEX, security.tradingSymbol()); + + //column 2 is the net value (price * quantity owned) + MyMoneyPrice price = file->price(m_account.currencyId(), m_tradingCurrency.id()); + if(price.isValid()) { + setText(COLUMN_VALUE_INDEX, (file->balance(m_account.id()) * price.rate(m_tradingCurrency.id())).formatMoney(m_tradingCurrency.tradingSymbol(), prec)); + } else { + setText(COLUMN_VALUE_INDEX, "---"); + } + + //column 3 (COLUMN_QUANTITY_INDEX) is the quantity of shares owned + prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); + setText(COLUMN_QUANTITY_INDEX, file->balance(m_account.id()).formatMoney("", prec)); + + //column 4 is the current price + // Get the price precision from the configuration + prec = KMyMoneyGlobalSettings::pricePrecision(); + + // prec = MyMoneyMoney::denomToPrec(m_tradingCurrency.smallestAccountFraction()); + if(price.isValid()) { + setText(COLUMN_PRICE_INDEX, price.rate(m_tradingCurrency.id()).formatMoney(m_tradingCurrency.tradingSymbol(), prec)); + } else { + setText(COLUMN_PRICE_INDEX, "---"); + } +} + +KInvestmentListItem::~KInvestmentListItem() +{ +} + +// FIXME PRICE +#if 0 +const QString KInvestmentListItem::calculate1WeekGain(const equity_price_history& history) +{ + return calculateGain(history, -7, 0, false, bColumn6Negative); +} + +const QString KInvestmentListItem::calculate4WeekGain(const equity_price_history& history) +{ + return calculateGain(history, -28, 0, false, bColumn7Negative); +} + +const QString KInvestmentListItem::calculate3MonthGain(const equity_price_history& history) +{ + return calculateGain(history, 0, -3, false, bColumn8Negative); +} + +const QString KInvestmentListItem::calculateYTDGain(const equity_price_history& history) +{ + return calculateGain(history, 0, 0, true, bColumn9Negative); +} + +const QString KInvestmentListItem::calculateGain(const equity_price_history& history, int dayDifference, int monthDifference, bool YTD, bool& bNegative) +{ + bNegative = false; + if(history.isEmpty()) + { + return QString("0.0%"); + } + else + { + bool bFoundCurrent = false, bFoundComparison = false; + QDate tempDate, comparisonDate = QDate::currentDate(); + + if(YTD) + { + //if it is YTD, set the date to 01/01/<current year> + comparisonDate.setYMD(comparisonDate.year(), 1, 1); + } + else + { + comparisonDate = comparisonDate.addDays(dayDifference); + comparisonDate = comparisonDate.addMonths(monthDifference); + } + + MyMoneyMoney comparisonValue, currentValue; + + //find the current value, or closest to the current value. + equity_price_history::ConstIterator itToday = history.end(); + for(tempDate = QDate::currentDate(); tempDate >= comparisonDate; ) + { + itToday = history.find(tempDate); + if(itToday != history.end()) + { + currentValue = itToday.data(); + bFoundCurrent = true; + break; + } + + tempDate = tempDate.addDays(-1); + } + + if(!bFoundCurrent) + { + return QString("0.0%"); + } + + //find a date that is closest to a week old, not older, and not today's date. Because its a QMap, this map + //should already be sorted earliest to latest. + for(equity_price_history::ConstIterator it = history.begin(); it != history.end(); ++it) + { + if(it.key() >= comparisonDate && it.key() < QDate::currentDate()) + { + comparisonDate = it.key(); + comparisonValue = it.data(); + bFoundComparison = true; + break; + } + } + + if(!bFoundComparison) + { + return QString("0.0%"); + } + + //qDebug("Current date/value to use is %s/%s, Previous is %s/%s", tempDate.toString().data(), currentValue.toString().data(), comparisonDate.toString().data(), comparisonValue.toString().data()); + + //compute the percentage difference + if(comparisonValue != currentValue) + { + double result = (currentValue.toDouble() / comparisonValue.toDouble()) * 100.0; + result -= 100.0; + if(result < 0.0) + { + bNegative = true; + } + + QString ds = QString("%1%").arg(result, 0, 'f', 3); + return ds; + + /*MyMoneyMoney result = (currentValue / comparisonValue); + result = result * 100; + result = result - 100; + qDebug("final result = %s", result.toString().data()); + return QString(result.formatMoney("", 3) + "%");*/ + } + } + return QString(""); +} +#endif + +int KInvestmentListItem::compare(QListViewItem* i, int col, bool ascending) const +{ + KInvestmentListItem* item = dynamic_cast<KInvestmentListItem*>(i); + // do special sorting only for numeric columns + // in all other cases use the standard sorting + if(item) { + switch(col) { + case COLUMN_VALUE_INDEX: + case COLUMN_QUANTITY_INDEX: + case COLUMN_PRICE_INDEX: + { + bool inv1 = text(col) == "---"; + bool inv2 = item->text(col) == "---"; + if(!inv1 && !inv2) { + MyMoneyMoney result = MyMoneyMoney(text(col)) - MyMoneyMoney(item->text(col)); + if(result.isNegative()) + return -1; + if(result.isZero()) + return 0; + return 1; + } else if(inv1 && inv2) { + return 0; + } else if(inv1) { + return -1; + } + return 1; + } + break; + + default: + break; + } + } + + // do standard sorting here + return KListViewItem::compare(i, col, ascending); +} + +void KInvestmentListItem::paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align) +{ + bool bPaintRed = false; + if((column == COLUMN_RAWGAIN_INDEX && bColumn5Negative) || + (column == COLUMN_1WEEKGAIN_INDEX && bColumn6Negative) || + (column == COLUMN_4WEEKGAIN_INDEX && bColumn7Negative) || + (column == COLUMN_3MONGAIN_INDEX && bColumn8Negative) || + (column == COLUMN_YTDGAIN_INDEX && bColumn9Negative)) + { + bPaintRed = true; + } + + p->save(); + + QColorGroup cg2(cg); + + if(isAlternate()) + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listColor()); + else + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listBGColor()); + +#ifndef KMM_DESIGNER + QFont font = KMyMoneyGlobalSettings::listCellFont(); + // strike out closed accounts + if(m_account.isClosed()) + font.setStrikeOut(true); + + p->setFont(font); +#endif + + if(bPaintRed) + { + QColorGroup _cg( cg2); + QColor c = _cg.text(); + _cg.setColor(QColorGroup::Text, Qt::red); + QListViewItem::paintCell(p, _cg, column, width, align); + _cg.setColor(QColorGroup::Text, c); + } + else + { + QListViewItem::paintCell(p, cg2, column, width, align); + } + + p->restore(); +} diff --git a/kmymoney2/views/kinvestmentlistitem.h b/kmymoney2/views/kinvestmentlistitem.h new file mode 100644 index 0000000..d0ad62b --- /dev/null +++ b/kmymoney2/views/kinvestmentlistitem.h @@ -0,0 +1,95 @@ +/*************************************************************************** + kinvestmentlistitem.h - description + ------------------- + begin : Wed Feb 6 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KINVESTMENTLISTITEM_H +#define KINVESTMENTLISTITEM_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klistview.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneysecurity.h> +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/mymoneytransaction.h> +#include <kmymoney/mymoneyobserver.h> + +//indexes for the various columns on the summary view +#define COLUMN_NAME_INDEX 0 +#define COLUMN_SYMBOL_INDEX 1 +#define COLUMN_VALUE_INDEX 2 +#define COLUMN_QUANTITY_INDEX 3 +#define COLUMN_PRICE_INDEX 4 +#define COLUMN_COSTBASIS_INDEX 5 +#define COLUMN_RAWGAIN_INDEX 6 +#define COLUMN_1WEEKGAIN_INDEX 7 +#define COLUMN_4WEEKGAIN_INDEX 8 +#define COLUMN_3MONGAIN_INDEX 9 +#define COLUMN_YTDGAIN_INDEX 10 + +/** + * @author Kevin Tambascio + * @author Thomas Baumgart + */ +class KInvestmentListItem : public KListViewItem +{ +public: + KInvestmentListItem(KListView* parent, const MyMoneyAccount& security); + ~KInvestmentListItem(); + + QString securityId() const { return m_account.currencyId(); }; + const MyMoneyAccount& account(void) const { return m_account; }; + const MyMoneySecurity tradingCurrency(void) const { return m_tradingCurrency; }; + + /** + * Helper method to show the right order + */ + int compare(QListViewItem* i, int col, bool ascending) const; + + +protected: + void paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align); + +private: + // FIXME PRICE +#if 0 + const QString calculate1WeekGain(const equity_price_history& history); + const QString calculate4WeekGain(const equity_price_history& history); + const QString calculate3MonthGain(const equity_price_history& history); + const QString calculateYTDGain(const equity_price_history& history); + const QString calculateGain(const equity_price_history& history, int dayDifference, int monthDifference, bool YTD, bool& bNegative); +#endif + +private: + KListView* m_listView; + MyMoneyAccount m_account; + MyMoneySecurity m_tradingCurrency; + bool bColumn5Negative, bColumn6Negative, bColumn7Negative, bColumn8Negative, bColumn9Negative; +}; + +#endif diff --git a/kmymoney2/views/kinvestmentview.cpp b/kmymoney2/views/kinvestmentview.cpp new file mode 100644 index 0000000..ae8cc11 --- /dev/null +++ b/kmymoney2/views/kinvestmentview.cpp @@ -0,0 +1,322 @@ +/*************************************************************************** + kinvestmentview.cpp - description + ------------------- + begin : Mon Mar 12 2007 + copyright : (C) 2007 by Thomas Baumgart + email : Thomas Baumgart <ipwizard@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. * + * * + ***************************************************************************/ + +#include <typeinfo> + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include "kdecompat.h" +#include <klocale.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/mymoneyutils.h> +#include <kmymoney/mymoneysecurity.h> +#include <kmymoney/mymoneytransaction.h> +#include <kmymoney/mymoneyinvesttransaction.h> +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/kmymoneyglobalsettings.h> +#include <kmymoney/kmymoneyaccountcombo.h> +#include <kmymoney/kmymoneycurrencyselector.h> + +#include "../kmymoney2.h" + +#include "kinvestmentview.h" +#include "kinvestmentlistitem.h" + +class KInvestmentView::Private +{ +public: + Private() : + m_needReload(false), + m_newAccountLoaded(false), + m_recursion(false), + m_precision(2) {} + + MyMoneyAccount m_account; + bool m_needReload; + bool m_newAccountLoaded; + bool m_recursion; + int m_precision; +}; + + + +KInvestmentView::KInvestmentView(QWidget *parent, const char *name) : + KInvestmentViewDecl(parent,name), + d(new Private) +{ + m_table->setRootIsDecorated(false); + // m_table->setColumnText(0, i18n("Symbol")); + m_table->addColumn(i18n("Name")); + m_table->addColumn(i18n("Symbol")); + + int col = m_table->addColumn(i18n("Value")); + m_table->setColumnAlignment(col, Qt::AlignRight); + + col = m_table->addColumn(i18n("Quantity")); + m_table->setColumnAlignment(col, Qt::AlignRight); + + col = m_table->addColumn(i18n("Price")); + m_table->setColumnAlignment(col, Qt::AlignRight); + + m_table->setMultiSelection(false); + m_table->setColumnWidthMode(0, QListView::Maximum); + m_table->header()->setResizeEnabled(true); + m_table->setAllColumnsShowFocus(true); + m_table->setShowSortIndicator(true); + m_table->restoreLayout(KGlobal::config(), "Investment Settings"); + + connect(m_table, SIGNAL(contextMenu(KListView*, QListViewItem* , const QPoint&)), + this, SLOT(slotListContextMenu(KListView*, QListViewItem*, const QPoint&))); + connect(m_table, SIGNAL(selectionChanged(QListViewItem *)), this, SLOT(slotSelectionChanged(QListViewItem *))); + + connect(m_accountComboBox, SIGNAL(accountSelected(const QString&)), + this, SLOT(slotSelectAccount(const QString&))); + + connect(m_table, SIGNAL(doubleClicked(QListViewItem*,const QPoint&, int)), kmymoney2->action("investment_edit"), SLOT(activate())); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); +} + +KInvestmentView::~KInvestmentView() +{ + m_table->saveLayout(KGlobal::config(), "Investment Settings"); + delete d; +} + +void KInvestmentView::slotSelectionChanged(QListViewItem *item) +{ + kmymoney2->slotSelectInvestment(); + + KInvestmentListItem *pItem = dynamic_cast<KInvestmentListItem*>(item); + if(pItem) { + try { + MyMoneyAccount account = MyMoneyFile::instance()->account(pItem->account().id()); + kmymoney2->slotSelectInvestment(account); + + } catch (MyMoneyException *e) { + delete e; + } + } +} + +void KInvestmentView::slotListContextMenu(KListView* /* lv */, QListViewItem* /*item*/, const QPoint& /*point*/) +{ + kmymoney2->slotSelectInvestment(); + KInvestmentListItem *pItem = dynamic_cast<KInvestmentListItem*>(m_table->selectedItem()); + if(pItem) { + kmymoney2->slotSelectInvestment(MyMoneyFile::instance()->account(pItem->account().id())); + } + emit investmentRightMouseClick(); +} + +void KInvestmentView::slotLoadView(void) +{ + d->m_needReload = true; + if(isVisible()) { + loadView(); + d->m_needReload = false; + // force a new account if the current one is empty + d->m_newAccountLoaded = d->m_account.id().isEmpty(); + } +} + +void KInvestmentView::loadAccounts(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + // check if the current account still exists and make it the + // current account + if(!d->m_account.id().isEmpty()) { + try { + d->m_account = file->account(d->m_account.id()); + } catch(MyMoneyException *e) { + delete e; + d->m_account = MyMoneyAccount(); + } + } + + m_accountComboBox->loadList(MyMoneyAccount::Investment); + + if(d->m_account.id().isEmpty()) { + QStringList list = m_accountComboBox->accountList(); + if(list.count()) { + QStringList::Iterator it; + for(it = list.begin(); it != list.end(); ++it) { + MyMoneyAccount a = file->account(*it); + if(a.accountType() == MyMoneyAccount::Investment) { + if(a.value("PreferredAccount") == "Yes") { + d->m_account = a; + break; + } else if(d->m_account.id().isEmpty()) { + d->m_account = a; + } + } + } + } + } + + if(!d->m_account.id().isEmpty()) { + m_accountComboBox->setSelected(d->m_account); + try { + d->m_precision = MyMoneyMoney::denomToPrec(d->m_account.fraction()); + } catch(MyMoneyException *e) { + qDebug("Security %s for account %s not found", d->m_account.currencyId().data(), d->m_account.name().data()); + delete e; + d->m_precision = 2; + } + } +} + + +bool KInvestmentView::slotSelectAccount(const MyMoneyObject& obj) +{ + if(typeid(obj) != typeid(MyMoneyAccount)) + return false; + + if(d->m_recursion) + return false; + + d->m_recursion = true; + const MyMoneyAccount& acc = dynamic_cast<const MyMoneyAccount&>(obj); + bool rc = slotSelectAccount(acc.id()); + d->m_recursion = false; + return rc; +} + +bool KInvestmentView::slotSelectAccount(const QString& id, const QString& transactionId, const bool /* reconciliation*/) +{ + bool rc = true; + + if(!id.isEmpty()) { + // if the account id differs, then we have to do something + if(d->m_account.id() != id) { + try { + d->m_account = MyMoneyFile::instance()->account(id); + // if a stock account is selected, we show the + // the corresponding parent (investment) account + if(d->m_account.isInvest()) { + d->m_account = MyMoneyFile::instance()->account(d->m_account.parentAccountId()); + } + // TODO if we don't have an investment account, then we should switch to the ledger view + d->m_newAccountLoaded = true; + if(d->m_account.accountType() == MyMoneyAccount::Investment) { + slotLoadView(); + } else { + emit accountSelected(id, transactionId); + d->m_account = MyMoneyAccount(); + d->m_needReload = true; + rc = false; + } + + } catch(MyMoneyException* e) { + qDebug("Unable to retrieve account %s", id.data()); + delete e; + rc = false; + } + } else { + emit accountSelected(d->m_account); + } + } + + return rc; +} + +void KInvestmentView::clear(void) +{ + // setup header font + QFont font = KMyMoneyGlobalSettings::listHeaderFont(); + QFontMetrics fm( font ); + int height = fm.lineSpacing()+6; + m_table->header()->setMinimumHeight(height); + m_table->header()->setMaximumHeight(height); + m_table->header()->setFont(font); + + // setup cell font + font = KMyMoneyGlobalSettings::listCellFont(); + m_table->setFont(font); + + // clear the table + m_table->clear(); + + // and the selected account in the combo box + m_accountComboBox->setSelected(QString()); +} + +void KInvestmentView::loadView(void) +{ + // no account selected + emit accountSelected(MyMoneyAccount()); + + // clear the current contents ... + clear(); + + // ... load the combobox widget and select current account ... + loadAccounts(); + + if(d->m_account.id().isEmpty()) { + // if we don't have an account we bail out + setEnabled(false); + return; + } + setEnabled(true); + + MyMoneyFile* file = MyMoneyFile::instance(); + bool showClosedAccounts = kmymoney2->toggleAction("view_show_all_accounts")->isChecked() + || !KMyMoneyGlobalSettings::hideClosedAccounts(); + try { + d->m_account = file->account(d->m_account.id()); + QStringList securities = d->m_account.accountList(); + + for(QStringList::ConstIterator it = securities.begin(); it != securities.end(); ++it) { + MyMoneyAccount acc = file->account(*it); + if(!acc.isClosed() || showClosedAccounts) + new KInvestmentListItem(m_table, acc); + } + } catch(MyMoneyException* e) { + qDebug("KInvestmentView::loadView() - selected account does not exist anymore"); + d->m_account = MyMoneyAccount(); + delete e; + } + + // and tell everyone what's selected + emit accountSelected(d->m_account); +} + +void KInvestmentView::show(void) +{ + if(d->m_needReload) { + loadView(); + d->m_needReload = false; + d->m_newAccountLoaded = false; + + } else { + emit accountSelected(d->m_account); + } + + // don't forget base class implementation + KInvestmentViewDecl::show(); +} + +#include "kinvestmentview.moc" diff --git a/kmymoney2/views/kinvestmentview.h b/kmymoney2/views/kinvestmentview.h new file mode 100644 index 0000000..ba0aecc --- /dev/null +++ b/kmymoney2/views/kinvestmentview.h @@ -0,0 +1,136 @@ +/*************************************************************************** + kinvestmentview.h - description + ------------------- + begin : Tue Jan 29 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KINVESTMENTVIEW_H +#define KINVESTMENTVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneysecurity.h> +#include <kmymoney/mymoneyaccount.h> +#include "kinvestmentviewdecl.h" +#include "kinvestmentlistitem.h" + +class MyMoneyTransaction; +class MyMoneyInvestTransaction; + +/** + * @author Kevin Tambascio + */ + +class KInvestmentView : public KInvestmentViewDecl +{ + Q_OBJECT + +public: + KInvestmentView(QWidget *parent=0, const char *name=0); + ~KInvestmentView(); + + /** + * Start reconciliation for the account in the current view + */ + void reconcileAccount(void); + +public slots: + /** + * This slot is used to reload all data from the MyMoneyFile engine. + * All existing data in the view will be discarded. + * Call this e.g. if a new file has been loaded. + */ + void slotLoadView(void); + + /** + * This slot is used to select the correct ledger view type for + * the account specified by @p id. If @p transactionId is not + * empty, then the respective transaction will be selected. + * + * @param accountId Internal id used for the account to show + * @param transactionId Internal id used for the transaction to select + * @param reconciliation if true, the account will be selected in + * reconciliation mode. If false, it will + * be selected in regular ledger mode. + * + * @retval true selection of account referenced by @p id succeeded + * @retval false selection of account failed + */ + bool slotSelectAccount(const QString& accountId, const QString& transactionId = QString(), const bool reconciliation = false); + + /** + * This method is provided for convenience and acts as the method above. + */ + bool slotSelectAccount(const MyMoneyObject& acc); + + void show(void); + +protected: + /** + * This method reloads the account selection combo box of the + * view with all asset and liability accounts from the engine. + * If the account id of the current account held in @p m_accountId is + * empty or if the referenced account does not exist in the engine, + * the first account found in the list will be made the current account. + */ + void loadAccounts(void); + + /** + * clear the view + */ + void clear(void); + + void loadView(void); + +protected slots: + /** + * This slot receives the signal from the listview @c lv control that the context menu + * was requested for @c item at @c point. + */ + void slotListContextMenu(KListView* lv, QListViewItem* item, const QPoint& point); + + void slotSelectionChanged(QListViewItem *item); + + +signals: + /** + * This signal is emitted, if an account has been selected + * which cannot handled by this view. + */ + void accountSelected(const QString& accountId, const QString& transactionId); + + void accountSelected(const MyMoneyObject&); + + void investmentRightMouseClick(void); + +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; +}; + +#endif diff --git a/kmymoney2/views/kinvestmentviewdecl.ui b/kmymoney2/views/kinvestmentviewdecl.ui new file mode 100644 index 0000000..1c74d50 --- /dev/null +++ b/kmymoney2/views/kinvestmentviewdecl.ui @@ -0,0 +1,83 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KInvestmentViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KInvestmentViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>707</width> + <height>346</height> + </rect> + </property> + <property name="caption"> + <string>Investment Summary</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Select Account:</string> + </property> + </widget> + <widget class="KMyMoneyAccountCombo"> + <property name="name"> + <cstring>m_accountComboBox</cstring> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>280</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <widget class="KListView"> + <property name="name"> + <cstring>m_table</cstring> + </property> + <property name="font"> + <font> + </font> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + <property name="toolTip" stdset="0"> + <string>Summary of the equities contained in this account, showing your holdings and their most recent price.</string> + </property> + </widget> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kmymoneyfile.cpp b/kmymoney2/views/kmymoneyfile.cpp new file mode 100644 index 0000000..4046cc0 --- /dev/null +++ b/kmymoney2/views/kmymoneyfile.cpp @@ -0,0 +1,114 @@ +/*************************************************************************** + kmymoneyfile.cpp - description + ------------------- + begin : Mon Jun 10 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +/* + * This file is currently not used anymore, but kept here for reference purposes + */ +#if 0 + +#include <klocale.h> + +#include "kmymoneyfile.h" +#include "../mymoney/storage/mymoneyseqaccessmgr.h" + +KMyMoneyFile::KMyMoneyFile() +{ + // m_file = MyMoneyFile::instance(); + m_storage = new MyMoneySeqAccessMgr; + // m_file->attachStorage(m_storage); + m_open = false; // lie a little bit for now +} + +/* +KMyMoneyFile::KMyMoneyFile(const QString&) +{ +} +*/ + +KMyMoneyFile::~KMyMoneyFile() +{ + if(m_storage) { + MyMoneyFile::instance()->detachStorage(m_storage); + delete m_storage; + } + + // if(m_file) + // delete m_file; +} + +/* +KMyMoneyFile *KMyMoneyFile::instance() +{ + if (_instance == 0) { + _instance = new KMyMoneyFile; + } + + return _instance; +} + +MyMoneyFile* KMyMoneyFile::file() +{ + return m_file; +} +*/ + +MyMoneySeqAccessMgr* KMyMoneyFile::storage() +{ + return m_storage; +} + +void KMyMoneyFile::reset() +{ +/* + delete m_storage; + delete m_file; + m_storage = new MyMoneySeqAccessMgr; + m_file = new MyMoneyFile(m_storage); +*/ +} + +void KMyMoneyFile::open() +{ + if(m_storage != 0) + close(); + + m_storage = new MyMoneySeqAccessMgr; + MyMoneyFile::instance()->attachStorage(m_storage); + m_open = true; +} + +void KMyMoneyFile::close() +{ + if(m_storage != 0) { + MyMoneyFile::instance()->detachStorage(m_storage); + delete m_storage; + m_storage = 0; + } + m_open = false; +} + +bool KMyMoneyFile::isOpen() +{ + return m_open; +} + +#endif diff --git a/kmymoney2/views/kmymoneyfile.h b/kmymoney2/views/kmymoneyfile.h new file mode 100644 index 0000000..ccda128 --- /dev/null +++ b/kmymoney2/views/kmymoneyfile.h @@ -0,0 +1,61 @@ +/*************************************************************************** + kmymoneyfile.h - description + ------------------- + begin : Mon Jun 10 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KMYMONEYFILE_H +#define KMYMONEYFILE_H + +/* + * This file is currently not used anymore, but kept here for reference purposes + */ +#if 0 +#include "../mymoney/mymoneyaccount.h" +class MyMoneySeqAccessMgr; + +/** + *@author Michael Edwardes + */ + +class KMyMoneyFile { +private: + // static KMyMoneyFile *_instance; + // MyMoneyFile *m_file; + MyMoneySeqAccessMgr *m_storage; + bool m_open; + +protected: + // KMyMoneyFile(const QString&); + +public: + KMyMoneyFile(); + ~KMyMoneyFile(); +// static KMyMoneyFile *instance(); + + // MyMoneyFile* file(); + MyMoneySeqAccessMgr* storage(); + void reset(); + void open(); + void close(); + bool isOpen(); + +}; +#endif +#endif diff --git a/kmymoney2/views/kmymoneytransaction.cpp b/kmymoney2/views/kmymoneytransaction.cpp new file mode 100644 index 0000000..ee16a6a --- /dev/null +++ b/kmymoney2/views/kmymoneytransaction.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + kmymoneytransaction.cpp - description + ------------------- + begin : Fri Sep 5 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + + +#include "kmymoneytransaction.h" + +KMyMoneyTransaction::KMyMoneyTransaction() +{ +} + +KMyMoneyTransaction::KMyMoneyTransaction(const MyMoneyTransaction& t) : + MyMoneyTransaction(t) +{ +} + +KMyMoneyTransaction::~KMyMoneyTransaction() +{ +} + +void KMyMoneyTransaction::setSplitId(const QString& id) +{ + m_splitId = id; +} + + diff --git a/kmymoney2/views/kmymoneytransaction.h b/kmymoney2/views/kmymoneytransaction.h new file mode 100644 index 0000000..4deb184 --- /dev/null +++ b/kmymoney2/views/kmymoneytransaction.h @@ -0,0 +1,61 @@ +/*************************************************************************** + kmymoneytransaction.h - description + ------------------- + begin : Fri Sep 5 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KMYMONEYTRANSACTION_H +#define KMYMONEYTRANSACTION_H + +// ---------------------------------------------------------------------------- +// QT Includes + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneytransaction.h> + +/** + * @author Thomas Baumgart + */ + +/** + * This class is used to store the information required to + * display a transaction in a ledger view (register). + * It is derived from MyMoneyTransaction but contains additional + * information. + */ +class KMyMoneyTransaction : public MyMoneyTransaction { +public: + KMyMoneyTransaction(); + KMyMoneyTransaction(const MyMoneyTransaction& t); + ~KMyMoneyTransaction(); + + void setSplitId(const QString& id); + const QString& splitId(void) const { return m_splitId; }; + +private: + QString m_splitId; +}; + + +#endif diff --git a/kmymoney2/views/kmymoneyview.cpp b/kmymoney2/views/kmymoneyview.cpp new file mode 100644 index 0000000..8719d79 --- /dev/null +++ b/kmymoney2/views/kmymoneyview.cpp @@ -0,0 +1,2248 @@ +/*************************************************************************** + kmymoneyview.cpp + ------------------- + copyright : (C) 2000 by Michael Edwardes + 2004 by Thomas Baumgart + email : mte@users.sourceforge.net + ipwizard@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. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <unistd.h> + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qlabel.h> +#include <qfile.h> +#include <qtextstream.h> +#include <qprogressdialog.h> +#include <qtextcodec.h> +#include <qstatusbar.h> + +#include <qcursor.h> +#include <qregexp.h> +#include <qlayout.h> +#include <qobjectlist.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include "kdecompat.h" + +#include <kfiledialog.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kicontheme.h> +#include <kiconloader.h> + +#include <kmessagebox.h> +#include <kurl.h> +#include <kio/netaccess.h> +#include <ktempfile.h> +#include <ksavefile.h> +#include <kfilterdev.h> +#include <kfilterbase.h> +#include <kfileitem.h> +#include <kpushbutton.h> +#include <kapplication.h> +#include <kdebug.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +// This is include is required here, because later it will produce +// compile errors on gcc 3.2 as we redefine new() in case of _CHECK_MEMORY +// being defined. To avoid these problems, we just include the header +// already here in this case +#ifdef _CHECK_MEMORY +#include <string> +#endif + +#include "../dialogs/kendingbalancedlg.h" +#include "../dialogs/kchooseimportexportdlg.h" +#include "../dialogs/kcsvprogressdlg.h" +#include "../dialogs/kimportdlg.h" +#include "../dialogs/kexportdlg.h" +#include "../dialogs/knewloanwizard.h" +#include "../dialogs/kcurrencyeditdlg.h" +#include "../dialogs/kfindtransactiondlg.h" +#include "../dialogs/knewbankdlg.h" +#include "../dialogs/knewfiledlg.h" + +#include "../mymoney/storage/mymoneyseqaccessmgr.h" +#include "../mymoney/storage/mymoneydatabasemgr.h" +#include "../mymoney/storage/imymoneystorageformat.h" +#include "../mymoney/storage/mymoneystoragebin.h" +#include "../mymoney/mymoneyexception.h" +#include "../mymoney/storage/mymoneystoragexml.h" +#include "../mymoney/storage/mymoneystoragesql.h" +#include "../converter/mymoneygncreader.h" +#include "../mymoney/storage/mymoneystorageanon.h" + +#include <kmymoney/transactioneditor.h> +#include <kmymoney/kmymoneyglobalsettings.h> + +#include "kmymoneyview.h" +#include "khomeview.h" +#include "kaccountsview.h" +#include "kcategoriesview.h" +#include "kinstitutionsview.h" +#include "kpayeesview.h" +#include "kscheduledview.h" +#include "kgloballedgerview.h" +#include "kinvestmentview.h" +#include "kreportsview.h" +#include "kbudgetview.h" +#include "kforecastview.h" + +#include <kmymoney/kmymoneytitlelabel.h> + + +#include "../kmymoney2.h" +#include "../kmymoneyutils.h" + +#include <libkgpgfile/kgpgfile.h> + +#define COMPRESSION_MIME_TYPE "application/x-gzip" +#define RECOVER_KEY_ID "0xD2B08440" + + +KMyMoneyView::KMyMoneyView(QWidget *parent, const char *name) + : KJanusWidget(parent, name, KJanusWidget::IconList), + // m_bankRightClick(false), + m_inConstructor(true), + m_fileOpen(false), + m_fmode(0600) +{ + // the global variable kmymoney2 is not yet assigned. So we construct it here + QObject* kmymoney2 = parent->parent(); + const int iconSize = (KMyMoneyGlobalSettings::iconSize()+1)*16; + newStorage(); + + // Page 0 + m_homeViewFrame = addVBoxPage( i18n("Home"), i18n("Home"), + DesktopIcon("home", iconSize)); + + m_homeView = new KHomeView(m_homeViewFrame, "HomeView"); + connect(m_homeView, SIGNAL(ledgerSelected(const QString&, const QString&)), + this, SLOT(slotLedgerSelected(const QString&, const QString&))); + connect(m_homeView, SIGNAL(scheduleSelected(const QString&)), + this, SLOT(slotScheduleSelected(const QString&))); + connect(m_homeView, SIGNAL(reportSelected(const QString&)), + this, SLOT(slotShowReport(const QString&))); + + // Page 1 + m_institutionsViewFrame = addVBoxPage( i18n("Institutions"), i18n("Institutions"), + DesktopIcon("institutions", iconSize)); + addTitleBar(m_institutionsViewFrame, i18n("Institutions")); + m_institutionsView = new KInstitutionsView(m_institutionsViewFrame, "InstitutionsView"); + connect(m_institutionsView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectInstitution(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowAccountContextMenu(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowInstitutionContextMenu(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(openObject(const MyMoneyObject&)), kmymoney2, SLOT(slotInstitutionEdit(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(openObject(const MyMoneyObject&)), kmymoney2, SLOT(slotAccountOpen(const MyMoneyObject&))); + connect(m_institutionsView, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyInstitution&)), kmymoney2, SLOT(slotReparentAccount(const MyMoneyAccount&, const MyMoneyInstitution&))); + connect(this, SIGNAL(reconciliationStarts(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&)), m_institutionsView, SLOT(slotReconcileAccount(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&))); + + // Page 2 + m_accountsViewFrame = addVBoxPage( i18n("Accounts"), i18n("Accounts"), + DesktopIcon("accounts", iconSize)); + addTitleBar(m_accountsViewFrame, i18n("Accounts")); + m_accountsView = new KAccountsView(m_accountsViewFrame, "AccountsView"); + connect(m_accountsView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_accountsView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectInstitution(const MyMoneyObject&))); + connect(m_accountsView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectInvestment(const MyMoneyObject&))); + connect(m_accountsView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowAccountContextMenu(const MyMoneyObject&))); + connect(m_accountsView, SIGNAL(openObject(const MyMoneyObject&)), kmymoney2, SLOT(slotAccountOpen(const MyMoneyObject&))); + connect(m_accountsView, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, SLOT(slotReparentAccount(const MyMoneyAccount&, const MyMoneyAccount&))); + connect(this, SIGNAL(kmmFilePlugin(unsigned int)), m_accountsView, SLOT(slotUpdateIconPos(unsigned int))); + connect(this, SIGNAL(reconciliationStarts(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&)), m_accountsView, SLOT(slotReconcileAccount(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&))); + + // Page 3 + m_scheduleViewFrame = addVBoxPage( i18n("Scheduled\ntransactions"), i18n("Bills & Reminders"), + DesktopIcon("schedule", iconSize)); + addTitleBar(m_scheduleViewFrame, i18n("Scheduled transactions")); + m_scheduledView = new KScheduledView(m_scheduleViewFrame, "ScheduledView"); + connect(kmymoney2, SIGNAL(fileLoaded(const KURL&)), m_scheduledView, SLOT(slotReloadView())); + connect(m_scheduledView, SIGNAL(scheduleSelected(const MyMoneySchedule&)), kmymoney2, SLOT(slotSelectSchedule(const MyMoneySchedule&))); + connect(m_scheduledView, SIGNAL(openContextMenu()), kmymoney2, SLOT(slotShowScheduleContextMenu())); + connect(m_scheduledView, SIGNAL(enterSchedule()), kmymoney2, SLOT(slotScheduleEnter())); + connect(m_scheduledView, SIGNAL(skipSchedule()), kmymoney2, SLOT(slotScheduleSkip())); + connect(m_scheduledView, SIGNAL(editSchedule()), kmymoney2, SLOT(slotScheduleEdit())); + + // Page 4 + m_categoriesViewFrame = addVBoxPage( i18n("Categories"), i18n("Categories"), + DesktopIcon("categories", iconSize)); + addTitleBar(m_categoriesViewFrame, i18n("Categories")); + m_categoriesView = new KCategoriesView(m_categoriesViewFrame, "CategoriesView"); + connect(m_categoriesView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_categoriesView, SIGNAL(selectObject(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectInstitution(const MyMoneyObject&))); + connect(m_categoriesView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowAccountContextMenu(const MyMoneyObject&))); + connect(m_categoriesView, SIGNAL(openObject(const MyMoneyObject&)), kmymoney2, SLOT(slotAccountOpen(const MyMoneyObject&))); + connect(m_categoriesView, SIGNAL(reparent(const MyMoneyAccount&, const MyMoneyAccount&)), kmymoney2, SLOT(slotReparentAccount(const MyMoneyAccount&, const MyMoneyAccount&))); + + // Page 5 + m_payeesViewFrame = addVBoxPage( i18n("Payees"), i18n("Payees"), + DesktopIcon("payee", iconSize)); + addTitleBar(m_payeesViewFrame, i18n("Payees")); + m_payeesView = new KPayeesView(m_payeesViewFrame, "PayeesView"); + connect(kmymoney2, SIGNAL(payeeCreated(const QString&)), m_payeesView, SLOT(slotSelectPayeeAndTransaction(const QString&))); + connect(kmymoney2, SIGNAL(payeeRename()), m_payeesView, SLOT(slotStartRename())); + connect(m_payeesView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowPayeeContextMenu())); + connect(m_payeesView, SIGNAL(selectObjects(const QValueList<MyMoneyPayee>&)), kmymoney2, SLOT(slotSelectPayees(const QValueList<MyMoneyPayee>&))); + connect(m_payeesView, SIGNAL(transactionSelected(const QString&, const QString&)), + this, SLOT(slotLedgerSelected(const QString&, const QString&))); + + // Page 6 + m_ledgerViewFrame = addVBoxPage( i18n("Ledgers"), i18n("Ledgers"), + DesktopIcon("ledger", iconSize)); + m_ledgerView = new KGlobalLedgerView(m_ledgerViewFrame, "GlobalLedgerView"); + connect(m_ledgerView, SIGNAL(accountSelected(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_ledgerView, SIGNAL(openContextMenu()), kmymoney2, SLOT(slotShowTransactionContextMenu())); + connect(m_ledgerView, SIGNAL(transactionsSelected(const KMyMoneyRegister::SelectedTransactions&)), kmymoney2, SLOT(slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions&))); + connect(m_ledgerView, SIGNAL(newTransaction()), kmymoney2, SLOT(slotTransactionsNew())); + connect(m_ledgerView, SIGNAL(cancelOrEndEdit(bool&)), kmymoney2, SLOT(slotTransactionsCancelOrEnter(bool&))); + connect(m_ledgerView, SIGNAL(startEdit()), kmymoney2, SLOT(slotTransactionsEdit())); + connect(m_ledgerView, SIGNAL(endEdit()), kmymoney2, SLOT(slotTransactionsEnter())); + connect(m_ledgerView, SIGNAL(toggleReconciliationFlag()), kmymoney2, SLOT(slotToggleReconciliationFlag())); + connect(this, SIGNAL(reconciliationStarts(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&)), m_ledgerView, SLOT(slotSetReconcileAccount(const MyMoneyAccount&, const QDate&, const MyMoneyMoney&))); + connect(kmymoney2, SIGNAL(selectAllTransactions()), m_ledgerView, SLOT(slotSelectAllTransactions())); + + // Page 7 + m_investmentViewFrame = addVBoxPage( i18n("Investments"), i18n("Investments"), + DesktopIcon("investments", iconSize)); + addTitleBar(m_investmentViewFrame, i18n("Investments")); + m_investmentView = new KInvestmentView(m_investmentViewFrame, "InvestmentView"); + connect(m_investmentView, SIGNAL(accountSelected(const QString&, const QString&)), + this, SLOT(slotLedgerSelected(const QString&, const QString&))); + connect(m_investmentView, SIGNAL(accountSelected(const MyMoneyObject&)), kmymoney2, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_investmentView, SIGNAL(investmentRightMouseClick()), kmymoney2, SLOT(slotShowInvestmentContextMenu())); + + // Page 8 + m_reportsViewFrame = addVBoxPage(i18n("Reports"), i18n("Reports"), + DesktopIcon("report", iconSize)); + m_reportsView = new KReportsView(m_reportsViewFrame, "ReportsView"); + + // Page 9 + m_budgetViewFrame = addVBoxPage(i18n("Budgets"), i18n("Budgets"), + DesktopIcon("budget", iconSize)); + addTitleBar(m_budgetViewFrame, i18n("Budgets")); + m_budgetView = new KBudgetView(m_budgetViewFrame, "BudgetView"); + connect(kmymoney2, SIGNAL(fileLoaded(const KURL&)), m_budgetView, SLOT(slotRefreshView())); + connect(m_budgetView, SIGNAL(openContextMenu(const MyMoneyObject&)), kmymoney2, SLOT(slotShowBudgetContextMenu())); + connect(m_budgetView, SIGNAL(selectObjects(const QValueList<MyMoneyBudget>&)), kmymoney2, SLOT(slotSelectBudget(const QValueList<MyMoneyBudget>&))); + connect(kmymoney2, SIGNAL(budgetRename()), m_budgetView, SLOT(slotStartRename())); + + // Page 10 + m_forecastViewFrame = addVBoxPage( i18n("Forecast"), i18n("Forecast"), + DesktopIcon("forcast", iconSize)); + addTitleBar(m_forecastViewFrame, i18n("Forecast")); + m_forecastView = new KForecastView(m_forecastViewFrame, "ForecastView"); + + // get rid of the title text + QWidget* widget = dynamic_cast<QWidget*>(child("KJanusWidgetTitleLabel", "QLabel")); + if(widget) + widget->hide(); + + // and the separator below it + widget = dynamic_cast<QWidget*>(child(0, "KSeparator")); + if(widget) + widget->hide(); + + // select the page first, before connecting the aboutToShow signal + // because we don't want to override the information stored in the config file + showPage(0); + connect(this, SIGNAL(aboutToShowPage(QWidget*)), this, SLOT(slotRememberPage(QWidget*))); + + m_inConstructor = false; +} + +KMyMoneyView::~KMyMoneyView() +{ + removeStorage(); +} + +void KMyMoneyView::addTitleBar(QWidget* parent, const QString& title) +{ + KMyMoneyTitleLabel* label = new KMyMoneyTitleLabel( parent, "titleLabel" ); + label->setMinimumSize( QSize( 100, 30 ) ); + label->setRightImageFile("pics/titlelabel_background.png" ); + label->setText(title); +} + +void KMyMoneyView::showTitleBar(bool show) +{ + QObjectList *l = queryList( 0, "titleLabel" ); + QObjectListIterator it( *l ); // iterate over the labels + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + ((QWidget*)obj)->setShown( show ); + } + delete l; // delete the list, not the objects +} + +bool KMyMoneyView::showPage(int index) +{ + // reset all selected items before showing the selected view + // but not while we're in our own constructor + if(!m_inConstructor && index != activePageIndex()) { + kmymoney2->slotResetSelections(); + } + + // pretend we're in the constructor to avoid calling the + // above resets. For some reason which I don't know the details + // of, KJanusWidget::showPage() calls itself recursively. This + // screws up the action handling, as items could have been selected + // in the meantime. We prevent this by setting the m_inConstructor + // to true and reset it to the previos value when we leave this method. + bool prevConstructor = m_inConstructor; + m_inConstructor = true; + + bool rc = KJanusWidget::showPage(index); + + m_inConstructor = prevConstructor; + + if(!m_inConstructor) { + // fixup some actions that are dependant on the view + // this does not work during construction + kmymoney2->slotUpdateActions(); + } + + return rc; +} + +bool KMyMoneyView::canPrint(void) +{ + bool rc = ( + activePageIndex() == pageIndex(m_reportsViewFrame) || + activePageIndex() == pageIndex(m_homeViewFrame) + ); + return rc; +} + +bool KMyMoneyView::canCreateTransactions(const KMyMoneyRegister::SelectedTransactions& /* list */, QString& tooltip) const +{ + // we can only create transactions in the ledger view so + // we check that this is the active page + + bool rc = (activePageIndex() == pageIndex(m_ledgerViewFrame)); + if(rc) + rc = m_ledgerView->canCreateTransactions(tooltip); + else + tooltip = i18n("Creating transactions can only be performed in the ledger view"); + return rc; +} + +bool KMyMoneyView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + // we can only modify transactions in the ledger view so + // we check that this is the active page + + bool rc = (activePageIndex() == pageIndex(m_ledgerViewFrame)); + + if(rc) { + rc = m_ledgerView->canModifyTransactions(list, tooltip); + } else { + tooltip = i18n("Modifying transactions can only be performed in the ledger view"); + } + return rc; +} + +bool KMyMoneyView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + // we can only duplicate transactions in the ledger view so + // we check that this is the active page + + bool rc = (activePageIndex() == pageIndex(m_ledgerViewFrame)); + + if(rc) { + rc = m_ledgerView->canDuplicateTransactions(list, tooltip); + } else { + tooltip = i18n("Duplicating transactions can only be performed in the ledger view"); + } + return rc; +} + +bool KMyMoneyView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const +{ + bool rc; + // we can only edit transactions in the ledger view so + // we check that this is the active page + + if((rc = canModifyTransactions(list, tooltip)) == true) { + tooltip = i18n("Edit the current selected transactions"); + rc = m_ledgerView->canEditTransactions(list, tooltip); + } + return rc; +} + +bool KMyMoneyView::createNewTransaction(void) +{ + bool rc = false; + KMyMoneyRegister::SelectedTransactions list; + QString txt; + if(canCreateTransactions(list, txt)) { + rc = m_ledgerView->selectEmptyTransaction(); + } + return rc; +} + +TransactionEditor* KMyMoneyView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) +{ + TransactionEditor* editor = 0; + QString txt; + if(canEditTransactions(list, txt) || canCreateTransactions(list, txt)) { + editor = m_ledgerView->startEdit(list); + } + return editor; +} + +void KMyMoneyView::newStorage(storageTypeE t) +{ + removeStorage(); + MyMoneyFile* file = MyMoneyFile::instance(); + if (t == Memory) file->attachStorage(new MyMoneySeqAccessMgr); + else file->attachStorage(new MyMoneyDatabaseMgr); +} + +void KMyMoneyView::removeStorage(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + IMyMoneyStorage* p = file->storage(); + if(p != 0) { + file->detachStorage(p); + delete p; + } +} + +void KMyMoneyView::enableViews(int state) +{ + if(state == -1) + state = m_fileOpen; + + m_accountsViewFrame->setEnabled(state); + m_institutionsViewFrame->setEnabled(state); + m_scheduleViewFrame->setEnabled(state); + m_categoriesViewFrame->setEnabled(state); + m_payeesViewFrame->setEnabled(state); + m_budgetViewFrame->setEnabled(state); + m_ledgerViewFrame->setEnabled(state); + m_investmentViewFrame->setEnabled(state); + m_reportsViewFrame->setEnabled(state); + m_forecastViewFrame->setEnabled(state); + + emit viewStateChanged(state != 0); +} + +void KMyMoneyView::slotLedgerSelected(const QString& _accId, const QString& transaction) +{ + MyMoneyAccount acc = MyMoneyFile::instance()->account(_accId); + QString accId(_accId); + + switch(acc.accountType()) { + case MyMoneyAccount::Stock: + // if a stock account is selected, we show the + // the corresponding parent (investment) account + acc = MyMoneyFile::instance()->account(acc.parentAccountId()); + accId = acc.id(); + // tricky fall through here + + case MyMoneyAccount::Checkings: + case MyMoneyAccount::Savings: + case MyMoneyAccount::Cash: + case MyMoneyAccount::CreditCard: + case MyMoneyAccount::Loan: + case MyMoneyAccount::Asset: + case MyMoneyAccount::Liability: + case MyMoneyAccount::AssetLoan: + case MyMoneyAccount::Income: + case MyMoneyAccount::Expense: + case MyMoneyAccount::Investment: + case MyMoneyAccount::Equity: + showPage(pageIndex(m_ledgerViewFrame)); + m_ledgerView->slotSelectAccount(accId, transaction); + break; + + case MyMoneyAccount::CertificateDep: + case MyMoneyAccount::MoneyMarket: + case MyMoneyAccount::Currency: + qDebug("No ledger view available for account type %d", acc.accountType()); + break; + + default: + qDebug("Unknown account type %d in KMyMoneyView::slotLedgerSelected", acc.accountType()); + break; + } +} + +void KMyMoneyView::slotPayeeSelected(const QString& payee, const QString& account, const QString& transaction) +{ + showPage(pageIndex(m_payeesViewFrame)); + m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); +} + +void KMyMoneyView::slotScheduleSelected(const QString& scheduleId) +{ + MyMoneySchedule sched = MyMoneyFile::instance()->schedule(scheduleId); + kmymoney2->slotSelectSchedule(sched); +} + +void KMyMoneyView::slotShowReport(const QString& reportid) +{ + showPage(pageIndex(m_reportsViewFrame)); + m_reportsView->slotOpenReport(reportid); +} + +void KMyMoneyView::slotShowReport(const MyMoneyReport& report) +{ + showPage(pageIndex(m_reportsViewFrame)); + m_reportsView->slotOpenReport(report); +} + +bool KMyMoneyView::fileOpen(void) +{ + return m_fileOpen; +} + +void KMyMoneyView::closeFile(void) +{ + if ( m_reportsView ) + m_reportsView->slotCloseAll(); + + emit kmmFilePlugin (preClose); + if (isDatabase()) + MyMoneyFile::instance()->storage()->close(); // to log off a database user + newStorage(); + slotShowHomePage(); + + emit kmmFilePlugin (postClose); + m_fileOpen = false; +} + +void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) +{ + buf = &buf[len-1]; + while(len--) { + qfile->ungetch(*buf--); + } +} + +bool KMyMoneyView::readFile(const KURL& url) +{ + QString filename; + +// newStorage(); + m_fileOpen = false; + bool isEncrypted = false; + + IMyMoneyStorageFormat* pReader = NULL; + +#if KDE_IS_VERSION(3,2,0) + if(!url.isValid()) { +#else + if(url.isMalformed()) { +#endif + qDebug("Invalid URL '%s'", url.url().latin1()); + return false; + } + + if (url.protocol() == "sql") { // handle reading of database + //newStorage(Database); + MyMoneyFile::instance()->detachStorage(); + m_fileType = KmmDb; + // get rid of the mode parameter which is now redundant + KURL newUrl(url); + if (!url.queryItem("mode").isNull()) { + newUrl.removeQueryItem("mode"); + } + return (openDatabase(newUrl)); // on error, any message will have been displayed + } + + newStorage(); + + if(url.isLocalFile()) { + filename = url.path(); + + } else { + if(!KIO::NetAccess::download(url, filename, NULL)) { + KMessageBox::detailedError(this, + i18n("Error while loading file '%1'!").arg(url.url()), + KIO::NetAccess::lastErrorString(), + i18n("File access error")); + return false; + } + } + + // let's glimps into the file to figure out, if it's one + // of the old (uncompressed) or new (compressed) files. + QFile file(filename); + QFileInfo info(file); + if(!info.isFile()) { + QString msg=i18n("<b>%1</b> is not a KMyMoney file.").arg(filename); + KMessageBox::error(this, QString("<p>")+msg, i18n("Filetype Error")); + return false; + } + m_fmode = 0600; + m_fmode |= info.permission(QFileInfo::ReadGroup) ? 040 : 0; + m_fmode |= info.permission(QFileInfo::WriteGroup) ? 020 : 0; + m_fmode |= info.permission(QFileInfo::ReadOther) ? 004 : 0; + m_fmode |= info.permission(QFileInfo::WriteOther) ? 002 : 0; + + QIODevice *qfile = 0; + bool rc = true; + + // There's a problem with the KFilterDev and KGPGFile classes: + // One supports the at(n) member but not ungetch() together with + // readBlock() and the other does not provide an at(n) method but + // supports readBlock() that considers the ungetch() buffer. QFile + // supports everything so this is not a problem. We solve the problem + // for now by keeping track of which method can be used. + bool haveAt = true; + + emit kmmFilePlugin (preOpen); + ::timetrace("start reading file"); + if(file.open(IO_ReadOnly)) { + QByteArray hdr(2); + int cnt; + cnt = file.readBlock(hdr.data(), 2); + file.close(); + + if(cnt == 2) { + if(QString(hdr) == QString("\037\213")) { // gzipped? + ::timetrace("detected GZIP"); + qfile = KFilterDev::deviceForFile(filename, COMPRESSION_MIME_TYPE); + } else if(QString(hdr) == QString("--")){ // PGP ASCII armored? + ::timetrace("detected GPG"); + if(KGPGFile::GPGAvailable()) { + ::timetrace("have GPG"); + qfile = new KGPGFile(filename); + haveAt = false; + isEncrypted = true; + } else { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("GPG is not available for decryption of file <b>%1</b>").arg(filename))); + qfile = new QFile(file.name()); + } + } else { + // we can't use file directly, as we delete qfile later on + qfile = new QFile(file.name()); + } + + ::timetrace("open file"); + if(qfile->open(IO_ReadOnly)) { + try { + hdr.resize(8); + if(qfile->readBlock(hdr.data(), 8) == 8) { + if(haveAt) + qfile->at(0); + else + ungetString(qfile, hdr.data(), 8); + + // Ok, we got the first block of 8 bytes. Read in the two + // unsigned long int's by preserving endianess. This is + // achieved by reading them through a QDataStream object + Q_INT32 magic0, magic1; + QDataStream s(hdr, IO_ReadOnly); + s >> magic0; + s >> magic1; + + // If both magic numbers match (we actually read in the + // text 'KMyMoney' then we assume a binary file and + // construct a reader for it. Otherwise, we construct + // an XML reader object. + // + // The expression magic0 < 30 is only used to create + // a binary reader if we assume an old binary file. This + // should be removed at some point. An alternative is to + // check the beginning of the file against an pattern + // of the XML file (e.g. '?<xml' ). + if((magic0 == MAGIC_0_50 && magic1 == MAGIC_0_51) + || magic0 < 30) { + // we do not support this file format anymore + pReader = 0; + m_fileType = KmmBinary; + } else { + ::timetrace("is not binary format"); + // Scan the first 70 bytes to see if we find something + // we know. For now, we support our own XML format and + // GNUCash XML format. If the file is smaller, then it + // contains no valid data and we reject it anyway. + hdr.resize(70); + if(qfile->readBlock(hdr.data(), 70) == 70) { + if(haveAt) + qfile->at(0); + else + ungetString(qfile, hdr.data(), 70); + QRegExp kmyexp("<!DOCTYPE KMYMONEY-FILE>"); + QRegExp gncexp("<gnc-v(\\d+)"); + QCString txt(hdr, 70); + if(kmyexp.search(txt) != -1) { + ::timetrace("is XML format"); + pReader = new MyMoneyStorageXML; + m_fileType = KmmXML; + } else if(gncexp.search(txt) != -1) { + ::timetrace("is GNC format"); + MyMoneyFileTransaction ft; + loadDefaultCurrencies(); // currency list required for gnc + loadAncientCurrencies(); // these too + ft.commit(); + pReader = new MyMoneyGncReader; + m_fileType = GncXML; + } + } + } + if(pReader) { + pReader->setProgressCallback(&KMyMoneyView::progressCallback); + ::timetrace("read data to memory"); + pReader->readFile(qfile, dynamic_cast<IMyMoneySerialize*> (MyMoneyFile::instance()->storage())); + ::timetrace("done reading to memory"); + } else { + if(m_fileType == KmmBinary) { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> contains the old binary format used by KMyMoney. Please use an older version of KMyMoney (0.8.x) that still supports this format to convert it to the new XML based format.").arg(filename))); + } else { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> contains an unknown file format!").arg(filename))); + } + rc = false; + } + } else { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot read from file <b>%1</b>!").arg(filename))); + rc = false; + } + } catch (MyMoneyException *e) { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("Cannot load file <b>%1</b>. Reason: %2").arg(filename, e->what()))); + delete e; + rc = false; + } + if(pReader) { + pReader->setProgressCallback(0); + delete pReader; + } + qfile->close(); + } else { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> not found!").arg(filename))); + rc = false; + } + delete qfile; + } + } else { + KMessageBox::sorry(this, QString("<qt>%1</qt>"). arg(i18n("File <b>%1</b> not found!").arg(filename))); + rc = false; + } + + ::timetrace("done reading file"); + if(rc == false) + return rc; + + // make sure we setup the encryption key correctly + MyMoneyFileTransaction ft; + if(isEncrypted && MyMoneyFile::instance()->value("kmm-encryption-key").isEmpty()) { + MyMoneyFile::instance()->setValue("kmm-encryption-key", KMyMoneyGlobalSettings::gpgRecipientList().join(",")); + } + + // make sure we setup the name of the base accounts in translated form + try { + MyMoneyFile* file = MyMoneyFile::instance(); + checkAccountName(file->asset(), i18n("Asset")); + checkAccountName(file->liability(), i18n("Liability")); + checkAccountName(file->income(), i18n("Income")); + checkAccountName(file->expense(), i18n("Expense")); + checkAccountName(file->equity(), i18n("Equity")); + ft.commit(); + } catch(MyMoneyException* e) { + delete e; + } + + // if a temporary file was constructed by NetAccess::download, + // then it will be removed with the next call. Otherwise, it + // stays untouched on the local filesystem + KIO::NetAccess::removeTempFile(filename); + return initializeStorage(); +} + +void KMyMoneyView::checkAccountName(const MyMoneyAccount& _acc, const QString& name) const +{ + MyMoneyFile* file = MyMoneyFile::instance(); + if(_acc.name() != name) { + MyMoneyAccount acc(_acc); + acc.setName(name); + file->modifyAccount(acc); + } +} + +bool KMyMoneyView::openDatabase (const KURL& url) { + ::timetrace("start opening database"); + m_fileOpen = false; + + // open the database + IMyMoneySerialize* pStorage = dynamic_cast<IMyMoneySerialize*> (MyMoneyFile::instance()->storage()); + MyMoneyDatabaseMgr* pDBMgr = 0; + if (! pStorage) { + pDBMgr = new MyMoneyDatabaseMgr; + pStorage = dynamic_cast<IMyMoneySerialize*> (pDBMgr); + } + KSharedPtr <MyMoneyStorageSql> reader = pStorage->connectToDatabase (url); + KURL dbURL (url); + bool retry = true; + while (retry) { + switch (reader->open(dbURL, IO_ReadWrite)) { + case 0: // opened okay + retry = false; + break; + case 1: // permanent error + KMessageBox::detailedError (this, i18n("Can't open database %1\n").arg(dbURL.prettyURL()), reader->lastError()); + if (pDBMgr) { + removeStorage(); + delete pDBMgr; + } + return false; + case -1: // retryable error + if (KMessageBox::warningYesNo (this, reader->lastError(), PACKAGE) == KMessageBox::No) { + if (pDBMgr) { + removeStorage(); + delete pDBMgr; + } + return false; + } else { + QString options = dbURL.queryItem("options") + ",override"; + dbURL.removeQueryItem("mode"); // now redundant + dbURL.removeQueryItem("options"); + dbURL.addQueryItem("options", options); + } + } + } + if (pDBMgr) { + removeStorage(); + MyMoneyFile::instance()->attachStorage(pDBMgr); + } + // single user mode; read some of the data into memory + // FIXME - readFile no longer relevant? + // tried removing it but then then got no indication that loading was complete + // also, didn't show home page + reader->setProgressCallback(&KMyMoneyView::progressCallback); + if (!reader->readFile()) { + KMessageBox::detailedError (0, + i18n("An unrecoverable error occurred while reading the database"), + reader->lastError().latin1(), + i18n("Database malfunction")); + return false; + } + m_fileOpen = true; + reader->setProgressCallback(0); + ::timetrace("done opening database"); + return initializeStorage(); +} + +bool KMyMoneyView::initializeStorage() +{ + bool blocked = MyMoneyFile::instance()->signalsBlocked(); + MyMoneyFile::instance()->blockSignals(true); + + // we check, if we have any currency in the file. If not, we load + // all the default currencies we know. + MyMoneyFileTransaction ft; + try { + loadDefaultCurrencies(); + loadAncientCurrencies(); + ft.commit(); + } catch(MyMoneyException *e) { + delete e; + MyMoneyFile::instance()->blockSignals(blocked); + return false; + } + + // make sure, we have a base currency and all accounts are + // also assigned to a currency. + if(MyMoneyFile::instance()->baseCurrency().id().isEmpty()) { + // Stay in this endless loop until we have a base currency, + // as without it the application does not work anymore. + while(MyMoneyFile::instance()->baseCurrency().id().isEmpty()) + selectBaseCurrency(); + + } else { + // in some odd intermediate cases there could be files out there + // that have a base currency set, but still have accounts that + // do not have a base currency assigned. This call will take + // care of it. We can safely remove it later. + // + // Another work-around for this scenario is to remove the base + // currency setting from the XML file by removing the line + // + // <PAIR key="kmm-baseCurrency" value="xxx" /> + // + // and restart the application with this file. This will force to + // run the above loop. + selectBaseCurrency(); + } + + KConfig *config = KGlobal::config(); + int page; + config->setGroup("General Options"); + if(KMyMoneyGlobalSettings::startLastViewSelected() != 0) { + config->setGroup("Last Use Settings"); + page = config->readNumEntry("LastViewSelected", 0); + } else { + page = pageIndex(m_homeViewFrame); + } + + ::timetrace("start fixing file"); + + // For debugging purposes, we can turn off the automatic fix manually + // by setting the entry in kmymoney2rc to true + config->setGroup("General Options"); + if(config->readBoolEntry("SkipFix", false) != true) { + MyMoneyFileTransaction ft; + try { + // Check if we have to modify the file before we allow to work with it + IMyMoneyStorage* s = MyMoneyFile::instance()->storage(); + while (s->fileFixVersion() < s->currentFixVersion()) { + qDebug("%s", (QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())).data()); + switch (s->fileFixVersion()) { + case 0: + fixFile_0(); + s->setFileFixVersion(1); + break; + + case 1: + fixFile_1(); + s->setFileFixVersion(2); + break; + + case 2: + fixFile_2(); + s->setFileFixVersion(3); + break; + + // add new levels above. Don't forget to increase currentFixVersion() for all + // the storage backends this fix applies to + default: + throw new MYMONEYEXCEPTION(i18n("Unknown fix level in input file")); + } + } + ft.commit(); + } catch(MyMoneyException *e) { + delete e; + MyMoneyFile::instance()->blockSignals(blocked); + return false; + } + } else { + qDebug("Skipping automatic transaction fix!"); + } + MyMoneyFile::instance()->blockSignals(blocked); + + // FIXME: we need to check, if it's necessary to have this + // automatic funcitonality + // if there's no asset account, then automatically start the + // new account wizard + // kmymoney2->createInitialAccount(); + + ::timetrace("file open"); + m_fileOpen = true; + emit kmmFilePlugin (postOpen); + + // inform everyone about new data + MyMoneyFile::instance()->preloadCache(); + MyMoneyFile::instance()->forceDataChanged(); + + // if we currently see a different page, then select the right one + if(page != activePageIndex()) { + showPage(page); + } + + return true; +} + +void KMyMoneyView::saveToLocalFile(QFile* qfile, IMyMoneyStorageFormat* pWriter, bool plaintext, const QString& keyList) +{ + QIODevice *dev = qfile; + KFilterBase *base = 0; + QIODevice *statusDevice = dev; + + bool encryptedOk = true; + bool encryptRecover = false; + if(!keyList.isEmpty()) { + if(!KGPGFile::GPGAvailable()) { + KMessageBox::sorry(this, i18n("GPG does not seem to be installed on your system. Please make sure, that GPG can be found using the standard search path. This time, encryption is disabled."), i18n("GPG not found")); + encryptedOk = false; + } + + if(KMyMoneyGlobalSettings::encryptRecover()) { + encryptRecover = true; + if(!KGPGFile::keyAvailable(QString(RECOVER_KEY_ID))) { + KMessageBox::sorry(this, QString("<p>")+i18n("You have selected to encrypt your data also with the KMyMoney recover key, but the key with id</p><p><center><b>%1</b></center></p>has not been found in your keyring at this time. Please make sure to import this key into your keyring. You can find it on the <a href=\"http://kmymoney2.sourceforge.net/\">KMyMoney web-site</a>. This time your data will not be encrypted with the KMyMoney recover key.").arg(RECOVER_KEY_ID), i18n("GPG-Key not found")); + encryptRecover = false; + } + } + + QStringList keys = QStringList::split(",", keyList); + QStringList::const_iterator it_s; + for(it_s = keys.begin(); it_s != keys.begin(); ++it_s) { + if(!KGPGFile::keyAvailable(*it_s)) { + KMessageBox::sorry(this, QString("<p>")+i18n("You have specified to encrypt your data for the user-id</p><p><center><b>%1</b>.</center></p>Unfortunately, a valid key for this user-id was not found in your keyring. Please make sure to import a valid key for this user-id. This time, encryption is disabled.").arg(*it_s), i18n("GPG-Key not found")); + encryptedOk = false; + } + } + + if(encryptedOk == true) { + QString msg = QString("<p>") + i18n("You have configured to save your data in encrypted form using GPG. Please be aware, that this is a brand new feature which is yet untested. Make sure, you have the necessary understanding that you might loose all your data if you store it encrypted and cannot decrypt it later on! If unsure, answer <b>No</b>."); + + if(KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStdGuiItem::yes(), KStdGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { + encryptedOk = false; + } + } + } + + int mask = umask((~m_fmode) & 0777); + bool blocked = MyMoneyFile::instance()->signalsBlocked(); + MyMoneyFile::instance()->blockSignals(true); + MyMoneyFileTransaction ft; + MyMoneyFile::instance()->deletePair("kmm-encryption-key"); + if(!keyList.isEmpty() && encryptedOk == true && !plaintext ) { + qfile->close(); + base++; + KGPGFile *kgpg = new KGPGFile(qfile->name()); + if(kgpg) { + QStringList keys = QStringList::split(",", keyList); + QStringList::const_iterator it_s; + for(it_s = keys.begin(); it_s != keys.end(); ++it_s) { + kgpg->addRecipient((*it_s).latin1()); + } + if(encryptRecover) { + kgpg->addRecipient(RECOVER_KEY_ID); + } + MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); + } + statusDevice = dev = kgpg; + if(!dev || !dev->open(IO_WriteOnly)) { + MyMoneyFile::instance()->blockSignals(blocked); + delete dev; + throw new MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.").arg(qfile->name())); + } + + } else if(!plaintext) { + + base = KFilterBase::findFilterByMimeType( COMPRESSION_MIME_TYPE ); + if(base) { + qfile->close(); + base->setDevice(qfile, false); + // we need to reopen the file to set the mode inside the filter stuff + dev = new KFilterDev(base, true); + if(!dev || !dev->open(IO_WriteOnly)) { + MyMoneyFile::instance()->blockSignals(blocked); + delete dev; + throw new MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.").arg(qfile->name())); + } + statusDevice = base->device(); + } + } + umask(mask); + ft.commit(); + + pWriter->setProgressCallback(&KMyMoneyView::progressCallback); + dev->resetStatus(); + pWriter->writeFile(dev, dynamic_cast<IMyMoneySerialize*> (MyMoneyFile::instance()->storage())); + MyMoneyFile::instance()->blockSignals(blocked); + if(statusDevice->status() != IO_Ok) { + throw new MYMONEYEXCEPTION(i18n("Failure while writing to '%1'").arg(qfile->name())); + } + pWriter->setProgressCallback(0); + + if(base != 0) { + dev->flush(); + dev->close(); + if(statusDevice->status() != IO_Ok) { + delete dev; + throw new MYMONEYEXCEPTION(i18n("Failure while writing to '%1'").arg(qfile->name())); + } + delete dev; + } else + qfile->close(); +} + +bool KMyMoneyView::saveFile(const KURL& url, const QString& keyList) +{ + QString filename = url.path(); + + if (!fileOpen()) { + KMessageBox::error(this, i18n("Tried to access a file when it's not open")); + return false; + } + +#if 0 + if(KMessageBox::warningContinueCancel(this, i18n( + "Since this version of KMyMoney only writes data files in its new " + "format, files written with this version cannot be read by KMyMoney version 0.4. " + "If you still want to use older versions of KMyMoney with your data files, " + "please make sure you keep a backup-file of your finance data. " + "If you want to abort this operation, please press Cancel now"), + QString::null, KStdGuiItem::cont(), "WarningNewFileVersion0.5") == KMessageBox::Cancel) + return false; +#endif + + emit kmmFilePlugin (preSave); + IMyMoneyStorageFormat* pWriter = NULL; + + // If this file ends in ".ANON.XML" then this should be written using the + // anonymous writer. + bool plaintext = filename.right(4).lower() == ".xml"; + if (filename.right(9).lower() == ".anon.xml") + { + pWriter = new MyMoneyStorageANON; + } + else + { + // only use XML writer. The binary format will be depreacated after 0.8 + pWriter = new MyMoneyStorageXML; + } + + // actually, url should be the parameter to this function + // but for now, this would involve too many changes + bool rc = true; + try { + if(! url.isValid()) { + throw new MYMONEYEXCEPTION(i18n("Malformed URL '%1'").arg(url.url())); + } + + if(url.isLocalFile()) { + filename = url.path(); + int fmode = 0600; + gid_t gid = static_cast<gid_t>(-1); // don't change the group id (see "man 2 chown") + QFileInfo fi(filename); + if(fi.exists()) { + fmode |= fi.permission(QFileInfo::ReadGroup) ? 040 : 0; + fmode |= fi.permission(QFileInfo::WriteGroup) ? 020 : 0; + fmode |= fi.permission(QFileInfo::ReadOther) ? 004 : 0; + fmode |= fi.permission(QFileInfo::WriteOther) ? 002 : 0; + if(fi.groupId() != static_cast<uint>(-2)) + gid = fi.groupId(); + } + + // create a new basic block here, so that the object qfile gets + // deleted, before we reach the chown() call + { + int mask = umask((~fmode) & 0777); + KSaveFile qfile(filename, fmode); + umask(mask); + if(qfile.status() == 0) { + try { + saveToLocalFile(qfile.file(), pWriter, plaintext, keyList); + } catch (MyMoneyException* e) { + qfile.abort(); + delete e; + throw new MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'").arg(filename)); + } + } else { + throw new MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'").arg(filename)); + } + } + chown(filename, static_cast<uid_t>(-1), gid); + } else { + KTempFile tmpfile; + saveToLocalFile(tmpfile.file(), pWriter, plaintext, keyList); + if(!KIO::NetAccess::upload(tmpfile.name(), url, NULL)) + throw new MYMONEYEXCEPTION(i18n("Unable to upload to '%1'").arg(url.url())); + tmpfile.unlink(); + } + m_fileType = KmmXML; + } catch (MyMoneyException *e) { + KMessageBox::error(this, e->what()); + delete e; + MyMoneyFile::instance()->setDirty(); + rc = false; + } + delete pWriter; + emit kmmFilePlugin (postSave); + return rc; +} + +bool KMyMoneyView::saveAsDatabase(const KURL& url) +{ + bool rc = false; + if (!fileOpen()) { + KMessageBox::error(this, i18n("Tried to access a file when it's not open")); + return (rc); + } + MyMoneyStorageSql *writer = new MyMoneyStorageSql(dynamic_cast<IMyMoneySerialize*> (MyMoneyFile::instance()->storage()), url); + bool canWrite = false; + switch (writer->open(url, IO_WriteOnly)) { + case 0: + canWrite = true; + break; + case -1: // dbase already has data, see if he wants to clear it out + if (KMessageBox::warningContinueCancel (0, + i18n("Database contains data which must be removed before using SaveAs.\n" + "Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) { + if (writer->open(url, IO_WriteOnly, true) == 0) canWrite = true; + } else { + delete writer; + return false; + } + break; + } + if (canWrite) { + writer->setProgressCallback(&KMyMoneyView::progressCallback); + if (!writer->writeFile()) { + KMessageBox::detailedError (0, + i18n("An unrecoverable error occurred while writing to the database.\n" + "It may well be corrupt."), + writer->lastError().latin1(), + i18n("Database malfunction")); + rc = false; + } + writer->setProgressCallback(0); + rc = true; + } else { + KMessageBox::detailedError (this, + i18n("Can't open or create database %1\n" + "Retry SaveAsDatabase and click Help" + " for further info").arg(url.prettyURL()), writer->lastError()); + } + delete writer; + return (rc); +} + +bool KMyMoneyView::dirty(void) +{ + if (!fileOpen()) + return false; + + return MyMoneyFile::instance()->dirty(); +} + +bool KMyMoneyView::startReconciliation(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) +{ + bool ok = true; + + // we cannot reconcile standard accounts + if(MyMoneyFile::instance()->isStandardAccount(account.id())) + ok = false; + + // check if we can reconcile this account + // it makes sense for asset and liability accounts only + if(ok == true) { + if(account.isAssetLiability()) { + showPage(pageIndex(m_ledgerViewFrame)); + // prepare reconciliation mode + emit reconciliationStarts(account, reconciliationDate, endingBalance); + } else { + ok = false; + } + } + + return ok; +} + +void KMyMoneyView::finishReconciliation(const MyMoneyAccount& /* account */) +{ + emit reconciliationStarts(MyMoneyAccount(), QDate(), MyMoneyMoney()); +} + +void KMyMoneyView::newFile(void) +{ + closeFile(); + m_fileType = KmmXML; // assume native type until saved + m_fileOpen = true; +} + +void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) +{ + if(!baseCurrency.id().isEmpty()) { + if(baseCurrency.id() != MyMoneyFile::instance()->baseCurrency().id()) { + MyMoneyFileTransaction ft; + try { + MyMoneyFile::instance()->setBaseCurrency(baseCurrency); + ft.commit(); + } catch(MyMoneyException *e) { + KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2").arg(baseCurrency.name()).arg(e->what()), i18n("Set base currency")); + delete e; + } + } + } +} + +void KMyMoneyView::selectBaseCurrency(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + + // check if we have a base currency. If not, we need to select one + if(file->baseCurrency().id().isEmpty()) { + KCurrencyEditDlg dlg(this, "CurrencyEditDlg"); + connect(&dlg, SIGNAL(selectBaseCurrency(const MyMoneySecurity&)), this, SLOT(slotSetBaseCurrency(const MyMoneySecurity&))); + dlg.exec(); + } + + if(!file->baseCurrency().id().isEmpty()) { + // check that all accounts have a currency + QValueList<MyMoneyAccount> list; + file->accountList(list); + QValueList<MyMoneyAccount>::Iterator it; + + // don't forget those standard accounts + list << file->asset(); + list << file->liability(); + list << file->income(); + list << file->expense(); + list << file->equity(); + + + for(it = list.begin(); it != list.end(); ++it) { + if((*it).currencyId().isEmpty() || (*it).currencyId().length() == 0) { + (*it).setCurrencyId(file->baseCurrency().id()); + MyMoneyFileTransaction ft; + try { + file->modifyAccount(*it); + ft.commit(); + } catch(MyMoneyException *e) { + qDebug("Unable to setup base currency in account %s (%s): %s", (*it).name().latin1(), (*it).id().data(), e->what().latin1()); + delete e; + } + } + } + } +} + +void KMyMoneyView::loadDefaultCurrency(const MyMoneySecurity& currency, const bool create) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneySecurity sec; + MyMoneyFileTransaction ft; + try { + sec = file->currency(currency.id()); + if(sec.name() != currency.name()) { + sec.setName(currency.name()); + file->modifyCurrency(sec); + } + ft.commit(); + } catch (MyMoneyException* e) { + delete e; + try { + if(create) { + file->addCurrency(currency); + } + ft.commit(); + } catch (MyMoneyException* e) { + qDebug("Error %s loading default currency", e->what().data()); + delete e; + } + } +} + +void KMyMoneyView::loadDefaultCurrencies(void) +{ + // more information can be obtained from http://en.wikipedia.org/wiki/Currency_codes + + bool create = MyMoneyFile::instance()->currencyList().count() == 0; + loadDefaultCurrency(MyMoneySecurity("AFA", i18n("Afghanistan Afghani")), create); + loadDefaultCurrency(MyMoneySecurity("ALL", i18n("Albanian Lek")), create); + loadDefaultCurrency(MyMoneySecurity("ANG", i18n("Netherland Antillian Guilder")), create); + loadDefaultCurrency(MyMoneySecurity("DZD", i18n("Algerian Dinar")), create); + loadDefaultCurrency(MyMoneySecurity("ADF", i18n("Andorran Franc")), create); + loadDefaultCurrency(MyMoneySecurity("ADP", i18n("Andorran Peseta")), create); + loadDefaultCurrency(MyMoneySecurity("AON", i18n("Angolan New Kwanza")), create); + loadDefaultCurrency(MyMoneySecurity("ARS", i18n("Argentine Peso"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("AWG", i18n("Aruban Florin")), create); + loadDefaultCurrency(MyMoneySecurity("AUD", i18n("Australian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("AZM", i18n("Azerbaijani Manat")), create); + loadDefaultCurrency(MyMoneySecurity("BSD", i18n("Bahamian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("BHD", i18n("Bahraini Dinar"), "BHD", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("BDT", i18n("Bangladeshi Taka")), create); + loadDefaultCurrency(MyMoneySecurity("BBD", i18n("Barbados Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("BYR", i18n("Belarussian Ruble"), "BYR", 1, 1), create); + loadDefaultCurrency(MyMoneySecurity("BZD", i18n("Belize Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("BMD", i18n("Bermudian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("BTN", i18n("Bhutan Ngultrum")), create); + loadDefaultCurrency(MyMoneySecurity("BOB", i18n("Bolivian Boliviano")), create); + loadDefaultCurrency(MyMoneySecurity("BAM", i18n("Bosnian Convertible Mark")), create); + loadDefaultCurrency(MyMoneySecurity("BWP", i18n("Botswana Pula")), create); + loadDefaultCurrency(MyMoneySecurity("BRL", i18n("Brazilian Real"), "R$"), create); + loadDefaultCurrency(MyMoneySecurity("GBP", i18n("British Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("BND", i18n("Brunei Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("BGL", i18n("Bulgarian Lev")), create); + loadDefaultCurrency(MyMoneySecurity("BIF", i18n("Burundi Franc")), create); + loadDefaultCurrency(MyMoneySecurity("XAF", i18n("CFA Franc BEAC")), create); + loadDefaultCurrency(MyMoneySecurity("XOF", i18n("CFA Franc BCEAO")), create); + loadDefaultCurrency(MyMoneySecurity("XPF", i18n("CFP Franc Pacifique"), "F", 1, 1, 100), create); + loadDefaultCurrency(MyMoneySecurity("KHR", i18n("Cambodia Riel")), create); + loadDefaultCurrency(MyMoneySecurity("CAD", i18n("Canadian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("CVE", i18n("Cape Verde Escudo")), create); + loadDefaultCurrency(MyMoneySecurity("KYD", i18n("Cayman Islands Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("CLP", i18n("Chilean Peso")), create); + loadDefaultCurrency(MyMoneySecurity("CNY", i18n("Chinese Yuan Renminbi")), create); + loadDefaultCurrency(MyMoneySecurity("COP", i18n("Colombian Peso")), create); + loadDefaultCurrency(MyMoneySecurity("KMF", i18n("Comoros Franc")), create); + loadDefaultCurrency(MyMoneySecurity("CRC", i18n("Costa Rican Colon"), QChar(0x20A1)), create); + loadDefaultCurrency(MyMoneySecurity("HRK", i18n("Croatian Kuna")), create); + loadDefaultCurrency(MyMoneySecurity("CUP", i18n("Cuban Peso")), create); + loadDefaultCurrency(MyMoneySecurity("CZK", i18n("Czech Koruna")), create); + loadDefaultCurrency(MyMoneySecurity("DKK", i18n("Danish Krone"), "kr"), create); + loadDefaultCurrency(MyMoneySecurity("DJF", i18n("Djibouti Franc")), create); + loadDefaultCurrency(MyMoneySecurity("DOP", i18n("Dominican Peso")), create); + loadDefaultCurrency(MyMoneySecurity("XCD", i18n("East Caribbean Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("EGP", i18n("Egyptian Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("SVC", i18n("El Salvador Colon")), create); + loadDefaultCurrency(MyMoneySecurity("ERN", i18n("Eritrean Nakfa")), create); + loadDefaultCurrency(MyMoneySecurity("EEK", i18n("Estonian Kroon")), create); + loadDefaultCurrency(MyMoneySecurity("ETB", i18n("Ethiopian Birr")), create); + loadDefaultCurrency(MyMoneySecurity("EUR", i18n("Euro"), QChar(0x20ac)), true); + loadDefaultCurrency(MyMoneySecurity("FKP", i18n("Falkland Islands Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("FJD", i18n("Fiji Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("GMD", i18n("Gambian Dalasi")), create); + loadDefaultCurrency(MyMoneySecurity("GEL", i18n("Georgian Lari")), create); + loadDefaultCurrency(MyMoneySecurity("GHC", i18n("Ghanaian Cedi")), create); + loadDefaultCurrency(MyMoneySecurity("GIP", i18n("Gibraltar Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("GTQ", i18n("Guatemalan Quetzal")), create); + loadDefaultCurrency(MyMoneySecurity("GWP", i18n("Guinea-Bissau Peso")), create); + loadDefaultCurrency(MyMoneySecurity("GYD", i18n("Guyanan Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("HTG", i18n("Haitian Gourde")), create); + loadDefaultCurrency(MyMoneySecurity("HNL", i18n("Honduran Lempira")), create); + loadDefaultCurrency(MyMoneySecurity("HKD", i18n("Hong Kong Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("HUF", i18n("Hungarian Forint"), "HUF", 1, 1, 100), create); + loadDefaultCurrency(MyMoneySecurity("ISK", i18n("Iceland Krona")), create); + loadDefaultCurrency(MyMoneySecurity("INR", i18n("Indian Rupee"), QChar(0x20A8)), create); + loadDefaultCurrency(MyMoneySecurity("IDR", i18n("Indonesian Rupiah"), "IDR", 100, 1), create); + loadDefaultCurrency(MyMoneySecurity("IRR", i18n("Iranian Rial"), "IRR", 1, 1), create); + loadDefaultCurrency(MyMoneySecurity("IQD", i18n("Iraqi Dinar"), "IQD", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("ILS", i18n("Israeli New Shekel"), QChar(0x20AA)), create); + loadDefaultCurrency(MyMoneySecurity("JMD", i18n("Jamaican Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("JPY", i18n("Japanese Yen"), QChar(0x00A5), 100, 1), create); + loadDefaultCurrency(MyMoneySecurity("JOD", i18n("Jordanian Dinar"), "JOD", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("KZT", i18n("Kazakhstan Tenge")), create); + loadDefaultCurrency(MyMoneySecurity("KES", i18n("Kenyan Shilling")), create); + loadDefaultCurrency(MyMoneySecurity("KWD", i18n("Kuwaiti Dinar"), "KWD", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("KGS", i18n("Kyrgyzstan Som")), create); + loadDefaultCurrency(MyMoneySecurity("LAK", i18n("Laos Kip"), QChar(0x20AD)), create); + loadDefaultCurrency(MyMoneySecurity("LVL", i18n("Latvian Lats")), create); + loadDefaultCurrency(MyMoneySecurity("LBP", i18n("Lebanese Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("LSL", i18n("Lesotho Loti")), create); + loadDefaultCurrency(MyMoneySecurity("LRD", i18n("Liberian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("LYD", i18n("Libyan Dinar"), "LYD", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("LTL", i18n("Lithuanian Litas")), create); + loadDefaultCurrency(MyMoneySecurity("MOP", i18n("Macau Pataca")), create); + loadDefaultCurrency(MyMoneySecurity("MKD", i18n("Macedonian Denar")), create); + loadDefaultCurrency(MyMoneySecurity("MGF", i18n("Malagasy Franc"), "MGF", 500, 500), create); + loadDefaultCurrency(MyMoneySecurity("MWK", i18n("Malawi Kwacha")), create); + loadDefaultCurrency(MyMoneySecurity("MYR", i18n("Malaysian Ringgit")), create); + loadDefaultCurrency(MyMoneySecurity("MVR", i18n("Maldive Rufiyaa")), create); + loadDefaultCurrency(MyMoneySecurity("MLF", i18n("Mali Republic Franc")), create); + loadDefaultCurrency(MyMoneySecurity("MRO", i18n("Mauritanian Ouguiya"), "MRO", 5, 5), create); + loadDefaultCurrency(MyMoneySecurity("MUR", i18n("Mauritius Rupee")), create); + loadDefaultCurrency(MyMoneySecurity("MXN", i18n("Mexican Peso"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("MDL", i18n("Moldavian Leu")), create); + loadDefaultCurrency(MyMoneySecurity("MNT", i18n("Mongolian Tugrik"), QChar(0x20AE)), create); + loadDefaultCurrency(MyMoneySecurity("MAD", i18n("Moroccan Dirham")), create); + loadDefaultCurrency(MyMoneySecurity("MZM", i18n("Mozambique Metical")), create); + loadDefaultCurrency(MyMoneySecurity("MMK", i18n("Myanmar Kyat")), create); + loadDefaultCurrency(MyMoneySecurity("NAD", i18n("Namibian Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("NPR", i18n("Nepalese Rupee")), create); + loadDefaultCurrency(MyMoneySecurity("NZD", i18n("New Zealand Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("NIC", i18n("Nicaraguan Cordoba Oro")), create); + loadDefaultCurrency(MyMoneySecurity("NGN", i18n("Nigerian Naira"), QChar(0x20A6)), create); + loadDefaultCurrency(MyMoneySecurity("KPW", i18n("North Korean Won"), QChar(0x20A9)), create); + loadDefaultCurrency(MyMoneySecurity("NOK", i18n("Norwegian Kroner"), "kr"), create); + loadDefaultCurrency(MyMoneySecurity("OMR", i18n("Omani Rial"), "OMR", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("PKR", i18n("Pakistan Rupee")), create); + loadDefaultCurrency(MyMoneySecurity("PAB", i18n("Panamanian Balboa")), create); + loadDefaultCurrency(MyMoneySecurity("PGK", i18n("Papua New Guinea Kina")), create); + loadDefaultCurrency(MyMoneySecurity("PYG", i18n("Paraguay Guarani")), create); + loadDefaultCurrency(MyMoneySecurity("PEN", i18n("Peruvian Nuevo Sol")), create); + loadDefaultCurrency(MyMoneySecurity("PHP", i18n("Philippine Peso"), QChar(0x20B1)), create); + loadDefaultCurrency(MyMoneySecurity("PLN", i18n("Polish Zloty")), create); + loadDefaultCurrency(MyMoneySecurity("QAR", i18n("Qatari Rial")), create); + loadDefaultCurrency(MyMoneySecurity("RON", i18n("Romanian Leu (new)")), true); + loadDefaultCurrency(MyMoneySecurity("RUB", i18n("Russian Ruble")), true); + loadDefaultCurrency(MyMoneySecurity("RWF", i18n("Rwanda Franc")), create); + loadDefaultCurrency(MyMoneySecurity("WST", i18n("Samoan Tala")), create); + loadDefaultCurrency(MyMoneySecurity("STD", i18n("Sao Tome and Principe Dobra")), create); + loadDefaultCurrency(MyMoneySecurity("SAR", i18n("Saudi Riyal")), create); + loadDefaultCurrency(MyMoneySecurity("SCR", i18n("Seychelles Rupee")), create); + loadDefaultCurrency(MyMoneySecurity("SLL", i18n("Sierra Leone Leone")), create); + loadDefaultCurrency(MyMoneySecurity("SGD", i18n("Singapore Dollar"), "$"), create); + // loadDefaultCurrency(MyMoneySecurity("SKK", i18n("Slovak Koruna")), create); + // loadDefaultCurrency(MyMoneySecurity("SIT", i18n("Slovenian Tolar")), create); + loadDefaultCurrency(MyMoneySecurity("SBD", i18n("Solomon Islands Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("SOS", i18n("Somali Shilling")), create); + loadDefaultCurrency(MyMoneySecurity("ZAR", i18n("South African Rand")), create); + loadDefaultCurrency(MyMoneySecurity("KRW", i18n("South Korean Won"), QChar(0x20A9)), create); + loadDefaultCurrency(MyMoneySecurity("LKR", i18n("Sri Lanka Rupee")), create); + loadDefaultCurrency(MyMoneySecurity("SHP", i18n("St. Helena Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("SDD", i18n("Sudanese Dinar")), create); + loadDefaultCurrency(MyMoneySecurity("SRG", i18n("Suriname Guilder")), create); + loadDefaultCurrency(MyMoneySecurity("SZL", i18n("Swaziland Lilangeni")), create); + loadDefaultCurrency(MyMoneySecurity("SEK", i18n("Swedish Krona")), create); + loadDefaultCurrency(MyMoneySecurity("CHF", i18n("Swiss Franc"), "SFr"), create); + loadDefaultCurrency(MyMoneySecurity("SYP", i18n("Syrian Pound"), QChar(0x00A3)), create); + loadDefaultCurrency(MyMoneySecurity("TWD", i18n("Taiwan Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("TJS", i18n("Tajikistan Somani")), create); + loadDefaultCurrency(MyMoneySecurity("TZS", i18n("Tanzanian Shilling")), create); + loadDefaultCurrency(MyMoneySecurity("THB", i18n("Thai Baht"), QChar(0x0E3F)), create); + loadDefaultCurrency(MyMoneySecurity("TOP", i18n("Tongan Pa'anga")), create); + loadDefaultCurrency(MyMoneySecurity("TTD", i18n("Trinidad and Tobago Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("TND", i18n("Tunisian Dinar"), "TND", 1000, 1000), create); + loadDefaultCurrency(MyMoneySecurity("TRY", i18n("Turkish Lira (new)"), "YTL"), true); + loadDefaultCurrency(MyMoneySecurity("TMM", i18n("Turkmenistan Manat")), create); + loadDefaultCurrency(MyMoneySecurity("USD", i18n("US Dollar"), "$"), create); + loadDefaultCurrency(MyMoneySecurity("UGX", i18n("Uganda Shilling")), create); + loadDefaultCurrency(MyMoneySecurity("UAH", i18n("Ukraine Hryvnia")), create); + loadDefaultCurrency(MyMoneySecurity("AED", i18n("United Arab Emirates Dirham")), create); + loadDefaultCurrency(MyMoneySecurity("UYU", i18n("Uruguayan Peso")), create); + loadDefaultCurrency(MyMoneySecurity("UZS", i18n("Uzbekistani Sum")), create); + loadDefaultCurrency(MyMoneySecurity("VUV", i18n("Vanuatu Vatu")), create); + loadDefaultCurrency(MyMoneySecurity("VEB", i18n("Venezuelan Bolivar")), create); + loadDefaultCurrency(MyMoneySecurity("VND", i18n("Vietnamese Dong"), QChar(0x20AB)), create); + loadDefaultCurrency(MyMoneySecurity("YUM", i18n("Yugoslav Dinar")), create); + loadDefaultCurrency(MyMoneySecurity("ZMK", i18n("Zambian Kwacha")), create); + loadDefaultCurrency(MyMoneySecurity("ZWD", i18n("Zimbabwe Dollar"), "$"), create); + + loadDefaultCurrency(MyMoneySecurity("XAU", i18n("Gold"), "XAU", 1, 1000000), create); + loadDefaultCurrency(MyMoneySecurity("XPD", i18n("Palladium"), "XPD", 1, 1000000), create); + loadDefaultCurrency(MyMoneySecurity("XPT", i18n("Platinum"), "XPT", 1, 1000000), create); + loadDefaultCurrency(MyMoneySecurity("XAG", i18n("Silver"), "XAG", 1, 1000000), create); +} + +void KMyMoneyView::loadAncientCurrency(const QString& id, const QString& name, const QString& sym, const QDate& date, const MyMoneyMoney& rate, const QString& newId, const int partsPerUnit, const int smallestCashFraction, const int smallestAccountFraction) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyPrice price(id, newId, date, rate, "KMyMoney"); + MyMoneyFileTransaction ft; + try { + // make sure if entry exists + file->currency(id); + // make sure we have the right price + if(file->price(id, newId, date, true) != price) { + file->addPrice(price); + } + ft.commit(); + } catch(MyMoneyException *e) { + delete e; + try { + file->addCurrency(MyMoneySecurity(id, name, sym, partsPerUnit, smallestCashFraction, smallestAccountFraction)); + if(date.isValid()) { + file->addPrice(price); + } + ft.commit(); + } catch(MyMoneyException *e) { + qDebug("Error loading currency: %s", e->what().data()); + delete e; + } + } +} + +void KMyMoneyView::loadAncientCurrencies(void) +{ + loadAncientCurrency("ATS", i18n("Austrian Schilling"), "ÖS", QDate(1998,12,31), MyMoneyMoney(10000, 137603), "EUR"); + loadAncientCurrency("DEM", i18n("German Mark"), "DM", QDate(1998,12,31), MyMoneyMoney(100000, 195583), "EUR"); + loadAncientCurrency("FRF", i18n("French Franc"), "FF", QDate(1998,12,31), MyMoneyMoney(100000, 655957), "EUR"); + loadAncientCurrency("ITL", i18n("Italian Lira"), QChar(0x20A4), QDate(1998,12,31), MyMoneyMoney(100, 193627), "EUR"); + loadAncientCurrency("ESP", i18n("Spanish Peseta"), QString(), QDate(1998,12,31), MyMoneyMoney(1000, 166386), "EUR"); + loadAncientCurrency("NLG", i18n("Dutch Guilder"), QString(), QDate(1998,12,31), MyMoneyMoney(100000, 220371), "EUR"); + loadAncientCurrency("BEF", i18n("Belgian Franc"), "Fr", QDate(1998,12,31), MyMoneyMoney(10000, 403399), "EUR"); + loadAncientCurrency("LUF", i18n("Luxembourg Franc"), "Fr", QDate(1998,12,31), MyMoneyMoney(10000, 403399), "EUR"); + loadAncientCurrency("PTE", i18n("Portuguese Escudo"), QString(), QDate(1998,12,31), MyMoneyMoney(1000, 200482), "EUR"); + loadAncientCurrency("IEP", i18n("Irish Pound"), QChar(0x00A3), QDate(1998,12,31), MyMoneyMoney(1000000, 787564), "EUR"); + loadAncientCurrency("FIM", i18n("Finnish Markka"), QString(), QDate(1998,12,31), MyMoneyMoney(100000, 594573), "EUR"); + loadAncientCurrency("GRD", i18n("Greek Drachma"), QChar(0x20AF), QDate(1998,12,31), MyMoneyMoney(100, 34075), "EUR"); + + loadAncientCurrency("ROL", i18n("Romanian Leu"), "ROL", QDate(2005,6,30), MyMoneyMoney(1, 10000), "RON"); + + loadAncientCurrency("RUR", i18n("Russian Ruble (old)"), "RUR", QDate(1998, 1, 1), MyMoneyMoney(1, 1000), "RUB"); + + loadAncientCurrency("SIT", i18n("Slovenian Tolar"), "SIT", QDate(2006,12,31), MyMoneyMoney(100, 23964), "EUR"); + + // Source: http://www.tf-portfoliosolutions.net/products/turkishlira.aspx + loadAncientCurrency("TRL", i18n("Turkish Lira"), "TL", QDate(2004,12,31), MyMoneyMoney(1,1000000), "TRY"); + + // Source: http://www.focus.de/finanzen/news/malta-und-zypern_aid_66058.html + loadAncientCurrency("MTL", i18n("Maltese Lira"), "MTL", QDate(2008,1,1), MyMoneyMoney(429300,1000000), "EUR"); + loadAncientCurrency("CYP", i18n("Cyprus Pound"), QString("C%1").arg(QChar(0x00A3)), QDate(2008,1,1), MyMoneyMoney(585274,1000000), "EUR"); + + // Source: http://www.focus.de/finanzen/news/waehrungszone-slowakei-ist-neuer-euro-staat_aid_359025.html + loadAncientCurrency("SKK", i18n("Slovak Koruna"), "SKK", QDate(2008,12,31), MyMoneyMoney(1000,30126), "EUR"); +} + +void KMyMoneyView::viewUp(void) +{ + if (!fileOpen()) + return; +} + +void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) +{ + if(pageIndex(m_accountsViewFrame) != activePageIndex()) + showPage(1); + + m_accountsView->show(); +} + +void KMyMoneyView::slotRefreshViews() +{ + // turn off sync between ledger and investment view + disconnect(m_investmentView, SIGNAL(accountSelected(const MyMoneyObject&)), m_ledgerView, SLOT(slotSelectAccount(const MyMoneyObject&))); + disconnect(m_ledgerView, SIGNAL(accountSelected(const MyMoneyObject&)), m_investmentView, SLOT(slotSelectAccount(const MyMoneyObject&))); + + // TODO turn sync between ledger and investment view if selected by user + if(KMyMoneyGlobalSettings::syncLedgerInvestment()) { + connect(m_investmentView, SIGNAL(accountSelected(const MyMoneyObject&)), m_ledgerView, SLOT(slotSelectAccount(const MyMoneyObject&))); + connect(m_ledgerView, SIGNAL(accountSelected(const MyMoneyObject&)), m_investmentView, SLOT(slotSelectAccount(const MyMoneyObject&))); + } + + showTitleBar(KMyMoneyGlobalSettings::showTitleBar()); + + m_accountsView->slotLoadAccounts(); + m_institutionsView->slotLoadAccounts(); + m_categoriesView->slotLoadAccounts(); + m_payeesView->slotLoadPayees(); + m_ledgerView->slotLoadView(); + m_budgetView->slotRefreshView(); + m_homeView->slotLoadView(); + m_investmentView->slotLoadView(); + m_reportsView->slotLoadView(); + m_forecastView->slotLoadForecast(); + + m_scheduledView->slotReloadView(); +} + +void KMyMoneyView::slotShowTransactionDetail(bool detailed) +{ + KMyMoneyGlobalSettings::setShowRegisterDetailed(detailed); + slotRefreshViews(); +} + + +void KMyMoneyView::progressCallback(int current, int total, const QString& msg) +{ + kmymoney2->progressCallback(current, total, msg); +} + +void KMyMoneyView::slotRememberPage(QWidget* w) +{ + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + config->writeEntry("LastViewSelected", pageIndex(w)); + config->sync(); +} + +/* DO NOT ADD code to this function or any of it's called ones. + Instead, create a new function, fixFile_n, and modify the initializeStorage() + logic above to call it */ + +void KMyMoneyView::fixFile_2(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyTransactionFilter filter; + filter.setReportAllSplits( false ); + QValueList<MyMoneyTransaction> transactionList; + file->transactionList(transactionList, filter); + + // scan the transactions and modify transactions with two splits + // which reference an account and a category to have the memo text + // of the account. + QValueList<MyMoneyTransaction>::Iterator it_t; + int count = 0; + for(it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) { + if((*it_t).splitCount() == 2) { + QString accountId; + QString categoryId; + QString accountMemo; + QString categoryMemo; + const QValueList<MyMoneySplit>& splits = (*it_t).splits(); + QValueList<MyMoneySplit>::const_iterator it_s; + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + if(acc.isIncomeExpense()) { + categoryId = (*it_s).id(); + categoryMemo = (*it_s).memo(); + } else { + accountId = (*it_s).id(); + accountMemo = (*it_s).memo(); + } + } + + if(!accountId.isEmpty() && !categoryId.isEmpty() + && accountMemo != categoryMemo) { + MyMoneyTransaction t(*it_t); + MyMoneySplit s(t.splitById(categoryId)); + s.setMemo(accountMemo); + t.modifySplit(s); + file->modifyTransaction(t); + ++count; + } + } + } + qDebug("%d transactions fixed in fixFile_2", count); +} + +void KMyMoneyView::fixFile_1(void) +{ + // we need to fix reports. If the account filter list contains + // investment accounts, we need to add the stock accounts to the list + // as well if we don't have the expert mode enabled + if(!KMyMoneyGlobalSettings::expertMode()) { + try { + QValueList<MyMoneyReport> reports = MyMoneyFile::instance()->reportList(); + QValueList<MyMoneyReport>::iterator it_r; + for(it_r = reports.begin(); it_r != reports.end(); ++it_r) { + QStringList list; + (*it_r).accounts(list); + QStringList missing; + QStringList::const_iterator it_a, it_b; + for(it_a = list.begin(); it_a != list.end(); ++it_a) { + MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); + if(acc.accountType() == MyMoneyAccount::Investment) { + for(it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) { + if(!list.contains(*it_b)) { + missing.append(*it_b); + } + } + } + } + if(!missing.isEmpty()) { + (*it_r).addAccount(missing); + MyMoneyFile::instance()->modifyReport(*it_r); + } + } + } catch(MyMoneyException* e) { + delete e; + } + } +} + +#if 0 + if(!m_accountsView->allItemsSelected()) { + // retrieve a list of selected accounts + QStringList list; + m_accountsView->selectedItems(list); + + // if we're not in expert mode, we need to make sure + // that all stock accounts for the selected investment + // account are also selected + if(!KMyMoneyGlobalSettings::expertMode()) { + QStringList missing; + QStringList::const_iterator it_a, it_b; + for(it_a = list.begin(); it_a != list.end(); ++it_a) { + MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); + if(acc.accountType() == MyMoneyAccount::Investment) { + for(it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) { + if(!list.contains(*it_b)) { + missing.append(*it_b); + } + } + } + } + list += missing; + } + + m_filter.addAccount(list); + } + +#endif + + + + + +void KMyMoneyView::fixFile_0(void) +{ + /* (Ace) I am on a crusade against file fixups. Whenever we have to fix the + * file, it is really a warning. So I'm going to print a debug warning, and + * then go track them down when I see them to figure out how they got saved + * out needing fixing anyway. + */ + + MyMoneyFile* file = MyMoneyFile::instance(); + QValueList<MyMoneyAccount> accountList; + file->accountList(accountList); + ::timetrace("Have account list"); + QValueList<MyMoneyAccount>::Iterator it_a; + QValueList<MyMoneySchedule> scheduleList = file->scheduleList(); + ::timetrace("Have schedule list"); + QValueList<MyMoneySchedule>::Iterator it_s; + + MyMoneyAccount equity = file->equity(); + MyMoneyAccount asset = file->asset(); + bool equityListEmpty = equity.accountList().count() == 0; + + ::timetrace("Fix accounts start"); + for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) { + if((*it_a).accountType() == MyMoneyAccount::Loan + || (*it_a).accountType() == MyMoneyAccount::AssetLoan) { + fixLoanAccount_0(*it_a); + } + // until early before 0.8 release, the equity account was not saved to + // the file. If we have an equity account with no sub-accounts but + // find and equity account that has equity() as it's parent, we reparent + // this account. Need to move it to asset() first, because otherwise + // MyMoneyFile::reparent would act as NOP. + if(equityListEmpty && (*it_a).accountType() == MyMoneyAccount::Equity) { + if((*it_a).parentAccountId() == equity.id()) { + MyMoneyAccount acc = *it_a; + // tricky, force parent account to be empty so that we really + // can re-parent it + acc.setParentAccountId(QString()); + file->reparentAccount(acc, equity); + kdDebug(2) << __func__ << " fixed account " << acc.id() << " reparented to " << equity.id() << endl; + } + } + } + + ::timetrace("Fix schedules start"); + for(it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { + fixSchedule_0(*it_s); + } + + ::timetrace("Fix transactions start"); + fixTransactions_0(); + ::timetrace("Fix transactions done"); +} + +void KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) +{ + MyMoneyTransaction t = sched.transaction(); + QValueList<MyMoneySplit> splitList = t.splits(); + QValueList<MyMoneySplit>::ConstIterator it_s; + bool updated = false; + + try { + // Check if the splits contain valid data and set it to + // be valid. + for(it_s = splitList.begin(); it_s != splitList.end(); ++it_s) { + // the first split is always the account on which this transaction operates + // and if the transaction commodity is not set, we take this + if(it_s == splitList.begin() && t.commodity().isEmpty()) { + kdDebug(2) << __func__ << " " << t.id() << " has no commodity" << endl; + try { + MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); + t.setCommodity(acc.currencyId()); + updated = true; + } catch(MyMoneyException *e) { + delete e; + } + } + // make sure the account exists. If not, remove the split + try { + MyMoneyFile::instance()->account((*it_s).accountId()); + } catch(MyMoneyException *e) { + kdDebug(2) << __func__ << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist." << endl; + t.removeSplit(*it_s); + updated = true; + delete e; + } + if((*it_s).reconcileFlag() != MyMoneySplit::NotReconciled) { + kdDebug(2) << __func__ << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'" << endl; + MyMoneySplit split = *it_s; + split.setReconcileDate(QDate()); + split.setReconcileFlag(MyMoneySplit::NotReconciled); + t.modifySplit(split); + updated = true; + } + // the schedule logic used to operate only on the value field. + // This is now obsolete. + if((*it_s).shares().isZero() && !(*it_s).value().isZero()) { + MyMoneySplit split = *it_s; + split.setShares(split.value()); + t.modifySplit(split); + updated = true; + } + } + + // If there have been changes, update the schedule and + // the engine data. + if(updated) { + sched.setTransaction(t); + MyMoneyFile::instance()->modifySchedule(sched); + } + } catch(MyMoneyException *e) { + qWarning("Unable to update broken schedule: %s", e->what().latin1()); + delete e; + } +} + +void KMyMoneyView::fixLoanAccount_0(MyMoneyAccount acc) +{ + if(acc.value("final-payment").isEmpty() + || acc.value("term").isEmpty() + || acc.value("periodic-payment").isEmpty() + || acc.value("loan-amount").isEmpty() + || acc.value("interest-calculation").isEmpty() + || acc.value("schedule").isEmpty() + || acc.value("fixed-interest").isEmpty()) { + KMessageBox::information(this, + i18n("The account \"%1\" was previously created as loan account but some information " + "is missing. The new loan wizard will be started to collect all relevant " + "information. Please use a KMyMoney version >= 0.8.7 and < 0.9 to correct the problem." + ).arg(acc.name()), + i18n("Account problem")); + + throw new MYMONEYEXCEPTION("Fix LoanAccount0 not supported anymore"); + } +} + +void KMyMoneyView::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) +{ + // Add the schedule only if one exists + // + // Remember to modify the first split to reference the newly created account + if (!newSchedule.name().isEmpty()) + { + MyMoneyFileTransaction ft; + try + { + // We assume at least 2 splits in the transaction + MyMoneyTransaction t = newSchedule.transaction(); + if(t.splitCount() < 2) { + throw new MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); + } + // now search the split that does not have an account reference + // and set it up to be the one of the account we just added + // to the account pool. Note: the schedule code used to leave + // this always the first split, but the loan code leaves it as + // the second one. So I thought, searching is a good alternative .... + QValueList<MyMoneySplit>::ConstIterator it_s; + for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { + if((*it_s).accountId().isEmpty()) { + MyMoneySplit s = (*it_s); + s.setAccountId(newAccount.id()); + t.modifySplit(s); + break; + } + } + newSchedule.setTransaction(t); + + MyMoneyFile::instance()->addSchedule(newSchedule); + + // in case of a loan account, we keep a reference to this + // schedule in the account + if(newAccount.isLoan()) { + newAccount.setValue("schedule", newSchedule.id()); + MyMoneyFile::instance()->modifyAccount(newAccount); + } + ft.commit(); + } + catch (MyMoneyException *e) + { + KMessageBox::information(this, i18n("Unable to add schedule: "), e->what()); + delete e; + } + } +} + +void KMyMoneyView::fixTransactions_0(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + +#if 0 + ::timetrace("Start alloc memory"); + int * p = new int [10000]; + delete p; + ::timetrace("Done alloc memory"); +#endif + + ::timetrace("fixTransactions: get schedule list"); + QValueList<MyMoneySchedule> scheduleList = file->scheduleList(); + ::timetrace("fixTransactions: get transaction list"); + MyMoneyTransactionFilter filter; + filter.setReportAllSplits( false ); + QValueList<MyMoneyTransaction> transactionList; + file->transactionList(transactionList, filter); + ::timetrace("fixTransactions: have list"); + + QValueList<MyMoneySchedule>::Iterator it_x; + QStringList interestAccounts; + + KMSTATUS(i18n("Fix transactions")); + kmymoney2->slotStatusProgressBar(0, scheduleList.count() + transactionList.count()); + + int cnt = 0; + // scan the schedules to find interest accounts + for(it_x = scheduleList.begin(); it_x != scheduleList.end(); ++it_x) { + MyMoneyTransaction t = (*it_x).transaction(); + QValueList<MyMoneySplit>::ConstIterator it_s; + QStringList accounts; + bool hasDuplicateAccounts = false; + + for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { + if(accounts.contains((*it_s).accountId())) { + hasDuplicateAccounts = true; + kdDebug(2) << __func__ << " " << t.id() << " has multiple splits with account " << (*it_s).accountId() << endl; + } else { + accounts << (*it_s).accountId(); + } + + if((*it_s).action() == MyMoneySplit::ActionInterest) { + if(interestAccounts.contains((*it_s).accountId()) == 0) { + interestAccounts << (*it_s).accountId(); + } + } + } + if(hasDuplicateAccounts) { + fixDuplicateAccounts_0(t); + } + ++cnt; + if(!(cnt % 10)) + kmymoney2->slotStatusProgressBar(cnt); + } + + ::timetrace("fixTransactions: start loop"); + // scan the transactions and modify loan transactions + QValueList<MyMoneyTransaction>::Iterator it_t; + for(it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) { + const char *defaultAction = 0; + QValueList<MyMoneySplit> splits = (*it_t).splits(); + QValueList<MyMoneySplit>::Iterator it_s; + QStringList accounts; + + // check if base commodity is set. if not, set baseCurrency + if((*it_t).commodity().isEmpty()) { + kdDebug(2) << __func__ << " " << (*it_t).id() << " has no base currency" << endl; + (*it_t).setCommodity(file->baseCurrency().id()); + file->modifyTransaction(*it_t); + } + + bool isLoan = false; + // Determine default action + if((*it_t).splitCount() == 2) { + // check for transfer + int accountCount = 0; + MyMoneyMoney val; + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + if(acc.accountGroup() == MyMoneyAccount::Asset + || acc.accountGroup() == MyMoneyAccount::Liability) { + val = (*it_s).value(); + accountCount++; + if(acc.accountType() == MyMoneyAccount::Loan + || acc.accountType() == MyMoneyAccount::AssetLoan) + isLoan = true; + } else + break; + } + if(accountCount == 2) { + if(isLoan) + defaultAction = MyMoneySplit::ActionAmortization; + else + defaultAction = MyMoneySplit::ActionTransfer; + } else { + if(val.isNegative()) + defaultAction = MyMoneySplit::ActionWithdrawal; + else + defaultAction = MyMoneySplit::ActionDeposit; + } + } + + isLoan = false; + for(it_s = splits.begin(); defaultAction == 0 && it_s != splits.end(); ++it_s) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + MyMoneyMoney val = (*it_s).value(); + if(acc.accountGroup() == MyMoneyAccount::Asset + || acc.accountGroup() == MyMoneyAccount::Liability) { + if(!val.isPositive()) + defaultAction = MyMoneySplit::ActionWithdrawal; + else + defaultAction = MyMoneySplit::ActionDeposit; + } + } + +#if 0 + // Check for correct actions in transactions referencing credit cards + bool needModify = false; + // The action fields are actually not used anymore in the ledger view logic + // so we might as well skip this whole thing here! + for(it_s = splits.begin(); needModify == false && it_s != splits.end(); ++it_s) { + MyMoneyAccount acc = file->account((*it_s).accountId()); + MyMoneyMoney val = (*it_s).value(); + if(acc.accountType() == MyMoneyAccount::CreditCard) { + if(val < 0 && (*it_s).action() != MyMoneySplit::ActionWithdrawal && (*it_s).action() != MyMoneySplit::ActionTransfer ) + needModify = true; + if(val >= 0 && (*it_s).action() != MyMoneySplit::ActionDeposit && (*it_s).action() != MyMoneySplit::ActionTransfer) + needModify = true; + } + } + + // (Ace) Extended the #endif down to cover this conditional, because as-written + // it will ALWAYS be skipped. + + if(needModify == true) { + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + (*it_s).setAction(defaultAction); + (*it_t).modifySplit(*it_s); + file->modifyTransaction(*it_t); + } + splits = (*it_t).splits(); // update local copy + qDebug("Fixed credit card assignment in %s", (*it_t).id().data()); + } +#endif + + bool hasDuplicateAccounts = false; + // Check for correct assignment of ActionInterest in all splits + // and check if there are any duplicates in this transactions + for(it_s = splits.begin(); it_s != splits.end(); ++it_s) { + MyMoneyAccount splitAccount = file->account((*it_s).accountId()); + if(accounts.contains((*it_s).accountId())) { + hasDuplicateAccounts = true; + } else { + accounts << (*it_s).accountId(); + } + // if this split references an interest account, the action + // must be of type ActionInterest + if(interestAccounts.contains((*it_s).accountId())) { + if((*it_s).action() != MyMoneySplit::ActionInterest) { + kdDebug(2) << __func__ << " " << (*it_t).id() << " contains an interest account (" << (*it_s).accountId() << ") but does not have ActionInterest" << endl; + (*it_s).setAction(MyMoneySplit::ActionInterest); + (*it_t).modifySplit(*it_s); + file->modifyTransaction(*it_t); + qDebug("Fixed interest action in %s", (*it_t).id().data()); + } + // if it does not reference an interest account, it must not be + // of type ActionInterest + } else { + if((*it_s).action() == MyMoneySplit::ActionInterest) { + kdDebug(2) << __func__ << " " << (*it_t).id() << " does not contain an interest account so it should not have ActionInterest" << endl; + (*it_s).setAction(defaultAction); + (*it_t).modifySplit(*it_s); + file->modifyTransaction(*it_t); + qDebug("Fixed interest action in %s", (*it_t).id().data()); + } + } + + // check that for splits referencing an account that has + // the same currency as the transactions commodity the value + // and shares field are the same. + if((*it_t).commodity() == splitAccount.currencyId() + && (*it_s).value() != (*it_s).shares()) { + kdDebug(2) << __func__ << " " << (*it_t).id() << " " << (*it_s).id() << " uses the transaction currency, but shares != value" << endl; + (*it_s).setShares((*it_s).value()); + (*it_t).modifySplit(*it_s); + file->modifyTransaction(*it_t); + } + + // fix the shares and values to have the correct fraction + if(!splitAccount.isInvest()) { + try { + int fract = splitAccount.fraction(); + if((*it_s).shares() != (*it_s).shares().convert(fract)) { + qDebug("adjusting fraction in %s,%s", (*it_t).id().data(), (*it_s).id().data()); + (*it_s).setShares((*it_s).shares().convert(fract)); + (*it_s).setValue((*it_s).value().convert(fract)); + (*it_t).modifySplit(*it_s); + file->modifyTransaction(*it_t); + } + } catch(MyMoneyException* e) { + qDebug("Missing security '%s', split not altered", splitAccount.currencyId().data()); + delete e; + } + } + } + +/* + // if there are at least two splits referencing the same account, + // we need to combine them into one and get rid of the others + if(hasDuplicateAccounts) { + fixDuplicateAccounts(*it_t); + } +*/ + ++cnt; + if(!(cnt % 10)) + kmymoney2->slotStatusProgressBar(cnt); + } + + kmymoney2->slotStatusProgressBar(-1, -1); +} + +void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) +{ + qDebug("Duplicate account in transaction %s", t.id().data()); +} + +void KMyMoneyView::slotPrintView(void) +{ + if(pageIndex(m_reportsViewFrame) == activePageIndex()) + m_reportsView->slotPrintView(); + else if(pageIndex(m_homeViewFrame) == activePageIndex()) + m_homeView->slotPrintView(); +} + +KMyMoneyViewBase* KMyMoneyView::addPage(const QString& title, const QString& icon) +{ + const int iconSize = (KMyMoneyGlobalSettings::iconSize()+1)*16; + QFrame* frm = KJanusWidget::addVBoxPage(title, title, DesktopIcon(icon, iconSize)); + return new KMyMoneyViewBase(frm, title.latin1(), title); +} + +/* ------------------------------------------------------------------------ */ +/* KMyMoneyViewBase */ +/* ------------------------------------------------------------------------ */ + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qlayout.h> +#include <qvbox.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "../widgets/kmymoneytitlelabel.h" + +class KMyMoneyViewBase::Private { + public: + QFrame* m_titleLine; + KMyMoneyTitleLabel* m_titleLabel; + QVBoxLayout* m_viewLayout; +}; + +KMyMoneyViewBase::KMyMoneyViewBase(QWidget* parent, const char* name, const QString& title) : + QWidget(parent, name), + d(new Private) +{ + d->m_viewLayout = new QVBoxLayout(this); + d->m_viewLayout->setSpacing( 6 ); + d->m_viewLayout->setMargin( 0 ); + + d->m_titleLabel = new KMyMoneyTitleLabel( this, "titleLabel" ); + d->m_titleLabel->setMinimumSize( QSize( 100, 30 ) ); + d->m_titleLabel->setRightImageFile("pics/titlelabel_background.png" ); + d->m_titleLabel->setText(title); + d->m_viewLayout->addWidget( d->m_titleLabel ); + +#if 0 + d->m_titleLine = new QFrame( this, "titleLine" ); + d->m_titleLine->setFrameShape( QFrame::HLine ); + d->m_titleLine->setFrameShadow( QFrame::Sunken ); + d->m_titleLine->setFrameShape( QFrame::HLine ); + d->m_viewLayout->addWidget( d->m_titleLine ); +#endif +} + +KMyMoneyViewBase::~KMyMoneyViewBase() +{ + delete d; +} + +void KMyMoneyViewBase::addWidget(QWidget* w) +{ + d->m_viewLayout->addWidget(w); +} + +QVBoxLayout* KMyMoneyViewBase::layout(void) const +{ + return d->m_viewLayout; +} + +#include "kmymoneyview.moc" diff --git a/kmymoney2/views/kmymoneyview.h b/kmymoney2/views/kmymoneyview.h new file mode 100644 index 0000000..13e3d76 --- /dev/null +++ b/kmymoney2/views/kmymoneyview.h @@ -0,0 +1,625 @@ +/*************************************************************************** + kmymoneyview.h + ------------------- + copyright : (C) 2000-2001 by Michael Edwardes + email : mte@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. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef KMYMONEYVIEW_H +#define KMYMONEYVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qwidget.h> +class QVBox; +class QFile; +class QVBoxLayout; + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kpopupmenu.h> +#include <kjanuswidget.h> + +#include <kurl.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/mymoneyinstitution.h> +#include <kmymoney/mymoneytransaction.h> +#include <kmymoney/mymoneyscheduled.h> +#include <kmymoney/mymoneysecurity.h> +#include <kmymoney/selectedtransaction.h> + +class KHomeView; +class KAccountsView; +class KCategoriesView; +class KInstitutionsView; +class KPayeesView; +class KBudgetView; +class KScheduledView; +class KGlobalLedgerView; +class IMyMoneyStorageFormat; +class MyMoneyTransaction; +class KInvestmentView; +class KReportsView; +class KMyMoneyViewBase; +class MyMoneyReport; +class TransactionEditor; +class KForecastView; + +/** + * This class represents the view of the MyMoneyFile which contains + * Banks/Accounts/Transactions, Recurring transactions (or Bills & Deposits) + * and scripts (yet to be implemented). Each different aspect of the file + * is represented by a tab within the view. + * + * @author Michael Edwardes 2001 Copyright 2000-2001 + * + * @short Handles the view of the MyMoneyFile. + */ +class KMyMoneyView : public KJanusWidget +{ + Q_OBJECT + +public: + enum viewID { + HomeView = 0, + AccountsView, + InstitutionsView, + SchedulesView, + CategoriesView, + PayeesView, + LedgersView, + InvestmentsView, + ReportsView, + BudgetView, + ForecastView + }; + // file actions for plugin + enum fileActions { + preOpen, postOpen, preSave, postSave, preClose, postClose + }; + +private: + enum menuID { + AccountNew = 1, + AccountOpen, + AccountReconcile, + AccountEdit, + AccountDelete, + AccountOnlineMap, + AccountOnlineUpdate, + AccountOfxConnect, + CategoryNew + }; + + typedef enum storageTypeE { // not used but keep for future implementation + Memory = 0, + Database + } _storageType; + + KHomeView *m_homeView; + KAccountsView *m_accountsView; + KInstitutionsView *m_institutionsView; + KCategoriesView *m_categoriesView; + KPayeesView *m_payeesView; + KBudgetView *m_budgetView; + KScheduledView *m_scheduledView; + KGlobalLedgerView *m_ledgerView; + KInvestmentView *m_investmentView; + KReportsView* m_reportsView; + KForecastView* m_forecastView; + + QVBox* m_homeViewFrame; + QVBox* m_accountsViewFrame; + QVBox* m_institutionsViewFrame; + QVBox* m_categoriesViewFrame; + QVBox* m_payeesViewFrame; + QVBox* m_budgetViewFrame; + QVBox* m_scheduleViewFrame; + QVBox* m_ledgerViewFrame; + QVBox* m_investmentViewFrame; + QVBox* m_reportsViewFrame; + QVBox* m_forecastViewFrame; + + bool m_inConstructor; + + bool m_fileOpen; + + int m_fmode; + + // bool m_bankRightClick; + // MyMoneyInstitution m_accountsInstitution; + + // Keep a note of the file type + typedef enum _fileTypeE { + KmmBinary = 0, // native, binary + KmmXML, // native, XML + KmmDb, // SQL database + /* insert new native file types above this line */ + MaxNativeFileType, + /* and non-native types below */ + GncXML // Gnucash XML + }fileTypeE; + fileTypeE m_fileType; + +private: + void addTitleBar(QWidget* parent, const QString& title); + + void ungetString(QIODevice *qfile, char * buf, int len); + + /** + * This method creates the currency @p curr if it does not exist and + * @p create is @p true. If the currency already exists, it checks + * if the name is equal. If it is not, the name of the object in the + * engine is updated to the name passed with @p curr. + * + * @param curr MyMoneySecurity to be checked + * @param create If true and currency does not exist it will be created + If false currency will not be created even if it does not exist + */ + void loadDefaultCurrency(const MyMoneySecurity& curr, const bool create); + + /** + * + */ + void loadAncientCurrency(const QString& id, const QString& name, const QString& sym, const QDate& date, const MyMoneyMoney& rate, const QString& newId, const int partsPerUnit = 100, const int smallestCashFraction = 100, const int smallestAccountFraction = 0); + + /** + * if no base currency is defined, start the dialog and force it to be set + */ + void selectBaseCurrency(void); + + /** + * This method attaches an empty storage object to the MyMoneyFile + * object. It calls removeStorage() to remove a possibly attached + * storage object. + */ + void newStorage(storageTypeE = Memory); + + /** + * This method removes an attached storage from the MyMoneyFile + * object. + */ + void removeStorage(void); + + void viewAccountList(const QString& selectAccount); // Show the accounts view + + static void progressCallback(int current, int total, const QString&); + + /** + */ + void fixFile_0(void); + void fixFile_1(void); + void fixFile_2(void); + + /** + */ + void fixLoanAccount_0(MyMoneyAccount acc); + + /** + */ + void fixTransactions_0(void); + void fixSchedule_0(MyMoneySchedule sched); + void fixDuplicateAccounts_0(MyMoneyTransaction& t); + + void createSchedule(MyMoneySchedule s, MyMoneyAccount& a); + + void checkAccountName(const MyMoneyAccount& acc, const QString& name) const; + +public: + /** + * The constructor for KMyMoneyView. Just creates all the tabs for the + * different aspects of the MyMoneyFile. + */ + KMyMoneyView(QWidget *parent=0, const char *name=0); + + /** + * Destructor + */ + ~KMyMoneyView(); + + /** + * Makes sure that a MyMoneyFile is open and has been created succesfully. + * + * @return Whether the file is open and initialised + */ + bool fileOpen(void); + + /** + * Closes the open MyMoneyFile and frees all the allocated memory, I hope ! + */ + void closeFile(void); + + + /** + * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate + * data structures in memory. The return result is examined to make sure no + * errors occured whilst parsing. + * + * @param url The URL to read from. + * If no protocol is specified, file:// is assumed. + * + * @return Whether the read was successfull. + */ + bool readFile(const KURL& url); + + /** + * Saves the data into permanent storage using the XML format. + * + * @param url The URL to save into. + * If no protocol is specified, file:// is assumed. + * @param keyList QString containing a comma separated list of keys + * to be used for encryption. If @p keyList is empty, + * the file will be saved unencrypted (the default) + * + * @retval false save operation failed + * @retval true save operation was successful + */ + bool saveFile(const KURL& url, const QString& keyList = QString()); + /** + * Saves the data into permanent storage on a new or empty SQL database. + * + * @param url The pseudo of tyhe database + * + * @retval false save operation failed + * @retval true save operation was successful + */ + //const bool saveDatabase(const KURL& url); This no longer relevant + /** + * Saves the data into permanent storage on a new or empty SQL database. + * + * @param url The pseudo URL of the database + * + * @retval false save operation failed + * @retval true save operation was successful + */ + bool saveAsDatabase(const KURL& url); + + /** + * Call this to find out if the currently open file is native KMM + * + * @retval true file is native + * @retval false file is foreign + */ + bool isNativeFile() { return (m_fileOpen && (m_fileType < MaxNativeFileType)); } + + /** + * Call this to find out if the currently open file is a sql database + * + * @retval true file is database + * @retval false file is serial + */ + bool isDatabase() + { return (m_fileOpen && ((m_fileType == KmmDb))); } + + /** + * Call this to see if the MyMoneyFile contains any unsaved data. + * + * @retval true if any data has been modified but not saved + * @retval false otherwise + */ + bool dirty(void); + + /** + * Close the currently opened file and create an empty new file. + * + * @see MyMoneyFile + */ + void newFile(void); + + /** + * Moves the view up from transaction to Bank/Account view. + */ + void viewUp(void); + + /** + * This method allows to set the enable state of all views (except home view) + * The argument @p state controls the availability. + * + * @param state Controls whether views are disabled @p (0), enabled @p (1) or + * enabled/disabled according to an open file @p (-1). The latter + * is the default. + */ + void enableViews(int state = -1); + + KMyMoneyViewBase* addPage(const QString& title, const QString& icon = QString()); + + void addWidget(QWidget* w); + + virtual bool showPage(int index); + + /** + * check if the current view allows to create a transaction + * + * @param list list of selected transactions + * @param tooltip reference to string receiving the tooltip text + * which explains why the modify function is not available (in case + * of returning @c false) + * + * @retval true Yes, view allows to create a transaction (tooltip is not changed) + * @retval false No, view cannot to create a transaction (tooltip is updated with message) + */ + bool canCreateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + /** + * check if the current view allows to modify (edit/delete) the selected transactions + * + * @param list list of selected transactions + * @param tooltip reference to string receiving the tooltip text + * which explains why the modify function is not available (in case + * of returning @c false) + * + * @retval true Yes, view allows to edit/delete transactions (tooltip is not changed) + * @retval false No, view cannot edit/delete transactions (tooltip is updated with message) + */ + bool canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + bool canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + /** + * check if the current view allows to edit the selected transactions + * + * @param list list of selected transactions + * @param tooltip reference to string receiving the tooltip text + * which explains why the edit function is not available (in case + * of returning @c false) + * + * @retval true Yes, view allows to enter/edit transactions + * @retval false No, view cannot enter/edit transactions + */ + bool canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; + + /** + * check if the current view allows to print something + * + * @retval true Yes, view allows to print + * @retval false No, view cannot print + */ + bool canPrint(void); + + TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions&); + + bool createNewTransaction(void); + + /** + * Used to start reconciliation of account @a account. It switches the + * ledger view into reconciliation mode and updates the view. + * + * @param account account which should be reconciled + * @param reconciliationDate the statement date + * @param endingBalance the ending balance entered for this account + * + * @retval true Reconciliation started + * @retval false Account cannot be reconciled + */ + bool startReconciliation(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); + + /** + * Used to finish reconciliation of account @a account. It switches the + * ledger view to normal mode and updates the view. + * + * @param account account which should be reconciled + */ + void finishReconciliation(const MyMoneyAccount& account); + + /** + * This method preloads all known currencies into the engine. + */ + void loadDefaultCurrencies(void); + + void loadAncientCurrencies(void); + + void showTitleBar(bool show); + +public slots: + /** + * This slot writes information about the page passed as argument @p widget + * in the kmymoney2.rc file so that in can be selected automatically when + * the application is started again. + * + * @param widget pointer to page widget + */ + void slotRememberPage(QWidget* widget); + + /** + * Brings up a dialog to change the list(s) settings and saves them into the + * class KMyMoneySettings (a singleton). + * + * @see KListSettingsDlg + * Refreshs all views. Used e.g. after settings have been changed or + * data has been loaded from external sources (QIF import). + **/ + void slotRefreshViews(); + + /** + * Called, whenever the ledger view should pop up and a specific + * transaction in an account should be shown. If @p transaction + * is empty, the last transaction should be selected + * + * @param acc The ID of the account to be shown + * @param transaction The ID of the transaction to be selected + */ + void slotLedgerSelected(const QString& acc, const QString& transaction = QString()); + + /** + * Called, whenever the payees view should pop up and a specific + * transaction in an account should be shown. + * + * @param payeeId The ID of the payee to be shown + * @param accountId The ID of the account to be shown + * @param transactionId The ID of the transaction to be selected + */ + void slotPayeeSelected(const QString& payeeId, const QString& accountId, const QString& transactionId); + + /** + * Called, whenever the schedule view should pop up and a specific + * schedule should be shown. + * + * @param schedule The ID of the schedule to be shown + */ + void slotScheduleSelected(const QString& schedule); + + /** + * Called, whenever the report view should pop up and a specific + * report should be shown. + * + * @param reportid The ID of the report to be shown + */ + void slotShowReport(const QString& reportid); + + /** + * Same as the above, but the caller passes in an actual report + * definition to be shown. + * + * @param report The report to be shown + */ + void slotShowReport(const MyMoneyReport& report); + + /** + * This slot prints the current view. + */ + void slotPrintView(void); + + /** + * This slot switches the view to present the home page + */ + void slotShowHomePage(void) { showPage(0); } + +protected slots: + /** + * Called when the user changes the detail + * setting of the transaction register + * + * @param detailed if true, the register is shown with all details + */ + void slotShowTransactionDetail(bool detailed); + + /** + * eventually replace this with KMyMoney2App::slotCurrencySetBase(void). + * it contains the same code + * + * @deprecated + */ + void slotSetBaseCurrency(const MyMoneySecurity& baseCurrency); + +private: + /** + * This method is called from readFile to open a database file which + * is to be processed in 'proper' database mode, i.e. in-place updates + * + * @param dbaseURL pseudo-KURL representation of database + * + * @retval true Database opened successfully + * @retval false Could not open or read database + */ + bool openDatabase (const KURL& dbaseURL); + /** + * This method is used after a file or database has been + * read into storage, and performs various initialization tasks + * + * @retval true all went okay + * @retval false an exception occurred during this process + */ + bool initializeStorage(); + /** + * This method is used by saveFile() to store the data + * either directly in the destination file if it is on + * the local file system or in a temporary file when + * the final destination is reached over a network + * protocol (e.g. FTP) + * + * @param qf pointer to QFile representing the opened file + * @param writer pointer to the formatter + * @param plaintext whether to override any compression & encryption settings + * @param keyList QString containing a comma separated list of keys to be used for encryption + * If @p keyList is empty, the file will be saved unencrypted + * + * @note This method will close the file when it is written. + */ + void saveToLocalFile(QFile* qf, IMyMoneyStorageFormat* writer, bool plaintext=false, const QString& keyList = QString()); + + /** + * Internal method used by slotAccountNew() and slotAccountCategory(). + */ + void accountNew(const bool createCategory); + +signals: + /** + * This signal is emitted whenever a view is selected. + * The parameter @p view is identified as one of KMyMoneyView::viewID. + */ + void viewActivated(int view); + + void accountSelectedForContextMenu(const MyMoneyAccount& acc); + + void viewStateChanged(bool enabled); + /** + * This signal is emitted to inform the kmmFile plugin when various file actions + * occur. The Action parameter distinguishes between them. + */ + void kmmFilePlugin (unsigned int action); + + /** + * Signal is emitted when reconciliation starts or ends. In case of end, + * @a account is MyMoneyAccount() + * + * @param account account for which reconciliation starts or MyMoneyAccount() + * if reconciliation ends. + * @param reconciliationDate the statement date + * @param endingBalance collected ending balance when reconciliation starts + * 0 otherwise + */ + void reconciliationStarts(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); + +}; + +/** + * This class is an abstract base class that all specific views + * should be based on. + */ +class KMyMoneyViewBase : public QWidget +{ + Q_OBJECT +public: + KMyMoneyViewBase(QWidget* parent, const char *name, const QString& title); + virtual ~KMyMoneyViewBase(); + + void setTitle(const QString& title); + QVBoxLayout* layout(void) const; + void addWidget(QWidget* w); + + /** + * This method is used to edit the currently selected transactions + * The default implementation returns @p false which signals to the caller, that + * the view was not capable to edit the transactions. + * + * @retval false view was not capable to edit transactions + * @retval true view was capable to edit the transactions and did so + */ + bool editTransactions(const QValueList<MyMoneyTransaction>& transactions) const { Q_UNUSED(transactions) return false; } +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + +}; +#endif diff --git a/kmymoney2/views/kpayeesview.cpp b/kmymoney2/views/kpayeesview.cpp new file mode 100644 index 0000000..ad17ff4 --- /dev/null +++ b/kmymoney2/views/kpayeesview.cpp @@ -0,0 +1,1108 @@ +/*************************************************************************** + kpayeesview.cpp + --------------- + begin : Thu Jan 24 2002 + copyright : (C) 2000-2002 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> + Andreas Nicolai <Andreas.Nicolai@gmx.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 <qpushbutton.h> +#include <qcombobox.h> +#include <qlineedit.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qmultilineedit.h> +#include <qpixmap.h> +#include <qtabwidget.h> +#include <qcursor.h> +#include <qcheckbox.h> +#include <qradiobutton.h> +#include <qpainter.h> +#include <qheader.h> +#include <qbuttongroup.h> +#include <qsplitter.h> +#include <qmap.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kglobal.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <kiconloader.h> +#include <kguiitem.h> +#include <kstandarddirs.h> +#include <kdebug.h> +#include <kapplication.h> +#include <keditlistbox.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/kmymoneyaccounttree.h> +#include <kmymoney/kmymoneyglobalsettings.h> +#include "../widgets/klistviewsearchline.h" + +#include "kpayeesview.h" + +/* -------------------------------------------------------------------------------*/ +/* KTransactionPtrVector */ +/* -------------------------------------------------------------------------------*/ + +int KTransactionPtrVector::compareItems(const QString& s1, const QString& s2) const +{ + if(s1 == s2) + return 0; + if(s1 < s2) + return -1; + return 1; +} + +int KTransactionPtrVector::compareItems(KTransactionPtrVector::Item d1, KTransactionPtrVector::Item d2) +{ + int rc = 0; + MyMoneyTransaction* t1 = static_cast<MyMoneyTransaction*>(d1); + MyMoneyTransaction* t2 = static_cast<MyMoneyTransaction*>(d2); + MyMoneyMoney tmp; + + try { + MyMoneySplit s1; + MyMoneySplit s2; + switch(m_idMode) { + case AccountMode: + s1 = t1->splitByAccount(m_id); + s2 = t2->splitByAccount(m_id); + break; + case PayeeMode: + s1 = t1->splitByPayee(m_id); + s2 = t2->splitByPayee(m_id); + break; + } + QString p1, p2; + + switch(m_sortType) { + case SortValue: + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? Sort by date + rc = t2->postDate().daysTo(t1->postDate()); + if(rc == 0) { + // same date? Sort by id + rc = compareItems(t1->id(), t2->id()); + } + } else if(tmp.isNegative()) { + rc = -1; + } + break; + + case SortEntryDate: + rc = t2->entryDate().daysTo(t1->entryDate()); + if(rc == 0) { + // on same day, lower check numbers show up first + rc = compareItems(s1.number(), s2.number()); + if(rc == 0) { + // same number (e.g. empty)? larger amounts show up first + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? Sort by id + rc = compareItems(t1->id(), t2->id()); + } else if(tmp.isNegative()) { + rc = -1; + } + } + } + break; + + case SortEntryOrder: + // sort by id + rc = compareItems(t1->id(), t2->id()); + break; + + case SortTypeNr: + rc = compareItems(s1.action(), s2.action()); + + if(rc == 0) { + // same action? Sort by nr + rc = compareItems(s1.number(), s2.number()); + if(rc == 0) { + // same number? Sort by date + rc = t2->postDate().daysTo(t1->postDate()); + if(rc == 0) { + // same date? Sort by value + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? sort by id + rc = compareItems(t1->id(), t2->id()); + } else if(tmp.isNegative()) { + rc = -1; + } + } + } + } + break; + + case SortReceiver: + if(!s2.payeeId().isEmpty()) { + p2 = MyMoneyFile::instance()->payee(s2.payeeId()).name(); + } + if(!s1.payeeId().isEmpty()) { + p1 = MyMoneyFile::instance()->payee(s1.payeeId()).name(); + } + + rc = compareItems(p1, p2); + + if(rc == 0) { + // same payee? Sort by date + rc = t2->postDate().daysTo(t1->postDate()); + if(rc == 0) { + // same date? Sort by value + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? sort by id + rc = compareItems(t1->id(), t2->id()); + } else if(tmp.isNegative()) { + rc = -1; + } + } + } + break; + + case SortNr: + rc = compareItems(s1.number(), s2.number()); + if(rc == 0) { + // same number? Sort by date + rc = t2->postDate().daysTo(t1->postDate()); + if(rc == 0) { + // same date? Sort by value + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? sort by id + rc = compareItems(t1->id(), t2->id()); + } else if(tmp.isNegative()) { + rc = -1; + } + } + } + break; + + case SortPostDate: + // tricky fall through here! + default: + // sort by post date + rc = t2->postDate().daysTo(t1->postDate()); + if(rc == 0) { + // on same day, lower check numbers show up first + rc = compareItems(s1.number(), s2.number()); + if(rc == 0) { + // same number (e.g. empty)? larger amounts show up first + rc = 1; + tmp = s2.value() - s1.value(); + if(tmp.isZero()) { + // same value? Sort by id + rc = compareItems(t1->id(), t2->id()); + } else if(tmp.isNegative()) { + rc = -1; + } + } + } + break; + } + } catch (MyMoneyException *e) { + delete e; + } + return rc; +} + +void KTransactionPtrVector::setSortType(const TransactionSortE type) +{ + m_sortType = type; + sort(); +} + +void KTransactionPtrVector::setAccountId(const QString& id) +{ + m_id = id; + m_idMode = AccountMode; +} + +void KTransactionPtrVector::setPayeeId(const QString& id) +{ + m_id = id; + m_idMode = PayeeMode; +} + + +// *** KPayeeListItem Implementation *** + +KPayeeListItem::KPayeeListItem(KListView *parent, const MyMoneyPayee& payee) : + KListViewItem(parent), + m_payee(payee) +{ + setText(0, payee.name()); + // allow in column rename + setRenameEnabled(0, true); +} + +KPayeeListItem::~KPayeeListItem() +{ +} + +void KPayeeListItem::paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align) +{ + QColorGroup cg2(cg); + + if(isAlternate()) + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listColor()); + else + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listBGColor()); + + p->setFont(KMyMoneyGlobalSettings::listCellFont()); + + QListViewItem::paintCell(p, cg2, column, width, align); +} + +KTransactionListItem::KTransactionListItem(KListView* view, KTransactionListItem* parent, const QString& accountId, const QString& transactionId) : + KListViewItem(view, parent) +{ + m_accountId = accountId; + m_transactionId = transactionId; +} + +KTransactionListItem::~KTransactionListItem() +{ +} + +void KTransactionListItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment) +{ + QColorGroup _cg = cg; + _cg.setColor(QColorGroup::Base, backgroundColor()); + QListViewItem::paintCell(p, _cg, column, width, alignment); +} + +const QColor KTransactionListItem::backgroundColor(void) +{ + return isAlternate() ? KMyMoneyGlobalSettings::listBGColor() : KMyMoneyGlobalSettings::listColor(); +} + + + + +// *** KPayeesView Implementation *** + +KPayeesView::KPayeesView(QWidget *parent, const char *name ) : + KPayeesViewDecl(parent,name), + m_needReload(false), + m_needConnection(true), + m_updatesQueued(0), + m_inSelection(false) +{ + // create the searchline widget + // and insert it into the existing layout + m_searchWidget = new KListViewSearchLineWidget(m_payeesList, this); + m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + KPayeesViewDeclLayout->insertWidget(0, m_searchWidget); + + m_splitter = new QSplitter(this); + m_payeesList->reparent(m_splitter, QPoint(0,0), true); + m_tabWidget->reparent(m_splitter, QPoint(0, 0), true); + m_splitter->setResizeMode(m_tabWidget, QSplitter::Stretch); + m_splitter->setOpaqueResize(); + layout10->addWidget(m_splitter); + + // use the size settings of the last run (if any) + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + QValueList<int> sizes = config->readIntListEntry("KPayeesViewSplitterSize"); + if(sizes.size() == 2) { + if(!sizes[0] || !sizes[1]) { + sizes[0] = 1; + sizes[1] = 2; + } + m_splitter->setSizes(sizes); + } + + m_transactionView->setSorting(-1); + m_transactionView->setColumnWidthMode(2, QListView::Manual); + m_transactionView->setColumnAlignment(3, Qt::AlignRight); + // never show horizontal scroll bars + m_transactionView->setHScrollBarMode(QScrollView::AlwaysOff); + + m_payeesList->addColumn(i18n("Name")); + + m_updateButton->setEnabled(false); + radioNoMatch->setChecked(true); + + checkMatchIgnoreCase->setEnabled(false); + + checkEnableDefaultAccount->setChecked(false); + labelDefaultAccount->setEnabled(false); + comboDefaultAccount->setEnabled(false); + + KIconLoader* il = KGlobal::iconLoader(); + KGuiItem updateButtenItem( i18n("Update"), + QIconSet(il->loadIcon("button_ok", KIcon::Small, KIcon::SizeSmall)), + i18n("Accepts the entered data and stores it"), + i18n("Use this to accept the modified data.")); + m_updateButton->setGuiItem(updateButtenItem); + + connect(m_payeesList, SIGNAL(selectionChanged()), this, SLOT(slotSelectPayee())); + connect(m_payeesList, SIGNAL(itemRenamed(QListViewItem*,int,const QString&)), this, SLOT(slotRenamePayee(QListViewItem*,int,const QString&))); + + connect(addressEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); + connect(postcodeEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotPayeeDataChanged())); + connect(telephoneEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotPayeeDataChanged())); + connect(emailEdit, SIGNAL(textChanged(const QString&)), this, SLOT(slotPayeeDataChanged())); + connect(notesEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); + connect(matchKeyEditList, SIGNAL(changed()), this, SLOT(slotKeyListChanged())); + + connect(radioNoMatch, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); + connect(radioNameMatch, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); + connect(radioKeyMatch, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); + connect(checkMatchIgnoreCase, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); + + connect(checkEnableDefaultAccount, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); + connect(comboDefaultAccount, SIGNAL(accountSelected(const QString&)), this, SLOT(slotPayeeDataChanged())); + connect(buttonSelectMyAccount, SIGNAL(clicked()), this, SLOT(slotChooseDefaultAccount())); + + connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdatePayee())); + connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); + + connect(m_payeesList, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), this, SLOT(slotOpenContextMenu(KListView*, QListViewItem*, const QPoint&))); + +// connect(m_payeesList, SIGNAL(rightButtonClicked(QListViewItem* , const QPoint&, int)), +// this, SLOT(slotOpenContextMenu(QListViewItem*))); + + connect(m_transactionView, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(slotTransactionDoubleClicked(QListViewItem*))); + + connect(m_tabWidget, SIGNAL(currentChanged(QWidget*)), this, SLOT(rearrange(void))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadPayees())); +} + +KPayeesView::~KPayeesView() +{ + // remember the splitter settings for startup + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + config->writeEntry("KPayeesViewSplitterSize", m_splitter->sizes()); +} + +void KPayeesView::slotQueueUpdate(void) +{ + m_updatesQueued++; + // The KListViewSearchLineWidget has an internal timer for update purposes + // of 200 ms, so we should be safe with 250 ms here + QTimer::singleShot(250, this, SLOT(slotActivateUpdate())); +} + +void KPayeesView::slotActivateUpdate(void) +{ + --m_updatesQueued; + if(m_updatesQueued == 0) + slotSelectPayee(); +} + +void KPayeesView::slotChooseDefaultAccount(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QMap<QString,int> account_count; + + for (int i = 0; i < m_transactionPtrVector.size(); ++i) { + KMyMoneyTransaction* t = m_transactionPtrVector[i]; + MyMoneySplit s = t->splitById(t->splitId()); + const MyMoneyAccount& acc = file->account(s.accountId()); + + QString txt; + if (s.action() != MyMoneySplit::ActionAmortization + && acc.accountType() != MyMoneyAccount::AssetLoan + && !file->isTransfer(*t) + && t->splitCount() == 2) { + MyMoneySplit s0 = t->splitByAccount(s.accountId(), false); + if (account_count.contains(s0.accountId())) { + account_count[s0.accountId()]++; + } + else { + account_count[s0.accountId()] = 1; + } + } + } + + QMapIterator<QString,int> most_frequent, iter; + most_frequent = account_count.end(); + for (iter = account_count.begin(); iter != account_count.end(); iter++) { + if (iter.data() > most_frequent.data()) { + most_frequent = iter; + } + } + + if (most_frequent != account_count.end()) { + checkEnableDefaultAccount->setChecked(true); + comboDefaultAccount->setSelected(most_frequent.key()); + } +} + +void KPayeesView::slotStartRename(void) +{ + QListViewItemIterator it_l(m_payeesList, QListViewItemIterator::Selected); + QListViewItem* it_v; + if((it_v = it_l.current()) != 0) { + it_v->startRename(0); + } +} + +// This variant is only called when a single payee is selected and renamed. +void KPayeesView::slotRenamePayee(QListViewItem* p , int /* col */, const QString& txt) +{ + //kdDebug() << "[KPayeesView::slotRenamePayee]" << endl; + // create a copy of the new name without appended whitespaces + QString new_name = txt.stripWhiteSpace(); + if (m_payee.name() != new_name) { + MyMoneyFileTransaction ft; + try { + // check if we already have a payee with the new name + try { + // this function call will throw an exception, if the payee + // hasn't been found. + MyMoneyFile::instance()->payeeByName(new_name); + // the name already exists, ask the user whether he's sure to keep the name + if (KMessageBox::questionYesNo(this, + i18n("A payee with the name '%1' already exists. It is not advisable to have " + "multiple payees with the same identification name. Are you sure you would like " + "to rename the payee?").arg(new_name)) != KMessageBox::Yes) + { + p->setText(0,m_payee.name()); + return; + } + } catch(MyMoneyException *e) { + // all ok, the name is unique + delete e; + } + + m_payee.setName(new_name); + m_newName = new_name; + MyMoneyFile::instance()->modifyPayee(m_payee); + + // the above call to modifyPayee will reload the view so + // all references and pointers to the view have to be + // re-established. + + // make sure, that the record is visible even if it moved + // out of sight due to the rename operation + ensurePayeeVisible(m_payee.id()); + + ft.commit(); + + } catch(MyMoneyException *e) { + KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), + (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line())); + delete e; + } + } + else { + p->setText(0, new_name); + } +} + +void KPayeesView::ensurePayeeVisible(const QString& id) +{ + for (QListViewItem * item = m_payeesList->firstChild(); item; item = item->itemBelow()) { + KPayeeListItem* p = dynamic_cast<KPayeeListItem*>(item); + if(p && p->payee().id() == id) { + if(p->itemAbove()) + m_payeesList->ensureItemVisible(p->itemAbove()); + if(p->itemBelow()) + m_payeesList->ensureItemVisible(p->itemBelow()); + + m_payeesList->setCurrentItem(p); // active item and deselect all others + m_payeesList->setSelected(p, true); // and select it + m_payeesList->ensureItemVisible(p); + break; + } + } +} + +void KPayeesView::selectedPayees(QValueList<MyMoneyPayee>& payeesList) const +{ + QListViewItemIterator it_l(m_payeesList, QListViewItemIterator::Selected | QListViewItemIterator::Visible); + QListViewItem* it_v; + while((it_v = it_l.current()) != 0) { + KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(it_v); + if(item) + payeesList << item->payee(); + ++it_l; + } +} + +void KPayeesView::slotSelectPayee(void) +{ + // check if the content of a currently selected payee was modified + // and ask to store the data + if (m_updateButton->isEnabled()) { + if (KMessageBox::questionYesNo(this, QString("<qt>%1</qt>").arg( + i18n("Do you want to save the changes for <b>%1</b>?").arg(m_newName)), + i18n("Save changes")) == KMessageBox::Yes) { + m_inSelection = true; + slotUpdatePayee(); + m_inSelection = false; + } + } + + // loop over all payees and count the number of payees, also + // optain last selected payee + QValueList<MyMoneyPayee> payeesList; + selectedPayees(payeesList); + + emit selectObjects(payeesList); + + if (payeesList.count() == 0) { + m_tabWidget->setEnabled(false); // disable tab widget + clearItemData(); + m_payee = MyMoneyPayee(); + return; // make sure we don't access an undefined payee + } + + // if we have multiple payees selected, clear and disable the payee informations + if (payeesList.count() > 1) { + m_tabWidget->setEnabled(false); // disable tab widget + clearItemData(); + // disable renaming in all listviewitem + for (QListViewItem * i = m_payeesList->firstChild(); i; i = i->itemBelow()) + i->setRenameEnabled(0, false); + return; + } + // otherwise we have just one selected, enable payee information widget + m_tabWidget->setEnabled(true); + // enable renaming in all listviewitem + for (QListViewItem * i = m_payeesList->firstChild(); i; i = i->itemBelow()) + i->setRenameEnabled(0, true); + + // as of now we are updating only the last selected payee, and until + // selection mode of the QListView has been changed to Extended, this + // will also be the only selection and behave exactly as before - Andreas + try { + m_payee = payeesList[0]; + m_newName = m_payee.name(); + + addressEdit->setEnabled(true); + addressEdit->setText(m_payee.address()); + postcodeEdit->setEnabled(true); + postcodeEdit->setText(m_payee.postcode()); + telephoneEdit->setEnabled(true); + telephoneEdit->setText(m_payee.telephone()); + emailEdit->setEnabled(true); + emailEdit->setText(m_payee.email()); + notesEdit->setText(m_payee.notes()); + + QStringList keys; + bool ignorecase = false; + MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); + + m_matchType->setButton(static_cast<int>(type)); + matchKeyEditList->clear(); + matchKeyEditList->insertStringList(keys); + checkMatchIgnoreCase->setChecked(ignorecase); + + checkEnableDefaultAccount->setChecked(m_payee.defaultAccountEnabled()); + comboDefaultAccount->setSelected(m_payee.defaultAccountId()); + + slotPayeeDataChanged(); + + showTransactions(); + + } catch(MyMoneyException *e) { + qDebug("exception during display of payee: %s at %s:%ld", e->what().latin1(), e->file().latin1(), e->line()); + m_transactionView->clear(); + m_payee = MyMoneyPayee(); + delete e; + } +} + +void KPayeesView::clearItemData(void) +{ + addressEdit->setText(QString()); + postcodeEdit->setText(QString()); + telephoneEdit->setText(QString()); + emailEdit->setText(QString()); + notesEdit->setText(QString()); + showTransactions(); +} + +void KPayeesView::showTransactions(void) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyMoney balance(0); + unsigned int i; + + // clear the current transaction listview + m_transactionView->clear(); + + if(m_payee.id().isEmpty() || !m_tabWidget->isEnabled()) { + m_balanceLabel->setText(i18n("Balance: %1").arg(balance.formatMoney(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()))); + return; + } + + // setup the list and the pointer vector + MyMoneyTransactionFilter filter; + filter.addPayee(m_payee.id()); + filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); + + QValueList<MyMoneyTransaction> list = file->transactionList(filter); + m_transactionList.clear(); + + m_transactionPtrVector.clear(); + m_transactionPtrVector.resize(list.size()); + m_transactionPtrVector.setPayeeId(m_payee.id()); + m_transactionPtrVector.setSortType(KTransactionPtrVector::SortPostDate); + + QValueList<MyMoneyTransaction>::ConstIterator it_t; + QString lastId; + int ofs = 0; + + for(i = 0, it_t = list.begin(); it_t != list.end(); ++it_t) { + KMyMoneyTransaction k(*it_t); + + filter.match(*it_t); + if(lastId != (*it_t).id()) { + ofs = 0; + lastId = (*it_t).id(); + } else + ofs++; + + k.setSplitId(filter.matchingSplits()[ofs].id()); + MyMoneyAccount acc = MyMoneyFile::instance()->account(filter.matchingSplits()[ofs].accountId()); + if(acc.accountGroup() == MyMoneyAccount::Asset + || acc.accountGroup() == MyMoneyAccount::Liability) { + QValueList<KMyMoneyTransaction>::ConstIterator it_k; + it_k = m_transactionList.append(k); + balance += k.splitById(k.splitId()).value(); + m_transactionPtrVector.insert(i, &(*it_k)); + ++i; + } + } + m_transactionPtrVector.resize(i); + + // sort the transactions + m_transactionPtrVector.sort(); + + // and fill the m_transactionView + KTransactionListItem *item = 0; + + for(i = 0; i < m_transactionPtrVector.size(); ++i) { + KMyMoneyTransaction* t = m_transactionPtrVector[i]; + MyMoneySplit s = t->splitById(t->splitId()); + const MyMoneyAccount& acc = file->account(s.accountId()); + + item = new KTransactionListItem(m_transactionView, item, s.accountId(), t->id()); + item->setText(0, s.number()); + item->setText(1, KGlobal::locale()->formatDate(t->postDate(), true)); + + QString txt; + if(s.action() == MyMoneySplit::ActionAmortization) { + if(acc.accountType() == MyMoneyAccount::Loan) { + if(s.value().isPositive()) { + txt = i18n("Amortization of %1").arg(acc.name()); + } else { + txt = i18n("Payment to %1").arg(acc.name()); + } + } else if(acc.accountType() == MyMoneyAccount::AssetLoan) { + if(s.value().isNegative()) { + txt = i18n("Amortization of %1").arg(acc.name()); + } else { + txt = i18n("Payment to %1").arg(acc.name()); + } + } else { + txt = i18n("Loan payment from %1").arg(acc.name()); + } + } else if (file->isTransfer(*t)) { + if(!s.value().isNegative()) { + txt = i18n("Transfer to %1").arg(acc.name()); + } else { + txt = i18n("Transfer from %1").arg(acc.name()); + } + } else if(t->splitCount() > 2) { + txt = i18n("Split transaction (category replacement)", "Split transaction"); + } else if(t->splitCount() == 2) { + MyMoneySplit s0 = t->splitByAccount(s.accountId(), false); + txt = MyMoneyFile::instance()->accountToCategory(s0.accountId()); + } + item->setText(2, txt); + item->setText(3, s.value().formatMoney(acc.fraction())); + } + m_balanceLabel->setText(i18n("Balance: %1").arg(balance.formatMoney(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()))); + + // Trick: it seems, that the initial sizing of the view does + // not work correctly. At least, the columns do not get displayed + // correct. Reason: the return value of m_transactionView->visibleWidth() + // is incorrect. If the widget is visible, resizing works correctly. + // So, we let the dialog show up and resize it then. It's not really + // clean, but the only way I got the damned thing working. + QTimer::singleShot(50, this, SLOT(rearrange())); +} + +void KPayeesView::slotKeyListChanged(void) +{ + bool rc = false; + bool ignorecase = false; + QStringList keys; + // J.Rodehueser: delete unused variable 'type' + // orig: MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); + m_payee.matchData(ignorecase, keys); + if(m_matchType->selectedId() == MyMoneyPayee::matchKey) { + rc |= (keys != matchKeyEditList->items()); + } + m_updateButton->setEnabled(rc); +} + +void KPayeesView::slotPayeeDataChanged(void) +{ + kdDebug(2) << "KPayeesView::slotPayeeDataChanged(void)" << endl; + + bool rc = false; + + if(m_tabWidget->isEnabled()) { + rc |= ((m_payee.email().isEmpty() != emailEdit->text().isEmpty()) + || (!emailEdit->text().isEmpty() && m_payee.email() != emailEdit->text())); + rc |= ((m_payee.address().isEmpty() != addressEdit->text().isEmpty()) + || (!addressEdit->text().isEmpty() && m_payee.address() != addressEdit->text())); + rc |= ((m_payee.postcode().isEmpty() != postcodeEdit->text().isEmpty()) + || (!postcodeEdit->text().isEmpty() && m_payee.postcode() != postcodeEdit->text())); + rc |= ((m_payee.telephone().isEmpty() != telephoneEdit->text().isEmpty()) + || (!telephoneEdit->text().isEmpty() && m_payee.telephone() != telephoneEdit->text())); + rc |= ((m_payee.name().isEmpty() != m_newName.isEmpty()) + || (!m_newName.isEmpty() && m_payee.name() != m_newName)); + rc |= ((m_payee.notes().isEmpty() != notesEdit->text().isEmpty()) + || (!notesEdit->text().isEmpty() && m_payee.notes() != notesEdit->text())); + + bool ignorecase = false; + QStringList keys; + + MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); + rc |= (static_cast<int>(type) != m_matchType->selectedId()); + + checkMatchIgnoreCase->setEnabled(false); + matchKeyEditList->setEnabled(false); + + if(m_matchType->selectedId() != MyMoneyPayee::matchDisabled) { + checkMatchIgnoreCase->setEnabled(true); + // if we turn matching on, we default to 'ignore case' + // TODO maybe make the default a user option + if(type == MyMoneyPayee::matchDisabled && m_matchType->selectedId() != MyMoneyPayee::matchDisabled) + checkMatchIgnoreCase->setChecked(true); + rc |= (ignorecase != checkMatchIgnoreCase->isChecked()); + if(m_matchType->selectedId() == MyMoneyPayee::matchKey) { + matchKeyEditList->setEnabled(true); + rc |= (keys != matchKeyEditList->items()); + } + } + + rc |= (checkEnableDefaultAccount->isChecked() != m_payee.defaultAccountEnabled()); + if (checkEnableDefaultAccount->isChecked()) { + comboDefaultAccount->setEnabled(true); + labelDefaultAccount->setEnabled(true); + // this is only going to understand the first in the list of selected accounts + if (comboDefaultAccount->selectedAccounts().empty()) { + rc |= !m_payee.defaultAccountId().isEmpty(); + } + else { + QString temp = comboDefaultAccount->selectedAccounts().front(); + rc |= ( temp.isEmpty() != m_payee.defaultAccountId().isEmpty()) + || (!m_payee.defaultAccountId().isEmpty() && temp != m_payee.defaultAccountId()); + } + } + else { + comboDefaultAccount->setEnabled(false); + labelDefaultAccount->setEnabled(false); + } + } + m_updateButton->setEnabled(rc); +} + +void KPayeesView::slotUpdatePayee(void) +{ + if(m_updateButton->isEnabled()) { + MyMoneyFileTransaction ft; + m_updateButton->setEnabled(false); + try { + m_payee.setName(m_newName); + m_payee.setAddress(addressEdit->text()); + m_payee.setPostcode(postcodeEdit->text()); + m_payee.setTelephone(telephoneEdit->text()); + m_payee.setEmail(emailEdit->text()); + m_payee.setNotes(notesEdit->text()); + m_payee.setMatchData(static_cast<MyMoneyPayee::payeeMatchType>(m_matchType->selectedId()), checkMatchIgnoreCase->isChecked(), matchKeyEditList->items()); + m_payee.setDefaultAccountId(); + + if (checkEnableDefaultAccount->isChecked()) { + QString temp; + if (!comboDefaultAccount->selectedAccounts().empty()) { + temp = comboDefaultAccount->selectedAccounts().front(); + m_payee.setDefaultAccountId(temp); + } + } + + MyMoneyFile::instance()->modifyPayee(m_payee); + ft.commit(); + + } catch(MyMoneyException *e) { + KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), + (e->what() + " " + i18n("thrown in") + " " + e->file()+ ":%1").arg(e->line())); + delete e; + } + } +} + +void KPayeesView::readConfig(void) +{ + m_transactionView->setFont(KMyMoneyGlobalSettings::listCellFont()); + + QFontMetrics fm( KMyMoneyGlobalSettings::listHeaderFont() ); + int height = fm.lineSpacing()+6; + + m_transactionView->header()->setMinimumHeight(height); + m_transactionView->header()->setMaximumHeight(height); + m_transactionView->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont()); + + m_payeesList->setDefaultRenameAction( + KMyMoneyGlobalSettings::focusChangeIsEnter() ? QListView::Accept : QListView::Reject); + + //initialize the account list? + comboDefaultAccount->loadList((KMyMoneyUtils::categoryTypeE)(KMyMoneyUtils::asset | KMyMoneyUtils::liability | MyMoneyAccount::Income | MyMoneyAccount::Expense)); + +} + +void KPayeesView::show(void) +{ + // since we could not construct the connection in our own ctor, + // we set it up now. The widgets of the KListViewSearchLineWidget must exist by now. + // If you want to learn about the details, see the source of KListViewSearchLineWidget's + // constructor + if(m_needConnection) { + connect(m_searchWidget->searchLine(), SIGNAL(textChanged(const QString&)), this, SLOT(slotQueueUpdate(void))); + m_needConnection = false; + } + + if(m_needReload) { + loadPayees(); + m_needReload = false; + } + + // fixup the layout + QTimer::singleShot(0, this, SLOT(rearrange())); + + // don't forget base class implementation + KPayeesViewDecl::show(); + + QValueList<MyMoneyPayee> list; + selectedPayees(list); + emit selectObjects(list); +} + +void KPayeesView::slotLoadPayees(void) +{ + if(isVisible()) { + if(m_inSelection) + QTimer::singleShot(0, this, SLOT(slotLoadPayees())); + else + loadPayees(); + } else { + m_needReload = true; + } +} + +void KPayeesView::loadPayees(void) +{ + if(m_inSelection) + return; + + QMap<QString, bool> isSelected; + QString id; + + ::timetrace("Start KPayeesView::loadPayees"); + readConfig(); + + // remember which items are selected in the list + QListViewItemIterator it_l(m_payeesList, QListViewItemIterator::Selected); + QListViewItem* it_v; + while((it_v = it_l.current()) != 0) { + KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(it_v); + if(item) + isSelected[item->payee().id()] = true; + ++it_l; + } + + // keep current selected item + KPayeeListItem *currentItem = static_cast<KPayeeListItem *>(m_payeesList->currentItem()); + if(currentItem) + id = currentItem->payee().id(); + + // remember the upper left corner of the viewport + QPoint startPoint = m_payeesList->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_payeesList->setUpdatesEnabled(false); + + // clear the list + m_payeesList->clear(); + m_transactionView->clear(); + currentItem = 0; + + QValueList<MyMoneyPayee>list = MyMoneyFile::instance()->payeeList(); + QValueList<MyMoneyPayee>::ConstIterator it; + + for (it = list.begin(); it != list.end(); ++it) { + KPayeeListItem* item = new KPayeeListItem(m_payeesList, *it); + if(item->payee().id() == id) + currentItem = item; + if(isSelected[item->payee().id()]) + item->setSelected(true); + } + + if (currentItem) { + m_payeesList->setCurrentItem(currentItem); + } + + // reposition viewport + m_payeesList->setContentsPos(startPoint.x(), startPoint.y()); + + m_searchWidget->searchLine()->updateSearch(QString::null); + + // turn updates back on + m_payeesList->setUpdatesEnabled(true); + m_payeesList->repaintContents(); + + slotSelectPayee(); + + ::timetrace("End KPayeesView::loadPayees"); +} + +void KPayeesView::rearrange(void) +{ + resizeEvent(0); +} + +void KPayeesView::resizeEvent(QResizeEvent* ev) +{ + // resize the register + int w = m_transactionView->visibleWidth(); + w -= m_transactionView->columnWidth(0); + w -= m_transactionView->columnWidth(1); + w -= m_transactionView->columnWidth(3); + m_transactionView->setColumnWidth(2, w); + m_transactionView->resizeContents( + m_transactionView->visibleWidth(), + m_transactionView->contentsHeight()); + + m_payeesList->setColumnWidth(0, m_payeesList->visibleWidth()); + KPayeesViewDecl::resizeEvent(ev); +} + +void KPayeesView::slotTransactionDoubleClicked(QListViewItem* i) +{ + KTransactionListItem* item = static_cast<KTransactionListItem *>(i); + if (item) + emit transactionSelected(item->accountId(), item->transactionId()); +} + +void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) +{ + if(!isVisible()) + return; + + try { + // clear filter + m_searchWidget->searchLine()->clear(); + m_searchWidget->searchLine()->updateSearch(); + + // deselect all other selected items + QListViewItemIterator it_l(m_payeesList, QListViewItemIterator::Selected); + QListViewItem* it_v; + while((it_v = it_l.current()) != 0) { + KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(it_v); + if(item) + item->setSelected(false); + ++it_l; + } + + // find the payee in the list + QListViewItem* it; + for(it = m_payeesList->firstChild(); it; it = it->itemBelow()) { + KPayeeListItem* item = dynamic_cast<KPayeeListItem *>(it); + if(item && item->payee().id() == payeeId) { + if(it->itemAbove()) + m_payeesList->ensureItemVisible(it->itemAbove()); + if(it->itemBelow()) + m_payeesList->ensureItemVisible(it->itemBelow()); + + m_payeesList->setCurrentItem(it); // active item and deselect all others + m_payeesList->setSelected(it,true); // and select it + m_payeesList->ensureItemVisible(it); + + KTransactionListItem* item = dynamic_cast<KTransactionListItem*> (m_transactionView->firstChild()); + while(item != 0) { + if(item->accountId() == accountId && item->transactionId() == transactionId) + break; + item = dynamic_cast<KTransactionListItem*> (item->nextSibling()); + } + if(!item) { + item = dynamic_cast<KTransactionListItem*> (m_transactionView->firstChild()); + } + if(item) { + m_transactionView->setSelected(item, true); + m_transactionView->ensureItemVisible(item); + } + // quit out of for() loop + break; + } + } + + } catch(MyMoneyException *e) { + qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction"); + delete e; + } +} + +void KPayeesView::slotOpenContextMenu(KListView* lv, QListViewItem* i, const QPoint& p) +{ + Q_UNUSED(p); + if(lv == m_payeesList) { + KPayeeListItem* item = dynamic_cast<KPayeeListItem*>(i); + if(item) { + emit openContextMenu(item->payee()); + } + } +} + +void KPayeesView::slotHelp(void) +{ + kapp->invokeHelp("details.payees.personalinformation"); +} + +#include "kpayeesview.moc" +// vim:cin:si:ai:et:ts=2:sw=2: diff --git a/kmymoney2/views/kpayeesview.h b/kmymoney2/views/kpayeesview.h new file mode 100644 index 0000000..92f98fd --- /dev/null +++ b/kmymoney2/views/kpayeesview.h @@ -0,0 +1,325 @@ +/*************************************************************************** + kpayeesview.h + ------------- + begin : Thu Jan 24 2002 + copyright : (C) 2000-2002 by Michael Edwardes + 2005 by Andrea Nicolai + 2006 by Thomas Baumgart + 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> + Andreas Nicolai <Andreas.Nicolai@gmx.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. * + * * + ***************************************************************************/ + +#ifndef KPAYEESVIEW_H +#define KPAYEESVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qwidget.h> +class QSplitter; + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klistview.h> +#include <kpopupmenu.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kpayeesviewdecl.h" +#include "kmymoneytransaction.h" +#include <kmymoney/mymoneypayee.h> + +class KListViewSearchLineWidget; + +/** + * @author Michael Edwardes, Thomas Baumgart + */ + +/** + * This class is used to store a sorted vector of pointers to + * the transactions that are visible in a ledger view. When the + * vector is created, the sort method is set to SortPostDate. + * The sort type can be changed using the method setSortType(). + */ +class KTransactionPtrVector : public QPtrVector<KMyMoneyTransaction> { +public: + /** + * This enumerator defines the possible sort methods. + * Possible values are: + * + */ + enum TransactionSortE { + SortEntryDate = 0, /**< Sort the vector so that the transactions appear sorted + * according to their entry date + */ + SortPostDate, /**< Sort the vector so that the transactions appear sorted + * according to their post date + */ + SortTypeNr, /**< Sort the vector so that the transactions appear sorted + * according to their action and nr + */ + SortReceiver, /**< Sort the vector so that the transactions appear sorted + * according to their receiver + */ + SortValue, /**< Sort the vector so that the transactions appear sorted + * according to their value + */ + SortNr, /**< Sort the vector so that the transactions appear sorted + * according to nr field contents + */ + SortEntryOrder /**< Sort the vector so that the transactions appear sorted + * according to order of entry + */ + }; + + KTransactionPtrVector() { m_sortType = SortPostDate; }; + ~KTransactionPtrVector() {} + + /** + * This method is used to set a different sort type. + * The vector is resorted. See KTransactionPtrVector::TransactionSortE + * for possible values. + */ + void setSortType(const TransactionSortE type); + + /** + * This method returns the current sort type. + * + * @return transactionSortE value of sort order. See + * KTransactionPtrVector::TransactionSortE for possible values. + */ + TransactionSortE sortType(void) const { return m_sortType; }; + + /** + * This method is used to set the account id to have a chance to + * get information about the split referencing the current account + * during the sort phase. + */ + void setAccountId(const QString& id); + + /** + * This method is used to set the payee id to have a chance to + * get information about the split referencing the current payee + * during the sort phase. + */ + void setPayeeId(const QString& id); + +protected: + int compareItems(KTransactionPtrVector::Item d1, KTransactionPtrVector::Item d2); + +private: + int compareItems(const QString& s1, const QString& s2) const; + +private: + enum { + AccountMode = 0, + PayeeMode + }; + short m_idMode; + QString m_id; + TransactionSortE m_sortType; +}; + + + +/** + * This class represents an item in the payees list view. + */ +class KPayeeListItem : public KListViewItem +{ +public: + /** + * Constructor to be used to construct a payee entry object. + * + * @param parent pointer to the KListView object this entry should be + * added to. + * @param payee const reference to MyMoneyPayee for which + * the KListView entry is constructed + */ + KPayeeListItem(KListView *parent, const MyMoneyPayee& payee); + ~KPayeeListItem(); + + /** + * This method is re-implemented from QListViewItem::paintCell(). + * Besides the standard implementation, the QPainter is set + * according to the applications settings. + */ + void paintCell(QPainter *p, const QColorGroup & cg, int column, int width, int align); + + const MyMoneyPayee& payee(void) const { return m_payee; }; + +private: + MyMoneyPayee m_payee; +}; + +/** + * This class represents an item in the transaction list view. It is used + * by the KPayeesView to select between transactions. + */ +class KTransactionListItem : public KListViewItem +{ +public: + KTransactionListItem(KListView* view, KTransactionListItem* parent, const QString& accountId, const QString& transaction); + ~KTransactionListItem(); + + const QString& transactionId(void) const { return m_transactionId; }; + + const QString& accountId(void) const { return m_accountId; }; + + /** + * use my own paint method + */ + void paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment); + + /** + * use my own backgroundColor method + */ + const QColor backgroundColor(); + +private: + QString m_transactionId; + QString m_accountId; +}; + +class KPayeesView : public KPayeesViewDecl +{ + Q_OBJECT + +public: + KPayeesView(QWidget *parent=0, const char *name=0); + ~KPayeesView(); + void show(void); + +public slots: + void slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId = QString(), const QString& transactionId = QString()); + void slotLoadPayees(void); + void slotStartRename(void); + void slotHelp(void); + +protected: + void resizeEvent(QResizeEvent*); + void loadPayees(void); + void selectedPayees(QValueList<MyMoneyPayee>& payeesList) const; + void ensurePayeeVisible(const QString& id); + void clearItemData(void); + +protected slots: + /** + * This method loads the m_transactionList, clears + * the m_TransactionPtrVector and rebuilds and sorts + * it according to the current settings. Then it + * loads the m_transactionView with the transaction data. + */ + void showTransactions(void); + + /** + * This slot is called whenever the selection in m_payeesList + * has been changed. + */ + void slotSelectPayee(void); + + /** + * This slot marks the current selected payee as modified (dirty). + */ + void slotPayeeDataChanged(void); + void slotKeyListChanged(void); + + /** + * This slot is called when the name of a payee is changed inside + * the payee list view and only a single payee is selected. + */ + void slotRenamePayee(QListViewItem *p, int col, const QString& txt); + + /** + * Updates the payee data in m_payee from the information in the + * payee information widget. + */ + void slotUpdatePayee(void); + + void slotTransactionDoubleClicked(QListViewItem *); + +private slots: + void rearrange(void); + + /** + * This slot receives the signal from the listview control that an item was right-clicked, + * If @p item points to a real payee item, emits openContextMenu(). + * + * @param lv pointer to the listview sending the signal + * @param item the item on which the cursor resides + * @param p position of the pointer device + */ + void slotOpenContextMenu(KListView* lv, QListViewItem* item, const QPoint& p); + + void slotQueueUpdate(void); + + void slotActivateUpdate(void); + + void slotChooseDefaultAccount(void); + +private: + void readConfig(void); + +signals: + void transactionSelected(const QString& accountId, const QString& transactionId); + void openContextMenu(const MyMoneyObject& obj); + void selectObjects(const QValueList<MyMoneyPayee>& payees); + +private: + MyMoneyPayee m_payee; + QString m_newName; + + QSplitter* m_splitter; + + /** + * This member holds a list of all transactions + */ + QValueList<KMyMoneyTransaction> m_transactionList; + + /** + * This member keeps a vector of pointers to all visible (filtered) + * transaction in m_transactionList in sorted order. Sorting is done + * in KTransactionPtrVector::compareItems + */ + KTransactionPtrVector m_transactionPtrVector; + + /** + * This member holds the state of the toggle switch used + * to suppress updates due to MyMoney engine data changes + */ + bool m_needReload; + + /** + * Search widget for the list + */ + KListViewSearchLineWidget* m_searchWidget; + bool m_needConnection; + + /** + * Counting semaphore to collect updates + */ + int m_updatesQueued; + + /** + * Semaphore to suppress loading during selection + */ + bool m_inSelection; +}; + +#endif diff --git a/kmymoney2/views/kpayeesviewdecl.ui b/kmymoney2/views/kpayeesviewdecl.ui new file mode 100644 index 0000000..3584e6f --- /dev/null +++ b/kmymoney2/views/kpayeesviewdecl.ui @@ -0,0 +1,666 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KPayeesViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KPayeesViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>689</width> + <height>540</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KListView"> + <property name="name"> + <cstring>m_payeesList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>1</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="selectionMode" stdset="0"> + <enum>Extended</enum> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="showSortIndicator"> + <bool>true</bool> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + <widget class="QTabWidget"> + <property name="name"> + <cstring>m_tabWidget</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>2</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Transactions</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>transactionGroup</cstring> + </property> + <property name="title"> + <string>Transactions</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>11</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="KListView"> + <column> + <property name="text"> + <string>No.</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Date</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Category</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Amount</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>m_transactionView</cstring> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>m_balanceLabel</cstring> + </property> + <property name="text"> + <string>Balance: </string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + </widget> + </vbox> + </widget> + </vbox> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>Address</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>detailsGroup</cstring> + </property> + <property name="title"> + <string>Payee Information</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>11</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>TextLabel7</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>E-Mail:</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel5</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Postal Code:</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>TextLabel6</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Telephone/Fax:</string> + </property> + </widget> + <widget class="QLabel" row="4" column="0"> + <property name="name"> + <cstring>TextLabel4_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Notes</string> + </property> + <property name="alignment"> + <set>AlignTop|AlignLeft</set> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>postcodeEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="2" column="1"> + <property name="name"> + <cstring>telephoneEdit</cstring> + </property> + </widget> + <widget class="QLineEdit" row="3" column="1"> + <property name="name"> + <cstring>emailEdit</cstring> + </property> + </widget> + <widget class="QMultiLineEdit" row="0" column="1"> + <property name="name"> + <cstring>addressEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QMultiLineEdit" row="4" column="1"> + <property name="name"> + <cstring>notesEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>TextLabel4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Address:</string> + </property> + <property name="alignment"> + <set>AlignTop|AlignLeft</set> + </property> + </widget> + </grid> + </widget> + <spacer> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>Matching</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>buttonGroup1</cstring> + </property> + <property name="title"> + <string>Transaction Matching</string> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>m_matchType</cstring> + </property> + <property name="geometry"> + <rect> + <x>11</x> + <y>25</y> + <width>403</width> + <height>87</height> + </rect> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + <property name="title"> + <string></string> + </property> + <property name="exclusive"> + <bool>true</bool> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioNoMatch</cstring> + </property> + <property name="geometry"> + <rect> + <x>1</x> + <y>1</y> + <width>401</width> + <height>24</height> + </rect> + </property> + <property name="text"> + <string>No matching</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioNameMatch</cstring> + </property> + <property name="geometry"> + <rect> + <x>1</x> + <y>31</y> + <width>401</width> + <height>24</height> + </rect> + </property> + <property name="text"> + <string>Match on Payee name</string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>radioKeyMatch</cstring> + </property> + <property name="geometry"> + <rect> + <x>2</x> + <y>62</y> + <width>399</width> + <height>24</height> + </rect> + </property> + <property name="text"> + <string>Match on a name listed below</string> + </property> + </widget> + </widget> + </widget> + <widget class="KEditListBox"> + <property name="name"> + <cstring>matchKeyEditList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>List of matching names</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This list contains the names that will match this payee if a transaction is imported from an external source. Keep in mind, that you can specify regular expressions here.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>checkMatchIgnoreCase</cstring> + </property> + <property name="text"> + <string>Ignore Case</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage2</cstring> + </property> + <attribute name="title"> + <string>Default Account</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>checkEnableDefaultAccount</cstring> + </property> + <property name="text"> + <string>Use the default account for +new transactions with this payee</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>labelDefaultAccount</cstring> + </property> + <property name="text"> + <string>Default category:</string> + </property> + </widget> + <widget class="KMyMoneyAccountCombo" row="0" column="1"> + <property name="name"> + <cstring>comboDefaultAccount</cstring> + </property> + </widget> + <widget class="KPushButton" row="1" column="1"> + <property name="name"> + <cstring>buttonSelectMyAccount</cstring> + </property> + <property name="text"> + <string>Suggest a category</string> + </property> + </widget> + </grid> + </widget> + <spacer> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>31</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_helpButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Help</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer14</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>260</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_updateButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<includes> + <include location="local" impldecl="in declaration">../widgets/kmymoneyaccountcombo.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kmymoney2/views/kreportsview.cpp b/kmymoney2/views/kreportsview.cpp new file mode 100755 index 0000000..6c7da52 --- /dev/null +++ b/kmymoney2/views/kreportsview.cpp @@ -0,0 +1,1510 @@ +/*************************************************************************** + kreportsview.cpp - description + ------------------- + begin : Sat Mar 27 2004 + copyright : (C) 2000-2004 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 <ace.j@hotpop.com> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "kdecompat.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qlayout.h> +#include <qdatetime.h> +#include <qregexp.h> +#include <qdragobject.h> +#include <qclipboard.h> +#include <qapplication.h> +#include <qprinter.h> +#include <qpainter.h> +#include <qfile.h> +#include <qtimer.h> +#include <qiconset.h> +#include <qpopupmenu.h> +#include <qpushbutton.h> +#include <qtooltip.h> +#include <qcheckbox.h> +#include <qvbox.h> +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kglobal.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <khtmlview.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kmessagebox.h> +#include <klistview.h> +#include <kmessagebox.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/mymoneyreport.h> +#include <kmymoney/kmymoneyglobalsettings.h> + +#include "kreportsview.h" +#include "../reports/querytable.h" +#include "../reports/objectinfotable.h" +#include "../dialogs/kreportconfigurationfilterdlg.h" +#include "../kmymoneyutils.h" + +using namespace reports; + +#define VIEW_LEDGER "ledger" +#define VIEW_SCHEDULE "schedule" +#define VIEW_WELCOME "welcome" +#define VIEW_HOME "home" +#define VIEW_REPORTS "reports" + +/** + * KReportsView::KReportTab Implementation + */ + +KReportsView::KReportTab::KReportTab(KTabWidget* parent, const MyMoneyReport& report ): + QWidget( parent, "reporttab" ), + m_part( new KHTMLPart( this, "reporttabpart" ) ), + m_chartView( new KReportChartView( this, "reportchart" ) ), + m_control( new kMyMoneyReportControlDecl( this, "reportcontrol" ) ), + m_layout( new QVBoxLayout( this, 11, 6, "reporttablayout" ) ), + m_report( report ), + m_deleteMe( false ), + m_showingChart( false ), + m_needReload( true ), + m_table(0) +{ + m_part->setZoomFactor( KMyMoneyGlobalSettings::fontSizePercentage() ); + + if ( ! KReportChartView::implemented() || m_report.reportType() != MyMoneyReport::ePivotTable ) + { + m_control->buttonChart->hide(); + } + + m_chartView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); + m_chartView->hide(); + m_layout->addWidget( m_control ); //, 0, Qt::AlignTop ); + m_layout->addWidget( m_part->view() ); + m_layout->addWidget( m_chartView ); + + // I like this icon... + QString icon = KGlobal::dirs()->findResource("icon", "default.kde/16x16/mimetypes/spreadsheet.png"); + // but if it's not there, we'll use ye ol' standard icon + if ( icon == QString::null ) + icon = KGlobal::dirs()->findResource("icon", "hicolor/16x16/apps/kmymoney2.png"); + + parent->insertTab( this, QIconSet(QPixmap(icon)), report.name() ); + parent->setTabEnabled( this, true ); + +#ifdef HAVE_KDCHART + if ( m_report.isChartByDefault() ) + toggleChart(); +#endif +} + +KReportsView::KReportTab::~KReportTab() +{ + delete m_table; +} + +void KReportsView::KReportTab::print(void) +{ + if(m_part && m_part->view()) + m_part->view()->print(); +} + +void KReportsView::KReportTab::copyToClipboard(void) +{ + QTextDrag* pdrag = new QTextDrag( createTable() ); + pdrag->setSubtype("html"); + QApplication::clipboard()->setData(pdrag); +} + +void KReportsView::KReportTab::saveAs( const QString& filename, bool includeCSS ) +{ + QFile file( filename ); + if ( file.open( IO_WriteOnly ) ) + { + if ( QFileInfo(filename).extension(false).lower() == "csv") + { + QTextStream(&file) << m_table->renderCSV(); + } + else { + QTextStream stream(&file); + + QRegExp exp(QString("(.*)(<link.*css\" href=)\"(.*)\">(<meta.*%1\" />)(.*)").arg(KGlobal::locale()->encoding())); + QString table = createTable(); + if(exp.search(table) != -1 && includeCSS) { + QFile cssFile(exp.cap(3)); + if(cssFile.open(IO_ReadOnly)) { + QTextStream cssStream(&cssFile); + stream << exp.cap(1); + stream << exp.cap(4); + stream << endl << "<style type=\"text/css\">" << endl << "<!--" << endl; + stream << cssStream.read(); + stream << "-->" << endl << "</style>" << endl; + stream << exp.cap(5); + cssFile.close(); + } else { + stream << table; + } + } else { + stream << table; + } + } + file.close(); + } +} + +void KReportsView::KReportTab::loadTab(void) +{ + m_needReload = true; + if(isVisible()) { + m_needReload = false; + updateReport(); + } +} + +void KReportsView::KReportTab::show(void) +{ + if(m_needReload) { + m_needReload = false; + updateReport(); + } + QWidget::show(); +} + +void KReportsView::KReportTab::updateReport(void) +{ + // reload the report from the engine. It might have + // been changed by the user + + try { + // Don't try to reload default reports from the engine + if(!m_report.id().isEmpty()) + m_report = MyMoneyFile::instance()->report(m_report.id()); + } catch(MyMoneyException* e) { + delete e; + } + + delete m_table; + m_table = 0; + + if ( m_report.reportType() == MyMoneyReport::ePivotTable ) { + m_table = new PivotTable(m_report); + } else if ( m_report.reportType() == MyMoneyReport::eQueryTable ) { + m_table = new QueryTable(m_report); + } else if ( m_report.reportType() == MyMoneyReport::eInfoTable ) { + m_table = new ObjectInfoTable(m_report); + } + + m_part->begin(); + m_part->write(createTable()); + m_part->end(); + + m_table->drawChart( *m_chartView ); + m_chartView->update(); +} + +QString KReportsView::KReportTab::createTable(const QString& links) +{ + QString filename; + if(!MyMoneyFile::instance()->value("reportstylesheet").isEmpty()) + filename = KGlobal::dirs()->findResource("appdata", QString("html/%1").arg(MyMoneyFile::instance()->value("reportstylesheet"))); + if(filename.isEmpty()) + filename = KGlobal::dirs()->findResource("appdata", "html/kmymoney2.css"); + QString header = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n") + + QString("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"%1\">").arg(filename); + + header += QString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(KGlobal::locale()->encoding()); + header += KMyMoneyUtils::variableCSS(); + + header += "</head><body>\n"; + + QString footer = "</body></html>\n"; + + QString html; + try { + html += header; + html += links; + + html += m_table->renderHTML(); + + html += footer; + } + catch(MyMoneyException *e) + { + kdDebug(2) << "KReportsView::KReportTab::createTable(): ERROR " << e->what() << endl; + + QString error = QString(i18n("There was an error creating your report: \"%1\".\nPlease report this error to the developer's list: kmymoney2-developer@lists.sourceforge.net")).arg(e->what()); + + KMessageBox::error(this, error, i18n("Critical Error")); + + html += header; + html += links; + html += "<h1>"+i18n("Unable to generate report")+"</h1><p>"+error+"</p>"; + html += footer; + + delete e; + } + return html; + +} + +void KReportsView::KReportTab::toggleChart(void) +{ + // for now it will just SHOW the chart. In the future it actually has to toggle it. + + if ( m_showingChart ) + { + m_part->show(); + m_chartView->hide(); + + m_control->buttonChart->setText( i18n( "Chart" ) ); + QToolTip::add( m_control->buttonChart, i18n( "Show the chart version of this report" ) ); + } + else + { + m_part->hide(); + m_chartView->show(); + + m_control->buttonChart->setText( i18n( "Report" ) ); + QToolTip::add( m_control->buttonChart, i18n( "Show the report version of this chart" ) ); + } + m_showingChart = ! m_showingChart; +} + +/** + * KReportsView Implementation + */ + +class KReportsView::Private +{ +public: + Private() : + includeCSS(0) {} + + QCheckBox* includeCSS; +}; + +KReportsView::KReportsView(QWidget *parent, const char *name ) : + KMyMoneyViewBase(parent, name, i18n("Reports")), + d(new Private), + m_needReload(false) +{ + m_reportTabWidget = new KTabWidget( this, "m_reportTabWidget" ); + addWidget( m_reportTabWidget ); + m_reportTabWidget->setHoverCloseButton( true ); + + m_listTab = (new QWidget( m_reportTabWidget, "indextab" )); + m_listTabLayout = ( new QVBoxLayout( m_listTab, 11, 6, "indextabLayout") ); + m_reportListView = new KListView( m_listTab, "m_reportListView" ); + m_listTabLayout->addWidget( m_reportListView ); + m_reportTabWidget->insertTab( m_listTab, i18n("Reports") ); + + m_reportListView->addColumn(i18n("Report")); + m_reportListView->addColumn(i18n("Comment")); + m_reportListView->setResizeMode(QListView::AllColumns); + m_reportListView->setAllColumnsShowFocus(true); + m_reportListView->setRootIsDecorated(true); + m_reportListView->setShadeSortColumn(false); + + connect( m_reportTabWidget, SIGNAL(closeRequest(QWidget*)), + this, SLOT(slotClose(QWidget*)) ); + connect(m_reportListView, SIGNAL(doubleClicked(QListViewItem*)), + this, SLOT(slotOpenReport(QListViewItem*))); + connect(m_reportListView, SIGNAL(returnPressed(QListViewItem*)), + this, SLOT(slotOpenReport(QListViewItem*))); + connect( m_reportListView, SIGNAL(contextMenu(KListView*,QListViewItem*,const QPoint &)), + this, SLOT(slotListContextMenu(KListView*,QListViewItem*,const QPoint &))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); +} + +KReportsView::~KReportsView() +{ + delete d; +} + +void KReportsView::show() +{ + if(m_needReload) { + loadView(); + m_needReload = false; + } + + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + if(tab) + emit reportSelected(tab->report()); + else + emit reportSelected(MyMoneyReport()); + + // don't forget base class implementation + KMyMoneyViewBase::show(); +} + +void KReportsView::slotLoadView(void) +{ + m_needReload = true; + if(isVisible()) { + loadView(); + m_needReload = false; + } +} + +QString KReportsView::KReportGroupListItem::key ( int column, bool ascending ) const +{ + if (column == 0) + return QString::number(m_nr).rightJustify(3,'0'); + else + return KListViewItem::key(column,ascending); +} + +KReportsView::KReportGroupListItem::KReportGroupListItem(KListView* parent, const int nr, QString name) : + KListViewItem(parent), + m_name(name) +{ + setNr(nr); +} + +void KReportsView::KReportGroupListItem::setNr(const int nr) +{ + m_nr = nr; + setText(0, QString("%1. %2").arg(nr).arg(m_name)); +} + +void KReportsView::loadView(void) +{ + ::timetrace("Start KReportsView::loadView"); + + // remember the id of the current selected item and the + // items that are shown 'expanded' + QMap<QString, bool> isOpen; + QListViewItem *item = m_reportListView->selectedItem(); + QString selectedPage = (item) ? item->text(0) : QString(); + + // keep a map of all 'expanded' accounts + QListViewItemIterator it_lvi(m_reportListView); + while(it_lvi.current()) { + item = it_lvi.current(); + if(item && item->isOpen()) { + isOpen[item->text(0)] = true; + } + ++it_lvi; + } + + // remember the upper left corner of the viewport + QPoint startPoint = m_reportListView->viewportToContents(QPoint(0, 0)); + + // turn off updates to avoid flickering during reload + m_reportListView->setUpdatesEnabled(false); + + // + // Rebuild the list page + // + m_reportListView->clear(); + unsigned pagenumber = 1; + + // Default Reports + KReportGroupListItem* chartnode = new KReportGroupListItem(m_reportListView, 10, i18n("Charts")); + + QMap<QString,KReportGroupListItem*> groupitems; + QValueList<ReportGroup> defaultreports; + defaultReports(defaultreports); + QValueList<ReportGroup>::const_iterator it_group = defaultreports.begin(); + while ( it_group != defaultreports.end() ) + { + QString groupname = (*it_group).name(); + KReportGroupListItem* curnode = new KReportGroupListItem(m_reportListView, pagenumber++, (*it_group).title()); + curnode->setOpen(isOpen.find(curnode->text(0)) != isOpen.end()); + groupitems[groupname] = curnode; + + QValueList<MyMoneyReport>::const_iterator it_report = (*it_group).begin(); + while( it_report != (*it_group).end() ) + { + MyMoneyReport report = *it_report; + report.setGroup(groupname); + KReportListItem* r = new KReportListItem( curnode, report ); + if(report.name() == selectedPage) + m_reportListView->setSelected(r, true); + + // ALSO place it into the Charts list if it's displayed as a chart by default + if ( (*it_report).isChartByDefault() ) + new KReportListItem( chartnode, *it_report ); + + ++it_report; + } + + ++it_group; + } + + // Rename the charts item to place it at this point in the list. + chartnode->setNr(pagenumber++); + chartnode->setOpen(isOpen.find(chartnode->text(0)) != isOpen.end()); + + // Custom reports + + KReportGroupListItem* favoritenode = new KReportGroupListItem(m_reportListView,pagenumber++, i18n("Favorite Reports")); + favoritenode->setOpen(isOpen.find(favoritenode->text(0)) != isOpen.end()); + KReportGroupListItem* orphannode = NULL; + + QValueList<MyMoneyReport> customreports = MyMoneyFile::instance()->reportList(); + QValueList<MyMoneyReport>::const_iterator it_report = customreports.begin(); + while( it_report != customreports.end() ) + { + // If this report is in a known group, place it there + KReportGroupListItem* groupnode = groupitems[(*it_report).group()]; + if ( groupnode ) + new KReportListItem( groupnode, *it_report ); + else + // otherwise, place it in the orphanage + { + if ( ! orphannode ) + orphannode = new KReportGroupListItem(m_reportListView, pagenumber++, i18n("Old Customized Reports")); + new KReportListItem( orphannode, *it_report ); + } + + // ALSO place it into the Favorites list if it's a favorite + if ( (*it_report).isFavorite() ) + new KReportListItem( favoritenode, *it_report ); + + // ALSO place it into the Charts list if it's displayed as a chart by default + if ( (*it_report).isChartByDefault() ) + new KReportListItem( chartnode, *it_report ); + + ++it_report; + } + + // reposition viewport + m_reportListView->setContentsPos(startPoint.x(), startPoint.y()); + + // turn updates back on + m_reportListView->setUpdatesEnabled(true); + m_reportListView->repaintContents(); + + // + // Go through the tabs to set their update flag or delete them if needed + // + + int index = 1; + while ( index < m_reportTabWidget->count() ) + { + // TODO: Find some way of detecting the file is closed and kill these tabs!! + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->page(index)); + if ( tab->isReadyToDelete() /* || ! reports.count() */ ) + { + delete tab; + --index; + } + else + tab->loadTab(); + ++index; + } + ::timetrace("Done KReportsView::loadView"); +} + +void KReportsView::slotOpenURL(const KURL &url, const KParts::URLArgs& /* args */) +{ + QString view = url.fileName(false); + QString command = url.queryItem("command").data(); + + if(view == VIEW_REPORTS) { + + if ( command.isEmpty() ) { + // slotRefreshView(); + } else if ( command == "print" ) + slotPrintView(); + else if ( command == "copy" ) + slotCopyView(); + else if ( command == "save" ) + slotSaveView(); + else if ( command == "configure" ) + slotConfigure(); + else if ( command == "duplicate" ) + slotDuplicate(); + else if ( command == "close" ) + slotCloseCurrent(); + else if ( command == "delete" ) + slotDelete(); + else + qDebug("Unknown command '%s' in KReportsView::slotOpenURL()", static_cast<const char*>(command)); + + } else { + qDebug("Unknown view '%s' in KReportsView::slotOpenURL()", view.latin1()); + } +} + +void KReportsView::slotPrintView(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + if(tab) + tab->print(); +} + +void KReportsView::slotCopyView(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + if(tab) + tab->copyToClipboard(); +} + +void KReportsView::slotSaveView(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + if(tab) { + QVBox* vbox = new QVBox(); + d->includeCSS = new QCheckBox(i18n("Include Stylesheet"), vbox); + + // the following code is copied from KFileDialog::getSaveFileName, + // adjust to our local needs (filetypes etc.) and + // enhanced to show the m_saveEncrypted combo box + KFileDialog dlg( ":kmymoney-export", + QString("%1|%2\n").arg("*.csv").arg(i18n("CSV (Filefilter)", "CSV files")) + + QString("%1|%2\n").arg("*.html").arg(i18n("HTML (Filefilter)", "HTML files")), + this, "filedialog", true, vbox); + connect(&dlg, SIGNAL(filterChanged(const QString&)), this, SLOT(slotSaveFilterChanged(const QString&))); + + dlg.setOperationMode( KFileDialog::Saving ); + dlg.setCaption(i18n("Export as")); + slotSaveFilterChanged("*.csv"); // init gui + + if(dlg.exec() == QDialog::Accepted) { + KURL newURL = dlg.selectedURL(); + if (!newURL.isEmpty()) { + QString newName = newURL.prettyURL(0, KURL::StripFileProtocol); + + if(newName.findRev('.') == -1) + newName.append(".html"); + + tab->saveAs( newName, d->includeCSS->isEnabled() && d->includeCSS->isChecked() ); + } + } + } +} + +void KReportsView::slotSaveFilterChanged(const QString& filter) +{ + d->includeCSS->setEnabled(filter == "*.html"); +} + +void KReportsView::slotConfigure(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + + if(tab) { + MyMoneyReport report = tab->report(); + if ( report.comment() == i18n("Default Report") || report.comment() == i18n("Generated Report") ) + { + report.setComment( i18n("Custom Report") ); + report.setName( report.name() + i18n(" (Customized)") ); + } + + KReportConfigurationFilterDlg dlg(report); + + if (dlg.exec()) + { + MyMoneyReport newreport = dlg.getConfig(); + + // If this report has an ID, then MODIFY it, otherwise ADD it + MyMoneyFileTransaction ft; + if ( ! newreport.id().isEmpty() ) + { + MyMoneyFile::instance()->modifyReport(newreport); + ft.commit(); + tab->modifyReport(newreport); + + m_reportTabWidget->changeTab( tab, newreport.name() ); + m_reportTabWidget->showPage(tab); + } + else + { + MyMoneyFile::instance()->addReport(newreport); + ft.commit(); + new KReportListItem( m_reportListView, newreport ); + addReportTab(newreport); + } + } + } +} + +void KReportsView::slotDuplicate(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + + if(tab) { + MyMoneyReport dupe = tab->report(); + dupe.setName( QString(i18n("Copy of %1")).arg(dupe.name()) ); + if ( dupe.comment() == i18n("Default Report") ) + dupe.setComment( i18n("Custom Report") ); + dupe.clearId(); + + KReportConfigurationFilterDlg dlg(dupe); + if (dlg.exec()) + { + dupe = dlg.getConfig(); + MyMoneyFileTransaction ft; + try { + MyMoneyFile::instance()->addReport(dupe); + ft.commit(); + new KReportListItem( m_reportListView, dupe ); + addReportTab(dupe); + } catch(MyMoneyException* e) { + qDebug("Cannot add report"); + delete e; + } + } + } +} + +void KReportsView::slotDelete(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + + if(tab) { + MyMoneyReport report = tab->report(); + if ( ! report.id().isEmpty() ) + { + if ( KMessageBox::Continue == KMessageBox::warningContinueCancel(this, QString("<qt>")+i18n("Are you sure you want to delete report <b>%1</b>? There is no way to recover it!").arg(report.name())+QString("</qt>"), i18n("Delete Report?"))) + { + // close the tab and then remove the report so that it is not + // generated again during the following loadView() call + slotClose(tab); + + MyMoneyFileTransaction ft; + MyMoneyFile::instance()->removeReport(report); + ft.commit(); + } + } + else + KMessageBox::information(this, QString("<qt>")+i18n("Sorry, <b>%1</b> is a default report. You may not delete it.").arg(report.name())+QString("</qt>"), i18n("Delete Report?")); + } +} + +void KReportsView::slotOpenReport(const QString& id) +{ + if ( ! id.isEmpty() ) + { + KReportTab* page = NULL; + int index = 1; + while ( index < m_reportTabWidget->count() ) + { + KReportTab* current = dynamic_cast<KReportTab*>(m_reportTabWidget->page(index)); + + if ( current->report().id() == id ) + { + page = current; + break; + } + + ++index; + } + + // Show the tab, or create a new one, as needed + if ( page ) + m_reportTabWidget->showPage( page ); + else + addReportTab(MyMoneyFile::instance()->report(id)); + } +} + +void KReportsView::slotOpenReport(QListViewItem* item) +{ + KReportListItem *reportItem = dynamic_cast<KReportListItem*> (item); + + if ( reportItem ) + { + KReportTab* page = NULL; + + // Find the tab which contains the report indicated by this list item + int index = 1; + while ( index < m_reportTabWidget->count() ) + { + KReportTab* current = dynamic_cast<KReportTab*>(m_reportTabWidget->page(index)); + + // If this report has an ID, we'll use the ID to match + if ( ! reportItem->report().id().isEmpty() ) + { + if ( current->report().id() == reportItem->report().id() ) + { + page = current; + break; + } + } + // Otherwise, use the name to match. THIS ASSUMES that no 2 default reports + // have the same name...but that would be pretty a boneheaded thing to do. + else + { + if ( current->report().name() == reportItem->report().name() ) + { + page = current; + break; + } + } + + ++index; + } + + // Show the tab, or create a new one, as needed + if ( page ) + m_reportTabWidget->showPage( page ); + else + addReportTab(reportItem->report()); + } + else if (item) + { + // this is not a KReportListItem, so it's a regular QListViewItem, which + // means its a header. + // + // double-click on a header means toggle the expand/collapse state + + item->setOpen( ! item->isOpen() ); + } +} + +void KReportsView::slotOpenReport(const MyMoneyReport& report) +{ + kdDebug(2) << __func__ << " " << report.name() << endl; + KReportTab* page = NULL; + + // Find the tab which contains the report indicated by this list item + int index = 1; + while ( index < m_reportTabWidget->count() ) + { + KReportTab* current = dynamic_cast<KReportTab*>(m_reportTabWidget->page(index)); + + if ( current->report().name() == report.name() ) + { + page = current; + break; + } + + ++index; + } + + // Show the tab, or create a new one, as needed + if ( page ) + m_reportTabWidget->showPage( page ); + else + addReportTab(report); +} + +void KReportsView::slotToggleChart(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->currentPage()); + if(tab) + tab->toggleChart(); +} + +void KReportsView::slotCloseCurrent(void) +{ + if(m_reportTabWidget->currentPage()) + slotClose(m_reportTabWidget->currentPage()); +} + +void KReportsView::slotClose(QWidget* w) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(w); + if(tab) { + m_reportTabWidget->removePage(tab); + tab->setReadyToDelete(true); + } +} + +void KReportsView::slotCloseAll(void) +{ + KReportTab* tab = dynamic_cast<KReportTab*>(m_reportTabWidget->page(1)); + while (tab) + { + m_reportTabWidget->removePage(tab); + tab->setReadyToDelete(true); + + tab = dynamic_cast<KReportTab*>(m_reportTabWidget->page(1)); + } +} + +void KReportsView::addReportTab(const MyMoneyReport& report) +{ + KReportTab* tab = new KReportTab(m_reportTabWidget,report); + + connect( tab->control()->buttonChart, SIGNAL(clicked()), + this, SLOT(slotToggleChart(void ))); + connect( tab->control()->buttonConfigure, SIGNAL(clicked()), + this, SLOT(slotConfigure(void ))); + connect( tab->control()->buttonNew, SIGNAL(clicked()), + this, SLOT(slotDuplicate(void ))); + connect( tab->control()->buttonCopy, SIGNAL(clicked()), + this, SLOT(slotCopyView(void ))); + connect( tab->control()->buttonExport, SIGNAL(clicked()), + this, SLOT(slotSaveView(void ))); + connect( tab->control()->buttonDelete, SIGNAL(clicked()), + this, SLOT(slotDelete(void ))); + connect( tab->control()->buttonClose, SIGNAL(clicked()), + this, SLOT(slotCloseCurrent(void ))); + + // if this is a default report, then you can't delete it! + if ( report.id().isEmpty() ) + tab->control()->buttonDelete->setEnabled(false); + + // slotRefreshView(); + + m_reportTabWidget->showPage(tab); + +} + +void KReportsView::slotListContextMenu(KListView* lv,QListViewItem* item,const QPoint & p) +{ + if ( lv == m_reportListView && item ) + { + QPopupMenu* contextmenu = new QPopupMenu(this); + contextmenu->insertItem( i18n("&Open"), this, SLOT(slotOpenFromList()) ); + contextmenu->insertItem( i18n("&Configure"), this, SLOT(slotConfigureFromList()) ); + contextmenu->insertItem( i18n("&New report"), this, SLOT(slotNewFromList()) ); + contextmenu->insertItem( i18n("&Delete"), this, SLOT(slotDeleteFromList()) ); + + contextmenu->popup(p); + } +} + +void KReportsView::slotOpenFromList(void) +{ + KReportListItem *reportItem = dynamic_cast<KReportListItem*> (m_reportListView->selectedItem()); + + if ( reportItem ) + slotOpenReport(reportItem); +} + +void KReportsView::slotConfigureFromList(void) +{ + KReportListItem *reportItem = dynamic_cast<KReportListItem*> (m_reportListView->selectedItem()); + + if ( reportItem ) + { + slotOpenReport(reportItem); + slotConfigure(); + } +} +void KReportsView::slotNewFromList(void) +{ + KReportListItem *reportItem = dynamic_cast<KReportListItem*> (m_reportListView->selectedItem()); + + if ( reportItem ) + { + slotOpenReport(reportItem); + slotDuplicate(); + } +} + +void KReportsView::slotDeleteFromList(void) +{ + KReportListItem *reportItem = dynamic_cast<KReportListItem*> (m_reportListView->selectedItem()); + + if ( reportItem ) + { + slotOpenReport(reportItem); + slotDelete(); + } +} + +void KReportsView::defaultReports(QValueList<ReportGroup>& groups) +{ + { + ReportGroup list("Income and Expenses", i18n("Income and Expenses")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eExpenseIncome, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentMonth, + MyMoneyReport::eDetailAll, + i18n("Income and Expenses This Month"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eExpenseIncome, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Income and Expenses This Year"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eExpenseIncome, + MyMoneyReport::eYears, + MyMoneyTransactionFilter::allDates, + MyMoneyReport::eDetailAll, + i18n("Income and Expenses By Year"), + i18n("Default Report") + )); + +#ifdef HAVE_KDCHART + list.push_back(MyMoneyReport( + MyMoneyReport::eExpenseIncome, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailTop, + i18n("Income and Expenses Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setChartDataLabels(false); + + list.push_back(MyMoneyReport( + MyMoneyReport::eExpenseIncome, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailGroup, + i18n("Income and Expenses Pie Chart"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartType(MyMoneyReport::eChartPie); + list.back().setShowingRowTotals(false); +#endif + + groups.push_back(list); + } + { + ReportGroup list("Net Worth", i18n("Net Worth")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailTop, + i18n("Net Worth By Month"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::today, + MyMoneyReport::eDetailTop, + i18n("Net Worth Today"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eYears, + MyMoneyTransactionFilter::allDates, + MyMoneyReport::eDetailTop, + i18n("Net Worth By Year"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::next7Days, + MyMoneyReport::eDetailTop, + i18n("7-day Cash Flow Forecast"), + i18n("Default Report") + )); + list.back().setIncludingSchedules( true ); + list.back().setColumnsAreDays( true ); + +#ifdef HAVE_KDCHART + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last12Months, + MyMoneyReport::eDetailTotal, + i18n("Net Worth Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + + list.push_back(MyMoneyReport( + MyMoneyReport::eInstitution, + MyMoneyReport::eQCnone, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailTop, + i18n("Account Balances by Institution"), + i18n("Default Report") + )); +#endif + + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountType, + MyMoneyReport::eQCnone, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailTop, + i18n("Account Balances by Type"), + i18n("Default Report") + )); + + groups.push_back(list); + } + { + ReportGroup list("Transactions", i18n("Transactions")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAccount, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCcategory|MyMoneyReport::eQCbalance, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Transactions by Account"), + i18n("Default Report") + )); + //list.back().setConvertCurrency(false); + list.push_back(MyMoneyReport( + MyMoneyReport::eCategory, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Transactions by Category"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::ePayee, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCcategory, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Transactions by Payee"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eMonth, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCcategory, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Transactions by Month"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eWeek, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCcategory, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Transactions by Week"), + i18n("Default Report") + )); + list.push_back(MyMoneyReport( + MyMoneyReport::eAccount, + MyMoneyReport::eQCloan, + MyMoneyTransactionFilter::allDates, + MyMoneyReport::eDetailAll, + i18n("Loan Transactions"), + i18n("Default Report") + )); + list.back().setLoansOnly(true); + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountReconcile, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCcategory|MyMoneyReport::eQCbalance, + MyMoneyTransactionFilter::last3Months, + MyMoneyReport::eDetailAll, + i18n("Transactions by Reconciliation Status"), + i18n("Default Report") + )); + groups.push_back(list); + } + { + ReportGroup list("CashFlow", i18n("Cash Flow")); + list.push_back(MyMoneyReport( + MyMoneyReport::eCashFlow, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Cash Flow Transactions This Month"), + i18n("Default Report") + )); + groups.push_back(list); + } + { + ReportGroup list("Investments", i18n("Investments")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eTopAccount, + MyMoneyReport::eQCaction|MyMoneyReport::eQCshares|MyMoneyReport::eQCprice, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Investment Transactions"), + i18n("Default Report") + )); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountByTopAccount, + MyMoneyReport::eQCshares|MyMoneyReport::eQCprice, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Investment Holdings by Account"), + i18n("Default Report") + )); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eEquityType, + MyMoneyReport::eQCshares|MyMoneyReport::eQCprice, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Investment Holdings by Type"), + i18n("Default Report") + )); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountByTopAccount, + MyMoneyReport::eQCperformance, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Investment Performance by Account"), + i18n("Default Report") + )); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eEquityType, + MyMoneyReport::eQCperformance, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Investment Performance by Type"), + i18n("Default Report") + )); + list.back().setInvestmentsOnly(true); +#ifdef HAVE_KDCHART + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::today, + MyMoneyReport::eDetailAll, + i18n("Investment Holdings Pie"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartPie); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last12Months, + MyMoneyReport::eDetailAll, + i18n("Investment Worth Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setColumnsAreDays( true ); + list.back().setInvestmentsOnly(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last12Months, + MyMoneyReport::eDetailAll, + i18n("Investment Price Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setColumnsAreDays( true ); + list.back().setInvestmentsOnly(true); + list.back().setIncludingBudgetActuals(false); + list.back().setIncludingPrice(true); + list.back().setConvertCurrency(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last12Months, + MyMoneyReport::eDetailAll, + i18n("Investment Moving Average Price Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setColumnsAreDays( true ); + list.back().setInvestmentsOnly(true); + list.back().setIncludingBudgetActuals(false); + list.back().setIncludingAveragePrice(true); + list.back().setMovingAverageDays(10); + list.back().setConvertCurrency(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last30Days, + MyMoneyReport::eDetailAll, + i18n("Investment Moving Average"), + i18n("Default Report") + )); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setColumnsAreDays( true ); + list.back().setInvestmentsOnly(true); + list.back().setIncludingBudgetActuals(false); + list.back().setIncludingMovingAverage(true); + list.back().setMovingAverageDays(10); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::last30Days, + MyMoneyReport::eDetailAll, + i18n("Investment Moving Average vs Actual"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); + list.back().setColumnsAreDays( true ); + list.back().setInvestmentsOnly(true); + list.back().setIncludingBudgetActuals(true); + list.back().setIncludingMovingAverage(true); + list.back().setMovingAverageDays(10); +#endif + groups.push_back(list); + } + { + ReportGroup list("Taxes", i18n("Taxes")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eCategory, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Tax Transactions by Category"), + i18n("Default Report") + )); + list.back().setTax(true); + list.push_back(MyMoneyReport( + MyMoneyReport::ePayee, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCcategory|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Tax Transactions by Payee"), + i18n("Default Report") + )); + list.back().setTax(true); + list.push_back(MyMoneyReport( + MyMoneyReport::eCategory, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCpayee|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::lastFiscalYear, + MyMoneyReport::eDetailAll, + i18n("Tax Transactions by Category Last Fiscal Year"), + i18n("Default Report") + )); + list.back().setTax(true); + list.push_back(MyMoneyReport( + MyMoneyReport::ePayee, + MyMoneyReport::eQCnumber|MyMoneyReport::eQCcategory|MyMoneyReport::eQCaccount, + MyMoneyTransactionFilter::lastFiscalYear, + MyMoneyReport::eDetailAll, + i18n("Tax Transactions by Payee Last Fiscal Year"), + i18n("Default Report") + )); + list.back().setTax(true); + groups.push_back(list); + } + { + ReportGroup list("Budgeting", i18n("Budgeting")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToDate, + MyMoneyReport::eDetailAll, + i18n("Budgeted vs. Actual This Year"), + i18n("Default Report") + )); + list.back().setShowingRowTotals(true); + list.back().setBudget("Any",true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::yearToMonth, + MyMoneyReport::eDetailAll, + i18n("Budgeted vs. Actual This Year (YTM)"), + i18n("Default Report") + )); + list.back().setShowingRowTotals(true); + list.back().setBudget("Any",true); + // in case we're in January, we show the last year + if(QDate::currentDate().month() == 1) { + list.back().setDateFilter(MyMoneyTransactionFilter::lastYear); + } + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentMonth, + MyMoneyReport::eDetailAll, + i18n("Monthly Budgeted vs. Actual"), + i18n("Default Report") + )); + list.back().setBudget("Any",true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentYear, + MyMoneyReport::eDetailAll, + i18n("Yearly Budgeted vs. Actual"), + i18n("Default Report") + )); + list.back().setBudget("Any",true); + list.back().setShowingRowTotals(true); + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudget, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentMonth, + MyMoneyReport::eDetailAll, + i18n("Monthly Budget"), + i18n("Default Report") + )); + list.back().setBudget("Any",false); + + list.push_back(MyMoneyReport( + MyMoneyReport::eBudget, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentYear, + MyMoneyReport::eDetailAll, + i18n("Yearly Budget"), + i18n("Default Report") + )); + list.back().setBudget("Any",false); + list.back().setShowingRowTotals(true); +#ifdef HAVE_KDCHART + list.push_back(MyMoneyReport( + MyMoneyReport::eBudgetActual, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::currentYear, + MyMoneyReport::eDetailGroup, + i18n("Yearly Budgeted vs Actual Graph"), + i18n("Default Report") + )); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setBudget("Any",true); + list.back().setChartType(MyMoneyReport::eChartLine); +#endif + + groups.push_back(list); + } + { + ReportGroup list("Forecast", i18n("Forecast")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::next12Months, + MyMoneyReport::eDetailTop, + i18n("Forecast By Month"), + i18n("Default Report") + )); + list.back().setIncludingForecast( true ); + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::nextQuarter, + MyMoneyReport::eDetailTop, + i18n("Forecast Next Quarter"), + i18n("Default Report") + )); + list.back().setColumnsAreDays( true ); + list.back().setIncludingForecast( true ); + +#ifdef HAVE_KDCHART + list.push_back(MyMoneyReport( + MyMoneyReport::eAssetLiability, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::next3Months, + MyMoneyReport::eDetailTotal, + i18n("Net Worth Forecast Graph"), + i18n("Default Report") + )); + list.back().setColumnsAreDays( true ); + list.back().setIncludingForecast( true ); + list.back().setChartByDefault(true); + list.back().setChartGridLines(false); + list.back().setChartType(MyMoneyReport::eChartLine); +#endif + groups.push_back(list); + } + { + ReportGroup list("Information", i18n("General Information")); + + list.push_back(MyMoneyReport( + MyMoneyReport::eSchedule, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::next12Months, + MyMoneyReport::eDetailAll, + i18n("Schedule Information"), + i18n("Default Report") + )); + list.back().setDetailLevel(MyMoneyReport::eDetailAll); + list.push_back(MyMoneyReport( + MyMoneyReport::eSchedule, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::next12Months, + MyMoneyReport::eDetailAll, + i18n("Schedule Summary Information"), + i18n("Default Report") + )); + list.back().setDetailLevel(MyMoneyReport::eDetailTop); + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountInfo, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::today, + MyMoneyReport::eDetailAll, + i18n("Account Information"), + i18n("Default Report") + )); + list.back().setConvertCurrency(false); + list.push_back(MyMoneyReport( + MyMoneyReport::eAccountLoanInfo, + MyMoneyReport::eMonths, + MyMoneyTransactionFilter::today, + MyMoneyReport::eDetailAll, + i18n("Loan Information"), + i18n("Default Report") + )); + list.back().setConvertCurrency(false); + groups.push_back(list); + } +} + +// 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 VIEW_LEDGER +#undef VIEW_SCHEDULE +#undef VIEW_WELCOME +#undef VIEW_HOME +#undef VIEW_REPORTS + +#include "kreportsview.moc" +// vim:cin:si:ai:et:ts=2:sw=2: diff --git a/kmymoney2/views/kreportsview.h b/kmymoney2/views/kreportsview.h new file mode 100755 index 0000000..eaa8c7c --- /dev/null +++ b/kmymoney2/views/kreportsview.h @@ -0,0 +1,262 @@ +/*************************************************************************** + kreportsview.h - description + ------------------- + begin : Sat Mar 27 2004 + copyright : (C) 2000-2004 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 <ace.jones@hotpop.com> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef KREPORTSVIEW_H +#define KREPORTSVIEW_H + +#include "kdecompat.h" + +// Some STL headers in GCC4.3 contain operator new. Memory checker mangles these +#ifdef _CHECK_MEMORY + #undef new +#endif + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qvaluevector.h> +#include <qwidget.h> + +class QVBoxLayout; +class QListViewItem; + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <khtml_part.h> +#include <klistview.h> +#include <ktabwidget.h> + +// ---------------------------------------------------------------------------- +// Project Includes +#ifdef _CHECK_MEMORY + #include <kmymoney/mymoneyutils.h> +#endif + +#include <kmymoney/mymoneyscheduled.h> +#include <kmymoney/mymoneyaccount.h> +#include <kmymoney/mymoneyreport.h> +#include "../reports/pivottable.h" +#include "../reports/querytable.h" +#include "../widgets/kmymoneyreportcontroldecl.h" +#include "../reports/kreportchartview.h" +#include "../views/kmymoneyview.h" + +class MyMoneyReport; + +namespace KReportView { +}; + + +/** + * Displays a page where reports can be placed. + * + * @author Ace Jones + * + * @short A view for reports. +**/ +class KReportsView : public KMyMoneyViewBase +{ + Q_OBJECT +public: + + /** + * Helper class for KReportView. + * + * This is the widget which displays a single report in the TabWidget that comprises this view. + * + * @author Ace Jones + */ + + class KReportTab: public QWidget + { + private: + KHTMLPart* m_part; + reports::KReportChartView* m_chartView; + kMyMoneyReportControlDecl* m_control; + QVBoxLayout* m_layout; + MyMoneyReport m_report; + bool m_deleteMe; + bool m_showingChart; + bool m_needReload; + reports::ReportTable* m_table; + + public: + KReportTab(KTabWidget* parent, const MyMoneyReport& report ); + ~KReportTab(); + const MyMoneyReport& report(void) const { return m_report; } + void print(void); + void toggleChart(void); + void copyToClipboard(void); + void saveAs( const QString& filename, bool includeCSS = false ); + void updateReport(void); + QString createTable(const QString& links=QString()); + const kMyMoneyReportControlDecl* control(void) const { return m_control; } + bool isReadyToDelete(void) const { return m_deleteMe; } + void setReadyToDelete(bool f) { m_deleteMe = f; } + void modifyReport( const MyMoneyReport& report ) { m_report = report; } + void show(void); + void loadTab(void); + }; + + /** + * Helper class for KReportView. + * + * Associates a report id with a list view item. + * + * @author Ace Jones + */ + + class KReportListItem: public KListViewItem + { + private: + QString m_id; + MyMoneyReport m_report; + + public: + KReportListItem( KListView* parent, const MyMoneyReport& report ): + KListViewItem( parent, report.name(), report.comment() ), + m_id( report.id() ), + m_report( report ) + {} + KReportListItem( KListViewItem* parent, const MyMoneyReport& report ): + KListViewItem( parent, report.name(), report.comment() ), + m_id( report.id() ), + m_report( report ) + {} + //const QString& id(void) const { return m_id; } + const MyMoneyReport& report(void) const { return m_report; } + }; + + class KReportGroupListItem: public KListViewItem + { + private: + int m_nr; + QString m_name; + + public: + KReportGroupListItem( KListView* parent,const int nr,const QString name); + virtual QString key ( int column, bool ascending ) const; + void setNr(const int nr); + }; + + /** + * Helper class for KReportView. + * + * This is a named list of reports, which will be one section + * in the list of default reports + * + * @author Ace Jones + */ + class ReportGroup: public QValueList<MyMoneyReport> + { + private: + QString m_name; ///< the title of the group in non-translated form + QString m_title; ///< the title of the group in i18n-ed form + public: + ReportGroup( void ) {} + ReportGroup( const QString& name, const QString& title ): m_name( name ), m_title(title) {} + const QString& name( void ) const { return m_name; } + const QString& title(void) const { return m_title; } + }; + +private: + /// \internal d-pointer class. + class Private; + /// \internal d-pointer instance. + Private* const d; + KTabWidget* m_reportTabWidget; + KListView* m_reportListView; + QWidget* m_listTab; + QVBoxLayout* m_listTabLayout; + bool m_needReload; + +public: + /** + * Standard constructor. + * + * @param parent The QWidget this is used in. + * @param name The QT name. + * + * @return An object of type KReportsView + * + * @see ~KReportsView + */ + KReportsView(QWidget *parent=0, const char *name=0); + + /** + * Standard destructor. + * + * @return Nothing. + * + * @see KReportsView + */ + ~KReportsView(); + + /** + * Overridden so we can reload the view if necessary. + * + * @return Nothing. + */ + void show(); + +protected: + void addReportTab(const MyMoneyReport&); + void loadView(void); + static void defaultReports(QValueList<ReportGroup>&); + +public slots: + void slotOpenURL(const KURL &url, const KParts::URLArgs& args); + + void slotLoadView(void); + void slotPrintView(void); + void slotCopyView(void); + void slotSaveView(void); + void slotConfigure(void); + void slotDuplicate(void); + void slotToggleChart(void); + void slotOpenReport(QListViewItem*); + void slotOpenReport(const QString&); + void slotOpenReport(const MyMoneyReport&); + void slotCloseCurrent(void); + void slotClose(QWidget*); + void slotCloseAll(void); + void slotDelete(void); + void slotListContextMenu(KListView*,QListViewItem*,const QPoint &); + void slotOpenFromList(void); + void slotConfigureFromList(void); + void slotNewFromList(void); + void slotDeleteFromList(void); + +protected slots: + void slotSaveFilterChanged(const QString&); + +signals: + /** + * This signal is emitted whenever a report is selected + */ + void reportSelected(const MyMoneyReport&); + + +}; + +#endif diff --git a/kmymoney2/views/kscheduledlistitem.cpp b/kmymoney2/views/kscheduledlistitem.cpp new file mode 100644 index 0000000..6ba0b56 --- /dev/null +++ b/kmymoney2/views/kscheduledlistitem.cpp @@ -0,0 +1,231 @@ +/*************************************************************************** + kscheduledlistitem.cpp - description + ------------------- + begin : Sun Jan 27 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qpainter.h> +#include <qstyle.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kconfig.h> +#include <klocale.h> +#include <kglobal.h> +#include <kglobalsettings.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kscheduledlistitem.h" +#include "../mymoney/mymoneyfile.h" +#include "../kmymoneyglobalsettings.h" +#include "../kmymoneyutils.h" + +KScheduledListItem::KScheduledListItem(KListView *parent, const QString& name, const QPixmap& pixmap, const QString& sortKey) : + KListViewItem(parent, name), + m_sortKey(sortKey) +{ + setPixmap(0, pixmap); + if(m_sortKey.isEmpty()) + m_sortKey = name; +} + +KScheduledListItem::KScheduledListItem(KScheduledListItem *parent, const MyMoneySchedule& schedule/*, bool even*/) + : KListViewItem(parent) +{ + m_schedule = schedule; + m_sortKey = schedule.name(); + setPixmap(0, KMyMoneyUtils::scheduleIcon(KIcon::Small)); + + try + { + MyMoneyTransaction transaction = schedule.transaction(); + MyMoneySplit s1 = transaction.splits()[0]; + MyMoneySplit s2 = transaction.splits()[1]; + QValueList<MyMoneySplit>::ConstIterator it_s; + MyMoneySplit split; + MyMoneyAccount acc; + + switch(schedule.type()) { + case MyMoneySchedule::TYPE_DEPOSIT: + if (s1.value().isNegative()) + split = s2; + else + split = s1; + break; + + case MyMoneySchedule::TYPE_LOANPAYMENT: + for(it_s = transaction.splits().begin(); it_s != transaction.splits().end(); ++it_s) { + acc = MyMoneyFile::instance()->account((*it_s).accountId()); + if(acc.accountGroup() == MyMoneyAccount::Asset + || acc.accountGroup() == MyMoneyAccount::Liability) { + if(acc.accountType() != MyMoneyAccount::Loan + && acc.accountType() != MyMoneyAccount::AssetLoan) { + split = *it_s; + break; + } + } + } + if(it_s == transaction.splits().end()) { + qFatal("Split for payment account not found in %s:%d.", __FILE__, __LINE__); + } + break; + + default: + if (!s1.value().isPositive()) + split = s1; + else + split = s2; + break; + } + acc = MyMoneyFile::instance()->account(split.accountId()); + +/* + if (schedule.type() == MyMoneySchedule::TYPE_DEPOSIT) + { + if (s1.value() >= 0) + split = s1; + else + split = s2; + } + else if(schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) + { + + } + else + { + if (s1.value() < 0) + split = s1; + else + split = s2; + } +*/ + setText(0, schedule.name()); + MyMoneySecurity currency = MyMoneyFile::instance()->currency(acc.currencyId()); + + setText(1, acc.name()); + if(!s1.payeeId().isEmpty()) + setText(2, MyMoneyFile::instance()->payee(s1.payeeId()).name()); + else + setText(2, "---"); + m_amount = split.shares().abs(); + setText(3, QString("%1 ").arg(m_amount.formatMoney(acc, currency))); + // Do the real next payment like ms-money etc + if (schedule.isFinished()) + { + setText(4, i18n("Finished")); + } + else + setText(4, KGlobal::locale()->formatDate(schedule.adjustedNextDueDate(), true)); + + setText(5, i18n(schedule.occurenceToString())); + setText(6, KMyMoneyUtils::paymentMethodToString(schedule.paymentType())); + } + catch (MyMoneyException *e) + { + setText(0, "Error:"); + setText(1, e->what()); + delete e; + } +} + +KScheduledListItem::~KScheduledListItem() +{ +} + +void KScheduledListItem::paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align) +{ + QColorGroup cg2(cg); + + QColor textColour = KGlobalSettings::textColor(); + QFont cellFont = KMyMoneyGlobalSettings::listCellFont(); + + // avoid colorizing lines that do not contain a schedule + if(!m_schedule.id().isEmpty()) { + if (m_schedule.isFinished()) + textColour = Qt::darkGreen; + else if (m_schedule.isOverdue()) + textColour = Qt::red; + } + + cg2.setColor(QColorGroup::Text, textColour); + + // display group items in bold + if (!parent()) + cellFont.setBold(true); + + p->setFont(cellFont); + + if (isAlternate()) + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listColor()); + else + cg2.setColor(QColorGroup::Base, KMyMoneyGlobalSettings::listBGColor()); + + QListViewItem::paintCell(p, cg2, column, width, align); +} + +int KScheduledListItem::compare(QListViewItem* i, int col, bool ascending) const +{ + KScheduledListItem* item = dynamic_cast<KScheduledListItem*>(i); + int rc; + // do special sorting only if + // a) date + // b) amount + // c) name/group + // d) occurence + // in all other cases use the standard sorting + MyMoneyMoney diff; + switch(col) { + case 0: // type and name + rc = m_sortKey.compare(item->m_sortKey); + break; + + case 3: // amount + diff = m_amount - item->m_amount; + if(diff.isZero()) + rc = 0; + else if(diff.isPositive()) + rc = 1; + else + rc = -1; + break; + + case 4: // date + rc = item->m_schedule.adjustedNextDueDate().daysTo(m_schedule.adjustedNextDueDate()); + break; + + case 5: // occurence + rc = (m_schedule.occurence() - item->m_schedule.occurence()); + break; + + default: + rc = KListViewItem::compare(i, col, ascending); + break; + } + // adjust to [-1..1] + if(rc != 0) { + rc = (rc > 0) ? 1 : -1; + } + return rc; +} diff --git a/kmymoney2/views/kscheduledlistitem.h b/kmymoney2/views/kscheduledlistitem.h new file mode 100644 index 0000000..c5e4728 --- /dev/null +++ b/kmymoney2/views/kscheduledlistitem.h @@ -0,0 +1,107 @@ +/*************************************************************************** + kscheduledlistitem.h - description + ------------------- + begin : Sun Jan 27 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef KSCHEDULEDLISTITEM_H +#define KSCHEDULEDLISTITEM_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qpixmap.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <klistview.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include <kmymoney/mymoneyscheduled.h> + +/** + * The list view item that describes a scheduled transaction. + * + * @author Michael Edwardes + */ +class KScheduledListItem : public KListViewItem +{ +public: + /** + * This constructor is used to create a child of the main list view widget. + * + * The child should be a descriptor for the schedule type and one of + * Bill, + * Deposit or + * Transfer. + * + * Other types may be added in the future. + * + * @param parent The list view to be a child of. + * @param description The (translated) description. + * @param pixmap A pixmap for the entry + * @param sortKey a sortkey, if empty, @c description will be used. + * + * @see MyMoneySchedule + */ + KScheduledListItem(KListView *parent, const QString& description, const QPixmap& pixmap = QPixmap(), const QString& sortKey = QString()); + + /** + * This constructor is used to create a child of one of the children + * created by the above method. + * + * This child describes a schedule and represents the data in schedule. + * + * @param parent The list view item to be a child of. + * @param schedule The schedule to be represented. + * + * @see MyMoneySchedule + */ + KScheduledListItem(KScheduledListItem *parent, const MyMoneySchedule& schedule/*, bool even*/); + + /** + * Standard destructor. + */ + ~KScheduledListItem(); + + /** + * Returns the schedule id for the instance being represented. To be used + * selection slots by the view. + * + * Returns an empty string for the top level items. + * + * @return The schedule id. + */ + const QString& scheduleId(void) const { return m_schedule.id(); } + + int compare(QListViewItem* i, int col, bool ascending) const; + +protected: + void paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align); + +private: + MyMoneySchedule m_schedule; + QString m_sortKey; + MyMoneyMoney m_amount; +}; + +#endif diff --git a/kmymoney2/views/kscheduledview.cpp b/kmymoney2/views/kscheduledview.cpp new file mode 100644 index 0000000..42c09db --- /dev/null +++ b/kmymoney2/views/kscheduledview.cpp @@ -0,0 +1,494 @@ +/*************************************************************************** + kscheduledview.cpp - description + ------------------- + begin : Sun Jan 27 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 <qheader.h> +#include <qtoolbutton.h> +#include <qcombobox.h> +#include <qtabwidget.h> +#include <qlayout.h> +#include <qtimer.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include <kglobal.h> +#include <klocale.h> +#include <kconfig.h> +#include <kpopupmenu.h> +#include <kiconloader.h> +#include <kmessagebox.h> +#include <klistview.h> +#include <kpushbutton.h> +#include <klistviewsearchline.h> + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kscheduledview.h" +#include "kscheduledlistitem.h" +#include "../widgets/kmymoneyscheduleddatetbl.h" +// #include "../dialogs/kenterscheduledialog.h" +#include <kmymoney/kmymoneyutils.h> +#include <kmymoney/kmymoneyglobalsettings.h> + +#include "../kmymoney2.h" + +KScheduledView::KScheduledView(QWidget *parent, const char *name ) : + KScheduledViewDecl(parent,name, false), + m_openBills(true), + m_openDeposits(true), + m_openTransfers(true), + m_openLoans(true) +{ + // create the searchline widget + // and insert it into the existing layout + m_searchWidget = new KListViewSearchLineWidget(m_qlistviewScheduled, m_listTab); + m_listTabLayout->insertWidget(0, m_searchWidget); + + m_qlistviewScheduled->addColumn(i18n("Type/Name")); + m_qlistviewScheduled->addColumn(i18n("Account")); + m_qlistviewScheduled->addColumn(i18n("Payee")); + m_qlistviewScheduled->addColumn(i18n("Amount")); + m_qlistviewScheduled->addColumn(i18n("Next Due Date")); + m_qlistviewScheduled->addColumn(i18n("Frequency")); + m_qlistviewScheduled->addColumn(i18n("Payment Method")); + m_qlistviewScheduled->setColumnAlignment(3, Qt::AlignRight); + + readConfig(); + + m_qlistviewScheduled->setMultiSelection(false); + m_qlistviewScheduled->header()->setResizeEnabled(true); + if(m_qlistviewScheduled->sortColumn() == -1) + m_qlistviewScheduled->setSorting(0); + + connect(m_qbuttonNew, SIGNAL(clicked()), kmymoney2->action("schedule_new"), SLOT(activate())); + + // attach popup to 'Filter...' button + m_kaccPopup = new KPopupMenu(this); + m_kaccPopup->setCheckable(true); + m_accountsCombo->setPopup(m_kaccPopup); + connect(m_kaccPopup, SIGNAL(activated(int)), this, SLOT(slotAccountActivated(int))); + + m_qbuttonNew->setGuiItem(KMyMoneyUtils::scheduleNewGuiItem()); + m_accountsCombo->setGuiItem(KMyMoneyUtils::accountsFilterGuiItem()); + + KIconLoader *il = KGlobal::iconLoader(); + m_tabWidget->setTabIconSet(m_listTab, QIconSet(il->loadIcon("contents", KIcon::Small, KIcon::SizeSmall))); + m_tabWidget->setTabIconSet(m_calendarTab, QIconSet(il->loadIcon("calendartab", KIcon::User, KIcon::SizeSmall))); + + connect(m_qlistviewScheduled, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + this, SLOT(slotListViewContextMenu(KListView*, QListViewItem*, const QPoint&))); + connect(m_qlistviewScheduled, SIGNAL(selectionChanged(QListViewItem*)), + this, SLOT(slotSetSelectedItem(QListViewItem*))); + + connect(m_qlistviewScheduled, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)), + this, SLOT(slotListItemExecuted(QListViewItem*, const QPoint&, int))); + connect(m_qlistviewScheduled, SIGNAL(expanded(QListViewItem*)), + this, SLOT(slotListViewExpanded(QListViewItem*))); + connect(m_qlistviewScheduled, SIGNAL(collapsed(QListViewItem*)), + this, SLOT(slotListViewCollapsed(QListViewItem*))); + + connect(m_calendar, SIGNAL(enterClicked(const MyMoneySchedule&, const QDate&)), this, SLOT(slotBriefEnterClicked(const MyMoneySchedule&, const QDate&))); + connect(m_calendar, SIGNAL(skipClicked(const MyMoneySchedule&, const QDate&)), this, SLOT(slotBriefSkipClicked(const MyMoneySchedule&, const QDate&))); + + connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadView())); +} + +KScheduledView::~KScheduledView() +{ + writeConfig(); +} + +void KScheduledView::refresh(bool full, const QString& schedId) +{ + m_qlistviewScheduled->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont()); + + QPoint startPoint = QPoint(m_qlistviewScheduled->contentsX(), m_qlistviewScheduled->contentsY()); + + m_qlistviewScheduled->clear(); + + try + { + if (full) + { + try + { + int accountCount=0; + + m_kaccPopup->clear(); + + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyAccount acc; + QStringList::ConstIterator it_s; + + acc = file->asset(); + for(it_s = acc.accountList().begin(); it_s != acc.accountList().end(); ++it_s) + { + MyMoneyAccount a = file->account(*it_s); + m_kaccPopup->insertItem(a.name(), accountCount); + m_kaccPopup->setItemChecked(accountCount, true); + accountCount++; + } + } + catch (MyMoneyException *e) + { + KMessageBox::detailedError(this, i18n("Unable to load accounts: "), e->what()); + delete e; + } + } + + // Refresh the calendar view first + m_calendar->refresh(); + + MyMoneyFile *file = MyMoneyFile::instance(); + QValueList<MyMoneySchedule> scheduledItems = file->scheduleList(); + + if (scheduledItems.count() == 0) + return; + + KScheduledListItem *itemBills = new KScheduledListItem(m_qlistviewScheduled, i18n("Bills"), KMyMoneyUtils::billScheduleIcon(KIcon::Small), "0"); + KScheduledListItem *itemDeposits = new KScheduledListItem(m_qlistviewScheduled, i18n("Deposits"), KMyMoneyUtils::depositScheduleIcon(KIcon::Small), "1"); + KScheduledListItem *itemLoans = new KScheduledListItem(m_qlistviewScheduled, i18n("Loans"), KMyMoneyUtils::transferScheduleIcon(KIcon::Small), "2"); + KScheduledListItem *itemTransfers = new KScheduledListItem(m_qlistviewScheduled, i18n("Transfers"), KMyMoneyUtils::transferScheduleIcon(KIcon::Small), "3"); + + QValueList<MyMoneySchedule>::Iterator it; + + KScheduledListItem *openItem=0; + + for (it = scheduledItems.begin(); it != scheduledItems.end(); ++it) + { + MyMoneySchedule schedData = (*it); + KScheduledListItem* item=0; + + bool bContinue=true; + QStringList::iterator accIt; + for (accIt=m_filterAccounts.begin(); accIt!=m_filterAccounts.end(); ++accIt) + { + if (*accIt == schedData.account().id()) + { + bContinue=false; // Filter it out + break; + } + } + + if (!bContinue) + continue; + + KScheduledListItem* parent = 0; + switch (schedData.type()) + { + case MyMoneySchedule::TYPE_ANY: + // Should we display an error ? + // We just sort it as bill and fall through here + + case MyMoneySchedule::TYPE_BILL: + parent = itemBills; + break; + + case MyMoneySchedule::TYPE_DEPOSIT: + parent = itemDeposits; + break; + + case MyMoneySchedule::TYPE_TRANSFER: + parent = itemTransfers; + break; + + case MyMoneySchedule::TYPE_LOANPAYMENT: + parent = itemLoans; + break; + + } + if(parent) { + if(!KMyMoneyGlobalSettings::hideFinishedSchedules() || !schedData.isFinished()) { + item = new KScheduledListItem(parent, schedData); + if (schedData.id() == schedId) + openItem = item; + } + } + } + + if (openItem) + { + m_qlistviewScheduled->setSelected(openItem, true); + } + // using a timeout is the only way, I got the 'ensureTransactionVisible' + // working when coming from hidden form to visible form. I assume, this + // has something to do with the delayed update of the display somehow. + resize(width(), height()-1); + QTimer::singleShot(10, this, SLOT(slotTimerDone())); + m_qlistviewScheduled->update(); + + // force repaint in case the filter is set + m_searchWidget->searchLine()->updateSearch(QString::null); + + if (m_openBills) + itemBills->setOpen(true); + + if (m_openDeposits) + itemDeposits->setOpen(true); + + if (m_openTransfers) + itemTransfers->setOpen(true); + + if (m_openLoans) + itemLoans->setOpen(true); + + m_qlistviewScheduled->setContentsPos(startPoint.x(), startPoint.y()); + + } catch (MyMoneyException *e) + { + KMessageBox::error(this, e->what()); + delete e; + } +} + +void KScheduledView::slotTimerDone(void) +{ + QListViewItem* item; + + item = m_qlistviewScheduled->selectedItem(); + if(item) { + m_qlistviewScheduled->ensureItemVisible(item); + } + + // force a repaint of all items to update the branches + for(item = m_qlistviewScheduled->firstChild(); item != 0; item = item->itemBelow()) { + m_qlistviewScheduled->repaintItem(item); + } + resize(width(), height()+1); +} + +void KScheduledView::slotReloadView(void) +{ + m_needReload = true; + if(isVisible()) { + m_qbuttonNew->setEnabled(true); + m_tabWidget->setEnabled(true); + + refresh(true, m_selectedSchedule); + + m_needReload = false; + QTimer::singleShot(50, this, SLOT(slotRearrange())); + } +} + +void KScheduledView::show() +{ + KScheduledViewDecl::show(); + + if(m_needReload) + slotReloadView(); +} + +void KScheduledView::slotRearrange(void) +{ + resizeEvent(0); +} + +void KScheduledView::readConfig(void) +{ + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + m_openBills = config->readBoolEntry("KScheduleView_openBills", true); + m_openDeposits = config->readBoolEntry("KScheduleView_openDeposits", true); + m_openTransfers = config->readBoolEntry("KScheduleView_openTransfers", true); + m_openLoans = config->readBoolEntry("KScheduleView_openLoans", true); + m_tabWidget->setCurrentPage(config->readNumEntry("KScheduleView_tab", 0)); + + m_qlistviewScheduled->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont()); + m_qlistviewScheduled->restoreLayout(KGlobal::config(), "Schedule View Settings"); + +} + +void KScheduledView::writeConfig(void) +{ + KConfig *config = KGlobal::config(); + config->setGroup("Last Use Settings"); + config->writeEntry("KScheduleView_openBills", m_openBills); + config->writeEntry("KScheduleView_openDeposits", m_openDeposits); + config->writeEntry("KScheduleView_openTransfers", m_openTransfers); + config->writeEntry("KScheduleView_openLoans", m_openLoans); + config->writeEntry("KScheduleView_tab", m_tabWidget->currentPageIndex()); + config->sync(); + + m_qlistviewScheduled->saveLayout(KGlobal::config(), "Schedule View Settings"); +} + +void KScheduledView::slotListViewContextMenu(KListView* /* view */, QListViewItem *item, const QPoint& /* pos */) +{ + KScheduledListItem *scheduleItem = dynamic_cast<KScheduledListItem *>(item); + if (scheduleItem) + { + try + { + QString scheduleId = scheduleItem->scheduleId(); + + if (!scheduleId.isEmpty()) // Top level item + { + MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(scheduleId); + emit scheduleSelected(schedule); + m_selectedSchedule = schedule.id(); + } + emit openContextMenu(); + } catch (MyMoneyException *e) + { + KMessageBox::detailedSorry(this, i18n("Error activating context menu"), e->what()); + delete e; + } + } + else + { + emit openContextMenu(); + } +} + +void KScheduledView::slotListItemExecuted(QListViewItem* item, const QPoint&, int) +{ + KScheduledListItem* scheduleItem = (KScheduledListItem*)item; + if (!scheduleItem) + return; + + try + { + QString scheduleId = scheduleItem->scheduleId(); + + if (!scheduleId.isEmpty()) // Top level item + { + MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(scheduleId); + m_selectedSchedule = schedule.id(); + emit editSchedule(); + } + } catch (MyMoneyException *e) + { + KMessageBox::detailedSorry(this, i18n("Error executing item"), e->what()); + delete e; + } +} + +void KScheduledView::slotAccountActivated(int id) +{ + m_filterAccounts.clear(); + + m_kaccPopup->setItemChecked(id, ((m_kaccPopup->isItemChecked(id))?false:true)); + + try + { + int accountCount=0; + MyMoneyFile* file = MyMoneyFile::instance(); + MyMoneyAccount acc; + QStringList::ConstIterator it_s; + + acc = file->asset(); + for(it_s = acc.accountList().begin(); it_s != acc.accountList().end(); ++it_s) + { + if (!m_kaccPopup->isItemChecked(accountCount)) + { + m_filterAccounts.append(*it_s); + } + accountCount++; + } + + m_calendar->setFilterAccounts(m_filterAccounts); + + refresh(false, m_selectedSchedule); + } + catch (MyMoneyException *e) + { + KMessageBox::detailedError(this, i18n("Unable to filter account"), e->what()); + delete e; + } +} + +void KScheduledView::slotListViewExpanded(QListViewItem* item) +{ + KScheduledListItem *scheduleItem = (KScheduledListItem*)item; + if (scheduleItem) + { + if (scheduleItem->text(0) == i18n("Bills")) + m_openBills = true; + else if (scheduleItem->text(0) == i18n("Deposits")) + m_openDeposits = true; + else if (scheduleItem->text(0) == i18n("Transfers")) + m_openTransfers = true; + else if (scheduleItem->text(0) == i18n("Loans")) + m_openLoans = true; + } +} + +void KScheduledView::slotListViewCollapsed(QListViewItem* item) +{ + KScheduledListItem *scheduleItem = (KScheduledListItem*)item; + if (scheduleItem) + { + if (scheduleItem->text(0) == i18n("Bills")) + m_openBills = false; + else if (scheduleItem->text(0) == i18n("Deposits")) + m_openDeposits = false; + else if (scheduleItem->text(0) == i18n("Transfers")) + m_openTransfers = false; + else if (scheduleItem->text(0) == i18n("Loans")) + m_openLoans = false; + } +} + +void KScheduledView::slotSelectSchedule(const QString& schedule) +{ + refresh(true, schedule); +} + +void KScheduledView::slotBriefEnterClicked(const MyMoneySchedule& schedule, const QDate& date) +{ + Q_UNUSED(date); + + emit scheduleSelected(schedule); + emit enterSchedule(); +} + +void KScheduledView::slotBriefSkipClicked(const MyMoneySchedule& schedule, const QDate& date) +{ + Q_UNUSED(date); + + emit scheduleSelected(schedule); + emit skipSchedule(); +} + +void KScheduledView::slotSetSelectedItem(QListViewItem* item) +{ + emit scheduleSelected(MyMoneySchedule()); + KScheduledListItem* schedItem = static_cast<KScheduledListItem*>(item); + if(item) { + try { + MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(schedItem->scheduleId()); + emit scheduleSelected(schedule); + m_selectedSchedule = schedItem->scheduleId(); + } catch(MyMoneyException* e) { + qDebug("KScheduledView::slotSetSelectedItem: %s", e->what().data()); + delete e; + } + } +} + + +#include "kscheduledview.moc" diff --git a/kmymoney2/views/kscheduledview.h b/kmymoney2/views/kscheduledview.h new file mode 100644 index 0000000..9eac41a --- /dev/null +++ b/kmymoney2/views/kscheduledview.h @@ -0,0 +1,149 @@ +/*************************************************************************** + kscheduledview.h - description + ------------------- + begin : Sun Jan 27 2002 + copyright : (C) 2000-2002 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> + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#ifndef KSCHEDULEDVIEW_H +#define KSCHEDULEDVIEW_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include <qwidget.h> +#include <qlistview.h> + +// ---------------------------------------------------------------------------- +// KDE Includes + +class KListViewSearchLineWidget; + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "kscheduledviewdecl.h" +#include <kmymoney/mymoneyfile.h> +#include <kmymoney/mymoneyaccount.h> +#include "../widgets/kmymoneyscheduledcalendar.h" + +class KPopupMenu; + +/** + * Contains all the scheduled transactions be they bills, deposits or transfers. + * Encapsulates all the operations including adding, editing and deleting. + * Used by the KMyMoneyView class to show the view. + * + * @author Michael Edwardes 2000-2002 + * $Id: kscheduledview.h,v 1.33 2009/03/01 19:13:08 ipwizard Exp $ + * + * @short A class to encapsulate recurring transaction operations. + */ +class KScheduledView : public KScheduledViewDecl +{ + Q_OBJECT + +public: + /** + * Standard constructor for QWidgets. + */ + KScheduledView(QWidget *parent=0, const char *name=0); + + /** + * Standard destructor. + */ + ~KScheduledView(); + + /** + * Called by KMyMoneyView. + */ + void show(); + +public slots: + void slotSelectSchedule(const QString& schedule); + void slotReloadView(void); + +signals: + void scheduleSelected(const MyMoneySchedule& schedule); + void openContextMenu(void); + void skipSchedule(void); + void enterSchedule(void); + void editSchedule(void); + +protected slots: + /** + * Shows the context menu when the user right clicks or presses + * a 'windows' key when an item is selected. + * + * @param view a pointer to the view + * @param item a pointer to the current selected listview item + * @param pos The position to popup + * @return none + **/ + void slotListViewContextMenu(KListView* view, QListViewItem* item, const QPoint& pos); + + void slotListItemExecuted(QListViewItem*, const QPoint&, int); + + void slotAccountActivated(int); + + void slotListViewCollapsed(QListViewItem* item); + void slotListViewExpanded(QListViewItem* item); + + void slotBriefSkipClicked(const MyMoneySchedule& schedule, const QDate&); + void slotBriefEnterClicked(const MyMoneySchedule& schedule, const QDate&); + + void slotTimerDone(void); + + void slotSetSelectedItem(QListViewItem* item); + + void slotRearrange(void); + +private: + /// The selected schedule id in the list view. + QString m_selectedSchedule; + + /// Read config file + void readConfig(void); + + /// Write config file + void writeConfig(void); + + /** + * Refresh the view. + */ + void refresh(bool full=true, const QString& schedId = QString()); + + /** + * Loads the accounts into the combo box. + */ +// void loadAccounts(void); + + KPopupMenu *m_kaccPopup; + QStringList m_filterAccounts; + bool m_openBills; + bool m_openDeposits; + bool m_openTransfers; + bool m_openLoans; + bool m_needReload; + + /** + * Search widget for the list + */ + KListViewSearchLineWidget* m_searchWidget; +}; + +#endif diff --git a/kmymoney2/views/kscheduledviewdecl.ui b/kmymoney2/views/kscheduledviewdecl.ui new file mode 100644 index 0000000..faa3fe8 --- /dev/null +++ b/kmymoney2/views/kscheduledviewdecl.ui @@ -0,0 +1,161 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KScheduledViewDecl</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KScheduledViewDecl</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>620</width> + <height>399</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_qbuttonNew</cstring> + </property> + <property name="text"> + <string>New Scheduled transaction...</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>80</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_accountsCombo</cstring> + </property> + <property name="text"> + <string>Filter Accounts</string> + </property> + </widget> + </hbox> + </widget> + <widget class="Line"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QTabWidget"> + <property name="name"> + <cstring>m_tabWidget</cstring> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>m_listTab</cstring> + </property> + <attribute name="title"> + <string>List View</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KListView"> + <property name="name"> + <cstring>m_qlistviewScheduled</cstring> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="showSortIndicator"> + <bool>true</bool> + </property> + <property name="rootIsDecorated"> + <bool>true</bool> + </property> + <property name="shadeSortColumn"> + <bool>false</bool> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>m_calendarTab</cstring> + </property> + <attribute name="title"> + <string>Calendar View</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="kMyMoneyScheduledCalendar"> + <property name="name"> + <cstring>m_calendar</cstring> + </property> + </widget> + </vbox> + </widget> + </widget> + </vbox> +</widget> +<customwidgets> + <customwidget> + <class>kMyMoneyScheduledCalendar</class> + <header location="local">../widgets/kmymoneyscheduledcalendar.h</header> + <sizehint> + <width>-1</width> + <height>-1</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>5</hordata> + <verdata>5</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="PNG" length="824">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000002ff49444154388db59531681c4714863f992dde820cb370815b50600f54e8ca0ba43970712a8fb838438a3895634813d238a5ab80e314ae4d0a812060a4226017c27221c8a9da6b8c4fe0e00d28b0571cec82043b85611f78c12966efa4bb8bc085f29a6567df7cef9f7fdeccaec571cc2cbaddee47ae21e2385e5b9b815f1ebcfcd8de6a63ad25cb338af7c52741acb5a4a729a3d723a82ec6bd99d267bf3f23fc1c4cab2442d14a915986e792fdfa59569766573049417784f1b12e8267954dab24b78714450a28beaf941f847c2a14e70a0841035a2d45d641eb027213c210c69756320767794684d6508bef0befde1a860796e4c402333542b4256c0f0cdd1e50b97191458be6e0e27d81563a87c643d8fb2d7793d685d696413cc8a6cae46f65f7d79c7c62b87b4f2e15fd0fb0d302be0fefde4a0d557a5f35e90f84e0334014d590f855c9de4ecee17e4eb319d1ff3a00ec02f8c67299f283307c61e7d06fbf1782d082588a33e1cf1705fd81cf773f3601e1f9bec59e2f4b5c7ef5209f0ac95f16630cfd818067c103b586dd274a726229cee0fe8380d191cb4d1267d3d58aa1de7d258ceae5d7d0a78fdd269a86f0c52d414c49bbe3762c9b686de41560d7a72e41c4795a6486a78f95e4c4151481d686efbe7b3398ac58b1a23868b8c474aaa8068c8e714a8dd06c1af2a9e5d1c38c641c909dba6e08237f19b358a7ac5cf3479bc2e41f257e55d2ffc6a73833746f09e186cfa387904f2cbffc90a2aa9886d0e99464d3c5965b512cebd01f1800f67672e2a392fb0f023a3d883a053ffddcc2340dd65ab452b6074dc2cd15c1cbceb863daed413e353cdfcfd97d92333a12da6d0181ec3443cf753ef3cdd092de0e116ff1a02cdc157338ca9d7b8269461cfee1ba2139b9286e1a427f10110f2d561555b076d18a39383d4d99a4c0cd0b787f20747b214962c8266e3cdcf0e97c59126ec2f6edd089f40a92f115e0d1eb11ba238461dd6a15f32b53666de841965bb203575a3cc15a48c64a965fe57105e3635db8fa96dcffc431172b5d715d7103dc3fea7f015f373c8ee3b57f0135105a0fae7717960000000049454e44ae426082</data> + </image> +</images> +<layoutdefaults spacing="6" margin="11"/> +</UI> |