diff --git a/CMakeLists.txt b/CMakeLists.txt index d840721d1..02768e6fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,306 +1,299 @@ # The CMake version we require cmake_minimum_required(VERSION 3.1) # Setting the name of the main project project(KMyMoney VERSION "4.100.0") # Determine the GIT reference (if we're based on GIT) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") execute_process(COMMAND git rev-parse --short HEAD WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE VERSION_SUFFIX OUTPUT_STRIP_TRAILING_WHITESPACE) set(VERSION_SUFFIX "-${VERSION_SUFFIX}") # Add variables which are similar to the build in names of cmake set(PROJECT_VERSION_SUFFIX "${VERSION_SUFFIX}") set(${PROJECT_NAME}_VERSION_SUFFIX "${VERSION_SUFFIX}") endif() # Automoc all sources set(CMAKE_AUTOMOC TRUE) if (POLICY CMP0063) cmake_policy(SET CMP0063 NEW) # Policy introduced in CMake version 3.3 endif() ######################### General Requirements ########################## find_package(ECM 0.0.11 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) include(KDEInstallDirs) include(KDECMakeSettings) include(FeatureSummary) include(GenerateExportHeader) include(KMyMoneyMacros) find_package(Qt5 5.6 REQUIRED COMPONENTS Core DBus Widgets Svg Sql Xml Test PrintSupport) find_package(KF5 5.2 REQUIRED COMPONENTS Archive CoreAddons Config ConfigWidgets I18n KHtml Completion KCMUtils ItemModels ItemViews Service Wallet IconThemes XmlGui TextWidgets Notifications KIO OPTIONAL_COMPONENTS DocTools Holidays Contacts Akonadi IdentityManagement Activities Kross ) find_package(LibAlkimia 6.0.0 REQUIRED) find_package(KChart 2.6.0 REQUIRED) find_package(Gpgmepp) add_definitions(-DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING) if(NOT MSVC) # Temporary solution until all targets have compile features set add_compile_options(-std=c++11) endif() -string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) -if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debugfull") - add_definitions("-DQT_STRICT_ITERATORS") - set(KMM_DEBUG 1) -endif() - # use DBus only on Linux if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(KMM_DBUS 1) endif() set(CMAKE_INCLUDE_CURRENT_DIR ON) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # check for Doxygen find_package(Doxygen) if(DOXYGEN_FOUND) set(APIDOC_DIR ${CMAKE_CURRENT_BINARY_DIR}/apidocs) make_directory("${APIDOC_DIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kmymoney.doxygen.in ${CMAKE_CURRENT_BINARY_DIR}/kmymoney.doxygen IMMEDIATE) add_custom_target(apidoc "${DOXYGEN}" "${CMAKE_CURRENT_BINARY_DIR}/kmymoney.doxygen") endif(DOXYGEN_FOUND) # check some include files exists set(CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE=500 -D_BSD_SOURCE) include (CheckIncludeFile) check_include_file("sys/types.h" HAVE_SYS_TYPES_H) check_include_file("unistd.h" HAVE_UNISTD_H) # include check for members in structs include (CheckStructHasMember) ######################### Special Requirements ########################## # This is needed for QtSqlite and QtDesigner # (they'll install files to ${QT_INSTALL_DIR}/plugins/) get_filename_component(QT_BIN_DIR "${QT_MOC_EXECUTABLE}" PATH) get_filename_component(QT_DIR ${QT_BIN_DIR} PATH) set(QT_INSTALL_DIR ${QT_DIR} CACHE PATH "Qt install prefix defaults to the Qt prefix: ${QT_DIR}") if(KF5IdentityManagement_FOUND AND KF5Akonadi_FOUND AND KF5Contacts_FOUND) set(KMM_ADDRESSBOOK_FOUND true) endif() # check for optional OFX support set(LIBOFX_DEFAULT "AUTO") if(DEFINED ENABLE_LIBOFX) set(LIBOFX_DEFAULT ${ENABLE_LIBOFX}) endif(DEFINED ENABLE_LIBOFX) option(ENABLE_LIBOFX "Enable OFX plugin" ON) if(ENABLE_LIBOFX) find_package(LibOfx) if(NOT LIBOFX_FOUND) if(NOT LIBOFX_DEFAULT STREQUAL "AUTO") message(FATAL_ERROR "LibOFX not found") endif(NOT LIBOFX_DEFAULT STREQUAL "AUTO") set(ENABLE_LIBOFX OFF CACHE BOOL "Enable OFX plugin" FORCE) else(NOT LIBOFX_FOUND) check_struct_has_member("struct OfxFiLogin" "clientuid" "libofx/libofx.h" LIBOFX_HAVE_CLIENTUID) endif(NOT LIBOFX_FOUND) endif(ENABLE_LIBOFX) # check for optional KBanking support set(KBANKING_FOUND "AUTO") mark_as_advanced(KBANKING_FOUND) if(DEFINED ENABLE_KBANKING) set(KBANKING_FOUND OFF) endif(DEFINED ENABLE_KBANKING) option(ENABLE_KBANKING "Enable KBanking plugin" ON) if(ENABLE_KBANKING) find_package(Qt5QuickWidgets) # Includes Qt5Qml find_package(AQBANKING 5.6.5) find_package(gwenhywfar 4.15.3) find_package(gwengui-cpp) find_package(gwengui-qt5) if (AQBANKING_FOUND AND gwengui-cpp_FOUND AND gwengui-qt5_FOUND AND Qt5QuickWidgets_FOUND) set(KBANKING_FOUND ON) else() if(NOT KBANKING_FOUND STREQUAL "AUTO") message(FATAL_ERROR "KBanking requirements not met") endif(NOT KBANKING_FOUND STREQUAL "AUTO") set(KBANKING_FOUND OFF) set(ENABLE_KBANKING OFF CACHE BOOL "Enable KBanking plugin" FORCE) endif () endif(ENABLE_KBANKING) # check for optional Weboob support set(WEBOOB_FOUND "AUTO") mark_as_advanced(WEBOOB_FOUND) if(DEFINED ENABLE_WEBOOB) set(WEBOOB_FOUND OFF) endif(DEFINED ENABLE_WEBOOB) option(ENABLE_WEBOOB "Enable weboob plugin" ON) if(ENABLE_WEBOOB) if(KF5Kross_FOUND) set(WEBOOB_FOUND ON) else(KF5Kross_FOUND) if(NOT WEBOOB_FOUND STREQUAL "AUTO") message(FATAL_ERROR "Weboob requirements not met") endif(NOT WEBOOB_FOUND STREQUAL "AUTO") set(WEBOOB_FOUND OFF) set(ENABLE_WEBOOB OFF CACHE BOOL "Enable weboob plugin" FORCE) endif(KF5Kross_FOUND) endif(ENABLE_WEBOOB) # check for optional ical support set(LIBICAL_DEFAULT "AUTO") if(DEFINED ENABLE_LIBICAL) set(LIBICAL_DEFAULT ${ENABLE_LIBICAL}) endif(DEFINED ENABLE_LIBICAL) option(ENABLE_LIBICAL "Enable Calendar plugin" ON) if(ENABLE_LIBICAL) find_package(Libical) if(NOT LIBICAL_FOUND) if(NOT LIBICAL_DEFAULT STREQUAL "AUTO") message(FATAL_ERROR "LIBICAL not found") endif(NOT LIBICAL_DEFAULT STREQUAL "AUTO") set(ENABLE_LIBICAL OFF CACHE BOOL "Enable Calendar plugin" FORCE) endif(NOT LIBICAL_FOUND) endif(ENABLE_LIBICAL) # TODO: this should be removed enable_testing() ######################### Settings ########################## # If no build type is set, use "Release with Debug Info" if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE RelWithDebInfo) endif(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING "Choose the type of build. Possible values are: 'Release' 'RelWithDebInfo' 'Debug' 'Debugfull' 'Profile' The default value is: 'RelWithDebInfo'" FORCE) -# tell the KMyMoney sources that a config.h is present: -add_definitions( -DHAVE_CONFIG_H ) - # tells gcc to enable exception handling include(KDECompilerSettings) kde_enable_exceptions() if(CMAKE_SYSTEM_NAME MATCHES "Linux") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") endif() # IDEA: Set on a per target base set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(CMAKE_COMPILER_IS_GNUCXX) # be more pedantic about common symbols set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Wlogical-op") # Debug, Debugfull, Profile set(CMAKE_CXX_FLAGS_DEBUG "-g -O2 -fno-reorder-blocks -fno-schedule-insns -fno-inline") set(CMAKE_CXX_FLAGS_DEBUGFULL "-g3 -fno-inline") set(CMAKE_CXX_FLAGS_PROFILE "-g3 -fno-inline -ftest-coverage -fprofile-arcs") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") # be pedantic about undefined symbols when linking shared libraries if(CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME MATCHES "kFreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "GNU") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined -Wl,--as-needed") endif() endif() # preprocessor definitions in case this is a debug build -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DKMM_DEBUG") -set(CMAKE_CXX_FLAGS_DEBUGFULL "${CMAKE_CXX_FLAGS_DEBUGFULL} -DKMM_DEBUG") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_PROFILE} -DKMM_DEBUG") +set(CMAKE_CXX_FLAGS_DEBUGFULL "${CMAKE_CXX_FLAGS_DEBUGFULL} -DQT_STRICT_ITERATORS") option(USE_MODELTEST "Compile with ModelTest code (default=OFF)" OFF) option(USE_QT_DESIGNER "Install KMyMoney specific widget library for Qt-Designer (default=OFF)" OFF) ######################### The Actual Targets ########################## add_subdirectory( libkgpgfile ) add_subdirectory( tools ) add_subdirectory( kmymoney ) if(KF5DocTools_FOUND) add_subdirectory( doc ) endif() ######################### Output Results ############################# # create the config.h file out of the config.h.cmake +string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) +if(CMAKE_BUILD_TYPE_TOLOWER MATCHES "debugfull" OR CMAKE_BUILD_TYPE_TOLOWER MATCHES "debug") + set(KMM_DEBUG 1) +endif() configure_file("config-kmymoney.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config-kmymoney.h") configure_file("config-kmymoney-version.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config-kmymoney-version.h") # this macro maps the boolean variable ${_varname} to "yes"/"no" # and writes the output to the variable nice_${_varname} macro(nice_yesno _varname) if(${_varname}) set("nice_${_varname}" "yes") else(${_varname}) set("nice_${_varname}" "no") endif(${_varname}) endmacro() nice_yesno("KF5Holidays_FOUND") nice_yesno("Gpgmepp_FOUND") nice_yesno("KMM_ADDRESSBOOK_FOUND") nice_yesno("LIBOFX_FOUND") nice_yesno("LIBOFX_HAVE_CLIENTUID") nice_yesno("KBANKING_FOUND") nice_yesno("WEBOOB_FOUND") nice_yesno("LIBICAL_FOUND") nice_yesno("ENABLE_SQLCIPHER") nice_yesno("USE_QT_DESIGNER") nice_yesno("USE_MODELTEST") nice_yesno("DOXYGEN_FOUND") message(" -------- KMyMoney ${PROJECT_VERSION}${PROJECT_VERSION_SUFFIX} -------- Configure results (user options): -------------------------------------------- GpgME Encryption: ${nice_Gpgmepp_FOUND} KDE PIM holidays: ${nice_KF5Holidays_FOUND} KDE PIM addressbook: ${nice_KMM_ADDRESSBOOK_FOUND} OFX plugin: ${nice_LIBOFX_FOUND} OFX supports CLIENTUID: ${nice_LIBOFX_HAVE_CLIENTUID} KBanking plugin: ${nice_KBANKING_FOUND} weboob plugin: ${nice_WEBOOB_FOUND} iCalendar export plugin: ${nice_LIBICAL_FOUND} SQLCipher plugin: ${nice_ENABLE_SQLCIPHER} -------------------------------------------- Configure results (developer options): -------------------------------------------- Qt-Designer library support: ${nice_USE_QT_DESIGNER} Generate modeltest code: ${nice_USE_MODELTEST} Generate API documentation with Doxygen: ${nice_DOXYGEN_FOUND}") message(" Build type: ${CMAKE_BUILD_TYPE}") diff --git a/kmymoney/converter/mymoneygncreader.cpp b/kmymoney/converter/mymoneygncreader.cpp index 4c6d28c4d..d551aa5dd 100644 --- a/kmymoney/converter/mymoneygncreader.cpp +++ b/kmymoney/converter/mymoneygncreader.cpp @@ -1,2635 +1,2636 @@ /*************************************************************************** mymoneygncreader - description ------------------- begin : Wed Mar 3 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * 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 "mymoneygncreader.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #ifndef _GNCFILEANON #include #include #endif #include // ---------------------------------------------------------------------------- // Third party Includes // ------------------------------------------------------------Box21---------------- // Project Includes +#include "config-kmymoney.h" #ifndef _GNCFILEANON #include "storage/imymoneystorage.h" #include "kmymoneyutils.h" #include "mymoneyfile.h" #include "mymoneyprice.h" #include "kgncimportoptionsdlg.h" #include "kgncpricesourcedlg.h" #include "keditscheduledlg.h" #include "kmymoneyedit.h" #define TRY try #define CATCH catch (const MyMoneyException &) #define PASS catch (const MyMoneyException &) { throw; } #else #include "mymoneymoney.h" #include // #define i18n QObject::tr #define TRY #define CATCH #define PASS #define MYMONEYEXCEPTION QString #define MyMoneyException QString #define PACKAGE "KMyMoney" #endif // _GNCFILEANON // init static variables double MyMoneyGncReader::m_fileHideFactor = 0.0; double GncObject::m_moneyHideFactor; // user options void MyMoneyGncReader::setOptions() { #ifndef _GNCFILEANON KGncImportOptionsDlg dlg; // display the dialog to allow the user to set own options if (dlg.exec()) { // set users input options m_dropSuspectSchedules = dlg.scheduleOption(); m_investmentOption = dlg.investmentOption(); m_useFinanceQuote = dlg.quoteOption(); m_useTxNotes = dlg.txNotesOption(); m_decoder = dlg.decodeOption(); gncdebug = dlg.generalDebugOption(); xmldebug = dlg.xmlDebugOption(); bAnonymize = dlg.anonymizeOption(); } else { // user declined, so set some sensible defaults m_dropSuspectSchedules = false; // investment option - 0, create investment a/c per stock a/c, 1 = single new investment account, 2 = prompt for each stock // option 2 doesn't really work too well at present m_investmentOption = 0; m_useFinanceQuote = false; m_useTxNotes = false; m_decoder = 0; gncdebug = false; // general debug messages xmldebug = false; // xml trace bAnonymize = false; // anonymize input } // no dialog option for the following; it will set base currency, and print actual XML data developerDebug = false; // set your fave currency here to save getting that enormous dialog each time you run a test // especially if you have to scroll down to USD... if (developerDebug) m_storage->setValue("kmm-baseCurrency", "GBP"); #endif // _GNCFILEANON } GncObject::GncObject() : pMain(0), m_subElementList(0), m_subElementListCount(0), m_dataElementList(0), m_dataElementListCount(0), m_dataPtr(0), m_state(0), m_anonClassList(0), m_anonClass(0) { } // Check that the current element is of a version we are coded for void GncObject::checkVersion(const QString& elName, const QXmlAttributes& elAttrs, const map_elementVersions& map) { TRY { if (map.contains(elName)) { // if it's not in the map, there's nothing to check if (!map[elName].contains(elAttrs.value("version"))) { QString em = Q_FUNC_INFO + i18n(": Sorry. This importer cannot handle version %1 of element %2" , elAttrs.value("version"), elName); throw MYMONEYEXCEPTION(em); } } return ; } PASS } // Check if this element is in the current object's sub element list GncObject *GncObject::isSubElement(const QString& elName, const QXmlAttributes& elAttrs) { TRY { uint i; GncObject *next = 0; for (i = 0; i < m_subElementListCount; i++) { if (elName == m_subElementList[i]) { m_state = i; next = startSubEl(); // go create the sub object if (next != 0) { next->initiate(elName, elAttrs); // initialize it next->m_elementName = elName; // save it's name so we can identify the end } break; } } return (next); } PASS } // Check if this element is in the current object's data element list bool GncObject::isDataElement(const QString &elName, const QXmlAttributes& elAttrs) { TRY { uint i; for (i = 0; i < m_dataElementListCount; i++) { if (elName == m_dataElementList[i]) { m_state = i; dataEl(elAttrs); // go set the pointer so the data can be stored return (true); } } m_dataPtr = 0; // we don't need this, so make sure we don't store extraneous data return (false); } PASS } // return the variable string, decoded if required QString GncObject::var(int i) const { /* This code was needed because the Qt3 XML reader apparently did not process the encoding parameter in the m_decoder == 0 ? m_v[i] : pMain->m_decoder->toUnicode(m_v[i].toUtf8())); } const QString GncObject::getKvpValue(const QString& key, const QString& type) const { QList::const_iterator it; // first check for exact match for (it = m_kvpList.begin(); it != m_kvpList.end(); ++it) { if (((*it).key() == key) && ((type.isEmpty()) || ((*it).type() == type))) return (*it).value(); } // then for partial match for (it = m_kvpList.begin(); it != m_kvpList.end(); ++it) { if (((*it).key().contains(key)) && ((type.isEmpty()) || ((*it).type() == type))) return (*it).value(); } return (QString()); } void GncObject::adjustHideFactor() { m_moneyHideFactor = pMain->m_fileHideFactor * (1.0 + (int)(200.0 * rand() / (RAND_MAX + 1.0))) / 100.0; } // data anonymizer QString GncObject::hide(QString data, unsigned int anonClass) { TRY { if (!pMain->bAnonymize) return (data); // no anonymizing required // counters used to generate names for anonymizer static int nextAccount; static int nextEquity; static int nextPayee; static int nextSched; static QMap anonPayees; // to check for duplicate payee names static QMap anonStocks; // for reference to equities QString result(data); QMap::const_iterator it; MyMoneyMoney in, mresult; switch (anonClass) { case ASIS: // this is not personal data break; case SUPPRESS: // this is personal and is not essential result = ""; break; case NXTACC: // generate account name result = ki18n("Account%1").subs(++nextAccount, -6).toString(); break; case NXTEQU: // generate/return an equity name it = anonStocks.constFind(data); if (it == anonStocks.constEnd()) { result = ki18n("Stock%1").subs(++nextEquity, -6).toString(); anonStocks.insert(data, result); } else { result = (*it); } break; case NXTPAY: // generate/return a payee name it = anonPayees.constFind(data); if (it == anonPayees.constEnd()) { result = ki18n("Payee%1").subs(++nextPayee, -6).toString(); anonPayees.insert(data, result); } else { result = (*it); } break; case NXTSCHD: // generate a schedule name result = ki18n("Schedule%1").subs(++nextSched, -6).toString(); break; case MONEY1: in = MyMoneyMoney(data); if (data == "-1/0") in = MyMoneyMoney(); // spurious gnucash data - causes a crash sometimes mresult = MyMoneyMoney(m_moneyHideFactor) * in; mresult.convert(10000); result = mresult.toString(); break; case MONEY2: in = MyMoneyMoney(data); if (data == "-1/0") in = MyMoneyMoney(); mresult = MyMoneyMoney(m_moneyHideFactor) * in; mresult.convert(10000); mresult.setThousandSeparator(' '); result = mresult.formatMoney("", 2); break; } return (result); } PASS } // dump current object data values // only called if gncdebug set void GncObject::debugDump() { uint i; qDebug() << "Object" << m_elementName; for (i = 0; i < m_dataElementListCount; i++) { qDebug() << m_dataElementList[i] << "=" << m_v[i]; } } //***************************************************************** GncFile::GncFile() { static const QString subEls[] = {"gnc:book", "gnc:count-data", "gnc:commodity", "price", "gnc:account", "gnc:transaction", "gnc:template-transactions", "gnc:schedxaction" }; m_subElementList = subEls; m_subElementListCount = END_FILE_SELS; m_dataElementListCount = 0; m_processingTemplates = false; m_bookFound = false; } GncFile::~GncFile() {} GncObject *GncFile::startSubEl() { TRY { if (pMain->xmldebug) qDebug("File start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case BOOK: if (m_bookFound) throw MYMONEYEXCEPTION(i18n("This version of the importer cannot handle multi-book files.")); m_bookFound = true; break; case COUNT: next = new GncCountData; break; case CMDTY: next = new GncCommodity; break; case PRICE: next = new GncPrice; break; case ACCT: // accounts within the template section are ignored if (!m_processingTemplates) next = new GncAccount; break; case TX: next = new GncTransaction(m_processingTemplates); break; case TEMPLATES: m_processingTemplates = true; break; case SCHEDULES: m_processingTemplates = false; next = new GncSchedule; break; default: throw MYMONEYEXCEPTION("GncFile rcvd invalid state"); } return (next); } PASS } void GncFile::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("File end subel"); if (!m_processingTemplates) delete subObj; // template txs must be saved awaiting schedules m_dataPtr = 0; return ; } //****************************************** GncDate ********************************************* GncDate::GncDate() { m_subElementListCount = 0; static const QString dEls[] = {"ts:date", "gdate"}; m_dataElementList = dEls; m_dataElementListCount = END_Date_DELS; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncDate::~GncDate() {} //*************************************GncCmdtySpec*************************************** GncCmdtySpec::GncCmdtySpec() { m_subElementListCount = 0; static const QString dEls[] = {"cmdty:space", "cmdty:id"}; m_dataElementList = dEls; m_dataElementListCount = END_CmdtySpec_DELS; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncCmdtySpec::~GncCmdtySpec() {} QString GncCmdtySpec::hide(QString data, unsigned int) { // hide equity names, but not currency names unsigned int newClass = ASIS; switch (m_state) { case CMDTYID: if (!isCurrency()) newClass = NXTEQU; } return (GncObject::hide(data, newClass)); } //************* GncKvp******************************************** GncKvp::GncKvp() { m_subElementListCount = END_Kvp_SELS; static const QString subEls[] = {"slot"}; // kvp's may be nested m_subElementList = subEls; m_dataElementListCount = END_Kvp_DELS; static const QString dataEls[] = {"slot:key", "slot:value"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncKvp::~GncKvp() {} void GncKvp::dataEl(const QXmlAttributes& elAttrs) { switch (m_state) { case VALUE: m_kvpType = elAttrs.value("type"); } m_dataPtr = &(m_v[m_state]); if (key().contains("formula")) { m_anonClass = MONEY2; } else { m_anonClass = ASIS; } return ; } GncObject *GncKvp::startSubEl() { if (pMain->xmldebug) qDebug("Kvp start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncKvp rcvd invalid m_state "); } return (next); } PASS } void GncKvp::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Kvp end subel"); m_kvpList.append(*(static_cast (subObj))); m_dataPtr = 0; return ; } //*********************************GncLot********************************************* GncLot::GncLot() { m_subElementListCount = 0; m_dataElementListCount = 0; } GncLot::~GncLot() {} //*********************************GncCountData*************************************** GncCountData::GncCountData() { m_subElementListCount = 0; m_dataElementListCount = 0; m_v.append(QString()); // only 1 data item } GncCountData::~GncCountData() {} void GncCountData::initiate(const QString&, const QXmlAttributes& elAttrs) { m_countType = elAttrs.value("cd:type"); m_dataPtr = &(m_v[0]); return ; } void GncCountData::terminate() { int i = m_v[0].toInt(); if (m_countType == "commodity") { pMain->setGncCommodityCount(i); return ; } if (m_countType == "account") { pMain->setGncAccountCount(i); return ; } if (m_countType == "transaction") { pMain->setGncTransactionCount(i); return ; } if (m_countType == "schedxaction") { pMain->setGncScheduleCount(i); return ; } if (i != 0) { if (m_countType == "budget") pMain->setBudgetsFound(true); else if (m_countType.left(7) == "gnc:Gnc") pMain->setSmallBusinessFound(true); else if (pMain->xmldebug) qDebug() << "Unknown count type" << m_countType; } return ; } //*********************************GncCommodity*************************************** GncCommodity::GncCommodity() { m_subElementListCount = 0; static const QString dEls[] = {"cmdty:space", "cmdty:id", "cmdty:name", "cmdty:fraction"}; m_dataElementList = dEls; m_dataElementListCount = END_Commodity_DELS; static const unsigned int anonClasses[] = {ASIS, NXTEQU, SUPPRESS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncCommodity::~GncCommodity() {} void GncCommodity::terminate() { TRY { pMain->convertCommodity(this); return ; } PASS } //************* GncPrice******************************************** GncPrice::GncPrice() { static const QString subEls[] = {"price:commodity", "price:currency", "price:time"}; m_subElementList = subEls; m_subElementListCount = END_Price_SELS; m_dataElementListCount = END_Price_DELS; static const QString dataEls[] = {"price:value"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCommodity = 0; m_vpCurrency = 0; m_vpPriceDate = 0; } GncPrice::~GncPrice() { delete m_vpCommodity; delete m_vpCurrency; delete m_vpPriceDate; } GncObject *GncPrice::startSubEl() { TRY { GncObject *next = 0; switch (m_state) { case CMDTY: next = new GncCmdtySpec; break; case CURR: next = new GncCmdtySpec; break; case PRICEDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncPrice rcvd invalid m_state"); } return (next); } PASS } void GncPrice::endSubEl(GncObject *subObj) { TRY { switch (m_state) { case CMDTY: m_vpCommodity = static_cast(subObj); break; case CURR: m_vpCurrency = static_cast(subObj); break; case PRICEDATE: m_vpPriceDate = static_cast(subObj); break; default: throw MYMONEYEXCEPTION("GncPrice rcvd invalid m_state"); } return; } PASS } void GncPrice::terminate() { TRY { pMain->convertPrice(this); return ; } PASS } //************* GncAccount******************************************** GncAccount::GncAccount() { m_subElementListCount = END_Account_SELS; static const QString subEls[] = {"act:commodity", "slot", "act:lots"}; m_subElementList = subEls; m_dataElementListCount = END_Account_DELS; static const QString dataEls[] = {"act:id", "act:name", "act:description", "act:type", "act:parent" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, NXTACC, SUPPRESS, ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCommodity = 0; } GncAccount::~GncAccount() { delete m_vpCommodity; } GncObject *GncAccount::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Account start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case CMDTY: next = new GncCmdtySpec; break; case KVP: next = new GncKvp; break; case LOTS: next = new GncLot(); pMain->setLotsFound(true); // we don't handle lots; just set flag to report break; default: throw MYMONEYEXCEPTION("GncAccount rcvd invalid m_state"); } return (next); } PASS } void GncAccount::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Account end subel"); switch (m_state) { case CMDTY: m_vpCommodity = static_cast(subObj); break; case KVP: m_kvpList.append(*(static_cast (subObj))); } return ; } void GncAccount::terminate() { TRY { pMain->convertAccount(this); return ; } PASS } //************* GncTransaction******************************************** GncTransaction::GncTransaction(bool processingTemplates) { m_subElementListCount = END_Transaction_SELS; static const QString subEls[] = {"trn:currency", "trn:date-posted", "trn:date-entered", "trn:split", "slot" }; m_subElementList = subEls; m_dataElementListCount = END_Transaction_DELS; static const QString dataEls[] = {"trn:id", "trn:num", "trn:description"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, NXTPAY}; m_anonClassList = anonClasses; adjustHideFactor(); m_template = processingTemplates; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpCurrency = 0; m_vpDateEntered = m_vpDatePosted = 0; } GncTransaction::~GncTransaction() { delete m_vpCurrency; delete m_vpDatePosted; delete m_vpDateEntered; } GncObject *GncTransaction::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Transaction start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case CURRCY: next = new GncCmdtySpec; break; case POSTED: case ENTERED: next = new GncDate; break; case SPLIT: if (isTemplate()) { next = new GncTemplateSplit; } else { next = new GncSplit; } break; case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncTransaction rcvd invalid m_state"); } return (next); } PASS } void GncTransaction::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Transaction end subel"); switch (m_state) { case CURRCY: m_vpCurrency = static_cast(subObj); break; case POSTED: m_vpDatePosted = static_cast(subObj); break; case ENTERED: m_vpDateEntered = static_cast(subObj); break; case SPLIT: m_splitList.append(subObj); break; case KVP: m_kvpList.append(*(static_cast (subObj))); } return ; } void GncTransaction::terminate() { TRY { if (isTemplate()) { pMain->saveTemplateTransaction(this); } else { pMain->convertTransaction(this); } return ; } PASS } //************* GncSplit******************************************** GncSplit::GncSplit() { m_subElementListCount = END_Split_SELS; static const QString subEls[] = {"split:reconcile-date"}; m_subElementList = subEls; m_dataElementListCount = END_Split_DELS; static const QString dataEls[] = {"split:id", "split:memo", "split:reconciled-state", "split:value", "split:quantity", "split:account" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, ASIS, MONEY1, MONEY1, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpDateReconciled = 0; } GncSplit::~GncSplit() { delete m_vpDateReconciled; } GncObject *GncSplit::startSubEl() { TRY { GncObject *next = 0; switch (m_state) { case RECDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncTemplateSplit rcvd invalid m_state "); } return (next); } PASS } void GncSplit::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Split end subel"); switch (m_state) { case RECDATE: m_vpDateReconciled = static_cast(subObj); break; } return ; } //************* GncTemplateSplit******************************************** GncTemplateSplit::GncTemplateSplit() { m_subElementListCount = END_TemplateSplit_SELS; static const QString subEls[] = {"slot"}; m_subElementList = subEls; m_dataElementListCount = END_TemplateSplit_DELS; static const QString dataEls[] = {"split:id", "split:memo", "split:reconciled-state", "split:value", "split:quantity", "split:account" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, SUPPRESS, ASIS, MONEY1, MONEY1, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncTemplateSplit::~GncTemplateSplit() {} GncObject *GncTemplateSplit::startSubEl() { if (pMain->xmldebug) qDebug("TemplateSplit start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case KVP: next = new GncKvp; break; default: throw MYMONEYEXCEPTION("GncTemplateSplit rcvd invalid m_state"); } return (next); } PASS } void GncTemplateSplit::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("TemplateSplit end subel"); m_kvpList.append(*(static_cast (subObj))); m_dataPtr = 0; return ; } //************* GncSchedule******************************************** GncSchedule::GncSchedule() { m_subElementListCount = END_Schedule_SELS; static const QString subEls[] = {"sx:start", "sx:last", "sx:end", "gnc:freqspec", "gnc:recurrence", "sx:deferredInstance"}; m_subElementList = subEls; m_dataElementListCount = END_Schedule_DELS; static const QString dataEls[] = {"sx:name", "sx:enabled", "sx:autoCreate", "sx:autoCreateNotify", "sx:autoCreateDays", "sx:advanceCreateDays", "sx:advanceRemindDays", "sx:instanceCount", "sx:num-occur", "sx:rem-occur", "sx:templ-acct" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {NXTSCHD, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); m_vpStartDate = m_vpLastDate = m_vpEndDate = 0; m_vpFreqSpec = 0; m_vpRecurrence.clear(); m_vpSchedDef = 0; } GncSchedule::~GncSchedule() { delete m_vpStartDate; delete m_vpLastDate; delete m_vpEndDate; delete m_vpFreqSpec; delete m_vpSchedDef; } GncObject *GncSchedule::startSubEl() { if (pMain->xmldebug) qDebug("Schedule start subel m_state %d", m_state); TRY { GncObject *next = 0; switch (m_state) { case STARTDATE: case LASTDATE: case ENDDATE: next = new GncDate; break; case FREQ: next = new GncFreqSpec; break; case RECURRENCE: next = new GncRecurrence; break; case DEFINST: next = new GncSchedDef; break; default: throw MYMONEYEXCEPTION("GncSchedule rcvd invalid m_state"); } return (next); } PASS } void GncSchedule::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Schedule end subel"); switch (m_state) { case STARTDATE: m_vpStartDate = static_cast(subObj); break; case LASTDATE: m_vpLastDate = static_cast(subObj); break; case ENDDATE: m_vpEndDate = static_cast(subObj); break; case FREQ: m_vpFreqSpec = static_cast(subObj); break; case RECURRENCE: m_vpRecurrence.append(static_cast(subObj)); break; case DEFINST: m_vpSchedDef = static_cast(subObj); break; } return ; } void GncSchedule::terminate() { TRY { pMain->convertSchedule(this); return ; } PASS } //************* GncFreqSpec******************************************** GncFreqSpec::GncFreqSpec() { m_subElementListCount = END_FreqSpec_SELS; static const QString subEls[] = {"gnc:freqspec"}; m_subElementList = subEls; m_dataElementListCount = END_FreqSpec_DELS; static const QString dataEls[] = {"fs:ui_type", "fs:monthly", "fs:daily", "fs:weekly", "fs:interval", "fs:offset", "fs:day" }; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS, ASIS, ASIS, ASIS, ASIS, ASIS }; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncFreqSpec::~GncFreqSpec() {} GncObject *GncFreqSpec::startSubEl() { TRY { if (pMain->xmldebug) qDebug("FreqSpec start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case COMPO: next = new GncFreqSpec; break; default: throw MYMONEYEXCEPTION("GncFreqSpec rcvd invalid m_state"); } return (next); } PASS } void GncFreqSpec::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("FreqSpec end subel"); switch (m_state) { case COMPO: m_fsList.append(subObj); break; } m_dataPtr = 0; return ; } void GncFreqSpec::terminate() { pMain->convertFreqSpec(this); return ; } //************* GncRecurrence******************************************** GncRecurrence::GncRecurrence() : m_vpStartDate(0) { m_subElementListCount = END_Recurrence_SELS; static const QString subEls[] = {"recurrence:start"}; m_subElementList = subEls; m_dataElementListCount = END_Recurrence_DELS; static const QString dataEls[] = {"recurrence:mult", "recurrence:period_type"}; m_dataElementList = dataEls; static const unsigned int anonClasses[] = {ASIS, ASIS}; m_anonClassList = anonClasses; for (uint i = 0; i < m_dataElementListCount; i++) m_v.append(QString()); } GncRecurrence::~GncRecurrence() { delete m_vpStartDate; } GncObject *GncRecurrence::startSubEl() { TRY { if (pMain->xmldebug) qDebug("Recurrence start subel m_state %d", m_state); GncObject *next = 0; switch (m_state) { case STARTDATE: next = new GncDate; break; default: throw MYMONEYEXCEPTION("GncRecurrence rcvd invalid m_state"); } return (next); } PASS } void GncRecurrence::endSubEl(GncObject *subObj) { if (pMain->xmldebug) qDebug("Recurrence end subel"); switch (m_state) { case STARTDATE: m_vpStartDate = static_cast(subObj); break; } m_dataPtr = 0; return ; } void GncRecurrence::terminate() { pMain->convertRecurrence(this); return ; } QString GncRecurrence::getFrequency() const { // This function converts a gnucash 2.2 recurrence specification into it's previous equivalent // This will all need re-writing when MTE finishes the schedule re-write if (periodType() == "once") return("once"); if ((periodType() == "day") && (mult() == "1")) return("daily"); if (periodType() == "week") { if (mult() == "1") return ("weekly"); if (mult() == "2") return ("bi_weekly"); if (mult() == "4") return ("four-weekly"); } if (periodType() == "month") { if (mult() == "1") return ("monthly"); if (mult() == "2") return ("two-monthly"); if (mult() == "3") return ("quarterly"); if (mult() == "4") return ("tri_annually"); if (mult() == "6") return ("semi_yearly"); if (mult() == "12") return ("yearly"); if (mult() == "24") return ("two-yearly"); } return ("unknown"); } //************* GncSchedDef******************************************** GncSchedDef::GncSchedDef() { // process ing for this sub-object is undefined at the present time m_subElementListCount = 0; m_dataElementListCount = 0; } GncSchedDef::~GncSchedDef() {} /************************************************************************************************ XML Reader ************************************************************************************************/ XmlReader::XmlReader(MyMoneyGncReader *pM) : m_source(0), m_reader(0), m_co(0), pMain(pM), m_headerFound(false) { } void XmlReader::processFile(QIODevice* pDevice) { m_source = new QXmlInputSource(pDevice); // set up the Qt XML reader m_reader = new QXmlSimpleReader; m_reader->setContentHandler(this); // go read the file if (!m_reader->parse(m_source)) { throw MYMONEYEXCEPTION(i18n("Input file cannot be parsed; may be corrupt\n%1", errorString())); } delete m_reader; delete m_source; return ; } // XML handling routines bool XmlReader::startDocument() { m_co = new GncFile; // create initial object, push to stack , pass it the 'main' pointer m_os.push(m_co); m_co->setPm(pMain); m_headerFound = false; #ifdef _GNCFILEANON pMain->oStream << ""; lastType = -1; indentCount = 0; #endif // _GNCFILEANON return (true); } bool XmlReader::startElement(const QString&, const QString&, const QString& elName , const QXmlAttributes& elAttrs) { try { if (pMain->gncdebug) qDebug() << "XML start -" << elName; #ifdef _GNCFILEANON int i; QString spaces; // anonymizer - write data if (elName == "gnc:book" || elName == "gnc:count-data" || elName == "book:id") lastType = -1; pMain->oStream << endl; switch (lastType) { case 0: indentCount += 2; // tricky fall through here case 2: spaces.fill(' ', indentCount); pMain->oStream << spaces.toLatin1(); break; } pMain->oStream << '<' << elName; for (i = 0; i < elAttrs.count(); ++i) { pMain->oStream << ' ' << elAttrs.qName(i) << '=' << '"' << elAttrs.value(i) << '"'; } pMain->oStream << '>'; lastType = 0; #else if ((!m_headerFound) && (elName != "gnc-v2")) throw MYMONEYEXCEPTION(i18n("Invalid header for file. Should be 'gnc-v2'")); m_headerFound = true; #endif // _GNCFILEANON m_co->checkVersion(elName, elAttrs, pMain->m_versionList); // check if this is a sub object element; if so, push stack and initialize GncObject *temp = m_co->isSubElement(elName, elAttrs); if (temp != 0) { m_os.push(temp); m_co = m_os.top(); m_co->setVersion(elAttrs.value("version")); m_co->setPm(pMain); // pass the 'main' pointer to the sub object // return true; // removed, as we hit a return true anyway } #if 0 // check for a data element if (m_co->isDataElement(elName, elAttrs)) return (true); #endif else { // reduced the above to m_co->isDataElement(elName, elAttrs); } } catch (const MyMoneyException &e) { #ifndef _GNCFILEANON // we can't pass on exceptions here coz the XML reader won't catch them and we just abort KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); #else qWarning("%s", e->toLatin1()); #endif // _GNCFILEANON } return true; // to keep compiler happy } bool XmlReader::endElement(const QString&, const QString&, const QString&elName) { try { if (pMain->xmldebug) qDebug() << "XML end -" << elName; #ifdef _GNCFILEANON QString spaces; switch (lastType) { case 2: indentCount -= 2; spaces.fill(' ', indentCount); pMain->oStream << endl << spaces.toLatin1(); break; } pMain->oStream << "' ; lastType = 2; #endif // _GNCFILEANON m_co->resetDataPtr(); // so we don't get extraneous data loaded into the variables if (elName == m_co->getElName()) { // check if this is the end of the current object if (pMain->gncdebug) m_co->debugDump(); // dump the object data (temp) // call the terminate routine, pop the stack, and advise the parent that it's done m_co->terminate(); GncObject *temp = m_co; m_os.pop(); m_co = m_os.top(); m_co->endSubEl(temp); } return (true); } catch (const MyMoneyException &e) { #ifndef _GNCFILEANON // we can't pass on exceptions here coz the XML reader won't catch them and we just abort KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); #else qWarning("%s", e->toLatin1()); #endif // _GNCFILEANON } return (true); // to keep compiler happy } bool XmlReader::characters(const QString &data) { if (pMain->xmldebug) qDebug("XML Data received - %d bytes", data.length()); QString pData = data.trimmed(); // data may contain line feeds and indentation spaces if (!pData.isEmpty()) { if (pMain->developerDebug) qDebug() << "XML Data -" << pData; m_co->storeData(pData); //go store it #ifdef _GNCFILEANON QString anonData = m_co->getData(); if (anonData.isEmpty()) anonData = pData; // there must be a Qt standard way of doing the following but I can't ... find it anonData.replace('<', "<"); anonData.replace('>', ">"); anonData.replace('&', "&"); pMain->oStream << anonData; // write original data lastType = 1; #endif // _GNCFILEANON } return (true); } bool XmlReader::endDocument() { #ifdef _GNCFILEANON pMain->oStream << endl << endl; pMain->oStream << "" << endl; pMain->oStream << "" << endl; pMain->oStream << "" << endl; #endif // _GNCFILEANON return (true); } /******************************************************************************************* Main class for this module Controls overall operation of the importer ********************************************************************************************/ //***************** Constructor *********************** MyMoneyGncReader::MyMoneyGncReader() : m_dropSuspectSchedules(0), m_investmentOption(0), m_useFinanceQuote(0), m_useTxNotes(0), gncdebug(0), xmldebug(0), bAnonymize(0), developerDebug(0), m_xr(0), m_progressCallback(0), m_ccCount(0), m_orCount(0), m_scCount(0), m_potentialTransfer(0), m_suspectSchedule(false) { #ifndef _GNCFILEANON m_storage = 0; #endif // _GNCFILEANON // to hold gnucash count data (only used for progress bar) m_gncCommodityCount = m_gncAccountCount = m_gncTransactionCount = m_gncScheduleCount = 0; m_smallBusinessFound = m_budgetsFound = m_lotsFound = false; m_commodityCount = m_priceCount = m_accountCount = m_transactionCount = m_templateCount = m_scheduleCount = 0; m_decoder = 0; // build a list of valid versions static const QString versionList[] = {"gnc:book 2.0.0", "gnc:commodity 2.0.0", "gnc:pricedb 1", "gnc:account 2.0.0", "gnc:transaction 2.0.0", "gnc:schedxaction 1.0.0", "gnc:schedxaction 2.0.0", // for gnucash 2.2 onward "gnc:freqspec 1.0.0", "zzz" // zzz = stopper }; unsigned int i; for (i = 0; versionList[i] != "zzz"; ++i) m_versionList[versionList[i].section(' ', 0, 0)].append(versionList[i].section(' ', 1, 1)); } //***************** Destructor ************************* MyMoneyGncReader::~MyMoneyGncReader() {} //**************************** Main Entry Point ************************************ #ifndef _GNCFILEANON void MyMoneyGncReader::readFile(QIODevice* pDevice, IMyMoneySerialize* storage) { Q_CHECK_PTR(pDevice); Q_CHECK_PTR(storage); m_storage = dynamic_cast(storage); qDebug("Entering gnucash importer"); setOptions(); // get a file anonymization factor from the user if (bAnonymize) setFileHideFactor(); //m_defaultPayee = createPayee (i18n("Unknown payee")); MyMoneyFile::instance()->attachStorage(m_storage); MyMoneyFileTransaction ft; m_xr = new XmlReader(this); bool blocked = MyMoneyFile::instance()->signalsBlocked(); MyMoneyFile::instance()->blockSignals(true); try { m_xr->processFile(pDevice); terminate(); // do all the wind-up things ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Import failed:\n\n%1", e.what()), PACKAGE); qWarning("%s", qPrintable(e.what())); } // end catch MyMoneyFile::instance()->blockSignals(blocked); MyMoneyFile::instance()->detachStorage(m_storage); signalProgress(0, 1, i18n("Import complete")); // switch off progress bar delete m_xr; qDebug("Exiting gnucash importer"); } #else // Control code for the file anonymizer void MyMoneyGncReader::readFile(QString in, QString out) { QFile pDevice(in); if (!pDevice.open(QIODevice::ReadOnly)) qWarning("Can't open input file"); QFile outFile(out); if (!outFile.open(QIODevice::WriteOnly)) qWarning("Can't open output file"); oStream.setDevice(&outFile); bAnonymize = true; // get a file anonymization factor from the user setFileHideFactor(); m_xr = new XmlReader(this); try { m_xr->processFile(&pDevice); } catch (const MyMoneyException &e) { qWarning("%s", e->toLatin1()); } // end catch delete m_xr; pDevice.close(); outFile.close(); return ; } #include int main(int argc, char ** argv) { QApplication a(argc, argv); MyMoneyGncReader m; QString inFile, outFile; if (argc > 0) inFile = a.argv()[1]; if (argc > 1) outFile = a.argv()[2]; if (inFile.isEmpty()) { inFile = KFileDialog::getOpenFileName("", "Gnucash files(*.nc *)", 0); } if (inFile.isEmpty()) qWarning("Input file required"); if (outFile.isEmpty()) outFile = inFile + ".anon"; m.readFile(inFile, outFile); exit(0); } #endif // _GNCFILEANON void MyMoneyGncReader::setFileHideFactor() { #define MINFILEHIDEF 0.01 #define MAXFILEHIDEF 99.99 srand(QTime::currentTime().second()); // seed randomizer for anonymize m_fileHideFactor = 0.0; while (m_fileHideFactor == 0.0) { m_fileHideFactor = QInputDialog::getDouble(0, i18n("Disguise your wealth"), i18n("Each monetary value on your file will be multiplied by a random number between 0.01 and 1.99\n" "with a different value used for each transaction. In addition, to further disguise the true\n" "values, you may enter a number between %1 and %2 which will be applied to all values.\n" "These numbers will not be stored in the file.", MINFILEHIDEF, MAXFILEHIDEF), (1.0 + (int)(1000.0 * rand() / (RAND_MAX + 1.0))) / 100.0, MINFILEHIDEF, MAXFILEHIDEF, 2); } } #ifndef _GNCFILEANON //********************************* convertCommodity ******************************************* void MyMoneyGncReader::convertCommodity(const GncCommodity *gcm) { Q_CHECK_PTR(gcm); MyMoneySecurity equ; if (m_commodityCount == 0) signalProgress(0, m_gncCommodityCount, i18n("Loading commodities...")); if (!gcm->isCurrency()) { // currencies should not be present here but... equ.setName(gcm->name()); equ.setTradingSymbol(gcm->id()); equ.setTradingMarket(gcm->space()); // the 'space' may be market or quote source, dep on what the user did // don't set the source here since he may not want quotes //equ.setValue ("kmm-online-source", gcm->space()); // we don't know, so use it as both equ.setTradingCurrency(""); // not available here, will set from pricedb or transaction equ.setSecurityType(MyMoneySecurity::SECURITY_STOCK); // default to it being a stock //tell the storage objects we have a new equity object. equ.setSmallestAccountFraction(gcm->fraction().toInt()); m_storage->addSecurity(equ); //assign the gnucash id as the key into the map to find our id if (gncdebug) qDebug() << "mapping, key =" << gcm->id() << "id =" << equ.id(); m_mapEquities[gcm->id().toUtf8()] = equ.id(); } signalProgress(++m_commodityCount, 0); return ; } //******************************* convertPrice ************************************************ void MyMoneyGncReader::convertPrice(const GncPrice *gpr) { Q_CHECK_PTR(gpr); // add this to our price history if (m_priceCount == 0) signalProgress(0, 1, i18n("Loading prices...")); MyMoneyMoney rate(convBadValue(gpr->value())); if (gpr->commodity()->isCurrency()) { MyMoneyPrice exchangeRate(gpr->commodity()->id().toUtf8(), gpr->currency()->id().toUtf8(), gpr->priceDate(), rate, i18n("Imported History")); if (!exchangeRate.rate(QString()).isZero()) m_storage->addPrice(exchangeRate); } else { MyMoneySecurity e = m_storage->security(m_mapEquities[gpr->commodity()->id().toUtf8()]); if (gncdebug) qDebug() << "Searching map, key = " << gpr->commodity()->id() << ", found id =" << e.id().data(); e.setTradingCurrency(gpr->currency()->id().toUtf8()); MyMoneyPrice stockPrice(e.id(), gpr->currency()->id().toUtf8(), gpr->priceDate(), rate, i18n("Imported History")); if (!stockPrice.rate(QString()).isZero()) m_storage->addPrice(stockPrice); m_storage->modifySecurity(e); } signalProgress(++m_priceCount, 0); return ; } //*********************************convertAccount **************************************** void MyMoneyGncReader::convertAccount(const GncAccount* gac) { Q_CHECK_PTR(gac); TRY { // we don't care about the GNC root account if ("ROOT" == gac->type()) { m_rootId = gac->id().toUtf8(); return; } MyMoneyAccount acc; if (m_accountCount == 0) signalProgress(0, m_gncAccountCount, i18n("Loading accounts...")); acc.setName(gac->name()); acc.setDescription(gac->desc()); QDate currentDate = QDate::currentDate(); acc.setOpeningDate(currentDate); acc.setLastModified(currentDate); acc.setLastReconciliationDate(currentDate); if (gac->commodity()->isCurrency()) { acc.setCurrencyId(gac->commodity()->id().toUtf8()); m_currencyCount[gac->commodity()->id()]++; } acc.setParentAccountId(gac->parent().toUtf8()); // now determine the account type and its parent id /* This list taken from # Feb 2006: A RELAX NG Compact schema for gnucash "v2" XML files. # Copyright (C) 2006 Joshua Sled "NO_TYPE" "BANK" "CASH" "CREDIT" "ASSET" "LIABILITY" "STOCK" "MUTUAL" "CURRENCY" "INCOME" "EXPENSE" "EQUITY" "RECEIVABLE" "PAYABLE" "CHECKING" "SAVINGS" "MONEYMRKT" "CREDITLINE" Some don't seem to be used in practice. Not sure what CREDITLINE s/be converted as. */ if ("BANK" == gac->type() || "CHECKING" == gac->type()) { acc.setAccountType(MyMoneyAccount::Checkings); } else if ("SAVINGS" == gac->type()) { acc.setAccountType(MyMoneyAccount::Savings); } else if ("ASSET" == gac->type()) { acc.setAccountType(MyMoneyAccount::Asset); } else if ("CASH" == gac->type()) { acc.setAccountType(MyMoneyAccount::Cash); } else if ("CURRENCY" == gac->type()) { acc.setAccountType(MyMoneyAccount::Cash); } else if ("STOCK" == gac->type() || "MUTUAL" == gac->type()) { // gnucash allows a 'broker' account to be denominated as type STOCK, but with // a currency balance. We do not need to create a stock account for this // actually, the latest version of gnc (1.8.8) doesn't seem to allow you to do // this any more, though I do have one in my own account... if (gac->commodity()->isCurrency()) { acc.setAccountType(MyMoneyAccount::Investment); } else { acc.setAccountType(MyMoneyAccount::Stock); } } else if ("EQUITY" == gac->type()) { acc.setAccountType(MyMoneyAccount::Equity); } else if ("LIABILITY" == gac->type()) { acc.setAccountType(MyMoneyAccount::Liability); } else if ("CREDIT" == gac->type()) { acc.setAccountType(MyMoneyAccount::CreditCard); } else if ("INCOME" == gac->type()) { acc.setAccountType(MyMoneyAccount::Income); } else if ("EXPENSE" == gac->type()) { acc.setAccountType(MyMoneyAccount::Expense); } else if ("RECEIVABLE" == gac->type()) { acc.setAccountType(MyMoneyAccount::Asset); } else if ("PAYABLE" == gac->type()) { acc.setAccountType(MyMoneyAccount::Liability); } else if ("MONEYMRKT" == gac->type()) { acc.setAccountType(MyMoneyAccount::MoneyMarket); } else { // we have here an account type we can't currently handle QString em = i18n("Current importer does not recognize GnuCash account type %1", gac->type()); throw MYMONEYEXCEPTION(em); } // if no parent account is present, assign to one of our standard accounts if ((acc.parentAccountId().isEmpty()) || (acc.parentAccountId() == m_rootId)) { switch (acc.accountGroup()) { case MyMoneyAccount::Asset: acc.setParentAccountId(m_storage->asset().id()); break; case MyMoneyAccount::Liability: acc.setParentAccountId(m_storage->liability().id()); break; case MyMoneyAccount::Income: acc.setParentAccountId(m_storage->income().id()); break; case MyMoneyAccount::Expense: acc.setParentAccountId(m_storage->expense().id()); break; case MyMoneyAccount::Equity: acc.setParentAccountId(m_storage->equity().id()); break; default: break; // not necessary but avoids compiler warnings } } // extra processing for a stock account if (acc.accountType() == MyMoneyAccount::Stock) { // save the id for later linking to investment account m_stockList.append(gac->id()); // set the equity type MyMoneySecurity e = m_storage->security(m_mapEquities[gac->commodity()->id().toUtf8()]); if (gncdebug) qDebug() << "Acct equity search, key =" << gac->commodity()->id() << "found id =" << e.id(); acc.setCurrencyId(e.id()); // actually, the security id if ("MUTUAL" == gac->type()) { e.setSecurityType(MyMoneySecurity::SECURITY_MUTUALFUND); if (gncdebug) qDebug() << "Setting" << e.name() << "to mutual"; m_storage->modifySecurity(e); } QString priceSource = gac->getKvpValue("price-source", "string"); if (!priceSource.isEmpty()) getPriceSource(e, priceSource); } if (gac->getKvpValue("tax-related", "integer") == QChar('1')) acc.setValue("Tax", "Yes"); // all the details from the file about the account should be known by now. // calling addAccount will automatically fill in the account ID. m_storage->addAccount(acc); m_mapIds[gac->id().toUtf8()] = acc.id(); // to link gnucash id to ours for tx posting if (gncdebug) qDebug() << "Gnucash account" << gac->id() << "has id of" << acc.id() << ", type of" << KMyMoneyUtils::accountTypeToString(acc.accountType()) << "parent is" << acc.parentAccountId(); signalProgress(++m_accountCount, 0); return ; } PASS } //********************************************** convertTransaction ***************************** void MyMoneyGncReader::convertTransaction(const GncTransaction *gtx) { Q_CHECK_PTR(gtx); MyMoneyTransaction tx; MyMoneySplit split; unsigned int i; if (m_transactionCount == 0) signalProgress(0, m_gncTransactionCount, i18n("Loading transactions...")); // initialize class variables related to transactions m_txCommodity = ""; m_txPayeeId = ""; m_potentialTransfer = true; m_splitList.clear(); m_liabilitySplitList.clear(); m_otherSplitList.clear(); // payee, dates, commodity if (!gtx->desc().isEmpty()) m_txPayeeId = createPayee(gtx->desc()); tx.setEntryDate(gtx->dateEntered()); tx.setPostDate(gtx->datePosted()); m_txDatePosted = tx.postDate(); // save for use in splits m_txChequeNo = gtx->no(); // ditto tx.setCommodity(gtx->currency().toUtf8()); m_txCommodity = tx.commodity(); // save in storage, maybe needed for Orphan accounts // process splits for (i = 0; i < gtx->splitCount(); i++) { convertSplit(static_cast(gtx->getSplit(i))); } // handle the odd case of just one split, which gnc allows, // by just duplicating the split // of course, we should change the sign but this case has only ever been seen // when the balance is zero, and can cause kmm to crash, so... if (gtx->splitCount() == 1) { convertSplit(static_cast(gtx->getSplit(0))); } m_splitList += m_liabilitySplitList += m_otherSplitList; // the splits are in order in splitList. Link them to the tx. also, determine the // action type, and fill in some fields which gnc holds at transaction level // first off, is it a transfer (can only have 2 splits?) // also, a tx with just 2 splits is shown by GnuCash as non-split bool nonSplitTx = true; if (m_splitList.count() != 2) { m_potentialTransfer = false; nonSplitTx = false; } QString slotMemo = gtx->getKvpValue(QString("notes")); if (!slotMemo.isEmpty()) tx.setMemo(slotMemo); QList::iterator it = m_splitList.begin(); while (!m_splitList.isEmpty()) { split = *it; // at this point, if m_potentialTransfer is still true, it is actually one! if (m_potentialTransfer) split.setAction(MyMoneySplit::ActionTransfer); if ((m_useTxNotes) // if use txnotes option is set && (nonSplitTx) // and it's a (GnuCash) non-split transaction && (!tx.memo().isEmpty())) // and tx notes are present split.setMemo(tx.memo()); // use the tx notes as memo tx.addSplit(split); it = m_splitList.erase(it); } m_storage->addTransaction(tx, true); // all done, add the transaction to storage signalProgress(++m_transactionCount, 0); return ; } //******************************************convertSplit******************************** void MyMoneyGncReader::convertSplit(const GncSplit *gsp) { Q_CHECK_PTR(gsp); MyMoneySplit split; MyMoneyAccount splitAccount; // find the kmm account id corresponding to the gnc id QString kmmAccountId; map_accountIds::const_iterator id = m_mapIds.constFind(gsp->acct().toUtf8()); if (id != m_mapIds.constEnd()) { kmmAccountId = id.value(); } else { // for the case where the acs not found (which shouldn't happen?), create an account with gnc name kmmAccountId = createOrphanAccount(gsp->acct()); } // find the account pointer and save for later splitAccount = m_storage->account(kmmAccountId); // print some data so we can maybe identify this split later // TODO : prints personal data //if (gncdebug) qDebug ("Split data - gncid %s, kmmid %s, memo %s, value %s, recon state %s", // gsp->acct().toLatin1(), kmmAccountId.data(), gsp->memo().toLatin1(), gsp->value().toLatin1(), // gsp->recon().toLatin1()); // payee id split.setPayeeId(m_txPayeeId.toUtf8()); // reconciled state and date switch (gsp->recon().at(0).toLatin1()) { case 'n': split.setReconcileFlag(MyMoneySplit::NotReconciled); break; case 'c': split.setReconcileFlag(MyMoneySplit::Cleared); break; case 'y': split.setReconcileFlag(MyMoneySplit::Reconciled); break; } split.setReconcileDate(gsp->reconDate()); // memo split.setMemo(gsp->memo()); // accountId split.setAccountId(kmmAccountId); // cheque no split.setNumber(m_txChequeNo); // value and quantity MyMoneyMoney splitValue(convBadValue(gsp->value())); if (gsp->value() == "-1/0") { // treat gnc invalid value as zero // it's not quite a consistency check, but easier to treat it as such m_messageList["CC"].append (i18n("Account or Category %1, transaction date %2; split contains invalid value; please check", splitAccount.name(), m_txDatePosted.toString(Qt::ISODate))); } MyMoneyMoney splitQuantity(convBadValue(gsp->qty())); split.setValue(splitValue); // if split currency = tx currency, set shares = value (14/10/05) if (splitAccount.currencyId() == m_txCommodity) { split.setShares(splitValue); } else { split.setShares(splitQuantity); } // in kmm, the first split is important. in this routine we will // save the splits in our split list with the priority: // 1. assets // 2. liabilities // 3. others (categories) // but keeping each in same order as gnucash switch (splitAccount.accountGroup()) { case MyMoneyAccount::Asset: if (splitAccount.accountType() == MyMoneyAccount::Stock) { split.value().isZero() ? split.setAction(MyMoneySplit::ActionAddShares) : // free shares? split.setAction(MyMoneySplit::ActionBuyShares); m_potentialTransfer = false; // ? // add a price history entry MyMoneySecurity e = m_storage->security(splitAccount.currencyId()); MyMoneyMoney price; if (!split.shares().isZero()) { static const signed64 NEW_DENOM = 10000; price = split.value() / split.shares(); price = MyMoneyMoney(price.toDouble(), NEW_DENOM); } if (!price.isZero()) { TRY { // we can't use m_storage->security coz security list is not built yet m_storage->currency(m_txCommodity); // will throw exception if not currency e.setTradingCurrency(m_txCommodity); if (gncdebug) qDebug() << "added price for" << e.name() << price.toString() << "date" << m_txDatePosted.toString(Qt::ISODate); m_storage->modifySecurity(e); MyMoneyPrice dealPrice(e.id(), m_txCommodity, m_txDatePosted, price, i18n("Imported Transaction")); m_storage->addPrice(dealPrice); } CATCH { // stock transfer; treat like free shares? split.setAction(MyMoneySplit::ActionAddShares); } } } else { // not stock if (split.value().isNegative()) { bool isNumeric = false; if (!split.number().isEmpty()) { split.number().toLong(&isNumeric); // No QString.isNumeric()?? } if (isNumeric) { split.setAction(MyMoneySplit::ActionCheck); } else { split.setAction(MyMoneySplit::ActionWithdrawal); } } else { split.setAction(MyMoneySplit::ActionDeposit); } } m_splitList.append(split); break; case MyMoneyAccount::Liability: split.value().isNegative() ? split.setAction(MyMoneySplit::ActionWithdrawal) : split.setAction(MyMoneySplit::ActionDeposit); m_liabilitySplitList.append(split); break; default: m_potentialTransfer = false; m_otherSplitList.append(split); } // backdate the account opening date if necessary if (m_txDatePosted < splitAccount.openingDate()) { splitAccount.setOpeningDate(m_txDatePosted); m_storage->modifyAccount(splitAccount); } return ; } //********************************* convertTemplateTransaction ********************************************** MyMoneyTransaction MyMoneyGncReader::convertTemplateTransaction(const QString& schedName, const GncTransaction *gtx) { Q_CHECK_PTR(gtx); MyMoneyTransaction tx; MyMoneySplit split; unsigned int i; if (m_templateCount == 0) signalProgress(0, 1, i18n("Loading templates...")); // initialize class variables related to transactions m_txCommodity = ""; m_txPayeeId = ""; m_potentialTransfer = true; m_splitList.clear(); m_liabilitySplitList.clear(); m_otherSplitList.clear(); // payee, dates, commodity if (!gtx->desc().isEmpty()) { m_txPayeeId = createPayee(gtx->desc()); } else { m_txPayeeId = createPayee(i18n("Unknown payee")); // schedules require a payee tho normal tx's don't. not sure why... } tx.setEntryDate(gtx->dateEntered()); tx.setPostDate(gtx->datePosted()); m_txDatePosted = tx.postDate(); tx.setCommodity(gtx->currency().toUtf8()); m_txCommodity = tx.commodity(); // save for possible use in orphan account // process splits for (i = 0; i < gtx->splitCount(); i++) { convertTemplateSplit(schedName, static_cast(gtx->getSplit(i))); } // determine the action type for the splits and link them to the template tx if (!m_otherSplitList.isEmpty()) m_potentialTransfer = false; // tfrs can occur only between assets and asset/liabilities m_splitList += m_liabilitySplitList += m_otherSplitList; // the splits are in order in splitList. Transfer them to the tx // also, determine the action type. first off, is it a transfer (can only have 2 splits?) if (m_splitList.count() != 2) m_potentialTransfer = false; // at this point, if m_potentialTransfer is still true, it is actually one! QString txMemo = ""; QList::iterator it = m_splitList.begin(); while (!m_splitList.isEmpty()) { split = *it; if (m_potentialTransfer) { split.setAction(MyMoneySplit::ActionTransfer); } else { if (split.value().isNegative()) { //split.setAction (negativeActionType); split.setAction(MyMoneySplit::ActionWithdrawal); } else { //split.setAction (positiveActionType); split.setAction(MyMoneySplit::ActionDeposit); } } split.setNumber(gtx->no()); // set cheque no (or equivalent description) // Arbitrarily, save the first non-null split memo as the memo for the whole tx // I think this is necessary because txs with just 2 splits (the majority) // are not viewable as split transactions in kmm so the split memo is not seen if ((txMemo.isEmpty()) && (!split.memo().isEmpty())) txMemo = split.memo(); tx.addSplit(split); it = m_splitList.erase(it); } // memo - set from split tx.setMemo(txMemo); signalProgress(++m_templateCount, 0); return (tx); } //********************************* convertTemplateSplit **************************************************** void MyMoneyGncReader::convertTemplateSplit(const QString& schedName, const GncTemplateSplit *gsp) { Q_CHECK_PTR(gsp); // convertTemplateSplit MyMoneySplit split; MyMoneyAccount splitAccount; unsigned int i, j; bool nonNumericFormula = false; // action, value and account will be set from slots // reconcile state, always Not since it hasn't even been posted yet (?) split.setReconcileFlag(MyMoneySplit::NotReconciled); // memo split.setMemo(gsp->memo()); // payee id split.setPayeeId(m_txPayeeId.toUtf8()); // read split slots (KVPs) int xactionCount = 0; int validSlotCount = 0; QString gncAccountId; for (i = 0; i < gsp->kvpCount(); i++) { const GncKvp& slot = gsp->getKvp(i); if ((slot.key() == "sched-xaction") && (slot.type() == "frame")) { bool bFoundStringCreditFormula = false; bool bFoundStringDebitFormula = false; bool bFoundGuidAccountId = false; QString gncCreditFormula, gncDebitFormula; for (j = 0; j < slot.kvpCount(); j++) { const GncKvp& subSlot = slot.getKvp(j); // again, see comments above. when we have a full specification // of all the options available to us, we can no doubt improve on this if ((subSlot.key() == "credit-formula") && (subSlot.type() == "string")) { gncCreditFormula = subSlot.value(); bFoundStringCreditFormula = true; } if ((subSlot.key() == "debit-formula") && (subSlot.type() == "string")) { gncDebitFormula = subSlot.value(); bFoundStringDebitFormula = true; } if ((subSlot.key() == "account") && (subSlot.type() == "guid")) { gncAccountId = subSlot.value(); bFoundGuidAccountId = true; } } // all data read, now check we have everything if ((bFoundStringCreditFormula) && (bFoundStringDebitFormula) && (bFoundGuidAccountId)) { if (gncdebug) qDebug() << "Found valid slot; credit" << gncCreditFormula << "debit" << gncDebitFormula << "acct" << gncAccountId; validSlotCount++; } // validate numeric, work out sign MyMoneyMoney exFormula; exFormula.setNegativeMonetarySignPosition(MyMoneyMoney::BeforeQuantityMoney); QString numericTest; char crdr = 0 ; if (!gncCreditFormula.isEmpty()) { crdr = 'C'; numericTest = gncCreditFormula; } else if (!gncDebitFormula.isEmpty()) { crdr = 'D'; numericTest = gncDebitFormula; } kMyMoneyMoneyValidator v(0); int pos; // useless, but required for validator if (v.validate(numericTest, pos) == QValidator::Acceptable) { switch (crdr) { case 'C': exFormula = QString("-" + numericTest); break; case 'D': exFormula = numericTest; } } else { if (gncdebug) qDebug() << numericTest << "is not numeric"; nonNumericFormula = true; } split.setValue(exFormula); xactionCount++; } else { m_messageList["SC"].append( i18n("Schedule %1 contains unknown action (key = %2, type = %3)", schedName, slot.key(), slot.type())); m_suspectSchedule = true; } } // report this as untranslatable tx if (xactionCount > 1) { m_messageList["SC"].append( i18n("Schedule %1 contains multiple actions; only one has been imported", schedName)); m_suspectSchedule = true; } if (validSlotCount == 0) { m_messageList["SC"].append( i18n("Schedule %1 contains no valid splits", schedName)); m_suspectSchedule = true; } if (nonNumericFormula) { m_messageList["SC"].append( i18n("Schedule %1 appears to contain a formula. GnuCash formulae are not convertible", schedName)); m_suspectSchedule = true; } // find the kmm account id corresponding to the gnc id QString kmmAccountId; map_accountIds::const_iterator id = m_mapIds.constFind(gncAccountId.toUtf8()); if (id != m_mapIds.constEnd()) { kmmAccountId = id.value(); } else { // for the case where the acs not found (which shouldn't happen?), create an account with gnc name kmmAccountId = createOrphanAccount(gncAccountId); } splitAccount = m_storage->account(kmmAccountId); split.setAccountId(kmmAccountId); // if split currency = tx currency, set shares = value (14/10/05) if (splitAccount.currencyId() == m_txCommodity) { split.setShares(split.value()); } /* else { //FIXME: scheduled currency or investment tx needs to be investigated split.setShares (splitQuantity); } */ // add the split to one of the lists switch (splitAccount.accountGroup()) { case MyMoneyAccount::Asset: m_splitList.append(split); break; case MyMoneyAccount::Liability: m_liabilitySplitList.append(split); break; default: m_otherSplitList.append(split); } // backdate the account opening date if necessary if (m_txDatePosted < splitAccount.openingDate()) { splitAccount.setOpeningDate(m_txDatePosted); m_storage->modifyAccount(splitAccount); } return ; } //********************************* convertSchedule ******************************************************** void MyMoneyGncReader::convertSchedule(const GncSchedule *gsc) { TRY { Q_CHECK_PTR(gsc); MyMoneySchedule sc; MyMoneyTransaction tx; m_suspectSchedule = false; QDate startDate, nextDate, lastDate, endDate; // for date calculations QDate today = QDate::currentDate(); int numOccurs, remOccurs; if (m_scheduleCount == 0) signalProgress(0, m_gncScheduleCount, i18n("Loading schedules...")); // schedule name sc.setName(gsc->name()); // find the transaction template as stored earlier QList::const_iterator itt; for (itt = m_templateList.constBegin(); itt != m_templateList.constEnd(); ++itt) { // the id to match against is the split:account value in the splits if (static_cast((*itt)->getSplit(0))->acct() == gsc->templId()) break; } if (itt == m_templateList.constEnd()) { throw MYMONEYEXCEPTION(i18n("Cannot find template transaction for schedule %1", sc.name())); } else { tx = convertTemplateTransaction(sc.name(), *itt); } tx.clearId(); // define the conversion table for intervals struct convIntvl { QString gncType; // the gnucash name unsigned char interval; // for date calculation unsigned int intervalCount; MyMoneySchedule::occurrenceE occ; // equivalent occurrence code MyMoneySchedule::weekendOptionE wo; }; /* other intervals supported by gnc according to Josh Sled's schema (see above) "none" "semi_monthly" */ /* some of these type names do not appear in gnucash and are difficult to generate for pre 2.2 files.They can be generated for 2.2 however, by GncRecurrence::getFrequency() */ static convIntvl vi [] = { {"once", 'o', 1, MyMoneySchedule::OCCUR_ONCE, MyMoneySchedule::MoveNothing }, {"daily" , 'd', 1, MyMoneySchedule::OCCUR_DAILY, MyMoneySchedule::MoveNothing }, //{"daily_mf", 'd', 1, MyMoneySchedule::OCCUR_DAILY, MyMoneySchedule::MoveAfter }, doesn't work, need new freq in kmm {"30-days" , 'd', 30, MyMoneySchedule::OCCUR_EVERYTHIRTYDAYS, MyMoneySchedule::MoveNothing }, {"weekly", 'w', 1, MyMoneySchedule::OCCUR_WEEKLY, MyMoneySchedule::MoveNothing }, {"bi_weekly", 'w', 2, MyMoneySchedule::OCCUR_EVERYOTHERWEEK, MyMoneySchedule::MoveNothing }, {"three-weekly", 'w', 3, MyMoneySchedule::OCCUR_EVERYTHREEWEEKS, MyMoneySchedule::MoveNothing }, {"four-weekly", 'w', 4, MyMoneySchedule::OCCUR_EVERYFOURWEEKS, MyMoneySchedule::MoveNothing }, {"eight-weekly", 'w', 8, MyMoneySchedule::OCCUR_EVERYEIGHTWEEKS, MyMoneySchedule::MoveNothing }, {"monthly", 'm', 1, MyMoneySchedule::OCCUR_MONTHLY, MyMoneySchedule::MoveNothing }, {"two-monthly", 'm', 2, MyMoneySchedule::OCCUR_EVERYOTHERMONTH, MyMoneySchedule::MoveNothing }, {"quarterly", 'm', 3, MyMoneySchedule::OCCUR_QUARTERLY, MyMoneySchedule::MoveNothing }, {"tri_annually", 'm', 4, MyMoneySchedule::OCCUR_EVERYFOURMONTHS, MyMoneySchedule::MoveNothing }, {"semi_yearly", 'm', 6, MyMoneySchedule::OCCUR_TWICEYEARLY, MyMoneySchedule::MoveNothing }, {"yearly", 'y', 1, MyMoneySchedule::OCCUR_YEARLY, MyMoneySchedule::MoveNothing }, {"two-yearly", 'y', 2, MyMoneySchedule::OCCUR_EVERYOTHERYEAR, MyMoneySchedule::MoveNothing }, {"zzz", 'y', 1, MyMoneySchedule::OCCUR_YEARLY, MyMoneySchedule::MoveNothing} // zzz = stopper, may cause problems. what else can we do? }; QString frequency = "unknown"; // set default to unknown frequency bool unknownOccurs = false; // may have zero, or more than one frequency/recurrence spec QString schedEnabled; if (gsc->version() == "2.0.0") { if (gsc->m_vpRecurrence.count() != 1) { unknownOccurs = true; } else { const GncRecurrence *gre = gsc->m_vpRecurrence.first(); //qDebug (QString("Sched %1, pt %2, mu %3, sd %4").arg(gsc->name()).arg(gre->periodType()) // .arg(gre->mult()).arg(gre->startDate().toString(Qt::ISODate))); frequency = gre->getFrequency(); schedEnabled = gsc->enabled(); } sc.setOccurrence(MyMoneySchedule::OCCUR_ONCE); // FIXME - how to convert } else { // find this interval const GncFreqSpec *fs = gsc->getFreqSpec(); if (fs == 0) { unknownOccurs = true; } else { frequency = fs->intervalType(); if (!fs->m_fsList.isEmpty()) unknownOccurs = true; // nested freqspec } schedEnabled = 'y'; // earlier versions did not have an enable flag } int i; for (i = 0; vi[i].gncType != "zzz"; i++) { if (frequency == vi[i].gncType) break; } if (vi[i].gncType == "zzz") { m_messageList["SC"].append( i18n("Schedule %1 has interval of %2 which is not currently available", sc.name(), frequency)); i = 0; // treat as single occurrence m_suspectSchedule = true; } if (unknownOccurs) { m_messageList["SC"].append( i18n("Schedule %1 contains unknown interval specification; please check for correct operation", sc.name())); m_suspectSchedule = true; } // set the occurrence interval, weekend option, start date sc.setOccurrence(vi[i].occ); sc.setWeekendOption(vi[i].wo); sc.setStartDate(gsc->startDate()); // if a last date was specified, use it, otherwise try to work out the last date sc.setLastPayment(gsc->lastDate()); numOccurs = gsc->numOccurs().toInt(); if (sc.lastPayment() == QDate()) { nextDate = lastDate = gsc->startDate(); while ((nextDate < today) && (numOccurs-- != 0)) { lastDate = nextDate; nextDate = incrDate(lastDate, vi[i].interval, vi[i].intervalCount); } sc.setLastPayment(lastDate); } // under Tom's new regime, the tx dates are the next due date (I think) tx.setPostDate(incrDate(sc.lastPayment(), vi[i].interval, vi[i].intervalCount)); tx.setEntryDate(incrDate(sc.lastPayment(), vi[i].interval, vi[i].intervalCount)); // if an end date was specified, use it, otherwise if the input file had a number // of occurs remaining, work out the end date sc.setEndDate(gsc->endDate()); numOccurs = gsc->numOccurs().toInt(); remOccurs = gsc->remOccurs().toInt(); if ((sc.endDate() == QDate()) && (remOccurs > 0)) { endDate = sc.lastPayment(); while (remOccurs-- > 0) { endDate = incrDate(endDate, vi[i].interval, vi[i].intervalCount); } sc.setEndDate(endDate); } // Check for sched deferred interval. Don't know how/if we can handle it, or even what it means... if (gsc->getSchedDef() != 0) { m_messageList["SC"].append( i18n("Schedule %1 contains a deferred interval specification; please check for correct operation", sc.name())); m_suspectSchedule = true; } // payment type, options sc.setPaymentType((MyMoneySchedule::paymentTypeE)MyMoneySchedule::STYPE_OTHER); sc.setFixed(!m_suspectSchedule); // if any probs were found, set it as variable so user will always be prompted // we don't currently have a 'disable' option, but just make sure auto-enter is off if not enabled //qDebug(QString("%1 and %2").arg(gsc->autoCreate()).arg(schedEnabled)); sc.setAutoEnter((gsc->autoCreate() == QChar('y')) && (schedEnabled == QChar('y'))); //qDebug(QString("autoEnter set to %1").arg(sc.autoEnter())); // type QString actionType = tx.splits().first().action(); if (actionType == MyMoneySplit::ActionDeposit) { sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_DEPOSIT); } else if (actionType == MyMoneySplit::ActionTransfer) { sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_TRANSFER); } else { sc.setType((MyMoneySchedule::typeE)MyMoneySchedule::TYPE_BILL); } // finally, set the transaction pointer sc.setTransaction(tx); //tell the storage objects we have a new schedule object. if (m_suspectSchedule && m_dropSuspectSchedules) { m_messageList["SC"].append( i18n("Schedule %1 dropped at user request", sc.name())); } else { m_storage->addSchedule(sc); if (m_suspectSchedule) m_suspectList.append(sc.id()); } signalProgress(++m_scheduleCount, 0); return ; } PASS } //********************************* convertFreqSpec ******************************************************** void MyMoneyGncReader::convertFreqSpec(const GncFreqSpec *) { // Nowt to do here at the moment, convertSched only retrieves the interval type // but we will probably need to look into the nested freqspec when we properly implement semi-monthly and stuff return ; } //********************************* convertRecurrence ******************************************************** void MyMoneyGncReader::convertRecurrence(const GncRecurrence *) { return ; } //********************************************************************************************************** //************************************* terminate ********************************************************** void MyMoneyGncReader::terminate() { TRY { // All data has been converted and added to storage // this code is just temporary to show us what is in the file. if (gncdebug) qDebug("%d accounts found in the GnuCash file", (unsigned int)m_mapIds.count()); for (map_accountIds::const_iterator it = m_mapIds.constBegin(); it != m_mapIds.constEnd(); ++it) { if (gncdebug) qDebug() << "key =" << it.key() << "value =" << it.value(); } // first step is to implement the users investment option, now we // have all the accounts available QList::iterator stocks; for (stocks = m_stockList.begin(); stocks != m_stockList.end(); ++stocks) { checkInvestmentOption(*stocks); } // Next step is to walk the list and assign the parent/child relationship between the objects. unsigned int i = 0; signalProgress(0, m_accountCount, i18n("Reorganizing accounts...")); QList list; QList::iterator acc; m_storage->accountList(list); for (acc = list.begin(); acc != list.end(); ++acc) { if ((*acc).parentAccountId() == m_storage->asset().id()) { MyMoneyAccount assets = m_storage->asset(); m_storage->addAccount(assets, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main asset account"; } else if ((*acc).parentAccountId() == m_storage->liability().id()) { MyMoneyAccount liabilities = m_storage->liability(); m_storage->addAccount(liabilities, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main liability account"; } else if ((*acc).parentAccountId() == m_storage->income().id()) { MyMoneyAccount incomes = m_storage->income(); m_storage->addAccount(incomes, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main income account"; } else if ((*acc).parentAccountId() == m_storage->expense().id()) { MyMoneyAccount expenses = m_storage->expense(); m_storage->addAccount(expenses, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main expense account"; } else if ((*acc).parentAccountId() == m_storage->equity().id()) { MyMoneyAccount equity = m_storage->equity(); m_storage->addAccount(equity, (*acc)); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main equity account"; } else if ((*acc).parentAccountId() == m_rootId) { if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of the main root account"; } else { // it is not under one of the main accounts, so find gnucash parent QString parentKey = (*acc).parentAccountId(); if (gncdebug) qDebug() << "Account id" << (*acc).id() << "is a child of " << (*acc).parentAccountId(); map_accountIds::const_iterator id = m_mapIds.constFind(parentKey); if (id != m_mapIds.constEnd()) { if (gncdebug) qDebug() << "Setting account id" << (*acc).id() << "parent account id to" << id.value(); MyMoneyAccount parent = m_storage->account(id.value()); parent = checkConsistency(parent, (*acc)); m_storage->addAccount(parent, (*acc)); } else { throw MYMONEYEXCEPTION("terminate() could not find account id"); } } signalProgress(++i, 0); } // end for account signalProgress(0, 1, (".")); // debug - get rid of reorg message // offer the most common account currency as a default QString mainCurrency = ""; unsigned int maxCount = 0; QMap::ConstIterator it; for (it = m_currencyCount.constBegin(); it != m_currencyCount.constEnd(); ++it) { if (it.value() > maxCount) { maxCount = it.value(); mainCurrency = it.key(); } } if (mainCurrency != "") { QString question = i18n("Your main currency seems to be %1 (%2); do you want to set this as your base currency?", mainCurrency, m_storage->currency(mainCurrency.toUtf8()).name()); if (KMessageBox::questionYesNo(0, question, PACKAGE) == KMessageBox::Yes) { m_storage->setValue("kmm-baseCurrency", mainCurrency); } } // now produce the end of job reports - first, work out which ones are required QList sectionsToReport; // list of sections needing report sectionsToReport.append("MN"); // always build the main section if ((m_ccCount = m_messageList["CC"].count()) > 0) sectionsToReport.append("CC"); if ((m_orCount = m_messageList["OR"].count()) > 0) sectionsToReport.append("OR"); if ((m_scCount = m_messageList["SC"].count()) > 0) sectionsToReport.append("SC"); // produce the sections in separate message boxes bool exit = false; int si; for (si = 0; (si < sectionsToReport.count()) && !exit; ++si) { QString button0Text = i18nc("Button to show more detailed data", "More"); if (si + 1 == sectionsToReport.count()) button0Text = i18nc("Button to close the current dialog", "Done"); // last section KGuiItem yesItem(button0Text, QIcon(), "", ""); KGuiItem noItem(i18n("Save Report"), QIcon(), "", ""); switch (KMessageBox::questionYesNoCancel(0, buildReportSection(sectionsToReport[si]), PACKAGE, yesItem, noItem)) { case KMessageBox::Yes: break; case KMessageBox::No: exit = writeReportToFile(sectionsToReport); break; default: exit = true; break; } } for (si = 0; si < m_suspectList.count(); ++si) { MyMoneySchedule sc = m_storage->schedule(m_suspectList[si]); KEditScheduleDlg *s; switch (KMessageBox::warningYesNo(0, i18n("Problems were encountered in converting schedule '%1'.\nDo you want to review or edit it now?", sc.name()), PACKAGE)) { case KMessageBox::Yes: s = new KEditScheduleDlg(sc); if (s->exec()) m_storage->modifySchedule(s->schedule()); delete s; break; default: break; } } } PASS } //************************************ buildReportSection************************************ QString MyMoneyGncReader::buildReportSection(const QString& source) { TRY { QString s = ""; bool more = false; if (source == "MN") { s.append(i18n("Found:\n\n")); s.append(i18np("%1 commodity (equity)\n", "%1 commodities (equities)\n", m_commodityCount)); s.append(i18np("%1 price\n", "%1 prices\n", m_priceCount)); s.append(i18np("%1 account\n", "%1 accounts\n", m_accountCount)); s.append(i18np("%1 transaction\n", "%1 transactions\n", m_transactionCount)); s.append(i18np("%1 schedule\n", "%1 schedules\n", m_scheduleCount)); s.append("\n\n"); if (m_ccCount == 0) { s.append(i18n("No inconsistencies were detected\n")); } else { s.append(i18np("%1 inconsistency was detected and corrected\n", "%1 inconsistencies were detected and corrected\n", m_ccCount)); more = true; } if (m_orCount > 0) { s.append("\n\n"); s.append(i18np("%1 orphan account was created\n", "%1 orphan accounts were created\n", m_orCount)); more = true; } if (m_scCount > 0) { s.append("\n\n"); s.append(i18np("%1 possible schedule problem was noted\n", "%1 possible schedule problems were noted\n", m_scCount)); more = true; } QString unsupported(""); QString lineSep("\n - "); if (m_smallBusinessFound) unsupported.append(lineSep + i18n("Small Business Features (Customers, Invoices, etc.)")); if (m_budgetsFound) unsupported.append(lineSep + i18n("Budgets")); if (m_lotsFound) unsupported.append(lineSep + i18n("Lots")); if (!unsupported.isEmpty()) { unsupported.prepend(i18n("The following features found in your file are not currently supported:")); s.append(unsupported); } if (more) s.append(i18n("\n\nPress More for further information")); } else { s = m_messageList[source].join(QChar('\n')); } if (gncdebug) qDebug() << s; return (static_cast(s)); } PASS } //************************ writeReportToFile********************************* bool MyMoneyGncReader::writeReportToFile(const QList& sectionsToReport) { TRY { int i; QString fd = QFileDialog::getSaveFileName(0, QString(), QString(), i18n("Save report as")); if (fd.isEmpty()) return (false); QFile reportFile(fd); if (!reportFile.open(QIODevice::WriteOnly)) { return (false); } QTextStream stream(&reportFile); for (i = 0; i < sectionsToReport.count(); i++) stream << buildReportSection(sectionsToReport[i]) << endl; reportFile.close(); return (true); } PASS } /**************************************************************************** Utility routines *****************************************************************************/ //************************ createPayee *************************** QString MyMoneyGncReader::createPayee(const QString& gncDescription) { MyMoneyPayee payee; TRY { payee = m_storage->payeeByName(gncDescription); } CATCH { // payee not found, create one payee.setName(gncDescription); m_storage->addPayee(payee); } return (payee.id()); } //************************************** createOrphanAccount ******************************* QString MyMoneyGncReader::createOrphanAccount(const QString& gncName) { MyMoneyAccount acc; acc.setName("orphan_" + gncName); acc.setDescription(i18n("Orphan created from unknown GnuCash account")); QDate today = QDate::currentDate(); acc.setOpeningDate(today); acc.setLastModified(today); acc.setLastReconciliationDate(today); acc.setCurrencyId(m_txCommodity); acc.setAccountType(MyMoneyAccount::Asset); acc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(acc); // assign the gnucash id as the key into the map to find our id m_mapIds[gncName.toUtf8()] = acc.id(); m_messageList["OR"].append( i18n("One or more transactions contain a reference to an otherwise unknown account\n" "An asset account with the name %1 has been created to hold the data", acc.name())); return (acc.id()); } //****************************** incrDate ********************************************* QDate MyMoneyGncReader::incrDate(QDate lastDate, unsigned char interval, unsigned int intervalCount) { TRY { switch (interval) { case 'd': return (lastDate.addDays(intervalCount)); case 'w': return (lastDate.addDays(intervalCount * 7)); case 'm': return (lastDate.addMonths(intervalCount)); case 'y': return (lastDate.addYears(intervalCount)); case 'o': // once-only return (lastDate); } throw MYMONEYEXCEPTION(i18n("Internal error - invalid interval char in incrDate")); QDate r = QDate(); return (r); // to keep compiler happy } PASS } //********************************* checkConsistency ********************************** MyMoneyAccount MyMoneyGncReader::checkConsistency(MyMoneyAccount& parent, MyMoneyAccount& child) { TRY { // gnucash is flexible/weird enough to allow various inconsistencies // these are a couple I found in my file, no doubt more will be discovered if ((child.accountType() == MyMoneyAccount::Investment) && (parent.accountType() != MyMoneyAccount::Asset)) { m_messageList["CC"].append( i18n("An Investment account must be a child of an Asset account\n" "Account %1 will be stored under the main Asset account", child.name())); return m_storage->asset(); } if ((child.accountType() == MyMoneyAccount::Income) && (parent.accountType() != MyMoneyAccount::Income)) { m_messageList["CC"].append( i18n("An Income account must be a child of an Income account\n" "Account %1 will be stored under the main Income account", child.name())); return m_storage->income(); } if ((child.accountType() == MyMoneyAccount::Expense) && (parent.accountType() != MyMoneyAccount::Expense)) { m_messageList["CC"].append( i18n("An Expense account must be a child of an Expense account\n" "Account %1 will be stored under the main Expense account", child.name())); return m_storage->expense(); } return (parent); } PASS } //*********************************** checkInvestmentOption ************************* void MyMoneyGncReader::checkInvestmentOption(QString stockId) { // implement the investment option for stock accounts // first check whether the parent account (gnucash id) is actually an // investment account. if it is, no further action is needed MyMoneyAccount stockAcc = m_storage->account(m_mapIds[stockId.toUtf8()]); MyMoneyAccount parent; QString parentKey = stockAcc.parentAccountId(); map_accountIds::const_iterator id = m_mapIds.constFind(parentKey); if (id != m_mapIds.constEnd()) { parent = m_storage->account(id.value()); if (parent.accountType() == MyMoneyAccount::Investment) return ; } // so now, check the investment option requested by the user // option 0 creates a separate investment account for each stock account if (m_investmentOption == 0) { MyMoneyAccount invAcc(stockAcc); invAcc.setAccountType(MyMoneyAccount::Investment); invAcc.setCurrencyId(QString("")); // we don't know what currency it is!! invAcc.setParentAccountId(parentKey); // intersperse it between old parent and child stock acct m_storage->addAccount(invAcc); m_mapIds [invAcc.id()] = invAcc.id(); // so stock account gets parented (again) to investment account later if (gncdebug) qDebug() << "Created investment account" << invAcc.name() << "as id" << invAcc.id() << "parent" << invAcc.parentAccountId(); if (gncdebug) qDebug() << "Setting stock" << stockAcc.name() << "id" << stockAcc.id() << "as child of" << invAcc.id(); stockAcc.setParentAccountId(invAcc.id()); m_storage->addAccount(invAcc, stockAcc); // investment option 1 creates a single investment account for all stocks } else if (m_investmentOption == 1) { static QString singleInvAccId = ""; MyMoneyAccount singleInvAcc; bool ok = false; if (singleInvAccId.isEmpty()) { // if the account has not yet been created QString invAccName; while (!ok) { invAccName = QInputDialog::getText(0, QStringLiteral(PACKAGE), i18n("Enter the investment account name "), QLineEdit::Normal, i18n("My Investments"), &ok); } singleInvAcc.setName(invAccName); singleInvAcc.setAccountType(MyMoneyAccount::Investment); singleInvAcc.setCurrencyId(QString("")); singleInvAcc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(singleInvAcc); m_mapIds [singleInvAcc.id()] = singleInvAcc.id(); // so stock account gets parented (again) to investment account later if (gncdebug) qDebug() << "Created investment account" << singleInvAcc.name() << "as id" << singleInvAcc.id() << "parent" << singleInvAcc.parentAccountId() << "reparenting stock"; singleInvAccId = singleInvAcc.id(); } else { // the account has already been created singleInvAcc = m_storage->account(singleInvAccId); } m_storage->addAccount(singleInvAcc, stockAcc); // add stock as child // the original intention of option 2 was to allow any asset account to be converted to an investment (broker) account // however, since we have already stored the accounts as asset, we have no way at present of changing their type // the only alternative would be to hold all the gnucash data in memory, then implement this option, then convert all the data // that would mean a major overhaul of the code. Perhaps I'll think of another way... } else if (m_investmentOption == 2) { static int lastSelected = 0; MyMoneyAccount invAcc(stockAcc); QStringList accList; QList list; QList::iterator acc; m_storage->accountList(list); // build a list of candidates for the input box for (acc = list.begin(); acc != list.end(); ++acc) { // if (((*acc).accountGroup() == MyMoneyAccount::Asset) && ((*acc).accountType() != MyMoneyAccount::Stock)) accList.append ((*acc).name()); if ((*acc).accountType() == MyMoneyAccount::Investment) accList.append((*acc).name()); } //if (accList.isEmpty()) qWarning ("No available accounts"); bool ok = false; while (!ok) { // keep going till we have a valid investment parent QString invAccName = QInputDialog::getItem(0, PACKAGE, i18n("Select parent investment account or enter new name. Stock %1", stockAcc.name()), accList, lastSelected, true, &ok); if (ok) { lastSelected = accList.indexOf(invAccName); // preserve selection for next time for (acc = list.begin(); acc != list.end(); ++acc) { if ((*acc).name() == invAccName) break; } if (acc != list.end()) { // an account was selected invAcc = *acc; } else { // a new account name was entered invAcc.setAccountType(MyMoneyAccount::Investment); invAcc.setName(invAccName); invAcc.setCurrencyId(QString("")); invAcc.setParentAccountId(m_storage->asset().id()); m_storage->addAccount(invAcc); ok = true; } if (invAcc.accountType() == MyMoneyAccount::Investment) { ok = true; } else { // this code is probably not going to be implemented coz we can't change account types (??) #if 0 QMessageBox mb(PACKAGE, i18n("%1 is not an Investment Account. Do you wish to make it one?", invAcc.name()), QMessageBox::Question, QMessageBox::Yes | QMessageBox::Default, QMessageBox::No | QMessageBox::Escape, Qt::NoButton); switch (mb.exec()) { case QMessageBox::No : ok = false; break; default: // convert it - but what if it has splits??? qWarning("Not yet implemented"); ok = true; break; } #endif switch (KMessageBox::questionYesNo(0, i18n("%1 is not an Investment Account. Do you wish to make it one?", invAcc.name()), PACKAGE)) { case KMessageBox::Yes: // convert it - but what if it has splits??? qWarning("Not yet implemented"); ok = true; break; default: ok = false; break; } } } // end if ok - user pressed Cancel } // end while !ok m_mapIds [invAcc.id()] = invAcc.id(); // so stock account gets parented (again) to investment account later m_storage->addAccount(invAcc, stockAcc); } else { // investment option != 0, 1, 2 qWarning("Invalid investment option %d", m_investmentOption); } } // get the price source for a stock (gnc account) where online quotes are requested void MyMoneyGncReader::getPriceSource(MyMoneySecurity stock, QString gncSource) { // if he wants to use Finance::Quote, no conversion of source name is needed if (m_useFinanceQuote) { stock.setValue("kmm-online-quote-system", "Finance::Quote"); stock.setValue("kmm-online-source", gncSource.toLower()); m_storage->modifySecurity(stock); return; } // first check if we have already asked about this source // (mapSources is initialy empty. We may be able to pre-fill it with some equivalent // sources, if such things do exist. User feedback may help here.) QMap::const_iterator it; for (it = m_mapSources.constBegin(); it != m_mapSources.constEnd(); ++it) { if (it.key() == gncSource) { stock.setValue("kmm-online-source", it.value()); m_storage->modifySecurity(stock); return; } } // not found in map, so ask the user QPointer dlg = new KGncPriceSourceDlg(stock.name(), gncSource); dlg->exec(); QString s = dlg->selectedSource(); if (!s.isEmpty()) { stock.setValue("kmm-online-source", s); m_storage->modifySecurity(stock); } if (dlg->alwaysUse()) m_mapSources[gncSource] = s; delete dlg; return; } // functions to control the progress bar //*********************** setProgressCallback ***************************** void MyMoneyGncReader::setProgressCallback(void(*callback)(int, int, const QString&)) { m_progressCallback = callback; return ; } //************************** signalProgress ******************************* void MyMoneyGncReader::signalProgress(int current, int total, const QString& msg) { if (m_progressCallback != 0) (*m_progressCallback)(current, total, msg); return ; } #endif // _GNCFILEANON diff --git a/kmymoney/dialogs/kfindtransactiondlg.cpp b/kmymoney/dialogs/kfindtransactiondlg.cpp index 91d6aaeb0..41eaf74dd 100644 --- a/kmymoney/dialogs/kfindtransactiondlg.cpp +++ b/kmymoney/dialogs/kfindtransactiondlg.cpp @@ -1,934 +1,934 @@ /*************************************************************************** kfindtransactiondlg.cpp ------------------- copyright : (C) 2003, 2007 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. * * * ***************************************************************************/ -#include +#include "config-kmymoney.h" #include "kfindtransactiondlg.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include #include #include #include #include "ui_kfindtransactiondlgdecl.h" #include "ui_ksortoptiondlg.h" #include enum ItemRoles { ItemIdRole = Qt::UserRole }; struct KSortOptionDlg::Private { Ui::KSortOptionDlg ui; }; KSortOptionDlg::KSortOptionDlg(QWidget *parent) : QDialog(parent), d(new Private) { d->ui.setupUi(this); init(); } KSortOptionDlg::~KSortOptionDlg() { delete d; } void KSortOptionDlg::init() { } void KSortOptionDlg::setSortOption(const QString& option, const QString& def) { if (option.isEmpty()) { d->ui.m_sortOption->setSettings(def); d->ui.m_useDefault->setChecked(true); } else { d->ui.m_sortOption->setSettings(option); d->ui.m_useDefault->setChecked(false); } } QString KSortOptionDlg::sortOption() const { QString rc; if (!d->ui.m_useDefault->isChecked()) { rc = d->ui.m_sortOption->settings(); } return rc; } void KSortOptionDlg::hideDefaultButton() { d->ui.m_useDefault->hide(); } KFindTransactionDlg::KFindTransactionDlg(QWidget *parent) : QDialog(parent), m_needReload(false), m_ui(new Ui::KFindTransactionDlgDecl) { m_ui->setupUi(this); m_dateRange = new DateRangeDlg(m_ui->m_dateTab); m_ui->dateRangeLayout->insertWidget(0, m_dateRange); m_ui->ButtonGroup1->setId(m_ui->m_amountButton, 0); m_ui->ButtonGroup1->setId(m_ui->m_amountRangeButton, 1); m_ui->m_register->installEventFilter(this); m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), false); // 'cause we don't have a separate setupTextPage connect(m_ui->m_textEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); // if return is pressed trigger a search (slotSearch checks if it's possible to perform the search) connect(m_ui->m_textEdit, SIGNAL(returnPressed()), this, SLOT(slotSearch())); setupAccountsPage(); setupCategoriesPage(); setupAmountPage(); setupPayeesPage(); setupTagsPage(); setupDetailsPage(); // We don't need to add the default into the list (see ::slotShowHelp() why) // m_helpAnchor[m_ui->m_textTab] = QString("details.search"); m_helpAnchor[m_ui->m_accountTab] = QString("details.search.account"); m_helpAnchor[m_ui->m_dateTab] = QString("details.search.date"); m_helpAnchor[m_ui->m_amountTab] = QString("details.search.amount"); m_helpAnchor[m_ui->m_categoryTab] = QString("details.search.category"); m_helpAnchor[m_ui->m_payeeTab] = QString("details.search.payee"); m_helpAnchor[m_ui->m_tagTab] = QString("details.search.tag"); //FIXME-ALEX update Help m_helpAnchor[m_ui->m_detailsTab] = QString("details.search.details"); // setup the register QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_ui->m_register->setupRegister(MyMoneyAccount(), cols); m_ui->m_register->setSelectionMode(QTableWidget::SingleSelection); connect(m_ui->m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(m_ui->m_register->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotSortOptions())); slotUpdateSelections(); // setup the connections connect(m_ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotSearch())); connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), this, SLOT(slotReset())); connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), m_ui->m_accountsView, SLOT(slotSelectAllAccounts())); connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), SIGNAL(clicked()), m_ui->m_categoriesView, SLOT(slotSelectAllAccounts())); connect(m_ui->buttonBox->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(deleteLater())); connect(m_ui->buttonBox->button(QDialogButtonBox::Help), SIGNAL(clicked()), this, SLOT(slotShowHelp())); // only allow searches when a selection has been made m_ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); KGuiItem::assign(m_ui->buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::find()); m_ui->buttonBox->button(QDialogButtonBox::Apply)->setToolTip(i18nc("@info:tooltip for find transaction apply button", "Search transactions")); connect(this, SIGNAL(selectionNotEmpty(bool)), m_ui->buttonBox->button(QDialogButtonBox::Apply), SLOT(setEnabled(bool))); // get signal about engine changes connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotRefreshView())); slotUpdateSelections(); m_ui->m_textEdit->setFocus(); } KFindTransactionDlg::~KFindTransactionDlg() { delete m_ui; } void KFindTransactionDlg::slotReset() { m_ui->m_textEdit->setText(QString()); m_ui->m_regExp->setChecked(false); m_ui->m_caseSensitive->setChecked(false); m_ui->m_textNegate->setCurrentItem(0); m_ui->m_amountEdit->setEnabled(true); m_ui->m_amountFromEdit->setEnabled(false); m_ui->m_amountToEdit->setEnabled(false); m_ui->m_amountEdit->loadText(QString()); m_ui->m_amountFromEdit->loadText(QString()); m_ui->m_amountToEdit->loadText(QString()); m_ui->m_amountButton->setChecked(true); m_ui->m_amountRangeButton->setChecked(false); m_ui->m_emptyPayeesButton->setChecked(false); selectAllItems(m_ui->m_payeesView, true); m_ui->m_emptyTagsButton->setChecked(false); selectAllItems(m_ui->m_tagsView, true); m_ui->m_typeBox->setCurrentIndex(MyMoneyTransactionFilter::allTypes); m_ui->m_stateBox->setCurrentIndex(MyMoneyTransactionFilter::allStates); m_ui->m_validityBox->setCurrentIndex(MyMoneyTransactionFilter::anyValidity); m_ui->m_nrEdit->setEnabled(true); m_ui->m_nrFromEdit->setEnabled(false); m_ui->m_nrToEdit->setEnabled(false); m_ui->m_nrEdit->setText(QString()); m_ui->m_nrFromEdit->setText(QString()); m_ui->m_nrToEdit->setText(QString()); m_ui->m_nrButton->setChecked(true); m_ui->m_nrRangeButton->setChecked(false); m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), false); m_ui->m_tabWidget->setCurrentIndex(m_ui->m_tabWidget->indexOf(m_ui->m_criteriaTab)); // the following call implies a call to slotUpdateSelections, // that's why we call it last m_dateRange->slotReset(); slotUpdateSelections(); } void KFindTransactionDlg::slotUpdateSelections() { QString txt; // Text tab if (!m_ui->m_textEdit->text().isEmpty()) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Text"); m_ui->m_regExp->setEnabled(QRegExp(m_ui->m_textEdit->text()).isValid()); } else m_ui->m_regExp->setEnabled(false); m_ui->m_caseSensitive->setEnabled(!m_ui->m_textEdit->text().isEmpty()); m_ui->m_textNegate->setEnabled(!m_ui->m_textEdit->text().isEmpty()); // Account tab if (!m_ui->m_accountsView->allItemsSelected()) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Account"); } m_dateRange->slotUpdateSelections(txt); // Amount tab if ((m_ui->m_amountButton->isChecked() && m_ui->m_amountEdit->isValid()) || (m_ui->m_amountRangeButton->isChecked() && (m_ui->m_amountFromEdit->isValid() || m_ui->m_amountToEdit->isValid()))) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Amount"); } // Categories tab if (!m_ui->m_categoriesView->allItemsSelected()) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Category"); } // Tags tab if (!allItemsSelected(m_ui->m_tagsView) || m_ui->m_emptyTagsButton->isChecked()) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Tags"); } m_ui->m_tagsView->setEnabled(!m_ui->m_emptyTagsButton->isChecked()); // Payees tab if (!allItemsSelected(m_ui->m_payeesView) || m_ui->m_emptyPayeesButton->isChecked()) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Payees"); } m_ui->m_payeesView->setEnabled(!m_ui->m_emptyPayeesButton->isChecked()); // Details tab if (m_ui->m_typeBox->currentIndex() != 0 || m_ui->m_stateBox->currentIndex() != 0 || m_ui->m_validityBox->currentIndex() != 0 || (m_ui->m_nrButton->isChecked() && m_ui->m_nrEdit->text().length() != 0) || (m_ui->m_nrRangeButton->isChecked() && (m_ui->m_nrFromEdit->text().length() != 0 || m_ui->m_nrToEdit->text().length() != 0))) { if (!txt.isEmpty()) txt += ", "; txt += i18n("Details"); } //Show a warning about transfers if Categories are filtered - bug #1523508 if (!m_ui->m_categoriesView->allItemsSelected()) { m_ui->m_transferWarning->setText(i18n("Warning: Filtering by Category will exclude all transfers from the results.")); } else { m_ui->m_transferWarning->setText(""); } // disable the search button if no selection is made emit selectionNotEmpty(!txt.isEmpty()); if (txt.isEmpty()) { txt = i18nc("No selection", "(None)"); } m_ui->m_selectedCriteria->setText(i18n("Current selections: %1", txt)); } bool KFindTransactionDlg::allItemsSelected(const QTreeWidgetItem *item) const { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) { return false; } } return true; } bool KFindTransactionDlg::allItemsSelected(const QTreeWidget* view) const { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (!(it_v->checkState(0) == Qt::Checked && allItemsSelected(it_v))) { return false; } else { if (!allItemsSelected(it_v)) return false; } } } return true; } void KFindTransactionDlg::setupAccountsPage() { m_ui->m_accountsView->setSelectionMode(QTreeWidget::MultiSelection); AccountSet accountSet; accountSet.addAccountGroup(MyMoneyAccount::Asset); accountSet.addAccountGroup(MyMoneyAccount::Liability); //set the accountset to show closed account if the settings say so accountSet.setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts()); accountSet.load(m_ui->m_accountsView); connect(m_ui->m_accountsView, SIGNAL(stateChanged()), this, SLOT(slotUpdateSelections())); } void KFindTransactionDlg::selectAllItems(QTreeWidget* view, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); if (it_v->flags() & Qt::ItemIsUserCheckable) { it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); } selectAllSubItems(it_v, state); } slotUpdateSelections(); } void KFindTransactionDlg::selectItems(QTreeWidget* view, const QStringList& list, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); QVariant idData = it_v->data(0, ItemIdRole); if (it_v->flags() & Qt::ItemIsUserCheckable && list.contains(idData.toString())) { it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); } selectSubItems(it_v, list, state); } slotUpdateSelections(); } void KFindTransactionDlg::setupCategoriesPage() { m_ui->m_categoriesView->setSelectionMode(QTreeWidget::MultiSelection); AccountSet categorySet; categorySet.addAccountGroup(MyMoneyAccount::Income); categorySet.addAccountGroup(MyMoneyAccount::Expense); categorySet.load(m_ui->m_categoriesView); connect(m_ui->m_categoriesView, SIGNAL(stateChanged()), this, SLOT(slotUpdateSelections())); } void KFindTransactionDlg::selectAllSubItems(QTreeWidgetItem* item, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); selectAllSubItems(it_v, state); } } void KFindTransactionDlg::selectSubItems(QTreeWidgetItem* item, const QStringList& list, const bool state) { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); QVariant idData = it_v->data(0, ItemIdRole); if (list.contains(idData.toString())) it_v->setCheckState(0, state ? Qt::Checked : Qt::Unchecked); selectSubItems(it_v, list, state); } } void KFindTransactionDlg::setupAmountPage() { connect(m_ui->m_amountButton, SIGNAL(clicked()), this, SLOT(slotAmountSelected())); connect(m_ui->m_amountRangeButton, SIGNAL(clicked()), this, SLOT(slotAmountRangeSelected())); connect(m_ui->m_amountEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_amountFromEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_amountToEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); m_ui->m_amountButton->setChecked(true); slotAmountSelected(); } void KFindTransactionDlg::slotAmountSelected() { m_ui->m_amountEdit->setEnabled(true); m_ui->m_amountFromEdit->setEnabled(false); m_ui->m_amountToEdit->setEnabled(false); slotUpdateSelections(); } void KFindTransactionDlg::slotAmountRangeSelected() { m_ui->m_amountEdit->setEnabled(false); m_ui->m_amountFromEdit->setEnabled(true); m_ui->m_amountToEdit->setEnabled(true); slotUpdateSelections(); } void KFindTransactionDlg::setupPayeesPage() { m_ui->m_payeesView->setSelectionMode(QAbstractItemView::SingleSelection); m_ui->m_payeesView->header()->hide(); m_ui->m_payeesView->setAlternatingRowColors(true); loadPayees(); m_ui->m_payeesView->sortItems(0, Qt::AscendingOrder); m_ui->m_emptyPayeesButton->setCheckState(Qt::Unchecked); connect(m_ui->m_allPayeesButton, SIGNAL(clicked()), this, SLOT(slotSelectAllPayees())); connect(m_ui->m_clearPayeesButton, SIGNAL(clicked()), this, SLOT(slotDeselectAllPayees())); connect(m_ui->m_emptyPayeesButton, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_payeesView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateSelections())); } void KFindTransactionDlg::loadPayees() { MyMoneyFile* file = MyMoneyFile::instance(); QList list; QList::Iterator it_l; list = file->payeeList(); // load view for (it_l = list.begin(); it_l != list.end(); ++it_l) { QTreeWidgetItem* item = new QTreeWidgetItem(m_ui->m_payeesView); item->setText(0, (*it_l).name()); item->setData(0, ItemIdRole, (*it_l).id()); item->setCheckState(0, Qt::Checked); } } void KFindTransactionDlg::slotSelectAllPayees() { selectAllItems(m_ui->m_payeesView, true); } void KFindTransactionDlg::slotDeselectAllPayees() { selectAllItems(m_ui->m_payeesView, false); } void KFindTransactionDlg::setupTagsPage() { m_ui->m_tagsView->setSelectionMode(QAbstractItemView::SingleSelection); m_ui->m_tagsView->header()->hide(); m_ui->m_tagsView->setAlternatingRowColors(true); loadTags(); m_ui->m_tagsView->sortItems(0, Qt::AscendingOrder); m_ui->m_emptyTagsButton->setCheckState(Qt::Unchecked); connect(m_ui->m_allTagsButton, SIGNAL(clicked()), this, SLOT(slotSelectAllTags())); connect(m_ui->m_clearTagsButton, SIGNAL(clicked()), this, SLOT(slotDeselectAllTags())); connect(m_ui->m_emptyTagsButton, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_tagsView, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotUpdateSelections())); } void KFindTransactionDlg::loadTags() { MyMoneyFile* file = MyMoneyFile::instance(); QList list; QList::Iterator it_l; list = file->tagList(); // load view for (it_l = list.begin(); it_l != list.end(); ++it_l) { QTreeWidgetItem* item = new QTreeWidgetItem(m_ui->m_tagsView); item->setText(0, (*it_l).name()); item->setData(0, ItemIdRole, (*it_l).id()); item->setCheckState(0, Qt::Checked); } } void KFindTransactionDlg::slotSelectAllTags() { selectAllItems(m_ui->m_tagsView, true); } void KFindTransactionDlg::slotDeselectAllTags() { selectAllItems(m_ui->m_tagsView, false); } void KFindTransactionDlg::setupDetailsPage() { connect(m_ui->m_typeBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_stateBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_validityBox, SIGNAL(activated(int)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_nrButton, SIGNAL(clicked()), this, SLOT(slotNrSelected())); connect(m_ui->m_nrRangeButton, SIGNAL(clicked()), this, SLOT(slotNrRangeSelected())); connect(m_ui->m_nrEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_nrFromEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); connect(m_ui->m_nrToEdit, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateSelections())); m_ui->m_nrButton->setChecked(true); slotNrSelected(); } void KFindTransactionDlg::slotNrSelected() { m_ui->m_nrEdit->setEnabled(true); m_ui->m_nrFromEdit->setEnabled(false); m_ui->m_nrToEdit->setEnabled(false); slotUpdateSelections(); } void KFindTransactionDlg::slotNrRangeSelected() { m_ui->m_nrEdit->setEnabled(false); m_ui->m_nrFromEdit->setEnabled(true); m_ui->m_nrToEdit->setEnabled(true); slotUpdateSelections(); } void KFindTransactionDlg::addItemToFilter(const opTypeE op, const QString& id) { switch (op) { case addAccountToFilter: m_filter.addAccount(id); break; case addCategoryToFilter: m_filter.addCategory(id); break; case addPayeeToFilter: m_filter.addPayee(id); break; case addTagToFilter: m_filter.addTag(id); break; } } void KFindTransactionDlg::scanCheckListItems(const QTreeWidgetItem* item, const opTypeE op) { QTreeWidgetItem* it_v; for (int i = 0; i < item->childCount(); ++i) { it_v = item->child(i); QVariant idData = it_v->data(0, ItemIdRole); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (it_v->checkState(0) == Qt::Checked) addItemToFilter(op, idData.toString()); } scanCheckListItems(it_v, op); } } void KFindTransactionDlg::scanCheckListItems(const QTreeWidget* view, const opTypeE op) { QTreeWidgetItem* it_v; for (int i = 0; i < view->invisibleRootItem()->childCount(); ++i) { it_v = view->invisibleRootItem()->child(i); QVariant idData = it_v->data(0, ItemIdRole); if (it_v->flags() & Qt::ItemIsUserCheckable) { if (it_v->checkState(0) == Qt::Checked) { addItemToFilter(op, idData.toString()); } } scanCheckListItems(it_v, op); } } void KFindTransactionDlg::setupFilter() { m_filter.clear(); // Text tab if (!m_ui->m_textEdit->text().isEmpty()) { QRegExp exp(m_ui->m_textEdit->text(), m_ui->m_caseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive, !m_ui->m_regExp->isChecked() ? QRegExp::Wildcard : QRegExp::RegExp); m_filter.setTextFilter(exp, m_ui->m_textNegate->currentIndex() != 0); } // Account tab if (!m_ui->m_accountsView->allItemsSelected()) { // retrieve a list of selected accounts QStringList list; m_ui->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.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.accountType() == MyMoneyAccount::Investment) { for (it_b = acc.accountList().constBegin(); it_b != acc.accountList().constEnd(); ++it_b) { if (!list.contains(*it_b)) { missing.append(*it_b); } } } } list += missing; } m_filter.addAccount(list); } // Date tab if (m_dateRange->m_ui->m_dateRange->currentItem() != 0) { m_filter.setDateFilter(m_dateRange->m_ui->m_fromDate->date(), m_dateRange->m_ui->m_toDate->date()); } // Amount tab if ((m_ui->m_amountButton->isChecked() && m_ui->m_amountEdit->isValid())) { m_filter.setAmountFilter(m_ui->m_amountEdit->value(), m_ui->m_amountEdit->value()); } else if ((m_ui->m_amountRangeButton->isChecked() && (m_ui->m_amountFromEdit->isValid() || m_ui->m_amountToEdit->isValid()))) { MyMoneyMoney from(MyMoneyMoney::minValue), to(MyMoneyMoney::maxValue); if (m_ui->m_amountFromEdit->isValid()) from = m_ui->m_amountFromEdit->value(); if (m_ui->m_amountToEdit->isValid()) to = m_ui->m_amountToEdit->value(); m_filter.setAmountFilter(from, to); } // Categories tab if (!m_ui->m_categoriesView->allItemsSelected()) { m_filter.addCategory(m_ui->m_categoriesView->selectedItems()); } // Tags tab if (m_ui->m_emptyTagsButton->isChecked()) { m_filter.addTag(QString()); } else if (!allItemsSelected(m_ui->m_tagsView)) { scanCheckListItems(m_ui->m_tagsView, addTagToFilter); } // Payees tab if (m_ui->m_emptyPayeesButton->isChecked()) { m_filter.addPayee(QString()); } else if (!allItemsSelected(m_ui->m_payeesView)) { scanCheckListItems(m_ui->m_payeesView, addPayeeToFilter); } // Details tab if (m_ui->m_typeBox->currentIndex() != 0) m_filter.addType(m_ui->m_typeBox->currentIndex()); if (m_ui->m_stateBox->currentIndex() != 0) m_filter.addState(m_ui->m_stateBox->currentIndex()); if (m_ui->m_validityBox->currentIndex() != 0) m_filter.addValidity(m_ui->m_validityBox->currentIndex()); if (m_ui->m_nrButton->isChecked() && !m_ui->m_nrEdit->text().isEmpty()) m_filter.setNumberFilter(m_ui->m_nrEdit->text(), m_ui->m_nrEdit->text()); if (m_ui->m_nrRangeButton->isChecked() && (!m_ui->m_nrFromEdit->text().isEmpty() || !m_ui->m_nrToEdit->text().isEmpty())) { m_filter.setNumberFilter(m_ui->m_nrFromEdit->text(), m_ui->m_nrToEdit->text()); } } void KFindTransactionDlg::slotSearch() { // perform the search only if the button is enabled if (!m_ui->buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) return; // setup the filter from the dialog widgets setupFilter(); // filter is setup, now fill the register slotRefreshView(); m_ui->m_register->setFocus(); } void KFindTransactionDlg::slotRefreshView() { m_needReload = true; if (isVisible()) { loadView(); m_needReload = false; } } void KFindTransactionDlg::showEvent(QShowEvent* event) { if (m_needReload) { loadView(); m_needReload = false; } QDialog::showEvent(event); } void KFindTransactionDlg::loadView() { // setup sort order m_ui->m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear out old data m_ui->m_register->clear(); // retrieve the list from the engine MyMoneyFile::instance()->transactionList(m_transactionList, m_filter); // create the elements for the register QList >::const_iterator it; QMapuniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = MyMoneyFile::instance()->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); { // debug stuff if (split.shares().isNegative()) { payment += split.shares().abs(); } else { deposit += split.shares().abs(); } } } // add the group markers m_ui->m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_ui->m_register->sortItems(); // remove trailing and adjacent markers m_ui->m_register->removeUnwantedGroupMarkers(); // turn on the ledger lens for the register m_ui->m_register->setLedgerLensForced(); m_ui->m_register->updateRegister(true); m_ui->m_register->setFocusToTop(); m_ui->m_register->selectItem(m_ui->m_register->focusItem()); #ifdef KMM_DEBUG m_ui->m_foundText->setText(i18np("Found %1 matching transaction (D %2 / P %3 = %4)", "Found %1 matching transactions (D %2 / P %3 = %4)", splitCount, deposit.formatMoney("", 2), payment.formatMoney("", 2), (deposit - payment).formatMoney("", 2))); #else m_ui->m_foundText->setText(i18np("Found %1 matching transaction", "Found %1 matching transactions", splitCount)); #endif m_ui->m_tabWidget->setTabEnabled(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage), true); m_ui->m_tabWidget->setCurrentIndex(m_ui->m_tabWidget->indexOf(m_ui->m_resultPage)); QTimer::singleShot(10, this, SLOT(slotRightSize())); } void KFindTransactionDlg::slotRightSize() { m_ui->m_register->update(); } void KFindTransactionDlg::resizeEvent(QResizeEvent* ev) { // Columns // 1 = Date // 2 = Account // 4 = Detail // 5 = C // 6 = Payment // 7 = Deposit // don't forget the resizer QDialog::resizeEvent(ev); if (!m_ui->m_register->isVisible()) return; // resize the register int w = m_ui->m_register->contentsRect().width(); int m_debitWidth = 80; int m_creditWidth = 80; m_ui->m_register->adjustColumn(1); m_ui->m_register->adjustColumn(2); m_ui->m_register->adjustColumn(5); m_ui->m_register->setColumnWidth(6, m_debitWidth); m_ui->m_register->setColumnWidth(7, m_creditWidth); for (int i = 0; i < m_ui->m_register->columnCount(); ++i) { switch (i) { case 4: // skip the one, we want to set break; default: w -= m_ui->m_register->columnWidth(i); break; } } m_ui->m_register->setColumnWidth(4, w); } void KFindTransactionDlg::slotSelectTransaction() { QList list = m_ui->m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) { emit transactionSelected(t->split().accountId(), t->transaction().id()); hide(); } } } bool KFindTransactionDlg::eventFilter(QObject* o, QEvent* e) { bool rc = false; if (o->isWidgetType()) { if (e->type() == QEvent::KeyPress) { const QWidget* w = dynamic_cast(o); QKeyEvent *k = static_cast(e); if (w == m_ui->m_register) { switch (k->key()) { default: break; case Qt::Key_Return: case Qt::Key_Enter: rc = true; slotSelectTransaction(); break; } } } } return rc; } void KFindTransactionDlg::slotShowHelp() { QString anchor = m_helpAnchor[m_ui->m_criteriaTab->currentWidget()]; if (anchor.isEmpty()) anchor = QString("details.search"); KHelpClient::invokeHelp(anchor); } void KFindTransactionDlg::slotSortOptions() { QPointer dlg = new KSortOptionDlg(this); dlg->setSortOption(KMyMoneyGlobalSettings::sortSearchView(), QString()); dlg->hideDefaultButton(); if (dlg->exec() == QDialog::Accepted) { QString sortOrder = dlg->sortOption(); if (sortOrder != KMyMoneyGlobalSettings::sortSearchView()) { KMyMoneyGlobalSettings::setSortSearchView(sortOrder); slotRefreshView(); } } delete dlg; } diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 18aa12b65..fffa9efd0 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7956 +1,7956 @@ /*************************************************************************** kmymoney.cpp ------------------- copyright : (C) 2000 by Michael Edwardes (C) 2007 by Thomas Baumgart ****************************************************************************/ /*************************************************************************** * * * 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 +#include "config-kmymoney.h" #include "kmymoney.h" // for _getpid #ifdef Q_OS_WIN32 //krazy:exclude=cpp #include #else #if HAVE_SYS_TYPES_H #include #endif #if HAVE_UNISTD_H #include #endif #endif // ---------------------------------------------------------------------------- // Std C++ / STL Includes #include #include #include #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // only for performance tests #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kexportdlg.h" #include "dialogs/kimportdlg.h" #include "dialogs/mymoneyqifprofileeditor.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/knewloanwizard.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/ktagreassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "dialogs/kmergetransactionsdlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/transactionmatcher.h" #include "wizards/newuserwizard/knewuserwizard.h" #include "wizards/newaccountwizard/knewaccountwizard.h" #include "dialogs/kbalancewarning.h" #include "widgets/onlinejobmessagesview.h" #include "widgets/kmymoneymvccombo.h" #include "widgets/kmymoneycompletion.h" #include "views/kmymoneyview.h" #include "views/konlinejoboutbox.h" #include "models/onlinejobmessagesmodel.h" #include "mymoney/mymoneyutils.h" #include "mymoney/mymoneystatement.h" #include "mymoney/storage/mymoneystoragedump.h" #include "mymoney/mymoneyforecast.h" #include "mymoney/onlinejob.h" #include "mymoney/onlinetransfer.h" #include "mymoney/onlinejobadministration.h" #include "converter/mymoneyqifwriter.h" #include "converter/mymoneyqifreader.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include #include #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" static constexpr char recoveryKeyId[] = "59B0F826D2B08440"; // define the default period to warn about an expiring recoverkey to 30 days // but allows to override this setting during build time #ifndef RECOVER_KEY_EXPIRATION_WARNING #define RECOVER_KEY_EXPIRATION_WARNING 30 #endif enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; class KMyMoneyApp::Private { public: Private(KMyMoneyApp *app) : q(app), m_ft(0), m_moveToAccountSelector(0), m_statementXMLindex(0), m_balanceWarning(0), m_collectingStatements(false), m_pluginLoader(0), m_myMoneyView(0), m_progressBar(0), m_qifReader(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef KF5Holidays_FOUND m_holidayRegion(0), #endif m_applicationIsReady(true) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; kMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; KMyMoneyPlugin::PluginLoader* m_pluginLoader; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief List of all plugged plugins * * The key is the file name of the plugin. */ QMap m_plugins; /** * @brief List of plugged importer plugins * * The key is the objectName of the plugin. */ QMap m_importerPlugins; /** * @brief List of plugged online plugins * * The key is the objectName of the plugin. */ QMap m_onlinePlugins; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; QProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. QUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; MyMoneyQifReader* m_qifReader; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; QObject* m_pluginInterface; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneyAccount m_selectedInvestment; MyMoneyInstitution m_selectedInstitution; MyMoneySchedule m_selectedSchedule; MyMoneySecurity m_selectedCurrency; MyMoneyPrice m_selectedPrice; QList m_selectedPayees; QList m_selectedTags; QList m_selectedBudgets; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; QPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; // methods void consistencyCheck(bool alwaysDisplayResults); void setCustomColors(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setCustomColors(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); QFrame* frame = new QFrame(this); frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); if (KMyMoneySettings::iconsTheme().compare(QLatin1Literal("system")) != 0) QIcon::setThemeName(KMyMoneySettings::iconsTheme()); initStatusBar(); initActions(); initDynamicMenus(); d->m_myMoneyView = new KMyMoneyView(frame); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, SIGNAL(aboutToChangeView()), this, SLOT(slotResetSelections())); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connectActionsAndViews(); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); loadPlugins(); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited())); // force to show the home page if the file is closed connect(action("view_show_transaction_detail"), SIGNAL(toggled(bool)), d->m_myMoneyView, SLOT(slotShowTransactionDetail(bool))); d->m_backupState = BACKUP_IDLE; // TODO: port kf5 int weekStart = 1;//KLocale::global()->workingWeekStartDay(); int weekEnd = 7;//KLocale::global()->workingWeekEndDay(); bool startFirst = (weekStart < weekEnd); for (int i = 0; i < 8; i++) { if (startFirst) d->m_processingDays.setBit(i, (i >= weekStart && i <= weekEnd)); else d->m_processingDays.setBit(i, (i >= weekStart || i <= weekEnd)); } d->m_autoSaveTimer = new QTimer(this); d->m_progressTimer = new QTimer(this); connect(d->m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); connect(d->m_progressTimer, SIGNAL(timeout()), this, SLOT(slotStatusProgressDone())); // make sure, we get a note when the engine changes state connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotDataChanged())); // make sure we have a balance warning object d->m_balanceWarning = new KBalanceWarning(this); // setup the initial configuration slotUpdateConfiguration(); // kickstart date change timer slotDateChanged(); connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties())); } KMyMoneyApp::~KMyMoneyApp() { delete d->m_searchDlg; delete d->m_qifReader; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } void KMyMoneyApp::createTransactionMoveMenu() { if (!d->m_moveToAccountSelector) { QWidget* w = factory()->container("transaction_move_menu", this); QMenu *menu = dynamic_cast(w); if (menu) { QWidgetAction *accountSelectorAction = new QWidgetAction(menu); d->m_moveToAccountSelector = new kMyMoneyAccountSelector(menu, 0, false); d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector); menu->addAction(accountSelectorAction); connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*))); connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString))); } } } void KMyMoneyApp::initDynamicMenus() { connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu())); } void KMyMoneyApp::initActions() { // ************* // The File menu // ************* actionCollection()->addAction(KStandardAction::New, this, SLOT(slotFileNew())); actionCollection()->addAction(KStandardAction::Open, this, SLOT(slotFileOpen())); d->m_recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); actionCollection()->addAction(KStandardAction::Save, this, SLOT(slotFileSave())); actionCollection()->addAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); actionCollection()->addAction(KStandardAction::Close, this, SLOT(slotFileClose())); actionCollection()->addAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionCollection()->addAction(KStandardAction::Print, this, SLOT(slotPrintView())); QAction *open_database = actionCollection()->addAction("open_database"); open_database->setText(i18n("Open database...")); connect(open_database, SIGNAL(triggered()), this, SLOT(slotOpenDatabase())); QAction *saveas_database = actionCollection()->addAction("saveas_database"); saveas_database->setText(i18n("Save as database...")); saveas_database->setIcon(QIcon::fromTheme("svn-update")); connect(saveas_database, SIGNAL(triggered()), this, SLOT(slotSaveAsDatabase())); QAction *file_backup = actionCollection()->addAction("file_backup"); file_backup->setText(i18n("Backup...")); file_backup->setIcon(QIcon::fromTheme(QStringLiteral("utilities-file-archiver"), QIcon::fromTheme(QStringLiteral("package")))); connect(file_backup, SIGNAL(triggered()), this, SLOT(slotFileBackup())); QAction *file_import_qif = actionCollection()->addAction("file_import_qif"); file_import_qif->setText(i18n("QIF...")); connect(file_import_qif, SIGNAL(triggered()), this, SLOT(slotQifImport())); QAction *file_import_gnc = actionCollection()->addAction("file_import_gnc"); file_import_gnc->setText(i18n("GnuCash...")); connect(file_import_gnc, SIGNAL(triggered()), this, SLOT(slotGncImport())); QAction *file_import_statement = actionCollection()->addAction("file_import_statement"); file_import_statement->setText(i18n("Statement file...")); connect(file_import_statement, SIGNAL(triggered()), this, SLOT(slotStatementImport())); QAction *file_import_template = actionCollection()->addAction("file_import_template"); file_import_template->setText(i18n("Account Template...")); connect(file_import_template, SIGNAL(triggered()), this, SLOT(slotLoadAccountTemplates())); QAction *file_export_template = actionCollection()->addAction("file_export_template"); file_export_template->setText(i18n("Account Template...")); connect(file_export_template, SIGNAL(triggered()), this, SLOT(slotSaveAccountTemplates())); QAction *file_export_qif = actionCollection()->addAction("file_export_qif"); file_export_qif->setText(i18n("QIF...")); connect(file_export_qif, SIGNAL(triggered()), this, SLOT(slotQifExport())); QAction *view_personal_data = actionCollection()->addAction("view_personal_data"); view_personal_data->setText(i18n("Personal Data...")); view_personal_data->setIcon(QIcon::fromTheme(QStringLiteral("user-properties"), QIcon::fromTheme(QStringLiteral("system-users")))); connect(view_personal_data, SIGNAL(triggered()), this, SLOT(slotFileViewPersonal())); #ifdef KMM_DEBUG QAction *file_dump = actionCollection()->addAction("file_dump"); file_dump->setText(i18n("Dump Memory")); connect(file_dump, SIGNAL(triggered()), this, SLOT(slotFileFileInfo())); #endif QAction *view_file_info = actionCollection()->addAction("view_file_info"); view_file_info->setText(i18n("File-Information...")); view_file_info->setIcon(QIcon::fromTheme("document-properties")); connect(view_file_info, SIGNAL(triggered()), this, SLOT(slotFileInfoDialog())); // ************* // The Edit menu // ************* QAction *edit_find_transaction = actionCollection()->addAction("edit_find_transaction"); edit_find_transaction->setText(i18n("Find transaction...")); edit_find_transaction->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "edit-find")); actionCollection()->setDefaultShortcut(edit_find_transaction, QKeySequence("Ctrl+F")); connect(edit_find_transaction, SIGNAL(triggered()), this, SLOT(slotFindTransaction())); // ************* // The View menu // ************* KToggleAction *view_show_transaction_detail = actionCollection()->add("view_show_transaction_detail"); view_show_transaction_detail->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"), QIcon::fromTheme(QStringLiteral("edit-find")))); view_show_transaction_detail->setText(i18n("Show Transaction Detail")); actionCollection()->setDefaultShortcut(view_show_transaction_detail, QKeySequence("Ctrl+T")); KToggleAction *view_hide_reconciled_transactions = actionCollection()->add("view_hide_reconciled_transactions"); view_hide_reconciled_transactions->setText(i18n("Hide reconciled transactions")); if (QIcon::hasThemeIcon(QStringLiteral("hide-reconciled"))) view_hide_reconciled_transactions->setIcon(QIcon::fromTheme(QStringLiteral("hide-reconciled"))); else view_hide_reconciled_transactions->setIcon(KMyMoneyUtils::overlayIcon("merge", "view-close")); actionCollection()->setDefaultShortcut(view_hide_reconciled_transactions, QKeySequence("Ctrl+R")); connect(view_hide_reconciled_transactions, SIGNAL(triggered()), this, SLOT(slotHideReconciledTransactions())); KToggleAction *view_hide_unused_categories = actionCollection()->add("view_hide_unused_categories"); view_hide_unused_categories->setText(i18n("Hide unused categories")); if (QIcon::hasThemeIcon(QStringLiteral("hide-categories"))) view_hide_unused_categories->setIcon(QIcon::fromTheme(QStringLiteral("hide-categories"))); else view_hide_unused_categories->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "view-close")); actionCollection()->setDefaultShortcut(view_hide_unused_categories, QKeySequence("Ctrl+U")); connect(view_hide_unused_categories, SIGNAL(triggered()), this, SLOT(slotHideUnusedCategories())); KToggleAction *view_show_all_accounts = actionCollection()->add("view_show_all_accounts"); view_show_all_accounts->setText(i18n("Show all accounts")); actionCollection()->setDefaultShortcut(view_show_all_accounts, QKeySequence("Ctrl+Shift+A")); connect(view_show_all_accounts, SIGNAL(triggered()), this, SLOT(slotShowAllAccounts())); // ********************* // The institutions menu // ********************* QAction *institution_new = actionCollection()->addAction("institution_new"); institution_new->setText(i18n("New institution...")); institution_new->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "list-add", Qt::TopRightCorner)); connect(institution_new, SIGNAL(triggered()), this, SLOT(slotInstitutionNew())); QAction *institution_edit = actionCollection()->addAction("institution_edit"); institution_edit->setText(i18n("Edit institution...")); institution_edit->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "document-edit")); connect(institution_edit, SIGNAL(triggered()), this, SLOT(slotInstitutionEdit())); QAction *institution_delete = actionCollection()->addAction("institution_delete"); institution_delete->setText(i18n("Delete institution...")); institution_delete->setIcon(KMyMoneyUtils::overlayIcon("view-bank", "edit-delete")); connect(institution_delete, SIGNAL(triggered()), this, SLOT(slotInstitutionDelete())); // ***************** // The accounts menu // ***************** QAction *account_new = actionCollection()->addAction("account_new"); account_new->setText(i18n("New account...")); account_new->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "list-add", Qt::TopRightCorner)); connect(account_new, SIGNAL(triggered()), this, SLOT(slotAccountNew())); // note : action "category_new" is included in this menu but defined below QAction *account_open = actionCollection()->addAction("account_open"); account_open->setText(i18n("Open ledger")); account_open->setIcon(QIcon::fromTheme("view-financial-list", QIcon::fromTheme(QStringLiteral("ledger")))); connect(account_open, SIGNAL(triggered()), this, SLOT(slotAccountOpen())); QAction *account_reconcile = actionCollection()->addAction("account_reconcile"); account_reconcile->setText(i18n("Reconcile...")); account_reconcile->setIcon(QIcon::fromTheme(QStringLiteral("merge"), QIcon::fromTheme(QStringLiteral("reconcile")))); actionCollection()->setDefaultShortcut(account_reconcile, QKeySequence("Ctrl+Shift+R")); connect(account_reconcile, SIGNAL(triggered()), this, SLOT(slotAccountReconcileStart())); QAction *account_reconcile_finish = actionCollection()->addAction("account_reconcile_finish"); account_reconcile_finish->setText(i18nc("Finish reconciliation", "Finish")); account_reconcile_finish->setIcon(KMyMoneyUtils::overlayIcon("merge", "dialog-ok")); connect(account_reconcile_finish, SIGNAL(triggered()), this, SLOT(slotAccountReconcileFinish())); QAction *account_reconcile_postpone = actionCollection()->addAction("account_reconcile_postpone"); account_reconcile_postpone->setText(i18n("Postpone reconciliation")); account_reconcile_postpone->setIcon(QIcon::fromTheme("media-playback-pause")); connect(account_reconcile_postpone, SIGNAL(triggered()), this, SLOT(slotAccountReconcilePostpone())); QAction *account_edit = actionCollection()->addAction("account_edit"); account_edit->setText(i18n("Edit account...")); account_edit->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "document-edit")); connect(account_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); QAction *account_delete = actionCollection()->addAction("account_delete"); account_delete->setText(i18n("Delete account...")); account_delete->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "edit-delete")); connect(account_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); QAction *account_close = actionCollection()->addAction("account_close"); account_close->setText(i18n("Close account")); account_close->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "dialog-close")); connect(account_close, SIGNAL(triggered()), this, SLOT(slotAccountClose())); QAction *account_reopen = actionCollection()->addAction("account_reopen"); account_reopen->setText(i18n("Reopen account")); account_reopen->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "dialog-ok")); connect(account_reopen, SIGNAL(triggered()), this, SLOT(slotAccountReopen())); QAction *account_transaction_report = actionCollection()->addAction("account_transaction_report"); account_transaction_report->setText(i18n("Transaction report")); account_transaction_report->setIcon(QIcon::fromTheme(QStringLiteral("view-financial-list"), QIcon::fromTheme(QStringLiteral("ledger")))); connect(account_transaction_report, SIGNAL(triggered()), this, SLOT(slotAccountTransactionReport())); QAction *account_chart = actionCollection()->addAction("account_chart"); account_chart->setText(i18n("Show balance chart...")); account_chart->setIcon(QIcon::fromTheme(QStringLiteral("office-chart-line"), QIcon::fromTheme(QStringLiteral("report-line"), QIcon::fromTheme(QStringLiteral("account-types-investments"))))); connect(account_chart, SIGNAL(triggered()), this, SLOT(slotAccountChart())); QAction *account_online_map = actionCollection()->addAction("account_online_map"); account_online_map->setText(i18n("Map to online account...")); account_online_map->setIcon(QIcon::fromTheme("news-subscribe")); connect(account_online_map, SIGNAL(triggered()), this, SLOT(slotAccountMapOnline())); QAction *account_online_unmap = actionCollection()->addAction("account_online_unmap"); account_online_unmap->setText(i18n("Unmap account...")); account_online_unmap->setIcon(QIcon::fromTheme("news-unsubscribe")); connect(account_online_unmap, SIGNAL(triggered()), this, SLOT(slotAccountUnmapOnline())); KActionMenu* menu = new KActionMenu(KMyMoneyUtils::overlayIcon("view-bank-account", "download"), i18nc("Update online accounts menu", "Update"), this); actionCollection()->addAction("account_online_update_menu", menu); // activating the menu button is the same as selecting the current account connect(menu, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); QAction *account_online_update = actionCollection()->addAction("account_online_update"); account_online_update->setText(i18n("Update account...")); account_online_update->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "download")); connect(account_online_update, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); menu->addAction(account_online_update); QAction *account_online_update_all = actionCollection()->addAction("account_online_update_all"); account_online_update_all->setText(i18n("Update all accounts...")); account_online_update_all->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "download")); connect(account_online_update_all, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnlineAll())); menu->addAction(account_online_update_all); QAction *account_online_transfer = actionCollection()->addAction("account_online_new_credit_transfer"); account_online_transfer->setText(i18n("New credit transfer")); account_online_transfer->setIcon(KMyMoneyUtils::overlayIcon("view-bank-account", "mail-message-new")); connect(account_online_transfer, SIGNAL(triggered()), this, SLOT(slotNewOnlineTransfer())); connect(onlineJobAdministration::instance(), SIGNAL(canSendCreditTransferChanged(bool)), account_online_transfer, SLOT(setEnabled(bool))); // ******************* // The categories menu // ******************* QAction *category_new = actionCollection()->addAction("category_new"); category_new->setText(i18n("New category...")); category_new->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "list-add", Qt::TopRightCorner)); connect(category_new, SIGNAL(triggered()), this, SLOT(slotCategoryNew())); QAction *category_edit = actionCollection()->addAction("category_edit"); category_edit->setText(i18n("Edit category...")); category_edit->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "document-edit")); connect(category_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); QAction *category_delete = actionCollection()->addAction("category_delete"); category_delete->setText(i18n("Delete category...")); category_delete->setIcon(KMyMoneyUtils::overlayIcon("view-financial-categories", "edit-delete")); connect(category_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); // ************** // The tools menu // ************** QAction *tools_qif_editor = actionCollection()->addAction("tools_qif_editor"); tools_qif_editor->setText(i18n("QIF Profile Editor...")); tools_qif_editor->setIcon(QIcon::fromTheme("document-properties")); connect(tools_qif_editor, SIGNAL(triggered()), this, SLOT(slotQifProfileEditor())); QAction *tools_currency_editor = actionCollection()->addAction("tools_currency_editor"); tools_currency_editor->setText(i18n("Currencies...")); tools_currency_editor->setIcon(QIcon::fromTheme("view-currency-list")); connect(tools_currency_editor, SIGNAL(triggered()), this, SLOT(slotCurrencyDialog())); QAction *tools_price_editor = actionCollection()->addAction("tools_price_editor"); tools_price_editor->setText(i18n("Prices...")); connect(tools_price_editor, SIGNAL(triggered()), this, SLOT(slotPriceDialog())); QAction *tools_update_prices = actionCollection()->addAction("tools_update_prices"); tools_update_prices->setText(i18n("Update Stock and Currency Prices...")); tools_update_prices->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "download")); connect(tools_update_prices, SIGNAL(triggered()), this, SLOT(slotEquityPriceUpdate())); QAction *tools_consistency_check = actionCollection()->addAction("tools_consistency_check"); tools_consistency_check->setText(i18n("Consistency Check")); connect(tools_consistency_check, SIGNAL(triggered()), this, SLOT(slotFileConsistencyCheck())); QAction *tools_performancetest = actionCollection()->addAction("tools_performancetest"); tools_performancetest->setText(i18n("Performance-Test")); tools_performancetest->setIcon(QIcon::fromTheme("fork")); connect(tools_performancetest, SIGNAL(triggered()), this, SLOT(slotPerformanceTest())); QAction *tools_generate_sql = actionCollection()->addAction("tools_generate_sql"); tools_generate_sql->setText(i18n("Generate Database SQL")); connect(tools_generate_sql, SIGNAL(triggered()), this, SLOT(slotGenerateSql())); QAction *tools_kcalc = actionCollection()->addAction("tools_kcalc"); tools_kcalc->setText(i18n("Calculator...")); tools_kcalc->setIcon(QIcon::fromTheme("accessories-calculator")); connect(tools_kcalc, SIGNAL(triggered()), this, SLOT(slotToolsStartKCalc())); // ***************** // The settings menu // ***************** actionCollection()->addAction(KStandardAction::Preferences, this, SLOT(slotSettings())); QAction *settings_enable_messages = actionCollection()->addAction("settings_enable_messages"); settings_enable_messages->setText(i18n("Enable all messages")); connect(settings_enable_messages, SIGNAL(triggered()), this, SLOT(slotEnableMessages())); QAction *settings_language = actionCollection()->addAction("settings_language"); settings_language->setText(i18n("KDE language settings...")); connect(settings_language, SIGNAL(triggered()), this, SLOT(slotKDELanguageSettings())); // ************* // The help menu // ************* QAction *help_show_tip = actionCollection()->addAction("help_show_tip"); help_show_tip->setText(i18n("&Show tip of the day")); help_show_tip->setIcon(QIcon::fromTheme(QStringLiteral("ktip"), QIcon::fromTheme(QStringLiteral("info")))); connect(help_show_tip, SIGNAL(triggered()), this, SLOT(slotShowTipOfTheDay())); // *************************** // Actions w/o main menu entry // *************************** QAction *transaction_new = actionCollection()->addAction("transaction_new"); transaction_new->setText(i18nc("New transaction button", "New")); transaction_new->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "list-add", Qt::TopRightCorner)); actionCollection()->setDefaultShortcut(transaction_new, QKeySequence(Qt::CTRL | Qt::Key_Insert)); connect(transaction_new, SIGNAL(triggered()), this, SLOT(slotTransactionsNew())); // we use Return as the same shortcut for Edit and Enter. Therefore, we don't allow // to change them (The standard KDE dialog complains anyway if you want to assign // the same shortcut to two actions) QAction *transaction_edit = actionCollection()->addAction("transaction_edit"); transaction_edit->setText(i18nc("Edit transaction button", "Edit")); transaction_edit->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "document-edit")); connect(transaction_edit, SIGNAL(triggered()), this, SLOT(slotTransactionsEdit())); QAction *transaction_enter = actionCollection()->addAction("transaction_enter"); transaction_enter->setText(i18nc("Enter transaction", "Enter")); transaction_enter->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok"), QIcon::fromTheme(QStringLiteral("finish")))); connect(transaction_enter, SIGNAL(triggered()), this, SLOT(slotTransactionsEnter())); QAction *transaction_editsplits = actionCollection()->addAction("transaction_editsplits"); transaction_editsplits->setText(i18nc("Edit split button", "Edit splits")); transaction_editsplits->setIcon(QIcon::fromTheme(QStringLiteral("split"), QIcon::fromTheme(QStringLiteral("transaction-split")))); connect(transaction_editsplits, SIGNAL(triggered()), this, SLOT(slotTransactionsEditSplits())); QAction *transaction_cancel = actionCollection()->addAction("transaction_cancel"); transaction_cancel->setText(i18nc("Cancel transaction edit", "Cancel")); transaction_cancel->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"), QIcon::fromTheme(QStringLiteral("stop")))); connect(transaction_cancel, SIGNAL(triggered()), this, SLOT(slotTransactionsCancel())); QAction *transaction_delete = actionCollection()->addAction("transaction_delete"); transaction_delete->setText(i18nc("Delete transaction", "Delete")); transaction_delete->setIcon(QIcon::fromTheme("edit-delete")); connect(transaction_delete, SIGNAL(triggered()), this, SLOT(slotTransactionsDelete())); QAction *transaction_duplicate = actionCollection()->addAction("transaction_duplicate"); transaction_duplicate->setText(i18nc("Duplicate transaction", "Duplicate")); transaction_duplicate->setIcon(QIcon::fromTheme("edit-copy")); connect(transaction_duplicate, SIGNAL(triggered()), this, SLOT(slotTransactionDuplicate())); QAction *transaction_match = actionCollection()->addAction("transaction_match"); transaction_match->setText(i18nc("Button text for match transaction", "Match")); transaction_match->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "document-import")); connect(transaction_match, SIGNAL(triggered()), this, SLOT(slotTransactionMatch())); QAction *transaction_accept = actionCollection()->addAction("transaction_accept"); transaction_accept->setText(i18nc("Accept 'imported' and 'matched' transaction", "Accept")); transaction_accept->setIcon(KMyMoneyUtils::overlayIcon("view-financial-transfer", "dialog-ok-apply")); connect(transaction_accept, SIGNAL(triggered()), this, SLOT(slotTransactionsAccept())); QAction *transaction_mark_toggle = actionCollection()->addAction("transaction_mark_toggle"); transaction_mark_toggle->setText(i18nc("Toggle reconciliation flag", "Toggle")); actionCollection()->setDefaultShortcut(transaction_mark_toggle, QKeySequence("Ctrl+Space")); connect(transaction_mark_toggle, SIGNAL(triggered()), this, SLOT(slotToggleReconciliationFlag())); QAction *transaction_mark_cleared = actionCollection()->addAction("transaction_mark_cleared"); transaction_mark_cleared->setText(i18nc("Mark transaction cleared", "Cleared")); actionCollection()->setDefaultShortcut(transaction_mark_cleared, QKeySequence("Ctrl+Alt+Space")); connect(transaction_mark_cleared, SIGNAL(triggered()), this, SLOT(slotMarkTransactionCleared())); QAction *transaction_mark_reconciled = actionCollection()->addAction("transaction_mark_reconciled"); transaction_mark_reconciled->setText(i18nc("Mark transaction reconciled", "Reconciled")); actionCollection()->setDefaultShortcut(transaction_mark_reconciled, QKeySequence("Ctrl+Shift+Space")); connect(transaction_mark_reconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionReconciled())); QAction *transaction_mark_notreconciled = actionCollection()->addAction("transaction_mark_notreconciled"); transaction_mark_notreconciled->setText(i18nc("Mark transaction not reconciled", "Not reconciled")); connect(transaction_mark_notreconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionNotReconciled())); QAction *transaction_select_all = actionCollection()->addAction("transaction_select_all"); transaction_select_all->setText(i18nc("Select all transactions", "Select all")); actionCollection()->setDefaultShortcut(transaction_select_all, QKeySequence("Ctrl+A")); connect(transaction_select_all, SIGNAL(triggered()), this, SIGNAL(selectAllTransactions())); QAction *transaction_goto_account = actionCollection()->addAction("transaction_goto_account"); transaction_goto_account->setText(i18n("Go to account")); transaction_goto_account->setIcon(QIcon::fromTheme("go-jump")); connect(transaction_goto_account, SIGNAL(triggered()), this, SLOT(slotTransactionGotoAccount())); QAction *transaction_goto_payee = actionCollection()->addAction("transaction_goto_payee"); transaction_goto_payee->setText(i18n("Go to payee")); transaction_goto_payee->setIcon(QIcon::fromTheme("go-jump")); connect(transaction_goto_payee, SIGNAL(triggered()), this, SLOT(slotTransactionGotoPayee())); QAction *transaction_create_schedule = actionCollection()->addAction("transaction_create_schedule"); transaction_create_schedule->setText(i18n("Create scheduled transaction...")); transaction_create_schedule->setIcon(QIcon::fromTheme("appointment-new")); connect(transaction_create_schedule, SIGNAL(triggered()), this, SLOT(slotTransactionCreateSchedule())); QAction *transaction_assign_number = actionCollection()->addAction("transaction_assign_number"); transaction_assign_number->setText(i18n("Assign next number")); actionCollection()->setDefaultShortcut(transaction_assign_number, QKeySequence("Ctrl+Shift+N")); connect(transaction_assign_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNumber())); QAction *transaction_combine = actionCollection()->addAction("transaction_combine"); transaction_combine->setText(i18nc("Combine transactions", "Combine")); connect(transaction_combine, SIGNAL(triggered()), this, SLOT(slotTransactionCombine())); QAction *transaction_copy_splits = actionCollection()->addAction("transaction_copy_splits"); transaction_copy_splits->setText(i18n("Copy splits")); connect(transaction_copy_splits, SIGNAL(triggered()), this, SLOT(slotTransactionCopySplits())); //Investment QAction *investment_new = actionCollection()->addAction("investment_new"); investment_new->setText(i18n("New investment...")); investment_new->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "list-add", Qt::TopRightCorner)); connect(investment_new, SIGNAL(triggered()), this, SLOT(slotInvestmentNew())); QAction *investment_edit = actionCollection()->addAction("investment_edit"); investment_edit->setText(i18n("Edit investment...")); investment_edit->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "document-edit")); connect(investment_edit, SIGNAL(triggered()), this, SLOT(slotInvestmentEdit())); QAction *investment_delete = actionCollection()->addAction("investment_delete"); investment_delete->setText(i18n("Delete investment...")); investment_delete->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "edit-delete")); connect(investment_delete, SIGNAL(triggered()), this, SLOT(slotInvestmentDelete())); QAction *investment_online_price_update = actionCollection()->addAction("investment_online_price_update"); investment_online_price_update->setText(i18n("Online price update...")); investment_online_price_update->setIcon(KMyMoneyUtils::overlayIcon("view-investment", "download")); connect(investment_online_price_update, SIGNAL(triggered()), this, SLOT(slotOnlinePriceUpdate())); QAction *investment_manual_price_update = actionCollection()->addAction("investment_manual_price_update"); investment_manual_price_update->setText(i18n("Manual price update...")); connect(investment_manual_price_update, SIGNAL(triggered()), this, SLOT(slotManualPriceUpdate())); //Schedule QAction *schedule_new = actionCollection()->addAction("schedule_new"); schedule_new->setText(i18n("New scheduled transaction")); schedule_new->setIcon(QIcon::fromTheme("appointment-new")); connect(schedule_new, SIGNAL(triggered()), this, SLOT(slotScheduleNew())); QAction *schedule_edit = actionCollection()->addAction("schedule_edit"); schedule_edit->setText(i18n("Edit scheduled transaction")); schedule_edit->setIcon(QIcon::fromTheme("document-edit")); connect(schedule_edit, SIGNAL(triggered()), this, SLOT(slotScheduleEdit())); QAction *schedule_delete = actionCollection()->addAction("schedule_delete"); schedule_delete->setText(i18n("Delete scheduled transaction")); schedule_delete->setIcon(QIcon::fromTheme("edit-delete")); connect(schedule_delete, SIGNAL(triggered()), this, SLOT(slotScheduleDelete())); QAction *schedule_duplicate = actionCollection()->addAction("schedule_duplicate"); schedule_duplicate->setText(i18n("Duplicate scheduled transaction")); schedule_duplicate->setIcon(QIcon::fromTheme("edit-copy")); connect(schedule_duplicate, SIGNAL(triggered()), this, SLOT(slotScheduleDuplicate())); QAction *schedule_enter = actionCollection()->addAction("schedule_enter"); schedule_enter->setText(i18n("Enter next transaction...")); schedule_enter->setIcon(QIcon::fromTheme("key-enter")); connect(schedule_enter, SIGNAL(triggered()), this, SLOT(slotScheduleEnter())); QAction *schedule_skip = actionCollection()->addAction("schedule_skip"); schedule_skip->setText(i18n("Skip next transaction...")); schedule_skip->setIcon(QIcon::fromTheme("media-seek-forward")); connect(schedule_skip, SIGNAL(triggered()), this, SLOT(slotScheduleSkip())); //Payees QAction *payee_new = actionCollection()->addAction("payee_new"); payee_new->setText(i18n("New payee")); payee_new->setIcon(QIcon::fromTheme("list-add-user")); connect(payee_new, SIGNAL(triggered()), this, SLOT(slotPayeeNew())); QAction *payee_rename = actionCollection()->addAction("payee_rename"); payee_rename->setText(i18n("Rename payee")); payee_rename->setIcon(QIcon::fromTheme("user-properties")); connect(payee_rename, SIGNAL(triggered()), this, SIGNAL(payeeRename())); QAction *payee_delete = actionCollection()->addAction("payee_delete"); payee_delete->setText(i18n("Delete payee")); payee_delete->setIcon(QIcon::fromTheme("list-remove-user")); connect(payee_delete, SIGNAL(triggered()), this, SLOT(slotPayeeDelete())); QAction *payee_merge = actionCollection()->addAction("payee_merge"); payee_merge->setText(i18n("Merge payees")); payee_merge->setIcon(QIcon::fromTheme("merge")); connect(payee_merge, SIGNAL(triggered()), this, SLOT(slotPayeeMerge())); //Tags QAction *tag_new = actionCollection()->addAction("tag_new"); tag_new->setText(i18n("New tag")); tag_new->setIcon(QIcon::fromTheme("list-add-tag")); connect(tag_new, SIGNAL(triggered()), this, SLOT(slotTagNew())); QAction *tag_rename = actionCollection()->addAction("tag_rename"); tag_rename->setText(i18n("Rename tag")); tag_rename->setIcon(QIcon::fromTheme("tag-rename")); connect(tag_rename, SIGNAL(triggered()), this, SIGNAL(tagRename())); QAction *tag_delete = actionCollection()->addAction("tag_delete"); tag_delete->setText(i18n("Delete tag")); tag_delete->setIcon(QIcon::fromTheme("list-remove-tag")); connect(tag_delete, SIGNAL(triggered()), this, SLOT(slotTagDelete())); //Budget QAction *budget_new = actionCollection()->addAction("budget_new"); budget_new->setText(i18n("New budget")); budget_new->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "list-add", Qt::TopRightCorner)); connect(budget_new, SIGNAL(triggered()), this, SLOT(slotBudgetNew())); QAction *budget_rename = actionCollection()->addAction("budget_rename"); budget_rename->setText(i18n("Rename budget")); budget_rename->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "document-edit")); connect(budget_rename, SIGNAL(triggered()), this, SIGNAL(budgetRename())); QAction *budget_delete = actionCollection()->addAction("budget_delete"); budget_delete->setText(i18n("Delete budget")); budget_delete->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "edit-delete")); connect(budget_delete, SIGNAL(triggered()), this, SLOT(slotBudgetDelete())); QAction *budget_copy = actionCollection()->addAction("budget_copy"); budget_copy->setText(i18n("Copy budget")); budget_copy->setIcon(KMyMoneyUtils::overlayIcon("view-time-schedule-calculus", "edit-copy")); connect(budget_copy, SIGNAL(triggered()), this, SLOT(slotBudgetCopy())); QAction *budget_change_year = actionCollection()->addAction("budget_change_year"); budget_change_year->setText(i18n("Change budget year")); budget_change_year->setIcon(QIcon::fromTheme("view-calendar")); connect(budget_change_year, SIGNAL(triggered()), this, SLOT(slotBudgetChangeYear())); QAction *budget_forecast = actionCollection()->addAction("budget_forecast"); budget_forecast->setText(i18n("Budget based on forecast")); budget_forecast->setIcon(QIcon::fromTheme("view-financial-forecast")); connect(budget_forecast, SIGNAL(triggered()), this, SLOT(slotBudgetForecast())); // Currency actions QAction *currency_new = actionCollection()->addAction("currency_new"); currency_new->setText(i18n("New currency")); currency_new->setIcon(QIcon::fromTheme("document-new")); connect(currency_new, SIGNAL(triggered()), this, SLOT(slotCurrencyNew())); QAction *currency_rename = actionCollection()->addAction("currency_rename"); currency_rename->setText(i18n("Rename currency")); currency_rename->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"), QIcon::fromTheme(QStringLiteral("text-editor")))); connect(currency_rename, SIGNAL(triggered()), this, SIGNAL(currencyRename())); QAction *currency_delete = actionCollection()->addAction("currency_delete"); currency_delete->setText(i18n("Delete currency")); currency_delete->setIcon(QIcon::fromTheme("edit-delete")); connect(currency_delete, SIGNAL(triggered()), this, SLOT(slotCurrencyDelete())); QAction *currency_setbase = actionCollection()->addAction("currency_setbase"); currency_setbase->setText(i18n("Select as base currency")); currency_setbase->setIcon(QIcon::fromTheme("kmymoney")); connect(currency_setbase, SIGNAL(triggered()), this, SLOT(slotCurrencySetBase())); //price actions QAction *price_new = actionCollection()->addAction("price_new"); price_new->setText(i18n("New price...")); price_new->setIcon(QIcon::fromTheme("document-new")); connect(price_new, SIGNAL(triggered()), this, SIGNAL(priceNew())); QAction *price_edit = actionCollection()->addAction("price_edit"); price_edit->setText(i18n("Edit price...")); price_edit->setIcon(QIcon::fromTheme("document-edit")); connect(price_edit, SIGNAL(triggered()), this, SIGNAL(priceEdit())); QAction *price_update = actionCollection()->addAction("price_update"); price_update->setText(i18n("Online Price Update...")); price_update->setIcon(KMyMoneyUtils::overlayIcon("view-currency-list", "download")); connect(price_update, SIGNAL(triggered()), this, SIGNAL(priceOnlineUpdate())); QAction *price_delete = actionCollection()->addAction("price_delete"); price_delete->setText(i18n("Delete price...")); price_delete->setIcon(QIcon::fromTheme("edit-delete")); connect(price_delete, SIGNAL(triggered()), this, SIGNAL(priceDelete())); //debug actions #ifdef KMM_DEBUG QAction *new_user_wizard = actionCollection()->addAction("new_user_wizard"); new_user_wizard->setText(i18n("Test new feature")); actionCollection()->setDefaultShortcut(new_user_wizard, QKeySequence("Ctrl+G")); connect(new_user_wizard, SIGNAL(triggered()), this, SLOT(slotNewFeature())); KToggleAction *debug_traces = actionCollection()->add("debug_traces"); debug_traces->setText(i18n("Debug Traces")); connect(debug_traces, SIGNAL(triggered()), this, SLOT(slotToggleTraces())); #endif KToggleAction *debug_timers = actionCollection()->add("debug_timers"); debug_timers->setText(i18n("Debug Timers")); connect(debug_timers, SIGNAL(triggered()), this, SLOT(slotToggleTimers())); // onlineJob actions QAction* onlineJob_delete = actionCollection()->addAction("onlinejob_delete"); onlineJob_delete->setText(i18n("Remove credit transfer")); onlineJob_delete->setIcon(QIcon::fromTheme("edit-delete")); QAction* onlineJob_edit = actionCollection()->addAction("onlinejob_edit"); onlineJob_edit->setText(i18n("Edit credit transfer")); onlineJob_edit->setIcon(QIcon::fromTheme("document-edit")); QAction* onlineJob_log = actionCollection()->addAction("onlinejob_log"); onlineJob_log->setText(i18n("Show log")); connect(onlineJob_log, SIGNAL(triggered()), this, SLOT(slotOnlineJobLog())); // ************************ // Currently unused actions // ************************ #if 0 new KToolBarPopupAction(i18n("View back"), "go-previous", 0, this, SLOT(slotShowPreviousView()), actionCollection(), "go_back"); new KToolBarPopupAction(i18n("View forward"), "go-next", 0, this, SLOT(slotShowNextView()), actionCollection(), "go_forward"); action("go_back")->setEnabled(false); action("go_forward")->setEnabled(false); #endif // Setup transaction detail switch toggleAction("view_show_transaction_detail")->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); toggleAction("view_show_all_accounts")->setChecked(false); // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); QMenu *menuContainer; menuContainer = static_cast(factory()->container("import", this)); if (QIcon::hasThemeIcon(QStringLiteral("document-import"))) menuContainer->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); else menuContainer->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); menuContainer = static_cast(factory()->container("export", this)); if (QIcon::hasThemeIcon(QStringLiteral("document-export"))) menuContainer->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); else menuContainer->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); } void KMyMoneyApp::connectActionsAndViews() { KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox(); Q_CHECK_PTR(outbox); QAction *const onlineJob_delete = actionCollection()->action("onlinejob_delete"); Q_CHECK_PTR(onlineJob_delete); connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob())); QAction *const onlineJob_edit = actionCollection()->action("onlinejob_edit"); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); QList::const_iterator it; for (it = list.begin(); it != list.end(); ++it) { std::cout << qPrintable((*it)->objectName()) << ": " << qPrintable((*it)->text()) << std::endl; } } KToggleAction* KMyMoneyApp::toggleAction(const QString& actionName) const { static KToggleAction dummyAction(QString("Dummy"), 0); KToggleAction* p = dynamic_cast(actionCollection()->action(QString(actionName.toLatin1()))); if (!p) { qWarning("ToggleAction with name '%s' not found!", qPrintable(actionName)); p = &dummyAction; } return p; } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel(statusBar()); statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar(statusBar()); statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); grp.writeEntry("Show Statusbar", toggleAction("options_show_statusbar")->isChecked()); KConfigGroup toolbarGrp = d->m_config->group("mainToolBar"); toolBar("mainToolBar")->saveSettings(toolbarGrp); d->m_recentFiles->saveEntries(d->m_config->group("Recent Files")); } void KMyMoneyApp::readOptions() { KConfigGroup grp = d->m_config->group("General Options"); toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); d->m_recentFiles->loadEntries(d->m_config->group("Recent Files")); // Startdialog is written in the settings dialog d->m_startDialog = grp.readEntry("StartDialog", true); } void KMyMoneyApp::resizeEvent(QResizeEvent* ev) { KMainWindow::resizeEvent(ev); updateCaption(true); } int KMyMoneyApp::askSaveOnClose() { int ans; if (KMyMoneyGlobalSettings::autoSaveOnClose()) { ans = KMessageBox::Yes; } else { ans = KMessageBox::warningYesNoCancel(this, i18n("The file has been changed, save it?")); } return ans; } bool KMyMoneyApp::queryClose() { if (!isReady()) return false; if (d->m_myMoneyView->dirty()) { int ans = askSaveOnClose(); if (ans == KMessageBox::Cancel) return false; else if (ans == KMessageBox::Yes) { bool saved = slotFileSave(); saveOptions(); return saved; } } if (d->m_myMoneyView->isDatabase()) slotFileClose(); // close off the database saveOptions(); return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KMyMoneyApp::slotFileInfoDialog() { QPointer dlg = new KMyMoneyFileInfoDlg(0); dlg->exec(); delete dlg; } void KMyMoneyApp::slotPerformanceTest() { // dump performance report to stderr int measurement[2]; QTime timer; MyMoneyAccount acc; qDebug("--- Starting performance tests ---"); // AccountList MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; timer.start(); for (int i = 0; i < 1000; ++i) { QList list; MyMoneyFile::instance()->accountList(list); measurement[i != 0] = timer.elapsed(); } std::cerr << "accountList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of asset account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of asset account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->asset(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "totalBalance(Asset)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // Balance of expense account(s) MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); for (int i = 0; i < 1000; ++i) { timer.start(); MyMoneyMoney result = MyMoneyFile::instance()->balance(acc.id()); measurement[i != 0] += timer.elapsed(); } std::cerr << "balance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // total balance of expense account MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; acc = MyMoneyFile::instance()->expense(); timer.start(); for (int i = 0; i < 1000; ++i) { MyMoneyMoney result = MyMoneyFile::instance()->totalBalance(acc.id()); measurement[i != 0] = timer.elapsed(); } std::cerr << "totalBalance(Expense)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 1000 << " msec" << std::endl; // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { list = MyMoneyFile::instance()->transactionList(filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList()" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } // transaction list MyMoneyFile::instance()->preloadCache(); measurement[0] = measurement[1] = 0; if (MyMoneyFile::instance()->asset().accountCount()) { MyMoneyTransactionFilter filter(MyMoneyFile::instance()->asset().accountList()[0]); filter.setDateFilter(QDate(), QDate::currentDate()); QList list; timer.start(); for (int i = 0; i < 100; ++i) { MyMoneyFile::instance()->transactionList(list, filter); measurement[i != 0] = timer.elapsed(); } std::cerr << "transactionList(list)" << std::endl; std::cerr << "First time: " << measurement[0] << " msec" << std::endl; std::cerr << "Total time: " << (measurement[0] + measurement[1]) << " msec" << std::endl; std::cerr << "Average : " << (measurement[0] + measurement[1]) / 100 << " msec" << std::endl; } MyMoneyFile::instance()->preloadCache(); } void KMyMoneyApp::slotFileNew() { KMSTATUS(i18n("Creating new document...")); slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->newFile(); d->m_fileName = QUrl(); updateCaption(); NewUserWizard::Wizard *wizard = new NewUserWizard::Wizard(); if (wizard->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // store the user info file->setUser(wizard->user()); // create and setup base currency file->addCurrency(wizard->baseCurrency()); file->setBaseCurrency(wizard->baseCurrency()); // create a possible institution MyMoneyInstitution inst = wizard->institution(); if (inst.name().length()) { file->addInstitution(inst); } // create a possible checking account MyMoneyAccount acc = wizard->account(); if (acc.name().length()) { acc.setInstitutionId(inst.id()); MyMoneyAccount asset = file->asset(); file->addAccount(acc, asset); // create possible opening balance transaction if (!wizard->openingBalance().isZero()) { file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); } } // import the account templates QList templates = wizard->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } d->m_fileName = wizard->url(); ft.commit(); KMyMoneyGlobalSettings::setFirstTimeRun(false); // FIXME This is a bit clumsy. We re-read the freshly // created file to be able to run through all the // fixup logic and then save it to keep the modified // flag off. slotFileSave(); d->m_myMoneyView->readFile(d->m_fileName); slotFileSave(); // now keep the filename in the recent files used list //KRecentFilesAction *p = dynamic_cast(action(KStandardAction::name(KStandardAction::OpenRecent))); //if(p) d->m_recentFiles->addUrl(d->m_fileName); writeLastUsedFile(d->m_fileName.url()); } catch (const MyMoneyException &) { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } if (wizard->startSettingsAfterFinished()) slotSettings(); } else { // next line required until we move all file handling out of KMyMoneyView d->m_myMoneyView->closeFile(); } delete wizard; updateCaption(); emit fileLoaded(d->m_fileName); } } QUrl KMyMoneyApp::selectFile(const QString& /*title*/, const QString& _path, const QString& mask, QFileDialog::FileMode mode, QWidget* widget) { Q_UNUSED(mask) Q_UNUSED(mode) QString path(_path); // if the path is not specified open the file dialog in the last used directory // 'kmymoney' is the keyword that identifies the last used directory in KFileDialog if (path.isEmpty()) path = "kfiledialog:///kmymoney-import"; // TODO: port KF5 QPointer dialog = new QFileDialog(this, QString(), path); //dialog->setMode(mode); QUrl url; if (dialog->exec() == QDialog::Accepted && dialog != 0) { // TODO: port KF5 //url = dialog->selectedUrl(); } // in case we have an additional widget, we remove it from the // dialog, so that the caller can still access it. Therefore, it is // the callers responsibility to delete the object if (widget) widget->setParent(0); delete dialog; return url; } // General open void KMyMoneyApp::slotFileOpen() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new QFileDialog(this, QString(), QStringLiteral("kfiledialog:///kmymoney-open"), i18n("KMyMoney files (*.kmy *.xml);;All files")); dialog->setFileMode(QFileDialog::ExistingFile); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { slotFileOpenRecent(dialog->selectedUrls().first()); } delete dialog; } void KMyMoneyApp::slotOpenDatabase() { KMSTATUS(i18n("Open a file.")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite); if (!dialog->checkDrivers()) { delete dialog; return; } if (dialog->exec() == QDialog::Accepted && dialog != 0) { slotFileOpenRecent(dialog->selectedURL()); } delete dialog; } bool KMyMoneyApp::isImportableFile(const QUrl &url) { bool result = false; // Iterate through the plugins and see if there's a loaded plugin who can handle it QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url.path())) { result = true; break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url.path())) result = true; // Place code here to test for QIF and other locally-supported formats // (i.e. not a plugin). If you add them here, be sure to add it to // the webConnect function. return result; } void KMyMoneyApp::slotFileOpenRecent(const QUrl &url) { KMSTATUS(i18n("Loading file...")); QUrl lastFile = d->m_fileName; // check if there are other instances which might have this file open QList list = instanceList(); QList::ConstIterator it; bool duplicate = false; #ifdef KMM_DBUS for (it = list.constBegin(); duplicate == false && it != list.constEnd(); ++it) { QDBusInterface remoteApp(*it, "/KMymoney", "org.kde.kmymoney"); QDBusReply reply = remoteApp.call("filename"); if (!reply.isValid()) { qDebug("D-Bus error while calling app->filename()"); } else { if (reply.value() == url.url()) { duplicate = true; } } } #endif if (!duplicate) { QUrl newurl = url; if ((newurl.scheme() == "sql")) { if (QUrlQuery(newurl).queryItemValue("driver") == "QMYSQL3") { // fix any old urls // TODO: port KF5 //newurl.removeQueryItem("driver"); //newurl.addQueryItem("driver", "QMYSQL"); } if (QUrlQuery(newurl).queryItemValue("driver") == "QSQLITE3") { // TODO: port KF5 //newurl.removeQueryItem("driver"); //newurl.addQueryItem("driver", "QSQLITE"); } // check if a password is needed. it may be if the URL came from the last/recent file list QPointer dialog = new KSelectDatabaseDlg(QIODevice::ReadWrite, newurl); if (!dialog->checkDrivers()) { delete dialog; return; } // if we need to supply a password, then show the dialog // otherwise it isn't needed if ((QUrlQuery(newurl).queryItemValue("secure").toLower() == "yes") && newurl.password().isEmpty()) { if (dialog->exec() == QDialog::Accepted && dialog != nullptr) { newurl = dialog->selectedURL(); } else { delete dialog; return; } } delete dialog; } // TODO: port KF5 if ((newurl.scheme() == "sql") || (newurl.isValid() /*&& KIO::NetAccess::exists(newurl, KIO::NetAccess::SourceSide, this)*/)) { slotFileClose(); if (!d->m_myMoneyView->fileOpen()) { try { if (d->m_myMoneyView->readFile(newurl)) { if ((d->m_myMoneyView->isNativeFile())) { d->m_fileName = newurl; updateCaption(); d->m_recentFiles->addUrl(newurl); writeLastUsedFile(newurl.toDisplayString(QUrl::PreferLocalFile)); } else { d->m_fileName = QUrl(); // imported files have no filename } // Check the schedules slotCheckSchedules(); } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot open file as requested. Error was: %1", e.what())); } updateCaption(); emit fileLoaded(d->m_fileName); } else { /*fileOpen failed - should we do something or maybe fileOpen puts out the message... - it does for database*/ } } else { // newurl invalid slotFileClose(); KMessageBox::sorry(this, i18n("

%1 is either an invalid filename or the file does not exist. You can open another file or create a new one.

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("File not found")); } } else { // isDuplicate KMessageBox::sorry(this, i18n("

File %1 is already opened in another instance of KMyMoney

", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Duplicate open")); } } bool KMyMoneyApp::slotFileSave() { // if there's nothing changed, there's no need to save anything if (!d->m_myMoneyView->dirty()) return true; bool rc = false; KMSTATUS(i18n("Saving file...")); if (d->m_fileName.isEmpty()) return slotFileSaveAs(); d->consistencyCheck(false); /*if (myMoneyView->isDatabase()) { rc = myMoneyView->saveDatabase(m_fileName); // the 'save' function is no longer relevant for a database*/ setEnabled(false); rc = d->m_myMoneyView->saveFile(d->m_fileName, MyMoneyFile::instance()->value("kmm-encryption-key")); setEnabled(true); d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileSaveAsFilterChanged(const QString& filter) { if (!d->m_saveEncrypted) return; if (filter != "*.kmy") { d->m_saveEncrypted->setCurrentItem(0); d->m_saveEncrypted->setEnabled(false); } else { d->m_saveEncrypted->setEnabled(true); } } void KMyMoneyApp::slotManageGpgKeys() { QPointer dlg = new KGpgKeySelectionDlg(this); dlg->setKeys(d->m_additionalGpgKeys); if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->m_additionalGpgKeys = dlg->keys(); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count())); } delete dlg; } void KMyMoneyApp::slotKeySelected(int idx) { int cnt = 0; if (idx != 0) { cnt = d->m_additionalGpgKeys.count(); } d->m_additionalKeyLabel->setEnabled(idx != 0); d->m_additionalKeyButton->setEnabled(idx != 0); d->m_additionalKeyLabel->setText(i18n("Additional encryption keys to be used: %1", cnt)); } bool KMyMoneyApp::slotFileSaveAs() { bool rc = false; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); KMSTATUS(i18n("Saving file with a new filename...")); QString prevDir = ""; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); // fill the additional key list with the default d->m_additionalGpgKeys = KMyMoneyGlobalSettings::gpgRecipientList(); QWidget* vbox = new QWidget(); QVBoxLayout *vboxVBoxLayout = new QVBoxLayout(vbox); vboxVBoxLayout->setMargin(0); if (KGPGFile::GPGAvailable()) { QWidget* keyBox = new QWidget(vbox); QHBoxLayout *keyBoxHBoxLayout = new QHBoxLayout(keyBox); keyBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(keyBox); QLabel *keyLabel = new QLabel(i18n("Encryption key to be used"), keyBox); keyBoxHBoxLayout->addWidget(keyLabel); d->m_saveEncrypted = new KComboBox(keyBox); keyBoxHBoxLayout->addWidget(d->m_saveEncrypted); QWidget* labelBox = new QWidget(vbox); QHBoxLayout *labelBoxHBoxLayout = new QHBoxLayout(labelBox); labelBoxHBoxLayout->setMargin(0); vboxVBoxLayout->addWidget(labelBox); d->m_additionalKeyLabel = new QLabel(i18n("Additional encryption keys to be used: %1", d->m_additionalGpgKeys.count()), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyLabel); d->m_additionalKeyButton = new QPushButton(i18n("Manage additional keys"), labelBox); labelBoxHBoxLayout->addWidget(d->m_additionalKeyButton); connect(d->m_additionalKeyButton, SIGNAL(clicked()), this, SLOT(slotManageGpgKeys())); connect(d->m_saveEncrypted, SIGNAL(activated(int)), this, SLOT(slotKeySelected(int))); // fill the secret key list and combo box QStringList keyList; KGPGFile::secretKeyList(keyList); d->m_saveEncrypted->addItem(i18n("No encryption")); for (QStringList::iterator it = keyList.begin(); it != keyList.end(); ++it) { QStringList fields = (*it).split(':', QString::SkipEmptyParts); if (fields[0] != recoveryKeyId) { // replace parenthesis in name field with brackets QString name = fields[1]; name.replace('(', "["); name.replace(')', "]"); name = QString("%1 (0x%2)").arg(name).arg(fields[0]); d->m_saveEncrypted->addItem(name); if (name.contains(KMyMoneyGlobalSettings::gpgRecipient())) { d->m_saveEncrypted->setCurrentItem(name); } } } } // the following code is copied from KFileDialog::getSaveFileName, // adjust to our local needs (filetypes etc.) and // enhanced to show the d->m_saveEncrypted combo box bool specialDir = !prevDir.isEmpty() && prevDir.at(0) == QLatin1Char(':'); // TODO: port KF5 QPointer dlg = new QFileDialog(this, i18n("Save As"), specialDir ? prevDir : QString(), QString("%1|%2\n").arg("*.kmy").arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString("%1|%2\n").arg("*.xml").arg(i18nc("XML (Filefilter)", "XML files")) + QString("%1|%2\n").arg("*.anon.xml").arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString("%1|%2\n").arg("*").arg(i18nc("All files (Filefilter)", "All files"))); dlg->setAcceptMode(QFileDialog::AcceptSave); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); // TODO: port KF5 //if (!specialDir) // dlg->setSelection(prevDir); // may also be a filename if (dlg->exec() == QDialog::Accepted && dlg != 0) { d->consistencyCheck(false); QUrl newURL = dlg->selectedUrls().isEmpty() ? QUrl() : dlg->selectedUrls().first(); // deleting the dialog will delete the combobox pointed to by d->m_saveEncrypted so get the key name here QString selectedKeyName; if (d->m_saveEncrypted && d->m_saveEncrypted->currentIndex() != 0) selectedKeyName = d->m_saveEncrypted->currentText(); d->m_saveEncrypted = 0; delete dlg; if (!newURL.isEmpty()) { QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); // end of copy // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if (!strExt.contains("kmy", Qt::CaseInsensitive) && !strExt.contains("xml", Qt::CaseInsensitive)) { strTemp.append("kmy"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmy"); } if (okToWriteFile(QUrl::fromUserInput(newName))) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(QUrl::fromUserInput(newName)); setEnabled(false); // If this is the anonymous file export, just save it, don't actually take the // name, or remember it! Don't even try to encrypt it if (newName.right(9).toLower() == ".anon.xml") { rc = d->m_myMoneyView->saveFile(QUrl::fromUserInput(newName)); } else { d->m_fileName = QUrl::fromUserInput(newName); QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys += ','; encryptionKeys += d->m_additionalGpgKeys.join(","); } rc = d->m_myMoneyView->saveFile(d->m_fileName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newName); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } } else { delete dlg; } updateCaption(); return rc; } bool KMyMoneyApp::slotSaveAsDatabase() { bool rc = false; QUrl oldUrl; // in event of it being a database, ensure that all data is read into storage for saveas if (d->m_myMoneyView->isDatabase()) { dynamic_cast(MyMoneyFile::instance()->storage())->fillStorage(); oldUrl = d->m_fileName.isEmpty() ? lastOpenedURL() : d->m_fileName; } KMSTATUS(i18n("Saving file to database...")); QPointer dialog = new KSelectDatabaseDlg(QIODevice::WriteOnly); QUrl url = oldUrl; if (!dialog->checkDrivers()) { delete dialog; return (false); } while (oldUrl == url && dialog->exec() == QDialog::Accepted && dialog != 0) { url = dialog->selectedURL(); // If the protocol is SQL for the old and new, and the hostname and database names match // Let the user know that the current database cannot be saved on top of itself. if (url.scheme() == "sql" && oldUrl.scheme() == "sql" && oldUrl.host() == url.host() && QUrlQuery(oldUrl).queryItemValue("driver") == QUrlQuery(url).queryItemValue("driver") && oldUrl.path().right(oldUrl.path().length() - 1) == url.path().right(url.path().length() - 1)) { KMessageBox::sorry(this, i18n("Cannot save to current database.")); } else { try { rc = d->m_myMoneyView->saveAsDatabase(url); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot save to current database: %1", e.what())); } } } delete dialog; if (rc) { //KRecentFilesAction *p = dynamic_cast(action("file_open_recent")); //if(p) d->m_recentFiles->addUrl(url); writeLastUsedFile(url.toDisplayString(QUrl::PreferLocalFile)); } d->m_autoSaveTimer->stop(); updateCaption(); return rc; } void KMyMoneyApp::slotFileCloseWindow() { KMSTATUS(i18n("Closing window...")); if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } close(); } void KMyMoneyApp::slotFileClose() { bool okToSelect = true; // check if transaction editor is open and ask user what he wants to do slotTransactionsCancelOrEnter(okToSelect); if (!okToSelect) return; // no update status here, as we might delete the status too early. if (d->m_myMoneyView->dirty()) { int answer = askSaveOnClose(); if (answer == KMessageBox::Cancel) return; else if (answer == KMessageBox::Yes) slotFileSave(); } d->closeFile(); } void KMyMoneyApp::slotFileQuit() { // don't modify the status message here as this will prevent quit from working!! // See the beginning of queryClose() and isReady() why. Thomas Baumgart 2005-10-17 bool quitApplication = true; QList memberList = KMainWindow::memberList(); if (!memberList.isEmpty()) { QList::const_iterator w_it = memberList.constBegin(); for (; w_it != memberList.constEnd(); ++w_it) { // only close the window if the closeEvent is accepted. If the user presses Cancel on the saveModified() dialog, // the window and the application stay open. if (!(*w_it)->close()) { quitApplication = false; break; } } } // We will only quit if all windows were processed and not cancelled if (quitApplication) QCoreApplication::quit(); } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(toggleAction("view_hide_reconciled_transactions")->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(toggleAction("view_hide_unused_categories")->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(toggleAction("debug_traces")->isChecked() ? 1 : 0); } void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = toggleAction("debug_timers")->isChecked(); } QString KMyMoneyApp::slotStatusMsg(const QString &text) { /////////////////////////////////////////////////////////////////// // change status message permanently QString previousMessage = d->m_statusLabel->text(); d->m_applicationIsReady = false; QString currentMessage = text; if (currentMessage.isEmpty() || currentMessage == i18nc("Application is ready to use", "Ready.")) { d->m_applicationIsReady = true; currentMessage = i18nc("Application is ready to use", "Ready."); } statusBar()->clearMessage(); d->m_statusLabel->setText(currentMessage); return previousMessage; } void KMyMoneyApp::ready() { slotStatusMsg(QString()); } bool KMyMoneyApp::isReady() { return d->m_applicationIsReady; } void KMyMoneyApp::slotStatusProgressBar(int current, int total) { if (total == -1 && current == -1) { // reset if (d->m_progressTimer) { d->m_progressTimer->start(500); // remove from screen in 500 msec d->m_progressBar->setValue(d->m_progressBar->maximum()); } } else if (total != 0) { // init d->m_progressTimer->stop(); d->m_progressBar->setMaximum(total); d->m_progressBar->setValue(0); d->m_progressBar->show(); } else { // update QTime currentTime = QTime::currentTime(); // only process painting if last update is at least 250 ms ago if (abs(d->m_lastUpdate.msecsTo(currentTime)) > 250) { d->m_progressBar->setValue(current); d->m_lastUpdate = currentTime; } } } void KMyMoneyApp::slotStatusProgressDone() { d->m_progressTimer->stop(); d->m_progressBar->reset(); d->m_progressBar->hide(); d->m_progressBar->setValue(0); } void KMyMoneyApp::progressCallback(int current, int total, const QString& msg) { if (!msg.isEmpty()) kmymoney->slotStatusMsg(msg); kmymoney->slotStatusProgressBar(current, total); } void KMyMoneyApp::slotFileViewPersonal() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } KMSTATUS(i18n("Viewing personal data...")); MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyPayee user = file->user(); QPointer editPersonalDataDlg = new EditPersonalDataDlg(user.name(), user.address(), user.city(), user.state(), user.postcode(), user.telephone(), user.email(), this, i18n("Edit Personal Data")); if (editPersonalDataDlg->exec() == QDialog::Accepted && editPersonalDataDlg != 0) { user.setName(editPersonalDataDlg->userNameText); user.setAddress(editPersonalDataDlg->userStreetText); user.setCity(editPersonalDataDlg->userTownText); user.setState(editPersonalDataDlg->userCountyText); user.setPostcode(editPersonalDataDlg->userPostcodeText); user.setTelephone(editPersonalDataDlg->userTelephoneText); user.setEmail(editPersonalDataDlg->userEmailText); MyMoneyFileTransaction ft; try { file->setUser(user); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store user information: %1", e.what())); } } delete editPersonalDataDlg; } void KMyMoneyApp::slotFileFileInfo() { if (!d->m_myMoneyView->fileOpen()) { KMessageBox::information(this, i18n("No KMyMoneyFile open")); return; } QFile g("kmymoney.dump"); g.open(QIODevice::WriteOnly); QDataStream st(&g); MyMoneyStorageDump dumper; dumper.writeStream(st, dynamic_cast(MyMoneyFile::instance()->storage())); g.close(); } void KMyMoneyApp::slotLoadAccountTemplates() { KMSTATUS(i18n("Importing account templates.")); int rc; QPointer dlg = new KLoadTemplateDlg(); if ((rc = dlg->exec()) == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { // import the account templates QList templates = dlg->templates(); QList::iterator it_t; for (it_t = templates.begin(); it_t != templates.end(); ++it_t) { (*it_t).importTemplate(&progressCallback); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to import template(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } delete dlg; } void KMyMoneyApp::slotSaveAccountTemplates() { KMSTATUS(i18n("Exporting account templates.")); QString savePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/templates/" + QLocale().name(); QDir d(savePath); if (!d.exists()) d.mkpath(savePath); QString newName = QFileDialog::getSaveFileName(this, i18n("Save as..."), savePath, i18n("KMyMoney template files (*.kmt);;All files (*)")); // // If there is no file extension, then append a .kmt at the end of the file name. // If there is a file extension, make sure it is .kmt, delete any others. // if (!newName.isEmpty()) { // find last . delimiter int nLoc = newName.lastIndexOf('.'); if (nLoc != -1) { QString strExt, strTemp; strTemp = newName.left(nLoc + 1); strExt = newName.right(newName.length() - (nLoc + 1)); if ((strExt.indexOf("kmt", 0, Qt::CaseInsensitive) == -1)) { strTemp.append("kmt"); //append to make complete file name newName = strTemp; } } else { newName.append(".kmt"); } if (okToWriteFile(QUrl::fromLocalFile(newName))) { MyMoneyTemplate templ; templ.exportTemplate(&progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } } } void KMyMoneyApp::slotQifImport() { if (d->m_qifReader == 0) { // FIXME: the menu entry for qif import should be disabled here QPointer dlg = new KImportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { KMSTATUS(i18n("Importing file...")); d->m_qifReader = new MyMoneyQifReader; // remove all kmm-statement-#.txt files d->unlinkStatementXML(); connect(d->m_qifReader, SIGNAL(importFinished()), this, SLOT(slotQifImportFinished())); d->m_qifReader->setURL(dlg->file()); d->m_qifReader->setProfile(dlg->profile()); d->m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); d->m_qifReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); d->m_ft = new MyMoneyFileTransaction(); d->m_collectingStatements = true; d->m_statementResults.clear(); if (!d->m_qifReader->startImport()) { // if the import failed to start make sure that slotQifImportFinished is called otherwise the application will be left disabled QTimer::singleShot(0, this, SLOT(slotQifImportFinished())); } } delete dlg; slotUpdateActions(); } } void KMyMoneyApp::slotQifImportFinished() { if (d->m_qifReader != 0) { d->m_qifReader->finishImport(); d->m_ft->commit(); d->m_collectingStatements = false; KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); #if 0 // fixme: re-enable the QIF import menu options if (d->m_qifReader->finishImport()) { if (verifyImportedData(d->m_qifReader->account())) { // keep the new data set, destroy the backup copy delete d->m_engineBackup; d->m_engineBackup = 0; } } if (d->m_engineBackup != 0) { // user cancelled, destroy the updated set and keep the backup copy IMyMoneyStorage* data = file->storage(); if (data != 0) { file->detachStorage(data); delete data; } file->attachStorage(d->m_engineBackup); d->m_engineBackup = 0; } #endif // update the views as they might still contain invalid data // from the import session. The same applies for the window caption d->m_myMoneyView->slotRefreshViews(); updateCaption(); delete d->m_qifReader; d->m_qifReader = 0; } delete d->m_ft; d->m_ft = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); slotUpdateActions(); } void KMyMoneyApp::slotGncImport() { if (d->m_myMoneyView->fileOpen()) { switch (KMessageBox::questionYesNoCancel(0, i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) { case KMessageBox::Yes: slotFileSave(); break; case KMessageBox::No: d->closeFile(); break; default: return; } } KMSTATUS(i18n("Importing a GnuCash file.")); QUrl fileToRead = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)")); if (!fileToRead.isEmpty()) { // call the importer d->m_myMoneyView->readFile(fileToRead); // imported files don't have a name d->m_fileName = QUrl(); updateCaption(); emit fileLoaded(d->m_fileName); } } void KMyMoneyApp::slotAccountChart() { if (!d->m_selectedAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_selectedAccount, this); dlg->exec(); delete dlg; } } // // KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement // is not intended to be exposed to users in XML form. // void KMyMoneyApp::slotStatementImport() { bool result = false; KMSTATUS(i18n("Importing an XML Statement.")); QList files{QFileDialog::getOpenFileUrls(this, QString(), QUrl(), i18n("XML files (*.xml);;All files (*)"))}; if (!files.isEmpty()) { d->m_collectingStatements = (files.count() > 1); foreach (const QUrl &url, files) { qDebug("Processing '%s'", qPrintable(url.path())); result |= slotStatementImport(url.path()); } /* QFile f( dialog->selectedURL().path() ); f.open( QIODevice::ReadOnly ); QString error = "Unable to parse file"; QDomDocument* doc = new QDomDocument; if(doc->setContent(&f, FALSE)) { if ( doc->doctype().name() == "KMYMONEY-STATEMENT" ) { QDomElement rootElement = doc->documentElement(); if(!rootElement.isNull()) { QDomNode child = rootElement.firstChild(); if(!child.isNull() && child.isElement()) { MyMoneyStatement s; if ( s.read(child.toElement()) ) result = slotStatementImport(s); else error = "File does not contain any statements"; } } } else error = "File is not a KMyMoney Statement"; } delete doc; if ( !result ) { QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 ); }*/ } if (!result) { // re-enable all standard widgets setEnabled(true); } } bool KMyMoneyApp::slotStatementImport(const QString& url) { bool result = false; MyMoneyStatement s; if (MyMoneyStatement::readXMLFile(s, url)) result = slotStatementImport(s); else KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement")); return result; } bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s) { bool result = false; // keep a copy of the statement MyMoneyStatement::writeXMLFile(s, QString("/home/thb/kmm-statement-%1.txt").arg(d->m_statementXMLindex++)); // we use an object on the heap here, so that we can check the presence // of it during slotUpdateActions() by looking at the pointer. d->m_smtReader = new MyMoneyStatementReader; d->m_smtReader->setAutoCreatePayee(true); d->m_smtReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); QStringList messages; result = d->m_smtReader->import(s, messages); bool transactionAdded = d->m_smtReader->anyTransactionAdded(); // get rid of the statement reader and tell everyone else // about the destruction by setting the pointer to zero delete d->m_smtReader; d->m_smtReader = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); if (!d->m_collectingStatements) KMessageBox::informationList(this, i18n("The statement has been processed with the following results:"), messages, i18n("Statement stats")); else if (transactionAdded) d->m_statementResults += messages; slotUpdateActions();// Re-enable menu items after import via plugin. return result; } void KMyMoneyApp::slotQifExport() { KMSTATUS(i18n("Exporting file...")); QPointer dlg = new KExportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { MyMoneyQifWriter writer; connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), dlg->accountSelected(), dlg->categorySelected(), dlg->startDate(), dlg->endDate()); } } delete dlg; } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; // TODO: port KF5 //if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) { // if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) // reallySaveFile = false; //} return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it KConfigDialog* dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); connect(dlg, &KConfigDialog::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setCustomColors(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } /** No descriptions */ void KMyMoneyApp::slotFileBackup() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_fileName.isEmpty()) return; if (!d->m_fileName.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), i18n("Local files only")); return; } // TODO: port KF5 #if 0 QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox->isChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->txtMountPoint->text(); if (d->m_backupMount) { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } else { // If we don't have to mount a device, we just issue // a dummy command to start the copy operation progressCallback(0, 300, ""); d->m_proc.setProgram("true"); d->m_proc.start(); } } delete backupDlg; #endif } /** No descriptions */ void KMyMoneyApp::slotProcessExited() { // TODO: port KF5 #if 0 switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { d->m_proc.clearProgram(); QString today; today.sprintf("-%04d-%02d-%02d.kmy", QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().day()); QString backupfile = d->m_mountpoint + '/' + d->m_fileName.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); // check if file already exists and ask what to do d->m_backupResult = 0; QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { d->m_backupResult = 1; if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } } if (d->m_backupResult == 0) { progressCallback(50, 0, i18n("Writing %1", backupfile)); //FIXME: FIX on windows d->m_proc << "cp" << "-f" << d->m_fileName.path() << backupfile; d->m_backupState = BACKUP_COPYING; d->m_proc.start(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } else { qDebug("cp exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } #endif } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } void KMyMoneyApp::slotQifProfileEditor() { QPointer editor = new MyMoneyQifProfileEditor(true, this); editor->setObjectName("QIF Profile Editor"); editor->exec(); delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createInstitution(MyMoneyInstitution& institution) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionNew() { MyMoneyInstitution institution; slotInstitutionNew(institution); } void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); createInstitution(institution); } delete dlg; } void KMyMoneyApp::slotInstitutionEdit(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; // make sure the selected object has an id if (d->m_selectedInstitution.id().isEmpty()) return; try { MyMoneyFile* file = MyMoneyFile::instance(); //grab a pointer to the view, regardless of it being a account or institution view. MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); // bankSuccess is not checked anymore because d->m_file->institution will throw anyway QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { file->modifyInstitution(dlg->institution()); ft.commit(); slotSelectInstitution(file->institution(dlg->institution().id())); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to store institution: %1", e.what())); } } delete dlg; } catch (const MyMoneyException &e) { if (!obj.id().isEmpty()) KMessageBox::information(this, i18n("Unable to edit institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionDelete() { MyMoneyFile *file = MyMoneyFile::instance(); try { MyMoneyInstitution institution = file->institution(d->m_selectedInstitution.id()); if ((KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the institution %1?

", institution.name()))) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { file->removeInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete institution: %1", e.what())); } } const MyMoneyAccount& KMyMoneyApp::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const { static MyMoneyAccount nullAccount; MyMoneyFile* file = MyMoneyFile::instance(); QList parents; try { // search by id if (!acc.id().isEmpty()) { return file->account(acc.id()); } // collect the parents. in case parent does not have an id, we scan the all top-level accounts if (parent.id().isEmpty()) { parents << file->asset(); parents << file->liability(); parents << file->income(); parents << file->expense(); parents << file->equity(); } else { parents << parent; } QList::const_iterator it_p; for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) { MyMoneyAccount parentAccount = *it_p; // search by name (allow hierarchy) int pos; // check for ':' in the name and use it as separator for a hierarchy QString name = acc.name(); while ((pos = name.indexOf(MyMoneyFile::AccountSeperator)) != -1) { QString part = name.left(pos); QString remainder = name.mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { return existingAccount; } parentAccount = existingAccount; name = remainder; } const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name); if (!existingAccount.id().isEmpty()) { if (acc.accountType() != MyMoneyAccount::UnknownAccountType) { if (acc.accountType() != existingAccount.accountType()) continue; } return existingAccount; } } } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Unable to find account: %1", e.what())); } return nullAccount; } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile* file = MyMoneyFile::instance(); // make sure we have a currency. If none is assigned, we assume base currency if (newAccount.currencyId().isEmpty()) newAccount.setCurrencyId(file->baseCurrency().id()); MyMoneyFileTransaction ft; try { int pos; // check for ':' in the name and use it as separator for a hierarchy while ((pos = newAccount.name().indexOf(MyMoneyFile::AccountSeperator)) != -1) { QString part = newAccount.name().left(pos); QString remainder = newAccount.name().mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); if (existingAccount.id().isEmpty()) { newAccount.setName(part); file->addAccount(newAccount, parentAccount); parentAccount = newAccount; } else { parentAccount = existingAccount; } newAccount.setParentAccountId(QString()); // make sure, there's no parent newAccount.clearId(); // and no id set for adding newAccount.removeAccountIds(); // and no sub-account ids newAccount.setName(remainder); } const MyMoneySecurity& sec = file->security(newAccount.currencyId()); // Check the opening balance if (openingBal.isPositive() && newAccount.accountGroup() == MyMoneyAccount::Liability) { QString message = i18n("This account is a liability and if the " "opening balance represents money owed, then it should be negative. " "Negate the amount?\n\n" "Please click Yes to change the opening balance to %1,\n" "Please click No to leave the amount as %2,\n" "Please click Cancel to abort the account creation." , MyMoneyUtils::formatMoney(-openingBal, newAccount, sec) , MyMoneyUtils::formatMoney(openingBal, newAccount, sec)); int ans = KMessageBox::questionYesNoCancel(this, message); if (ans == KMessageBox::Yes) { openingBal = -openingBal; } else if (ans == KMessageBox::Cancel) return; } file->addAccount(newAccount, parentAccount); // in case of a loan account, we add the initial payment if ((newAccount.accountType() == MyMoneyAccount::Loan || newAccount.accountType() == MyMoneyAccount::AssetLoan) && !newAccount.value("kmm-loan-payment-acc").isEmpty() && !newAccount.value("kmm-loan-payment-date").isEmpty()) { MyMoneyAccountLoan acc(newAccount); MyMoneyTransaction t; MyMoneySplit a, b; a.setAccountId(acc.id()); b.setAccountId(acc.value("kmm-loan-payment-acc").toLatin1()); a.setValue(acc.loanAmount()); if (acc.accountType() == MyMoneyAccount::Loan) a.setValue(-a.value()); a.setShares(a.value()); b.setValue(-a.value()); b.setShares(b.value()); a.setMemo(i18n("Loan payout")); b.setMemo(i18n("Loan payout")); t.setPostDate(QDate::fromString(acc.value("kmm-loan-payment-date"), Qt::ISODate)); newAccount.deletePair("kmm-loan-payment-acc"); newAccount.deletePair("kmm-loan-payment-date"); MyMoneyFile::instance()->modifyAccount(newAccount); t.addSplit(a); t.addSplit(b); file->addTransaction(t); file->createOpeningBalanceTransaction(newAccount, openingBal); // in case of an investment account we check if we should create // a brokerage account } else if (newAccount.accountType() == MyMoneyAccount::Investment && !brokerageAccount.name().isEmpty()) { file->addAccount(brokerageAccount, parentAccount); // set a link from the investment account to the brokerage account file->modifyAccount(newAccount); file->createOpeningBalanceTransaction(brokerageAccount, openingBal); } else file->createOpeningBalanceTransaction(newAccount, openingBal); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add account: %1", e.what())); } } void KMyMoneyApp::slotCategoryNew(const QString& name, QString& id) { MyMoneyAccount account; account.setName(name); slotCategoryNew(account, MyMoneyFile::instance()->expense()); id = account.id(); } void KMyMoneyApp::slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (KMessageBox::questionYesNo(this, QString("%1").arg(i18n("

The category %1 currently does not exist. Do you want to create it?

The parent account will default to %2 but can be changed in the following dialog.

", account.name(), parent.name())), i18n("Create category"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "CreateNewCategories") == KMessageBox::Yes) { createCategory(account, parent); } else { // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("CreateNewCategories")); } } } void KMyMoneyApp::slotCategoryNew() { MyMoneyAccount parent; MyMoneyAccount account; // Preselect the parent account by looking at the current selected account/category if (!d->m_selectedAccount.id().isEmpty() && d->m_selectedAccount.isIncomeExpense()) { MyMoneyFile* file = MyMoneyFile::instance(); try { parent = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &) { } } createCategory(account, parent); } void KMyMoneyApp::createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent) { if (!parent.id().isEmpty()) { try { // make sure parent account exists MyMoneyFile::instance()->account(parent.id()); account.setParentAccountId(parent.id()); account.setAccountType(parent.accountType()); } catch (const MyMoneyException &) { } } QPointer dialog = new KNewAccountDlg(account, false, true, 0, i18n("Create a new Category")); dialog->setOpeningBalanceShown(false); dialog->setOpeningDateShown(false); if (dialog->exec() == QDialog::Accepted && dialog != 0) { MyMoneyAccount parentAccount, brokerageAccount; account = dialog->account(); parentAccount = dialog->parentAccount(); createAccount(account, parentAccount, brokerageAccount, MyMoneyMoney()); } delete dialog; } void KMyMoneyApp::slotAccountNew() { MyMoneyAccount acc; acc.setInstitutionId(d->m_selectedInstitution.id()); acc.setOpeningDate(KMyMoneyGlobalSettings::firstFiscalDate()); slotAccountNew(acc); } void KMyMoneyApp::slotAccountNew(MyMoneyAccount& account) { NewAccountWizard::Wizard* wizard = new NewAccountWizard::Wizard(); connect(wizard, SIGNAL(createInstitution(MyMoneyInstitution&)), this, SLOT(slotInstitutionNew(MyMoneyInstitution&))); connect(wizard, SIGNAL(createAccount(MyMoneyAccount&)), this, SLOT(slotAccountNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(wizard, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); wizard->setAccount(account); if (wizard->exec() == QDialog::Accepted) { MyMoneyAccount acc = wizard->account(); if (!(acc == MyMoneyAccount())) { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { // create the account MyMoneyAccount parent = wizard->parentAccount(); file->addAccount(acc, parent); // tell the wizard about the account id which it // needs to create a possible schedule and transactions wizard->setAccount(acc); // store a possible conversion rate for the currency if (acc.currencyId() != file->baseCurrency().id()) { file->addPrice(wizard->conversionRate()); } // create the opening balance transaction if any file->createOpeningBalanceTransaction(acc, wizard->openingBalance()); // create the payout transaction for loans if any MyMoneyTransaction payoutTransaction = wizard->payoutTransaction(); if (payoutTransaction.splits().count() > 0) { file->addTransaction(payoutTransaction); } // create a brokerage account if selected MyMoneyAccount brokerageAccount = wizard->brokerageAccount(); if (!(brokerageAccount == MyMoneyAccount())) { file->addAccount(brokerageAccount, parent); } // create a possible schedule MyMoneySchedule sch = wizard->schedule(); if (!(sch == MyMoneySchedule())) { MyMoneyFile::instance()->addSchedule(sch); if (acc.isLoan()) { MyMoneyAccountLoan accLoan = MyMoneyFile::instance()->account(acc.id()); accLoan.setSchedule(sch.id()); acc = accLoan; MyMoneyFile::instance()->modifyAccount(acc); } } ft.commit(); account = acc; } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to create account: %1", e.what())); } } } delete wizard; } void KMyMoneyApp::slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent) { QString dontShowAgain = "CreateNewInvestments"; if (KMessageBox::questionYesNo(this, QString("") + i18n("The security %1 currently does not exist as sub-account of %2. " "Do you want to create it?", account.name(), parent.name()) + QString(""), i18n("Create security"), KStandardGuiItem::yes(), KStandardGuiItem::no(), dontShowAgain) == KMessageBox::Yes) { KNewInvestmentWizard dlg; dlg.setName(account.name()); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(parent.id()); account = dlg.account(); } } else { // in case the user said no but turned on the don't show again selection, we will enable // the message no matter what. Otherwise, the user is not able to use this feature // in the future anymore. KMessageBox::enableMessage(dontShowAgain); } } void KMyMoneyApp::slotInvestmentNew() { KNewInvestmentWizard dlg; if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(d->m_selectedAccount.id()); } } void KMyMoneyApp::slotInvestmentEdit() { KNewInvestmentWizard dlg(d->m_selectedInvestment); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(d->m_selectedAccount.id()); } } void KMyMoneyApp::slotInvestmentDelete() { if (KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the investment %1?

", d->m_selectedInvestment.name()), i18n("Delete investment"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "DeleteInvestment") == KMessageBox::Yes) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->removeAccount(d->m_selectedInvestment); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to delete investment: %1", e.what())); } } else { // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("DeleteInvestment")); } } } void KMyMoneyApp::slotOnlinePriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { QPointer dlg = new KEquityPriceUpdateDlg(0, d->m_selectedInvestment.currencyId()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { dlg->storePrices(); } delete dlg; } } void KMyMoneyApp::slotManualPriceUpdate() { if (!d->m_selectedInvestment.id().isEmpty()) { try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); MyMoneySecurity currency = MyMoneyFile::instance()->security(security.tradingCurrency()); const MyMoneyPrice &price = MyMoneyFile::instance()->price(security.id(), currency.id()); QPointer calc = new KCurrencyCalculator(security, currency, MyMoneyMoney::ONE, price.rate(currency.id()), price.date(), MyMoneyMoney::precToDenom(security.pricePrecision())); calc->setupPriceEditor(); // The dialog takes care of adding the price if necessary calc->exec(); delete calc; } catch (const MyMoneyException &e) { qDebug("Error in price update: %s", qPrintable(e.what())); } } } void KMyMoneyApp::createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount) { MyMoneyFile* file = MyMoneyFile::instance(); // Add the schedule only if one exists // // Remember to modify the first split to reference the newly created account if (!newSchedule.name().isEmpty()) { try { // We assume at least 2 splits in the transaction MyMoneyTransaction t = newSchedule.transaction(); if (t.splitCount() < 2) { throw MYMONEYEXCEPTION("Transaction for schedule has less than 2 splits!"); } MyMoneyFileTransaction ft; try { file->addSchedule(newSchedule); // in case of a loan account, we keep a reference to this // schedule in the account if (newAccount.accountType() == MyMoneyAccount::Loan || newAccount.accountType() == MyMoneyAccount::AssetLoan) { newAccount.setValue("schedule", newSchedule.id()); file->modifyAccount(newAccount); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add scheduled transaction: %1", e.what())); } } } void KMyMoneyApp::slotAccountDelete() { if (d->m_selectedAccount.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); // can't delete standard accounts or account which still have transactions assigned if (file->isStandardAccount(d->m_selectedAccount.id())) return; // check if the account is referenced by a transaction or schedule MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckAccount); skip.setBit(IMyMoneyStorage::RefCheckInstitution); skip.setBit(IMyMoneyStorage::RefCheckPayee); skip.setBit(IMyMoneyStorage::RefCheckTag); skip.setBit(IMyMoneyStorage::RefCheckSecurity); skip.setBit(IMyMoneyStorage::RefCheckCurrency); skip.setBit(IMyMoneyStorage::RefCheckPrice); bool hasReference = file->isReferenced(d->m_selectedAccount, skip); // make sure we only allow transactions in a 'category' (income/expense account) switch (d->m_selectedAccount.accountType()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: break; default: // if the account is still referenced if (hasReference) { return; } break; } // if we get here and still have transactions referencing the account, we // need to check with the user to possibly re-assign them to a different account bool needAskUser = true; bool exit = false; MyMoneyFileTransaction ft; if (hasReference) { // show transaction reassignment dialog needAskUser = false; KCategoryReassignDlg* dlg = new KCategoryReassignDlg(this); QString categoryId = dlg->show(d->m_selectedAccount); delete dlg; // and kill the dialog if (categoryId.isEmpty()) return; // the user aborted the dialog, so let's abort as well MyMoneyAccount newCategory = file->account(categoryId); try { { KMSTATUS(i18n("Adjusting transactions...")); /* d->m_selectedAccount.id() is the old id, categoryId the new one Now search all transactions and schedules that reference d->m_selectedAccount.id() and replace that with categoryId. */ // get the list of all transactions that reference the old account MyMoneyTransactionFilter filter(d->m_selectedAccount.id()); filter.setReportAllSplits(false); QList tlist; QList::iterator it_t; file->transactionList(tlist, filter); slotStatusProgressBar(0, tlist.count()); int cnt = 0; for (it_t = tlist.begin(); it_t != tlist.end(); ++it_t) { slotStatusProgressBar(++cnt, 0); MyMoneyTransaction t = (*it_t); if (t.replaceId(categoryId, d->m_selectedAccount.id())) file->modifyTransaction(t); } slotStatusProgressBar(tlist.count(), 0); } // now fix all schedules { KMSTATUS(i18n("Adjusting scheduled transactions...")); QList slist = file->scheduleList(d->m_selectedAccount.id()); QList::iterator it_s; int cnt = 0; slotStatusProgressBar(0, slist.count()); for (it_s = slist.begin(); it_s != slist.end(); ++it_s) { slotStatusProgressBar(++cnt, 0); MyMoneySchedule sch = (*it_s); if (sch.replaceId(categoryId, d->m_selectedAccount.id())) { file->modifySchedule(sch); } } slotStatusProgressBar(slist.count(), 0); } // now fix all budgets { KMSTATUS(i18n("Adjusting budgets...")); QList blist = file->budgetList(); QList::const_iterator it_b; for (it_b = blist.constBegin(); it_b != blist.constEnd(); ++it_b) { if ((*it_b).hasReferenceTo(d->m_selectedAccount.id())) { MyMoneyBudget b = (*it_b); MyMoneyBudget::AccountGroup fromBudget = b.account(d->m_selectedAccount.id()); MyMoneyBudget::AccountGroup toBudget = b.account(categoryId); toBudget += fromBudget; b.setAccount(toBudget, categoryId); b.removeReference(d->m_selectedAccount.id()); file->modifyBudget(b); } } slotStatusProgressBar(blist.count(), 0); } } catch (MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to exchange category %1 with category %2. Reason: %3", d->m_selectedAccount.name(), newCategory.name(), e.what())); exit = true; } slotStatusProgressBar(-1, -1); } if (exit) return; // retain the account name for a possible later usage in the error message box // since the account removal notifies the views the selected account can be changed // so we make sure by doing this that we display the correct name in the error message QString selectedAccountName = d->m_selectedAccount.name(); // at this point, we must not have a reference to the account // to be deleted anymore switch (d->m_selectedAccount.accountGroup()) { // special handling for categories to allow deleting of empty subcategories case MyMoneyAccount::Income: case MyMoneyAccount::Expense: { // open a compound statement here to be able to declare variables // which would otherwise not work within a case label. // case A - only a single, unused category without subcats selected if (d->m_selectedAccount.accountList().isEmpty()) { if (!needAskUser || (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to delete category %1?", selectedAccountName) + QString("")) == KMessageBox::Yes)) { try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete category %1. Cause: %2", selectedAccountName, e.what()) + QString("")); } } return; } // case B - we have some subcategories, maybe the user does not want to // delete them all, but just the category itself? MyMoneyAccount parentAccount = file->account(d->m_selectedAccount.parentAccountId()); QStringList accountsToReparent; int result = KMessageBox::questionYesNoCancel(this, QString("") + i18n("Do you want to delete category %1 with all its sub-categories or only " "the category itself? If you only delete the category itself, all its sub-categories " "will be made sub-categories of %2.", selectedAccountName, parentAccount.name()) + QString(""), QString(), KGuiItem(i18n("Delete all")), KGuiItem(i18n("Just the category"))); if (result == KMessageBox::Cancel) return; // cancel pressed? ok, no delete then... // "No" means "Just the category" and that means we need to reparent all subaccounts bool need_confirmation = false; // case C - User only wants to delete the category itself if (result == KMessageBox::No) accountsToReparent = d->m_selectedAccount.accountList(); else { // case D - User wants to delete all subcategories, now check all subcats of // d->m_selectedAccount and remember all that cannot be deleted and // must be "reparented" for (QStringList::const_iterator it = d->m_selectedAccount.accountList().begin(); it != d->m_selectedAccount.accountList().end(); ++it) { // reparent account if a transaction is assigned if (file->transactionCount(*it) != 0) accountsToReparent.push_back(*it); else if (!file->account(*it).accountList().isEmpty()) { // or if we have at least one sub-account that is used for transactions if (!file->hasOnlyUnusedAccounts(file->account(*it).accountList())) { accountsToReparent.push_back(*it); //qDebug() << "subaccount not empty"; } } } if (!accountsToReparent.isEmpty()) need_confirmation = true; } if (!accountsToReparent.isEmpty() && need_confirmation) { if (KMessageBox::questionYesNo(this, i18n("

Some sub-categories of category %1 cannot " "be deleted, because they are still used. They will be made sub-categories of %2. Proceed?

", selectedAccountName, parentAccount.name())) != KMessageBox::Yes) { return; // user gets wet feet... } } // all good, now first reparent selected sub-categories try { MyMoneyAccount parent = file->account(d->m_selectedAccount.parentAccountId()); for (QStringList::const_iterator it = accountsToReparent.constBegin(); it != accountsToReparent.constEnd(); ++it) { MyMoneyAccount child = file->account(*it); file->reparentAccount(child, parent); } // reload the account because the sub-account list might have changed d->m_selectedAccount = file->account(d->m_selectedAccount.id()); // now recursively delete remaining sub-categories file->removeAccountList(d->m_selectedAccount.accountList()); // don't forget to update d->m_selectedAccount, because we still have a copy of // the old account list, which is no longer valid d->m_selectedAccount = file->account(d->m_selectedAccount.id()); } catch (const MyMoneyException &e) { KMessageBox::error(this, QString("") + i18n("Unable to delete a sub-category of category %1. Reason: %2", selectedAccountName, e.what()) + QString("")); return; } } break; // the category/account is deleted after the switch default: if (!d->m_selectedAccount.accountList().isEmpty()) return; // can't delete accounts which still have subaccounts if (KMessageBox::questionYesNo(this, i18n("

Do you really want to " "delete account %1?

", selectedAccountName)) != KMessageBox::Yes) { return; // ok, you don't want to? why did you click then, hmm? } } // switch; try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, e.what())); } } void KMyMoneyApp::slotAccountEdit() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { if (d->m_selectedAccount.accountType() != MyMoneyAccount::Loan && d->m_selectedAccount.accountType() != MyMoneyAccount::AssetLoan) { QString caption; bool category = false; switch (d->m_selectedAccount.accountGroup()) { default: caption = i18n("Edit account '%1'", d->m_selectedAccount.name()); break; case MyMoneyAccount::Expense: case MyMoneyAccount::Income: caption = i18n("Edit category '%1'", d->m_selectedAccount.name()); category = true; break; } // set a status message so that the application can't be closed until the editing is done slotStatusMsg(caption); QString tid = file->openingBalanceTransaction(d->m_selectedAccount); MyMoneyTransaction t; MyMoneySplit s0, s1; QPointer dlg = new KNewAccountDlg(d->m_selectedAccount, true, category, 0, caption); if (category || d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { dlg->setOpeningBalanceShown(false); dlg->setOpeningDateShown(false); tid.clear(); } else { if (!tid.isEmpty()) { try { t = file->transaction(tid); s0 = t.splitByAccount(d->m_selectedAccount.id()); s1 = t.splitByAccount(d->m_selectedAccount.id(), false); dlg->setOpeningBalance(s0.shares()); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { dlg->setOpeningBalance(-s0.shares()); } } catch (const MyMoneyException &e) { qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n"; tid.clear(); } } } // check for online modules QMap::const_iterator it_plugin = d->m_onlinePlugins.constEnd(); const MyMoneyKeyValueContainer& kvp = d->m_selectedAccount.onlineBankingSettings(); if (!kvp["provider"].isEmpty()) { // if we have an online provider for this account, we need to check // that we have the corresponding plugin. If that exists, we ask it // to provide an additional tab for the account editor. it_plugin = d->m_onlinePlugins.constFind(kvp["provider"]); if (it_plugin != d->m_onlinePlugins.constEnd()) { QString name; QWidget *w = (*it_plugin)->accountConfigTab(d->m_selectedAccount, name); dlg->addTab(w, name); } } if (dlg != 0 && dlg->exec() == QDialog::Accepted) { try { MyMoneyFileTransaction ft; MyMoneyAccount account = dlg->account(); MyMoneyAccount parent = dlg->parentAccount(); if (it_plugin != d->m_onlinePlugins.constEnd()) { account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings())); } MyMoneyMoney bal = dlg->openingBalance(); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { bal = -bal; } // we need to modify first, as reparent would override all other changes file->modifyAccount(account); if (account.parentAccountId() != parent.id()) { file->reparentAccount(account, parent); } if (!tid.isEmpty() && dlg->openingBalance().isZero()) { file->removeTransaction(t); } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) { s0.setShares(bal); s0.setValue(bal); t.modifySplit(s0); s1.setShares(-bal); s1.setValue(-bal); t.modifySplit(s1); t.setPostDate(account.openingDate()); file->modifyTransaction(t); } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) { file->createOpeningBalanceTransaction(d->m_selectedAccount, bal); } ft.commit(); // reload the account object as it might have changed in the meantime slotSelectAccount(file->account(account.id())); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to modify account '%1'. Cause: %2", d->m_selectedAccount.name(), e.what())); } } delete dlg; ready(); } else { QPointer wizard = new KEditLoanWizard(d->m_selectedAccount); connect(wizard, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (wizard->exec() == QDialog::Accepted && wizard != 0) { MyMoneySchedule sch = file->schedule(d->m_selectedAccount.value("schedule").toLatin1()); if (!(d->m_selectedAccount == wizard->account()) || !(sch == wizard->schedule())) { MyMoneyFileTransaction ft; try { file->modifyAccount(wizard->account()); sch = wizard->schedule(); try { file->schedule(sch.id()); file->modifySchedule(sch); ft.commit(); } catch (const MyMoneyException &) { try { file->addSchedule(sch); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Cannot add schedule: '%s'", qPrintable(e.what())); } } } catch (const MyMoneyException &e) { qDebug("Unable to modify account %s: '%s'", qPrintable(d->m_selectedAccount.name()), qPrintable(e.what())); } } } delete wizard; } } } } QList > KMyMoneyApp::Private::automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount) { static const int NR_OF_STEPS_LIMIT = 300000; static const int PROGRESSBAR_STEPS = 1000; QList > result = transactions; KMSTATUS(i18n("Running automatic reconciliation")); int progressBarIndex = 0; kmymoney->slotStatusProgressBar(progressBarIndex, NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS); // optimize the most common case - all transactions should be cleared QListIterator > itTransactionSplitResult(result); MyMoneyMoney transactionsBalance; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); transactionsBalance += transactionSplit.second.shares(); } if (amount == transactionsBalance) { result = transactions; return result; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // only one transaction is uncleared itTransactionSplitResult.toFront(); int index = 0; while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); if (transactionsBalance - transactionSplit.second.shares() == amount) { result.removeAt(index); return result; } index++; } kmymoney->slotStatusProgressBar(progressBarIndex++, 0); // more than one transaction is uncleared - apply the algorithm result.clear(); const MyMoneySecurity &security = MyMoneyFile::instance()->security(account.currencyId()); double precision = 0.1 / account.fraction(security); QList sumList; sumList << MyMoneyMoney(); QMap > > sumToComponentsMap; // compute the possible matches QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); QListIterator itSum(sumList); QList tempList; while (itSum.hasNext()) { const MyMoneyMoney &sum = itSum.next(); QList > splitIds; splitIds << qMakePair(transactionSplit.first.id(), transactionSplit.second.id()); if (sumToComponentsMap.contains(sum)) { if (sumToComponentsMap.value(sum).contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { continue; } splitIds.append(sumToComponentsMap.value(sum)); } tempList << transactionSplit.second.shares() + sum; sumToComponentsMap[transactionSplit.second.shares() + sum] = splitIds; int size = sumToComponentsMap.size(); if (size % PROGRESSBAR_STEPS == 0) { kmymoney->slotStatusProgressBar(progressBarIndex++, 0); } if (size > NR_OF_STEPS_LIMIT) { return result; // it's taking too much resources abort the algorithm } } QList unionList; unionList.append(tempList); unionList.append(sumList); qSort(unionList); sumList.clear(); MyMoneyMoney smallestSumFromUnion = unionList.first(); sumList.append(smallestSumFromUnion); QListIterator itUnion(unionList); while (itUnion.hasNext()) { MyMoneyMoney sumFromUnion = itUnion.next(); if (smallestSumFromUnion < MyMoneyMoney(1 - precision / transactions.size())*sumFromUnion) { smallestSumFromUnion = sumFromUnion; sumList.append(sumFromUnion); } } } kmymoney->slotStatusProgressBar(NR_OF_STEPS_LIMIT / PROGRESSBAR_STEPS, 0); if (sumToComponentsMap.contains(amount)) { QListIterator > itTransactionSplit(transactions); while (itTransactionSplit.hasNext()) { const QPair &transactionSplit = itTransactionSplit.next(); const QList > &splitIds = sumToComponentsMap.value(amount); if (splitIds.contains(qMakePair(transactionSplit.first.id(), transactionSplit.second.id()))) { result.append(transactionSplit); } } } #ifdef KMM_DEBUG qDebug("For the amount %s a number of %d possible sums where computed from the set of %d transactions: ", qPrintable(MyMoneyUtils::formatMoney(amount, security)), sumToComponentsMap.size(), transactions.size()); #endif kmymoney->slotStatusProgressBar(-1, -1); return result; } void KMyMoneyApp::slotAccountReconcileStart() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyAccount account; // we cannot reconcile standard accounts if (!file->isStandardAccount(d->m_selectedAccount.id())) { // check if we can reconcile this account // it make's sense for asset and liability accounts try { // check if we have overdue schedules for this account QList schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); if (schedules.count() > 0) { if (KMessageBox::questionYesNo(this, i18n("KMyMoney has detected some overdue scheduled transactions for this account. Do you want to enter those scheduled transactions now?"), i18n("Scheduled transactions found")) == KMessageBox::Yes) { QMap skipMap; bool processedOne; KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter; do { processedOne = false; QList::const_iterator it_sch; for (it_sch = schedules.constBegin(); (rc != KMyMoneyUtils::Cancel) && (it_sch != schedules.constEnd()); ++it_sch) { MyMoneySchedule sch(*(it_sch)); // and enter it if it is not on the skip list if (skipMap.find((*it_sch).id()) == skipMap.end()) { rc = enterSchedule(sch, false, true); if (rc == KMyMoneyUtils::Ignore) { skipMap[(*it_sch).id()] = true; } } } // reload list (maybe this schedule needs to be added again) schedules = file->scheduleList(d->m_selectedAccount.id(), MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); } while (processedOne); } } account = file->account(d->m_selectedAccount.id()); // get rid of previous run. delete d->m_endingBalanceDlg; d->m_endingBalanceDlg = new KEndingBalanceDlg(account, this); if (account.isAssetLiability()) { connect(d->m_endingBalanceDlg, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); connect(d->m_endingBalanceDlg, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), this, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); if (d->m_endingBalanceDlg->exec() == QDialog::Accepted) { if (KMyMoneyGlobalSettings::autoReconciliation()) { MyMoneyMoney startBalance = d->m_endingBalanceDlg->previousBalance(); MyMoneyMoney endBalance = d->m_endingBalanceDlg->endingBalance(); QDate endDate = d->m_endingBalanceDlg->statementDate(); QList > transactionList; MyMoneyTransactionFilter filter(account.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.setDateFilter(QDate(), endDate); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); QList > result = d->automaticReconciliation(account, transactionList, endBalance - startBalance); if (!result.empty()) { QString message = i18n("KMyMoney has detected transactions matching your reconciliation data.\nWould you like KMyMoney to clear these transactions for you?"); if (KMessageBox::questionYesNo(this, message, i18n("Automatic reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "AcceptAutomaticReconciliation") == KMessageBox::Yes) { // mark the transactions cleared KMyMoneyRegister::SelectedTransactions oldSelection = d->m_selectedTransactions; d->m_selectedTransactions.clear(); QListIterator > itTransactionSplitResult(result); while (itTransactionSplitResult.hasNext()) { const QPair &transactionSplit = itTransactionSplitResult.next(); d->m_selectedTransactions.append(KMyMoneyRegister::SelectedTransaction(transactionSplit.first, transactionSplit.second)); } // mark all transactions in d->m_selectedTransactions as 'Cleared' markTransaction(MyMoneySplit::Cleared); d->m_selectedTransactions = oldSelection; } } } if (d->m_myMoneyView->startReconciliation(account, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance())) { // check if the user requests us to create interest // or charge transactions. MyMoneyTransaction ti = d->m_endingBalanceDlg->interestTransaction(); MyMoneyTransaction tc = d->m_endingBalanceDlg->chargeTransaction(); MyMoneyFileTransaction ft; try { if (ti != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(ti); } if (tc != MyMoneyTransaction()) { MyMoneyFile::instance()->addTransaction(tc); } ft.commit(); } catch (const MyMoneyException &e) { qWarning("interest transaction not stored: '%s'", qPrintable(e.what())); } // reload the account object as it might have changed in the meantime d->m_reconciliationAccount = file->account(account.id()); slotUpdateActions(); } } } } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotAccountReconcileFinish() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // retrieve list of all transactions that are not reconciled or cleared QList > transactionList; MyMoneyTransactionFilter filter(d->m_reconciliationAccount.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.setDateFilter(QDate(), d->m_endingBalanceDlg->statementDate()); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); MyMoneyMoney balance = MyMoneyFile::instance()->balance(d->m_reconciliationAccount.id(), d->m_endingBalanceDlg->statementDate()); MyMoneyMoney actBalance, clearedBalance; actBalance = clearedBalance = balance; // walk the list of transactions to figure out the balance(s) QList >::const_iterator it; for (it = transactionList.constBegin(); it != transactionList.constEnd(); ++it) { if ((*it).second.reconcileFlag() == MyMoneySplit::NotReconciled) { clearedBalance -= (*it).second.shares(); } } if (d->m_endingBalanceDlg->endingBalance() != clearedBalance) { QString message = i18n("You are about to finish the reconciliation of this account with a difference between your bank statement and the transactions marked as cleared.\n" "Are you sure you want to finish the reconciliation?"); if (KMessageBox::questionYesNo(this, message, i18n("Confirm end of reconciliation"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::No) return; } MyMoneyFileTransaction ft; // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); // only update the last statement balance here, if we haven't a newer one due // to download of online statements. if (d->m_reconciliationAccount.value("lastImportedTransactionDate").isEmpty() || QDate::fromString(d->m_reconciliationAccount.value("lastImportedTransactionDate"), Qt::ISODate) < d->m_endingBalanceDlg->statementDate()) { d->m_reconciliationAccount.setValue("lastStatementBalance", d->m_endingBalanceDlg->endingBalance().toString()); // in case we override the last statement balance here, we have to make sure // that we don't show the online balance anymore, as it might be different d->m_reconciliationAccount.deletePair("lastImportedTransactionDate"); } d->m_reconciliationAccount.setLastReconciliationDate(d->m_endingBalanceDlg->statementDate()); // keep a record of this reconciliation d->m_reconciliationAccount.addReconciliation(d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->endingBalance()); d->m_reconciliationAccount.deletePair("lastReconciledBalance"); d->m_reconciliationAccount.deletePair("statementBalance"); d->m_reconciliationAccount.deletePair("statementDate"); try { // update the account data file->modifyAccount(d->m_reconciliationAccount); /* // collect the list of cleared splits for this account filter.clear(); filter.addAccount(d->m_reconciliationAccount.id()); filter.addState(MyMoneyTransactionFilter::cleared); filter.setConsiderCategory(false); filter.setReportAllSplits(true); file->transactionList(transactionList, filter); */ // walk the list of transactions/splits and mark the cleared ones as reconciled QList >::iterator it; for (it = transactionList.begin(); it != transactionList.end(); ++it) { MyMoneySplit sp = (*it).second; // skip the ones that are not marked cleared if (sp.reconcileFlag() != MyMoneySplit::Cleared) continue; // always retrieve a fresh copy of the transaction because we // might have changed it already with another split MyMoneyTransaction t = file->transaction((*it).first.id()); sp.setReconcileFlag(MyMoneySplit::Reconciled); sp.setReconcileDate(d->m_endingBalanceDlg->statementDate()); t.modifySplit(sp); // update the engine ... file->modifyTransaction(t); // ... and the list (*it) = qMakePair(t, sp); } ft.commit(); // reload account data from engine as the data might have changed in the meantime d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); emit accountReconciled(d->m_reconciliationAccount, d->m_endingBalanceDlg->statementDate(), d->m_endingBalanceDlg->previousBalance(), d->m_endingBalanceDlg->endingBalance(), transactionList); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting cleared to reconcile"); } } // Turn off reconciliation mode d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } void KMyMoneyApp::slotAccountReconcilePostpone() { MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_reconciliationAccount.id().isEmpty()) { // refresh object d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); // Turn off reconciliation mode d->m_myMoneyView->finishReconciliation(d->m_reconciliationAccount); d->m_reconciliationAccount.setValue("lastReconciledBalance", d->m_endingBalanceDlg->previousBalance().toString()); d->m_reconciliationAccount.setValue("statementBalance", d->m_endingBalanceDlg->endingBalance().toString()); d->m_reconciliationAccount.setValue("statementDate", d->m_endingBalanceDlg->statementDate().toString(Qt::ISODate)); try { file->modifyAccount(d->m_reconciliationAccount); ft.commit(); d->m_reconciliationAccount = MyMoneyAccount(); slotUpdateActions(); } catch (const MyMoneyException &) { qDebug("Unexpected exception when setting last reconcile info into account"); ft.rollback(); d->m_reconciliationAccount = file->account(d->m_reconciliationAccount.id()); } } } void KMyMoneyApp::slotAccountOpen(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; MyMoneyFile* file = MyMoneyFile::instance(); QString id = d->m_selectedAccount.id(); // if the caller passed a non-empty object, we need to select that if (!obj.id().isEmpty()) { id = obj.id(); } // we cannot reconcile standard accounts if (!file->isStandardAccount(id)) { // check if we can open this account // currently it make's sense for asset and liability accounts try { MyMoneyAccount account = file->account(id); d->m_myMoneyView->slotLedgerSelected(account.id()); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::enableCloseAccountAction(const MyMoneyAccount& acc) { switch (canCloseAccount(acc)) { case KMyMoneyUtils::AccountCanClose: { action("account_close")->setEnabled(true); break; } case KMyMoneyUtils::AccountBalanceNonZero: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); break; } case KMyMoneyUtils::AccountChildrenOpen: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); break; } case KMyMoneyUtils::AccountScheduleReference: { action("account_close")->setEnabled(false); action("account_close")->setToolTip(i18n("This account is still included in an active schedule")); break; } } } KMyMoneyUtils::CanCloseAccountCodeE KMyMoneyApp::canCloseAccount(const MyMoneyAccount& acc) const { // balance must be zero if (!acc.balance().isZero()) return KMyMoneyUtils::AccountBalanceNonZero; // all children must be already closed QStringList::const_iterator it_a; for (it_a = acc.accountList().constBegin(); it_a != acc.accountList().constEnd(); ++it_a) { MyMoneyAccount a = MyMoneyFile::instance()->account(*it_a); if (!a.isClosed()) { return KMyMoneyUtils::AccountChildrenOpen; } } // there must be no unfinished schedule referencing the account QList list = MyMoneyFile::instance()->scheduleList(); QList::const_iterator it_l; for (it_l = list.constBegin(); it_l != list.constEnd(); ++it_l) { if ((*it_l).isFinished()) continue; if ((*it_l).hasReferenceTo(acc.id())) return KMyMoneyUtils::AccountScheduleReference; } return KMyMoneyUtils::AccountCanClose; } void KMyMoneyApp::slotAccountClose() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFileTransaction ft; try { a.setClosed(true); MyMoneyFile::instance()->modifyAccount(a); ft.commit(); if (KMyMoneyGlobalSettings::hideClosedAccounts()) { KMessageBox::information(this, QString("") + i18n("You have closed this account. It remains in the system because you have transactions which still refer to it, but it is not shown in the views. You can make it visible again by going to the View menu and selecting Show all accounts or by deselecting the Do not show closed accounts setting.") + QString(""), i18n("Information"), "CloseAccountInfo"); } } catch (const MyMoneyException &) { } } void KMyMoneyApp::slotAccountReopen() { MyMoneyAccount a; if (!d->m_selectedInvestment.id().isEmpty()) a = d->m_selectedInvestment; else if (!d->m_selectedAccount.id().isEmpty()) a = d->m_selectedAccount; if (a.id().isEmpty()) return; // need an account ID MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { while (a.isClosed()) { a.setClosed(false); file->modifyAccount(a); a = file->account(a.parentAccountId()); } ft.commit(); } catch (const MyMoneyException &) { } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyInstitution& _dst) { MyMoneyAccount src(_src); src.setInstitutionId(_dst.id()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(src); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to institution %2. Reason: %3

", src.name(), _dst.name(), e.what())); } } void KMyMoneyApp::slotReparentAccount(const MyMoneyAccount& _src, const MyMoneyAccount& _dst) { MyMoneyAccount src(_src); MyMoneyAccount dst(_dst); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->reparentAccount(src, dst); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("

%1 cannot be moved to %2. Reason: %3

", src.name(), dst.name(), e.what())); } } void KMyMoneyApp::slotAccountTransactionReport() { // Generate a transaction report that contains transactions for only the // currently selected account. if (!d->m_selectedAccount.id().isEmpty()) { MyMoneyReport report( MyMoneyReport::eAccount, MyMoneyReport::eQCnumber | MyMoneyReport::eQCpayee | MyMoneyReport::eQCcategory, MyMoneyTransactionFilter::yearToDate, MyMoneyReport::eDetailAll, i18n("%1 YTD Account Transactions", d->m_selectedAccount.name()), i18n("Generated Report") ); report.setGroup(i18n("Transactions")); report.addAccount(d->m_selectedAccount.id()); d->m_myMoneyView->slotShowReport(report); } } void KMyMoneyApp::slotScheduleNew() { slotScheduleNew(MyMoneyTransaction()); } void KMyMoneyApp::slotScheduleNew(const MyMoneyTransaction& _t, MyMoneySchedule::occurrenceE occurrence) { MyMoneySchedule schedule; schedule.setOccurrence(occurrence); // if the schedule is based on an existing transaction, // we take the post date and project it to the next // schedule in a month. if (_t != MyMoneyTransaction()) { MyMoneyTransaction t(_t); schedule.setTransaction(t); if (occurrence != MyMoneySchedule::OCCUR_ONCE) schedule.setNextDueDate(schedule.nextPayment(t.postDate())); } QPointer dlg = new KEditScheduleDlg(schedule, this); TransactionEditor* transactionEditor = dlg->startEdit(); if (transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); if (dlg->exec() == QDialog::Accepted && dlg != 0) { MyMoneyFileTransaction ft; try { schedule = dlg->schedule(); MyMoneyFile::instance()->addSchedule(schedule); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to add scheduled transaction: %1", e.what()), i18n("Add scheduled transaction")); } } } delete transactionEditor; delete dlg; } void KMyMoneyApp::slotScheduleEdit() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); KEditScheduleDlg* sched_dlg = 0; KEditLoanWizard* loan_wiz = 0; switch (schedule.type()) { case MyMoneySchedule::TYPE_BILL: case MyMoneySchedule::TYPE_DEPOSIT: case MyMoneySchedule::TYPE_TRANSFER: sched_dlg = new KEditScheduleDlg(schedule, this); d->m_transactionEditor = sched_dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(sched_dlg, !KMyMoneySettings::stringMatchFromStart()); if (sched_dlg->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = sched_dlg->schedule(); // Check whether the new Schedule Date // is at or before the lastPaymentDate // If it is, ask the user whether to clear the // lastPaymentDate const QDate& next = sched.nextDueDate(); const QDate& last = sched.lastPayment(); if (next.isValid() && last.isValid() && next <= last) { // Entered a date effectively no later // than previous payment. Date would be // updated automatically so we probably // want to clear it. Let's ask the user. if (KMessageBox::questionYesNo(this, QString("") + i18n("You have entered a scheduled transaction date of %1. Because the scheduled transaction was last paid on %2, KMyMoney will automatically adjust the scheduled transaction date to the next date unless the last payment date is reset. Do you want to reset the last payment date?", QLocale().toString(next, QLocale::ShortFormat), QLocale().toString(last, QLocale::ShortFormat)) + QString(""), i18n("Reset Last Payment Date"), KStandardGuiItem::yes(), KStandardGuiItem::no()) == KMessageBox::Yes) { sched.setLastPayment(QDate()); } } MyMoneyFile::instance()->modifySchedule(sched); // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } deleteTransactionEditor(); } delete sched_dlg; break; case MyMoneySchedule::TYPE_LOANPAYMENT: loan_wiz = new KEditLoanWizard(schedule.account(2)); connect(loan_wiz, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(loan_wiz, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (loan_wiz->exec() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifySchedule(loan_wiz->schedule()); MyMoneyFile::instance()->modifyAccount(loan_wiz->account()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } delete loan_wiz; break; case MyMoneySchedule::TYPE_ANY: break; } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDelete() { if (!d->m_selectedSchedule.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); QString msg = i18n("

Are you sure you want to delete the scheduled transaction %1?

", d->m_selectedSchedule.name()); if (sched.type() == MyMoneySchedule::TYPE_LOANPAYMENT) { msg += QString(" "); msg += i18n("In case of loan payments it is currently not possible to recreate the scheduled transaction."); } if (KMessageBox::questionYesNo(this, msg) == KMessageBox::No) return; MyMoneyFile::instance()->removeSchedule(sched); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("schedule_duplicate")->isEnabled()) { MyMoneySchedule sch = d->m_selectedSchedule; sch.clearId(); sch.setLastPayment(QDate()); sch.setName(i18nc("Copy of scheduled transaction name", "Copy of %1", sch.name())); // make sure that we set a valid next due date if the original next due date is invalid if (!sch.nextDueDate().isValid()) sch.setNextDueDate(QDate::currentDate()); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addSchedule(sch); ft.commit(); // select the new schedule in the view if (!d->m_selectedSchedule.id().isEmpty()) d->m_myMoneyView->slotScheduleSelected(sch.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to duplicate scheduled transaction: '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::slotScheduleSkip() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); skipSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } void KMyMoneyApp::skipSchedule(MyMoneySchedule& schedule) { if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); if (!schedule.isFinished()) { if (schedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { QDate next = schedule.nextDueDate(); if (!schedule.isFinished() && (KMessageBox::questionYesNo(this, QString("") + i18n("Do you really want to skip the %1 transaction scheduled for %2?", schedule.name(), QLocale().toString(next, QLocale::ShortFormat)) + QString(""))) == KMessageBox::Yes) { MyMoneyFileTransaction ft; schedule.setLastPayment(next); schedule.setNextDueDate(schedule.nextPayment(next)); MyMoneyFile::instance()->modifySchedule(schedule); ft.commit(); } } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, QString("") + i18n("Unable to skip scheduled transaction %1.", schedule.name()) + QString(""), e.what()); } } } void KMyMoneyApp::slotScheduleEnter() { if (!d->m_selectedSchedule.id().isEmpty()) { try { MyMoneySchedule schedule = MyMoneyFile::instance()->schedule(d->m_selectedSchedule.id()); enterSchedule(schedule); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unknown scheduled transaction '%1'", d->m_selectedSchedule.name()), e.what()); } } } KMyMoneyUtils::EnterScheduleResultCodeE KMyMoneyApp::enterSchedule(MyMoneySchedule& schedule, bool autoEnter, bool extendedKeys) { KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Cancel; if (!schedule.id().isEmpty()) { try { schedule = MyMoneyFile::instance()->schedule(schedule.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); return rc; } QPointer dlg = new KEnterScheduleDlg(this, schedule); try { QDate origDueDate = schedule.nextDueDate(); dlg->showExtendedKeys(extendedKeys); d->m_transactionEditor = dlg->startEdit(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); MyMoneyTransaction torig, taccepted; d->m_transactionEditor->createTransaction(torig, dlg->transaction(), schedule.transaction().splits().isEmpty() ? MyMoneySplit() : schedule.transaction().splits().front(), true); // force actions to be available no matter what (will be updated according to the state during // slotTransactionsEnter or slotTransactionsCancel) kmymoney->action("transaction_cancel")->setEnabled(true); kmymoney->action("transaction_enter")->setEnabled(true); KConfirmManualEnterDlg::Action action = KConfirmManualEnterDlg::ModifyOnce; if (!autoEnter || !schedule.isFixed()) { for (; dlg != 0;) { rc = KMyMoneyUtils::Cancel; if (dlg->exec() == QDialog::Accepted && dlg != 0) { rc = dlg->resultCode(); if (rc == KMyMoneyUtils::Enter) { d->m_transactionEditor->createTransaction(taccepted, torig, torig.splits().isEmpty() ? MyMoneySplit() : torig.splits().front(), true); // make sure to suppress comparison of some data: postDate torig.setPostDate(taccepted.postDate()); if (torig != taccepted) { QPointer cdlg = new KConfirmManualEnterDlg(schedule, this); cdlg->loadTransactions(torig, taccepted); if (cdlg->exec() == QDialog::Accepted) { action = cdlg->action(); delete cdlg; break; } delete cdlg; // the user has chosen 'cancel' during confirmation, // we go back to the editor continue; } } else if (rc == KMyMoneyUtils::Skip) { slotTransactionsCancel(); skipSchedule(schedule); } else { slotTransactionsCancel(); } } else { if (autoEnter) { if (KMessageBox::warningYesNo(this, i18n("Are you sure you wish to stop this scheduled transaction from being entered into the register?\n\nKMyMoney will prompt you again next time it starts unless you manually enter it later.")) == KMessageBox::No) { // the user has chosen 'No' for the above question, // we go back to the editor continue; } } slotTransactionsCancel(); } break; } } // if we still have the editor around here, the user did not cancel if ((d->m_transactionEditor != 0) && (dlg != 0)) { MyMoneyFileTransaction ft; try { MyMoneyTransaction t; // add the new transaction switch (action) { case KConfirmManualEnterDlg::UseOriginal: // setup widgets with original transaction data d->m_transactionEditor->setTransaction(dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front()); // and create a transaction based on that data taccepted = MyMoneyTransaction(); d->m_transactionEditor->createTransaction(taccepted, dlg->transaction(), dlg->transaction().splits().isEmpty() ? MyMoneySplit() : dlg->transaction().splits().front(), true); break; case KConfirmManualEnterDlg::ModifyAlways: torig = taccepted; torig.setPostDate(origDueDate); schedule.setTransaction(torig); break; case KConfirmManualEnterDlg::ModifyOnce: break; } QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId, false)) { if (!newId.isEmpty()) { MyMoneyTransaction t = MyMoneyFile::instance()->transaction(newId); schedule.setLastPayment(t.postDate()); } // in case the next due date is invalid, the schedule is finished // we mark it as such by setting the next due date to one day past the end QDate nextDueDate = schedule.nextPayment(origDueDate); if (!nextDueDate.isValid()) { schedule.setNextDueDate(schedule.endDate().addDays(1)); } else { schedule.setNextDueDate(nextDueDate); } MyMoneyFile::instance()->modifySchedule(schedule); rc = KMyMoneyUtils::Enter; // delete the editor before we emit the dataChanged() signal from the // engine. Calling this twice in a row does not hurt. deleteTransactionEditor(); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } deleteTransactionEditor(); } } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to enter scheduled transaction '%1'", schedule.name()), e.what()); } delete dlg; } return rc; } bool KMyMoneyApp::slotPayeeNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Payee")) { // Ask the user if that is what he intended to do? QString msg = QLatin1String("") + i18n("Do you want to add %1 as payer/receiver?", newnameBase) + QLatin1String(""); if (KMessageBox::questionYesNo(this, msg, i18n("New payee/receiver"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewPayee") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->payeeByName(newname); newname = QString("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyPayee p; p.setName(newname); MyMoneyFile::instance()->addPayee(p); id = p.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); doit = false; } } return doit; } void KMyMoneyApp::slotPayeeNew() { QString id; slotPayeeNew(i18n("New Payee"), id); // the callbacks should have made sure, that the payees view has been // updated already. So we search for the id in the list of items // and select it. emit payeeCreated(id); } bool KMyMoneyApp::payeeInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotPayeeDelete() { if (d->m_selectedPayees.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayees.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

", d->m_selectedPayees.front().name()); else prompt = i18n("Do you really want to remove all selected payees?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No) return; payeeReassign(KPayeeReassignDlg::TypeDelete); } void KMyMoneyApp::slotPayeeMerge() { if (d->m_selectedPayees.size() < 1) return; // shouldn't happen if (KMessageBox::questionYesNo(this, i18n("

Do you really want to merge the selected payees?"), i18n("Merge Payees")) == KMessageBox::No) return; if (payeeReassign(KPayeeReassignDlg::TypeMerge)) // clean selection since we just deleted the selected payees slotSelectPayees(QList()); } bool KMyMoneyApp::payeeReassign(int type) { if (!(type >= 0 && type < KPayeeReassignDlg::TypeCount)) return false; MyMoneyFile * file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { // create a transaction filter that contains all payees selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedPayees.constBegin(); it != d->m_selectedPayees.constEnd(); ++it) { f.addPayee((*it).id()); } // request a list of all transactions that still use the payees in question QList translist = file->transactionList(f); // qDebug() << "[KPayeesView::slotDeletePayee] " << translist.count() << " transaction still assigned to payees"; // now get a list of all schedules that make use of one of the payees QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { // is the payee in the split to be deleted? if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { used_schedules.push_back(*it); // remember this schedule break; } } } // qDebug() << "[KPayeesView::slotDeletePayee] " << used_schedules.count() << " schedules use one of the selected payees"; // and a list of all loan accounts that references one of the payees QList allAccounts; QList usedAccounts; file->accountList(allAccounts); foreach (const MyMoneyAccount &account, allAccounts) { if (account.isLoan()) { MyMoneyAccountLoan loanAccount(account); foreach (const MyMoneyPayee &payee, d->m_selectedPayees) { if (loanAccount.hasReferenceTo(payee.id())) { usedAccounts.append(account); } } } } MyMoneyPayee newPayee; bool addToMatchList = false; // if at least one payee is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty() || !usedAccounts.isEmpty()) { // first create list with all non-selected payees QList remainingPayees; if (type == KPayeeReassignDlg::TypeMerge) { remainingPayees = d->m_selectedPayees; } else { remainingPayees = file->payeeList(); QList::iterator it_p; for (it_p = remainingPayees.begin(); it_p != remainingPayees.end();) { if (d->m_selectedPayees.contains(*it_p)) { it_p = remainingPayees.erase(it_p); } else { ++it_p; } } } // show error message if no payees remain if (remainingPayees.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction or loan account is still referenced by a payee. " "Currently you have all payees selected. However, at least one payee must remain so " "that the transaction/scheduled transaction or loan account can be reassigned.")); return false; } // show transaction reassignment dialog KPayeeReassignDlg * dlg = new KPayeeReassignDlg(static_cast(type), this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString payee_id = dlg->show(remainingPayees); addToMatchList = dlg->addToMatchList(); delete dlg; // and kill the dialog if (payee_id.isEmpty()) return false; // the user aborted the dialog, so let's abort as well // try to get selected payee. If not possible and we are merging payees, // then we create a new one try { newPayee = file->payee(payee_id); } catch (const MyMoneyException &e) { if (type == KPayeeReassignDlg::TypeMerge) { // it's ok to use payee_id for both arguments since the first is const, // so it's garantee not to change its content if (!slotPayeeNew(payee_id, payee_id)) return false; // the user aborted the dialog, so let's abort as well newPayee = file->payee(payee_id); } else { return false; } } // TODO : check if we have a report that explicitively uses one of our payees // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign payee for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { // if the split is assigned to one of the selected payees, we need to modify it if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); // first modify payee in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign payees for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { if (payeeInList(d->m_selectedPayees, (*s_it).payeeId())) { (*s_it).setPayeeId(payee_id); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules // reassign the payees in the loans that reference the deleted payees foreach (const MyMoneyAccount &account, usedAccounts) { MyMoneyAccountLoan loanAccount(account); loanAccount.setPayee(payee_id); file->modifyAccount(loanAccount); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign payee of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { // if !translist.isEmpty() if (type == KPayeeReassignDlg::TypeMerge) { KMessageBox::sorry(this, i18n("Nothing to merge."), i18n("Merge Payees")); return false; } } bool ignorecase; QStringList payeeNames; MyMoneyPayee::payeeMatchType matchType = newPayee.matchData(ignorecase, payeeNames); QStringList deletedPayeeNames; // now loop over all selected payees and remove them for (QList::iterator it = d->m_selectedPayees.begin(); it != d->m_selectedPayees.end(); ++it) { if (newPayee.id() != (*it).id()) { if (addToMatchList) { deletedPayeeNames << (*it).name(); } file->removePayee(*it); } } // if we initially have no matching turned on, we just ignore the case (default) if (matchType == MyMoneyPayee::matchDisabled) ignorecase = true; // update the destination payee if this was requested by the user if (addToMatchList && deletedPayeeNames.count() > 0) { // add new names to the list // TODO: it would be cool to somehow shrink the list to make better use // of regular expressions at this point. For now, we leave this task // to the user himeself. QStringList::const_iterator it_n; for (it_n = deletedPayeeNames.constBegin(); it_n != deletedPayeeNames.constEnd(); ++it_n) { if (matchType == MyMoneyPayee::matchKey) { // make sure we really need it and it is not caught by an existing regexp QStringList::const_iterator it_k; for (it_k = payeeNames.constBegin(); it_k != payeeNames.constEnd(); ++it_k) { QRegExp exp(*it_k, ignorecase ? Qt::CaseInsensitive : Qt::CaseSensitive); if (exp.indexIn(*it_n) != -1) break; } if (it_k == payeeNames.constEnd()) payeeNames << QRegExp::escape(*it_n); } else if (payeeNames.contains(*it_n) == 0) payeeNames << QRegExp::escape(*it_n); } // and update the payee in the engine context // make sure to turn on matching for this payee in the right mode newPayee.setMatchData(MyMoneyPayee::matchKey, ignorecase, payeeNames); file->modifyPayee(newPayee); } ft.commit(); // If we just deleted the payees, they sure don't exist anymore slotSelectPayees(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove payee(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } return true; } void KMyMoneyApp::slotTagNew(const QString& newnameBase, QString& id) { bool doit = true; if (newnameBase != i18n("New Tag")) { // Ask the user if that is what he intended to do? QString msg = QString("") + i18n("Do you want to add %1 as tag?", newnameBase) + QString(""); if (KMessageBox::questionYesNo(this, msg, i18n("New tag"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "NewTag") == KMessageBox::No) { doit = false; // we should not keep the 'no' setting because that can confuse people like // I have seen in some usability tests. So we just delete it right away. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag")); } } } if (doit) { MyMoneyFileTransaction ft; try { QString newname(newnameBase); // adjust name until a unique name has been created int count = 0; for (;;) { try { MyMoneyFile::instance()->tagByName(newname); newname = QString("%1 [%2]").arg(newnameBase).arg(++count); } catch (const MyMoneyException &) { break; } } MyMoneyTag ta; ta.setName(newname); MyMoneyFile::instance()->addTag(ta); id = ta.id(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to add tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotTagNew() { QString id; slotTagNew(i18n("New Tag"), id); // the callbacks should have made sure, that the tags view has been // updated already. So we search for the id in the list of items // and select it. emit tagCreated(id); } bool KMyMoneyApp::tagInList(const QList& list, const QString& id) const { bool rc = false; QList::const_iterator it_p = list.begin(); while (it_p != list.end()) { if ((*it_p).id() == id) { rc = true; break; } ++it_p; } return rc; } void KMyMoneyApp::slotTagDelete() { if (d->m_selectedTags.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // first create list with all non-selected tags QList remainingTags = file->tagList(); QList::iterator it_ta; for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) { if (d->m_selectedTags.contains(*it_ta)) { it_ta = remainingTags.erase(it_ta); } else { ++it_ta; } } // get confirmation from user QString prompt; if (d->m_selectedTags.size() == 1) prompt = i18n("

Do you really want to remove the tag %1?

", d->m_selectedTags.front().name()); else prompt = i18n("Do you really want to remove all selected tags?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // create a transaction filter that contains all tags selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedTags.constBegin(); it != d->m_selectedTags.constEnd(); ++it) { f.addTag((*it).id()); } // request a list of all transactions that still use the tags in question QList translist = file->transactionList(f); // qDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags"; // now get a list of all schedules that make use of one of the tags QList all_schedules = file->scheduleList(); QList used_schedules; for (QList::ConstIterator it = all_schedules.constBegin(); it != all_schedules.constEnd(); ++it) { // loop over all splits in the transaction of the schedule for (QList::ConstIterator s_it = (*it).transaction().splits().constBegin(); s_it != (*it).transaction().splits().constEnd(); ++s_it) { for (int i = 0; i < (*s_it).tagIdList().size(); i++) { // is the tag in the split to be deleted? if (tagInList(d->m_selectedTags, (*s_it).tagIdList()[i])) { used_schedules.push_back(*it); // remember this schedule break; } } } } // qDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags"; MyMoneyTag newTag; // if at least one tag is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty()) { // show error message if no tags remain //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction if (remainingTags.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. " "Currently you have all tags selected. However, at least one tag must remain so " "that the transaction/scheduled transaction can be reassigned.")); return; } // show transaction reassignment dialog KTagReassignDlg * dlg = new KTagReassignDlg(this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); QString tag_id = dlg->show(remainingTags); delete dlg; // and kill the dialog if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one. return; // the user aborted the dialog, so let's abort as well newTag = file->tag(tag_id); // TODO : check if we have a report that explicitively uses one of our tags // and issue an appropriate warning try { QList::iterator s_it; // now loop over all transactions and reassign tag for (QList::iterator it = translist.begin(); it != translist.end(); ++it) { // create a copy of the splits list in the transaction QList splits = (*it).splits(); // loop over all splits for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { // if the split is assigned to one of the selected tags, we need to modify it if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); // first modify tag list in current split // then modify the split in our local copy of the transaction list (*it).modifySplit(*s_it); // this does not modify the list object 'splits'! } // for - Splits file->modifyTransaction(*it); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign tags for (QList::iterator it = used_schedules.begin(); it != used_schedules.end(); ++it) { // create copy of transaction in current schedule MyMoneyTransaction trans = (*it).transaction(); // create copy of lists of splits QList splits = trans.splits(); for (s_it = splits.begin(); s_it != splits.end(); ++s_it) { QList tagIdList = (*s_it).tagIdList(); for (int i = 0; i < tagIdList.size(); i++) { if (tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } (*s_it).setTagIdList(tagIdList); trans.modifySplit(*s_it); // does not modify the list object 'splits'! } // for - Splits // store transaction in current schedule (*it).setTransaction(trans); file->modifySchedule(*it); // modify the schedule in the MyMoney engine } // for - Schedules } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reassign tag of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } // if !translist.isEmpty() // now loop over all selected tags and remove them for (QList::iterator it = d->m_selectedTags.begin(); it != d->m_selectedTags.end(); ++it) { file->removeTag(*it); } ft.commit(); // If we just deleted the tags, they sure don't exist anymore slotSelectTags(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to remove tag(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotCurrencyNew() { QString sid = QInputDialog::getText(0, i18n("New currency"), i18n("Enter ISO 4217 code for the new currency")); if (!sid.isEmpty()) { QString id(sid); MyMoneySecurity currency(id, i18n("New currency")); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->addCurrency(currency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot create new currency. %1", e.what()), i18n("New currency")); } emit currencyCreated(id); } } void KMyMoneyApp::slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol) { MyMoneyFile* file = MyMoneyFile::instance(); try { if (currencyName != d->m_selectedCurrency.name() || currencyTradingSymbol != d->m_selectedCurrency.tradingSymbol()) { MyMoneySecurity currency = file->currency(currencyId); currency.setName(currencyName); currency.setTradingSymbol(currencyTradingSymbol); MyMoneyFileTransaction ft; try { file->modifyCurrency(currency); d->m_selectedCurrency = currency; ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot update currency. %1", e.what()), i18n("Update currency")); } } void KMyMoneyApp::slotCurrencyDelete() { if (!d->m_selectedCurrency.id().isEmpty()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->removeCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot delete currency %1. %2", d->m_selectedCurrency.name(), e.what()), i18n("Delete currency")); } } } void KMyMoneyApp::slotCurrencySetBase() { if (!d->m_selectedCurrency.id().isEmpty()) { if (d->m_selectedCurrency.id() != MyMoneyFile::instance()->baseCurrency().id()) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(d->m_selectedCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", d->m_selectedCurrency.name(), e.what()), i18n("Set base currency")); } } } } void KMyMoneyApp::slotBudgetNew() { QDate date = QDate::currentDate(); date.setDate(date.year(), 1, 1); QString newname = i18n("Budget %1", date.year()); MyMoneyBudget budget; // make sure we have a unique name try { int i = 1; // Exception thrown when the name is not found while (1) { MyMoneyFile::instance()->budgetByName(newname); newname = i18n("Budget %1 (%2)", date.year(), i++); } } catch (const MyMoneyException &) { // all ok, the name is unique } MyMoneyFileTransaction ft; try { budget.setName(newname); budget.setBudgetStart(date); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetDelete() { if (d->m_selectedBudgets.isEmpty()) return; // shouldn't happen MyMoneyFile * file = MyMoneyFile::instance(); // get confirmation from user QString prompt; if (d->m_selectedBudgets.size() == 1) prompt = i18n("

Do you really want to remove the budget %1?

", d->m_selectedBudgets.front().name()); else prompt = i18n("Do you really want to remove all selected budgets?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Budget")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // now loop over all selected budgets and remove them for (QList::iterator it = d->m_selectedBudgets.begin(); it != d->m_selectedBudgets.end(); ++it) { file->removeBudget(*it); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to remove budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotBudgetCopy() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.first(); budget.clearId(); budget.setName(i18n("Copy of %1", budget.name())); MyMoneyFile::instance()->addBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to add budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotBudgetChangeYear() { if (d->m_selectedBudgets.size() == 1) { QStringList years; int current = 0; bool haveCurrent = false; MyMoneyBudget budget = *(d->m_selectedBudgets.begin()); for (int i = (QDate::currentDate().year() - 3); i < (QDate::currentDate().year() + 5); ++i) { years << QString("%1").arg(i); if (i == budget.budgetStart().year()) { haveCurrent = true; } if (!haveCurrent) ++current; } if (!haveCurrent) current = 0; bool ok = false; QString yearString = QInputDialog::getItem(this, i18n("Select year"), i18n("Budget year"), years, current, false, &ok); if (ok) { int year = yearString.toInt(0, 0); QDate newYear = QDate(year, 1, 1); if (newYear != budget.budgetStart()) { MyMoneyFileTransaction ft; try { budget.setBudgetStart(newYear); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } } } void KMyMoneyApp::slotBudgetForecast() { if (d->m_selectedBudgets.size() == 1) { MyMoneyFileTransaction ft; try { MyMoneyBudget budget = d->m_selectedBudgets.first(); bool calcBudget = budget.getaccounts().count() == 0; if (!calcBudget) { if (KMessageBox::warningContinueCancel(0, i18n("The current budget already contains data. Continuing will replace all current values of this budget."), i18nc("Warning message box", "Warning")) == KMessageBox::Continue) calcBudget = true; } if (calcBudget) { QDate historyStart; QDate historyEnd; QDate budgetStart; QDate budgetEnd; budgetStart = budget.budgetStart(); budgetEnd = budgetStart.addYears(1).addDays(-1); historyStart = budgetStart.addYears(-1); historyEnd = budgetEnd.addYears(-1); MyMoneyForecast forecast = KMyMoneyGlobalSettings::forecast(); forecast.createBudget(budget, historyStart, historyEnd, budgetStart, budgetEnd, true); MyMoneyFile::instance()->modifyBudget(budget); ft.commit(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify budget: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KMyMoneyApp::slotKDELanguageSettings() { KMessageBox::information(this, i18n("Please be aware that changes made in the following dialog affect all KDE applications not only KMyMoney."), i18nc("Warning message box", "Warning"), "LanguageSettingsWarning"); QStringList args; args << "language"; QString error; int pid; KToolInvocation::kdeinitExec("kcmshell4", args, &error, &pid); } void KMyMoneyApp::slotNewFeature() { } void KMyMoneyApp::slotTransactionsDelete() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (!kmymoney->action("transaction_delete")->isEnabled()) return; if (d->m_selectedTransactions.isEmpty()) return; if (d->m_selectedTransactions.warnLevel() == 1) { if (KMessageBox::warningContinueCancel(0, i18n("At least one split of the selected transactions has been reconciled. " "Do you wish to delete the transactions anyway?"), i18n("Transaction already reconciled")) == KMessageBox::Cancel) return; } QString msg = i18np("Do you really want to delete the selected transaction?", "Do you really want to delete all %1 selected transactions?", d->m_selectedTransactions.count()); if (KMessageBox::questionYesNo(this, msg, i18n("Delete transaction")) == KMessageBox::Yes) { KMSTATUS(i18n("Deleting transactions")); doDeleteTransactions(); } } void KMyMoneyApp::slotTransactionDuplicate() { // since we may jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_duplicate")->isEnabled()) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int i = 0; int cnt = d->m_selectedTransactions.count(); KMSTATUS(i18n("Duplicating transactions")); slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyTransaction lt; try { for (it_t = list.begin(); it_t != list.end(); ++it_t) { MyMoneyTransaction t = (*it_t).transaction(); QList::iterator it_s; // wipe out any reconciliation information for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { (*it_s).setReconcileFlag(MyMoneySplit::NotReconciled); (*it_s).setReconcileDate(QDate()); (*it_s).setBankID(QString()); } // clear invalid data t.setEntryDate(QDate()); t.clearId(); // and set the post date to today t.setPostDate(QDate::currentDate()); MyMoneyFile::instance()->addTransaction(t); lt = t; slotStatusProgressBar(i++, 0); } ft.commit(); // select the new transaction in the ledger if (!d->m_selectedAccount.id().isEmpty()) d->m_myMoneyView->slotLedgerSelected(d->m_selectedAccount.id(), lt.id()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to duplicate transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } // switch off the progress bar slotStatusProgressBar(-1, -1); } } void KMyMoneyApp::doDeleteTransactions() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; MyMoneyFile* file = MyMoneyFile::instance(); try { it_t = list.begin(); while (it_t != list.end()) { // only remove those transactions that do not reference a closed account if (!file->referencesClosedAccount((*it_t).transaction())) { file->removeTransaction((*it_t).transaction()); // remove all those references in the list of selected transactions // that refer to the same transaction we just removed so that we // will not be caught by an exception later on (see bko #285310) KMyMoneyRegister::SelectedTransactions::iterator it_td = it_t; ++it_td; while (it_td != list.end()) { if ((*it_t).transaction().id() == (*it_td).transaction().id()) { it_td = list.erase(it_td); i++; // bump count of deleted transactions } else { ++it_td; } } } // need to ensure "nextCheckNumber" is still correct MyMoneyAccount acc = file->account((*it_t).split().accountId()); // the "lastNumberUsed" might have been the txn number deleted // so adjust it QString deletedNum = (*it_t).split().number(); // decrement deletedNum and set new "lastNumberUsed" QString num = KMyMoneyUtils::getAdjacentNumber(deletedNum, -1); acc.setValue("lastNumberUsed", num); file->modifyAccount(acc); list.erase(it_t); it_t = list.begin(); slotStatusProgressBar(i++, 0); } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to delete transaction(s): %1, thrown in %2:%3", e.what(), e.file(), e.line())); } slotStatusProgressBar(-1, -1); } void KMyMoneyApp::slotTransactionsNew() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_new")->isEnabled()) { if (d->m_myMoneyView->createNewTransaction()) { d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !d->m_lastPayeeEnteredId.isEmpty()) { // in case we entered a new transaction before and used a payee, // we reuse it here. Save the text to the edit widget, select it // so that hitting any character will start entering another payee. payeeEdit->setSelectedItem(d->m_lastPayeeEnteredId); payeeEdit->lineEdit()->selectAll(); } if (d->m_transactionEditor) { connect(d->m_transactionEditor, SIGNAL(statusProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); connect(d->m_transactionEditor, SIGNAL(statusMsg(QString)), this, SLOT(slotStatusMsg(QString))); connect(d->m_transactionEditor, SIGNAL(scheduleTransaction(MyMoneyTransaction,MyMoneySchedule::occurrenceE)), this, SLOT(slotScheduleNew(MyMoneyTransaction,MyMoneySchedule::occurrenceE))); } slotUpdateActions(); } } } } void KMyMoneyApp::slotTransactionsEdit() { // qDebug("KMyMoneyApp::slotTransactionsEdit()"); // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_edit")->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); slotUpdateActions(); } } void KMyMoneyApp::deleteTransactionEditor() { // make sure, we don't use the transaction editor pointer // anymore from now on TransactionEditor* p = d->m_transactionEditor; d->m_transactionEditor = 0; delete p; } void KMyMoneyApp::slotTransactionsEditSplits() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_editsplits")->isEnabled()) { // as soon as we edit a transaction, we don't remember the last payee entered d->m_lastPayeeEnteredId.clear(); d->m_transactionEditor = d->m_myMoneyView->startEdit(d->m_selectedTransactions); slotUpdateActions(); if (d->m_transactionEditor) { KMyMoneyMVCCombo::setSubstringSearchForChildren(d->m_myMoneyView, !KMyMoneySettings::stringMatchFromStart()); if (d->m_transactionEditor->slotEditSplits() == QDialog::Accepted) { MyMoneyFileTransaction ft; try { QString id; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); d->m_transactionEditor->enterTransactions(id); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } } deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancel() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_cancel")->isEnabled()) { // make sure, we block the enter function action("transaction_enter")->setEnabled(false); // qDebug("KMyMoneyApp::slotTransactionsCancel"); deleteTransactionEditor(); slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsEnter() { // since we jump here via code, we have to make sure to react only // if the action is enabled if (kmymoney->action("transaction_enter")->isEnabled()) { // disable the action while we process it to make sure it's processed only once since // d->m_transactionEditor->enterTransactions(newId) will run QCoreApplication::processEvents // we could end up here twice which will cause a crash slotUpdateActions() will enable the action again kmymoney->action("transaction_enter")->setEnabled(false); if (d->m_transactionEditor) { QString accountId = d->m_selectedAccount.id(); QString newId; connect(d->m_transactionEditor, SIGNAL(balanceWarning(QWidget*,MyMoneyAccount,QString)), d->m_balanceWarning, SLOT(slotShowMessage(QWidget*,MyMoneyAccount,QString))); if (d->m_transactionEditor->enterTransactions(newId)) { KMyMoneyPayeeCombo* payeeEdit = dynamic_cast(d->m_transactionEditor->haveWidget("payee")); if (payeeEdit && !newId.isEmpty()) { d->m_lastPayeeEnteredId = payeeEdit->selectedItem(); } deleteTransactionEditor(); } if (!newId.isEmpty()) { d->m_myMoneyView->slotLedgerSelected(accountId, newId); } } slotUpdateActions(); } } void KMyMoneyApp::slotTransactionsCancelOrEnter(bool& okToSelect) { static bool oneTime = false; if (!oneTime) { oneTime = true; QString dontShowAgain = "CancelOrEditTransaction"; // qDebug("KMyMoneyApp::slotCancelOrEndEdit"); if (d->m_transactionEditor) { if (KMyMoneyGlobalSettings::focusChangeIsEnter() && kmymoney->action("transaction_enter")->isEnabled()) { slotTransactionsEnter(); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } } else { // okToSelect is preset to true if a cancel of the dialog is useful and false if it is not int rc; KGuiItem noGuiItem = KStandardGuiItem::save(); KGuiItem yesGuiItem = KStandardGuiItem::discard(); KGuiItem cancelGuiItem = KStandardGuiItem::cont(); // if the transaction can't be entered make sure that it can't be entered by pressing no either if (!kmymoney->action("transaction_enter")->isEnabled()) { noGuiItem.setEnabled(false); noGuiItem.setToolTip(kmymoney->action("transaction_enter")->toolTip()); } if (okToSelect == true) { rc = KMessageBox::warningYesNoCancel(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, cancelGuiItem, dontShowAgain); } else { rc = KMessageBox::warningYesNo(0, i18n("

Please select what you want to do: discard the changes, save the changes or continue to edit the transaction.

You can also set an option to save the transaction automatically when e.g. selecting another transaction.

"), i18n("End transaction edit"), yesGuiItem, noGuiItem, dontShowAgain); } switch (rc) { case KMessageBox::Yes: slotTransactionsCancel(); break; case KMessageBox::No: slotTransactionsEnter(); // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); if (d->m_transactionEditor) { // if at this stage the editor is still there that means that entering the transaction was cancelled // for example by pressing cancel on the exchange rate editor so we must stay in edit mode okToSelect = false; } break; case KMessageBox::Cancel: // make sure that we'll see this message the next time no matter // if the user has chosen the 'Don't show again' checkbox KMessageBox::enableMessage(dontShowAgain); okToSelect = false; break; } } } oneTime = false; } } void KMyMoneyApp::slotToggleReconciliationFlag() { markTransaction(MyMoneySplit::Unknown); } void KMyMoneyApp::slotMarkTransactionCleared() { markTransaction(MyMoneySplit::Cleared); } void KMyMoneyApp::slotMarkTransactionReconciled() { markTransaction(MyMoneySplit::Reconciled); } void KMyMoneyApp::slotMarkTransactionNotReconciled() { markTransaction(MyMoneySplit::NotReconciled); } void KMyMoneyApp::markTransaction(MyMoneySplit::reconcileFlagE flag) { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // turn on signals before we modify the last entry in the list cnt--; MyMoneyFile::instance()->blockSignals(cnt != 0); // get a fresh copy MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); MyMoneySplit sp = t.splitById((*it_t).split().id()); if (sp.reconcileFlag() != flag) { if (flag == MyMoneySplit::Unknown) { if (d->m_reconciliationAccount.id().isEmpty()) { // in normal mode we cycle through all states switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::Reconciled); break; case MyMoneySplit::Reconciled: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } else { // in reconciliation mode we skip the reconciled state switch (sp.reconcileFlag()) { case MyMoneySplit::NotReconciled: sp.setReconcileFlag(MyMoneySplit::Cleared); break; case MyMoneySplit::Cleared: sp.setReconcileFlag(MyMoneySplit::NotReconciled); break; default: break; } } } else { sp.setReconcileFlag(flag); } t.modifySplit(sp); MyMoneyFile::instance()->modifyTransaction(t); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to modify transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionsAccept() { KMyMoneyRegister::SelectedTransactions list = d->m_selectedTransactions; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; int cnt = list.count(); int i = 0; slotStatusProgressBar(0, cnt); MyMoneyFileTransaction ft; try { for (it_t = list.constBegin(); it_t != list.constEnd(); ++it_t) { // reload transaction in case it got changed during the course of this loop MyMoneyTransaction t = MyMoneyFile::instance()->transaction((*it_t).transaction().id()); if (t.isImported()) { t.setImported(false); if (!d->m_selectedAccount.id().isEmpty()) { QList list = t.splits(); QList::const_iterator it_s; for (it_s = list.constBegin(); it_s != list.constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { if ((*it_s).reconcileFlag() == MyMoneySplit::NotReconciled) { MyMoneySplit s = (*it_s); s.setReconcileFlag(MyMoneySplit::Cleared); t.modifySplit(s); } } } } MyMoneyFile::instance()->modifyTransaction(t); } if ((*it_t).split().isMatched()) { // reload split in case it got changed during the course of this loop MyMoneySplit s = t.splitById((*it_t).split().id()); TransactionMatcher matcher(d->m_selectedAccount); matcher.accept(t, s); } slotStatusProgressBar(i++, 0); } slotStatusProgressBar(-1, -1); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Error"), i18n("Unable to accept transaction: %1, thrown in %2:%3", e.what(), e.file(), e.line())); } } void KMyMoneyApp::slotTransactionGotoAccount() { if (!d->m_accountGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass a copy, as d->myMoneyView->slotLedgerSelected() overrides // d->m_accountGoto while calling slotUpdateActions() QString accountId = d->m_accountGoto; d->m_myMoneyView->slotLedgerSelected(accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionGotoPayee() { if (!d->m_payeeGoto.isEmpty()) { try { QString transactionId; if (d->m_selectedTransactions.count() == 1) { transactionId = d->m_selectedTransactions[0].transaction().id(); } // make sure to pass copies, as d->myMoneyView->slotPayeeSelected() overrides // d->m_payeeGoto and d->m_selectedAccount while calling slotUpdateActions() QString payeeId = d->m_payeeGoto; QString accountId = d->m_selectedAccount.id(); d->m_myMoneyView->slotPayeeSelected(payeeId, accountId, transactionId); } catch (const MyMoneyException &) { } } } void KMyMoneyApp::slotTransactionCreateSchedule() { if (d->m_selectedTransactions.count() == 1) { // make sure to have the current selected split as first split in the schedule MyMoneyTransaction t = d->m_selectedTransactions[0].transaction(); MyMoneySplit s = d->m_selectedTransactions[0].split(); QString splitId = s.id(); s.clearId(); s.setReconcileFlag(MyMoneySplit::NotReconciled); s.setReconcileDate(QDate()); t.removeSplits(); t.addSplit(s); const QList& splits = d->m_selectedTransactions[0].transaction().splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).id() != splitId) { MyMoneySplit s0 = (*it_s); s0.clearId(); s0.setReconcileFlag(MyMoneySplit::NotReconciled); s0.setReconcileDate(QDate()); t.addSplit(s0); } } slotScheduleNew(t); } } void KMyMoneyApp::slotTransactionAssignNumber() { if (d->m_transactionEditor) d->m_transactionEditor->assignNextNumber(); } void KMyMoneyApp::slotTransactionCombine() { qDebug("slotTransactionCombine() not implemented yet"); } void KMyMoneyApp::slotTransactionCopySplits() { MyMoneyFile* file = MyMoneyFile::instance(); if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; KMyMoneyRegister::SelectedTransaction selectedSourceTransaction; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: selectedSourceTransaction = st; multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { MyMoneyFileTransaction ft; try { const MyMoneyTransaction& sourceTransaction = selectedSourceTransaction.transaction(); const MyMoneySplit& sourceSplit = selectedSourceTransaction.split(); foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { MyMoneyTransaction t = st.transaction(); // don't process the source transaction if (sourceTransaction.id() == t.id()) { continue; } const MyMoneySplit& baseSplit = st.split(); if (t.splitCount() == 1) { foreach (const MyMoneySplit& split, sourceTransaction.splits()) { // Don't copy the source split, as we already have that // as part of the destination transaction if (split.id() == sourceSplit.id()) { continue; } MyMoneySplit sp(split); // clear the ID and reconciliation state sp.clearId(); sp.setReconcileFlag(MyMoneySplit::NotReconciled); sp.setReconcileDate(QDate()); // in case it is a simple transaction consisting of two splits, // we can adjust the share and value part of the second split we // just created. We need to keep a possible price in mind in case // of different currencies if (sourceTransaction.splitCount() == 2) { sp.setValue(-baseSplit.value()); sp.setShares(-(baseSplit.shares() * baseSplit.price())); } t.addSplit(sp); } file->modifyTransaction(t); } } ft.commit(); } catch (const MyMoneyException &) { qDebug() << "transactionCopySplits() failed"; } } } } void KMyMoneyApp::slotMoveToAccount(const QString& id) { // close the menu, if it is still open QWidget* w = factory()->container("transaction_context_menu", this); if (w && w->isVisible()) { w->close(); } if (!d->m_selectedTransactions.isEmpty()) { MyMoneyFileTransaction ft; try { KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { d->moveInvestmentTransaction(d->m_selectedAccount.id(), id, (*it_t).transaction()); } else { QList::const_iterator it_s; bool changed = false; MyMoneyTransaction t = (*it_t).transaction(); for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { if ((*it_s).accountId() == d->m_selectedAccount.id()) { MyMoneySplit s = (*it_s); s.setAccountId(id); t.modifySplit(s); changed = true; } } if (changed) { MyMoneyFile::instance()->modifyTransaction(t); } } } ft.commit(); } catch (const MyMoneyException &) { } } } // move a stock transaction from one investment account to another void KMyMoneyApp::Private::moveInvestmentTransaction(const QString& /*fromId*/, const QString& toId, const MyMoneyTransaction& tx) { MyMoneyAccount toInvAcc = MyMoneyFile::instance()->account(toId); MyMoneyTransaction t(tx); // first determine which stock we are dealing with. // fortunately, investment transactions have only one stock involved QString stockAccountId; QString stockSecurityId; MyMoneySplit s; for (QList::const_iterator it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { stockAccountId = (*it_s).accountId(); stockSecurityId = MyMoneyFile::instance()->account(stockAccountId).currencyId(); if (!MyMoneyFile::instance()->security(stockSecurityId).isCurrency()) { s = *it_s; break; } } // Now check the target investment account to see if it // contains a stock with this id QString newStockAccountId; QStringList accountList = toInvAcc.accountList(); for (QStringList::const_iterator it_a = accountList.constBegin(); it_a != accountList.constEnd(); ++it_a) { if (MyMoneyFile::instance()->account((*it_a)).currencyId() == stockSecurityId) { newStockAccountId = (*it_a); break; } } // if it doesn't exist, we need to add it as a copy of the old one // no 'copyAccount()' function?? if (newStockAccountId.isEmpty()) { MyMoneyAccount stockAccount = MyMoneyFile::instance()->account(stockAccountId); MyMoneyAccount newStock; newStock.setName(stockAccount.name()); newStock.setNumber(stockAccount.number()); newStock.setDescription(stockAccount.description()); newStock.setInstitutionId(stockAccount.institutionId()); newStock.setOpeningDate(stockAccount.openingDate()); newStock.setAccountType(stockAccount.accountType()); newStock.setCurrencyId(stockAccount.currencyId()); newStock.setClosed(stockAccount.isClosed()); MyMoneyFile::instance()->addAccount(newStock, toInvAcc); newStockAccountId = newStock.id(); } // now update the split and the transaction s.setAccountId(newStockAccountId); t.modifySplit(s); MyMoneyFile::instance()->modifyTransaction(t); } void KMyMoneyApp::slotUpdateMoveToAccountMenu() { createTransactionMoveMenu(); // in case we were not able to create the selector, we // better get out of here. Anything else would cause // a crash later on (accountSet.load) if (!d->m_moveToAccountSelector) return; if (!d->m_selectedAccount.id().isEmpty()) { AccountSet accountSet; if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { accountSet.addAccountType(MyMoneyAccount::Investment); } else if (d->m_selectedAccount.isAssetLiability()) { accountSet.addAccountType(MyMoneyAccount::Checkings); accountSet.addAccountType(MyMoneyAccount::Savings); accountSet.addAccountType(MyMoneyAccount::Cash); accountSet.addAccountType(MyMoneyAccount::AssetLoan); accountSet.addAccountType(MyMoneyAccount::CertificateDep); accountSet.addAccountType(MyMoneyAccount::MoneyMarket); accountSet.addAccountType(MyMoneyAccount::Asset); accountSet.addAccountType(MyMoneyAccount::Currency); accountSet.addAccountType(MyMoneyAccount::CreditCard); accountSet.addAccountType(MyMoneyAccount::Loan); accountSet.addAccountType(MyMoneyAccount::Liability); } else if (d->m_selectedAccount.isIncomeExpense()) { accountSet.addAccountType(MyMoneyAccount::Income); accountSet.addAccountType(MyMoneyAccount::Expense); } accountSet.load(d->m_moveToAccountSelector); // remove those accounts that we currently reference KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); it_t != d->m_selectedTransactions.constEnd(); ++it_t) { QList::const_iterator it_s; for (it_s = (*it_t).transaction().splits().constBegin(); it_s != (*it_t).transaction().splits().constEnd(); ++it_s) { d->m_moveToAccountSelector->removeItem((*it_s).accountId()); } } // remove those accounts from the list that are denominated // in a different currency QStringList list = d->m_moveToAccountSelector->accountList(); QList::const_iterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a); if (acc.currencyId() != d->m_selectedAccount.currencyId()) d->m_moveToAccountSelector->removeItem((*it_a)); } } } void KMyMoneyApp::slotTransactionMatch() { // if the menu action is retrieved it can contain an '&' character for the accelerator causing the comparison to fail if not removed QString transactionActionText = action("transaction_match")->text(); transactionActionText.remove('&'); if (transactionActionText == i18nc("Button text for match transaction", "Match")) transactionMatch(); else transactionUnmatch(); } void KMyMoneyApp::transactionUnmatch() { KMyMoneyRegister::SelectedTransactions::const_iterator it; MyMoneyFileTransaction ft; try { for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).split().isMatched()) { TransactionMatcher matcher(d->m_selectedAccount); matcher.unmatch((*it).transaction(), (*it).split()); } } ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to unmatch the selected transactions"), e.what()); } } void KMyMoneyApp::transactionMatch() { if (d->m_selectedTransactions.count() != 2) return; MyMoneyTransaction startMatchTransaction; MyMoneyTransaction endMatchTransaction; MyMoneySplit startSplit; MyMoneySplit endSplit; KMyMoneyRegister::SelectedTransactions::const_iterator it; KMyMoneyRegister::SelectedTransactions toBeDeleted; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) { if (endMatchTransaction.id().isEmpty()) { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } else { //This is a second imported transaction, we still want to merge startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } } else if (!(*it).split().isMatched()) { if (startMatchTransaction.id().isEmpty()) { startMatchTransaction = (*it).transaction(); startSplit = (*it).split(); } else { endMatchTransaction = (*it).transaction(); endSplit = (*it).split(); toBeDeleted << *it; } } } #if 0 KMergeTransactionsDlg dlg(d->m_selectedAccount); dlg.addTransaction(startMatchTransaction); dlg.addTransaction(endMatchTransaction); if (dlg.exec() == QDialog::Accepted) #endif { MyMoneyFileTransaction ft; try { if (startMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No manually entered transaction selected for matching")); if (endMatchTransaction.id().isEmpty()) throw MYMONEYEXCEPTION(i18n("No imported transaction selected for matching")); TransactionMatcher matcher(d->m_selectedAccount); matcher.match(startMatchTransaction, startSplit, endMatchTransaction, endSplit, true); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to match the selected transactions"), e.what()); } } } void KMyMoneyApp::showContextMenu(const QString& containerName) { QWidget* w = factory()->container(containerName, this); QMenu *menu = dynamic_cast(w); if (menu) menu->exec(QCursor::pos()); else qDebug("menu '%s' not found: w = %p, menu = %p", qPrintable(containerName), w, menu); } void KMyMoneyApp::slotShowTransactionContextMenu() { if (d->m_selectedTransactions.isEmpty() && d->m_selectedSchedule != MyMoneySchedule()) { showContextMenu("schedule_context_menu"); } else { showContextMenu("transaction_context_menu"); } } void KMyMoneyApp::slotShowInvestmentContextMenu() { showContextMenu("investment_context_menu"); } void KMyMoneyApp::slotShowScheduleContextMenu() { showContextMenu("schedule_context_menu"); } void KMyMoneyApp::slotShowAccountContextMenu(const MyMoneyObject& obj) { // qDebug("KMyMoneyApp::slotShowAccountContextMenu"); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); // if the selected account is actually a stock account, we // call the right slot instead if (acc.isInvest()) { showContextMenu("investment_context_menu"); } else if (acc.isIncomeExpense()) { showContextMenu("category_context_menu"); } else { showContextMenu("account_context_menu"); } } void KMyMoneyApp::slotShowInstitutionContextMenu(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyInstitution)) return; showContextMenu("institution_context_menu"); } void KMyMoneyApp::slotShowPayeeContextMenu() { showContextMenu("payee_context_menu"); } void KMyMoneyApp::slotShowTagContextMenu() { showContextMenu("tag_context_menu"); } void KMyMoneyApp::slotShowBudgetContextMenu() { showContextMenu("budget_context_menu"); } void KMyMoneyApp::slotShowCurrencyContextMenu() { showContextMenu("currency_context_menu"); } void KMyMoneyApp::slotShowPriceContextMenu() { showContextMenu("price_context_menu"); } void KMyMoneyApp::slotShowOnlineJobContextMenu() { showContextMenu("onlinejob_context_menu"); } void KMyMoneyApp::slotPrintView() { d->m_myMoneyView->slotPrintView(); } void KMyMoneyApp::updateCaption(bool skipActions) { QString caption; caption = d->m_fileName.fileName(); if (caption.isEmpty() && d->m_myMoneyView && d->m_myMoneyView->fileOpen()) caption = i18n("Untitled"); // MyMoneyFile::instance()->dirty() throws an exception, if // there's no storage object available. In this case, we // assume that the storage object is not changed. Actually, // this can only happen if we are newly created early on. bool modified; try { modified = MyMoneyFile::instance()->dirty(); } catch (const MyMoneyException &) { modified = false; skipActions = true; } #ifdef KMM_DEBUG caption += QString(" (%1 x %2)").arg(width()).arg(height()); #endif setCaption(caption, modified); if (!skipActions) { d->m_myMoneyView->enableViewsIfFileOpen(); slotUpdateActions(); } } void KMyMoneyApp::slotUpdateActions() { MyMoneyFile* file = MyMoneyFile::instance(); bool fileOpen = d->m_myMoneyView->fileOpen(); bool modified = file->dirty(); QWidget* w; action("open_database")->setEnabled(true); action("saveas_database")->setEnabled(fileOpen); action("file_save")->setEnabled(modified && !d->m_myMoneyView->isDatabase()); action("file_save_as")->setEnabled(fileOpen); action("file_close")->setEnabled(fileOpen); action("view_personal_data")->setEnabled(fileOpen); action("file_backup")->setEnabled(fileOpen && !d->m_myMoneyView->isDatabase()); action("file_print")->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); #ifdef KMM_DEBUG action("view_file_info")->setEnabled(fileOpen); action("file_dump")->setEnabled(fileOpen); #endif action("edit_find_transaction")->setEnabled(fileOpen); bool importRunning = (d->m_qifReader != 0) || (d->m_smtReader != 0); action("file_export_qif")->setEnabled(fileOpen && !importRunning); action("file_import_qif")->setEnabled(fileOpen && !importRunning); action("file_import_gnc")->setEnabled(!importRunning); action("file_import_template")->setEnabled(fileOpen && !importRunning); action("file_export_template")->setEnabled(fileOpen && !importRunning); action("tools_currency_editor")->setEnabled(fileOpen); action("tools_price_editor")->setEnabled(fileOpen); action("tools_update_prices")->setEnabled(fileOpen); action("tools_consistency_check")->setEnabled(fileOpen); action("account_new")->setEnabled(fileOpen); action("account_reconcile")->setEnabled(false); action("account_reconcile_finish")->setEnabled(false); action("account_reconcile_postpone")->setEnabled(false); action("account_edit")->setEnabled(false); action("account_delete")->setEnabled(false); action("account_open")->setEnabled(false); action("account_close")->setEnabled(false); action("account_reopen")->setEnabled(false); action("account_transaction_report")->setEnabled(false); action("account_online_map")->setEnabled(false); action("account_online_update")->setEnabled(false); action("account_online_update_all")->setEnabled(false); action("account_online_unmap")->setEnabled(false); action("account_online_new_credit_transfer")->setEnabled(onlineJobAdministration::instance()->canSendCreditTransfer()); action("account_chart")->setEnabled(false); action("category_new")->setEnabled(fileOpen); action("category_edit")->setEnabled(false); action("category_delete")->setEnabled(false); action("institution_new")->setEnabled(fileOpen); action("institution_edit")->setEnabled(false); action("institution_delete")->setEnabled(false); action("investment_new")->setEnabled(false); action("investment_edit")->setEnabled(false); action("investment_delete")->setEnabled(false); action("investment_online_price_update")->setEnabled(false); action("investment_manual_price_update")->setEnabled(false); action("schedule_edit")->setEnabled(false); action("schedule_delete")->setEnabled(false); action("schedule_enter")->setEnabled(false); action("schedule_skip")->setEnabled(false); action("payee_delete")->setEnabled(false); action("payee_rename")->setEnabled(false); action("payee_merge")->setEnabled(false); action("tag_delete")->setEnabled(false); action("tag_rename")->setEnabled(false); action("budget_delete")->setEnabled(false); action("budget_rename")->setEnabled(false); action("budget_change_year")->setEnabled(false); action("budget_new")->setEnabled(true); action("budget_copy")->setEnabled(false); action("budget_forecast")->setEnabled(false); QString tooltip = i18n("Create a new transaction"); action("transaction_new")->setEnabled(fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)); action("transaction_new")->setToolTip(tooltip); action("transaction_edit")->setEnabled(false); action("transaction_editsplits")->setEnabled(false); action("transaction_enter")->setEnabled(false); action("transaction_cancel")->setEnabled(false); action("transaction_delete")->setEnabled(false); action("transaction_match")->setEnabled(false); action("transaction_match")->setText(i18nc("Button text for match transaction", "Match")); action("transaction_accept")->setEnabled(false); action("transaction_duplicate")->setEnabled(false); action("transaction_mark_toggle")->setEnabled(false); action("transaction_mark_cleared")->setEnabled(false); action("transaction_mark_reconciled")->setEnabled(false); action("transaction_mark_notreconciled")->setEnabled(false); action("transaction_goto_account")->setEnabled(false); action("transaction_goto_payee")->setEnabled(false); action("transaction_assign_number")->setEnabled(false); action("transaction_create_schedule")->setEnabled(false); action("transaction_combine")->setEnabled(false); action("transaction_select_all")->setEnabled(false); action("transaction_copy_splits")->setEnabled(false); action("schedule_new")->setEnabled(fileOpen); action("schedule_edit")->setEnabled(false); action("schedule_delete")->setEnabled(false); action("schedule_duplicate")->setEnabled(false); action("schedule_enter")->setEnabled(false); action("schedule_skip")->setEnabled(false); action("currency_new")->setEnabled(fileOpen); action("currency_rename")->setEnabled(false); action("currency_delete")->setEnabled(false); action("currency_setbase")->setEnabled(false); action("price_new")->setEnabled(fileOpen); action("price_edit")->setEnabled(false); action("price_delete")->setEnabled(false); action("price_update")->setEnabled(false); w = factory()->container("transaction_move_menu", this); if (w) w->setEnabled(false); w = factory()->container("transaction_mark_menu", this); if (w) w->setEnabled(false); w = factory()->container("transaction_context_mark_menu", this); if (w) w->setEnabled(false); // FIXME for now it's always on, but we should only allow it, if we // can select at least a single transaction action("transaction_select_all")->setEnabled(true); if (!d->m_selectedTransactions.isEmpty()) { // enable 'delete transaction' only if at least one of the // selected transactions does not reference a closed account bool enable = false; KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = d->m_selectedTransactions.constBegin(); (enable == false) && (it_t != d->m_selectedTransactions.constEnd()); ++it_t) { enable = !(*it_t).transaction().id().isEmpty() && !file->referencesClosedAccount((*it_t).transaction()); } action("transaction_delete")->setEnabled(enable); if (!d->m_transactionEditor) { tooltip = i18n("Duplicate the current selected transactions"); action("transaction_duplicate")->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); action("transaction_duplicate")->setToolTip(tooltip); if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) { action("transaction_edit")->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { action("transaction_editsplits")->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) { action("transaction_create_schedule")->setEnabled(d->m_selectedTransactions.count() == 1); } } action("transaction_edit")->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { w = factory()->container("transaction_move_menu", this); if (w) w->setEnabled(true); } w = factory()->container("transaction_mark_menu", this); if (w) w->setEnabled(true); w = factory()->container("transaction_context_mark_menu", this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected action("transaction_mark_cleared")->setEnabled(true); action("transaction_mark_reconciled")->setEnabled(true); action("transaction_mark_notreconciled")->setEnabled(true); action("transaction_mark_toggle")->setEnabled(true); if (!d->m_accountGoto.isEmpty()) action("transaction_goto_account")->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) action("transaction_goto_payee")->setEnabled(true); // Matching is enabled as soon as one regular and one imported transaction is selected int matchedCount = 0; int importedCount = 0; KMyMoneyRegister::SelectedTransactions::const_iterator it; for (it = d->m_selectedTransactions.constBegin(); it != d->m_selectedTransactions.constEnd(); ++it) { if ((*it).transaction().isImported()) ++importedCount; if ((*it).split().isMatched()) ++matchedCount; } if (d->m_selectedTransactions.count() == 2 /* && action("transaction_edit")->isEnabled() */) { action("transaction_match")->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) action("transaction_accept")->setEnabled(true); if (matchedCount != 0) { action("transaction_match")->setEnabled(true); action("transaction_match")->setText(i18nc("Button text for unmatch transaction", "Unmatch")); action("transaction_match")->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { action("transaction_combine")->setEnabled(true); } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach (const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch (st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if (singleSplitTransactions > 0 && multipleSplitTransactions == 1) { action("transaction_copy_splits")->setEnabled(true); } } if (d->m_selectedTransactions.count() >= 2) { int singleSplitTransactions = 0; int multipleSplitTransactions = 0; foreach(const KMyMoneyRegister::SelectedTransaction& st, d->m_selectedTransactions) { switch(st.transaction().splitCount()) { case 0: break; case 1: singleSplitTransactions++; break; default: multipleSplitTransactions++; break; } } if(singleSplitTransactions > 0 && multipleSplitTransactions == 1) { action("transaction_copy_splits")->setEnabled(true); } } } else { action("transaction_assign_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); action("transaction_new")->setEnabled(false); action("transaction_delete")->setEnabled(false); QString reason; action("transaction_enter")->setEnabled(d->m_transactionEditor->isComplete(reason)); //FIXME: Port to KDE4 // the next line somehow worked in KDE3 but does not have // any influence under KDE4 /// Works for me when 'reason' is set. Allan action("transaction_enter")->setToolTip(reason); action("transaction_cancel")->setEnabled(true); } } QList accList; file->accountList(accList); QList::const_iterator it_a; QMap::const_iterator it_p = d->m_onlinePlugins.constEnd(); for (it_a = accList.constBegin(); (it_p == d->m_onlinePlugins.constEnd()) && (it_a != accList.constEnd()); ++it_a) { if (!(*it_a).onlineBankingSettings().value("provider").isEmpty()) { // check if provider is available it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { action("account_online_update_all")->setEnabled(true); action("account_online_update_menu")->setEnabled(true); } } } } MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { switch (d->m_selectedAccount.accountGroup()) { case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Equity: action("account_transaction_report")->setEnabled(true); action("account_edit")->setEnabled(true); action("account_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount)); action("account_open")->setEnabled(true); if (d->m_selectedAccount.accountGroup() != MyMoneyAccount::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { action("account_reconcile")->setEnabled(true); action("account_reconcile")->setToolTip(i18n("Reconcile")); } else { QString tip; tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); action("account_reconcile")->setToolTip(tip); if (!d->m_transactionEditor) { action("account_reconcile_finish")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); action("account_reconcile_postpone")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); } } } if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) action("investment_new")->setEnabled(true); if (d->m_selectedAccount.isClosed()) action("account_reopen")->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { action("account_online_unmap")->setEnabled(true); // check if provider is available QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { QStringList protocols; (*it_p)->protocols(protocols); if (protocols.count() > 0) { action("account_online_update")->setEnabled(true); action("account_online_update_menu")->setEnabled(true); } } } else { action("account_online_map")->setEnabled(d->m_onlinePlugins.count() > 0); } action("account_chart")->setEnabled(true); break; case MyMoneyAccount::Income : case MyMoneyAccount::Expense : action("category_edit")->setEnabled(true); // enable delete action, if category/account itself is not referenced // by any object except accounts, because we want to allow // deleting of sub-categories. Also, we allow transactions, schedules and budgets // to be present because we can re-assign them during the delete process skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); skip.setBit(IMyMoneyStorage::RefCheckAccount); skip.setBit(IMyMoneyStorage::RefCheckSchedule); skip.setBit(IMyMoneyStorage::RefCheckBudget); action("category_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); action("account_open")->setEnabled(true); break; default: break; } } } if (!d->m_selectedInstitution.id().isEmpty()) { action("institution_edit")->setEnabled(true); action("institution_delete")->setEnabled(!file->isReferenced(d->m_selectedInstitution)); } if (!d->m_selectedInvestment.id().isEmpty()) { action("investment_edit")->setEnabled(true); action("investment_delete")->setEnabled(!file->isReferenced(d->m_selectedInvestment)); action("investment_manual_price_update")->setEnabled(true); try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); if (!security.value("kmm-online-source").isEmpty()) action("investment_online_price_update")->setEnabled(true); } catch (const MyMoneyException &e) { qDebug("Error retrieving security for investment %s: %s", qPrintable(d->m_selectedInvestment.name()), qPrintable(e.what())); } if (d->m_selectedInvestment.isClosed()) action("account_reopen")->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { action("schedule_edit")->setEnabled(true); action("schedule_duplicate")->setEnabled(true); action("schedule_delete")->setEnabled(!file->isReferenced(d->m_selectedSchedule)); if (!d->m_selectedSchedule.isFinished()) { action("schedule_enter")->setEnabled(true); // a schedule with a single occurrence cannot be skipped if (d->m_selectedSchedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { action("schedule_skip")->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { action("payee_rename")->setEnabled(d->m_selectedPayees.count() == 1); action("payee_merge")->setEnabled(d->m_selectedPayees.count() > 1); action("payee_delete")->setEnabled(true); } if (d->m_selectedTags.count() >= 1) { action("tag_rename")->setEnabled(d->m_selectedTags.count() == 1); action("tag_delete")->setEnabled(true); } if (d->m_selectedBudgets.count() >= 1) { action("budget_delete")->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { action("budget_change_year")->setEnabled(true); action("budget_copy")->setEnabled(true); action("budget_rename")->setEnabled(true); action("budget_forecast")->setEnabled(true); } } if (!d->m_selectedCurrency.id().isEmpty()) { action("currency_rename")->setEnabled(true); // no need to check each transaction. accounts are enough in this case skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); action("currency_delete")->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) action("currency_setbase")->setEnabled(true); } if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != "KMyMoney") { action("price_edit")->setEnabled(true); action("price_delete")->setEnabled(true); //enable online update if it is a currency MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from()); action("price_update")->setEnabled(security.isCurrency()); } } void KMyMoneyApp::slotResetSelections() { slotSelectAccount(); slotSelectInstitution(); slotSelectInvestment(); slotSelectSchedule(); slotSelectCurrency(); slotSelectPrice(); slotSelectPayees(QList()); slotSelectTags(QList()); slotSelectBudget(QList()); slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); slotUpdateActions(); } void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency) { d->m_selectedCurrency = currency; slotUpdateActions(); emit currencySelected(d->m_selectedCurrency); } void KMyMoneyApp::slotSelectPrice(const MyMoneyPrice& price) { d->m_selectedPrice = price; slotUpdateActions(); emit priceSelected(d->m_selectedPrice); } void KMyMoneyApp::slotSelectBudget(const QList& list) { d->m_selectedBudgets = list; slotUpdateActions(); emit budgetSelected(d->m_selectedBudgets); } void KMyMoneyApp::slotSelectPayees(const QList& list) { d->m_selectedPayees = list; slotUpdateActions(); emit payeesSelected(d->m_selectedPayees); } void KMyMoneyApp::slotSelectTags(const QList& list) { d->m_selectedTags = list; slotUpdateActions(); emit tagsSelected(d->m_selectedTags); } void KMyMoneyApp::slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list) { // list can either contain a list of transactions or a single selected scheduled transaction // in the latter case, the transaction id is actually the one of the schedule. In order // to differentiate between the two, we just ask for the schedule. If we don't find one - because // we passed the id of a real transaction - then we know that fact. We use the schedule here, // because the list of schedules is kept in a cache by MyMoneyFile. This way, we save some trips // to the backend which we would have to do if we check for the transaction. d->m_selectedTransactions.clear(); d->m_selectedSchedule = MyMoneySchedule(); d->m_accountGoto.clear(); d->m_payeeGoto.clear(); if (!list.isEmpty() && !list.first().isScheduled()) { d->m_selectedTransactions = list; if (list.count() == 1) { const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); if (!sp.payeeId().isEmpty()) { try { MyMoneyPayee payee = MyMoneyFile::instance()->payee(sp.payeeId()); if (!payee.name().isEmpty()) { d->m_payeeGoto = payee.id(); QString name = payee.name(); name.replace(QRegExp("&(?!&)"), "&&"); action("transaction_goto_payee")->setText(i18n("Go to '%1'", name)); } } catch (const MyMoneyException &) { } } try { QList::const_iterator it_s; const MyMoneyTransaction& t = d->m_selectedTransactions[0].transaction(); // search the first non-income/non-expense accunt and use it for the 'goto account' const MyMoneySplit& sp = d->m_selectedTransactions[0].split(); for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) { if ((*it_s).id() != sp.id()) { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (!acc.isIncomeExpense()) { // for stock accounts we show the portfolio account if (acc.isInvest()) { acc = MyMoneyFile::instance()->account(acc.parentAccountId()); } d->m_accountGoto = acc.id(); QString name = acc.name(); name.replace(QRegExp("&(?!&)"), "&&"); action("transaction_goto_account")->setText(i18n("Go to '%1'", name)); break; } } } } catch (const MyMoneyException &) { } } slotUpdateActions(); emit transactionsSelected(d->m_selectedTransactions); } else if (!list.isEmpty()) { slotSelectSchedule(MyMoneyFile::instance()->schedule(list.first().scheduleId())); } else { slotUpdateActions(); } // make sure, we show some neutral menu entry if we don't have an object if (d->m_payeeGoto.isEmpty()) action("transaction_goto_payee")->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) action("transaction_goto_account")->setText(i18n("Go to account")); } void KMyMoneyApp::slotSelectInstitution(const MyMoneyObject& institution) { if (typeid(institution) != typeid(MyMoneyInstitution)) return; d->m_selectedInstitution = dynamic_cast(institution); // qDebug("slotSelectInstitution('%s')", d->m_selectedInstitution.name().data()); slotUpdateActions(); emit institutionSelected(d->m_selectedInstitution); } void KMyMoneyApp::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; d->m_selectedAccount = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (!acc.isInvest()) d->m_selectedAccount = acc; // qDebug("slotSelectAccount('%s')", d->m_selectedAccount.name().data()); slotUpdateActions(); emit accountSelected(d->m_selectedAccount); } void KMyMoneyApp::slotSelectInvestment(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return; // qDebug("slotSelectInvestment('%s')", account.name().data()); d->m_selectedInvestment = MyMoneyAccount(); const MyMoneyAccount& acc = dynamic_cast(obj); if (acc.isInvest()) d->m_selectedInvestment = acc; slotUpdateActions(); emit investmentSelected(d->m_selectedInvestment); } void KMyMoneyApp::slotSelectSchedule(const MyMoneySchedule& schedule) { // qDebug("slotSelectSchedule('%s')", schedule.name().data()); d->m_selectedSchedule = schedule; slotUpdateActions(); emit scheduleSelected(d->m_selectedSchedule); } void KMyMoneyApp::slotDataChanged() { // As this method is called every time the MyMoneyFile instance // notifies a modification, it's the perfect place to start the timer if needed if (d->m_autoSaveEnabled && !d->m_autoSaveTimer->isActive()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); //miliseconds } updateCaption(); } void KMyMoneyApp::slotCurrencyDialog() { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneySecurity)), this, SLOT(slotSelectCurrency(MyMoneySecurity))); connect(dlg, SIGNAL(openContextMenu(MyMoneySecurity)), this, SLOT(slotShowCurrencyContextMenu())); connect(this, SIGNAL(currencyRename()), dlg, SLOT(slotStartRename())); connect(dlg, SIGNAL(updateCurrency(QString,QString,QString)), this, SLOT(slotCurrencyUpdate(QString,QString,QString))); connect(this, SIGNAL(currencyCreated(QString)), dlg, SLOT(slotSelectCurrency(QString))); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotCurrencySetBase())); dlg->exec(); delete dlg; slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotPriceDialog() { QPointer dlg = new KMyMoneyPriceDlg(this); connect(dlg, SIGNAL(selectObject(MyMoneyPrice)), this, SLOT(slotSelectPrice(MyMoneyPrice))); connect(dlg, SIGNAL(openContextMenu(MyMoneyPrice)), this, SLOT(slotShowPriceContextMenu())); connect(this, SIGNAL(priceNew()), dlg, SLOT(slotNewPrice())); connect(this, SIGNAL(priceEdit()), dlg, SLOT(slotEditPrice())); connect(this, SIGNAL(priceDelete()), dlg, SLOT(slotDeletePrice())); connect(this, SIGNAL(priceOnlineUpdate()), dlg, SLOT(slotOnlinePriceUpdate())); dlg->exec(); } void KMyMoneyApp::slotFileConsistencyCheck() { d->consistencyCheck(true); updateCaption(); } void KMyMoneyApp::Private::consistencyCheck(bool alwaysDisplayResult) { KMSTATUS(i18n("Running consistency check...")); MyMoneyFileTransaction ft; try { m_consistencyCheckResult = MyMoneyFile::instance()->consistencyCheck(); ft.commit(); } catch (const MyMoneyException &e) { m_consistencyCheckResult.append(i18n("Consistency check failed: %1", e.what())); // always display the result if the check failed alwaysDisplayResult = true; } // in case the consistency check was OK, we get a single line as result // in all errneous cases, we get more than one line and force the // display of them. if (alwaysDisplayResult || m_consistencyCheckResult.size() > 1) { QString msg = i18n("The consistency check has found no issues in your data. Details are presented below."); if (m_consistencyCheckResult.size() > 1) msg = i18n("The consistency check has found some issues in your data. Details are presented below. Those issues that could not be corrected automatically need to be solved by the user."); // install a context menu for the list after the dialog is displayed QTimer::singleShot(500, q, SLOT(slotInstallConsistencyCheckContextMenu())); KMessageBox::informationList(0, msg, m_consistencyCheckResult, i18n("Consistency check result")); } // this data is no longer needed m_consistencyCheckResult.clear(); } void KMyMoneyApp::Private::copyConsistencyCheckResults() { QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(m_consistencyCheckResult.join(QLatin1String("\n"))); } void KMyMoneyApp::Private::saveConsistencyCheckResults() { QUrl fileUrl = QFileDialog::getSaveFileUrl(q); if (!fileUrl.isEmpty()) { QFile file(fileUrl.toLocalFile()); if (file.open(QFile::WriteOnly | QFile::Append | QFile::Text)) { QTextStream out(&file); out << m_consistencyCheckResult.join(QLatin1String("\n")); file.close(); } } } void KMyMoneyApp::Private::setCustomColors() { // setup the kmymoney custom colors if needed if (KMyMoneyGlobalSettings::useSystemColors()) { qApp->setStyleSheet(QString()); } else { qApp->setStyleSheet("QTreeView, QTableView#register, QTableView#m_register, QTableView#splittable, QListView { background-color: " + KMyMoneyGlobalSettings::listBGColor().name() + ';' + "alternate-background-color: " + KMyMoneyGlobalSettings::listColor().name() + ';' + "background-clip: content;}"); } } void KMyMoneyApp::slotCheckSchedules() { if (KMyMoneyGlobalSettings::checkSchedule() == true) { KMSTATUS(i18n("Checking for overdue scheduled transactions...")); MyMoneyFile *file = MyMoneyFile::instance(); QDate checkDate = QDate::currentDate().addDays(KMyMoneyGlobalSettings::checkSchedulePreview()); QList scheduleList = file->scheduleList(); QList::Iterator it; KMyMoneyUtils::EnterScheduleResultCodeE rc = KMyMoneyUtils::Enter; for (it = scheduleList.begin(); (it != scheduleList.end()) && (rc != KMyMoneyUtils::Cancel); ++it) { // Get the copy in the file because it might be modified by commitTransaction MyMoneySchedule schedule = file->schedule((*it).id()); if (schedule.autoEnter()) { try { while (!schedule.isFinished() && (schedule.adjustedNextDueDate() <= checkDate) && rc != KMyMoneyUtils::Ignore && rc != KMyMoneyUtils::Cancel) { rc = enterSchedule(schedule, true, true); schedule = file->schedule((*it).id()); // get a copy of the modified schedule } } catch (const MyMoneyException &) { } } if (rc == KMyMoneyUtils::Ignore) { // if the current schedule was ignored then we must make sure that the user can still enter the next scheduled transaction rc = KMyMoneyUtils::Enter; } } updateCaption(); } } void KMyMoneyApp::writeLastUsedDir(const QString& directory) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = kconfig->group("General Options"); //write path entry, no error handling since its void. grp.writeEntry("LastUsedDirectory", directory); } } void KMyMoneyApp::writeLastUsedFile(const QString& fileName) { //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // write path entry, no error handling since its void. // use a standard string, as fileName could contain a protocol // e.g. file:/home/thb/.... grp.writeEntry("LastUsedFile", fileName); } } QString KMyMoneyApp::readLastUsedDir() const { QString str; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); //read path entry. Second parameter is the default if the setting is not found, which will be the default document path. str = grp.readEntry("LastUsedDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); // if the path stored is empty, we use the default nevertheless if (str.isEmpty()) str = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return str; } QString KMyMoneyApp::readLastUsedFile() const { QString str; // get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); if (kconfig) { KConfigGroup grp = d->m_config->group("General Options"); // read filename entry. str = grp.readEntry("LastUsedFile", ""); } return str; } QString KMyMoneyApp::filename() const { return d->m_fileName.url(); } QList KMyMoneyApp::instanceList() const { QList list; #ifdef KMM_DBUS QDBusReply reply = QDBusConnection::sessionBus().interface()->registeredServiceNames(); if (reply.isValid()) { QStringList apps = reply.value(); QStringList::ConstIterator it; // build a list of service names of all running kmymoney applications without this one for (it = apps.constBegin(); it != apps.constEnd(); ++it) { // please shange this method of creating a list of 'all the other kmymoney instances that are running on the system' // since assuming that D-Bus creates service names with org.kde.kmymoney-PID is an observation I don't think that it's documented somwhere if ((*it).indexOf("org.kde.kmymoney-") == 0) { #ifdef _MSC_VER uint thisProcPid = _getpid(); #else uint thisProcPid = getpid(); #endif if ((*it).indexOf(QString("org.kde.kmymoney-%1").arg(thisProcPid)) != 0) list += (*it); } } } else { qDebug("D-Bus returned the following error while obtaining instances: %s", qPrintable(reply.error().message())); } #endif return list; } void KMyMoneyApp::slotEquityPriceUpdate() { QPointer dlg = new KEquityPriceUpdateDlg(this); if (dlg->exec() == QDialog::Accepted && dlg != 0) dlg->storePrices(); delete dlg; } void KMyMoneyApp::webConnect(const QString& sourceUrl, const QByteArray& asn_id) { // // Web connect attempts to go through the known importers and see if the file // can be importing using that method. If so, it will import it using that // plugin // Q_UNUSED(asn_id) d->m_importUrlsQueue.enqueue(sourceUrl); // only start processing if this is the only import so far if (d->m_importUrlsQueue.count() == 1) { while (!d->m_importUrlsQueue.isEmpty()) { // get the value of the next item from the queue // but leave it on the queue for now QString url = d->m_importUrlsQueue.head(); // Bring this window to the forefront. This method was suggested by // Lubos Lunak of the KDE core development team. // TODO: port KF5 //KStartupInfo::setNewStartupId(this, asn_id); // Make sure we have an open file if (! d->m_myMoneyView->fileOpen() && KMessageBox::warningContinueCancel(kmymoney, i18n("You must first select a KMyMoney file before you can import a statement.")) == KMessageBox::Continue) kmymoney->slotFileOpen(); // only continue if the user really did open a file. if (d->m_myMoneyView->fileOpen()) { KMSTATUS(i18n("Importing a statement via Web Connect")); // remove the statement files d->unlinkStatementXML(); QMap::const_iterator it_plugin = d->m_importerPlugins.constBegin(); while (it_plugin != d->m_importerPlugins.constEnd()) { if ((*it_plugin)->isMyFormat(url)) { QList statements; if (!(*it_plugin)->import(url)) { KMessageBox::error(this, i18n("Unable to import %1 using %2 plugin. The plugin returned the following error: %3", url, (*it_plugin)->formatName(), (*it_plugin)->lastError()), i18n("Importing error")); } break; } ++it_plugin; } // If we did not find a match, try importing it as a KMM statement file, // which is really just for testing. the statement file is not exposed // to users. if (it_plugin == d->m_importerPlugins.constEnd()) if (MyMoneyStatement::isStatementFile(url)) slotStatementImport(url); } // remove the current processed item from the queue d->m_importUrlsQueue.dequeue(); } } } void KMyMoneyApp::slotEnableMessages() { KMessageBox::enableAllMessages(); KMessageBox::information(this, i18n("All messages have been enabled."), i18n("All messages")); } void KMyMoneyApp::createInterfaces() { // Sets up the plugin interface KMyMoneyPlugin::pluginInterfaces().importInterface = new KMyMoneyPlugin::KMMImportInterface(this, this); KMyMoneyPlugin::pluginInterfaces().statementInterface = new KMyMoneyPlugin::KMMStatementInterface(this, this); KMyMoneyPlugin::pluginInterfaces().viewInterface = new KMyMoneyPlugin::KMMViewInterface(this, d->m_myMoneyView, this); // setup the calendar interface for schedules MyMoneySchedule::setProcessingCalendar(this); } void KMyMoneyApp::loadPlugins() { Q_ASSERT(!d->m_pluginLoader); d->m_pluginLoader = new KMyMoneyPlugin::PluginLoader(this); //! @todo Junior Job: Improve the config read system KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group{ config->group("Plugins") }; const auto plugins = KPluginLoader::findPlugins("kmymoney"); d->m_pluginLoader->addPluginInfo(plugins); for (const KPluginMetaData & pluginData : plugins) { // Only load plugins which are enabled and have the right serviceType. Other serviceTypes are loaded on demand. if (isPluginEnabled(pluginData, group)) slotPluginLoad(pluginData); } connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginEnabled, this, &KMyMoneyApp::slotPluginLoad); connect(d->m_pluginLoader, &KMyMoneyPlugin::PluginLoader::pluginDisabled, this, &KMyMoneyApp::slotPluginUnload); } inline bool KMyMoneyApp::isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup) { //! @fixme: there is a function in KMyMoneyPlugin::PluginLoader which has to have the same content if (metaData.serviceTypes().contains("KMyMoney/Plugin")) { const QString keyName{metaData.name() + "Enabled"}; if (configGroup.hasKey(keyName)) return configGroup.readEntry(keyName, true); return metaData.isEnabledByDefault(); } return false; } void KMyMoneyApp::slotPluginLoad(const KPluginMetaData& metaData) { std::unique_ptr loader{new QPluginLoader{metaData.fileName()}}; QObject* plugin = loader->instance(); if (!plugin) { qWarning("Could not load plugin '%s', error: %s", qPrintable(metaData.fileName()), qPrintable(loader->errorString())); return; } if ( d->m_plugins.contains(metaData.fileName()) ) { /** @fixme Handle a reload e.g. objectNames are equal but the object are different (plugin != d->m_plugins[plugin->objectName()]) * Also it could be usefull to drop the dependence on objectName() */ /* Note: there is nothing to delete here because if the plugin was loaded already, plugin points to the same object as the previously loaded one. */ return; } KMyMoneyPlugin::Plugin* kmmPlugin = qobject_cast(plugin); if (!kmmPlugin) { qWarning("Could not load plugin '%s'.", qPrintable(metaData.fileName())); return; } // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for extended online plugin KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // Tell the plugin it is about to get plugged kmmPlugin->plug(); // plug the plugin guiFactory()->addClient(kmmPlugin); d->m_plugins[metaData.fileName()] = kmmPlugin; if (op) d->m_onlinePlugins[plugin->objectName()] = op; if (ope) onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope); if (ip) d->m_importerPlugins[plugin->objectName()] = ip; slotUpdateActions(); } void KMyMoneyApp::slotPluginUnload(const KPluginMetaData& metaData) { KMyMoneyPlugin::Plugin* plugin = d->m_plugins[metaData.fileName()]; // Remove and test if the plugin was actually loaded if (!d->m_plugins.remove(metaData.fileName()) || plugin == nullptr) return; // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // unplug the plugin guiFactory()->removeClient(plugin); if (op) d->m_onlinePlugins.remove(plugin->objectName()); if (ip) d->m_importerPlugins.remove(plugin->objectName()); plugin->unplug(); slotUpdateActions(); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->m_myMoneyView->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } const MyMoneyAccount& KMyMoneyApp::account(const QString& key, const QString& value) const { QList list; QList::const_iterator it_a; MyMoneyFile::instance()->accountList(list); QList accList; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { // search in the account's kvp container const QString& accountKvpValue = (*it_a).value(key); // search in the account's online settings kvp container const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key); if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) { accList << MyMoneyFile::instance()->account((*it_a).id()); } if (accountKvpValue == value || onlineSettingsKvpValue == value) { return MyMoneyFile::instance()->account((*it_a).id()); } } // if we did not find an exact match of the value, we take the one that partially // matched, but only if not more than one matched partially. if (accList.count() == 1) { return accList.first(); } // return reference to empty element return MyMoneyFile::instance()->account(QString()); } void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps) { MyMoneyFileTransaction ft; try { MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id()); acc.setOnlineBankingSettings(kvps); MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what()); } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins.count()) { case 0: break; case 1: provider = d->m_onlinePlugins.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins.constFind(provider); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider; MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if ((*it_a).onlineBankingSettings().value("provider").isEmpty() || d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) { it_a = accList.erase(it_a); } else ++it_a; } action("account_online_update")->setEnabled(false); action("account_online_update_menu")->setEnabled(false); action("account_online_update_all")->setEnabled(false); // now work on the remaining list of accounts int cnt = accList.count() - 1; for (it_a = accList.begin(); it_a != accList.end(); ++it_a) { it_p = d->m_onlinePlugins.constFind((*it_a).onlineBankingSettings().value("provider")); (*it_p)->updateAccount(*it_a, cnt != 0); --cnt; } d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotAccountUpdateOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // no online account mapped if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; action("account_online_update")->setEnabled(false); action("account_online_update_menu")->setEnabled(false); action("account_online_update_all")->setEnabled(false); // find the provider QMap::const_iterator it_p; it_p = d->m_onlinePlugins.constFind(d->m_selectedAccount.onlineBankingSettings().value("provider")); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it d->m_collectingStatements = true; d->m_statementResults.clear(); (*it_p)->updateAccount(d->m_selectedAccount); d->m_collectingStatements = false; if (!d->m_statementResults.isEmpty()) KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); } // re-enable the disabled actions slotUpdateActions(); } void KMyMoneyApp::slotNewOnlineTransfer() { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); if (!d->m_selectedAccount.id().isEmpty()) { transferForm->setCurrentAccount(d->m_selectedAccount.id()); } connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotEditOnlineJob(const QString jobId) { try { const onlineJob constJob = MyMoneyFile::instance()->getOnlineJob(jobId); slotEditOnlineJob(constJob); } catch (MyMoneyException&) { // Prevent a crash in very rare cases } } void KMyMoneyApp::slotEditOnlineJob(onlineJob job) { try { slotEditOnlineJob(onlineJobTyped(job)); } catch (MyMoneyException&) { } } void KMyMoneyApp::slotEditOnlineJob(const onlineJobTyped job) { kOnlineTransferForm *transferForm = new kOnlineTransferForm(this); transferForm->setOnlineJob(job); connect(transferForm, SIGNAL(rejected()), transferForm, SLOT(deleteLater())); connect(transferForm, SIGNAL(acceptedForSave(onlineJob)), this, SLOT(slotOnlineJobSave(onlineJob))); connect(transferForm, SIGNAL(acceptedForSend(onlineJob)), this, SLOT(slotOnlineJobSend(onlineJob))); connect(transferForm, SIGNAL(accepted()), transferForm, SLOT(deleteLater())); transferForm->show(); } void KMyMoneyApp::slotOnlineJobSave(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); } /** @todo when onlineJob queue is used, continue here */ void KMyMoneyApp::slotOnlineJobSend(onlineJob job) { MyMoneyFileTransaction fileTransaction; if (job.id() == MyMoneyObject::emptyId()) MyMoneyFile::instance()->addOnlineJob(job); else MyMoneyFile::instance()->modifyOnlineJob(job); fileTransaction.commit(); QList jobList; jobList.append(job); slotOnlineJobSend(jobList); } void KMyMoneyApp::slotOnlineJobSend(QList jobs) { MyMoneyFile *const kmmFile = MyMoneyFile::instance(); QMultiMap jobsByPlugin; // Sort jobs by online plugin & lock them foreach (onlineJob job, jobs) { Q_ASSERT(job.id() != MyMoneyObject::emptyId()); // find the provider const MyMoneyAccount originAcc = job.responsibleMyMoneyAccount(); job.setLock(); job.addJobMessage(onlineJobMessage(onlineJobMessage::debug, "KMyMoneyApp::slotOnlineJobSend", "Added to queue for plugin '" + originAcc.onlineBankingSettings().value("provider") + '\'')); MyMoneyFileTransaction fileTransaction; kmmFile->modifyOnlineJob(job); fileTransaction.commit(); jobsByPlugin.insert(originAcc.onlineBankingSettings().value("provider"), job); } // Send onlineJobs to plugins QList usedPlugins = jobsByPlugin.keys(); std::sort(usedPlugins.begin(), usedPlugins.end()); const QList::iterator newEnd = std::unique(usedPlugins.begin(), usedPlugins.end()); usedPlugins.erase(newEnd, usedPlugins.end()); foreach (const QString& pluginKey, usedPlugins) { QMap::const_iterator it_p = d->m_onlinePlugins.constFind(pluginKey); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it KMyMoneyPlugin::OnlinePluginExtended *pluginExt = dynamic_cast< KMyMoneyPlugin::OnlinePluginExtended* >(*it_p); if (pluginExt == 0) { qWarning("Job given for plugin which is not an extended plugin"); continue; } //! @fixme remove debug message qDebug() << "Sending " << jobsByPlugin.count(pluginKey) << " job(s) to online plugin " << pluginKey; QList jobsToExecute = jobsByPlugin.values(pluginKey); QList executedJobs = jobsToExecute; pluginExt->sendOnlineJob(executedJobs); // Save possible changes of the online job and remove lock MyMoneyFileTransaction fileTransaction; foreach (onlineJob job, executedJobs) { fileTransaction.restart(); job.setLock(false); kmmFile->modifyOnlineJob(job); fileTransaction.commit(); } if (Q_UNLIKELY(executedJobs.size() != jobsToExecute.size())) { // OnlinePlugin did not return all jobs qWarning() << "Error saving send online tasks. After restart you should see at minimum all successfully executed jobs marked send. Imperfect plugin: " << pluginExt->objectName(); } } else { qWarning() << "Error, got onlineJob for an account without online plugin."; /** @FIXME can this actually happen? */ } } } void KMyMoneyApp::slotOnlineJobLog() { QStringList jobIds = d->m_myMoneyView->getOnlineJobOutbox()->selectedOnlineJobs(); slotOnlineJobLog(jobIds); } void KMyMoneyApp::slotOnlineJobLog(const QStringList& onlineJobIds) { onlineJobMessagesView *const dialog = new onlineJobMessagesView(); onlineJobMessagesModel *const model = new onlineJobMessagesModel(dialog); model->setOnlineJob(MyMoneyFile::instance()->getOnlineJob(onlineJobIds.first())); dialog->setModel(model); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); // Note: Objects are not deleted here, Qt's parent-child system has to do that. } void KMyMoneyApp::setHolidayRegion(const QString& holidayRegion) { #ifdef KF5Holidays_FOUND //since the cost of updating the cache is now not negligible //check whether the region has been modified if (!d->m_holidayRegion || d->m_holidayRegion->regionCode() != holidayRegion) { // Delete the previous holidayRegion before creating a new one. delete d->m_holidayRegion; // Create a new holidayRegion. d->m_holidayRegion = new KHolidays::HolidayRegion(holidayRegion); //clear and update the holiday cache preloadHolidays(); } #else Q_UNUSED(holidayRegion); #endif } bool KMyMoneyApp::isProcessingDate(const QDate& date) const { #ifdef KF5Holidays_FOUND if (!d->m_processingDays.testBit(date.dayOfWeek())) return false; if (!d->m_holidayRegion || !d->m_holidayRegion->isValid()) return true; //check first whether it's already in cache if (d->m_holidayMap.contains(date)) { return d->m_holidayMap.value(date, true); } else { bool processingDay = !d->m_holidayRegion->isHoliday(date); d->m_holidayMap.insert(date, processingDay); return processingDay; } #else Q_UNUSED(date); return true; #endif } void KMyMoneyApp::preloadHolidays() { #ifdef KF5Holidays_FOUND //clear the cache before loading d->m_holidayMap.clear(); //only do this if it is a valid region if (d->m_holidayRegion && d->m_holidayRegion->isValid()) { //load holidays for the forecast days plus 1 cycle, to be on the safe side int forecastDays = KMyMoneyGlobalSettings::forecastDays() + KMyMoneyGlobalSettings::forecastAccountCycle(); QDate endDate = QDate::currentDate().addDays(forecastDays); //look for holidays for the next 2 years as a minimum. That should give a good margin for the cache if (endDate < QDate::currentDate().addYears(2)) endDate = QDate::currentDate().addYears(2); KHolidays::Holiday::List holidayList = d->m_holidayRegion->holidays(QDate::currentDate(), endDate); KHolidays::Holiday::List::const_iterator holiday_it; for (holiday_it = holidayList.constBegin(); holiday_it != holidayList.constEnd(); ++holiday_it) { for (QDate holidayDate = (*holiday_it).observedStartDate(); holidayDate <= (*holiday_it).observedEndDate(); holidayDate = holidayDate.addDays(1)) d->m_holidayMap.insert(holidayDate, false); } for (QDate date = QDate::currentDate(); date <= endDate; date = date.addDays(1)) { //if it is not a processing day, set it to false if (!d->m_processingDays.testBit(date.dayOfWeek())) { d->m_holidayMap.insert(date, false); } else if (!d->m_holidayMap.contains(date)) { //if it is not a holiday nor a weekend, it is a processing day d->m_holidayMap.insert(date, true); } } } #endif } KMStatus::KMStatus(const QString &text) { m_prevText = kmymoney->slotStatusMsg(text); } KMStatus::~KMStatus() { kmymoney->slotStatusMsg(m_prevText); } void KMyMoneyApp::Private::unlinkStatementXML() { QDir d("/home/thb", "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); //FIXME: FIX on windows d.remove(QString("/home/thb/%1").arg(d[i])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(); q->slotSelectInstitution(); q->slotSelectInvestment(); q->slotSelectSchedule(); q->slotSelectCurrency(); q->slotSelectBudget(QList()); q->slotSelectPayees(QList()); q->slotSelectTags(QList()); q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(m_reconciliationAccount); m_myMoneyView->closeFile(); m_fileName = QUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/mymoney/mymoneycontact.cpp b/kmymoney/mymoney/mymoneycontact.cpp index 822c5d53b..1e4018aa3 100644 --- a/kmymoney/mymoney/mymoneycontact.cpp +++ b/kmymoney/mymoney/mymoneycontact.cpp @@ -1,166 +1,166 @@ /* * Copyright 2014 Cristian OneÈ› * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ -#include +#include "config-kmymoney.h" #include "mymoneycontact.h" #ifdef KMM_ADDRESSBOOK_FOUND #include #include #include #include #include #include #include #endif MyMoneyContact::MyMoneyContact(QObject *parent) : QObject(parent) { } bool MyMoneyContact::ownerExists() const { #ifdef KMM_ADDRESSBOOK_FOUND KIdentityManagement::IdentityManager im; KIdentityManagement::Identity id = im.defaultIdentity(); return !id.isNull(); #else return false; #endif } QString MyMoneyContact::ownerEmail() const { #ifdef KMM_ADDRESSBOOK_FOUND KIdentityManagement::IdentityManager im; KIdentityManagement::Identity id = im.defaultIdentity(); return id.primaryEmailAddress(); #else return QString(); #endif } QString MyMoneyContact::ownerFullName() const { #ifdef KMM_ADDRESSBOOK_FOUND KIdentityManagement::IdentityManager im; KIdentityManagement::Identity id = im.defaultIdentity(); return id.fullName(); #else return QString(); #endif } void MyMoneyContact::fetchContact(const QString &email) { #ifdef KMM_ADDRESSBOOK_FOUND QRegularExpression re(".+@.+"); if (!re.match(email).hasMatch()) { ContactData contact; emit contactFetched(contact); } else { // fetch the contact data Akonadi::RecursiveItemFetchJob *job = new Akonadi::RecursiveItemFetchJob(Akonadi::Collection::root(), QStringList() << KContacts::Addressee::mimeType()); job->fetchScope().fetchFullPayload(); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); job->setProperty("MyMoneyContact_email", email); connect(job, SIGNAL(result(KJob*)), this, SLOT(searchContactResult(KJob*))); job->start(); } #else ContactData contact; emit contactFetched(contact); #endif } void MyMoneyContact::searchContactResult(KJob *job) { #ifdef KMM_ADDRESSBOOK_FOUND const Akonadi::RecursiveItemFetchJob *contactJob = qobject_cast(job); Akonadi::Item::List items; if (contactJob) items = contactJob->items(); ContactData contactData; contactData.email = job->property("MyMoneyContact_email").toString(); foreach (const Akonadi::Item &item, items) { const KContacts::Addressee &contact = item.payload(); if (contact.emails().contains(contactData.email)) { KContacts::PhoneNumber phone; KContacts::PhoneNumber::List phones = contact.phoneNumbers(); if (phones.count() == 1) phone = phones.first(); else { QList typesList = {KContacts::PhoneNumber::Work | KContacts::PhoneNumber::Pref, KContacts::PhoneNumber::Work, KContacts::PhoneNumber::Home | KContacts::PhoneNumber::Pref, KContacts::PhoneNumber::Home}; foreach (auto type, typesList) { foreach (auto phn, phones) { if (phn.type() & type) { phone = phn; break; } } if (!phone.isEmpty()) break; } } if (phone.isEmpty() && !phones.isEmpty()) phone = phones.first(); contactData.phoneNumber = phone.number(); KContacts::Address address; KContacts::Address::List addresses = contact.addresses(); if (addresses.count() == 1) address = addresses.first(); else { QList typesList = {KContacts::Address::Work | KContacts::Address::Pref, KContacts::Address::Work, KContacts::Address::Home | KContacts::Address::Pref, KContacts::Address::Home}; foreach (auto type, typesList) { foreach (auto addr, addresses) { if (addr.type() & type) { address = addr; break; } } if (!address.isEmpty()) break; } } if (address.isEmpty() && !addresses.isEmpty()) address = addresses.first(); contactData.street = address.street(); contactData.locality = address.locality(); contactData.country = address.country(); contactData.region = address.region(); contactData.postalCode = address.postalCode(); break; } } emit contactFetched(contactData); #else Q_UNUSED(job); #endif } diff --git a/kmymoney/plugins/kbanking/mymoneybanking.cpp b/kmymoney/plugins/kbanking/mymoneybanking.cpp index 3e2f25baa..5af61c648 100644 --- a/kmymoney/plugins/kbanking/mymoneybanking.cpp +++ b/kmymoney/plugins/kbanking/mymoneybanking.cpp @@ -1,1508 +1,1506 @@ /*************************************************************************** * Copyright 2004 Martin Preuss aquamaniac@users.sourceforge.net * * Copyright 2009 Cristian Onet onet.cristian@gmail.com * * Copyright 2010 Thomas Baumgart ipwizard@users.sourceforge.net * * Copyright 2015 Christian David christian-david@web.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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see * ***************************************************************************/ -#ifdef HAVE_CONFIG_H -# include -#endif +#include #include "mymoneybanking.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include //! @todo remove @c #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Library Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoney/onlinejob.h" #include "kbaccountsettings.h" #include "kbmapaccount.h" #include "mymoneyfile.h" #include "onlinejobadministration.h" #include "kmymoneyview.h" #include "kbpickstartdate.h" #include "gwenkdegui.h" #include "gwenhywfarqtoperators.h" #include "aqbankingkmmoperators.h" #ifdef KMM_DEBUG // Added an option to open the chipTanDialog from the menu for debugging purposes #include "chiptandialog.h" #endif class KBankingPlugin::Private { public: Private() : passwordCacheTimer(nullptr), jobList(), fileId() { QString gwenProxy = QString::fromLocal8Bit(qgetenv("GWEN_PROXY")); if (gwenProxy.isEmpty()) { std::unique_ptr cfg = std::unique_ptr(new KConfig("kioslaverc")); QRegExp exp("(\\w+://)?([^/]{2}.+:\\d+)"); QString proxy; KConfigGroup grp = cfg->group("Proxy Settings"); int type = grp.readEntry("ProxyType", 0); switch (type) { case 0: // no proxy break; case 1: // manual specified proxy = grp.readEntry("httpsProxy"); qDebug("KDE https proxy setting is '%s'", qPrintable(proxy)); if (exp.exactMatch(proxy)) { proxy = exp.cap(2); qDebug("Setting GWEN_PROXY to '%s'", qPrintable(proxy)); if (setenv("GWEN_PROXY", qPrintable(proxy), 1) == -1) { qDebug("Unable to setup GWEN_PROXY"); } } break; default: // other currently not supported qDebug("KDE proxy setting of type %d not supported", type); break; } } } /** * KMyMoney asks for accounts over and over again which causes a lot of "Job not supported with this account" error messages. * This function filters messages with that string. */ static int gwenLogHook(GWEN_GUI* gui, const char* domain, GWEN_LOGGER_LEVEL level, const char* message) { Q_UNUSED(gui); Q_UNUSED(domain); Q_UNUSED(level); const char* messageToFilter = "Job not supported with this account"; if (strstr(message, messageToFilter) != 0) return 1; return 0; } QTimer *passwordCacheTimer; QMap jobList; QString fileId; }; KBankingPlugin::KBankingPlugin() : KMyMoneyPlugin::OnlinePluginExtended(nullptr, "KBanking"/*must be the same as X-KDE-PluginInfo-Name*/), d(new Private), m_configAction(nullptr), m_importAction(nullptr), // m_kbanking(), set below m_protocolConversionMap(), m_accountSettings(nullptr), m_onlineJobQueue() { m_kbanking = new KMyMoneyBanking(this, "KMyMoney"); d->passwordCacheTimer = new QTimer(this); d->passwordCacheTimer->setSingleShot(true); d->passwordCacheTimer->setInterval(60000); connect(d->passwordCacheTimer, &QTimer::timeout, this, &KBankingPlugin::slotClearPasswordCache); if (m_kbanking) { if (AB_Banking_HasConf4(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: No AqB4 config found."); if (AB_Banking_HasConf3(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: No AqB3 config found."); if (!AB_Banking_HasConf2(m_kbanking->getCInterface())) { qDebug("KBankingPlugin: AqB2 config found - converting."); AB_Banking_ImportConf2(m_kbanking->getCInterface()); } } else { qDebug("KBankingPlugin: AqB3 config found - converting."); AB_Banking_ImportConf3(m_kbanking->getCInterface()); } } //! @todo when is gwenKdeGui deleted? gwenKdeGui *gui = new gwenKdeGui(); GWEN_Gui_SetGui(gui->getCInterface()); GWEN_Logger_SetLevel(0, GWEN_LoggerLevel_Warning); if (m_kbanking->init() == 0) { // Tell the host application to load my GUI component setComponentName("kmm_kbanking", "KBanking"); setXMLFile("kmm_kbanking.rc"); qDebug("KMyMoney kbanking plugin loaded"); // get certificate handling and dialog settings management AB_Gui_Extend(gui->getCInterface(), m_kbanking->getCInterface()); // create actions createActions(); // load protocol conversion list loadProtocolConversion(); GWEN_Logger_SetLevel(AQBANKING_LOGDOMAIN, GWEN_LoggerLevel_Warning); GWEN_Gui_SetLogHookFn(GWEN_Gui_GetGui(), &KBankingPlugin::Private::gwenLogHook); } else { qWarning("Could not initialize KBanking online banking interface"); delete m_kbanking; m_kbanking = 0; } } } KBankingPlugin::~KBankingPlugin() { if (m_kbanking) { m_kbanking->fini(); delete m_kbanking; } delete d; } void KBankingPlugin::loadProtocolConversion() { if (m_kbanking) { m_protocolConversionMap = { {"aqhbci", "HBCI"}, {"aqofxconnect", "OFX"}, {"aqyellownet", "YellowNet"}, {"aqgeldkarte", "Geldkarte"}, {"aqdtaus", "DTAUS"} }; } } void KBankingPlugin::protocols(QStringList& protocolList) const { if (m_kbanking) { std::list list = m_kbanking->getActiveProviders(); std::list::iterator it; for (it = list.begin(); it != list.end(); ++it) { // skip the dummy if (*it == "aqnone") continue; QMap::const_iterator it_m; it_m = m_protocolConversionMap.find((*it).c_str()); if (it_m != m_protocolConversionMap.end()) protocolList << (*it_m); else protocolList << (*it).c_str(); } } } QWidget* KBankingPlugin::accountConfigTab(const MyMoneyAccount& acc, QString& name) { const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); name = i18n("Online settings"); if (m_kbanking) { m_accountSettings = new KBAccountSettings(acc, 0); m_accountSettings->loadUi(kvp); return m_accountSettings; } QLabel* label = new QLabel(i18n("KBanking module not correctly initialized"), 0); label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); return label; } MyMoneyKeyValueContainer KBankingPlugin::onlineBankingSettings(const MyMoneyKeyValueContainer& current) { MyMoneyKeyValueContainer kvp(current); kvp["provider"] = objectName(); if (m_accountSettings) { m_accountSettings->loadKvp(kvp); } return kvp; } void KBankingPlugin::createActions() { QAction *settings_aqbanking = actionCollection()->addAction("settings_aqbanking"); settings_aqbanking->setText(i18n("Configure Aq&Banking...")); connect(settings_aqbanking, &QAction::triggered, this, &KBankingPlugin::slotSettings); QAction *file_import_aqbanking = actionCollection()->addAction("file_import_aqbanking"); file_import_aqbanking->setText(i18n("AqBanking importer...")); connect(file_import_aqbanking, &QAction::triggered, this, &KBankingPlugin::slotImport); Q_CHECK_PTR(viewInterface()); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action("file_import_aqbanking"), &QAction::setEnabled); #ifdef KMM_DEBUG QAction *openChipTanDialog = actionCollection()->addAction("open_chiptan_dialog"); openChipTanDialog->setText("Open ChipTan Dialog"); connect(openChipTanDialog, &QAction::triggered, [&](){ auto dlg = new chipTanDialog(); dlg->setHhdCode("0F04871100030333555414312C32331D"); dlg->setInfoText("

Test Graphic for debugging

The encoded data is

Account Number: 335554
Amount: 1,23

"); connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater); connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater); dlg->show(); }); #endif } void KBankingPlugin::slotSettings() { if (m_kbanking) { GWEN_DIALOG* dlg = AB_SetupDialog_new(m_kbanking->getCInterface()); if (dlg == NULL) { DBG_ERROR(0, "Could not create setup dialog."); return; } if (GWEN_Gui_ExecDialog(dlg, 0) == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); return; } GWEN_Dialog_free(dlg); } } bool KBankingPlugin::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings) { bool rc = false; if (m_kbanking && !acc.id().isEmpty()) { m_kbanking->askMapAccount(acc); // at this point, the account should be mapped // so we search it and setup the account reference in the KMyMoney object AB_ACCOUNT* ab_acc; ab_acc = aqbAccount(acc); if (ab_acc) { MyMoneyAccount a(acc); setupAccountReference(a, ab_acc); settings = a.onlineBankingSettings(); rc = true; } } return rc; } AB_ACCOUNT* KBankingPlugin::aqbAccount(const MyMoneyAccount& acc) const { if (m_kbanking == 0) { return 0; } // certainly looking for an expense or income account does not make sense at this point // so we better get out right away if (acc.isIncomeExpense()) { return 0; } AB_ACCOUNT *ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), m_kbanking->mappingId(acc).toUtf8().data()); // if the account is not found, we temporarily scan for the 'old' mapping (the one w/o the file id) // in case we find it, we setup the new mapping in addition on the fly. if (!ab_acc && acc.isAssetLiability()) { ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), acc.id().toUtf8().data()); if (ab_acc) { qDebug("Found old mapping for '%s' but not new. Setup new mapping", qPrintable(acc.name())); m_kbanking->setAccountAlias(ab_acc, m_kbanking->mappingId(acc).toUtf8().constData()); // TODO at some point in time, we should remove the old mapping } } return ab_acc; } AB_ACCOUNT* KBankingPlugin::aqbAccount(const QString& accountId) const { MyMoneyAccount account = MyMoneyFile::instance()->account(accountId); return aqbAccount(account); } QString KBankingPlugin::stripLeadingZeroes(const QString& s) const { QString rc(s); QRegExp exp("^(0*)([^0].*)"); if (exp.exactMatch(s)) { rc = exp.cap(2); } return rc; } void KBankingPlugin::setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT* ab_acc) { MyMoneyKeyValueContainer kvp; if (ab_acc) { QString accountNumber = stripLeadingZeroes(AB_Account_GetAccountNumber(ab_acc)); QString routingNumber = stripLeadingZeroes(AB_Account_GetBankCode(ab_acc)); QString val = QString("%1-%2").arg(routingNumber, accountNumber); if (val != acc.onlineBankingSettings().value("kbanking-acc-ref")) { MyMoneyKeyValueContainer kvp; // make sure to keep our own previous settings const QMap& vals = acc.onlineBankingSettings().pairs(); QMap::const_iterator it_p; for (it_p = vals.begin(); it_p != vals.end(); ++it_p) { if (QString(it_p.key()).startsWith("kbanking-")) { kvp.setValue(it_p.key(), *it_p); } } kvp.setValue("kbanking-acc-ref", val); kvp.setValue("provider", objectName()); setAccountOnlineParameters(acc, kvp); } } else { // clear the connection setAccountOnlineParameters(acc, kvp); } } bool KBankingPlugin::accountIsMapped(const MyMoneyAccount& acc) { return aqbAccount(acc) != 0; } bool KBankingPlugin::updateAccount(const MyMoneyAccount& acc) { return updateAccount(acc, false); } bool KBankingPlugin::updateAccount(const MyMoneyAccount& acc, bool moreAccounts) { if (!m_kbanking) return false; bool rc = false; if (!acc.id().isEmpty()) { AB_JOB *job = 0; int rv; /* get AqBanking account */ AB_ACCOUNT *ba = aqbAccount(acc); // Update the connection between the KMyMoney account and the AqBanking equivalent. // If the account is not found anymore ba == 0 and the connection is removed. setupAccountReference(acc, ba); if (!ba) { KMessageBox::error(0, i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", acc.name()), i18n("Account Not Mapped")); } else { bool enqueJob = true; if (acc.onlineBankingSettings().value("kbanking-txn-download") != "no") { /* create getTransactions job */ job = AB_JobGetTransactions_new(ba); rv = AB_Job_CheckAvailability(job); if (rv) { DBG_ERROR(0, "Job \"GetTransactions\" is not available (%d)", rv); KMessageBox::error(0, i18n("" "The update job is not supported by the " "bank/account/backend.\n" ""), i18n("Job not Available")); AB_Job_free(job); job = 0; } if (job) { int days = AB_JobGetTransactions_GetMaxStoreDays(job); QDate qd; if (days > 0) { GWEN_TIME *ti1; GWEN_TIME *ti2; ti1 = GWEN_CurrentTime(); ti2 = GWEN_Time_fromSeconds(GWEN_Time_Seconds(ti1) - (60 * 60 * 24 * days)); GWEN_Time_free(ti1); ti1 = ti2; int year, month, day; if (GWEN_Time_GetBrokenDownDate(ti1, &day, &month, &year)) { DBG_ERROR(0, "Bad date"); qd = QDate(); } else qd = QDate(year, month + 1, day); GWEN_Time_free(ti1); } // get last statement request date from application account object // and start from a few days before if the date is valid QDate lastUpdate = QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate); if (lastUpdate.isValid()) lastUpdate = lastUpdate.addDays(-3); int dateOption = acc.onlineBankingSettings().value("kbanking-statementDate").toInt(); switch (dateOption) { case 0: // Ask user break; case 1: // No date qd = QDate(); break; case 2: // Last download qd = lastUpdate; break; case 3: // First possible // qd is already setup break; } // the pick start date option dialog is needed in // case the dateOption is 0 or the date option is > 1 // and the qd is invalid if (dateOption == 0 || (dateOption > 1 && !qd.isValid())) { QPointer psd = new KBPickStartDate(m_kbanking, qd, lastUpdate, acc.name(), lastUpdate.isValid() ? 2 : 3, 0, true); if (psd->exec() == QDialog::Accepted) { qd = psd->date(); } else { enqueJob = false; } delete psd; } if (enqueJob) { if (qd.isValid()) { GWEN_TIME *ti1; ti1 = GWEN_Time_new(qd.year(), qd.month() - 1, qd.day(), 0, 0, 0, 0); AB_JobGetTransactions_SetFromTime(job, ti1); GWEN_Time_free(ti1); } rv = m_kbanking->enqueueJob(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } } AB_Job_free(job); } } if (enqueJob) { /* create getBalance job */ job = AB_JobGetBalance_new(ba); rv = AB_Job_CheckAvailability(job); if (!rv) rv = m_kbanking->enqueueJob(job); else rv = 0; AB_Job_free(job); if (rv) { DBG_ERROR(0, "Error %d", rv); KMessageBox::error(0, i18n("" "Could not enqueue the job.\n" ""), i18n("Error")); } else { rc = true; emit queueChanged(); } } } } // make sure we have at least one job in the queue before sending it if (!moreAccounts && m_kbanking->getEnqueuedJobs().size() > 0) executeQueue(); return rc; } void KBankingPlugin::executeQueue() { if (m_kbanking && m_kbanking->getEnqueuedJobs().size() > 0) { AB_IMEXPORTER_CONTEXT *ctx; ctx = AB_ImExporterContext_new(); int rv = m_kbanking->executeQueue(ctx); if (!rv) { m_kbanking->importContext(ctx, 0); } else { DBG_ERROR(0, "Error: %d", rv); } AB_ImExporterContext_free(ctx); } } /** @todo improve error handling, e.g. by adding a .isValid to nationalTransfer * @todo use new onlineJob system */ void KBankingPlugin::sendOnlineJob(QList& jobs) { Q_CHECK_PTR(m_kbanking); m_onlineJobQueue.clear(); QList unhandledJobs; if (!jobs.isEmpty()) { foreach (onlineJob job, jobs) { if (sepaOnlineTransfer::name() == job.task()->taskName()) { onlineJobTyped typedJob(job); enqueTransaction(typedJob); job = typedJob; } else { job.addJobMessage(onlineJobMessage(onlineJobMessage::error, "KBanking", "Cannot handle this request")); unhandledJobs.append(job); } m_onlineJobQueue.insert(m_kbanking->mappingId(job), job); } executeQueue(); } jobs = m_onlineJobQueue.values() + unhandledJobs; m_onlineJobQueue.clear(); } QStringList KBankingPlugin::availableJobs(QString accountId) { try { MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId); QString id = MyMoneyFile::instance()->value("kmm-id"); if(id != d->fileId) { d->jobList.clear(); d->fileId = id; } } catch (const MyMoneyException&) { // Exception usually means account was not found return QStringList(); } if(d->jobList.contains(accountId)) { return d->jobList[accountId]; } QStringList list; AB_ACCOUNT* abAccount = aqbAccount(accountId); if (!abAccount) { return list; } // Check availableJobs // sepa transfer AB_JOB* abJob = AB_JobSepaTransfer_new(abAccount); if (AB_Job_CheckAvailability(abJob) == 0) list.append(sepaOnlineTransfer::name()); AB_Job_free(abJob); d->jobList[accountId] = list; return list; } /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbJobDeleter { public: static void cleanup(AB_JOB* job) { AB_Job_free(job); } }; /** @brief experimenting with QScopedPointer and aqBanking pointers */ class QScopedPointerAbAccountDeleter { public: static void cleanup(AB_ACCOUNT* account) { AB_Account_free(account); } }; IonlineTaskSettings::ptr KBankingPlugin::settings(QString accountId, QString taskName) { AB_ACCOUNT* abAcc = aqbAccount(accountId); if (abAcc == 0) return IonlineTaskSettings::ptr(); if (sepaOnlineTransfer::name() == taskName) { // Get limits for sepaonlinetransfer QScopedPointer abJob(AB_JobSepaTransfer_new(abAcc)); if (AB_Job_CheckAvailability(abJob.data()) != 0) return IonlineTaskSettings::ptr(); const AB_TRANSACTION_LIMITS* limits = AB_Job_GetFieldLimits(abJob.data()); return AB_TransactionLimits_toSepaOnlineTaskSettings(limits).dynamicCast(); } return IonlineTaskSettings::ptr(); } bool KBankingPlugin::enqueTransaction(onlineJobTyped& job) { /* get AqBanking account */ const QString accId = job.constTask()->responsibleAccount(); AB_ACCOUNT *abAccount = aqbAccount(accId); if (!abAccount) { job.addJobMessage(onlineJobMessage(onlineJobMessage::warning, "KBanking", i18n("" "The given application account %1 " "has not been mapped to an online " "account." "", MyMoneyFile::instance()->account(accId).name()))); return false; } //setupAccountReference(acc, ba); // needed? AB_JOB *abJob = AB_JobSepaTransfer_new(abAccount); int rv = AB_Job_CheckAvailability(abJob); if (rv) { qDebug("AB_ERROR_OFFSET is %i", AB_ERROR_OFFSET); job.addJobMessage(onlineJobMessage(onlineJobMessage::error, "AqBanking", QString("Sepa credit transfers for account \"%1\" are not available, error code %2.").arg(MyMoneyFile::instance()->account(accId).name(), rv) ) ); return false; } AB_TRANSACTION *AbTransaction = AB_Transaction_new(); // Recipient payeeIdentifiers::ibanBic beneficiaryAcc = job.constTask()->beneficiaryTyped(); AB_Transaction_SetRemoteName(AbTransaction, GWEN_StringList_fromQString(beneficiaryAcc.ownerName())); AB_Transaction_SetRemoteIban(AbTransaction, beneficiaryAcc.electronicIban().toUtf8().constData()); AB_Transaction_SetRemoteBic(AbTransaction, beneficiaryAcc.fullStoredBic().toUtf8().constData()); // Origin Account AB_Transaction_SetLocalAccount(AbTransaction, abAccount); // Purpose QStringList qPurpose = job.constTask()->purpose().split('\n'); GWEN_STRINGLIST *purpose = GWEN_StringList_fromQStringList(qPurpose); AB_Transaction_SetPurpose(AbTransaction, purpose); GWEN_StringList_free(purpose); // Reference // AqBanking duplicates the string. This should be safe. AB_Transaction_SetEndToEndReference(AbTransaction, job.constTask()->endToEndReference().toUtf8().constData()); // Other Fields AB_Transaction_SetTextKey(AbTransaction, job.constTask()->textKey()); AB_Transaction_SetValue(AbTransaction, AB_Value_fromMyMoneyMoney(job.constTask()->value())); /** @todo LOW remove Debug info */ qDebug() << "SetTransaction: " << AB_Job_SetTransaction(abJob, AbTransaction); GWEN_DB_NODE *gwenNode = AB_Job_GetAppData(abJob); GWEN_DB_SetCharValue(gwenNode, GWEN_DB_FLAGS_DEFAULT, "kmmOnlineJobId", m_kbanking->mappingId(job).toLatin1().constData()); qDebug() << "Enqueue: " << m_kbanking->enqueueJob(abJob); //delete localAcc; return true; } void KBankingPlugin::startPasswordTimer() { if (d->passwordCacheTimer->isActive()) d->passwordCacheTimer->stop(); d->passwordCacheTimer->start(); } void KBankingPlugin::slotClearPasswordCache() { m_kbanking->clearPasswordCache(); } void KBankingPlugin::slotImport() { if (!m_kbanking->interactiveImport()) qWarning("Error on import dialog"); } bool KBankingPlugin::importStatement(const MyMoneyStatement& s) { return statementInterface()->import(s); } const MyMoneyAccount& KBankingPlugin::account(const QString& key, const QString& value) const { return statementInterface()->account(key, value); } void KBankingPlugin::setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const { return statementInterface()->setAccountOnlineParameters(acc, kvps); } KMyMoneyBanking::KMyMoneyBanking(KBankingPlugin* parent, const char* appname, const char* fname) : AB_Banking(appname, fname) , m_parent(parent) , _jobQueue(0) { m_sepaKeywords = {QString::fromUtf8("SEPA-BASISLASTSCHRIFT"), QString::fromUtf8("SEPA-ÃœBERWEISUNG")}; } int KMyMoneyBanking::init() { int rv = AB_Banking::init(); if (rv < 0) return rv; rv = onlineInit(); if (rv) { fprintf(stderr, "Error on online init (%d).\n", rv); AB_Banking::fini(); return rv; } _jobQueue = AB_Job_List2_new(); return 0; } int KMyMoneyBanking::fini() { if (_jobQueue) { AB_Job_List2_FreeAll(_jobQueue); _jobQueue = 0; } const int rv = onlineFini(); if (rv) { AB_Banking::fini(); return rv; } return AB_Banking::fini(); } int KMyMoneyBanking::executeQueue(AB_IMEXPORTER_CONTEXT *ctx) { m_parent->startPasswordTimer(); int rv = AB_Banking::executeJobs(_jobQueue, ctx); if (rv != 0) { qDebug() << "Sending queue by aqbanking got error no " << rv; } /** check result of each job */ AB_JOB_LIST2_ITERATOR* jobIter = AB_Job_List2_First(_jobQueue); if (jobIter) { AB_JOB* abJob = AB_Job_List2Iterator_Data(jobIter); while (abJob) { GWEN_DB_NODE* gwenNode = AB_Job_GetAppData(abJob); if (gwenNode == 0) { qWarning("Executed AB_Job without KMyMoney id"); abJob = AB_Job_List2Iterator_Next(jobIter); break; } QString jobIdent = QString::fromUtf8(GWEN_DB_GetCharValue(gwenNode, "kmmOnlineJobId", 0, "")); onlineJob job = m_parent->m_onlineJobQueue.value(jobIdent); if (job.isNull()) { // It should not be possiblie that this will happen (only if AqBanking fails heavily). //! @todo correct exception text qWarning("Executed a job which was not in queue. Please inform the KMyMoney developers."); abJob = AB_Job_List2Iterator_Next(jobIter); continue; } AB_JOB_STATUS abStatus = AB_Job_GetStatus(abJob); if (abStatus == AB_Job_StatusSent || abStatus == AB_Job_StatusPending || abStatus == AB_Job_StatusFinished || abStatus == AB_Job_StatusError || abStatus == AB_Job_StatusUnknown) job.setJobSend(); if (abStatus == AB_Job_StatusFinished) job.setBankAnswer(onlineJob::acceptedByBank); else if (abStatus == AB_Job_StatusError || abStatus == AB_Job_StatusUnknown) job.setBankAnswer(onlineJob::sendingError); job.addJobMessage(onlineJobMessage(onlineJobMessage::debug, "KBanking", "Job was processed")); m_parent->m_onlineJobQueue.insert(jobIdent, job); abJob = AB_Job_List2Iterator_Next(jobIter); } AB_Job_List2Iterator_free(jobIter); } AB_JOB_LIST2 *oldQ = _jobQueue; _jobQueue = AB_Job_List2_new(); AB_Job_List2_FreeAll(oldQ); emit m_parent->queueChanged(); m_parent->startPasswordTimer(); return rv; } void KMyMoneyBanking::clearPasswordCache() { /* clear password DB */ GWEN_Gui_SetPasswordStatus(NULL, NULL, GWEN_Gui_PasswordStatus_Remove, 0); } std::list KMyMoneyBanking::getEnqueuedJobs() { AB_JOB_LIST2 *ll; std::list rl; ll = _jobQueue; if (ll && AB_Job_List2_GetSize(ll)) { AB_JOB *j; AB_JOB_LIST2_ITERATOR *it; it = AB_Job_List2_First(ll); assert(it); j = AB_Job_List2Iterator_Data(it); assert(j); while (j) { rl.push_back(j); j = AB_Job_List2Iterator_Next(it); } AB_Job_List2Iterator_free(it); } return rl; } int KMyMoneyBanking::enqueueJob(AB_JOB *j) { assert(_jobQueue); assert(j); AB_Job_Attach(j); AB_Job_List2_PushBack(_jobQueue, j); return 0; } int KMyMoneyBanking::dequeueJob(AB_JOB *j) { assert(_jobQueue); AB_Job_List2_Remove(_jobQueue, j); AB_Job_free(j); emit m_parent->queueChanged(); return 0; } void KMyMoneyBanking::transfer() { //m_parent->transfer(); } bool KMyMoneyBanking::askMapAccount(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); QString bankId; QString accountId; // extract some information about the bank. if we have a sortcode // (BLZ) we display it, otherwise the name is enough. try { const MyMoneyInstitution &bank = file->institution(acc.institutionId()); bankId = bank.name(); if (!bank.sortcode().isEmpty()) bankId = bank.sortcode(); } catch (const MyMoneyException &e) { // no bank assigned, we just leave the field emtpy } // extract account information. if we have an account number // we show it, otherwise the name will be displayed accountId = acc.number(); if (accountId.isEmpty()) accountId = acc.name(); // do the mapping. the return value of this method is either // true, when the user mapped the account or false, if he // decided to quit the dialog. So not really a great thing // to present some more information. KBMapAccount *w; w = new KBMapAccount(this, bankId.toUtf8().constData(), accountId.toUtf8().constData()); if (w->exec() == QDialog::Accepted) { AB_ACCOUNT *a; a = w->getAccount(); assert(a); DBG_NOTICE(0, "Mapping application account \"%s\" to " "online account \"%s/%s\"", qPrintable(acc.name()), AB_Account_GetBankCode(a), AB_Account_GetAccountNumber(a)); // TODO remove the following line once we don't need backward compatibility setAccountAlias(a, acc.id().toUtf8().constData()); qDebug("Setup mapping to '%s'", acc.id().toUtf8().constData()); setAccountAlias(a, mappingId(acc).toUtf8().constData()); qDebug("Setup mapping to '%s'", mappingId(acc).toUtf8().constData()); delete w; return true; } delete w; return false; } QString KMyMoneyBanking::mappingId(const MyMoneyObject& object) const { QString id = MyMoneyFile::instance()->storageId() + QLatin1Char('-') + object.id(); // AqBanking does not handle the enclosing parens, so we remove it id.remove('{'); id.remove('}'); return id; } bool KMyMoneyBanking::interactiveImport() { AB_IMEXPORTER_CONTEXT *ctx; GWEN_DIALOG *dlg; int rv; ctx = AB_ImExporterContext_new(); dlg = AB_ImporterDialog_new(getCInterface(), ctx, NULL); if (dlg == NULL) { DBG_ERROR(0, "Could not create importer dialog."); AB_ImExporterContext_free(ctx); return false; } rv = GWEN_Gui_ExecDialog(dlg, 0); if (rv == 0) { DBG_ERROR(0, "Aborted by user"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } if (!importContext(ctx, 0)) { DBG_ERROR(0, "Error on importContext"); GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return false; } GWEN_Dialog_free(dlg); AB_ImExporterContext_free(ctx); return true; } const AB_ACCOUNT_STATUS* KMyMoneyBanking::_getAccountStatus(AB_IMEXPORTER_ACCOUNTINFO *ai) { const AB_ACCOUNT_STATUS *ast; const AB_ACCOUNT_STATUS *best; best = 0; ast = AB_ImExporterAccountInfo_GetFirstAccountStatus(ai); while (ast) { if (!best) best = ast; else { const GWEN_TIME *tiBest; const GWEN_TIME *ti; tiBest = AB_AccountStatus_GetTime(best); ti = AB_AccountStatus_GetTime(ast); if (!tiBest) { best = ast; } else { if (ti) { double d; /* we have two times, compare them */ d = GWEN_Time_Diff(ti, tiBest); if (d > 0) /* newer */ best = ast; } } } ast = AB_ImExporterAccountInfo_GetNextAccountStatus(ai); } /* while */ return best; } void KMyMoneyBanking::_xaToStatement(MyMoneyStatement &ks, const MyMoneyAccount& acc, const AB_TRANSACTION *t) { const GWEN_STRINGLIST *sl; QString s; QString memo; const char *p; const AB_VALUE *val; const GWEN_TIME *ti; const GWEN_TIME *startTime = 0; MyMoneyStatement::Transaction kt; unsigned long h; kt.m_fees = MyMoneyMoney(); // bank's transaction id p = AB_Transaction_GetFiId(t); if (p) kt.m_strBankID = QString("ID ") + QString::fromUtf8(p); // payee s.truncate(0); sl = AB_Transaction_GetRemoteName(t); if (sl) { GWEN_STRINGLISTENTRY *se; se = GWEN_StringList_FirstEntry(sl); while (se) { p = GWEN_StringListEntry_Data(se); assert(p); s += QString::fromUtf8(p); se = GWEN_StringListEntry_Next(se); } // while } kt.m_strPayee = s; // memo // The variable 's' contains the old method of extracting // the memo which added a linefeed after each part received // from AqBanking. The new variable 'memo' does not have // this inserted linefeed. We keep the variable 's' to // construct the hash-value to retrieve the reference s.truncate(0); sl = AB_Transaction_GetPurpose(t); if (sl) { GWEN_STRINGLISTENTRY *se; bool insertLineSep = false; se = GWEN_StringList_FirstEntry(sl); while (se) { p = GWEN_StringListEntry_Data(se); assert(p); if (insertLineSep) s += '\n'; insertLineSep = true; s += QString::fromUtf8(p).trimmed(); memo += QString::fromUtf8(p).trimmed(); se = GWEN_StringListEntry_Next(se); } // while // Sparda / Netbank hack: the software these banks use stores // parts of the payee name in the beginning of the purpose field // in case the payee name exceeds the 27 character limit. This is // the case, when one of the strings listed in m_sepaKeywords is part // of the purpose fields but does not start at the beginning. In this // case, the part leading up to the keyword is to be treated as the // tail of the payee. Also, a blank is inserted after the keyword. QSet::const_iterator itk; for (itk = m_sepaKeywords.constBegin(); itk != m_sepaKeywords.constEnd(); ++itk) { int idx = s.indexOf(*itk); if (idx >= 0) { if (idx > 0) { // re-add a possibly removed blank to name if (kt.m_strPayee.length() < 27) kt.m_strPayee += ' '; kt.m_strPayee += s.left(idx); s = s.mid(idx); } s = QString("%1 %2").arg(*itk).arg(s.mid((*itk).length())); // now do the same for 'memo' except for updating the payee idx = memo.indexOf(*itk); if (idx >= 0) { if (idx > 0) { memo = memo.mid(idx); } } memo = QString("%1 %2").arg(*itk).arg(memo.mid((*itk).length())); break; } } // in case we have some SEPA fields filled with information // we add them to the memo field p = AB_Transaction_GetEndToEndReference(t); if (p) { s += QString(", EREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("EREF: %1").arg(p)); } p = AB_Transaction_GetCustomerReference(t); if (p) { s += QString(", CREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CREF: %1").arg(p)); } p = AB_Transaction_GetMandateId(t); if (p) { s += QString(", MREF: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("MREF: %1").arg(p)); } p = AB_Transaction_GetCreditorSchemeId(t); if (p) { s += QString(", CRED: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("CRED: %1").arg(p)); } p = AB_Transaction_GetOriginatorIdentifier(t); if (p) { s += QString(", DEBT: %1").arg(p); if(memo.length()) memo.append('\n'); memo.append(QString("DEBT: %1").arg(p)); } } kt.m_strMemo = memo; // calculate the hash code and start with the payee info // and append the memo field h = MyMoneyTransaction::hash(kt.m_strPayee.trimmed()); h = MyMoneyTransaction::hash(s, h); // see, if we need to extract the payee from the memo field const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings(); QString rePayee = kvp.value("kbanking-payee-regexp"); if (!rePayee.isEmpty() && kt.m_strPayee.isEmpty()) { QString reMemo = kvp.value("kbanking-memo-regexp"); QStringList exceptions = kvp.value("kbanking-payee-exceptions").split(';', QString::SkipEmptyParts); bool needExtract = true; QStringList::const_iterator it_s; for (it_s = exceptions.constBegin(); needExtract && it_s != exceptions.constEnd(); ++it_s) { QRegExp exp(*it_s, Qt::CaseInsensitive); if (exp.indexIn(kt.m_strMemo) != -1) { needExtract = false; } } if (needExtract) { QRegExp expPayee(rePayee, Qt::CaseInsensitive); QRegExp expMemo(reMemo, Qt::CaseInsensitive); if (expPayee.indexIn(kt.m_strMemo) != -1) { kt.m_strPayee = expPayee.cap(1); if (expMemo.indexIn(kt.m_strMemo) != -1) { kt.m_strMemo = expMemo.cap(1); } } } } kt.m_strPayee = kt.m_strPayee.trimmed(); // date ti = AB_Transaction_GetDate(t); if (!ti) ti = AB_Transaction_GetValutaDate(t); if (ti) { int year, month, day; if (!startTime) startTime = ti; else { if (GWEN_Time_Diff(ti, startTime) < 0) startTime = ti; } if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) { kt.m_datePosted = QDate(year, month + 1, day); } } else { DBG_WARN(0, "No date for transaction"); } // value val = AB_Transaction_GetValue(t); if (val) { if (ks.m_strCurrency.isEmpty()) { p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } else { p = AB_Value_GetCurrency(val); if (p) s = p; if (ks.m_strCurrency.toLower() != s.toLower()) { // TODO: handle currency difference DBG_ERROR(0, "Mixed currencies currently not allowed"); } } kt.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val)); // The initial implementation of this feature was based on // a denominator of 100. Since the denominator might be // different nowadays, we make sure to use 100 for the // duplicate detection QString tmpVal = kt.m_amount.formatMoney(100, false); tmpVal.remove(QRegExp("[,\\.]")); tmpVal += QLatin1String("/100"); h = MyMoneyTransaction::hash(tmpVal, h); } else { DBG_WARN(0, "No value for transaction"); } if (startTime) { int year, month, day; if (!GWEN_Time_GetBrokenDownDate(startTime, &day, &month, &year)) { QDate d(year, month + 1, day); if (!ks.m_dateBegin.isValid()) ks.m_dateBegin = d; else if (d < ks.m_dateBegin) ks.m_dateBegin = d; if (!ks.m_dateEnd.isValid()) ks.m_dateEnd = d; else if (d > ks.m_dateEnd) ks.m_dateEnd = d; } } else { DBG_WARN(0, "No date in current transaction"); } // add information about remote account to memo in case we have something const char *remoteAcc = AB_Transaction_GetRemoteAccountNumber(t); const char *remoteBankCode = AB_Transaction_GetRemoteBankCode(t); if (remoteAcc && remoteBankCode) { kt.m_strMemo += QString("\n%1/%2").arg(remoteBankCode, remoteAcc); } // make hash value unique in case we don't have one already if (kt.m_strBankID.isEmpty()) { QString hashBase; hashBase.sprintf("%s-%07lx", qPrintable(kt.m_datePosted.toString(Qt::ISODate)), h); int idx = 1; QString hash; for (;;) { hash = QString("%1-%2").arg(hashBase).arg(idx); QMap::const_iterator it; it = m_hashMap.constFind(hash); if (it == m_hashMap.constEnd()) { m_hashMap[hash] = true; break; } ++idx; } kt.m_strBankID = QString("%1-%2").arg(acc.id()).arg(hash); } // store transaction ks.m_listTransactions += kt; } bool KMyMoneyBanking::importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai, uint32_t /*flags*/) { const char *p; DBG_INFO(0, "Importing account..."); // account number MyMoneyStatement ks; p = AB_ImExporterAccountInfo_GetAccountNumber(ai); if (p) { ks.m_strAccountNumber = m_parent->stripLeadingZeroes(p); } p = AB_ImExporterAccountInfo_GetBankCode(ai); if (p) { ks.m_strRoutingNumber = m_parent->stripLeadingZeroes(p); } MyMoneyAccount kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber)); ks.m_accountId = kacc.id(); // account name p = AB_ImExporterAccountInfo_GetAccountName(ai); if (p) ks.m_strAccountName = p; // account type switch (AB_ImExporterAccountInfo_GetType(ai)) { case AB_AccountType_Bank: ks.m_eType = MyMoneyStatement::etSavings; break; case AB_AccountType_CreditCard: ks.m_eType = MyMoneyStatement::etCreditCard; break; case AB_AccountType_Checking: ks.m_eType = MyMoneyStatement::etCheckings; break; case AB_AccountType_Savings: ks.m_eType = MyMoneyStatement::etSavings; break; case AB_AccountType_Investment: ks.m_eType = MyMoneyStatement::etInvestment; break; case AB_AccountType_Cash: ks.m_eType = MyMoneyStatement::etNone; break; default: ks.m_eType = MyMoneyStatement::etNone; } // account status const AB_ACCOUNT_STATUS* ast = _getAccountStatus(ai); if (ast) { const AB_BALANCE *bal; bal = AB_AccountStatus_GetBookedBalance(ast); if (!bal) bal = AB_AccountStatus_GetNotedBalance(ast); if (bal) { const AB_VALUE* val = AB_Balance_GetValue(bal); if (val) { DBG_INFO(0, "Importing balance"); ks.m_closingBalance = AB_Value_toMyMoneyMoney(val); p = AB_Value_GetCurrency(val); if (p) ks.m_strCurrency = p; } const GWEN_TIME* ti = AB_Balance_GetTime(bal); if (ti) { int year, month, day; if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) ks.m_dateEnd = QDate(year, month + 1, day); } else { DBG_WARN(0, "No time for balance"); } } else { DBG_WARN(0, "No account balance"); } } else { DBG_WARN(0, "No account status"); } // clear hash map m_hashMap.clear(); // get all transactions const AB_TRANSACTION* t = AB_ImExporterAccountInfo_GetFirstTransaction(ai); while (t) { _xaToStatement(ks, kacc, t); t = AB_ImExporterAccountInfo_GetNextTransaction(ai); } // import them if (!m_parent->importStatement(ks)) { if (KMessageBox::warningYesNo(0, i18n("Error importing statement. Do you want to continue?"), i18n("Critical Error")) == KMessageBox::No) { DBG_ERROR(0, "User aborted"); return false; } } return true; } #include "mymoneybanking.moc" diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index 59589d3c2..4f890ebfe 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,2159 +1,2158 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000 by Michael Edwardes 2004 by Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * 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 "kmymoneyview.h" -#include // ---------------------------------------------------------------------------- // Std Includes #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include "kendingbalancedlg.h" #include "kchooseimportexportdlg.h" #include "kimportdlg.h" #include "kexportdlg.h" #include "knewloanwizard.h" #include "kcurrencyeditdlg.h" #include "kfindtransactiondlg.h" #include "knewbankdlg.h" #include "mymoneyseqaccessmgr.h" #include "mymoneydatabasemgr.h" #include "imymoneystorageformat.h" #include "mymoneystoragebin.h" #include "mymoneyexception.h" #include "mymoneystoragexml.h" #include "mymoneystoragesql.h" #include "mymoneygncreader.h" #include "mymoneystorageanon.h" #include #include "khomeview.h" #include "kaccountsview.h" #include "kcategoriesview.h" #include "kinstitutionsview.h" #include "kpayeesview.h" #include "ktagsview.h" #include "kscheduledview.h" #include "kgloballedgerview.h" #include "simpleledgerview.h" #include "kinvestmentview.h" #include "kreportsview.h" #include "kbudgetview.h" #include "kforecastview.h" #include "konlinejoboutbox.h" #include "kmymoney.h" #include "kmymoneyutils.h" #include "models.h" static constexpr KCompressionDevice::CompressionType COMPRESSION_TYPE = KCompressionDevice::GZip; static constexpr char recoveryKeyId[] = "0xD2B08440"; KMyMoneyView::KMyMoneyView(QWidget *parent) : KPageWidget(parent), m_header(0), m_inConstructor(true), m_fileOpen(false), m_fmode(0600), m_lastViewSelected(0) #ifdef KF5Activities_FOUND , m_activityResourceInstance(0) #endif { // this is a workaround for the bug in KPageWidget that causes the header to be shown // for a short while during page switch which causes a kind of bouncing of the page's // content and if the page's content is at it's minimum size then during a page switch // the main window's size is also increased to fit the header that is shown for a sort // period - reading the code in kpagewidget.cpp we know that the header should be at (1,1) // in a grid layout so if we find it there remove it for good to avoid the described issues QGridLayout* gridLayout = qobject_cast(layout()); if (gridLayout) { QLayoutItem* headerItem = gridLayout->itemAtPosition(1, 1); // make sure that we remove only the header - we avoid surprises if the header is not at (1,1) in the layout if (headerItem && qobject_cast(headerItem->widget()) != NULL) { gridLayout->removeItem(headerItem); // after we remove the KPageWidget standard header replace it with our own title label m_header = new KMyMoneyTitleLabel(this); m_header->setObjectName("titleLabel"); m_header->setMinimumSize(QSize(100, 30)); m_header->setRightImageFile("pics/titlelabel_background.png"); m_header->setVisible(KMyMoneyGlobalSettings::showTitleBar()); gridLayout->addWidget(m_header, 1, 1); } } // the global variable kmymoney is not yet assigned. So we construct it here QObject* kmymoney = parent->parent(); newStorage(); m_model = new KPageWidgetModel(parent); connect(kmymoney, SIGNAL(fileLoaded(QUrl)), this, SLOT(slotRefreshViews())); // let the accounts model know which account is being currently reconciled connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), Models::instance()->accountsModel(), SLOT(slotReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney))); // Page 0 m_homeView = new KHomeView(); m_homeViewFrame = m_model->addPage(m_homeView, i18n("Home")); m_homeViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("go-home"), QIcon::fromTheme(QStringLiteral("home")))); connect(m_homeView, SIGNAL(ledgerSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_homeView, SIGNAL(scheduleSelected(QString)), this, SLOT(slotScheduleSelected(QString))); connect(m_homeView, SIGNAL(reportSelected(QString)), this, SLOT(slotShowReport(QString))); connect(m_homeView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 1 m_institutionsView = new KInstitutionsView(); m_institutionsViewFrame = m_model->addPage(m_institutionsView, i18n("Institutions")); m_institutionsViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-bank"), QIcon::fromTheme(QStringLiteral("institution")))); connect(m_institutionsView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_institutionsView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInstitution(MyMoneyObject))); connect(m_institutionsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowAccountContextMenu(MyMoneyObject))); connect(m_institutionsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowInstitutionContextMenu(MyMoneyObject))); connect(m_institutionsView, SIGNAL(openObject(MyMoneyObject)), kmymoney, SLOT(slotInstitutionEdit(MyMoneyObject))); connect(m_institutionsView, SIGNAL(openObject(MyMoneyObject)), kmymoney, SLOT(slotAccountOpen(MyMoneyObject))); connect(m_institutionsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 2 m_accountsView = new KAccountsView(); m_accountsViewFrame = m_model->addPage(m_accountsView, i18n("Accounts")); m_accountsViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-bank-account"), QIcon::fromTheme(QStringLiteral("account")))); connect(m_accountsView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_accountsView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInstitution(MyMoneyObject))); connect(m_accountsView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInvestment(MyMoneyObject))); connect(m_accountsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowAccountContextMenu(MyMoneyObject))); connect(m_accountsView, SIGNAL(openObject(MyMoneyObject)), kmymoney, SLOT(slotAccountOpen(MyMoneyObject))); connect(m_accountsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 3 m_scheduledView = new KScheduledView(); //this is to solve the way long strings are handled differently among versions of KPageWidget m_scheduleViewFrame = m_model->addPage(m_scheduledView, i18n("Scheduled transactions")); m_scheduleViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-calendar"), QIcon::fromTheme(QStringLiteral("schedule")))); connect(m_scheduledView, SIGNAL(scheduleSelected(MyMoneySchedule)), kmymoney, SLOT(slotSelectSchedule(MyMoneySchedule))); connect(m_scheduledView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowScheduleContextMenu())); connect(m_scheduledView, SIGNAL(enterSchedule()), kmymoney, SLOT(slotScheduleEnter())); connect(m_scheduledView, SIGNAL(skipSchedule()), kmymoney, SLOT(slotScheduleSkip())); connect(m_scheduledView, SIGNAL(editSchedule()), kmymoney, SLOT(slotScheduleEdit())); connect(m_scheduledView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 4 m_categoriesView = new KCategoriesView(); m_categoriesViewFrame = m_model->addPage(m_categoriesView, i18n("Categories")); m_categoriesViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-financial-categories"), QIcon::fromTheme(QStringLiteral("categories")))); connect(m_categoriesView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_categoriesView, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInstitution(MyMoneyObject))); connect(m_categoriesView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowAccountContextMenu(MyMoneyObject))); connect(m_categoriesView, SIGNAL(openObject(MyMoneyObject)), kmymoney, SLOT(slotAccountOpen(MyMoneyObject))); connect(m_categoriesView, SIGNAL(reparent(MyMoneyAccount,MyMoneyAccount)), kmymoney, SLOT(slotReparentAccount(MyMoneyAccount,MyMoneyAccount))); connect(m_categoriesView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 5 m_tagsView = new KTagsView(); m_tagsViewFrame = m_model->addPage(m_tagsView, i18n("Tags")); m_tagsViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("mail-tagged"), QIcon::fromTheme(QStringLiteral("bookmark-new")))); connect(kmymoney, SIGNAL(tagCreated(QString)), m_tagsView, SLOT(slotSelectTagAndTransaction(QString))); connect(kmymoney, SIGNAL(tagRename()), m_tagsView, SLOT(slotRenameButtonCliked())); connect(m_tagsView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowTagContextMenu())); connect(m_tagsView, SIGNAL(selectObjects(QList)), kmymoney, SLOT(slotSelectTags(QList))); connect(m_tagsView, SIGNAL(transactionSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_tagsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 6 m_payeesView = new KPayeesView(); m_payeesViewFrame = m_model->addPage(m_payeesView, i18n("Payees")); m_payeesViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("system-users"), QIcon::fromTheme(QStringLiteral("payee")))); connect(kmymoney, SIGNAL(payeeCreated(QString)), m_payeesView, SLOT(slotSelectPayeeAndTransaction(QString))); connect(kmymoney, SIGNAL(payeeRename()), m_payeesView, SLOT(slotRenameButtonCliked())); connect(m_payeesView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowPayeeContextMenu())); connect(m_payeesView, SIGNAL(selectObjects(QList)), kmymoney, SLOT(slotSelectPayees(QList))); connect(m_payeesView, SIGNAL(transactionSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_payeesView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 7 m_ledgerView = new KGlobalLedgerView(); m_ledgerViewFrame = m_model->addPage(m_ledgerView, i18n("Ledgers")); m_ledgerViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-financial-list"), QIcon::fromTheme(QStringLiteral("ledger")))); connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_ledgerView, SIGNAL(openContextMenu()), kmymoney, SLOT(slotShowTransactionContextMenu())); connect(m_ledgerView, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), kmymoney, SLOT(slotSelectTransactions(KMyMoneyRegister::SelectedTransactions))); connect(m_ledgerView, SIGNAL(newTransaction()), kmymoney, SLOT(slotTransactionsNew())); connect(m_ledgerView, SIGNAL(cancelOrEndEdit(bool&)), kmymoney, SLOT(slotTransactionsCancelOrEnter(bool&))); connect(m_ledgerView, SIGNAL(startEdit()), kmymoney, SLOT(slotTransactionsEdit())); connect(m_ledgerView, SIGNAL(endEdit()), kmymoney, SLOT(slotTransactionsEnter())); connect(m_ledgerView, SIGNAL(toggleReconciliationFlag()), kmymoney, SLOT(slotToggleReconciliationFlag())); connect(this, SIGNAL(reconciliationStarts(MyMoneyAccount,QDate,MyMoneyMoney)), m_ledgerView, SLOT(slotSetReconcileAccount(MyMoneyAccount,QDate,MyMoneyMoney))); connect(kmymoney, SIGNAL(selectAllTransactions()), m_ledgerView, SLOT(slotSelectAllTransactions())); connect(m_ledgerView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 8 m_investmentView = new KInvestmentView(); m_investmentViewFrame = m_model->addPage(m_investmentView, i18n("Investments")); m_investmentViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-investment"), QIcon::fromTheme(QStringLiteral("investment")))); connect(m_investmentView, SIGNAL(accountSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_investmentView, SIGNAL(investmentRightMouseClick()), kmymoney, SLOT(slotShowInvestmentContextMenu())); connect(m_investmentView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 9 m_reportsView = new KReportsView(); m_reportsViewFrame = m_model->addPage(m_reportsView, i18n("Reports")); m_reportsViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("office-chart-bar"), QIcon::fromTheme(QStringLiteral("report")))); connect(m_reportsView, SIGNAL(ledgerSelected(QString,QString)), this, SLOT(slotLedgerSelected(QString,QString))); connect(m_reportsView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 10 m_budgetView = new KBudgetView(); m_budgetViewFrame = m_model->addPage(m_budgetView, i18n("Budgets")); m_budgetViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-time-schedule-calculus"), QIcon::fromTheme(QStringLiteral("budget")))); connect(m_budgetView, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowBudgetContextMenu())); connect(m_budgetView, SIGNAL(selectObjects(QList)), kmymoney, SLOT(slotSelectBudget(QList))); connect(kmymoney, SIGNAL(budgetRename()), m_budgetView, SLOT(slotStartRename())); connect(m_budgetView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 11 m_forecastView = new KForecastView(); m_forecastViewFrame = m_model->addPage(m_forecastView, i18n("Forecast")); m_forecastViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("view-financial-forecast"), QIcon::fromTheme(QStringLiteral("forecast")))); connect(m_forecastView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 12 m_onlineJobOutboxView = new KOnlineJobOutbox(); m_onlineJobOutboxViewFrame = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox")); m_onlineJobOutboxViewFrame->setIcon(QIcon::fromTheme(QStringLiteral("online-banking"))); connect(m_onlineJobOutboxView, SIGNAL(sendJobs(QList)), kmymoney, SLOT(slotOnlineJobSend(QList))); connect(m_onlineJobOutboxView, SIGNAL(editJob(QString)), kmymoney, SLOT(slotEditOnlineJob(QString))); connect(m_onlineJobOutboxView, SIGNAL(newCreditTransfer()), kmymoney, SLOT(slotNewOnlineTransfer())); connect(m_onlineJobOutboxView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); connect(m_onlineJobOutboxView, SIGNAL(showContextMenu(onlineJob)), kmymoney, SLOT(slotShowOnlineJobContextMenu())); SimpleLedgerView* view = new SimpleLedgerView; KPageWidgetItem* frame = m_model->addPage(view, i18n("New ledger")); frame->setIcon(QIcon::fromTheme("document-properties")); connect(this, SIGNAL(fileClosed()), view, SLOT(closeLedgers())); connect(this, SIGNAL(fileOpened()), view, SLOT(openFavoriteLedgers())); //set the model setModel(m_model); setCurrentPage(m_homeViewFrame); connect(this, SIGNAL(currentPageChanged(QModelIndex,QModelIndex)), this, SLOT(slotCurrentPageChanged(QModelIndex,QModelIndex))); updateViewType(); m_inConstructor = false; // Initialize kactivities resource instance #ifdef KF5Activities_FOUND m_activityResourceInstance = new KActivities::ResourceInstance(window()->winId()); m_activityResourceInstance->setParent(this); connect(kmymoney, SIGNAL(fileLoaded(QUrl)), m_activityResourceInstance, SLOT(setUri(QUrl))); #endif } KMyMoneyView::~KMyMoneyView() { KMyMoneyGlobalSettings::setLastViewSelected(m_lastViewSelected); removeStorage(); } void KMyMoneyView::showTitleBar(bool show) { if (m_header) m_header->setVisible(show); } void KMyMoneyView::updateViewType() { // set the face type KPageView::FaceType faceType = KPageView::List; switch (KMyMoneySettings::viewType()) { case 0: faceType = KPageView::List; break; case 1: faceType = KPageView::Tree; break; case 2: faceType = KPageView::Tabbed; break; } if (faceType != KMyMoneyView::faceType()) { setFaceType(faceType); if (faceType == KPageView::Tree) { QList views = findChildren(); foreach (QTreeView * view, views) { if (view && (view->parent() == this)) { view->setRootIsDecorated(false); break; } } } } } bool KMyMoneyView::showPageHeader() const { return false; } void KMyMoneyView::showPage(KPageWidgetItem* pageItem) { // reset all selected items before showing the selected view // but not while we're in our own constructor if (!m_inConstructor && pageItem != currentPage()) { kmymoney->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; setCurrentPage(pageItem); m_inConstructor = prevConstructor; if (!m_inConstructor) { // fixup some actions that are dependant on the view // this does not work during construction kmymoney->slotUpdateActions(); } } bool KMyMoneyView::canPrint() { bool rc = ( m_reportsViewFrame == currentPage() || m_homeViewFrame == currentPage() ); 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 = (m_ledgerViewFrame == currentPage()); 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 = (m_ledgerViewFrame == currentPage()); 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 = (m_ledgerViewFrame == currentPage()); 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() { 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() { MyMoneyFile* file = MyMoneyFile::instance(); IMyMoneyStorage* p = file->storage(); if (p != 0) { file->detachStorage(p); delete p; } } void KMyMoneyView::enableViewsIfFileOpen() { // call set enabled only if the state differs to avoid widgets 'bouncing on the screen' while doing this if (m_accountsViewFrame->isEnabled() != m_fileOpen) m_accountsViewFrame->setEnabled(m_fileOpen); if (m_institutionsViewFrame->isEnabled() != m_fileOpen) m_institutionsViewFrame->setEnabled(m_fileOpen); if (m_scheduleViewFrame->isEnabled() != m_fileOpen) m_scheduleViewFrame->setEnabled(m_fileOpen); if (m_categoriesViewFrame->isEnabled() != m_fileOpen) m_categoriesViewFrame->setEnabled(m_fileOpen); if (m_payeesViewFrame->isEnabled() != m_fileOpen) m_payeesViewFrame->setEnabled(m_fileOpen); if (m_tagsViewFrame->isEnabled() != m_fileOpen) m_tagsViewFrame->setEnabled(m_fileOpen); if (m_budgetViewFrame->isEnabled() != m_fileOpen) m_budgetViewFrame->setEnabled(m_fileOpen); if (m_ledgerViewFrame->isEnabled() != m_fileOpen) m_ledgerViewFrame->setEnabled(m_fileOpen); if (m_investmentViewFrame->isEnabled() != m_fileOpen) m_investmentViewFrame->setEnabled(m_fileOpen); if (m_reportsViewFrame->isEnabled() != m_fileOpen) m_reportsViewFrame->setEnabled(m_fileOpen); if (m_forecastViewFrame->isEnabled() != m_fileOpen) m_forecastViewFrame->setEnabled(m_fileOpen); if (m_onlineJobOutboxViewFrame->isEnabled() != m_fileOpen) m_onlineJobOutboxViewFrame->setEnabled(m_fileOpen); emit viewStateChanged(m_fileOpen); } 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: setCurrentPage(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(m_payeesViewFrame); m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(m_tagsViewFrame); m_tagsView->slotSelectTagAndTransaction(tag, account, transaction); } void KMyMoneyView::slotScheduleSelected(const QString& scheduleId) { MyMoneySchedule sched = MyMoneyFile::instance()->schedule(scheduleId); kmymoney->slotSelectSchedule(sched); } void KMyMoneyView::slotShowReport(const QString& reportid) { showPage(m_reportsViewFrame); m_reportsView->slotOpenReport(reportid); } void KMyMoneyView::slotShowReport(const MyMoneyReport& report) { showPage(m_reportsViewFrame); m_reportsView->slotOpenReport(report); } bool KMyMoneyView::fileOpen() { return m_fileOpen; } void KMyMoneyView::closeFile() { if (m_reportsView) m_reportsView->slotCloseAll(); // disconnect the signals disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); disconnect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); disconnect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); disconnect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); // notify the models that the file is going to be closed (we should have something like dataChanged that reaches the models first) Models::instance()->fileClosed(); emit kmmFilePlugin(preClose); if (isDatabase()) MyMoneyFile::instance()->storage()->close(); // to log off a database user newStorage(); slotShowHomePage(); emit kmmFilePlugin(postClose); m_fileOpen = false; emit fileClosed(); } void KMyMoneyView::ungetString(QIODevice *qfile, char *buf, int len) { buf = &buf[len-1]; while (len--) { qfile->ungetChar(*buf--); } } bool KMyMoneyView::readFile(const QUrl &url) { QString filename; m_fileOpen = false; bool isEncrypted = false; IMyMoneyStorageFormat* pReader = 0; if (!url.isValid()) { qDebug("Invalid URL '%s'", qPrintable(url.url())); return false; } // disconnect the current storga manager from the engine MyMoneyFile::instance()->detachStorage(); if (url.scheme() == "sql") { // handle reading of database m_fileType = KmmDb; // get rid of the mode parameter which is now redundant QUrl newUrl(url); // TODO: port to kf5 #if 0 if (QUrlQuery(url).hasQueryItem("mode")) { newUrl.removeQueryItem("mode"); } #endif return (openDatabase(newUrl)); // on error, any message will have been displayed } IMyMoneyStorage *storage = new MyMoneySeqAccessMgr; if (url.isLocalFile()) { filename = url.toLocalFile(); } else { // TODO: port to kf5 #if 0 if (!KIO::NetAccess::download(url, filename, 0)) { KMessageBox::detailedError(this, i18n("Error while loading file '%1'.", url.url()), KIO::NetAccess::lastErrorString(), i18n("File access error")); return false; } #endif } // 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("

%1 is not a KMyMoney file.

", filename); KMessageBox::error(this, msg, i18n("Filetype Error")); return false; } m_fmode = 0600; m_fmode |= info.permission(QFile::ReadGroup) ? 040 : 0; m_fmode |= info.permission(QFile::WriteGroup) ? 020 : 0; m_fmode |= info.permission(QFile::ReadOther) ? 004 : 0; m_fmode |= info.permission(QFile::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 // read() and the other does not provide an at(n) method but // supports read() 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); if (file.open(QIODevice::ReadOnly)) { QByteArray hdr(2, '\0'); int cnt; cnt = file.read(hdr.data(), 2); file.close(); if (cnt == 2) { if (QString(hdr) == QString("\037\213")) { // gzipped? qfile = new KCompressionDevice(filename, COMPRESSION_TYPE); } else if (QString(hdr) == QString("--") // PGP ASCII armored? || QString(hdr) == QString("\205\001") // PGP binary? || QString(hdr) == QString("\205\002")) { // PGP binary? if (KGPGFile::GPGAvailable()) { qfile = new KGPGFile(filename); haveAt = false; isEncrypted = true; } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("GPG is not available for decryption of file %1", filename))); qfile = new QFile(file.fileName()); } } else { // we can't use file directly, as we delete qfile later on qfile = new QFile(file.fileName()); } if (qfile->open(QIODevice::ReadOnly)) { try { hdr.resize(8); if (qfile->read(hdr.data(), 8) == 8) { if (haveAt) qfile->seek(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 qint32 magic0, magic1; QDataStream s(&hdr, QIODevice::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. '?read(hdr.data(), 70) == 70) { if (haveAt) qfile->seek(0); else ungetString(qfile, hdr.data(), 70); QRegExp kmyexp(""); QRegExp gncexp("attachStorage(storage); loadAllCurrencies(); // currency list required for gnc MyMoneyFile::instance()->detachStorage(storage); pReader = new MyMoneyGncReader; m_fileType = GncXML; } } } if (pReader) { pReader->setProgressCallback(&KMyMoneyView::progressCallback); pReader->readFile(qfile, dynamic_cast(storage)); } else { if (m_fileType == KmmBinary) { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 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.", filename))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 contains an unknown file format.", filename))); } rc = false; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot read from file %1.", filename))); rc = false; } } catch (const MyMoneyException &e) { KMessageBox::sorry(this, QString("%1"). arg(i18n("Cannot load file %1. Reason: %2", filename, e.what()))); rc = false; } if (pReader) { pReader->setProgressCallback(0); delete pReader; } qfile->close(); } else { KGPGFile *gpgFile = qobject_cast(qfile); if (gpgFile && !gpgFile->errorToString().isEmpty()) { KMessageBox::sorry(this, QString("%1"). arg(i18n("The following error was encountered while decrypting file %1: %2", filename, gpgFile->errorToString()))); } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); } rc = false; } delete qfile; } } else { KMessageBox::sorry(this, QString("%1"). arg(i18n("File %1 not found.", filename))); rc = false; } // things are finished, now we connect the storage to the engine // which forces a reload of the cache in the engine with those // objects that are cached MyMoneyFile::instance()->attachStorage(storage); if (rc == false) return rc; // encapsulate transactions to the engine to be able to commit/rollback MyMoneyFileTransaction ft; // make sure we setup the encryption key correctly 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 (const MyMoneyException &) { } // 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 // TODO: port to kf5 //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 QUrl &url) { m_fileOpen = false; // open the database IMyMoneySerialize* pStorage = dynamic_cast(MyMoneyFile::instance()->storage()); MyMoneyDatabaseMgr* pDBMgr = 0; if (! pStorage) { pDBMgr = new MyMoneyDatabaseMgr; pStorage = dynamic_cast(pDBMgr); } QExplicitlySharedDataPointer reader = pStorage->connectToDatabase(url); QUrl dbURL(url); bool retry = true; while (retry) { switch (reader->open(dbURL, QIODevice::ReadWrite)) { case 0: // opened okay retry = false; break; case 1: // permanent error KMessageBox::detailedError(this, i18n("Cannot open database %1\n", dbURL.toDisplayString()), 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 { // TODO: port to kf5 #if 0 QString options = QUrlQuery(dbURL).queryItemValue("options") + ",override"; dbURL.removeQueryItem("mode"); // now redundant dbURL.removeQueryItem("options"); dbURL.addQueryItem("options", options); #endif } } } 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 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().toLatin1(), i18n("Database malfunction")); return false; } m_fileOpen = true; reader->setProgressCallback(0); 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 { updateCurrencyNames(); ft.commit(); } catch (const MyMoneyException &) { MyMoneyFile::instance()->blockSignals(blocked); return false; } // make sure, we have a base currency and all accounts are // also assigned to a currency. QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseId.isEmpty()) { // Stay in this endless loop until we have a base currency, // as without it the application does not work anymore. while (baseId.isEmpty()) { selectBaseCurrency(); try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } } } 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 // // // // and restart the application with this file. This will force to // run the above loop. selectBaseCurrency(); } KSharedConfigPtr config = KSharedConfig::openConfig(); KPageWidgetItem* page; KConfigGroup grp = config->group("General Options"); if (KMyMoneyGlobalSettings::startLastViewSelected() != 0) { page = m_model->item(m_model->index(KMyMoneyGlobalSettings::lastViewSelected(), 0)); } else { page = m_homeViewFrame; } // For debugging purposes, we can turn off the automatic fix manually // by setting the entry in kmymoneyrc to true grp = config->group("General Options"); if (grp.readEntry("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", qPrintable((QString("testing fileFixVersion %1 < %2").arg(s->fileFixVersion()).arg(s->currentFixVersion())))); 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; case 3: fixFile_3(); s->setFileFixVersion(4); break; // add new levels above. Don't forget to increase currentFixVersion() for all // the storage backends this fix applies to default: throw MYMONEYEXCEPTION(i18n("Unknown fix level in input file")); } } ft.commit(); } catch (const MyMoneyException &) { 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 // kmymoney->createInitialAccount(); m_fileOpen = true; emit kmmFilePlugin(postOpen); Models::instance()->fileOpened(); // connect the needed signals connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->accountsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->accountsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->accountsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(objectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectAdded(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const)), Models::instance()->institutionsModel(), SLOT(slotObjectModified(MyMoneyFile::notificationObjectT,MyMoneyObject*const))); connect(MyMoneyFile::instance(), SIGNAL(objectRemoved(MyMoneyFile::notificationObjectT,QString)), Models::instance()->institutionsModel(), SLOT(slotObjectRemoved(MyMoneyFile::notificationObjectT,QString))); connect(MyMoneyFile::instance(), SIGNAL(balanceChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); connect(MyMoneyFile::instance(), SIGNAL(valueChanged(MyMoneyAccount)), Models::instance()->institutionsModel(), SLOT(slotBalanceOrValueChanged(MyMoneyAccount))); // inform everyone about new data MyMoneyFile::instance()->preloadCache(); MyMoneyFile::instance()->forceDataChanged(); // views can wait since they are going to be refresed in slotRefreshViews connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), m_homeView, SLOT(slotLoadView())); // if we currently see a different page, then select the right one if (m_model->index(page).row() != KPageView::currentPage().row()) { showPage(page); } emit fileOpened(); return true; } void KMyMoneyView::saveToLocalFile(const QString& localFile, IMyMoneyStorageFormat* pWriter, bool plaintext, const QString& keyList) { // Check GPG encryption bool encryptFile = 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")); encryptFile = false; } else { if (KMyMoneyGlobalSettings::encryptRecover()) { encryptRecover = true; if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) { KMessageBox::sorry(this, i18n("

You have selected to encrypt your data also with the KMyMoney recover key, but the key with id

%1

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 KMyMoney web-site. This time your data will not be encrypted with the KMyMoney recover key.

", QString(recoveryKeyId)), i18n("GPG Key not found")); encryptRecover = false; } } for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { if (!KGPGFile::keyAvailable(key)) { KMessageBox::sorry(this, i18n("

You have specified to encrypt your data for the user-id

%1.

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.

", key), i18n("GPG Key not found")); encryptFile = false; break; } } if (encryptFile == true) { QString msg = i18n("

You have configured to save your data in encrypted form using GPG. Make sure you understand that you might lose all your data if you encrypt it, but cannot decrypt it later on. If unsure, answer No.

"); if (KMessageBox::questionYesNo(this, msg, i18n("Store GPG encrypted"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "StoreEncrypted") == KMessageBox::No) { encryptFile = false; } } } } // Create a temporary file if needed QString writeFile = localFile; QTemporaryFile tmpFile; if (QFile::exists(localFile)) { tmpFile.open(); writeFile = tmpFile.fileName(); tmpFile.close(); } /** * @brief Automatically restore settings when scope is left */ struct restorePreviousSettingsHelper { restorePreviousSettingsHelper(mode_t mode) : m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()}, m_oldMask{umask((~mode) & 0777u)} { MyMoneyFile::instance()->blockSignals(true); } ~restorePreviousSettingsHelper() { MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked); umask(m_oldMask); } const bool m_signalsWereBlocked; const mode_t m_oldMask; } restoreHelper{m_fmode}; MyMoneyFileTransaction ft; MyMoneyFile::instance()->deletePair("kmm-encryption-key"); std::unique_ptr device; if (!keyList.isEmpty() && encryptFile && !plaintext) { std::unique_ptr kgpg = std::unique_ptr(new KGPGFile{writeFile}); if (kgpg) { for(const QString& key: keyList.split(',', QString::SkipEmptyParts)) { kgpg->addRecipient(key.toLatin1()); } if (encryptRecover) { kgpg->addRecipient(recoveryKeyId); } MyMoneyFile::instance()->setValue("kmm-encryption-key", keyList); device = std::unique_ptr(kgpg.release()); } } else { QFile *file = new QFile(writeFile); // The second parameter of KCompressionDevice means that KCompressionDevice will delete the QFile object device = std::unique_ptr(new KCompressionDevice{file, true, (plaintext) ? KCompressionDevice::None : COMPRESSION_TYPE}); } ft.commit(); if (!device || !device->open(QIODevice::WriteOnly)) { throw MYMONEYEXCEPTION(i18n("Unable to open file '%1' for writing.", localFile)); } pWriter->setProgressCallback(&KMyMoneyView::progressCallback); pWriter->writeFile(device.get(), dynamic_cast(MyMoneyFile::instance()->storage())); device->close(); // Check for errors if possible, only possible for KGPGFile QFileDevice *fileDevice = qobject_cast(device.get()); if (fileDevice && fileDevice->error() != QFileDevice::NoError) { throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } if (writeFile != localFile) { // This simple comparison is possible because the strings are equal if no temporary file was created. // If a temporary file was created, it is made in a way that the name is definitely different. So no // symlinks etc. have to be evaluated. if (!QFile::remove(localFile) || !QFile::rename(writeFile, localFile)) throw MYMONEYEXCEPTION(i18n("Failure while writing to '%1'", localFile)); } pWriter->setProgressCallback(0); } bool KMyMoneyView::saveFile(const QUrl &url, const QString& keyList) { QString filename = url.path(); if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return false; } emit kmmFilePlugin(preSave); std::unique_ptr storageWriter; // If this file ends in ".ANON.XML" then this should be written using the // anonymous writer. bool plaintext = filename.right(4).toLower() == ".xml"; if (filename.right(9).toLower() == ".anon.xml") { //! @todo C++14: use std::make_unique, also some lines below storageWriter = std::unique_ptr(new MyMoneyStorageANON); } else { storageWriter = std::unique_ptr(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 MYMONEYEXCEPTION(i18n("Malformed URL '%1'", url.url())); } if (url.isLocalFile()) { filename = url.toLocalFile(); try { const unsigned int nbak = KMyMoneyGlobalSettings::autoBackupCopies(); if (nbak) { KBackup::numberedBackupFile(filename, QString(), QString::fromLatin1("~"), nbak); } saveToLocalFile(filename, storageWriter.get(), plaintext, keyList); } catch (const MyMoneyException &) { throw MYMONEYEXCEPTION(i18n("Unable to write changes to '%1'", filename)); } } else { QTemporaryFile tmpfile; tmpfile.open(); // to obtain the name tmpfile.close(); saveToLocalFile(tmpfile.fileName(), storageWriter.get(), plaintext, keyList); // TODO: port to kf5 //if (!KIO::NetAccess::upload(tmpfile.fileName(), url, 0)) // throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'", url.toDisplayString())); } m_fileType = KmmXML; } catch (const MyMoneyException &e) { KMessageBox::error(this, e.what()); MyMoneyFile::instance()->setDirty(); rc = false; } emit kmmFilePlugin(postSave); return rc; } bool KMyMoneyView::saveAsDatabase(const QUrl &url) { bool rc = false; if (!fileOpen()) { KMessageBox::error(this, i18n("Tried to access a file when it has not been opened")); return (rc); } MyMoneyStorageSql *writer = new MyMoneyStorageSql(dynamic_cast(MyMoneyFile::instance()->storage()), url); bool canWrite = false; switch (writer->open(url, QIODevice::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 Save As.\n" "Do you wish to continue?"), "Database not empty") == KMessageBox::Continue) { if (writer->open(url, QIODevice::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().toLatin1(), i18n("Database malfunction")); rc = false; } writer->setProgressCallback(0); rc = true; } else { KMessageBox::detailedError(this, i18n("Cannot open or create database %1.\n" "Retry Save As Database and click Help" " for further info.", url.toDisplayString()), writer->lastError()); } delete writer; return (rc); } bool KMyMoneyView::dirty() { 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(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() { closeFile(); m_fileType = KmmXML; // assume native type until saved m_fileOpen = true; } void KMyMoneyView::slotSetBaseCurrency(const MyMoneySecurity& baseCurrency) { if (!baseCurrency.id().isEmpty()) { QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseCurrency.id() != baseId) { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->setBaseCurrency(baseCurrency); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::sorry(this, i18n("Cannot set %1 as base currency: %2", baseCurrency.name(), e.what()), i18n("Set base currency")); } } } } void KMyMoneyView::selectBaseCurrency() { MyMoneyFile* file = MyMoneyFile::instance(); // check if we have a base currency. If not, we need to select one QString baseId; try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (baseId.isEmpty()) { QPointer dlg = new KCurrencyEditDlg(this); connect(dlg, SIGNAL(selectBaseCurrency(MyMoneySecurity)), this, SLOT(slotSetBaseCurrency(MyMoneySecurity))); dlg->exec(); delete dlg; } try { baseId = MyMoneyFile::instance()->baseCurrency().id(); } catch (const MyMoneyException &e) { qDebug("%s", qPrintable(e.what())); } if (!baseId.isEmpty()) { // check that all accounts have a currency QList list; file->accountList(list); QList::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) { QString cid; try { if (!(*it).currencyId().isEmpty() || (*it).currencyId().length() != 0) cid = MyMoneyFile::instance()->currency((*it).currencyId()).id(); } catch (const MyMoneyException &) { } if (cid.isEmpty()) { (*it).setCurrencyId(baseId); MyMoneyFileTransaction ft; try { file->modifyAccount(*it); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to setup base currency in account %s (%s): %s", qPrintable((*it).name()), qPrintable((*it).id()), qPrintable(e.what())); } } } } } void KMyMoneyView::updateCurrencyNames() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; QList storedCurrencies = MyMoneyFile::instance()->currencyList(); QList availableCurrencies = MyMoneyFile::instance()->availableCurrencyList(); QStringList currencyIDs; foreach (auto currency, availableCurrencies) currencyIDs.append(currency.id()); try { foreach (auto currency, storedCurrencies) { int i = currencyIDs.indexOf(currency.id()); if (i != -1 && availableCurrencies.at(i).name() != currency.name()) { currency.setName(availableCurrencies.at(i).name()); file->modifyCurrency(currency); } } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s updating currency names", qPrintable(e.what())); } } void KMyMoneyView::loadAllCurrencies() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; if (!file->currencyList().isEmpty()) return; QMap ancientCurrencies = file->ancientCurrencies(); try { foreach (auto currency, file->availableCurrencyList()) { file->addCurrency(currency); MyMoneyPrice price = ancientCurrencies.value(currency, MyMoneyPrice()); if (price != MyMoneyPrice()) file->addPrice(price); } ft.commit(); } catch (const MyMoneyException &e) { qDebug("Error %s loading currency", qPrintable(e.what())); } } void KMyMoneyView::viewAccountList(const QString& /*selectAccount*/) { if (m_accountsViewFrame != currentPage()) showPage(m_accountsViewFrame); m_accountsView->show(); } void KMyMoneyView::slotRefreshViews() { // turn off sync between ledger and investment view disconnect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject))); disconnect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject))); // TODO turn sync between ledger and investment view if selected by user if (KMyMoneyGlobalSettings::syncLedgerInvestment()) { connect(m_investmentView, SIGNAL(accountSelected(MyMoneyObject)), m_ledgerView, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_ledgerView, SIGNAL(accountSelected(MyMoneyObject)), m_investmentView, SLOT(slotSelectAccount(MyMoneyObject))); } showTitleBar(KMyMoneyGlobalSettings::showTitleBar()); m_accountsView->slotLoadAccounts(); m_institutionsView->slotLoadAccounts(); m_categoriesView->slotLoadAccounts(); m_payeesView->slotLoadPayees(); m_tagsView->slotLoadTags(); 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) { kmymoney->progressCallback(current, total, msg); } void KMyMoneyView::slotCurrentPageChanged(const QModelIndex current, const QModelIndex) { // remember the current page m_lastViewSelected = current.row(); // set the current page's title in the header if (m_header) m_header->setText(m_model->data(current).toString()); } /* 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_3() { // make sure each storage object contains a (unique) id MyMoneyFile::instance()->storageId(); } void KMyMoneyView::fixFile_2() { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList 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. QList::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 QList& splits = (*it_t).splits(); QList::const_iterator it_s; for (it_s = splits.constBegin(); it_s != splits.constEnd(); ++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() { // 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 { QList reports = MyMoneyFile::instance()->reportList(); QList::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.constBegin(); it_a != list.constEnd(); ++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 (const MyMoneyException &) { } } } #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() { /* (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(); QList accountList; file->accountList(accountList); QList::Iterator it_a; QList scheduleList = file->scheduleList(); QList::Iterator it_s; MyMoneyAccount equity = file->equity(); MyMoneyAccount asset = file->asset(); bool equityListEmpty = equity.accountList().count() == 0; 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); qDebug() << Q_FUNC_INFO << " fixed account " << acc.id() << " reparented to " << equity.id(); } } } for (it_s = scheduleList.begin(); it_s != scheduleList.end(); ++it_s) { fixSchedule_0(*it_s); } fixTransactions_0(); } void KMyMoneyView::fixSchedule_0(MyMoneySchedule sched) { MyMoneyTransaction t = sched.transaction(); QList splitList = t.splits(); QList::ConstIterator it_s; bool updated = false; try { // Check if the splits contain valid data and set it to // be valid. for (it_s = splitList.constBegin(); it_s != splitList.constEnd(); ++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.constBegin() && t.commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << t.id() << " has no commodity"; try { MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId()); t.setCommodity(acc.currencyId()); updated = true; } catch (const MyMoneyException &) { } } // make sure the account exists. If not, remove the split try { MyMoneyFile::instance()->account((*it_s).accountId()); } catch (const MyMoneyException &) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " removed, because account '" << (*it_s).accountId() << "' does not exist."; t.removeSplit(*it_s); updated = true; } if ((*it_s).reconcileFlag() != MyMoneySplit::NotReconciled) { qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'"; 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 (const MyMoneyException &e) { qWarning("Unable to update broken schedule: %s", qPrintable(e.what())); } } 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 KMyMoney version 0.8.7 or later and earlier than version 0.9 to correct the problem.

" , acc.name()), i18n("Account problem")); throw 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 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 .... QList::ConstIterator it_s; for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++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 (const MyMoneyException &e) { KMessageBox::information(this, i18n("Unable to add schedule: %1", e.what())); } } } void KMyMoneyView::fixTransactions_0() { MyMoneyFile* file = MyMoneyFile::instance(); QList scheduleList = file->scheduleList(); MyMoneyTransactionFilter filter; filter.setReportAllSplits(false); QList transactionList; file->transactionList(transactionList, filter); QList::Iterator it_x; QStringList interestAccounts; KMSTATUS(i18n("Fix transactions")); kmymoney->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(); QList::ConstIterator it_s; QStringList accounts; bool hasDuplicateAccounts = false; for (it_s = t.splits().constBegin(); it_s != t.splits().constEnd(); ++it_s) { if (accounts.contains((*it_s).accountId())) { hasDuplicateAccounts = true; qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << (*it_s).accountId(); } 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)) kmymoney->slotStatusProgressBar(cnt); } // scan the transactions and modify loan transactions QList::Iterator it_t; for (it_t = transactionList.begin(); it_t != transactionList.end(); ++it_t) { const char *defaultAction = 0; QList splits = (*it_t).splits(); QList::Iterator it_s; QStringList accounts; // check if base commodity is set. if not, set baseCurrency if ((*it_t).commodity().isEmpty()) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " has no base currency"; (*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 // 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())) { 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) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " contains an interest account (" << (*it_s).accountId() << ") but does not have ActionInterest"; (*it_s).setAction(MyMoneySplit::ActionInterest); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); qDebug("Fixed interest action in %s", qPrintable((*it_t).id())); } // if it does not reference an interest account, it must not be // of type ActionInterest } else { if ((*it_s).action() == MyMoneySplit::ActionInterest) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " does not contain an interest account so it should not have ActionInterest"; (*it_s).setAction(defaultAction); (*it_t).modifySplit(*it_s); file->modifyTransaction(*it_t); qDebug("Fixed interest action in %s", qPrintable((*it_t).id())); } } // 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()) { qDebug() << Q_FUNC_INFO << " " << (*it_t).id() << " " << (*it_s).id() << " uses the transaction currency, but shares != value"; (*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", qPrintable((*it_t).id()), qPrintable((*it_s).id())); (*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 (const MyMoneyException &) { qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId())); } } } ++cnt; if (!(cnt % 10)) kmymoney->slotStatusProgressBar(cnt); } kmymoney->slotStatusProgressBar(-1, -1); } void KMyMoneyView::fixDuplicateAccounts_0(MyMoneyTransaction& t) { qDebug("Duplicate account in transaction %s", qPrintable(t.id())); } void KMyMoneyView::slotPrintView() { if (m_reportsViewFrame == currentPage()) m_reportsView->slotPrintView(); else if (m_homeViewFrame == currentPage()) m_homeView->slotPrintView(); } KMyMoneyViewBase* KMyMoneyView::addBasePage(const QString& title, const QString& icon) { KMyMoneyViewBase* viewBase = new KMyMoneyViewBase(this, title, title); connect(viewBase, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); KPageWidgetItem* frm = m_model->addPage(viewBase, title); frm->setIcon(QIcon::fromTheme(icon)); return viewBase; } /* ------------------------------------------------------------------------ */ /* KMyMoneyViewBase */ /* ------------------------------------------------------------------------ */ // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes class KMyMoneyViewBase::Private { public: QVBoxLayout* m_viewLayout; }; KMyMoneyViewBase::KMyMoneyViewBase(QWidget* parent, const QString& name, const QString& title) : QWidget(parent), d(new Private) { setAccessibleName(name); setAccessibleDescription(title); d->m_viewLayout = new QVBoxLayout(this); d->m_viewLayout->setSpacing(6); d->m_viewLayout->setMargin(0); } KMyMoneyViewBase::~KMyMoneyViewBase() { delete d; } void KMyMoneyViewBase::addWidget(QWidget* w) { d->m_viewLayout->addWidget(w); } QVBoxLayout* KMyMoneyViewBase::layout() const { return d->m_viewLayout; } diff --git a/kmymoney/views/kmymoneyview.h b/kmymoney/views/kmymoneyview.h index 52d5bba2b..7de80e198 100644 --- a/kmymoney/views/kmymoneyview.h +++ b/kmymoney/views/kmymoneyview.h @@ -1,671 +1,672 @@ /*************************************************************************** kmymoneyview.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes email : ***************************************************************************/ /*************************************************************************** * * * 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 KMYMONEYVIEW_H #define KMYMONEYVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes -#include -#include -#include +#include "config-kmymoney.h" +#include "mymoneyaccount.h" +#include "mymoneyinstitution.h" +#include "mymoneytransaction.h" #include "mymoneyschedule.h" -#include -#include +#include "mymoneysecurity.h" +#include "selectedtransaction.h" #ifdef KF5Activities_FOUND namespace KActivities { class ResourceInstance; } #endif class KHomeView; class KAccountsView; class KCategoriesView; class KInstitutionsView; class KPayeesView; class KTagsView; class KBudgetView; class KScheduledView; class KGlobalLedgerView; class IMyMoneyStorageFormat; class MyMoneyTransaction; class KInvestmentView; class KReportsView; class KMyMoneyViewBase; class MyMoneyReport; class TransactionEditor; class KForecastView; class KOnlineJobOutbox; class KMyMoneyTitleLabel; /** * 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 KPageWidget { Q_OBJECT public: enum viewID { HomeView = 0, AccountsView, InstitutionsView, SchedulesView, CategoriesView, PayeesView, LedgersView, InvestmentsView, ReportsView, BudgetView, ForecastView, OnlineJobOutboxView }; // file actions for plugin enum fileActions { preOpen, postOpen, preSave, postSave, preClose, postClose }; KOnlineJobOutbox* getOnlineJobOutbox() const { return m_onlineJobOutboxView; } private: enum menuID { AccountNew = 1, AccountOpen, AccountReconcile, AccountEdit, AccountDelete, AccountOnlineMap, AccountOnlineUpdate, AccountOfxConnect, CategoryNew }; enum storageTypeE { Memory = 0, Database } _storageType; KPageWidgetModel* m_model; KHomeView *m_homeView; KAccountsView *m_accountsView; KInstitutionsView *m_institutionsView; KCategoriesView *m_categoriesView; KPayeesView *m_payeesView; KTagsView *m_tagsView; KBudgetView *m_budgetView; KScheduledView *m_scheduledView; KGlobalLedgerView *m_ledgerView; KInvestmentView *m_investmentView; KReportsView* m_reportsView; KForecastView* m_forecastView; KOnlineJobOutbox* m_onlineJobOutboxView; KPageWidgetItem* m_homeViewFrame; KPageWidgetItem* m_accountsViewFrame; KPageWidgetItem* m_institutionsViewFrame; KPageWidgetItem* m_categoriesViewFrame; KPageWidgetItem* m_payeesViewFrame; KPageWidgetItem* m_tagsViewFrame; KPageWidgetItem* m_budgetViewFrame; KPageWidgetItem* m_scheduleViewFrame; KPageWidgetItem* m_ledgerViewFrame; KPageWidgetItem* m_investmentViewFrame; KPageWidgetItem* m_reportsViewFrame; KPageWidgetItem* m_forecastViewFrame; KPageWidgetItem* m_onlineJobOutboxViewFrame; KMyMoneyTitleLabel* m_header; bool m_inConstructor; bool m_fileOpen; mode_t m_fmode; int m_lastViewSelected; // 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; #ifdef KF5Activities_FOUND private: KActivities::ResourceInstance * m_activityResourceInstance; #endif private: void ungetString(QIODevice *qfile, char * buf, int len); /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency(); /** * 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 viewAccountList(const QString& selectAccount); // Show the accounts view static void progressCallback(int current, int total, const QString&); /** */ void fixFile_0(); void fixFile_1(); void fixFile_2(); void fixFile_3(); /** */ void fixLoanAccount_0(MyMoneyAccount acc); /** */ void fixTransactions_0(); 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. */ explicit KMyMoneyView(QWidget *parent = 0); /** * Destructor */ ~KMyMoneyView(); /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen(); /** * Closes the open MyMoneyFile and frees all the allocated memory, I hope ! */ void closeFile(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ bool readFile(const QUrl &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 QUrl &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 QUrl &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 QUrl &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(); /** * Close the currently opened file and create an empty new file. * * @see MyMoneyFile */ void newFile(); /** * This method enables the state of all views (except home view) according * to an open file. */ void enableViewsIfFileOpen(); KMyMoneyViewBase* addBasePage(const QString& title, const QString& icon = QString()); void addWidget(QWidget* w); void showPage(KPageWidgetItem* pageItem); /** * 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(); TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions&); bool createNewTransaction(); /** * 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 updates names of currencies from file to localized names */ void updateCurrencyNames(); /** * This method loads all known currencies and saves them to the storage */ void loadAllCurrencies(); void showTitleBar(bool show); /** * This method changes the view type according to the settings. */ void updateViewType(); protected: /** * Overwritten because KMyMoney has it's custom header. */ virtual bool showPageHeader() const; public slots: /** * This slot writes information about the page passed as argument @a current * in the kmymoney.rc file so that it can be selected automatically when * the application is started again. * * @param current QModelIndex of the current page item * @param previous QModelIndex of the previous page item */ void slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous); /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes 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 tags view should pop up and a specific * transaction in an account should be shown. * * @param tagId The ID of the tag to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotTagSelected(const QString& tagId, 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(); /** * This slot switches the view to present the home page */ void slotShowHomePage() { setCurrentPage(m_homeViewFrame); } 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 KMyMoneyApp::slotCurrencySetBase(). * 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-QUrl representation of database * * @retval true Database opened successfully * @retval false Could not open or read database */ bool openDatabase(const QUrl &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 localFile the name of the local 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(const QString& localFile, 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); /** * This signal is emitted whenever a new view is about to be selected. */ void aboutToChangeView(); 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 signal is emitted after a data source has been closed */ void fileClosed(); /** * This signal is emitted after a data source has been opened */ void fileOpened(); }; /** * 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 QString& name, const QString& title); virtual ~KMyMoneyViewBase(); void setTitle(const QString& title); QVBoxLayout* layout() 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 QList& transactions) const { Q_UNUSED(transactions) return false; } signals: /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; #endif diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp index 96a4fa072..ddc4bd2b2 100644 --- a/kmymoney/views/kpayeesview.cpp +++ b/kmymoney/views/kpayeesview.cpp @@ -1,970 +1,971 @@ /*************************************************************************** kpayeesview.cpp --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai ***************************************************************************/ /*************************************************************************** * * * 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 "kpayeesview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes +#include "config-kmymoney.h" #include "mymoneyfile.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include "mymoneysecurity.h" // *** KPayeeListItem Implementation *** KPayeeListItem::KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) : QListWidgetItem(parent, QListWidgetItem::UserType), m_payee(payee) { setText(payee.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } KPayeeListItem::~KPayeeListItem() { } // *** KPayeesView Implementation *** KPayeesView::KPayeesView(QWidget *parent) : QWidget(parent), m_contact(new MyMoneyContact(this)), m_needReload(false), m_inSelection(false), m_allowEditing(true), m_payeeFilterType(0) { setupUi(this); m_filterProxyModel = new AccountNamesFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->sort(0); comboDefaultCategory->setModel(m_filterProxyModel); matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), MyMoneyPayee::matchDisabled); matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), MyMoneyPayee::matchName); matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), MyMoneyPayee::matchNameExact); matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), MyMoneyPayee::matchKey); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(this, m_payeesList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu); m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type m_filterBox->addItem(i18nc("@item Show all payees", "All")); m_filterBox->addItem(i18nc("@item Show only used payees", "Used")); m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused")); m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); KGuiItem newButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("Creates a new payee"), i18n("Use this to create a new payee.")); KGuiItem::assign(m_newButton, newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("user-properties"), QIcon::fromTheme(QStringLiteral("text-editor"))), i18n("Rename the current selected payee"), i18n("Use this to start renaming the selected payee.")); KGuiItem::assign(m_renameButton, renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("Delete selected payee(s)"), i18n("Use this to delete the selected payee. You can also select " "multiple payees to be deleted.")); KGuiItem::assign(m_deleteButton, deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem mergeButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("merge")), i18n("Merge multiple selected payees"), i18n("Use this to merge multiple selected payees.")); KGuiItem::assign(m_mergeButton, mergeButtonItem); m_mergeButton->setToolTip(mergeButtonItem.toolTip()); KGuiItem updateButtonItem(i18nc("Update payee", "Update"), QIcon::fromTheme(QStringLiteral("dialog-ok"), QIcon::fromTheme(QStringLiteral("finish"))), i18n("Accepts the entered data and stores it"), i18n("Use this to accept the modified data.")); KGuiItem::assign(m_updateButton, updateButtonItem); KGuiItem syncButtonItem(i18nc("Sync payee", "Sync"), QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Fetches the payee's data from your addressbook."), i18n("Use this to fetch payee's data.")); KGuiItem::assign(m_syncAddressbook, syncButtonItem); KGuiItem sendMailButtonItem(i18nc("Send mail", "Send"), QIcon::fromTheme(QStringLiteral("mail-message"), QIcon::fromTheme(QStringLiteral("internet-mail"))), i18n("Creates new e-mail to your payee."), i18n("Use this to create new e-mail to your payee.")); KGuiItem::assign(m_sendMail, sendMailButtonItem); m_updateButton->setEnabled(false); m_syncAddressbook->setEnabled(false); #ifndef KMM_ADDRESSBOOK_FOUND m_syncAddressbook->hide(); #endif matchTypeCombo->setCurrentIndex(0); checkMatchIgnoreCase->setEnabled(false); checkEnableDefaultCategory->setChecked(false); labelDefaultCategory->setEnabled(false); comboDefaultCategory->setEnabled(false); QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_register->setupRegister(MyMoneyAccount(), cols); m_register->setSelectionMode(QTableWidget::SingleSelection); m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst); m_balanceLabel->hide(); connect(m_contact, SIGNAL(contactFetched(ContactData)), this, SLOT(slotContactFetched(ContactData))); connect(m_payeesList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectPayee(QListWidgetItem*,QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectPayee())); connect(m_payeesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenamePayee(QListWidgetItem*))); connect(m_payeesList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked())); connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("payee_delete"), SLOT(trigger())); connect(m_mergeButton, SIGNAL(clicked()), kmymoney->action("payee_merge"), SLOT(trigger())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotPayeeNew())); connect(addressEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(postcodeEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(telephoneEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(emailEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(notesEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(matchKeyEditList, SIGNAL(changed()), this, SLOT(slotKeyListChanged())); connect(matchTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeDataChanged())); connect(checkMatchIgnoreCase, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(checkEnableDefaultCategory, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(comboDefaultCategory, SIGNAL(accountSelected(QString)), this, SLOT(slotPayeeDataChanged())); connect(buttonSuggestACategory, SIGNAL(clicked()), this, SLOT(slotChooseDefaultAccount())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdatePayee())); connect(m_syncAddressbook, SIGNAL(clicked()), this, SLOT(slotSyncAddressBook())); connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_sendMail, SIGNAL(clicked()), this, SLOT(slotSendMail())); connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadPayees())); connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int))); connect(payeeIdentifiers, SIGNAL(dataChanged()), this, SLOT(slotPayeeDataChanged())); // use the size settings of the last run (if any) KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); //At start we haven't any payee selected m_tabWidget->setEnabled(false); // disable tab widget m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee clearItemData(); } KPayeesView::~KPayeesView() { // remember the splitter settings for startup KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KPayeesViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KPayeesView::slotChooseDefaultAccount() { MyMoneyFile* file = MyMoneyFile::instance(); QMap account_count; KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); while (item) { //only walk through selectable items. eg. transactions and not group markers if (item->isSelectable()) { KMyMoneyRegister::Transaction* t = dynamic_cast(item); MyMoneySplit s = t->transaction().splitByPayee(m_payee.id()); const MyMoneyAccount& acc = file->account(s.accountId()); QString txt; if (s.action() != MyMoneySplit::ActionAmortization && acc.accountType() != MyMoneyAccount::AssetLoan && !file->isTransfer(t->transaction()) && t->transaction().splitCount() == 2) { MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false); if (account_count.contains(s0.accountId())) { account_count[s0.accountId()]++; } else { account_count[s0.accountId()] = 1; } } } item = item->nextItem(); } QMap::Iterator most_frequent, iter; most_frequent = account_count.begin(); for (iter = account_count.begin(); iter != account_count.end(); ++iter) { if (iter.value() > most_frequent.value()) { most_frequent = iter; } } if (most_frequent != account_count.end()) { checkEnableDefaultCategory->setChecked(true); comboDefaultCategory->setSelected(most_frequent.key()); setDirty(); } } void KPayeesView::slotStartRename(QListWidgetItem* item) { m_allowEditing = true; m_payeesList->editItem(item); } void KPayeesView::slotRenameButtonCliked() { if (m_payeesList->currentItem() && m_payeesList->selectedItems().count() == 1) { slotStartRename(m_payeesList->currentItem()); } } // This variant is only called when a single payee is selected and renamed. void KPayeesView::slotRenamePayee(QListWidgetItem* p) { //if there is no current item selected, exit if (m_allowEditing == false || !m_payeesList->currentItem() || p != m_payeesList->currentItem()) return; //qDebug() << "[KPayeesView::slotRenamePayee]"; // create a copy of the new name without appended whitespaces QString new_name = p->text(); 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?", new_name)) != KMessageBox::Yes) { p->setText(m_payee.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } 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 (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { p->setText(new_name); } } void KPayeesView::ensurePayeeVisible(const QString& id) { for (int i = 0; i < m_payeesList->count(); ++i) { KPayeeListItem* p = dynamic_cast(m_payeesList->item(0)); if (p && p->payee().id() == id) { m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(p); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KPayeesView::selectedPayees(QList& payeesList) const { QList selectedItems = m_payeesList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*itemsIt); if (item) payeesList << item->payee(); ++itemsIt; } } void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev) { Q_UNUSED(cur); Q_UNUSED(prev); m_allowEditing = false; } void KPayeesView::slotSelectPayee() { // check if the content of a currently selected payee was modified // and ask to store the data if (isDirty()) { QString question = QString("%1").arg(i18n("Do you want to save the changes for %1?", m_newName)); if (KMessageBox::questionYesNo(this, question, i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdatePayee(); m_inSelection = false; } } // make sure we always clear the selected list when listing again m_selectedPayeesList.clear(); // loop over all payees and count the number of payees, also // obtain last selected payee selectedPayees(m_selectedPayeesList); emit selectObjects(m_selectedPayeesList); if (m_selectedPayeesList.isEmpty()) { m_tabWidget->setEnabled(false); // disable tab widget m_balanceLabel->hide(); m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); clearItemData(); m_payee = MyMoneyPayee(); m_syncAddressbook->setEnabled(false); return; // make sure we don't access an undefined payee } m_deleteButton->setEnabled(true); //re-enable delete button m_syncAddressbook->setEnabled(true); // if we have multiple payees selected, clear and disable the payee information if (m_selectedPayeesList.count() > 1) { m_tabWidget->setEnabled(false); // disable tab widget m_renameButton->setEnabled(false); // disable also the rename button m_mergeButton->setEnabled(true); m_balanceLabel->hide(); clearItemData(); } else { m_mergeButton->setEnabled(false); m_renameButton->setEnabled(true); } // otherwise we have just one selected, enable payee information widget m_tabWidget->setEnabled(true); m_balanceLabel->show(); // 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 = m_selectedPayeesList[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); matchTypeCombo->setCurrentIndex(matchTypeCombo->findData(type)); matchKeyEditList->clear(); matchKeyEditList->insertStringList(keys); checkMatchIgnoreCase->setChecked(ignorecase); checkEnableDefaultCategory->setChecked(m_payee.defaultAccountEnabled()); comboDefaultCategory->setSelected(m_payee.defaultAccountId()); payeeIdentifiers->setSource(m_payee); slotPayeeDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of payee: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); m_register->clear(); m_selectedPayeesList.clear(); m_payee = MyMoneyPayee(); } m_allowEditing = true; } void KPayeesView::clearItemData() { addressEdit->setText(QString()); postcodeEdit->setText(QString()); telephoneEdit->setText(QString()); emailEdit->setText(QString()); notesEdit->setText(QString()); showTransactions(); } void KPayeesView::showTransactions() { MyMoneyMoney balance; MyMoneyFile *file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear the register m_register->clear(); if (m_selectedPayeesList.isEmpty() || !m_tabWidget->isEnabled()) { m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; for (QList::const_iterator it = m_selectedPayeesList.constBegin(); it != m_selectedPayeesList.constEnd(); ++it) filter.addPayee((*it).id()); filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // 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(); m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here m_register->setUpdatesEnabled(true); m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KPayeesView::slotKeyListChanged() { bool rc = false; bool ignorecase = false; QStringList keys; m_payee.matchData(ignorecase, keys); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { rc |= (keys != matchKeyEditList->items()); } setDirty(rc); } void KPayeesView::slotPayeeDataChanged() { 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->toPlainText().isEmpty()) || (!addressEdit->toPlainText().isEmpty() && m_payee.address() != addressEdit->toPlainText())); 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->toPlainText().isEmpty()) || (!notesEdit->toPlainText().isEmpty() && m_payee.notes() != notesEdit->toPlainText())); bool ignorecase = false; QStringList keys; MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); rc |= (static_cast(type) != matchTypeCombo->currentData().toUInt()); checkMatchIgnoreCase->setEnabled(false); matchKeyEditList->setEnabled(false); if (matchTypeCombo->currentData().toUInt() != 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 && matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) checkMatchIgnoreCase->setChecked(true); rc |= (ignorecase != checkMatchIgnoreCase->isChecked()); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { matchKeyEditList->setEnabled(true); rc |= (keys != matchKeyEditList->items()); } } rc |= (checkEnableDefaultCategory->isChecked() != m_payee.defaultAccountEnabled()); if (checkEnableDefaultCategory->isChecked()) { comboDefaultCategory->setEnabled(true); labelDefaultCategory->setEnabled(true); // this is only going to understand the first in the list of selected accounts if (comboDefaultCategory->getSelected().isEmpty()) { rc |= !m_payee.defaultAccountId().isEmpty(); } else { QString temp = comboDefaultCategory->getSelected(); rc |= (temp.isEmpty() != m_payee.defaultAccountId().isEmpty()) || (!m_payee.defaultAccountId().isEmpty() && temp != m_payee.defaultAccountId()); } } else { comboDefaultCategory->setEnabled(false); labelDefaultCategory->setEnabled(false); } rc |= (m_payee.payeeIdentifiers() != payeeIdentifiers->identifiers()); } setDirty(rc); } void KPayeesView::slotUpdatePayee() { if (isDirty()) { MyMoneyFileTransaction ft; setDirty(false); try { m_payee.setName(m_newName); m_payee.setAddress(addressEdit->toPlainText()); m_payee.setPostcode(postcodeEdit->text()); m_payee.setTelephone(telephoneEdit->text()); m_payee.setEmail(emailEdit->text()); m_payee.setNotes(notesEdit->toPlainText()); m_payee.setMatchData(static_cast(matchTypeCombo->currentData().toUInt()), checkMatchIgnoreCase->isChecked(), matchKeyEditList->items()); m_payee.setDefaultAccountId(); m_payee.resetPayeeIdentifiers(payeeIdentifiers->identifiers()); if (checkEnableDefaultCategory->isChecked()) { QString temp; if (!comboDefaultCategory->getSelected().isEmpty()) { temp = comboDefaultCategory->getSelected(); m_payee.setDefaultAccountId(temp); } } MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KPayeesView::slotSyncAddressBook() { if (m_payeeRows.isEmpty()) { // empty list means no syncing is pending... foreach (auto item, m_payeesList->selectedItems()) { m_payeeRows.append(m_payeesList->row(item)); // ...so initialize one } m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update // m_syncAddressbook->setEnabled(false); // disallow concurent syncs } if (m_payeeRows.count() <= m_payeeRow) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { // update ui if something is selected m_payee = item->payee(); addressEdit->setText(m_payee.address()); postcodeEdit->setText(m_payee.postcode()); telephoneEdit->setText(m_payee.telephone()); } m_payeeRows.clear(); // that means end of sync m_payeeRow = 0; return; } KPayeeListItem* item = dynamic_cast(m_payeesList->item(m_payeeRows.at(m_payeeRow))); if (item) m_payee = item->payee(); ++m_payeeRow; m_contact->fetchContact(m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched } void KPayeesView::slotContactFetched(const ContactData &identity) { if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched QString txt; if (!identity.street.isEmpty()) txt.append(identity.street + "\n"); if (!identity.locality.isEmpty()) { txt.append(identity.locality); if (!identity.postalCode.isEmpty()) txt.append(' ' + identity.postalCode + "\n"); else txt.append("\n"); } if (!identity.country.isEmpty()) txt.append(identity.country + "\n"); if (!txt.isEmpty() && m_payee.address().compare(txt) != 0) m_payee.setAddress(txt); if (!identity.postalCode.isEmpty() && m_payee.postcode().compare(identity.postalCode) != 0) m_payee.setPostcode(identity.postalCode); if (!identity.phoneNumber.isEmpty() && m_payee.telephone().compare(identity.phoneNumber) != 0) m_payee.setTelephone(identity.phoneNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } slotSyncAddressBook(); // process next payee } void KPayeesView::slotSendMail() { QRegularExpression re(".+@.+"); if (re.match(m_payee.email()).hasMatch()) QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + m_payee.email(), QUrl::TolerantMode)); } void KPayeesView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { loadPayees(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); QList list; selectedPayees(list); emit selectObjects(list); } void KPayeesView::slotLoadPayees() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotLoadPayees())); else loadPayees(); } else { m_needReload = true; } } void KPayeesView::loadPayees() { if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) isSelected[item->payee().id()] = true; ++payeesIt; } // keep current selected item KPayeeListItem *currentItem = static_cast(m_payeesList->currentItem()); if (currentItem) id = currentItem->payee().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); m_payeesList->clear(); m_register->clear(); currentItem = 0; QListlist = file->payeeList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_payeeFilterType == eAllPayees || (m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) || (m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) { KPayeeListItem* item = new KPayeeListItem(m_payeesList, *it); if (item->payee().id() == id) currentItem = item; if (isSelected[item->payee().id()]) item->setSelected(true); } } m_payeesList->sortItems(); if (currentItem) { m_payeesList->setCurrentItem(currentItem); m_payeesList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); comboDefaultCategory->expandAll(); slotSelectPayee(0, 0); m_allowEditing = true; } void KPayeesView::slotSelectTransaction() { QList list = m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit transactionSelected(t->split().accountId(), t->transaction().id()); } } void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; try { // clear filter m_searchWidget->clear(); m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) item->setSelected(false); ++payeesIt; } // find the payee in the list QListWidgetItem* it; for (int i = 0; i < m_payeesList->count(); ++i) { it = m_payeesList->item(i); KPayeeListItem* item = dynamic_cast(it); if (item && item->payee().id() == payeeId) { m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(it); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the payee selection is updated and transactions are updated accordingly slotSelectPayee(); KMyMoneyRegister::RegisterItem *item = 0; for (int i = 0; i < m_register->rowCount(); ++i) { item = m_register->itemAtRow(i); KMyMoneyRegister::Transaction* t = dynamic_cast(item); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { m_register->selectItem(item); m_register->ensureItemVisible(item); break; } } } // quit out of for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", qPrintable(e.what())); } } void KPayeesView::slotOpenContextMenu(const QPoint& /*p*/) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { slotSelectPayee(); emit openContextMenu(item->payee()); } } void KPayeesView::slotPayeeNew() { kmymoney->action("payee_new")->trigger(); } void KPayeesView::slotHelp() { KHelpClient::invokeHelp("details.payees"); } void KPayeesView::slotChangeFilter(int index) { //update the filter type then reload the payees list m_payeeFilterType = index; loadPayees(); } bool KPayeesView::isDirty() const { return m_updateButton->isEnabled(); } void KPayeesView::setDirty(bool dirty) { m_updateButton->setEnabled(dirty); }