diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp index 7f4db712e..05ba944a9 100644 --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -1,7866 +1,7712 @@ /*************************************************************************** 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 #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #ifdef KF5Holidays_FOUND #include #include #endif // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyglobalsettings.h" #include "kmymoneyadaptor.h" #include "dialogs/settings/ksettingskmymoney.h" #include "dialogs/kbackupdlg.h" #include "dialogs/kexportdlg.h" #include "dialogs/kimportdlg.h" #include "dialogs/mymoneyqifprofileeditor.h" #include "dialogs/kenterscheduledlg.h" #include "dialogs/kconfirmmanualenterdlg.h" #include "dialogs/kmymoneypricedlg.h" #include "dialogs/kcurrencyeditdlg.h" #include "dialogs/kequitypriceupdatedlg.h" #include "dialogs/kmymoneyfileinfodlg.h" #include "dialogs/kfindtransactiondlg.h" #include "dialogs/knewbankdlg.h" #include "wizards/newinvestmentwizard/knewinvestmentwizard.h" #include "dialogs/knewaccountdlg.h" #include "dialogs/editpersonaldatadlg.h" #include "dialogs/kselectdatabasedlg.h" #include "dialogs/kcurrencycalculator.h" #include "dialogs/keditscheduledlg.h" #include "wizards/newloanwizard/knewloanwizard.h" #include "wizards/newloanwizard/keditloanwizard.h" #include "dialogs/kpayeereassigndlg.h" #include "dialogs/ktagreassigndlg.h" #include "dialogs/kcategoryreassigndlg.h" #include "dialogs/kmergetransactionsdlg.h" #include "wizards/endingbalancedlg/kendingbalancedlg.h" #include "dialogs/kbalancechartdlg.h" #include "dialogs/kgeneratesqldlg.h" #include "dialogs/kloadtemplatedlg.h" #include "dialogs/kgpgkeyselectiondlg.h" #include "dialogs/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/mymoneyqifwriter.h" #include "converter/mymoneyqifreader.h" #include "converter/mymoneystatementreader.h" #include "converter/mymoneytemplate.h" #include "plugins/interfaces/kmmviewinterface.h" #include "plugins/interfaces/kmmstatementinterface.h" #include "plugins/interfaces/kmmimportinterface.h" #include "plugins/interfaceloader.h" #include "plugins/onlinepluginextended.h" #include "pluginloader.h" #include "icons/icons.h" #include #include #include "konlinetransferform.h" #include #include #include "kmymoneyutils.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::FileImportQIF, QStringLiteral("file_import_qif")}, + {Action::FileExportQIF, QStringLiteral("file_export_qif")}, + {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::ToolQIF, QStringLiteral("tools_qif_editor")}, + {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_myMoneyView(0), m_progressBar(0), m_qifReader(0), m_smtReader(0), m_searchDlg(0), m_autoSaveTimer(0), m_progressTimer(0), m_inAutoSaving(false), m_transactionEditor(0), m_endingBalanceDlg(0), m_saveEncrypted(0), m_additionalKeyLabel(0), m_additionalKeyButton(0), m_recentFiles(0), #ifdef KF5Holidays_FOUND m_holidayRegion(0), #endif m_applicationIsReady(true) { // since the days of the week are from 1 to 7, // and a day of the week is used to index this bit array, // resize the array to 8 elements (element 0 is left unused) m_processingDays.resize(8); } void closeFile(); void unlinkStatementXML(); void moveInvestmentTransaction(const QString& fromId, const QString& toId, const MyMoneyTransaction& t); QList > automaticReconciliation(const MyMoneyAccount &account, const QList > &transactions, const MyMoneyMoney &amount); /** * The public interface. */ KMyMoneyApp * const q; MyMoneyFileTransaction* m_ft; kMyMoneyAccountSelector* m_moveToAccountSelector; int m_statementXMLindex; KBalanceWarning* m_balanceWarning; bool m_collectingStatements; QStringList m_statementResults; KMyMoneyPlugin::PluginLoader* m_pluginLoader; QString m_lastPayeeEnteredId; /** the configuration object of the application */ KSharedConfigPtr m_config; /** * @brief List of all plugged plugins * * The key is the file name of the plugin. */ QMap m_plugins; /** * @brief List of plugged importer plugins * * The key is the objectName of the plugin. */ QMap m_importerPlugins; /** * @brief List of plugged online plugins * * The key is the objectName of the plugin. */ QMap m_onlinePlugins; /** * The following variable represents the state while crafting a backup. * It can have the following values * * - IDLE: the default value if not performing a backup * - MOUNTING: when a mount command has been issued * - COPYING: when a copy command has been issued * - UNMOUNTING: when an unmount command has been issued */ backupStateE m_backupState; /** * This variable keeps the result of the backup operation. */ int m_backupResult; /** * This variable is set, when the user selected to mount/unmount * the backup volume. */ bool m_backupMount; QProcess m_proc; /// A pointer to the view holding the tabs. KMyMoneyView *m_myMoneyView; /// The URL of the file currently being edited when open. QUrl m_fileName; bool m_startDialog; QString m_mountpoint; QProgressBar* m_progressBar; QTime m_lastUpdate; QLabel* m_statusLabel; MyMoneyQifReader* m_qifReader; MyMoneyStatementReader* m_smtReader; // allows multiple imports to be launched trough web connect and to be executed sequentially QQueue m_importUrlsQueue; KFindTransactionDlg* m_searchDlg; QObject* m_pluginInterface; MyMoneyAccount m_selectedAccount; MyMoneyAccount m_reconciliationAccount; MyMoneyAccount m_selectedInvestment; MyMoneyInstitution m_selectedInstitution; MyMoneySchedule m_selectedSchedule; MyMoneySecurity m_selectedCurrency; MyMoneyPrice m_selectedPrice; QList m_selectedPayees; QList m_selectedTags; QList m_selectedBudgets; KMyMoneyRegister::SelectedTransactions m_selectedTransactions; // This is Auto Saving related bool m_autoSaveEnabled; QTimer* m_autoSaveTimer; QTimer* m_progressTimer; int m_autoSavePeriod; bool m_inAutoSaving; // pointer to the current transaction editor TransactionEditor* m_transactionEditor; // Reconciliation dialog KEndingBalanceDlg* m_endingBalanceDlg; // Pointer to the combo box used for key selection during // File/Save as KComboBox* m_saveEncrypted; // id's that need to be remembered QString m_accountGoto, m_payeeGoto; QStringList m_additionalGpgKeys; QLabel* m_additionalKeyLabel; QPushButton* m_additionalKeyButton; KRecentFilesAction* m_recentFiles; #ifdef KF5Holidays_FOUND // used by the calendar interface for schedules KHolidays::HolidayRegion* m_holidayRegion; #endif QBitArray m_processingDays; QMap m_holidayMap; QStringList m_consistencyCheckResult; bool m_applicationIsReady; // methods void consistencyCheck(bool alwaysDisplayResults); void setCustomColors(); void copyConsistencyCheckResults(); void saveConsistencyCheckResults(); }; KMyMoneyApp::KMyMoneyApp(QWidget* parent) : KXmlGuiWindow(parent), d(new Private(this)) { #ifdef KMM_DBUS new KmymoneyAdaptor(this); QDBusConnection::sessionBus().registerObject("/KMymoney", this); QDBusConnection::sessionBus().interface()->registerService( "org.kde.kmymoney", QDBusConnectionInterface::DontQueueService); #endif // Register the main engine types used as meta-objects qRegisterMetaType("MyMoneyMoney"); qRegisterMetaType("MyMoneySecurity"); // preset the pointer because we need it during the course of this constructor kmymoney = this; d->m_config = KSharedConfig::openConfig(); d->setCustomColors(); MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); updateCaption(true); QFrame* frame = new QFrame(this); frame->setFrameStyle(QFrame::NoFrame); // values for margin (11) and spacing(6) taken from KDialog implementation QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, frame); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(6); Icons::setIconThemeNames(KMyMoneySettings::iconsTheme()); if (KMyMoneySettings::iconsTheme().compare(QLatin1Literal("system")) != 0) QIcon::setThemeName(KMyMoneySettings::iconsTheme()); initStatusBar(); initActions(); initDynamicMenus(); d->m_myMoneyView = new KMyMoneyView(frame); layout->addWidget(d->m_myMoneyView, 10); connect(d->m_myMoneyView, SIGNAL(aboutToChangeView()), this, SLOT(slotResetSelections())); connect(d->m_myMoneyView, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slotUpdateActions())); connectActionsAndViews(); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts readOptions(); // now initialize the plugin structure createInterfaces(); loadPlugins(); setCentralWidget(frame); connect(&d->m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited())); // force to show the home page if the file is closed - connect(action("view_show_transaction_detail"), SIGNAL(toggled(bool)), d->m_myMoneyView, SLOT(slotShowTransactionDetail(bool))); + 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 d->m_searchDlg; delete d->m_qifReader; delete d->m_transactionEditor; delete d->m_endingBalanceDlg; delete d->m_moveToAccountSelector; #ifdef KF5Holidays_FOUND delete d->m_holidayRegion; #endif delete d; } QUrl KMyMoneyApp::lastOpenedURL() { QUrl url = d->m_startDialog ? QUrl() : d->m_fileName; if (!url.isValid()) { url = QUrl::fromUserInput(readLastUsedFile()); } ready(); return url; } void KMyMoneyApp::slotObjectDestroyed(QObject* o) { if (o == d->m_moveToAccountSelector) { d->m_moveToAccountSelector = 0; } } void KMyMoneyApp::slotInstallConsistencyCheckContextMenu() { // this code relies on the implementation of KMessageBox::informationList to add a context menu to that list, // please adjust it if it's necessary or rewrite the way the consistency check results are displayed if (QWidget* dialog = QApplication::activeModalWidget()) { if (QListWidget* widget = dialog->findChild()) { // give the user a hint that the data can be saved widget->setToolTip(i18n("This is the consistency check log, use the context menu to copy or save it.")); widget->setWhatsThis(widget->toolTip()); widget->setContextMenuPolicy(Qt::CustomContextMenu); connect(widget, SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotShowContextMenuForConsistencyCheck(QPoint))); } } } void KMyMoneyApp::slotShowContextMenuForConsistencyCheck(const QPoint &pos) { // allow the user to save the consistency check results if (QWidget* widget = qobject_cast< QWidget* >(sender())) { QMenu contextMenu(widget); QAction* copy = new QAction(i18n("Copy to clipboard"), widget); QAction* save = new QAction(i18n("Save to file"), widget); contextMenu.addAction(copy); contextMenu.addAction(save); QAction *result = contextMenu.exec(widget->mapToGlobal(pos)); if (result == copy) { // copy the consistency check results to the clipboard d->copyConsistencyCheckResults(); } else if (result == save) { // save the consistency check results to a file d->saveConsistencyCheckResults(); } } } void KMyMoneyApp::createTransactionMoveMenu() { if (!d->m_moveToAccountSelector) { QWidget* w = factory()->container("transaction_move_menu", this); QMenu *menu = dynamic_cast(w); if (menu) { QWidgetAction *accountSelectorAction = new QWidgetAction(menu); d->m_moveToAccountSelector = new kMyMoneyAccountSelector(menu, 0, false); d->m_moveToAccountSelector->setObjectName("transaction_move_menu_selector"); accountSelectorAction->setDefaultWidget(d->m_moveToAccountSelector); menu->addAction(accountSelectorAction); connect(d->m_moveToAccountSelector, SIGNAL(destroyed(QObject*)), this, SLOT(slotObjectDestroyed(QObject*))); connect(d->m_moveToAccountSelector, SIGNAL(itemSelected(QString)), this, SLOT(slotMoveToAccount(QString))); } } } void KMyMoneyApp::initDynamicMenus() { connect(this, SIGNAL(accountSelected(MyMoneyAccount)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateMoveToAccountMenu())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotUpdateMoveToAccountMenu())); } void KMyMoneyApp::initActions() { + KActionCollection *aC = actionCollection(); + // ************* - // The File menu + // 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; - actionCollection()->addAction(KStandardAction::New, this, SLOT(slotFileNew())); - actionCollection()->addAction(KStandardAction::Open, this, SLOT(slotFileOpen())); - d->m_recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); - actionCollection()->addAction(KStandardAction::Save, this, SLOT(slotFileSave())); - actionCollection()->addAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); - actionCollection()->addAction(KStandardAction::Close, this, SLOT(slotFileClose())); - actionCollection()->addAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); - actionCollection()->addAction(KStandardAction::Print, this, SLOT(slotPrintView())); - - QAction *open_database = actionCollection()->addAction("open_database"); - open_database->setText(i18n("Open database...")); - connect(open_database, SIGNAL(triggered()), this, SLOT(slotOpenDatabase())); - - QAction *saveas_database = actionCollection()->addAction("saveas_database"); - saveas_database->setText(i18n("Save as database...")); - saveas_database->setIcon(QIcon::fromTheme(g_Icons[Icon::SVNUpdate])); - connect(saveas_database, SIGNAL(triggered()), this, SLOT(slotSaveAsDatabase())); - - QAction *file_backup = actionCollection()->addAction("file_backup"); - file_backup->setText(i18n("Backup...")); - file_backup->setIcon(QIcon::fromTheme(g_Icons[Icon::FileArchiver])); - connect(file_backup, SIGNAL(triggered()), this, SLOT(slotFileBackup())); - - QAction *file_import_qif = actionCollection()->addAction("file_import_qif"); - file_import_qif->setText(i18n("QIF...")); - connect(file_import_qif, SIGNAL(triggered()), this, SLOT(slotQifImport())); - - QAction *file_import_gnc = actionCollection()->addAction("file_import_gnc"); - file_import_gnc->setText(i18n("GnuCash...")); - connect(file_import_gnc, SIGNAL(triggered()), this, SLOT(slotGncImport())); - - QAction *file_import_statement = actionCollection()->addAction("file_import_statement"); - file_import_statement->setText(i18n("Statement file...")); - connect(file_import_statement, SIGNAL(triggered()), this, SLOT(slotStatementImport())); - - QAction *file_import_template = actionCollection()->addAction("file_import_template"); - file_import_template->setText(i18n("Account Template...")); - connect(file_import_template, SIGNAL(triggered()), this, SLOT(slotLoadAccountTemplates())); - - QAction *file_export_template = actionCollection()->addAction("file_export_template"); - file_export_template->setText(i18n("Account Template...")); - connect(file_export_template, SIGNAL(triggered()), this, SLOT(slotSaveAccountTemplates())); - - QAction *file_export_qif = actionCollection()->addAction("file_export_qif"); - file_export_qif->setText(i18n("QIF...")); - connect(file_export_qif, SIGNAL(triggered()), this, SLOT(slotQifExport())); - - QAction *view_personal_data = actionCollection()->addAction("view_personal_data"); - view_personal_data->setText(i18n("Personal Data...")); - view_personal_data->setIcon(QIcon::fromTheme(g_Icons[Icon::UserProperties])); - connect(view_personal_data, SIGNAL(triggered()), this, SLOT(slotFileViewPersonal())); - + // ************* + // 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::slotFileBackup, i18n("Backup..."), Icon::Empty}, + {Action::FileImportGNC, &KMyMoneyApp::slotGncImport, i18n("GnuCash..."), Icon::Empty}, + {Action::FileImportQIF, &KMyMoneyApp::slotQifImport, i18n("QIF..."), Icon::Empty}, + {Action::FileExportQIF, &KMyMoneyApp::slotQifExport, i18n("QIF..."), 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 - QAction *file_dump = actionCollection()->addAction("file_dump"); - file_dump->setText(i18n("Dump Memory")); - connect(file_dump, SIGNAL(triggered()), this, SLOT(slotFileFileInfo())); + {Action::FileDump, &KMyMoneyApp::slotFileFileInfo, i18n("Dump Memory"), Icon::Empty}, #endif - - QAction *view_file_info = actionCollection()->addAction("view_file_info"); - view_file_info->setText(i18n("File-Information...")); - view_file_info->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentProperties])); - connect(view_file_info, SIGNAL(triggered()), this, SLOT(slotFileInfoDialog())); + {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::ToolQIF, &KMyMoneyApp::slotQifProfileEditor, i18n("QIF Profile Editor..."), Icon::DocumentProperties}, + {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(); + // 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 + } + } // ************* - // The Edit menu + // Setting some of added actions checkable // ************* - QAction *edit_find_transaction = actionCollection()->addAction("edit_find_transaction"); - edit_find_transaction->setText(i18n("Find transaction...")); - edit_find_transaction->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialTransfer], g_Icons[Icon::EditFind])); - actionCollection()->setDefaultShortcut(edit_find_transaction, QKeySequence("Ctrl+F")); - connect(edit_find_transaction, SIGNAL(triggered()), this, SLOT(slotFindTransaction())); + { + // 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); + } // ************* - // The View menu + // Setting custom icons for some of added actions // ************* - KToggleAction *view_show_transaction_detail = actionCollection()->add("view_show_transaction_detail"); - view_show_transaction_detail->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewTransactionDetail])); - view_show_transaction_detail->setText(i18n("Show Transaction Detail")); - actionCollection()->setDefaultShortcut(view_show_transaction_detail, QKeySequence("Ctrl+T")); - - KToggleAction *view_hide_reconciled_transactions = actionCollection()->add("view_hide_reconciled_transactions"); - view_hide_reconciled_transactions->setText(i18n("Hide reconciled transactions")); - if (QIcon::hasThemeIcon(QStringLiteral("hide-reconciled"))) - view_hide_reconciled_transactions->setIcon(QIcon::fromTheme(g_Icons[Icon::HideReconciled])); - else - view_hide_reconciled_transactions->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::Merge], g_Icons[Icon::ViewClose])); - actionCollection()->setDefaultShortcut(view_hide_reconciled_transactions, QKeySequence("Ctrl+R")); - connect(view_hide_reconciled_transactions, SIGNAL(triggered()), this, SLOT(slotHideReconciledTransactions())); - - KToggleAction *view_hide_unused_categories = actionCollection()->add("view_hide_unused_categories"); - view_hide_unused_categories->setText(i18n("Hide unused categories")); - if (QIcon::hasThemeIcon(QStringLiteral("hide-categories"))) - view_hide_unused_categories->setIcon(QIcon::fromTheme(g_Icons[Icon::HideCategories])); - else - view_hide_unused_categories->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialCategories], g_Icons[Icon::ViewClose])); - actionCollection()->setDefaultShortcut(view_hide_unused_categories, QKeySequence("Ctrl+U")); - connect(view_hide_unused_categories, SIGNAL(triggered()), this, SLOT(slotHideUnusedCategories())); - - KToggleAction *view_show_all_accounts = actionCollection()->add("view_show_all_accounts"); - view_show_all_accounts->setText(i18n("Show all accounts")); - actionCollection()->setDefaultShortcut(view_show_all_accounts, QKeySequence("Ctrl+Shift+A")); - connect(view_show_all_accounts, SIGNAL(triggered()), this, SLOT(slotShowAllAccounts())); - - // ********************* - // The institutions menu - // ********************* - QAction *institution_new = actionCollection()->addAction("institution_new"); - institution_new->setText(i18n("New institution...")); - institution_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBank], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - connect(institution_new, SIGNAL(triggered()), this, SLOT(slotInstitutionNew())); - - QAction *institution_edit = actionCollection()->addAction("institution_edit"); - institution_edit->setText(i18n("Edit institution...")); - institution_edit->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBank], g_Icons[Icon::DocumentEdit])); - connect(institution_edit, SIGNAL(triggered()), this, SLOT(slotInstitutionEdit())); - - QAction *institution_delete = actionCollection()->addAction("institution_delete"); - institution_delete->setText(i18n("Delete institution...")); - institution_delete->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBank], g_Icons[Icon::EditDelete])); - connect(institution_delete, SIGNAL(triggered()), this, SLOT(slotInstitutionDelete())); - - // ***************** - // The accounts menu - // ***************** - QAction *account_new = actionCollection()->addAction("account_new"); - account_new->setText(i18n("New account...")); - account_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - connect(account_new, SIGNAL(triggered()), this, SLOT(slotAccountNew())); - - // note : action "category_new" is included in this menu but defined below - QAction *account_open = actionCollection()->addAction("account_open"); - account_open->setText(i18n("Open ledger")); - account_open->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewFinancialList])); - connect(account_open, SIGNAL(triggered()), this, SLOT(slotAccountOpen())); - - QAction *account_reconcile = actionCollection()->addAction("account_reconcile"); - account_reconcile->setText(i18n("Reconcile...")); - account_reconcile->setIcon(QIcon::fromTheme(g_Icons[Icon::Reconcile])); - actionCollection()->setDefaultShortcut(account_reconcile, QKeySequence("Ctrl+Shift+R")); - connect(account_reconcile, SIGNAL(triggered()), this, SLOT(slotAccountReconcileStart())); - - QAction *account_reconcile_finish = actionCollection()->addAction("account_reconcile_finish"); - account_reconcile_finish->setText(i18nc("Finish reconciliation", "Finish")); - account_reconcile_finish->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::Merge], g_Icons[Icon::DialogOK])); - connect(account_reconcile_finish, SIGNAL(triggered()), this, SLOT(slotAccountReconcileFinish())); - - QAction *account_reconcile_postpone = actionCollection()->addAction("account_reconcile_postpone"); - account_reconcile_postpone->setText(i18n("Postpone reconciliation")); - account_reconcile_postpone->setIcon(QIcon::fromTheme(g_Icons[Icon::MediaPlaybackPause])); - connect(account_reconcile_postpone, SIGNAL(triggered()), this, SLOT(slotAccountReconcilePostpone())); - - QAction *account_edit = actionCollection()->addAction("account_edit"); - account_edit->setText(i18n("Edit account...")); - account_edit->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::DocumentEdit])); - connect(account_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); - - QAction *account_delete = actionCollection()->addAction("account_delete"); - account_delete->setText(i18n("Delete account...")); - account_delete->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::EditDelete])); - connect(account_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); - - QAction *account_close = actionCollection()->addAction("account_close"); - account_close->setText(i18n("Close account")); - account_close->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::DialogClose])); - connect(account_close, SIGNAL(triggered()), this, SLOT(slotAccountClose())); - - QAction *account_reopen = actionCollection()->addAction("account_reopen"); - account_reopen->setText(i18n("Reopen account")); - account_reopen->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::DialogOK])); - connect(account_reopen, SIGNAL(triggered()), this, SLOT(slotAccountReopen())); - - QAction *account_transaction_report = actionCollection()->addAction("account_transaction_report"); - account_transaction_report->setText(i18n("Transaction report")); - account_transaction_report->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewFinancialList])); - connect(account_transaction_report, SIGNAL(triggered()), this, SLOT(slotAccountTransactionReport())); - - QAction *account_chart = actionCollection()->addAction("account_chart"); - account_chart->setText(i18n("Show balance chart...")); - account_chart->setIcon(QIcon::fromTheme(g_Icons[Icon::OfficeChartLine])); - connect(account_chart, SIGNAL(triggered()), this, SLOT(slotAccountChart())); - - QAction *account_online_map = actionCollection()->addAction("account_online_map"); - account_online_map->setText(i18n("Map to online account...")); - account_online_map->setIcon(QIcon::fromTheme(g_Icons[Icon::NewsSubscribe])); - connect(account_online_map, SIGNAL(triggered()), this, SLOT(slotAccountMapOnline())); - - QAction *account_online_unmap = actionCollection()->addAction("account_online_unmap"); - account_online_unmap->setText(i18n("Unmap account...")); - account_online_unmap->setIcon(QIcon::fromTheme(g_Icons[Icon::NewsUnsubscribe])); - connect(account_online_unmap, SIGNAL(triggered()), this, SLOT(slotAccountUnmapOnline())); - - KActionMenu* menu = new KActionMenu(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::Download]), i18nc("Update online accounts menu", "Update"), this); - actionCollection()->addAction("account_online_update_menu", menu); - - // activating the menu button is the same as selecting the current account - connect(menu, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); - - QAction *account_online_update = actionCollection()->addAction("account_online_update"); - account_online_update->setText(i18n("Update account...")); - account_online_update->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::Download])); - connect(account_online_update, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnline())); - menu->addAction(account_online_update); - - QAction *account_online_update_all = actionCollection()->addAction("account_online_update_all"); - account_online_update_all->setText(i18n("Update all accounts...")); - account_online_update_all->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::Download])); - connect(account_online_update_all, SIGNAL(triggered()), this, SLOT(slotAccountUpdateOnlineAll())); - menu->addAction(account_online_update_all); - - QAction *account_online_transfer = actionCollection()->addAction("account_online_new_credit_transfer"); - account_online_transfer->setText(i18n("New credit transfer")); - account_online_transfer->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewBankAccount], g_Icons[Icon::MailMessageNew])); - connect(account_online_transfer, SIGNAL(triggered()), this, SLOT(slotNewOnlineTransfer())); - connect(onlineJobAdministration::instance(), SIGNAL(canSendCreditTransferChanged(bool)), account_online_transfer, SLOT(setEnabled(bool))); - - // ******************* - // The categories menu - // ******************* - QAction *category_new = actionCollection()->addAction("category_new"); - category_new->setText(i18n("New category...")); - category_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialCategories], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - connect(category_new, SIGNAL(triggered()), this, SLOT(slotCategoryNew())); - - QAction *category_edit = actionCollection()->addAction("category_edit"); - category_edit->setText(i18n("Edit category...")); - category_edit->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialCategories], g_Icons[Icon::DocumentEdit])); - connect(category_edit, SIGNAL(triggered()), this, SLOT(slotAccountEdit())); - - QAction *category_delete = actionCollection()->addAction("category_delete"); - category_delete->setText(i18n("Delete category...")); - category_delete->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialCategories], g_Icons[Icon::EditDelete])); - connect(category_delete, SIGNAL(triggered()), this, SLOT(slotAccountDelete())); - - // ************** - // The tools menu - // ************** - QAction *tools_qif_editor = actionCollection()->addAction("tools_qif_editor"); - tools_qif_editor->setText(i18n("QIF Profile Editor...")); - tools_qif_editor->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentProperties])); - connect(tools_qif_editor, SIGNAL(triggered()), this, SLOT(slotQifProfileEditor())); - - QAction *tools_currency_editor = actionCollection()->addAction("tools_currency_editor"); - tools_currency_editor->setText(i18n("Currencies...")); - tools_currency_editor->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewCurrencyList])); - connect(tools_currency_editor, SIGNAL(triggered()), this, SLOT(slotCurrencyDialog())); - - QAction *tools_price_editor = actionCollection()->addAction("tools_price_editor"); - tools_price_editor->setText(i18n("Prices...")); - connect(tools_price_editor, SIGNAL(triggered()), this, SLOT(slotPriceDialog())); - - QAction *tools_update_prices = actionCollection()->addAction("tools_update_prices"); - tools_update_prices->setText(i18n("Update Stock and Currency Prices...")); - tools_update_prices->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::Download])); - connect(tools_update_prices, SIGNAL(triggered()), this, SLOT(slotEquityPriceUpdate())); - - QAction *tools_consistency_check = actionCollection()->addAction("tools_consistency_check"); - tools_consistency_check->setText(i18n("Consistency Check")); - connect(tools_consistency_check, SIGNAL(triggered()), this, SLOT(slotFileConsistencyCheck())); - - QAction *tools_performancetest = actionCollection()->addAction("tools_performancetest"); - tools_performancetest->setText(i18n("Performance-Test")); - tools_performancetest->setIcon(QIcon::fromTheme(g_Icons[Icon::Fork])); - connect(tools_performancetest, SIGNAL(triggered()), this, SLOT(slotPerformanceTest())); - - QAction *tools_generate_sql = actionCollection()->addAction("tools_generate_sql"); - tools_generate_sql->setText(i18n("Generate Database SQL")); - connect(tools_generate_sql, SIGNAL(triggered()), this, SLOT(slotGenerateSql())); - - QAction *tools_kcalc = actionCollection()->addAction("tools_kcalc"); - tools_kcalc->setText(i18n("Calculator...")); - tools_kcalc->setIcon(QIcon::fromTheme(g_Icons[Icon::AccessoriesCalculator])); - connect(tools_kcalc, SIGNAL(triggered()), this, SLOT(slotToolsStartKCalc())); - - // ***************** - // The settings menu - // ***************** - actionCollection()->addAction(KStandardAction::Preferences, this, SLOT(slotSettings())); - - QAction *settings_enable_messages = actionCollection()->addAction("settings_enable_messages"); - settings_enable_messages->setText(i18n("Enable all messages")); - connect(settings_enable_messages, SIGNAL(triggered()), this, SLOT(slotEnableMessages())); - - QAction *settings_language = actionCollection()->addAction("settings_language"); - settings_language->setText(i18n("KDE language settings...")); - connect(settings_language, SIGNAL(triggered()), this, SLOT(slotKDELanguageSettings())); + { + // 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)); + } // ************* - // The help menu + // Setting keyboard shortcuts for some of added actions // ************* - QAction *help_show_tip = actionCollection()->addAction("help_show_tip"); - help_show_tip->setText(i18n("&Show tip of the day")); - help_show_tip->setIcon(QIcon::fromTheme(g_Icons[Icon::Tip])); - connect(help_show_tip, SIGNAL(triggered()), this, SLOT(slotShowTipOfTheDay())); - - // *************************** - // Actions w/o main menu entry - // *************************** - QAction *transaction_new = actionCollection()->addAction("transaction_new"); - transaction_new->setText(i18nc("New transaction button", "New")); - transaction_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialTransfer], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - actionCollection()->setDefaultShortcut(transaction_new, QKeySequence(Qt::CTRL | Qt::Key_Insert)); - connect(transaction_new, SIGNAL(triggered()), this, SLOT(slotTransactionsNew())); - - - // we use Return as the same shortcut for Edit and Enter. Therefore, we don't allow - // to change them (The standard KDE dialog complains anyway if you want to assign - // the same shortcut to two actions) - QAction *transaction_edit = actionCollection()->addAction("transaction_edit"); - transaction_edit->setText(i18nc("Edit transaction button", "Edit")); - transaction_edit->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialTransfer], g_Icons[Icon::DocumentEdit])); - connect(transaction_edit, SIGNAL(triggered()), this, SLOT(slotTransactionsEdit())); - - QAction *transaction_enter = actionCollection()->addAction("transaction_enter"); - transaction_enter->setText(i18nc("Enter transaction", "Enter")); - transaction_enter->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogOK])); - connect(transaction_enter, SIGNAL(triggered()), this, SLOT(slotTransactionsEnter())); - - QAction *transaction_editsplits = actionCollection()->addAction("transaction_editsplits"); - transaction_editsplits->setText(i18nc("Edit split button", "Edit splits")); - transaction_editsplits->setIcon(QIcon::fromTheme(g_Icons[Icon::Split])); - connect(transaction_editsplits, SIGNAL(triggered()), this, SLOT(slotTransactionsEditSplits())); - - QAction *transaction_cancel = actionCollection()->addAction("transaction_cancel"); - transaction_cancel->setText(i18nc("Cancel transaction edit", "Cancel")); - transaction_cancel->setIcon(QIcon::fromTheme(g_Icons[Icon::DialogCancel])); - connect(transaction_cancel, SIGNAL(triggered()), this, SLOT(slotTransactionsCancel())); - - QAction *transaction_delete = actionCollection()->addAction("transaction_delete"); - transaction_delete->setText(i18nc("Delete transaction", "Delete")); - transaction_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); - connect(transaction_delete, SIGNAL(triggered()), this, SLOT(slotTransactionsDelete())); - - QAction *transaction_duplicate = actionCollection()->addAction("transaction_duplicate"); - transaction_duplicate->setText(i18nc("Duplicate transaction", "Duplicate")); - transaction_duplicate->setIcon(QIcon::fromTheme(g_Icons[Icon::EditCopy])); - connect(transaction_duplicate, SIGNAL(triggered()), this, SLOT(slotTransactionDuplicate())); - - QAction *transaction_match = actionCollection()->addAction("transaction_match"); - transaction_match->setText(i18nc("Button text for match transaction", "Match")); - transaction_match->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialTransfer], g_Icons[Icon::DocumentImport])); - connect(transaction_match, SIGNAL(triggered()), this, SLOT(slotTransactionMatch())); - - QAction *transaction_accept = actionCollection()->addAction("transaction_accept"); - transaction_accept->setText(i18nc("Accept 'imported' and 'matched' transaction", "Accept")); - transaction_accept->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewFinancialTransfer], g_Icons[Icon::DialogOKApply])); - connect(transaction_accept, SIGNAL(triggered()), this, SLOT(slotTransactionsAccept())); - - QAction *transaction_mark_toggle = actionCollection()->addAction("transaction_mark_toggle"); - transaction_mark_toggle->setText(i18nc("Toggle reconciliation flag", "Toggle")); - actionCollection()->setDefaultShortcut(transaction_mark_toggle, QKeySequence("Ctrl+Space")); - connect(transaction_mark_toggle, SIGNAL(triggered()), this, SLOT(slotToggleReconciliationFlag())); - - QAction *transaction_mark_cleared = actionCollection()->addAction("transaction_mark_cleared"); - transaction_mark_cleared->setText(i18nc("Mark transaction cleared", "Cleared")); - actionCollection()->setDefaultShortcut(transaction_mark_cleared, QKeySequence("Ctrl+Alt+Space")); - connect(transaction_mark_cleared, SIGNAL(triggered()), this, SLOT(slotMarkTransactionCleared())); - - QAction *transaction_mark_reconciled = actionCollection()->addAction("transaction_mark_reconciled"); - transaction_mark_reconciled->setText(i18nc("Mark transaction reconciled", "Reconciled")); - actionCollection()->setDefaultShortcut(transaction_mark_reconciled, QKeySequence("Ctrl+Shift+Space")); - connect(transaction_mark_reconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionReconciled())); - - QAction *transaction_mark_notreconciled = actionCollection()->addAction("transaction_mark_notreconciled"); - transaction_mark_notreconciled->setText(i18nc("Mark transaction not reconciled", "Not reconciled")); - connect(transaction_mark_notreconciled, SIGNAL(triggered()), this, SLOT(slotMarkTransactionNotReconciled())); - - QAction *transaction_select_all = actionCollection()->addAction("transaction_select_all"); - transaction_select_all->setText(i18nc("Select all transactions", "Select all")); - actionCollection()->setDefaultShortcut(transaction_select_all, QKeySequence("Ctrl+A")); - connect(transaction_select_all, SIGNAL(triggered()), this, SIGNAL(selectAllTransactions())); - - QAction *transaction_goto_account = actionCollection()->addAction("transaction_goto_account"); - transaction_goto_account->setText(i18n("Go to account")); - transaction_goto_account->setIcon(QIcon::fromTheme(g_Icons[Icon::GoJump])); - connect(transaction_goto_account, SIGNAL(triggered()), this, SLOT(slotTransactionGotoAccount())); - - QAction *transaction_goto_payee = actionCollection()->addAction("transaction_goto_payee"); - transaction_goto_payee->setText(i18n("Go to payee")); - transaction_goto_payee->setIcon(QIcon::fromTheme(g_Icons[Icon::GoJump])); - connect(transaction_goto_payee, SIGNAL(triggered()), this, SLOT(slotTransactionGotoPayee())); - - QAction *transaction_create_schedule = actionCollection()->addAction("transaction_create_schedule"); - transaction_create_schedule->setText(i18n("Create scheduled transaction...")); - transaction_create_schedule->setIcon(QIcon::fromTheme(g_Icons[Icon::AppointmentNew])); - connect(transaction_create_schedule, SIGNAL(triggered()), this, SLOT(slotTransactionCreateSchedule())); - - QAction *transaction_assign_number = actionCollection()->addAction("transaction_assign_number"); - transaction_assign_number->setText(i18n("Assign next number")); - actionCollection()->setDefaultShortcut(transaction_assign_number, QKeySequence("Ctrl+Shift+N")); - connect(transaction_assign_number, SIGNAL(triggered()), this, SLOT(slotTransactionAssignNumber())); - - QAction *transaction_combine = actionCollection()->addAction("transaction_combine"); - transaction_combine->setText(i18nc("Combine transactions", "Combine")); - connect(transaction_combine, SIGNAL(triggered()), this, SLOT(slotTransactionCombine())); - - QAction *transaction_copy_splits = actionCollection()->addAction("transaction_copy_splits"); - transaction_copy_splits->setText(i18n("Copy splits")); - connect(transaction_copy_splits, SIGNAL(triggered()), this, SLOT(slotTransactionCopySplits())); - - //Investment - QAction *investment_new = actionCollection()->addAction("investment_new"); - investment_new->setText(i18n("New investment...")); - investment_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - connect(investment_new, SIGNAL(triggered()), this, SLOT(slotInvestmentNew())); - - QAction *investment_edit = actionCollection()->addAction("investment_edit"); - investment_edit->setText(i18n("Edit investment...")); - investment_edit->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::DocumentEdit])); - connect(investment_edit, SIGNAL(triggered()), this, SLOT(slotInvestmentEdit())); - - QAction *investment_delete = actionCollection()->addAction("investment_delete"); - investment_delete->setText(i18n("Delete investment...")); - investment_delete->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::EditDelete])); - connect(investment_delete, SIGNAL(triggered()), this, SLOT(slotInvestmentDelete())); - - QAction *investment_online_price_update = actionCollection()->addAction("investment_online_price_update"); - investment_online_price_update->setText(i18n("Online price update...")); - investment_online_price_update->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewInvestment], g_Icons[Icon::Download])); - connect(investment_online_price_update, SIGNAL(triggered()), this, SLOT(slotOnlinePriceUpdate())); - - QAction *investment_manual_price_update = actionCollection()->addAction("investment_manual_price_update"); - investment_manual_price_update->setText(i18n("Manual price update...")); - connect(investment_manual_price_update, SIGNAL(triggered()), this, SLOT(slotManualPriceUpdate())); - - //Schedule - QAction *schedule_new = actionCollection()->addAction("schedule_new"); - schedule_new->setText(i18n("New scheduled transaction")); - schedule_new->setIcon(QIcon::fromTheme(g_Icons[Icon::AppointmentNew])); - connect(schedule_new, SIGNAL(triggered()), this, SLOT(slotScheduleNew())); - - QAction *schedule_edit = actionCollection()->addAction("schedule_edit"); - schedule_edit->setText(i18n("Edit scheduled transaction")); - schedule_edit->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); - connect(schedule_edit, SIGNAL(triggered()), this, SLOT(slotScheduleEdit())); - - QAction *schedule_delete = actionCollection()->addAction("schedule_delete"); - schedule_delete->setText(i18n("Delete scheduled transaction")); - schedule_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); - connect(schedule_delete, SIGNAL(triggered()), this, SLOT(slotScheduleDelete())); - - QAction *schedule_duplicate = actionCollection()->addAction("schedule_duplicate"); - schedule_duplicate->setText(i18n("Duplicate scheduled transaction")); - schedule_duplicate->setIcon(QIcon::fromTheme(g_Icons[Icon::EditCopy])); - connect(schedule_duplicate, SIGNAL(triggered()), this, SLOT(slotScheduleDuplicate())); - - QAction *schedule_enter = actionCollection()->addAction("schedule_enter"); - schedule_enter->setText(i18n("Enter next transaction...")); - schedule_enter->setIcon(QIcon::fromTheme(g_Icons[Icon::KeyEnter])); - connect(schedule_enter, SIGNAL(triggered()), this, SLOT(slotScheduleEnter())); - - QAction *schedule_skip = actionCollection()->addAction("schedule_skip"); - schedule_skip->setText(i18n("Skip next transaction...")); - schedule_skip->setIcon(QIcon::fromTheme(g_Icons[Icon::MediaSeekForward])); - connect(schedule_skip, SIGNAL(triggered()), this, SLOT(slotScheduleSkip())); - - //Payees - QAction *payee_new = actionCollection()->addAction("payee_new"); - payee_new->setText(i18n("New payee")); - payee_new->setIcon(QIcon::fromTheme(g_Icons[Icon::ListAddUser])); - connect(payee_new, SIGNAL(triggered()), this, SLOT(slotPayeeNew())); - - QAction *payee_rename = actionCollection()->addAction("payee_rename"); - payee_rename->setText(i18n("Rename payee")); - payee_rename->setIcon(QIcon::fromTheme(g_Icons[Icon::PayeeRename])); - connect(payee_rename, SIGNAL(triggered()), this, SIGNAL(payeeRename())); - - QAction *payee_delete = actionCollection()->addAction("payee_delete"); - payee_delete->setText(i18n("Delete payee")); - payee_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::ListRemoveUser])); - connect(payee_delete, SIGNAL(triggered()), this, SLOT(slotPayeeDelete())); - - QAction *payee_merge = actionCollection()->addAction("payee_merge"); - payee_merge->setText(i18n("Merge payees")); - payee_merge->setIcon(QIcon::fromTheme(g_Icons[Icon::PayeeMerge])); - connect(payee_merge, SIGNAL(triggered()), this, SLOT(slotPayeeMerge())); - - //Tags - QAction *tag_new = actionCollection()->addAction("tag_new"); - tag_new->setText(i18n("New tag")); - tag_new->setIcon(QIcon::fromTheme(g_Icons[Icon::ListAddTag])); - connect(tag_new, SIGNAL(triggered()), this, SLOT(slotTagNew())); - - QAction *tag_rename = actionCollection()->addAction("tag_rename"); - tag_rename->setText(i18n("Rename tag")); - tag_rename->setIcon(QIcon::fromTheme(g_Icons[Icon::TagRename])); - connect(tag_rename, SIGNAL(triggered()), this, SIGNAL(tagRename())); - - QAction *tag_delete = actionCollection()->addAction("tag_delete"); - tag_delete->setText(i18n("Delete tag")); - tag_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::ListRemoveTag])); - connect(tag_delete, SIGNAL(triggered()), this, SLOT(slotTagDelete())); - - //Budget - QAction *budget_new = actionCollection()->addAction("budget_new"); - budget_new->setText(i18n("New budget")); - budget_new->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner)); - connect(budget_new, SIGNAL(triggered()), this, SLOT(slotBudgetNew())); - - QAction *budget_rename = actionCollection()->addAction("budget_rename"); - budget_rename->setText(i18n("Rename budget")); - budget_rename->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::DocumentEdit])); - connect(budget_rename, SIGNAL(triggered()), this, SIGNAL(budgetRename())); - - QAction *budget_delete = actionCollection()->addAction("budget_delete"); - budget_delete->setText(i18n("Delete budget")); - budget_delete->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::EditDelete])); - connect(budget_delete, SIGNAL(triggered()), this, SLOT(slotBudgetDelete())); - - QAction *budget_copy = actionCollection()->addAction("budget_copy"); - budget_copy->setText(i18n("Copy budget")); - budget_copy->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::EditCopy])); - connect(budget_copy, SIGNAL(triggered()), this, SLOT(slotBudgetCopy())); - - QAction *budget_change_year = actionCollection()->addAction("budget_change_year"); - budget_change_year->setText(i18n("Change budget year")); - budget_change_year->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewCalendar])); - connect(budget_change_year, SIGNAL(triggered()), this, SLOT(slotBudgetChangeYear())); - - QAction *budget_forecast = actionCollection()->addAction("budget_forecast"); - budget_forecast->setText(i18n("Budget based on forecast")); - budget_forecast->setIcon(QIcon::fromTheme(g_Icons[Icon::ViewForecast])); - connect(budget_forecast, SIGNAL(triggered()), this, SLOT(slotBudgetForecast())); - - // Currency actions - QAction *currency_new = actionCollection()->addAction("currency_new"); - currency_new->setText(i18n("New currency")); - currency_new->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentNew])); - connect(currency_new, SIGNAL(triggered()), this, SLOT(slotCurrencyNew())); - - QAction *currency_rename = actionCollection()->addAction("currency_rename"); - currency_rename->setText(i18n("Rename currency")); - currency_rename->setIcon(QIcon::fromTheme(g_Icons[Icon::EditRename])); - connect(currency_rename, SIGNAL(triggered()), this, SIGNAL(currencyRename())); - - QAction *currency_delete = actionCollection()->addAction("currency_delete"); - currency_delete->setText(i18n("Delete currency")); - currency_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); - connect(currency_delete, SIGNAL(triggered()), this, SLOT(slotCurrencyDelete())); - - QAction *currency_setbase = actionCollection()->addAction("currency_setbase"); - currency_setbase->setText(i18n("Select as base currency")); - currency_setbase->setIcon(QIcon::fromTheme(g_Icons[Icon::KMyMoney])); - connect(currency_setbase, SIGNAL(triggered()), this, SLOT(slotCurrencySetBase())); - - //price actions - QAction *price_new = actionCollection()->addAction("price_new"); - price_new->setText(i18n("New price...")); - price_new->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentNew])); - connect(price_new, SIGNAL(triggered()), this, SIGNAL(priceNew())); - - QAction *price_edit = actionCollection()->addAction("price_edit"); - price_edit->setText(i18n("Edit price...")); - price_edit->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); - connect(price_edit, SIGNAL(triggered()), this, SIGNAL(priceEdit())); - - QAction *price_update = actionCollection()->addAction("price_update"); - price_update->setText(i18n("Online Price Update...")); - price_update->setIcon(KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewCurrencyList], g_Icons[Icon::Download])); - connect(price_update, SIGNAL(triggered()), this, SIGNAL(priceOnlineUpdate())); - - QAction *price_delete = actionCollection()->addAction("price_delete"); - price_delete->setText(i18n("Delete price...")); - price_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); - connect(price_delete, SIGNAL(triggered()), this, SIGNAL(priceDelete())); - - //debug 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 - QAction *new_user_wizard = actionCollection()->addAction("new_user_wizard"); - new_user_wizard->setText(i18n("Test new feature")); - actionCollection()->setDefaultShortcut(new_user_wizard, QKeySequence("Ctrl+G")); - connect(new_user_wizard, SIGNAL(triggered()), this, SLOT(slotNewFeature())); - - KToggleAction *debug_traces = actionCollection()->add("debug_traces"); - debug_traces->setText(i18n("Debug Traces")); - connect(debug_traces, SIGNAL(triggered()), this, SLOT(slotToggleTraces())); + {qMakePair(Action::WizardNewUser, Qt::CTRL + Qt::Key_G)}, #endif + {qMakePair(Action::TransactionAssignNumber, Qt::CTRL + Qt::Key_Shift + Qt::Key_N)} + }; - KToggleAction *debug_timers = actionCollection()->add("debug_timers"); - debug_timers->setText(i18n("Debug Timers")); - connect(debug_timers, SIGNAL(triggered()), this, SLOT(slotToggleTimers())); + for(const auto& it : actionShortcuts) + aC->setDefaultShortcut(lutActions[it.first], it.second); + } - // onlineJob actions - QAction* onlineJob_delete = actionCollection()->addAction("onlinejob_delete"); - onlineJob_delete->setText(i18n("Remove credit transfer")); - onlineJob_delete->setIcon(QIcon::fromTheme(g_Icons[Icon::EditDelete])); + // ************* + // Misc settings + // ************* + connect(onlineJobAdministration::instance(), &onlineJobAdministration::canSendCreditTransferChanged, lutActions.value(Action::AccountCreditTransfer), &QAction::setEnabled); - QAction* onlineJob_edit = actionCollection()->addAction("onlinejob_edit"); - onlineJob_edit->setText(i18n("Edit credit transfer")); - onlineJob_edit->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentEdit])); + // 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); - QAction* onlineJob_log = actionCollection()->addAction("onlinejob_log"); - onlineJob_log->setText(i18n("Show log")); - connect(onlineJob_log, SIGNAL(triggered()), this, SLOT(slotOnlineJobLog())); + // ************* + // 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 - // Setup transaction detail switch - toggleAction("view_show_transaction_detail")->setChecked(KMyMoneyGlobalSettings::showRegisterDetailed()); - toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); - toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); - toggleAction("view_show_all_accounts")->setChecked(false); - // use the absolute path to your kmymoneyui.rc file for testing purpose in createGUI(); setupGUI(); QMenu *menuContainer; - menuContainer = static_cast(factory()->container("import", this)); + menuContainer = static_cast(factory()->container(QStringLiteral("import"), this)); menuContainer->setIcon(QIcon::fromTheme(g_Icons[Icon::DocumentImport])); - menuContainer = static_cast(factory()->container("export", this)); + 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("onlinejob_delete"); + 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("onlinejob_edit"); + QAction *const onlineJob_edit = actionCollection()->action(s_Actions[Action::OnlineJobEdit]); Q_CHECK_PTR(onlineJob_edit); connect(onlineJob_edit, SIGNAL(triggered()), outbox, SLOT(slotEditJob())); } void KMyMoneyApp::dumpActions() const { const QList list = actionCollection()->actions(); QList::const_iterator it; for (it = list.begin(); it != list.end(); ++it) { std::cout << qPrintable((*it)->objectName()) << ": " << qPrintable((*it)->text()) << std::endl; } } -KToggleAction* KMyMoneyApp::toggleAction(const QString& actionName) const +bool KMyMoneyApp::isActionToggled(const Action _a) { - static KToggleAction dummyAction(QString("Dummy"), 0); - - KToggleAction* p = dynamic_cast(actionCollection()->action(QString(actionName.toLatin1()))); - if (!p) { - qWarning("ToggleAction with name '%s' not found!", qPrintable(actionName)); - p = &dummyAction; - } - return p; + return actionCollection()->action(s_Actions[_a])->isChecked(); } - void KMyMoneyApp::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR d->m_statusLabel = new QLabel(statusBar()); statusBar()->addWidget(d->m_statusLabel); ready(); // Initialization of progress bar taken from KDevelop ;-) d->m_progressBar = new QProgressBar(statusBar()); statusBar()->addWidget(d->m_progressBar); d->m_progressBar->setFixedHeight(d->m_progressBar->sizeHint().height() - 8); // hide the progress bar for now slotStatusProgressBar(-1, -1); } void KMyMoneyApp::saveOptions() { KConfigGroup grp = d->m_config->group("General Options"); grp.writeEntry("Geometry", size()); - grp.writeEntry("Show Statusbar", toggleAction("options_show_statusbar")->isChecked()); + 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"); - toggleAction("view_hide_reconciled_transactions")->setChecked(KMyMoneyGlobalSettings::hideReconciledTransactions()); - toggleAction("view_hide_unused_categories")->setChecked(KMyMoneyGlobalSettings::hideUnusedCategory()); + + 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; } -bool KMyMoneyApp::slotSaveAsDatabase() +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(toggleAction("view_hide_reconciled_transactions")->isChecked()); + KMyMoneyGlobalSettings::setHideReconciledTransactions(actionCollection()->action(s_Actions[Action::ViewHideReconciled])->isChecked()); d->m_myMoneyView->slotRefreshViews(); } void KMyMoneyApp::slotHideUnusedCategories() { - KMyMoneyGlobalSettings::setHideUnusedCategory(toggleAction("view_hide_unused_categories")->isChecked()); + 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(toggleAction("debug_traces")->isChecked() ? 1 : 0); + MyMoneyTracer::onOff(action(s_Actions[Action::DebugTraces])->isChecked() ? 1 : 0); } +#endif void KMyMoneyApp::slotToggleTimers() { extern bool timersOn; // main.cpp - timersOn = toggleAction("debug_timers")->isChecked(); + + 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::slotQifImport() { if (d->m_qifReader == 0) { // FIXME: the menu entry for qif import should be disabled here QPointer dlg = new KImportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { KMSTATUS(i18n("Importing file...")); d->m_qifReader = new MyMoneyQifReader; // remove all kmm-statement-#.txt files d->unlinkStatementXML(); connect(d->m_qifReader, SIGNAL(importFinished()), this, SLOT(slotQifImportFinished())); d->m_qifReader->setURL(dlg->file()); d->m_qifReader->setProfile(dlg->profile()); d->m_qifReader->setCategoryMapping(dlg->m_typeComboBox->currentIndex() == 0); d->m_qifReader->setProgressCallback(&progressCallback); // disable all standard widgets during the import setEnabled(false); d->m_ft = new MyMoneyFileTransaction(); d->m_collectingStatements = true; d->m_statementResults.clear(); if (!d->m_qifReader->startImport()) { // if the import failed to start make sure that slotQifImportFinished is called otherwise the application will be left disabled QTimer::singleShot(0, this, SLOT(slotQifImportFinished())); } } delete dlg; slotUpdateActions(); } } void KMyMoneyApp::slotQifImportFinished() { if (d->m_qifReader != 0) { d->m_qifReader->finishImport(); d->m_ft->commit(); d->m_collectingStatements = false; KMessageBox::informationList(this, i18n("The statements have been processed with the following results:"), d->m_statementResults, i18n("Statement stats")); #if 0 // fixme: re-enable the QIF import menu options if (d->m_qifReader->finishImport()) { if (verifyImportedData(d->m_qifReader->account())) { // keep the new data set, destroy the backup copy delete d->m_engineBackup; d->m_engineBackup = 0; } } if (d->m_engineBackup != 0) { // user cancelled, destroy the updated set and keep the backup copy IMyMoneyStorage* data = file->storage(); if (data != 0) { file->detachStorage(data); delete data; } file->attachStorage(d->m_engineBackup); d->m_engineBackup = 0; } #endif // update the views as they might still contain invalid data // from the import session. The same applies for the window caption d->m_myMoneyView->slotRefreshViews(); updateCaption(); delete d->m_qifReader; d->m_qifReader = 0; } delete d->m_ft; d->m_ft = 0; slotStatusProgressBar(-1, -1); ready(); // re-enable all standard widgets setEnabled(true); slotUpdateActions(); } void KMyMoneyApp::slotGncImport() { if (d->m_myMoneyView->fileOpen()) { switch (KMessageBox::questionYesNoCancel(0, i18n("You cannot import GnuCash data into an existing file. Do you wish to save this file?"), PACKAGE)) { case KMessageBox::Yes: slotFileSave(); break; case KMessageBox::No: d->closeFile(); break; default: return; } } KMSTATUS(i18n("Importing a GnuCash file.")); QUrl fileToRead = QFileDialog::getOpenFileUrl(this, QString(), QUrl(), i18n("GnuCash files (*.gnucash *.xac *.gnc);;All files (*)")); if (!fileToRead.isEmpty()) { // call the importer d->m_myMoneyView->readFile(fileToRead); // imported files don't have a name d->m_fileName = QUrl(); updateCaption(); emit fileLoaded(d->m_fileName); } } void KMyMoneyApp::slotAccountChart() { if (!d->m_selectedAccount.id().isEmpty()) { QPointer dlg = new KBalanceChartDlg(d->m_selectedAccount, this); dlg->exec(); delete dlg; } } // // KMyMoneyApp::slotStatementImport() is for testing only. The MyMoneyStatement // is not intended to be exposed to users in XML form. // void KMyMoneyApp::slotStatementImport() { bool result = false; KMSTATUS(i18n("Importing an XML Statement.")); QList files{QFileDialog::getOpenFileUrls(this, QString(), QUrl(), i18n("XML files (*.xml);;All files (*)"))}; if (!files.isEmpty()) { d->m_collectingStatements = (files.count() > 1); foreach (const QUrl &url, files) { qDebug("Processing '%s'", qPrintable(url.path())); result |= slotStatementImport(url.path()); } /* QFile f( dialog->selectedURL().path() ); f.open( QIODevice::ReadOnly ); QString error = "Unable to parse file"; QDomDocument* doc = new QDomDocument; if(doc->setContent(&f, FALSE)) { if ( doc->doctype().name() == "KMYMONEY-STATEMENT" ) { QDomElement rootElement = doc->documentElement(); if(!rootElement.isNull()) { QDomNode child = rootElement.firstChild(); if(!child.isNull() && child.isElement()) { MyMoneyStatement s; if ( s.read(child.toElement()) ) result = slotStatementImport(s); else error = "File does not contain any statements"; } } } else error = "File is not a KMyMoney Statement"; } delete doc; if ( !result ) { QMessageBox::critical( this, i18n("Critical Error"), i18n("Unable to read file %1: %2").arg( dialog->selectedURL().path()).arg(error), QMessageBox::Ok, 0 ); }*/ } if (!result) { // re-enable all standard widgets setEnabled(true); } } bool KMyMoneyApp::slotStatementImport(const QString& url) { bool result = false; MyMoneyStatement s; if (MyMoneyStatement::readXMLFile(s, url)) result = slotStatementImport(s); else KMessageBox::error(this, i18n("Error importing %1: This file is not a valid KMM statement file.", url), i18n("Invalid Statement")); return result; } bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s, bool 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; } void KMyMoneyApp::slotQifExport() { KMSTATUS(i18n("Exporting file...")); QPointer dlg = new KExportDlg(0); if (dlg->exec() == QDialog::Accepted && dlg != 0) { if (okToWriteFile(QUrl::fromLocalFile(dlg->filename()))) { MyMoneyQifWriter writer; connect(&writer, SIGNAL(signalProgress(int,int)), this, SLOT(slotStatusProgressBar(int,int))); writer.write(dlg->filename(), dlg->profile(), dlg->accountId(), dlg->accountSelected(), dlg->categorySelected(), dlg->startDate(), dlg->endDate()); } } delete dlg; } bool KMyMoneyApp::okToWriteFile(const QUrl &url) { Q_UNUSED(url) // check if the file exists and warn the user bool reallySaveFile = true; // TODO: port KF5 //if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, this)) { // if (KMessageBox::warningYesNo(this, QLatin1String("") + i18n("The file %1 already exists. Do you really want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)) + QLatin1String(""), i18n("File already exists")) != KMessageBox::Yes) // reallySaveFile = false; //} return reallySaveFile; } void KMyMoneyApp::slotSettings() { // if we already have an instance of the settings dialog, then use it if (KConfigDialog::showDialog("KMyMoney-Settings")) return; // otherwise, we have to create it KConfigDialog* dlg = new KSettingsKMyMoney(this, "KMyMoney-Settings", KMyMoneyGlobalSettings::self()); connect(dlg, &KConfigDialog::settingsChanged, this, &KMyMoneyApp::slotUpdateConfiguration); dlg->show(); } void KMyMoneyApp::slotUpdateConfiguration() { MyMoneyTransactionFilter::setFiscalYearStart(KMyMoneyGlobalSettings::firstFiscalMonth(), KMyMoneyGlobalSettings::firstFiscalDay()); d->m_myMoneyView->updateViewType(); // update the sql storage module settings MyMoneyStorageSql::setStartDate(KMyMoneyGlobalSettings::startDate().date()); // update the report module settings MyMoneyReport::setLineWidth(KMyMoneyGlobalSettings::lineWidth()); // update the holiday region configuration setHolidayRegion(KMyMoneyGlobalSettings::holidayRegion()); d->m_myMoneyView->slotRefreshViews(); // re-read autosave configuration d->m_autoSaveEnabled = KMyMoneyGlobalSettings::autoSaveFile(); d->m_autoSavePeriod = KMyMoneyGlobalSettings::autoSavePeriod(); // stop timer if turned off but running if (d->m_autoSaveTimer->isActive() && !d->m_autoSaveEnabled) { d->m_autoSaveTimer->stop(); } // start timer if turned on and needed but not running if (!d->m_autoSaveTimer->isActive() && d->m_autoSaveEnabled && d->m_myMoneyView->dirty()) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } d->setCustomColors(); // check if the recovery key is still valid or expires soon if (KMyMoneySettings::writeDataEncrypted() && KMyMoneySettings::encryptRecover()) { if (KGPGFile::GPGAvailable()) { KGPGFile file; QDateTime expirationDate = file.keyExpires(QLatin1String(recoveryKeyId)); if (expirationDate.isValid() && QDateTime::currentDateTime().daysTo(expirationDate) <= RECOVER_KEY_EXPIRATION_WARNING) { bool skipMessage = false; //get global config object for our app. KSharedConfigPtr kconfig = KSharedConfig::openConfig(); KConfigGroup grp; QDate lastWarned; if (kconfig) { grp = d->m_config->group("General Options"); lastWarned = grp.readEntry("LastRecoverKeyExpirationWarning", QDate()); if (QDate::currentDate() == lastWarned) { skipMessage = true; } } if (!skipMessage) { if (kconfig) { grp.writeEntry("LastRecoverKeyExpirationWarning", QDate::currentDate()); } KMessageBox::information(this, i18np("You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 day. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", "You have configured KMyMoney to use GPG to protect your data and to encrypt your data also with the KMyMoney recover key. This key is about to expire in %1 days. Please update the key from a keyserver using your GPG frontend (e.g. KGPG).", QDateTime::currentDateTime().daysTo(expirationDate)), i18n("Recover key expires soon")); } } } } } /** No descriptions */ void KMyMoneyApp::slotFileBackup() { // Save the file first so isLocalFile() works if (d->m_myMoneyView && d->m_myMoneyView->dirty()) { if (KMessageBox::questionYesNo(this, i18n("The file must be saved first " "before it can be backed up. Do you want to continue?")) == KMessageBox::No) { return; } slotFileSave(); } if (d->m_fileName.isEmpty()) return; if (!d->m_fileName.isLocalFile()) { KMessageBox::sorry(this, i18n("The current implementation of the backup functionality only supports local files as source files. Your current source file is '%1'.", d->m_fileName.url()), i18n("Local files only")); return; } // TODO: port KF5 #if 0 QPointer backupDlg = new KBackupDlg(this); int returncode = backupDlg->exec(); if (returncode == QDialog::Accepted && backupDlg != 0) { d->m_backupMount = backupDlg->mountCheckBox->isChecked(); d->m_proc.clearProgram(); d->m_backupState = BACKUP_MOUNTING; d->m_mountpoint = backupDlg->txtMountPoint->text(); if (d->m_backupMount) { progressCallback(0, 300, i18n("Mounting %1", d->m_mountpoint)); d->m_proc.setProgram("mount"); d->m_proc << d->m_mountpoint; d->m_proc.start(); } else { // If we don't have to mount a device, we just issue // a dummy command to start the copy operation progressCallback(0, 300, ""); d->m_proc.setProgram("true"); d->m_proc.start(); } } delete backupDlg; #endif } /** No descriptions */ void KMyMoneyApp::slotProcessExited() { // TODO: port KF5 #if 0 switch (d->m_backupState) { case BACKUP_MOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { d->m_proc.clearProgram(); QString today; today.sprintf("-%04d-%02d-%02d.kmy", QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().day()); QString backupfile = d->m_mountpoint + '/' + d->m_fileName.fileName(); KMyMoneyUtils::appendCorrectFileExt(backupfile, today); // check if file already exists and ask what to do d->m_backupResult = 0; QFile f(backupfile); if (f.exists()) { int answer = KMessageBox::warningContinueCancel(this, i18n("Backup file for today exists on that device. Replace?"), i18n("Backup"), KGuiItem(i18n("&Replace"))); if (answer == KMessageBox::Cancel) { d->m_backupResult = 1; if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } } if (d->m_backupResult == 0) { progressCallback(50, 0, i18n("Writing %1", backupfile)); //FIXME: FIX on windows d->m_proc << "cp" << "-f" << d->m_fileName.path() << backupfile; d->m_backupState = BACKUP_COPYING; d->m_proc.start(); } } else { KMessageBox::information(this, i18n("Error mounting device"), i18n("Backup")); d->m_backupResult = 1; if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } break; case BACKUP_COPYING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { progressCallback(300, 0, i18nc("Backup done", "Done")); KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } else { qDebug("cp exit code is %d", d->m_proc.exitCode()); d->m_backupResult = 1; KMessageBox::information(this, i18n("Error copying file to device"), i18n("Backup")); if (d->m_backupMount) { progressCallback(250, 0, i18n("Unmounting %1", d->m_mountpoint)); d->m_proc.clearProgram(); d->m_proc.setProgram("umount"); d->m_proc << d->m_mountpoint; d->m_backupState = BACKUP_UNMOUNTING; d->m_proc.start(); } else { d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); } } break; case BACKUP_UNMOUNTING: if (d->m_proc.exitStatus() == QProcess::NormalExit && d->m_proc.exitCode() == 0) { progressCallback(300, 0, i18nc("Backup done", "Done")); if (d->m_backupResult == 0) KMessageBox::information(this, i18n("File successfully backed up"), i18n("Backup")); } else { KMessageBox::information(this, i18n("Error unmounting device"), i18n("Backup")); } d->m_backupState = BACKUP_IDLE; progressCallback(-1, -1, QString()); ready(); break; default: qWarning("Unknown state for backup operation!"); progressCallback(-1, -1, QString()); ready(); break; } #endif } void KMyMoneyApp::slotShowTipOfTheDay() { KTipDialog::showTip(d->m_myMoneyView, "", true); } void KMyMoneyApp::slotShowPreviousView() { } void KMyMoneyApp::slotShowNextView() { } void KMyMoneyApp::slotGenerateSql() { QPointer editor = new KGenerateSqlDlg(this); editor->setObjectName("Generate Database SQL"); editor->exec(); delete editor; } void KMyMoneyApp::slotQifProfileEditor() { QPointer editor = new MyMoneyQifProfileEditor(true, this); editor->setObjectName("QIF Profile Editor"); editor->exec(); delete editor; } void KMyMoneyApp::slotToolsStartKCalc() { QString cmd = KMyMoneyGlobalSettings::externalCalculator(); // if none is present, we fall back to the default if (cmd.isEmpty()) { #if defined(Q_OS_WIN32) cmd = QLatin1String("calc"); #elif defined(Q_OS_MAC) cmd = QLatin1String("open -a Calculator"); #else cmd = QLatin1String("kcalc"); #endif } KRun::runCommand(cmd, this); } void KMyMoneyApp::slotFindTransaction() { if (d->m_searchDlg == 0) { d->m_searchDlg = new KFindTransactionDlg(); connect(d->m_searchDlg, SIGNAL(destroyed()), this, SLOT(slotCloseSearchDialog())); connect(d->m_searchDlg, SIGNAL(transactionSelected(QString,QString)), d->m_myMoneyView, SLOT(slotLedgerSelected(QString,QString))); } d->m_searchDlg->show(); d->m_searchDlg->raise(); d->m_searchDlg->activateWindow(); } void KMyMoneyApp::slotCloseSearchDialog() { if (d->m_searchDlg) d->m_searchDlg->deleteLater(); d->m_searchDlg = 0; } void KMyMoneyApp::createInstitution(MyMoneyInstitution& institution) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyFileTransaction ft; try { file->addInstitution(institution); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::information(this, i18n("Cannot add institution: %1", e.what())); } } void KMyMoneyApp::slotInstitutionNew() { MyMoneyInstitution institution; slotInstitutionNew(institution); } void KMyMoneyApp::slotInstitutionNew(MyMoneyInstitution& institution) { institution.clearId(); QPointer dlg = new KNewBankDlg(institution); if (dlg->exec() == QDialog::Accepted && dlg != 0) { institution = dlg->institution(); createInstitution(institution); } delete dlg; } +void KMyMoneyApp::slotInstitutionEdit() +{ + 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())); } } const MyMoneyAccount& KMyMoneyApp::findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const { static MyMoneyAccount nullAccount; MyMoneyFile* file = MyMoneyFile::instance(); QList parents; try { // search by id if (!acc.id().isEmpty()) { return file->account(acc.id()); } // collect the parents. in case parent does not have an id, we scan the all top-level accounts if (parent.id().isEmpty()) { parents << file->asset(); parents << file->liability(); parents << file->income(); parents << file->expense(); parents << file->equity(); } else { parents << parent; } QList::const_iterator it_p; for (it_p = parents.constBegin(); it_p != parents.constEnd(); ++it_p) { MyMoneyAccount parentAccount = *it_p; // search by name (allow hierarchy) int pos; // check for ':' in the name and use it as separator for a hierarchy QString name = acc.name(); bool notFound = false; while ((pos = name.indexOf(MyMoneyFile::AccountSeperator)) != -1) { QString part = name.left(pos); QString remainder = name.mid(pos + 1); const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, part); // if account has not been found, continue with next top level parent if (existingAccount.id().isEmpty()) { notFound = true; break; } parentAccount = existingAccount; name = remainder; } if (notFound) continue; const MyMoneyAccount& existingAccount = file->subAccountByName(parentAccount, name); if (!existingAccount.id().isEmpty()) { if (acc.accountType() != MyMoneyAccount::UnknownAccountType) { if (acc.accountType() != existingAccount.accountType()) continue; } return existingAccount; } } } catch (const MyMoneyException &e) { KMessageBox::error(0, i18n("Unable to find account: %1", e.what())); } return nullAccount; } void KMyMoneyApp::createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal) { MyMoneyFile *file = MyMoneyFile::instance(); 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()); } } void KMyMoneyApp::slotInvestmentEdit() { KNewInvestmentWizard dlg(d->m_selectedInvestment); if (dlg.exec() == QDialog::Accepted) { dlg.createObjects(d->m_selectedAccount.id()); } } void KMyMoneyApp::slotInvestmentDelete() { if (KMessageBox::questionYesNo(this, i18n("

Do you really want to delete the investment %1?

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

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

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

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

", selectedAccountName)) != KMessageBox::Yes) { return; // ok, you don't want to? why did you click then, hmm? } } // switch; try { file->removeAccount(d->m_selectedAccount); d->m_selectedAccount.clearId(); slotUpdateActions(); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to delete account '%1'. Cause: %2", selectedAccountName, e.what())); } } void KMyMoneyApp::slotAccountEdit() { MyMoneyFile* file = MyMoneyFile::instance(); if (!d->m_selectedAccount.id().isEmpty()) { if (!file->isStandardAccount(d->m_selectedAccount.id())) { if (d->m_selectedAccount.accountType() != MyMoneyAccount::Loan && d->m_selectedAccount.accountType() != MyMoneyAccount::AssetLoan) { QString caption; bool category = false; switch (d->m_selectedAccount.accountGroup()) { default: caption = i18n("Edit account '%1'", d->m_selectedAccount.name()); break; case MyMoneyAccount::Expense: case MyMoneyAccount::Income: caption = i18n("Edit category '%1'", d->m_selectedAccount.name()); category = true; break; } // set a status message so that the application can't be closed until the editing is done slotStatusMsg(caption); QString tid = file->openingBalanceTransaction(d->m_selectedAccount); MyMoneyTransaction t; MyMoneySplit s0, s1; QPointer dlg = new KNewAccountDlg(d->m_selectedAccount, true, category, 0, caption); if (category || d->m_selectedAccount.accountType() == MyMoneyAccount::Investment) { dlg->setOpeningBalanceShown(false); dlg->setOpeningDateShown(false); tid.clear(); } else { if (!tid.isEmpty()) { try { t = file->transaction(tid); s0 = t.splitByAccount(d->m_selectedAccount.id()); s1 = t.splitByAccount(d->m_selectedAccount.id(), false); dlg->setOpeningBalance(s0.shares()); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { dlg->setOpeningBalance(-s0.shares()); } } catch (const MyMoneyException &e) { qDebug() << "Error retrieving opening balance transaction " << tid << ": " << e.what() << "\n"; tid.clear(); } } } // check for online modules QMap::const_iterator it_plugin = d->m_onlinePlugins.constEnd(); const MyMoneyKeyValueContainer& kvp = d->m_selectedAccount.onlineBankingSettings(); if (!kvp["provider"].isEmpty()) { // if we have an online provider for this account, we need to check // that we have the corresponding plugin. If that exists, we ask it // to provide an additional tab for the account editor. it_plugin = d->m_onlinePlugins.constFind(kvp["provider"]); if (it_plugin != d->m_onlinePlugins.constEnd()) { QString name; QWidget *w = (*it_plugin)->accountConfigTab(d->m_selectedAccount, name); dlg->addTab(w, name); } } if (dlg != 0 && dlg->exec() == QDialog::Accepted) { try { MyMoneyFileTransaction ft; MyMoneyAccount account = dlg->account(); MyMoneyAccount parent = dlg->parentAccount(); if (it_plugin != d->m_onlinePlugins.constEnd()) { account.setOnlineBankingSettings((*it_plugin)->onlineBankingSettings(account.onlineBankingSettings())); } MyMoneyMoney bal = dlg->openingBalance(); if (d->m_selectedAccount.accountGroup() == MyMoneyAccount::Liability) { bal = -bal; } // we need to modify first, as reparent would override all other changes file->modifyAccount(account); if (account.parentAccountId() != parent.id()) { file->reparentAccount(account, parent); } if (!tid.isEmpty() && dlg->openingBalance().isZero()) { file->removeTransaction(t); } else if (!tid.isEmpty() && !dlg->openingBalance().isZero()) { s0.setShares(bal); s0.setValue(bal); t.modifySplit(s0); s1.setShares(-bal); s1.setValue(-bal); t.modifySplit(s1); t.setPostDate(account.openingDate()); file->modifyTransaction(t); } else if (tid.isEmpty() && !dlg->openingBalance().isZero()) { file->createOpeningBalanceTransaction(d->m_selectedAccount, bal); } ft.commit(); // reload the account object as it might have changed in the meantime slotSelectAccount(file->account(account.id())); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to modify account '%1'. Cause: %2", d->m_selectedAccount.name(), e.what())); } } delete dlg; ready(); } else { QPointer wizard = new KEditLoanWizard(d->m_selectedAccount); connect(wizard, SIGNAL(newCategory(MyMoneyAccount&)), this, SLOT(slotCategoryNew(MyMoneyAccount&))); connect(wizard, SIGNAL(createPayee(QString,QString&)), this, SLOT(slotPayeeNew(QString,QString&))); if (wizard->exec() == QDialog::Accepted && wizard != 0) { MyMoneySchedule sch; 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: { - action("account_close")->setEnabled(true); + a->setEnabled(true); break; } case KMyMoneyUtils::AccountBalanceNonZero: { - action("account_close")->setEnabled(false); - action("account_close")->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); + a->setEnabled(false); + a->setToolTip(i18n("The balance of the account must be zero before the account can be closed")); break; } case KMyMoneyUtils::AccountChildrenOpen: { - action("account_close")->setEnabled(false); - action("account_close")->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); + a->setEnabled(false); + a->setToolTip(i18n("All subaccounts must be closed before the account can be closed")); break; } case KMyMoneyUtils::AccountScheduleReference: { - action("account_close")->setEnabled(false); - action("account_close")->setToolTip(i18n("This account is still included in an active schedule")); + 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->action("schedule_duplicate")->isEnabled()) { + 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->action("transaction_cancel")->setEnabled(true); - kmymoney->action("transaction_enter")->setEnabled(true); + 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->action("transaction_delete")->isEnabled()) + 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->action("transaction_duplicate")->isEnabled()) { + 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->action("transaction_new")->isEnabled()) { + 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->action("transaction_edit")->isEnabled()) { + 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->action("transaction_editsplits")->isEnabled()) { + 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->action("transaction_cancel")->isEnabled()) { + if (kmymoney->actionCollection()->action(s_Actions[Action::TransactionCancel])->isEnabled()) { // make sure, we block the enter function - action("transaction_enter")->setEnabled(false); + 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->action("transaction_enter")->isEnabled()) { + 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->action("transaction_enter")->setEnabled(false); + 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->action("transaction_enter")->isEnabled()) { + 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->action("transaction_enter")->isEnabled()) { + if (!kmymoney->actionCollection()->action(s_Actions[Action::TransactionEnter])->isEnabled()) { noGuiItem.setEnabled(false); - noGuiItem.setToolTip(kmymoney->action("transaction_enter")->toolTip()); + 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 = action("transaction_match")->text(); + 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(); - bool fileOpen = d->m_myMoneyView->fileOpen(); - bool modified = file->dirty(); + const bool fileOpen = d->m_myMoneyView->fileOpen(); + const bool modified = file->dirty(); + const bool importRunning = (d->m_qifReader != 0) || (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); + } - action("open_database")->setEnabled(true); - action("saveas_database")->setEnabled(fileOpen); - action("file_save")->setEnabled(modified && !d->m_myMoneyView->isDatabase()); - action("file_save_as")->setEnabled(fileOpen); - action("file_close")->setEnabled(fileOpen); - action("view_personal_data")->setEnabled(fileOpen); - action("file_backup")->setEnabled(fileOpen && !d->m_myMoneyView->isDatabase()); - action("file_print")->setEnabled(fileOpen && d->m_myMoneyView->canPrint()); + // ************* + // 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::FileExportQIF, fileOpen && !importRunning)}, + {qMakePair(Action::FileImportQIF, fileOpen && !importRunning)}, + {qMakePair(Action::FileImportGNC, !importRunning)}, + {qMakePair(Action::FileImportTemplate, fileOpen && !importRunning)}, + {qMakePair(Action::FileExportTemplate, fileOpen && !importRunning)}, #ifdef KMM_DEBUG - action("view_file_info")->setEnabled(fileOpen); - action("file_dump")->setEnabled(fileOpen); + {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); + } - action("edit_find_transaction")->setEnabled(fileOpen); - - bool importRunning = (d->m_qifReader != 0) || (d->m_smtReader != 0); - action("file_export_qif")->setEnabled(fileOpen && !importRunning); - action("file_import_qif")->setEnabled(fileOpen && !importRunning); - action("file_import_gnc")->setEnabled(!importRunning); - action("file_import_template")->setEnabled(fileOpen && !importRunning); - action("file_export_template")->setEnabled(fileOpen && !importRunning); - - action("tools_currency_editor")->setEnabled(fileOpen); - action("tools_price_editor")->setEnabled(fileOpen); - action("tools_update_prices")->setEnabled(fileOpen); - action("tools_consistency_check")->setEnabled(fileOpen); - - action("account_new")->setEnabled(fileOpen); - action("account_reconcile")->setEnabled(false); - action("account_reconcile_finish")->setEnabled(false); - action("account_reconcile_postpone")->setEnabled(false); - action("account_edit")->setEnabled(false); - action("account_delete")->setEnabled(false); - action("account_open")->setEnabled(false); - action("account_close")->setEnabled(false); - action("account_reopen")->setEnabled(false); - action("account_transaction_report")->setEnabled(false); - action("account_online_map")->setEnabled(false); - action("account_online_update")->setEnabled(false); - action("account_online_update_all")->setEnabled(false); - action("account_online_unmap")->setEnabled(false); - action("account_online_new_credit_transfer")->setEnabled(onlineJobAdministration::instance()->canSendCreditTransfer()); - action("account_chart")->setEnabled(false); - - action("category_new")->setEnabled(fileOpen); - action("category_edit")->setEnabled(false); - action("category_delete")->setEnabled(false); - - action("institution_new")->setEnabled(fileOpen); - action("institution_edit")->setEnabled(false); - action("institution_delete")->setEnabled(false); - action("investment_new")->setEnabled(false); - action("investment_edit")->setEnabled(false); - action("investment_delete")->setEnabled(false); - action("investment_online_price_update")->setEnabled(false); - action("investment_manual_price_update")->setEnabled(false); - - action("schedule_edit")->setEnabled(false); - action("schedule_delete")->setEnabled(false); - action("schedule_enter")->setEnabled(false); - action("schedule_skip")->setEnabled(false); - - action("payee_delete")->setEnabled(false); - action("payee_rename")->setEnabled(false); - action("payee_merge")->setEnabled(false); - - action("tag_delete")->setEnabled(false); - action("tag_rename")->setEnabled(false); - - action("budget_delete")->setEnabled(false); - action("budget_rename")->setEnabled(false); - action("budget_change_year")->setEnabled(false); - action("budget_new")->setEnabled(true); - action("budget_copy")->setEnabled(false); - action("budget_forecast")->setEnabled(false); - - QString tooltip = i18n("Create a new transaction"); - action("transaction_new")->setEnabled(fileOpen && d->m_myMoneyView->canCreateTransactions(KMyMoneyRegister::SelectedTransactions(), tooltip)); - action("transaction_new")->setToolTip(tooltip); - - action("transaction_edit")->setEnabled(false); - action("transaction_editsplits")->setEnabled(false); - action("transaction_enter")->setEnabled(false); - action("transaction_cancel")->setEnabled(false); - action("transaction_delete")->setEnabled(false); - action("transaction_match")->setEnabled(false); - action("transaction_match")->setText(i18nc("Button text for match transaction", "Match")); - - action("transaction_accept")->setEnabled(false); - action("transaction_duplicate")->setEnabled(false); - action("transaction_mark_toggle")->setEnabled(false); - action("transaction_mark_cleared")->setEnabled(false); - action("transaction_mark_reconciled")->setEnabled(false); - action("transaction_mark_notreconciled")->setEnabled(false); - action("transaction_goto_account")->setEnabled(false); - action("transaction_goto_payee")->setEnabled(false); - action("transaction_assign_number")->setEnabled(false); - action("transaction_create_schedule")->setEnabled(false); - action("transaction_combine")->setEnabled(false); - action("transaction_select_all")->setEnabled(false); - action("transaction_copy_splits")->setEnabled(false); - - action("schedule_new")->setEnabled(fileOpen); - action("schedule_edit")->setEnabled(false); - action("schedule_delete")->setEnabled(false); - action("schedule_duplicate")->setEnabled(false); - action("schedule_enter")->setEnabled(false); - action("schedule_skip")->setEnabled(false); - - action("currency_new")->setEnabled(fileOpen); - action("currency_rename")->setEnabled(false); - action("currency_delete")->setEnabled(false); - action("currency_setbase")->setEnabled(false); - - action("price_new")->setEnabled(fileOpen); - action("price_edit")->setEnabled(false); - action("price_delete")->setEnabled(false); - action("price_update")->setEnabled(false); - - w = factory()->container("transaction_move_menu", this); + // ************* + // 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("transaction_mark_menu", this); + w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(false); - w = factory()->container("transaction_context_mark_menu", this); + 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 - action("transaction_select_all")->setEnabled(true); + 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()); } - action("transaction_delete")->setEnabled(enable); + aC->action(s_Actions[Action::TransactionDelete])->setEnabled(enable); if (!d->m_transactionEditor) { - tooltip = i18n("Duplicate the current selected transactions"); - action("transaction_duplicate")->setEnabled(d->m_myMoneyView->canDuplicateTransactions(d->m_selectedTransactions, tooltip) && !d->m_selectedTransactions[0].transaction().id().isEmpty()); - action("transaction_duplicate")->setToolTip(tooltip); + 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)) { - action("transaction_edit")->setEnabled(true); + 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) { - action("transaction_editsplits")->setEnabled(true); + aC->action(s_Actions[Action::TransactionEditSplits])->setEnabled(true); } if (d->m_selectedAccount.isAssetLiability() && d->m_selectedAccount.accountType() != MyMoneyAccount::Investment) { - action("transaction_create_schedule")->setEnabled(d->m_selectedTransactions.count() == 1); + aC->action(s_Actions[Action::TransactionCreateSchedule])->setEnabled(d->m_selectedTransactions.count() == 1); } } - action("transaction_edit")->setToolTip(tooltip); + aC->action(s_Actions[Action::TransactionEdit])->setToolTip(tooltip); if (!d->m_selectedAccount.isClosed()) { - w = factory()->container("transaction_move_menu", this); + w = factory()->container(s_Actions[Action::TransactionMoveMenu], this); if (w) w->setEnabled(true); } - w = factory()->container("transaction_mark_menu", this); + w = factory()->container(s_Actions[Action::TransactionMarkMenu], this); if (w) w->setEnabled(true); - w = factory()->container("transaction_context_mark_menu", this); + w = factory()->container(s_Actions[Action::TransactionContextMarkMenu], this); if (w) w->setEnabled(true); // Allow marking the transaction if at least one is selected - action("transaction_mark_cleared")->setEnabled(true); - action("transaction_mark_reconciled")->setEnabled(true); - action("transaction_mark_notreconciled")->setEnabled(true); - action("transaction_mark_toggle")->setEnabled(true); + 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()) - action("transaction_goto_account")->setEnabled(true); + aC->action(s_Actions[Action::TransactionGoToAccount])->setEnabled(true); if (!d->m_payeeGoto.isEmpty()) - action("transaction_goto_payee")->setEnabled(true); + 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 /* && action("transaction_edit")->isEnabled() */) { - action("transaction_match")->setEnabled(true); + 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) - action("transaction_accept")->setEnabled(true); + aC->action(s_Actions[Action::TransactionAccept])->setEnabled(true); if (matchedCount != 0) { - action("transaction_match")->setEnabled(true); - action("transaction_match")->setText(i18nc("Button text for unmatch transaction", "Unmatch")); - action("transaction_match")->setIcon(QIcon("process-stop")); + 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) { - action("transaction_combine")->setEnabled(true); + 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) { - action("transaction_copy_splits")->setEnabled(true); + 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) { - action("transaction_copy_splits")->setEnabled(true); + aC->action(s_Actions[Action::TransactionCopySplits])->setEnabled(true); } } } else { - action("transaction_assign_number")->setEnabled(d->m_transactionEditor->canAssignNumber()); - action("transaction_new")->setEnabled(false); - action("transaction_delete")->setEnabled(false); + 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; - action("transaction_enter")->setEnabled(d->m_transactionEditor->isComplete(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 - action("transaction_enter")->setToolTip(reason); - action("transaction_cancel")->setEnabled(true); + 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) { - action("account_online_update_all")->setEnabled(true); - action("account_online_update_menu")->setEnabled(true); + 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: - action("account_transaction_report")->setEnabled(true); - action("account_edit")->setEnabled(true); - action("account_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount)); - action("account_open")->setEnabled(true); + 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()) { - action("account_reconcile")->setEnabled(true); - action("account_reconcile")->setToolTip(i18n("Reconcile")); + 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()); - action("account_reconcile")->setToolTip(tip); + aC->action(s_Actions[Action::AccountStartReconciliation])->setToolTip(tip); if (!d->m_transactionEditor) { - action("account_reconcile_finish")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); - action("account_reconcile_postpone")->setEnabled(d->m_selectedAccount.id() == d->m_reconciliationAccount.id()); + 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) - action("investment_new")->setEnabled(true); + aC->action(s_Actions[Action::InvestmentNew])->setEnabled(true); if (d->m_selectedAccount.isClosed()) - action("account_reopen")->setEnabled(true); + aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedAccount); if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) { - action("account_online_unmap")->setEnabled(true); + 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) { - action("account_online_update")->setEnabled(true); - action("account_online_update_menu")->setEnabled(true); + aC->action(s_Actions[Action::AccountUpdate])->setEnabled(true); + aC->action(s_Actions[Action::AccountUpdateMenu])->setEnabled(true); } } } else { - action("account_online_map")->setEnabled(d->m_onlinePlugins.count() > 0); + aC->action(s_Actions[Action::AccountOnlineMap])->setEnabled(d->m_onlinePlugins.count() > 0); } - action("account_chart")->setEnabled(true); + aC->action(s_Actions[Action::AccountBalanceChart])->setEnabled(true); break; case MyMoneyAccount::Income : case MyMoneyAccount::Expense : - action("category_edit")->setEnabled(true); + 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); - action("category_delete")->setEnabled(!file->isReferenced(d->m_selectedAccount, skip)); - action("account_open")->setEnabled(true); + 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()) { - action("institution_edit")->setEnabled(true); - action("institution_delete")->setEnabled(!file->isReferenced(d->m_selectedInstitution)); + 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()) { - action("investment_edit")->setEnabled(true); - action("investment_delete")->setEnabled(!file->isReferenced(d->m_selectedInvestment)); - action("investment_manual_price_update")->setEnabled(true); + 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()) - action("investment_online_price_update")->setEnabled(true); + 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()) - action("account_reopen")->setEnabled(true); + aC->action(s_Actions[Action::AccountReopen])->setEnabled(true); else enableCloseAccountAction(d->m_selectedInvestment); } if (!d->m_selectedSchedule.id().isEmpty()) { - action("schedule_edit")->setEnabled(true); - action("schedule_duplicate")->setEnabled(true); - action("schedule_delete")->setEnabled(!file->isReferenced(d->m_selectedSchedule)); + 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()) { - action("schedule_enter")->setEnabled(true); + 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) { - action("schedule_skip")->setEnabled(true); + aC->action(s_Actions[Action::ScheduleSkip])->setEnabled(true); } } } if (d->m_selectedPayees.count() >= 1) { - action("payee_rename")->setEnabled(d->m_selectedPayees.count() == 1); - action("payee_merge")->setEnabled(d->m_selectedPayees.count() > 1); - action("payee_delete")->setEnabled(true); + 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) { - action("tag_rename")->setEnabled(d->m_selectedTags.count() == 1); - action("tag_delete")->setEnabled(true); + 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) { - action("budget_delete")->setEnabled(true); + aC->action(s_Actions[Action::BudgetDelete])->setEnabled(true); if (d->m_selectedBudgets.count() == 1) { - action("budget_change_year")->setEnabled(true); - action("budget_copy")->setEnabled(true); - action("budget_rename")->setEnabled(true); - action("budget_forecast")->setEnabled(true); + 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()) { - action("currency_rename")->setEnabled(true); + 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); - action("currency_delete")->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); + aC->action(s_Actions[Action::CurrencyDelete])->setEnabled(!file->isReferenced(d->m_selectedCurrency, skip)); if (d->m_selectedCurrency.id() != file->baseCurrency().id()) - action("currency_setbase")->setEnabled(true); + aC->action(s_Actions[Action::CurrencySetBase])->setEnabled(true); } - if (!d->m_selectedPrice.from().isEmpty() && d->m_selectedPrice.source() != "KMyMoney") { - action("price_edit")->setEnabled(true); - action("price_delete")->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()); - action("price_update")->setEnabled(security.isCurrency()); + 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("&(?!&)"), "&&"); - action("transaction_goto_payee")->setText(i18n("Go to '%1'", name)); + 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("&(?!&)"), "&&"); - action("transaction_goto_account")->setText(i18n("Go to '%1'", name)); + 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()) - action("transaction_goto_payee")->setText(i18n("Go to payee")); + actionCollection()->action(s_Actions[Action::TransactionGoToPayee])->setText(i18n("Go to payee")); if (d->m_accountGoto.isEmpty()) - action("transaction_goto_account")->setText(i18n("Go to account")); + 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); } inline bool KMyMoneyApp::isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup) { //! @fixme: there is a function in KMyMoneyPlugin::PluginLoader which has to have the same content if (metaData.serviceTypes().contains("KMyMoney/Plugin")) { const QString keyName{metaData.name() + "Enabled"}; if (configGroup.hasKey(keyName)) return configGroup.readEntry(keyName, true); return metaData.isEnabledByDefault(); } return false; } void KMyMoneyApp::slotPluginLoad(const KPluginMetaData& metaData) { std::unique_ptr loader{new QPluginLoader{metaData.fileName()}}; QObject* plugin = loader->instance(); if (!plugin) { qWarning("Could not load plugin '%s', error: %s", qPrintable(metaData.fileName()), qPrintable(loader->errorString())); return; } if ( d->m_plugins.contains(metaData.fileName()) ) { /** @fixme Handle a reload e.g. objectNames are equal but the object are different (plugin != d->m_plugins[plugin->objectName()]) * Also it could be usefull to drop the dependence on objectName() */ /* Note: there is nothing to delete here because if the plugin was loaded already, plugin points to the same object as the previously loaded one. */ return; } KMyMoneyPlugin::Plugin* kmmPlugin = qobject_cast(plugin); if (!kmmPlugin) { qWarning("Could not load plugin '%s'.", qPrintable(metaData.fileName())); return; } // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for extended online plugin KMyMoneyPlugin::OnlinePluginExtended* ope = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // Tell the plugin it is about to get plugged kmmPlugin->plug(); // plug the plugin guiFactory()->addClient(kmmPlugin); d->m_plugins[metaData.fileName()] = kmmPlugin; if (op) d->m_onlinePlugins[plugin->objectName()] = op; if (ope) onlineJobAdministration::instance()->addPlugin(plugin->objectName(), ope); if (ip) d->m_importerPlugins[plugin->objectName()] = ip; slotUpdateActions(); } void KMyMoneyApp::slotPluginUnload(const KPluginMetaData& metaData) { KMyMoneyPlugin::Plugin* plugin = d->m_plugins[metaData.fileName()]; // Remove and test if the plugin was actually loaded if (!d->m_plugins.remove(metaData.fileName()) || plugin == nullptr) return; // check for online plugin KMyMoneyPlugin::OnlinePlugin* op = dynamic_cast(plugin); // check for importer plugin KMyMoneyPlugin::ImporterPlugin* ip = dynamic_cast(plugin); // unplug the plugin guiFactory()->removeClient(plugin); if (op) d->m_onlinePlugins.remove(plugin->objectName()); if (ip) d->m_importerPlugins.remove(plugin->objectName()); plugin->unplug(); slotUpdateActions(); } void KMyMoneyApp::slotAutoSave() { if (!d->m_inAutoSaving) { // store the focus widget so we can restore it after save QPointer focusWidget = qApp->focusWidget(); d->m_inAutoSaving = true; KMSTATUS(i18n("Auto saving...")); //calls slotFileSave if needed, and restart the timer //it the file is not saved, reinitializes the countdown. if (d->m_myMoneyView->dirty() && d->m_autoSaveEnabled) { if (!slotFileSave() && d->m_autoSavePeriod > 0) { d->m_autoSaveTimer->setSingleShot(true); d->m_autoSaveTimer->start(d->m_autoSavePeriod * 60 * 1000); } } d->m_inAutoSaving = false; if (focusWidget && focusWidget != qApp->focusWidget()) { // we have a valid focus widget so restore it focusWidget->setFocus(); } } } void KMyMoneyApp::slotDateChanged() { QDateTime dt = QDateTime::currentDateTime(); QDateTime nextDay(QDate(dt.date().addDays(1)), QTime(0, 0, 0)); // +1 is to make sure that we're already in the next day when the // signal is sent (this way we also avoid setting the timer to 0) QTimer::singleShot((dt.secsTo(nextDay) + 1)*1000, this, SLOT(slotDateChanged())); d->m_myMoneyView->slotRefreshViews(); } const MyMoneyAccount& KMyMoneyApp::account(const QString& key, const QString& value) const { QList list; QList::const_iterator it_a; MyMoneyFile::instance()->accountList(list); QList accList; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { // search in the account's kvp container const QString& accountKvpValue = (*it_a).value(key); // search in the account's online settings kvp container const QString& onlineSettingsKvpValue = (*it_a).onlineBankingSettings().value(key); if (accountKvpValue.contains(value) || onlineSettingsKvpValue.contains(value)) { accList << MyMoneyFile::instance()->account((*it_a).id()); } if (accountKvpValue == value || onlineSettingsKvpValue == value) { return MyMoneyFile::instance()->account((*it_a).id()); } } // if we did not find an exact match of the value, we take the one that partially // matched, but only if not more than one matched partially. if (accList.count() == 1) { return accList.first(); } // return reference to empty element return MyMoneyFile::instance()->account(QString()); } void KMyMoneyApp::setAccountOnlineParameters(const MyMoneyAccount& _acc, const MyMoneyKeyValueContainer& kvps) { MyMoneyFileTransaction ft; try { MyMoneyAccount acc = MyMoneyFile::instance()->account(_acc.id()); acc.setOnlineBankingSettings(kvps); MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to setup online parameters for account '%1'", _acc.name()), e.what()); } } void KMyMoneyApp::slotAccountUnmapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // not a mapped account if (d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; if (KMessageBox::warningYesNo(this, QString("%1").arg(i18n("Do you really want to remove the mapping of account %1 to an online account? Depending on the details of the online banking method used, this action cannot be reverted.", d->m_selectedAccount.name())), i18n("Remove mapping to online account")) == KMessageBox::Yes) { MyMoneyFileTransaction ft; try { d->m_selectedAccount.setOnlineBankingSettings(MyMoneyKeyValueContainer()); // delete the kvp that is used in MyMoneyStatementReader too // we should really get rid of it, but since I don't know what it // is good for, I'll keep it around. (ipwizard) d->m_selectedAccount.deletePair("StatementKey"); MyMoneyFile::instance()->modifyAccount(d->m_selectedAccount); ft.commit(); // The mapping could disable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to unmap account from online account: %1", e.what())); } } } void KMyMoneyApp::slotAccountMapOnline() { // no account selected if (d->m_selectedAccount.id().isEmpty()) return; // already an account mapped if (!d->m_selectedAccount.onlineBankingSettings().value("provider").isEmpty()) return; // check if user tries to map a brokerageAccount if (d->m_selectedAccount.name().contains(i18n(" (Brokerage)"))) { if (KMessageBox::warningContinueCancel(this, i18n("You try to map a brokerage account to an online account. This is usually not advisable. In general, the investment account should be mapped to the online account. Please cancel if you intended to map the investment account, continue otherwise"), i18n("Mapping brokerage account")) == KMessageBox::Cancel) { return; } } // if we have more than one provider let the user select the current provider QString provider; QMap::const_iterator it_p; switch (d->m_onlinePlugins.count()) { case 0: break; case 1: provider = d->m_onlinePlugins.begin().key(); break; default: { QMenu popup(this); popup.setTitle(i18n("Select online banking plugin")); // Populate the pick list with all the provider for (it_p = d->m_onlinePlugins.constBegin(); it_p != d->m_onlinePlugins.constEnd(); ++it_p) { popup.addAction(it_p.key())->setData(it_p.key()); } QAction *item = popup.actions()[0]; if (item) { popup.setActiveAction(item); } // cancelled if ((item = popup.exec(QCursor::pos(), item)) == 0) { return; } provider = item->data().toString(); } break; } if (provider.isEmpty()) return; // find the provider it_p = d->m_onlinePlugins.constFind(provider); if (it_p != d->m_onlinePlugins.constEnd()) { // plugin found, call it MyMoneyKeyValueContainer settings; if ((*it_p)->mapAccount(d->m_selectedAccount, settings)) { settings["provider"] = provider; MyMoneyAccount acc(d->m_selectedAccount); acc.setOnlineBankingSettings(settings); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(acc); ft.commit(); // The mapping could enable the online task system onlineJobAdministration::instance()->updateOnlineTaskProperties(); } catch (const MyMoneyException &e) { KMessageBox::error(this, i18n("Unable to map account to online account: %1", e.what())); } } } } void KMyMoneyApp::slotAccountUpdateOnlineAll() { QList accList; MyMoneyFile::instance()->accountList(accList); QList::iterator it_a; QMap::const_iterator it_p; d->m_statementResults.clear(); d->m_collectingStatements = true; // remove all those from the list, that don't have a 'provider' or the // provider is not currently present for (it_a = accList.begin(); it_a != accList.end();) { if ((*it_a).onlineBankingSettings().value("provider").isEmpty() || d->m_onlinePlugins.find((*it_a).onlineBankingSettings().value("provider")) == d->m_onlinePlugins.end()) { it_a = accList.erase(it_a); } else ++it_a; } - - action("account_online_update")->setEnabled(false); - action("account_online_update_menu")->setEnabled(false); - action("account_online_update_all")->setEnabled(false); + 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; - action("account_online_update")->setEnabled(false); - action("account_online_update_menu")->setEnabled(false); - action("account_online_update_all")->setEnabled(false); + 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("/home/thb", "kmm-statement*"); for (uint i = 0; i < d.count(); ++i) { qDebug("Remove %s", qPrintable(d[i])); //FIXME: FIX on windows d.remove(QString("/home/thb/%1").arg(d[i])); } m_statementXMLindex = 0; } void KMyMoneyApp::Private::closeFile() { q->slotSelectAccount(); q->slotSelectInstitution(); q->slotSelectInvestment(); q->slotSelectSchedule(); q->slotSelectCurrency(); q->slotSelectBudget(QList()); q->slotSelectPayees(QList()); q->slotSelectTags(QList()); q->slotSelectTransactions(KMyMoneyRegister::SelectedTransactions()); m_reconciliationAccount = MyMoneyAccount(); m_myMoneyView->finishReconciliation(m_reconciliationAccount); m_myMoneyView->closeFile(); m_fileName = QUrl(); q->updateCaption(); // just create a new balance warning object delete m_balanceWarning; m_balanceWarning = new KBalanceWarning(q); emit q->fileLoaded(m_fileName); } diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h index 715b0929a..d1eab679f 100644 --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -1,1373 +1,1459 @@ /*************************************************************************** kmymoney.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes ***************************************************************************/ /*************************************************************************** * * * 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 KMYMONEY_H #define KMYMONEY_H // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include "mymoneyschedule.h" #include #include #include #include #include #include #include #include #include "mymoney/onlinejob.h" #include "mymoney/onlinejobtyped.h" #include "onlinetasks/interfaces/tasks/credittransfer.h" class QResizeEvent; class QTreeWidgetItem; class KPluginInfo; +enum class Action { + FileOpenDatabase, FileSaveAsDatabase, FileBackup, + FileImportGNC, FileImportQIF, FileExportQIF, + FileImportStatement, + FileImportTemplate, FileExportTemplate, + #ifdef KMM_DEBUG + FileDump, + #endif + FilePersonalData, FileInformation, + EditFindTransaction, + ViewTransactionDetail, ViewHideReconciled, + ViewHideCategories, ViewShowAll, + InstitutionNew, InstitutionEdit, + InstitutionDelete, + AccountNew, AccountOpen, + AccountStartReconciliation, AccountFinishReconciliation, + AccountPostponeReconciliation, AccountEdit, + AccountDelete, AccountClose, AccountReopen, + AccountTransactionReport, AccountBalanceChart, + AccountUpdateMenu, + AccountOnlineMap, AccountOnlineUnmap, + AccountUpdate, AccountUpdateAll, + AccountCreditTransfer, + CategoryNew, CategoryEdit, + CategoryDelete, + ToolQIF, ToolCurrencies, + ToolPrices, ToolUpdatePrices, + ToolConsistency, ToolPerformance, + ToolSQL, ToolCalculator, + SettingsAllMessages, SettingsLanguage, + HelpShow, + TransactionNew, TransactionEdit, + TransactionEnter, TransactionEditSplits, + TransactionCancel, TransactionDelete, + TransactionDuplicate, TransactionMatch, + TransactionAccept, TransactionToggleReconciled, + TransactionToggleCleared, TransactionReconciled, + TransactionNotReconciled, TransactionSelectAll, + TransactionGoToAccount, TransactionGoToPayee, + TransactionCreateSchedule, TransactionAssignNumber, + TransactionCombine, TransactionCopySplits, + TransactionMoveMenu, TransactionMarkMenu, + TransactionContextMarkMenu, + InvestmentNew, InvestmentEdit, + InvestmentDelete, InvestmentOnlinePrice, + InvestmentManualPrice, + ScheduleNew, ScheduleEdit, + ScheduleDelete, ScheduleDuplicate, + ScheduleEnter, ScheduleSkip, + PayeeNew, PayeeRename, PayeeDelete, + PayeeMerge, + TagNew, TagRename, TagDelete, + BudgetNew, BudgetRename, BudgetDelete, + BudgetCopy, BudgetChangeYear, BudgetForecast, + CurrencyNew, CurrencyRename, CurrencyDelete, + CurrencySetBase, + PriceNew, PriceDelete, + PriceUpdate, PriceEdit, + #ifdef KMM_DEBUG + WizardNewUser, DebugTraces, + #endif + DebugTimers, + OnlineJobDelete, OnlineJobEdit, OnlineJobLog +}; + +inline uint qHash(const Action key, uint seed) +{ + return ::qHash(static_cast(key), seed); +} + /*! \mainpage KMyMoney Main Page for API documentation. * * \section intro Introduction * * This is the API documentation for KMyMoney. It should be used as a reference * for KMyMoney developers and users who wish to see how KMyMoney works. This * documentation will be kept up-to-date as development progresses and should be * read for new features that have been developed in KMyMoney. */ /** * The base class for KMyMoney application windows. It sets up the main * window and reads the config file as well as providing a menubar, toolbar * and statusbar. * * @see KMyMoneyView * * @author Michael Edwardes 2000-2001 * @author Thomas Baumgart 2006-2008 * * @short Main application class. */ class KMyMoneyApp : public KXmlGuiWindow, public IMyMoneyProcessingCalendar { Q_OBJECT private slots: /** * Keep track of objects that are destroyed by external events */ void slotObjectDestroyed(QObject* o); /** * Add a context menu to the list used by KMessageBox::informationList to display the consistency check results. */ void slotInstallConsistencyCheckContextMenu(); /** * Handle the context menu of the list used by KMessageBox::informationList to display the consistency check results. */ void slotShowContextMenuForConsistencyCheck(const QPoint &); protected slots: void slotFileSaveAsFilterChanged(const QString& filter); /** * This slot is intended to be used as part of auto saving. This is used when the * QTimer emits the timeout signal and simply checks that the file is dirty (has * received modifications to its contents), and call the appropriate method to * save the file. Furthermore, re-starts the timer (possibly not needed). * @author mvillarino 2005 * @see KMyMoneyApp::slotDataChanged() */ void slotAutoSave(); /** * This slot re-enables all message for which the "Don't show again" * option had been selected. */ void slotEnableMessages(); /** * Called when the user asks for file information. */ void slotFileFileInfo(); /** * Called to run performance test. */ void slotPerformanceTest(); /** * Called to generate the sql to create kmymoney database tables etc. */ void slotGenerateSql(); +#ifdef KMM_DEBUG /** * Debugging only: turn on/off traces */ void slotToggleTraces(); +#endif /** * Debugging only: turn on/off timers */ void slotToggleTimers(); /** * Called when the user asks for the personal information. */ void slotFileViewPersonal(); /** * Called when the user wishes to import tab delimeted transactions * into the current account. An account must be open for this to * work. Calls KMyMoneyView::slotAccountImportAscii. * * @see MyMoneyAccount */ void slotQifImport(); /** * Called when a QIF import is finished. */ void slotQifImportFinished(); /** * Opens a file selector dialog for the user to choose an existing OFX * file from the file system to be imported. This slot is expected to * be called from the UI. */ void slotGncImport(); /** * Open a dialog with a chart of the balance for the currently selected * account (m_selectedAccount). Return once the dialog is closed. Don't do * anything if no account is selected or charts are not available. */ void slotAccountChart(); /** * Opens a file selector dialog for the user to choose an existing KMM * statement file from the file system to be imported. This is for testing * only. KMM statement files are not designed to be exposed to the user. */ void slotStatementImport(); void slotLoadAccountTemplates(); void slotSaveAccountTemplates(); /** * Called when the user wishes to export some transaction to a * QIF formatted file. An account must be open for this to work. * Uses MyMoneyQifWriter() for the actual output. */ void slotQifExport(); /** * Open up the application wide settings dialog. * * @see KSettingsDlg */ void slotSettings(); /** No descriptions */ void slotFileBackup(); void slotShowTipOfTheDay(); void slotQifProfileEditor(); void slotShowPreviousView(); void slotShowNextView(); /** * Brings up a dialog to let the user search for specific transaction(s). It then * opens a results window to display those transactions. */ void slotFindTransaction(); /** * Destroys a possibly open the search dialog */ void slotCloseSearchDialog(); /** * Preloads the input dialog with the data of the current * selected institution and brings up the input dialog * and saves the information entered. */ - void slotInstitutionEdit(const MyMoneyObject& obj = MyMoneyInstitution()); + void slotInstitutionEdit(); + void slotInstitutionEdit(const MyMoneyObject &obj); /** * Deletes the current selected institution. */ void slotInstitutionDelete(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name. The parent defaults to * MyMoneyFile::expense() * * @param name Name of the account to be created. Could include a full hierarchy * @param id reference to storage which will receive the id after successful creation * * @note Typically, this slot can be connected to the * StdTransactionEditor::createCategory(const QString&, QString&) or * KMyMoneyCombo::createItem(const QString&, QString&) signal. */ void slotCategoryNew(const QString& name, QString& id); /** * Calls the print logic for the current view */ void slotPrintView(); /** * Create a new investment */ void slotInvestmentNew(); /** * Create a new investment in a given @p parent investment account */ void slotInvestmentNew(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This slot opens the investment editor to edit the currently * selected investment if possible */ void slotInvestmentEdit(); /** * Deletes the current selected investment. */ void slotInvestmentDelete(); /** * Performs online update for currently selected investment */ void slotOnlinePriceUpdate(); /** * Performs manual update for currently selected investment */ void slotManualPriceUpdate(); /** * Call this slot, if any configuration parameter has changed */ void slotUpdateConfiguration(); /** */ bool slotPayeeNew(const QString& newnameBase, QString& id); void slotPayeeNew(); /** */ void slotPayeeDelete(); /** * Slot that merges two or more selected payess into a new payee */ void slotPayeeMerge(); /** */ void slotTagNew(const QString& newnameBase, QString& id); void slotTagNew(); /** */ void slotTagDelete(); /** */ void slotBudgetNew(); /** */ void slotBudgetDelete(); /** */ void slotBudgetCopy(); /** */ void slotBudgetChangeYear(); /** */ void slotBudgetForecast(); /** */ void slotCurrencyNew(); /** */ void slotCurrencyUpdate(const QString ¤cyId, const QString& currencyName, const QString& currencyTradingSymbol); /** */ void slotCurrencyDelete(); /** */ void slotCurrencySetBase(); /** * This slot is used to start new features during the development cycle */ void slotNewFeature(); /** */ void slotTransactionsNew(); /** */ void slotTransactionsEdit(); /** */ void slotTransactionsEditSplits(); /** */ void slotTransactionsDelete(); /** */ void slotTransactionsEnter(); /** */ void slotTransactionsCancel(); /** */ void slotTransactionsCancelOrEnter(bool& okToSelect); /** */ void slotTransactionDuplicate(); /** */ void slotToggleReconciliationFlag(); /** */ void slotMarkTransactionCleared(); /** */ void slotMarkTransactionReconciled(); /** */ void slotMarkTransactionNotReconciled(); /** */ void slotTransactionGotoAccount(); /** */ void slotTransactionGotoPayee(); /** */ void slotTransactionCreateSchedule(); /** */ void slotTransactionAssignNumber(); /** */ void slotTransactionCombine(); /** * This method takes the selected splits and checks that only one transaction (src) * has more than one split and all others have only a single one. It then copies the * splits of the @b src transaction to all others. */ void slotTransactionCopySplits(); /** * Accept the selected transactions that are marked as 'imported' and remove the flag */ void slotTransactionsAccept(); /** * This slot triggers an update of all views and restarts * a single shot timer to call itself again at beginning of * the next day. */ void slotDateChanged(); /** * This slot will be called when the engine data changed * and the application object needs to update its state. */ void slotDataChanged(); void slotMoveToAccount(const QString& id); void slotUpdateMoveToAccountMenu(); /** * This slot collects information for a new scheduled transaction * based on transaction @a t and @a occurrence and saves it in the engine. */ void slotScheduleNew(const MyMoneyTransaction& t, MyMoneySchedule::occurrenceE occurrence = MyMoneySchedule::OCCUR_MONTHLY); /** */ void slotScheduleDuplicate(); void slotKDELanguageSettings(); void slotAccountMapOnline(); void slotAccountUnmapOnline(); void slotAccountUpdateOnline(); void slotAccountUpdateOnlineAll(); /** * @brief Start dialog for an online banking transfer */ void slotNewOnlineTransfer(); /** * @brief Start dialog to edit onlineJob if possible * @param onlineJob id to edit */ void slotEditOnlineJob(const QString); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJob); /** * @brief Start dialog to edit onlineJob if possible */ void slotEditOnlineJob(const onlineJobTyped); /** * @brief Saves an online banking job */ void slotOnlineJobSave(onlineJob job); /** * @brief Queue an online banking job */ void slotOnlineJobSend(onlineJob job); /** * @brief Send a list of onlineJobs */ void slotOnlineJobSend(QList jobs); + /** + * dummy method needed just for initialization + */ + void slotRemoveJob(); + void slotEditJob(); + /** * @brief Show the log currently selected online job */ void slotOnlineJobLog(); void slotOnlineJobLog(const QStringList& onlineJobIds); void slotManageGpgKeys(); void slotKeySelected(int idx); void slotStatusProgressDone(); public: /** * This method checks if there is at least one asset or liability account * in the current storage object. If not, it starts the new account wizard. */ void createInitialAccount(); /** * This method returns the last URL used or an empty URL * depending on the option setting if the last file should * be opened during startup or the open file dialog should * be displayed. * * @return URL of last opened file or empty if the program * should start with the open file dialog */ QUrl lastOpenedURL(); /** * construtor of KMyMoneyApp, calls all init functions to create the application. */ explicit KMyMoneyApp(QWidget* parent = 0); /** * Destructor */ ~KMyMoneyApp(); static void progressCallback(int current, int total, const QString&); void writeLastUsedDir(const QString& directory); QString readLastUsedDir() const; void writeLastUsedFile(const QString& fileName); QString readLastUsedFile() const; /** * Returns whether there is an importer available that can handle this file */ bool isImportableFile(const QUrl &url); /** * This method is used to update the caption of the application window. * It sets the caption to "filename [modified] - KMyMoney". * * @param skipActions if true, the actions will not be updated. This * is usually onyl required by some early calls when * these widgets are not yet created (the default is false). */ void updateCaption(bool skipActions = false); /** * This method returns a list of all 'other' dcop registered kmymoney processes. * It's a subset of the return of DCOPclient()->registeredApplications(). * * @retval QStringList of process ids */ QList instanceList() const; /** * Dump a list of the names of all defined KActions to stdout. */ void dumpActions() const; /** * Popup the context menu with the respective @p containerName. * Valid container names are defined in kmymoneyui.rc */ void showContextMenu(const QString& containerName); /** * This method opens the category editor with the data found in @a account. The * parent account is preset to @a parent but can be modified. If the user * acknowledges, the category is created. */ void createCategory(MyMoneyAccount& account, const MyMoneyAccount& parent); /** * This method returns the account for a given @a key - @a value pair. * If the account is not found in the list of accounts, MyMoneyAccount() * is returned. The @a key - @a value pair can be in the account's kvp * container or the account's online settings kvp container. */ const MyMoneyAccount& account(const QString& key, const QString& value) const; /** * This method set the online parameters stored in @a kvps with the * account referenced by @a acc. */ void setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps); QUrl selectFile(const QString& title, const QString& path, const QString& mask, QFileDialog::FileMode, QWidget *widget); /** * This methods returns the account from the list of accounts identified by * an account id or account name including an account hierachy. * * The parent account specifies from which account the search should be started. * In case the parent account does not have an id, the method scans all top-level accounts. * * If the account is not found in the list of accounts, MyMoneyAccount() is returned. * * @param acc account to find * @param parent parent account to search from * @retval found MyMoneyAccount account instance * @retval MyMoneyAccount() if not found */ const MyMoneyAccount& findAccount(const MyMoneyAccount& acc, const MyMoneyAccount& parent) const; void createAccount(MyMoneyAccount& newAccount, MyMoneyAccount& parentAccount, MyMoneyAccount& brokerageAccount, MyMoneyMoney openingBal); QString filename() const; void webConnect(const QString& sourceUrl, const QByteArray& asn_id); /** * Checks if the file with the @a url already exists. If so, * the user is asked if he/she wants to override the file. * If the user's answer is negative, @p false will be returned. * @p true will be returned in all other cases. */ bool okToWriteFile(const QUrl &url); protected: /** save general Options like all bar positions and status as well as the geometry and the recent file list to the configuration * file */ void saveOptions(); /** * Creates the interfaces necessary for the plugins to work. Therefore, * this method must be called prior to loadPlugins(). */ void createInterfaces(); /** * load all available plugins. Make sure you have called createInterfaces() * before you call this one. */ void loadPlugins(); /** * @brief Checks if the given plugin is loaded on start up * * This filters plugins which are loaded on demand only and deactivated plugins. * The configGroup must point to the correct group already. */ bool isPluginEnabled(const KPluginMetaData& metaData, const KConfigGroup& configGroup); /** * read general options again and initialize all variables like the recent file list */ void readOptions(); /** initializes the KActions of the application */ void initActions(); /** initializes the dynamic menus (account selectors) */ void initDynamicMenus(); /** * sets up the statusbar for the main window by initialzing a statuslabel. */ void initStatusBar(); /** * @brief Establish connections between actions and views * * Must be called after creation of actions and views. */ void connectActionsAndViews(); /** queryClose is called by KMainWindow on each closeEvent of a window. Against the * default implementation (only returns true), this calls saveModified() on the document object to ask if the document shall * be saved if Modified; on cancel the closeEvent is rejected. * The settings are saved using saveOptions() if we are about to close. * @see KMainWindow#queryClose * @see QWidget#closeEvent */ virtual bool queryClose(); void slotCheckSchedules(); virtual void resizeEvent(QResizeEvent*); void createSchedule(MyMoneySchedule newSchedule, MyMoneyAccount& newAccount); /** * This method checks, if an account can be closed or not. An account * can be closed if: * * - the balance is zero and * - all children are already closed and * - there is no unfinished schedule referencing the account * * @param acc reference to MyMoneyAccount object in question * @retval true account can be closed * @retval false account cannot be closed */ KMyMoneyUtils::CanCloseAccountCodeE canCloseAccount(const MyMoneyAccount& acc) const; /** * This method checks if an account can be closed and enables/disables * the close account action * If disabled, it sets a tooltip explaning why it cannot be closed * @brief enableCloseAccountAction * @param acc reference to MyMoneyAccount object in question */ void enableCloseAccountAction(const MyMoneyAccount& acc); /** * Check if a list contains a payee with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool payeeInList(const QList& list, const QString& id) const; /** * Check if a list contains a tag with a given id * * @param list const reference to value list * @param id const reference to id * * @retval true object has been found * @retval false object is not in list */ bool tagInList(const QList& list, const QString& id) const; /** * Mark the selected transactions as provided by @a flag. If * flag is @a MyMoneySplit::Unknown, the future state depends * on the current stat of the split's flag accoring to the * following table: * * - NotReconciled --> Cleared * - Cleared --> Reconciled * - Reconciled --> NotReconciled */ void markTransaction(MyMoneySplit::reconcileFlagE flag); /** * This method allows to skip the next scheduled transaction of * the given schedule @a s. * */ void skipSchedule(MyMoneySchedule& s); /** * This method allows to enter the next scheduled transaction of * the given schedule @a s. In case @a extendedKeys is @a true, * the given schedule can also be skipped or ignored. * If @a autoEnter is @a true and the schedule does not contain * an estimated value, the schedule is entered as is without further * interaction with the user. In all other cases, the user will * be presented a dialog and allowed to adjust the values for this * instance of the schedule. * * The transaction will be created and entered into the ledger * and the schedule updated. */ KMyMoneyUtils::EnterScheduleResultCodeE enterSchedule(MyMoneySchedule& s, bool autoEnter = false, bool extendedKeys = false); /** * Creates a new institution entry in the MyMoneyFile engine * * @param institution MyMoneyInstitution object containing the data of * the institution to be created. */ void createInstitution(MyMoneyInstitution& institution); /** * This method unmatches the currently selected transactions */ void transactionUnmatch(); /** * This method matches the currently selected transactions */ void transactionMatch(); /** * This method preloads the holidays for the duration of the default forecast period */ void preloadHolidays(); public slots: void slotFileInfoDialog(); /** */ void slotFileNew(); /** open a file and load it into the document*/ void slotFileOpen(); /** opens a file from the recent files menu */ void slotFileOpenRecent(const QUrl &url); /** open a SQL database */ void slotOpenDatabase(); /** * saves the current document. If it has no name yet, the user * will be queried for it. * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSave(); /** * ask the user for the filename and save the current document * * @retval false save operation failed * @retval true save operation was successful */ bool slotFileSaveAs(); /** * ask the user to select a database and save the current document * * @retval false save operation failed * @retval true save operation was successful */ - bool slotSaveAsDatabase(); + bool saveAsDatabase(); + void slotSaveAsDatabase(); /** asks for saving if the file is modified, then closes the actual file and window */ void slotFileCloseWindow(); /** asks for saving if the file is modified, then closes the actual file */ void slotFileClose(); /** * closes all open windows by calling close() on each memberList item * until the list is empty, then quits the application. * If queryClose() returns false because the user canceled the * saveModified() dialog, the closing breaks. */ void slotFileQuit(); void slotFileConsistencyCheck(); /** * fires up the price table editor */ void slotPriceDialog(); /** * fires up the currency table editor */ void slotCurrencyDialog(); + /** + * dummy method needed just for initialization + */ + void slotShowTransactionDetail(); + /** * Toggles the hide reconciled transactions setting */ void slotHideReconciledTransactions(); /** * Toggles the hide unused categories setting */ void slotHideUnusedCategories(); /** * Toggles the show all accounts setting */ void slotShowAllAccounts(); /** * changes the statusbar contents for the standard label permanently, * used to indicate current actions. Returns the previous value for * 'stacked' usage. * * @param text the text that is displayed in the statusbar */ QString slotStatusMsg(const QString &text); /** * This method changes the progress bar in the status line according * to the parameters @p current and @p total. The following special * cases exist: * * - current = -1 and total = -1 will reset the progress bar * - current = ?? and total != 0 will setup the 100% mark to @p total * - current = xx and total == 0 will set the percentage * * @param current the current value with respect to the initialised * 100% mark * @param total the total value (100%) */ void slotStatusProgressBar(int current, int total = 0); /** No descriptions */ void slotProcessExited(); /** * Called to update stock and currency prices from the user menu */ void slotEquityPriceUpdate(); /** * Imports a KMM statement into the engine, triggering the appropriate * UI to handle account matching, payee creation, and someday * payee and transaction matching. */ bool slotStatementImport(const MyMoneyStatement& s, bool silent = false); /** * Essentially similar to the above slot, except this will load the file * from disk first, given the URL. */ bool slotStatementImport(const QString& url); /** * This slot starts the reconciliation of the currently selected account */ void slotAccountReconcileStart(); /** * This slot finishes a previously started reconciliation */ void slotAccountReconcileFinish(); /** * This slot postpones a previously started reconciliations */ void slotAccountReconcilePostpone(); /** * This slot deletes the currently selected account if possible */ void slotAccountDelete(); /** * This slot opens the account editor to edit the currently * selected account if possible */ void slotAccountEdit(); /** * This slot opens the selected account in the ledger view */ - void slotAccountOpen(const MyMoneyObject& = MyMoneyAccount()); + void slotAccountOpen(); + void slotAccountOpen(const MyMoneyObject&); /** * This slot closes the currently selected account if possible */ void slotAccountClose(); /** * This slot re-openes the currently selected account if possible */ void slotAccountReopen(); /** * This slot reparents account @p src to be a child of account @p dest * * @param src account to be reparented * @param dest new parent */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyAccount& dest); /** * This slot reparents account @p src to be a held at institution @p dest * * @param src account to be reparented * @param dest new parent institution */ void slotReparentAccount(const MyMoneyAccount& src, const MyMoneyInstitution& dest); /** * This slot creates a transaction report for the selected account * and opens it in the reports view. */ void slotAccountTransactionReport(); /** * This slot opens the account options menu at the current cursor * position. */ void slotShowAccountContextMenu(const MyMoneyObject&); /** * This slot opens the schedule options menu at the current cursor * position. */ void slotShowScheduleContextMenu(); /** * This slot opens the institution options menu at the current cursor * position. */ void slotShowInstitutionContextMenu(const MyMoneyObject&); /** * This slot opens the investment options menu at the current cursor * position. */ void slotShowInvestmentContextMenu(); /** * This slot opens the payee options menu at the current cursor * position. */ void slotShowPayeeContextMenu(); /** * This slot opens the tag options menu at the current cursor * position. */ void slotShowTagContextMenu(); /** * This slot opens the budget options menu at the current cursor * position. */ void slotShowBudgetContextMenu(); /** * This slot opens the transaction options menu at the current cursor * position. */ void slotShowTransactionContextMenu(); /** * This slot opens the currency options menu at the current cursor * position. */ void slotShowCurrencyContextMenu(); /** * This slot opens the price options menu at the current cursor * position. */ void slotShowPriceContextMenu(); /** * Open onlineJob options menu at current cursor position. */ void slotShowOnlineJobContextMenu(); /** * This slot collects information for a new scheduled transaction * and saves it in the engine. @sa slotScheduleNew(const MyMoneyTransaction&) */ void slotScheduleNew(); /** * This slot allows to edit information the currently selected schedule */ void slotScheduleEdit(); /** * This slot allows to delete the currently selected schedule */ void slotScheduleDelete(); /** * This slot allows to enter the next scheduled transaction of * the currently selected schedule */ void slotScheduleEnter(); /** * This slot allows to skip the next scheduled transaction of * the currently selected schedule */ void slotScheduleSkip(); /** * This slot fires up the KCalc application */ void slotToolsStartKCalc(); void slotResetSelections(); - void slotSelectAccount(const MyMoneyObject& account = MyMoneyAccount()); + void slotSelectAccount(); + void slotSelectAccount(const MyMoneyObject& account); - void slotSelectInstitution(const MyMoneyObject& institution = MyMoneyInstitution()); + void slotSelectInstitution(); + void slotSelectInstitution(const MyMoneyObject& institution); - void slotSelectInvestment(const MyMoneyObject& account = MyMoneyAccount()); + void slotSelectInvestment(); + void slotSelectInvestment(const MyMoneyObject& account); - void slotSelectSchedule(const MyMoneySchedule& schedule = MyMoneySchedule()); + void slotSelectSchedule(); + void slotSelectSchedule(const MyMoneySchedule& schedule); void slotSelectPayees(const QList& list); void slotSelectTags(const QList& list); void slotSelectBudget(const QList& list); void slotSelectTransactions(const KMyMoneyRegister::SelectedTransactions& list); - void slotSelectCurrency(const MyMoneySecurity& currency = MyMoneySecurity()); + void slotSelectCurrency(); + void slotSelectCurrency(const MyMoneySecurity& currency); - void slotSelectPrice(const MyMoneyPrice& price = MyMoneyPrice()); + void slotSelectPrice(); + void slotSelectPrice(const MyMoneyPrice& price); void slotTransactionMatch(); /** * Brings up the new account wizard and saves the information. */ void slotAccountNew(); void slotAccountNew(MyMoneyAccount&); /** * Brings up the new category editor and saves the information. */ void slotCategoryNew(); /** * Brings up the new category editor and saves the information. * The dialog will be preset with the name and parent account. * * @param account reference of category to be created. The @p name member * should be filled by the caller. The object will be filled * with additional information during the creation process * esp. the @p id member. * @param parent reference to parent account (defaults to none) */ void slotCategoryNew(MyMoneyAccount& account, const MyMoneyAccount& parent = MyMoneyAccount()); /** * This method updates all KAction items to the current state. */ void slotUpdateActions(); /** * Brings up the input dialog and saves the information. */ void slotInstitutionNew(); /** * Brings up the input dialog and saves the information. If * the institution has been created, the @a id member is filled, * otherwise it is empty. * * @param institution reference to data to be used to create the * institution. id member will be updated. */ void slotInstitutionNew(MyMoneyInstitution& institution); /** * Loads a plugin */ void slotPluginLoad(const KPluginMetaData& metaData); /** * Unloads a plugin */ void slotPluginUnload(const KPluginMetaData& metaData); private: /** * Create the transaction move menu and setup necessary connections. */ void createTransactionMoveMenu(); /** * This method sets the holidayRegion for use by the processing calendar. */ void setHolidayRegion(const QString& holidayRegion); /** * Load the status bar with the 'ready' message. This is hold in a single * place, so that is consistent with isReady(). */ void ready(); /** * Check if the status bar contains the 'ready' message. The return * value is used e.g. to detect if a quit operation is allowed or not. * * @retval true application is idle * @retval false application is active working on a longer operation */ bool isReady(); /** * Delete a possibly existing transaction editor but make sure to remove * any reference to it so that we avoid using a half-dead object */ void deleteTransactionEditor(); /** * delete all selected transactions w/o further questions */ void doDeleteTransactions(); /** * Re-implemented from IMyMoneyProcessingCalendar */ bool isProcessingDate(const QDate& date) const; /** * Depending on the setting of AutoSaveOnQuit, this method * asks the user to save the file or not. * * @returns see return values of KMessageBox::warningYesNoCancel() */ int askSaveOnClose(); /** * Implement common task when deleting or merging payees */ bool payeeReassign(int type); signals: /** * This signal is emitted when a new file is loaded. In the case file * is closed, this signal is also emitted with an empty url. */ void fileLoaded(const QUrl &url); /** * This signal is emitted when a payee/list of payees has been selected by * the GUI. If no payee is selected or the selection is removed, * @p payees is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void payeesSelected(const QList& payees); /** * This signal is emitted when a tag/list of tags has been selected by * the GUI. If no tag is selected or the selection is removed, * @p tags is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void tagsSelected(const QList& tags); /** * This signal is emitted when a transaction/list of transactions has been selected by * the GUI. If no transaction is selected or the selection is removed, * @p transactions is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void transactionsSelected(const KMyMoneyRegister::SelectedTransactions& transactions); /** * This signal is sent out, when the user presses Ctrl+A or activates * the Select all transactions action. */ void selectAllTransactions(); /** * This signal is emitted when a list of budgets has been selected by * the GUI. If no budget is selected or the selection is removed, * @a budget is identical to an empty QList. This signal is used * by plugins to get information about changes. */ void budgetSelected(const QList& budget); void budgetRename(); /** * This signal is emitted when a new account has been selected by * the GUI. If no account is selected or the selection is removed, * @a account is identical to MyMoneyAccount(). This signal is used * by plugins to get information about changes. */ void accountSelected(const MyMoneyAccount& account); void investmentSelected(const MyMoneyAccount& account); /** * This signal is emitted when a new institution has been selected by * the GUI. If no institution is selected or the selection is removed, * @a institution is identical to MyMoneyInstitution(). This signal is used * by plugins to get information about changes. */ void institutionSelected(const MyMoneyInstitution& institution); /** * This signal is emitted when a new schedule has been selected by * the GUI. If no schedule is selected or the selection is removed, * @a schedule is identical to MyMoneySchedule(). This signal is used * by plugins to get information about changes. */ void scheduleSelected(const MyMoneySchedule& schedule); /** * This signal is emitted when a new currency has been selected by * the GUI. If no currency is selected or the selection is removed, * @a currency is identical to MyMoneySecurity(). This signal is used * by plugins to get information about changes. */ void currencySelected(const MyMoneySecurity& currency); /** * This signal is emitted when a new price has been selected by * the GUI. If no price is selected or the selection is removed, * @a price is identical to MyMoneyPrice(). */ void priceSelected(const MyMoneyPrice& price); void payeeRename(); void payeeCreated(const QString& id); void tagRename(); void tagCreated(const QString& id); void currencyRename(); void currencyCreated(const QString& id); void priceEdit(); void priceNew(); void priceDelete(); void priceOnlineUpdate(); void startMatchTransaction(const MyMoneyTransaction& t); void cancelMatchTransaction(); /** * This signal is emitted when an account has been successfully reconciled * and all transactions are updated in the engine. It can be used by plugins * to create reconciliation reports. * * @param account the account data * @param date the reconciliation date as provided through the dialog * @param startingBalance the starting balance as provided through the dialog * @param endingBalance the ending balance as provided through the dialog * @param transactionList reference to QList of QPair containing all * transaction/split pairs processed by the reconciliation. */ void accountReconciled(const MyMoneyAccount& account, const QDate& date, const MyMoneyMoney& startingBalance, const MyMoneyMoney& endingBalance, const QList >& transactionList); public: - /** - * This method is implemented for convenience. It returns a dynamic_cast-ed - * pointer to an action found in actionCollection(). - * If the action with the name @p actionName is not found or the object - * is not of type KToggleAction, a pointer to a static non-configured - * KToggleAction object is returned and a warning is printed to stderr. - */ - KToggleAction* toggleAction(const QString& actionName) const; + bool isActionToggled(const Action _a); + static const QHash s_Actions; private: /// \internal d-pointer class. class Private; /* * Actually, one should write "Private * const d" but that confuses the KIDL * compiler in this context. It complains about the const keyword. So we leave * it out here */ /// \internal d-pointer instance. Private* d; }; extern KMyMoneyApp *kmymoney; class KMStatus { public: KMStatus(const QString &text); ~KMStatus(); private: QString m_prevText; }; #define KMSTATUS(msg) KMStatus _thisStatus(msg) #endif // KMYMONEY_H diff --git a/kmymoney/views/kaccountsview.cpp b/kmymoney/views/kaccountsview.cpp index dca3ae8c2..44a24be27 100644 --- a/kmymoney/views/kaccountsview.cpp +++ b/kmymoney/views/kaccountsview.cpp @@ -1,193 +1,192 @@ /*************************************************************************** kaccountsview.cpp ------------------- copyright : (C) 2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kaccountsview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include -#include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneyview.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "kmymoneyaccounttreeview.h" #include "models.h" #include using namespace Icons; KAccountsView::KAccountsView(QWidget *parent) : QWidget(parent) { setupUi(this); // setup icons for collapse and expand button KGuiItem collapseGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListCollapse]), QString(), QString()); KGuiItem expandGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListExpand]), QString(), QString()); KGuiItem::assign(m_collapseButton, collapseGuiItem); KGuiItem::assign(m_expandButton, expandGuiItem); connect(Models::instance()->accountsModel(), SIGNAL(netWorthChanged(MyMoneyMoney)), this, SLOT(slotNetWorthChanged(MyMoneyMoney))); // the proxy filter model m_filterProxyModel = new AccountsViewFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Equity); if (KMyMoneyGlobalSettings::showCategoriesInAccountsView()) { m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); } m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->setFilterKeyColumn(-1); connect(m_filterProxyModel, SIGNAL(unusedIncomeExpenseAccountHidden()), this, SLOT(slotUnusedIncomeExpenseAccountHidden())); m_accountTree->setModel(m_filterProxyModel); m_accountTree->setConfigGroupName("KAccountsView"); m_accountTree->setAlternatingRowColors(true); m_accountTree->setIconSize(QSize(22, 22)); m_accountTree->setSortingEnabled(true); connect(m_searchWidget, SIGNAL(textChanged(QString)), m_filterProxyModel, SLOT(setFilterFixedString(QString))); // let the model know if the item is expanded or collapsed connect(m_accountTree, SIGNAL(collapsed(QModelIndex)), m_filterProxyModel, SLOT(collapsed(QModelIndex))); connect(m_accountTree, SIGNAL(expanded(QModelIndex)), m_filterProxyModel, SLOT(expanded(QModelIndex))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), this, SIGNAL(selectObject(MyMoneyObject))); connect(m_accountTree, SIGNAL(openContextMenu(MyMoneyObject)), this, SIGNAL(openContextMenu(MyMoneyObject))); connect(m_accountTree, SIGNAL(openObject(MyMoneyObject)), this, SIGNAL(openObject(MyMoneyObject))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); // connect the two buttons to all required slots connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_collapseButton, SIGNAL(clicked()), m_accountTree, SLOT(collapseAll())); connect(m_accountTree, SIGNAL(collapsedAll()), m_filterProxyModel, SLOT(collapseAll())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), m_accountTree, SLOT(expandAll())); connect(m_accountTree, SIGNAL(expandedAll()), m_filterProxyModel, SLOT(expandAll())); } KAccountsView::~KAccountsView() { } void KAccountsView::slotExpandCollapse() { if (sender()) { KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); } } /** * The view is notified that an unused income expense account has been hidden. */ void KAccountsView::slotUnusedIncomeExpenseAccountHidden() { m_haveUnusedCategories = true; m_hiddenCategories->setVisible(m_haveUnusedCategories); } void KAccountsView::slotLoadAccounts() { // TODO: check why the invalidate is needed here m_filterProxyModel->invalidate(); - m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->toggleAction("view_show_all_accounts")->isChecked()); + m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->isActionToggled(Action::ViewShowAll)); m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); if (KMyMoneyGlobalSettings::showCategoriesInAccountsView()) { m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); } else { m_filterProxyModel->removeAccountType(MyMoneyAccount::Income); m_filterProxyModel->removeAccountType(MyMoneyAccount::Expense); } // reinitialize the default state of the hidden categories label m_haveUnusedCategories = false; m_hiddenCategories->hide(); - m_filterProxyModel->setHideUnusedIncomeExpenseAccounts(kmymoney->toggleAction("view_hide_unused_categories")->isChecked()); + m_filterProxyModel->setHideUnusedIncomeExpenseAccounts(kmymoney->isActionToggled(Action::ViewShowAll)); // and in case we need to show things expanded, we'll do so if (KMyMoneyGlobalSettings::showAccountsExpanded()) { m_filterProxyModel->expandAll(); m_accountTree->expandAll(); } } void KAccountsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) { QString s(i18n("Net Worth: ")); // FIXME figure out how to deal with the approximate // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) // s += "~ "; s.replace(QString(" "), QString(" ")); if (netWorth.isNegative()) { s += ""; } const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(netWorth, sec)); s += v.replace(QString(" "), QString(" ")); if (netWorth.isNegative()) { s += ""; } m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); m_totalProfitsLabel->setText(s); } void KAccountsView::slotOpenContextMenu(MyMoneyAccount account) { emit openContextMenu(account); } void KAccountsView::slotOpenObject(QListWidgetItem* item) { if (item) emit openObject((item->data(Qt::UserRole)).value()); } diff --git a/kmymoney/views/kbudgetview.cpp b/kmymoney/views/kbudgetview.cpp index a7e1a327c..0239a37f0 100644 --- a/kmymoney/views/kbudgetview.cpp +++ b/kmymoney/views/kbudgetview.cpp @@ -1,898 +1,899 @@ /*************************************************************************** kbudgetview.cpp --------------- begin : Thu Jan 10 2006 copyright : (C) 2006 by Darren Gould email : darren_gould@gmx.de Alvaro Soliverez ***************************************************************************/ /*************************************************************************** * * * 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 "kbudgetview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include "knewbudgetdlg.h" #include "kmymoney.h" #include "models.h" #include using namespace Icons; /** * @author Darren Gould * @author Thomas Baumgart * * This class represents an item in the budgets list view. */ class KBudgetListItem : public QTreeWidgetItem { public: /** * Constructor to be used to construct a budget entry object. * * @param parent pointer to the QTreeWidget object this entry should be * added to. * @param budget const reference to MyMoneyBudget for which * the QTreeWidget entry is constructed */ KBudgetListItem(QTreeWidget *parent, const MyMoneyBudget& budget); ~KBudgetListItem(); const MyMoneyBudget& budget() { return m_budget; }; void setBudget(const MyMoneyBudget& budget) { m_budget = budget; } private: MyMoneyBudget m_budget; }; // *** KBudgetListItem Implementation *** KBudgetListItem::KBudgetListItem(QTreeWidget *parent, const MyMoneyBudget& budget) : QTreeWidgetItem(parent), m_budget(budget) { setText(0, budget.name()); setText(1, QString("%1").arg(budget.budgetStart().year())); setFlags(flags() | Qt::ItemIsEditable); } KBudgetListItem::~KBudgetListItem() { } // *** KBudgetView Implementation *** //TODO: This has to go to user settings const int KBudgetView::m_iBudgetYearsAhead = 5; const int KBudgetView::m_iBudgetYearsBack = 3; BudgetAccountsProxyModel::BudgetAccountsProxyModel(QObject *parent/* = 0*/) : AccountsViewFilterProxyModel(parent) { addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Expense); } /** * This function was reimplemented to add the data needed by the other columns that this model * is adding besides the columns of the @ref AccountsModel. */ QVariant BudgetAccountsProxyModel::data(const QModelIndex &index, int role) const { if (!MyMoneyFile::instance()->storageAttached()) return QVariant(); QModelIndex normalizedIndex = BudgetAccountsProxyModel::index(index.row(), 0, index.parent()); if (role == AccountsModel::AccountBalanceRole || role == AccountsModel::AccountBalanceDisplayRole || role == AccountsModel::AccountValueRole || role == AccountsModel::AccountValueDisplayRole) { QVariant accountData = data(normalizedIndex, AccountsModel::AccountRole); if (accountData.canConvert()) { MyMoneyAccount account = accountData.value(); MyMoneyMoney balance = accountBalance(account.id()); MyMoneyMoney value = accountValue(account, balance); switch (role) { case AccountsModel::AccountBalanceRole: return QVariant::fromValue(balance); case AccountsModel::AccountBalanceDisplayRole: if (MyMoneyFile::instance()->security(account.currencyId()) != MyMoneyFile::instance()->baseCurrency()) { return MyMoneyUtils::formatMoney(balance, MyMoneyFile::instance()->security(account.currencyId())); } else { return QVariant(); } case AccountsModel::AccountValueRole: return QVariant::fromValue(value); case AccountsModel::AccountValueDisplayRole: return MyMoneyUtils::formatMoney(value, MyMoneyFile::instance()->baseCurrency()); } } } if (role == AccountsModel::AccountTotalValueRole || role == AccountsModel::AccountTotalValueDisplayRole) { MyMoneyMoney totalValue = computeTotalValue(mapToSource(normalizedIndex)); switch (role) { case AccountsModel::AccountTotalValueRole: return QVariant::fromValue(totalValue); case AccountsModel::AccountTotalValueDisplayRole: return MyMoneyUtils::formatMoney(totalValue, MyMoneyFile::instance()->baseCurrency()); } } return AccountsViewFilterProxyModel::data(index, role); } Qt::ItemFlags BudgetAccountsProxyModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = AccountsViewFilterProxyModel::flags(index); if (!index.parent().isValid()) return flags & ~Qt::ItemIsSelectable; // check if any of the parent accounts has the 'include subaccounts' // flag set. If so, we don't allow selecting this account QModelIndex idx = index.parent(); while (idx.isValid()) { QModelIndex source_idx = mapToSource(idx); QVariant accountData = sourceModel()->data(source_idx, AccountsModel::AccountRole); if (accountData.canConvert()) { MyMoneyAccount account = accountData.value(); // find out if the account is budgeted MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(account.id()); if (budgetAccount.id() == account.id()) { if (budgetAccount.budgetSubaccounts()) { return flags & ~Qt::ItemIsEnabled; } } } idx = idx.parent(); } return flags; } void BudgetAccountsProxyModel::setBudget(const MyMoneyBudget& budget) { m_budget = budget; invalidate(); checkBalance(); } bool BudgetAccountsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (hideUnusedIncomeExpenseAccounts()) { QModelIndex index = sourceModel()->index(source_row, 0, source_parent); QVariant accountData = sourceModel()->data(index, AccountsModel::AccountRole); if (accountData.canConvert()) { MyMoneyAccount account = accountData.value(); MyMoneyMoney balance; // find out if the account is budgeted MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(account.id()); if (budgetAccount.id() == account.id()) { balance = budgetAccount.balance(); switch (budgetAccount.budgetLevel()) { case MyMoneyBudget::AccountGroup::eMonthly: balance = balance * 12; break; default: break; } } if (!balance.isZero()) return AccountsFilterProxyModel::filterAcceptsRow(source_row, source_parent); } for (int i = 0; i < sourceModel()->rowCount(index); ++i) { if (filterAcceptsRow(i, index)) return AccountsFilterProxyModel::filterAcceptsRow(i, index); } return false; } return AccountsFilterProxyModel::filterAcceptsRow(source_row, source_parent); } bool BudgetAccountsProxyModel::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { if (source_column == AccountsModel::Tax || source_column == AccountsModel::VAT) return false; return AccountsFilterProxyModel::filterAcceptsColumn(source_column, source_parent); } MyMoneyMoney BudgetAccountsProxyModel::accountBalance(const QString &accountId) const { MyMoneyMoney balance; // find out if the account is budgeted MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(accountId); if (budgetAccount.id() == accountId) { balance = budgetAccount.balance(); switch (budgetAccount.budgetLevel()) { case MyMoneyBudget::AccountGroup::eMonthly: balance = balance * 12; break; default: break; } } return balance; } MyMoneyMoney BudgetAccountsProxyModel::accountValue(const MyMoneyAccount &account, const MyMoneyMoney &balance) const { return Models::instance()->accountsModel()->accountValue(account, balance); } MyMoneyMoney BudgetAccountsProxyModel::computeTotalValue(const QModelIndex &source_index) const { MyMoneyMoney totalValue; QVariant accountData = sourceModel()->data(source_index, AccountsModel::AccountRole); if (accountData.canConvert()) { MyMoneyAccount account = accountData.value(); totalValue = accountValue(account, accountBalance(account.id())); for (int i = 0; i < sourceModel()->rowCount(source_index); ++i) { totalValue += computeTotalValue(sourceModel()->index(i, 0, source_index)); } } return totalValue; } void BudgetAccountsProxyModel::checkBalance() { // compute the balance QModelIndexList incomeList = match(index(0, 0), AccountsModel::AccountIdRole, MyMoneyFile::instance()->income().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); QModelIndexList expenseList = match(index(0, 0), AccountsModel::AccountIdRole, MyMoneyFile::instance()->expense().id(), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive)); MyMoneyMoney balance; if (!incomeList.isEmpty() && !expenseList.isEmpty()) { QVariant incomeValue = data(incomeList.front(), AccountsModel::AccountTotalValueRole); QVariant expenseValue = data(expenseList.front(), AccountsModel::AccountTotalValueRole); if (incomeValue.isValid() && expenseValue.isValid()) { balance = incomeValue.value() - expenseValue.value(); } } if (m_lastBalance != balance) { m_lastBalance = balance; emit balanceChanged(m_lastBalance); } } KBudgetView::KBudgetView(QWidget *parent) : QWidget(parent), m_needReload(false), m_inSelection(false) { setupUi(this); m_budgetList->setRootIsDecorated(false); m_budgetList->setContextMenuPolicy(Qt::CustomContextMenu); KGuiItem newButtonItem(QString(), KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::ListAdd], Qt::TopRightCorner), i18n("Creates a new budget"), i18n("Use this to create a new empty budget.")); KGuiItem::assign(m_newButton, newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(), KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::DocumentEdit]), i18n("Rename the current selected budget"), i18n("Use this to start renaming the selected budget.")); KGuiItem::assign(m_renameButton, renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(), KMyMoneyUtils::overlayIcon(g_Icons[Icon::ViewTimeScheduleCalculus], g_Icons[Icon::EditDelete]), i18n("Delete the current selected budget"), i18n("Use this to delete the selected budget.")); KGuiItem::assign(m_deleteButton, deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem updateButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::DocumentSave]), i18n("Accepts the entered values and stores the budget"), i18n("Use this to store the modified data.")); KGuiItem::assign(m_updateButton, updateButtonItem); m_updateButton->setToolTip(updateButtonItem.toolTip()); KGuiItem resetButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::EditUndo]), i18n("Revert budget to last saved state"), i18n("Use this to discard the modified data.")); KGuiItem::assign(m_resetButton, resetButtonItem); m_resetButton->setToolTip(resetButtonItem.toolTip()); KGuiItem collapseGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListCollapse]), QString(), QString()); KGuiItem expandGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListExpand]), QString(), QString()); KGuiItem::assign(m_collapseButton, collapseGuiItem); KGuiItem::assign(m_expandButton, expandGuiItem); m_filterProxyModel = new BudgetAccountsProxyModel(this); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->setFilterKeyColumn(-1); m_accountTree->setModel(m_filterProxyModel); m_accountTree->setConfigGroupName("KBudgetsView"); m_accountTree->setAlternatingRowColors(true); m_accountTree->setIconSize(QSize(22, 22)); m_accountTree->setSortingEnabled(true); connect(m_filterProxyModel, SIGNAL(balanceChanged(MyMoneyMoney)), this, SLOT(slotBudgetBalanceChanged(MyMoneyMoney))); // let the model know if the item is expanded or collapsed connect(m_accountTree, SIGNAL(collapsed(QModelIndex)), m_filterProxyModel, SLOT(collapsed(QModelIndex))); connect(m_accountTree, SIGNAL(expanded(QModelIndex)), m_filterProxyModel, SLOT(expanded(QModelIndex))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), this, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectAccount(MyMoneyObject))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInstitution(MyMoneyObject))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), kmymoney, SLOT(slotSelectInvestment(MyMoneyObject))); connect(m_accountTree, SIGNAL(openContextMenu(MyMoneyObject)), kmymoney, SLOT(slotShowAccountContextMenu(MyMoneyObject))); connect(m_accountTree, SIGNAL(openObject(MyMoneyObject)), kmymoney, SLOT(slotAccountOpen(MyMoneyObject))); connect(m_budgetList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_budgetList->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(slotSelectBudget())); connect(m_budgetList, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(slotItemChanged(QTreeWidgetItem*,int))); connect(m_cbBudgetSubaccounts, SIGNAL(clicked()), this, SLOT(cb_includesSubaccounts_clicked())); // connect the buttons to the actions. Make sure the enabled state // of the actions is reflected by the buttons - connect(m_renameButton, SIGNAL(clicked()), kmymoney->action("budget_rename"), SLOT(trigger())); - connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("budget_delete"), SLOT(trigger())); + connect(m_renameButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename]), SLOT(trigger())); + connect(m_deleteButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete]), SLOT(trigger())); connect(m_budgetValue, SIGNAL(valuesChanged()), this, SLOT(slotBudgetedAmountChanged())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotNewBudget())); connect(m_updateButton, SIGNAL(pressed()), this, SLOT(slotUpdateBudget())); connect(m_resetButton, SIGNAL(pressed()), this, SLOT(slotResetBudget())); connect(m_hideUnusedButton, SIGNAL(toggled(bool)), this, SLOT(slotHideUnused(bool))); connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); // connect the two buttons to all required slots connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_collapseButton, SIGNAL(clicked()), m_accountTree, SLOT(collapseAll())); connect(m_accountTree, SIGNAL(collapsedAll()), m_filterProxyModel, SLOT(collapseAll())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), m_accountTree, SLOT(expandAll())); connect(m_accountTree, SIGNAL(expandedAll()), m_filterProxyModel, SLOT(expandAll())); connect(m_searchWidget, SIGNAL(textChanged(QString)), m_filterProxyModel, SLOT(setFilterFixedString(QString))); // setup initial state - m_newButton->setEnabled(kmymoney->action("budget_new")->isEnabled()); - m_renameButton->setEnabled(kmymoney->action("budget_rename")->isEnabled()); - m_deleteButton->setEnabled(kmymoney->action("budget_delete")->isEnabled()); + m_newButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetNew])->isEnabled()); + m_renameButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetRename])->isEnabled()); + m_deleteButton->setEnabled(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetDelete])->isEnabled()); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotRefreshView())); KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KBudgetViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); } KBudgetView::~KBudgetView() { // remember the splitter settings for startup KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KBudgetViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KBudgetView::showEvent(QShowEvent * event) { emit aboutToShow(); if (m_needReload) { slotRefreshView(); } QWidget::showEvent(event); } void KBudgetView::loadBudgets() { m_filterProxyModel->invalidate(); // remember which item is currently selected QString id = m_budget.id(); // clear the budget list m_budgetList->clear(); // add the correct years to the drop down list QDate date = QDate::currentDate(); int iStartYear = date.year() - m_iBudgetYearsBack; m_yearList.clear(); for (int i = 0; i < m_iBudgetYearsAhead + m_iBudgetYearsBack; i++) m_yearList += QString::number(iStartYear + i); KBudgetListItem* currentItem = 0; QList list = MyMoneyFile::instance()->budgetList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { KBudgetListItem* item = new KBudgetListItem(m_budgetList, *it); // create a list of unique years if (m_yearList.indexOf(QString::number((*it).budgetStart().year())) == -1) m_yearList += QString::number((*it).budgetStart().year()); //sort the list by name m_budgetList->sortItems(0, Qt::AscendingOrder); if (item->budget().id() == id) { m_budget = (*it); currentItem = item; item->setSelected(true); } } m_yearList.sort(); if (currentItem) { m_budgetList->setCurrentItem(currentItem); } // reset the status of the buttons m_updateButton->setEnabled(false); m_resetButton->setEnabled(false); // make sure the world around us knows what we have selected slotSelectBudget(); } void KBudgetView::ensureBudgetVisible(const QString& id) { QTreeWidgetItemIterator widgetIt = QTreeWidgetItemIterator(m_budgetList); while (*widgetIt) { KBudgetListItem* p = dynamic_cast(*widgetIt); if ((p)->budget().id() == id) { m_budgetList->scrollToItem((p), QAbstractItemView::PositionAtCenter); m_budgetList->setCurrentItem(p, 0, QItemSelectionModel::ClearAndSelect); // active item and deselect all others } } } void KBudgetView::slotRefreshView() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotRefreshView())); else { loadBudgets(); m_needReload = false; } } else { m_needReload = true; } } void KBudgetView::loadAccounts() { // if no budgets are selected, don't load the accounts // and clear out the previously shown list if (m_budget.id().isEmpty()) { m_budgetValue->clear(); m_updateButton->setEnabled(false); m_resetButton->setEnabled(false); return; } m_updateButton->setEnabled(!(selectedBudget() == m_budget)); m_resetButton->setEnabled(!(selectedBudget() == m_budget)); m_filterProxyModel->setBudget(m_budget); // and in case we need to show things expanded, we'll do so if (KMyMoneyGlobalSettings::showAccountsExpanded()) { m_accountTree->expandAll(); } } void KBudgetView::askSave() { // check if the content of a currently selected budget was modified // and ask to store the data if (m_updateButton->isEnabled()) { if (KMessageBox::questionYesNo(this, QString("%1").arg( i18n("Do you want to save the changes for %1?", m_budget.name())), i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdateBudget(); m_inSelection = false; } } } void KBudgetView::slotRefreshHideUnusedButton() { m_hideUnusedButton->setDisabled(m_budget.getaccounts().isEmpty()); } void KBudgetView::slotSelectBudget() { askSave(); KBudgetListItem* item; QTreeWidgetItemIterator widgetIt = QTreeWidgetItemIterator(m_budgetList); if (m_budget.id().isEmpty()) { item = dynamic_cast(*widgetIt); if (item) { m_budgetList->blockSignals(true); m_budgetList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect); m_budgetList->blockSignals(false); } } m_accountTree->setEnabled(false); m_assignmentBox->setEnabled(false); m_budget = MyMoneyBudget(); QTreeWidgetItemIterator it_l(m_budgetList, QTreeWidgetItemIterator::Selected); item = dynamic_cast(*it_l); if (item) { m_budget = item->budget(); m_accountTree->setEnabled(true); } slotRefreshHideUnusedButton(); loadAccounts(); QModelIndex index = m_accountTree->currentIndex(); if (index.isValid()) { MyMoneyAccount acc = m_accountTree->model()->data(index, AccountsModel::AccountRole).value(); slotSelectAccount(acc); } else { m_budgetValue->clear(); } QList budgetList; if (!m_budget.id().isEmpty()) budgetList << m_budget; emit selectObjects(budgetList); } void KBudgetView::slotHideUnused(bool toggled) { // make sure we show all items for an empty budget bool prevState = !toggled; slotRefreshHideUnusedButton(); if (prevState != m_hideUnusedButton->isChecked()) m_filterProxyModel->setHideUnusedIncomeExpenseAccounts(m_hideUnusedButton->isChecked()); } const MyMoneyBudget& KBudgetView::selectedBudget() const { static MyMoneyBudget nullBudget; QTreeWidgetItemIterator it_l(m_budgetList, QTreeWidgetItemIterator::Selected); KBudgetListItem* item = dynamic_cast(*it_l); if (item) { return item->budget(); } return nullBudget; } void KBudgetView::slotOpenContextMenu(const QPoint& p) { KBudgetListItem* item = dynamic_cast(m_budgetList->itemAt(p)); if (item) emit openContextMenu(item->budget()); else emit openContextMenu(MyMoneyBudget()); } void KBudgetView::slotStartRename() { QTreeWidgetItemIterator it_l(m_budgetList, QTreeWidgetItemIterator::Selected); QTreeWidgetItem* it_v; if ((it_v = *it_l) != 0) { m_budgetList->editItem(it_v, 0); } } void KBudgetView::slotItemChanged(QTreeWidgetItem* p, int col) { // if we don't have an item we actually don't care about it if (!p) return; KBudgetListItem *pBudget = dynamic_cast(p); if (col == 1) { pBudget->setText(1, QString().setNum(pBudget->budget().budgetStart().year())); return; } // create a copy of the new name without leading and trailing whitespaces QString new_name = p->text(0).trimmed(); if (pBudget->budget().name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a budget with the new name try { // this function call will throw an exception, if the budget // hasn't been found. MyMoneyFile::instance()->budgetByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A budget with the name '%1' already exists. It is not advisable to have " "multiple budgets with the same identification name. Are you sure you would like " "to rename the budget?", new_name)) != KMessageBox::Yes) { p->setText(0, pBudget->budget().name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } MyMoneyBudget b = pBudget->budget(); b.setName(new_name); // don't use pBudget beyond this point as it will change due to call to modifyBudget pBudget = 0; MyMoneyFile::instance()->modifyBudget(b); // the above call to modifyBudget will reload the view so // all references and pointers to the view have to be // re-established. You cannot use pBudget beyond this point!!! ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify budget"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { pBudget->setText(0, new_name); } } void KBudgetView::slotSelectAccount(const MyMoneyObject &obj) { m_assignmentBox->setEnabled(false); if (typeid(obj) != typeid(MyMoneyAccount)) return; const MyMoneyAccount& acc = dynamic_cast(obj); m_assignmentBox->setEnabled(true); if (m_budget.id().isEmpty()) return; QString id = acc.id(); m_leAccounts->setText(MyMoneyFile::instance()->accountToCategory(id)); m_cbBudgetSubaccounts->setChecked(m_budget.account(id).budgetSubaccounts()); m_accountTotal->setValue(m_budget.account(id).totalBalance()); MyMoneyBudget::AccountGroup budgetAccount = m_budget.account(id); if (id != budgetAccount.id()) { budgetAccount.setBudgetLevel(MyMoneyBudget::AccountGroup::eMonthly); } m_budgetValue->setBudgetValues(m_budget, budgetAccount); } void KBudgetView::slotExpandCollapse() { if (sender()) { KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); } } void KBudgetView::slotBudgetedAmountChanged() { if (m_budget.id().isEmpty()) return; QModelIndexList indexes = m_accountTree->selectionModel()->selectedIndexes(); if (indexes.empty()) return; QString accountID = indexes.front().data(AccountsModel::AccountIdRole).toString(); MyMoneyBudget::AccountGroup accountGroup = m_budget.account(accountID); accountGroup.setId(accountID); m_budgetValue->budgetValues(m_budget, accountGroup); m_budget.setAccount(accountGroup, accountID); m_filterProxyModel->setBudget(m_budget); m_accountTotal->setValue(accountGroup.totalBalance()); m_updateButton->setEnabled(!(selectedBudget() == m_budget)); m_resetButton->setEnabled(!(selectedBudget() == m_budget)); } void KBudgetView::AccountEnter() { if (m_budget.id().isEmpty()) return; } void KBudgetView::cb_includesSubaccounts_clicked() { if (m_budget.id().isEmpty()) return; QModelIndexList indexes = m_accountTree->selectionModel()->selectedIndexes(); if (!indexes.empty()) { QString accountID = indexes.front().data(AccountsModel::AccountIdRole).toString(); // now, we get a reference to the accountgroup, to modify its attribute, // and then put the resulting account group instead of the original MyMoneyBudget::AccountGroup auxAccount = m_budget.account(accountID); auxAccount.setBudgetSubaccounts(m_cbBudgetSubaccounts->isChecked()); // in case we turn the option on, we check that no subordinate account // has a budget. If we find some, we ask the user if he wants to move it // to the current account or leave things as they are if (m_cbBudgetSubaccounts->isChecked()) { // TODO: asking the user needs to be added. So long, we assume yes if (1) { MyMoneyBudget::AccountGroup subAccount; if (collectSubBudgets(subAccount, indexes.front())) { // we found a sub-budget somewhere // so we add those figures found and // clear the subaccounts auxAccount += subAccount; clearSubBudgets(indexes.front()); } if (auxAccount.budgetLevel() == MyMoneyBudget::AccountGroup::eNone) { MyMoneyBudget::PeriodGroup period; auxAccount.addPeriod(m_budget.budgetStart(), period); auxAccount.setBudgetLevel(MyMoneyBudget::AccountGroup::eMonthly); } } } m_budget.setAccount(auxAccount, accountID); m_filterProxyModel->setBudget(m_budget); m_budgetValue->setBudgetValues(m_budget, auxAccount); loadAccounts(); } } void KBudgetView::clearSubBudgets(const QModelIndex &index) { int children = m_accountTree->model()->rowCount(index); for (int i = 0; i < children; ++i) { QModelIndex childIdx = index.child(i, 0); QString accountID = childIdx.data(AccountsModel::AccountIdRole).toString(); m_budget.removeReference(accountID); clearSubBudgets(childIdx); } } bool KBudgetView::collectSubBudgets(MyMoneyBudget::AccountGroup &destination, const QModelIndex &index) const { bool rc = false; int children = m_accountTree->model()->rowCount(index); for (int i = 0; i < children; ++i) { QModelIndex childIdx = index.child(i, 0); QString accountID = childIdx.data(AccountsModel::AccountIdRole).toString(); MyMoneyBudget::AccountGroup auxAccount = m_budget.account(accountID); if (auxAccount.budgetLevel() != MyMoneyBudget::AccountGroup::eNone && !auxAccount.isZero()) { rc = true; // add the subaccount // TODO: deal with budgets in different currencies // https://bugs.kde.org/attachment.cgi?id=54813 contains a demo file destination += auxAccount; } rc |= collectSubBudgets(destination, childIdx); } return rc; } void KBudgetView::slotNewBudget() { askSave(); - kmymoney->action("budget_new")->trigger(); + kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::BudgetNew])->trigger(); } void KBudgetView::slotResetBudget() { try { m_budget = MyMoneyFile::instance()->budget(m_budget.id()); loadAccounts(); QModelIndex index = m_accountTree->currentIndex(); if (index.isValid()) { MyMoneyAccount acc = m_accountTree->model()->data(index, AccountsModel::AccountRole).value(); slotSelectAccount(acc); } else { m_budgetValue->clear(); } } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to reset budget"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } void KBudgetView::slotUpdateBudget() { MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyBudget(m_budget); ft.commit(); slotRefreshHideUnusedButton(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify budget"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } void KBudgetView::slotBudgetBalanceChanged(const MyMoneyMoney &balance) { QString s(i18nc("The balance of the selected budget", "Balance: ")); s.replace(QString(" "), QString(" ")); if (balance.isNegative()) { s += ""; } const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(balance, sec)); s += v.replace(QString(" "), QString(" ")); if (balance.isNegative()) { s += ""; } m_balanceLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); m_balanceLabel->setText(s); } diff --git a/kmymoney/views/kcategoriesview.cpp b/kmymoney/views/kcategoriesview.cpp index d7adc4b6a..ce4e63a34 100644 --- a/kmymoney/views/kcategoriesview.cpp +++ b/kmymoney/views/kcategoriesview.cpp @@ -1,191 +1,190 @@ /*************************************************************************** kcategoriesview.cpp - description ------------------- begin : Sun Jan 20 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kcategoriesview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include -#include // ---------------------------------------------------------------------------- // Project Includes #include #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include using namespace Icons; KCategoriesView::KCategoriesView(QWidget *parent) : QWidget(parent), m_needReload(false), m_haveUnusedCategories(false) { setupUi(this); // setup icons for collapse and expand button KGuiItem collapseGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListCollapse]), QString(), QString()); KGuiItem expandGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListExpand]), QString(), QString()); KGuiItem::assign(m_collapseButton, collapseGuiItem); KGuiItem::assign(m_expandButton, expandGuiItem); connect(Models::instance()->accountsModel(), SIGNAL(profitChanged(MyMoneyMoney)), this, SLOT(slotProfitChanged(MyMoneyMoney))); // the proxy filter model m_filterProxyModel = new AccountsViewFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->setFilterKeyColumn(-1); connect(m_filterProxyModel, SIGNAL(unusedIncomeExpenseAccountHidden()), this, SLOT(slotUnusedIncomeExpenseAccountHidden())); m_accountTree->setModel(m_filterProxyModel); m_accountTree->setConfigGroupName("KCategoriesView"); m_accountTree->setAlternatingRowColors(true); m_accountTree->setIconSize(QSize(22, 22)); m_accountTree->setSortingEnabled(true); connect(m_searchWidget, SIGNAL(textChanged(QString)), m_filterProxyModel, SLOT(setFilterFixedString(QString))); // let the model know if the item is expanded or collapsed connect(m_accountTree, SIGNAL(collapsed(QModelIndex)), m_filterProxyModel, SLOT(collapsed(QModelIndex))); connect(m_accountTree, SIGNAL(expanded(QModelIndex)), m_filterProxyModel, SLOT(expanded(QModelIndex))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), this, SIGNAL(selectObject(MyMoneyObject))); connect(m_accountTree, SIGNAL(openContextMenu(MyMoneyObject)), this, SIGNAL(openContextMenu(MyMoneyObject))); connect(m_accountTree, SIGNAL(openObject(MyMoneyObject)), this, SIGNAL(openObject(MyMoneyObject))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); // connect the two buttons to all required slots connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_collapseButton, SIGNAL(clicked()), m_accountTree, SLOT(collapseAll())); connect(m_accountTree, SIGNAL(collapsedAll()), m_filterProxyModel, SLOT(collapseAll())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), m_accountTree, SLOT(expandAll())); connect(m_accountTree, SIGNAL(expandedAll()), m_filterProxyModel, SLOT(expandAll())); } KCategoriesView::~KCategoriesView() { } void KCategoriesView::slotExpandCollapse() { if (sender()) { KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); } } void KCategoriesView::showEvent(QShowEvent * event) { emit aboutToShow(); if (m_needReload) { loadAccounts(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); } void KCategoriesView::slotLoadAccounts() { if (isVisible()) { loadAccounts(); } else { m_needReload = true; } } void KCategoriesView::loadAccounts() { m_filterProxyModel->invalidate(); - m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->toggleAction("view_show_all_accounts")->isChecked()); + m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->isActionToggled(Action::ViewShowAll)); // reinitialize the default state of the hidden categories label m_haveUnusedCategories = false; m_hiddenCategories->hide(); - m_filterProxyModel->setHideUnusedIncomeExpenseAccounts(kmymoney->toggleAction("view_hide_unused_categories")->isChecked()); + m_filterProxyModel->setHideUnusedIncomeExpenseAccounts(kmymoney->isActionToggled(Action::ViewHideCategories)); // and in case we need to show things expanded, we'll do so if (KMyMoneyGlobalSettings::showAccountsExpanded()) { m_accountTree->expandAll(); } } /** * The view is notified that an unused income expense account has been hidden. */ void KCategoriesView::slotUnusedIncomeExpenseAccountHidden() { m_haveUnusedCategories = true; m_hiddenCategories->setVisible(m_haveUnusedCategories); } void KCategoriesView::slotProfitChanged(const MyMoneyMoney &profit) { QString s(i18n("Profit: ")); if (profit.isNegative()) s = i18n("Loss: "); // FIXME figure out how to deal with the approximate // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) // s += "~ "; s.replace(QString(" "), QString(" ")); if (profit.isNegative()) { s += ""; } const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(profit.abs(), sec)); s += v.replace(QString(" "), QString(" ")); if (profit.isNegative()) { s += ""; } m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); m_totalProfitsLabel->setText(s); } diff --git a/kmymoney/views/kgloballedgerview.cpp b/kmymoney/views/kgloballedgerview.cpp index 2b4a2f5e1..d8a3d4acf 100644 --- a/kmymoney/views/kgloballedgerview.cpp +++ b/kmymoney/views/kgloballedgerview.cpp @@ -1,1604 +1,1604 @@ /*************************************************************************** kgloballedgerview.cpp - description ------------------- begin : Wed Jul 26 2006 copyright : (C) 2006 by Thomas Baumgart email : 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 "kgloballedgerview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include -#include +#include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyaccount.h" #include "mymoneyfile.h" #include "kmymoneyaccountcombo.h" #include "kmymoneytitlelabel.h" #include "register.h" #include "transactioneditor.h" #include "selectedtransaction.h" #include "kmymoneyglobalsettings.h" #include "registersearchline.h" #include "kfindtransactiondlg.h" #include "kmymoney.h" #include "scheduledtransaction.h" #include "models.h" class KGlobalLedgerView::Private { public: Private(); // used to store the id of an item and the id of an immeadiate unselected sibling void storeId(KMyMoneyRegister::RegisterItem *item, QString &id, QString &backupId) { if (item) { // the id of the item id = item->id(); // the id of the item's previous/next unselected item for (KMyMoneyRegister::RegisterItem *it = item->prevItem(); it != 0 && backupId.isEmpty(); it = it->prevItem()) { if (!it->isSelected()) { backupId = it->id(); } } // if we didn't found previous unselected items search trough the next items for (KMyMoneyRegister::RegisterItem *it = item->nextItem(); it != 0 && backupId.isEmpty(); it = it->nextItem()) { if (!it->isSelected()) { backupId = it->id(); } } } } // use to match an item by it's id or a backup id which has a lower precedence void matchItemById(KMyMoneyRegister::RegisterItem **item, KMyMoneyRegister::Transaction* t, QString &id, QString &backupId) { if (!backupId.isEmpty() && t->id() == backupId) *item = t; if (!id.isEmpty() && t->id() == id) { // we found the real thing there's no need for the backup anymore backupId.clear(); *item = t; } } MousePressFilter* m_mousePressFilter; KMyMoneyRegister::RegisterSearchLineWidget* m_registerSearchLine; QString m_reconciliationAccount; QDate m_reconciliationDate; MyMoneyMoney m_endingBalance; int m_precision; bool m_recursion; bool m_showDetails; KMyMoneyRegister::Action m_action; // models AccountNamesFilterProxyModel *m_filterProxyModel; // widgets KMyMoneyAccountCombo* m_accountComboBox; MyMoneyMoney m_totalBalance; bool m_balanceIsApproximated; }; MousePressFilter::MousePressFilter(QWidget* parent) : QObject(parent), m_lastMousePressEvent(0), m_filterActive(true) { } void MousePressFilter::addWidget(QWidget* w) { m_parents.append(w); } void MousePressFilter::setFilterActive(bool state) { m_filterActive = state; } bool MousePressFilter::isChildOf(QWidget* child, QWidget *parent) { while (child) { if (child == parent) return true; // If one of the ancestors is a KPassivePopup or a KDialog or a popup widget then // it's as if it is a child of our own because these widgets could // appear during transaction entry (message boxes, completer widgets) if (dynamic_cast(child) || dynamic_cast(child) || ((child->windowFlags() & Qt::Popup) && child != kmymoney)) return true; child = child->parentWidget(); } return false; } bool MousePressFilter::eventFilter(QObject* o, QEvent* e) { if (m_filterActive) { if (e->type() == QEvent::MouseButtonPress && !m_lastMousePressEvent) { QList::const_iterator it_w; for (it_w = m_parents.constBegin(); it_w != m_parents.constEnd(); ++it_w) { if (isChildOf((QWidget*)o, (*it_w))) { m_lastMousePressEvent = e; break; } } if (it_w == m_parents.constEnd()) { m_lastMousePressEvent = e; bool rc = false; emit mousePressedOnExternalWidget(rc); } } if (e->type() != QEvent::MouseButtonPress) { m_lastMousePressEvent = 0; } } return false; } KGlobalLedgerView::Private::Private() : m_mousePressFilter(0), m_registerSearchLine(0), m_recursion(false), m_showDetails(false), m_filterProxyModel(0), m_accountComboBox(0), m_balanceIsApproximated(false) { } QDate KGlobalLedgerView::m_lastPostDate; KGlobalLedgerView::KGlobalLedgerView(QWidget *parent, const char *name) : KMyMoneyViewBase(parent, name, i18n("Ledgers")), d(new Private), m_needReload(false), m_newAccountLoaded(true), m_inEditMode(false) { d->m_mousePressFilter = new MousePressFilter((QWidget*)this); d->m_action = KMyMoneyRegister::ActionNone; // the proxy filter model d->m_filterProxyModel = new AccountNamesFilterProxyModel(this); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); d->m_filterProxyModel->addAccountGroup(MyMoneyAccount::Equity); d->m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); d->m_filterProxyModel->sort(0); // create the toolbar frame at the top of the view m_toolbarFrame = new QFrame(this); QHBoxLayout* toolbarLayout = new QHBoxLayout(m_toolbarFrame); toolbarLayout->setContentsMargins(0, 0, 0, 0); toolbarLayout->setSpacing(6); // the account selector widget d->m_accountComboBox = new KMyMoneyAccountCombo(d->m_filterProxyModel, m_toolbarFrame); toolbarLayout->addWidget(d->m_accountComboBox); layout()->addWidget(m_toolbarFrame); toolbarLayout->setStretchFactor(d->m_accountComboBox, 60); // create the register frame m_registerFrame = new QFrame(this); QVBoxLayout* registerFrameLayout = new QVBoxLayout(m_registerFrame); registerFrameLayout->setContentsMargins(0, 0, 0, 0); registerFrameLayout->setSpacing(0); layout()->addWidget(m_registerFrame); layout()->setStretchFactor(m_registerFrame, 2); m_register = new KMyMoneyRegister::Register(m_registerFrame); m_register->setUsedWithEditor(true); registerFrameLayout->addWidget(m_register); m_register->installEventFilter(this); connect(m_register, SIGNAL(openContextMenu()), this, SIGNAL(openContextMenu())); connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotUpdateSummaryLine(KMyMoneyRegister::SelectedTransactions))); connect(m_register->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotSortOptions())); connect(m_register, SIGNAL(reconcileStateColumnClicked(KMyMoneyRegister::Transaction*)), this, SLOT(slotToggleTransactionMark(KMyMoneyRegister::Transaction*))); // insert search line widget d->m_registerSearchLine = new KMyMoneyRegister::RegisterSearchLineWidget(m_register, m_toolbarFrame); toolbarLayout->addWidget(d->m_registerSearchLine); toolbarLayout->setStretchFactor(d->m_registerSearchLine, 100); // create the summary frame m_summaryFrame = new QFrame(this); QHBoxLayout* summaryFrameLayout = new QHBoxLayout(m_summaryFrame); summaryFrameLayout->setContentsMargins(0, 0, 0, 0); summaryFrameLayout->setSpacing(0); m_leftSummaryLabel = new QLabel(m_summaryFrame); m_centerSummaryLabel = new QLabel(m_summaryFrame); m_rightSummaryLabel = new QLabel(m_summaryFrame); summaryFrameLayout->addWidget(m_leftSummaryLabel); QSpacerItem* spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); summaryFrameLayout->addItem(spacer); summaryFrameLayout->addWidget(m_centerSummaryLabel); spacer = new QSpacerItem(20, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); summaryFrameLayout->addItem(spacer); summaryFrameLayout->addWidget(m_rightSummaryLabel); layout()->addWidget(m_summaryFrame); // create the button frame m_buttonFrame = new QFrame(this); QVBoxLayout* buttonLayout = new QVBoxLayout(m_buttonFrame); buttonLayout->setContentsMargins(0, 0, 0, 0); buttonLayout->setSpacing(0); layout()->addWidget(m_buttonFrame); m_buttonbar = new KToolBar(m_buttonFrame, 0, true); m_buttonbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); buttonLayout->addWidget(m_buttonbar); - m_buttonbar->addAction(kmymoney->action("transaction_new")); - m_buttonbar->addAction(kmymoney->action("transaction_delete")); - m_buttonbar->addAction(kmymoney->action("transaction_edit")); - m_buttonbar->addAction(kmymoney->action("transaction_enter")); - m_buttonbar->addAction(kmymoney->action("transaction_cancel")); - m_buttonbar->addAction(kmymoney->action("transaction_accept")); - m_buttonbar->addAction(kmymoney->action("transaction_match")); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionNew])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionDelete])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEdit])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionCancel])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionAccept])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionMatch])); // create the transaction form frame m_formFrame = new QFrame(this); QVBoxLayout* frameLayout = new QVBoxLayout(m_formFrame); frameLayout->setContentsMargins(5, 5, 5, 5); frameLayout->setSpacing(0); m_form = new KMyMoneyTransactionForm::TransactionForm(m_formFrame); frameLayout->addWidget(m_form->tabBar(m_formFrame)); frameLayout->addWidget(m_form); m_formFrame->setFrameShape(QFrame::Panel); m_formFrame->setFrameShadow(QFrame::Raised); layout()->addWidget(m_formFrame); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadView())); connect(m_register, SIGNAL(focusChanged(KMyMoneyRegister::Transaction*)), m_form, SLOT(slotSetTransaction(KMyMoneyRegister::Transaction*))); connect(m_register, SIGNAL(focusChanged()), kmymoney, SLOT(slotUpdateActions())); connect(d->m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(slotSelectAccount(QString))); connect(m_register, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions)), this, SIGNAL(transactionsSelected(KMyMoneyRegister::SelectedTransactions))); connect(m_register, SIGNAL(editTransaction()), this, SIGNAL(startEdit())); connect(m_register, SIGNAL(emptyItemSelected()), this, SLOT(slotNewTransaction())); connect(m_register, SIGNAL(aboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&)), this, SLOT(slotAboutToSelectItem(KMyMoneyRegister::RegisterItem*,bool&))); // TODO: port to KF5 - the current implementation is no longer working //connect(d->m_mousePressFilter, SIGNAL(mousePressedOnExternalWidget(bool&)), this, SIGNAL(cancelOrEndEdit(bool&))); connect(m_form, SIGNAL(newTransaction(KMyMoneyRegister::Action)), this, SLOT(slotNewTransaction(KMyMoneyRegister::Action))); // setup mouse press filter d->m_mousePressFilter->addWidget(m_formFrame); d->m_mousePressFilter->addWidget(m_buttonFrame); d->m_mousePressFilter->addWidget(m_summaryFrame); d->m_mousePressFilter->addWidget(m_registerFrame); m_tooltipPosn = QPoint(); } KGlobalLedgerView::~KGlobalLedgerView() { delete d; } void KGlobalLedgerView::slotAboutToSelectItem(KMyMoneyRegister::RegisterItem* item, bool& okToSelect) { Q_UNUSED(item); emit cancelOrEndEdit(okToSelect); } void KGlobalLedgerView::slotLoadView() { m_needReload = true; if (isVisible()) { if (!m_inEditMode) { setUpdatesEnabled(false); loadView(); setUpdatesEnabled(true); m_needReload = false; // force a new account if the current one is empty m_newAccountLoaded = m_account.id().isEmpty(); } } } void KGlobalLedgerView::clear() { // clear current register contents m_register->clear(); // setup header font QFont font = KMyMoneyGlobalSettings::listHeaderFont(); QFontMetrics fm(font); int height = fm.lineSpacing() + 6; m_register->horizontalHeader()->setMinimumHeight(height); m_register->horizontalHeader()->setMaximumHeight(height); m_register->horizontalHeader()->setFont(font); // setup cell font font = KMyMoneyGlobalSettings::listCellFont(); m_register->setFont(font); // clear the form m_form->clear(); // the selected transactions list m_transactionList.clear(); // and the selected account in the combo box d->m_accountComboBox->setSelected(QString()); // fraction defaults to two digits d->m_precision = 2; } void KGlobalLedgerView::loadView() { MYMONEYTRACER(tracer); // setup form visibility m_formFrame->setVisible(KMyMoneyGlobalSettings::transactionForm()); // no account selected emit accountSelected(MyMoneyAccount()); // no transaction selected KMyMoneyRegister::SelectedTransactions list; emit transactionsSelected(list); QMap isSelected; QString focusItemId; QString backUpFocusItemId; // in case the focus item is removed QString anchorItemId; QString backUpAnchorItemId; // in case the anchor item is removed if (!m_newAccountLoaded) { // remember the current selected transactions KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); for (; item; item = item->nextItem()) { if (item->isSelected()) { isSelected[item->id()] = true; } } // remember the item that has the focus d->storeId(m_register->focusItem(), focusItemId, backUpFocusItemId); // and the one that has the selection anchor d->storeId(m_register->anchorItem(), anchorItemId, backUpAnchorItemId); } else { d->m_registerSearchLine->searchLine()->reset(); } // clear the current contents ... clear(); // ... load the combobox widget and select current account ... loadAccounts(); // ... setup the register columns ... m_register->setupRegister(m_account); // ... setup the form ... m_form->setupForm(m_account); if (m_account.id().isEmpty()) { // if we don't have an account we bail out setEnabled(false); return; } setEnabled(true); m_register->setUpdatesEnabled(false); // ... and recreate it KMyMoneyRegister::RegisterItem* focusItem = 0; KMyMoneyRegister::RegisterItem* anchorItem = 0; QMap actBalance, clearedBalance, futureBalance; QMap::iterator it_b; try { // setup the filter to select the transactions we want to display // and update the sort order QString sortOrder; QString key; QDate reconciliationDate = d->m_reconciliationDate; MyMoneyTransactionFilter filter(m_account.id()); // if it's an investment account, we also take care of // the sub-accounts (stock accounts) if (m_account.accountType() == MyMoneyAccount::Investment) filter.addAccount(m_account.accountList()); if (isReconciliationAccount()) { key = "kmm-sort-reconcile"; sortOrder = KMyMoneyGlobalSettings::sortReconcileView(); filter.addState(MyMoneyTransactionFilter::notReconciled); filter.addState(MyMoneyTransactionFilter::cleared); } else { filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); key = "kmm-sort-std"; sortOrder = KMyMoneyGlobalSettings::sortNormalView(); if (KMyMoneyGlobalSettings::hideReconciledTransactions() && !m_account.isIncomeExpense()) { filter.addState(MyMoneyTransactionFilter::notReconciled); filter.addState(MyMoneyTransactionFilter::cleared); } } filter.setReportAllSplits(true); // check if we have an account override of the sort order if (!m_account.value(key).isEmpty()) sortOrder = m_account.value(key); // setup sort order m_register->setSortOrder(sortOrder); // retrieve the list from the engine MyMoneyFile::instance()->transactionList(m_transactionList, filter); kmymoney->slotStatusProgressBar(0, m_transactionList.count()); // create the elements for the register QList >::const_iterator it; QMapuniqueMap; int i = 0; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Transaction* t = KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); actBalance[t->split().accountId()] = MyMoneyMoney(); kmymoney->slotStatusProgressBar(++i, 0); // if we're in reconciliation and the state is cleared, we // force the item to show in dimmed intensity to get a visual focus // on those items, that we need to work on if (isReconciliationAccount() && (*it).second.reconcileFlag() == MyMoneySplit::Cleared) { t->setReducedIntensity(true); } } // create dummy entries for the scheduled transactions if sorted by postdate int period = KMyMoneyGlobalSettings::schedulePreview(); if (m_register->primarySortKey() == KMyMoneyRegister::PostDateSort) { // show scheduled transactions which have a scheduled postdate // within the next 'period' days. In reconciliation mode, the // period starts on the statement date. QDate endDate = QDate::currentDate().addDays(period); if (isReconciliationAccount()) endDate = reconciliationDate.addDays(period); QList scheduleList = MyMoneyFile::instance()->scheduleList(m_account.id()); while (scheduleList.count() > 0) { MyMoneySchedule& s = scheduleList.first(); for (;;) { if (s.isFinished() || s.adjustedNextDueDate() > endDate) { break; } MyMoneyTransaction t(s.id(), KMyMoneyUtils::scheduledTransaction(s)); // if the transaction is scheduled and overdue, it can't // certainly be posted in the past. So we take today's date // as the alternative if (s.isOverdue()) { t.setPostDate(s.adjustedDate(QDate::currentDate(), s.weekendOption())); } else { t.setPostDate(s.adjustedNextDueDate()); } const QList& splits = t.splits(); QList::const_iterator it_s; for (it_s = splits.begin(); it_s != splits.end(); ++it_s) { if ((*it_s).accountId() == m_account.id()) { new KMyMoneyRegister::StdTransactionScheduled(m_register, t, *it_s, uniqueMap[t.id()]); } } // keep track of this payment locally (not in the engine) if (s.isOverdue()) { s.setLastPayment(QDate::currentDate()); } else { s.setLastPayment(s.nextDueDate()); } // if this is a one time schedule, we can bail out here as we're done if (s.occurrence() == MyMoneySchedule::OCCUR_ONCE) break; // for all others, we check if the next payment date is still 'in range' QDate nextDueDate = s.nextPayment(s.nextDueDate()); if (nextDueDate.isValid()) { s.setNextDueDate(nextDueDate); } else { break; } } scheduleList.pop_front(); } } // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); // add special markers for reconciliation now so that they do not get // removed by m_register->removeUnwantedGroupMarkers(). Needs resorting // of items but that's ok. KMyMoneyRegister::StatementGroupMarker* statement = 0; KMyMoneyRegister::StatementGroupMarker* dStatement = 0; KMyMoneyRegister::StatementGroupMarker* pStatement = 0; if (isReconciliationAccount()) { switch (m_register->primarySortKey()) { case KMyMoneyRegister::PostDateSort: statement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Details")); m_register->sortItems(); break; case KMyMoneyRegister::TypeSort: dStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Deposit, reconciliationDate, i18n("Statement Deposit Details")); pStatement = new KMyMoneyRegister::StatementGroupMarker(m_register, KMyMoneyRegister::Payment, reconciliationDate, i18n("Statement Payment Details")); m_register->sortItems(); break; default: break; } } // we need at least the balance for the account we currently show actBalance[m_account.id()] = MyMoneyMoney(); if (m_account.accountType() == MyMoneyAccount::Investment) { QList::const_iterator it_a; for (it_a = m_account.accountList().begin(); it_a != m_account.accountList().end(); ++it_a) { actBalance[*it_a] = MyMoneyMoney(); } } // determine balances (actual, cleared). We do this by getting the actual // balance of all entered transactions from the engine and walk the list // of transactions backward. Also re-select a transaction if it was // selected before and setup the focus item. MyMoneyMoney factor(1, 1); if (m_account.accountGroup() == MyMoneyAccount::Liability || m_account.accountGroup() == MyMoneyAccount::Equity) factor = -factor; QMap deposits; QMap payments; QMap depositAmount; QMap paymentAmount; for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { MyMoneyMoney balance = MyMoneyFile::instance()->balance(it_b.key()); balance = balance * factor; clearedBalance[it_b.key()] = futureBalance[it_b.key()] = (*it_b) = balance; deposits[it_b.key()] = payments[it_b.key()] = 0; depositAmount[it_b.key()] = MyMoneyMoney(); paymentAmount[it_b.key()] = MyMoneyMoney(); } tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2))); tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2))); tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2))); KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); focusItem = 0; // take care of possibly trailing scheduled transactions (bump up the future balance) while (p) { if (p->isSelectable()) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t && t->isScheduled()) { MyMoneyMoney balance = futureBalance[t->split().accountId()]; const MyMoneySplit& split = t->split(); // if this split is a stock split, we can't just add the amount of shares if (t->transaction().isStockSplit()) { balance = balance * split.shares(); } else { balance += split.shares() * factor; } futureBalance[split.accountId()] = balance; } else if (t && !focusItem) focusItem = p; } p = p->prevItem(); } p = m_register->lastItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (isSelected.contains(t->id())) t->setSelected(true); d->matchItemById(&focusItem, t, focusItemId, backUpFocusItemId); d->matchItemById(&anchorItem, t, anchorItemId, backUpAnchorItemId); const MyMoneySplit& split = t->split(); MyMoneyMoney balance = futureBalance[split.accountId()]; t->setBalance(balance); // if this split is a stock split, we can't just add the amount of shares if (t->transaction().isStockSplit()) { balance /= split.shares(); } else { balance -= split.shares() * factor; } if (!t->isScheduled()) { if (isReconciliationAccount() && t->transaction().postDate() <= reconciliationDate && split.reconcileFlag() == MyMoneySplit::Cleared) { if (split.shares().isNegative()) { payments[split.accountId()]++; paymentAmount[split.accountId()] += split.shares(); } else { deposits[split.accountId()]++; depositAmount[split.accountId()] += split.shares(); } } if (t->transaction().postDate() > QDate::currentDate()) { tracer.printf("Reducing actual balance by %s because %s/%s(%s) is in the future", qPrintable((split.shares() * factor).formatMoney("", 2)), qPrintable(t->transaction().id()), qPrintable(split.id()), qPrintable(t->transaction().postDate().toString(Qt::ISODate))); actBalance[split.accountId()] -= split.shares() * factor; } } futureBalance[split.accountId()] = balance; } p = p->prevItem(); } clearedBalance[m_account.id()] = MyMoneyFile::instance()->clearedBalance(m_account.id(), reconciliationDate); tracer.printf("total balance of %s = %s", qPrintable(m_account.name()), qPrintable(actBalance[m_account.id()].formatMoney("", 2))); tracer.printf("future balance of %s = %s", qPrintable(m_account.name()), qPrintable(futureBalance[m_account.id()].formatMoney("", 2))); tracer.printf("cleared balance of %s = %s", qPrintable(m_account.name()), qPrintable(clearedBalance[m_account.id()].formatMoney("", 2))); // update statement information if (statement) { const QString aboutDeposits = i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_account.id()], depositAmount[m_account.id()].abs().formatMoney(m_account.fraction())); const QString aboutPayments = i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_account.id()], paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction())); statement->setText(i18nc("%1 is a string, e.g. 7 deposits; %2 is a string, e.g. 4 payments", "%1, %2", aboutDeposits, aboutPayments)); } if (pStatement) { pStatement->setText(i18np("%1 payment (%2)", "%1 payments (%2)", payments[m_account.id()] , paymentAmount[m_account.id()].abs().formatMoney(m_account.fraction()))); } if (dStatement) { dStatement->setText(i18np("%1 deposit (%2)", "%1 deposits (%2)", deposits[m_account.id()] , depositAmount[m_account.id()].abs().formatMoney(m_account.fraction()))); } // add a last empty entry for new transactions // leave some information about the current account MyMoneySplit split; split.setReconcileFlag(MyMoneySplit::NotReconciled); // make sure to use the value specified in the option during reconciliation if (isReconciliationAccount()) split.setReconcileFlag(static_cast(KMyMoneyGlobalSettings::defaultReconciliationState())); KMyMoneyRegister::Register::transactionFactory(m_register, MyMoneyTransaction(), split, 0); m_register->updateRegister(true); if (focusItem) { // in case we have some selected items we just set the focus item // in other cases, we make the focusitem also the selected item if (anchorItem && (anchorItem != focusItem)) { m_register->setFocusItem(focusItem); m_register->setAnchorItem(anchorItem); } else m_register->selectItem(focusItem, true); } else { // just use the empty line at the end if nothing else exists in the ledger p = m_register->lastItem(); m_register->setFocusItem(p); m_register->selectItem(p); focusItem = p; } updateSummaryLine(actBalance, clearedBalance); kmymoney->slotStatusProgressBar(-1, -1); } catch (const MyMoneyException &) { m_account = MyMoneyAccount(); clear(); } d->m_showDetails = KMyMoneyGlobalSettings::showRegisterDetailed(); // and tell everyone what's selected emit accountSelected(m_account); KMyMoneyRegister::SelectedTransactions actualSelection(m_register); emit transactionsSelected(actualSelection); } void KGlobalLedgerView::updateSummaryLine(const QMap& actBalance, const QMap& clearedBalance) { MyMoneyFile* file = MyMoneyFile::instance(); m_leftSummaryLabel->show(); m_centerSummaryLabel->show(); m_rightSummaryLabel->show(); if (isReconciliationAccount()) { if (m_account.accountType() != MyMoneyAccount::Investment) { m_leftSummaryLabel->setText(i18n("Statement: %1", d->m_endingBalance.formatMoney("", d->m_precision))); m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision))); d->m_totalBalance = clearedBalance[m_account.id()] - d->m_endingBalance; } } else { // update summary line in normal mode QDate reconcileDate = m_account.lastReconciliationDate(); if (reconcileDate.isValid()) { m_leftSummaryLabel->setText(i18n("Last reconciled: %1", QLocale().toString(reconcileDate, QLocale::ShortFormat))); } else { m_leftSummaryLabel->setText(i18n("Never reconciled")); } QPalette palette = m_rightSummaryLabel->palette(); palette.setColor(m_rightSummaryLabel->foregroundRole(), m_leftSummaryLabel->palette().color(foregroundRole())); if (m_account.accountType() != MyMoneyAccount::Investment) { m_centerSummaryLabel->setText(i18nc("Cleared balance", "Cleared: %1", clearedBalance[m_account.id()].formatMoney("", d->m_precision))); d->m_totalBalance = actBalance[m_account.id()]; } else { m_centerSummaryLabel->hide(); MyMoneyMoney balance; MyMoneySecurity base = file->baseCurrency(); QMap::const_iterator it_b; // reset the approximated flag d->m_balanceIsApproximated = false; for (it_b = actBalance.begin(); it_b != actBalance.end(); ++it_b) { MyMoneyAccount stock = file->account(it_b.key()); QString currencyId = stock.currencyId(); MyMoneySecurity sec = file->security(currencyId); MyMoneyMoney rate(1, 1); if (stock.isInvest()) { currencyId = sec.tradingCurrency(); const MyMoneyPrice &priceInfo = file->price(sec.id(), currencyId); d->m_balanceIsApproximated |= !priceInfo.isValid(); rate = priceInfo.rate(sec.tradingCurrency()); } if (currencyId != base.id()) { const MyMoneyPrice &priceInfo = file->price(sec.tradingCurrency(), base.id()); d->m_balanceIsApproximated |= !priceInfo.isValid(); rate = (rate * priceInfo.rate(base.id())).convertPrecision(sec.pricePrecision()); } balance += ((*it_b) * rate).convert(base.smallestAccountFraction()); } d->m_totalBalance = balance; } m_rightSummaryLabel->setPalette(palette); } // determine the number of selected transactions KMyMoneyRegister::SelectedTransactions selection; m_register->selectedTransactions(selection); slotUpdateSummaryLine(selection); } void KGlobalLedgerView::slotUpdateSummaryLine(const KMyMoneyRegister::SelectedTransactions& selection) { if (selection.count() > 1) { MyMoneyMoney balance; foreach (const KMyMoneyRegister::SelectedTransaction& t, selection) { if (!t.isScheduled()) { balance += t.split().shares(); } } m_rightSummaryLabel->setText(QString("%1: %2").arg(QChar(0x2211), balance.formatMoney("", d->m_precision))); } else { if (isReconciliationAccount()) { m_rightSummaryLabel->setText(i18n("Difference: %1", d->m_totalBalance.formatMoney("", d->m_precision))); } else { if (m_account.accountType() != MyMoneyAccount::Investment) { m_rightSummaryLabel->setText(i18n("Balance: %1", d->m_totalBalance.formatMoney("", d->m_precision))); bool showNegative = d->m_totalBalance.isNegative(); if (m_account.accountGroup() == MyMoneyAccount::Liability && !d->m_totalBalance.isZero()) showNegative = !showNegative; if (showNegative) { QPalette palette = m_rightSummaryLabel->palette(); palette.setColor(m_rightSummaryLabel->foregroundRole(), KMyMoneyGlobalSettings::listNegativeValueColor()); m_rightSummaryLabel->setPalette(palette); } } else { m_rightSummaryLabel->setText(i18n("Investment value: %1%2", d->m_balanceIsApproximated ? "~" : "", d->m_totalBalance.formatMoney(MyMoneyFile::instance()->baseCurrency().tradingSymbol(), d->m_precision))); } } } } void KGlobalLedgerView::resizeEvent(QResizeEvent* ev) { m_register->resize(KMyMoneyRegister::DetailColumn); m_form->resize(KMyMoneyTransactionForm::ValueColumn1); KMyMoneyViewBase::resizeEvent(ev); } void KGlobalLedgerView::loadAccounts() { MyMoneyFile* file = MyMoneyFile::instance(); // check if the current account still exists and make it the // current account if (!m_account.id().isEmpty()) { try { m_account = file->account(m_account.id()); } catch (const MyMoneyException &) { m_account = MyMoneyAccount(); return; } } // TODO: check why the invalidate is needed here d->m_filterProxyModel->invalidate(); d->m_filterProxyModel->sort(AccountsModel::Account); - d->m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->toggleAction("view_show_all_accounts")->isChecked()); + d->m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->isActionToggled(Action::ViewShowAll)); d->m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); d->m_accountComboBox->expandAll(); if (m_account.id().isEmpty()) { // find the first favorite account QModelIndexList list = Models::instance()->accountsModel()->match(Models::instance()->accountsModel()->index(0, 0), AccountsModel::AccountFavoriteRole, QVariant(true), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); if (list.count() > 0) { QVariant accountId = list.front().data(AccountsModel::AccountIdRole); if (accountId.isValid()) { m_account = file->account(accountId.toString()); } } if (m_account.id().isEmpty()) { // there are no favorite accounts find any account QModelIndexList list = Models::instance()->accountsModel()->match(Models::instance()->accountsModel()->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.isInvest()) { m_account = a; break; } } } } } if (!m_account.id().isEmpty()) { d->m_accountComboBox->setSelected(m_account.id()); try { d->m_precision = MyMoneyMoney::denomToPrec(m_account.fraction()); } catch (const MyMoneyException &) { qDebug("Security %s for account %s not found", qPrintable(m_account.currencyId()), qPrintable(m_account.name())); d->m_precision = 2; } } } void KGlobalLedgerView::selectTransaction(const QString& id) { if (!id.isEmpty()) { KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (t->transaction().id() == id) { m_register->selectItem(t); m_register->ensureItemVisible(t); break; } } p = p->prevItem(); } } } void KGlobalLedgerView::slotSelectAllTransactions() { m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = m_register->firstItem(); while (p) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) { if (t->isVisible() && t->isSelectable() && !t->isScheduled() && !t->id().isEmpty()) { t->setSelected(true); } } p = p->nextItem(); } // this is here only to re-paint the items without selecting anything because the data (including the selection) is not really held in the model right now m_register->selectAll(); // inform everyone else about the selected items KMyMoneyRegister::SelectedTransactions list(m_register); emit transactionsSelected(list); } void KGlobalLedgerView::slotSetReconcileAccount(const MyMoneyAccount& acc, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance) { if (d->m_reconciliationAccount != acc.id()) { // make sure the account is selected if (!acc.id().isEmpty()) slotSelectAccount(acc.id()); d->m_reconciliationAccount = acc.id(); d->m_reconciliationDate = reconciliationDate; d->m_endingBalance = endingBalance; if (acc.accountGroup() == MyMoneyAccount::Liability) d->m_endingBalance = -endingBalance; m_newAccountLoaded = true; if (acc.id().isEmpty()) { - m_buttonbar->removeAction(kmymoney->action("account_reconcile_postpone")); - m_buttonbar->removeAction(kmymoney->action("account_reconcile_finish")); + m_buttonbar->removeAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountPostponeReconciliation])); + m_buttonbar->removeAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountFinishReconciliation])); } else { - m_buttonbar->addAction(kmymoney->action("account_reconcile_postpone")); - m_buttonbar->addAction(kmymoney->action("account_reconcile_finish")); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountPostponeReconciliation])); + m_buttonbar->addAction(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountFinishReconciliation])); // when we start reconciliation, we need to reload the view // because no data has been changed. When postponing or finishing // reconciliation, the data change in the engine takes care of updateing // the view. slotLoadView(); } } } bool KGlobalLedgerView::isReconciliationAccount() const { return m_account.id() == d->m_reconciliationAccount; } bool KGlobalLedgerView::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return false; if (d->m_recursion) return false; d->m_recursion = true; const MyMoneyAccount& acc = dynamic_cast(obj); bool rc = slotSelectAccount(acc.id()); d->m_recursion = false; return rc; } bool KGlobalLedgerView::slotSelectAccount(const QString& id, const QString& transactionId) { bool rc = true; if (!id.isEmpty()) { if (m_account.id() != id) { try { m_account = MyMoneyFile::instance()->account(id); // if a stock account is selected, we show the // the corresponding parent (investment) account if (m_account.isInvest()) { m_account = MyMoneyFile::instance()->account(m_account.parentAccountId()); } m_newAccountLoaded = true; slotLoadView(); } catch (const MyMoneyException &) { qDebug("Unable to retrieve account %s", qPrintable(id)); rc = false; } } else { // we need to refresh m_account.m_accountList, a child could have been deleted m_account = MyMoneyFile::instance()->account(id); emit accountSelected(m_account); } selectTransaction(transactionId); } return rc; } void KGlobalLedgerView::slotNewTransaction(KMyMoneyRegister::Action id) { if (!m_inEditMode) { d->m_action = id; emit newTransaction(); } } void KGlobalLedgerView::slotNewTransaction() { slotNewTransaction(KMyMoneyRegister::ActionNone); } void KGlobalLedgerView::setupDefaultAction() { switch (m_account.accountType()) { case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: case MyMoneyAccount::Savings: d->m_action = KMyMoneyRegister::ActionDeposit; break; default: d->m_action = KMyMoneyRegister::ActionWithdrawal; break; } } bool KGlobalLedgerView::selectEmptyTransaction() { bool rc = false; if (!m_inEditMode) { // in case we don't know the type of transaction to be created, // have at least one selected transaction and the id of // this transaction is not empty, we take it as template for the // transaction to be created KMyMoneyRegister::SelectedTransactions list(m_register); if ((d->m_action == KMyMoneyRegister::ActionNone) && (!list.isEmpty()) && (!list[0].transaction().id().isEmpty())) { // the new transaction to be created will have the same type // as the one that currently has the focus KMyMoneyRegister::Transaction* t = dynamic_cast(m_register->focusItem()); if (t) d->m_action = t->actionType(); m_register->clearSelection(); } // if we still don't have an idea which type of transaction // to create, we use the default. if (d->m_action == KMyMoneyRegister::ActionNone) { setupDefaultAction(); } m_register->selectItem(m_register->lastItem()); m_register->updateRegister(); rc = true; } return rc; } TransactionEditor* KGlobalLedgerView::startEdit(const KMyMoneyRegister::SelectedTransactions& list) { // we use the warnlevel to keep track, if we have to warn the // user that some or all splits have been reconciled or if the // user cannot modify the transaction if at least one split // has the status frozen. The following value are used: // // 0 - no sweat, user can modify // 1 - user should be warned that at least one split has been reconciled // already // 2 - user will be informed, that this transaction cannot be changed anymore int warnLevel = list.warnLevel(); Q_ASSERT(warnLevel < 2); // otherwise the edit action should not be enabled switch (warnLevel) { case 0: break; case 1: if (KMessageBox::warningContinueCancel(0, i18n( "At least one split of the selected transactions has been reconciled. " "Do you wish to continue to edit the transactions anyway?" ), i18n("Transaction already reconciled"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "EditReconciledTransaction") == KMessageBox::Cancel) { warnLevel = 2; } break; case 2: KMessageBox::sorry(0, i18n("At least one split of the selected transactions has been frozen. " "Editing the transactions is therefore prohibited."), i18n("Transaction already frozen")); break; case 3: KMessageBox::sorry(0, i18n("At least one split of the selected transaction references an account that has been closed. " "Editing the transactions is therefore prohibited."), i18n("Account closed")); break; } if (warnLevel > 1) return 0; TransactionEditor* editor = 0; KMyMoneyRegister::Transaction* item = dynamic_cast(m_register->focusItem()); if (item) { // in case the current focus item is not selected, we move the focus to the first selected transaction if (!item->isSelected()) { KMyMoneyRegister::RegisterItem* p; for (p = m_register->firstItem(); p; p = p->nextItem()) { KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t && t->isSelected()) { m_register->setFocusItem(t); item = t; break; } } } // decide, if we edit in the register or in the form TransactionEditorContainer* parent; if (m_formFrame->isVisible()) parent = m_form; else { parent = m_register; } editor = item->createEditor(parent, list, m_lastPostDate); // check that we use the same transaction commodity in all selected transactions // if not, we need to update this in the editor's list. The user can also bail out // of this operation which means that we have to stop editing here. if (editor) { if (!editor->fixTransactionCommodity(m_account)) { // if the user wants to quit, we need to destroy the editor // and bail out delete editor; editor = 0; } } if (editor) { if (parent == m_register) { // make sure, the height of the table is correct m_register->updateRegister(KMyMoneyGlobalSettings::ledgerLens() | !KMyMoneyGlobalSettings::transactionForm()); } m_inEditMode = true; - connect(editor, SIGNAL(transactionDataSufficient(bool)), kmymoney->action("transaction_enter"), SLOT(setEnabled(bool))); - connect(editor, SIGNAL(returnPressed()), kmymoney->action("transaction_enter"), SLOT(trigger())); - connect(editor, SIGNAL(escapePressed()), kmymoney->action("transaction_cancel"), SLOT(trigger())); + connect(editor, SIGNAL(transactionDataSufficient(bool)), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter]), SLOT(setEnabled(bool))); + connect(editor, SIGNAL(returnPressed()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionEnter]), SLOT(trigger())); + connect(editor, SIGNAL(escapePressed()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TransactionCancel]), SLOT(trigger())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), editor, SLOT(slotReloadEditWidgets())); connect(editor, SIGNAL(finishEdit(KMyMoneyRegister::SelectedTransactions)), this, SLOT(slotLeaveEditMode(KMyMoneyRegister::SelectedTransactions))); connect(editor, SIGNAL(objectCreation(bool)), d->m_mousePressFilter, SLOT(setFilterDeactive(bool))); connect(editor, SIGNAL(createPayee(QString,QString&)), kmymoney, SLOT(slotPayeeNew(QString,QString&))); connect(editor, SIGNAL(createTag(QString,QString&)), kmymoney, SLOT(slotTagNew(QString,QString&))); connect(editor, SIGNAL(createCategory(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotCategoryNew(MyMoneyAccount&,MyMoneyAccount))); connect(editor, SIGNAL(createSecurity(MyMoneyAccount&,MyMoneyAccount)), kmymoney, SLOT(slotInvestmentNew(MyMoneyAccount&,MyMoneyAccount))); connect(editor, SIGNAL(assignNumber()), kmymoney, SLOT(slotTransactionAssignNumber())); connect(editor, SIGNAL(lastPostDateUsed(QDate)), this, SLOT(slotKeepPostDate(QDate))); // create the widgets, place them in the parent and load them with data // setup tab order m_tabOrderWidgets.clear(); editor->setup(m_tabOrderWidgets, m_account, d->m_action); Q_ASSERT(!m_tabOrderWidgets.isEmpty()); // install event filter in all taborder widgets QWidgetList::const_iterator it_w = m_tabOrderWidgets.constBegin(); for (; it_w != m_tabOrderWidgets.constEnd(); ++it_w) { (*it_w)->installEventFilter(this); } // Install a filter that checks if a mouse press happened outside // of one of our own widgets. qApp->installEventFilter(d->m_mousePressFilter); // Check if the editor has some preference on where to set the focus // If not, set the focus to the first widget in the tab order QWidget* focusWidget = editor->firstWidget(); if (!focusWidget) focusWidget = m_tabOrderWidgets.first(); // for some reason, this only works reliably if delayed a bit QTimer::singleShot(10, focusWidget, SLOT(setFocus())); // preset to 'I have no idea which type to create' for the next round. d->m_action = KMyMoneyRegister::ActionNone; } } return editor; } void KGlobalLedgerView::slotLeaveEditMode(const KMyMoneyRegister::SelectedTransactions& list) { m_inEditMode = false; qApp->removeEventFilter(d->m_mousePressFilter); // a possible focusOut event may have removed the focus, so we // install it back again. m_register->focusItem()->setFocus(true); // if we come back from editing a new item, we make sure that // we always select the very last known transaction entry no // matter if the transaction has been created or not. if (list.count() && list[0].transaction().id().isEmpty()) { // block signals to prevent some infinite loops that might occur here. m_register->blockSignals(true); m_register->clearSelection(); KMyMoneyRegister::RegisterItem* p = m_register->lastItem(); if (p && p->prevItem()) p = p->prevItem(); m_register->selectItem(p); m_register->updateRegister(true); m_register->blockSignals(false); // we need to update the form manually as sending signals was blocked KMyMoneyRegister::Transaction* t = dynamic_cast(p); if (t) m_form->slotSetTransaction(t); } else { if (!KMyMoneySettings::transactionForm()) { // update the row height of the transactions because it might differ between viewing/editing mode when not using the transaction form m_register->blockSignals(true); m_register->updateRegister(true); m_register->blockSignals(false); } } if (m_needReload) slotLoadView(); m_register->setFocus(); } bool KGlobalLedgerView::focusNextPrevChild(bool next) { bool rc = false; // qDebug("KGlobalLedgerView::focusNextPrevChild(editmode=%s)", m_inEditMode ? "true" : "false"); if (m_inEditMode) { QWidget *w = 0; w = qApp->focusWidget(); // qDebug("w = %p", w); int currentWidgetIndex = m_tabOrderWidgets.indexOf(w); while (w && currentWidgetIndex == -1) { // qDebug("'%s' not in list, use parent", qPrintable(w->objectName())); w = w->parentWidget(); currentWidgetIndex = m_tabOrderWidgets.indexOf(w); } if (currentWidgetIndex != -1) { // if(w) qDebug("tab order is at '%s'", qPrintable(w->objectName())); currentWidgetIndex += next ? 1 : -1; if (currentWidgetIndex < 0) currentWidgetIndex = m_tabOrderWidgets.size() - 1; else if (currentWidgetIndex >= m_tabOrderWidgets.size()) currentWidgetIndex = 0; w = m_tabOrderWidgets[currentWidgetIndex]; // qDebug("currentWidgetIndex = %d, w = %p", currentWidgetIndex, w); if (((w->focusPolicy() & Qt::TabFocus) == Qt::TabFocus) && w->isVisible() && w->isEnabled()) { // qDebug("Selecting '%s' (%p) as focus", qPrintable(w->objectName()), w); w->setFocus(); rc = true; } } } else rc = KMyMoneyViewBase::focusNextPrevChild(next); return rc; } void KGlobalLedgerView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { if (!m_inEditMode) { setUpdatesEnabled(false); loadView(); setUpdatesEnabled(true); m_needReload = false; m_newAccountLoaded = false; } } else { emit accountSelected(m_account); KMyMoneyRegister::SelectedTransactions list(m_register); emit transactionsSelected(list); } // don't forget base class implementation KMyMoneyViewBase::showEvent(event); } bool KGlobalLedgerView::eventFilter(QObject* o, QEvent* e) { bool rc = false; // Need to capture mouse position here as QEvent::ToolTip is too slow m_tooltipPosn = QCursor::pos(); if (e->type() == QEvent::KeyPress) { if (m_inEditMode) { // qDebug("object = %s, key = %d", o->className(), k->key()); if (o == m_register) { // we hide all key press events from the register // while editing a transaction rc = true; } } } if (!rc) rc = KMyMoneyViewBase::eventFilter(o, e); return rc; } void KGlobalLedgerView::showTooltip(const QString msg) const { QToolTip::showText(m_tooltipPosn, msg); } void KGlobalLedgerView::slotSortOptions() { QPointer dlg = new KSortOptionDlg(this); QString key; QString sortOrder, def; if (isReconciliationAccount()) { key = "kmm-sort-reconcile"; def = KMyMoneyGlobalSettings::sortReconcileView(); } else { key = "kmm-sort-std"; def = KMyMoneyGlobalSettings::sortNormalView(); } // check if we have an account override of the sort order if (!m_account.value(key).isEmpty()) sortOrder = m_account.value(key); QString oldOrder = sortOrder; dlg->setSortOption(sortOrder, def); if (dlg->exec() == QDialog::Accepted) { if (dlg != 0) { sortOrder = dlg->sortOption(); if (sortOrder != oldOrder) { if (sortOrder.isEmpty()) { m_account.deletePair(key); } else { m_account.setValue(key, sortOrder); } MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyAccount(m_account); ft.commit(); } catch (const MyMoneyException &e) { qDebug("Unable to update sort order for account '%s': %s", qPrintable(m_account.name()), qPrintable(e.what())); } } } } delete dlg; } void KGlobalLedgerView::slotToggleTransactionMark(KMyMoneyRegister::Transaction* /* t */) { if (!m_inEditMode) { emit toggleReconciliationFlag(); } } void KGlobalLedgerView::slotKeepPostDate(const QDate& date) { m_lastPostDate = date; } bool KGlobalLedgerView::canCreateTransactions(QString& tooltip) const { bool rc = true; if (m_account.id().isEmpty()) { tooltip = i18n("Cannot create transactions when no account is selected."); rc = false; } if (m_account.accountGroup() == MyMoneyAccount::Income || m_account.accountGroup() == MyMoneyAccount::Expense) { tooltip = i18n("Cannot create transactions in the context of a category."); showTooltip(tooltip); rc = false; } if (m_account.isClosed()) { tooltip = i18n("Cannot create transactions in a closed account."); showTooltip(tooltip); rc = false; } return rc; } bool KGlobalLedgerView::canProcessTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { MyMoneyAccount acc; QString closedAccount; if (m_register->focusItem() == 0) return false; bool rc = true; if (list.warnLevel() == 3) { //Closed account somewhere KMyMoneyRegister::SelectedTransactions::const_iterator it_t; for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { QList splitList = (*it_t).transaction().splits(); QString id = splitList.first().accountId(); acc = MyMoneyFile::instance()->account(id); if (!acc.isClosed()) { //wrong split, try other id = splitList.last().accountId(); acc = MyMoneyFile::instance()->account(id); } closedAccount = acc.name(); break; } tooltip = i18n("Cannot process transactions in account %1, which is closed.", closedAccount); showTooltip(tooltip); return false; } if (!m_register->focusItem()->isSelected()) { tooltip = i18n("Cannot process transaction with focus if it is not selected."); showTooltip(tooltip); return false; } tooltip.clear(); return !list.isEmpty(); } bool KGlobalLedgerView::canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { return canProcessTransactions(list, tooltip) && list.canModify(); } bool KGlobalLedgerView::canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { return canProcessTransactions(list, tooltip) && list.canDuplicate(); } bool KGlobalLedgerView::canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const { // check if we can edit the list of transactions. We can edit, if // // a) no mix of standard and investment transactions exist // b) if a split transaction is selected, this is the only selection // c) none of the splits is frozen // d) the transaction having the current focus is selected // check for d) if (!canProcessTransactions(list, tooltip)) return false; // check for c) if (list.warnLevel() == 2) { tooltip = i18n("Cannot edit transactions with frozen splits."); showTooltip(tooltip); return false; } bool rc = true; int investmentTransactions = 0; int normalTransactions = 0; if (m_account.accountGroup() == MyMoneyAccount::Income || m_account.accountGroup() == MyMoneyAccount::Expense) { tooltip = i18n("Cannot edit transactions in the context of a category."); showTooltip(tooltip); rc = false; } if (m_account.isClosed()) { tooltip = i18n("Cannot create or edit any transactions in Account %1 as it is closed", m_account.name()); showTooltip(tooltip); rc = false; } KMyMoneyRegister::SelectedTransactions::const_iterator it_t; QString action; for (it_t = list.begin(); rc && it_t != list.end(); ++it_t) { if ((*it_t).transaction().id().isEmpty()) { tooltip.clear(); rc = false; continue; } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) { if (action.isEmpty()) { action = (*it_t).split().action(); continue; } if (action == (*it_t).split().action()) { continue; } else { tooltip = (i18n("Cannot edit mixed investment action/type transactions together.")); showTooltip(tooltip); rc = false; break; } } if (KMyMoneyUtils::transactionType((*it_t).transaction()) == KMyMoneyUtils::InvestmentTransaction) ++investmentTransactions; else ++normalTransactions; // check for a) if (investmentTransactions != 0 && normalTransactions != 0) { tooltip = i18n("Cannot edit investment transactions and non-investment transactions together."); showTooltip(tooltip); rc = false; break; } // check for b) but only for normalTransactions if ((*it_t).transaction().splitCount() > 2 && normalTransactions != 0) { if (list.count() > 1) { tooltip = i18n("Cannot edit multiple split transactions at once."); showTooltip(tooltip); rc = false; break; } } } // check for multiple transactions being selected in an investment account // we do not allow editing in this case: https://bugs.kde.org/show_bug.cgi?id=240816 // later on, we might allow to edit investment transactions of the same type /// Can now disable the following check. /* if (rc == true && investmentTransactions > 1) { tooltip = i18n("Cannot edit multiple investment transactions at once"); rc = false; }*/ // now check that we have the correct account type for investment transactions if (rc == true && investmentTransactions != 0) { if (m_account.accountType() != MyMoneyAccount::Investment) { tooltip = i18n("Cannot edit investment transactions in the context of this account."); rc = false; } } return rc; } diff --git a/kmymoney/views/khomeview.cpp b/kmymoney/views/khomeview.cpp index 7d61354a1..281f509dc 100644 --- a/kmymoney/views/khomeview.cpp +++ b/kmymoney/views/khomeview.cpp @@ -1,2027 +1,2026 @@ /*************************************************************************** khomeview.cpp - description ------------------- begin : Tue Jan 22 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "khomeview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include #include #include #include -#include #include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyutils.h" #include "kwelcomepage.h" #include "kmymoneyglobalsettings.h" #include "mymoneyfile.h" #include "mymoneyforecast.h" #include "kmymoney.h" #include "kreportchartview.h" #include "pivottable.h" #include "pivotgrid.h" #include "reportaccount.h" #include #define VIEW_LEDGER "ledger" #define VIEW_SCHEDULE "schedule" #define VIEW_WELCOME "welcome" #define VIEW_HOME "home" #define VIEW_REPORTS "reports" using namespace Icons; bool accountNameLess(const MyMoneyAccount &acc1, const MyMoneyAccount &acc2) { return acc1.name().localeAwareCompare(acc2.name()) < 0; } using namespace reports; class KHomeView::Private { public: Private() : m_showAllSchedules(false), m_needReload(false), m_netWorthGraphLastValidSize(400, 300) { } /** * daily balances of an account */ typedef QMap dailyBalances; KHTMLPart* m_part; QString m_html; bool m_showAllSchedules; bool m_needReload; MyMoneyForecast m_forecast; MyMoneyMoney m_total; /** * Hold the last valid size of the net worth graph * for the times when the needed size can't be computed. */ QSize m_netWorthGraphLastValidSize; /** * daily forecast balance of accounts */ QMap m_accountList; }; /** * @brief Converts a QPixmap to an data URI scheme * * According to RFC 2397 * * @param pixmap Source to convert * @return full data URI */ QString QPixmapToDataUri(const QPixmap& pixmap) { QImage image(pixmap.toImage()); QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer return QLatin1String("data:image/png;base64,") + QString(byteArray.toBase64()); } KHomeView::KHomeView(QWidget *parent, const char *name) : KMyMoneyViewBase(parent, name, i18n("Home")), d(new Private) { d->m_part = new KHTMLPart(this); d->m_part->setOnlyLocalReferences(true); addWidget(d->m_part->view()); d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); // we are going to handle the zoom view signal to change the font scale connect(d->m_part->view(), SIGNAL(zoomView(int)), this, SLOT(slotZoomView(int))); disconnect(d->m_part->view(), SIGNAL(zoomView(int)), d->m_part, SLOT(slotZoomView(int))); connect(d->m_part->browserExtension(), SIGNAL(openUrlRequest(const QUrl &, const KParts::OpenUrlArguments &, const KParts::BrowserArguments &)), this, SLOT(slotOpenUrl(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments))); } KHomeView::~KHomeView() { // if user wants to remember the font size, store it here if (KMyMoneyGlobalSettings::rememberFontSize()) { KMyMoneyGlobalSettings::setFontSizePercentage(d->m_part->fontScaleFactor()); KMyMoneyGlobalSettings::self()->save(); } //This is to prevent a crash on exit with KDE 4.3.2 delete d->m_part; delete d; } void KHomeView::slotLoadView() { d->m_needReload = true; if (isVisible()) { loadView(); d->m_needReload = false; } } void KHomeView::showEvent(QShowEvent* event) { emit aboutToShow(); if (d->m_needReload) { loadView(); d->m_needReload = false; } QWidget::showEvent(event); } void KHomeView::slotPrintView() { if (d->m_part && d->m_part->view()) d->m_part->view()->print(); } void KHomeView::slotZoomView(int delta) { const int fontScaleStepping = 10; if (delta > 0) d->m_part->setFontScaleFactor(d->m_part->fontScaleFactor() + fontScaleStepping); else d->m_part->setFontScaleFactor(d->m_part->fontScaleFactor() - fontScaleStepping); } void KHomeView::loadView() { d->m_part->setFontScaleFactor(KMyMoneyGlobalSettings::fontSizePercentage()); QList list; MyMoneyFile::instance()->accountList(list); if (list.count() == 0) { d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); } else { //clear the forecast flag so it will be reloaded d->m_forecast.setForecastDone(false); const QString filename = QStandardPaths::locate(QStandardPaths::DataLocation, "html/kmymoney.css"); QString header = QString("\n\n").arg(QUrl::fromLocalFile(filename).url()); header += KMyMoneyUtils::variableCSS(); header += "\n"; QString footer = "\n"; d->m_html.clear(); d->m_html += header; d->m_html += QString("
%1
").arg(i18n("Your Financial Summary")); QStringList settings = KMyMoneyGlobalSettings::itemList(); QStringList::ConstIterator it; for (it = settings.constBegin(); it != settings.constEnd(); ++it) { int option = (*it).toInt(); if (option > 0) { switch (option) { case 1: // payments showPayments(); break; case 2: // preferred accounts showAccounts(Preferred, i18n("Preferred Accounts")); break; case 3: // payment accounts // Check if preferred accounts are shown separately if (settings.contains("2")) { showAccounts(static_cast(Payment | Preferred), i18n("Payment Accounts")); } else { showAccounts(Payment, i18n("Payment Accounts")); } break; case 4: // favorite reports showFavoriteReports(); break; case 5: // forecast showForecast(); break; case 6: // net worth graph over all accounts showNetWorthGraph(); break; case 8: // assets and liabilities showAssetsLiabilities(); break; case 9: // budget showBudget(); break; case 10: // cash flow summary showCashFlowSummary(); break; } d->m_html += "
 
\n"; } } d->m_html += "
"; d->m_html += link(VIEW_WELCOME, QString()) + i18n("Show KMyMoney welcome page") + linkend(); d->m_html += "
"; d->m_html += "
"; d->m_html += footer; d->m_part->begin(); d->m_part->write(d->m_html); d->m_part->end(); } } void KHomeView::showNetWorthGraph() { d->m_html += QString("
%1
\n
 
\n").arg(i18n("Net Worth Forecast")); MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eAssetLiability, MyMoneyReport::eMonths, MyMoneyTransactionFilter::userDefined, // overridden by the setDateFilter() call below MyMoneyReport::eDetailTotal, i18n("Net Worth Forecast"), i18n("Generated Report")); reportCfg.setChartByDefault(true); reportCfg.setChartCHGridLines(false); reportCfg.setChartSVGridLines(false); reportCfg.setChartDataLabels(false); reportCfg.setChartType(MyMoneyReport::eChartLine); reportCfg.setIncludingSchedules(false); reportCfg.addAccountGroup(MyMoneyAccount::Asset); reportCfg.addAccountGroup(MyMoneyAccount::Liability); reportCfg.setColumnsAreDays(true); reportCfg.setConvertCurrency(true); reportCfg.setIncludingForecast(true); reportCfg.setDateFilter(QDate::currentDate(), QDate::currentDate().addDays(+ 90)); reports::PivotTable table(reportCfg); reports::KReportChartView* chartWidget = new reports::KReportChartView(0); table.drawChart(*chartWidget); // Adjust the size QSize netWorthGraphSize = KHomeView::size(); netWorthGraphSize -= QSize(80, 30); // consider the computed size valid only if it's smaller on both axes that the applications size if (netWorthGraphSize.width() < kmymoney->width() || netWorthGraphSize.height() < kmymoney->height()) { d->m_netWorthGraphLastValidSize = netWorthGraphSize; } chartWidget->resize(d->m_netWorthGraphLastValidSize); //save the chart to an image QString chart = QPixmapToDataUri(QPixmap::grabWidget(chartWidget->coordinatePlane()->parent())); d->m_html += QString(""); d->m_html += QString(""); d->m_html += QString("").arg(chart); d->m_html += QString(""); d->m_html += QString("
\"Networth\"
"); //delete the widget since we no longer need it delete chartWidget; } void KHomeView::showPayments() { MyMoneyFile* file = MyMoneyFile::instance(); QList overdues; QList schedule; int i = 0; //if forecast has not been executed yet, do it. if (!d->m_forecast.isForecastDone()) doForecast(); schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate::currentDate(), QDate::currentDate().addMonths(1)); overdues = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), QDate(), true); if (schedule.empty() && overdues.empty()) return; // HACK // Remove the finished schedules QList::Iterator d_it; //regular schedules d_it = schedule.begin(); while (d_it != schedule.end()) { if ((*d_it).isFinished()) { d_it = schedule.erase(d_it); continue; } ++d_it; } //overdue schedules d_it = overdues.begin(); while (d_it != overdues.end()) { if ((*d_it).isFinished()) { d_it = overdues.erase(d_it); continue; } ++d_it; } d->m_html += "
"; d->m_html += QString("
%1
\n").arg(i18n("Payments")); if (!overdues.isEmpty()) { d->m_html += "
 
\n"; qSort(overdues); QList::Iterator it; QList::Iterator it_f; d->m_html += ""; d->m_html += QString("\n").arg(showColoredAmount(i18n("Overdue payments"), true)); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; for (it = overdues.begin(); it != overdues.end(); ++it) { // determine number of overdue payments int cnt = (*it).transactionsRemainingUntil(QDate::currentDate().addDays(-1)); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it, cnt); d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; } if (!schedule.isEmpty()) { qSort(schedule); // Extract todays payments if any QList todays; QList::Iterator t_it; for (t_it = schedule.begin(); t_it != schedule.end();) { if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { todays.append(*t_it); (*t_it).setNextDueDate((*t_it).nextPayment(QDate::currentDate())); // if adjustedNextDueDate is still currentDate then remove it from // scheduled payments if ((*t_it).adjustedNextDueDate() == QDate::currentDate()) { t_it = schedule.erase(t_it); continue; } } ++t_it; } if (todays.count() > 0) { d->m_html += "
 
\n"; d->m_html += ""; d->m_html += QString("\n").arg(i18n("Today's due payments")); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; for (t_it = todays.begin(); t_it != todays.end(); ++t_it) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*t_it); d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; } if (!schedule.isEmpty()) { d->m_html += "
 
\n"; QList::Iterator it; d->m_html += ""; d->m_html += QString("\n").arg(i18n("Future payments")); d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; // show all or the first 6 entries int cnt; cnt = (d->m_showAllSchedules) ? -1 : 6; bool needMoreLess = d->m_showAllSchedules; QDate lastDate = QDate::currentDate().addMonths(1); qSort(schedule); do { it = schedule.begin(); if (it == schedule.end()) break; // if the next due date is invalid (schedule is finished) // we remove it from the list QDate nextDate = (*it).nextDueDate(); if (!nextDate.isValid()) { schedule.erase(it); continue; } if (nextDate > lastDate) break; if (cnt == 0) { needMoreLess = true; break; } // in case we've shown the current recurrence as overdue, // we don't show it here again, but keep the schedule // as it might show up later in the list again if (!(*it).isOverdue()) { if (cnt > 0) --cnt; d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showPaymentEntry(*it); d->m_html += ""; // for single occurrence we have reported everything so we // better get out of here. if ((*it).occurrence() == MyMoneySchedule::OCCUR_ONCE) { schedule.erase(it); continue; } } // if nextPayment returns an invalid date, setNextDueDate will // just skip it, resulting in a loop // we check the resulting date and erase the schedule if invalid if (!((*it).nextPayment((*it).nextDueDate())).isValid()) { schedule.erase(it); continue; } (*it).setNextDueDate((*it).nextPayment((*it).nextDueDate())); qSort(schedule); } while (1); if (needMoreLess) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += ""; d->m_html += ""; } d->m_html += "
%1
"; d->m_html += i18n("Date"); d->m_html += ""; d->m_html += i18n("Schedule"); d->m_html += ""; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Amount"); d->m_html += ""; d->m_html += i18n("Balance after"); d->m_html += "
"; if (d->m_showAllSchedules) { d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("reduced")) + i18nc("Less...", "Show fewer schedules on the list") + linkend(); } else { d->m_html += link(VIEW_SCHEDULE, QString("?mode=%1").arg("full")) + i18nc("More...", "Show more schedules on the list") + linkend(); } d->m_html += "
"; } } d->m_html += "
"; } void KHomeView::showPaymentEntry(const MyMoneySchedule& sched, int cnt) { QString tmp; MyMoneyFile* file = MyMoneyFile::instance(); try { MyMoneyAccount acc = sched.account(); if (!acc.id().isEmpty()) { MyMoneyTransaction t = sched.transaction(); // only show the entry, if it is still active if (!sched.isFinished()) { MyMoneySplit sp = t.splitByAccount(acc.id(), true); QString pathEnter = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::KeyEnter]).pixmap(QSize(16,16))); QString pathSkip = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::MediaSkipForward]).pixmap(QSize(16,16))); //show payment date tmp = QString("") + QLocale().toString(sched.adjustedNextDueDate(), QLocale::ShortFormat) + ""; if (!pathEnter.isEmpty()) tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=enter").arg(sched.id()), i18n("Enter schedule")) + QString("").arg(pathEnter) + linkend(); if (!pathSkip.isEmpty()) tmp += " " + link(VIEW_SCHEDULE, QString("?id=%1&mode=skip").arg(sched.id()), i18n("Skip schedule")) + QString("").arg(pathSkip) + linkend(); tmp += QString(" "); tmp += link(VIEW_SCHEDULE, QString("?id=%1&mode=edit").arg(sched.id()), i18n("Edit schedule")) + sched.name() + linkend(); //show quantity of payments overdue if any if (cnt > 1) tmp += i18np(" (%1 payment)", " (%1 payments)", cnt); //show account of the main split tmp += ""; tmp += QString(file->account(acc.id()).name()); //show amount of the schedule tmp += ""; const MyMoneySecurity& currency = MyMoneyFile::instance()->currency(acc.currencyId()); MyMoneyMoney payment = MyMoneyMoney(sp.value(t.commodity(), acc.currencyId()) * cnt); QString amount = MyMoneyUtils::formatMoney(payment, acc, currency); amount.replace(QChar(' '), " "); tmp += showColoredAmount(amount, payment.isNegative()); tmp += ""; //show balance after payments tmp += ""; QDate paymentDate = QDate(sched.adjustedNextDueDate()); MyMoneyMoney balanceAfter = forecastPaymentBalance(acc, payment, paymentDate); QString balance = MyMoneyUtils::formatMoney(balanceAfter, acc, currency); balance.replace(QChar(' '), " "); tmp += showColoredAmount(balance, balanceAfter.isNegative()); tmp += ""; // qDebug("paymentEntry = '%s'", tmp.toLatin1()); d->m_html += tmp; } } } catch (const MyMoneyException &e) { qDebug("Unable to display schedule entry: %s", qPrintable(e.what())); } } void KHomeView::showAccounts(KHomeView::paymentTypeE type, const QString& header) { MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); QList accounts; - bool showClosedAccounts = kmymoney->toggleAction("view_show_all_accounts")->isChecked(); + bool showClosedAccounts = kmymoney->isActionToggled(Action::ViewShowAll); // get list of all accounts file->accountList(accounts); for (QList::Iterator it = accounts.begin(); it != accounts.end();) { bool removeAccount = false; if (!(*it).isClosed() || showClosedAccounts) { switch ((*it).accountType()) { case MyMoneyAccount::Expense: case MyMoneyAccount::Income: // never show a category account // Note: This might be different in a future version when // the homepage also shows category based information removeAccount = true; break; // Asset and Liability accounts are only shown if they // have the preferred flag set case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: case MyMoneyAccount::Investment: // if preferred accounts are requested, then keep in list if ((*it).value("PreferredAccount") != "Yes" || (type & Preferred) == 0) { removeAccount = true; } break; // Check payment accounts. If payment and preferred is selected, // then always show them. If only payment is selected, then // show only if preferred flag is not set. case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::CreditCard: switch (type & (Payment | Preferred)) { case Payment: if ((*it).value("PreferredAccount") == "Yes") removeAccount = true; break; case Preferred: if ((*it).value("PreferredAccount") != "Yes") removeAccount = true; break; case Payment | Preferred: break; default: removeAccount = true; break; } break; // filter all accounts that are not used on homepage views default: removeAccount = true; break; } } else if ((*it).isClosed() || (*it).isInvest()) { // don't show if closed or a stock account removeAccount = true; } if (removeAccount) it = accounts.erase(it); else ++it; } if (!accounts.isEmpty()) { // sort the accounts by name qStableSort(accounts.begin(), accounts.end(), accountNameLess); QString tmp; int i = 0; tmp = "
" + header + "
\n
 
\n"; d->m_html += tmp; d->m_html += ""; d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::Download]).pixmap(QSize(16,16))); d->m_html += QString("").arg(pathStatusHeader); } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += QString(""); if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += QString(""); if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += QString(""); d->m_html += ""; //only show limit info if user chose to do so if (KMyMoneyGlobalSettings::showLimitInfo()) { d->m_html += ""; } d->m_html += ""; d->m_total = 0; QList::const_iterator it_m; for (it_m = accounts.constBegin(); it_m != accounts.constEnd(); ++it_m) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); showAccountEntry(*it_m); d->m_html += ""; } d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); QString amount = d->m_total.formatMoney(file->baseCurrency().tradingSymbol(), prec); if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) d->m_html += ""; d->m_html += QString("").arg(i18n("Total")); if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += QString("").arg(showColoredAmount(amount, d->m_total.isNegative())); d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += ""; d->m_html += i18n("To Minimum Balance / Maximum Credit"); d->m_html += "
%1%1
"; } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneySecurity currency = file->currency(acc.currencyId()); MyMoneyMoney value; bool showLimit = KMyMoneyGlobalSettings::showLimitInfo(); if (acc.accountType() == MyMoneyAccount::Investment) { //investment accounts show the balances of all its subaccounts value = investmentBalance(acc); //investment accounts have no minimum balance showAccountEntry(acc, value, MyMoneyMoney(), showLimit); } else { //get balance for normal accounts value = file->balance(acc.id(), QDate::currentDate()); if (acc.currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount(acc.id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(file->baseCurrency().smallestAccountFraction()); d->m_total += baseValue; } else { d->m_total += value; } //if credit card or checkings account, show maximum credit if (acc.accountType() == MyMoneyAccount::CreditCard || acc.accountType() == MyMoneyAccount::Checkings) { QString maximumCredit = acc.value("maxCreditAbsolute"); if (maximumCredit.isEmpty()) { maximumCredit = acc.value("minBalanceAbsolute"); } MyMoneyMoney maxCredit = MyMoneyMoney(maximumCredit); showAccountEntry(acc, value, value - maxCredit, showLimit); } else { //otherwise use minimum balance QString minimumBalance = acc.value("minBalanceAbsolute"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); showAccountEntry(acc, value, value - minBalance, showLimit); } } } void KHomeView::showAccountEntry(const MyMoneyAccount& acc, const MyMoneyMoney& value, const MyMoneyMoney& valueToMinBal, const bool showMinBal) { MyMoneyFile* file = MyMoneyFile::instance(); QString tmp; MyMoneySecurity currency = file->currency(acc.currencyId()); QString amount; QString amountToMinBal; //format amounts amount = MyMoneyUtils::formatMoney(value, acc, currency); amount.replace(QChar(' '), " "); if (showMinBal) { amountToMinBal = MyMoneyUtils::formatMoney(valueToMinBal, acc, currency); amountToMinBal.replace(QChar(' '), " "); } QString cellStatus, cellCounts, pathOK, pathTODO, pathNotOK; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { //show account's online-status pathOK = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::DialogOKApply]).pixmap(QSize(16,16))); pathTODO = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::MailReceive]).pixmap(QSize(16,16))); pathNotOK = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::DialogCancel]).pixmap(QSize(16,16))); if (acc.value("lastImportedTransactionDate").isEmpty() || acc.value("lastStatementBalance").isEmpty()) cellStatus = '-'; else if (file->hasMatchingOnlineBalance(acc)) { if (file->hasNewerTransaction(acc.id(), QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate))) cellStatus = QString("").arg(pathTODO); else cellStatus = QString("").arg(pathOK); } else cellStatus = QString("").arg(pathNotOK); tmp = QString("%1").arg(cellStatus); } tmp += QString("") + link(VIEW_LEDGER, QString("?id=%1").arg(acc.id())) + acc.name() + linkend() + ""; int countNotMarked = 0, countCleared = 0, countNotReconciled = 0; QString countStr; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countNotMarked = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::notReconciled); if (KMyMoneyGlobalSettings::showCountOfClearedTransactions() || KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countCleared = file->countTransactionsWithSpecificReconciliationState(acc.id(), MyMoneyTransactionFilter::cleared); if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) countNotReconciled = countNotMarked + countCleared; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) { if (countNotMarked) countStr = QString("%1").arg(countNotMarked); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) { if (countCleared) countStr = QString("%1").arg(countCleared); else countStr = '-'; tmp += QString("%1").arg(countStr); } if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) { if (countNotReconciled) countStr = QString("%1").arg(countNotReconciled); else countStr = '-'; tmp += QString("%1").arg(countStr); } //show account balance tmp += QString("%1").arg(showColoredAmount(amount, value.isNegative())); //show minimum balance column if requested if (showMinBal) { //if it is an investment, show minimum balance empty if (acc.accountType() == MyMoneyAccount::Investment) { tmp += QString(" "); } else { //show minimum balance entry tmp += QString("%1").arg(showColoredAmount(amountToMinBal, valueToMinBal.isNegative())); } } // qDebug("accountEntry = '%s'", tmp.toLatin1()); d->m_html += tmp; } MyMoneyMoney KHomeView::investmentBalance(const MyMoneyAccount& acc) { MyMoneyFile* file = MyMoneyFile::instance(); MyMoneyMoney value; value = file->balance(acc.id(), QDate::currentDate()); QList::const_iterator it_a; for (it_a = acc.accountList().begin(); it_a != acc.accountList().end(); ++it_a) { MyMoneyAccount stock = file->account(*it_a); if (!stock.isClosed()) { try { MyMoneyMoney val; MyMoneyMoney balance = file->balance(stock.id(), QDate::currentDate()); MyMoneySecurity security = file->security(stock.currencyId()); const MyMoneyPrice &price = file->price(stock.currencyId(), security.tradingCurrency()); val = (balance * price.rate(security.tradingCurrency())).convertPrecision(security.pricePrecision()); // adjust value of security to the currency of the account MyMoneySecurity accountCurrency = file->currency(acc.currencyId()); val = val * file->price(security.tradingCurrency(), accountCurrency.id()).rate(accountCurrency.id()); val = val.convert(acc.fraction()); value += val; } catch (const MyMoneyException &e) { qWarning("%s", qPrintable(QString("cannot convert stock balance of %1 to base currency: %2").arg(stock.name(), e.what()))); } } } return value; } void KHomeView::showFavoriteReports() { QList reports = MyMoneyFile::instance()->reportList(); if (!reports.isEmpty()) { bool firstTime = 1; int row = 0; QList::const_iterator it_report = reports.constBegin(); while (it_report != reports.constEnd()) { if ((*it_report).isFavorite()) { if (firstTime) { d->m_html += QString("
%1
\n
 
\n").arg(i18n("Favorite Reports")); d->m_html += ""; d->m_html += ""; firstTime = false; } d->m_html += QString("") .arg(row++ & 0x01 ? "even" : "odd") .arg(link(VIEW_REPORTS, QString("?id=%1").arg((*it_report).id()))) .arg((*it_report).name()) .arg(linkend()) .arg((*it_report).comment()); } ++it_report; } if (!firstTime) d->m_html += "
"; d->m_html += i18n("Report"); d->m_html += ""; d->m_html += i18n("Comment"); d->m_html += "
%2%3%4%5
"; } } void KHomeView::showForecast() { MyMoneyFile* file = MyMoneyFile::instance(); QList accList; //if forecast has not been executed yet, do it. if (!d->m_forecast.isForecastDone()) doForecast(); accList = d->m_forecast.accountList(); if (accList.count() > 0) { // sort the accounts by name qStableSort(accList.begin(), accList.end(), accountNameLess); int i = 0; int colspan = 1; //get begin day int beginDay = QDate::currentDate().daysTo(d->m_forecast.beginForecastDate()); //if begin day is today skip to next cycle if (beginDay == 0) beginDay = d->m_forecast.accountsCycle(); // Now output header d->m_html += QString("
%1
\n
 
\n").arg(i18n("%1 Day Forecast", d->m_forecast.forecastDays())); d->m_html += ""; d->m_html += ""; int colWidth = 55 / (d->m_forecast.forecastDays() / d->m_forecast.accountsCycle()); for (i = 0; (i*d->m_forecast.accountsCycle() + beginDay) <= d->m_forecast.forecastDays(); ++i) { d->m_html += QString(""; colspan++; } d->m_html += ""; // Now output entries i = 0; QList::ConstIterator it_account; for (it_account = accList.constBegin(); it_account != accList.constEnd(); ++it_account) { //MyMoneyAccount acc = (*it_n); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString(""; int dropZero = -1; //account dropped below zero int dropMinimum = -1; //account dropped below minimum balance QString minimumBalance = (*it_account).value("minimumBalance"); MyMoneyMoney minBalance = MyMoneyMoney(minimumBalance); MyMoneySecurity currency; MyMoneyMoney forecastBalance; //change account to deep currency if account is an investment if ((*it_account).isInvest()) { MyMoneySecurity underSecurity = file->security((*it_account).currencyId()); currency = file->security(underSecurity.tradingCurrency()); } else { currency = file->security((*it_account).currencyId()); } for (int f = beginDay; f <= d->m_forecast.forecastDays(); f += d->m_forecast.accountsCycle()) { forecastBalance = d->m_forecast.forecastBalance(*it_account, QDate::currentDate().addDays(f)); QString amount; amount = MyMoneyUtils::formatMoney(forecastBalance, *it_account, currency); amount.replace(QChar(' '), " "); d->m_html += QString("").arg(showColoredAmount(amount, forecastBalance.isNegative())); } d->m_html += ""; //Check if the account is going to be below zero or below the minimal balance in the forecast period //Check if the account is going to be below minimal balance dropMinimum = d->m_forecast.daysToMinimumBalance(*it_account); //Check if the account is going to be below zero in the future dropZero = d->m_forecast.daysToZeroBalance(*it_account); // spit out possible warnings QString msg; // if a minimum balance has been specified, an appropriate warning will // only be shown, if the drop below 0 is on a different day or not present if (dropMinimum != -1 && !minBalance.isZero() && (dropMinimum < dropZero || dropZero == -1)) { switch (dropMinimum) { case -1: break; case 0: msg = i18n("The balance of %1 is below the minimum balance %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; default: msg = i18np("The balance of %2 will drop below the minimum balance %3 in %1 day.", "The balance of %2 will drop below the minimum balance %3 in %1 days.", dropMinimum - 1, (*it_account).name(), MyMoneyUtils::formatMoney(minBalance, *it_account, currency)); msg = showColoredAmount(msg, true); break; } if (!msg.isEmpty()) { d->m_html += QString("").arg(msg).arg(colspan); } } // a drop below zero is always shown msg.clear(); switch (dropZero) { case -1: break; case 0: if ((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18n("The balance of %1 is below %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18n("The balance of %1 is above %2 today.", (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } break; default: if ((*it_account).accountGroup() == MyMoneyAccount::Asset) { msg = i18np("The balance of %2 will drop below %3 in %1 day.", "The balance of %2 will drop below %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); msg = showColoredAmount(msg, true); break; } if ((*it_account).accountGroup() == MyMoneyAccount::Liability) { msg = i18np("The balance of %2 will raise above %3 in %1 day.", "The balance of %2 will raise above %3 in %1 days.", dropZero, (*it_account).name(), MyMoneyUtils::formatMoney(MyMoneyMoney(), *it_account, currency)); break; } } if (!msg.isEmpty()) { d->m_html += QString("").arg(msg).arg(colspan); } } d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += "").arg(colWidth); d->m_html += i18ncp("Forecast days", "%1 day", "%1 days", i * d->m_forecast.accountsCycle() + beginDay); d->m_html += "
") + link(VIEW_LEDGER, QString("?id=%1").arg((*it_account).id())) + (*it_account).name() + linkend() + "").arg(colWidth); d->m_html += QString("%1
%1
%1
"; } } QString KHomeView::link(const QString& view, const QString& query, const QString& _title) const { QString titlePart; QString title(_title); if (!title.isEmpty()) titlePart = QString(" title=\"%1\"").arg(title.replace(QLatin1Char(' '), " ")); return QString("").arg(view, query, titlePart); } QString KHomeView::linkend() const { return QStringLiteral(""); } void KHomeView::slotOpenUrl(const QUrl &url, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&) { QString protocol = url.scheme(); QString view = url.fileName(); QUrlQuery query(url); QString id = query.queryItemValue("id"); QString mode = query.queryItemValue("mode"); if (protocol == "http") { QDesktopServices::openUrl(url); } else if (protocol == "mailto") { QDesktopServices::openUrl(url); } else { KXmlGuiWindow* mw = KMyMoneyUtils::mainWindow(); Q_CHECK_PTR(mw); if (view == VIEW_LEDGER) { emit ledgerSelected(id, QString()); } else if (view == VIEW_SCHEDULE) { if (mode == "enter") { emit scheduleSelected(id); - QTimer::singleShot(0, mw->actionCollection()->action("schedule_enter"), SLOT(trigger())); - } else if (mode == "edit") { + QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleEnter]), SLOT(trigger())); + } else if (mode == QLatin1String("edit")) { emit scheduleSelected(id); - QTimer::singleShot(0, mw->actionCollection()->action("schedule_edit"), SLOT(trigger())); - } else if (mode == "skip") { + QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleEdit]), SLOT(trigger())); + } else if (mode == QLatin1String("skip")) { emit scheduleSelected(id); - QTimer::singleShot(0, mw->actionCollection()->action("schedule_skip"), SLOT(trigger())); - } else if (mode == "full") { + QTimer::singleShot(0, mw->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleSkip]), SLOT(trigger())); + } else if (mode == QLatin1String("full")) { d->m_showAllSchedules = true; loadView(); } else if (mode == "reduced") { d->m_showAllSchedules = false; loadView(); } } else if (view == VIEW_REPORTS) { emit reportSelected(id); } else if (view == VIEW_WELCOME) { if (mode == "whatsnew") { d->m_part->begin(); d->m_part->write(KWelcomePage::whatsNewPage()); d->m_part->end(); } else { d->m_part->begin(); d->m_part->write(KWelcomePage::welcomePage()); d->m_part->end(); } } else if (view == "action") { QTimer::singleShot(0, mw->actionCollection()->action(id), SLOT(trigger())); } else if (view == VIEW_HOME) { QList list; MyMoneyFile::instance()->accountList(list); if (list.count() == 0) { KMessageBox::information(this, i18n("Before KMyMoney can give you detailed information about your financial status, you need to create at least one account. Until then, KMyMoney shows the welcome page instead.")); } loadView(); } else { qDebug("Unknown view '%s' in KHomeView::slotOpenURL()", qPrintable(view)); } } } void KHomeView::showAssetsLiabilities() { QList accounts; QList::ConstIterator it; QList assets; QList liabilities; MyMoneyMoney netAssets; MyMoneyMoney netLiabilities; QString fontStart, fontEnd; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); int i = 0; // get list of all accounts file->accountList(accounts); for (it = accounts.constBegin(); it != accounts.constEnd();) { if (!(*it).isClosed()) { switch ((*it).accountType()) { // group all assets into one list but make sure that investment accounts always show up case MyMoneyAccount::Investment: assets << *it; break; case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: case MyMoneyAccount::Asset: case MyMoneyAccount::AssetLoan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { assets << *it; } break; // group the liabilities into the other case MyMoneyAccount::CreditCard: case MyMoneyAccount::Liability: case MyMoneyAccount::Loan: // list account if it's the last in the hierarchy or has transactions in it if ((*it).accountList().isEmpty() || (file->transactionCount((*it).id()) > 0)) { liabilities << *it; } break; default: break; } } ++it; } //only do it if we have assets or liabilities account if (assets.count() > 0 || liabilities.count() > 0) { // sort the accounts by name qStableSort(assets.begin(), assets.end(), accountNameLess); qStableSort(liabilities.begin(), liabilities.end(), accountNameLess); QString statusHeader; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { QString pathStatusHeader; pathStatusHeader = QPixmapToDataUri(QIcon::fromTheme(g_Icons[Icon::ViewOutbox]).pixmap(QSize(16,16))); statusHeader = QString("").arg(pathStatusHeader); } //print header d->m_html += "
" + i18n("Assets and Liabilities Summary") + "
\n
 
\n"; d->m_html += ""; //column titles d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { d->m_html += ""; } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += ""; //intermediate row to separate both columns d->m_html += ""; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) { d->m_html += ""; } d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) d->m_html += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) d->m_html += ""; d->m_html += ""; QString placeHolder_Status, placeHolder_Counts; if (KMyMoneyGlobalSettings::showBalanceStatusOfOnlineAccounts()) placeHolder_Status = ""; if (KMyMoneyGlobalSettings::showCountOfUnmarkedTransactions()) placeHolder_Counts = ""; if (KMyMoneyGlobalSettings::showCountOfClearedTransactions()) placeHolder_Counts += ""; if (KMyMoneyGlobalSettings::showCountOfNotReconciledTransactions()) placeHolder_Counts += ""; //get asset and liability accounts QList::const_iterator asset_it = assets.constBegin(); QList::const_iterator liabilities_it = liabilities.constBegin(); for (; asset_it != assets.constEnd() || liabilities_it != liabilities.constEnd();) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //write an asset account if we still have any if (asset_it != assets.constEnd()) { MyMoneyMoney value; //investment accounts consolidate the balance of its subaccounts if ((*asset_it).accountType() == MyMoneyAccount::Investment) { value = investmentBalance(*asset_it); } else { value = MyMoneyFile::instance()->balance((*asset_it).id(), QDate::currentDate()); } //calculate balance for foreign currency accounts if ((*asset_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*asset_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netAssets += baseValue; } else { netAssets += value; } //show the account without minimum balance showAccountEntry(*asset_it, value, MyMoneyMoney(), false); ++asset_it; } else { //write a white space if we don't d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } //leave the intermediate column empty d->m_html += ""; //write a liability account if (liabilities_it != liabilities.constEnd()) { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*liabilities_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*liabilities_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*liabilities_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; baseValue = baseValue.convert(10000); netLiabilities += baseValue; } else { netLiabilities += value; } //show the account without minimum balance showAccountEntry(*liabilities_it, value, MyMoneyMoney(), false); ++liabilities_it; } else { //leave the space empty if we run out of liabilities d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); } d->m_html += ""; } //calculate net worth MyMoneyMoney netWorth = netAssets + netLiabilities; //format assets, liabilities and net worth QString amountAssets = netAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiabilities = netLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountNetWorth = netWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountAssets.replace(QChar(' '), " "); amountLiabilities.replace(QChar(' '), " "); amountNetWorth.replace(QChar(' '), " "); d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //print total for assets d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Assets")).arg(placeHolder_Counts).arg(showColoredAmount(amountAssets, netAssets.isNegative())); //leave the intermediate column empty d->m_html += ""; //print total liabilities d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Total Liabilities")).arg(placeHolder_Counts).arg(showColoredAmount(amountLiabilities, netLiabilities.isNegative())); d->m_html += ""; //print net worth d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString("%1%2").arg(placeHolder_Status).arg(placeHolder_Counts); d->m_html += QString("%1%3").arg(placeHolder_Status).arg(i18n("Net Worth")).arg(placeHolder_Counts).arg(showColoredAmount(amountNetWorth, netWorth.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += statusHeader; d->m_html += ""; d->m_html += i18n("Asset Accounts"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += ""; d->m_html += statusHeader; d->m_html += ""; d->m_html += i18n("Liability Accounts"); d->m_html += "!MC!R"; d->m_html += i18n("Current Balance"); d->m_html += "
%2%4%2%4
%2%4
"; d->m_html += "
"; } } void KHomeView::showBudget() { MyMoneyFile* file = MyMoneyFile::instance(); if (file->countBudgets()) { int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); bool isOverrun = false; int i = 0; //config report just like "Monthly Budgeted vs Actual MyMoneyReport reportCfg = MyMoneyReport( MyMoneyReport::eBudgetActual, MyMoneyReport::eMonths, MyMoneyTransactionFilter::currentMonth, MyMoneyReport::eDetailAll, i18n("Monthly Budgeted vs. Actual"), i18n("Generated Report")); reportCfg.setBudget("Any", true); reports::PivotTable table(reportCfg); PivotGrid grid = table.grid(); //div header d->m_html += "
" + i18n("Budget") + "
\n
 
\n"; //display budget summary d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += QString(""); MyMoneyMoney totalBudgetValue = grid.m_total[eBudget].m_total; MyMoneyMoney totalActualValue = grid.m_total[eActual].m_total; MyMoneyMoney totalBudgetDiffValue = grid.m_total[eBudgetDiff].m_total; QString totalBudgetAmount = totalBudgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalActualAmount = totalActualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString totalBudgetDiffAmount = totalBudgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); d->m_html += QString("").arg(showColoredAmount(totalBudgetAmount, totalBudgetValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(totalActualAmount, totalActualValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(totalBudgetDiffAmount, totalBudgetDiffValue.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Current Month Summary"); d->m_html += "
"; d->m_html += i18n("Budgeted"); d->m_html += ""; d->m_html += i18n("Actual"); d->m_html += ""; d->m_html += i18n("Difference"); d->m_html += "
%1%1%1
"; //budget overrun d->m_html += "
 
\n"; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; PivotGrid::iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { i = 0; PivotOuterGroup::iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { PivotInnerGroup::iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { //column number is 1 because the report includes only current month if (it_row.value()[eBudgetDiff][1].isNegative()) { //get report account to get the name later ReportAccount rowname = it_row.key(); //write the outergroup if it is the first row of outergroup being shown if (i == 0) { d->m_html += ""; d->m_html += QString("").arg(KMyMoneyUtils::accountTypeToString(rowname.accountType())); d->m_html += ""; } d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); //get values from grid MyMoneyMoney actualValue = it_row.value()[eActual][1]; MyMoneyMoney budgetValue = it_row.value()[eBudget][1]; MyMoneyMoney budgetDiffValue = it_row.value()[eBudgetDiff][1]; //format amounts QString actualAmount = actualValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetAmount = budgetValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString budgetDiffAmount = budgetDiffValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); //account name d->m_html += QString(""; //show amounts d->m_html += QString("").arg(showColoredAmount(budgetAmount, budgetValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(actualAmount, actualValue.isNegative())); d->m_html += QString("").arg(showColoredAmount(budgetDiffAmount, budgetDiffValue.isNegative())); d->m_html += ""; //set the flag that there are overruns isOverrun = true; } ++it_row; } ++it_innergroup; } ++it_outergroup; } //if no negative differences are found, then inform that if (!isOverrun) { d->m_html += QString("").arg(i++ & 0x01 ? "even" : "odd"); d->m_html += QString("").arg(i18n("No Budget Categories have been overrun")); d->m_html += ""; } d->m_html += "
"; d->m_html += i18n("Budget Overruns"); d->m_html += "
"; d->m_html += i18n("Account"); d->m_html += ""; d->m_html += i18n("Budgeted"); d->m_html += ""; d->m_html += i18n("Actual"); d->m_html += ""; d->m_html += i18n("Difference"); d->m_html += "
%1
") + link(VIEW_LEDGER, QString("?id=%1").arg(rowname.id())) + rowname.name() + linkend() + "%1%1%1
%1
"; } } QString KHomeView::showColoredAmount(const QString& amount, bool isNegative) { if (isNegative) { //if negative, get the settings for negative numbers return QString("%2").arg(KMyMoneyGlobalSettings::listNegativeValueColor().name(), amount); } //if positive, return the same string return amount; } void KHomeView::doForecast() { //clear m_accountList because forecast is about to changed d->m_accountList.clear(); //reinitialize the object d->m_forecast = KMyMoneyGlobalSettings::forecast(); //If forecastDays lower than accountsCycle, adjust to the first cycle if (d->m_forecast.accountsCycle() > d->m_forecast.forecastDays()) d->m_forecast.setForecastDays(d->m_forecast.accountsCycle()); //Get all accounts of the right type to calculate forecast d->m_forecast.doForecast(); } MyMoneyMoney KHomeView::forecastPaymentBalance(const MyMoneyAccount& acc, const MyMoneyMoney& payment, QDate& paymentDate) { //if paymentDate before or equal to currentDate set it to current date plus 1 //so we get to accumulate forecast balance correctly if (paymentDate <= QDate::currentDate()) paymentDate = QDate::currentDate().addDays(1); //check if the account is already there if (d->m_accountList.find(acc.id()) == d->m_accountList.end() || d->m_accountList[acc.id()].find(paymentDate) == d->m_accountList[acc.id()].end()) { if (paymentDate == QDate::currentDate()) { d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate); } else { d->m_accountList[acc.id()][paymentDate] = d->m_forecast.forecastBalance(acc, paymentDate.addDays(-1)); } } d->m_accountList[acc.id()][paymentDate] = d->m_accountList[acc.id()][paymentDate] + payment; return d->m_accountList[acc.id()][paymentDate]; } void KHomeView::showCashFlowSummary() { MyMoneyTransactionFilter filter; MyMoneyMoney incomeValue; MyMoneyMoney expenseValue; MyMoneyFile* file = MyMoneyFile::instance(); int prec = MyMoneyMoney::denomToPrec(file->baseCurrency().smallestAccountFraction()); //set start and end of month dates QDate startOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), 1); QDate endOfMonth = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().daysInMonth()); //Add total income and expenses for this month //get transactions for current month filter.setDateFilter(startOfMonth, endOfMonth); filter.setReportAllSplits(false); QList transactions = file->transactionList(filter); //if no transaction then skip and print total in zero if (transactions.size() > 0) { QList::const_iterator it_transaction; //get all transactions for this month for (it_transaction = transactions.constBegin(); it_transaction != transactions.constEnd(); ++it_transaction) { //get the splits for each transaction const QList& splits = (*it_transaction).splits(); QList::const_iterator it_split; for (it_split = splits.begin(); it_split != splits.end(); ++it_split) { if (!(*it_split).shares().isZero()) { ReportAccount repSplitAcc = ReportAccount((*it_split).accountId()); //only add if it is an income or expense if (repSplitAcc.isIncomeExpense()) { MyMoneyMoney value; //convert to base currency if necessary if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice((*it_transaction).postDate()); value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE) * curPrice; value = value.convert(10000); } else { value = ((*it_split).shares() * MyMoneyMoney::MINUS_ONE); } //store depending on account type if (repSplitAcc.accountType() == MyMoneyAccount::Income) { incomeValue += value; } else { expenseValue += value; } } } } } } //format income and expenses QString amountIncome = incomeValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpense = expenseValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountIncome.replace(QChar(' '), " "); amountExpense.replace(QChar(' '), " "); //calculate schedules //Add all schedules for this month MyMoneyMoney scheduledIncome; MyMoneyMoney scheduledExpense; MyMoneyMoney scheduledLiquidTransfer; MyMoneyMoney scheduledOtherTransfer; //get overdues and schedules until the end of this month QList schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY, QDate(), endOfMonth); //Remove the finished schedules QList::Iterator finished_it; for (finished_it = schedule.begin(); finished_it != schedule.end();) { if ((*finished_it).isFinished()) { finished_it = schedule.erase(finished_it); continue; } ++finished_it; } //add income and expenses QList::Iterator sched_it; for (sched_it = schedule.begin(); sched_it != schedule.end();) { QDate nextDate = (*sched_it).nextDueDate(); int cnt = 0; while (nextDate.isValid() && nextDate <= endOfMonth) { ++cnt; nextDate = (*sched_it).nextPayment(nextDate); // for single occurrence nextDate will not change, so we // better get out of here. if ((*sched_it).occurrence() == MyMoneySchedule::OCCUR_ONCE) break; } MyMoneyAccount acc = (*sched_it).account(); if (!acc.id().isEmpty()) { MyMoneyTransaction transaction = (*sched_it).transaction(); // only show the entry, if it is still active MyMoneySplit sp = transaction.splitByAccount(acc.id(), true); // take care of the autoCalc stuff if ((*sched_it).type() == MyMoneySchedule::TYPE_LOANPAYMENT) { QDate nextDate = (*sched_it).nextPayment((*sched_it).lastPayment()); //make sure we have all 'starting balances' so that the autocalc works QList::const_iterator it_s; QMap balanceMap; for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { MyMoneyAccount acc = file->account((*it_s).accountId()); // collect all overdues on the first day QDate schedDate = nextDate; if (QDate::currentDate() >= nextDate) schedDate = QDate::currentDate().addDays(1); balanceMap[acc.id()] += file->balance(acc.id(), QDate::currentDate()); } KMyMoneyUtils::calculateAutoLoan(*sched_it, transaction, balanceMap); } //go through the splits and assign to liquid or other transfers const QList splits = transaction.splits(); QList::const_iterator split_it; for (split_it = splits.constBegin(); split_it != splits.constEnd(); ++split_it) { if ((*split_it).accountId() != acc.id()) { ReportAccount repSplitAcc = ReportAccount((*split_it).accountId()); //get the shares and multiply by the quantity of occurrences in the period MyMoneyMoney value = (*split_it).shares() * cnt; //convert to foreign currency if needed if (repSplitAcc.currencyId() != file->baseCurrency().id()) { MyMoneyMoney curPrice = repSplitAcc.baseCurrencyPrice(QDate::currentDate()); value = value * curPrice; value = value.convert(10000); } if ((repSplitAcc.isLiquidLiability() || repSplitAcc.isLiquidAsset()) && acc.accountGroup() != repSplitAcc.accountGroup()) { scheduledLiquidTransfer += value; } else if (repSplitAcc.isAssetLiability() && !repSplitAcc.isLiquidLiability() && !repSplitAcc.isLiquidAsset()) { scheduledOtherTransfer += value; } else if (repSplitAcc.isIncomeExpense()) { //income and expenses are stored as negative values if (repSplitAcc.accountType() == MyMoneyAccount::Income) scheduledIncome -= value; if (repSplitAcc.accountType() == MyMoneyAccount::Expense) scheduledExpense -= value; } } } } ++sched_it; } //format the currency strings QString amountScheduledIncome = scheduledIncome.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledExpense = scheduledExpense.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledLiquidTransfer = scheduledLiquidTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountScheduledOtherTransfer = scheduledOtherTransfer.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountScheduledIncome.replace(QChar(' '), " "); amountScheduledExpense.replace(QChar(' '), " "); amountScheduledLiquidTransfer.replace(QChar(' '), " "); amountScheduledOtherTransfer.replace(QChar(' '), " "); //get liquid assets and liabilities QList accounts; QList::const_iterator account_it; MyMoneyMoney liquidAssets; MyMoneyMoney liquidLiabilities; // get list of all accounts file->accountList(accounts); for (account_it = accounts.constBegin(); account_it != accounts.constEnd();) { if (!(*account_it).isClosed()) { switch ((*account_it).accountType()) { //group all assets into one list case MyMoneyAccount::Checkings: case MyMoneyAccount::Savings: case MyMoneyAccount::Cash: { MyMoneyMoney value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance for foreign currency accounts if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidAssets += baseValue; liquidAssets = liquidAssets.convert(10000); } else { liquidAssets += value; } break; } //group the liabilities into the other case MyMoneyAccount::CreditCard: { MyMoneyMoney value; value = MyMoneyFile::instance()->balance((*account_it).id(), QDate::currentDate()); //calculate balance if foreign currency if ((*account_it).currencyId() != file->baseCurrency().id()) { ReportAccount repAcc = ReportAccount((*account_it).id()); MyMoneyMoney curPrice = repAcc.baseCurrencyPrice(QDate::currentDate()); MyMoneyMoney baseValue = value * curPrice; liquidLiabilities += baseValue; liquidLiabilities = liquidLiabilities.convert(10000); } else { liquidLiabilities += value; } break; } default: break; } } ++account_it; } //calculate net worth MyMoneyMoney liquidWorth = liquidAssets + liquidLiabilities; //format assets, liabilities and net worth QString amountLiquidAssets = liquidAssets.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidLiabilities = liquidLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountLiquidWorth = liquidWorth.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountLiquidAssets.replace(QChar(' '), " "); amountLiquidLiabilities.replace(QChar(' '), " "); amountLiquidWorth.replace(QChar(' '), " "); //show the summary d->m_html += "
" + i18n("Cash Flow Summary") + "
\n
 
\n"; //print header d->m_html += ""; //income and expense title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); //print current income d->m_html += QString("").arg(showColoredAmount(amountIncome, incomeValue.isNegative())); //print the scheduled income d->m_html += QString("").arg(showColoredAmount(amountScheduledIncome, scheduledIncome.isNegative())); //print current expenses d->m_html += QString("").arg(showColoredAmount(amountExpense, expenseValue.isNegative())); //print the scheduled expenses d->m_html += QString("").arg(showColoredAmount(amountScheduledExpense, scheduledExpense.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Income and Expenses of Current Month"); d->m_html += "
"; d->m_html += i18n("Income"); d->m_html += ""; d->m_html += i18n("Scheduled Income"); d->m_html += ""; d->m_html += i18n("Expenses"); d->m_html += ""; d->m_html += i18n("Scheduled Expenses"); d->m_html += "
%2%2%2%2
"; //print header of assets and liabilities d->m_html += "
 
\n"; d->m_html += ""; //assets and liabilities title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); //print current liquid assets d->m_html += QString("").arg(showColoredAmount(amountLiquidAssets, liquidAssets.isNegative())); //print the scheduled transfers d->m_html += QString("").arg(showColoredAmount(amountScheduledLiquidTransfer, scheduledLiquidTransfer.isNegative())); //print current liabilities d->m_html += QString("").arg(showColoredAmount(amountLiquidLiabilities, liquidLiabilities.isNegative())); //print the scheduled transfers d->m_html += QString("").arg(showColoredAmount(amountScheduledOtherTransfer, scheduledOtherTransfer.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Liquid Assets and Liabilities"); d->m_html += "
"; d->m_html += i18n("Liquid Assets"); d->m_html += ""; d->m_html += i18n("Transfers to Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Other Transfers"); d->m_html += "
%2%2%2%2
"; //final conclusion MyMoneyMoney profitValue = incomeValue + expenseValue + scheduledIncome + scheduledExpense; MyMoneyMoney expectedAsset = liquidAssets + scheduledIncome + scheduledExpense + scheduledLiquidTransfer + scheduledOtherTransfer; MyMoneyMoney expectedLiabilities = liquidLiabilities + scheduledLiquidTransfer; QString amountExpectedAsset = expectedAsset.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountExpectedLiabilities = expectedLiabilities.formatMoney(file->baseCurrency().tradingSymbol(), prec); QString amountProfit = profitValue.formatMoney(file->baseCurrency().tradingSymbol(), prec); amountProfit.replace(QChar(' '), " "); amountExpectedAsset.replace(QChar(' '), " "); amountExpectedLiabilities.replace(QChar(' '), " "); //print header of cash flow status d->m_html += "
 
\n"; d->m_html += ""; //income and expense title d->m_html += ""; d->m_html += ""; //column titles d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; d->m_html += ""; //add row with banding d->m_html += QString(""); d->m_html += ""; //print expected assets d->m_html += QString("").arg(showColoredAmount(amountExpectedAsset, expectedAsset.isNegative())); //print expected liabilities d->m_html += QString("").arg(showColoredAmount(amountExpectedLiabilities, expectedLiabilities.isNegative())); //print expected profit d->m_html += QString("").arg(showColoredAmount(amountProfit, profitValue.isNegative())); d->m_html += ""; d->m_html += "
"; d->m_html += i18n("Cash Flow Status"); d->m_html += "
 "; d->m_html += i18n("Expected Liquid Assets"); d->m_html += ""; d->m_html += i18n("Expected Liquid Liabilities"); d->m_html += ""; d->m_html += i18n("Expected Profit/Loss"); d->m_html += "
 %2%2%2
"; d->m_html += "
"; } // Make sure, that these definitions are only used within this file // this does not seem to be necessary, but when building RPMs the // build option 'final' is used and all CPP files are concatenated. // So it could well be, that in another CPP file these definitions // are also used. #undef VIEW_LEDGER #undef VIEW_SCHEDULE #undef VIEW_WELCOME #undef VIEW_HOME #undef VIEW_REPORTS diff --git a/kmymoney/views/kinstitutionsview.cpp b/kmymoney/views/kinstitutionsview.cpp index 6337dd0c1..b6312a80a 100644 --- a/kmymoney/views/kinstitutionsview.cpp +++ b/kmymoney/views/kinstitutionsview.cpp @@ -1,158 +1,157 @@ /*************************************************************************** kinstitutionsview.cpp ------------------- copyright : (C) 2005 by Thomas Baumgart email : ipwizard@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kinstitutionsview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include -#include // ---------------------------------------------------------------------------- // Project Includes #include #include "models.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include using namespace Icons; KInstitutionsView::KInstitutionsView(QWidget *parent) : QWidget(parent), m_needReload(false) { setupUi(this); // setup icons for collapse and expand button KGuiItem collapseGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListCollapse]), QString(), QString()); KGuiItem expandGuiItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListExpand]), QString(), QString()); KGuiItem::assign(m_collapseButton, collapseGuiItem); KGuiItem::assign(m_expandButton, expandGuiItem); // the proxy filter model m_filterProxyModel = new AccountsViewFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Equity); m_filterProxyModel->setSourceModel(Models::instance()->institutionsModel()); m_filterProxyModel->setFilterKeyColumn(-1); m_accountTree->setModel(m_filterProxyModel); m_accountTree->setConfigGroupName("KInstitutionsView"); m_accountTree->setAlternatingRowColors(true); m_accountTree->setIconSize(QSize(22, 22)); m_accountTree->setSortingEnabled(true); // let the model know if the item is expanded or collapsed connect(m_accountTree, SIGNAL(collapsed(QModelIndex)), m_filterProxyModel, SLOT(collapsed(QModelIndex))); connect(m_accountTree, SIGNAL(expanded(QModelIndex)), m_filterProxyModel, SLOT(expanded(QModelIndex))); connect(m_accountTree, SIGNAL(selectObject(MyMoneyObject)), this, SIGNAL(selectObject(MyMoneyObject))); connect(m_accountTree, SIGNAL(openContextMenu(MyMoneyObject)), this, SIGNAL(openContextMenu(MyMoneyObject))); connect(m_accountTree, SIGNAL(openObject(MyMoneyObject)), this, SIGNAL(openObject(MyMoneyObject))); // connect the two buttons to all required slots connect(m_collapseButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_collapseButton, SIGNAL(clicked()), m_accountTree, SLOT(collapseAll())); connect(m_collapseButton, SIGNAL(clicked()), m_filterProxyModel, SLOT(collapseAll())); connect(m_expandButton, SIGNAL(clicked()), this, SLOT(slotExpandCollapse())); connect(m_expandButton, SIGNAL(clicked()), m_accountTree, SLOT(expandAll())); connect(m_expandButton, SIGNAL(clicked()), m_filterProxyModel, SLOT(expandAll())); connect(m_searchWidget, SIGNAL(textChanged(QString)), m_filterProxyModel, SLOT(setFilterFixedString(QString))); connect(Models::instance()->accountsModel(), SIGNAL(netWorthChanged(MyMoneyMoney)), this, SLOT(slotNetWorthChanged(MyMoneyMoney))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadAccounts())); } KInstitutionsView::~KInstitutionsView() { } void KInstitutionsView::showEvent(QShowEvent * event) { emit aboutToShow(); if (m_needReload) { loadAccounts(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); } void KInstitutionsView::slotLoadAccounts() { if (isVisible()) { loadAccounts(); } else { m_needReload = true; } } void KInstitutionsView::loadAccounts() { m_filterProxyModel->invalidate(); m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); - m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->toggleAction("view_show_all_accounts")->isChecked()); + m_filterProxyModel->setHideClosedAccounts(KMyMoneyGlobalSettings::hideClosedAccounts() && !kmymoney->isActionToggled(Action::ViewShowAll)); } void KInstitutionsView::slotNetWorthChanged(const MyMoneyMoney &netWorth) { QString s(i18n("Net Worth: ")); // FIXME figure out how to deal with the approximate // if(!(file->totalValueValid(assetAccount.id()) & file->totalValueValid(liabilityAccount.id()))) // s += "~ "; s.replace(QString(" "), QString(" ")); if (netWorth.isNegative()) { s += ""; } const MyMoneySecurity& sec = MyMoneyFile::instance()->baseCurrency(); QString v(MyMoneyUtils::formatMoney(netWorth, sec)); s += v.replace(QString(" "), QString(" ")); if (netWorth.isNegative()) { s += ""; } m_totalProfitsLabel->setFont(KMyMoneyGlobalSettings::listCellFont()); m_totalProfitsLabel->setText(s); } void KInstitutionsView::slotExpandCollapse() { if (sender()) { KMyMoneyGlobalSettings::setShowAccountsExpanded(sender() == m_expandButton); } } diff --git a/kmymoney/views/kinvestmentview.cpp b/kmymoney/views/kinvestmentview.cpp index e07b7a1b6..8e849db68 100644 --- a/kmymoney/views/kinvestmentview.cpp +++ b/kmymoney/views/kinvestmentview.cpp @@ -1,571 +1,571 @@ /*************************************************************************** kinvestmentview.cpp - description ------------------- begin : Mon Mar 12 2007 copyright : (C) 2007 by Thomas Baumgart email : 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 "kinvestmentview.h" #include // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include -#include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include #include #include #include #include #include "kmymoney.h" #include "models.h" #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; }; KInvestmentView::KInvestmentView(QWidget *parent) : QWidget(parent), d(new Private), m_currencyMarket("ISO 4217") { 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); d->m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); d->m_filterProxyModel->sort(0); m_accountComboBox->setModel(d->m_filterProxyModel); m_investmentsList->setContextMenuPolicy(Qt::CustomContextMenu); m_investmentsList->setSortingEnabled(true); for (int 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())); connect(m_accountComboBox, SIGNAL(accountSelected(QString)), this, SLOT(slotSelectAccount(QString))); - connect(m_investmentsList, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), kmymoney->action("investment_edit"), SLOT(trigger())); + 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())); } KInvestmentView::~KInvestmentView() { // 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); // save the header state of the securities list grp = KSharedConfig::openConfig()->group("KInvestmentView_Securities"); columns = m_securitiesList->header()->saveState(); grp.writeEntry("HeaderState", columns); delete d; } void KInvestmentView::loadView(InvestmentsViewTab tab) { if (d->m_needReload[tab]) { switch (tab) { case EquitiesTab: 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: break; } d->m_needReload[tab] = false; } } void KInvestmentView::slotInvestmentSelectionChanged() { 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 &) { } } } void KInvestmentView::slotInvestmentContextMenu(const QPoint& /*point*/) { kmymoney->slotSelectInvestment(); QTreeWidgetItem *item = m_investmentsList->currentItem(); if (item) { kmymoney->slotSelectInvestment(MyMoneyFile::instance()->account(item->data(0, Qt::UserRole).value().id())); } emit investmentRightMouseClick(); } void KInvestmentView::slotLoadView() { d->m_needReload[EquitiesTab] = true; d->m_needReload[SecuritiesTab] = true; if (isVisible()) slotTabCurrentChanged(m_tab->currentIndex()); } void KInvestmentView::slotTabCurrentChanged(int index) { 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; } } } bool KInvestmentView::slotSelectAccount(const MyMoneyObject& obj) { if (typeid(obj) != typeid(MyMoneyAccount)) return false; if (d->m_recursion) return false; d->m_recursion = true; const MyMoneyAccount& acc = dynamic_cast(obj); bool rc = slotSelectAccount(acc.id()); d->m_recursion = false; return rc; } bool KInvestmentView::slotSelectAccount(const QString& id, const QString& transactionId, const bool /* reconciliation*/) { bool rc = true; if (!id.isEmpty()) { // if the account id differs, then we have to do something if (d->m_account.id() != id) { try { d->m_account = MyMoneyFile::instance()->account(id); // if a stock account is selected, we show the // the corresponding parent (investment) account if (d->m_account.isInvest()) { d->m_account = MyMoneyFile::instance()->account(d->m_account.parentAccountId()); } // TODO if we don't have an investment account, then we should switch to the ledger view d->m_newAccountLoaded = true; if (d->m_account.accountType() == MyMoneyAccount::Investment) { slotLoadView(); } else { emit accountSelected(id, transactionId); d->m_account = MyMoneyAccount(); d->m_needReload[EquitiesTab] = true; rc = false; } } catch (const MyMoneyException &) { qDebug("Unable to retrieve account %s", qPrintable(id)); rc = false; } } else { emit accountSelected(d->m_account); } } return rc; } void KInvestmentView::clear() { // 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); } } void KInvestmentView::loadInvestmentTab() { // no account selected emit accountSelected(MyMoneyAccount()); // clear the current contents ... clear(); // ... load the combobox widget and select current account ... loadAccounts(); if (d->m_account.id().isEmpty()) { // if we don't have an account we bail out setEnabled(false); return; } setEnabled(true); MyMoneyFile* file = MyMoneyFile::instance(); - bool showClosedAccounts = kmymoney->toggleAction("view_show_all_accounts")->isChecked() + 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(); } // and tell everyone what's selected emit accountSelected(d->m_account); } void KInvestmentView::loadInvestmentItem(const MyMoneyAccount& account) { 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, "---"); } 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)); //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); } void KInvestmentView::showEvent(QShowEvent* event) { 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()); // 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) { 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())); } void KInvestmentView::slotUpdateSecuritiesButtons() { 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)); } else { m_editSecurityButton->setEnabled(false); m_deleteSecurityButton->setEnabled(false); } } void KInvestmentView::slotEditSecurity() { QTreeWidgetItem* item = m_securitiesList->currentItem(); if (item) { MyMoneySecurity security = MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()); 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; } } void KInvestmentView::slotDeleteSecurity() { QTreeWidgetItem* item = m_securitiesList->currentItem(); if (item) KMyMoneyUtils::deleteSecurity(MyMoneyFile::instance()->security(item->text(eIdColumn).toLatin1()), this); } diff --git a/kmymoney/views/kmymoneyview.h b/kmymoney/views/kmymoneyview.h index 7de80e198..0c74bc0a2 100644 --- a/kmymoney/views/kmymoneyview.h +++ b/kmymoney/views/kmymoneyview.h @@ -1,672 +1,672 @@ /*************************************************************************** kmymoneyview.h ------------------- copyright : (C) 2000-2001 by Michael Edwardes email : ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYVIEW_H #define KMYMONEYVIEW_H // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "config-kmymoney.h" #include "mymoneyaccount.h" #include "mymoneyinstitution.h" #include "mymoneytransaction.h" #include "mymoneyschedule.h" #include "mymoneysecurity.h" #include "selectedtransaction.h" #ifdef KF5Activities_FOUND namespace KActivities { class ResourceInstance; } #endif class KHomeView; class KAccountsView; class KCategoriesView; class KInstitutionsView; class KPayeesView; class KTagsView; class KBudgetView; class KScheduledView; class KGlobalLedgerView; class IMyMoneyStorageFormat; class MyMoneyTransaction; class KInvestmentView; class KReportsView; class KMyMoneyViewBase; class MyMoneyReport; class TransactionEditor; class KForecastView; class KOnlineJobOutbox; class KMyMoneyTitleLabel; /** * This class represents the view of the MyMoneyFile which contains * Banks/Accounts/Transactions, Recurring transactions (or Bills & Deposits) * and scripts (yet to be implemented). Each different aspect of the file * is represented by a tab within the view. * * @author Michael Edwardes 2001 Copyright 2000-2001 * * @short Handles the view of the MyMoneyFile. */ class KMyMoneyView : public KPageWidget { Q_OBJECT public: enum viewID { HomeView = 0, AccountsView, InstitutionsView, SchedulesView, CategoriesView, PayeesView, LedgersView, InvestmentsView, ReportsView, BudgetView, ForecastView, OnlineJobOutboxView }; // file actions for plugin enum fileActions { preOpen, postOpen, preSave, postSave, preClose, postClose }; KOnlineJobOutbox* getOnlineJobOutbox() const { return m_onlineJobOutboxView; } private: enum menuID { AccountNew = 1, AccountOpen, AccountReconcile, AccountEdit, AccountDelete, AccountOnlineMap, AccountOnlineUpdate, AccountOfxConnect, CategoryNew }; enum storageTypeE { Memory = 0, Database } _storageType; KPageWidgetModel* m_model; KHomeView *m_homeView; KAccountsView *m_accountsView; KInstitutionsView *m_institutionsView; KCategoriesView *m_categoriesView; KPayeesView *m_payeesView; KTagsView *m_tagsView; KBudgetView *m_budgetView; KScheduledView *m_scheduledView; KGlobalLedgerView *m_ledgerView; KInvestmentView *m_investmentView; KReportsView* m_reportsView; KForecastView* m_forecastView; KOnlineJobOutbox* m_onlineJobOutboxView; KPageWidgetItem* m_homeViewFrame; KPageWidgetItem* m_accountsViewFrame; KPageWidgetItem* m_institutionsViewFrame; KPageWidgetItem* m_categoriesViewFrame; KPageWidgetItem* m_payeesViewFrame; KPageWidgetItem* m_tagsViewFrame; KPageWidgetItem* m_budgetViewFrame; KPageWidgetItem* m_scheduleViewFrame; KPageWidgetItem* m_ledgerViewFrame; KPageWidgetItem* m_investmentViewFrame; KPageWidgetItem* m_reportsViewFrame; KPageWidgetItem* m_forecastViewFrame; KPageWidgetItem* m_onlineJobOutboxViewFrame; KMyMoneyTitleLabel* m_header; bool m_inConstructor; bool m_fileOpen; mode_t m_fmode; int m_lastViewSelected; // Keep a note of the file type typedef enum _fileTypeE { KmmBinary = 0, // native, binary KmmXML, // native, XML KmmDb, // SQL database /* insert new native file types above this line */ MaxNativeFileType, /* and non-native types below */ GncXML // Gnucash XML } fileTypeE; fileTypeE m_fileType; #ifdef KF5Activities_FOUND private: KActivities::ResourceInstance * m_activityResourceInstance; #endif private: void ungetString(QIODevice *qfile, char * buf, int len); /** * if no base currency is defined, start the dialog and force it to be set */ void selectBaseCurrency(); /** * This method attaches an empty storage object to the MyMoneyFile * object. It calls removeStorage() to remove a possibly attached * storage object. */ void newStorage(storageTypeE = Memory); /** * This method removes an attached storage from the MyMoneyFile * object. */ void removeStorage(); void viewAccountList(const QString& selectAccount); // Show the accounts view static void progressCallback(int current, int total, const QString&); /** */ void fixFile_0(); void fixFile_1(); void fixFile_2(); void fixFile_3(); /** */ void fixLoanAccount_0(MyMoneyAccount acc); /** */ void fixTransactions_0(); void fixSchedule_0(MyMoneySchedule sched); void fixDuplicateAccounts_0(MyMoneyTransaction& t); void createSchedule(MyMoneySchedule s, MyMoneyAccount& a); void checkAccountName(const MyMoneyAccount& acc, const QString& name) const; public: /** * The constructor for KMyMoneyView. Just creates all the tabs for the * different aspects of the MyMoneyFile. */ explicit KMyMoneyView(QWidget *parent = 0); /** * Destructor */ ~KMyMoneyView(); /** * Makes sure that a MyMoneyFile is open and has been created successfully. * * @return Whether the file is open and initialised */ bool fileOpen(); /** * Closes the open MyMoneyFile and frees all the allocated memory, I hope ! */ void closeFile(); /** * Calls MyMoneyFile::readAllData which reads a MyMoneyFile into appropriate * data structures in memory. The return result is examined to make sure no * errors occurred whilst parsing. * * @param url The URL to read from. * If no protocol is specified, file:// is assumed. * * @return Whether the read was successful. */ bool readFile(const QUrl &url); /** * Saves the data into permanent storage using the XML format. * * @param url The URL to save into. * If no protocol is specified, file:// is assumed. * @param keyList QString containing a comma separated list of keys * to be used for encryption. If @p keyList is empty, * the file will be saved unencrypted (the default) * * @retval false save operation failed * @retval true save operation was successful */ bool saveFile(const QUrl &url, const QString& keyList = QString()); /** * Saves the data into permanent storage on a new or empty SQL database. * * @param url The pseudo of tyhe database * * @retval false save operation failed * @retval true save operation was successful */ //const bool saveDatabase(const QUrl &url); This no longer relevant /** * Saves the data into permanent storage on a new or empty SQL database. * * @param url The pseudo URL of the database * * @retval false save operation failed * @retval true save operation was successful */ bool saveAsDatabase(const QUrl &url); /** * Call this to find out if the currently open file is native KMM * * @retval true file is native * @retval false file is foreign */ bool isNativeFile() { return (m_fileOpen && (m_fileType < MaxNativeFileType)); } /** * Call this to find out if the currently open file is a sql database * * @retval true file is database * @retval false file is serial */ bool isDatabase() { return (m_fileOpen && ((m_fileType == KmmDb))); } /** * Call this to see if the MyMoneyFile contains any unsaved data. * * @retval true if any data has been modified but not saved * @retval false otherwise */ bool dirty(); /** * Close the currently opened file and create an empty new file. * * @see MyMoneyFile */ void newFile(); /** * This method enables the state of all views (except home view) according * to an open file. */ void enableViewsIfFileOpen(); KMyMoneyViewBase* addBasePage(const QString& title, const QString& icon = QString()); void addWidget(QWidget* w); void showPage(KPageWidgetItem* pageItem); /** * check if the current view allows to create a transaction * * @param list list of selected transactions * @param tooltip reference to string receiving the tooltip text * which explains why the modify function is not available (in case * of returning @c false) * * @retval true Yes, view allows to create a transaction (tooltip is not changed) * @retval false No, view cannot to create a transaction (tooltip is updated with message) */ bool canCreateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; /** * check if the current view allows to modify (edit/delete) the selected transactions * * @param list list of selected transactions * @param tooltip reference to string receiving the tooltip text * which explains why the modify function is not available (in case * of returning @c false) * * @retval true Yes, view allows to edit/delete transactions (tooltip is not changed) * @retval false No, view cannot edit/delete transactions (tooltip is updated with message) */ bool canModifyTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; bool canDuplicateTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; /** * check if the current view allows to edit the selected transactions * * @param list list of selected transactions * @param tooltip reference to string receiving the tooltip text * which explains why the edit function is not available (in case * of returning @c false) * * @retval true Yes, view allows to enter/edit transactions * @retval false No, view cannot enter/edit transactions */ bool canEditTransactions(const KMyMoneyRegister::SelectedTransactions& list, QString& tooltip) const; /** * check if the current view allows to print something * * @retval true Yes, view allows to print * @retval false No, view cannot print */ bool canPrint(); TransactionEditor* startEdit(const KMyMoneyRegister::SelectedTransactions&); bool createNewTransaction(); /** * Used to start reconciliation of account @a account. It switches the * ledger view into reconciliation mode and updates the view. * * @param account account which should be reconciled * @param reconciliationDate the statement date * @param endingBalance the ending balance entered for this account * * @retval true Reconciliation started * @retval false Account cannot be reconciled */ bool startReconciliation(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); /** * Used to finish reconciliation of account @a account. It switches the * ledger view to normal mode and updates the view. * * @param account account which should be reconciled */ void finishReconciliation(const MyMoneyAccount& account); /** * This method updates names of currencies from file to localized names */ void updateCurrencyNames(); /** * This method loads all known currencies and saves them to the storage */ void loadAllCurrencies(); void showTitleBar(bool show); /** * This method changes the view type according to the settings. */ void updateViewType(); protected: /** * Overwritten because KMyMoney has it's custom header. */ virtual bool showPageHeader() const; public slots: /** * This slot writes information about the page passed as argument @a current * in the kmymoney.rc file so that it can be selected automatically when * the application is started again. * * @param current QModelIndex of the current page item * @param previous QModelIndex of the previous page item */ void slotCurrentPageChanged(const QModelIndex current, const QModelIndex previous); /** * Brings up a dialog to change the list(s) settings and saves them into the * class KMyMoneySettings (a singleton). * * @see KListSettingsDlg * Refreshes all views. Used e.g. after settings have been changed or * data has been loaded from external sources (QIF import). **/ void slotRefreshViews(); /** * Called, whenever the ledger view should pop up and a specific * transaction in an account should be shown. If @p transaction * is empty, the last transaction should be selected * * @param acc The ID of the account to be shown * @param transaction The ID of the transaction to be selected */ void slotLedgerSelected(const QString& acc, const QString& transaction = QString()); /** * Called, whenever the payees view should pop up and a specific * transaction in an account should be shown. * * @param payeeId The ID of the payee to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotPayeeSelected(const QString& payeeId, const QString& accountId, const QString& transactionId); /** * Called, whenever the tags view should pop up and a specific * transaction in an account should be shown. * * @param tagId The ID of the tag to be shown * @param accountId The ID of the account to be shown * @param transactionId The ID of the transaction to be selected */ void slotTagSelected(const QString& tagId, const QString& accountId, const QString& transactionId); /** * Called, whenever the schedule view should pop up and a specific * schedule should be shown. * * @param schedule The ID of the schedule to be shown */ void slotScheduleSelected(const QString& schedule); /** * Called, whenever the report view should pop up and a specific * report should be shown. * * @param reportid The ID of the report to be shown */ void slotShowReport(const QString& reportid); /** * Same as the above, but the caller passes in an actual report * definition to be shown. * * @param report The report to be shown */ void slotShowReport(const MyMoneyReport& report); /** * This slot prints the current view. */ void slotPrintView(); /** * This slot switches the view to present the home page */ void slotShowHomePage() { setCurrentPage(m_homeViewFrame); } -protected slots: /** * Called when the user changes the detail * setting of the transaction register * * @param detailed if true, the register is shown with all details */ void slotShowTransactionDetail(bool detailed); +protected slots: /** * eventually replace this with KMyMoneyApp::slotCurrencySetBase(). * it contains the same code * * @deprecated */ void slotSetBaseCurrency(const MyMoneySecurity& baseCurrency); private: /** * This method is called from readFile to open a database file which * is to be processed in 'proper' database mode, i.e. in-place updates * * @param dbaseURL pseudo-QUrl representation of database * * @retval true Database opened successfully * @retval false Could not open or read database */ bool openDatabase(const QUrl &dbaseURL); /** * This method is used after a file or database has been * read into storage, and performs various initialization tasks * * @retval true all went okay * @retval false an exception occurred during this process */ bool initializeStorage(); /** * This method is used by saveFile() to store the data * either directly in the destination file if it is on * the local file system or in a temporary file when * the final destination is reached over a network * protocol (e.g. FTP) * * @param localFile the name of the local file * @param writer pointer to the formatter * @param plaintext whether to override any compression & encryption settings * @param keyList QString containing a comma separated list of keys to be used for encryption * If @p keyList is empty, the file will be saved unencrypted * * @note This method will close the file when it is written. */ void saveToLocalFile(const QString& localFile, IMyMoneyStorageFormat* writer, bool plaintext = false, const QString& keyList = QString()); /** * Internal method used by slotAccountNew() and slotAccountCategory(). */ void accountNew(const bool createCategory); signals: /** * This signal is emitted whenever a view is selected. * The parameter @p view is identified as one of KMyMoneyView::viewID. */ void viewActivated(int view); /** * This signal is emitted whenever a new view is about to be selected. */ void aboutToChangeView(); void accountSelectedForContextMenu(const MyMoneyAccount& acc); void viewStateChanged(bool enabled); /** * This signal is emitted to inform the kmmFile plugin when various file actions * occur. The Action parameter distinguishes between them. */ void kmmFilePlugin(unsigned int action); /** * Signal is emitted when reconciliation starts or ends. In case of end, * @a account is MyMoneyAccount() * * @param account account for which reconciliation starts or MyMoneyAccount() * if reconciliation ends. * @param reconciliationDate the statement date * @param endingBalance collected ending balance when reconciliation starts * 0 otherwise */ void reconciliationStarts(const MyMoneyAccount& account, const QDate& reconciliationDate, const MyMoneyMoney& endingBalance); /** * This signal is emitted after a data source has been closed */ void fileClosed(); /** * This signal is emitted after a data source has been opened */ void fileOpened(); }; /** * This class is an abstract base class that all specific views * should be based on. */ class KMyMoneyViewBase : public QWidget { Q_OBJECT public: KMyMoneyViewBase(QWidget* parent, const QString& name, const QString& title); virtual ~KMyMoneyViewBase(); void setTitle(const QString& title); QVBoxLayout* layout() const; void addWidget(QWidget* w); /** * This method is used to edit the currently selected transactions * The default implementation returns @p false which signals to the caller, that * the view was not capable to edit the transactions. * * @retval false view was not capable to edit transactions * @retval true view was capable to edit the transactions and did so */ bool editTransactions(const QList& transactions) const { Q_UNUSED(transactions) return false; } signals: /** * This signal is emitted whenever the view is about to be shown. */ void aboutToShow(); private: /// \internal d-pointer class. class Private; /// \internal d-pointer instance. Private* const d; }; #endif diff --git a/kmymoney/views/konlinejoboutbox.cpp b/kmymoney/views/konlinejoboutbox.cpp index a0476f04e..3b219d8b3 100644 --- a/kmymoney/views/konlinejoboutbox.cpp +++ b/kmymoney/views/konlinejoboutbox.cpp @@ -1,234 +1,235 @@ /* * This file is part of KMyMoney, A Personal Finance Manager for KDE * Copyright (C) 2013-2014 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This 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 "konlinejoboutbox.h" #include "ui_konlinejoboutbox.h" #include #include #include #include #include #include +#include #include "models/models.h" #include "models/onlinejobmodel.h" #include "mymoney/mymoneyfile.h" #include "kmymoney.h" #include KOnlineJobOutbox::KOnlineJobOutbox(QWidget *parent) : QWidget(parent), ui(new Ui::KOnlineJobOutbox) { ui->setupUi(this); // Restore column state KConfigGroup configGroup = KSharedConfig::openConfig()->group("KOnlineJobOutbox"); QByteArray columns; columns = configGroup.readEntry("HeaderState", columns); ui->m_onlineJobView->header()->restoreState(columns); ui->m_onlineJobView->setModel(Models::instance()->onlineJobsModel()); connect(ui->m_buttonSend, SIGNAL(clicked()), this, SLOT(slotSendJobs())); connect(ui->m_buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveJob())); connect(ui->m_buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditJob())); connect(ui->m_onlineJobView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotEditJob(QModelIndex))); connect(ui->m_onlineJobView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(updateButtonState())); // Set new credit transfer button - connect(kmymoney->action("account_online_new_credit_transfer"), SIGNAL(changed()), SLOT(updateNewCreditTransferButton())); + connect(kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountCreditTransfer]), SIGNAL(changed()), SLOT(updateNewCreditTransferButton())); connect(ui->m_buttonNewCreditTransfer, SIGNAL(clicked()), this, SIGNAL(newCreditTransfer())); updateNewCreditTransferButton(); } KOnlineJobOutbox::~KOnlineJobOutbox() { // Save column state KConfigGroup configGroup = KSharedConfig::openConfig()->group("KOnlineJobOutbox"); configGroup.writeEntry("HeaderState", ui->m_onlineJobView->header()->saveState()); } void KOnlineJobOutbox::updateButtonState() const { const QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedRows(); const int selectedItems = indexes.count(); // Send button //! @todo Enable button if it is useful //ui->m_buttonSend->setEnabled(selectedItems > 0); // Edit button/action bool editable = true; QString tooltip; if (selectedItems == 1) { const onlineJob job = ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobRole).value(); if (!job.isEditable()) { editable = false; if (job.sendDate().isValid()) tooltip = i18n("This job cannot be edited anymore because is was sent already."); else if (job.isLocked()) tooltip = i18n("Job is being processed at the moment."); else Q_ASSERT(false); } else if (!onlineJobAdministration::instance()->canEditOnlineJob(job)) { editable = false; tooltip = i18n("The plugin to edit this job is not available."); } } else { editable = false; tooltip = i18n("You must select a single job for editing."); } - QAction *const onlinejob_edit = kmymoney->action("onlinejob_edit"); + QAction *const onlinejob_edit = kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::OnlineJobEdit]); Q_CHECK_PTR(onlinejob_edit); onlinejob_edit->setEnabled(editable); onlinejob_edit->setToolTip(tooltip); ui->m_buttonEdit->setEnabled(editable); ui->m_buttonEdit->setToolTip(tooltip); // Delete button/action - QAction *const onlinejob_delete = kmymoney->action("onlinejob_delete"); + QAction *const onlinejob_delete = kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::OnlineJobDelete]); Q_CHECK_PTR(onlinejob_delete); onlinejob_delete->setEnabled(selectedItems > 0); ui->m_buttonRemove->setEnabled(onlinejob_delete->isEnabled()); } void KOnlineJobOutbox::updateNewCreditTransferButton() { - QAction* action = kmymoney->action("account_online_new_credit_transfer"); + QAction* action = kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::AccountCreditTransfer]); Q_CHECK_PTR(action); ui->m_buttonNewCreditTransfer->setEnabled(action->isEnabled()); } void KOnlineJobOutbox::slotRemoveJob() { QAbstractItemModel* model = ui->m_onlineJobView->model(); QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedRows(); while (!indexes.isEmpty()) { model->removeRow(indexes.at(0).row()); indexes = ui->m_onlineJobView->selectionModel()->selectedRows(); } } QStringList KOnlineJobOutbox::selectedOnlineJobs() const { QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedRows(); if (indexes.isEmpty()) return QStringList(); QStringList list; list.reserve(indexes.count()); const QAbstractItemModel *const model = ui->m_onlineJobView->model(); Q_FOREACH(const QModelIndex& index, indexes) { list.append(model->data(index, onlineJobModel::OnlineJobId).toString()); } return list; } void KOnlineJobOutbox::slotSendJobs() { if (ui->m_onlineJobView->selectionModel()->hasSelection()) slotSendSelectedJobs(); else slotSendAllSendableJobs(); } void KOnlineJobOutbox::slotSendAllSendableJobs() { QList validJobs; foreach (const onlineJob& job, MyMoneyFile::instance()->onlineJobList()) { if (job.isValid() && job.isEditable()) validJobs.append(job); } qDebug() << "I shall send " << validJobs.count() << "/" << MyMoneyFile::instance()->onlineJobList().count() << " onlineJobs"; if (!validJobs.isEmpty()) emit sendJobs(validJobs); } void KOnlineJobOutbox::slotSendSelectedJobs() { QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedRows(); if (indexes.isEmpty()) return; // Valid jobs to send QList validJobs; validJobs.reserve(indexes.count()); // Get valid jobs const QAbstractItemModel *const model = ui->m_onlineJobView->model(); foreach (const QModelIndex& index, indexes) { onlineJob job = model->data(index, onlineJobModel::OnlineJobRole).value(); if (job.isValid() && job.isEditable()) validJobs.append(job); } // Abort if not all jobs can be sent if (validJobs.count() != indexes.count()) { QMessageBox::information(this, i18nc("The user selected credit transfers to send. But they cannot be sent.", "Cannot send selection"), i18n("Not all selected credit transfers can be sent because some of them are invalid or were already sent.")); return; } emit sendJobs(validJobs); } void KOnlineJobOutbox::slotEditJob() { QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedIndexes(); if (!indexes.isEmpty()) { QString jobId = ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobId).toString(); Q_ASSERT(!jobId.isEmpty()); emit editJob(jobId); } } void KOnlineJobOutbox::slotEditJob(const QModelIndex &index) { QString jobId = ui->m_onlineJobView->model()->data(index, onlineJobModel::OnlineJobId).toString(); emit editJob(jobId); } void KOnlineJobOutbox::contextMenuEvent(QContextMenuEvent*) { QModelIndexList indexes = ui->m_onlineJobView->selectionModel()->selectedIndexes(); if (!indexes.isEmpty()) { onlineJob job = ui->m_onlineJobView->model()->data(indexes.first(), onlineJobModel::OnlineJobRole).value(); emit showContextMenu(job); } } /** * Do not know why this is needed, but all other views in KMyMoney have it. */ void KOnlineJobOutbox::showEvent(QShowEvent* event) { emit aboutToShow(); // don't forget base class implementation QWidget::showEvent(event); } diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp index 0ff954d48..529e55183 100644 --- a/kmymoney/views/kpayeesview.cpp +++ b/kmymoney/views/kpayeesview.cpp @@ -1,973 +1,974 @@ /*************************************************************************** kpayeesview.cpp --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kpayeesview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes #include "config-kmymoney.h" #include "mymoneyfile.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include "mymoneysecurity.h" #include using namespace Icons; // *** KPayeeListItem Implementation *** KPayeeListItem::KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) : QListWidgetItem(parent, QListWidgetItem::UserType), m_payee(payee) { setText(payee.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } KPayeeListItem::~KPayeeListItem() { } // *** KPayeesView Implementation *** KPayeesView::KPayeesView(QWidget *parent) : QWidget(parent), m_contact(new MyMoneyContact(this)), m_needReload(false), m_inSelection(false), m_allowEditing(true), m_payeeFilterType(0) { setupUi(this); m_filterProxyModel = new AccountNamesFilterProxyModel(this); m_filterProxyModel->setHideEquityAccounts(!KMyMoneyGlobalSettings::expertMode()); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Equity); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->sort(0); comboDefaultCategory->setModel(m_filterProxyModel); matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), MyMoneyPayee::matchDisabled); matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), MyMoneyPayee::matchName); matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), MyMoneyPayee::matchNameExact); matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), MyMoneyPayee::matchKey); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(this, m_payeesList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu); m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type m_filterBox->addItem(i18nc("@item Show all payees", "All")); m_filterBox->addItem(i18nc("@item Show only used payees", "Used")); m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused")); m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); KGuiItem newButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListAddUser]), i18n("Creates a new payee"), i18n("Use this to create a new payee.")); KGuiItem::assign(m_newButton, newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::UserProperties]), i18n("Rename the current selected payee"), i18n("Use this to start renaming the selected payee.")); KGuiItem::assign(m_renameButton, renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListRemoveUser]), i18n("Delete selected payee(s)"), i18n("Use this to delete the selected payee. You can also select " "multiple payees to be deleted.")); KGuiItem::assign(m_deleteButton, deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem mergeButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::Merge]), i18n("Merge multiple selected payees"), i18n("Use this to merge multiple selected payees.")); KGuiItem::assign(m_mergeButton, mergeButtonItem); m_mergeButton->setToolTip(mergeButtonItem.toolTip()); KGuiItem updateButtonItem(i18nc("Update payee", "Update"), QIcon::fromTheme(g_Icons[Icon::DialogOK]), i18n("Accepts the entered data and stores it"), i18n("Use this to accept the modified data.")); KGuiItem::assign(m_updateButton, updateButtonItem); KGuiItem syncButtonItem(i18nc("Sync payee", "Sync"), QIcon::fromTheme(g_Icons[Icon::Refresh]), i18n("Fetches the payee's data from your addressbook."), i18n("Use this to fetch payee's data.")); KGuiItem::assign(m_syncAddressbook, syncButtonItem); KGuiItem sendMailButtonItem(i18nc("Send mail", "Send"), QIcon::fromTheme(g_Icons[Icon::MailMessage]), i18n("Creates new e-mail to your payee."), i18n("Use this to create new e-mail to your payee.")); KGuiItem::assign(m_sendMail, sendMailButtonItem); m_updateButton->setEnabled(false); m_syncAddressbook->setEnabled(false); #ifndef KMM_ADDRESSBOOK_FOUND m_syncAddressbook->hide(); #endif matchTypeCombo->setCurrentIndex(0); checkMatchIgnoreCase->setEnabled(false); checkEnableDefaultCategory->setChecked(false); labelDefaultCategory->setEnabled(false); comboDefaultCategory->setEnabled(false); QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_register->setupRegister(MyMoneyAccount(), cols); m_register->setSelectionMode(QTableWidget::SingleSelection); m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst); m_balanceLabel->hide(); connect(m_contact, SIGNAL(contactFetched(ContactData)), this, SLOT(slotContactFetched(ContactData))); connect(m_payeesList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectPayee(QListWidgetItem*,QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectPayee())); connect(m_payeesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenamePayee(QListWidgetItem*))); connect(m_payeesList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked())); - connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("payee_delete"), SLOT(trigger())); - connect(m_mergeButton, SIGNAL(clicked()), kmymoney->action("payee_merge"), SLOT(trigger())); + connect(m_deleteButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeDelete]), SLOT(trigger())); + connect(m_mergeButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeMerge]), SLOT(trigger())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotPayeeNew())); connect(addressEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(postcodeEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(telephoneEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(emailEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(notesEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(matchKeyEditList, SIGNAL(changed()), this, SLOT(slotKeyListChanged())); connect(matchTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeDataChanged())); connect(checkMatchIgnoreCase, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(checkEnableDefaultCategory, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(comboDefaultCategory, SIGNAL(accountSelected(QString)), this, SLOT(slotPayeeDataChanged())); connect(buttonSuggestACategory, SIGNAL(clicked()), this, SLOT(slotChooseDefaultAccount())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdatePayee())); connect(m_syncAddressbook, SIGNAL(clicked()), this, SLOT(slotSyncAddressBook())); connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_sendMail, SIGNAL(clicked()), this, SLOT(slotSendMail())); connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadPayees())); connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int))); connect(payeeIdentifiers, SIGNAL(dataChanged()), this, SLOT(slotPayeeDataChanged())); // use the size settings of the last run (if any) KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); //At start we haven't any payee selected m_tabWidget->setEnabled(false); // disable tab widget m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee clearItemData(); } KPayeesView::~KPayeesView() { // remember the splitter settings for startup KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KPayeesViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KPayeesView::slotChooseDefaultAccount() { MyMoneyFile* file = MyMoneyFile::instance(); QMap account_count; KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); while (item) { //only walk through selectable items. eg. transactions and not group markers if (item->isSelectable()) { KMyMoneyRegister::Transaction* t = dynamic_cast(item); MyMoneySplit s = t->transaction().splitByPayee(m_payee.id()); const MyMoneyAccount& acc = file->account(s.accountId()); QString txt; if (s.action() != MyMoneySplit::ActionAmortization && acc.accountType() != MyMoneyAccount::AssetLoan && !file->isTransfer(t->transaction()) && t->transaction().splitCount() == 2) { MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false); if (account_count.contains(s0.accountId())) { account_count[s0.accountId()]++; } else { account_count[s0.accountId()] = 1; } } } item = item->nextItem(); } QMap::Iterator most_frequent, iter; most_frequent = account_count.begin(); for (iter = account_count.begin(); iter != account_count.end(); ++iter) { if (iter.value() > most_frequent.value()) { most_frequent = iter; } } if (most_frequent != account_count.end()) { checkEnableDefaultCategory->setChecked(true); comboDefaultCategory->setSelected(most_frequent.key()); setDirty(); } } void KPayeesView::slotStartRename(QListWidgetItem* item) { m_allowEditing = true; m_payeesList->editItem(item); } void KPayeesView::slotRenameButtonCliked() { if (m_payeesList->currentItem() && m_payeesList->selectedItems().count() == 1) { slotStartRename(m_payeesList->currentItem()); } } // This variant is only called when a single payee is selected and renamed. void KPayeesView::slotRenamePayee(QListWidgetItem* p) { //if there is no current item selected, exit if (m_allowEditing == false || !m_payeesList->currentItem() || p != m_payeesList->currentItem()) return; //qDebug() << "[KPayeesView::slotRenamePayee]"; // create a copy of the new name without appended whitespaces QString new_name = p->text(); if (m_payee.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a payee with the new name try { // this function call will throw an exception, if the payee // hasn't been found. MyMoneyFile::instance()->payeeByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A payee with the name '%1' already exists. It is not advisable to have " "multiple payees with the same identification name. Are you sure you would like " "to rename the payee?", new_name)) != KMessageBox::Yes) { p->setText(m_payee.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } m_payee.setName(new_name); m_newName = new_name; MyMoneyFile::instance()->modifyPayee(m_payee); // the above call to modifyPayee will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation ensurePayeeVisible(m_payee.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { p->setText(new_name); } } void KPayeesView::ensurePayeeVisible(const QString& id) { for (int i = 0; i < m_payeesList->count(); ++i) { KPayeeListItem* p = dynamic_cast(m_payeesList->item(0)); if (p && p->payee().id() == id) { m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(p); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KPayeesView::selectedPayees(QList& payeesList) const { QList selectedItems = m_payeesList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*itemsIt); if (item) payeesList << item->payee(); ++itemsIt; } } void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev) { Q_UNUSED(cur); Q_UNUSED(prev); m_allowEditing = false; } void KPayeesView::slotSelectPayee() { // check if the content of a currently selected payee was modified // and ask to store the data if (isDirty()) { QString question = QString("%1").arg(i18n("Do you want to save the changes for %1?", m_newName)); if (KMessageBox::questionYesNo(this, question, i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdatePayee(); m_inSelection = false; } } // make sure we always clear the selected list when listing again m_selectedPayeesList.clear(); // loop over all payees and count the number of payees, also // obtain last selected payee selectedPayees(m_selectedPayeesList); emit selectObjects(m_selectedPayeesList); if (m_selectedPayeesList.isEmpty()) { m_tabWidget->setEnabled(false); // disable tab widget m_balanceLabel->hide(); m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); clearItemData(); m_payee = MyMoneyPayee(); m_syncAddressbook->setEnabled(false); return; // make sure we don't access an undefined payee } m_deleteButton->setEnabled(true); //re-enable delete button m_syncAddressbook->setEnabled(true); // if we have multiple payees selected, clear and disable the payee information if (m_selectedPayeesList.count() > 1) { m_tabWidget->setEnabled(false); // disable tab widget m_renameButton->setEnabled(false); // disable also the rename button m_mergeButton->setEnabled(true); m_balanceLabel->hide(); clearItemData(); } else { m_mergeButton->setEnabled(false); m_renameButton->setEnabled(true); } // otherwise we have just one selected, enable payee information widget m_tabWidget->setEnabled(true); m_balanceLabel->show(); // as of now we are updating only the last selected payee, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { m_payee = m_selectedPayeesList[0]; m_newName = m_payee.name(); addressEdit->setEnabled(true); addressEdit->setText(m_payee.address()); postcodeEdit->setEnabled(true); postcodeEdit->setText(m_payee.postcode()); telephoneEdit->setEnabled(true); telephoneEdit->setText(m_payee.telephone()); emailEdit->setEnabled(true); emailEdit->setText(m_payee.email()); notesEdit->setText(m_payee.notes()); QStringList keys; bool ignorecase = false; MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); matchTypeCombo->setCurrentIndex(matchTypeCombo->findData(type)); matchKeyEditList->clear(); matchKeyEditList->insertStringList(keys); checkMatchIgnoreCase->setChecked(ignorecase); checkEnableDefaultCategory->setChecked(m_payee.defaultAccountEnabled()); comboDefaultCategory->setSelected(m_payee.defaultAccountId()); payeeIdentifiers->setSource(m_payee); slotPayeeDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of payee: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); m_register->clear(); m_selectedPayeesList.clear(); m_payee = MyMoneyPayee(); } m_allowEditing = true; } void KPayeesView::clearItemData() { addressEdit->setText(QString()); postcodeEdit->setText(QString()); telephoneEdit->setText(QString()); emailEdit->setText(QString()); notesEdit->setText(QString()); showTransactions(); } void KPayeesView::showTransactions() { MyMoneyMoney balance; MyMoneyFile *file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear the register m_register->clear(); if (m_selectedPayeesList.isEmpty() || !m_tabWidget->isEnabled()) { m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; for (QList::const_iterator it = m_selectedPayeesList.constBegin(); it != m_selectedPayeesList.constEnd(); ++it) filter.addPayee((*it).id()); filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here m_register->setUpdatesEnabled(true); m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KPayeesView::slotKeyListChanged() { bool rc = false; bool ignorecase = false; QStringList keys; m_payee.matchData(ignorecase, keys); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { rc |= (keys != matchKeyEditList->items()); } setDirty(rc); } void KPayeesView::slotPayeeDataChanged() { bool rc = false; if (m_tabWidget->isEnabled()) { rc |= ((m_payee.email().isEmpty() != emailEdit->text().isEmpty()) || (!emailEdit->text().isEmpty() && m_payee.email() != emailEdit->text())); rc |= ((m_payee.address().isEmpty() != addressEdit->toPlainText().isEmpty()) || (!addressEdit->toPlainText().isEmpty() && m_payee.address() != addressEdit->toPlainText())); rc |= ((m_payee.postcode().isEmpty() != postcodeEdit->text().isEmpty()) || (!postcodeEdit->text().isEmpty() && m_payee.postcode() != postcodeEdit->text())); rc |= ((m_payee.telephone().isEmpty() != telephoneEdit->text().isEmpty()) || (!telephoneEdit->text().isEmpty() && m_payee.telephone() != telephoneEdit->text())); rc |= ((m_payee.name().isEmpty() != m_newName.isEmpty()) || (!m_newName.isEmpty() && m_payee.name() != m_newName)); rc |= ((m_payee.notes().isEmpty() != notesEdit->toPlainText().isEmpty()) || (!notesEdit->toPlainText().isEmpty() && m_payee.notes() != notesEdit->toPlainText())); bool ignorecase = false; QStringList keys; MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); rc |= (static_cast(type) != matchTypeCombo->currentData().toUInt()); checkMatchIgnoreCase->setEnabled(false); matchKeyEditList->setEnabled(false); if (matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) { checkMatchIgnoreCase->setEnabled(true); // if we turn matching on, we default to 'ignore case' // TODO maybe make the default a user option if (type == MyMoneyPayee::matchDisabled && matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) checkMatchIgnoreCase->setChecked(true); rc |= (ignorecase != checkMatchIgnoreCase->isChecked()); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { matchKeyEditList->setEnabled(true); rc |= (keys != matchKeyEditList->items()); } } rc |= (checkEnableDefaultCategory->isChecked() != m_payee.defaultAccountEnabled()); if (checkEnableDefaultCategory->isChecked()) { comboDefaultCategory->setEnabled(true); labelDefaultCategory->setEnabled(true); // this is only going to understand the first in the list of selected accounts if (comboDefaultCategory->getSelected().isEmpty()) { rc |= !m_payee.defaultAccountId().isEmpty(); } else { QString temp = comboDefaultCategory->getSelected(); rc |= (temp.isEmpty() != m_payee.defaultAccountId().isEmpty()) || (!m_payee.defaultAccountId().isEmpty() && temp != m_payee.defaultAccountId()); } } else { comboDefaultCategory->setEnabled(false); labelDefaultCategory->setEnabled(false); } rc |= (m_payee.payeeIdentifiers() != payeeIdentifiers->identifiers()); } setDirty(rc); } void KPayeesView::slotUpdatePayee() { if (isDirty()) { MyMoneyFileTransaction ft; setDirty(false); try { m_payee.setName(m_newName); m_payee.setAddress(addressEdit->toPlainText()); m_payee.setPostcode(postcodeEdit->text()); m_payee.setTelephone(telephoneEdit->text()); m_payee.setEmail(emailEdit->text()); m_payee.setNotes(notesEdit->toPlainText()); m_payee.setMatchData(static_cast(matchTypeCombo->currentData().toUInt()), checkMatchIgnoreCase->isChecked(), matchKeyEditList->items()); m_payee.setDefaultAccountId(); m_payee.resetPayeeIdentifiers(payeeIdentifiers->identifiers()); if (checkEnableDefaultCategory->isChecked()) { QString temp; if (!comboDefaultCategory->getSelected().isEmpty()) { temp = comboDefaultCategory->getSelected(); m_payee.setDefaultAccountId(temp); } } MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KPayeesView::slotSyncAddressBook() { if (m_payeeRows.isEmpty()) { // empty list means no syncing is pending... foreach (auto item, m_payeesList->selectedItems()) { m_payeeRows.append(m_payeesList->row(item)); // ...so initialize one } m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update // m_syncAddressbook->setEnabled(false); // disallow concurent syncs } if (m_payeeRows.count() <= m_payeeRow) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { // update ui if something is selected m_payee = item->payee(); addressEdit->setText(m_payee.address()); postcodeEdit->setText(m_payee.postcode()); telephoneEdit->setText(m_payee.telephone()); } m_payeeRows.clear(); // that means end of sync m_payeeRow = 0; return; } KPayeeListItem* item = dynamic_cast(m_payeesList->item(m_payeeRows.at(m_payeeRow))); if (item) m_payee = item->payee(); ++m_payeeRow; m_contact->fetchContact(m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched } void KPayeesView::slotContactFetched(const ContactData &identity) { if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched QString txt; if (!identity.street.isEmpty()) txt.append(identity.street + "\n"); if (!identity.locality.isEmpty()) { txt.append(identity.locality); if (!identity.postalCode.isEmpty()) txt.append(' ' + identity.postalCode + "\n"); else txt.append("\n"); } if (!identity.country.isEmpty()) txt.append(identity.country + "\n"); if (!txt.isEmpty() && m_payee.address().compare(txt) != 0) m_payee.setAddress(txt); if (!identity.postalCode.isEmpty() && m_payee.postcode().compare(identity.postalCode) != 0) m_payee.setPostcode(identity.postalCode); if (!identity.phoneNumber.isEmpty() && m_payee.telephone().compare(identity.phoneNumber) != 0) m_payee.setTelephone(identity.phoneNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } slotSyncAddressBook(); // process next payee } void KPayeesView::slotSendMail() { QRegularExpression re(".+@.+"); if (re.match(m_payee.email()).hasMatch()) QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + m_payee.email(), QUrl::TolerantMode)); } void KPayeesView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { loadPayees(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); QList list; selectedPayees(list); emit selectObjects(list); } void KPayeesView::slotLoadPayees() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotLoadPayees())); else loadPayees(); } else { m_needReload = true; } } void KPayeesView::loadPayees() { if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) isSelected[item->payee().id()] = true; ++payeesIt; } // keep current selected item KPayeeListItem *currentItem = static_cast(m_payeesList->currentItem()); if (currentItem) id = currentItem->payee().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); m_payeesList->clear(); m_register->clear(); currentItem = 0; QListlist = file->payeeList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_payeeFilterType == eAllPayees || (m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) || (m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) { KPayeeListItem* item = new KPayeeListItem(m_payeesList, *it); if (item->payee().id() == id) currentItem = item; if (isSelected[item->payee().id()]) item->setSelected(true); } } m_payeesList->sortItems(); if (currentItem) { m_payeesList->setCurrentItem(currentItem); m_payeesList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); comboDefaultCategory->expandAll(); slotSelectPayee(0, 0); m_allowEditing = true; } void KPayeesView::slotSelectTransaction() { QList list = m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit transactionSelected(t->split().accountId(), t->transaction().id()); } } void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; try { // clear filter m_searchWidget->clear(); m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) item->setSelected(false); ++payeesIt; } // find the payee in the list QListWidgetItem* it; for (int i = 0; i < m_payeesList->count(); ++i) { it = m_payeesList->item(i); KPayeeListItem* item = dynamic_cast(it); if (item && item->payee().id() == payeeId) { m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(it); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the payee selection is updated and transactions are updated accordingly slotSelectPayee(); KMyMoneyRegister::RegisterItem *item = 0; for (int i = 0; i < m_register->rowCount(); ++i) { item = m_register->itemAtRow(i); KMyMoneyRegister::Transaction* t = dynamic_cast(item); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { m_register->selectItem(item); m_register->ensureItemVisible(item); break; } } } // quit out of for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", qPrintable(e.what())); } } void KPayeesView::slotOpenContextMenu(const QPoint& /*p*/) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { slotSelectPayee(); emit openContextMenu(item->payee()); } } void KPayeesView::slotPayeeNew() { - kmymoney->action("payee_new")->trigger(); + kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::PayeeNew])->trigger(); } void KPayeesView::slotHelp() { KHelpClient::invokeHelp("details.payees"); } void KPayeesView::slotChangeFilter(int index) { //update the filter type then reload the payees list m_payeeFilterType = index; loadPayees(); } bool KPayeesView::isDirty() const { return m_updateButton->isEnabled(); } void KPayeesView::setDirty(bool dirty) { m_updateButton->setEnabled(dirty); } diff --git a/kmymoney/views/kscheduledview.cpp b/kmymoney/views/kscheduledview.cpp index 70b260b37..56d75bcee 100644 --- a/kmymoney/views/kscheduledview.cpp +++ b/kmymoney/views/kscheduledview.cpp @@ -1,595 +1,596 @@ /*************************************************************************** kscheduledview.cpp - description ------------------- begin : Sun Jan 27 2002 copyright : (C) 2000-2002 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kscheduledview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include +#include // ---------------------------------------------------------------------------- // Project Includes #include "kmymoneyutils.h" #include "kmymoneyglobalsettings.h" #include "kscheduletreeitem.h" #include "ktreewidgetfilterlinewidget.h" #include "kmymoney.h" #include using namespace Icons; KScheduledView::KScheduledView(QWidget *parent) : QWidget(parent), m_openBills(true), m_openDeposits(true), m_openTransfers(true), m_openLoans(true) { setupUi(this); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KTreeWidgetFilterLineWidget(this, m_scheduleTree); vboxLayout->insertWidget(1, m_searchWidget); //enable custom context menu m_scheduleTree->setContextMenuPolicy(Qt::CustomContextMenu); m_scheduleTree->setSelectionMode(QAbstractItemView::SingleSelection); readConfig(); - connect(m_qbuttonNew, SIGNAL(clicked()), kmymoney->action("schedule_new"), SLOT(trigger())); + connect(m_qbuttonNew, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::ScheduleNew]), SLOT(trigger())); // attach popup to 'Filter...' button m_kaccPopup = new QMenu(this); m_accountsCombo->setMenu(m_kaccPopup); connect(m_kaccPopup, SIGNAL(triggered(QAction*)), this, SLOT(slotAccountActivated())); KGuiItem::assign(m_qbuttonNew, KMyMoneyUtils::scheduleNewGuiItem()); KGuiItem::assign(m_accountsCombo, KMyMoneyUtils::accountsFilterGuiItem()); connect(m_scheduleTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotListViewContextMenu(QPoint))); connect(m_scheduleTree, SIGNAL(itemSelectionChanged()), this, SLOT(slotSetSelectedItem())); connect(m_scheduleTree, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(slotListItemExecuted(QTreeWidgetItem*,int))); connect(m_scheduleTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(slotListViewExpanded(QTreeWidgetItem*))); connect(m_scheduleTree, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(slotListViewCollapsed(QTreeWidgetItem*))); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotReloadView())); } KScheduledView::~KScheduledView() { writeConfig(); } static bool accountNameLessThan(const MyMoneyAccount& acc1, const MyMoneyAccount& acc2) { return acc1.name().toLower() < acc2.name().toLower(); } void KScheduledView::refresh(bool full, const QString& schedId) { m_scheduleTree->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont()); m_scheduleTree->clear(); try { if (full) { try { m_kaccPopup->clear(); MyMoneyFile* file = MyMoneyFile::instance(); // extract a list of all accounts under the asset group // and sort them by name QList list; QStringList accountList = file->asset().accountList(); accountList.append(file->liability().accountList()); file->accountList(list, accountList, true); qStableSort(list.begin(), list.end(), accountNameLessThan); QList::ConstIterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { if (!(*it_a).isClosed()) { QAction* act; act = m_kaccPopup->addAction((*it_a).name()); act->setCheckable(true); act->setChecked(true); } } } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Unable to load accounts: "), e.what()); } } MyMoneyFile *file = MyMoneyFile::instance(); QList scheduledItems = file->scheduleList(); if (scheduledItems.count() == 0) return; //disable sorting for performance m_scheduleTree->setSortingEnabled(false); KScheduleTreeItem *itemBills = new KScheduleTreeItem(m_scheduleTree); itemBills->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewExpense])); itemBills->setText(0, i18n("Bills")); itemBills->setData(0, KScheduleTreeItem::OrderRole, QVariant("0")); itemBills->setFirstColumnSpanned(true); itemBills->setFlags(Qt::ItemIsEnabled); QFont bold = itemBills->font(0); bold.setBold(true); itemBills->setFont(0, bold); KScheduleTreeItem *itemDeposits = new KScheduleTreeItem(m_scheduleTree); itemDeposits->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewIncome])); itemDeposits->setText(0, i18n("Deposits")); itemDeposits->setData(0, KScheduleTreeItem::OrderRole, QVariant("1")); itemDeposits->setFirstColumnSpanned(true); itemDeposits->setFlags(Qt::ItemIsEnabled); itemDeposits->setFont(0, bold); KScheduleTreeItem *itemLoans = new KScheduleTreeItem(m_scheduleTree); itemLoans->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewLoan])); itemLoans->setText(0, i18n("Loans")); itemLoans->setData(0, KScheduleTreeItem::OrderRole, QVariant("2")); itemLoans->setFirstColumnSpanned(true); itemLoans->setFlags(Qt::ItemIsEnabled); itemLoans->setFont(0, bold); KScheduleTreeItem *itemTransfers = new KScheduleTreeItem(m_scheduleTree); itemTransfers->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewFinancialTransfer])); itemTransfers->setText(0, i18n("Transfers")); itemTransfers->setData(0, KScheduleTreeItem::OrderRole, QVariant("3")); itemTransfers->setFirstColumnSpanned(true); itemTransfers->setFlags(Qt::ItemIsEnabled); itemTransfers->setFont(0, bold); QList::Iterator it; QTreeWidgetItem *openItem = 0; for (it = scheduledItems.begin(); it != scheduledItems.end(); ++it) { MyMoneySchedule schedData = (*it); QTreeWidgetItem* item = 0; bool bContinue = true; QStringList::iterator accIt; for (accIt = m_filterAccounts.begin(); accIt != m_filterAccounts.end(); ++accIt) { if (*accIt == schedData.account().id()) { bContinue = false; // Filter it out break; } } if (!bContinue) continue; QTreeWidgetItem* parent = 0; switch (schedData.type()) { case MyMoneySchedule::TYPE_ANY: // Should we display an error ? // We just sort it as bill and fall through here case MyMoneySchedule::TYPE_BILL: parent = itemBills; break; case MyMoneySchedule::TYPE_DEPOSIT: parent = itemDeposits; break; case MyMoneySchedule::TYPE_TRANSFER: parent = itemTransfers; break; case MyMoneySchedule::TYPE_LOANPAYMENT: parent = itemLoans; break; } if (parent) { if (!KMyMoneyGlobalSettings::hideFinishedSchedules() || !schedData.isFinished()) { item = addScheduleItem(parent, schedData); if (schedData.id() == schedId) openItem = item; } } } if (openItem) { m_scheduleTree->setCurrentItem(openItem); } // using a timeout is the only way, I got the 'ensureTransactionVisible' // working when coming from hidden form to visible form. I assume, this // has something to do with the delayed update of the display somehow. resize(width(), height() - 1); QTimer::singleShot(10, this, SLOT(slotTimerDone())); m_scheduleTree->update(); // force repaint in case the filter is set m_searchWidget->searchLine()->updateSearch(QString()); if (m_openBills) itemBills->setExpanded(true); if (m_openDeposits) itemDeposits->setExpanded(true); if (m_openTransfers) itemTransfers->setExpanded(true); if (m_openLoans) itemLoans->setExpanded(true); } catch (const MyMoneyException &e) { KMessageBox::error(this, e.what()); } for (int i = 0; i < m_scheduleTree->columnCount(); ++i) { m_scheduleTree->resizeColumnToContents(i); } //reenable sorting after loading items m_scheduleTree->setSortingEnabled(true); } QTreeWidgetItem* KScheduledView::addScheduleItem(QTreeWidgetItem* parent, MyMoneySchedule& schedule) { KScheduleTreeItem* item = new KScheduleTreeItem(parent); item->setData(0, Qt::UserRole, QVariant::fromValue(schedule)); item->setData(0, KScheduleTreeItem::OrderRole, schedule.name()); if (!schedule.isFinished()) { if (schedule.isOverdue()) { item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewUpcominEvents])); QBrush brush = item->foreground(0); brush.setColor(Qt::red); for (int i = 0; i < m_scheduleTree->columnCount(); ++i) { item->setForeground(i, brush); } } else { item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::ViewCalendarDay])); } } else { item->setIcon(0, QIcon::fromTheme(g_Icons[Icon::DialogClose])); QBrush brush = item->foreground(0); brush.setColor(Qt::darkGreen); for (int i = 0; i < m_scheduleTree->columnCount(); ++i) { item->setForeground(i, brush); } } try { MyMoneyTransaction transaction = schedule.transaction(); MyMoneySplit s1 = (transaction.splits().size() < 1) ? MyMoneySplit() : transaction.splits()[0]; MyMoneySplit s2 = (transaction.splits().size() < 2) ? MyMoneySplit() : transaction.splits()[1]; QList::ConstIterator it_s; MyMoneySplit split; MyMoneyAccount acc; switch (schedule.type()) { case MyMoneySchedule::TYPE_DEPOSIT: if (s1.value().isNegative()) split = s2; else split = s1; break; case MyMoneySchedule::TYPE_LOANPAYMENT: for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) { acc = MyMoneyFile::instance()->account((*it_s).accountId()); if (acc.accountGroup() == MyMoneyAccount::Asset || acc.accountGroup() == MyMoneyAccount::Liability) { if (acc.accountType() != MyMoneyAccount::Loan && acc.accountType() != MyMoneyAccount::AssetLoan) { split = *it_s; break; } } } if (it_s == transaction.splits().constEnd()) { qWarning("Split for payment account not found in %s:%d.", __FILE__, __LINE__); } break; default: if (!s1.value().isPositive()) split = s1; else split = s2; break; } acc = MyMoneyFile::instance()->account(split.accountId()); item->setText(0, schedule.name()); MyMoneySecurity currency = MyMoneyFile::instance()->currency(acc.currencyId()); QString accName = acc.name(); if (!accName.isEmpty()) { item->setText(1, accName); } else { item->setText(1, "---"); } item->setData(1, KScheduleTreeItem::OrderRole, QVariant(accName)); QString payeeName; if (!s1.payeeId().isEmpty()) { payeeName = MyMoneyFile::instance()->payee(s1.payeeId()).name(); item->setText(2, payeeName); } else { item->setText(2, "---"); } item->setData(2, KScheduleTreeItem::OrderRole, QVariant(payeeName)); MyMoneyMoney amount = split.shares().abs(); item->setData(3, Qt::UserRole, QVariant::fromValue(amount)); if (!accName.isEmpty()) { item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, acc, currency))); } else { //there are some cases where the schedule does not have an account //in those cases the account will not have a fraction //use base currency instead item->setText(3, QString("%1 ").arg(MyMoneyUtils::formatMoney(amount, MyMoneyFile::instance()->baseCurrency()))); } item->setTextAlignment(3, Qt::AlignRight | Qt::AlignVCenter); item->setData(3, KScheduleTreeItem::OrderRole, QVariant::fromValue(amount)); // Do the real next payment like ms-money etc QDate nextDueDate; if (schedule.isFinished()) { item->setText(4, i18nc("Finished schedule", "Finished")); } else { nextDueDate = schedule.adjustedNextDueDate(); item->setText(4, QLocale().toString(schedule.adjustedNextDueDate(), QLocale::ShortFormat)); } item->setData(4, KScheduleTreeItem::OrderRole, QVariant(nextDueDate)); item->setText(5, i18nc("Frequency of schedule", schedule.occurrenceToString().toLatin1())); item->setText(6, KMyMoneyUtils::paymentMethodToString(schedule.paymentType())); } catch (const MyMoneyException &e) { item->setText(0, "Error:"); item->setText(1, e.what()); } return item; } void KScheduledView::slotTimerDone() { QTreeWidgetItem* item; item = m_scheduleTree->currentItem(); if (item) { m_scheduleTree->scrollToItem(item); } // force a repaint of all items to update the branches /*for (item = m_scheduleTree->item(0); item != 0; item = m_scheduleTree->item(m_scheduleTree->row(item) + 1)) { m_scheduleTree->repaintItem(item); } resize(width(), height() + 1);*/ } void KScheduledView::slotReloadView() { m_needReload = true; if (isVisible()) { m_qbuttonNew->setEnabled(true); refresh(true, m_selectedSchedule); m_needReload = false; QTimer::singleShot(50, this, SLOT(slotRearrange())); } } void KScheduledView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) slotReloadView(); QWidget::showEvent(event); } void KScheduledView::slotRearrange() { resizeEvent(0); } void KScheduledView::readConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); m_openBills = grp.readEntry("KScheduleView_openBills", true); m_openDeposits = grp.readEntry("KScheduleView_openDeposits", true); m_openTransfers = grp.readEntry("KScheduleView_openTransfers", true); m_openLoans = grp.readEntry("KScheduleView_openLoans", true); QByteArray columns; columns = grp.readEntry("KScheduleView_treeState", columns); m_scheduleTree->header()->restoreState(columns); m_scheduleTree->header()->setFont(KMyMoneyGlobalSettings::listHeaderFont()); } void KScheduledView::writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup grp = config->group("Last Use Settings"); grp.writeEntry("KScheduleView_openBills", m_openBills); grp.writeEntry("KScheduleView_openDeposits", m_openDeposits); grp.writeEntry("KScheduleView_openTransfers", m_openTransfers); grp.writeEntry("KScheduleView_openLoans", m_openLoans); QByteArray columns = m_scheduleTree->header()->saveState(); grp.writeEntry("KScheduleView_treeState", columns); config->sync(); } void KScheduledView::slotListViewContextMenu(const QPoint& pos) { QTreeWidgetItem* item = m_scheduleTree->itemAt(pos); if (item) { try { MyMoneySchedule schedule = item->data(0, Qt::UserRole).value(); emit scheduleSelected(schedule); m_selectedSchedule = schedule.id(); emit openContextMenu(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Error activating context menu"), e.what()); } } else { emit openContextMenu(); } } void KScheduledView::slotListItemExecuted(QTreeWidgetItem* item, int) { if (!item) return; try { MyMoneySchedule schedule = item->data(0, Qt::UserRole).value(); m_selectedSchedule = schedule.id(); emit editSchedule(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Error executing item"), e.what()); } } void KScheduledView::slotAccountActivated() { m_filterAccounts.clear(); try { int accountCount = 0; MyMoneyFile* file = MyMoneyFile::instance(); // extract a list of all accounts under the asset and liability groups // and sort them by name QList list; QStringList accountList = file->asset().accountList(); accountList.append(file->liability().accountList()); file->accountList(list, accountList, true); qStableSort(list.begin(), list.end(), accountNameLessThan); QList::ConstIterator it_a; for (it_a = list.constBegin(); it_a != list.constEnd(); ++it_a) { if (!(*it_a).isClosed()) { if (!m_kaccPopup->actions().value(accountCount)->isChecked()) { m_filterAccounts.append((*it_a).id()); } ++accountCount; } } refresh(false, m_selectedSchedule); } catch (const MyMoneyException &e) { KMessageBox::detailedError(this, i18n("Unable to filter account"), e.what()); } } void KScheduledView::slotListViewExpanded(QTreeWidgetItem* item) { if (item) { if (item->text(0) == i18n("Bills")) m_openBills = true; else if (item->text(0) == i18n("Deposits")) m_openDeposits = true; else if (item->text(0) == i18n("Transfers")) m_openTransfers = true; else if (item->text(0) == i18n("Loans")) m_openLoans = true; } } void KScheduledView::slotListViewCollapsed(QTreeWidgetItem* item) { if (item) { if (item->text(0) == i18n("Bills")) m_openBills = false; else if (item->text(0) == i18n("Deposits")) m_openDeposits = false; else if (item->text(0) == i18n("Transfers")) m_openTransfers = false; else if (item->text(0) == i18n("Loans")) m_openLoans = false; } } void KScheduledView::slotSelectSchedule(const QString& schedule) { refresh(true, schedule); } void KScheduledView::slotBriefEnterClicked(const MyMoneySchedule& schedule, const QDate& date) { Q_UNUSED(date); emit scheduleSelected(schedule); emit enterSchedule(); } void KScheduledView::slotBriefSkipClicked(const MyMoneySchedule& schedule, const QDate& date) { Q_UNUSED(date); emit scheduleSelected(schedule); emit skipSchedule(); } void KScheduledView::slotSetSelectedItem() { emit scheduleSelected(MyMoneySchedule()); QTreeWidgetItem* item = m_scheduleTree->currentItem(); if (item) { try { MyMoneySchedule schedule = item->data(0, Qt::UserRole).value(); emit scheduleSelected(schedule); m_selectedSchedule = schedule.id(); } catch (const MyMoneyException &e) { qDebug("KScheduledView::slotSetSelectedItem: %s", qPrintable(e.what())); } } } diff --git a/kmymoney/views/ktagsview.cpp b/kmymoney/views/ktagsview.cpp index 2a33d7868..10c114e58 100644 --- a/kmymoney/views/ktagsview.cpp +++ b/kmymoney/views/ktagsview.cpp @@ -1,671 +1,672 @@ /*************************************************************************** ktagsview.h ------------- begin : Sat Oct 13 2012 copyright : (C) 2012 by Alessandro Russo ***************************************************************************/ /*************************************************************************** * * * 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 "ktagsview.h" // ---------------------------------------------------------------------------- // 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 // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include "mymoneysecurity.h" #include using namespace Icons; /* -------------------------------------------------------------------------*/ /* KTransactionPtrVector */ /* -------------------------------------------------------------------------*/ // *** KTagListItem Implementation *** KTagListItem::KTagListItem(QListWidget *parent, const MyMoneyTag& tag) : QListWidgetItem(parent, QListWidgetItem::UserType), m_tag(tag) { setText(tag.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } KTagListItem::~KTagListItem() { } // *** KTagsView Implementation *** KTagsView::KTagsView(QWidget *parent) : QWidget(parent), m_needReload(false), m_inSelection(false), m_allowEditing(true), m_tagFilterType(0) { setupUi(this); m_filterProxyModel = new AccountNamesFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->sort(0); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(this, m_tagsList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_tagsList->setContextMenuPolicy(Qt::CustomContextMenu); m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type m_filterBox->addItem(i18nc("@item Show all tags", "All")); m_filterBox->addItem(i18nc("@item Show only used tags", "Used")); m_filterBox->addItem(i18nc("@item Show only unused tags", "Unused")); m_filterBox->addItem(i18nc("@item Show only opened tags", "Opened")); m_filterBox->addItem(i18nc("@item Show only closed tags", "Closed")); m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); KGuiItem newButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListAddTag]), i18n("Creates a new tag"), i18n("Use this to create a new tag.")); KGuiItem::assign(m_newButton, newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::EditRename]), i18n("Rename the current selected tag"), i18n("Use this to start renaming the selected tag.")); KGuiItem::assign(m_renameButton, renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(), QIcon::fromTheme(g_Icons[Icon::ListRemoveTag]), i18n("Delete the current selected tag"), i18n("Use this to delete the selected tag.")); KGuiItem::assign(m_deleteButton, deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem updateButtonItem(i18nc("Update tag", "Update"), QIcon::fromTheme(g_Icons[Icon::DialogOK]), i18n("Accepts the entered data and stores it"), i18n("Use this to accept the modified data.")); KGuiItem::assign(m_updateButton, updateButtonItem); m_updateButton->setEnabled(false); QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_register->setupRegister(MyMoneyAccount(), cols); m_register->setSelectionMode(QTableWidget::SingleSelection); m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst); m_balanceLabel->hide(); connect(m_tagsList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectTag(QListWidgetItem*,QListWidgetItem*))); connect(m_tagsList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectTag())); connect(m_tagsList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_tagsList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenameTag(QListWidgetItem*))); connect(m_tagsList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked())); - connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("tag_delete"), SLOT(trigger())); + connect(m_deleteButton, SIGNAL(clicked()), kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TagDelete]), SLOT(trigger())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotTagNew())); connect(m_colorbutton, SIGNAL(changed(QColor)), this, SLOT(slotTagDataChanged())); connect(m_closed, SIGNAL(stateChanged(int)), this, SLOT(slotTagDataChanged())); connect(m_notes, SIGNAL(textChanged()), this, SLOT(slotTagDataChanged())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateTag())); connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadTags())); connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int))); // use the size settings of the last run (if any) KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KTagsViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); // At start we haven't any tag selected m_tabWidget->setEnabled(false); // disable tab widget m_deleteButton->setEnabled(false); // disable delete and rename button m_renameButton->setEnabled(false); m_tag = MyMoneyTag(); // make sure we don't access an undefined tag clearItemData(); } KTagsView::~KTagsView() { // remember the splitter settings for startup KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KTagsViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KTagsView::slotStartRename(QListWidgetItem* item) { m_allowEditing = true; m_tagsList->editItem(item); } void KTagsView::slotRenameButtonCliked() { if (m_tagsList->currentItem() && m_tagsList->selectedItems().count() == 1) { slotStartRename(m_tagsList->currentItem()); } } // This variant is only called when a single tag is selected and renamed. void KTagsView::slotRenameTag(QListWidgetItem* ta) { //if there is no current item selected, exit if (m_allowEditing == false || !m_tagsList->currentItem() || ta != m_tagsList->currentItem()) return; //qDebug() << "[KTagsView::slotRenameTag]"; // create a copy of the new name without appended whitespaces QString new_name = ta->text(); if (m_tag.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a tag with the new name try { // this function call will throw an exception, if the tag // hasn't been found. MyMoneyFile::instance()->tagByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A tag with the name '%1' already exists. It is not advisable to have " "multiple tags with the same identification name. Are you sure you would like " "to rename the tag?", new_name)) != KMessageBox::Yes) { ta->setText(m_tag.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } m_tag.setName(new_name); m_newName = new_name; MyMoneyFile::instance()->modifyTag(m_tag); // the above call to modifyTag will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation ensureTagVisible(m_tag.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { ta->setText(new_name); } } void KTagsView::ensureTagVisible(const QString& id) { for (int i = 0; i < m_tagsList->count(); ++i) { KTagListItem* ta = dynamic_cast(m_tagsList->item(0)); if (ta && ta->tag().id() == id) { m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter); m_tagsList->setCurrentItem(ta); // active item and deselect all others m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KTagsView::selectedTags(QList& tagsList) const { QList selectedItems = m_tagsList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*itemsIt); if (item) tagsList << item->tag(); ++itemsIt; } } void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev) { Q_UNUSED(cur); Q_UNUSED(prev); m_allowEditing = false; } void KTagsView::slotSelectTag() { // check if the content of a currently selected tag was modified // and ask to store the data if (m_updateButton->isEnabled()) { if (KMessageBox::questionYesNo(this, QString("%1").arg( i18n("Do you want to save the changes for %1?", m_newName)), i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdateTag(); m_inSelection = false; } } // loop over all tags and count the number of tags, also // obtain last selected tag QList tagsList; selectedTags(tagsList); emit selectObjects(tagsList); if (tagsList.isEmpty()) { m_tabWidget->setEnabled(false); // disable tab widget m_balanceLabel->hide(); m_deleteButton->setEnabled(false); //disable delete and rename button m_renameButton->setEnabled(false); clearItemData(); m_tag = MyMoneyTag(); return; // make sure we don't access an undefined tag } m_deleteButton->setEnabled(true); //re-enable delete button // if we have multiple tags selected, clear and disable the tag information if (tagsList.count() > 1) { m_tabWidget->setEnabled(false); // disable tab widget m_renameButton->setEnabled(false); // disable also the rename button m_balanceLabel->hide(); clearItemData(); } else m_renameButton->setEnabled(true); // otherwise we have just one selected, enable tag information widget and renameButton m_tabWidget->setEnabled(true); m_balanceLabel->show(); // as of now we are updating only the last selected tag, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { m_tag = tagsList[0]; m_newName = m_tag.name(); m_colorbutton->setEnabled(true); m_colorbutton->setColor(m_tag.tagColor()); m_closed->setEnabled(true); m_closed->setChecked(m_tag.isClosed()); m_notes->setEnabled(true); m_notes->setText(m_tag.notes()); slotTagDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of tag: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); m_register->clear(); m_tag = MyMoneyTag(); } m_allowEditing = true; } void KTagsView::clearItemData() { m_colorbutton->setColor(QColor()); m_closed->setChecked(false); m_notes->setText(QString()); showTransactions(); } void KTagsView::showTransactions() { MyMoneyMoney balance; MyMoneyFile *file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear the register m_register->clear(); if (m_tag.id().isEmpty() || !m_tabWidget->isEnabled()) { m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; filter.addTag(m_tag.id()); filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here m_register->setUpdatesEnabled(true); m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KTagsView::slotTagDataChanged() { bool rc = false; if (m_tabWidget->isEnabled()) { rc |= ((m_tag.tagColor().isValid() != m_colorbutton->color().isValid()) || (m_colorbutton->color().isValid() && m_tag.tagColor() != m_colorbutton->color())); rc |= (m_closed->isChecked() != m_tag.isClosed()); rc |= ((m_tag.notes().isEmpty() != m_notes->toPlainText().isEmpty()) || (!m_notes->toPlainText().isEmpty() && m_tag.notes() != m_notes->toPlainText())); } m_updateButton->setEnabled(rc); } void KTagsView::slotUpdateTag() { if (m_updateButton->isEnabled()) { MyMoneyFileTransaction ft; m_updateButton->setEnabled(false); try { m_tag.setName(m_newName); m_tag.setTagColor(m_colorbutton->color()); m_tag.setClosed(m_closed->isChecked()); m_tag.setNotes(m_notes->toPlainText()); MyMoneyFile::instance()->modifyTag(m_tag); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KTagsView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { loadTags(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); QList list; selectedTags(list); emit selectObjects(list); } void KTagsView::slotLoadTags() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotLoadTags())); else loadTags(); } else { m_needReload = true; } } void KTagsView::loadTags() { if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) isSelected[item->tag().id()] = true; ++tagsIt; } // keep current selected item KTagListItem *currentItem = static_cast(m_tagsList->currentItem()); if (currentItem) id = currentItem->tag().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); m_tagsList->clear(); m_register->clear(); currentItem = 0; QListlist = file->tagList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_tagFilterType == eAllTags || (m_tagFilterType == eReferencedTags && file->isReferenced(*it)) || (m_tagFilterType == eUnusedTags && !file->isReferenced(*it)) || (m_tagFilterType == eOpenedTags && !(*it).isClosed()) || (m_tagFilterType == eClosedTags && (*it).isClosed())) { KTagListItem* item = new KTagListItem(m_tagsList, *it); if (item->tag().id() == id) currentItem = item; if (isSelected[item->tag().id()]) item->setSelected(true); } } m_tagsList->sortItems(); if (currentItem) { m_tagsList->setCurrentItem(currentItem); m_tagsList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); slotSelectTag(0, 0); m_allowEditing = true; } void KTagsView::slotSelectTransaction() { QList list = m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit transactionSelected(t->split().accountId(), t->transaction().id()); } } void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; try { // clear filter m_searchWidget->clear(); m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) item->setSelected(false); ++tagsIt; } // find the tag in the list QListWidgetItem* it; for (int i = 0; i < m_tagsList->count(); ++i) { it = m_tagsList->item(i); KTagListItem* item = dynamic_cast(it); if (item && item->tag().id() == tagId) { m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter); m_tagsList->setCurrentItem(it); // active item and deselect all others m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the tag selection is updated and transactions are updated accordingly slotSelectTag(); KMyMoneyRegister::RegisterItem *item = 0; for (int i = 0; i < m_register->rowCount(); ++i) { item = m_register->itemAtRow(i); KMyMoneyRegister::Transaction* t = dynamic_cast(item); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { m_register->selectItem(item); m_register->ensureItemVisible(item); break; } } } // quit out of for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", qPrintable(e.what())); } } void KTagsView::slotOpenContextMenu(const QPoint& /*ta*/) { KTagListItem* item = dynamic_cast(m_tagsList->currentItem()); if (item) { slotSelectTag(); emit openContextMenu(item->tag()); } } void KTagsView::slotTagNew() { - kmymoney->action("tag_new")->trigger(); + kmymoney->actionCollection()->action(kmymoney->s_Actions[Action::TagNew])->trigger(); } void KTagsView::slotHelp() { KHelpClient::invokeHelp("details.tags.attributes"); //FIXME-ALEX update help file } void KTagsView::slotChangeFilter(int index) { //update the filter type then reload the tags list m_tagFilterType = index; loadTags(); }