diff --git a/CMakeLists.txt b/CMakeLists.txt
index e330f36d7..e3363930d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,341 +1,343 @@
# The CMake version we require
cmake_minimum_required(VERSION 3.1)
# Setting the name of the main project
project(KMyMoney VERSION "5.0.80" LANGUAGES CXX)
# 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)
list(APPEND CMAKE_AUTOMOC_MACRO_NAMES "K_PLUGIN_FACTORY" "K_PLUGIN_FACTORY_WITH_JSON")
if (POLICY CMP0063)
cmake_policy(SET CMP0063 NEW) # Policy introduced in CMake version 3.3
endif()
if (POLICY CMP0071)
# We do not require the old behaviour. It is only set to old, to prevent accidential use of
# the new behavour. If the new behaviour becomes important, cmake_minimum_required has to be
# set to "3.10".
cmake_policy(SET CMP0071 OLD) # Policy introduced in CMake version 3.10
endif()
######################### General Requirements ##########################
find_package(ECM 5.10 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(CMakeDependentOption)
include(GenerateExportHeader)
include(KMyMoneyMacros)
set (OPT_KF5_COMPONENTS DocTools Holidays Contacts Akonadi IdentityManagement Activities)
find_package(Gpgmepp)
if (Gpgmepp_FOUND)
set(GPG_ENCRYPTION ON)
else()
set(GPG_ENCRYPTION OFF)
list(APPEND OPT_KF5_COMPONENTS Gpgmepp)
endif()
find_package(Qt5 5.6 REQUIRED
COMPONENTS Core DBus Widgets Svg Sql Xml Test PrintSupport
OPTIONAL_COMPONENTS Concurrent QuickWidgets)
find_package(KF5 5.2 REQUIRED
COMPONENTS Archive CoreAddons Config ConfigWidgets I18n Completion KCMUtils ItemModels ItemViews Service Wallet IconThemes XmlGui TextWidgets Notifications KIO
OPTIONAL_COMPONENTS ${OPT_KF5_COMPONENTS}
)
find_package(LibAlkimia5 7.0.0 REQUIRED)
# Recent changes to LibAlkimia should allow us to remove this construct
#if(CMAKE_SYSTEM_NAME MATCHES "Windows")
# include_directories(${GMP_INCLUDE_DIR})
#endif()
find_package(KChart 2.6.0 REQUIRED)
if(KF5Gpgmepp_FOUND)
set(GPG_ENCRYPTION ON)
add_definitions(-DGpgmepp_FOUND)
endif()
add_feature_info("Encryption" GPG_ENCRYPTION "It allows encrypting your financial data.")
add_definitions(-DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 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)
add_feature_info("Doxygen" DOXYGEN_FOUND "Generate API documentation with Doxygen (for devs only).")
# check some include files exists
set(CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE=500 -D_BSD_SOURCE)
include (CheckIncludeFileCXX)
check_include_file_cxx("unistd.h" HAVE_UNISTD_H)
check_include_file_cxx("pwd.h" HAVE_PWD_H)
check_include_file_cxx("windows.h" HAVE_WINDOWS_H)
check_include_file_cxx("lmcons.h" HAVE_LMCONS_H)
check_include_file_cxx("process.h" HAVE_PROCESS_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()
add_feature_info("Address book" KMM_ADDRESSBOOK_FOUND "It allows fetching payee information from KDE PIM system.")
add_feature_info("Holidays" KF5Holidays_FOUND "It allows fetching holidays from KDE PIM system.")
option(ENABLE_FORECASTVIEW "Enable forecast view" ON)
add_feature_info("Forecast view" ENABLE_FORECASTVIEW "It adds possibility to calculate forecasts.")
option(ENABLE_REPORTSVIEW "Enable reports view" ON)
add_feature_info("Reports view" ENABLE_REPORTSVIEW "It adds possibility to display chart and table reports.")
+option(ENABLE_ONLINEJOBOUTBOXVIEW "Enable online job outbox view" ON)
+add_feature_info("Online job outbox view" ENABLE_ONLINEJOBOUTBOXVIEW "It adds outbox for sending online jobs.")
cmake_dependent_option(ENABLE_SQLSTORAGE "Enable SQL storage support." ON
"Qt5Sql_FOUND" OFF)
add_feature_info("SQL Storage" ENABLE_SQLSTORAGE "It allows storing your financial data in SQL database (note: no encryption yet supported, no iban yet supported).")
# check for optional QWebEngine
option(ENABLE_WEBENGINE "Enable QWebEngine" OFF)
if(ENABLE_WEBENGINE)
find_package(Qt5WebEngineWidgets 5.8 REQUIRED)
if(Qt5WebEngineWidgets_VERSION VERSION_GREATER 5.8.99 AND Qt5WebEngineWidgets_VERSION VERSION_LESS 5.9.3)
message(WARNING "QWebEngine version ${Qt5WebEngineWidgets_VERSION} is known to be unstable with KMyMoney")
endif()
else(ENABLE_WEBENGINE)
find_package(KF5WebKit REQUIRED)
endif(ENABLE_WEBENGINE)
# check for optional LibOFX support
find_package(LibOfx)
cmake_dependent_option(ENABLE_OFXIMPORTER "Enable OFX Importer" ON
"LIBOFX_FOUND" OFF)
if(ENABLE_OFXIMPORTER)
if(NOT LIBOFX_HAVE_CLIENTUID)
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(PATH_TO_LIBOFX_HEADER "${LIBOFX_INCLUDE_DIR}/libofx/libofx.h") # Windows doesn't even see the header if it's not full path
else()
set(PATH_TO_LIBOFX_HEADER "libofx/libofx.h")
endif()
unset(LIBOFX_HAVE_CLIENTUID)
unset(LIBOFX_HAVE_CLIENTUID CACHE) #not doing this will prevent updating below check
check_struct_has_member("struct OfxFiLogin" clientuid ${PATH_TO_LIBOFX_HEADER} LIBOFX_HAVE_CLIENTUID LANGUAGE CXX)
endif()
if (LIBOFX_HAVE_CLIENTUID)
set (nice_LIBOFX_HAVE_CLIENTUID "yes")
else()
set (nice_LIBOFX_HAVE_CLIENTUID "no")
endif()
else()
set (nice_LIBOFX_HAVE_CLIENTUID "unknown")
unset(LIBOFX_HAVE_CLIENTUID)
unset(LIBOFX_HAVE_CLIENTUID CACHE)
endif(ENABLE_OFXIMPORTER)
add_feature_info("OFX Importer" ENABLE_OFXIMPORTER "It allows importing OFX files (have client uid version: ${nice_LIBOFX_HAVE_CLIENTUID})" )
# check for optional KBanking support
find_package(AQBANKING 5.6.5)
find_package(gwenhywfar 4.15.3)
find_package(gwengui-cpp)
find_package(gwengui-qt5)
cmake_dependent_option(ENABLE_KBANKING "Enable KBanking plugin" ON
"AQBANKING_FOUND;gwengui-cpp_FOUND;gwengui-qt5_FOUND;Qt5QuickWidgets_FOUND" OFF)
add_feature_info(KBanking ENABLE_KBANKING "Interface for the following online banking protocols: HBCI, EBICS, OFX Direct Connect, Paypal")
# check for optional Weboob support
set(Python_ADDITIONAL_VERSIONS 2.7 2.6)
find_package(PythonInterp 2.6)
find_package(PythonLibs ${PYTHON_VERSION_STRING})
if(PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND)
if(NOT PYTHON_VERSION_MAJOR VERSION_LESS 3)
unset(PYTHONLIBS_FOUND)
unset(PYTHONINTERP_FOUND)
message(WARNING "Python 2 required, but Python 3 found.")
else()
include(FindPythonModule)
find_python_module(weboob REQUIRED)
endif()
endif()
cmake_dependent_option(ENABLE_WEBOOB "Enable Weboob plugin" ON
"PYTHONLIBS_FOUND;PYTHONINTERP_FOUND;PY_WEBOOB;Qt5Concurrent_FOUND" OFF)
add_feature_info(Weboob ENABLE_WEBOOB "Online banking interface using Weboob.")
# check for optional ical support
find_package(Libical)
cmake_dependent_option(ENABLE_LIBICAL "Enable Calendar plugin" ON
"LIBICAL_FOUND" OFF)
add_feature_info(iCalendar ENABLE_LIBICAL "iCalendar integration.")
option(ENABLE_QIFIMPORTER "Enable QIF Importer" ON)
option(ENABLE_QIFEXPORTER "Enable QIF Exporter" ON)
add_feature_info("QIF Importer" ENABLE_QIFIMPORTER "It allows importing QIF files.")
add_feature_info("QIF Exporter" ENABLE_QIFEXPORTER "It allows exporting QIF files.")
option(ENABLE_GNCIMPORTER "Enable GNC Importer" ON)
add_feature_info("GNC Importer" ENABLE_GNCIMPORTER "It allows importing GNUCash files.")
option(ENABLE_CSVIMPORTER "Enable CSV Importer" ON)
option(ENABLE_CSVEXPORTER "Enable CSV Exporter" ON)
add_feature_info("CSV Importer" ENABLE_CSVIMPORTER "It allows importing CSV files.")
add_feature_info("CSV Exporter" ENABLE_CSVEXPORTER "It allows exporting CSV files.")
option(ENABLE_UNFINISHEDFEATURES "For devs only" OFF)
# 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' 'DebugKMM' 'Debugfull' 'Profile'
The default value is: 'RelWithDebInfo'" FORCE)
# 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_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# be more pedantic about common symbols
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-common -Wextra")
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(IS_GNU 1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op")
# TODO: remove multiple definitions of payeeIdentifierLoader::createPayeeIdentifierFromSqlDatabase
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xlinker --allow-multiple-definition")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xlinker --allow-multiple-definition")
endif()
endif()
# DebugKMM, Debugfull, Profile
set(CMAKE_CXX_FLAGS_DEBUGKMM
"-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")
# 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()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /FORCE:Multiple")
endif()
# preprocessor definitions in case this is a debug build
set(CMAKE_CXX_FLAGS_DEBUGFULL "${CMAKE_CXX_FLAGS_DEBUGFULL} -DQT_STRICT_ITERATORS -DKMM_DEBUG")
set(CMAKE_CXX_FLAGS_DEBUGKMM "${CMAKE_CXX_FLAGS_DEBUGFULL} -DKMM_DEBUG")
# and a variable we can use in the build environment to detect debug builds
if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]")
set(KMM_DEBUG "1")
endif()
option(USE_MODELTEST
"Compile with ModelTest code (default=OFF)" OFF)
add_feature_info("Model test" USE_MODELTEST "Generate modeltest code (for devs only).")
option(USE_QT_DESIGNER
"Install KMyMoney specific widget library for Qt-Designer (default=OFF)" OFF)
add_feature_info("QtDesigner" USE_QT_DESIGNER "Qt-Designer library support (for devs only).")
######################### 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
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")
message("
Build type: ${CMAKE_BUILD_TYPE}")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND
DESCRIPTION "The following REQUIRED packages have not been found:")
feature_summary(WHAT OPTIONAL_PACKAGES_NOT_FOUND
DESCRIPTION "The following OPTIONAL packages have not been found:")
feature_summary(WHAT ENABLED_FEATURES
DESCRIPTION "The following features have been enabled:")
feature_summary(WHAT DISABLED_FEATURES
DESCRIPTION "The following features have been disabled:")
diff --git a/kmymoney/dialogs/CMakeLists.txt b/kmymoney/dialogs/CMakeLists.txt
index 02ff9903c..22df2874c 100644
--- a/kmymoney/dialogs/CMakeLists.txt
+++ b/kmymoney/dialogs/CMakeLists.txt
@@ -1,107 +1,105 @@
add_subdirectory( settings )
########### next target ###############
set(libdialogs_a_SOURCES
splitadjustdialog.cpp
investactivities.cpp
investtransactioneditor.cpp
kaccountselectdlg.cpp
kbackupdlg.cpp
kbalancechartdlg.cpp
kbalancewarning.cpp
kcategoryreassigndlg.cpp
kchooseimportexportdlg.cpp
kconfirmmanualenterdlg.cpp
kcurrencycalculator.cpp
kcurrencyeditdlg.cpp
kavailablecurrencydlg.cpp
kcurrencyeditordlg.cpp
keditscheduledlg.cpp
kenterscheduledlg.cpp
kequitypriceupdatedlg.cpp
kequitypriceupdateconfdlg.cpp
kfindtransactiondlg.cpp
kgpgkeyselectiondlg.cpp
kloadtemplatedlg.cpp
kmergetransactionsdlg.cpp
kmymoneyfileinfodlg.cpp
kmymoneypricedlg.cpp
kmymoneysplittable.cpp
knewaccountdlg.cpp
hierarchyfilterproxymodel.cpp
knewbankdlg.cpp
knewbudgetdlg.cpp
knewequityentrydlg.cpp
editpersonaldatadlg.cpp
kpayeereassigndlg.cpp
ktagreassigndlg.cpp
kselecttransactionsdlg.cpp
ksplittransactiondlg.cpp
ktemplateexportdlg.cpp
kupdatestockpricedlg.cpp
transactioneditor.cpp
stdtransactioneditor.cpp
transactionmatcher.cpp
- konlinetransferform.cpp
)
set(dialogs_HEADERS
splitadjustdialog.h
investtransactioneditor.h kcurrencycalculator.h transactioneditor.h stdtransactioneditor.h
)
set(dialogs_UI
splitadjustdialog.ui
kaccountselectdlg.ui kbackupdlg.ui
kcategoryreassigndlg.ui kchooseimportexportdlg.ui
kconfirmmanualenterdlg.ui
kcurrencycalculator.ui kcurrencyeditdlg.ui kavailablecurrencydlg.ui kcurrencyeditordlg.ui
keditscheduledlg.ui
kenterscheduledlg.ui
kequitypriceupdatedlg.ui kequitypriceupdateconfdlg.ui
kfindtransactiondlg.ui
kgpgkeyselectiondlg.ui
kloadtemplatedlg.ui
kmymoneyfileinfodlg.ui kmymoneypricedlg.ui
knewaccountdlg.ui knewbankdlg.ui knewbudgetdlg.ui
knewequityentrydlg.ui editpersonaldatadlg.ui kpayeereassigndlg.ui
ktagreassigndlg.ui
kselecttransactionsdlg.ui
ksortoptiondlg.ui ksplitcorrectiondlg.ui ksplittransactiondlg.ui
ktemplateexportdlg.ui
kupdatestockpricedlg.ui
- konlinetransferform.ui
)
ki18n_wrap_ui(libdialogs_a_SOURCES ${dialogs_UI} )
add_library(dialogs STATIC ${libdialogs_a_SOURCES})
target_link_libraries(dialogs
PUBLIC
KChart
KF5::ItemViews
KF5::I18n
KF5::TextWidgets
KF5::Completion
Qt5::Widgets
Qt5::Sql
Alkimia::alkimia
kmm_mymoney
onlinetask_interfaces
kmm_widgets
kmm_utils_platformtools
)
target_link_libraries(dialogs LINK_PUBLIC
kmm_widgets
kmm_mymoney
onlinetask_interfaces
)
########### install files ###############
install(FILES ${dialogs_HEADERS}
DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel)
diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp
index ef183f364..bc8e8942c 100644
--- a/kmymoney/kmymoney.cpp
+++ b/kmymoney/kmymoney.cpp
@@ -1,4323 +1,4324 @@
/***************************************************************************
kmymoney.cpp
-------------------
copyright : (C) 2000 by Michael Edwardes
(C) 2007 by Thomas Baumgart
(C) 2017, 2018 by Łukasz Wojniłowicz
****************************************************************************/
/***************************************************************************
* *
* 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 "kmymoney.h"
// ----------------------------------------------------------------------------
// Std C++ / STL Includes
#include
#include
#include
// ----------------------------------------------------------------------------
// QT Includes
#include
#include // only for performance tests
#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
#include
#include
#include
#ifdef KF5Holidays_FOUND
#include
#include
#endif
// ----------------------------------------------------------------------------
// Project Includes
#include "kmymoneysettings.h"
#include "kmymoneyadaptor.h"
#include "dialogs/settings/ksettingskmymoney.h"
#include "dialogs/kbackupdlg.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/kcurrencycalculator.h"
#include "dialogs/keditscheduledlg.h"
#include "wizards/newloanwizard/keditloanwizard.h"
#include "dialogs/kpayeereassigndlg.h"
#include "dialogs/kcategoryreassigndlg.h"
#include "wizards/endingbalancedlg/kendingbalancedlg.h"
#include "dialogs/kbalancechartdlg.h"
#include "dialogs/kloadtemplatedlg.h"
#include "dialogs/kgpgkeyselectiondlg.h"
#include "dialogs/ktemplateexportdlg.h"
#include "dialogs/transactionmatcher.h"
#include "wizards/newuserwizard/knewuserwizard.h"
#include "wizards/newaccountwizard/knewaccountwizard.h"
#include "dialogs/kbalancewarning.h"
#include "widgets/kmymoneyaccountselector.h"
#include "widgets/kmymoneypayeecombo.h"
#include "widgets/onlinejobmessagesview.h"
#include "widgets/amountedit.h"
#include "widgets/kmymoneyedit.h"
#include "widgets/kmymoneymvccombo.h"
#include "views/kmymoneyview.h"
-#include "views/konlinejoboutbox.h"
-#include "models/onlinejobmessagesmodel.h"
#include "models/models.h"
#include "models/accountsmodel.h"
#include "models/equitiesmodel.h"
#include "models/securitiesmodel.h"
#include "mymoney/mymoneyobject.h"
#include "mymoney/mymoneyfile.h"
#include "mymoney/mymoneyinstitution.h"
#include "mymoney/mymoneyaccount.h"
#include "mymoney/mymoneyaccountloan.h"
#include "mymoney/mymoneysecurity.h"
#include "mymoney/mymoneypayee.h"
#include "mymoney/mymoneyprice.h"
#include "mymoney/mymoneytag.h"
#include "mymoney/mymoneybudget.h"
#include "mymoney/mymoneyreport.h"
#include "mymoney/mymoneysplit.h"
#include "mymoney/mymoneyutils.h"
#include "mymoney/mymoneystatement.h"
#include "mymoney/mymoneyforecast.h"
#include "mymoney/mymoneytransactionfilter.h"
#include "mymoney/onlinejobmessage.h"
#include "converter/mymoneystatementreader.h"
#include "converter/mymoneytemplate.h"
#include "plugins/interfaces/kmmappinterface.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 "tasks/credittransfer.h"
#include "icons/icons.h"
#include "misc/webconnect.h"
#include "storage/mymoneystoragemgr.h"
#include "storage/mymoneystoragexml.h"
#include "storage/mymoneystoragebin.h"
#include "storage/mymoneystorageanon.h"
#include
#include "transactioneditor.h"
-#include "konlinetransferform.h"
#include
#include
#include "kmymoneyutils.h"
#include "kcreditswindow.h"
#include "ledgerdelegate.h"
#include "storageenums.h"
#include "mymoneyenums.h"
#include "dialogenums.h"
+#include "viewenums.h"
#include "menuenums.h"
#include "misc/platformtools.h"
#ifdef KMM_DEBUG
#include "mymoney/storage/mymoneystoragedump.h"
#include "mymoneytracer.h"
#endif
using namespace Icons;
using namespace eMenu;
static constexpr KCompressionDevice::CompressionType const& COMPRESSION_TYPE = KCompressionDevice::GZip;
static constexpr char recoveryKeyId[] = "0xD2B08440";
static constexpr char recoveryKeyId2[] = "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_backupState(backupStateE::BACKUP_IDLE),
m_backupResult(0),
m_backupMount(0),
m_ignoreBackupExitCode(false),
m_fileOpen(false),
m_fmode(QFileDevice::ReadUser | QFileDevice::WriteUser),
m_fileType(KMyMoneyApp::KmmXML),
m_myMoneyView(nullptr),
m_startDialog(false),
m_progressBar(nullptr),
m_statusLabel(nullptr),
m_searchDlg(nullptr),
m_autoSaveEnabled(true),
m_autoSaveTimer(nullptr),
m_progressTimer(nullptr),
m_autoSavePeriod(0),
m_inAutoSaving(false),
m_transactionEditor(nullptr),
m_endingBalanceDlg(nullptr),
m_saveEncrypted(nullptr),
m_additionalKeyLabel(nullptr),
m_additionalKeyButton(nullptr),
m_recentFiles(nullptr),
#ifdef KF5Holidays_FOUND
m_holidayRegion(0),
#endif
m_applicationIsReady(true),
m_webConnect(new WebConnect(app)) {
// 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;
/** the configuration object of the application */
KSharedConfigPtr m_config;
/**
* @brief Structure of plugins objects by their interfaces
*/
KMyMoneyPlugin::Container m_plugins;
/**
* 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;
/**
* Flag for internal run control
*/
bool m_ignoreBackupExitCode;
bool m_fileOpen;
QFileDevice::Permissions m_fmode;
KMyMoneyApp::fileTypeE m_fileType;
KProcess 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;
// allows multiple imports to be launched trough web connect and to be executed sequentially
QQueue m_importUrlsQueue;
KFindTransactionDlg* m_searchDlg;
// 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;
WebConnect* m_webConnect;
// methods
void consistencyCheck(bool alwaysDisplayResults);
static void setThemedCSS();
void copyConsistencyCheckResults();
void saveConsistencyCheckResults();
void checkAccountName(const MyMoneyAccount& _acc, const QString& name) const
{
auto file = MyMoneyFile::instance();
if (_acc.name() != name) {
MyMoneyAccount acc(_acc);
acc.setName(name);
file->modifyAccount(acc);
}
}
/**
* This method updates names of currencies from file to localized names
*/
void updateCurrencyNames()
{
auto 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 updateAccountNames()
{
// make sure we setup the name of the base accounts in translated form
try {
MyMoneyFileTransaction ft;
const auto 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 &) {
}
}
void ungetString(QIODevice *qfile, char *buf, int len)
{
buf = &buf[len-1];
while (len--) {
qfile->ungetChar(*buf--);
}
}
bool applyFileFixes()
{
const auto blocked = MyMoneyFile::instance()->blockSignals(true);
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup grp = config->group("General Options");
// 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
auto 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);
return true;
}
void connectStorageToModels()
{
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->accountsModel(), &AccountsModel::slotObjectModified);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified);
q->connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved);
}
void disconnectStorageFromModels()
{
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->accountsModel(), &AccountsModel::slotObjectModified);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged,
Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged,
Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified);
q->disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved,
Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved);
}
/**
* 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()
{
const auto blocked = MyMoneyFile::instance()->blockSignals(true);
updateAccountNames();
updateCurrencyNames();
selectBaseCurrency();
// setup the standard precision
AmountEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
KMyMoneyEdit::setStandardPrecision(MyMoneyMoney::denomToPrec(MyMoneyFile::instance()->baseCurrency().smallestAccountFraction()));
if (!applyFileFixes())
return false;
MyMoneyFile::instance()->blockSignals(blocked);
emit q->kmmFilePlugin(KMyMoneyApp::postOpen);
Models::instance()->fileOpened();
connectStorageToModels();
// inform everyone about new data
MyMoneyFile::instance()->forceDataChanged();
q->slotCheckSchedules();
m_myMoneyView->slotFileOpened();
onlineJobAdministration::instance()->updateActions();
return true;
}
/**
* This method attaches an empty storage object to the MyMoneyFile
* object. It calls removeStorage() to remove a possibly attached
* storage object.
*/
void newStorage()
{
removeStorage();
auto file = MyMoneyFile::instance();
file->attachStorage(new MyMoneyStorageMgr);
}
/**
* This method removes an attached storage from the MyMoneyFile
* object.
*/
void removeStorage()
{
auto file = MyMoneyFile::instance();
auto p = file->storage();
if (p) {
file->detachStorage(p);
delete p;
}
}
/**
* if no base currency is defined, start the dialog and force it to be set
*/
void selectBaseCurrency()
{
auto 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(q);
// 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& e) {
qDebug() << QLatin1String("Account") << (*it).id() << (*it).name() << e.what();
}
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()));
}
}
}
}
}
/**
* 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 openNondatabase(const QUrl &url)
{
if (!url.isValid())
throw MYMONEYEXCEPTION(QString::fromLatin1("Invalid URL %1").arg(qPrintable(url.url())));
QString fileName;
auto downloadedFile = false;
if (url.isLocalFile()) {
fileName = url.toLocalFile();
} else {
fileName = KMyMoneyUtils::downloadFile(url);
downloadedFile = true;
}
if (!KMyMoneyUtils::fileExists(QUrl::fromLocalFile(fileName)))
throw MYMONEYEXCEPTION(QString::fromLatin1("Error opening the file.\n"
"Requested file: '%1'.\n"
"Downloaded file: '%2'").arg(qPrintable(url.url()), fileName));
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName));
QByteArray qbaFileHeader(2, '\0');
const auto sFileToShort = QString::fromLatin1("File %1 is too short.").arg(fileName);
if (file.read(qbaFileHeader.data(), 2) != 2)
throw MYMONEYEXCEPTION(sFileToShort);
file.close();
// 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.
auto haveAt = true;
auto isEncrypted = false;
emit q->kmmFilePlugin(preOpen);
QIODevice* qfile = nullptr;
QString sFileHeader(qbaFileHeader);
if (sFileHeader == QString("\037\213")) { // gzipped?
qfile = new KCompressionDevice(fileName, COMPRESSION_TYPE);
} else if (sFileHeader == QString("--") || // PGP ASCII armored?
sFileHeader == QString("\205\001") || // PGP binary?
sFileHeader == QString("\205\002")) { // PGP binary?
if (KGPGFile::GPGAvailable()) {
qfile = new KGPGFile(fileName);
haveAt = false;
isEncrypted = true;
} else {
throw MYMONEYEXCEPTION(QString::fromLatin1("%1").arg(i18n("GPG is not available for decryption of file %1", fileName)));
}
} else {
// we can't use file directly, as we delete qfile later on
qfile = new QFile(file.fileName());
}
if (!qfile->open(QIODevice::ReadOnly)) {
delete qfile;
throw MYMONEYEXCEPTION(QString::fromLatin1("Cannot read the file: %1").arg(fileName));
}
qbaFileHeader.resize(8);
if (qfile->read(qbaFileHeader.data(), 8) != 8)
throw MYMONEYEXCEPTION(sFileToShort);
if (haveAt)
qfile->seek(0);
else
ungetString(qfile, qbaFileHeader.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(&qbaFileHeader, 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. '?%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)));
}
// Scan the first 70 bytes to see if we find something
// we know. For now, we support our own XML format and
// GNUCash XML format. If the file is smaller, then it
// contains no valid data and we reject it anyway.
qbaFileHeader.resize(70);
if (qfile->read(qbaFileHeader.data(), 70) != 70)
throw MYMONEYEXCEPTION(sFileToShort);
if (haveAt)
qfile->seek(0);
else
ungetString(qfile, qbaFileHeader.data(), 70);
IMyMoneyOperationsFormat* pReader = nullptr;
QRegExp kmyexp("");
QRegExp gncexp("formatName().compare(QLatin1String("GNC")) == 0) {
pReader = plugin->reader();
break;
}
}
if (!pReader) {
KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage."));
return false;
}
m_fileType = KMyMoneyApp::GncXML;
} else {
throw MYMONEYEXCEPTION(QString::fromLatin1("%1").arg(i18n("File %1 contains an unknown file format.", fileName)));
}
// disconnect the current storga manager from the engine
MyMoneyFile::instance()->detachStorage();
// create a new empty storage object
auto storage = new MyMoneyStorageMgr;
// attach the storage before reading the file, since the online
// onlineJobAdministration object queries the engine during
// loading.
MyMoneyFile::instance()->attachStorage(storage);
pReader->setProgressCallback(&KMyMoneyApp::progressCallback);
pReader->readFile(qfile, storage);
pReader->setProgressCallback(0);
delete pReader;
qfile->close();
delete qfile;
// if a temporary file was downloaded, then it will be removed
// with the next call. Otherwise, it stays untouched on the local
// filesystem.
if (downloadedFile)
QFile::remove(fileName);
// 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", KMyMoneySettings::gpgRecipientList().join(","));
ft.commit();
return true;
}
/**
* 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 &url)
{
// open the database
auto pStorage = MyMoneyFile::instance()->storage();
if (!pStorage)
pStorage = new MyMoneyStorageMgr;
auto rc = false;
auto pluginFound = false;
for (const auto& plugin : m_plugins.storage) {
if (plugin->formatName().compare(QLatin1String("SQL")) == 0) {
rc = plugin->open(pStorage, url);
pluginFound = true;
break;
}
}
if(!pluginFound)
KMessageBox::error(q, i18n("Couldn't find suitable plugin to read your storage."));
if(!rc) {
removeStorage();
delete pStorage;
return false;
}
if (pStorage) {
MyMoneyFile::instance()->detachStorage();
MyMoneyFile::instance()->attachStorage(pStorage);
}
return true;
}
/**
* Close the currently opened file and create an empty new file.
*
* @see MyMoneyFile
*/
void newFile()
{
closeFile();
m_fileType = KMyMoneyApp::KmmXML; // assume native type until saved
m_fileOpen = true;
}
/**
* 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())
{
QString filename = url.path();
if (!m_fileOpen) {
KMessageBox::error(q, i18n("Tried to access a file when it has not been opened"));
return false;
}
emit q->kmmFilePlugin(KMyMoneyApp::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")
storageWriter = std::make_unique();
else
storageWriter = std::make_unique();
// 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 = KMyMoneySettings::autoBackupCopies();
if (nbak) {
KBackup::numberedBackupFile(filename, QString(), QStringLiteral("~"), 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);
Q_CONSTEXPR int permission = -1;
QFile file(tmpfile.fileName());
file.open(QIODevice::ReadOnly);
KIO::StoredTransferJob *putjob = KIO::storedPut(file.readAll(), url, permission, KIO::JobFlag::Overwrite);
if (!putjob->exec()) {
throw MYMONEYEXCEPTION(i18n("Unable to upload to '%1'.
%2", url.toDisplayString(), putjob->errorString()));
}
file.close();
}
m_fileType = KMyMoneyApp::KmmXML;
} catch (const MyMoneyException &e) {
KMessageBox::error(q, e.what());
MyMoneyFile::instance()->setDirty();
rc = false;
}
emit q->kmmFilePlugin(postSave);
return rc;
}
/**
* 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, IMyMoneyOperationsFormat* pWriter, bool plaintext, const QString& keyList)
{
// Check GPG encryption
bool encryptFile = true;
bool encryptRecover = false;
if (!keyList.isEmpty()) {
if (!KGPGFile::GPGAvailable()) {
KMessageBox::sorry(q, 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 (KMyMoneySettings::encryptRecover()) {
encryptRecover = true;
if (!KGPGFile::keyAvailable(QString(recoveryKeyId))) {
KMessageBox::sorry(q, 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(q, 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(q, 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()
: m_signalsWereBlocked{MyMoneyFile::instance()->signalsBlocked()}
{
MyMoneyFile::instance()->blockSignals(true);
}
~restorePreviousSettingsHelper()
{
MyMoneyFile::instance()->blockSignals(m_signalsWereBlocked);
}
const bool m_signalsWereBlocked;
} restoreHelper;
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(&KMyMoneyApp::progressCallback);
pWriter->writeFile(device.get(), 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));
}
QFile::setPermissions(localFile, m_fmode);
pWriter->setProgressCallback(0);
}
/**
* 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()
{
if (!m_fileOpen)
return false;
return MyMoneyFile::instance()->dirty();
}
/* 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 fixFile_3()
{
// make sure each storage object contains a (unique) id
MyMoneyFile::instance()->storageId();
}
void fixFile_2()
{
auto 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.
auto count = 0;
foreach (const auto transaction, transactionList) {
if (transaction.splitCount() == 2) {
QString accountId;
QString categoryId;
QString accountMemo;
QString categoryMemo;
foreach (const auto split, transaction.splits()) {
auto acc = file->account(split.accountId());
if (acc.isIncomeExpense()) {
categoryId = split.id();
categoryMemo = split.memo();
} else {
accountId = split.id();
accountMemo = split.memo();
}
}
if (!accountId.isEmpty() && !categoryId.isEmpty()
&& accountMemo != categoryMemo) {
MyMoneyTransaction t(transaction);
MyMoneySplit s(t.splitById(categoryId));
s.setMemo(accountMemo);
t.modifySplit(s);
file->modifyTransaction(t);
++count;
}
}
}
qDebug("%d transactions fixed in fixFile_2", count);
}
void 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 (!KMyMoneySettings::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) {
auto acc = MyMoneyFile::instance()->account(*it_a);
if (acc.accountType() == eMyMoney::Account::Type::Investment) {
foreach (const auto accountID, acc.accountList()) {
if (!list.contains(accountID)) {
missing.append(accountID);
}
}
}
}
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 (!KMyMoneySettings::expertMode()) {
QStringList missing;
QStringList::const_iterator it_a, it_b;
for (it_a = list.begin(); it_a != list.end(); ++it_a) {
auto acc = MyMoneyFile::instance()->account(*it_a);
if (acc.accountType() == Account::Type::Investment) {
foreach (const auto accountID, acc.accountList()) {
if (!list.contains(accountID)) {
missing.append(accountID);
}
}
}
}
list += missing;
}
m_filter.addAccount(list);
}
#endif
void 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.
*/
auto 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() == eMyMoney::Account::Type::Loan
|| (*it_a).accountType() == eMyMoney::Account::Type::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() == eMyMoney::Account::Type::Equity) {
if ((*it_a).parentAccountId() == equity.id()) {
auto 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 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 {
auto 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() != eMyMoney::Split::State::NotReconciled) {
qDebug() << Q_FUNC_INFO << " " << sched.id() << " " << (*it_s).id() << " should be 'not reconciled'";
MyMoneySplit split = *it_s;
split.setReconcileDate(QDate());
split.setReconcileFlag(eMyMoney::Split::State::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 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(q,
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 fixTransactions_0()
{
auto 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"));
q->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;
foreach (const auto split, t.splits()) {
if (accounts.contains(split.accountId())) {
hasDuplicateAccounts = true;
qDebug() << Q_FUNC_INFO << " " << t.id() << " has multiple splits with account " << split.accountId();
} else {
accounts << split.accountId();
}
if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
if (interestAccounts.contains(split.accountId()) == 0) {
interestAccounts << split.accountId();
}
}
}
if (hasDuplicateAccounts) {
fixDuplicateAccounts_0(t);
}
++cnt;
if (!(cnt % 10))
q->slotStatusProgressBar(cnt);
}
// scan the transactions and modify loan transactions
for (auto& transaction : transactionList) {
QString defaultAction;
QList splits = transaction.splits();
QStringList accounts;
// check if base commodity is set. if not, set baseCurrency
if (transaction.commodity().isEmpty()) {
qDebug() << Q_FUNC_INFO << " " << transaction.id() << " has no base currency";
transaction.setCommodity(file->baseCurrency().id());
file->modifyTransaction(transaction);
}
bool isLoan = false;
// Determine default action
if (transaction.splitCount() == 2) {
// check for transfer
int accountCount = 0;
MyMoneyMoney val;
foreach (const auto split, splits) {
auto acc = file->account(split.accountId());
if (acc.accountGroup() == eMyMoney::Account::Type::Asset
|| acc.accountGroup() == eMyMoney::Account::Type::Liability) {
val = split.value();
accountCount++;
if (acc.accountType() == eMyMoney::Account::Type::Loan
|| acc.accountType() == eMyMoney::Account::Type::AssetLoan)
isLoan = true;
} else
break;
}
if (accountCount == 2) {
if (isLoan)
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization);
else
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer);
} else {
if (val.isNegative())
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
else
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
}
}
isLoan = false;
foreach (const auto split, splits) {
auto acc = file->account(split.accountId());
MyMoneyMoney val = split.value();
if (acc.accountGroup() == eMyMoney::Account::Type::Asset
|| acc.accountGroup() == eMyMoney::Account::Type::Liability) {
if (!val.isPositive()) {
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal);
break;
} else {
defaultAction = MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit);
break;
}
}
}
#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) {
auto acc = file->account((*it_s).accountId());
MyMoneyMoney val = (*it_s).value();
if (acc.accountType() == Account::Type::CreditCard) {
if (val < 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Withdrawal) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
needModify = true;
if (val >= 0 && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Deposit) && (*it_s).action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Transfer))
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);
transaction.modifySplit(*it_s);
file->modifyTransaction(transaction);
}
splits = transaction.splits(); // update local copy
qDebug("Fixed credit card assignment in %s", transaction.id().data());
}
#endif
// Check for correct assignment of ActionInterest in all splits
// and check if there are any duplicates in this transactions
for (auto& split : splits) {
MyMoneyAccount splitAccount = file->account(split.accountId());
if (!accounts.contains(split.accountId())) {
accounts << split.accountId();
}
// if this split references an interest account, the action
// must be of type ActionInterest
if (interestAccounts.contains(split.accountId())) {
if (split.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
qDebug() << Q_FUNC_INFO << " " << transaction.id() << " contains an interest account (" << split.accountId() << ") but does not have ActionInterest";
split.setAction(MyMoneySplit::actionName(eMyMoney::Split::Action::Interest));
transaction.modifySplit(split);
file->modifyTransaction(transaction);
qDebug("Fixed interest action in %s", qPrintable(transaction.id()));
}
// if it does not reference an interest account, it must not be
// of type ActionInterest
} else {
if (split.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::Interest)) {
qDebug() << Q_FUNC_INFO << " " << transaction.id() << " does not contain an interest account so it should not have ActionInterest";
split.setAction(defaultAction);
transaction.modifySplit(split);
file->modifyTransaction(transaction);
qDebug("Fixed interest action in %s", qPrintable(transaction.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 (transaction.commodity() == splitAccount.currencyId()
&& split.value() != split.shares()) {
qDebug() << Q_FUNC_INFO << " " << transaction.id() << " " << split.id() << " uses the transaction currency, but shares != value";
split.setShares(split.value());
transaction.modifySplit(split);
file->modifyTransaction(transaction);
}
// fix the shares and values to have the correct fraction
if (!splitAccount.isInvest()) {
try {
int fract = splitAccount.fraction();
if (split.shares() != split.shares().convert(fract)) {
qDebug("adjusting fraction in %s,%s", qPrintable(transaction.id()), qPrintable(split.id()));
split.setShares(split.shares().convert(fract));
split.setValue(split.value().convert(fract));
transaction.modifySplit(split);
file->modifyTransaction(transaction);
}
} catch (const MyMoneyException &) {
qDebug("Missing security '%s', split not altered", qPrintable(splitAccount.currencyId()));
}
}
}
++cnt;
if (!(cnt % 10))
q->slotStatusProgressBar(cnt);
}
q->slotStatusProgressBar(-1, -1);
}
void fixDuplicateAccounts_0(MyMoneyTransaction& t)
{
qDebug("Duplicate account in transaction %s", qPrintable(t.id()));
}
};
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-" + QString::number(platformTools::processId()), 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->setThemedCSS();
MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneySettings::firstFiscalMonth(), KMyMoneySettings::firstFiscalDay());
updateCaption(true);
QFrame* frame = new QFrame;
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);
{
#ifdef Q_OS_WIN
QString themeName = QLatin1Literal("system"); // using QIcon::setThemeName on Craft build system causes icons to disappear
#else
QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants
#endif
if (!themeName.isEmpty() && themeName != QLatin1Literal("system")) // if it isn't default theme then set it
QIcon::setThemeName(themeName);
Icons::setIconThemeNames(QIcon::themeName()); // get whatever theme user ends up with and hope our icon names will fit that theme
}
initStatusBar();
pActions = initActions();
pMenus = initMenus();
d->newStorage();
d->m_myMoneyView = new KMyMoneyView(this/*the global variable kmymoney is not yet assigned. So we pass it here*/);
layout->addWidget(d->m_myMoneyView, 10);
connect(d->m_myMoneyView, &KMyMoneyView::aboutToChangeView, this, &KMyMoneyApp::slotResetSelections);
connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)),
this, SLOT(slotUpdateActions()));
connect(d->m_myMoneyView, &KMyMoneyView::statusMsg, this, &KMyMoneyApp::slotStatusMsg);
connect(d->m_myMoneyView, &KMyMoneyView::statusProgress, this, &KMyMoneyApp::slotStatusProgressBar);
///////////////////////////////////////////////////////////////////
// call inits to invoke all other construction parts
readOptions();
// now initialize the plugin structure
createInterfaces();
KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Load, d->m_plugins, this, guiFactory());
onlineJobAdministration::instance()->setOnlinePlugins(d->m_plugins.extended);
d->m_myMoneyView->setOnlinePlugins(d->m_plugins.online);
d->m_myMoneyView->setStoragePlugins(d->m_plugins.storage);
setCentralWidget(frame);
connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotBackupHandleEvents()));
// force to show the home page if the file is closed
connect(pActions[Action::ViewTransactionDetail], &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail);
d->m_backupState = BACKUP_IDLE;
QLocale locale;
for (auto const& weekDay: locale.weekdays())
{
d->m_processingDays.setBit(static_cast(weekDay));
}
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()));
// connect the WebConnect server
connect(d->m_webConnect, SIGNAL(gotUrl(QUrl)), this, SLOT(webConnect(QUrl)));
// make sure we have a balance warning object
d->m_balanceWarning = new KBalanceWarning(this);
// setup the initial configuration
slotUpdateConfiguration(QString());
// kickstart date change timer
slotDateChanged();
connect(this, SIGNAL(fileLoaded(QUrl)), onlineJobAdministration::instance(), SLOT(updateOnlineTaskProperties()));
}
KMyMoneyApp::~KMyMoneyApp()
{
d->removeStorage();
// delete cached objects since they are in the way
// when unloading the plugins
onlineJobAdministration::instance()->clearCaches();
// we need to unload all plugins before we destroy anything else
KMyMoneyPlugin::pluginHandling(KMyMoneyPlugin::Action::Unload, d->m_plugins, this, guiFactory());
delete d->m_searchDlg;
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();
}
}
}
QHash KMyMoneyApp::initMenus()
{
QHash