diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 30b4ae716..59e5b5c61 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7559 +1,7560 @@ /*************************************************************************** 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 "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 // ---------------------------------------------------------------------------- // KDE Includes #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 "kmymoneyglobalsettings.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/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/ktemplateexportdlg.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/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 "icons/icons.h" #include #include #include "konlinetransferform.h" #include #include #include "kmymoneyutils.h" #include "kcreditswindow.h" #include "ledgerdelegate.h" using namespace Icons; 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 const QHash KMyMoneyApp::s_Actions { {Action::FileOpenDatabase, QStringLiteral("open_database")}, {Action::FileSaveAsDatabase, QStringLiteral("saveas_database")}, {Action::FileBackup, QStringLiteral("file_backup")}, {Action::FileImportGNC, QStringLiteral("file_import_gnc")}, {Action::FileImportStatement, QStringLiteral("file_import_statement")}, {Action::FileImportTemplate, QStringLiteral("file_import_template")}, {Action::FileExportTemplate, QStringLiteral("file_export_template")}, {Action::FilePersonalData, QStringLiteral("view_personal_data")}, #ifdef KMM_DEBUG {Action::FileDump, QStringLiteral("file_dump")}, #endif {Action::FileInformation, QStringLiteral("view_file_info")}, {Action::EditFindTransaction, QStringLiteral("edit_find_transaction")}, {Action::ViewTransactionDetail, QStringLiteral("view_show_transaction_detail")}, {Action::ViewHideReconciled, QStringLiteral("view_hide_reconciled_transactions")}, {Action::ViewHideCategories, QStringLiteral("view_hide_unused_categories")}, {Action::ViewShowAll, QStringLiteral("view_show_all_accounts")}, {Action::InstitutionNew, QStringLiteral("institution_new")}, {Action::InstitutionEdit, QStringLiteral("institution_edit")}, {Action::InstitutionDelete, QStringLiteral("institution_delete")}, {Action::AccountNew, QStringLiteral("account_new")}, {Action::AccountOpen, QStringLiteral("account_open")}, {Action::AccountStartReconciliation, QStringLiteral("account_reconcile")}, {Action::AccountFinishReconciliation, QStringLiteral("account_reconcile_finish")}, {Action::AccountPostponeReconciliation, QStringLiteral("account_reconcile_postpone")}, {Action::AccountEdit, QStringLiteral("account_edit")}, {Action::AccountDelete, QStringLiteral("account_delete")}, {Action::AccountClose, QStringLiteral("account_close")}, {Action::AccountReopen, QStringLiteral("account_reopen")}, {Action::AccountTransactionReport, QStringLiteral("account_transaction_report")}, {Action::AccountBalanceChart, QStringLiteral("account_chart")}, {Action::AccountOnlineMap, QStringLiteral("account_online_map")}, {Action::AccountOnlineUnmap, QStringLiteral("account_online_unmap")}, {Action::AccountUpdateMenu, QStringLiteral("account_online_update_menu")}, {Action::AccountUpdate, QStringLiteral("account_online_update")}, {Action::AccountUpdateAll, QStringLiteral("account_online_update_all")}, {Action::AccountCreditTransfer, QStringLiteral("account_online_new_credit_transfer")}, {Action::CategoryNew, QStringLiteral("category_new")}, {Action::CategoryEdit, QStringLiteral("category_edit")}, {Action::CategoryDelete, QStringLiteral("category_delete")}, {Action::ToolCurrencies, QStringLiteral("tools_currency_editor")}, {Action::ToolPrices, QStringLiteral("tools_price_editor")}, {Action::ToolUpdatePrices, QStringLiteral("tools_update_prices")}, {Action::ToolConsistency, QStringLiteral("tools_consistency_check")}, {Action::ToolPerformance, QStringLiteral("tools_performancetest")}, {Action::ToolSQL, QStringLiteral("tools_generate_sql")}, {Action::ToolCalculator, QStringLiteral("tools_kcalc")}, {Action::SettingsAllMessages, QStringLiteral("settings_enable_messages")}, {Action::SettingsLanguage, QStringLiteral("settings_language")}, {Action::HelpShow, QStringLiteral("help_show_tip")}, {Action::TransactionNew, QStringLiteral("transaction_new")}, {Action::TransactionEdit, QStringLiteral("transaction_edit")}, {Action::TransactionEnter, QStringLiteral("transaction_enter")}, {Action::TransactionEditSplits, QStringLiteral("transaction_editsplits")}, {Action::TransactionCancel, QStringLiteral("transaction_cancel")}, {Action::TransactionDelete, QStringLiteral("transaction_delete")}, {Action::TransactionDuplicate, QStringLiteral("transaction_duplicate")}, {Action::TransactionMatch, QStringLiteral("transaction_match")}, {Action::TransactionAccept, QStringLiteral("transaction_accept")}, {Action::TransactionToggleReconciled, QStringLiteral("transaction_mark_toggle")}, {Action::TransactionToggleCleared, QStringLiteral("transaction_mark_cleared")}, {Action::TransactionReconciled, QStringLiteral("transaction_mark_reconciled")}, {Action::TransactionNotReconciled, QStringLiteral("transaction_mark_notreconciled")}, {Action::TransactionSelectAll, QStringLiteral("transaction_select_all")}, {Action::TransactionGoToAccount, QStringLiteral("transaction_goto_account")}, {Action::TransactionGoToPayee, QStringLiteral("transaction_goto_payee")}, {Action::TransactionCreateSchedule, QStringLiteral("transaction_create_schedule")}, {Action::TransactionAssignNumber, QStringLiteral("transaction_assign_number")}, {Action::TransactionCombine, QStringLiteral("transaction_combine")}, {Action::TransactionCopySplits, QStringLiteral("transaction_copy_splits")}, {Action::TransactionMoveMenu, QStringLiteral("transaction_move_menu")}, {Action::TransactionMarkMenu, QStringLiteral("transaction_mark_menu")}, {Action::TransactionContextMarkMenu, QStringLiteral("transaction_context_mark_menu")}, {Action::InvestmentNew, QStringLiteral("investment_new")}, {Action::InvestmentEdit, QStringLiteral("investment_edit")}, {Action::InvestmentDelete, QStringLiteral("investment_delete")}, {Action::InvestmentOnlinePrice, QStringLiteral("investment_online_price_update")}, {Action::InvestmentManualPrice, QStringLiteral("investment_manual_price_update")}, {Action::ScheduleNew, QStringLiteral("schedule_new")}, {Action::ScheduleEdit, QStringLiteral("schedule_edit")}, {Action::ScheduleDelete, QStringLiteral("schedule_delete")}, {Action::ScheduleDuplicate, QStringLiteral("schedule_duplicate")}, {Action::ScheduleEnter, QStringLiteral("schedule_enter")}, {Action::ScheduleSkip, QStringLiteral("schedule_skip")}, {Action::PayeeNew, QStringLiteral("payee_new")}, {Action::PayeeRename, QStringLiteral("payee_rename")}, {Action::PayeeDelete, QStringLiteral("payee_delete")}, {Action::PayeeMerge, QStringLiteral("payee_merge")}, {Action::TagNew, QStringLiteral("tag_new")}, {Action::TagRename, QStringLiteral("tag_rename")}, {Action::TagDelete, QStringLiteral("tag_delete")}, {Action::BudgetNew, QStringLiteral("budget_new")}, {Action::BudgetRename, QStringLiteral("budget_rename")}, {Action::BudgetDelete, QStringLiteral("budget_delete")}, {Action::BudgetCopy, QStringLiteral("budget_copy")}, {Action::BudgetChangeYear, QStringLiteral("budget_change_year")}, {Action::BudgetForecast, QStringLiteral("budget_forecast")}, {Action::CurrencyNew, QStringLiteral("currency_new")}, {Action::CurrencyRename, QStringLiteral("currency_rename")}, {Action::CurrencyDelete, QStringLiteral("currency_delete")}, {Action::CurrencySetBase, QStringLiteral("currency_setbase")}, {Action::PriceNew, QStringLiteral("price_new")}, {Action::PriceEdit, QStringLiteral("price_edit")}, {Action::PriceUpdate, QStringLiteral("price_update")}, {Action::PriceDelete, QStringLiteral("price_delete")}, #ifdef KMM_DEBUG {Action::WizardNewUser, QStringLiteral("new_user_wizard")}, {Action::DebugTraces, QStringLiteral("debug_traces")}, #endif {Action::DebugTimers, QStringLiteral("debug_timers")}, {Action::OnlineJobDelete, QStringLiteral("onlinejob_delete")}, {Action::OnlineJobEdit, QStringLiteral("onlinejob_edit")}, {Action::OnlineJobLog, QStringLiteral("onlinejob_log")}, }; enum backupStateE { BACKUP_IDLE = 0, BACKUP_MOUNTING, BACKUP_COPYING, BACKUP_UNMOUNTING }; typedef void(KMyMoneyApp::*KMyMoneyAppFunc)(); 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_backupResult(0), m_backupMount(0), m_ignoreBackupExitCode(false), m_myMoneyView(0), m_progressBar(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; /** * Flag for internal run control */ bool m_ignoreBackupExitCode; 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; 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; 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); { QString themeName = KMyMoneySettings::iconsTheme(); // get theme user wants 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(); initActions(); initDynamicMenus(); 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, 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(slotBackupHandleEvents())); // force to show the home page if the file is closed connect(actionCollection()->action(s_Actions[Action::ViewTransactionDetail]), &QAction::toggled, d->m_myMoneyView, &KMyMoneyView::slotShowTransactionDetail); 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 cached objects since the are in the way // when unloading the plugins onlineJobAdministration::instance()->clearCaches(); // we need to unload all plugins before we destroy anything else unloadPlugins(); 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(); } } } 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() { KActionCollection *aC = actionCollection(); // ************* // Adding standard actions // ************* KStandardAction::openNew(this, &KMyMoneyApp::slotFileNew, aC); KStandardAction::open(this, &KMyMoneyApp::slotFileOpen, aC); d->m_recentFiles = KStandardAction::openRecent(this, &KMyMoneyApp::slotFileOpenRecent, aC); KStandardAction::save(this, &KMyMoneyApp::slotFileSave, aC); KStandardAction::saveAs(this, &KMyMoneyApp::slotFileSaveAs, aC); KStandardAction::close(this, &KMyMoneyApp::slotFileClose, aC); KStandardAction::quit(this, &KMyMoneyApp::slotFileQuit, aC); KStandardAction::print(this, &KMyMoneyApp::slotPrintView, aC); KStandardAction::preferences(this, &KMyMoneyApp::slotSettings, aC); /* Look-up table for all custom actions. It's required for: 1) building QList with QActions to be added to ActionCollection 2) adding custom features to QActions like e.g. overlaid icon or keyboard shortcut */ QHash lutActions; // ************* // Adding all actions // ************* { // struct for QAction's vital (except icon) informations struct actionInfo { Action action; KMyMoneyAppFunc callback; QString text; Icon icon; }; const QVector actionInfos { // ************* // The File menu // ************* {Action::FileOpenDatabase, &KMyMoneyApp::slotOpenDatabase, i18n("Open database..."), Icon::SVNUpdate}, {Action::FileSaveAsDatabase, &KMyMoneyApp::slotSaveAsDatabase, i18n("Save as database..."), Icon::FileArchiver}, {Action::FileBackup, &KMyMoneyApp::slotBackupFile, i18n("Backup..."), Icon::Empty}, {Action::FileImportGNC, &KMyMoneyApp::slotGncImport, i18n("GnuCash..."), Icon::Empty}, {Action::FileImportStatement, &KMyMoneyApp::slotStatementImport, i18n("Statement file..."), Icon::Empty}, {Action::FileImportTemplate, &KMyMoneyApp::slotLoadAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FileExportTemplate, &KMyMoneyApp::slotSaveAccountTemplates, i18n("Account Template..."), Icon::Empty}, {Action::FilePersonalData, &KMyMoneyApp::slotFileViewPersonal, i18n("Personal Data..."), Icon::UserProperties}, #ifdef KMM_DEBUG {Action::FileDump, &KMyMoneyApp::slotFileFileInfo, i18n("Dump Memory"), Icon::Empty}, #endif {Action::FileInformation, &KMyMoneyApp::slotFileInfoDialog, i18n("File-Information..."), Icon::DocumentProperties}, // ************* // The Edit menu // ************* {Action::EditFindTransaction, &KMyMoneyApp::slotFindTransaction, i18n("Find transaction..."), Icon::Empty}, // ************* // The View menu // ************* {Action::ViewTransactionDetail, &KMyMoneyApp::slotShowTransactionDetail, i18n("Show Transaction Detail"), Icon::ViewTransactionDetail}, {Action::ViewHideReconciled, &KMyMoneyApp::slotHideReconciledTransactions, i18n("Hide reconciled transactions"), Icon::HideReconciled}, {Action::ViewHideCategories, &KMyMoneyApp::slotHideUnusedCategories, i18n("Hide unused categories"), Icon::HideCategories}, {Action::ViewShowAll, &KMyMoneyApp::slotShowAllAccounts, i18n("Show all accounts"), Icon::Empty}, // ********************* // The institutions menu // ********************* {Action::InstitutionNew, &KMyMoneyApp::slotInstitutionNew, i18n("New institution..."), Icon::Empty}, {Action::InstitutionEdit, &KMyMoneyApp::slotInstitutionEdit, i18n("Edit institution..."), Icon::Empty}, {Action::InstitutionDelete, &KMyMoneyApp::slotInstitutionDelete, i18n("Delete institution..."), Icon::Empty}, // ***************** // The accounts menu // ***************** {Action::AccountNew, &KMyMoneyApp::slotAccountNew, i18n("New account..."), Icon::Empty}, {Action::AccountOpen, &KMyMoneyApp::slotAccountOpen, i18n("Open ledger"), Icon::ViewFinancialList}, {Action::AccountStartReconciliation, &KMyMoneyApp::slotAccountReconcileStart, i18n("Reconcile..."), Icon::Reconcile}, {Action::AccountFinishReconciliation, &KMyMoneyApp::slotAccountReconcileFinish, i18nc("Finish reconciliation", "Finish"), Icon::Empty}, {Action::AccountPostponeReconciliation, &KMyMoneyApp::slotAccountReconcilePostpone, i18n("Postpone reconciliation"), Icon::MediaPlaybackPause}, {Action::AccountEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit account..."), Icon::Empty}, {Action::AccountDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete account..."), Icon::Empty}, {Action::AccountClose, &KMyMoneyApp::slotAccountClose, i18n("Close account"), Icon::Empty}, {Action::AccountReopen, &KMyMoneyApp::slotAccountReopen, i18n("Reopen account"), Icon::Empty}, {Action::AccountTransactionReport, &KMyMoneyApp::slotAccountTransactionReport, i18n("Transaction report"), Icon::ViewFinancialList}, {Action::AccountBalanceChart, &KMyMoneyApp::slotAccountChart, i18n("Show balance chart..."), Icon::OfficeChartLine}, {Action::AccountOnlineMap, &KMyMoneyApp::slotAccountMapOnline, i18n("Map account..."), Icon::NewsSubscribe}, {Action::AccountOnlineUnmap, &KMyMoneyApp::slotAccountUnmapOnline, i18n("Unmap account..."), Icon::NewsUnsubscribe}, {Action::AccountUpdateMenu, &KMyMoneyApp::slotAccountUpdateOnline, i18nc("Update online accounts menu", "Update"), Icon::Empty}, {Action::AccountUpdate, &KMyMoneyApp::slotAccountUpdateOnline, i18n("Update account..."), Icon::Empty}, {Action::AccountUpdateAll, &KMyMoneyApp::slotAccountUpdateOnlineAll, i18n("Update all accounts..."), Icon::Empty}, {Action::AccountCreditTransfer, &KMyMoneyApp::slotNewOnlineTransfer, i18n("New credit transfer"), Icon::Empty}, // ******************* // The categories menu // ******************* {Action::CategoryNew, &KMyMoneyApp::slotCategoryNew, i18n("New category..."), Icon::Empty}, {Action::CategoryEdit, &KMyMoneyApp::slotAccountEdit, i18n("Edit category..."), Icon::Empty}, {Action::CategoryDelete, &KMyMoneyApp::slotAccountDelete, i18n("Delete category..."), Icon::Empty}, // ************** // The tools menu // ************** {Action::ToolCurrencies, &KMyMoneyApp::slotCurrencyDialog, i18n("Currencies..."), Icon::ViewCurrencyList}, {Action::ToolPrices, &KMyMoneyApp::slotPriceDialog, i18n("Prices..."), Icon::Empty}, {Action::ToolUpdatePrices, &KMyMoneyApp::slotEquityPriceUpdate, i18n("Update Stock and Currency Prices..."), Icon::Empty}, {Action::ToolConsistency, &KMyMoneyApp::slotFileConsistencyCheck, i18n("Consistency Check"), Icon::Empty}, {Action::ToolPerformance, &KMyMoneyApp::slotPerformanceTest, i18n("Performance-Test"), Icon::Fork}, {Action::ToolSQL, &KMyMoneyApp::slotGenerateSql, i18n("Generate Database SQL"), Icon::Empty}, {Action::ToolCalculator, &KMyMoneyApp::slotToolsStartKCalc, i18n("Calculator..."), Icon::AccessoriesCalculator}, // ***************** // The settings menu // ***************** {Action::SettingsAllMessages, &KMyMoneyApp::slotEnableMessages, i18n("Enable all messages"), Icon::Empty}, {Action::SettingsLanguage, &KMyMoneyApp::slotKDELanguageSettings, i18n("KDE language settings..."), Icon::Empty}, // ************* // The help menu // ************* {Action::HelpShow, &KMyMoneyApp::slotShowTipOfTheDay, i18n("&Show tip of the day"), Icon::Tip}, // *************************** // Actions w/o main menu entry // *************************** {Action::TransactionNew, &KMyMoneyApp::slotTransactionsNew, i18nc("New transaction button", "New"), Icon::Empty}, {Action::TransactionEdit, &KMyMoneyApp::slotTransactionsEdit, i18nc("Edit transaction button", "Edit"), Icon::Empty}, {Action::TransactionEnter, &KMyMoneyApp::slotTransactionsEnter, i18nc("Enter transaction", "Enter"), Icon::DialogOK}, {Action::TransactionEditSplits, &KMyMoneyApp::slotTransactionsEditSplits, i18nc("Edit split button", "Edit splits"), Icon::Split}, {Action::TransactionCancel, &KMyMoneyApp::slotTransactionsCancel, i18nc("Cancel transaction edit", "Cancel"), Icon::DialogCancel}, {Action::TransactionDelete, &KMyMoneyApp::slotTransactionsDelete, i18nc("Delete transaction", "Delete"), Icon::EditDelete}, {Action::TransactionDuplicate, &KMyMoneyApp::slotTransactionDuplicate, i18nc("Duplicate transaction", "Duplicate"), Icon::EditCopy}, {Action::TransactionMatch, &KMyMoneyApp::slotTransactionMatch, i18nc("Button text for match transaction", "Match"),Icon::Empty}, {Action::TransactionAccept, &KMyMoneyApp::slotTransactionsAccept, i18nc("Accept 'imported' and 'matched' transaction", "Accept"), Icon::Empty}, {Action::TransactionToggleReconciled, &KMyMoneyApp::slotToggleReconciliationFlag, i18nc("Toggle reconciliation flag", "Toggle"), Icon::Empty}, {Action::TransactionToggleCleared, &KMyMoneyApp::slotMarkTransactionCleared, i18nc("Mark transaction cleared", "Cleared"), Icon::Empty}, {Action::TransactionReconciled, &KMyMoneyApp::slotMarkTransactionReconciled, i18nc("Mark transaction reconciled", "Reconciled"), Icon::Empty}, {Action::TransactionNotReconciled, &KMyMoneyApp::slotMarkTransactionNotReconciled, i18nc("Mark transaction not reconciled", "Not reconciled"), Icon::Empty}, {Action::TransactionSelectAll, &KMyMoneyApp::selectAllTransactions, i18nc("Select all transactions", "Select all"), Icon::Empty}, {Action::TransactionGoToAccount, &KMyMoneyApp::slotTransactionGotoAccount, i18n("Go to account"), Icon::GoJump}, {Action::TransactionGoToPayee, &KMyMoneyApp::slotTransactionGotoPayee, i18n("Go to payee"), Icon::GoJump}, {Action::TransactionCreateSchedule, &KMyMoneyApp::slotTransactionCreateSchedule, i18n("Create scheduled transaction..."), Icon::AppointmentNew}, {Action::TransactionAssignNumber, &KMyMoneyApp::slotTransactionAssignNumber, i18n("Assign next number"), Icon::Empty}, {Action::TransactionCombine, &KMyMoneyApp::slotTransactionCombine, i18nc("Combine transactions", "Combine"), Icon::Empty}, {Action::TransactionCopySplits, &KMyMoneyApp::slotTransactionCopySplits, i18n("Copy splits"), Icon::Empty}, //Investment {Action::InvestmentNew, &KMyMoneyApp::slotInvestmentNew, i18n("New investment..."), Icon::Empty}, {Action::InvestmentEdit, &KMyMoneyApp::slotInvestmentEdit, i18n("Edit investment..."), Icon::Empty}, {Action::InvestmentDelete, &KMyMoneyApp::slotInvestmentDelete, i18n("Delete investment..."), Icon::Empty}, {Action::InvestmentOnlinePrice, &KMyMoneyApp::slotOnlinePriceUpdate, i18n("Online price update..."), Icon::Empty}, {Action::InvestmentManualPrice, &KMyMoneyApp::slotManualPriceUpdate, i18n("Manual price update..."), Icon::Empty}, //Schedule {Action::ScheduleNew, &KMyMoneyApp::slotScheduleNew, i18n("New scheduled transaction"), Icon::AppointmentNew}, {Action::ScheduleEdit, &KMyMoneyApp::slotScheduleEdit, i18n("Edit scheduled transaction"), Icon::DocumentEdit}, {Action::ScheduleDelete, &KMyMoneyApp::slotScheduleDelete, i18n("Delete scheduled transaction"), Icon::EditDelete}, {Action::ScheduleDuplicate, &KMyMoneyApp::slotScheduleDuplicate, i18n("Duplicate scheduled transaction"), Icon::EditCopy}, {Action::ScheduleEnter, &KMyMoneyApp::slotScheduleEnter, i18n("Enter next transaction..."), Icon::KeyEnter}, {Action::ScheduleSkip, &KMyMoneyApp::slotScheduleSkip, i18n("Skip next transaction..."), Icon::MediaSeekForward}, //Payees {Action::PayeeNew, &KMyMoneyApp::slotPayeeNew, i18n("New payee"), Icon::ListAddUser}, {Action::PayeeRename, &KMyMoneyApp::payeeRename, i18n("Rename payee"), Icon::PayeeRename}, {Action::PayeeDelete, &KMyMoneyApp::slotPayeeDelete, i18n("Delete payee"), Icon::ListRemoveUser}, {Action::PayeeMerge, &KMyMoneyApp::slotPayeeMerge, i18n("Merge payees"), Icon::PayeeMerge}, //Tags {Action::TagNew, &KMyMoneyApp::slotTagNew, i18n("New tag"), Icon::ListAddTag}, {Action::TagRename, &KMyMoneyApp::tagRename, i18n("Rename tag"), Icon::TagRename}, {Action::TagDelete, &KMyMoneyApp::slotTagDelete, i18n("Delete tag"), Icon::ListRemoveTag}, //Budget {Action::BudgetNew, &KMyMoneyApp::slotBudgetNew, i18n("New budget"), Icon::Empty}, {Action::BudgetRename, &KMyMoneyApp::budgetRename, i18n("Rename budget"), Icon::Empty}, {Action::BudgetDelete, &KMyMoneyApp::slotBudgetDelete, i18n("Delete budget"), Icon::Empty}, {Action::BudgetCopy, &KMyMoneyApp::slotBudgetCopy, i18n("Copy budget"), Icon::Empty}, {Action::BudgetChangeYear, &KMyMoneyApp::slotBudgetChangeYear, i18n("Change budget year"), Icon::ViewCalendar}, {Action::BudgetForecast, &KMyMoneyApp::slotBudgetForecast, i18n("Budget based on forecast"), Icon::ViewForecast}, //Currency actions {Action::CurrencyNew, &KMyMoneyApp::slotCurrencyNew, i18n("New currency"), Icon::Empty}, {Action::CurrencyRename, &KMyMoneyApp::currencyRename, i18n("Rename currency"), Icon::EditRename}, {Action::CurrencyDelete, &KMyMoneyApp::slotCurrencyDelete, i18n("Delete currency"), Icon::EditDelete}, {Action::CurrencySetBase, &KMyMoneyApp::slotCurrencySetBase, i18n("Select as base currency"), Icon::KMyMoney}, //Price actions {Action::PriceNew, &KMyMoneyApp::priceNew, i18n("New price..."), Icon::DocumentNew}, {Action::PriceEdit, &KMyMoneyApp::priceEdit, i18n("Edit price..."), Icon::DocumentEdit}, {Action::PriceUpdate, &KMyMoneyApp::priceOnlineUpdate, i18n("Online Price Update..."), Icon::Empty}, {Action::PriceDelete, &KMyMoneyApp::priceDelete, i18n("Delete price..."), Icon::EditDelete}, //debug actions #ifdef KMM_DEBUG {Action::WizardNewUser, &KMyMoneyApp::slotNewFeature, i18n("Test new feature"), Icon::Empty}, {Action::DebugTraces, &KMyMoneyApp::slotToggleTraces, i18n("Debug Traces"), Icon::Empty}, #endif {Action::DebugTimers, &KMyMoneyApp::slotToggleTimers, i18n("Debug Timers"), Icon::Empty}, // onlineJob actions {Action::OnlineJobDelete, &KMyMoneyApp::slotRemoveJob, i18n("Remove credit transfer"), Icon::EditDelete}, {Action::OnlineJobEdit, &KMyMoneyApp::slotEditJob, i18n("Edit credit transfer"), Icon::DocumentEdit}, {Action::OnlineJobLog, &KMyMoneyApp::slotOnlineJobLog, i18n("Show log"), Icon::Empty}, }; foreach (const auto info, actionInfos) { QAction *a = new QAction(0); // KActionCollection::addAction by name sets object name anyways, // so, as better alternative, set it here right from the start a->setObjectName(s_Actions.value(info.action)); a->setText(info.text); if (info.icon != Icon::Empty) // no need to set empty icon a->setIcon(QIcon::fromTheme(g_Icons.value(info.icon))); connect(a, &QAction::triggered, this, info.callback); lutActions.insert(info.action, a); // store QAction's pointer for later processing } } // ************* // Setting some of added actions checkable // ************* { // Some of acitions schould be checkable, // so set them here const QVector checkableActions { Action::ViewTransactionDetail, Action::ViewHideReconciled, Action::ViewHideCategories, #ifdef KMM_DEBUG Action::DebugTraces, #endif Action::ViewShowAll }; foreach (const auto it, checkableActions) lutActions[it]->setCheckable(true); } // ************* // Setting custom icons for some of added actions // ************* { // struct for QAction's icon overlays struct iconOverlayInfo { Action action; Icon icon; Icon overlay; Qt::Corner corner; }; const QVector actionOverlaidIcons { {Action::EditFindTransaction, Icon::ViewFinancialTransfer, Icon::EditFind, Qt::BottomRightCorner}, {Action::InstitutionNew, Icon::ViewBank, Icon::ListAdd, Qt::BottomRightCorner}, {Action::InstitutionEdit, Icon::ViewBank, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InstitutionDelete, Icon::ViewBank, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountNew, Icon::ViewBankAccount, Icon::ListAdd, Qt::TopRightCorner}, {Action::AccountFinishReconciliation, Icon::Merge, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountEdit, Icon::ViewBankAccount, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::AccountDelete, Icon::ViewBankAccount, Icon::EditDelete, Qt::BottomRightCorner}, {Action::AccountClose, Icon::ViewBankAccount, Icon::DialogClose, Qt::BottomRightCorner}, {Action::AccountReopen, Icon::ViewBankAccount, Icon::DialogOK, Qt::BottomRightCorner}, {Action::AccountUpdateMenu, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdate, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountUpdateAll, Icon::ViewBankAccount, Icon::Download, Qt::BottomRightCorner}, {Action::AccountCreditTransfer, Icon::ViewBankAccount, Icon::MailMessageNew, Qt::BottomRightCorner}, {Action::CategoryNew, Icon::ViewFinancialCategories, Icon::ListAdd, Qt::TopRightCorner}, {Action::CategoryEdit, Icon::ViewFinancialCategories, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::CategoryDelete, Icon::ViewFinancialCategories, Icon::EditDelete, Qt::BottomRightCorner}, {Action::ToolUpdatePrices, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::TransactionNew, Icon::ViewFinancialTransfer, Icon::ListAdd, Qt::TopRightCorner}, {Action::TransactionEdit, Icon::ViewFinancialTransfer, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::TransactionMatch, Icon::ViewFinancialTransfer, Icon::DocumentImport, Qt::BottomRightCorner}, {Action::TransactionAccept, Icon::ViewFinancialTransfer, Icon::DialogOKApply, Qt::BottomRightCorner}, {Action::InvestmentNew, Icon::ViewInvestment, Icon::ListAdd, Qt::TopRightCorner}, {Action::InvestmentEdit, Icon::ViewInvestment, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::InvestmentDelete, Icon::ViewInvestment, Icon::EditDelete, Qt::BottomRightCorner}, {Action::InvestmentOnlinePrice, Icon::ViewInvestment, Icon::Download, Qt::BottomRightCorner}, {Action::BudgetNew, Icon::ViewTimeScheduleCalculus, Icon::ListAdd, Qt::TopRightCorner}, {Action::BudgetRename, Icon::ViewTimeScheduleCalculus, Icon::DocumentEdit, Qt::BottomRightCorner}, {Action::BudgetDelete, Icon::ViewTimeScheduleCalculus, Icon::EditDelete, Qt::BottomRightCorner}, {Action::BudgetCopy, Icon::ViewTimeScheduleCalculus, Icon::EditCopy, Qt::BottomRightCorner}, {Action::PriceUpdate, Icon::ViewCurrencyList, Icon::Download, Qt::BottomRightCorner} }; for(const auto& it : actionOverlaidIcons) lutActions[it.action]->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[it.icon], g_Icons[it.overlay], it.corner)); } // ************* // Setting keyboard shortcuts for some of added actions // ************* { const QVector> actionShortcuts { {qMakePair(Action::EditFindTransaction, Qt::CTRL + Qt::Key_F)}, {qMakePair(Action::ViewTransactionDetail, Qt::CTRL + Qt::Key_T)}, {qMakePair(Action::ViewHideReconciled, Qt::CTRL + Qt::Key_R)}, {qMakePair(Action::ViewHideCategories, Qt::CTRL + Qt::Key_U)}, {qMakePair(Action::ViewShowAll, Qt::CTRL + Qt::SHIFT + Qt::Key_A)}, {qMakePair(Action::AccountStartReconciliation, Qt::CTRL + Qt::SHIFT + Qt::Key_R)}, {qMakePair(Action::TransactionNew, Qt::CTRL + Qt::Key_Insert)}, {qMakePair(Action::TransactionToggleReconciled, Qt::CTRL + Qt::Key_Space)}, {qMakePair(Action::TransactionToggleCleared, Qt::CTRL + Qt::Key_Alt + Qt::Key_Space)}, {qMakePair(Action::TransactionReconciled, Qt::CTRL + Qt::Key_Shift + Qt::Key_Space)}, {qMakePair(Action::TransactionSelectAll, Qt::CTRL + Qt::Key_A)}, #ifdef KMM_DEBUG {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif {qMakePair(Action::TransactionAssignNumber, Qt::CTRL + Qt::Key_Shift + Qt::Key_N)} }; for(const auto& it : actionShortcuts) aC->setDefaultShortcut(lutActions[it.first], it.second); } // ************* // Misc settings // ************* connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); // Setup transaction detail switch lutActions[Action::ViewTransactionDetail]->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); lutActions[Action::ViewHideReconciled]->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); lutActions[Action::ViewHideCategories]->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); lutActions[Action::ViewShowAll]->setChecked(false); // ************* // Adding actions to ActionCollection // ************* actionCollection()->addActions(lutActions.values()); // ************************ // 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 // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); // reconnect about app entry to dialog with full credits information QAction *aboutApp = aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::AboutApp))); aboutApp->disconnect(); connect(aboutApp, &QAction::triggered, this, &KMyMoneyApp::slotShowCredits); QMenu *menuContainer; menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentImport])); menuContainer = static_cast(factory()->container(QStringLiteral("export"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentExport])); } void KMyMoneyApp::connectActionsAndViews() { KOnlineJobOutbox *const outbox = d->m_myMoneyView->getOnlineJobOutbox(); Q_CHECK_PTR(outbox); QAction *const onlineJob_delete = actionCollection()->action(s_Actions[Action::OnlineJobDelete]); Q_CHECK_PTR(onlineJob_delete); connect(onlineJob_delete, SIGNAL(triggered()), outbox, SLOT(slotRemoveJob())); QAction *const onlineJob_edit = actionCollection()->action(s_Actions[Action::OnlineJobEdit]); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } #ifdef KMM_DEBUG void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); foreach (const auto it, list) std::cout << qPrintable(it->objectName()) << ": " << qPrintable(it->text()) << std::endl; } #endif bool KMyMoneyApp::isActionToggled(const Action _a) { return actionCollection()->action(s_Actions[_a])->isChecked(); } void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel; statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar; 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", actionCollection()->action(KStandardAction::name(KStandardAction::ShowStatusbar))->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"); actionCollection()->action(s_Actions[Action::ViewHideReconciled])->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); actionCollection()->action(s_Actions[Action::ViewHideCategories])->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.")); QString prevDir = readLastUsedDir(); QPointer dialog = new QFileDialog(this, QString(), prevDir, 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.compare(QLatin1String("*.kmy"), Qt::CaseInsensitive) != 0) { 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...")); // 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); } } } } QString prevDir; // don't prompt file name if not a native file if (d->m_myMoneyView->isNativeFile()) prevDir = readLastUsedDir(); QPointer dlg = new QFileDialog(this, i18n("Save As"), prevDir, QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.kmy")).arg(i18nc("KMyMoney (Filefilter)", "KMyMoney files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.xml")).arg(i18nc("XML (Filefilter)", "XML files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*.anon.xml")).arg(i18nc("Anonymous (Filefilter)", "Anonymous files")) + QString(QLatin1String("%2 (%1);;")).arg(QStringLiteral("*")).arg(i18nc("All files (Filefilter)", "All files"))); dlg->setAcceptMode(QFileDialog::AcceptSave); connect(dlg, SIGNAL(filterChanged(QString)), this, SLOT(slotFileSaveAsFilterChanged(QString))); if (dlg->exec() == QDialog::Accepted && dlg != 0) { QUrl newURL = dlg->selectedUrls().first(); if (!newURL.fileName().isEmpty()) { d->consistencyCheck(false); // 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; QString newName = newURL.toDisplayString(QUrl::PreferLocalFile); // append extension if not present if (!newName.endsWith(QLatin1String(".kmy"), Qt::CaseInsensitive) && !newName.endsWith(QLatin1String(".xml"), Qt::CaseInsensitive)) newName.append(QLatin1String(".kmy")); newURL = QUrl::fromUserInput(newName); d->m_recentFiles->addUrl(newURL); 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.endsWith(QLatin1String(".anon.xml"), Qt::CaseInsensitive)) rc = d->m_myMoneyView->saveFile(newURL); else { d->m_fileName = newURL; QString encryptionKeys; QRegExp keyExp(".* \\((.*)\\)"); if (keyExp.indexIn(selectedKeyName) != -1) { encryptionKeys = keyExp.cap(1); } if (!d->m_additionalGpgKeys.isEmpty()) { if (!encryptionKeys.isEmpty()) encryptionKeys.append(QLatin1Char(',')); encryptionKeys.append(d->m_additionalGpgKeys.join(QLatin1Char(','))); } rc = d->m_myMoneyView->saveFile(d->m_fileName, encryptionKeys); //write the directory used for this file as the default one for next time. writeLastUsedDir(newURL.toDisplayString(QUrl::RemoveFilename | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); writeLastUsedFile(newName); } d->m_autoSaveTimer->stop(); setEnabled(true); } } delete dlg; updateCaption(); return rc; } void KMyMoneyApp::slotSaveAsDatabase() { saveAsDatabase(); } bool KMyMoneyApp::saveAsDatabase() { 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::slotShowTransactionDetail() { } void KMyMoneyApp::slotHideReconciledTransactions() { KMyMoneyGlobalSettings::setHideReconciledTransactions(actionCollection()->action(s_Actions[Action::ViewHideReconciled])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { KMyMoneyGlobalSettings::setHideUnusedCategory(actionCollection()->action(s_Actions[Action::ViewHideCategories])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotShowAllAccounts() { d->m_myMoneyView->slotRefreshViews(); } #ifdef KMM_DEBUG void KMyMoneyApp::slotToggleTraces() { MyMoneyTracer::onOff(actionCollection()->action(s_Actions[Action::DebugTraces])->isChecked() ? 1 : 0); } #endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp timersOn = actionCollection()->action(s_Actions[Action::DebugTimers])->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))) { QPointer dlg = new KTemplateExportDlg(this); if (dlg->exec() == QDialog::Accepted && dlg) { MyMoneyTemplate templ; templ.setTitle(dlg->title()); templ.setShortDescription(dlg->shortDescription()); templ.setLongDescription(dlg->longDescription()); templ.exportTemplate(&progressCallback); templ.saveTemplate(QUrl::fromLocalFile(newName)); } delete dlg; } } } 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 if (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 silent) { bool result = false; // keep a copy of the statement if (KMyMoneySettings::logImportedStatements()) { QString logFile = QString("%1/kmm-statement-%2.txt").arg(KMyMoneySettings::logPath()).arg(d->m_statementXMLindex++); MyMoneyStatement::writeXMLFile(s, logFile); } // 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 && !silent) 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; } 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::slotShowCredits() { KAboutData aboutData = initializeCreditsData(); KAboutApplicationDialog dlg(aboutData, this); dlg.exec(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); LedgerSeperator::setFirstFiscalDate(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")); } } } } } void KMyMoneyApp::slotBackupFile() { // 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); #ifdef Q_OS_WIN backupDlg->mountCheckBox->setEnabled(false); #endif 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) { slotBackupMount(); } else { progressCallback(0, 300, ""); #ifdef Q_OS_WIN d->m_ignoreBackupExitCode = true; QTimer::singleShot(0, this, SLOT(slotBackupHandleEvents())); #else // If we don't have to mount a device, we just issue // a dummy command to start the copy operation d->m_proc.setProgram("true"); d->m_proc.start(); #endif } } delete backupDlg; #endif } void KMyMoneyApp::slotBackupMount() { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } bool KMyMoneyApp::slotBackupWriteFile() { 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 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) { return false; } } progressCallback(50, 0, i18n("Writing %1", backupfile)); d->m_proc.clearProgram(); #ifdef Q_OS_WIN d->m_proc << "cmd.exe" << "/c" << "copy" << "/b" << "/y"; d->m_proc << (QDir::toNativeSeparators(d->m_fileName.toLocalFile()) + "+ nul") << QDir::toNativeSeparators(backupfile); #else d->m_proc << "cp" << "-f"; d->m_proc << d->m_fileName.toLocalFile() << backupfile; #endif d->m_backupState = BACKUP_COPYING; d->m_proc.start(); return true; } void KMyMoneyApp::slotBackupUnmount() { 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(); } void KMyMoneyApp::slotBackupFinish() { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } void KMyMoneyApp::slotBackupHandleEvents() { // TODO: port KF5 #if 0 switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_ignoreBackupExitCode || (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0)) { d->m_ignoreBackupExitCode = false; d->m_backupResult = 0; if (!slotBackupWriteFile()) { d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) slotBackupUnmount(); else slotBackupFinish(); } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { slotBackupUnmount(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); slotBackupFinish(); } } else { qDebug("copy 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) slotBackupUnmount(); else slotBackupFinish(); } 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")); } slotBackupFinish(); 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::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(this); 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() { slotInstitutionEdit(MyMoneyInstitution()); } 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())); } } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); try { 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->createAccount(newAccount, parentAccount, brokerageAccount, openingBal); } 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(); MyMoneyFile::instance()->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()); - } + QPointer dlg = new KNewInvestmentWizard(this); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(d->m_selectedAccount.id()); + delete dlg; } void KMyMoneyApp::slotInvestmentEdit() { - KNewInvestmentWizard dlg(d->m_selectedInvestment); - if (dlg.exec() == QDialog::Accepted) { - dlg.createObjects(d->m_selectedAccount.id()); - } + QPointer dlg = new KNewInvestmentWizard(d->m_selectedInvestment); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(d->m_selectedAccount.id()); + delete dlg; } 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 { + d->m_selectedAccount = MyMoneyAccount(); // CAUTION: deleting equity from investments view needs this, if ID of the equity to be deleted is the smallest from all 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) { 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; try { MyMoneySchedule sch = file->schedule(d->m_selectedAccount.value("schedule").toLatin1()); } catch (const MyMoneyException &e) { qDebug() << "schedule" << d->m_selectedAccount.value("schedule").toLatin1() << "not found"; } if (!(d->m_selectedAccount == wizard->account()) || !(sch == wizard->schedule())) { MyMoneyFileTransaction ft; try { file->modifyAccount(wizard->account()); if (!sch.id().isEmpty()) { sch = wizard->schedule(); } try { file->schedule(sch.id()); file->modifySchedule(sch); ft.commit(); } catch (const MyMoneyException &) { try { if(sch.transaction().splitCount() >= 2) { 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() { slotAccountOpen(MyMoneyAccount()); } 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) { QAction *a = actionCollection()->action(s_Actions[Action::AccountClose]); switch (canCloseAccount(acc)) { case KMyMoneyUtils::AccountCanClose: { a->setEnabled(true); break; } case KMyMoneyUtils::AccountBalanceNonZero: { a->setEnabled(false); a->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); break; } case KMyMoneyUtils::AccountChildrenOpen: { a->setEnabled(false); a->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); break; } case KMyMoneyUtils::AccountScheduleReference: { a->setEnabled(false); a->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->actionCollection()->action(s_Actions[Action::ScheduleDuplicate])->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->actionCollection()->action(s_Actions[Action::TransactionCancel])->setEnabled(true); kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->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(), KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); 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, budget.budgetStart().month(), budget.budgetStart().day()); 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->actionCollection()->action(s_Actions[Action::TransactionDelete])->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->actionCollection()->action(s_Actions[Action::TransactionDuplicate])->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->actionCollection()->action(s_Actions[Action::TransactionNew])->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->actionCollection()->action(s_Actions[Action::TransactionEdit])->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->actionCollection()->action(s_Actions[Action::TransactionEditSplits])->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->actionCollection()->action(s_Actions[Action::TransactionCancel])->isEnabled()) { // make sure, we block the enter function actionCollection()->action(s_Actions[Action::TransactionEnter])->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->actionCollection()->action(s_Actions[Action::TransactionEnter])->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->actionCollection()->action(s_Actions[Action::TransactionEnter])->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->actionCollection()->action(s_Actions[Action::TransactionEnter])->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->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) { noGuiItem.setEnabled(false); noGuiItem.setToolTip(kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->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 = actionCollection()->action(s_Actions[Action::TransactionMatch])->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(); const bool fileOpen = d->m_myMoneyView->fileOpen(); const bool modified = file->dirty(); const bool importRunning = (d->m_smtReader != 0); QWidget* w; KActionCollection *aC = actionCollection(); // ************* // Disabling actions to be disabled at this point // ************* { static const QVector disabledActions { Action::AccountStartReconciliation, Action::AccountFinishReconciliation, Action::AccountPostponeReconciliation, Action::AccountEdit, Action::AccountDelete, Action::AccountOpen, Action::AccountClose, Action::AccountReopen, Action::AccountTransactionReport, Action::AccountOnlineMap, Action::AccountOnlineUnmap, Action::AccountUpdate, Action::AccountUpdateAll, Action::AccountBalanceChart, Action::CategoryEdit, Action::CategoryDelete, Action::InstitutionEdit, Action::InstitutionDelete, Action::InvestmentEdit, Action::InvestmentNew, Action::InvestmentDelete, Action::InvestmentOnlinePrice, Action::InvestmentManualPrice, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleEnter, Action::ScheduleSkip, Action::PayeeDelete, Action::PayeeRename, Action::PayeeMerge, Action::TagDelete, Action::TagRename, Action::BudgetDelete, Action::BudgetRename, Action::BudgetChangeYear, Action::BudgetNew, Action::BudgetCopy, Action::BudgetForecast, Action::TransactionEdit, Action::TransactionEditSplits, Action::TransactionEnter, Action::TransactionCancel, Action::TransactionDelete, Action::TransactionMatch, Action::TransactionAccept, Action::TransactionDuplicate, Action::TransactionToggleReconciled, Action::TransactionToggleCleared, Action::TransactionGoToAccount, Action::TransactionGoToPayee, Action::TransactionAssignNumber, Action::TransactionCreateSchedule, Action::TransactionCombine, Action::TransactionSelectAll, Action::TransactionCopySplits, Action::ScheduleEdit, Action::ScheduleDelete, Action::ScheduleDuplicate, Action::ScheduleEnter, Action::ScheduleSkip, Action::CurrencyRename, Action::CurrencyDelete, Action::CurrencySetBase, Action::PriceEdit, Action::PriceDelete, Action::PriceUpdate }; foreach (const auto a, disabledActions) aC->action(s_Actions.value(a))->setEnabled(false); } // ************* // Disabling actions based on conditions // ************* { QString tooltip = i18n("Create a new transaction"); const QVector> actionStates { {qMakePair(Action::FileOpenDatabase, true)}, {qMakePair(Action::FileSaveAsDatabase, fileOpen)}, {qMakePair(Action::FilePersonalData, fileOpen)}, {qMakePair(Action::FileBackup, (fileOpen && !d->m_myMoneyView->isDatabase()))}, {qMakePair(Action::FileInformation, fileOpen)}, {qMakePair(Action::FileImportGNC, !importRunning)}, {qMakePair(Action::FileImportTemplate, fileOpen && !importRunning)}, {qMakePair(Action::FileExportTemplate, fileOpen && !importRunning)}, #ifdef KMM_DEBUG {qMakePair(Action::FileDump, fileOpen)}, #endif {qMakePair(Action::EditFindTransaction, fileOpen)}, {qMakePair(Action::ToolCurrencies, fileOpen)}, {qMakePair(Action::ToolPrices, fileOpen)}, {qMakePair(Action::ToolUpdatePrices, fileOpen)}, {qMakePair(Action::ToolConsistency, fileOpen)}, {qMakePair(Action::AccountNew, fileOpen)}, {qMakePair(Action::AccountCreditTransfer, onlineJobAdministration::instance()->canSendCreditTransfer())}, {qMakePair(Action::CategoryDelete, fileOpen)}, {qMakePair(Action::InstitutionNew, fileOpen)}, {qMakePair(Action::TransactionNew, (fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)))}, {qMakePair(Action::ScheduleNew, fileOpen)}, {qMakePair(Action::CurrencyNew, fileOpen)}, {qMakePair(Action::PriceNew, fileOpen)}, }; foreach (const auto a, actionStates) aC->action(s_Actions.value(a.first))->setEnabled(a.second); } // ************* // Disabling standard actions based on conditions // ************* aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Save)))->setEnabled(modified && !d->m_myMoneyView->isDatabase()); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::SaveAs)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Close)))->setEnabled(fileOpen); aC->action(QString::fromLatin1(KStandardAction::name(KStandardAction::Print)))->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for match transaction", "Match")); aC->action(s_Actions[Action::TransactionNew])->setToolTip(i18n("Create a new transaction")); w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(false); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(false); // ************* // Enabling actions based on conditions // ************* // FIXME for now it's always on, but we should only allow it, if we // can select at least a single transaction aC->action(s_Actions[Action::TransactionSelectAll])->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()); } aC->action(s_Actions[Action::TransactionDelete])->setEnabled(enable); if (!d->m_transactionEditor) { QString tooltip = i18n("Duplicate the current selected transactions"); aC->action(s_Actions[Action::TransactionDuplicate])->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); aC->action(s_Actions[Action::TransactionDuplicate])->setToolTip(tooltip); if (d->m_myMoneyView->canEditTransactions(d->m_selectedTransactions, tooltip)) { aC->action(s_Actions[Action::TransactionEdit])->setEnabled(true); // editing splits is allowed only if we have one transaction selected if (d->m_selectedTransactions.count() == 1) { aC->action(s_Actions[Action::TransactionEditSplits])->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) { aC->action(s_Actions[Action::TransactionCreateSchedule])->setEnabled(d->m_selectedTransactions.count() == 1); } } aC->action(s_Actions[Action::TransactionEdit])->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(true); } w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(true); w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected aC->action(s_Actions[Action::TransactionToggleCleared])->setEnabled(true); aC->action(s_Actions[Action::TransactionReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionNotReconciled])->setEnabled(true); aC->action(s_Actions[Action::TransactionToggleReconciled])->setEnabled(true); if (!d->m_accountGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToAccount])->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) aC->action(s_Actions[Action::TransactionGoToPayee])->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 /* && aC->action(s_Actions[Action::TransactionEdit])->isEnabled() */) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); } if (importedCount != 0 || matchedCount != 0) aC->action(s_Actions[Action::TransactionAccept])->setEnabled(true); if (matchedCount != 0) { aC->action(s_Actions[Action::TransactionMatch])->setEnabled(true); aC->action(s_Actions[Action::TransactionMatch])->setText(i18nc("Button text for unmatch transaction", "Unmatch")); aC->action(s_Actions[Action::TransactionMatch])->setIcon(QIcon("process-stop")); } if (d->m_selectedTransactions.count() > 1) { aC->action(s_Actions[Action::TransactionCombine])->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) { aC->action(s_Actions[Action::TransactionCopySplits])->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) { aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } } else { aC->action(s_Actions[Action::TransactionAssignNumber])->setEnabled(d->m_transactionEditor->canAssignNumber()); aC->action(s_Actions[Action::TransactionNew])->setEnabled(false); aC->action(s_Actions[Action::TransactionDelete])->setEnabled(false); QString reason; aC->action(s_Actions[Action::TransactionEnter])->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 aC->action(s_Actions[Action::TransactionEnter])->setToolTip(reason); aC->action(s_Actions[Action::TransactionCancel])->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) { aC->action(s_Actions[Action::AccountUpdateAll])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->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: aC->action(s_Actions[Action::AccountTransactionReport])->setEnabled(true); aC->action(s_Actions[Action::AccountEdit])->setEnabled(true); aC->action(s_Actions[Action::AccountDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); if (d->m_selectedAccount.accountGroup() != MyMoneyAccount::Equity) { if (d->m_reconciliationAccount.id().isEmpty()) { aC->action(s_Actions[Action::AccountStartReconciliation])->setEnabled(true); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(i18n("Reconcile")); } else { QString tip; tip = i18n("Reconcile - disabled because you are currently reconciling %1", d->m_reconciliationAccount.name()); aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(tip); if (!d->m_transactionEditor) { aC->action(s_Actions[Action::AccountFinishReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); aC->action(s_Actions[Action::AccountPostponeReconciliation])->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); } } } if (d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) aC->action(s_Actions[Action::InvestmentNew])->setEnabled(true); if (d->m_selectedAccount.isClosed()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { aC->action(s_Actions[Action::AccountOnlineUnmap])->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) { aC->action(s_Actions[Action::AccountUpdate])->setEnabled(true); aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } else { aC->action(s_Actions[Action::AccountOnlineMap])->setEnabled(d->m_onlinePlugins.count() > 0); } aC->action(s_Actions[Action::AccountBalanceChart])->setEnabled(true); break; case MyMoneyAccount::Income : case MyMoneyAccount::Expense : aC->action(s_Actions[Action::CategoryEdit])->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); aC->action(s_Actions[Action::CategoryDelete])->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); aC->action(s_Actions[Action::AccountOpen])->setEnabled(true); break; default: break; } } } if (!d->m_selectedInstitution.id().isEmpty()) { aC->action(s_Actions[Action::InstitutionEdit])->setEnabled(true); aC->action(s_Actions[Action::InstitutionDelete])->setEnabled(!file->isReferenced(d->m_selectedInstitution)); } if (!d->m_selectedInvestment.id().isEmpty()) { aC->action(s_Actions[Action::InvestmentEdit])->setEnabled(true); aC->action(s_Actions[Action::InvestmentDelete])->setEnabled(!file->isReferenced(d->m_selectedInvestment)); aC->action(s_Actions[Action::InvestmentManualPrice])->setEnabled(true); try { MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedInvestment.currencyId()); if (!security.value("kmm-online-source").isEmpty()) aC->action(s_Actions[Action::InvestmentOnlinePrice])->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()) aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { aC->action(s_Actions[Action::ScheduleEdit])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDuplicate])->setEnabled(true); aC->action(s_Actions[Action::ScheduleDelete])->setEnabled(!file->isReferenced(d->m_selectedSchedule)); if (!d->m_selectedSchedule.isFinished()) { aC->action(s_Actions[Action::ScheduleEnter])->setEnabled(true); // a schedule with a single occurrence cannot be skipped if (d->m_selectedSchedule.occurrence() != MyMoneySchedule::OCCUR_ONCE) { aC->action(s_Actions[Action::ScheduleSkip])->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { aC->action(s_Actions[Action::PayeeRename])->setEnabled(d->m_selectedPayees.count() == 1); aC->action(s_Actions[Action::PayeeMerge])->setEnabled(d->m_selectedPayees.count() > 1); aC->action(s_Actions[Action::PayeeDelete])->setEnabled(true); } if (d->m_selectedTags.count() >= 1) { aC->action(s_Actions[Action::TagRename])->setEnabled(d->m_selectedTags.count() == 1); aC->action(s_Actions[Action::TagDelete])->setEnabled(true); } if (d->m_selectedBudgets.count() >= 1) { aC->action(s_Actions[Action::BudgetDelete])->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { aC->action(s_Actions[Action::BudgetChangeYear])->setEnabled(true); aC->action(s_Actions[Action::BudgetCopy])->setEnabled(true); aC->action(s_Actions[Action::BudgetRename])->setEnabled(true); aC->action(s_Actions[Action::BudgetForecast])->setEnabled(true); } } if (!d->m_selectedCurrency.id().isEmpty()) { aC->action(s_Actions[Action::CurrencyRename])->setEnabled(true); // no need to check each transaction. accounts are enough in this case skip.fill(false); skip.setBit(IMyMoneyStorage::RefCheckTransaction); aC->action(s_Actions[Action::CurrencyDelete])->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) aC->action(s_Actions[Action::CurrencySetBase])->setEnabled(true); } if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != QLatin1String("KMyMoney")) { aC->action(s_Actions[Action::PriceEdit])->setEnabled(true); aC->action(s_Actions[Action::PriceDelete])->setEnabled(true); //enable online update if it is a currency MyMoneySecurity security = MyMoneyFile::instance()->security(d->m_selectedPrice.from()); aC->action(s_Actions[Action::PriceUpdate])->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() { slotSelectCurrency(MyMoneySecurity()); } void KMyMoneyApp::slotSelectCurrency(const MyMoneySecurity& currency) { d->m_selectedCurrency = currency; slotUpdateActions(); emit currencySelected(d->m_selectedCurrency); } void KMyMoneyApp::slotSelectPrice() { slotSelectPrice(MyMoneyPrice()); } 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("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->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("&(?!&)"), "&&"); actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->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()) actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) actionCollection()->action(s_Actions[Action::TransactionGoToAccount])->setText(i18n("Go to account")); } void KMyMoneyApp::slotSelectInstitution() { slotSelectInstitution(MyMoneyInstitution()); } 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() { slotSelectAccount(MyMoneyAccount()); } 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() { slotSelectInvestment(MyMoneyAccount()); } 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() { slotSelectSchedule(MyMoneySchedule()); } 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); } void KMyMoneyApp::unloadPlugins() { Q_ASSERT(d->m_pluginLoader); delete d->m_pluginLoader; } 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); QString accId; 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)) { if(accId.isEmpty()) { accId = (*it_a).id(); } } if (accountKvpValue == value || onlineSettingsKvpValue == value) { accId = (*it_a).id(); break; } } // return the account found or an empty element return MyMoneyFile::instance()->account(accId); } 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; } QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->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; QVector disabledActions {Action::AccountUpdate, Action::AccountUpdateMenu, Action::AccountUpdateAll}; foreach (const auto a, disabledActions) actionCollection()->action(s_Actions.value(a))->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::slotRemoveJob() { } void KMyMoneyApp::slotEditJob() { } 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(KMyMoneySettings::logPath(), "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); d.remove(KMyMoneySettings::logPath() + QString("/%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/models/CMakeLists.txt b/kmymoney/models/CMakeLists.txt index a5f40e878..6d106b022 100644 --- a/kmymoney/models/CMakeLists.txt +++ b/kmymoney/models/CMakeLists.txt @@ -1,26 +1,28 @@ set(libmodels_a_SOURCES ledgermodel.cpp costcentermodel.cpp payeesmodel.cpp accountsmodel.cpp onlinejobmodel.cpp onlinebankingaccountsfilterproxymodel.cpp models.cpp payeeidentifiercontainermodel.cpp onlinejobmessagesmodel.cpp + equitiesmodel.cpp + securitiesmodel.cpp ) if (USE_MODELTEST) add_definitions( -DKMM_MODELTEST ) set(libmodels_a_SOURCES ${libmodels_a_SOURCES} modeltest.cpp) endif(USE_MODELTEST) add_library(models STATIC ${libmodels_a_SOURCES}) target_link_libraries(models LINK_PUBLIC kmm_mymoney kmymoney_common KF5::ItemModels ) add_dependencies(models kmm_config) diff --git a/kmymoney/models/equitiesmodel.cpp b/kmymoney/models/equitiesmodel.cpp new file mode 100644 index 000000000..ba50b234c --- /dev/null +++ b/kmymoney/models/equitiesmodel.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + equitiesmodel.cpp + ------------------- + copyright : (C) 2017 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 "equitiesmodel.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +class EquitiesModel::Private +{ +public: + Private() : m_file(MyMoneyFile::instance()) + { + QVector columns {Column::Equity, Column::Symbol, Column::Value, + Column::Quantity, Column::Price}; + foreach (auto const column, columns) + m_columns.append(column); + } + + ~Private() {} + + void loadInvestmentAccount(QStandardItem *node, const MyMoneyAccount &invAcc) + { + auto itInvAcc = new QStandardItem(invAcc.name()); + node->appendRow(itInvAcc); // investment account is meant to be added under root item + itInvAcc->setEditable(false); + itInvAcc->setColumnCount(m_columns.count()); + setAccountData(node, itInvAcc->row(), invAcc, m_columns); + + const auto strStkAccList = invAcc.accountList(); // only stock or bond accounts are expected here + foreach (const auto strStkAcc, strStkAccList) { + auto stkAcc = m_file->account(strStkAcc); + auto itStkAcc = new QStandardItem(strStkAcc); + itStkAcc->setEditable(false); + itInvAcc->appendRow(itStkAcc); + setAccountData(itInvAcc, itStkAcc->row(), stkAcc, m_columns); + } + } + + void setAccountData(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Equity); + if (colNum == -1) + return; + + // Equity + getCell(colNum); + if (columns.contains(Column::Equity)) { + cell->setData(account.name(), Qt::DisplayRole); + cell->setData(account.id(), Role::EquityID); + cell->setData(account.currencyId(), Role::SecurityID); + } + + if (account.accountType() == MyMoneyAccount::Investment) // investments accounts are not meant to be displayed, so stop here + return; + + // Symbol + if (columns.contains(Column::Symbol)) { + colNum = m_columns.indexOf(Column::Symbol); + if (colNum != -1) { + auto security = m_file->security(account.currencyId()); + getCell(colNum); + cell->setData(security.tradingSymbol(), Qt::DisplayRole); + } + } + + setAccountBalanceAndValue(node, row, account, columns); + } + + void setAccountBalanceAndValue(QStandardItem *node, const int row, const MyMoneyAccount &account, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Equity); + if (colNum == -1) + return; + + auto balance = m_file->balance(account.id()); + auto security = m_file->security(account.currencyId()); + auto tradingCurrency = m_file->security(security.tradingCurrency()); + auto price = m_file->price(account.currencyId(), tradingCurrency.id()); + + // Value + if (columns.contains(Column::Value)) { + colNum = m_columns.indexOf(Column::Value); + if (colNum != -1) { + getCell(colNum); + if (price.isValid()) { + auto prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction()); + auto value = balance * price.rate(tradingCurrency.id()); + auto strValue = QVariant(value.formatMoney(tradingCurrency.tradingSymbol(), prec)); + cell->setData(strValue, Qt::DisplayRole); + } else { + cell->setData(QVariant("---"), Qt::DisplayRole); + } + cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); + } + } + + // Quantity + if (columns.contains(Column::Quantity)) { + colNum = m_columns.indexOf(Column::Quantity); + if (colNum != -1) { + getCell(colNum); + auto prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); + auto strQuantity = QVariant(balance.formatMoney(QString(), prec)); + cell->setData(strQuantity, Qt::DisplayRole); + } + } + + // Price + if (columns.contains(Column::Price)) { + colNum = m_columns.indexOf(Column::Price); + if (colNum != -1) { + getCell(colNum); + if (price.isValid()) { + auto prec = security.pricePrecision(); + auto strPrice = QVariant(price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec)); + cell->setData(strPrice, Qt::DisplayRole); + } else { + cell->setData(QVariant("---"), Qt::DisplayRole); + } + cell->setData(QVariant(Qt::AlignRight | Qt::AlignVCenter), Qt::TextAlignmentRole); + } + } + } + + QStandardItem *itemFromId(QStandardItemModel *model, const QString &id, const Role role) + { + const auto itemList = model->match(model->index(0, 0), role, QVariant(id), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); + if (!itemList.isEmpty()) + return model->itemFromIndex(itemList.first()); + return nullptr; + } + + MyMoneyFile *m_file; + QList m_columns; +}; + +EquitiesModel::EquitiesModel(QObject *parent) + : QStandardItemModel(parent), d(new Private) +{ + init(); +} + +EquitiesModel::~EquitiesModel() +{ + delete d; +} + +void EquitiesModel::init() +{ + QStringList headerLabels; + foreach (const auto column, d->m_columns) + headerLabels.append(getHeaderName(column)); + setHorizontalHeaderLabels(headerLabels); +} + +void EquitiesModel::load() +{ + this->blockSignals(true); + + auto rootItem = invisibleRootItem(); + QList accList; + d->m_file->accountList(accList); // get all available accounts + foreach (const auto acc, accList) + if (acc.accountType() == MyMoneyAccount::Investment) // but add only investment accounts (and its children) to the model + d->loadInvestmentAccount(rootItem, acc); + + this->blockSignals(false); +} + +/** + * Notify the model that an object has been added. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + // check whether change is about accounts + if (objType != MyMoneyFile::notifyAccount) + return; + + // check whether change is about either investment or stock account + const auto acc = dynamic_cast(obj); + if (!acc || + (acc->accountType() != MyMoneyAccount::Investment && + acc->accountType() != MyMoneyAccount::Stock)) + return; + auto itAcc = d->itemFromId(this, acc->id(), Role::EquityID); + + QStandardItem *itParentAcc; + if (acc->accountType() == MyMoneyAccount::Investment) // if it's investment account then its parent is root item + itParentAcc = invisibleRootItem(); + else // otherwise it's stock account and its parent is investment account + itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID); + + // if account doesn't exist in model then add it + if (!itAcc) { + itAcc = new QStandardItem(acc->name()); + itParentAcc->appendRow(itAcc); + itAcc->setEditable(false); + } + + d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns); +} + +/** + * Notify the model that an object has been modified. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + const MyMoneyAccount *acc; + QStandardItem *itAcc; + switch (objType) { + case MyMoneyFile::notifyAccount: + { + auto tmpAcc = dynamic_cast(obj); + if (!tmpAcc || tmpAcc->accountType() != MyMoneyAccount::Stock) + return; + acc = tmpAcc; + itAcc = d->itemFromId(this, acc->id(), Role::EquityID); + break; + } + case MyMoneyFile::notifySecurity: + { + auto sec = dynamic_cast(obj); + itAcc = d->itemFromId(this, sec->id(), Role::SecurityID); + if (!itAcc) + return; + const auto idAcc = itAcc->data(Role::EquityID).toString(); + acc = &d->m_file->account(idAcc); + break; + } + default: + return; + } + + auto itParentAcc = d->itemFromId(this, acc->parentAccountId(), Role::InvestmentID); + + auto modelID = itParentAcc->data(Role::InvestmentID).toString(); // get parent account from model + if (modelID == acc->parentAccountId()) { // and if it matches with those from file then modify only + d->setAccountData(itParentAcc, itAcc->row(), *acc, d->m_columns); + } else { // and if not then reparent + slotObjectRemoved(MyMoneyFile::notifyAccount, acc->id()); + slotObjectAdded(MyMoneyFile::notifyAccount, obj); + } +} + +/** + * Notify the model that an object has been removed. An action is performed only if the object is an account. + * + */ +void EquitiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id) +{ + if (objType != MyMoneyFile::notifyAccount) + return; + + const auto indexList = match(index(0, 0), Role::EquityID, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); + foreach (const auto index, indexList) + removeRow(index.row(), index.parent()); +} + +/** + * Notify the model that the account balance has been changed. + */ +void EquitiesModel::slotBalanceOrValueChanged(const MyMoneyAccount &account) +{ + if (account.accountType() != MyMoneyAccount::Stock) + return; + + const auto itAcc = d->itemFromId(this, account.id(), Role::EquityID); + if (!itAcc) + return; + d->setAccountBalanceAndValue(itAcc->parent(), itAcc->row(), account, d->m_columns); +} + +auto EquitiesModel::getColumns() +{ + return &d->m_columns; +} + +QString EquitiesModel::getHeaderName(const Column column) +{ + switch(column) { + case Equity: + return i18n("Equity"); + case Symbol: + return i18n("Symbol"); + case Value: + return i18n("Value"); + case Quantity: + return i18n("Quantity"); + case Price: + return i18n("Price"); + default: + return QString(); + } +} + +class EquitiesFilterProxyModel::Private +{ +public: + Private() : + m_mdlColumns(nullptr), + m_file(MyMoneyFile::instance()), + m_hideClosedAccounts(false), + m_hideZeroBalanceAccounts(false) + {} + + ~Private() {} + + QList *m_mdlColumns; + QList m_visColumns; + + MyMoneyFile *m_file; + + bool m_hideClosedAccounts; + bool m_hideZeroBalanceAccounts; +}; + +EquitiesFilterProxyModel::EquitiesFilterProxyModel(QObject *parent, EquitiesModel *model, const QList &columns) + : KRecursiveFilterProxyModel(parent), d(new Private) +{ + setDynamicSortFilter(true); + setFilterKeyColumn(-1); + setSortLocaleAware(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSourceModel(model); + d->m_mdlColumns = model->getColumns(); + d->m_visColumns.append(columns); +} + +EquitiesFilterProxyModel::~EquitiesFilterProxyModel() +{ + delete d; +} + +/** + * Set if closed accounts should be hidden or not. + * @param hideClosedAccounts + */ +void EquitiesFilterProxyModel::setHideClosedAccounts(const bool hideClosedAccounts) +{ + d->m_hideClosedAccounts = hideClosedAccounts; +} + +/** + * Set if zero balance accounts should be hidden or not. + * @param hideZeroBalanceAccounts + */ +void EquitiesFilterProxyModel::setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts) +{ + d->m_hideZeroBalanceAccounts = hideZeroBalanceAccounts; +} + +bool EquitiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) + return true; + return false; +} + +bool EquitiesFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const +{ + if (d->m_hideClosedAccounts || d->m_hideZeroBalanceAccounts) { + const auto ixRow = sourceModel()->index(source_row, EquitiesModel::Equity, source_parent); + const auto idAcc = sourceModel()->data(ixRow, EquitiesModel::EquityID).toString(); + const auto acc = d->m_file->account(idAcc); + + if (d->m_hideClosedAccounts && + acc.isClosed()) + return false; + if (d->m_hideZeroBalanceAccounts && + acc.accountType() != MyMoneyAccount::Investment && acc.balance().isZero()) // we should never hide investment account because all underlaying stocks will be hidden as well + return false; + } + return true; +} + +QList &EquitiesFilterProxyModel::getVisibleColumns() +{ + return d->m_visColumns; +} + +void EquitiesFilterProxyModel::slotColumnsMenu(const QPoint) +{ + // construct all hideable columns list + const QList idColumns { + EquitiesModel::Symbol, EquitiesModel::Value, + EquitiesModel::Quantity, EquitiesModel::Price + }; + + // create menu + QMenu menu(i18n("Displayed columns")); + QList actions; + foreach (const auto idColumn, idColumns) { + auto a = new QAction(nullptr); + a->setObjectName(QString::number(idColumn)); + a->setText(EquitiesModel::getHeaderName(idColumn)); + a->setCheckable(true); + a->setChecked(d->m_visColumns.contains(idColumn)); + actions.append(a); + } + menu.addActions(actions); + + // execute menu and get result + const auto retAction = menu.exec(QCursor::pos()); + if (retAction) { + const auto idColumn = static_cast(retAction->objectName().toInt()); + const auto isChecked = retAction->isChecked(); + const auto contains = d->m_visColumns.contains(idColumn); + if (isChecked && !contains) { // column has just been enabled + d->m_visColumns.append(idColumn); // change filtering variable + emit columnToggled(idColumn, true); // emit signal for method to add column to model + invalidate(); // refresh model to reflect recent changes + } else if (!isChecked && contains) { // column has just been disabled + d->m_visColumns.removeOne(idColumn); + emit columnToggled(idColumn, false); + invalidate(); + } + } +} diff --git a/kmymoney/models/equitiesmodel.h b/kmymoney/models/equitiesmodel.h new file mode 100644 index 000000000..77a915b6c --- /dev/null +++ b/kmymoney/models/equitiesmodel.h @@ -0,0 +1,94 @@ +/*************************************************************************** + equitiesmodel.h + ------------------- + copyright : (C) 2017 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. * + * * + ***************************************************************************/ + +#ifndef EQUITIESMODEL_H +#define EQUITIESMODEL_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include + +class EquitiesModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Column { Equity = 0, Symbol, Value, Quantity, Price }; + enum Role { InvestmentID = Qt::UserRole, EquityID = Qt::UserRole, SecurityID = Qt::UserRole + 1 }; + + ~EquitiesModel(); + + auto getColumns(); + static QString getHeaderName(const Column column); + +public slots: + void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id); + void slotBalanceOrValueChanged(const MyMoneyAccount &account); + +private: + EquitiesModel(QObject *parent = nullptr); + EquitiesModel(const EquitiesModel&); + EquitiesModel& operator=(EquitiesModel&); + friend class Models; // only this class can create EquitiesModel + + void init(); + void load(); + +protected: + class Private; + Private* const d; +}; + +class EquitiesFilterProxyModel : public KRecursiveFilterProxyModel +{ + Q_OBJECT + +public: + EquitiesFilterProxyModel(QObject *parent , EquitiesModel *model, const QList &columns = QList()); + ~EquitiesFilterProxyModel(); + + QList &getVisibleColumns(); + void setHideClosedAccounts(const bool hideClosedAccounts); + void setHideZeroBalanceAccounts(const bool hideZeroBalanceAccounts); + +signals: + void columnToggled(const EquitiesModel::Column column, const bool show); + +public slots: + void slotColumnsMenu(const QPoint); + +protected: + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +private: + class Private; + Private* const d; +}; + +#endif // EQUITIESMODEL_H diff --git a/kmymoney/models/models.cpp b/kmymoney/models/models.cpp index b4040dcd1..9cbcc4fd2 100644 --- a/kmymoney/models/models.cpp +++ b/kmymoney/models/models.cpp @@ -1,213 +1,254 @@ /*************************************************************************** * Copyright 2010 Cristian Onet onet.cristian@gmail.com * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) 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 "models.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "accountsmodel.h" #include "onlinejobmodel.h" #include "ledgermodel.h" #include "costcentermodel.h" #include "payeesmodel.h" +#include "equitiesmodel.h" +#include "securitiesmodel.h" #ifdef KMM_MODELTEST #include "modeltest.h" #endif Q_GLOBAL_STATIC(Models, models); struct Models::Private { Private() : m_accountsModel(0) , m_institutionsModel(0) , m_onlineJobModel(0) , m_ledgerModel(0) , m_costCenterModel(0) , m_payeesModel(0) + , m_equitiesModel(0) + , m_securitiesModel(0) {} AccountsModel *m_accountsModel; InstitutionsModel *m_institutionsModel; onlineJobModel *m_onlineJobModel; LedgerModel *m_ledgerModel; CostCenterModel *m_costCenterModel; PayeesModel *m_payeesModel; + EquitiesModel *m_equitiesModel; + SecuritiesModel *m_securitiesModel; }; /** * This object is a singleton so it will be created very early in the application's life * so don't do anything on this constructor that relies on application data (even a i18n call). */ Models::Models() : QObject(), d(new Private) { } Models::~Models() { delete d; } Models* Models::instance() { return models; } /** * This is the function to get a reference to the core @ref AccountsModel object. * The returned object is owned by this object so don't delete it. It creates the * model on the first access to it. */ AccountsModel* Models::accountsModel() { if (!d->m_accountsModel) { d->m_accountsModel = new AccountsModel(this); #ifdef KMM_MODELTEST new ModelTest(d->m_accountsModel, Models::instance()); #endif } return d->m_accountsModel; } /** * This is the function to get a reference to the core @ref InstitutionsModel. * The returned object is owned by this object so don't delete it. It creates the * model on the first access to it. */ InstitutionsModel* Models::institutionsModel() { if (!d->m_institutionsModel) { d->m_institutionsModel = new InstitutionsModel(this); #ifdef KMM_MODELTEST new ModelTest(d->m_institutionsModel, Models::instance()); #endif } return d->m_institutionsModel; } onlineJobModel* Models::onlineJobsModel() { if (!d->m_onlineJobModel) { d->m_onlineJobModel = new onlineJobModel(this); #ifdef KMM_MODELTEST /// @todo using the ModelTest feature on the onlineJobModel crashes. Need to fix. // new ModelTest(d->m_onlineJobModel, Models::instance()); #endif } return d->m_onlineJobModel; } /** * This is the function to get a reference to the core @ref LedgerModel. * The returned object is owned by this object so don't delete it. It creates the * model on the first access to it. */ LedgerModel* Models::ledgerModel() { if (!d->m_ledgerModel) { d->m_ledgerModel = new LedgerModel(this); #ifdef KMM_MODELTEST new ModelTest(d->m_ledgerModel, Models::instance()); #endif } return d->m_ledgerModel; } /** * This is the function to get a reference to the core @ref CostCenterModel. * The returned object is owned by this object so don't delete it. It creates the * model on the first access to it. */ CostCenterModel* Models::costCenterModel() { if (!d->m_costCenterModel) { d->m_costCenterModel = new CostCenterModel(this); #ifdef KMM_MODELTEST new ModelTest(d->m_costCenterModel, Models::instance()); #endif } return d->m_costCenterModel; } /** * This is the function to get a reference to the core @ref PayeesModel. * The returned object is owned by this object so don't delete it. It creates the * model on the first access to it. */ PayeesModel* Models::payeesModel() { if (!d->m_payeesModel) { d->m_payeesModel = new PayeesModel(this); #ifdef KMM_MODELTEST new ModelTest(d->m_payeesModel, Models::instance()); #endif } return d->m_payeesModel; } +/** + * This is the function to get a reference to the core @ref EquitiesModel. + * The returned object is owned by this object so don't delete it. It creates the + * model on the first access to it. + */ +EquitiesModel* Models::equitiesModel() +{ + if (!d->m_equitiesModel) { + d->m_equitiesModel = new EquitiesModel(this); + #ifdef KMM_MODELTEST + new ModelTest(d->m_equitiesModel, Models::instance()); + #endif + } + return d->m_equitiesModel; +} + +/** + * This is the function to get a reference to the core @ref SecuritiesModel. + * The returned object is owned by this object so don't delete it. It creates the + * model on the first access to it. + */ +SecuritiesModel* Models::securitiesModel() +{ + if (!d->m_securitiesModel) { + d->m_securitiesModel = new SecuritiesModel(this); + #ifdef KMM_MODELTEST + new ModelTest(d->m_securitiesModel, Models::instance()); + #endif + } + return d->m_securitiesModel; +} QModelIndex Models::indexById(QAbstractItemModel* model, int role, const QString& id) { QModelIndexList indexList = model->match(model->index(0, 0), role, id, 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); if(indexList.count() == 1) { return indexList.first(); } return QModelIndex(); } void Models::fileOpened() { accountsModel()->load(); institutionsModel()->load(); onlineJobsModel()->load(); costCenterModel()->load(); ledgerModel()->load(); payeesModel()->load(); + equitiesModel()->load(); + securitiesModel()->load(); emit modelsLoaded(); } void Models::fileClosed() { // TODO: make this cleaner in the future, for now just clear the accounts model before the file is closed // to avoid any uncaught KMyMoneyExceptions while using the account objects from this model after the file has been closed accountsModel()->removeRows(0, accountsModel()->rowCount()); institutionsModel()->removeRows(0, institutionsModel()->rowCount()); onlineJobsModel()->unload(); ledgerModel()->unload(); costCenterModel()->unload(); payeesModel()->unload(); + equitiesModel()->removeRows(0, equitiesModel()->rowCount()); + securitiesModel()->removeRows(0, securitiesModel()->rowCount()); } diff --git a/kmymoney/models/models.h b/kmymoney/models/models.h index ecc6f1bb8..e1a8bd1d5 100644 --- a/kmymoney/models/models.h +++ b/kmymoney/models/models.h @@ -1,120 +1,124 @@ /*************************************************************************** * Copyright 2010 Cristian Onet onet.cristian@gmail.com * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) 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 * ***************************************************************************/ #ifndef MODELS_H #define MODELS_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes /** * Forward declarations for the returned models. */ class AccountsModel; class InstitutionsModel; class onlineJobModel; class LedgerModel; class CostCenterModel; class PayeesModel; +class EquitiesModel; +class SecuritiesModel; /** * This object is the owner and maintainer of all the core models of KMyMoney. * It's a singleton so the instance should be accessed in the following way: * * @code * Models *models = Models::instance(); * AccountsModel *accountsModel = models->accountsModel(); * @endcode * * In order for the data synchronization between the @ref MyMoneyFile and the * models managed by this object to work, the @ref MyMoneyFile::dataChanged * signal must be connected to this object's @ref fileClosed slot. * * @author Cristian Onet 2010 * */ class Models : public QObject { Q_OBJECT public: Models(); ~Models(); /** * This is the function to access the Models object. * It returns a pointer to the single instance of the object. */ static Models* instance(); AccountsModel* accountsModel(); InstitutionsModel* institutionsModel(); onlineJobModel* onlineJobsModel(); LedgerModel* ledgerModel(); CostCenterModel* costCenterModel(); PayeesModel* payeesModel(); + EquitiesModel* equitiesModel(); + SecuritiesModel* securitiesModel(); /** * returns the index of an item the @a model based on the @a id of role @a role. */ static QModelIndex indexById(QAbstractItemModel* model, int role, const QString& id); public slots: /** * This slot is used to notify the models that the data has been loaded and ready to use. * @ref MyMoneyFile. */ void fileOpened(); /** * This slot is used to notify the models that the data has been unloaded. * @ref MyMoneyFile. */ void fileClosed(); Q_SIGNALS: void modelsLoaded(); private: /** * This class defines a singleton. */ Models(const Models&); /** * This class defines a singleton. */ Models& operator=(Models&); private: struct Private; Private* const d; }; #endif // MODELS_H diff --git a/kmymoney/models/securitiesmodel.cpp b/kmymoney/models/securitiesmodel.cpp new file mode 100644 index 000000000..49f595b78 --- /dev/null +++ b/kmymoney/models/securitiesmodel.cpp @@ -0,0 +1,368 @@ +/*************************************************************************** + securitiesmodel.cpp + ------------------- + copyright : (C) 2017 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 "securitiesmodel.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +class SecuritiesModel::Private +{ +public: + Private() : m_file(MyMoneyFile::instance()) + { + QVector columns { + Column::Security, Column::Symbol, Column::Type, + Column::Market, Column::Currency, Column::Fraction + }; + foreach (auto const column, columns) + m_columns.append(column); + } + + ~Private() {} + + void loadSecurity(QStandardItem *node, const MyMoneySecurity &sec) + { + auto itSec = new QStandardItem(sec.name()); + node->appendRow(itSec); + itSec->setEditable(false); + setSecurityData(node, itSec->row(), sec, m_columns); + } + + void setSecurityData(QStandardItem *node, const int row, const MyMoneySecurity &security, const QList &columns) + { + QStandardItem *cell; + + auto getCell = [&, row](const auto column) { + cell = node->child(row, column); // try to get QStandardItem + if (!cell) { // it may be uninitialized + cell = new QStandardItem; // so create one + node->setChild(row, column, cell); // and add it under the node + cell->setEditable(false); // and don't forget that it's non-editable + } + }; + + auto colNum = m_columns.indexOf(Column::Security); + if (colNum == -1) + return; + + // Security + getCell(colNum); + if (columns.contains(Column::Security)) { + cell->setData(security.name(), Qt::DisplayRole); + cell->setData(security.id(), Qt::UserRole); + } + + // Symbol + if (columns.contains(Column::Symbol)) { + colNum = m_columns.indexOf(Column::Symbol); + if (colNum != -1) { + getCell(colNum); + cell->setData(security.tradingSymbol(), Qt::DisplayRole); + } + } + + // Type + if (columns.contains(Column::Type)) { + colNum = m_columns.indexOf(Column::Type); + if (colNum != -1) { + getCell(colNum); + cell->setData(security.securityTypeToString(security.securityType()), Qt::DisplayRole); + } + } + + // Market + if (columns.contains(Column::Market)) { + colNum = m_columns.indexOf(Column::Market); + if (colNum != -1) { + getCell(colNum); + QString market; + if (security.isCurrency()) + market = QLatin1String("ISO 4217"); + else + market = security.tradingMarket(); + cell->setData(market, Qt::DisplayRole); + } + } + + // Currency + if (columns.contains(Column::Currency)) { + colNum = m_columns.indexOf(Column::Currency); + if (colNum != -1) { + getCell(colNum); + MyMoneySecurity tradingCurrency; + if (!security.isCurrency()) + tradingCurrency = m_file->security(security.tradingCurrency()); + cell->setData(tradingCurrency.tradingSymbol(), Qt::DisplayRole); + } + } + + // Fraction + if (columns.contains(Column::Fraction)) { + colNum = m_columns.indexOf(Column::Fraction); + if (colNum != -1) { + getCell(colNum); + cell->setData(QString::number(security.smallestAccountFraction()), Qt::DisplayRole); + } + } + } + + QStandardItem *itemFromSecurityId(QStandardItemModel *model, const QString &securityId) + { + const auto itemList = model->match(model->index(0, 0), Qt::UserRole, QVariant(securityId), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); + if (!itemList.isEmpty()) + return model->itemFromIndex(itemList.first()); + return nullptr; + } + + MyMoneyFile *m_file; + QList m_columns; + QStandardItem *m_ndCurrencies; + QStandardItem *m_ndSecurities; +}; + +SecuritiesModel::SecuritiesModel(QObject *parent) + : QStandardItemModel(parent), d(new Private) +{ + init(); +} + +SecuritiesModel::~SecuritiesModel() +{ + delete d; +} + +void SecuritiesModel::init() +{ + QStringList headerLabels; + foreach (const auto column, d->m_columns) + headerLabels.append(getHeaderName(column)); + setHorizontalHeaderLabels(headerLabels); +} + +void SecuritiesModel::load() +{ + this->blockSignals(true); + + auto rootItem = invisibleRootItem(); + auto secList = d->m_file->securityList(); // get all available securities + d->m_ndSecurities = new QStandardItem(QStringLiteral("Securities")); + d->m_ndSecurities->setEditable(false); + rootItem->appendRow(d->m_ndSecurities); + + foreach (const auto sec, secList) + d->loadSecurity(d->m_ndSecurities, sec); + + secList = d->m_file->currencyList(); // get all available currencies + d->m_ndCurrencies = new QStandardItem(QStringLiteral("Currencies")); + d->m_ndCurrencies->setEditable(false); + rootItem->appendRow(d->m_ndCurrencies); + foreach (const auto sec, secList) + d->loadSecurity(d->m_ndCurrencies, sec); + + this->blockSignals(false); +} + +/** + * Notify the model that an object has been added. An action is performed only if the object is a security. + * + */ +void SecuritiesModel::slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + // check whether change is about security + if (objType != MyMoneyFile::notifySecurity) + return; + + // check that we're about to add security + auto sec = dynamic_cast(obj); + if (!sec) + return; + + auto itSec = d->itemFromSecurityId(this, sec->id()); + + QStandardItem *node; + if (sec->isCurrency()) + node = d->m_ndCurrencies; + else + node = d->m_ndSecurities; + + // if security doesn't exist in model then add it + if (!itSec) { + itSec = new QStandardItem(sec->name()); + node->appendRow(itSec); + itSec->setEditable(false); + } + + d->setSecurityData(node, itSec->row(), *sec, d->m_columns); +} + +/** + * Notify the model that an object has been modified. An action is performed only if the object is a security. + * + */ +void SecuritiesModel::slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj) +{ + if (objType != MyMoneyFile::notifySecurity) + return; + + // check that we're about to modify security + auto sec = dynamic_cast(obj); + if (!sec) + return; + + auto itSec = d->itemFromSecurityId(this, sec->id()); + + QStandardItem *node; + if (sec->isCurrency()) + node = d->m_ndCurrencies; + else + node = d->m_ndSecurities; + d->setSecurityData(node, itSec->row(), *sec, d->m_columns); +} + +/** + * Notify the model that an object has been removed. An action is performed only if the object is an account. + * + */ +void SecuritiesModel::slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id) +{ + if (objType != MyMoneyFile::notifySecurity) + return; + + const auto indexList = match(index(0, 0), Qt::UserRole, id, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); + foreach (const auto index, indexList) + removeRow(index.row(), index.parent()); +} + +auto SecuritiesModel::getColumns() +{ + return &d->m_columns; +} + +QString SecuritiesModel::getHeaderName(const Column column) +{ + switch(column) { + case Security: + return i18n("Security"); + case Symbol: + return i18n("Symbol"); + case Type: + return i18n("Type"); + case Market: + return i18n("Market"); + case Currency: + return i18n("Currency"); + case Fraction: + return i18n("Fraction"); + default: + return QString(); + } +} + +class SecuritiesFilterProxyModel::Private +{ +public: + Private() : + m_mdlColumns(nullptr), + m_file(MyMoneyFile::instance()) + {} + + ~Private() {} + + QList *m_mdlColumns; + QList m_visColumns; + + MyMoneyFile *m_file; +}; + +SecuritiesFilterProxyModel::SecuritiesFilterProxyModel(QObject *parent, SecuritiesModel *model, const QList &columns) + : KRecursiveFilterProxyModel(parent), d(new Private) +{ + setDynamicSortFilter(true); + setFilterKeyColumn(-1); + setSortLocaleAware(true); + setFilterCaseSensitivity(Qt::CaseInsensitive); + setSourceModel(model); + d->m_mdlColumns = model->getColumns(); + d->m_visColumns.append(columns); +} + +SecuritiesFilterProxyModel::~SecuritiesFilterProxyModel() +{ + delete d; +} + +bool SecuritiesFilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const +{ + Q_UNUSED(source_parent) + if (d->m_visColumns.isEmpty() || d->m_visColumns.contains(d->m_mdlColumns->at(source_column))) + return true; + return false; +} + +QList &SecuritiesFilterProxyModel::getVisibleColumns() +{ + return d->m_visColumns; +} + +void SecuritiesFilterProxyModel::slotColumnsMenu(const QPoint) +{ + // construct all hideable columns list + const QList idColumns { + SecuritiesModel::Symbol, SecuritiesModel::Type, + SecuritiesModel::Market, SecuritiesModel::Currency, + SecuritiesModel::Fraction + }; + + // create menu + QMenu menu(i18n("Displayed columns")); + QList actions; + foreach (const auto idColumn, idColumns) { + auto a = new QAction(nullptr); + a->setObjectName(QString::number(idColumn)); + a->setText(SecuritiesModel::getHeaderName(idColumn)); + a->setCheckable(true); + a->setChecked(d->m_visColumns.contains(idColumn)); + actions.append(a); + } + menu.addActions(actions); + + // execute menu and get result + const auto retAction = menu.exec(QCursor::pos()); + if (retAction) { + const auto idColumn = static_cast(retAction->objectName().toInt()); + const auto isChecked = retAction->isChecked(); + const auto contains = d->m_visColumns.contains(idColumn); + if (isChecked && !contains) { // column has just been enabled + d->m_visColumns.append(idColumn); // change filtering variable + emit columnToggled(idColumn, true); // emit signal for method to add column to model + invalidate(); // refresh model to reflect recent changes + } else if (!isChecked && contains) { // column has just been disabled + d->m_visColumns.removeOne(idColumn); + emit columnToggled(idColumn, false); + invalidate(); + } + } +} diff --git a/kmymoney/models/securitiesmodel.h b/kmymoney/models/securitiesmodel.h new file mode 100644 index 000000000..64f39e4ed --- /dev/null +++ b/kmymoney/models/securitiesmodel.h @@ -0,0 +1,89 @@ +/*************************************************************************** + securitiesmodel.h + ------------------- + copyright : (C) 2017 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. * + * * + ***************************************************************************/ + +#ifndef SECURITIESMODEL_H +#define SECURITIESMODEL_H + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include + +class SecuritiesModel : public QStandardItemModel +{ + Q_OBJECT + +public: + enum Column { Security = 0, Symbol, Type, Market, Currency, Fraction }; + + ~SecuritiesModel(); + + auto getColumns(); + static QString getHeaderName(const Column column); + +public slots: + void slotObjectAdded(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectModified(MyMoneyFile::notificationObjectT objType, const MyMoneyObject * const obj); + void slotObjectRemoved(MyMoneyFile::notificationObjectT objType, const QString& id); + +private: + SecuritiesModel(QObject *parent = nullptr); + SecuritiesModel(const SecuritiesModel&); + SecuritiesModel& operator=(SecuritiesModel&); + friend class Models; // only this class can create SecuritiesModel + + void init(); + void load(); + +protected: + class Private; + Private* const d; +}; + +class SecuritiesFilterProxyModel : public KRecursiveFilterProxyModel +{ + Q_OBJECT + +public: + SecuritiesFilterProxyModel(QObject *parent , SecuritiesModel *model, const QList &columns = QList()); + ~SecuritiesFilterProxyModel(); + + QList &getVisibleColumns(); + +signals: + void columnToggled(const SecuritiesModel::Column column, const bool show); + +public slots: + void slotColumnsMenu(const QPoint); + +protected: + bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const override; + +private: + class Private; + Private* const d; +}; + +#endif // SECURITIESMODEL_H diff --git a/kmymoney/mymoney/mymoneysecurity.cpp b/kmymoney/mymoney/mymoneysecurity.cpp index 6a8cceb21..0078524ac 100644 --- a/kmymoney/mymoney/mymoneysecurity.cpp +++ b/kmymoney/mymoney/mymoneysecurity.cpp @@ -1,251 +1,242 @@ /*************************************************************************** mymoneysecurity.cpp - description ------------------- begin : Tue Jan 29 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 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 "mymoneysecurity.h" // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; MyMoneySecurity::MyMoneySecurity() : m_securityType(SECURITY_NONE), m_smallestCashFraction(DEFAULT_CASH_FRACTION), m_smallestAccountFraction(DEFAULT_ACCOUNT_FRACTION), m_pricePrecision(DEFAULT_PRICE_PRECISION), m_roundingMethod(AlkValue::RoundRound) { } MyMoneySecurity::MyMoneySecurity(const QString& id, const QString& name, const QString& symbol, const int smallestCashFraction, const int smallestAccountFraction, const int pricePrecision) : MyMoneyObject(id), m_name(name), m_securityType(SECURITY_CURRENCY), m_smallestCashFraction(smallestCashFraction), m_smallestAccountFraction(smallestAccountFraction), m_pricePrecision(pricePrecision), m_roundingMethod(AlkValue::RoundRound) { if (symbol.isEmpty()) m_tradingSymbol = id; else m_tradingSymbol = symbol; if (smallestAccountFraction) m_smallestAccountFraction = smallestAccountFraction; else m_smallestAccountFraction = smallestCashFraction; } MyMoneySecurity::MyMoneySecurity(const QString& id, const MyMoneySecurity& equity) : MyMoneyObject(id) { *this = equity; m_id = id; } MyMoneySecurity::MyMoneySecurity(const QDomElement& node) : MyMoneyObject(node), MyMoneyKeyValueContainer(node.elementsByTagName(nodeNames[nnKeyValuePairs]).item(0).toElement()) { if ((nodeNames[nnSecurity] != node.tagName()) && (nodeNames[nnEquity] != node.tagName()) && (nodeNames[nnCurrency] != node.tagName())) throw MYMONEYEXCEPTION("Node was not SECURITY or CURRENCY"); setName(node.attribute(getAttrName(anName))); setTradingSymbol(node.attribute(getAttrName(anSymbol))); setSecurityType(static_cast(node.attribute(getAttrName(anType)).toInt())); setRoundingMethod(static_cast(node.attribute(getAttrName(anRoundingMethod)).toInt())); int saf = node.attribute(getAttrName(anSAF)).toUInt(); int pp = node.attribute(getAttrName(anPP)).toUInt(); if (saf == 0) saf = 100; if (pp == 0 || pp > 10) pp = 4; setSmallestAccountFraction(saf); setPricePrecision(pp); if (isCurrency()) { int scf = node.attribute(getAttrName(anSCF)).toUInt(); if (scf == 0) scf = 100; setSmallestCashFraction(scf); } else { setTradingCurrency(node.attribute(getAttrName(anTradingCurrency))); setTradingMarket(node.attribute(getAttrName(anTradingMarket))); } } MyMoneySecurity::~MyMoneySecurity() { } bool MyMoneySecurity::operator == (const MyMoneySecurity& r) const { return (m_id == r.m_id) && (m_name == r.m_name) && (m_tradingSymbol == r.m_tradingSymbol) && (m_tradingMarket == r.m_tradingMarket) && (m_roundingMethod == r.m_roundingMethod) && (m_tradingSymbol == r.m_tradingSymbol) && (m_tradingCurrency == r.m_tradingCurrency) && (m_securityType == r.m_securityType) && (m_smallestAccountFraction == r.m_smallestAccountFraction) && (m_smallestCashFraction == r.m_smallestCashFraction) && (m_pricePrecision == r.m_pricePrecision) && this->MyMoneyKeyValueContainer::operator == (r); } bool MyMoneySecurity::operator < (const MyMoneySecurity& right) const { if (m_securityType == right.m_securityType) return m_name < right.m_name; return m_securityType < right.m_securityType; } bool MyMoneySecurity::hasReferenceTo(const QString& id) const { return (id == m_tradingCurrency); } void MyMoneySecurity::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el; if (isCurrency()) el = document.createElement(nodeNames[nnCurrency]); else el = document.createElement(nodeNames[nnSecurity]); writeBaseXML(document, el); el.setAttribute(getAttrName(anName), m_name); el.setAttribute(getAttrName(anSymbol), m_tradingSymbol); el.setAttribute(getAttrName(anType), static_cast(m_securityType)); el.setAttribute(getAttrName(anRoundingMethod), static_cast(m_roundingMethod)); el.setAttribute(getAttrName(anSAF), m_smallestAccountFraction); el.setAttribute(getAttrName(anPP), m_pricePrecision); if (isCurrency()) el.setAttribute(getAttrName(anSCF), m_smallestCashFraction); else { el.setAttribute(getAttrName(anTradingCurrency), m_tradingCurrency); el.setAttribute(getAttrName(anTradingMarket), m_tradingMarket); } //Add in Key-Value Pairs for securities. MyMoneyKeyValueContainer::writeXML(document, el); parent.appendChild(el); } QString MyMoneySecurity::securityTypeToString(const eSECURITYTYPE securityType) { - QString returnString; - switch (securityType) { case MyMoneySecurity::SECURITY_STOCK: - returnString = I18N_NOOP("Stock"); - break; + return i18nc("Security type", "Stock"); case MyMoneySecurity::SECURITY_MUTUALFUND: - returnString = I18N_NOOP("Mutual Fund"); - break; + return i18nc("Security type", "Mutual Fund"); case MyMoneySecurity::SECURITY_BOND: - returnString = I18N_NOOP("Bond"); - break; + return i18nc("Security type", "Bond"); case MyMoneySecurity::SECURITY_CURRENCY: - returnString = I18N_NOOP("Currency"); - break; + return i18nc("Security type", "Currency"); case MyMoneySecurity::SECURITY_NONE: - returnString = I18N_NOOP("None"); - break; + return i18nc("Security type", "None"); default: - returnString = I18N_NOOP("Unknown"); + return i18nc("Security type", "Unknown"); } - - return returnString; } QString MyMoneySecurity::roundingMethodToString(const AlkValue::RoundingMethod roundingMethod) { QString returnString; switch (roundingMethod) { case AlkValue::RoundNever: returnString = I18N_NOOP("Never"); break; case AlkValue::RoundFloor: returnString = I18N_NOOP("Floor"); break; case AlkValue::RoundCeil: returnString = I18N_NOOP("Ceil"); break; case AlkValue::RoundTruncate: returnString = I18N_NOOP("Truncate"); break; case AlkValue::RoundPromote: returnString = I18N_NOOP("Promote"); break; case AlkValue::RoundHalfDown: returnString = I18N_NOOP("HalfDown"); break; case AlkValue::RoundHalfUp: returnString = I18N_NOOP("HalfUp"); break; case AlkValue::RoundRound: returnString = I18N_NOOP("Round"); break; default: returnString = I18N_NOOP("Unknown"); } return returnString; } const QString MyMoneySecurity::getAttrName(const attrNameE _attr) { static const QHash attrNames = { {anName, QStringLiteral("name")}, {anSymbol, QStringLiteral("symbol")}, {anType, QStringLiteral("type")}, {anRoundingMethod, QStringLiteral("rounding-method")}, {anSAF, QStringLiteral("saf")}, {anPP, QStringLiteral("pp")}, {anSCF, QStringLiteral("scf")}, {anTradingCurrency, QStringLiteral("trading-currency")}, {anTradingMarket, QStringLiteral("trading-market")} }; return attrNames[_attr]; } diff --git a/kmymoney/views/CMakeLists.txt b/kmymoney/views/CMakeLists.txt index 4f8f66361..a3d23ea45 100644 --- a/kmymoney/views/CMakeLists.txt +++ b/kmymoney/views/CMakeLists.txt @@ -1,95 +1,95 @@ ############# next target (views) STATIC ################### set(libviews_a_SOURCES kaccountsview.cpp kbudgetview.cpp kcategoriesview.cpp kforecastview.cpp kgloballedgerview.cpp ledgerview.cpp kwelcomepage.cpp khomeview.cpp kinstitutionsview.cpp kinvestmentview.cpp kmymoneyfile.cpp kmymoneyview.cpp kpayeesview.cpp kreportsview.cpp kscheduledview.cpp tocitem.cpp tocitemgroup.cpp tocitemreport.cpp kscheduletreeitem.cpp ktagsview.cpp konlinejoboutbox.cpp kpayeeidentifierview.cpp payeeidentifierselectiondelegate.cpp simpleledgerview.cpp ledgerviewpage.cpp ledgerview.cpp ledgerdelegate.cpp newspliteditor.cpp newtransactioneditor.cpp newtransactionform.cpp splitdialog.cpp splitdelegate.cpp widgethintframe.cpp kmymoneywebpage.cpp ) set(libviews_a_HEADER kmymoneyview.h) set(libviews_a_UI kaccountsviewdecl.ui kbudgetviewdecl.ui kcategoriesviewdecl.ui kforecastviewdecl.ui kinstitutionsviewdecl.ui - kinvestmentviewdecl.ui + kinvestmentview.ui kpayeesviewdecl.ui kscheduledviewdecl.ui ktagsviewdecl.ui konlinejoboutbox.ui kpayeeidentifierview.ui simpleledgerview.ui ledgerview.ui ledgerviewpage.ui simpleledgerview.ui splitdialog.ui newspliteditor.ui newtransactioneditor.ui newtransactionform.ui ) # The handling of these ui files depends # on libkmymoney.so (the widgets library) ki18n_wrap_ui(libviews_a_SOURCES ${libviews_a_UI}) add_library(views STATIC ${libviews_a_SOURCES}) target_link_libraries(views PUBLIC KChart KF5::KIOFileWidgets KF5::Notifications KF5::Archive KF5::TextWidgets Qt5::Sql Alkimia::alkimia) if(ENABLE_WEBENGINE) target_link_libraries(views PUBLIC Qt5::WebEngineWidgets) else(ENABLE_WEBENGINE) target_link_libraries(views PUBLIC KF5::WebKit) endif(ENABLE_WEBENGINE) if(KF5Activities_FOUND) target_link_libraries(views PRIVATE KF5::Activities) endif() # TODO: Remove this dependency. But it is needed as long as the payee editor uses these objects directly # This should be replaced by virtual methods in a pure abstract object. target_link_libraries( views PUBLIC payeeidentifier_iban_bic payeeidentifier_nationalAccount kmm_mymoney # needed to load payeeIdentifier ) # we rely on some of the dialogs to be generated add_dependencies(views dialogs newinvestmentwizard newloanwizard endingbalancedlg) ########### install files ############### install(FILES ${libviews_a_HEADER} DESTINATION ${INCLUDE_INSTALL_DIR}/kmymoney COMPONENT Devel) diff --git a/kmymoney/views/kinvestmentview.cpp b/kmymoney/views/kinvestmentview.cpp index 8bce6a573..97109dc9f 100644 --- a/kmymoney/views/kinvestmentview.cpp +++ b/kmymoney/views/kinvestmentview.cpp @@ -1,583 +1,375 @@ /*************************************************************************** kinvestmentview.cpp - description ------------------- begin : Mon Mar 12 2007 copyright : (C) 2007 by Thomas Baumgart email : Thomas Baumgart + (C) 2017 Ł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 "kinvestmentview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include -#include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include -#include -#include #include #include #include -#include #include -#include "kmymoney.h" -#include "models.h" +#include +#include +#include +#include +#include #include using namespace Icons; -/** - * This class is only needed to implement proper sorting. - */ -class InvestmentItem : public QTreeWidgetItem -{ -public: - InvestmentItem(QTreeWidget *view) : QTreeWidgetItem(view) {} - virtual bool operator<(const QTreeWidgetItem &other) const; -}; - -bool InvestmentItem::operator<(const QTreeWidgetItem &other) const -{ - const int sortColumn = treeWidget()->sortColumn(); - if (sortColumn > eInvestmentSymbolColumn) { - // these columns have a MyMoneyMoney value in the Qt::UserRole role - const MyMoneyMoney &money = data(sortColumn, Qt::UserRole).value(); - const MyMoneyMoney &otherMoney = other.data(sortColumn, Qt::UserRole).value(); - return money < otherMoney; - } - return QTreeWidgetItem::operator<(other); -} - class KInvestmentView::Private { public: Private() : - m_newAccountLoaded(false), - m_recursion(false), - m_precision(2), - m_filterProxyModel(0) {} - - MyMoneyAccount m_account; - bool m_needReload[MaxViewTabs]; - bool m_newAccountLoaded; - bool m_recursion; - int m_precision; - AccountNamesFilterProxyModel *m_filterProxyModel; + m_idInvAcc(QString()), + m_accountsProxyModel(nullptr), + m_equitiesProxyModel(nullptr), + m_securitiesProxyModel(nullptr) {} + QString m_idInvAcc; + + bool m_needReload[2]; + AccountNamesFilterProxyModel *m_accountsProxyModel; + EquitiesFilterProxyModel *m_equitiesProxyModel; + SecuritiesFilterProxyModel *m_securitiesProxyModel; }; -KInvestmentView::KInvestmentView(QWidget *parent) : - QWidget(parent), +KInvestmentView::KInvestmentView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview) : + QWidget(nullptr), d(new Private), - m_currencyMarket("ISO 4217"), + m_kmymoney(kmymoney), + m_kmymoneyview(kmymoneyview), m_needLoad(true) { } KInvestmentView::~KInvestmentView() { if (!m_needLoad) { // save the header state of the equities list - KConfigGroup grp = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); - QByteArray columns = m_investmentsList->header()->saveState(); - grp.writeEntry("HeaderState", columns); + auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); + auto cfgHeader = m_equitiesTree->header()->saveState(); + auto visEColumns = d->m_equitiesProxyModel->getVisibleColumns(); + + QList cfgColumns; + foreach (const auto visColumn, visEColumns) + cfgColumns.append(static_cast(visColumn)); + + cfgGroup.writeEntry("HeaderState", cfgHeader); + cfgGroup.writeEntry("ColumnsSelection", cfgColumns); // save the header state of the securities list - grp = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); - columns = m_securitiesList->header()->saveState(); - grp.writeEntry("HeaderState", columns); + cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); + cfgHeader = m_securitiesTree->header()->saveState(); + auto visSColumns = d->m_securitiesProxyModel->getVisibleColumns(); + cfgColumns.clear(); + foreach (const auto visColumn, visSColumns) + cfgColumns.append(static_cast(visColumn)); + + cfgGroup.writeEntry("HeaderState", cfgHeader); + cfgGroup.writeEntry("ColumnsSelection", cfgColumns); } delete d; } void KInvestmentView::init() { m_needLoad = false; setupUi(this); - // load the header state of the equities list - KConfigGroup grp = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); - QByteArray columns; - columns = grp.readEntry("HeaderState", columns); - m_investmentsList->header()->restoreState(columns); - - // load the header state of the securities list - grp = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); - columns.clear(); - columns = grp.readEntry("HeaderState", columns); - m_securitiesList->header()->restoreState(columns); - - //first set up everything for the equities tab - d->m_filterProxyModel = new AccountNamesFilterProxyModel(this); - d->m_filterProxyModel->addAccountType(MyMoneyAccount::Investment); - d->m_filterProxyModel->setHideEquityAccounts(false); - auto const model = Models::instance()->accountsModel(); - d->m_filterProxyModel->init(model, model->getColumns()); - d->m_filterProxyModel->sort(AccountsModel::Account); - m_accountComboBox->setModel(d->m_filterProxyModel); - - m_investmentsList->setContextMenuPolicy(Qt::CustomContextMenu); - m_investmentsList->setSortingEnabled(true); - - for (auto i = 0; i < MaxViewTabs; ++i) - d->m_needReload[i] = false; - - connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(slotTabCurrentChanged(int))); - - connect(m_investmentsList, SIGNAL(customContextMenuRequested(QPoint)), - this, SLOT(slotInvestmentContextMenu(QPoint))); - connect(m_investmentsList, SIGNAL(itemSelectionChanged()), this, SLOT(slotInvestmentSelectionChanged())); + // Equities tab + d->m_accountsProxyModel = new AccountNamesFilterProxyModel(this); + d->m_accountsProxyModel->addAccountType(MyMoneyAccount::Investment); + d->m_accountsProxyModel->setHideEquityAccounts(false); + d->m_accountsProxyModel->init(Models::instance()->accountsModel()); + d->m_accountsProxyModel->sort(AccountsModel::Account); + m_accountComboBox->setModel(d->m_accountsProxyModel); + m_accountComboBox->expandAll(); + + auto cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Equities"); + auto cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray()); + auto cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList()); + QList visEColumns {EquitiesModel::Equity}; + foreach (const auto cfgColumn, cfgColumns) { + const auto visColumn = static_cast(cfgColumn); + if (!visEColumns.contains(visColumn)) + visEColumns.append(visColumn); + } + + d->m_equitiesProxyModel = new EquitiesFilterProxyModel(this, Models::instance()->equitiesModel(), visEColumns); + m_equitiesTree->setModel(d->m_equitiesProxyModel); + m_equitiesTree->header()->restoreState(cfgHeader); + + connect(m_equitiesTree, &QWidget::customContextMenuRequested, this, &KInvestmentView::slotEquityRightClicked); +// connect(m_equitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotEquitySelected); + connect(m_equitiesTree, &QTreeView::doubleClicked, this, &KInvestmentView::slotEquityDoubleClicked); + m_equitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_equitiesTree->header(), &QWidget::customContextMenuRequested, d->m_equitiesProxyModel, &EquitiesFilterProxyModel::slotColumnsMenu); connect(m_accountComboBox, SIGNAL(accountSelected(QString)), - this, SLOT(slotSelectAccount(QString))); - connect(m_investmentsList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::InvestmentEdit]), SLOT(trigger())); - connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); - - // create the searchline widget - // and insert it into the existing layout - m_searchSecuritiesWidget = new KTreeWidgetSearchLineWidget(this, m_securitiesList); - m_searchSecuritiesWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); - m_securitiesLayout->insertWidget(0, m_searchSecuritiesWidget); - - KGuiItem removeButtonItem(i18n("&Delete"), - QIcon::fromTheme(g_Icons[Icon::EditDelete]), - i18n("Delete this entry"), - i18n("Remove this security item from the file")); - KGuiItem::assign(m_deleteSecurityButton, removeButtonItem); - - KGuiItem editButtonItem(i18n("&Edit"), - QIcon::fromTheme(g_Icons[Icon::DocumentEdit]), - i18n("Modify the selected entry"), - i18n("Change the security information of the selected entry.")); - KGuiItem::assign(m_editSecurityButton, editButtonItem); - - connect(m_securitiesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotUpdateSecuritiesButtons())); - connect(m_editSecurityButton, SIGNAL(clicked()), this, SLOT(slotEditSecurity())); - connect(m_deleteSecurityButton, SIGNAL(clicked()), this, SLOT(slotDeleteSecurity())); + this, SLOT(slotLoadAccount(QString))); + + // Securities tab + cfgGroup = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); + cfgHeader = cfgGroup.readEntry("HeaderState", QByteArray()); + cfgColumns = cfgGroup.readEntry("ColumnsSelection", QList()); + QList visSColumns {SecuritiesModel::Security}; + foreach (const auto cfgColumn, cfgColumns) { + const auto visColumn = static_cast(cfgColumn); + if (!visSColumns.contains(visColumn)) + visSColumns.append(visColumn); + } + + d->m_securitiesProxyModel = new SecuritiesFilterProxyModel(this, Models::instance()->securitiesModel(), visSColumns); + m_securitiesTree->setModel(d->m_securitiesProxyModel); + m_securitiesTree->header()->restoreState(cfgHeader); + + m_searchSecurities->setProxy(d->m_securitiesProxyModel); + m_deleteSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); + m_editSecurityButton->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); + + connect(m_securitiesTree->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &KInvestmentView::slotSecuritySelected); + connect(m_editSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotEditSecurity); + connect(m_deleteSecurityButton, &QAbstractButton::clicked, this, &KInvestmentView::slotDeleteSecurity); + m_securitiesTree->header()->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_securitiesTree->header(), &QWidget::customContextMenuRequested, d->m_securitiesProxyModel, &SecuritiesFilterProxyModel::slotColumnsMenu); + + // Investment Page + d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true; + connect(m_tab, &QTabWidget::currentChanged, this, &KInvestmentView::slotLoadTab); + connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, this, &KInvestmentView::slotLoadView); + + connect(this, SIGNAL(accountSelected(MyMoneyObject)), m_kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); + connect(this, &KInvestmentView::equityRightClicked, m_kmymoney, &KMyMoneyApp::slotShowInvestmentContextMenu); + connect(this, &KInvestmentView::aboutToShow, m_kmymoneyview, &KMyMoneyView::aboutToChangeView); } -void KInvestmentView::loadView(InvestmentsViewTab tab) +void KInvestmentView::slotLoadTab(int index) { + auto tab = static_cast(index); if (d->m_needReload[tab]) { switch (tab) { - case EquitiesTab: + case Tab::Equities: loadInvestmentTab(); - // force a new account if the current one is empty - d->m_newAccountLoaded = d->m_account.id().isEmpty(); break; - case SecuritiesTab: - loadSecuritiesList(); - break; - default: + case Tab::Securities: + loadSecuritiesTab(); break; } d->m_needReload[tab] = false; } } -void KInvestmentView::slotInvestmentSelectionChanged() +void KInvestmentView::slotEquitySelected(const QModelIndex ¤t, const QModelIndex &previous) { - kmymoney->slotSelectInvestment(); - - QTreeWidgetItem *item = m_investmentsList->currentItem(); - if (item) { - try { - MyMoneyAccount account = MyMoneyFile::instance()->account(item->data(0, Qt::UserRole).value().id()); - kmymoney->slotSelectInvestment(account); - - } catch (const MyMoneyException &) { - } + Q_UNUSED(current); + Q_UNUSED(previous); + MyMoneyAccount acc; + + auto treeItem = m_equitiesTree->currentIndex(); + if (treeItem.isValid()) { + auto mdlItem = d->m_equitiesProxyModel->index(treeItem.row(), EquitiesModel::Equity, treeItem.parent()); + acc = MyMoneyFile::instance()->account(mdlItem.data(EquitiesModel::EquityID).toString()); } + m_kmymoney->slotSelectInvestment(acc); } -void KInvestmentView::slotInvestmentContextMenu(const QPoint& /*point*/) +void KInvestmentView::slotEquityRightClicked(const QPoint&) { - kmymoney->slotSelectInvestment(); - QTreeWidgetItem *item = m_investmentsList->currentItem(); - if (item) { - kmymoney->slotSelectInvestment(MyMoneyFile::instance()->account(item->data(0, Qt::UserRole).value().id())); - } - emit investmentRightMouseClick(); + slotEquitySelected(QModelIndex(), QModelIndex()); + emit equityRightClicked(); } -void KInvestmentView::slotLoadView() +void KInvestmentView::slotEquityDoubleClicked() { - d->m_needReload[EquitiesTab] = true; - d->m_needReload[SecuritiesTab] = true; - if (isVisible()) - slotTabCurrentChanged(m_tab->currentIndex()); + slotEquitySelected(QModelIndex(), QModelIndex()); + m_kmymoney->actionCollection()->action(m_kmymoney->s_Actions[Action::InvestmentEdit])->trigger(); } -void KInvestmentView::slotTabCurrentChanged(int index) +void KInvestmentView::slotSecuritySelected(const QModelIndex ¤t, const QModelIndex &previous) { - InvestmentsViewTab tab = static_cast(index); - - loadView(tab); -} - -void KInvestmentView::loadAccounts() -{ - MyMoneyFile* file = MyMoneyFile::instance(); - - // check if the current account still exists and make it the - // current account - if (!d->m_account.id().isEmpty()) { - try { - d->m_account = file->account(d->m_account.id()); - } catch (const MyMoneyException &) { - d->m_account = MyMoneyAccount(); - } - } - - d->m_filterProxyModel->invalidate(); - m_accountComboBox->expandAll(); - - if (d->m_account.id().isEmpty()) { - // there are no favorite accounts find any account - QModelIndexList list = d->m_filterProxyModel->match(d->m_filterProxyModel->index(0, 0), - Qt::DisplayRole, - QVariant(QString("*")), - -1, - Qt::MatchFlags(Qt::MatchWildcard | Qt::MatchRecursive)); - for (QModelIndexList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) { - if (!it->parent().isValid()) - continue; // skip the top level accounts - QVariant accountId = (*it).data(AccountsModel::AccountIdRole); - if (accountId.isValid()) { - MyMoneyAccount a = file->account(accountId.toString()); - if (a.value("PreferredAccount") == "Yes") { - d->m_account = a; - break; - } else if (d->m_account.id().isEmpty()) { - d->m_account = a; - } - } - } - } - - if (!d->m_account.id().isEmpty()) { - m_accountComboBox->setSelected(d->m_account.id()); - try { - d->m_precision = MyMoneyMoney::denomToPrec(d->m_account.fraction()); - } catch (const MyMoneyException &) { - qDebug("Security %s for account %s not found", qPrintable(d->m_account.currencyId()), qPrintable(d->m_account.name())); - d->m_precision = 2; - } + Q_UNUSED(current); + Q_UNUSED(previous); + const auto sec = currentSecurity(); + if (!sec.id().isEmpty()) { + MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); + skip.fill(false); + skip.setBit(IMyMoneyStorage::RefCheckPrice); + m_editSecurityButton->setEnabled(true); + m_deleteSecurityButton->setEnabled(!MyMoneyFile::instance()->isReferenced(sec, skip)); + } else { + m_editSecurityButton->setEnabled(false); + m_deleteSecurityButton->setEnabled(false); } } - -bool KInvestmentView::slotSelectAccount(const MyMoneyObject& obj) +void KInvestmentView::slotLoadView() { - if (typeid(obj) != typeid(MyMoneyAccount)) - return false; - - if (d->m_recursion) - return false; - - d->m_recursion = true; - const MyMoneyAccount& acc = dynamic_cast(obj); - bool rc = slotSelectAccount(acc.id()); - d->m_recursion = false; - return rc; + d->m_needReload[Tab::Equities] = d->m_needReload[Tab::Securities] = true; + if (isVisible()) + slotLoadTab(m_tab->currentIndex()); } -bool KInvestmentView::slotSelectAccount(const QString& id, const QString& transactionId, const bool /* reconciliation*/) +void KInvestmentView::selectDefaultInvestmentAccount() { - bool rc = true; - - if (!id.isEmpty()) { - // if the account id differs, then we have to do something - if (d->m_account.id() != id) { - try { - d->m_account = MyMoneyFile::instance()->account(id); - // if a stock account is selected, we show the - // the corresponding parent (investment) account - if (d->m_account.isInvest()) { - d->m_account = MyMoneyFile::instance()->account(d->m_account.parentAccountId()); - } - // TODO if we don't have an investment account, then we should switch to the ledger view - d->m_newAccountLoaded = true; - if (d->m_account.accountType() == MyMoneyAccount::Investment) { - slotLoadView(); - } else { - emit accountSelected(id, transactionId); - d->m_account = MyMoneyAccount(); - d->m_needReload[EquitiesTab] = true; - rc = false; - } - - } catch (const MyMoneyException &) { - qDebug("Unable to retrieve account %s", qPrintable(id)); - rc = false; - } - } else { - emit accountSelected(d->m_account); + if (d->m_accountsProxyModel->rowCount() > 0) { + auto firsitem = d->m_accountsProxyModel->index(0, 0, QModelIndex()); + if (d->m_accountsProxyModel->hasChildren(firsitem)) { + auto seconditem = d->m_accountsProxyModel->index(0, 0, firsitem); + slotSelectAccount(seconditem.data(EquitiesModel::EquityID).toString()); } } - - return rc; } -void KInvestmentView::clear() +void KInvestmentView::slotSelectAccount(const QString &id) { - // setup header font - QFont font = KMyMoneyGlobalSettings::listHeaderFont(); - QFontMetrics fm(font); - int height = fm.lineSpacing() + 6; - m_investmentsList->header()->setMinimumHeight(height); - m_investmentsList->header()->setMaximumHeight(height); - m_investmentsList->header()->setFont(font); - - // setup cell font - font = KMyMoneyGlobalSettings::listCellFont(); - m_investmentsList->setFont(font); - - // clear the table - m_investmentsList->clear(); - - // and the selected account in the combo box - m_accountComboBox->setSelected(QString()); - - // right align col headers for quantity, price and value - for (int i = 2; i < 5; ++i) { - m_investmentsList->headerItem()->setTextAlignment(i, Qt::AlignRight | Qt::AlignVCenter); + if (!id.isEmpty()) { + d->m_idInvAcc = id; + if (isVisible()) + m_accountComboBox->setSelected(id); } } -void KInvestmentView::loadInvestmentTab() +void KInvestmentView::slotSelectAccount(const MyMoneyObject &obj) { - // no account selected - emit accountSelected(MyMoneyAccount()); - - // clear the current contents ... - clear(); + if (typeid(obj) != typeid(MyMoneyAccount)) + return; + const auto acc = dynamic_cast(obj); - // ... load the combobox widget and select current account ... - loadAccounts(); + if (acc.accountType() == MyMoneyAccount::Investment) + slotSelectAccount(acc.id()); +} - if (d->m_account.id().isEmpty()) { - // if we don't have an account we bail out - setEnabled(false); - return; - } - setEnabled(true); - - MyMoneyFile* file = MyMoneyFile::instance(); - bool showClosedAccounts = kmymoney->isActionToggled(Action::ViewShowAll) - || !KMyMoneyGlobalSettings::hideClosedAccounts(); - const bool hideZeroBalance = KMyMoneyGlobalSettings::hideZeroBalanceEquities(); - - try { - d->m_account = file->account(d->m_account.id()); - QStringList securities = d->m_account.accountList(); - - for (QStringList::ConstIterator it = securities.constBegin(); it != securities.constEnd(); ++it) { - MyMoneyAccount acc = file->account(*it); - bool displayThisBalance = true; - if (hideZeroBalance) { - const MyMoneyMoney &balance = file->balance(acc.id()); - displayThisBalance = !balance.isZero(); - } - if ((!acc.isClosed() || showClosedAccounts) && displayThisBalance) - loadInvestmentItem(acc); - } - } catch (const MyMoneyException &) { - qDebug("KInvestmentView::loadView() - selected account does not exist anymore"); - d->m_account = MyMoneyAccount(); +void KInvestmentView::slotLoadAccount(const QString &id) +{ + const auto indexList = d->m_equitiesProxyModel->match(d->m_equitiesProxyModel->index(0,0), EquitiesModel::InvestmentID, id, 1, + Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap)); + if (!indexList.isEmpty()) { + m_equitiesTree->setRootIndex(indexList.first()); + d->m_idInvAcc = id; + if (isVisible()) + emit accountSelected(MyMoneyFile::instance()->account(id)); } - - // and tell everyone what's selected - emit accountSelected(d->m_account); } -void KInvestmentView::loadInvestmentItem(const MyMoneyAccount& account) +void KInvestmentView::loadInvestmentTab() { - QTreeWidgetItem* item = new InvestmentItem(m_investmentsList); - MyMoneySecurity security; - MyMoneyFile* file = MyMoneyFile::instance(); - - security = file->security(account.currencyId()); - MyMoneySecurity tradingCurrency = file->security(security.tradingCurrency()); - - int prec = MyMoneyMoney::denomToPrec(tradingCurrency.smallestAccountFraction()); - - //column 0 (COLUMN_NAME_INDEX) is the name of the stock - item->setText(eInvestmentNameColumn, account.name()); - item->setData(eInvestmentNameColumn, Qt::UserRole, QVariant::fromValue(account)); - - //column 1 (COLUMN_SYMBOL_INDEX) is the ticker symbol - item->setText(eInvestmentSymbolColumn, security.tradingSymbol()); - - //column 2 is the net value (price * quantity owned) - const MyMoneyPrice &price = file->price(account.currencyId(), tradingCurrency.id()); - const MyMoneyMoney &balance = file->balance(account.id()); - if (price.isValid()) { - const MyMoneyMoney &value = balance * price.rate(tradingCurrency.id()); - item->setText(eValueColumn, value.formatMoney(tradingCurrency.tradingSymbol(), prec)); - item->setData(eValueColumn, Qt::UserRole, QVariant::fromValue(value)); - } else { - item->setText(eValueColumn, "---"); + d->m_equitiesProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll)); + d->m_equitiesProxyModel->setHideZeroBalanceAccounts(KMyMoneyGlobalSettings::hideZeroBalanceEquities()); + d->m_equitiesProxyModel->invalidate(); + + d->m_accountsProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !m_kmymoney->isActionToggled(Action::ViewShowAll)); + d->m_accountsProxyModel->invalidate(); + + if (!d->m_idInvAcc.isEmpty()) { // check if account to be selected exist + try { // it could not exist anymore (e.g. another file has been opened) + const auto acc = MyMoneyFile::instance()->account(d->m_idInvAcc); // then this should throw an exception + if (acc.accountType() == MyMoneyAccount::Investment) // it could be that id exists but account in new file isn't investment account anymore + slotSelectAccount(d->m_idInvAcc); // otherwise select preset account + else + d->m_idInvAcc.clear(); + } catch (const MyMoneyException &) { + d->m_idInvAcc.clear(); // account is invalid + } } - item->setTextAlignment(eValueColumn, Qt::AlignRight | Qt::AlignVCenter); - - //column 3 (COLUMN_QUANTITY_INDEX) is the quantity of shares owned - prec = MyMoneyMoney::denomToPrec(security.smallestAccountFraction()); - item->setText(eQuantityColumn, balance.formatMoney("", prec)); - item->setTextAlignment(eQuantityColumn, Qt::AlignRight | Qt::AlignVCenter); - item->setData(eQuantityColumn, Qt::UserRole, QVariant::fromValue(balance)); + if (d->m_idInvAcc.isEmpty()) // if account is invalid select default one + selectDefaultInvestmentAccount(); - //column 4 is the current price - // Get the price precision from the configuration - prec = security.pricePrecision(); - - // prec = MyMoneyMoney::denomToPrec(m_tradingCurrency.smallestAccountFraction()); - if (price.isValid()) { - item->setText(ePriceColumn, price.rate(tradingCurrency.id()).formatMoney(tradingCurrency.tradingSymbol(), prec)); - item->setData(ePriceColumn, Qt::UserRole, QVariant::fromValue(price.rate(tradingCurrency.id()))); - } else { - item->setText(ePriceColumn, "---"); - } - item->setTextAlignment(ePriceColumn, Qt::AlignRight | Qt::AlignVCenter); + m_accountComboBox->expandAll(); } void KInvestmentView::showEvent(QShowEvent* event) { if (m_needLoad) init(); emit aboutToShow(); - /*if (d->m_needReload) { - loadInvestmentTab(); - d->m_needReload = false; - d->m_newAccountLoaded = false; - - } else { - emit accountSelected(d->m_account); - }*/ - - slotTabCurrentChanged(m_tab->currentIndex()); + d->m_needReload[Tab::Equities] = true; // ensure tree view will be reloaded after selecting account in ledger view + slotLoadTab(m_tab->currentIndex()); // don't forget base class implementation QWidget::showEvent(event); } -void KInvestmentView::loadSecuritiesList() -{ - m_securitiesList->setColumnWidth(eIdColumn, 0); - m_securitiesList->setSortingEnabled(false); - m_securitiesList->clear(); - - QList list = MyMoneyFile::instance()->securityList(); - QList::ConstIterator it; - for (it = list.constBegin(); it != list.constEnd(); ++it) { - QTreeWidgetItem* newItem = new QTreeWidgetItem(m_securitiesList); - loadSecurityItem(newItem, *it); - - } - m_securitiesList->setSortingEnabled(true); - - slotUpdateSecuritiesButtons(); -} - -void KInvestmentView::loadSecurityItem(QTreeWidgetItem* item, const MyMoneySecurity& security) +void KInvestmentView::loadSecuritiesTab() { - QString market = security.tradingMarket(); - MyMoneySecurity tradingCurrency; - if (security.isCurrency()) - market = m_currencyMarket; - else - tradingCurrency = MyMoneyFile::instance()->security(security.tradingCurrency()); - - item->setText(eIdColumn, security.id()); - item->setText(eTypeColumn, KMyMoneyUtils::securityTypeToString(security.securityType())); - item->setText(eSecurityNameColumn, security.name()); - item->setText(eSecuritySymbolColumn, security.tradingSymbol()); - item->setText(eMarketColumn, market); - item->setText(eCurrencyColumn, tradingCurrency.tradingSymbol()); - item->setTextAlignment(eCurrencyColumn, Qt::AlignHCenter); - item->setText(eAcctFractionColumn, QString::number(security.smallestAccountFraction())); - - // smallestCashFraction is only applicable for currencies - if (security.isCurrency()) - item->setText(eCashFractionColumn, QString::number(security.smallestCashFraction())); + m_deleteSecurityButton->setEnabled(false); + m_editSecurityButton->setEnabled(false); + + d->m_securitiesProxyModel->invalidate(); + // securities model contains both securities and currencies, so... + // ...search here for securities node and show only this + const auto indexList = d->m_securitiesProxyModel->match(d->m_securitiesProxyModel->index(0, 0), Qt::DisplayRole, QLatin1String("Securities"), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap)); + if (!indexList.isEmpty()) + m_securitiesTree->setRootIndex(indexList.first()); } -void KInvestmentView::slotUpdateSecuritiesButtons() +void KInvestmentView::slotEditSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - - if (item) { - MyMoneySecurity security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); - m_editSecurityButton->setEnabled(item->text(eMarketColumn) != m_currencyMarket); - MyMoneyFileBitArray skip(IMyMoneyStorage::MaxRefCheckBits); - skip.fill(false); - skip.setBit(IMyMoneyStorage::RefCheckPrice); - m_deleteSecurityButton->setEnabled(!MyMoneyFile::instance()->isReferenced(security, skip)); + auto sec = currentSecurity(); - } else { - m_editSecurityButton->setEnabled(false); - m_deleteSecurityButton->setEnabled(false); + if (!sec.id().isEmpty()) { + QPointer dlg = new KNewInvestmentWizard(sec, this); + dlg->setObjectName("KNewInvestmentWizard"); + if (dlg->exec() == QDialog::Accepted) + dlg->createObjects(QString()); + delete dlg; } } -void KInvestmentView::slotEditSecurity() +MyMoneySecurity KInvestmentView::currentSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - if (item) { - MyMoneySecurity security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); + MyMoneySecurity sec; - QPointer dlg = new KNewInvestmentWizard(security, this); - dlg->setObjectName("KNewInvestmentWizard"); - if (dlg->exec() == QDialog::Accepted) { - dlg->createObjects(QString()); - try { - // For some reason, the item gets deselected, and the pointer - // invalidated. So fix it here before continuing. - item = m_securitiesList->findItems(security.id(), Qt::MatchExactly).at(0); - m_securitiesList->setCurrentItem(item); - if (item) { - security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); - loadSecurityItem(item, security); - } - } catch (const MyMoneyException &e) { - KMessageBox::error(this, i18n("Failed to edit security: %1", e.what())); - } - } - delete dlg; + auto treeItem = m_securitiesTree->currentIndex(); + if (treeItem.isValid()) { + auto mdlItem = d->m_securitiesProxyModel->index(treeItem.row(), SecuritiesModel::Security, treeItem.parent()); + try { + sec = MyMoneyFile::instance()->security(mdlItem.data(Qt::UserRole).toString()); + } catch (const MyMoneyException &) {} } + return sec; } void KInvestmentView::slotDeleteSecurity() { - QTreeWidgetItem* item = m_securitiesList->currentItem(); - if (item) - KMyMoneyUtils::deleteSecurity(MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()), this); + auto sec = currentSecurity(); + if (!sec.id().isEmpty()) + KMyMoneyUtils::deleteSecurity(sec, this); } diff --git a/kmymoney/views/kinvestmentview.h b/kmymoney/views/kinvestmentview.h index fec8915e3..f85e18762 100644 --- a/kmymoney/views/kinvestmentview.h +++ b/kmymoney/views/kinvestmentview.h @@ -1,183 +1,137 @@ /*************************************************************************** kinvestmentview.h - description ------------------- begin : Tue Jan 29 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio + (C) 2017 Ł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. * * * ***************************************************************************/ #ifndef KINVESTMENTVIEW_H #define KINVESTMENTVIEW_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes -#include - // ---------------------------------------------------------------------------- // Project Includes -#include -#include -#include "ui_kinvestmentviewdecl.h" - -enum eInvestmentColumn { eInvestmentNameColumn, eInvestmentSymbolColumn, eValueColumn, eQuantityColumn, ePriceColumn }; +#include "ui_kinvestmentview.h" -enum eSecurityColum { eIdColumn, eTypeColumn, eSecurityNameColumn, eSecuritySymbolColumn, eMarketColumn, eCurrencyColumn, eAcctFractionColumn, eCashFractionColumn }; +class KMyMoneyApp; +class KMyMoneyView; +class MyMoneySecurity; /** * @author Kevin Tambascio + * @author Łukasz Wojniłowicz */ -class KInvestmentView : public QWidget, private Ui::KInvestmentViewDecl +class KInvestmentView : public QWidget, private Ui::KInvestmentView { Q_OBJECT public: - KInvestmentView(QWidget *parent = 0); + explicit KInvestmentView(KMyMoneyApp *kmymoney, KMyMoneyView *kmymoneyview); ~KInvestmentView(); - /** - * Start reconciliation for the account in the current view - */ - void reconcileAccount(); - public slots: /** * This slot is used to reload all data from the MyMoneyFile engine. - * All existing data in the view will be discarded. + * All existing data in the view will be invalidated. * Call this e.g. if a new file has been loaded. */ void slotLoadView(); /** - * This slot is used to select the correct ledger view type for - * the account specified by @p id. If @p transactionId is not - * empty, then the respective transaction will be selected. - * - * @param accountId Internal id used for the account to show - * @param transactionId Internal id used for the transaction to select - * @param reconciliation if true, the account will be selected in - * reconciliation mode. If false, it will - * be selected in regular ledger mode. - * - * @retval true selection of account referenced by @p id succeeded - * @retval false selection of account failed + * This slot is used to preselect investment account from ledger view */ - bool slotSelectAccount(const QString& accountId, const QString& transactionId = QString(), const bool reconciliation = false); - - /** - * This method is provided for convenience and acts as the method above. - */ - bool slotSelectAccount(const MyMoneyObject& acc); + void slotSelectAccount(const MyMoneyObject &obj); void showEvent(QShowEvent* event); -protected: - - typedef enum { - EquitiesTab = 0, - SecuritiesTab, - // insert new values above this line - MaxViewTabs - } InvestmentsViewTab; - - /** - * This method loads the investments and securities for the respective tab. - * - * @param tab which tab should be loaded - */ - void loadView(InvestmentsViewTab tab); - - /** - * This method reloads the account selection combo box of the - * view with all asset and liability accounts from the engine. - * If the account id of the current account held in @p m_accountId is - * empty or if the referenced account does not exist in the engine, - * the first account found in the list will be made the current account. - */ - void loadAccounts(); - - /** - * clear the view - */ - void clear(); - - void loadInvestmentTab(); - - void loadInvestmentItem(const MyMoneyAccount& account); - - void loadSecuritiesList(); - - void loadSecurityItem(QTreeWidgetItem* item, const MyMoneySecurity& security); - -protected slots: - void slotTabCurrentChanged(int index); +private slots: /** - * This slot receives the signal from the listview @c lv control that the context menu - * was requested for @c item at @c point. + * This slot is used to reload (filters + equities account) specific tab */ - void slotInvestmentContextMenu(const QPoint& point); + void slotLoadTab(int index); - void slotInvestmentSelectionChanged(); + void slotEquitySelected(const QModelIndex ¤t, const QModelIndex &previous); + void slotEquityRightClicked(const QPoint& point); + void slotEquityDoubleClicked(); + void slotSecuritySelected(const QModelIndex ¤t, const QModelIndex &previous); - void slotUpdateSecuritiesButtons(); void slotEditSecurity(); void slotDeleteSecurity(); + /** + * This slot is used to programatically preselect account in investment view + */ + void slotSelectAccount(const QString &id); -signals: /** - * This signal is emitted, if an account has been selected - * which cannot be handled by this view. + * This slot is used to load investment account into tree view */ - void accountSelected(const QString& accountId, const QString& transactionId); + void slotLoadAccount(const QString &id); +signals: void accountSelected(const MyMoneyObject&); - void investmentRightMouseClick(); + void equityRightClicked(); /** * 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; - const QString m_currencyMarket; + KMyMoneyApp *m_kmymoney; + KMyMoneyView *m_kmymoneyview; + + /** Initializes page and sets its load status to initialized + */ + void init(); + /** - * Search widget for the securities list + * This slot is used to programatically preselect default account in investment view */ - KTreeWidgetSearchLineWidget* m_searchSecuritiesWidget; + void selectDefaultInvestmentAccount(); + enum Tab { Equities = 0, Securities }; + + /** + * This slots are used to reload tabs + */ + void loadInvestmentTab(); + void loadSecuritiesTab(); + + /** + * This slots returns security currently selected in tree view + */ + MyMoneySecurity currentSecurity(); /** * This member holds the load state of page */ bool m_needLoad; - - /** Initializes page and sets its load status to initialized - */ - void init(); }; #endif diff --git a/kmymoney/views/kinvestmentviewdecl.ui b/kmymoney/views/kinvestmentview.ui similarity index 65% rename from kmymoney/views/kinvestmentviewdecl.ui rename to kmymoney/views/kinvestmentview.ui index d663b9a46..f8f2c02c5 100644 --- a/kmymoney/views/kinvestmentviewdecl.ui +++ b/kmymoney/views/kinvestmentview.ui @@ -1,268 +1,243 @@ - KInvestmentViewDecl - + KInvestmentView + 0 0 439 - 164 + 274 Investment Summary 0 Equities 0 0 0 0 Select Account: false Qt::Horizontal QSizePolicy::Expanding 280 20 - - - - 1 - 1 - - - - + + + Qt::CustomContextMenu Summary of the equities contained in this account, showing your holdings and their most recent price. + + QAbstractItemView::DoubleClicked + + + false + true - - QAbstractItemView::SingleSelection + + 0 false + + false + + + true + true - - - Name - - - - - Symbol - - - - - Value - - - - - Quantity - - - - - Price - - Securities - - - - 1 - 1 - + + + + + + QAbstractItemView::DoubleClicked + + + false true - - QAbstractItemView::SingleSelection + + 0 false + + false + + + true + true - - - ID - - - - - Type - - - - - Name - - - - - Symbol - - - - - Market - - - - - Currency - - - - - Fraction - - + + false + + + Modify the selected entry + + + Change the security information of the selected entry. + Edit... + + Delete this entry + + + Remove this security item from the file + Delete... Qt::Vertical QSizePolicy::Expanding 20 110 Qt::Vertical QSizePolicy::Expanding 20 51 + + KFilterProxySearchLine + QWidget +
kfilterproxysearchline.h
+
KComboBox QComboBox
kcombobox.h
KMyMoneyAccountCombo KComboBox
kmymoneyaccountcombo.h
diff --git a/kmymoney/views/kmymoneyview.cpp b/kmymoney/views/kmymoneyview.cpp index 95bad8a26..67913d215 100644 --- a/kmymoney/views/kmymoneyview.cpp +++ b/kmymoney/views/kmymoneyview.cpp @@ -1,2211 +1,2243 @@ /*************************************************************************** kmymoneyview.cpp ------------------- copyright : (C) 2000-2001 by Michael Edwardes 2004 by Thomas Baumgart 2017 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 "kmymoneyview.h" // ---------------------------------------------------------------------------- // 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 "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" +#include +#include +#include #include using namespace Icons; static constexpr KCompressionDevice::CompressionType COMPRESSION_TYPE = KCompressionDevice::GZip; static constexpr char recoveryKeyId[] = "0xD2B08440"; KMyMoneyView::KMyMoneyView(KMyMoneyApp *kmymoney) : KPageWidget(nullptr), 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); } } newStorage(); m_model = new KPageWidgetModel(this); // cannot be parentless, otherwise segfaults at exit 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(); viewFrames[View::Home] = m_model->addPage(m_homeView, i18n("Home")); viewFrames[View::Home]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewHome])); 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(kmymoney, this); viewFrames[View::Institutions] = m_model->addPage(m_institutionsView, i18n("Institutions")); viewFrames[View::Institutions]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInstitutions])); // Page 2 m_accountsView = new KAccountsView(kmymoney, this); viewFrames[View::Accounts] = m_model->addPage(m_accountsView, i18n("Accounts")); viewFrames[View::Accounts]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewAccounts])); // Page 3 m_scheduledView = new KScheduledView(); //this is to solve the way long strings are handled differently among versions of KPageWidget viewFrames[View::Schedules] = m_model->addPage(m_scheduledView, i18n("Scheduled transactions")); viewFrames[View::Schedules]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewSchedules])); 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(kmymoney, this); viewFrames[View::Categories] = m_model->addPage(m_categoriesView, i18n("Categories")); viewFrames[View::Categories]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewCategories])); // Page 5 m_tagsView = new KTagsView(); viewFrames[View::Tags] = m_model->addPage(m_tagsView, i18n("Tags")); viewFrames[View::Tags]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewTags])); 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(); viewFrames[View::Payees] = m_model->addPage(m_payeesView, i18n("Payees")); viewFrames[View::Payees]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewPayees])); 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(); viewFrames[View::Ledgers] = m_model->addPage(m_ledgerView, i18n("Ledgers")); viewFrames[View::Ledgers]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewLedgers])); 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_investmentView = new KInvestmentView(kmymoney, this); viewFrames[View::Investments] = m_model->addPage(m_investmentView, i18n("Investments")); viewFrames[View::Investments]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewInvestment])); - 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(); viewFrames[View::Reports] = m_model->addPage(m_reportsView, i18n("Reports")); viewFrames[View::Reports]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewReports])); 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(kmymoney, this); viewFrames[View::Budget] = m_model->addPage(m_budgetView, i18n("Budgets")); viewFrames[View::Budget]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewBudgets])); // Page 11 m_forecastView = new KForecastView(); viewFrames[View::Forecast] = m_model->addPage(m_forecastView, i18n("Forecast")); viewFrames[View::Forecast]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewForecast])); connect(m_forecastView, SIGNAL(aboutToShow()), this, SIGNAL(aboutToChangeView())); // Page 12 m_onlineJobOutboxView = new KOnlineJobOutbox(); viewFrames[View::OnlineJobOutbox] = m_model->addPage(m_onlineJobOutboxView, i18n("Outbox")); viewFrames[View::OnlineJobOutbox]->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewOutbox])); 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(g_Icons[Icon::DocumentProperties])); connect(this, SIGNAL(fileClosed()), view, SLOT(closeLedgers())); connect(this, SIGNAL(fileOpened()), view, SLOT(openFavoriteLedgers())); //set the model setModel(m_model); setCurrentPage(viewFrames[View::Home]); 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(), 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; } } } } } void KMyMoneyView::slotAccountTreeViewChanged(const AccountsModel::Columns column, const bool show) { struct viewInfo { KRecursiveFilterProxyModel *proxyModel; QList *proxyColumns; }; QList viewInfos; if (m_institutionsView->isLoaded()) viewInfos.append({m_institutionsView->getProxyModel(), m_institutionsView->getProxyColumns()}); if (m_accountsView->isLoaded()) viewInfos.append({m_accountsView->getProxyModel(), m_accountsView->getProxyColumns()}); if (m_categoriesView->isLoaded()) viewInfos.append({m_categoriesView->getProxyModel(), m_categoriesView->getProxyColumns()}); if (m_budgetView->isLoaded()) viewInfos.append({m_budgetView->getProxyModel(), m_budgetView->getProxyColumns()}); if (show) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); if (viewInfos.count() == 1 || KMessageBox::questionYesNo(this, i18n("Do you want to show %1 column on every loaded view?", AccountsModel::getHeaderName(column)), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("ShowColumnOnEveryView")) == KMessageBox::Yes) { foreach(viewInfo view, viewInfos) { if (!view.proxyColumns->contains(column)) { view.proxyColumns->append(column); view.proxyModel->invalidate(); } } } } else { if (viewInfos.count() == 1 || KMessageBox::questionYesNo(this, i18n("Do you want to hide %1 column on every loaded view?", AccountsModel::getHeaderName(column)), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("HideColumnOnEveryView")) == KMessageBox::Yes) { Models::instance()->accountsModel()->setColumnVisibility(column, show); Models::instance()->institutionsModel()->setColumnVisibility(column, show); foreach(viewInfo view, viewInfos) { if (view.proxyColumns->contains(column)) { view.proxyColumns->removeOne(column); view.proxyModel->invalidate(); } } } } } void KMyMoneyView::slotNetBalProChanged(const MyMoneyMoney &val, QLabel *label, const View view) { QString s; const auto isNegative = val.isNegative(); switch (view) { case View::Institutions: case View::Accounts: s = i18n("Net Worth: "); break; case View::Categories: if (isNegative) s = i18n("Loss: "); else s = i18n("Profit: "); break; case View::Budget: s = (i18nc("The balance of the selected budget", "Balance: ")); break; default: return; } // FIXME figure out how to deal with the approximate // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) // s += "~ "; s.replace(QLatin1Char(' '), QLatin1String(" ")); if (isNegative) s.append(QLatin1String("")); const auto sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(val, sec)); s.append((v.replace(QLatin1Char(' '), QLatin1String(" ")))); if (isNegative) s.append(QLatin1String("")); label->setFont(KMyMoneyGlobalSettings::listCellFont()); label->setText(s); } 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 = ( viewFrames[View::Reports] == currentPage() || viewFrames[View::Home] == 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 = (viewFrames[View::Ledgers] == 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 = (viewFrames[View::Ledgers] == 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 = (viewFrames[View::Ledgers] == 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 (viewFrames[View::Accounts]->isEnabled() != m_fileOpen) viewFrames[View::Accounts]->setEnabled(m_fileOpen); if (viewFrames[View::Institutions]->isEnabled() != m_fileOpen) viewFrames[View::Institutions]->setEnabled(m_fileOpen); if (viewFrames[View::Schedules]->isEnabled() != m_fileOpen) viewFrames[View::Schedules]->setEnabled(m_fileOpen); if (viewFrames[View::Categories]->isEnabled() != m_fileOpen) viewFrames[View::Categories]->setEnabled(m_fileOpen); if (viewFrames[View::Payees]->isEnabled() != m_fileOpen) viewFrames[View::Payees]->setEnabled(m_fileOpen); if (viewFrames[View::Tags]->isEnabled() != m_fileOpen) viewFrames[View::Tags]->setEnabled(m_fileOpen); if (viewFrames[View::Budget]->isEnabled() != m_fileOpen) viewFrames[View::Budget]->setEnabled(m_fileOpen); if (viewFrames[View::Ledgers]->isEnabled() != m_fileOpen) viewFrames[View::Ledgers]->setEnabled(m_fileOpen); if (viewFrames[View::Investments]->isEnabled() != m_fileOpen) viewFrames[View::Investments]->setEnabled(m_fileOpen); if (viewFrames[View::Reports]->isEnabled() != m_fileOpen) viewFrames[View::Reports]->setEnabled(m_fileOpen); if (viewFrames[View::Forecast]->isEnabled() != m_fileOpen) viewFrames[View::Forecast]->setEnabled(m_fileOpen); if (viewFrames[View::OnlineJobOutbox]->isEnabled() != m_fileOpen) viewFrames[View::OnlineJobOutbox]->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(); // intentional fall through 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(viewFrames[View::Ledgers]); 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(viewFrames[View::Payees]); m_payeesView->slotSelectPayeeAndTransaction(payee, account, transaction); } void KMyMoneyView::slotTagSelected(const QString& tag, const QString& account, const QString& transaction) { showPage(viewFrames[View::Tags]); 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(viewFrames[View::Reports]); m_reportsView->slotOpenReport(reportid); } void KMyMoneyView::slotShowReport(const MyMoneyReport& report) { showPage(viewFrames[View::Reports]); 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())); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); + disconnect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); + + disconnect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::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; 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) { QIODevice* qfile = nullptr; 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 = viewFrames.value(static_cast(KMyMoneyGlobalSettings::lastViewSelected())); else page = viewFrames[View::Home]; // 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))); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->accountsModel(), &AccountsModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->accountsModel(), &AccountsModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->accountsModel(), &AccountsModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->accountsModel(), &AccountsModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->institutionsModel(), &InstitutionsModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->institutionsModel(), &AccountsModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->equitiesModel(), &EquitiesModel::slotObjectRemoved); + connect(MyMoneyFile::instance(), &MyMoneyFile::balanceChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + connect(MyMoneyFile::instance(), &MyMoneyFile::valueChanged, + Models::instance()->equitiesModel(), &EquitiesModel::slotBalanceOrValueChanged); + + connect(MyMoneyFile::instance(), &MyMoneyFile::objectAdded, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectAdded); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectModified, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectModified); + connect(MyMoneyFile::instance(), &MyMoneyFile::objectRemoved, + Models::instance()->securitiesModel(), &SecuritiesModel::slotObjectRemoved); // 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())); + connect(MyMoneyFile::instance(), &MyMoneyFile::dataChanged, m_homeView, &KHomeView::slotLoadView); // if we currently see a different page, then select the right one if (page != currentPage()) { 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(viewFrames[View::Ledgers]); // 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 (viewFrames[View::Accounts] != currentPage()) showPage(viewFrames[View::Accounts]); 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, KPageModel::HeaderRole).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 (viewFrames[View::Reports] == currentPage()) m_reportsView->slotPrintView(); else if (viewFrames[View::Home] == currentPage()) m_homeView->slotPrintView(); } void KMyMoneyView::slotShowHomePage() { setCurrentPage(viewFrames[View::Home]); } 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; }