diff --git a/kmymoney/converter/mymoneystatementreader.cpp b/kmymoney/converter/mymoneystatementreader.cpp --- a/kmymoney/converter/mymoneystatementreader.cpp +++ b/kmymoney/converter/mymoneystatementreader.cpp @@ -558,17 +558,19 @@ if (!p_in.m_strCurrency.isEmpty()) { security = p_in.m_strSecurity; currency = p_in.m_strCurrency; - } else if (d->securitiesBySymbol.contains(p_in.m_strSecurity)) + } else if (d->securitiesBySymbol.contains(p_in.m_strSecurity)) { security = d->securitiesBySymbol[p_in.m_strSecurity].id(); - else if (d->securitiesByName.contains(p_in.m_strSecurity)) + currency = file->security(file->security(security).tradingCurrency()).id(); + } else if (d->securitiesByName.contains(p_in.m_strSecurity)) { security = d->securitiesByName[p_in.m_strSecurity].id(); - else + currency = file->security(file->security(security).tradingCurrency()).id(); + } else return; MyMoneyPrice price(security, currency, p_in.m_date, - p_in.m_amount, i18n("Prices Importer")); + p_in.m_amount, p_in.m_sourceName.isEmpty() ? i18n("Prices Importer") : p_in.m_sourceName); MyMoneyFile::instance()->addPrice(price); } diff --git a/kmymoney/kmymoney.h b/kmymoney/kmymoney.h --- a/kmymoney/kmymoney.h +++ b/kmymoney/kmymoney.h @@ -902,7 +902,7 @@ * UI to handle account matching, payee creation, and someday * payee and transaction matching. */ - bool slotStatementImport(const MyMoneyStatement& s); + bool slotStatementImport(const MyMoneyStatement& s, bool silent = false); /** * Essentially similar to the above slot, except this will load the file diff --git a/kmymoney/kmymoney.cpp b/kmymoney/kmymoney.cpp --- a/kmymoney/kmymoney.cpp +++ b/kmymoney/kmymoney.cpp @@ -2425,7 +2425,7 @@ return result; } -bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s) +bool KMyMoneyApp::slotStatementImport(const MyMoneyStatement& s, bool silent) { bool result = false; @@ -2457,7 +2457,7 @@ // re-enable all standard widgets setEnabled(true); - if (!d->m_collectingStatements) + 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; diff --git a/kmymoney/mymoney/mymoneystatement.h b/kmymoney/mymoney/mymoneystatement.h --- a/kmymoney/mymoney/mymoneystatement.h +++ b/kmymoney/mymoney/mymoneystatement.h @@ -93,6 +93,7 @@ struct Price { QDate m_date; + QString m_sourceName; QString m_strSecurity; QString m_strCurrency; MyMoneyMoney m_amount; diff --git a/kmymoney/plugins/csvimport/CMakeLists.txt b/kmymoney/plugins/csvimport/CMakeLists.txt --- a/kmymoney/plugins/csvimport/CMakeLists.txt +++ b/kmymoney/plugins/csvimport/CMakeLists.txt @@ -7,6 +7,7 @@ set(kmm_csvimport_PART_SRCS csvimporterplugin.cpp + csvimporter.cpp csvwizard.cpp bankingwizardpage.cpp investmentwizardpage.cpp @@ -36,7 +37,7 @@ currenciesdlg.ui ) -# Use static library for tests only +# Use static library for tests and prices importer only add_library(kmm_csvimport MODULE ${kmm_csvimport_PART_SRCS}) target_link_libraries(kmm_csvimport diff --git a/kmymoney/plugins/csvimport/bankingwizardpage.h b/kmymoney/plugins/csvimport/bankingwizardpage.h --- a/kmymoney/plugins/csvimport/bankingwizardpage.h +++ b/kmymoney/plugins/csvimport/bankingwizardpage.h @@ -24,108 +24,68 @@ // QT Includes #include -#include -#include #include -#include // ---------------------------------------------------------------------------- // KDE Includes -#include - // ---------------------------------------------------------------------------- // Project Includes #include +#include // ---------------------------------------------------------------------------- -class CSVWizard; -class BankingPage; +class BankingProfile; namespace Ui { class BankingPage; } -class BankingPage : public QWizardPage +class BankingPage : public CSVWizardPage { Q_OBJECT public: - explicit BankingPage(QDialog *parent = 0); + explicit BankingPage(CSVWizard *dlg, CSVImporter *imp); ~BankingPage(); Ui::BankingPage *ui; QVBoxLayout *m_pageLayout; - CSVWizard* m_wiz; - - typedef enum:uchar { ColumnNumber, ColumnDate, ColumnPayee, ColumnAmount, - ColumnCredit, ColumnDebit, ColumnCategory, - ColumnMemo, ColumnEmpty = 0xFE, ColumnInvalid = 0xFF - } columnTypeE; - - QMap m_colTypeNum; - QMap m_colNumType; - QMap m_colTypeName; - - void saveSettings(); - void readSettings(const KSharedConfigPtr &config); - void setParent(CSVWizard* dlg); - + bool validateCreditDebit(); /** * This method fills QIF file with bank/credit card data */ void makeQIF(MyMoneyStatement &st, QFile &file); - /** - * This method feeds file buffer in banking lines parser. - */ - bool createStatement(MyMoneyStatement& st); - private: - - QStringList m_columnList; - QSet m_hashSet; - int m_oppositeSigns; - void initializePage(); bool isComplete() const; int nextId() const; void initializeComboBoxes(); bool validateMemoComboBox(); void resetComboBox(const columnTypeE comboBox); - bool validateSelectedColumn(int col, columnTypeE type); - - /** - * This method is called during processing. It ensures that processed credit/debit - * are valid. - */ - bool processCreditDebit(QString& credit, QString& debit , MyMoneyMoney& amount); + bool validateSelectedColumn(const int col, const columnTypeE type); - /** - * This method is called when the user clicks 'import'. - * or 'Make QIF' It will evaluate a line and prepare it to be added to a statement, - * and to a QIF file, if required. - */ - bool processBankLine(const QString &line, MyMoneyStatement &st); + BankingProfile *m_profile; private slots: - void slotMemoColSelected(int col); - void slotCategoryColSelected(int col); - void slotNumberColSelected(int col); - void slotPayeeColSelected(int col); - void slotDateColSelected(int col); - void slotDebitColSelected(int col); - void slotCreditColSelected(int col); - void slotAmountColSelected(int col); - void slotAmountToggled(bool checked); - void slotDebitCreditToggled(bool checked); - void slotOppositeSignsClicked(bool checked); - void slotClearColumns(); + void memoColSelected(int col); + void categoryColSelected(int col); + void numberColSelected(int col); + void payeeColSelected(int col); + void dateColSelected(int col); + void debitColSelected(int col); + void creditColSelected(int col); + void amountColSelected(int col); + void amountToggled(bool checked); + void debitCreditToggled(bool checked); + void oppositeSignsClicked(bool checked); + void clearColumns(); }; #endif // BANKINGWIZARDPAGE_H diff --git a/kmymoney/plugins/csvimport/bankingwizardpage.cpp b/kmymoney/plugins/csvimport/bankingwizardpage.cpp --- a/kmymoney/plugins/csvimport/bankingwizardpage.cpp +++ b/kmymoney/plugins/csvimport/bankingwizardpage.cpp @@ -28,127 +28,120 @@ // KDE Includes #include -#include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" - -#include "convdate.h" -#include "csvutil.h" #include "csvwizard.h" #include "ui_bankingwizardpage.h" -#include "ui_csvwizard.h" // ---------------------------------------------------------------------------- - -BankingPage::BankingPage(QDialog *parent) : QWizardPage(parent), ui(new Ui::BankingPage) +BankingPage::BankingPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), + ui(new Ui::BankingPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); - connect(ui->button_clear, SIGNAL(clicked()), this, SLOT(slotClearColumns())); - connect(ui->radioBnk_amount, SIGNAL(toggled(bool)), this, SLOT(slotAmountToggled(bool))); - connect(ui->radioBnk_debCred, SIGNAL(toggled(bool)), this, SLOT(slotDebitCreditToggled(bool))); - connect(ui->checkBoxBnk_oppositeSigns, SIGNAL(clicked(bool)), this, SLOT(slotOppositeSignsClicked(bool))); + connect(ui->m_clear, &QAbstractButton::clicked, this, &BankingPage::clearColumns); + connect(ui->m_radioAmount, &QAbstractButton::toggled, this, &BankingPage::amountToggled); + connect(ui->m_radioDebitCredit, &QAbstractButton::toggled, this, &BankingPage::debitCreditToggled); + connect(ui->m_oppositeSigns, &QAbstractButton::clicked, this, &BankingPage::oppositeSignsClicked); // initialize column names - m_colTypeName.insert(ColumnPayee,i18n("Payee")); - m_colTypeName.insert(ColumnNumber,i18n("Number")); - m_colTypeName.insert(ColumnDebit,i18n("Debit")); - m_colTypeName.insert(ColumnCredit,i18n("Credit")); - m_colTypeName.insert(ColumnDate,i18n("Date")); - m_colTypeName.insert(ColumnAmount,i18n("Amount")); - m_colTypeName.insert(ColumnCategory,i18n("Category")); - m_colTypeName.insert(ColumnMemo,i18n("Memo")); + m_dlg->m_colTypeName.insert(ColumnPayee,i18n("Payee")); + m_dlg->m_colTypeName.insert(ColumnNumber,i18n("Number")); + m_dlg->m_colTypeName.insert(ColumnDebit,i18n("Debit")); + m_dlg->m_colTypeName.insert(ColumnCredit,i18n("Credit")); + m_dlg->m_colTypeName.insert(ColumnDate,i18n("Date")); + m_dlg->m_colTypeName.insert(ColumnAmount,i18n("Amount")); + m_dlg->m_colTypeName.insert(ColumnCategory,i18n("Category")); + m_dlg->m_colTypeName.insert(ColumnMemo,i18n("Memo")); + + m_profile = dynamic_cast(m_imp->m_profile); } BankingPage::~BankingPage() { delete ui; } -void BankingPage::setParent(CSVWizard* dlg) -{ - m_wiz = dlg; -} - void BankingPage::initializeComboBoxes() { // disable banking widgets allowing their initialization - disconnect(ui->comboBoxBnk_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAmountColSelected(int))); - disconnect(ui->comboBoxBnk_debitCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDebitColSelected(int))); - disconnect(ui->comboBoxBnk_creditCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCreditColSelected(int))); - disconnect(ui->comboBoxBnk_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotMemoColSelected(int))); - disconnect(ui->comboBoxBnk_numberCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotNumberColSelected(int))); - disconnect(ui->comboBoxBnk_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - disconnect(ui->comboBoxBnk_payeeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeColSelected(int))); - disconnect(ui->comboBoxBnk_categoryCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCategoryColSelected(int))); + disconnect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int))); + disconnect(ui->m_debitCol, SIGNAL(currentIndexChanged(int)), this, SLOT(debitColSelected(int))); + disconnect(ui->m_creditCol, SIGNAL(currentIndexChanged(int)), this, SLOT(creditColSelected(int))); + disconnect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int))); + disconnect(ui->m_numberCol, SIGNAL(currentIndexChanged(int)), this, SLOT(numberColSelected(int))); + disconnect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + disconnect(ui->m_payeeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(payeeColSelected(int))); + disconnect(ui->m_categoryCol, SIGNAL(currentIndexChanged(int)), this, SLOT(categoryColSelected(int))); // clear all existing items before adding new ones - ui->comboBoxBnk_numberCol->clear(); - ui->comboBoxBnk_dateCol->clear(); - ui->comboBoxBnk_payeeCol->clear(); - ui->comboBoxBnk_memoCol->clear(); - ui->comboBoxBnk_amountCol->clear(); - ui->comboBoxBnk_creditCol->clear(); - ui->comboBoxBnk_debitCol->clear(); - ui->comboBoxBnk_categoryCol->clear(); + ui->m_numberCol->clear(); + ui->m_dateCol->clear(); + ui->m_payeeCol->clear(); + ui->m_memoCol->clear(); + ui->m_amountCol->clear(); + ui->m_creditCol->clear(); + ui->m_debitCol->clear(); + ui->m_categoryCol->clear(); QStringList columnNumbers; - for (int i = 0; i < m_wiz->m_maxColumnCount; ++i) - columnNumbers << QString::number(i + 1); + for (int i = 0; i < m_imp->m_file->m_columnCount; ++i) + columnNumbers.append(QString::number(i + 1)); // populate comboboxes with col # values - ui->comboBoxBnk_numberCol->addItems(columnNumbers); - ui->comboBoxBnk_dateCol->addItems(columnNumbers); - ui->comboBoxBnk_payeeCol->addItems(columnNumbers); - ui->comboBoxBnk_memoCol->addItems(columnNumbers); - ui->comboBoxBnk_amountCol->addItems(columnNumbers); - ui->comboBoxBnk_creditCol->addItems(columnNumbers); - ui->comboBoxBnk_debitCol->addItems(columnNumbers); - ui->comboBoxBnk_categoryCol->addItems(columnNumbers); - - slotClearColumns(); // all comboboxes are set to 0 so set them to -1 - connect(ui->comboBoxBnk_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAmountColSelected(int))); - connect(ui->comboBoxBnk_debitCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDebitColSelected(int))); - connect(ui->comboBoxBnk_creditCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCreditColSelected(int))); - connect(ui->comboBoxBnk_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotMemoColSelected(int))); - connect(ui->comboBoxBnk_numberCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotNumberColSelected(int))); - connect(ui->comboBoxBnk_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - connect(ui->comboBoxBnk_payeeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeColSelected(int))); - connect(ui->comboBoxBnk_categoryCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotCategoryColSelected(int))); + ui->m_numberCol->addItems(columnNumbers); + ui->m_dateCol->addItems(columnNumbers); + ui->m_payeeCol->addItems(columnNumbers); + ui->m_memoCol->addItems(columnNumbers); + ui->m_amountCol->addItems(columnNumbers); + ui->m_creditCol->addItems(columnNumbers); + ui->m_debitCol->addItems(columnNumbers); + ui->m_categoryCol->addItems(columnNumbers); + + clearColumns(); // all comboboxes are set to 0 so set them to -1 + connect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int))); + connect(ui->m_debitCol, SIGNAL(currentIndexChanged(int)), this, SLOT(debitColSelected(int))); + connect(ui->m_creditCol, SIGNAL(currentIndexChanged(int)), this, SLOT(creditColSelected(int))); + connect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int))); + connect(ui->m_numberCol, SIGNAL(currentIndexChanged(int)), this, SLOT(numberColSelected(int))); + connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + connect(ui->m_payeeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(payeeColSelected(int))); + connect(ui->m_categoryCol, SIGNAL(currentIndexChanged(int)), this, SLOT(categoryColSelected(int))); } void BankingPage::initializePage() { - if (ui->comboBoxBnk_dateCol->count() != m_wiz->m_endColumn) + if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount) initializeComboBoxes(); - ui->comboBoxBnk_payeeCol->setCurrentIndex(m_colTypeNum.value(ColumnPayee)); - ui->comboBoxBnk_numberCol->setCurrentIndex(m_colTypeNum.value(ColumnNumber)); - ui->comboBoxBnk_amountCol->setCurrentIndex(m_colTypeNum.value(ColumnAmount)); - ui->comboBoxBnk_debitCol->setCurrentIndex(m_colTypeNum.value(ColumnDebit)); - ui->comboBoxBnk_creditCol->setCurrentIndex(m_colTypeNum.value(ColumnCredit)); - ui->comboBoxBnk_dateCol->setCurrentIndex(m_colTypeNum.value(ColumnDate)); - ui->comboBoxBnk_categoryCol->setCurrentIndex(m_colTypeNum.value(ColumnCategory)); - ui->checkBoxBnk_oppositeSigns->setChecked(m_oppositeSigns); + ui->m_payeeCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnPayee)); + ui->m_numberCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnNumber)); + ui->m_amountCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnAmount)); + ui->m_debitCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnDebit)); + ui->m_creditCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnCredit)); + ui->m_dateCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnDate)); + ui->m_categoryCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnCategory)); + ui->m_oppositeSigns->setChecked(m_profile->m_oppositeSigns); - if (m_wiz->m_memoColList.count() > 0) + if (m_profile->m_memoColList.count() > 0) { - for (int i = 0; i < m_wiz->m_memoColList.count(); ++i) - ui->comboBoxBnk_memoCol->setCurrentIndex(m_wiz->m_memoColList[i]); + for (int i = 0; i < m_profile->m_memoColList.count(); ++i) + ui->m_memoCol->setCurrentIndex(m_profile->m_memoColList.value(i)); } else - ui->comboBoxBnk_memoCol->setCurrentIndex(-1); + ui->m_memoCol->setCurrentIndex(-1); - if (this->m_colTypeNum.value(ColumnDebit) == -1) // If amount previously selected, set check radio_amount - ui->radioBnk_amount->setChecked(true); + if (this->m_profile->m_colTypeNum.value(ColumnDebit) == -1) // If amount previously selected, set check radio_amount + ui->m_radioAmount->setChecked(true); else // ...else set check radio_debCred to clear amount col - ui->radioBnk_debCred->setChecked(true); + ui->m_radioDebitCredit->setChecked(true); } int BankingPage::nextId() const @@ -158,289 +151,238 @@ bool BankingPage::isComplete() const { - return ui->comboBoxBnk_dateCol->currentIndex() > -1 && - ui->comboBoxBnk_payeeCol->currentIndex() > -1 && - (ui->comboBoxBnk_amountCol->currentIndex() > -1 || - (ui->comboBoxBnk_debitCol->currentIndex() > -1 && - ui->comboBoxBnk_creditCol->currentIndex() > -1)); + return ui->m_dateCol->currentIndex() > -1 && + ui->m_payeeCol->currentIndex() > -1 && + (ui->m_amountCol->currentIndex() > -1 || + (ui->m_debitCol->currentIndex() > -1 && + ui->m_creditCol->currentIndex() > -1)); } bool BankingPage::validateMemoComboBox() { - if (m_wiz->m_memoColList.count() == 0) + if (m_profile->m_memoColList.count() == 0) return true; - for (int i = 0; i < ui->comboBoxBnk_memoCol->count(); ++i) + for (int i = 0; i < ui->m_memoCol->count(); ++i) { - QString txt = ui->comboBoxBnk_memoCol->itemText(i); + QString txt = ui->m_memoCol->itemText(i); if (txt.contains(QChar(QLatin1Char('*')))) // check if text containing '*' belongs to valid column types - if (m_colNumType.value(i) != ColumnPayee) { - ui->comboBoxBnk_memoCol->setItemText(i, QString().setNum(i + 1)); - m_wiz->m_memoColList.removeOne(i); + if (m_profile->m_colNumType.value(i) != ColumnPayee) { + ui->m_memoCol->setItemText(i, QString::number(i + 1)); + m_profile->m_memoColList.removeOne(i); return false; } } return true; } -void BankingPage::slotMemoColSelected(int col) +void BankingPage::memoColSelected(int col) { - if (m_colNumType.value(col) == ColumnPayee ) { + if (m_profile->m_colNumType.value(col) == ColumnPayee ) { int rc = KMessageBox::Yes; if (isVisible()) - rc = KMessageBox::questionYesNo(m_wiz, i18n("
The '%1' field already has this column selected.
" + rc = KMessageBox::questionYesNo(m_dlg, i18n("
The '%1' field already has this column selected.
" "
If you wish to copy the Payee data to the memo field, click 'Yes'.
", - m_colTypeName.value(m_colNumType[col]))); + m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); if (rc == KMessageBox::Yes) { - ui->comboBoxBnk_memoCol->setItemText(col, QString().setNum(col + 1) + QChar(QLatin1Char('*'))); - if (!m_wiz->m_memoColList.contains(col)) - m_wiz->m_memoColList.append(col); + ui->m_memoCol->setItemText(col, QString::number(col + 1) + QChar(QLatin1Char('*'))); + if (!m_profile->m_memoColList.contains(col)) + m_profile->m_memoColList.append(col); } else { - ui->comboBoxBnk_memoCol->setItemText(col, QString().setNum(col + 1)); - m_wiz->m_memoColList.removeOne(col); + ui->m_memoCol->setItemText(col, QString::number(col + 1)); + m_profile->m_memoColList.removeOne(col); } //allow only separate memo field occupy combobox - ui->comboBoxBnk_memoCol->blockSignals(true); - if (m_colTypeNum.value(ColumnMemo) != -1) - ui->comboBoxBnk_memoCol->setCurrentIndex(m_colTypeNum.value(ColumnMemo)); + ui->m_memoCol->blockSignals(true); + if (m_profile->m_colTypeNum.value(ColumnMemo) != -1) + ui->m_memoCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnMemo)); else - ui->comboBoxBnk_memoCol->setCurrentIndex(-1); - ui->comboBoxBnk_memoCol->blockSignals(false); + ui->m_memoCol->setCurrentIndex(-1); + ui->m_memoCol->blockSignals(false); return; } - if (m_colTypeNum.value(ColumnMemo) != -1) // check if this memo has any column 'number' assigned... - m_wiz->m_memoColList.removeOne(col); // ...if true remove it from memo list + if (m_profile->m_colTypeNum.value(ColumnMemo) != -1) // check if this memo has any column 'number' assigned... + m_profile->m_memoColList.removeOne(col); // ...if true remove it from memo list if(validateSelectedColumn(col, ColumnMemo)) - if (col != - 1 && !m_wiz->m_memoColList.contains(col)) - m_wiz->m_memoColList.append(col); + if (col != - 1 && !m_profile->m_memoColList.contains(col)) + m_profile->m_memoColList.append(col); } -void BankingPage::slotCategoryColSelected(int col) +void BankingPage::categoryColSelected(int col) { validateSelectedColumn(col, ColumnCategory); } -void BankingPage::slotNumberColSelected(int col) +void BankingPage::numberColSelected(int col) { validateSelectedColumn(col, ColumnNumber); } -void BankingPage::slotPayeeColSelected(int col) +void BankingPage::payeeColSelected(int col) { if (validateSelectedColumn(col, ColumnPayee)) if (!validateMemoComboBox()) // user could have it already in memo so... - slotMemoColSelected(col); // ...if true set memo field again + memoColSelected(col); // ...if true set memo field again } -void BankingPage::slotDateColSelected(int col) +void BankingPage::dateColSelected(int col) { validateSelectedColumn(col, ColumnDate); } -void BankingPage::slotDebitColSelected(int col) +void BankingPage::debitColSelected(int col) { validateSelectedColumn(col, ColumnDebit); } -void BankingPage::slotCreditColSelected(int col) +void BankingPage::creditColSelected(int col) { validateSelectedColumn(col, ColumnCredit); } -void BankingPage::slotAmountColSelected(int col) +void BankingPage::amountColSelected(int col) { validateSelectedColumn(col, ColumnAmount); } -void BankingPage::slotAmountToggled(bool checked) +void BankingPage::amountToggled(bool checked) { if (checked) { - ui->comboBoxBnk_amountCol->setEnabled(true); // disable credit & debit ui choices + ui->m_amountCol->setEnabled(true); // disable credit & debit ui choices ui->labelBnk_amount->setEnabled(true); ui->labelBnk_credits->setEnabled(false); ui->labelBnk_debits->setEnabled(false); - ui->comboBoxBnk_debitCol->setEnabled(false); - ui->comboBoxBnk_debitCol->setCurrentIndex(-1); - ui->comboBoxBnk_creditCol->setEnabled(false); - ui->comboBoxBnk_creditCol->setCurrentIndex(-1); + ui->m_debitCol->setEnabled(false); + ui->m_debitCol->setCurrentIndex(-1); + ui->m_creditCol->setEnabled(false); + ui->m_creditCol->setCurrentIndex(-1); } } -void BankingPage::slotDebitCreditToggled(bool checked) +void BankingPage::debitCreditToggled(bool checked) { if (checked) { - ui->comboBoxBnk_debitCol->setEnabled(true); // if 'debit/credit' selected + ui->m_debitCol->setEnabled(true); // if 'debit/credit' selected ui->labelBnk_debits->setEnabled(true); - ui->comboBoxBnk_creditCol->setEnabled(true); + ui->m_creditCol->setEnabled(true); ui->labelBnk_credits->setEnabled(true); - ui->comboBoxBnk_amountCol->setEnabled(false); // disable 'amount' ui choices - ui->comboBoxBnk_amountCol->setCurrentIndex(-1); // as credit/debit chosen + ui->m_amountCol->setEnabled(false); // disable 'amount' ui choices + ui->m_amountCol->setCurrentIndex(-1); // as credit/debit chosen ui->labelBnk_amount->setEnabled(false); } } -void BankingPage::slotOppositeSignsClicked(bool checked) +void BankingPage::oppositeSignsClicked(bool checked) { - m_oppositeSigns = checked; + m_profile->m_oppositeSigns = checked; } -void BankingPage::slotClearColumns() +void BankingPage::clearColumns() { - ui->comboBoxBnk_dateCol->setCurrentIndex(-1); - ui->comboBoxBnk_payeeCol->setCurrentIndex(-1); - ui->comboBoxBnk_memoCol->setCurrentIndex(-1); - ui->comboBoxBnk_numberCol->setCurrentIndex(-1); - ui->comboBoxBnk_amountCol->setCurrentIndex(-1); - ui->comboBoxBnk_debitCol->setCurrentIndex(-1); - ui->comboBoxBnk_creditCol->setCurrentIndex(-1); - ui->comboBoxBnk_categoryCol->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); + ui->m_payeeCol->setCurrentIndex(-1); + ui->m_memoCol->setCurrentIndex(-1); + ui->m_numberCol->setCurrentIndex(-1); + ui->m_amountCol->setCurrentIndex(-1); + ui->m_debitCol->setCurrentIndex(-1); + ui->m_creditCol->setCurrentIndex(-1); + ui->m_categoryCol->setCurrentIndex(-1); } -void BankingPage::resetComboBox(columnTypeE comboBox) +void BankingPage::resetComboBox(const columnTypeE comboBox) { switch (comboBox) { case ColumnAmount: - ui->comboBoxBnk_amountCol->setCurrentIndex(-1); + ui->m_amountCol->setCurrentIndex(-1); break; case ColumnCredit: - ui->comboBoxBnk_creditCol->setCurrentIndex(-1); + ui->m_creditCol->setCurrentIndex(-1); break; case ColumnDate: - ui->comboBoxBnk_dateCol->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); break; case ColumnDebit: - ui->comboBoxBnk_debitCol->setCurrentIndex(-1); + ui->m_debitCol->setCurrentIndex(-1); break; case ColumnMemo: - ui->comboBoxBnk_memoCol->setCurrentIndex(-1); + ui->m_memoCol->setCurrentIndex(-1); break; case ColumnNumber: - ui->comboBoxBnk_numberCol->setCurrentIndex(-1); + ui->m_numberCol->setCurrentIndex(-1); break; case ColumnPayee: - ui->comboBoxBnk_payeeCol->setCurrentIndex(-1); + ui->m_payeeCol->setCurrentIndex(-1); break; case ColumnCategory: - ui->comboBoxBnk_categoryCol->setCurrentIndex(-1); + ui->m_categoryCol->setCurrentIndex(-1); break; default: - KMessageBox::sorry(m_wiz, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections." + KMessageBox::sorry(m_dlg, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections." , comboBox), i18n("CSV import")); } } -bool BankingPage::validateSelectedColumn(int col, columnTypeE type) +bool BankingPage::validateSelectedColumn(const int col, const columnTypeE type) { - if (m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... - m_colNumType.remove(m_colTypeNum[type]); // ...if true remove 'type' assigned to this column 'number' + if (m_profile->m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... + m_profile->m_colNumType.remove(m_profile->m_colTypeNum[type]); // ...if true remove 'type' assigned to this column 'number' bool ret = true; if (col == -1) { // user only wanted to reset his column so allow him - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - } else if (m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned - KMessageBox::information(m_wiz, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", - m_colTypeName.value(m_colNumType[col]))); - resetComboBox(m_colNumType[col]); + m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' + } else if (m_profile->m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned + KMessageBox::information(m_dlg, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", + m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); + resetComboBox(m_profile->m_colNumType.value(col)); resetComboBox(type); ret = false; } else { - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - m_colNumType[col] = type; // assign new 'type' to this column 'number' + m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' + m_profile->m_colNumType[col] = type; // assign new 'type' to this column 'number' } emit completeChanged(); return ret; } -void BankingPage::saveSettings() +bool BankingPage::validateCreditDebit() { - KConfigGroup profileNamesGroup(m_wiz->m_config, "ProfileNames"); - profileNamesGroup.writeEntry("Bank", m_wiz->m_profileList); - profileNamesGroup.writeEntry("PriorBank", m_wiz->m_profileList.indexOf(m_wiz->m_profileName)); - profileNamesGroup.config()->sync(); - - KConfigGroup profilesGroup(m_wiz->m_config, "Bank-" + m_wiz->m_profileName); - if (m_wiz->m_inFileName.startsWith(QStringLiteral("/home/"))) // replace /home/user with ~/ for brevity - { - QFileInfo fileInfo = QFileInfo(m_wiz->m_inFileName); - if (fileInfo.isFile()) - m_wiz->m_inFileName = fileInfo.absolutePath(); - m_wiz->m_inFileName = QStringLiteral("~/") + m_wiz->m_inFileName.section('/',3); - } - - profilesGroup.writeEntry("Directory", m_wiz->m_inFileName); - profilesGroup.writeEntry("Encoding", m_wiz->m_encodeIndex); - profilesGroup.writeEntry("DateFormat", m_wiz->m_dateFormatIndex); - profilesGroup.writeEntry("OppositeSigns", m_oppositeSigns); - profilesGroup.writeEntry("FieldDelimiter", m_wiz->m_fieldDelimiterIndex); - profilesGroup.writeEntry("TextDelimiter", m_wiz->m_textDelimiterIndex); - profilesGroup.writeEntry("DecimalSymbol", m_wiz->m_decimalSymbolIndex); - profilesGroup.writeEntry("StartLine", m_wiz->m_startLine - 1); - profilesGroup.writeEntry("TrailerLines", m_wiz->m_trailerLines); - - profilesGroup.writeEntry("DateCol", m_colTypeNum.value(ColumnDate)); - profilesGroup.writeEntry("PayeeCol", m_colTypeNum.value(ColumnPayee)); - - QList list = m_wiz->m_memoColList; - int posn = 0; - if ((posn = list.indexOf(-1)) > -1) { - list.removeOne(-1); - } - profilesGroup.writeEntry("MemoCol", list); - - profilesGroup.writeEntry("NumberCol", ui->comboBoxBnk_numberCol->currentIndex()); - profilesGroup.writeEntry("AmountCol", ui->comboBoxBnk_amountCol->currentIndex()); - profilesGroup.writeEntry("DebitCol", ui->comboBoxBnk_debitCol->currentIndex()); - profilesGroup.writeEntry("CreditCol", ui->comboBoxBnk_creditCol->currentIndex()); - profilesGroup.writeEntry("CategoryCol", ui->comboBoxBnk_categoryCol->currentIndex()); - profilesGroup.config()->sync(); -} - -void BankingPage::readSettings(const KSharedConfigPtr& config) -{ - for (int i = 0; i < m_wiz->m_profileList.count(); ++i) { - if (m_wiz->m_profileList[i] != m_wiz->m_profileName) - continue; - KConfigGroup profilesGroup(config, QStringLiteral("Bank-") + m_wiz->m_profileList[i]); - m_wiz->m_inFileName = profilesGroup.readEntry("Directory", QString()); - m_colTypeNum[ColumnPayee] = profilesGroup.readEntry("PayeeCol", -1); - m_colTypeNum[ColumnNumber] = profilesGroup.readEntry("NumberCol", -1); - m_colTypeNum[ColumnAmount] = profilesGroup.readEntry("AmountCol", -1); - m_colTypeNum[ColumnDebit] = profilesGroup.readEntry("DebitCol", -1); - m_colTypeNum[ColumnCredit] = profilesGroup.readEntry("CreditCol", -1); - m_colTypeNum[ColumnDate] = profilesGroup.readEntry("DateCol", -1); - m_colTypeNum[ColumnCategory] = profilesGroup.readEntry("CategoryCol", -1); - m_colTypeNum[ColumnMemo] = -1; // initialize, otherwise random data may go here - m_oppositeSigns = profilesGroup.readEntry("OppositeSigns", 0); - m_wiz->m_memoColList = profilesGroup.readEntry("MemoCol", QList()); - m_wiz->m_dateFormatIndex = profilesGroup.readEntry("DateFormat", -1); - m_wiz->m_textDelimiterIndex = profilesGroup.readEntry("TextDelimiter", 0); - m_wiz->m_fieldDelimiterIndex = profilesGroup.readEntry("FieldDelimiter", -1); - m_wiz->m_decimalSymbolIndex = profilesGroup.readEntry("DecimalSymbol", -1); - - if (m_wiz->m_decimalSymbolIndex != -1 && m_wiz->m_decimalSymbolIndex != 2) { - m_wiz->m_parse->setDecimalSymbolIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setDecimalSymbol(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_parse->setThousandsSeparatorIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_decimalSymbol = m_wiz->m_parse->decimalSymbol(m_wiz->m_decimalSymbolIndex); - - } else - m_wiz->m_decimalSymbol.clear(); - - m_wiz->m_parse->setFieldDelimiterIndex(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_parse->setTextDelimiterIndex(m_wiz->m_textDelimiterIndex); - m_wiz->m_fieldDelimiterCharacter = m_wiz->m_parse->fieldDelimiterCharacter(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_textDelimiterCharacter = m_wiz->m_parse->textDelimiterCharacter(m_wiz->m_textDelimiterIndex); - m_wiz->m_startLine = profilesGroup.readEntry("StartLine", 0) + 1; - m_wiz->m_trailerLines = profilesGroup.readEntry("TrailerLines", 0); - m_wiz->m_encodeIndex = profilesGroup.readEntry("Encoding", 0); - break; + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + // process credit/debit field + if (m_profile->m_colTypeNum.value(ColumnCredit) != -1 && + m_profile->m_colTypeNum.value(ColumnDebit) != -1) { + QString credit = m_imp->m_file->m_model->item(row, m_profile->m_colTypeNum.value(ColumnCredit))->text(); + QString debit = m_imp->m_file->m_model->item(row, m_profile->m_colTypeNum.value(ColumnDebit))->text(); + m_imp->processCreditDebit(credit, debit); + if (!credit.isEmpty() && !debit.isEmpty()) { + int ret = KMessageBox::questionYesNoCancel(m_dlg, + i18n("
The %1 field contains '%2'
" + "
and the %3 field contains '%4'.
" + "
Please choose which you wish to accept.
", + m_dlg->m_colTypeName.value(ColumnDebit), + debit, + m_dlg->m_colTypeName.value(ColumnCredit), + credit), + i18n("CSV invalid field values"), + KGuiItem(i18n("Accept %1", m_dlg->m_colTypeName.value(ColumnDebit))), + KGuiItem(i18n("Accept %1", m_dlg->m_colTypeName.value(ColumnCredit))), + KGuiItem(i18n("Cancel"))); + switch(ret) { + case KMessageBox::Cancel: + return false; + case KMessageBox::Yes: + m_imp->m_file->m_model->item(row, m_profile->m_colTypeNum.value(ColumnCredit))->setText(QString()); + break; + case KMessageBox::No: + m_imp->m_file->m_model->item(row, m_profile->m_colTypeNum.value(ColumnDebit))->setText(QString()); + break; + } + } + } } + return true; } void BankingPage::makeQIF(MyMoneyStatement& st, QFile& file) @@ -485,235 +427,3 @@ buffer.clear(); } } - -bool BankingPage::createStatement(MyMoneyStatement& st) -{ - if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one - return true; - - st.m_eType = MyMoneyStatement::etNone; - if (m_wiz->m_autodetect.value(CSVWizard::AutoAccountBank)) - m_wiz->detectAccount(st); - - m_hashSet.clear(); - for (int line = m_wiz->m_startLine - 1; line < m_wiz->m_endLine; ++line) - if (!processBankLine(m_wiz->m_lineList[line], st)) // parse fields - return false; - return true; -} - -bool BankingPage::processBankLine(const QString &line, MyMoneyStatement &st) -{ - MyMoneyStatement::Transaction tr; - m_columnList = m_wiz->m_parse->parseLine(line); // split line into fields - if (m_columnList.count() < m_wiz->m_endColumn) { - if (!m_wiz->m_accept) { - int ret = KMessageBox::questionYesNoCancel(m_wiz, i18n("
Row number %1 does not have the expected number of columns.
" - "
This might not be a problem, but it may be a header line.
" - "
You may accept all similar items, or just this one, or cancel.
", - QString::number(m_wiz->m_row)), i18n("CSV import"), - KGuiItem(i18n("Accept All")), - KGuiItem(i18n("Accept This")), - KGuiItem(i18n("Cancel"))); - if (ret == KMessageBox::Cancel) - return false; - if (ret == KMessageBox::Yes) - m_wiz->m_accept = true; - } - } - - int neededFieldsCount = 0; - QString memo; - QString txt; - - for (int i = 0; i < m_columnList.count(); ++i) { - m_columnList[i].trimmed().remove(m_wiz->m_textDelimiterCharacter); - } - - // process number field - if (m_colTypeNum.value(ColumnNumber) != -1) - tr.m_strNumber = txt; - - // process date field - if (m_colTypeNum.value(ColumnDate) != -1) { - ++neededFieldsCount; - txt = m_columnList[m_colTypeNum[ColumnDate]]; - tr.m_datePosted = m_wiz->m_convertDate->convertDate(txt); // Date column - if (tr.m_datePosted == QDate()) { - KMessageBox::sorry(m_wiz, i18n("
An invalid date has been detected during import.
" - "
'%1'
" - "Please check that you have set the correct date format,\n" - "
and start and end lines.
" - , txt), i18n("CSV import")); - m_wiz->m_importError = true; - return false; - } - } - - // process payee field - if (m_colTypeNum.value(ColumnPayee) != -1) { - ++neededFieldsCount; - tr.m_strPayee = m_columnList[m_colTypeNum[ColumnPayee]]; - } - - // process memo field - if (m_colTypeNum.value(ColumnMemo) != -1) - memo.append(m_columnList[m_colTypeNum[ColumnMemo]]); - - for (int i = 0; i < m_wiz->m_memoColList.count(); ++i) { - if (m_wiz->m_memoColList[i] != m_colTypeNum[ColumnMemo]) { - if (!memo.isEmpty()) - memo.append(QChar(QLatin1Char('\n'))); - if (m_wiz->m_memoColList[i] < m_columnList.count()) - memo.append(m_columnList[m_wiz->m_memoColList[i]]); - } - } - tr.m_strMemo = memo; - - // process amount field - if (m_colTypeNum.value(ColumnAmount) != -1) { - ++neededFieldsCount; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnAmount]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnAmount]]; - if (txt.startsWith(QChar(QLatin1Char('(')))) { // check if brackets notation is used for negative numbers - txt.remove(QRegularExpression(QStringLiteral("[()]"))); - txt.prepend(QChar(QLatin1Char('-'))); - } - - if (m_oppositeSigns) { // change signs to opposite if requested by user - if(txt.startsWith(QChar(QLatin1Char('-')))) - txt.remove(0, 1); - else if (txt.startsWith(QChar(QLatin1Char('+')))) - txt.replace(0, 1, QChar(QLatin1Char('-'))); - else - txt.prepend(QChar(QLatin1Char('-'))); - } - if (txt.isEmpty()) - tr.m_amount = MyMoneyMoney(); - else - tr.m_amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - } - - // process credit/debit field - if (m_colTypeNum.value(ColumnCredit) != -1 && - m_colTypeNum.value(ColumnDebit) != -1) { - ++neededFieldsCount; - ++neededFieldsCount; - if (!processCreditDebit(m_columnList[m_colTypeNum[ColumnCredit]], - m_columnList[m_colTypeNum[ColumnDebit]], - tr.m_amount)) - return false; - } - - MyMoneyStatement::Split s1; - s1.m_amount = tr.m_amount; - s1.m_strMemo = tr.m_strMemo; - MyMoneyStatement::Split s2 = s1; - s2.m_reconcile = tr.m_reconcile; - s2.m_amount = (-s1.m_amount); - - // process category field - if (m_colTypeNum.value(ColumnCategory) != -1) { - txt = m_columnList[m_colTypeNum[ColumnCategory]]; - QString accountId = m_wiz->m_csvUtil->checkCategory(txt, s1.m_amount, s2.m_amount); - - if (!accountId.isEmpty()) { - s2.m_accountId = accountId; - s2.m_strCategoryName = txt; - tr.m_listSplits.append(s2); - } - } - - if (neededFieldsCount <= 2) { - QString errMsg = i18n("
The columns selected are invalid.
" - "There must an amount or debit and credit fields, plus date and payee fields."); - if (m_wiz->m_skipSetup) - errMsg += i18n("
You possibly need to check the start and end line settings, or reset 'Skip setup'.
"); - KMessageBox::information(m_wiz, errMsg); - m_wiz->m_importError = true; - return KMessageBox::Cancel; - } - - // calculate hash - txt = line; - QString hashBase = QString("%1-%2") - .arg(tr.m_datePosted.toString(Qt::ISODate)) - .arg(MyMoneyTransaction::hash(txt.remove(m_wiz->m_textDelimiterCharacter))); - QString hash; - for (uchar idx = 0; idx < 0xFF; ++idx) { // assuming threre will be no more than 256 transactions with the same hashBase - hash = QString("%1-%2").arg(hashBase).arg(idx); - QSet::const_iterator it = m_hashSet.constFind(hash); - if (it == m_hashSet.constEnd()) - break; - } - m_hashSet.insert(hash); - tr.m_strBankID = hash; - - st.m_listTransactions.append(tr); // Add the transaction to the statement - return true; -} - -bool BankingPage::processCreditDebit(QString& credit, QString& debit , MyMoneyMoney& amount) -{ - QString decimalSymbol = m_wiz->m_decimalSymbol; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnCredit]); - decimalSymbol = m_wiz->m_parse->decimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - if (credit.startsWith(QChar(QLatin1Char('(')))) { // check if brackets notation is used for negative numbers - credit.remove(QRegularExpression(QStringLiteral("[()]"))); - credit.prepend(QChar(QLatin1Char('-'))); - } - if (debit.startsWith(QChar(QLatin1Char('(')))) { // check if brackets notation is used for negative numbers - debit.remove(QRegularExpression(QStringLiteral("[()]"))); - debit.prepend(QChar(QLatin1Char('-'))); - } - - if (!credit.isEmpty() && !debit.isEmpty()) { // we do not expect both fields to be non-zero - if (MyMoneyMoney(credit).isZero()) - credit = QString(); - if (MyMoneyMoney(debit).isZero()) - debit = QString(); - } - - if (!debit.startsWith(QChar(QLatin1Char('-'))) && !debit.isEmpty()) // ensure debit field is negative - debit.prepend(QChar(QLatin1Char('-'))); - - if (!credit.isEmpty() && debit.isEmpty()) - amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(credit)); - else if (credit.isEmpty() && !debit.isEmpty()) - amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(debit)); - else if (!credit.isEmpty() && !debit.isEmpty()) { // both fields are non-empty and non-zero so let user decide - int ret = KMessageBox::questionYesNoCancel(m_wiz, - i18n("
The %1 field contains '%2'
" - "
and the %3 field contains '%4'.
" - "
Please choose which you wish to accept.
", - m_colTypeName.value(ColumnDebit), - m_columnList[m_colTypeNum.value(ColumnDebit)], - m_colTypeName.value(ColumnCredit), - m_columnList[m_colTypeNum.value(ColumnCredit)]), - i18n("CSV invalid field values"), - KGuiItem(i18n("Accept %1", m_colTypeName.value(ColumnDebit))), - KGuiItem(i18n("Accept %1", m_colTypeName.value(ColumnCredit))), - KGuiItem(i18n("Cancel"))); - if (ret == KMessageBox::Cancel) - return false; - if (ret == KMessageBox::Yes) - amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(debit)); - else if (ret == KMessageBox::No) - amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(credit)); - } else { - amount = MyMoneyMoney(); // both fields are empty and zero so set amount to zero - - } - - return true; -} diff --git a/kmymoney/plugins/csvimport/bankingwizardpage.ui b/kmymoney/plugins/csvimport/bankingwizardpage.ui --- a/kmymoney/plugins/csvimport/bankingwizardpage.ui +++ b/kmymoney/plugins/csvimport/bankingwizardpage.ui @@ -2,6 +2,14 @@ BankingPage + + + 0 + 0 + 681 + 253 + + 0 @@ -85,7 +93,7 @@ - + 0 @@ -101,7 +109,7 @@ - + 0 @@ -133,7 +141,7 @@ - + 0 @@ -165,7 +173,7 @@ - + 0 @@ -205,7 +213,7 @@ - + 0 @@ -221,7 +229,7 @@ - + 0 @@ -237,7 +245,7 @@ - + 0 @@ -266,7 +274,7 @@ - + 0 @@ -282,7 +290,7 @@ - + 0 @@ -311,7 +319,7 @@ - + 0 @@ -330,7 +338,7 @@ - + 0 @@ -375,7 +383,7 @@ - + 0 diff --git a/kmymoney/plugins/csvimport/csvimporter.h b/kmymoney/plugins/csvimport/csvimporter.h new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/csvimport/csvimporter.h @@ -0,0 +1,363 @@ +/*************************************************************************** + csvimporter.h + ------------------- + begin : Sun May 21 2017 + copyright : (C) 2015 by Allan Anderson + email : agander93@gmail.com + copyright : (C) 2017 by Łukasz Wojniłowicz + email : lukasz.wojnilowicz@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef CSVIMPORTER_H +#define CSVIMPORTER_H + +// ---------------------------------------------------------------------------- +// KDE Includes +#include +#include + +// ---------------------------------------------------------------------------- +// QT Includes + +#include + +// Project Includes + +#include "convdate.h" +#include "csvutil.h" +#include "mymoneystatement.h" + +#include + +class CSVWizard; +class CSVImporter; + +class CSVWizardPage : public QWizardPage +{ +public: + CSVWizardPage(CSVWizard *dlg, CSVImporter *imp) : QWizardPage(nullptr), m_dlg(dlg), m_imp(imp) {} + +protected: + CSVWizard *m_dlg; + CSVImporter *m_imp; +}; + +enum profileTypeE { ProfileBank, ProfileInvest, + ProfileCurrencyPrices, ProfileStockPrices + }; + +enum profilesActionE { ProfilesAdd, ProfilesRemove, ProfilesRename, ProfilesUpdateLastUsed }; + +enum autodetectTypeE { AutoFieldDelimiter, AutoDecimalSymbol, AutoDateFormat, + AutoAccountInvest, AutoAccountBank + }; + +enum columnTypeE { ColumnDate, ColumnMemo, + ColumnNumber, ColumnPayee, ColumnAmount, + ColumnCredit, ColumnDebit, ColumnCategory, + ColumnType, ColumnPrice, ColumnQuantity, + ColumnFee, ColumnSymbol, ColumnName, + ColumnEmpty = 0xFE, ColumnInvalid = 0xFF + }; + +enum miscSettingsE { ConfDirectory, ConfEncoding, ConfDateFormat, + ConfFieldDelimiter, ConfTextDeimiter, ConfDecimalSymbol, + ConfStartLine, ConfTrailerLines, + ConfOppositeSigns, + ConfFeeIsPercentage, ConfFeeRate, ConfMinFee, + ConfSecurityName, ConfSecuritySymbol, ConfCurrencySymbol, + ConfPriceFraction, ConfDontAsk, + ConfHeight, ConfWidth +}; + +enum validationResultE { ValidActionType, InvalidActionValues, NoActionType }; + + +class CSVProfile +{ +protected: + CSVProfile() {} + CSVProfile(const QString &profileName, int encodingMIBEnum, + int startLine, int trailerLines, + int dateFormatIndex, int fieldDelimiterIndex, int textDelimiterIndex, int decimalSymbolIndex, + QMap &colTypeNum) : + m_profileName(profileName), m_encodingMIBEnum(encodingMIBEnum), + m_startLine(startLine), m_trailerLines(trailerLines), + m_dateFormatIndex(dateFormatIndex), m_fieldDelimiterIndex(fieldDelimiterIndex), + m_textDelimiterIndex(textDelimiterIndex), m_decimalSymbolIndex(decimalSymbolIndex), + m_colTypeNum(colTypeNum) + { + initColNumType(); + } + void readSettings(const KConfigGroup &profilesGroup); + void writeSettings(KConfigGroup &profilesGroup); + void initColNumType() { + for (auto it = m_colTypeNum.constBegin(); it != m_colTypeNum.constEnd(); ++it) + m_colNumType.insert(it.value(), it.key()); + } + +public: + virtual ~CSVProfile() {} + virtual profileTypeE type() const = 0; + virtual bool readSettings(const KSharedConfigPtr &config) = 0; + virtual void writeSettings(const KSharedConfigPtr &config) = 0; + + QString m_profileName; + QString m_lastUsedDirectory; + + int m_encodingMIBEnum; + + int m_startLine; + int m_endLine; + int m_trailerLines; + + int m_dateFormatIndex; + int m_fieldDelimiterIndex; + int m_textDelimiterIndex; + int m_decimalSymbolIndex; + + QMap m_colTypeNum; + QMap m_colNumType; +}; + +class BankingProfile : public CSVProfile +{ +public: + profileTypeE type() const { return ProfileBank; } + bool readSettings(const KSharedConfigPtr &config); + void writeSettings(const KSharedConfigPtr &config); + + QList m_memoColList; + + bool m_oppositeSigns; +}; + +class InvestmentProfile : public CSVProfile +{ +public: + profileTypeE type() const { return ProfileInvest; } + bool readSettings(const KSharedConfigPtr &config); + void writeSettings(const KSharedConfigPtr &config); + + QMap m_transactionNames; + + QString m_feeRate; + QString m_minFee; + QString m_securityName; + QString m_securitySymbol; + + QList m_memoColList; + + int m_priceFraction; + int m_feeIsPercentage; + int m_dontAsk; +}; + +class PricesProfile : public CSVProfile +{ +public: + explicit PricesProfile() : CSVProfile() {} + explicit PricesProfile(const profileTypeE profileType) : CSVProfile(), m_profileType(profileType) {} + PricesProfile(QString profileName, int encodingMIBEnum, + int startLine, int trailerLines, + int dateFormatIndex, int fieldDelimiterIndex, int textDelimiterIndex, int decimalSymbolIndex, + QMap colTypeNum, + int priceFraction, profileTypeE profileType) : + CSVProfile(profileName, encodingMIBEnum, + startLine, trailerLines, + dateFormatIndex, fieldDelimiterIndex, textDelimiterIndex, decimalSymbolIndex, + colTypeNum), + m_priceFraction(priceFraction), m_profileType(profileType) {} + + profileTypeE type() const { return m_profileType; } + bool readSettings(const KSharedConfigPtr &config); + void writeSettings(const KSharedConfigPtr &config); + + QString m_securityName; + QString m_securitySymbol; + QString m_currencySymbol; + + int m_dontAsk; + int m_priceFraction; + + profileTypeE m_profileType; +}; + +class CSVFile +{ +public: + explicit CSVFile(); + ~CSVFile(); + + void getStartEndRow(CSVProfile *profile); + /** + * If delimiter = -1 this method tries different field + * delimiters to get the one with which file has the most columns. + * Otherwise it gets only column count for specified delimiter. + */ + void getColumnCount(CSVProfile *profile, const QStringList &rows); + + /** + * This method gets the filename of + * the financial statement. + */ + bool getInFileName(QString startDir = QString()); + + void setupParser(CSVProfile *profile); + + /** + * This method gets file into buffer + * It will laso store file's end column and row. + */ + void readFile(CSVProfile *profile); + + CsvUtil *m_csvUtil; + Parse *m_parse; + QStandardItemModel *m_model; + + QString m_inFileName; + + int m_columnCount; + int m_rowCount; +}; + +class CSVImporter : public QObject +{ + Q_OBJECT +public: + explicit CSVImporter(); + ~CSVImporter(); + + /** + * This method will silently import csv file. Main purpose of this method are online quotes. + */ + MyMoneyStatement unattendedPricesImport(const QString &filename, PricesProfile *profile); + + static KSharedConfigPtr configFile(); + void profileFactory(const profileTypeE type, const QString &name); + void readMiscSettings(); + + /** + * This method ensures that configuration file contains all neccesary fields + * and that it is up to date. + */ + void validateConfigFile(); + + /** + * This method contains routines to update configuration file + * from kmmVer to latest. + */ + bool updateConfigFile(QList &confVer); + + /** + * This method will update [ProfileNames] in csvimporterrrc + */ + static bool profilesAction(const profileTypeE type, const profilesActionE action, const QString &name, const QString &newname); + + /** + * This methods will ensure that fields of input rows are correct. + */ + bool validateDateFormat(const int col); + bool validateDecimalSymbols(const QList &columns); + bool validateCurrencies(const PricesProfile *profile); + bool validateSecurity(const PricesProfile *profile); + bool validateSecurity(const InvestmentProfile *profile); + bool validateSecurities(); + validationResultE validateActionType(MyMoneyStatement::Transaction &tr); + + /** + * This method will try to detect decimal symbol in input column. + */ + int detectDecimalSymbols(const QList &columns); + int detectDecimalSymbol(const int col, const QString &exclude); + + /** + * This method will try to detect account from csv header. + */ + QList findAccounts(const QList &accountTypes, const QString &statementHeader); + bool detectAccount(MyMoneyStatement &st); + + /** + * This methods will evaluate input row and append it to a statement. + */ + bool processBankRow(MyMoneyStatement &st, const BankingProfile *profile, const int row); + bool processInvestRow(MyMoneyStatement &st, const InvestmentProfile *profile, const int row); + bool processPriceRow(MyMoneyStatement &st, const PricesProfile *profile, const int row); + + /** + * This methods will evaluate fields of input row and return statement's useful value. + */ + QDate processDateField(const int row, const int col); + MyMoneyMoney processCreditDebit(QString &credit, QString &debit ); + MyMoneyMoney processPriceField(const InvestmentProfile *profile, const int row, const int col); + MyMoneyMoney processPriceField(const PricesProfile *profile, const int row, const int col); + MyMoneyMoney processAmountField(const CSVProfile *profile, const int row, const int col); + MyMoneyMoney processQuantityField(const CSVProfile *profile, const int row, const int col); + MyMoneyStatement::Transaction::EAction processActionTypeField(const InvestmentProfile *profile, const int row, const int col); + + /** + * This method creates valid set of possible transactions + * according to quantity, amount and price + */ + QList createValidActionTypes(MyMoneyStatement::Transaction &tr); + + /** + * This method will add fee column to model based on amount and fee rate. + */ + bool calculateFee(); + + /** + * This method gets securities from investment statement and + * tries to get pairs of symbol and name either + * from KMM or from statement data. + * In case it's not successfull onlySymbols and onlyNames won't be empty. + */ + bool sortSecurities(QSet& onlySymbols, QSet& onlyNames, QMap& mapSymbolName); + + /** + * Helper method to set decimal symbol in case it was set to autodetect. + */ + void setupFieldDecimalSymbol(int col); + + /** + * Helper method to get all column numbers that were pointed as nummeric + */ + QList getNumericalColumns(); + + bool createStatement(MyMoneyStatement &st); + + ConvertDate *m_convertDate; + CSVFile *m_file; + CSVProfile *m_profile; + KSharedConfigPtr m_config; + + bool m_isActionTypeValidated; + + QList m_priceFractions; + QSet m_hashSet; + QMap m_decimalSymbolIndexMap; + QMap m_mapSymbolName; + QMap m_autodetect; + + static const QMap m_colTypeConfName; + static const QMap m_profileConfPrefix; + static const QMap m_transactionConfName; + static const QMap m_miscSettingsConfName; + static const QString m_confProfileNames; + static const QString m_confPriorName; + static const QString m_confMiscName; + +signals: + void statementReady(MyMoneyStatement&); +}; + +#endif diff --git a/kmymoney/plugins/csvimport/csvimporter.cpp b/kmymoney/plugins/csvimport/csvimporter.cpp new file mode 100644 --- /dev/null +++ b/kmymoney/plugins/csvimport/csvimporter.cpp @@ -0,0 +1,1720 @@ +/*************************************************************************** + csvimporter.cpp + ------------------- + begin : Sun May 21 2017 + copyright : (C) 2010 by Allan Anderson + email : agander93@gmail.com + copyright : (C) 2016-2017 by Łukasz Wojniłowicz + email : lukasz.wojnilowicz@gmail.com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "csvimporter.h" + +// ---------------------------------------------------------------------------- +// QT Includes + +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +// KDE Includes + +#include +#include +#include + +// ---------------------------------------------------------------------------- +// Project Includes + +#include "mymoneystatementreader.h" +#include "mymoneyfile.h" + +const QMap CSVImporter::m_profileConfPrefix = QMap{ + {ProfileBank, QStringLiteral("Bank")}, + {ProfileInvest, QStringLiteral("Invest")}, + {ProfileCurrencyPrices, QStringLiteral("CPrices")}, + {ProfileStockPrices, QStringLiteral("SPrices")} +}; + +const QMap CSVImporter::m_colTypeConfName = QMap{ + {ColumnDate, QStringLiteral("DateCol")}, + {ColumnMemo, QStringLiteral("MemoCol")}, + {ColumnNumber, QStringLiteral("NumberCol")}, + {ColumnPayee, QStringLiteral("PayeeCol")}, + {ColumnAmount, QStringLiteral("AmountCol")}, + {ColumnCredit, QStringLiteral("CreditCol")}, + {ColumnDebit, QStringLiteral("DebitCol")}, + {ColumnCategory, QStringLiteral("CategoryCol")}, + {ColumnType, QStringLiteral("TypeCol")}, + {ColumnPrice, QStringLiteral("PriceCol")}, + {ColumnQuantity, QStringLiteral("QuantityCol")}, + {ColumnFee, QStringLiteral("FeeCol")}, + {ColumnSymbol, QStringLiteral("SymbolCol")}, + {ColumnName, QStringLiteral("NameCol")}, +}; + +const QMap CSVImporter::m_miscSettingsConfName = QMap{ + {ConfDirectory, QStringLiteral("Directory")}, + {ConfEncoding, QStringLiteral("Encoding")}, + {ConfDateFormat, QStringLiteral("DateFormat")}, + {ConfFieldDelimiter, QStringLiteral("FieldDelimiter")}, + {ConfTextDeimiter, QStringLiteral("TextDelimiter")}, + {ConfDecimalSymbol, QStringLiteral("DecimalSymbol")}, + {ConfStartLine, QStringLiteral("StartLine")}, + {ConfTrailerLines, QStringLiteral("TrailerLines")}, + {ConfOppositeSigns, QStringLiteral("OppositeSigns")}, + {ConfFeeIsPercentage, QStringLiteral("FeeIsPercentage")}, + {ConfFeeRate, QStringLiteral("FeeRate")}, + {ConfMinFee, QStringLiteral("MinFee")}, + {ConfSecurityName, QStringLiteral("SecurityName")}, + {ConfSecuritySymbol, QStringLiteral("SecuritySymbol")}, + {ConfCurrencySymbol, QStringLiteral("CurrencySymbol")}, + {ConfPriceFraction, QStringLiteral("PriceFraction")}, + {ConfDontAsk, QStringLiteral("DontAsk")}, + {ConfHeight, QStringLiteral("Height")}, + {ConfWidth, QStringLiteral("Width")} +}; + +const QMap CSVImporter::m_transactionConfName = QMap{ + {MyMoneyStatement::Transaction::eaBuy, QStringLiteral("BuyParam")}, + {MyMoneyStatement::Transaction::eaSell, QStringLiteral("SellParam")}, + {MyMoneyStatement::Transaction::eaReinvestDividend, QStringLiteral("ReinvdivParam")}, + {MyMoneyStatement::Transaction::eaCashDividend, QStringLiteral("DivXParam")}, + {MyMoneyStatement::Transaction::eaInterest, QStringLiteral("IntIncParam")}, + {MyMoneyStatement::Transaction::eaShrsin, QStringLiteral("ShrsinParam")}, + {MyMoneyStatement::Transaction::eaShrsout, QStringLiteral("ShrsoutParam")} +}; + +const QString CSVImporter::m_confProfileNames = QStringLiteral("ProfileNames"); +const QString CSVImporter::m_confPriorName = QStringLiteral("Prior"); +const QString CSVImporter::m_confMiscName = QStringLiteral("Misc"); + +CSVImporter::CSVImporter() +{ + m_convertDate = new ConvertDate; + m_file = new CSVFile; + + m_priceFractions << MyMoneyMoney(0.01) << MyMoneyMoney(0.1) << MyMoneyMoney::ONE << MyMoneyMoney(10) << MyMoneyMoney(100); + + validateConfigFile(); + readMiscSettings(); +} +CSVImporter::~CSVImporter() +{ + delete m_convertDate; + delete m_file; +} + +MyMoneyStatement CSVImporter::unattendedPricesImport(const QString &filename, PricesProfile *profile) +{ + MyMoneyStatement st; + m_profile = profile; + + if (m_file->getInFileName(filename)) { + m_file->readFile(m_profile); + m_file->setupParser(m_profile); + + if (!createStatement(st)) + st = MyMoneyStatement(); + } + + return st; +} + +KSharedConfigPtr CSVImporter::configFile() +{ + return KSharedConfig::openConfig(QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)) + .filePath(QStringLiteral("csvimporterrc"))); +} + +void CSVImporter::profileFactory(const profileTypeE type, const QString &name) +{ + if (!m_profile) + delete m_profile; + + switch (type) { + default: + case ProfileInvest: + m_profile = new InvestmentProfile; + break; + case ProfileBank: + m_profile = new BankingProfile; + break; + case ProfileCurrencyPrices: + case ProfileStockPrices: + m_profile = new PricesProfile(type); + break; + } + m_profile->m_profileName = name; +} + +void CSVImporter::readMiscSettings() { + KConfigGroup miscGroup(configFile(), m_confMiscName); + m_autodetect.clear(); + m_autodetect.insert(AutoFieldDelimiter, miscGroup.readEntry(QStringLiteral("AutoFieldDelimiter"), true)); + m_autodetect.insert(AutoDecimalSymbol, miscGroup.readEntry(QStringLiteral("AutoDecimalSymbol"), true)); + m_autodetect.insert(AutoDateFormat, miscGroup.readEntry(QStringLiteral("AutoDateFormat"), true)); + m_autodetect.insert(AutoAccountInvest, miscGroup.readEntry(QStringLiteral("AutoAccountInvest"), true)); + m_autodetect.insert(AutoAccountBank, miscGroup.readEntry(QStringLiteral("AutoAccountBank"), true)); +} + +void CSVImporter::validateConfigFile() +{ + const KSharedConfigPtr config = configFile(); + KConfigGroup profileNamesGroup(config, m_confProfileNames); + if (!profileNamesGroup.exists()) { + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileBank), QStringList()); + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileInvest), QStringList()); + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileCurrencyPrices), QStringList()); + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileStockPrices), QStringList()); + profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(ProfileBank), int()); + profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(ProfileInvest), int()); + profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(ProfileCurrencyPrices), int()); + profileNamesGroup.writeEntry(m_confPriorName + m_profileConfPrefix.value(ProfileStockPrices), int()); + profileNamesGroup.sync(); + } + + KConfigGroup miscGroup(config, m_confMiscName); + if (!miscGroup.exists()) { + miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfHeight), "400"); + miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfWidth), "800"); + miscGroup.sync(); + } + + QList confVer = miscGroup.readEntry("KMMVer", QList {0, 0, 0}); + if (updateConfigFile(confVer)) // write kmmVer only if there were no errors + miscGroup.writeEntry("KMMVer", confVer); +} + +bool CSVImporter::updateConfigFile(QList &confVer) +{ + bool ret = true; + + QList kmmVer = QList {5, 0, 0}; + int kmmVersion = kmmVer.at(0) * 100 + kmmVer.at(1) * 10 + kmmVer.at(2); + int confVersion = confVer.at(0) * 100 + confVer.at(1) * 10 + confVer.at(2); + if (confVersion > kmmVersion) { + KMessageBox::information(0, + i18n("Version of your CSV config file is %1.%2.%3 and is newer than supported version %4.%5.%6. Expect troubles.", + confVer.at(0), confVer.at(1), confVer.at(2), + kmmVer.at(0), kmmVer.at(1), kmmVer.at(2))); + ret = false; + return ret; + } else if (confVersion == kmmVersion) + return true; + confVer = kmmVer; + + const KSharedConfigPtr config = configFile(); + QString configFilePath = config.constData()->name(); + QFile::copy(configFilePath, configFilePath + QLatin1String(".bak")); + + KConfigGroup profileNamesGroup(config, m_confProfileNames); + QStringList bankProfiles = profileNamesGroup.readEntry(m_profileConfPrefix.value(ProfileBank), QStringList()); + QStringList investProfiles = profileNamesGroup.readEntry(m_profileConfPrefix.value(ProfileInvest), QStringList()); + QStringList invalidBankProfiles = profileNamesGroup.readEntry(QLatin1String("Invalid") + m_profileConfPrefix.value(ProfileBank), QStringList()); // get profiles that was marked invalid during last update + QStringList invalidInvestProfiles = profileNamesGroup.readEntry(QLatin1String("Invalid") + m_profileConfPrefix.value(ProfileInvest), QStringList()); + QString bankPrefix = m_profileConfPrefix.value(ProfileBank) + QLatin1Char('-'); + QString investPrefix = m_profileConfPrefix.value(ProfileInvest) + QLatin1Char('-'); + + // for kmm < 5.0.0 change 'BankNames' to 'ProfileNames' and remove 'MainWindow' group + if (confVersion < 500 && bankProfiles.isEmpty()) { + KConfigGroup oldProfileNamesGroup(config, "BankProfiles"); + bankProfiles = oldProfileNamesGroup.readEntry("BankNames", QStringList()); // profile names are under 'BankNames' entry for kmm < 5.0.0 + bankPrefix = QLatin1String("Profiles-"); // needed to remove non-existent profiles in first run + oldProfileNamesGroup.deleteGroup(); + KConfigGroup oldMainWindowGroup(config, "MainWindow"); + oldMainWindowGroup.deleteGroup(); + KConfigGroup oldSecuritiesGroup(config, "Securities"); + oldSecuritiesGroup.deleteGroup(); + } + + bool firstTry = false; + if (invalidBankProfiles.isEmpty() && invalidInvestProfiles.isEmpty()) // if there is no invalid profiles then this might be first update try + firstTry = true; + + int invalidProfileResponse = QMessageBox::No; + + for (auto profileName = bankProfiles.begin(); profileName != bankProfiles.end();) { + KConfigGroup bankProfile(config, bankPrefix + *profileName); + if (!bankProfile.exists() && !invalidBankProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference + profileName = bankProfiles.erase(profileName); + continue; + } + + // for kmm < 5.0.0 remove 'FileType' and 'ProfileName' and assign them to either "Bank=" or "Invest=" + if (confVersion < 500) { + QString lastUsedDirectory; + KConfigGroup oldBankProfile(config, QLatin1String("Profiles-") + *profileName); // if half of configuration is updated and the other one untouched this is needed + QString oldProfileType = oldBankProfile.readEntry("FileType", QString()); + KConfigGroup newProfile; + if (oldProfileType == QLatin1String("Invest")) { + oldBankProfile.deleteEntry("BrokerageParam"); + oldBankProfile.writeEntry(m_colTypeConfName.value(ColumnType), oldBankProfile.readEntry("PayeeCol")); + oldBankProfile.deleteEntry("PayeeCol"); + oldBankProfile.deleteEntry("Filter"); + oldBankProfile.deleteEntry("SecurityName"); + + lastUsedDirectory = oldBankProfile.readEntry("InvDirectory"); + newProfile = KConfigGroup(config, m_profileConfPrefix.value(ProfileInvest) + QLatin1Char('-') + *profileName); + investProfiles.append(*profileName); + profileName = bankProfiles.erase(profileName); + } else if (oldProfileType == QLatin1String("Banking")) { + lastUsedDirectory = oldBankProfile.readEntry("CsvDirectory"); + newProfile = KConfigGroup(config, m_profileConfPrefix.value(ProfileBank) + QLatin1Char('-') + *profileName); + ++profileName; + } else { + if (invalidProfileResponse != QMessageBox::YesToAll && invalidProfileResponse != QMessageBox::NoToAll) { + if (!firstTry && + !invalidBankProfiles.contains(*profileName)) { // if it isn't first update run and profile isn't on the list of invalid ones then don't bother + ++profileName; + continue; + } + invalidProfileResponse = QMessageBox::warning(0, i18n("CSV import"), + i18n("
During update of %1
" + "the profile type for %2 could not be recognized.
" + "The profile cannot be used because of that.
" + "Do you want to delete it?
", + configFilePath, *profileName), + QMessageBox::Yes | QMessageBox::YesToAll | + QMessageBox::No | QMessageBox::NoToAll, QMessageBox::No ); + } + + switch (invalidProfileResponse) { + case QMessageBox::YesToAll: + case QMessageBox::Yes: + oldBankProfile.deleteGroup(); + invalidBankProfiles.removeOne(*profileName); + profileName = bankProfiles.erase(profileName); + break; + case QMessageBox::NoToAll: + case QMessageBox::No: + if (!invalidBankProfiles.contains(*profileName)) // on user request: don't delete profile but keep eye on it + invalidBankProfiles.append(*profileName); + ret = false; + ++profileName; + break; + } + continue; + } + oldBankProfile.deleteEntry("FileType"); + oldBankProfile.deleteEntry("ProfileName"); + oldBankProfile.deleteEntry("DebitFlag"); + oldBankProfile.deleteEntry("InvDirectory"); + oldBankProfile.deleteEntry("CsvDirectory"); + oldBankProfile.sync(); + oldBankProfile.copyTo(&newProfile); + oldBankProfile.deleteGroup(); + newProfile.writeEntry(m_miscSettingsConfName.value(ConfDirectory), lastUsedDirectory); + newProfile.writeEntry(m_miscSettingsConfName.value(ConfEncoding), "106" /*UTF-8*/ ); // in 4.8 encoding wasn't supported well so set it to utf8 by default + newProfile.sync(); + } + } + + for (auto profileName = investProfiles.begin(); profileName != investProfiles.end();) { + KConfigGroup investProfile(config, investPrefix + *profileName); + if (!investProfile.exists() && !invalidInvestProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference + profileName = investProfiles.erase(profileName); + continue; + } + ++profileName; + } + + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileBank), bankProfiles); // update profile names as some of them might have been changed + profileNamesGroup.writeEntry(m_profileConfPrefix.value(ProfileInvest), investProfiles); + + if (invalidBankProfiles.isEmpty()) // if no invalid profiles then we don't need this variable anymore + profileNamesGroup.deleteEntry("InvalidBank"); + else + profileNamesGroup.writeEntry("InvalidBank", invalidBankProfiles); + + if (invalidInvestProfiles.isEmpty()) + profileNamesGroup.deleteEntry("InvalidInvest"); + else + profileNamesGroup.writeEntry("InvalidInvest", invalidInvestProfiles); + + if (ret) + QFile::remove(configFilePath + ".bak"); // remove backup if all is ok + + return ret; +} + +bool CSVImporter::profilesAction(const profileTypeE type, const profilesActionE action, const QString &name, const QString &newname) +{ + bool ret = false; + const KSharedConfigPtr config = configFile(); + KConfigGroup profileNamesGroup(config, m_confProfileNames); + QString profileTypeStr = m_profileConfPrefix.value(type); + QStringList profiles = profileNamesGroup.readEntry(profileTypeStr, QStringList()); + + KConfigGroup profileName(config, profileTypeStr + QLatin1Char('-') + name); + switch (action) { + case ProfilesUpdateLastUsed: + profileNamesGroup.writeEntry(m_confPriorName + profileTypeStr, profiles.indexOf(name)); + break; + case ProfilesAdd: + if (!profiles.contains(newname)) { + profiles.append(newname); + ret = true; + } + break; + case ProfilesRemove: + { + profiles.removeOne(name); + profileName.deleteGroup(); + profileName.sync(); + ret = true; + break; + } + case ProfilesRename: + { + if (!newname.isEmpty() && name != newname) { + int idx = profiles.indexOf(name); + if (idx != -1) { + profiles[idx] = newname; + KConfigGroup newProfileName(config, profileTypeStr + QLatin1Char('-') + newname); + if (profileName.exists() && !newProfileName.exists()) { + profileName.copyTo(&newProfileName); + profileName.deleteGroup(); + profileName.sync(); + newProfileName.sync(); + ret = true; + } + } + } + break; + } + } + profileNamesGroup.writeEntry(profileTypeStr, profiles); + profileNamesGroup.sync(); + return ret; +} + +bool CSVImporter::validateDateFormat(const int col) +{ + bool isOK = true; + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + QStandardItem* item = m_file->m_model->item(row, col); + QDate dat = m_convertDate->convertDate(item->text()); + if (dat == QDate()) { + isOK = false; + break; + } + } + return isOK; +} + +bool CSVImporter::validateDecimalSymbols(const QList &columns) +{ + bool isOK = true; + foreach (const auto column, columns) { + m_file->m_parse->setDecimalSymbol(m_decimalSymbolIndexMap.value(column)); + m_file->m_parse->setThousandsSeparator(m_decimalSymbolIndexMap.value(column)); + + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + QStandardItem *item = m_file->m_model->item(row, column); + QString rawNumber = item->text(); + m_file->m_parse->possiblyReplaceSymbol(rawNumber); + if (m_file->m_parse->invalidConversion() && + !rawNumber.isEmpty()) { // empty strings are welcome + isOK = false; + break; + } + } + + } + return isOK; +} + +bool CSVImporter::validateCurrencies(const PricesProfile *profile) +{ + if (profile->m_securitySymbol.isEmpty() || + profile->m_currencySymbol.isEmpty()) + return false; + return true; +} + +bool CSVImporter::validateSecurity(const PricesProfile *profile) +{ + if (profile->m_securitySymbol.isEmpty() || + profile->m_securityName.isEmpty()) + return false; + return true; +} + +bool CSVImporter::validateSecurity(const InvestmentProfile *profile) +{ + if (profile->m_securitySymbol.isEmpty() || + profile->m_securityName.isEmpty()) + return false; + return true; +} + +bool CSVImporter::validateSecurities() +{ + QSet onlySymbols; + QSet onlyNames; + sortSecurities(onlySymbols, onlyNames, m_mapSymbolName); + + if (!onlySymbols.isEmpty() || !onlyNames.isEmpty()) + return false; + return true; +} + +MyMoneyStatement::Transaction::EAction CSVImporter::processActionTypeField(const InvestmentProfile *profile, const int row, const int col) +{ + if (col == -1) + return MyMoneyStatement::Transaction::eaNone; + + QString type = m_file->m_model->item(row, col)->text(); + QList actions; + actions << MyMoneyStatement::Transaction::eaBuy << MyMoneyStatement::Transaction::eaSell << // first and second most frequent action + MyMoneyStatement::Transaction::eaReinvestDividend << MyMoneyStatement::Transaction::eaCashDividend << // we don't want "reinv-dividend" to be accidentaly caught by "dividend" + MyMoneyStatement::Transaction::eaInterest << + MyMoneyStatement::Transaction::eaShrsin << MyMoneyStatement::Transaction::eaShrsout; + + foreach (const auto action, actions) { + if (profile->m_transactionNames.value(action).contains(type, Qt::CaseInsensitive)) + return action; + } + + return MyMoneyStatement::Transaction::eaNone; +} + +validationResultE CSVImporter::validateActionType(MyMoneyStatement::Transaction &tr) +{ + validationResultE ret = ValidActionType; + QList validActionTypes = createValidActionTypes(tr); + if (validActionTypes.isEmpty()) + ret = InvalidActionValues; + else if (!validActionTypes.contains(tr.m_eAction)) + ret = NoActionType; + return ret; +} + +bool CSVImporter::calculateFee() +{ + InvestmentProfile *profile = dynamic_cast(m_profile); + if (!profile) + return false; + if ((profile->m_feeRate.isEmpty() || // check whether feeRate... + profile->m_colTypeNum.value(ColumnAmount) == -1)) // ...and amount is in place + return false; + + QString decimalSymbol; + if (profile->m_decimalSymbolIndex == 2 || + profile->m_decimalSymbolIndex == -1) { + int detectedSymbol = detectDecimalSymbol(profile->m_colTypeNum.value(ColumnAmount), QString()); + if (detectedSymbol == -1) + return false; + m_file->m_parse->setDecimalSymbol(detectedSymbol); + m_file->m_parse->setThousandsSeparator(detectedSymbol); // separator list is in reverse so it's ok + decimalSymbol = m_file->m_parse->decimalSymbol(detectedSymbol); + } else + decimalSymbol = m_file->m_parse->decimalSymbol(profile->m_decimalSymbolIndex); + + + MyMoneyMoney feePercent(m_file->m_parse->possiblyReplaceSymbol(profile->m_feeRate)); // convert 0.67% ... + feePercent /= MyMoneyMoney(100); // ... to 0.0067 + + if (profile->m_minFee.isEmpty()) + profile->m_minFee = QString::number(0.00, 'f', 2); + + MyMoneyMoney minFee(m_file->m_parse->possiblyReplaceSymbol(profile->m_minFee)); + + QList items; + for (int row = 0; row < profile->m_startLine; ++row) // fill rows above with whitespace for nice effect with markUnwantedRows + items.append(new QStandardItem(QString())); + + for (int row = profile->m_startLine; row <= profile->m_endLine; ++row) { + QString txt, numbers; + bool ok = false; + numbers = txt = m_file->m_model->item(row, profile->m_colTypeNum.value(ColumnAmount))->text(); + numbers.remove(QRegularExpression(QStringLiteral("[,. ]"))).toInt(&ok); + if (!ok) { // check if it's numerical string... + items.append(new QStandardItem(QString())); + continue; // ...and skip if not (TODO: allow currency symbols and IDs) + } + + if (txt.startsWith(QLatin1Char('('))) { + txt.remove(QRegularExpression(QStringLiteral("[()]"))); + txt.prepend(QLatin1Char('-')); + } + txt = m_file->m_parse->possiblyReplaceSymbol(txt); + MyMoneyMoney fee(txt); + fee *= feePercent; + if (fee < minFee) + fee = minFee; + txt.setNum(fee.toDouble(), 'f', 4); + txt.replace(QLatin1Char('.'), decimalSymbol); //make sure decimal symbol is uniform in whole line + items.append(new QStandardItem(txt)); + } + + for (int row = profile->m_endLine + 1; row < m_file->m_rowCount; ++row) // fill rows below with whitespace for nice effect with markUnwantedRows + items.append(new QStandardItem(QString())); + int col = profile->m_colTypeNum.value(ColumnFee); + if (col == -1) { // fee column isn't present + m_file->m_model->appendColumn(items); + ++m_file->m_columnCount; + } else if (col >= m_file->m_columnCount) { // column number must have been stored in profile + m_file->m_model->appendColumn(items); + ++m_file->m_columnCount; + } else { // fee column is present and has been recalculated + m_file->m_model->removeColumn(m_file->m_columnCount - 1); + m_file->m_model->appendColumn(items); + } + profile->m_colTypeNum[ColumnFee] = m_file->m_columnCount - 1; + return true; +} + +int CSVImporter::detectDecimalSymbol(const int col, const QString &exclude) +{ + int detectedSymbol = -1; + QString pattern; + + QRegularExpression re("^[\\(+-]?\\d+[\\)]?$"); // matches '0' ; '+12' ; '-345' ; '(6789)' + + bool dotIsDecimalSeparator = false; + bool commaIsDecimalSeparator = false; + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + QString txt = m_file->m_model->item(row, col)->text(); + if (txt.isEmpty()) // nothing to process, so go to next row + continue; + int dotPos = txt.lastIndexOf(QLatin1Char('.')); // get last positions of decimal/thousand separator... + int commaPos = txt.lastIndexOf(QLatin1Char(',')); // ...to be able to determine which one is the last + + if (dotPos != -1 && commaPos != -1) { + if (dotPos > commaPos && commaIsDecimalSeparator == false) // follwing case 1,234.56 + dotIsDecimalSeparator = true; + else if (dotPos < commaPos && dotIsDecimalSeparator == false) // follwing case 1.234,56 + commaIsDecimalSeparator = true; + else // follwing case 1.234,56 and somwhere earlier there was 1,234.56 so unresolvable conflict + return detectedSymbol; + } else if (dotPos != -1) { // follwing case 1.23 + if (dotIsDecimalSeparator) // it's already know that dotIsDecimalSeparator + continue; + if (!commaIsDecimalSeparator) // if there is no conflict with comma as decimal separator + dotIsDecimalSeparator = true; + else { + if (txt.count(QLatin1Char('.')) > 1) // follwing case 1.234.567 so OK + continue; + else if (txt.length() - 4 == dotPos) // follwing case 1.234 and somwhere earlier there was 1.234,56 so OK + continue; + else // follwing case 1.23 and somwhere earlier there was 1,23 so unresolvable conflict + return detectedSymbol; + } + } else if (commaPos != -1) { // follwing case 1,23 + if (commaIsDecimalSeparator) // it's already know that commaIsDecimalSeparator + continue; + else if (!dotIsDecimalSeparator) // if there is no conflict with dot as decimal separator + commaIsDecimalSeparator = true; + else { + if (txt.count(QLatin1Char(',')) > 1) // follwing case 1,234,567 so OK + continue; + else if (txt.length() - 4 == commaPos) // follwing case 1,234 and somwhere earlier there was 1,234.56 so OK + continue; + else // follwing case 1,23 and somwhere earlier there was 1.23 so unresolvable conflict + return detectedSymbol; + } + + } else { // follwing case 123 + if (pattern.isEmpty()) { + + } + + txt.remove(QRegularExpression(QLatin1String("[ ") + QRegularExpression::escape(exclude) + QLatin1String("]"))); + QRegularExpressionMatch match = re.match(txt); + if (match.hasMatch()) // if string is pure numerical then go forward... + continue; + else // ...if not then it's non-numerical garbage + return detectedSymbol; + } + } + + if (dotIsDecimalSeparator) + detectedSymbol = 0; + else if (commaIsDecimalSeparator) + detectedSymbol = 1; + else { // whole column was empty, but we don't want to fail so take OS's decimal symbol + if (QLocale().decimalPoint() == QLatin1Char('.')) + detectedSymbol = 0; + else + detectedSymbol = 1; + } + return detectedSymbol; +} + +int CSVImporter::detectDecimalSymbols(const QList &columns) +{ + int ret = -2; + + // get list of used currencies to remove them from col + QList accounts; + MyMoneyFile *file = MyMoneyFile::instance(); + file->accountList(accounts); + + QList accountTypes; + accountTypes << MyMoneyAccount::Checkings << + MyMoneyAccount::Savings << + MyMoneyAccount::Liability << + MyMoneyAccount::Checkings << + MyMoneyAccount::Savings << + MyMoneyAccount::Cash << + MyMoneyAccount::CreditCard << + MyMoneyAccount::Loan << + MyMoneyAccount::Asset << + MyMoneyAccount::Liability; + + QSet currencySymbols; + foreach (const auto account, accounts) { + if (accountTypes.contains(account.accountType())) { // account must actually have currency property + currencySymbols.insert(account.currencyId()); // add currency id + currencySymbols.insert(file->currency(account.currencyId()).tradingSymbol()); // add currency symbol + } + } + QString filteredCurrencies = QStringList(currencySymbols.values()).join(""); + QString pattern = QString(QLatin1String("%1%2")).arg(QLocale().currencySymbol()).arg(filteredCurrencies); + + foreach (const auto column, columns) { + int detectedSymbol = detectDecimalSymbol(column, pattern); + if (detectedSymbol == -1) { + ret = column; + return ret; + } + m_decimalSymbolIndexMap.insert(column, detectedSymbol); + } + return ret; +} + +QList CSVImporter::findAccounts(const QList &accountTypes, const QString &statementHeader) +{ + MyMoneyFile* file = MyMoneyFile::instance(); + QList accountList; + file->accountList(accountList); + QList filteredTypes; + QList filteredAccounts; + QList::iterator account; + QRegularExpression filterOutChars = QRegularExpression(QStringLiteral("-., ")); + + foreach (const auto account, accountList) { + if (accountTypes.contains(account.accountType()) && !(account).isClosed()) + filteredTypes.append(account); + } + + // filter out accounts whose names aren't in statements header + foreach (const auto account, filteredTypes) { + QString txt = account.name(); + txt.remove(filterOutChars); + if (statementHeader.contains(txt, Qt::CaseInsensitive)) + filteredAccounts.append(account); + } + + // if filtering returned more results, filter out accounts whose numbers aren't in statements header + if (filteredAccounts.count() > 1) { + for (account = filteredAccounts.begin(); account != filteredAccounts.end();) { + QString txt = (*account).number(); + txt.remove(filterOutChars); + if (txt.isEmpty() || txt.length() < 3) { + ++account; + continue; + } + if (statementHeader.contains(txt, Qt::CaseInsensitive)) + ++account; + else + account = filteredAccounts.erase(account); + } + } + + // if filtering by name and number didn't return nothing, then try filtering by number only + if (filteredAccounts.isEmpty()) { + foreach (const auto account, filteredTypes) { + QString txt = account.number(); + txt.remove(filterOutChars); + if (statementHeader.contains(txt, Qt::CaseInsensitive)) + filteredAccounts.append(account); + } + } + return filteredAccounts; +} + +bool CSVImporter::detectAccount(MyMoneyStatement &st) +{ + QString statementHeader; + for (int row = 0; row < m_profile->m_startLine; ++row) // concatenate header for better search + for (int col = 0; col < m_file->m_columnCount; ++col) + statementHeader.append(m_file->m_model->item(row, col)->text()); + + statementHeader.remove(QRegularExpression(QStringLiteral("-., "))); + + QList accounts; + QList accountTypes; + + switch(m_profile->type()) { + default: + case ProfileBank: + accountTypes << MyMoneyAccount::Checkings << + MyMoneyAccount::Savings << + MyMoneyAccount::Liability << + MyMoneyAccount::Checkings << + MyMoneyAccount::Savings << + MyMoneyAccount::Cash << + MyMoneyAccount::CreditCard << + MyMoneyAccount::Loan << + MyMoneyAccount::Asset << + MyMoneyAccount::Liability; + accounts = findAccounts(accountTypes, statementHeader); + break; + case ProfileInvest: + accountTypes << MyMoneyAccount::Investment; // take investment accounts... + accounts = findAccounts(accountTypes, statementHeader); //...and search them in statement header + break; + } + + if (accounts.count() == 1) { // set account in statement, if it was the only one match + st.m_strAccountName = accounts.first().name(); + st.m_strAccountNumber = accounts.first().number(); + st.m_accountId = accounts.first().id(); + + switch (accounts.first().accountType()) { + case MyMoneyAccount::Checkings: + st.m_eType = MyMoneyStatement::etCheckings; + break; + case MyMoneyAccount::Savings: + st.m_eType = MyMoneyStatement::etSavings; + break; + case MyMoneyAccount::Investment: + st.m_eType = MyMoneyStatement::etInvestment; + break; + case MyMoneyAccount::CreditCard: + st.m_eType = MyMoneyStatement::etCreditCard; + break; + default: + st.m_eType = MyMoneyStatement::etNone; + } + return true; + } + return false; +} + +bool CSVImporter::processBankRow(MyMoneyStatement &st, const BankingProfile *profile, const int row) +{ + MyMoneyStatement::Transaction tr; + QString memo; + QString txt; + + // process number field + if (profile->m_colTypeNum.value(ColumnNumber) != -1) + tr.m_strNumber = txt; + + // process date field + int col = profile->m_colTypeNum.value(ColumnDate); + tr.m_datePosted = processDateField(row, col); + if (tr.m_datePosted == QDate()) + return false; + + // process payee field + col = profile->m_colTypeNum.value(ColumnPayee); + if (col != -1) + tr.m_strPayee = m_file->m_model->item(row, col)->text(); + + // process memo field + col = profile->m_colTypeNum.value(ColumnMemo); + if (col != -1) + memo.append(m_file->m_model->item(row, col)->text()); + + for (int i = 0; i < profile->m_memoColList.count(); ++i) { + if (profile->m_memoColList.at(i) != col) { + if (!memo.isEmpty()) + memo.append(QLatin1Char('\n')); + if (profile->m_memoColList.at(i) < m_file->m_columnCount) + memo.append(m_file->m_model->item(row, profile->m_memoColList.at(i))->text()); + } + } + tr.m_strMemo = memo; + + // process amount field + col = profile->m_colTypeNum.value(ColumnAmount); + tr.m_amount = processAmountField(profile, row, col); + if (col != -1 && profile->m_oppositeSigns) // change signs to opposite if requested by user + tr.m_amount *= MyMoneyMoney(-1); + + // process credit/debit field + if (profile->m_colTypeNum.value(ColumnCredit) != -1 && + profile->m_colTypeNum.value(ColumnDebit) != -1) { + QString credit = m_file->m_model->item(row, profile->m_colTypeNum.value(ColumnCredit))->text(); + QString debit = m_file->m_model->item(row, profile->m_colTypeNum.value(ColumnDebit))->text(); + tr.m_amount = processCreditDebit(credit, debit); + if (!credit.isEmpty() && !debit.isEmpty()) + return false; + } + + MyMoneyStatement::Split s1; + s1.m_amount = tr.m_amount; + s1.m_strMemo = tr.m_strMemo; + MyMoneyStatement::Split s2 = s1; + s2.m_reconcile = tr.m_reconcile; + s2.m_amount = (-s1.m_amount); + + // process category field + col = profile->m_colTypeNum.value(ColumnCategory); + if (col != -1) { + txt = m_file->m_model->item(row, col)->text(); + QString accountId = m_file->m_csvUtil->checkCategory(txt, s1.m_amount, s2.m_amount); + + if (!accountId.isEmpty()) { + s2.m_accountId = accountId; + s2.m_strCategoryName = txt; + tr.m_listSplits.append(s2); + } + } + + // calculate hash + txt.clear(); + for (int i = 0; i < m_file->m_columnCount; ++i) + txt.append(m_file->m_model->item(row, i)->text()); + QString hashBase = QString(QLatin1String("%1-%2")) + .arg(tr.m_datePosted.toString(Qt::ISODate)) + .arg(MyMoneyTransaction::hash(txt)); + QString hash; + for (uchar idx = 0; idx < 0xFF; ++idx) { // assuming threre will be no more than 256 transactions with the same hashBase + hash = QString(QLatin1String("%1-%2")).arg(hashBase).arg(idx); + QSet::const_iterator it = m_hashSet.constFind(hash); + if (it == m_hashSet.constEnd()) + break; + } + m_hashSet.insert(hash); + tr.m_strBankID = hash; + + st.m_listTransactions.append(tr); // Add the MyMoneyStatement::Transaction to the statement + return true; +} + +bool CSVImporter::processInvestRow(MyMoneyStatement &st, const InvestmentProfile *profile, const int row) +{ + MyMoneyStatement::Transaction tr; + + QString memo; + QString txt; + // process date field + int col = profile->m_colTypeNum.value(ColumnDate); + tr.m_datePosted = processDateField(row, col); + if (tr.m_datePosted == QDate()) + return false; + + // process quantity field + col = profile->m_colTypeNum.value(ColumnQuantity); + tr.m_shares = processQuantityField(profile, row, col); + + // process price field + col = profile->m_colTypeNum.value(ColumnPrice); + tr.m_price = processPriceField(profile, row, col); + + // process amount field + col = profile->m_colTypeNum.value(ColumnAmount); + tr.m_amount = processAmountField(profile, row, col); + + // process type field + col = profile->m_colTypeNum.value(ColumnType); + tr.m_eAction = processActionTypeField(profile, row, col); + if (!m_isActionTypeValidated && col != -1 && // if action type wasn't validated in wizard then... + validateActionType(tr) != ValidActionType) // ...check if price, amount, quantity is appropriate + return false; + + // process fee field + col = profile->m_colTypeNum.value(ColumnFee); + if (col != -1) { + if (profile->m_decimalSymbolIndex == 2) { + int decimalSymbolIndex = m_decimalSymbolIndexMap.value(col); + m_file->m_parse->setDecimalSymbol(decimalSymbolIndex); + m_file->m_parse->setThousandsSeparator(decimalSymbolIndex); + } + + txt = m_file->m_model->item(row, col)->text(); + if (txt.startsWith(QLatin1Char('('))) // check if brackets notation is used for negative numbers + txt.remove(QRegularExpression(QStringLiteral("[()]"))); + + if (txt.isEmpty()) + tr.m_fees = MyMoneyMoney(); + else { + MyMoneyMoney fee(m_file->m_parse->possiblyReplaceSymbol(txt)); + if (profile->m_feeIsPercentage && profile->m_feeRate.isEmpty()) // fee is percent + fee *= tr.m_amount / MyMoneyMoney(100); // as percentage + fee.abs(); + tr.m_fees = fee; + } + } + + // process symbol and name field + col = profile->m_colTypeNum.value(ColumnSymbol); + if (col != -1) + tr.m_strSymbol = m_file->m_model->item(row, col)->text(); + col = profile->m_colTypeNum.value(ColumnName); + if (col != -1 && + tr.m_strSymbol.isEmpty()) { // case in which symbol field is empty + txt = m_file->m_model->item(row, col)->text(); + tr.m_strSymbol = m_mapSymbolName.key(txt); // it's all about getting the right symbol + } else if (!profile->m_securitySymbol.isEmpty()) + tr.m_strSymbol = profile->m_securitySymbol; + else + return false; + tr.m_strSecurity = m_mapSymbolName.value(tr.m_strSymbol); // take name from prepared names to avoid potential name mismatch + + // process memo field + col = profile->m_colTypeNum.value(ColumnMemo); + if (col != -1) + memo.append(m_file->m_model->item(row, col)->text()); + + for (int i = 0; i < profile->m_memoColList.count(); ++i) { + if (profile->m_memoColList.at(i) != col) { + if (!memo.isEmpty()) + memo.append(QLatin1Char('\n')); + if (profile->m_memoColList.at(i) < m_file->m_columnCount) + memo.append(m_file->m_model->item(row, profile->m_memoColList.at(i))->text()); + } + } + tr.m_strMemo = memo; + + tr.m_strInterestCategory.clear(); // no special category + tr.m_strBrokerageAccount.clear(); // no brokerage account auto-detection + + MyMoneyStatement::Split s1; + s1.m_amount = tr.m_amount; + s1.m_strMemo = tr.m_strMemo; + MyMoneyStatement::Split s2 = s1; + s2.m_amount = MyMoneyMoney(-s1.m_amount); + s2.m_accountId = m_file->m_csvUtil->checkCategory(tr.m_strInterestCategory, s1.m_amount, s2.m_amount); + + // deduct fees from amount + if (tr.m_eAction == MyMoneyStatement::Transaction::eaCashDividend || + tr.m_eAction == MyMoneyStatement::Transaction::eaSell || + tr.m_eAction == MyMoneyStatement::Transaction::eaInterest) + tr.m_amount -= tr.m_fees; + + else if (tr.m_eAction == MyMoneyStatement::Transaction::eaBuy) { + if (tr.m_amount.isPositive()) + tr.m_amount = -tr.m_amount; //if broker doesn't use minus sings for buy transactions, set it manually here + tr.m_amount -= tr.m_fees; + } else if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone) + tr.m_listSplits.append(s2); + + st.m_listTransactions.append(tr); // Add the MyMoneyStatement::Transaction to the statement + return true; +} + +bool CSVImporter::processPriceRow(MyMoneyStatement &st, const PricesProfile *profile, const int row) +{ + MyMoneyStatement::Price pr; + + // process date field + int col = profile->m_colTypeNum.value(ColumnDate); + pr.m_date = processDateField(row, col); + if (pr.m_date == QDate()) + return false; + + // process price field + col = profile->m_colTypeNum.value(ColumnPrice); + pr.m_amount = processPriceField(profile, row, col); + + switch (profile->type()) { + case ProfileCurrencyPrices: + if (profile->m_securitySymbol.isEmpty() || profile->m_currencySymbol.isEmpty()) + return false; + pr.m_strSecurity = profile->m_securitySymbol; + pr.m_strCurrency = profile->m_currencySymbol; + break; + case ProfileStockPrices: + if (profile->m_securityName.isEmpty()) + return false; + pr.m_strSecurity = profile->m_securityName; + break; + default: + return false; + } + + pr.m_sourceName = profile->m_profileName; + st.m_listPrices.append(pr); // Add price to the statement + return true; +} + +QDate CSVImporter::processDateField(const int row, const int col) +{ + QDate date; + if (col != -1) { + QString txt = m_file->m_model->item(row, col)->text(); + date = m_convertDate->convertDate(txt); // Date column + } + return date; +} + +MyMoneyMoney CSVImporter::processCreditDebit(QString &credit, QString &debit) +{ + MyMoneyMoney amount; + if (m_profile->m_decimalSymbolIndex == 2) + setupFieldDecimalSymbol(m_profile->m_colTypeNum.value(ColumnCredit)); + + if (credit.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers + credit.remove(QRegularExpression(QStringLiteral("[()]"))); + credit.prepend(QLatin1Char('-')); + } + if (debit.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers + debit.remove(QRegularExpression(QStringLiteral("[()]"))); + debit.prepend(QLatin1Char('-')); + } + + if (!credit.isEmpty() && !debit.isEmpty()) { // we do not expect both fields to be non-zero + if (MyMoneyMoney(credit).isZero()) + credit = QString(); + if (MyMoneyMoney(debit).isZero()) + debit = QString(); + } + + if (!debit.startsWith(QLatin1Char('-')) && !debit.isEmpty()) // ensure debit field is negative + debit.prepend(QLatin1Char('-')); + + if (!credit.isEmpty() && debit.isEmpty()) + amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(credit)); + else if (credit.isEmpty() && !debit.isEmpty()) + amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(debit)); + else if (!credit.isEmpty() && !debit.isEmpty()) { // both fields are non-empty and non-zero so let user decide + return amount; + + } else + amount = MyMoneyMoney(); // both fields are empty and zero so set amount to zero + + return amount; +} + + +MyMoneyMoney CSVImporter::processQuantityField(const CSVProfile *profile, const int row, const int col) +{ + MyMoneyMoney shares; + if (col != -1) { + if (profile->m_decimalSymbolIndex == 2) + setupFieldDecimalSymbol(col); + + QString txt = m_file->m_model->item(row, col)->text(); + txt.remove(QRegularExpression(QStringLiteral("+-"))); // remove unwanted sings in quantity + + if (!txt.isEmpty()) + shares = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt)); + } + return shares; +} + +MyMoneyMoney CSVImporter::processAmountField(const CSVProfile *profile, const int row, const int col) +{ + MyMoneyMoney amount; + if (col != -1) { + if (profile->m_decimalSymbolIndex == 2) + setupFieldDecimalSymbol(col); + + QString txt = m_file->m_model->item(row, col)->text(); + if (txt.startsWith(QLatin1Char('('))) { // check if brackets notation is used for negative numbers + txt.remove(QRegularExpression(QStringLiteral("[()]"))); + txt.prepend(QLatin1Char('-')); + } + + if (!txt.isEmpty()) + amount = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt)); + } + return amount; +} + +MyMoneyMoney CSVImporter::processPriceField(const InvestmentProfile *profile, const int row, const int col) +{ + MyMoneyMoney price; + if (col != -1) { + if (profile->m_decimalSymbolIndex == 2) + setupFieldDecimalSymbol(col); + + QString txt = m_file->m_model->item(row, col)->text(); + if (!txt.isEmpty()) { + price = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt)); + price *= m_priceFractions.at(profile->m_priceFraction); + } + } + return price; +} + +MyMoneyMoney CSVImporter::processPriceField(const PricesProfile *profile, const int row, const int col) +{ + MyMoneyMoney price; + if (col != -1) { + if (profile->m_decimalSymbolIndex == 2) + setupFieldDecimalSymbol(col); + + QString txt = m_file->m_model->item(row, col)->text(); + if (!txt.isEmpty()) { + price = MyMoneyMoney(m_file->m_parse->possiblyReplaceSymbol(txt)); + price *= m_priceFractions.at(profile->m_priceFraction); + } + } + return price; +} + + +QList CSVImporter::createValidActionTypes(MyMoneyStatement::Transaction &tr) +{ + QList validActionTypes; + if (tr.m_shares.isPositive() && + tr.m_price.isPositive() && + !tr.m_amount.isZero()) + validActionTypes << MyMoneyStatement::Transaction::eaReinvestDividend << + MyMoneyStatement::Transaction::eaBuy << + MyMoneyStatement::Transaction::eaSell; + else if (tr.m_shares.isZero() && + tr.m_price.isZero() && + !tr.m_amount.isZero()) + validActionTypes << MyMoneyStatement::Transaction::eaCashDividend << + MyMoneyStatement::Transaction::eaInterest; + else if (tr.m_shares.isPositive() && + tr.m_price.isZero() && + tr.m_amount.isZero()) + validActionTypes << MyMoneyStatement::Transaction::eaShrsin << + MyMoneyStatement::Transaction::eaShrsout; + return validActionTypes; +} + + +bool CSVImporter::sortSecurities(QSet& onlySymbols, QSet& onlyNames, QMap& mapSymbolName) +{ + QList securityList = MyMoneyFile::instance()->securityList(); + int symbolCol = m_profile->m_colTypeNum.value(ColumnSymbol); + int nameCol = m_profile->m_colTypeNum.value(ColumnName); + + // sort by availability of symbol and name + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + QString symbol; + QString name; + if (symbolCol != -1) + symbol = m_file->m_model->item(row, symbolCol)->text().trimmed(); + if (nameCol != -1) + name = m_file->m_model->item(row, nameCol)->text().trimmed(); + + if (!symbol.isEmpty() && !name.isEmpty()) + mapSymbolName.insert(symbol, name); + else if (!symbol.isEmpty()) + onlySymbols.insert(symbol); + else if (!name.isEmpty()) + onlyNames.insert(name); + else + return false; + } + + // try to find names for symbols + for (QSet::iterator symbol = onlySymbols.begin(); symbol != onlySymbols.end();) { + QList filteredSecurities; + foreach (const auto sec, securityList) { + if ((*symbol).compare(sec.tradingSymbol(), Qt::CaseInsensitive) == 0) + filteredSecurities.append(sec); // gather all securities that by matched by symbol + } + + if (filteredSecurities.count() == 1) { // single security matched by the symbol so... + mapSymbolName.insert(*symbol, filteredSecurities.first().name()); + symbol = onlySymbols.erase(symbol); // ...it's no longer unknown + } else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the symbol + // TODO: Ask user which security should we match to + mapSymbolName.insert(*symbol, filteredSecurities.first().name()); + symbol = onlySymbols.erase(symbol); + } else // no security matched, so leave it as unknown + ++symbol; + } + + // try to find symbols for names + for (QSet::iterator name = onlyNames.begin(); name != onlyNames.end();) { + QList filteredSecurities; + foreach (const auto sec, securityList) { + if ((*name).compare(sec.name(), Qt::CaseInsensitive) == 0) + filteredSecurities.append(sec); // gather all securities that by matched by name + } + + if (filteredSecurities.count() == 1) { // single security matched by the name so... + mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name); + name = onlyNames.erase(name); // ...it's no longer unknown + } else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the name + // TODO: Ask user which security should we match to + mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name); + name = onlySymbols.erase(name); + } else // no security matched, so leave it as unknown + ++name; + } + return true; +} + +void CSVImporter::setupFieldDecimalSymbol(int col) { + int decimalSymbolIndex = m_decimalSymbolIndexMap.value(col); + m_file->m_parse->setDecimalSymbol(decimalSymbolIndex); + m_file->m_parse->setThousandsSeparator(decimalSymbolIndex); +} + +QList CSVImporter::getNumericalColumns() +{ + QList columns; + switch(m_profile->type()) { + case ProfileBank: + if (m_profile->m_colTypeNum.value(ColumnAmount) != -1) { + columns << m_profile->m_colTypeNum.value(ColumnAmount); + } else { + columns << m_profile->m_colTypeNum.value(ColumnDebit); + columns << m_profile->m_colTypeNum.value(ColumnCredit); + } + break; + case ProfileInvest: + columns << m_profile->m_colTypeNum.value(ColumnAmount); + columns << m_profile->m_colTypeNum.value(ColumnPrice); + columns << m_profile->m_colTypeNum.value(ColumnQuantity); + if (m_profile->m_colTypeNum.value(ColumnFee) != -1) + columns << m_profile->m_colTypeNum.value(ColumnFee); + break; + case ProfileCurrencyPrices: + case ProfileStockPrices: + columns << m_profile->m_colTypeNum.value(ColumnPrice); + break; + default: + break; + } + return columns; +} + +bool CSVImporter::createStatement(MyMoneyStatement &st) +{ + switch (m_profile->type()) { + case ProfileBank: + { + if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one + return true; + st.m_eType = MyMoneyStatement::etNone; + if (m_autodetect.value(AutoAccountBank)) + detectAccount(st); + + m_hashSet.clear(); + BankingProfile *profile = dynamic_cast(m_profile); + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) + if (!processBankRow(st, profile, row)) { // parse fields + st = MyMoneyStatement(); + return false; + } + return true; + break; + } + case ProfileInvest: + { + if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one + return true; + st.m_eType = MyMoneyStatement::etInvestment; + if (m_autodetect.value(AutoAccountInvest)) + detectAccount(st); + + InvestmentProfile *profile = dynamic_cast(m_profile); + if ((m_profile->m_colTypeNum.value(ColumnFee) == -1 || + m_profile->m_colTypeNum.value(ColumnFee) >= m_file->m_columnCount) && + !profile->m_feeRate.isEmpty()) // fee column has not been calculated so do it now + calculateFee(); + + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) + if (!processInvestRow(st, profile, row)) { // parse fields + st = MyMoneyStatement(); + return false; + } + + for (QMap::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) { + MyMoneyStatement::Security security; + security.m_strSymbol = it.key(); + security.m_strName = it.value(); + st.m_listSecurities.append(security); + } + return true; + break; + } + default: + case ProfileCurrencyPrices: + case ProfileStockPrices: + { + if (!st.m_listPrices.isEmpty()) // don't create statement if there is one + return true; + st.m_eType = MyMoneyStatement::etNone; + + PricesProfile *profile = dynamic_cast(m_profile); + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) + if (!processPriceRow(st, profile, row)) { // parse fields + st = MyMoneyStatement(); + return false; + } + + for (QMap::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) { + MyMoneyStatement::Security security; + security.m_strSymbol = it.key(); + security.m_strName = it.value(); + st.m_listSecurities.append(security); + } + return true; + } + } + return true; +} + +void CSVProfile::readSettings(const KConfigGroup &profilesGroup) +{ + m_lastUsedDirectory = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDirectory), QString()); + m_startLine = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfStartLine), 0); + m_trailerLines = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfTrailerLines), 0); + m_encodingMIBEnum = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfEncoding), 106 /* UTF-8 */); + + m_dateFormatIndex = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDateFormat), 0); + m_textDelimiterIndex = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfTextDeimiter), 0); + m_fieldDelimiterIndex = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFieldDelimiter), -1); + m_decimalSymbolIndex = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDecimalSymbol), 2); + initColNumType(); +} + +void CSVProfile::writeSettings(KConfigGroup &profilesGroup) +{ + if (m_lastUsedDirectory.startsWith(QDir::homePath())) { // replace /home/user with ~/ for brevity + QFileInfo fileInfo = QFileInfo(m_lastUsedDirectory); + if (fileInfo.isFile()) + m_lastUsedDirectory = fileInfo.absolutePath(); + m_lastUsedDirectory.replace(0, QDir::homePath().length(), QLatin1Char('~')); + } + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDirectory), m_lastUsedDirectory); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfEncoding), m_encodingMIBEnum); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDateFormat), m_dateFormatIndex); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFieldDelimiter), m_fieldDelimiterIndex); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfTextDeimiter), m_textDelimiterIndex); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDecimalSymbol), m_decimalSymbolIndex); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfStartLine), m_startLine); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfTrailerLines), m_trailerLines); +} + +bool BankingProfile::readSettings(const KSharedConfigPtr &config) +{ + bool exists = true; + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + if (!profilesGroup.exists()) + exists = false; + + m_colTypeNum[ColumnPayee] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnPayee), -1); + m_colTypeNum[ColumnNumber] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnNumber), -1); + m_colTypeNum[ColumnAmount] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnAmount), -1); + m_colTypeNum[ColumnDebit] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnDebit), -1); + m_colTypeNum[ColumnCredit] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnCredit), -1); + m_colTypeNum[ColumnDate] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), -1); + m_colTypeNum[ColumnCategory] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnCategory), -1); + m_colTypeNum[ColumnMemo] = -1; // initialize, otherwise random data may go here + m_oppositeSigns = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfOppositeSigns), 0); + m_memoColList = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnMemo), QList()); + + CSVProfile::readSettings(profilesGroup); + return exists; +} + +void BankingProfile::writeSettings(const KSharedConfigPtr &config) +{ + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + CSVProfile::writeSettings(profilesGroup); + + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfOppositeSigns), m_oppositeSigns); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnPayee), + m_colTypeNum.value(ColumnPayee)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnNumber), + m_colTypeNum.value(ColumnNumber)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnAmount), + m_colTypeNum.value(ColumnAmount)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnDebit), + m_colTypeNum.value(ColumnDebit)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnCredit), + m_colTypeNum.value(ColumnCredit)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), + m_colTypeNum.value(ColumnDate)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnCategory), + m_colTypeNum.value(ColumnCategory)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnMemo), + m_memoColList); + profilesGroup.config()->sync(); +} + +bool InvestmentProfile::readSettings(const KSharedConfigPtr &config) +{ + bool exists = true; + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + if (!profilesGroup.exists()) + exists = false; + + m_transactionNames[MyMoneyStatement::Transaction::eaBuy] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaBuy), + QString(i18nc("Type of operation as in financial statement", "buy")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaSell] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaSell), + QString(i18nc("Type of operation as in financial statement", "sell,repurchase")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaReinvestDividend] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaReinvestDividend), + QString(i18nc("Type of operation as in financial statement", "reinvest,reinv,re-inv")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaCashDividend] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaCashDividend), + QString(i18nc("Type of operation as in financial statement", "dividend")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaInterest] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaInterest), + QString(i18nc("Type of operation as in financial statement", "interest,income")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaShrsin] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsin), + QString(i18nc("Type of operation as in financial statement", "add,stock dividend,divd reinv,transfer in,re-registration in,journal entry")).split(',', QString::SkipEmptyParts)); + m_transactionNames[MyMoneyStatement::Transaction::eaShrsout] = profilesGroup.readEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsout), + QString(i18nc("Type of operation as in financial statement", "remove")).split(',', QString::SkipEmptyParts)); + + m_colTypeNum[ColumnDate] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), -1); + m_colTypeNum[ColumnType] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnType), -1); //use for type col. + m_colTypeNum[ColumnPrice] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnPrice), -1); + m_colTypeNum[ColumnQuantity] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnQuantity), -1); + m_colTypeNum[ColumnAmount] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnAmount), -1); + m_colTypeNum[ColumnName] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnName), -1); + m_colTypeNum[ColumnFee] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnFee), -1); + m_colTypeNum[ColumnSymbol] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnSymbol), -1); + m_colTypeNum[ColumnMemo] = -1; // initialize, otherwise random data may go here + m_feeIsPercentage = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeIsPercentage), 0); + m_feeRate = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeRate), QString()); + m_minFee = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfMinFee), QString()); + + m_memoColList = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnMemo), QList()); + m_securityName = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), QString()); + m_securitySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), QString()); + m_dontAsk = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), 0); + m_priceFraction = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), 2); + + CSVProfile::readSettings(profilesGroup); + return exists; +} + +void InvestmentProfile::writeSettings(const KSharedConfigPtr &config) +{ + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + CSVProfile::writeSettings(profilesGroup); + + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaBuy), + m_transactionNames.value(MyMoneyStatement::Transaction::eaBuy)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaSell), + m_transactionNames.value(MyMoneyStatement::Transaction::eaSell)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaReinvestDividend), + m_transactionNames.value(MyMoneyStatement::Transaction::eaReinvestDividend)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaCashDividend), + m_transactionNames.value(MyMoneyStatement::Transaction::eaCashDividend)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaInterest), + m_transactionNames.value(MyMoneyStatement::Transaction::eaInterest)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsin), + m_transactionNames.value(MyMoneyStatement::Transaction::eaShrsin)); + profilesGroup.writeEntry(CSVImporter::m_transactionConfName.value(MyMoneyStatement::Transaction::eaShrsout), + m_transactionNames.value(MyMoneyStatement::Transaction::eaShrsout)); + + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), m_priceFraction); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeIsPercentage), m_feeIsPercentage); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfFeeRate), m_feeRate); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfMinFee), m_minFee); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), m_securityName); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), m_securitySymbol); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), m_dontAsk); + + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), + m_colTypeNum.value(ColumnDate)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnType), + m_colTypeNum.value(ColumnType)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnQuantity), + m_colTypeNum.value(ColumnQuantity)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnAmount), + m_colTypeNum.value(ColumnAmount)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnPrice), + m_colTypeNum.value(ColumnPrice)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnSymbol), + m_colTypeNum.value(ColumnSymbol)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnName), + m_colTypeNum.value(ColumnName)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnFee), + m_colTypeNum.value(ColumnFee)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnMemo), + m_memoColList); + profilesGroup.config()->sync(); +} + +bool PricesProfile::readSettings(const KSharedConfigPtr &config) +{ + bool exists = true; + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + if (!profilesGroup.exists()) + exists = false; + + m_colTypeNum[ColumnDate] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), -1); + m_colTypeNum[ColumnPrice] = profilesGroup.readEntry(CSVImporter::m_colTypeConfName.value(ColumnPrice), -1); + m_priceFraction = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), 2); + m_securityName = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), QString()); + m_securitySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), QString()); + m_currencySymbol = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfCurrencySymbol), QString()); + m_dontAsk = profilesGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), 0); + + CSVProfile::readSettings(profilesGroup); + return exists; +} + +void PricesProfile::writeSettings(const KSharedConfigPtr &config) +{ + KConfigGroup profilesGroup(config, CSVImporter::m_profileConfPrefix.value(type()) + QLatin1Char('-') + m_profileName); + CSVProfile::writeSettings(profilesGroup); + + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnDate), + m_colTypeNum.value(ColumnDate)); + profilesGroup.writeEntry(CSVImporter::m_colTypeConfName.value(ColumnPrice), + m_colTypeNum.value(ColumnPrice)); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfPriceFraction), m_priceFraction); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecurityName), m_securityName); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfSecuritySymbol), m_securitySymbol); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfCurrencySymbol), m_currencySymbol); + profilesGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfDontAsk), m_dontAsk); + profilesGroup.config()->sync(); +} + +CSVFile::CSVFile() +{ + m_parse = new Parse; + m_csvUtil = new CsvUtil; + m_model = new QStandardItemModel(); +} + +CSVFile::~CSVFile() +{ + delete m_parse; + delete m_csvUtil; + delete m_model; +} + +void CSVFile::getStartEndRow(CSVProfile *profile) +{ + profile->m_endLine = m_rowCount - 1; + if (profile->m_endLine > profile->m_trailerLines) + profile->m_endLine -= profile->m_trailerLines; + + if (profile->m_startLine > m_rowCount - 1) // Don't allow m_startLine > m_endLine + profile->m_startLine = m_rowCount - 1; +} + +void CSVFile::getColumnCount(CSVProfile *profile, const QStringList &rows) +{ + if (rows.isEmpty()) + return; + + QList delimiterIndexes; + if (profile->m_fieldDelimiterIndex == -1) + delimiterIndexes = QList{0, 1, 2, 3}; // include all delimiters to test or ... + else + delimiterIndexes = QList{profile->m_fieldDelimiterIndex}; // ... only the one specified + + QList totalDelimiterCount({0, 0, 0, 0}); // Total in file for each delimiter + QList thisDelimiterCount({0, 0, 0, 0}); // Total in this line for each delimiter + int colCount = 0; // Total delimiters in this line + int possibleDelimiter = 0; + m_columnCount = 0; + + foreach (const auto row, rows) { + foreach(const auto delimiterIndex, delimiterIndexes) { + m_parse->setFieldDelimiterIndex(delimiterIndex); + colCount = m_parse->parseLine(row).count(); // parse each line using each delimiter + + if (colCount > thisDelimiterCount.at(delimiterIndex)) + thisDelimiterCount[delimiterIndex] = colCount; + + if (thisDelimiterCount[delimiterIndex] > m_columnCount) + m_columnCount = thisDelimiterCount.at(delimiterIndex); + + totalDelimiterCount[delimiterIndex] += colCount; + if (totalDelimiterCount.at(delimiterIndex) > totalDelimiterCount.at(possibleDelimiter)) + possibleDelimiter = delimiterIndex; + } + } + if (delimiterIndexes.count() != 1) // if purpose was to autodetect... + profile->m_fieldDelimiterIndex = possibleDelimiter; // ... then change field delimiter + m_parse->setFieldDelimiterIndex(profile->m_fieldDelimiterIndex); // restore original field delimiter +} + +bool CSVFile::getInFileName(QString inFileName) +{ + QFileInfo fileInfo; + if (!inFileName.isEmpty()) { + if (inFileName.startsWith(QLatin1Char('~'))) + inFileName.replace(0, 1, QDir::homePath()); + fileInfo = QFileInfo(inFileName); + if (fileInfo.isFile()) { // if it is file... + if (fileInfo.exists()) { // ...and exists... + m_inFileName = inFileName; // ...then set as valid filename + return true; // ...and return success... + } else { // ...but if not... + fileInfo.setFile(fileInfo.absolutePath()); //...then set start directory to directory of that file... + if (!fileInfo.exists()) //...and if it doesn't exist too... + fileInfo.setFile(QDir::homePath()); //...then set start directory to home path + } + } else if (fileInfo.isDir()) { + if (fileInfo.exists()) + fileInfo = QFileInfo(inFileName); + else + fileInfo.setFile(QDir::homePath()); + } + } else + fileInfo = QFileInfo(QDir::homePath()); + + QPointer dialog = new QFileDialog(nullptr, QString(), + fileInfo.absoluteFilePath(), + i18n("CSV Files (*.csv)")); + dialog->setFileMode(QFileDialog::ExistingFile); + QUrl url; + if (dialog->exec() == QDialog::Accepted) + url = dialog->selectedUrls().first(); + delete dialog; + + if (url.isEmpty()) { + m_inFileName.clear(); + return false; + } else + m_inFileName = url.toDisplayString(QUrl::PreferLocalFile); + + return true; +} + +void CSVFile::setupParser(CSVProfile *profile) +{ + if (profile->m_decimalSymbolIndex != 2) { + m_parse->setDecimalSymbol(profile->m_decimalSymbolIndex); + m_parse->setThousandsSeparator(profile->m_decimalSymbolIndex); + } + m_parse->setFieldDelimiterCharacter(profile->m_fieldDelimiterIndex); + m_parse->setTextDelimiterCharacter(profile->m_textDelimiterIndex); +} + +void CSVFile::readFile(CSVProfile *profile) +{ + QFile inFile(m_inFileName); + if (!inFile.exists()) + return; + inFile.open(QIODevice::ReadOnly); + QTextStream inStream(&inFile); + QTextCodec* codec = QTextCodec::codecForMib(profile->m_encodingMIBEnum); + inStream.setCodec(codec); + + QString buf = inStream.readAll(); + inFile.close(); + m_parse->setTextDelimiterCharacter(profile->m_textDelimiterIndex); + QStringList rows = m_parse->parseFile(buf, 1, 0); // parse the buffer + m_rowCount = m_parse->lastLine(); // won't work without above line + getColumnCount(profile, rows); + getStartEndRow(profile); + + // prepare model from rows having rowCount and columnCount + m_model->clear(); + for (int i = 0; i < m_rowCount; ++i) { + QList itemList; + QStringList columns = m_parse->parseLine(rows.takeFirst()); // take instead of read from rows to preserve memory + for (int j = 0; j < m_columnCount; ++j) + itemList.append(new QStandardItem(columns.value(j, QString()))); + m_model->appendRow(itemList); + } +} diff --git a/kmymoney/plugins/csvimport/csvimporterplugin.h b/kmymoney/plugins/csvimport/csvimporterplugin.h --- a/kmymoney/plugins/csvimport/csvimporterplugin.h +++ b/kmymoney/plugins/csvimport/csvimporterplugin.h @@ -4,6 +4,8 @@ begin : Sat Jan 01 2010 copyright : (C) 2010 by Allan Anderson email : agander93@gmail.com + copyright : (C) 2016-2017 by Łukasz Wojniłowicz + email : lukasz.wojnilowicz@gmail.com ***************************************************************************/ /*************************************************************************** @@ -26,7 +28,12 @@ // Project Includes +#include "csvimporter.h" #include "kmymoneyplugin.h" +#include "csvwizard.h" + +class CSVImporter; +class CSVWizard; class CsvImporterPlugin : public KMyMoneyPlugin::Plugin { @@ -38,17 +45,18 @@ ~CsvImporterPlugin(); QAction* m_action; - + CSVWizard* m_wizard; + CSVImporter* m_importer; +private: + bool m_silent; public slots: bool slotGetStatement(MyMoneyStatement& s); protected slots: - void slotImportFile(); + void startWizardRun(); protected: void createActions(); - bool importStatement(const MyMoneyStatement& s); - void processStatement(const MyMoneyStatement& s); }; #endif diff --git a/kmymoney/plugins/csvimport/csvimporterplugin.cpp b/kmymoney/plugins/csvimport/csvimporterplugin.cpp --- a/kmymoney/plugins/csvimport/csvimporterplugin.cpp +++ b/kmymoney/plugins/csvimport/csvimporterplugin.cpp @@ -25,19 +25,22 @@ #include #include +#include +#include +#include // ---------------------------------------------------------------------------- // KDE Includes #include -#include +#include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneystatementreader.h" #include "mymoneystatement.h" -#include "csvwizard.h" +#include "mymoneyfile.h" CsvImporterPlugin::CsvImporterPlugin() : KMyMoneyPlugin::Plugin(nullptr, "csvimport"/*must be the same as X-KDE-PluginInfo-Name*/) @@ -57,23 +60,24 @@ { m_action = actionCollection()->addAction("file_import_csv"); m_action->setText(i18n("CSV...")); - connect(m_action, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); + connect(m_action, SIGNAL(triggered(bool)), this, SLOT(startWizardRun())); } -void CsvImporterPlugin::slotImportFile() +void CsvImporterPlugin::startWizardRun() { m_action->setEnabled(false); - CSVWizard *csvImporter = new CSVWizard; - csvImporter->m_plugin = this; - csvImporter->init(); - - connect(csvImporter, SIGNAL(statementReady(MyMoneyStatement&)), this, SLOT(slotGetStatement(MyMoneyStatement&))); + m_importer = new CSVImporter; + m_wizard = new CSVWizard(this, m_importer); + m_silent = false; + connect(m_importer, SIGNAL(statementReady(MyMoneyStatement&)), this, SLOT(slotGetStatement(MyMoneyStatement&))); m_action->setEnabled(false);// don't allow further plugins to start while this is open } bool CsvImporterPlugin::slotGetStatement(MyMoneyStatement& s) { - return statementInterface()->import(s); + bool ret = statementInterface()->import(s, m_silent); + delete m_importer; + return ret; } #include "csvimporterplugin.moc" diff --git a/kmymoney/plugins/csvimport/csvwizard.h b/kmymoney/plugins/csvimport/csvwizard.h --- a/kmymoney/plugins/csvimport/csvwizard.h +++ b/kmymoney/plugins/csvimport/csvwizard.h @@ -20,29 +20,54 @@ #ifndef CSVWIZARD_H #define CSVWIZARD_H +// ---------------------------------------------------------------------------- +// QT Includes + #include #include #include -#include #include -#include +#include + +// ---------------------------------------------------------------------------- +// KDE Includes -#include #include +// ---------------------------------------------------------------------------- +// Project Includes + +#include "transactiondlg.h" +#include "securitydlg.h" +#include "securitiesdlg.h" +#include "currenciesdlg.h" + +//#include "csvimporter.h" #include "csvimporterplugin.h" #include "bankingwizardpage.h" #include "investmentwizardpage.h" #include "priceswizardpage.h" -class ConvertDate; -class Parse; -class CsvUtil; -class FormatsPage; + +class CsvImporterPlugin; +class CSVImporter; + +class TransactionDlg; +class SecurityDlg; +class SecuritiesDlg; +class CurrenciesDlg; + +class BankingProfile; +class InvestmentProfile; +class PricesProfile; + class IntroPage; class SeparatorPage; class RowsPage; -class CsvImporterPlugin; +class BankingPage; +class InvestmentPage; +class PricesPage; +class FormatsPage; namespace Ui { @@ -54,221 +79,103 @@ Q_OBJECT public: - explicit CSVWizard(); - virtual ~CSVWizard(); + CSVWizard(CsvImporterPlugin *plugin, CSVImporter *importer); + ~CSVWizard(); - enum { PageIntro, PageSeparator, PageRows, - PageBanking, PageInvestment, PagePrices, PageFormats - }; + enum wizardPageE { PageIntro, PageSeparator, PageRows, + PageBanking, PageInvestment, PagePrices, PageFormats + }; - typedef enum:int { ProfileInvest, ProfileBank, ProfileCurrencyPrices, ProfileStockPrices, ProfileNone = 0xFF, - } profileTypeE; + Ui::CSVWizard* ui; - typedef enum:uchar { AutoFieldDelimiter, AutoDecimalSymbol, AutoDateFormat, - AutoAccountInvest, AutoAccountBank, - } autodetectTypeE; + CsvImporterPlugin* m_plugin; + CSVImporter* m_imp; + QWizard* m_wiz; - Ui::CSVWizard* ui; - QWizard* m_wizard; - IntroPage* m_pageIntro; - SeparatorPage* m_pageSeparator; - RowsPage* m_pageRows; + IntroPage *m_pageIntro; + SeparatorPage *m_pageSeparator; + RowsPage *m_pageRows; QPointer m_pageBanking; QPointer m_pageInvestment; QPointer m_pagePrices; - FormatsPage* m_pageFormats; - ConvertDate* m_convertDate; - CsvUtil* m_csvUtil; - Parse* m_parse; - CsvImporterPlugin* m_plugin; - - MyMoneyStatement st; + FormatsPage *m_pageFormats; - KSharedConfigPtr m_config; + MyMoneyStatement m_st; - QList m_stageLabels; - QScrollBar* m_vScrollBar; - QList m_codecs; + QList m_stageLabels; + QScrollBar *m_vScrollBar; QBrush m_clearBrush; QBrush m_clearBrushText; QBrush m_colorBrush; QBrush m_colorBrushText; QBrush m_errorBrush; QBrush m_errorBrushText; - QString m_fieldDelimiterCharacter; - QString m_textDelimiterCharacter; - QString m_decimalSymbol; - QString m_inFileName; - int m_profileType; - QString m_date; - QString m_profileName; - QStringList m_lineList; - QStringList m_dateFormats; - QStringList m_profileList; - QList m_memoColList; - QUrl m_url; - int m_initialHeight; int m_initialWidth; - int m_fieldDelimiterIndex; - int m_textDelimiterIndex; - int m_decimalSymbolIndex; - QMap m_decimalSymbolIndexMap; - int m_row; - int m_maxColumnCount; - int m_endColumn; - int m_encodeIndex; - int m_startLine; - int m_endLine; - int m_trailerLines; - int m_fileEndLine; - int m_dateFormatIndex; - int m_memoColumn; - int m_dateColumn; - - QMap m_autodetect; - - bool m_accept; - bool m_importError; - bool m_skipSetup; - bool m_acceptAllInvalid; - /** - * This method is called after startup, to initialise some parameters. - */ - void init(); + QMap m_colTypeName; + bool m_skipSetup; void showStage(); - void readMiscSettings(const KSharedConfigPtr& config); - void saveWindowSize(const KSharedConfigPtr& config); - - /** - * This method contains routines to update configuration file - * from kmmVer to latest. - */ - bool updateConfigFile(const KSharedConfigPtr &config, const QList &kmmVer); - - /** - * This method ensures that configuration file contains all neccesary fields - * and that it is up to date. - */ - void validateConfigFile(const KSharedConfigPtr &config); - - /** - * This method is called on opening the plugin. - * It will add all codec names to the encoding combobox. - */ - void setCodecList(const QList &list, QComboBox *comboBoxEncode); - - /** - * This method is called on opening the plugin. - * It will populate a list with all available codecs. - */ - void findCodecs(); - - void clearColumnsBackground(int col); - void clearColumnsBackground(QList& columnList); + void clearColumnsBackground(const int col); + void clearColumnsBackground(const QList &columnList); void clearBackground(); void markUnwantedRows(); - QList findAccounts(QList &accountTypes, QString& statementHeader); - bool detectAccount(MyMoneyStatement& st); -signals: - void statementReady(MyMoneyStatement&); public slots: /** - * This method is called when the user clicks 'Encoding' and selects an - * encoding setting. The file is re-read with the corresponding codec. - */ - void encodingChanged(int); - - bool detectDecimalSymbol(const int col, int& symbol); - - /** * This method is called when 'Exit' is clicked. The plugin settings will * be saved and the plugin will be terminated. */ - void slotClose(); - - /** - * If delimiter = -1 this method tries different fild - * delimiters to get the one with which file has the most columns. - * Otherwise it gets only column count for specified delimiter. - */ - int getMaxColumnCount(QStringList &lineList, int &delimiter); - - /** - * This method gets the filename of - * the financial statement. - */ - bool getInFileName(QString &startDir); - - /** - * This method gets file into buffer - * It will laso store file's end column and row. - */ - void readFile(const QString& fname); - - /** - * It will display lines list in the UI table widget. - */ - void displayLines(const QStringList &lineList, Parse *parse); + void slotClose(); /** * Called in order to adjust window size to suit the file, */ - void updateWindowSize(); + void updateWindowSize(); - /** - * This method is called when the user clicks 'Open File'. It ends - * in importing ready bank statement. - */ - void slotFileDialogClicked(); + void readWindowSize(const KSharedConfigPtr& config); + void saveWindowSize(const KSharedConfigPtr& config); - void slotIdChanged(int id); + void slotIdChanged(int id); + + void fileDialogClicked(); + void importClicked(); + void saveAsQIFClicked(); private: - int m_curId; - int m_lastId; + int m_curId; + int m_lastId; - void closeEvent(QCloseEvent *event); - bool eventFilter(QObject *object, QEvent *event); - void resizeEvent(QResizeEvent* ev); + void closeEvent(QCloseEvent *event); + bool eventFilter(QObject *object, QEvent *event); +// void resizeEvent(QResizeEvent* ev); } ; namespace Ui { class IntroPage; } -class CSVWizardPage : public QWizardPage { -public: - CSVWizardPage(QWidget *parent = 0) : QWizardPage(parent), m_wizDlg(0) {} - - virtual void setParent(CSVWizard* dlg) { m_wizDlg = dlg; } - -protected: - CSVWizard* m_wizDlg; -}; - class IntroPage : public CSVWizardPage { Q_OBJECT public: - explicit IntroPage(QDialog *parent = 0); + explicit IntroPage(CSVWizard *dlg, CSVImporter *imp); ~IntroPage(); Ui::IntroPage *ui; void initializePage(); - void setParent(CSVWizard* dlg); +// void setParent(CSVWizard* dlg, CSVImporter *imp); QVBoxLayout* m_pageLayout; + profileTypeE m_profileType; signals: void signalBankClicked(bool); @@ -279,11 +186,12 @@ bool validatePage(); int nextId() const; - typedef enum:uchar { ProfileAdd, ProfileRemove, ProfileRename, - } profileActionsE; + QStringList m_profiles; + + void profileChanged(const profilesActionE action); + void profileTypeChanged(const profileTypeE profileType, bool toggled); - void profileChanged(const profileActionsE& action); - void profileTypeChanged(const CSVWizard::profileTypeE profileType, bool toggled); +public slots: private slots: void slotAddProfile(); @@ -306,7 +214,7 @@ Q_OBJECT public: - explicit SeparatorPage(QDialog *parent = 0); + explicit SeparatorPage(CSVWizard *dlg, CSVImporter *imp); ~SeparatorPage(); Ui::SeparatorPage *ui; @@ -317,21 +225,15 @@ bool isComplete() const; public slots: - /** - * This method is called when a text delimiter is changed. - */ - void textDelimiterChanged(const int index); - - /** - * This method is called when a field delimiter is changed. The - * input file is redisplayed using the new delimiter. - */ + void encodingChanged(const int index); void fieldDelimiterChanged(const int index); + void textDelimiterChanged(const int index); signals: void completeChanged(); private: + void initializeEncodingCombobox(); void cleanupPage(); bool validatePage(); @@ -350,7 +252,7 @@ Q_OBJECT public: - explicit RowsPage(QDialog *parent = 0); + explicit RowsPage(CSVWizard *dlg, CSVImporter *imp); ~RowsPage(); Ui::RowsPage *ui; @@ -376,6 +278,8 @@ void cleanupPage(); }; + + namespace Ui { class FormatsPage; @@ -386,7 +290,7 @@ Q_OBJECT public: - explicit FormatsPage(QDialog *parent = 0); + explicit FormatsPage(CSVWizard *dlg, CSVImporter *imp); ~FormatsPage(); Ui::FormatsPage *ui; @@ -400,29 +304,20 @@ * UI is updated using the new symbol, and on importing, the new symbol * also will be used. */ - bool validateDecimalSymbol(int col); + bool validateDecimalSymbols(const QList &columns); /** * This method checks if all dates in date column are valid. */ - bool validateDateFormat(int index); + bool validateDateFormat(const int index); signals: void completeChanged(); public slots: - void slotImportClicked(); - void slotSaveAsQIFClicked(); - /** - * This method is called when the user selects a new decimal symbol. The - * UI is updated using the new symbol. - */ - void decimalSymbolChanged(int); - /** - * This method is called when the user clicks 'Date format' and selects a - * format, which is used by convertDate(). - */ - void dateFormatChanged(int index); + + void decimalSymbolChanged(int); + void dateFormatChanged(const int index); private: bool m_isDecimalSymbolOK; bool m_isDateFormatOK; diff --git a/kmymoney/plugins/csvimport/csvwizard.cpp b/kmymoney/plugins/csvimport/csvwizard.cpp --- a/kmymoney/plugins/csvimport/csvwizard.cpp +++ b/kmymoney/plugins/csvimport/csvwizard.cpp @@ -19,364 +19,133 @@ #include "csvwizard.h" -#include -#include +// ---------------------------------------------------------------------------- +// QT Includes + +#include #include -#include #include -#include #include +#include + +// ---------------------------------------------------------------------------- +// KDE Includes #include #include #include #include -#include -#include -#include "convdate.h" -#include "csvutil.h" - -#include "mymoneyfile.h" +// ---------------------------------------------------------------------------- +// Project Includes #include "ui_csvwizard.h" #include "ui_introwizardpage.h" #include "ui_separatorwizardpage.h" #include "ui_rowswizardpage.h" #include "ui_bankingwizardpage.h" -#include "ui_formatswizardpage.h" #include "ui_investmentwizardpage.h" #include "ui_priceswizardpage.h" +#include "ui_formatswizardpage.h" + +#include "ui_securitiesdlg.h" +#include "ui_securitydlg.h" -CSVWizard::CSVWizard() : +CSVWizard::CSVWizard(CsvImporterPlugin* plugin, CSVImporter* importer) : ui(new Ui::CSVWizard), - m_pageIntro(0), - m_pageSeparator(0), - m_pageBanking(0), - m_pageInvestment(0) + m_plugin(plugin), + m_imp(importer), + m_wiz(new QWizard) { ui->setupUi(this); - - m_parse = new Parse; - m_convertDate = new ConvertDate; - m_csvUtil = new CsvUtil; - - st = MyMoneyStatement(); + ui->tableView->setModel(m_imp->m_file->m_model); + readWindowSize(CSVImporter::configFile()); + m_wiz->setWizardStyle(QWizard::ClassicStyle); + ui->horizontalLayout->addWidget(m_wiz); m_curId = -1; m_lastId = -1; - m_maxColumnCount = 0; - m_importError = false; - m_fileEndLine = 0; - ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); - - m_wizard = new QWizard; - m_wizard->setWizardStyle(QWizard::ClassicStyle); - ui->horizontalLayout->addWidget(m_wizard); - m_wizard->installEventFilter(this); // event filter for escape key presses - - m_wizard->button(QWizard::BackButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); - m_wizard->button(QWizard::CancelButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"), + m_wiz->installEventFilter(this); // event filter for escape key presses + + m_wiz->button(QWizard::BackButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); + m_wiz->button(QWizard::CancelButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"), QIcon::fromTheme(QStringLiteral("stop")))); - m_wizard->button(QWizard::CustomButton2)->setIcon(QIcon::fromTheme(QStringLiteral("kmymoney"))); - m_wizard->button(QWizard::FinishButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"), + m_wiz->button(QWizard::CustomButton2)->setIcon(QIcon::fromTheme(QStringLiteral("kmymoney"))); + m_wiz->button(QWizard::FinishButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"), QIcon::fromTheme(QStringLiteral("finish")))); - m_wizard->button(QWizard::CustomButton1)->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); - m_wizard->button(QWizard::CustomButton3)->setIcon(QIcon::fromTheme(QStringLiteral("invest-applet"))); - m_wizard->button(QWizard::NextButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); -} + m_wiz->button(QWizard::CustomButton1)->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); + m_wiz->button(QWizard::CustomButton3)->setIcon(QIcon::fromTheme(QStringLiteral("invest-applet"))); + m_wiz->button(QWizard::NextButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); -void CSVWizard::init() -{ - m_pageIntro = new IntroPage; - m_wizard->setPage(PageIntro, m_pageIntro); - m_pageIntro->setParent(this); + m_pageIntro = new IntroPage(this, m_imp); + m_wiz->setPage(PageIntro, m_pageIntro); + + m_pageSeparator = new SeparatorPage(this, m_imp); + m_wiz->setPage(PageSeparator, m_pageSeparator); - m_pageSeparator = new SeparatorPage; - m_wizard->setPage(PageSeparator, m_pageSeparator); - m_pageSeparator->setParent(this); + m_pageRows = new RowsPage(this, m_imp); + m_wiz->setPage(PageRows, m_pageRows); - m_pageRows = new RowsPage; - m_wizard->setPage(PageRows, m_pageRows); - m_pageRows->setParent(this); + m_pageFormats = new FormatsPage(this, m_imp); + m_wiz->setPage(PageFormats, m_pageFormats); - m_pageFormats = new FormatsPage; - m_wizard->setPage(PageFormats, m_pageFormats); - m_pageFormats->setParent(this); + showStage(); + m_wiz->button(QWizard::CustomButton1)->setEnabled(false); m_stageLabels << ui->label_intro << ui->label_separators << ui->label_rows << ui->label_columns << ui->label_columns << ui->label_columns << ui->label_formats; m_pageFormats->setFinalPage(true); this->setAttribute(Qt::WA_DeleteOnClose, true); - m_profileList.clear(); - findCodecs(); - m_config = KSharedConfig::openConfig(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - QDir::separator() + - "csvimporterrc"); - validateConfigFile(m_config); - readMiscSettings(m_config); - - connect(m_pageFormats, SIGNAL(statementReady(MyMoneyStatement&)), m_plugin, SLOT(slotGetStatement(MyMoneyStatement&))); + connect(m_wiz->button(QWizard::FinishButton), &QAbstractButton::clicked, this, &CSVWizard::slotClose); + connect(m_wiz->button(QWizard::CancelButton), &QAbstractButton::clicked, this, &QWidget::close); + connect(m_wiz->button(QWizard::CustomButton1), &QAbstractButton::clicked, this, &CSVWizard::fileDialogClicked); + connect(m_wiz->button(QWizard::CustomButton2), &QAbstractButton::clicked, this, &CSVWizard::importClicked); + connect(m_wiz->button(QWizard::CustomButton3), &QAbstractButton::clicked, this, &CSVWizard::saveAsQIFClicked); + connect(m_wiz, SIGNAL(currentIdChanged(int)), this, SLOT(slotIdChanged(int))); - connect(m_wizard->button(QWizard::FinishButton), SIGNAL(clicked()), this, SLOT(slotClose())); - connect(m_wizard->button(QWizard::CancelButton), SIGNAL(clicked()), this, SLOT(close())); - connect(m_wizard->button(QWizard::CustomButton1), SIGNAL(clicked()), this, SLOT(slotFileDialogClicked())); - connect(m_wizard->button(QWizard::CustomButton2), SIGNAL(clicked()), m_pageFormats, SLOT(slotImportClicked())); - connect(m_wizard->button(QWizard::CustomButton3), SIGNAL(clicked()), m_pageFormats, SLOT(slotSaveAsQIFClicked())); - connect(m_wizard, SIGNAL(currentIdChanged(int)), this, SLOT(slotIdChanged(int))); + ui->tableView->setWordWrap(false); - ui->tableWidget->setWordWrap(false); - - m_vScrollBar = ui->tableWidget->verticalScrollBar(); + m_vScrollBar = ui->tableView->verticalScrollBar(); m_vScrollBar->setTracking(false); - m_dateFormats << "yyyy/MM/dd" << "MM/dd/yyyy" << "dd/MM/yyyy"; m_clearBrush = KColorScheme(QPalette::Normal).background(KColorScheme::NormalBackground); m_clearBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText); m_colorBrush = KColorScheme(QPalette::Normal).background(KColorScheme::PositiveBackground); m_colorBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::PositiveText); m_errorBrush = KColorScheme(QPalette::Normal).background(KColorScheme::NegativeBackground); m_errorBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText); - int y = (QApplication::desktop()->height() - this->height()) / 2; - int x = (QApplication::desktop()->width() - this->width()) / 2; - move(x, y); show(); } CSVWizard::~CSVWizard() { delete ui; - delete m_parse; - delete m_convertDate; - delete m_csvUtil; - delete m_wizard; + if (m_wiz) + delete m_wiz; } void CSVWizard::showStage() { QString str = ui->label_intro->text(); - ui->label_intro->setText("" + str + ""); + ui->label_intro->setText(QLatin1String("") + str + QLatin1String("/")); } -void CSVWizard::readMiscSettings(const KSharedConfigPtr& config) { - KConfigGroup miscGroup(config, "Misc"); - m_initialWidth = miscGroup.readEntry("Width", 800); - m_initialHeight = miscGroup.readEntry("Height", 400); - m_autodetect.clear(); - m_autodetect.insert(CSVWizard::AutoFieldDelimiter, miscGroup.readEntry("AutoFieldDelimiter", true)); - m_autodetect.insert(CSVWizard::AutoDecimalSymbol, miscGroup.readEntry("AutoDecimalSymbol", true)); - m_autodetect.insert(CSVWizard::AutoDateFormat, miscGroup.readEntry("AutoDateFormat", true)); - m_autodetect.insert(CSVWizard::AutoAccountInvest, miscGroup.readEntry("AutoAccountInvest", true)); - m_autodetect.insert(CSVWizard::AutoAccountBank, miscGroup.readEntry("AutoAccountBank", true)); +void CSVWizard::readWindowSize(const KSharedConfigPtr& config) { + KConfigGroup miscGroup(config, CSVImporter::m_confMiscName); + m_initialWidth = miscGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfWidth), 800); + m_initialHeight = miscGroup.readEntry(CSVImporter::m_miscSettingsConfName.value(ConfHeight), 400); } void CSVWizard::saveWindowSize(const KSharedConfigPtr& config) { - KConfigGroup miscGroup(config, "Misc"); + KConfigGroup miscGroup(config, CSVImporter::m_confMiscName); m_initialHeight = this->geometry().height(); m_initialWidth = this->geometry().width(); - miscGroup.writeEntry("Width", m_initialWidth); - miscGroup.writeEntry("Height", m_initialHeight); + miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfWidth), m_initialWidth); + miscGroup.writeEntry(CSVImporter::m_miscSettingsConfName.value(ConfHeight), m_initialHeight); miscGroup.sync(); } -bool CSVWizard::updateConfigFile(const KSharedConfigPtr& config, const QList& kmmVer) -{ - QString configFilePath = config.constData()->name(); - QFile::copy(configFilePath, configFilePath + ".bak"); - - KConfigGroup profileNamesGroup(config, "ProfileNames"); - QStringList bankProfiles = profileNamesGroup.readEntry("Bank", QStringList()); - QStringList investProfiles = profileNamesGroup.readEntry("Invest", QStringList()); - QStringList invalidBankProfiles = profileNamesGroup.readEntry("InvalidBank", QStringList()); // get profiles that was marked invalid during last update - QStringList invalidInvestProfiles = profileNamesGroup.readEntry("InvalidInvest", QStringList()); - QString bankPrefix = "Bank-"; - QString investPrefix = "Invest-"; - - uint version = kmmVer[0]*100 + kmmVer[1]*10 + kmmVer[2]; - - // for kmm < 5.0.0 change 'BankNames' to 'ProfileNames' and remove 'MainWindow' group - if (version < 500 && bankProfiles.isEmpty()) { - KConfigGroup oldProfileNamesGroup(config, "BankProfiles"); - bankProfiles = oldProfileNamesGroup.readEntry("BankNames", QStringList()); // profile names are under 'BankNames' entry for kmm < 5.0.0 - bankPrefix = "Profiles-"; // needed to remove non-existent profiles in first run - oldProfileNamesGroup.deleteGroup(); - KConfigGroup oldMainWindowGroup(config, "MainWindow"); - oldMainWindowGroup.deleteGroup(); - } - - bool firstTry = false; - if (invalidBankProfiles.isEmpty() && invalidInvestProfiles.isEmpty()) // if there is no invalid profiles then this might be first update try - firstTry = true; - - bool ret = true; - int invalidProfileResponse = QMessageBox::No; - - for (QStringList::Iterator profileName = bankProfiles.begin(); profileName != bankProfiles.end(); ++profileName) { - - KConfigGroup bankProfile(config, bankPrefix + *profileName); - if (!bankProfile.exists() && !invalidBankProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference - profileName = bankProfiles.erase(profileName); - profileName--; - continue; - } - - // for kmm < 5.0.0 remove 'FileType' and 'ProfileName' and assign them to either "Bank=" or "Invest=" - if (version < 500) { - KConfigGroup oldBankProfile(config, "Profiles-" + *profileName); // if half of configuration is updated and the other one untouched this is needed - QString oldProfileType = oldBankProfile.readEntry("FileType", QString()); - KConfigGroup newProfile; - if (oldProfileType == "Invest") { - newProfile = KConfigGroup(config, "Invest-" + *profileName); - investProfiles.append(*profileName); - profileName = bankProfiles.erase(profileName); - profileName--; - } - else if (oldProfileType == "Banking") - newProfile = KConfigGroup(config, "Bank-" + *profileName); - else { - if (invalidProfileResponse != QMessageBox::YesToAll && invalidProfileResponse != QMessageBox::NoToAll) { - if (!firstTry && - !invalidBankProfiles.contains(*profileName)) // if it isn't first update run and profile isn't on the list of invalid ones then don't bother - continue; - invalidProfileResponse = QMessageBox::warning(m_wizard, i18n("CSV import"), - i18n("
During update of %1
" - "the profile type for %2 could not be recognized.
" - "The profile cannot be used because of that.
" - "Do you want to delete it?
", - configFilePath, *profileName), - QMessageBox::Yes | QMessageBox::YesToAll | - QMessageBox::No | QMessageBox::NoToAll, QMessageBox::No ); - } - - switch (invalidProfileResponse) { - case QMessageBox::YesToAll: - case QMessageBox::Yes: - oldBankProfile.deleteGroup(); - invalidBankProfiles.removeOne(*profileName); - profileName = bankProfiles.erase(profileName); - --profileName; - break; - case QMessageBox::NoToAll: - case QMessageBox::No: - if (!invalidBankProfiles.contains(*profileName)) // on user request: don't delete profile but keep eye on it - invalidBankProfiles.append(*profileName); - ret = false; - break; - } - continue; - } - oldBankProfile.deleteEntry("FileType"); - oldBankProfile.deleteEntry("ProfileName"); - oldBankProfile.deleteEntry("DebitFlag"); - oldBankProfile.deleteEntry("InvDirectory"); - oldBankProfile.deleteEntry("CsvDirectory"); - oldBankProfile.copyTo(&newProfile); - oldBankProfile.deleteGroup(); - newProfile.sync(); - oldBankProfile.sync(); - } - } - - for (QStringList::Iterator profileName = investProfiles.begin(); profileName != investProfiles.end(); ++profileName) { - - KConfigGroup investProfile(config, investPrefix + *profileName); - if (!investProfile.exists() && !invalidInvestProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference - investProfiles.erase(profileName); - continue; - } - } - - profileNamesGroup.writeEntry("Bank", bankProfiles); // update profile names as some of them might have been changed - profileNamesGroup.writeEntry("Invest", investProfiles); - - if (invalidBankProfiles.isEmpty()) // if no invalid profiles then we don't need this variable anymore - profileNamesGroup.deleteEntry("InvalidBank"); - else - profileNamesGroup.writeEntry("InvalidBank", invalidBankProfiles); - - if (invalidInvestProfiles.isEmpty()) - profileNamesGroup.deleteEntry("InvalidInvest"); - else - profileNamesGroup.writeEntry("InvalidInvest", invalidInvestProfiles); - - if (ret) - QFile::remove(configFilePath + ".bak"); // remove backup if all is ok - - return ret; -} - -void CSVWizard::validateConfigFile(const KSharedConfigPtr& config) -{ - KConfigGroup profileNamesGroup(config, "ProfileNames"); - if (!profileNamesGroup.exists()) { - profileNamesGroup.writeEntry("Bank", QStringList()); - profileNamesGroup.writeEntry("Invest", QStringList()); - profileNamesGroup.writeEntry("SPrices", QStringList()); - profileNamesGroup.writeEntry("PriorBank", int()); - profileNamesGroup.writeEntry("PriorInvest", int()); - profileNamesGroup.writeEntry("PriorSPrices", int()); - profileNamesGroup.sync(); - } - - KConfigGroup miscGroup(config, "Misc"); - if (!miscGroup.exists()) { - miscGroup.writeEntry("Height", "400"); - miscGroup.writeEntry("Width", "800"); - miscGroup.sync(); - } - - QList kmmVer = miscGroup.readEntry("KMMVer", QList {0, 0, 0}); - QList curKmmVer = QList {5, 0, 0}; - if (curKmmVer != kmmVer) { - if (updateConfigFile(config, kmmVer)) // write kmmVer only if there were no errors - miscGroup.writeEntry("KMMVer", curKmmVer); - } - - KConfigGroup securitiesGroup(config, "Securities"); - if (!securitiesGroup.exists()) { - securitiesGroup.writeEntry("SecurityNameList", QStringList()); - securitiesGroup.sync(); - } -} - -void CSVWizard::setCodecList(const QList &list, QComboBox* comboBoxEncode) -{ - comboBoxEncode->clear(); - foreach (QTextCodec * codec, list) - comboBoxEncode->addItem(codec->name(), codec->mibEnum()); -} - -void CSVWizard::findCodecs() -{ - QMap codecMap; - QRegExp iso8859RegExp("ISO[- ]8859-([0-9]+).*"); - - foreach (int mib, QTextCodec::availableMibs()) { - QTextCodec *codec = QTextCodec::codecForMib(mib); - - QString sortKey = codec->name().toUpper(); - int rank; - - if (sortKey.startsWith("UTF-8")) { // krazy:exclude=strings - rank = 1; - } else if (sortKey.startsWith("UTF-16")) { // krazy:exclude=strings - rank = 2; - } else if (iso8859RegExp.exactMatch(sortKey)) { - if (iso8859RegExp.cap(1).size() == 1) - rank = 3; - else - rank = 4; - } else { - rank = 5; - } - sortKey.prepend(QChar('0' + rank)); - - codecMap.insert(sortKey, codec); - } - m_codecs = codecMap.values(); -} - void CSVWizard::slotIdChanged(int id) { QString txt; @@ -386,565 +155,267 @@ return; } txt = m_stageLabels[m_lastId]->text(); - txt.remove(QRegExp("[/]")); + txt.remove(QRegularExpression(QStringLiteral("[/]"))); m_stageLabels[m_lastId]->setText(txt); txt = m_stageLabels[m_curId]->text(); - txt = "" + txt + ""; + txt = QLatin1String("") + txt + QLatin1String(""); m_stageLabels[m_curId]->setText(txt); } -void CSVWizard::clearColumnsBackground(int col) { +void CSVWizard::clearColumnsBackground(const int col) +{ QList columnList; columnList << col; clearColumnsBackground(columnList); } -void CSVWizard::clearColumnsBackground(QList& columnList) +void CSVWizard::clearColumnsBackground(const QList &columnList) { - for (int row = m_startLine -1 ; row < m_endLine; ++row) { - for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { - QTableWidgetItem* item = ui->tableWidget->item(row, *col); - item->setBackground(m_clearBrush); - item->setForeground(m_clearBrushText); + QStandardItemModel *model = m_imp->m_file->m_model; + for (int i = m_imp->m_profile->m_startLine; i <= m_imp->m_profile->m_endLine; ++i) { + foreach (const auto j, columnList) { + model->item(i, j)->setBackground(m_clearBrush); + model->item(i, j)->setForeground(m_clearBrushText); } } } void CSVWizard::clearBackground() { - for (int row = 0; row < ui->tableWidget->rowCount(); ++row) { - for (int col = 0; col < ui->tableWidget->columnCount(); ++col) { - QTableWidgetItem *item = ui->tableWidget->item(row, col); - item->setBackground(m_clearBrush); - item->setForeground(m_clearBrushText); + QStandardItemModel *model = m_imp->m_file->m_model; + int rowCount = model->rowCount(); + int colCount = model->columnCount(); + for (int i = 0; i < rowCount; ++i) { + for (int j = 0; j < colCount; ++j) { + model->item(i, j)->setBackground(m_clearBrush); + model->item(i, j)->setForeground(m_clearBrushText); } } } void CSVWizard::markUnwantedRows() { - int first = m_startLine - 1; - int last = m_endLine - 1; - // - // highlight unwanted lines instead of not showing them. - // + QStandardItemModel *model = m_imp->m_file->m_model; + int rowCount = model->rowCount(); + int colCount = model->columnCount(); QBrush brush; QBrush brushText; - for (int row = 0; row < ui->tableWidget->rowCount(); row++) { - if ((row < first) || (row > last)) { + for (int i = 0; i < rowCount; ++i) { + if ((i < m_imp->m_profile->m_startLine) || (i > m_imp->m_profile->m_endLine)) { brush = m_errorBrush; brushText = m_errorBrushText; } else { brush = m_clearBrush; brushText = m_clearBrushText; } - for (int col = 0; col < ui->tableWidget->columnCount(); col ++) { - if (ui->tableWidget->item(row, col) != 0) { - ui->tableWidget->item(row, col)->setBackground(brush); - ui->tableWidget->item(row, col)->setForeground(brushText); - } + for (int j = 0; j < colCount; ++j) { + model->item(i, j)->setBackground(brush); + model->item(i, j)->setForeground(brushText); } } } -QList CSVWizard::findAccounts(QList &accountTypes, QString& statementHeader) -{ - MyMoneyFile* file = MyMoneyFile::instance(); - QList accountList; - file->accountList(accountList); - QList filteredTypes; - QList filteredAccounts; - QList::iterator account; - QRegExp filterOutChars = QRegExp("-., "); - - for (account = accountList.begin(); account != accountList.end(); ++account) { - if (accountTypes.contains((*account).accountType()) && !(*account).isClosed()) - filteredTypes << *account; - } - - // filter out accounts whose names aren't in statements header - for (account = filteredTypes.begin(); account != filteredTypes.end(); ++account) { - QString txt = (*account).name(); - txt = txt.replace(filterOutChars, ""); - if (statementHeader.contains(txt, Qt::CaseInsensitive)) - filteredAccounts << *account; - } - - // if filtering returned more results, filter out accounts whose numbers aren't in statements header - if (filteredAccounts.count() > 1) { - for (account = filteredAccounts.begin(); account != filteredAccounts.end();) { - QString txt = (*account).number(); - txt = txt.replace(filterOutChars, ""); - if (txt.isEmpty() || txt.length() < 3) { - ++account; - continue; - } - if (statementHeader.contains(txt, Qt::CaseInsensitive)) - ++account; - else - account = filteredAccounts.erase(account); - } - } - - // if filtering by name and number didn't return nothing, then try filtering by number only - if (filteredAccounts.isEmpty()) { - for (account = filteredTypes.begin(); account != filteredTypes.end(); ++account) { - QString txt = (*account).number(); - txt = txt.replace(filterOutChars, ""); - if (statementHeader.contains(txt, Qt::CaseInsensitive)) - filteredAccounts << *account; - } - } - return filteredAccounts; -} - -bool CSVWizard::detectAccount(MyMoneyStatement& st) +void CSVWizard::updateWindowSize() { - QString statementHeader; - for (int row = 0; row < m_startLine - 1; ++row) // concatenate header for better search - statementHeader += m_lineList.value(row); - - QRegExp filterOutChars = QRegExp("-., "); - statementHeader.replace(filterOutChars, ""); - - QList accounts; - QList accountTypes; - - if (m_profileType == CSVWizard::ProfileBank) { - accountTypes << MyMoneyAccount::Checkings << - MyMoneyAccount::Savings << - MyMoneyAccount::Liability << - MyMoneyAccount::Checkings << - MyMoneyAccount::Savings << - MyMoneyAccount::Cash << - MyMoneyAccount::CreditCard << - MyMoneyAccount::Loan << - MyMoneyAccount::Asset << - MyMoneyAccount::Liability; - accounts = findAccounts(accountTypes, statementHeader); - } else if (m_profileType == CSVWizard::ProfileInvest) { - accountTypes << MyMoneyAccount::Investment; // take investment accounts... - accounts = findAccounts(accountTypes, statementHeader); //...and search them in statement header - } + QTableView *table = this->ui->tableView; + table->resizeColumnsToContents(); + this->repaint(); - if (accounts.count() == 1) { // set account in statement, if it was the only one match - st.m_strAccountName = accounts.first().name(); - st.m_strAccountNumber = accounts.first().number(); - st.m_accountId = accounts.first().id(); + QRect screen = QApplication::desktop()->availableGeometry(); //get available screen size + QRect wizard = this->frameGeometry(); //get current wizard size - switch (accounts.first().accountType()) { - case MyMoneyAccount::Checkings: - st.m_eType=MyMoneyStatement::etCheckings; - break; - case MyMoneyAccount::Savings: - st.m_eType=MyMoneyStatement::etSavings; - break; - case MyMoneyAccount::Investment: - st.m_eType=MyMoneyStatement::etInvestment; - break; - case MyMoneyAccount::CreditCard: - st.m_eType=MyMoneyStatement::etCreditCard; - break; - default: - st.m_eType=MyMoneyStatement::etNone; - } - return true; + int newWidth = table->contentsMargins().left() + + table->contentsMargins().right() + + table->horizontalHeader()->length() + + table->verticalHeader()->width() + + (wizard.width() - table->width()); + if (table->verticalScrollBar()->isEnabled()) { + if (!table->verticalScrollBar()->isVisible() && // vertical scrollbar may be not visible after repaint... + table->horizontalScrollBar()->isVisible()) // ...so use horizontal scrollbar dimension + newWidth += table->horizontalScrollBar()->height(); + else + newWidth += table->verticalScrollBar()->width(); } - return false; -} -bool CSVWizard::detectDecimalSymbol(const int col, int& symbol) -{ - if (symbol != 2) - return true; - - // get list of used currencies to remove them from col - QList accountList; - MyMoneyFile::instance()->accountList(accountList); - - QList accountTypes; - accountTypes << MyMoneyAccount::Checkings << - MyMoneyAccount::Savings << - MyMoneyAccount::Liability << - MyMoneyAccount::Checkings << - MyMoneyAccount::Savings << - MyMoneyAccount::Cash << - MyMoneyAccount::CreditCard << - MyMoneyAccount::Loan << - MyMoneyAccount::Asset << - MyMoneyAccount::Liability; - - QSet currencySymbols; - for (QList::ConstIterator account = accountList.cbegin(); account != accountList.cend(); ++account) { - if (accountTypes.contains((*account).accountType())) { // account must actually have currency property - currencySymbols.insert((*account).currencyId()); // add currency id - currencySymbols.insert(MyMoneyFile::instance()->currency((*account).currencyId()).tradingSymbol()); // add currency symbol - } - } - QString filteredCurrencies = QStringList(currencySymbols.values()).join(""); - QString pattern = QString("%1%2").arg(QLocale().currencySymbol()).arg(filteredCurrencies); - QRegularExpression re("^[\\(+-]?\\d+[\\)]?$"); // matches '0' ; '+12' ; '-345' ; '(6789)' - - bool dotIsDecimalSeparator = false; - bool commaIsDecimalSeparator = false; - for (int row = m_startLine - 1; row < m_endLine; ++row) { - QString txt = ui->tableWidget->item(row, col)->text(); - if (txt.isEmpty()) // nothing to process, so go to next row - continue; - int dotPos = txt.lastIndexOf("."); // get last positions of decimal/thousand separator... - int commaPos = txt.lastIndexOf(","); // ...to be able to determine which one is the last - - if (dotPos != -1 && commaPos != -1) { - if (dotPos > commaPos && commaIsDecimalSeparator == false) // follwing case 1,234.56 - dotIsDecimalSeparator = true; - else if (dotPos < commaPos && dotIsDecimalSeparator == false) // follwing case 1.234,56 - commaIsDecimalSeparator = true; - else // follwing case 1.234,56 and somwhere earlier there was 1,234.56 so unresolvable conflict - return false; - } else if (dotPos != -1) { // follwing case 1.23 - if (dotIsDecimalSeparator) // it's already know that dotIsDecimalSeparator - continue; - if (!commaIsDecimalSeparator) // if there is no conflict with comma as decimal separator - dotIsDecimalSeparator = true; - else { - if (txt.count('.') > 1) // follwing case 1.234.567 so OK - continue; - else if (txt.length() - 4 == dotPos) // follwing case 1.234 and somwhere earlier there was 1.234,56 so OK - continue; - else // follwing case 1.23 and somwhere earlier there was 1,23 so unresolvable conflict - return false; - } - } else if (commaPos != -1) { // follwing case 1,23 - if (commaIsDecimalSeparator) // it's already know that commaIsDecimalSeparator - continue; - else if (!dotIsDecimalSeparator) // if there is no conflict with dot as decimal separator - commaIsDecimalSeparator = true; - else { - if (txt.count(',') > 1) // follwing case 1,234,567 so OK - continue; - else if (txt.length() - 4 == commaPos) // follwing case 1,234 and somwhere earlier there was 1,234.56 so OK - continue; - else // follwing case 1,23 and somwhere earlier there was 1.23 so unresolvable conflict - return false; - } + int newHeight = table->contentsMargins().top() + + table->contentsMargins().bottom() + + table->verticalHeader()->length() + + table->horizontalHeader()->height() + + (wizard.height() - table->height()); - } else { // follwing case 123 - txt.remove(QRegularExpression("[ " + QRegularExpression::escape(pattern) + ']')); - QRegularExpressionMatch match = re.match(txt); - if (match.hasMatch()) // if string is pure numerical then go forward... - continue; - else // ...if not then it's non-numerical garbage - return false; - } - } - - if (dotIsDecimalSeparator) - symbol = 0; - else if (commaIsDecimalSeparator) - symbol = 1; - else { // whole column was empty, but we don't want to fail so take os decimal symbol - if (QLocale().decimalPoint() == '.') - symbol = 0; + if (table->horizontalScrollBar()->isEnabled()) { + if (!table->horizontalScrollBar()->isVisible() && // horizontal scrollbar may be not visible after repaint... + table->verticalScrollBar()->isVisible()) // ...so use vertical scrollbar dimension + newHeight += table->verticalScrollBar()->width(); else - symbol = 1; + newHeight += table->horizontalScrollBar()->height(); } - return true; -} - -int CSVWizard::getMaxColumnCount(QStringList &lineList, int &delimiter) -{ - if (lineList.isEmpty()) - return 0; - - QList delimiterIndexes; - if (delimiter == -1) - delimiterIndexes = QList{0, 1, 2, 3}; // include all delimiters to test or ... - else - delimiterIndexes = QList{delimiter}; // ... only the one specified - int totalDelimiterCount[4] = {0}; // Total in file for each delimiter - int thisDelimiterCount[4] = {0}; // Total in this line for each delimiter - int colCount = 0; // Total delimiters in this line - int possibleDelimiter = 0; - int maxColumnCount = 0; + // limit wizard size to screen size + if (newHeight > screen.height()) + newHeight = screen.height(); - for (int i = 0; i < lineList.count(); i++) { - QString data = lineList[i]; + if (newWidth > screen.width()) + newWidth = screen.width(); - for (QList::ConstIterator it = delimiterIndexes.constBegin(); it != delimiterIndexes.constEnd(); it++) { - m_parse->setFieldDelimiterIndex(*it); - colCount = m_parse->parseLine(data).count(); // parse each line using each delimiter - if (colCount > thisDelimiterCount[*it]) - thisDelimiterCount[*it] = colCount; + // don't shrink wizard if required size is less than initial + if (newWidth < this->m_initialWidth) + newWidth = this->m_initialWidth; + if (newHeight < this->m_initialHeight) + newHeight = this->m_initialHeight; - if (thisDelimiterCount[*it] > maxColumnCount) - maxColumnCount = thisDelimiterCount[*it]; + newWidth -= (wizard.width() - this->geometry().width()); // remove window frame + newHeight -= (wizard.height() - this->geometry().height()); - totalDelimiterCount[*it] += colCount; - if (totalDelimiterCount[*it] > totalDelimiterCount[possibleDelimiter]) - possibleDelimiter = *it; - } - } - delimiter = possibleDelimiter; - m_parse->setFieldDelimiterIndex(delimiter); - return maxColumnCount; + wizard.setWidth(newWidth); + wizard.setHeight(newHeight); + wizard.moveTo((screen.width() - wizard.width()) / 2, + (screen.height() - wizard.height()) / 2); + this->setGeometry(wizard); } -bool CSVWizard::getInFileName(QString& inFileName) +void CSVWizard::closeEvent(QCloseEvent *event) { - if (inFileName.isEmpty()) - inFileName = QDir::homePath(); - - if(inFileName.startsWith("~/")) //expand Linux home directory - inFileName.replace(0, 1, QDir::home().absolutePath()); - - QFileInfo fileInfo = QFileInfo(inFileName); - if (fileInfo.isFile()) { // if it is file... - if (fileInfo.exists()) // ...and exists... - return true; // ...then all is OK... - else { // ...but if not... - fileInfo.setFile(fileInfo.absolutePath()); //...then set start directory to directory of that file... - if (!fileInfo.exists()) //...and if it doesn't exist too... - fileInfo.setFile(QDir::homePath()); //...then set start directory to home path - } - } + this->m_plugin->m_action->setEnabled(true); // reenable File->Import->CSV + event->accept(); +} - QPointer dialog = new QFileDialog(this, QString(), - fileInfo.absoluteFilePath(), - i18n("CSV Files (*.csv)")); - dialog->setOption(QFileDialog::DontUseNativeDialog, true); //otherwise we cannot add custom QComboBox - dialog->setFileMode(QFileDialog::ExistingFile); - QPointer label = new QLabel(i18n("Encoding")); - dialog->layout()->addWidget(label); - // Add encoding selection to FileDialog - QPointer comboBoxEncode = new QComboBox(); - setCodecList(m_codecs, comboBoxEncode); - comboBoxEncode->setCurrentIndex(m_encodeIndex); - connect(comboBoxEncode, SIGNAL(activated(int)), this, SLOT(encodingChanged(int))); - dialog->layout()->addWidget(comboBoxEncode); - QUrl url; - if (dialog->exec() == QDialog::Accepted) - url = dialog->selectedUrls().first(); - else - url.clear(); - delete dialog; - - if (url.isEmpty()) - return false; - else if (url.isLocalFile()) - inFileName = url.toLocalFile(); - else { - inFileName = QDir::tempPath(); - if (!inFileName.endsWith(QDir::separator())) - inFileName += QDir::separator(); - inFileName += url.fileName(); - qDebug() << "Source:" << url.toDisplayString() << "Destination:" << inFileName; - KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromUserInput(inFileName), - -1, KIO::Overwrite); - KJobWidgets::setWindow(job, this); - job->exec(); - if (job->error()) { - KMessageBox::detailedError(this, - i18n("Error while loading file '%1'.", url.toDisplayString()), - job->errorString(), - i18n("File access error")); - return false; +bool CSVWizard::eventFilter(QObject *object, QEvent *event) +{ + // prevent the QWizard part of CSVWizard window from closing on Escape key press + if (object == this->m_wiz) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + close(); + return true; + } } } - - if (inFileName.isEmpty()) - return false; - return true; + return false; } -void CSVWizard::encodingChanged(int index) +void CSVWizard::slotClose() { - m_encodeIndex = index; + m_imp->m_profile->m_lastUsedDirectory = m_imp->m_file->m_inFileName; + m_imp->m_profile->writeSettings(CSVImporter::configFile()); + m_imp->profilesAction(m_imp->m_profile->type(), ProfilesUpdateLastUsed, m_imp->m_profile->m_profileName, m_imp->m_profile->m_profileName); + close(); } -void CSVWizard::readFile(const QString& fname) +void CSVWizard::fileDialogClicked() { - if (!fname.isEmpty()) - m_inFileName = fname; - - m_importError = false; + m_imp->profileFactory(m_pageIntro->m_profileType, m_pageIntro->ui->m_profiles->currentText()); + bool profileExists = m_imp->m_profile->readSettings(CSVImporter::configFile()); - QFile inFile(m_inFileName); - inFile.open(QIODevice::ReadOnly); // allow a Carriage return -// QIODevice::Text - QTextStream inStream(&inFile); - QTextCodec* codec = QTextCodec::codecForMib(m_codecs.value(m_encodeIndex)->mibEnum()); - inStream.setCodec(codec); + if (!m_imp->m_file->getInFileName(m_imp->m_profile->m_lastUsedDirectory)) + return; - QString buf = inStream.readAll(); - inFile.close(); - m_lineList = m_parse->parseFile(buf, 1, 0); // parse the buffer - m_fileEndLine = m_parse->lastLine(); // won't work without above line + saveWindowSize(CSVImporter::configFile()); + m_imp->m_file->readFile(m_imp->m_profile); + m_imp->m_file->setupParser(m_imp->m_profile); - if (m_fileEndLine > m_trailerLines) - m_endLine = m_fileEndLine - m_trailerLines; - else - m_endLine = m_fileEndLine; // Ignore m_trailerLines as > file length. + m_skipSetup = m_pageIntro->ui->m_skipSetup->isChecked(); - if (m_startLine > m_endLine) // Don't allow m_startLine > m_endLine - m_startLine = m_endLine; -} - -void CSVWizard::displayLines(const QStringList &lineList, Parse* parse) -{ - ui->tableWidget->clear(); - m_row = 0; - QFont font(QApplication::font()); - ui->tableWidget->setFont(font); - ui->tableWidget->setRowCount(lineList.count()); - ui->tableWidget->setColumnCount(m_maxColumnCount); - - for (int line = 0; line < lineList.count(); ++line) { - QStringList columnList = parse->parseLine(lineList[line]); - for (int col = 0; col < columnList.count(); ++col) { - QTableWidgetItem *item = new QTableWidgetItem; // new item for tableWidget - item->setText(columnList[col]); - ui->tableWidget->setItem(m_row, col, item); // add item to tableWidget - } - if (columnList.count() < m_maxColumnCount) { // if 'header' area has less columns than 'data' area, then fill this area with whitespaces for nice effect with markUnwantedRows - for (int col = columnList.count(); col < m_maxColumnCount; ++col) { - QTableWidgetItem *item = new QTableWidgetItem; // new item for tableWidget - item->setText(""); - ui->tableWidget->setItem(m_row, col, item); // add item to tableWidget + switch(m_imp->m_profile->type()) { + case ProfileInvest: + if (!m_pageInvestment) { + m_pageInvestment = new InvestmentPage(this, m_imp); + m_wiz->setPage(CSVWizard::PageInvestment, m_pageInvestment); } - } - m_row ++; + break; + case ProfileBank: + if (!m_pageBanking) { + m_pageBanking = new BankingPage(this, m_imp); + m_wiz->setPage(CSVWizard::PageBanking, m_pageBanking); + } + break; + case ProfileStockPrices: + case ProfileCurrencyPrices: + if (!m_pagePrices) { + m_pagePrices = new PricesPage(this, m_imp); + m_wiz->setPage(CSVWizard::PagePrices, m_pagePrices); + } + break; + default: + return; } - for (int col = 0; col < ui->tableWidget->columnCount(); col ++) - ui->tableWidget->resizeColumnToContents(col); + m_wiz->next(); //go to separator page + + if (m_skipSetup && profileExists) + for (int i = 0; i < 4; i++) //programmaticaly go through separator-, rows-, investment-/bank-, formatspage + m_wiz->next(); } -void CSVWizard::updateWindowSize() +void CSVWizard::importClicked() { - QTableWidget *table = this->ui->tableWidget; - table->resizeColumnsToContents(); - layout()->invalidate(); - layout()->activate(); - - QRect screen = QApplication::desktop()->availableGeometry(); //get available screen size - QRect wizard = this->frameGeometry(); //get current wizard size - - int newWidth = table->verticalHeader()->width() + //take header, margins nad scrollbar into account - table->contentsMargins().left() + - table->contentsMargins().right(); - if (table->verticalScrollBar()->isEnabled()) - newWidth += table->verticalScrollBar()->width(); - for(int i = 0; i < table->columnCount(); ++i) - newWidth += table->columnWidth(i); //add up required column widths - - int newHeight = table->horizontalHeader()->height() + - table->horizontalScrollBar()->height() + - table->contentsMargins().top() + - table->contentsMargins().bottom(); - if (table->horizontalScrollBar()->isEnabled()) - newHeight += table->horizontalScrollBar()->height(); - - if( this->ui->tableWidget->rowCount() > 0) - newHeight += this->ui->tableWidget->rowCount() * table->rowHeight(0); //add up estimated row heights - - newWidth = wizard.width() + (newWidth - table->width()); - newHeight = wizard.height() + (newHeight - table->height()); - - if (newWidth > screen.width()) //limit wizard size to screen size - newWidth = screen.width(); - if (newHeight > screen.height()) - newHeight = screen.height(); - - if (newWidth < this->m_initialWidth) //don't shrink wizard if required size is less than initial - newWidth = this->m_initialWidth; - if (newHeight < this->m_initialHeight) - newHeight = this->m_initialHeight; - - newWidth -= (wizard.width() - this->geometry().width()); // remove window frame - newHeight -= (wizard.height() - this->geometry().height()); + switch (m_imp->m_profile->type()) { + case ProfileBank: + if (!m_pageBanking->validateCreditDebit()) + return; + break; + case ProfileInvest: + if (!m_pageInvestment->validateActionType()) + return; + break; + default: + break; + } - wizard.setWidth(newWidth); - wizard.setHeight(newHeight); - wizard.moveTo((screen.width() - wizard.width()) / 2, - (screen.height() - wizard.height()) / 2); - this->setGeometry(wizard); + if (!m_imp->createStatement(m_st)) + return; + slotClose(); + emit m_imp->statementReady(m_st); } -void CSVWizard::slotFileDialogClicked() +void CSVWizard::saveAsQIFClicked() { - saveWindowSize(m_config); - if (!m_pageInvestment.isNull()) - m_wizard->removePage(PageInvestment); - delete m_pageInvestment; - if (!m_pageBanking.isNull()) - m_wizard->removePage(PageBanking); - delete m_pageBanking; - - m_profileName = m_pageIntro->ui->combobox_source->currentText(); - m_skipSetup = m_pageIntro->ui->checkBoxSkipSetup->isChecked(); - m_accept = false; - m_acceptAllInvalid = false; // Don't accept further invalid values. - m_memoColList.clear(); - - if (m_profileType == CSVWizard::ProfileInvest) { - m_pageInvestment = new InvestmentPage; - m_wizard->setPage(PageInvestment, m_pageInvestment); - m_pageInvestment->setParent(this); - m_pageInvestment->readSettings(m_config); - } - else if (m_profileType == CSVWizard::ProfileBank) { - m_pageBanking = new BankingPage; - m_wizard->setPage(PageBanking, m_pageBanking); - m_pageBanking->setParent(this); - m_pageBanking->readSettings(m_config); - } - else if (m_profileType == CSVWizard::ProfileStockPrices || - m_profileType == CSVWizard::ProfileCurrencyPrices) { - m_pagePrices = new PricesPage; - m_wizard->setPage(PagePrices, m_pagePrices); - m_pagePrices->setParent(this); - m_pagePrices->readSettings(m_config); + switch (m_imp->m_profile->type()) { + case ProfileBank: + if (!m_pageBanking->validateCreditDebit()) + return; + break; + case ProfileInvest: + if (!m_pageInvestment->validateActionType()) + return; + break; + default: + break; } - - if (!getInFileName(m_inFileName)) + bool isOK = m_imp->createStatement(m_st); + if (!isOK || m_st.m_listTransactions.isEmpty()) return; - readFile(m_inFileName); - m_wizard->next(); //go to separator page - - if (m_skipSetup) - for (int i = 0; i < 4; i++) //programmaticaly go through separator-, rows-, investment-/bank-, formatspage - m_wizard->next(); -} - -void CSVWizard::resizeEvent(QResizeEvent* ev) -{ - if (ev->spontaneous()) { - ev->ignore(); + QString outFileName = m_imp->m_file->m_inFileName; + outFileName.truncate(outFileName.lastIndexOf('.')); + outFileName.append(QLatin1String(".qif")); + outFileName = QFileDialog::getSaveFileName(this, i18n("Save QIF"), outFileName, i18n("QIF Files (*.qif)")); + if (outFileName.isEmpty()) return; + QFile oFile(outFileName); + oFile.open(QIODevice::WriteOnly); + switch (m_imp->m_profile->type()) { + case ProfileBank: + m_pageBanking->makeQIF(m_st, oFile); + break; + case ProfileInvest: + m_pageInvestment->makeQIF(m_st, oFile); + break; + default: + break; } -} - -void CSVWizard::slotClose() -{ - if (m_profileType == ProfileBank) - m_pageBanking->saveSettings(); - else if (m_profileType == ProfileInvest) - m_pageInvestment->saveSettings(); - else if (m_profileType == ProfileStockPrices || - m_profileType == ProfileCurrencyPrices) - m_pagePrices->saveSettings(); - close(); + oFile.close(); } //------------------------------------------------------------------------------------------------------- -IntroPage::IntroPage(QDialog *parent) : - CSVWizardPage(parent), - ui(new Ui::IntroPage), - m_pageLayout(0) +IntroPage::IntroPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), + ui(new Ui::IntroPage), + m_pageLayout(0) { ui->setupUi(this); } @@ -954,227 +425,212 @@ delete ui; } -void IntroPage::setParent(CSVWizard* dlg) +int IntroPage::nextId() const { - CSVWizardPage::setParent(dlg); - m_wizDlg->showStage(); + return CSVWizard::PageSeparator; +} + +void IntroPage::initializePage() +{ + m_imp->m_file->m_model->clear(); + + wizard()->setButtonText(QWizard::CustomButton1, i18n("Select File")); + wizard()->button(QWizard::CustomButton1)->setToolTip(i18n("A profile must be selected before selecting a file.")); + QList layout; + layout << QWizard::Stretch << + QWizard::CustomButton1 << + QWizard::CancelButton; + wizard()->setButtonLayout(layout); + + ui->m_profiles->lineEdit()->setClearButtonEnabled(true); + + connect(ui->m_profiles, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboSourceIndexChanged(int))); + connect(ui->m_add, SIGNAL(clicked()), this, SLOT(slotAddProfile())); + connect(ui->m_remove, SIGNAL(clicked()), this, SLOT(slotRemoveProfile())); + connect(ui->m_rename, SIGNAL(clicked()), this, SLOT(slotRenameProfile())); + connect(ui->m_profilesBank, SIGNAL(toggled(bool)), this, SLOT(slotBankRadioToggled(bool))); + connect(ui->m_profilesInvest, SIGNAL(toggled(bool)), this, SLOT(slotInvestRadioToggled(bool))); + connect(ui->m_profilesCurrencyPrices, SIGNAL(toggled(bool)), this, SLOT(slotCurrencyPricesRadioToggled(bool))); + connect(ui->m_profilesStockPrices, SIGNAL(toggled(bool)), this, SLOT(slotStockPricesRadioToggled(bool))); + if (m_dlg->m_initialHeight == -1 || m_dlg->m_initialWidth == -1) { + m_dlg->m_initialHeight = m_dlg->geometry().height(); + m_dlg->m_initialWidth = m_dlg->geometry().width(); + } else { + //resize wizard to its initial size and center it + m_dlg->setGeometry( + QStyle::alignedRect( + Qt::LeftToRight, + Qt::AlignCenter, + QSize(m_dlg->m_initialWidth, m_dlg->m_initialHeight), + QApplication::desktop()->availableGeometry() + ) + ); + } +} - wizard()->button(QWizard::CustomButton1)->setEnabled(false); +bool IntroPage::validatePage() +{ + return true; } void IntroPage::slotAddProfile() { - profileChanged(ProfileAdd); + profileChanged(ProfilesAdd); } void IntroPage::slotRemoveProfile() { - profileChanged(ProfileRemove); + profileChanged(ProfilesRemove); } void IntroPage::slotRenameProfile() { - profileChanged(ProfileRename); + profileChanged(ProfilesRename); } -void IntroPage::profileChanged(const profileActionsE& action) +void IntroPage::profileChanged(const profilesActionE action) { - int cbIndex = ui->combobox_source->currentIndex(); - QString cbText = ui->combobox_source->currentText(); + QString cbText = ui->m_profiles->currentText(); if (cbText.isEmpty()) // you cannot neither add nor remove empty name profile or rename to empty name return; - QString profileTypeStr; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - profileTypeStr = "Bank"; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - profileTypeStr = "Invest"; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices) - profileTypeStr = "SPrices"; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) - profileTypeStr = "CPrices"; - - KConfigGroup profileNamesGroup(m_wizDlg->m_config, "ProfileNames"); - KConfigGroup currentProfileName(m_wizDlg->m_config, profileTypeStr + '-' + cbText); - if (action == ProfileRemove) { - if (m_wizDlg->m_profileList.value(cbIndex) != cbText) - return; - m_wizDlg->m_profileList.removeAt(cbIndex); - currentProfileName.deleteGroup(); - ui->combobox_source->removeItem(cbIndex); - KMessageBox::information(m_wizDlg->m_wizard, - i18n("
Profile %1 has been removed.
", - cbText)); - } + int cbIndex = ui->m_profiles->currentIndex(); - if (action == ProfileAdd || action == ProfileRename) { - int dupIndex = m_wizDlg->m_profileList.indexOf(cbText, Qt::CaseInsensitive); - if (dupIndex == cbIndex && cbIndex != -1) // if profile name wasn't changed then return - return; - else if (dupIndex != -1) { // profile with the same name already exists - ui->combobox_source->setItemText(cbIndex, m_wizDlg->m_profileList.value(cbIndex)); - KMessageBox::information(m_wizDlg->m_wizard, - i18n("
Profile %1 already exists.
" - "Please enter another name
", cbText)); - return; + switch (action) { + case ProfilesRename: + case ProfilesAdd: + { + int dupIndex = m_profiles.indexOf(QRegularExpression (cbText)); + if (dupIndex == cbIndex && cbIndex != -1) // if profile name wasn't changed then return + return; + else if (dupIndex != -1) { // profile with the same name already exists + ui->m_profiles->setItemText(cbIndex, m_profiles.value(cbIndex)); + KMessageBox::information(m_dlg, + i18n("
Profile %1 already exists.
" + "Please enter another name
", cbText)); + return; + } + break; } + case ProfilesRemove: + if (m_profiles.value(cbIndex) != cbText) // user changed name of the profile and tries to remove it + return; + break; + default: + break; + } - if (action == ProfileAdd) { - m_wizDlg->m_profileList.append(cbText); - ui->combobox_source->addItem(cbText); - ui->combobox_source->setCurrentIndex(m_wizDlg->m_profileList.count() - 1); - currentProfileName.writeEntry("Directory", QString()); - KMessageBox::information(m_wizDlg->m_wizard, - i18n("
Profile %1 has been added.
", cbText)); - } else if (action == ProfileRename) { - KConfigGroup oldProfileName(m_wizDlg->m_config, profileTypeStr + '-' + m_wizDlg->m_profileList.value(cbIndex)); - oldProfileName.copyTo(¤tProfileName); - oldProfileName.deleteGroup(); - ui->combobox_source->setItemText(cbIndex, cbText); - oldProfileName.sync(); - KMessageBox::information(m_wizDlg->m_wizard, - i18n("
Profile name has been renamed from %1 to %2.
", - m_wizDlg->m_profileList.value(cbIndex), cbText)); - m_wizDlg->m_profileList[cbIndex] = cbText; + if (CSVImporter::profilesAction(m_profileType, action, m_profiles.value(cbIndex), cbText)) { + switch (action) { + case ProfilesAdd: + m_profiles.append(cbText); + ui->m_profiles->addItem(cbText); + ui->m_profiles->setCurrentIndex(m_profiles.count() - 1); + KMessageBox::information(m_dlg, + i18n("
Profile %1 has been added.
", cbText)); + break; + case ProfilesRemove: + m_profiles.removeAt(cbIndex); + ui->m_profiles->removeItem(cbIndex); + KMessageBox::information(m_dlg, + i18n("
Profile %1 has been removed.
", + cbText)); + break; + case ProfilesRename: + ui->m_profiles->setItemText(cbIndex, cbText); + KMessageBox::information(m_dlg, + i18n("
Profile name has been renamed from %1 to %2.
", + m_profiles.value(cbIndex), cbText)); + m_profiles[cbIndex] = cbText; + break; + default: + break; } } - currentProfileName.sync(); - profileNamesGroup.writeEntry(profileTypeStr, m_wizDlg->m_profileList); // update profiles list - profileNamesGroup.sync(); } void IntroPage::slotComboSourceIndexChanged(int idx) { if (idx == -1) { wizard()->button(QWizard::CustomButton1)->setEnabled(false); - ui->checkBoxSkipSetup->setEnabled(false); - ui->buttonRemove->setEnabled(false); - ui->buttonRename->setEnabled(false); + ui->m_skipSetup->setEnabled(false); + ui->m_remove->setEnabled(false); + ui->m_rename->setEnabled(false); } else { wizard()->button(QWizard::CustomButton1)->setEnabled(true); - ui->checkBoxSkipSetup->setEnabled(true); - ui->buttonRemove->setEnabled(true); - ui->buttonRename->setEnabled(true); + ui->m_skipSetup->setEnabled(true); + ui->m_remove->setEnabled(true); + ui->m_rename->setEnabled(true); } } -void IntroPage::profileTypeChanged(const CSVWizard::profileTypeE profileType, bool toggled) +void IntroPage::profileTypeChanged(const profileTypeE profileType, bool toggled) { if (!toggled) return; - KConfigGroup profilesGroup(m_wizDlg->m_config, "ProfileNames"); - m_wizDlg->m_profileType = profileType; + KConfigGroup profilesGroup(CSVImporter::configFile(), CSVImporter::m_confProfileNames); + m_profileType = profileType; QString profileTypeStr; - int priorProfile; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { - ui->radioButton_invest->setChecked(false); - ui->radioButton_stockprices->setChecked(false); - ui->radioButton_currencyprices->setChecked(false); - profileTypeStr = "Bank"; - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { - ui->radioButton_bank->setChecked(false); - ui->radioButton_stockprices->setChecked(false); - ui->radioButton_currencyprices->setChecked(false); - profileTypeStr = "Invest"; - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices) { - ui->radioButton_bank->setChecked(false); - ui->radioButton_invest->setChecked(false); - ui->radioButton_currencyprices->setChecked(false); - profileTypeStr = "SPrices"; - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { - ui->radioButton_bank->setChecked(false); - ui->radioButton_invest->setChecked(false); - ui->radioButton_stockprices->setChecked(false); - profileTypeStr = "CPrices"; + switch (m_profileType) { + case ProfileBank: + ui->m_profilesInvest->setChecked(false); + ui->m_profilesStockPrices->setChecked(false); + ui->m_profilesCurrencyPrices->setChecked(false); + break; + case ProfileInvest: + ui->m_profilesBank->setChecked(false); + ui->m_profilesStockPrices->setChecked(false); + ui->m_profilesCurrencyPrices->setChecked(false); + break; + case ProfileStockPrices: + ui->m_profilesBank->setChecked(false); + ui->m_profilesInvest->setChecked(false); + ui->m_profilesCurrencyPrices->setChecked(false); + break; + case ProfileCurrencyPrices: + ui->m_profilesBank->setChecked(false); + ui->m_profilesInvest->setChecked(false); + ui->m_profilesStockPrices->setChecked(false); + break; + default: + break; } + profileTypeStr = CSVImporter::m_profileConfPrefix.value(m_profileType); - m_wizDlg->m_profileList = profilesGroup.readEntry(profileTypeStr, QStringList()); - priorProfile = profilesGroup.readEntry("Prior" + profileTypeStr, 0); - ui->combobox_source->clear(); - ui->combobox_source->addItems(m_wizDlg->m_profileList); - ui->combobox_source->setCurrentIndex(priorProfile); - ui->combobox_source->setEnabled(true); - ui->buttonAdd->setEnabled(true); + m_profiles = profilesGroup.readEntry(profileTypeStr, QStringList()); + int priorProfile = profilesGroup.readEntry(CSVImporter::m_confPriorName + profileTypeStr, 0); + ui->m_profiles->clear(); + ui->m_profiles->addItems(m_profiles); + ui->m_profiles->setCurrentIndex(priorProfile); + ui->m_profiles->setEnabled(true); + ui->m_add->setEnabled(true); } void IntroPage::slotBankRadioToggled(bool toggled) { - profileTypeChanged(CSVWizard::ProfileBank, toggled); + profileTypeChanged(ProfileBank, toggled); } void IntroPage::slotInvestRadioToggled(bool toggled) { - profileTypeChanged(CSVWizard::ProfileInvest, toggled); + profileTypeChanged(ProfileInvest, toggled); } void IntroPage::slotCurrencyPricesRadioToggled(bool toggled) { - profileTypeChanged(CSVWizard::ProfileCurrencyPrices, toggled); + profileTypeChanged(ProfileCurrencyPrices, toggled); } void IntroPage::slotStockPricesRadioToggled(bool toggled) { - profileTypeChanged(CSVWizard::ProfileStockPrices, toggled); + profileTypeChanged(ProfileStockPrices, toggled); } -void IntroPage::initializePage() -{ - m_wizDlg->ui->tableWidget->clear(); - m_wizDlg->ui->tableWidget->setColumnCount(0); - m_wizDlg->ui->tableWidget->setRowCount(0); - m_wizDlg->ui->tableWidget->verticalScrollBar()->setValue(0); - m_wizDlg->ui->tableWidget->horizontalScrollBar()->setValue(0); - - wizard()->setButtonText(QWizard::CustomButton1, i18n("Select File")); - wizard()->button(QWizard::CustomButton1)->setToolTip(i18n("A profile must be selected before selecting a file.")); - QList layout; - layout << QWizard::Stretch << - QWizard::CustomButton1 << - QWizard::CancelButton; - wizard()->setButtonLayout(layout); - - m_wizDlg->m_importError = false; - ui->combobox_source->lineEdit()->setClearButtonEnabled(true); - - connect(ui->combobox_source, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboSourceIndexChanged(int))); - connect(ui->buttonAdd, SIGNAL(clicked()), this, SLOT(slotAddProfile())); - connect(ui->buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveProfile())); - connect(ui->buttonRename, SIGNAL(clicked()), this, SLOT(slotRenameProfile())); - connect(ui->radioButton_bank, SIGNAL(toggled(bool)), this, SLOT(slotBankRadioToggled(bool))); - connect(ui->radioButton_invest, SIGNAL(toggled(bool)), this, SLOT(slotInvestRadioToggled(bool))); - connect(ui->radioButton_currencyprices, SIGNAL(toggled(bool)), this, SLOT(slotCurrencyPricesRadioToggled(bool))); - connect(ui->radioButton_stockprices, SIGNAL(toggled(bool)), this, SLOT(slotStockPricesRadioToggled(bool))); - if (m_wizDlg->m_initialHeight == -1 || m_wizDlg->m_initialWidth == -1) { - m_wizDlg->m_initialHeight = m_wizDlg->geometry().height(); - m_wizDlg->m_initialWidth = m_wizDlg->geometry().width(); - } else { - //resize wizard to its initial size and center it - m_wizDlg->setGeometry( - QStyle::alignedRect( - Qt::LeftToRight, - Qt::AlignCenter, - QSize(m_wizDlg->m_initialWidth, m_wizDlg->m_initialHeight), - QApplication::desktop()->availableGeometry() - ) - ); - } -} - -bool IntroPage::validatePage() -{ - return true; -} - -int IntroPage::nextId() const -{ - return CSVWizard::PageSeparator; -} - -SeparatorPage::SeparatorPage(QDialog *parent) : - CSVWizardPage(parent), - ui(new Ui::SeparatorPage) +SeparatorPage::SeparatorPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), + ui(new Ui::SeparatorPage) { ui->setupUi(this); @@ -1189,62 +645,142 @@ void SeparatorPage::initializePage() { - ui->comboBox_fieldDelimiter->setCurrentIndex(m_wizDlg->m_fieldDelimiterIndex); - ui->comboBox_textDelimiter->setCurrentIndex(m_wizDlg->m_textDelimiterIndex); - connect(ui->comboBox_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); - connect(ui->comboBox_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); - emit ui->comboBox_fieldDelimiter->currentIndexChanged(m_wizDlg->m_fieldDelimiterIndex); - emit ui->comboBox_textDelimiter->currentIndexChanged(m_wizDlg->m_textDelimiterIndex); + // comboboxes are preset to -1 and, in new profile case, can be set here to -1 as well ... + initializeEncodingCombobox(); + ui->m_encoding->setCurrentIndex(ui->m_encoding->findData(m_imp->m_profile->m_encodingMIBEnum)); + ui->m_fieldDelimiter->setCurrentIndex(m_imp->m_profile->m_fieldDelimiterIndex); + ui->m_textDelimiter->setCurrentIndex(m_imp->m_profile->m_textDelimiterIndex); + + // ... so connect their signals after setting them ... + connect(ui->m_encoding, SIGNAL(currentIndexChanged(int)), this, SLOT(encodingChanged(int))); + connect(ui->m_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); + connect(ui->m_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); + + // ... and ensure that their signal receivers will always be called + emit ui->m_encoding->currentIndexChanged(ui->m_encoding->currentIndex()); + emit ui->m_fieldDelimiter->currentIndexChanged(ui->m_fieldDelimiter->currentIndex()); + emit ui->m_textDelimiter->currentIndexChanged(ui->m_textDelimiter->currentIndex()); + m_dlg->updateWindowSize(); QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } -void SeparatorPage::textDelimiterChanged(const int index) +void SeparatorPage::initializeEncodingCombobox() { - if (index < 0) - ui->comboBox_textDelimiter->setCurrentIndex(0); // for now there is no better idea how to detect textDelimiter + ui->m_encoding->blockSignals(true); + ui->m_encoding->clear(); + + QList codecs; + QMap codecMap; + QRegExp iso8859RegExp(QLatin1Literal("ISO[- ]8859-([0-9]+).*")); + + foreach (const auto mib, QTextCodec::availableMibs()) { + QTextCodec *codec = QTextCodec::codecForMib(mib); + + QString sortKey = codec->name().toUpper(); + int rank; + + if (sortKey.startsWith(QLatin1String("UTF-8"))) { // krazy:exclude=strings + rank = 1; + } else if (sortKey.startsWith(QLatin1String("UTF-16"))) { // krazy:exclude=strings + rank = 2; + } else if (iso8859RegExp.exactMatch(sortKey)) { + if (iso8859RegExp.cap(1).size() == 1) + rank = 3; + else + rank = 4; + } else { + rank = 5; + } + sortKey.prepend(QChar('0' + rank)); + + codecMap.insert(sortKey, codec); + } + codecs = codecMap.values(); - m_wizDlg->m_textDelimiterIndex = index; - m_wizDlg->m_parse->setTextDelimiterIndex(index); - m_wizDlg->m_parse->setTextDelimiterCharacter(index); - m_wizDlg->m_textDelimiterCharacter = m_wizDlg->m_parse->textDelimiterCharacter(index); + foreach (const auto codec, codecs) + ui->m_encoding->addItem(codec->name(), codec->mibEnum()); + ui->m_encoding->blockSignals(false); +} + +void SeparatorPage::encodingChanged(const int index) +{ + if (index == -1) { + ui->m_encoding->setCurrentIndex(ui->m_encoding->findText("UTF-8")); + return; + } else if (index == ui->m_encoding->findData(m_imp->m_profile->m_encodingMIBEnum)) + return; + m_imp->m_profile->m_encodingMIBEnum = ui->m_encoding->currentData().toInt(); + m_imp->m_file->readFile(m_imp->m_profile); + emit completeChanged(); } void SeparatorPage::fieldDelimiterChanged(const int index) { - if (index == -1 && !m_wizDlg->m_autodetect.value(CSVWizard::AutoFieldDelimiter)) + if (index == -1 && // if field delimiter isn't set... + !m_imp->m_autodetect.value(AutoFieldDelimiter)) // ... and user disabled autodetecting... + return; // ... then wait for him to choose + else if (index == m_imp->m_profile->m_fieldDelimiterIndex) return; - m_wizDlg->m_fieldDelimiterIndex = index; - m_wizDlg->m_maxColumnCount = m_wizDlg->getMaxColumnCount(m_wizDlg->m_lineList, m_wizDlg->m_fieldDelimiterIndex); // get column count, we get with this fieldDelimiter - m_wizDlg->m_endColumn = m_wizDlg->m_maxColumnCount; - m_wizDlg->m_parse->setFieldDelimiterIndex(m_wizDlg->m_fieldDelimiterIndex); - m_wizDlg->m_parse->setFieldDelimiterCharacter(m_wizDlg->m_fieldDelimiterIndex); - m_wizDlg->m_fieldDelimiterCharacter = m_wizDlg->m_parse->fieldDelimiterCharacter(m_wizDlg->m_fieldDelimiterIndex); + + m_imp->m_profile->m_fieldDelimiterIndex = index; + m_imp->m_file->readFile(m_imp->m_profile); // get column count, we get with this fieldDelimiter + m_imp->m_file->setupParser(m_imp->m_profile); + if (index == -1) { - ui->comboBox_fieldDelimiter->blockSignals(true); - ui->comboBox_fieldDelimiter->setCurrentIndex(m_wizDlg->m_fieldDelimiterIndex); - ui->comboBox_fieldDelimiter->blockSignals(false); + ui->m_fieldDelimiter->blockSignals(true); + ui->m_fieldDelimiter->setCurrentIndex(m_imp->m_profile->m_fieldDelimiterIndex); + ui->m_fieldDelimiter->blockSignals(false); + } + m_dlg->updateWindowSize(); + emit completeChanged(); +} + +void SeparatorPage::textDelimiterChanged(const int index) +{ + if (index == -1) { // if text delimiter isn't set... + ui->m_textDelimiter->setCurrentIndex(0); // ...then set it to 0, as for now there is no better idea how to detect it + return; + } + + m_imp->m_profile->m_textDelimiterIndex = index; + m_imp->m_file->setupParser(m_imp->m_profile); + + if (index == -1) { + ui->m_textDelimiter->blockSignals(true); + ui->m_textDelimiter->setCurrentIndex(m_imp->m_profile->m_textDelimiterIndex); + ui->m_textDelimiter->blockSignals(false); } - m_wizDlg->displayLines(m_wizDlg->m_lineList, m_wizDlg->m_parse); // refresh tableWidget with new fieldDelimiter set - m_wizDlg->updateWindowSize(); emit completeChanged(); } bool SeparatorPage::isComplete() const { - if (ui->comboBox_fieldDelimiter->currentIndex() != -1) { - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank && m_wizDlg->m_endColumn > 2) - return true; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest && m_wizDlg->m_endColumn > 3) - return true; - else if ((m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) && - m_wizDlg->m_endColumn > 1) - return true; + bool rc = false; + if (ui->m_encoding->currentIndex() != -1 && + ui->m_fieldDelimiter->currentIndex() != -1 && + ui->m_textDelimiter->currentIndex() != -1) { + switch(m_imp->m_profile->type()) { + case ProfileBank: + if (m_imp->m_file->m_columnCount > 2) + rc = true; + break; + case ProfileInvest: + if (m_imp->m_file->m_columnCount > 3) + rc = true; + break; + case ProfileCurrencyPrices: + case ProfileStockPrices: + if (m_imp->m_file->m_columnCount > 1) + rc = true; + break; + default: + break; + } } - return false; + return rc; } bool SeparatorPage::validatePage() @@ -1256,13 +792,13 @@ { // On completion with error force use of 'Back' button. // ...to allow resetting of 'Skip setup' - disconnect(ui->comboBox_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); - disconnect(ui->comboBox_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); - m_wizDlg->m_pageIntro->initializePage(); // Need to show button(QWizard::CustomButton1) not 'NextButton' + disconnect(ui->m_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); + disconnect(ui->m_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); + m_dlg->m_pageIntro->initializePage(); // Need to show button(QWizard::CustomButton1) not 'NextButton' } -RowsPage::RowsPage(QDialog *parent) : - CSVWizardPage(parent), +RowsPage::RowsPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), ui(new Ui::RowsPage) { ui->setupUi(this); @@ -1277,19 +813,19 @@ void RowsPage::initializePage() { - disconnect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); - disconnect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); +// disconnect(ui->m_startLine, &QSpinBox::valueChanged, this, &RowsPage::startRowChanged); +// disconnect(ui->m_endLine, &QSpinBox::valueChanged, this, &RowsPage::endRowChanged); - ui->spinBox_skip->setMaximum(m_wizDlg->m_fileEndLine); - ui->spinBox_skipToLast->setMaximum(m_wizDlg->m_fileEndLine); - ui->spinBox_skip->setValue(m_wizDlg->m_startLine); - ui->spinBox_skipToLast->setValue(m_wizDlg->m_endLine); + ui->m_startLine->setMaximum(m_imp->m_file->m_rowCount); + ui->m_endLine->setMaximum(m_imp->m_file->m_rowCount); + ui->m_startLine->setValue(m_imp->m_profile->m_startLine + 1); + ui->m_endLine->setValue(m_imp->m_profile->m_endLine + 1); - m_wizDlg->markUnwantedRows(); - m_wizDlg->m_vScrollBar->setValue(m_wizDlg->m_startLine - 1); + m_dlg->markUnwantedRows(); + m_dlg->m_vScrollBar->setValue(m_imp->m_profile->m_startLine); - connect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); - connect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); + connect(ui->m_startLine, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int)));; + connect(ui->m_endLine, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); QList layout; layout << QWizard::Stretch << @@ -1299,62 +835,72 @@ wizard()->setButtonLayout(layout); } +void RowsPage::cleanupPage() +{ + disconnect(ui->m_startLine, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int)));; + disconnect(ui->m_endLine, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); + m_dlg->clearBackground(); +} + +int RowsPage::nextId() const +{ + int ret; + switch (m_imp->m_profile->type()) { + case ProfileBank: + ret = CSVWizard::PageBanking; + break; + case ProfileInvest: + ret = CSVWizard::PageInvestment; + break; + case ProfileStockPrices: + case ProfileCurrencyPrices: + ret = CSVWizard::PagePrices; + break; + default: + ret = CSVWizard::PageRows; + break; + } + return ret; +} + void RowsPage::startRowChanged(int val) { - if (val > m_wizDlg->m_fileEndLine) { - ui->spinBox_skip->setValue(m_wizDlg->m_fileEndLine); + if (val > m_imp->m_file->m_rowCount) { + ui->m_startLine->setValue(m_imp->m_file->m_rowCount); return; } - if (val > m_wizDlg->m_endLine) { - ui->spinBox_skip->setValue(m_wizDlg->m_endLine); + --val; + if (val > m_imp->m_profile->m_endLine) { + ui->m_startLine->setValue(m_imp->m_profile->m_endLine); return; } - m_wizDlg->m_startLine = val; - if (!m_wizDlg->m_inFileName.isEmpty()) { - m_wizDlg->m_vScrollBar->setValue(m_wizDlg->m_startLine - 1); - m_wizDlg->markUnwantedRows(); - } + m_imp->m_profile->m_startLine = val; + m_dlg->m_vScrollBar->setValue(val); + m_dlg->markUnwantedRows(); } void RowsPage::endRowChanged(int val) { - if (val > m_wizDlg->m_fileEndLine) { - ui->spinBox_skipToLast->setValue(m_wizDlg->m_fileEndLine); + if (val > m_imp->m_file->m_rowCount) { + ui->m_endLine->setValue(m_imp->m_file->m_rowCount); return; } - if (val < m_wizDlg->m_startLine) { - ui->spinBox_skipToLast->setValue(m_wizDlg->m_startLine); + --val; + if (val < m_imp->m_profile->m_startLine) { + ui->m_endLine->setValue(m_imp->m_profile->m_startLine); return; } - m_wizDlg->m_trailerLines = m_wizDlg->m_fileEndLine - val; - m_wizDlg->m_endLine = val; - if (!m_wizDlg->m_inFileName.isEmpty()) { - m_wizDlg->markUnwantedRows(); - } + m_imp->m_profile->m_trailerLines = m_imp->m_file->m_rowCount - val; + m_imp->m_profile->m_endLine = val; + m_dlg->markUnwantedRows(); } -void RowsPage::cleanupPage() -{ - disconnect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); - disconnect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); - m_wizDlg->clearBackground(); -} -int RowsPage::nextId() const -{ - int ret = CSVWizard::PageBanking; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - ret = CSVWizard::PageBanking; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - ret = CSVWizard::PageInvestment; - else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) - ret = CSVWizard::PagePrices; - return ret; -} -FormatsPage::FormatsPage(QDialog *parent) : - CSVWizardPage(parent), + + +FormatsPage::FormatsPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), ui(new Ui::FormatsPage) { ui->setupUi(this); @@ -1391,144 +937,102 @@ ui->comboBox_thousandsDelimiter->setEnabled(false); - ui->comboBox_dateFormat->setCurrentIndex(m_wizDlg->m_dateFormatIndex); // put before connect to not emit update signal + ui->comboBox_dateFormat->setCurrentIndex(m_imp->m_profile->m_dateFormatIndex); // put before connect to not emit update signal connect(ui->comboBox_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); - emit ui->comboBox_dateFormat->currentIndexChanged(m_wizDlg->m_dateFormatIndex); // emit update signal manually regardless of change to combobox + emit ui->comboBox_dateFormat->currentIndexChanged(m_imp->m_profile->m_dateFormatIndex); // emit update signal manually regardless of change to combobox - ui->comboBox_decimalSymbol->setCurrentIndex(m_wizDlg->m_decimalSymbolIndex); // put before connect to not emit update signal + ui->comboBox_decimalSymbol->setCurrentIndex(m_imp->m_profile->m_decimalSymbolIndex); // put before connect to not emit update signal connect(ui->comboBox_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); - emit ui->comboBox_decimalSymbol->currentIndexChanged(m_wizDlg->m_decimalSymbolIndex); // emit update signal manually regardless of change to combobox + emit ui->comboBox_decimalSymbol->currentIndexChanged(m_imp->m_profile->m_decimalSymbolIndex); // emit update signal manually regardless of change to combobox - if (m_wizDlg->m_skipSetup && + if (m_dlg->m_skipSetup && wizard()->button(QWizard::CustomButton2)->isEnabled()) - slotImportClicked(); + m_dlg->importClicked(); } void FormatsPage::decimalSymbolChanged(int index) { + QList columns = m_imp->getNumericalColumns(); switch (index) { - case -1: - if (!m_wizDlg->m_autodetect.value(CSVWizard::AutoDecimalSymbol)) - return; - case 2: - m_wizDlg->m_decimalSymbolIndex = 2; - m_wizDlg->m_decimalSymbol.clear(); - break; - default: - m_wizDlg->m_parse->setDecimalSymbol(index); - m_wizDlg->m_parse->setDecimalSymbolIndex(index); - m_wizDlg->m_parse->setThousandsSeparator(index); - m_wizDlg->m_parse->setThousandsSeparatorIndex(index); - - m_wizDlg->m_decimalSymbol = m_wizDlg->m_parse->decimalSymbol(index); - m_wizDlg->m_decimalSymbolIndex = m_wizDlg->m_parse->decimalSymbolIndex(); - } - ui->comboBox_thousandsDelimiter->setCurrentIndex(m_wizDlg->m_decimalSymbolIndex); - - bool isOk = true; - QList columnList; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { - if (m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount) >= 0) { - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount); - } else { - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDebit); - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnCredit); - } - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnAmount); - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnPrice); - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnQuantity); - if (m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee) != -1) - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee); - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { - columnList << m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnPrice); - } - - for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { - int detectedSymbol = m_wizDlg->m_decimalSymbolIndex; - if (!m_wizDlg->detectDecimalSymbol(*col, detectedSymbol)) { - isOk = false; - KMessageBox::sorry(this, i18n("
Autodetect could not detect your decimal symbol in column %1.
" - "
Try manual selection to see problematic cells and correct your data.
", *col + 1), i18n("CSV import")); + case -1: + if (!m_imp->m_autodetect.value(AutoDecimalSymbol)) + break; + case 2: + { ui->comboBox_decimalSymbol->blockSignals(true); - ui->comboBox_decimalSymbol->setCurrentIndex(-1); + m_imp->m_profile->m_decimalSymbolIndex = 2; + int failColumn = m_imp->detectDecimalSymbols(columns); + if (failColumn != -2) { + KMessageBox::sorry(this, i18n("
Autodetect could not detect your decimal symbol in column %1.
" + "
Try manual selection to see problematic cells and correct your data.
", failColumn), i18n("CSV import")); + ui->comboBox_decimalSymbol->setCurrentIndex(-1); + ui->comboBox_thousandsDelimiter->setCurrentIndex(-1); + } else if (index == -1) { // if detection went well and decimal symbol was unspecified then we'll be specifying it + int firstDecSymbol = m_imp->m_decimalSymbolIndexMap.first(); + bool allSymbolsEqual = true; + foreach (const auto mapDecSymbol, m_imp->m_decimalSymbolIndexMap) { + if (firstDecSymbol != mapDecSymbol) + allSymbolsEqual = false; + } + if (allSymbolsEqual) { // if symbol in all columns is equal then set it... + m_imp->m_profile->m_decimalSymbolIndex = firstDecSymbol; + ui->comboBox_decimalSymbol->setCurrentIndex(firstDecSymbol); + ui->comboBox_thousandsDelimiter->setCurrentIndex(firstDecSymbol); + } else { // else set to auto + m_imp->m_profile->m_decimalSymbolIndex = 2; + ui->comboBox_decimalSymbol->setCurrentIndex(2); + ui->comboBox_thousandsDelimiter->setCurrentIndex(2); + } + } ui->comboBox_decimalSymbol->blockSignals(false); - ui->comboBox_thousandsDelimiter->setCurrentIndex(-1); break; } - m_wizDlg->m_decimalSymbolIndexMap.insert(*col, detectedSymbol); - m_wizDlg->m_parse->setDecimalSymbol(detectedSymbol); - m_wizDlg->m_parse->setThousandsSeparator(detectedSymbol); // separator list is in reverse so it's ok - - isOk &= validateDecimalSymbol(*col); - } - - if (index == -1 && isOk) { // if detection went well and decimal symbol was unspeciffied then we'll be specifying it - int prevDecimalSymbol = columnList.value(0); - bool allSymbolsEqual = true; - for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { - if (m_wizDlg->m_decimalSymbolIndexMap.value(*col) != prevDecimalSymbol) - allSymbolsEqual = false; - } - ui->comboBox_decimalSymbol->blockSignals(true); - if (allSymbolsEqual) { // if symbol in all columns is equal then set it... - ui->comboBox_decimalSymbol->setCurrentIndex(prevDecimalSymbol); - ui->comboBox_thousandsDelimiter->setCurrentIndex(prevDecimalSymbol); - } else { // else set to auto - ui->comboBox_decimalSymbol->setCurrentIndex(2); - ui->comboBox_thousandsDelimiter->setCurrentIndex(2); - } - ui->comboBox_decimalSymbol->blockSignals(false); + default: + foreach (const auto column, columns) + m_imp->m_decimalSymbolIndexMap.insert(column, index); + ui->comboBox_thousandsDelimiter->setCurrentIndex(index); + m_imp->m_profile->m_decimalSymbolIndex = index; } - m_isDecimalSymbolOK = isOk; + m_isDecimalSymbolOK = validateDecimalSymbols(columns); emit completeChanged(); } -bool FormatsPage::validateDecimalSymbol(int col) +bool FormatsPage::validateDecimalSymbols(const QList &columns) { - m_wizDlg->clearColumnsBackground(col); - bool isOK = true; - for (int row = m_wizDlg->m_startLine - 1; row < m_wizDlg->m_endLine; ++row) { - QTableWidgetItem* item = m_wizDlg->ui->tableWidget->item(row, col); + foreach (const auto col, columns) { + m_imp->m_file->m_parse->setDecimalSymbol(m_imp->m_decimalSymbolIndexMap.value(col)); + m_imp->m_file->m_parse->setThousandsSeparator(m_imp->m_decimalSymbolIndexMap.value(col)); + m_dlg->clearColumnsBackground(col); + for (int row = m_imp->m_profile->m_startLine; row <= m_imp->m_profile->m_endLine; ++row) { + QStandardItem *item = m_imp->m_file->m_model->item(row, col); QString rawNumber = item->text(); - m_wizDlg->m_parse->possiblyReplaceSymbol(rawNumber); - - if (!m_wizDlg->m_parse->invalidConversion() || + m_imp->m_file->m_parse->possiblyReplaceSymbol(rawNumber); + if (!m_imp->m_file->m_parse->invalidConversion() || rawNumber.isEmpty()) { // empty strings are welcome - item->setBackground(m_wizDlg->m_colorBrush); - item->setForeground(m_wizDlg->m_colorBrushText); + item->setBackground(m_dlg->m_colorBrush); + item->setForeground(m_dlg->m_colorBrushText); } else { isOK = false; - m_wizDlg->ui->tableWidget->scrollToItem(item, QAbstractItemView::EnsureVisible); - item->setBackground(m_wizDlg->m_errorBrush); - item->setForeground(m_wizDlg->m_errorBrushText); + m_dlg->ui->tableView->scrollTo(item->index(), QAbstractItemView::EnsureVisible); + item->setBackground(m_dlg->m_errorBrush); + item->setForeground(m_dlg->m_errorBrushText); } + } + } return isOK; } -void FormatsPage::dateFormatChanged(int index) +void FormatsPage::dateFormatChanged(const int index) { - if (index < 0) + if (index == -1) return; - else { - m_wizDlg->m_dateFormatIndex = index; - m_wizDlg->m_date = m_wizDlg->m_dateFormats[m_wizDlg->m_dateFormatIndex]; - } - - int col = 0; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - col = m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDate); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - col = m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnDate); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) - col = m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnDate); - m_wizDlg->m_convertDate->setDateFormatIndex(index); + int col = m_imp->m_profile->m_colTypeNum.value(ColumnDate); + m_imp->m_profile->m_dateFormatIndex = index; + m_imp->m_convertDate->setDateFormatIndex(index); m_isDateFormatOK = validateDateFormat(col); if (!m_isDateFormatOK) { KMessageBox::sorry(this, i18n("
There are invalid date formats in column '%1'.
" @@ -1538,82 +1042,33 @@ emit completeChanged(); } -bool FormatsPage::validateDateFormat(int col) +bool FormatsPage::validateDateFormat(const int col) { - m_wizDlg->clearColumnsBackground(col); + m_dlg->clearColumnsBackground(col); bool isOK = true; - for (int row = m_wizDlg->m_startLine - 1; row < m_wizDlg->m_endLine; ++row) { - QTableWidgetItem* item = m_wizDlg->ui->tableWidget->item(row, col); - - QDate dat = m_wizDlg->m_convertDate->convertDate(item->text()); - + for (int row = m_imp->m_profile->m_startLine; row <= m_imp->m_profile->m_endLine; ++row) { + QStandardItem* item = m_imp->m_file->m_model->item(row, col); + QDate dat = m_imp->m_convertDate->convertDate(item->text()); if (dat == QDate()) { isOK = false; - m_wizDlg->ui->tableWidget->scrollToItem(item, QAbstractItemView::EnsureVisible); - item->setBackground(m_wizDlg->m_errorBrush); - item->setForeground(m_wizDlg->m_errorBrushText); + m_dlg->ui->tableView->scrollTo(item->index(), QAbstractItemView::EnsureVisible); + item->setBackground(m_dlg->m_errorBrush); + item->setForeground(m_dlg->m_errorBrushText); } else { - item->setBackground(m_wizDlg->m_colorBrush); - item->setForeground(m_wizDlg->m_colorBrushText); + item->setBackground(m_dlg->m_colorBrush); + item->setForeground(m_dlg->m_colorBrushText); } } return isOK; } -void FormatsPage::slotImportClicked() -{ - bool isOK = true; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - isOK = m_wizDlg->m_pageBanking->createStatement(m_wizDlg->st); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - isOK = m_wizDlg->m_pageInvestment->createStatement(m_wizDlg->st); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) - isOK = m_wizDlg->m_pagePrices->createStatement(m_wizDlg->st); - - if (!isOK) { - m_wizDlg->st = MyMoneyStatement(); // statement is invalid so erase it - return; - } - - m_wizDlg->hide(); //hide wizard so it will not cover accountselector - emit m_wizDlg->statementReady(m_wizDlg->st); - m_wizDlg->slotClose(); //close hidden window as it isn't needed anymore -} - -void FormatsPage::slotSaveAsQIFClicked() -{ - bool isOK = true; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - isOK = m_wizDlg->m_pageBanking->createStatement(m_wizDlg->st); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - isOK = m_wizDlg->m_pageInvestment->createStatement(m_wizDlg->st); - - if (!isOK || m_wizDlg->st.m_listTransactions.isEmpty()) - return; - - QString outFileName = m_wizDlg->m_inFileName; - outFileName.truncate(m_wizDlg->m_inFileName.lastIndexOf('.')); - outFileName += ".qif"; - outFileName = QFileDialog::getSaveFileName(this, i18n("Save QIF"), outFileName, i18n("QIF Files (*.qif)")); - if (outFileName.isEmpty()) - return; - QFile oFile(outFileName); - oFile.open(QIODevice::WriteOnly); - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) - m_wizDlg->m_pageBanking->makeQIF(m_wizDlg->st, oFile); - else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) - m_wizDlg->m_pageInvestment->makeQIF(m_wizDlg->st, oFile); - oFile.close(); -} - bool FormatsPage::isComplete() const { const bool enable = m_isDecimalSymbolOK && m_isDateFormatOK; wizard()->button(QWizard::CustomButton2)->setEnabled(enable); - if (m_wizDlg->m_profileType != CSVWizard::ProfileStockPrices && - m_wizDlg->m_profileType != CSVWizard::ProfileCurrencyPrices) + if (m_imp->m_profile->type() != ProfileStockPrices && + m_imp->m_profile->type() != ProfileCurrencyPrices) wizard()->button(QWizard::CustomButton3)->setEnabled(enable); return enable; } @@ -1623,54 +1078,15 @@ disconnect(ui->comboBox_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); disconnect(ui->comboBox_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); - QList columnList; - if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDate); - if (m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount) >= 0) - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount); - else - columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDebit) << - m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnCredit); - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnAmount) << - m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnPrice) << - m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnQuantity) << - m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnDate); - if (m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee) != -1) - columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee); - } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || - m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { - columnList << m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnPrice) << - m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnDate); - } - m_wizDlg->clearColumnsBackground(columnList); - m_wizDlg->st = MyMoneyStatement(); // any change on investment/banking page invalidates created statement + QList columns = m_imp->getNumericalColumns(); + columns.append(m_imp->m_profile->m_colTypeNum.value(ColumnDate)); + m_dlg->clearColumnsBackground(columns); + m_dlg->m_st = MyMoneyStatement(); // any change on investment/banking page invalidates created statement QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } - -void CSVWizard::closeEvent(QCloseEvent *event) -{ - this->m_plugin->m_action->setEnabled(true); // reenable File->Import->CSV - event->accept(); -} - -bool CSVWizard::eventFilter(QObject *object, QEvent *event) -{ - // prevent the QWizard part of CSVWizard window from closing on Escape key press - if (object == this->m_wizard) { - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Escape) { - close(); - return true; - } - } - } - return false; -} diff --git a/kmymoney/plugins/csvimport/csvwizard.ui b/kmymoney/plugins/csvimport/csvwizard.ui --- a/kmymoney/plugins/csvimport/csvwizard.ui +++ b/kmymoney/plugins/csvimport/csvwizard.ui @@ -13,21 +13,21 @@ 400
+ + + 0 + 0 + + true - + - + - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::MultiSelection + QAbstractScrollArea::AdjustToContentsOnFirstShow - - false - diff --git a/kmymoney/plugins/csvimport/introwizardpage.ui b/kmymoney/plugins/csvimport/introwizardpage.ui --- a/kmymoney/plugins/csvimport/introwizardpage.ui +++ b/kmymoney/plugins/csvimport/introwizardpage.ui @@ -82,7 +82,7 @@ - + true @@ -93,12 +93,12 @@ - &Banking + Ban&king - + true @@ -109,12 +109,12 @@ - &Investment + In&vestment - + 0 @@ -127,7 +127,7 @@ - + C&urrency prices @@ -153,7 +153,7 @@ - + false @@ -172,7 +172,7 @@ - + 0 @@ -189,7 +189,7 @@ - + false @@ -208,7 +208,7 @@ - + false @@ -227,7 +227,7 @@ - + false diff --git a/kmymoney/plugins/csvimport/investmentwizardpage.h b/kmymoney/plugins/csvimport/investmentwizardpage.h --- a/kmymoney/plugins/csvimport/investmentwizardpage.h +++ b/kmymoney/plugins/csvimport/investmentwizardpage.h @@ -25,96 +25,55 @@ #include #include -#include #include // ---------------------------------------------------------------------------- // KDE Includes -#include - // ---------------------------------------------------------------------------- // Project Includes #include +#include // ---------------------------------------------------------------------------- +class InvestmentProfile; class TransactionDlg; class SecurityDlg; class SecuritiesDlg; -class CSVWizard; -class InvestmentPage; namespace Ui { class InvestmentPage; } -class InvestmentPage : public QWizardPage +class InvestmentPage : public CSVWizardPage { Q_OBJECT public: - explicit InvestmentPage(QDialog *parent = 0); + explicit InvestmentPage(CSVWizard *dlg, CSVImporter *imp); ~InvestmentPage(); Ui::InvestmentPage *ui; QVBoxLayout *m_pageLayout; - CSVWizard* m_wiz; - typedef enum:uchar { ColumnDate, ColumnType, ColumnAmount, - ColumnPrice, ColumnQuantity, ColumnFee, - ColumnSymbol, ColumnName, ColumnMemo, - ColumnEmpty = 0xFE, ColumnInvalid = 0xFF - } columnTypeE; - - QMap m_colTypeNum; - QMap m_colNumType; - QMap m_colTypeName; - - void saveSettings(); - void readSettings(const KSharedConfigPtr &config); - void setParent(CSVWizard* dlg); /** * This method fills QIF file with investment data */ void makeQIF(MyMoneyStatement& st, QFile& file); /** - * This method feeds file buffer in investment lines parser. + * This method validates the column numbers entered by the user. It then + * checks the values in those columns for compatibility with the input + * investment activity type. */ - bool createStatement(MyMoneyStatement& st); + bool validateActionType(); private: - QPointer m_securityDlg; - QPointer m_securitiesDlg; - - QMap m_mapSymbolName; - - QStringList m_shrsinList; - QStringList m_divXList; - QStringList m_intIncList; - QStringList m_feeList; - QStringList m_brokerageList; - QStringList m_reinvdivList; - QStringList m_buyList; - QStringList m_sellList; - QStringList m_shrsoutList; - QStringList m_columnList; - - QString m_priceFractionValue; - QString m_feeRate; - QString m_minFee; - QString m_securityName; - QString m_securitySymbol; - - int m_feeIsPercentage; - int m_priceFraction; - int m_dontAsk; - void initializePage(); bool isComplete() const; bool validatePage(); @@ -134,73 +93,36 @@ * This method is called column on investment page is selected. * It sets m_colTypeNum, m_colNumType and runs column validation. */ - bool validateSelectedColumn(int col, columnTypeE type); - - /** - * This method is called when the user clicks 'Import'. - * It will evaluate an input line and append it to a statement. - */ - bool processInvestLine(const QString& line, MyMoneyStatement& st); - - /** - * This method creates valid set of possible transactions - * according to quantity, amount and price - */ - bool createValidActionTypes(QList &validActionTypes, MyMoneyStatement::Transaction &tr); - - /** - * This method stores user selection of action type so it will not ask - * for the same action type twice. - */ - void storeActionType(MyMoneyStatement::Transaction::EAction &actionType, const QString &userType); - - /** - * This method validates the column numbers entered by the user. It then - * checks the values in those columns for compatibility with the input - * investment activity type. - */ - bool validateActionType(MyMoneyStatement::Transaction &tr, QStringList &colList); - - /** - * This method is called during input. It validates the action types - * in the input file, and assigns appropriate QString types. - */ - MyMoneyStatement::Transaction::EAction processActionType(QString& type); - - /** - * This method gets securities from investment statement and - * tries to get pairs of symbol and name either - * from KMM or from statement data. - * In case it's not successfull onlySymbols and onlyNames won't be empty. - */ - bool sortSecurities(QSet& onlySymbols, QSet& onlyNames, QMap& mapSymbolName); + bool validateSelectedColumn(const int col, const columnTypeE type); /** * This method ensures that every security has symbol and name. */ bool validateSecurity(); bool validateSecurities(); -public slots: - void slotClearFee(); + QPointer m_securityDlg; + QPointer m_securitiesDlg; + InvestmentProfile *m_profile; private slots: - void slotMemoColSelected(int col); - void slotDateColSelected(int col); - void slotFeeColSelected(int col); - void slotTypeColSelected(int col); - void slotQuantityColSelected(int col); - void slotPriceColSelected(int col); - void slotAmountColSelected(int col); - void slotSymbolColSelected(int col); - void slotNameColSelected(int col); - void slotFeeIsPercentageClicked(bool checked); - void slotFractionChanged(int col); - void slotClearColumns(); - void slotCalculateFee(); - void slotFeeInputsChanged(); - void slotFeeRateChanged(const QString text); - void slotMinFeeChanged(const QString text); + void clearFee(); + void memoColSelected(int col); + void dateColSelected(int col); + void feeColSelected(int col); + void typeColSelected(int col); + void quantityColSelected(int col); + void priceColSelected(int col); + void amountColSelected(int col); + void symbolColSelected(int col); + void nameColSelected(int col); + void feeIsPercentageClicked(bool checked); + void fractionChanged(int col); + void clearColumns(); + void feeInputsChanged(); + void feeRateChanged(const QString &text); + void minFeeChanged(const QString &text); + void calculateFee(); }; #endif // INVESTMENTWIZARDPAGE_H diff --git a/kmymoney/plugins/csvimport/investmentwizardpage.cpp b/kmymoney/plugins/csvimport/investmentwizardpage.cpp --- a/kmymoney/plugins/csvimport/investmentwizardpage.cpp +++ b/kmymoney/plugins/csvimport/investmentwizardpage.cpp @@ -23,183 +23,188 @@ // QT Includes #include +#include // ---------------------------------------------------------------------------- // KDE Includes #include -#include // ---------------------------------------------------------------------------- // Project Includes -#include "kmymoney.h" #include "mymoneyfile.h" -#include "mymoneymoney.h" - #include "csvwizard.h" -#include "convdate.h" -#include "csvutil.h" #include "transactiondlg.h" #include "securitydlg.h" #include "securitiesdlg.h" #include "ui_investmentwizardpage.h" -#include "ui_csvwizard.h" // ---------------------------------------------------------------------------- -InvestmentPage::InvestmentPage(QDialog *parent) : QWizardPage(parent), ui(new Ui::InvestmentPage) +InvestmentPage::InvestmentPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), + ui(new Ui::InvestmentPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); - connect(ui->button_clear, SIGNAL(clicked()), this, SLOT(slotClearColumns())); - connect(ui->buttonInv_clearFee, SIGNAL(clicked()), this, SLOT(slotClearFee())); - connect(ui->buttonInv_calculateFee, SIGNAL(clicked()), this, SLOT(slotCalculateFee())); + connect(ui->m_clear, &QAbstractButton::clicked, this, &InvestmentPage::clearColumns); + connect(ui->m_clearFee, &QAbstractButton::clicked, this, &InvestmentPage::clearFee); + connect(ui->m_calculateFee, &QAbstractButton::clicked, this, &InvestmentPage::calculateFee); // initialize column names - m_colTypeName.insert(ColumnType, i18n("Type")); - m_colTypeName.insert(ColumnPrice, i18n("Price")); - m_colTypeName.insert(ColumnQuantity, i18n("Quantity")); - m_colTypeName.insert(ColumnFee, i18n("Fee")); - m_colTypeName.insert(ColumnDate, i18n("Date")); - m_colTypeName.insert(ColumnAmount, i18n("Amount")); - m_colTypeName.insert(ColumnSymbol, i18n("Symbol")); - m_colTypeName.insert(ColumnName, i18n("Name")); - m_colTypeName.insert(ColumnMemo, i18n("Memo")); - - // initialize operation type names - m_buyList = QString(i18nc("Type of operation as in financial statement", "buy")).split(',', QString::SkipEmptyParts); - m_sellList = QString(i18nc("Type of operation as in financial statement", "sell,repurchase")).split(',', QString::SkipEmptyParts); - m_divXList = QString(i18nc("Type of operation as in financial statement", "dividend")).split(',', QString::SkipEmptyParts); - m_intIncList = QString(i18nc("Type of operation as in financial statement", "interest,income")).split(',', QString::SkipEmptyParts); - m_reinvdivList = QString(i18nc("Type of operation as in financial statement", "reinvest,reinv,re-inv")).split(',', QString::SkipEmptyParts); - m_shrsinList = QString(i18nc("Type of operation as in financial statement", "add,stock dividend,divd reinv,transfer in,re-registration in,journal entry")).split(',', QString::SkipEmptyParts); - m_shrsoutList = QString(i18nc("Type of operation as in financial statement", "remove")).split(',', QString::SkipEmptyParts); + m_dlg->m_colTypeName.insert(ColumnType, i18n("Type")); + m_dlg->m_colTypeName.insert(ColumnPrice, i18n("Price")); + m_dlg->m_colTypeName.insert(ColumnQuantity, i18n("Quantity")); + m_dlg->m_colTypeName.insert(ColumnFee, i18n("Fee")); + m_dlg->m_colTypeName.insert(ColumnDate, i18n("Date")); + m_dlg->m_colTypeName.insert(ColumnAmount, i18n("Amount")); + m_dlg->m_colTypeName.insert(ColumnSymbol, i18n("Symbol")); + m_dlg->m_colTypeName.insert(ColumnName, i18n("Name")); + m_dlg->m_colTypeName.insert(ColumnMemo, i18n("Memo")); + + m_profile = dynamic_cast(m_imp->m_profile); } InvestmentPage::~InvestmentPage() { delete ui; delete m_securitiesDlg; } -void InvestmentPage::setParent(CSVWizard* dlg) +void InvestmentPage::calculateFee() { - m_wiz = dlg; + m_imp->calculateFee(); + m_dlg->updateWindowSize(); + m_dlg->markUnwantedRows(); } void InvestmentPage::initializeComboBoxes() { // disable investment widgets allowing their initialization - disconnect(ui->comboBoxInv_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotMemoColSelected(int))); - disconnect(ui->comboBoxInv_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotTypeColSelected(int))); - disconnect(ui->comboBoxInv_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - disconnect(ui->comboBoxInv_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotQuantityColSelected(int))); - disconnect(ui->comboBoxInv_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPriceColSelected(int))); - disconnect(ui->comboBoxInv_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFractionChanged(int))); - disconnect(ui->comboBoxInv_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAmountColSelected(int))); - disconnect(ui->lineEdit_feeRate, SIGNAL(editingFinished()), this, SLOT(slotFeeInputsChanged())); - disconnect(ui->lineEdit_feeRate, SIGNAL(textChanged(QString)), this, SLOT(slotFeeRateChanged(QString))); - disconnect(ui->lineEdit_minFee, SIGNAL(textChanged(QString)), this, SLOT(slotMinFeeChanged(QString))); - disconnect(ui->comboBoxInv_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFeeColSelected(int))); - disconnect(ui->comboBoxInv_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSymbolColSelected(int))); - disconnect(ui->comboBoxInv_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotNameColSelected(int))); - disconnect(ui->checkBoxInv_feeIsPercentage, SIGNAL(clicked(bool)), this, SLOT(slotFeeIsPercentageClicked(bool))); + disconnect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int))); + disconnect(ui->m_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(typeColSelected(int))); + disconnect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + disconnect(ui->m_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(quantityColSelected(int))); + disconnect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int))); + disconnect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int))); + disconnect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int))); + disconnect(ui->m_feeRate, SIGNAL(editingFinished()), this, SLOT(feeInputsChanged())); + disconnect(ui->m_feeRate, SIGNAL(textChanged(QString)), this, SLOT(feeRateChanged(QString))); + disconnect(ui->m_minFee, SIGNAL(textChanged(QString)), this, SLOT(minFeeChanged(QString))); + disconnect(ui->m_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(feeColSelected(int))); + disconnect(ui->m_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolColSelected(int))); + disconnect(ui->m_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(nameColSelected(int))); + disconnect(ui->m_feeIsPercentage, SIGNAL(clicked(bool)), this, SLOT(feeIsPercentageClicked(bool))); // clear all existing items before adding new ones - ui->comboBoxInv_amountCol->clear(); // clear all existing items before adding new ones - ui->comboBoxInv_dateCol->clear(); - ui->comboBoxInv_memoCol->clear(); - ui->comboBoxInv_priceCol->clear(); - ui->comboBoxInv_quantityCol->clear(); - ui->comboBoxInv_typeCol->clear(); - ui->comboBoxInv_feeCol->clear(); - ui->comboBoxInv_symbolCol->clear(); - ui->comboBoxInv_nameCol->clear(); - ui->lineEdit_feeRate->clear(); - ui->lineEdit_minFee->clear(); + ui->m_amountCol->clear(); // clear all existing items before adding new ones + ui->m_dateCol->clear(); + ui->m_memoCol->clear(); + ui->m_priceCol->clear(); + ui->m_quantityCol->clear(); + ui->m_typeCol->clear(); + ui->m_feeCol->clear(); + ui->m_symbolCol->clear(); + ui->m_nameCol->clear(); + ui->m_feeRate->clear(); + ui->m_minFee->clear(); QStringList columnNumbers; - for (int i = 0; i < m_wiz->m_endColumn; ++i) { - columnNumbers << QString::number(i + 1); - } + for (int i = 0; i < m_imp->m_file->m_columnCount; ++i) + columnNumbers.append(QString::number(i + 1)); // populate comboboxes with col # values - ui->comboBoxInv_amountCol->addItems(columnNumbers); - ui->comboBoxInv_dateCol->addItems(columnNumbers); - ui->comboBoxInv_memoCol->addItems(columnNumbers); - ui->comboBoxInv_priceCol->addItems(columnNumbers); - ui->comboBoxInv_quantityCol->addItems(columnNumbers); - ui->comboBoxInv_typeCol->addItems(columnNumbers); - ui->comboBoxInv_feeCol->addItems(columnNumbers); - ui->comboBoxInv_symbolCol->addItems(columnNumbers); - ui->comboBoxInv_nameCol->addItems(columnNumbers); - - slotClearColumns(); // all comboboxes are set to 0 so set them to -1 - connect(ui->comboBoxInv_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotMemoColSelected(int))); - connect(ui->comboBoxInv_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotTypeColSelected(int))); - connect(ui->comboBoxInv_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - connect(ui->comboBoxInv_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotQuantityColSelected(int))); - connect(ui->comboBoxInv_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPriceColSelected(int))); - connect(ui->comboBoxInv_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFractionChanged(int))); - connect(ui->comboBoxInv_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAmountColSelected(int))); - connect(ui->lineEdit_feeRate, SIGNAL(editingFinished()), this, SLOT(slotFeeInputsChanged())); - connect(ui->lineEdit_feeRate, SIGNAL(textChanged(QString)), this, SLOT(slotFeeRateChanged(QString))); - connect(ui->lineEdit_minFee, SIGNAL(textChanged(QString)), this, SLOT(slotMinFeeChanged(QString))); - connect(ui->comboBoxInv_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFeeColSelected(int))); - connect(ui->comboBoxInv_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSymbolColSelected(int))); - connect(ui->comboBoxInv_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotNameColSelected(int))); - connect(ui->checkBoxInv_feeIsPercentage, SIGNAL(clicked(bool)), this, SLOT(slotFeeIsPercentageClicked(bool))); + ui->m_amountCol->addItems(columnNumbers); + ui->m_dateCol->addItems(columnNumbers); + ui->m_memoCol->addItems(columnNumbers); + ui->m_priceCol->addItems(columnNumbers); + ui->m_quantityCol->addItems(columnNumbers); + ui->m_typeCol->addItems(columnNumbers); + ui->m_feeCol->addItems(columnNumbers); + ui->m_symbolCol->addItems(columnNumbers); + ui->m_nameCol->addItems(columnNumbers); + + clearColumns(); // all comboboxes are set to 0 so set them to -1 + connect(ui->m_memoCol, SIGNAL(currentIndexChanged(int)), this, SLOT(memoColSelected(int))); + connect(ui->m_typeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(typeColSelected(int))); + connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + connect(ui->m_quantityCol, SIGNAL(currentIndexChanged(int)), this, SLOT(quantityColSelected(int))); + connect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int))); + connect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int))); + connect(ui->m_amountCol, SIGNAL(currentIndexChanged(int)), this, SLOT(amountColSelected(int))); + connect(ui->m_feeRate, SIGNAL(editingFinished()), this, SLOT(feeInputsChanged())); + connect(ui->m_feeRate, SIGNAL(textChanged(QString)), this, SLOT(feeRateChanged(QString))); + connect(ui->m_minFee, SIGNAL(textChanged(QString)), this, SLOT(minFeeChanged(QString))); + connect(ui->m_feeCol, SIGNAL(currentIndexChanged(int)), this, SLOT(feeColSelected(int))); + connect(ui->m_symbolCol, SIGNAL(currentIndexChanged(int)), this, SLOT(symbolColSelected(int))); + connect(ui->m_nameCol, SIGNAL(currentIndexChanged(int)), this, SLOT(nameColSelected(int))); + connect(ui->m_feeIsPercentage, SIGNAL(clicked(bool)), this, SLOT(feeIsPercentageClicked(bool))); } void InvestmentPage::initializePage() { - if (ui->comboBoxInv_dateCol->count() != m_wiz->m_endColumn) + if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount) initializeComboBoxes(); - ui->comboBoxInv_dateCol->setCurrentIndex(m_colTypeNum.value(ColumnDate)); - ui->comboBoxInv_typeCol->setCurrentIndex(m_colTypeNum.value(ColumnType)); - ui->comboBoxInv_priceCol->setCurrentIndex(m_colTypeNum.value(ColumnPrice)); - ui->comboBoxInv_quantityCol->setCurrentIndex(m_colTypeNum.value(ColumnQuantity)); - ui->comboBoxInv_amountCol->setCurrentIndex(m_colTypeNum.value(ColumnAmount)); - ui->comboBoxInv_nameCol->setCurrentIndex(m_colTypeNum.value(ColumnName)); - ui->comboBoxInv_symbolCol->setCurrentIndex(m_colTypeNum.value(ColumnSymbol)); - ui->checkBoxInv_feeIsPercentage->setChecked(m_feeIsPercentage); - ui->comboBoxInv_feeCol->setCurrentIndex(m_colTypeNum.value(ColumnFee)); - ui->comboBoxInv_priceFraction->setCurrentIndex(m_priceFraction); - ui->lineEdit_feeRate->setText(m_feeRate); - ui->lineEdit_minFee->setText(m_minFee); - ui->lineEdit_feeRate->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,2}[") + QLocale().decimalPoint() + QStringLiteral("]{1,1}[0-9]{0,2}")), this) ); - ui->lineEdit_minFee->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,}[") + QLocale().decimalPoint() + QStringLiteral("]{0,1}[0-9]{0,}")), this) ); - slotCalculateFee(); - - for (int i = 0; i < m_wiz->m_memoColList.count(); ++i) { // Set up all memo fields... - int tmp = m_wiz->m_memoColList[i]; - if (tmp < m_colTypeNum.count()) - ui->comboBoxInv_memoCol->setCurrentIndex(tmp); - } - - slotFeeInputsChanged(); + ui->m_dateCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnDate)); + ui->m_typeCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnType)); + ui->m_priceCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnPrice)); + ui->m_quantityCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnQuantity)); + ui->m_amountCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnAmount)); + ui->m_nameCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnName)); + ui->m_symbolCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnSymbol)); + ui->m_feeIsPercentage->setChecked(m_profile->m_feeIsPercentage); + ui->m_feeCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnFee)); + ui->m_priceFraction->blockSignals(true); + foreach (const auto priceFraction, m_imp->m_priceFractions) + ui->m_priceFraction->addItem(QString::number(priceFraction.toDouble(), 'g', 3)); + ui->m_priceFraction->blockSignals(false); + ui->m_priceFraction->setCurrentIndex(m_profile->m_priceFraction); + ui->m_feeRate->setText(m_profile->m_feeRate); + ui->m_minFee->setText(m_profile->m_minFee); + ui->m_feeRate->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,2}[") + QLocale().decimalPoint() + QStringLiteral("]{1,1}[0-9]{0,2}")), this) ); + ui->m_minFee->setValidator(new QRegularExpressionValidator(QRegularExpression(QStringLiteral("[0-9]{1,}[") + QLocale().decimalPoint() + QStringLiteral("]{0,1}[0-9]{0,}")), this) ); + + if (!m_profile->m_feeRate.isEmpty() && + m_imp->calculateFee()) { + ui->m_feeCol->blockSignals(true); + int feeCol = ui->m_feeCol->count(); + ui->m_feeCol->addItem(QString::number(feeCol + 1)); + ui->m_feeCol->setEnabled(false); + ui->m_feeCol->setCurrentIndex(feeCol); + ui->m_feeCol->blockSignals(false); + m_dlg->updateWindowSize(); + m_dlg->markUnwantedRows(); + } + + for (int i = 0; i < m_profile->m_memoColList.count(); ++i) { // Set up all memo fields... + int tmp = m_profile->m_memoColList.at(i); + if (tmp < m_profile->m_colTypeNum.count()) + ui->m_memoCol->setCurrentIndex(tmp); + } + + feeInputsChanged(); } bool InvestmentPage::isComplete() const { - return ui->comboBoxInv_dateCol->currentIndex() > -1 && - ui->comboBoxInv_typeCol->currentIndex() > -1 && - ui->comboBoxInv_quantityCol->currentIndex() > -1 && - ui->comboBoxInv_priceCol->currentIndex() > -1 && - ui->comboBoxInv_amountCol->currentIndex() > -1 && - ui->comboBoxInv_priceFraction->currentIndex() > -1; + return ui->m_dateCol->currentIndex() > -1 && + ui->m_typeCol->currentIndex() > -1 && + ui->m_quantityCol->currentIndex() > -1 && + ui->m_priceCol->currentIndex() > -1 && + ui->m_amountCol->currentIndex() > -1 && + ui->m_priceFraction->currentIndex() > -1; } bool InvestmentPage::validatePage() { - if (ui->comboBoxInv_symbolCol->currentIndex() == -1 && - ui->comboBoxInv_nameCol->currentIndex() == -1) + if (ui->m_symbolCol->currentIndex() == -1 && + ui->m_nameCol->currentIndex() == -1) return validateSecurity(); else return validateSecurities(); @@ -210,544 +215,330 @@ clearFeeCol(); } -void InvestmentPage::slotMemoColSelected(int col) +void InvestmentPage::memoColSelected(int col) { - if (m_colNumType.value(col) == ColumnType || - m_colNumType.value(col) == ColumnName) { + if (m_profile->m_colNumType.value(col) == ColumnType || + m_profile->m_colNumType.value(col) == ColumnName) { int rc = KMessageBox::Yes; if (isVisible()) - rc = KMessageBox::questionYesNo(m_wiz, i18n("
The '%1' field already has this column selected.
" + rc = KMessageBox::questionYesNo(m_dlg, i18n("
The '%1' field already has this column selected.
" "
If you wish to copy that data to the memo field, click 'Yes'.
", - m_colTypeName.value(m_colNumType[col]))); + m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); if (rc == KMessageBox::Yes) { - ui->comboBoxInv_memoCol->setItemText(col, QString().setNum(col + 1) + QChar(QLatin1Char('*'))); - if (!m_wiz->m_memoColList.contains(col)) - m_wiz->m_memoColList.append(col); + ui->m_memoCol->setItemText(col, QString::number(col + 1) + QChar(QLatin1Char('*'))); + if (!m_profile->m_memoColList.contains(col)) + m_profile->m_memoColList.append(col); } else { - ui->comboBoxInv_memoCol->setItemText(col, QString().setNum(col + 1)); - m_wiz->m_memoColList.removeOne(col); + ui->m_memoCol->setItemText(col, QString::number(col + 1)); + m_profile->m_memoColList.removeOne(col); } //allow only separate memo field occupy combobox - ui->comboBoxInv_memoCol->blockSignals(true); - if (m_colTypeNum.value(ColumnMemo) != -1) - ui->comboBoxInv_memoCol->setCurrentIndex(m_colTypeNum.value(ColumnMemo)); + ui->m_memoCol->blockSignals(true); + if (m_profile->m_colTypeNum.value(ColumnMemo) != -1) + ui->m_memoCol->setCurrentIndex(m_profile->m_colTypeNum.value(ColumnMemo)); else - ui->comboBoxInv_memoCol->setCurrentIndex(-1); - ui->comboBoxInv_memoCol->blockSignals(false); + ui->m_memoCol->setCurrentIndex(-1); + ui->m_memoCol->blockSignals(false); return; } - if (m_colTypeNum.value(ColumnMemo) != -1) // check if this memo has any column 'number' assigned... - m_wiz->m_memoColList.removeOne(col); // ...if true remove it from memo list + if (m_profile->m_colTypeNum.value(ColumnMemo) != -1) // check if this memo has any column 'number' assigned... + m_profile->m_memoColList.removeOne(col); // ...if true remove it from memo list if(validateSelectedColumn(col, ColumnMemo)) - if (col != - 1 && !m_wiz->m_memoColList.contains(col)) - m_wiz->m_memoColList.append(col); + if (col != - 1 && !m_profile->m_memoColList.contains(col)) + m_profile->m_memoColList.append(col); } -void InvestmentPage::slotDateColSelected(int col) +void InvestmentPage::dateColSelected(int col) { validateSelectedColumn(col, ColumnDate); } -void InvestmentPage::slotFeeColSelected(int col) +void InvestmentPage::feeColSelected(int col) { validateSelectedColumn(col, ColumnFee); - slotFeeInputsChanged(); + feeInputsChanged(); } -void InvestmentPage::slotTypeColSelected(int col) +void InvestmentPage::typeColSelected(int col) { if (validateSelectedColumn(col, ColumnType)) if (!validateMemoComboBox()) // user could have it already in memo so... - slotMemoColSelected(col); // ...if true set memo field again + memoColSelected(col); // ...if true set memo field again } -void InvestmentPage::slotQuantityColSelected(int col) +void InvestmentPage::quantityColSelected(int col) { validateSelectedColumn(col, ColumnQuantity); } -void InvestmentPage::slotPriceColSelected(int col) +void InvestmentPage::priceColSelected(int col) { validateSelectedColumn(col, ColumnPrice); } -void InvestmentPage::slotAmountColSelected(int col) +void InvestmentPage::amountColSelected(int col) { validateSelectedColumn(col, ColumnAmount); clearFeeCol(); - slotFeeInputsChanged(); + feeInputsChanged(); } -void InvestmentPage::slotSymbolColSelected(int col) +void InvestmentPage::symbolColSelected(int col) { validateSelectedColumn(col, ColumnSymbol); - m_mapSymbolName.clear(); // new symbol column so this map is no longer valid + m_imp->m_mapSymbolName.clear(); // new symbol column so this map is no longer valid } -void InvestmentPage::slotNameColSelected(int col) +void InvestmentPage::nameColSelected(int col) { if (validateSelectedColumn(col, ColumnName)) if (!validateMemoComboBox()) // user could have it already in memo so... - slotMemoColSelected(col); // ...if true set memo field again - m_mapSymbolName.clear(); // new name column so this map is no longer valid + memoColSelected(col); // ...if true set memo field again + m_imp->m_mapSymbolName.clear(); // new name column so this map is no longer valid } -void InvestmentPage::slotFeeIsPercentageClicked(bool checked) +void InvestmentPage::feeIsPercentageClicked(bool checked) { - m_feeIsPercentage = checked; + m_profile->m_feeIsPercentage = checked; } -void InvestmentPage::slotFractionChanged(int col) +void InvestmentPage::fractionChanged(int col) { - m_priceFraction = col; - m_priceFractionValue = ui->comboBoxInv_priceFraction->itemText(col); + m_profile->m_priceFraction = col; emit completeChanged(); } -void InvestmentPage::slotClearColumns() +void InvestmentPage::clearColumns() { - ui->comboBoxInv_amountCol->setCurrentIndex(-1); - ui->comboBoxInv_dateCol->setCurrentIndex(-1); - ui->comboBoxInv_priceCol->setCurrentIndex(-1); - ui->comboBoxInv_quantityCol->setCurrentIndex(-1); - ui->comboBoxInv_memoCol->setCurrentIndex(-1); - ui->comboBoxInv_typeCol->setCurrentIndex(-1); - ui->comboBoxInv_nameCol->setCurrentIndex(-1); - ui->comboBoxInv_symbolCol->setCurrentIndex(-1); - ui->comboBoxInv_priceFraction->setCurrentIndex(-1); - slotClearFee(); + ui->m_amountCol->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); + ui->m_priceCol->setCurrentIndex(-1); + ui->m_quantityCol->setCurrentIndex(-1); + ui->m_memoCol->setCurrentIndex(-1); + ui->m_typeCol->setCurrentIndex(-1); + ui->m_nameCol->setCurrentIndex(-1); + ui->m_symbolCol->setCurrentIndex(-1); + ui->m_priceFraction->setCurrentIndex(-1); + clearFee(); } -void InvestmentPage::slotClearFee() +void InvestmentPage::clearFee() { clearFeeCol(); - ui->comboBoxInv_feeCol->setCurrentIndex(-1); - ui->checkBoxInv_feeIsPercentage->setChecked(false); - ui->buttonInv_calculateFee->setEnabled(false); - ui->lineEdit_feeRate->setEnabled(true); - ui->lineEdit_minFee->setEnabled(false); - ui->lineEdit_feeRate->clear(); - ui->lineEdit_minFee->clear(); -} - -void InvestmentPage::slotFeeInputsChanged() -{ - ui->buttonInv_calculateFee->setEnabled(false); - if (ui->comboBoxInv_feeCol->currentIndex() < m_wiz->m_endColumn && - ui->comboBoxInv_feeCol->currentIndex() > -1) { - ui->lineEdit_minFee->setEnabled(false); - ui->lineEdit_feeRate->setEnabled(false); - ui->lineEdit_minFee->clear(); - ui->lineEdit_feeRate->clear(); - } else if (m_feeRate.isEmpty()) { - ui->comboBoxInv_feeCol->setEnabled(true); - ui->checkBoxInv_feeIsPercentage->setEnabled(true); - ui->lineEdit_minFee->setEnabled(false); + ui->m_feeCol->setCurrentIndex(-1); + ui->m_feeIsPercentage->setChecked(false); + ui->m_calculateFee->setEnabled(false); + ui->m_feeRate->setEnabled(true); + ui->m_minFee->setEnabled(false); + ui->m_feeRate->clear(); + ui->m_minFee->clear(); +} + +void InvestmentPage::feeInputsChanged() +{ + + +// if (ui->comboBoxInv_feeCol->currentIndex() < m_importer->m_file->m_columnCount && +// ui->comboBoxInv_feeCol->currentIndex() > -1) { +// ui->lineEdit_minFee->setEnabled(false); +// ui->lineEdit_feeRate->setEnabled(false); +// ui->lineEdit_minFee->clear(); +// ui->lineEdit_feeRate->clear(); +// } + if (m_profile->m_feeRate.isEmpty()) { + ui->m_feeCol->setEnabled(true); + ui->m_feeIsPercentage->setEnabled(true); + ui->m_minFee->setEnabled(false); + ui->m_calculateFee->setEnabled(false); } else { - ui->comboBoxInv_feeCol->setEnabled(false); - ui->checkBoxInv_feeIsPercentage->setEnabled(false); - ui->checkBoxInv_feeIsPercentage->setChecked(true); - ui->lineEdit_minFee->setEnabled(true); - ui->lineEdit_feeRate->setEnabled(true); - if (m_colTypeNum.value(ColumnAmount) != -1) - ui->buttonInv_calculateFee->setEnabled(true); + ui->m_feeCol->setEnabled(false); + ui->m_feeIsPercentage->setEnabled(false); + ui->m_feeIsPercentage->setChecked(true); + ui->m_minFee->setEnabled(true); + ui->m_feeRate->setEnabled(true); + if (m_profile->m_colTypeNum.value(ColumnAmount) != -1) + ui->m_calculateFee->setEnabled(true); } } -void InvestmentPage::slotFeeRateChanged(const QString text) +void InvestmentPage::feeRateChanged(const QString &text) { - m_feeRate = text; + m_profile->m_feeRate = text; } -void InvestmentPage::slotMinFeeChanged(const QString text) +void InvestmentPage::minFeeChanged(const QString &text) { - m_minFee = text; -} - -void InvestmentPage::slotCalculateFee() -{ - QString txt; - QString newTxt; - QString decimalSymbol; - double d; - bool ok; - - if (m_feeRate.isEmpty() || m_colTypeNum.value(ColumnAmount) == -1) //check if feeRate is in place - return; - - decimalSymbol = m_wiz->m_decimalSymbol; - if (m_wiz->m_decimalSymbolIndex == 2 || m_wiz->m_decimalSymbolIndex == -1) { - int detectedSymbol = 2; - if (!m_wiz->detectDecimalSymbol(m_colTypeNum.value(ColumnAmount), detectedSymbol)) - return; - m_wiz->m_parse->setDecimalSymbol(detectedSymbol); - m_wiz->m_parse->setThousandsSeparator(detectedSymbol); // separator list is in reverse so it's ok - decimalSymbol = m_wiz->m_parse->decimalSymbol(detectedSymbol); - } - - MyMoneyMoney percent(m_wiz->m_parse->possiblyReplaceSymbol(m_feeRate)); - - if (m_minFee.isEmpty()) - m_minFee = QChar(QLatin1Char('0')) + QLocale().decimalPoint() + QStringLiteral("00"); - - MyMoneyMoney minFee(m_wiz->m_parse->possiblyReplaceSymbol(m_minFee)); - - if (m_colTypeNum.value(ColumnFee) == -1) { // if fee column not present, add it at the end - m_colNumType.insert(m_wiz->m_maxColumnCount, ColumnFee); - m_colTypeNum.insert(ColumnFee, m_wiz->m_maxColumnCount); - } - - if (m_colTypeNum.value(ColumnFee) >= m_wiz->m_maxColumnCount) // if fee column out of boudary, expand it - m_wiz->m_maxColumnCount ++; - - m_wiz->ui->tableWidget->setColumnCount(m_wiz->m_maxColumnCount); - txt.setNum(m_colTypeNum.value(ColumnFee) + 1); - if (ui->comboBoxInv_feeCol->itemText(m_colTypeNum.value(ColumnFee)) != txt) - ui->comboBoxInv_feeCol->insertItem(m_colTypeNum.value(ColumnFee), txt); - ui->comboBoxInv_feeCol->setCurrentIndex(m_colTypeNum.value(ColumnFee)); // ...and select it by default - - for (int i = 0; i < m_wiz->m_startLine - 1; ++i) { // fill rows above with whitespace for nice effect with markUnwantedRows - QTableWidgetItem *item = new QTableWidgetItem; - item->setText(QString()); - m_wiz->ui->tableWidget->setItem(i, m_colTypeNum.value(ColumnFee), item); - } - - for (int i = m_wiz->m_endLine; i < m_wiz->ui->tableWidget->rowCount(); ++i) { // fill rows below with whitespace for nice effect with markUnwantedRows - QTableWidgetItem *item = new QTableWidgetItem; - item->setText(QString()); - m_wiz->ui->tableWidget->setItem(i, m_colTypeNum.value(ColumnFee), item); - } - - for (int i = m_wiz->m_startLine - 1; i < m_wiz->m_endLine; ++i) { - m_columnList = m_wiz->m_parse->parseLine(m_wiz->m_lineList[i]); - txt = m_columnList[m_colTypeNum.value(ColumnAmount)]; - txt.remove(QRegularExpression(QStringLiteral("[,. ]"))).toInt(&ok); - if (!ok) { // if the line is in statement's header, skip - m_wiz->m_lineList[i].append(m_wiz->m_fieldDelimiterCharacter); - continue; - } - txt = m_columnList[m_colTypeNum.value(ColumnAmount)]; - txt = txt.remove(m_wiz->m_fieldDelimiterCharacter); - if (txt.startsWith(QChar(QLatin1Char('(')))) { - txt.remove(QRegularExpression(QStringLiteral("[()]"))); - txt.prepend(QChar(QLatin1Char('-'))); - } - newTxt = m_wiz->m_parse->possiblyReplaceSymbol(txt); - MyMoneyMoney amount(newTxt); - MyMoneyMoney fee(percent * amount / MyMoneyMoney(100)); - if (fee < minFee) - fee = minFee; - d = fee.toDouble(); - txt.setNum(d, 'f', 4); - txt.replace(QChar(QLatin1Char('.')), decimalSymbol); //make sure decimal symbol is uniform in whole line - - if (decimalSymbol == m_wiz->m_fieldDelimiterCharacter) { //make sure fee has the same notation as the line it's being attached to - if (m_columnList.count() == m_wiz->m_maxColumnCount) - m_wiz->m_lineList[i] = m_wiz->m_lineList[i].left(m_wiz->m_lineList[i].length() - txt.length() - 2 * m_wiz->m_textDelimiterCharacter.length() - m_wiz->m_fieldDelimiterCharacter.length()); - m_wiz->m_lineList[i].append(m_wiz->m_fieldDelimiterCharacter + m_wiz->m_textDelimiterCharacter + txt + m_wiz->m_textDelimiterCharacter); - } - else { - if (m_columnList.count() == m_wiz->m_maxColumnCount) - m_wiz->m_lineList[i] = m_wiz->m_lineList[i].left(m_wiz->m_lineList[i].length()-txt.length() - m_wiz->m_fieldDelimiterCharacter.length() ); - m_wiz->m_lineList[i].append(m_wiz->m_fieldDelimiterCharacter + txt); - } - - QTableWidgetItem *item = new QTableWidgetItem; - item->setText(txt); - m_wiz->ui->tableWidget->setItem(i, m_colTypeNum.value(ColumnFee), item); - } - if(isVisible()) - m_wiz->updateWindowSize(); + m_profile->m_minFee = text; } void InvestmentPage::clearFeeCol() { - if (!m_feeRate.isEmpty() && m_colTypeNum.value(ColumnFee) >= m_wiz->m_endColumn) { //delete fee colum, but only if it was generated - m_wiz->m_maxColumnCount--; - m_wiz->ui->tableWidget->setColumnCount(m_wiz->m_maxColumnCount); - int i = ui->comboBoxInv_feeCol->currentIndex(); - ui->comboBoxInv_feeCol->setCurrentIndex(-1); - ui->comboBoxInv_feeCol->removeItem(i); - if(isVisible()) - m_wiz->updateWindowSize(); + if (!m_profile->m_feeRate.isEmpty() && // if fee rate isn't empty... + m_profile->m_colTypeNum.value(ColumnFee) >= m_imp->m_file->m_columnCount - 1 && + !ui->m_feeCol->isEnabled()) { // ...and fee column is last... + --m_imp->m_file->m_columnCount; + m_imp->m_file->m_model->removeColumn(m_imp->m_file->m_columnCount); + int feeCol = ui->m_feeCol->currentIndex(); + ui->m_feeCol->setCurrentIndex(-1); + ui->m_feeCol->removeItem(feeCol); + m_dlg->updateWindowSize(); } - ui->comboBoxInv_feeCol->setEnabled(true); - ui->checkBoxInv_feeIsPercentage->setEnabled(true); - ui->checkBoxInv_feeIsPercentage->setChecked(false); + ui->m_feeCol->setEnabled(true); + ui->m_feeIsPercentage->setEnabled(true); + ui->m_feeIsPercentage->setChecked(false); } void InvestmentPage::resetComboBox(const columnTypeE comboBox) { switch (comboBox) { case ColumnAmount: - ui->comboBoxInv_amountCol->setCurrentIndex(-1); + ui->m_amountCol->setCurrentIndex(-1); break; case ColumnDate: - ui->comboBoxInv_dateCol->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); break; case ColumnFee: - ui->comboBoxInv_feeCol->setCurrentIndex(-1); + ui->m_feeCol->setCurrentIndex(-1); break; case ColumnMemo: - ui->comboBoxInv_memoCol->setCurrentIndex(-1); + ui->m_memoCol->setCurrentIndex(-1); break; case ColumnPrice: - ui->comboBoxInv_priceCol->setCurrentIndex(-1); + ui->m_priceCol->setCurrentIndex(-1); break; case ColumnQuantity: - ui->comboBoxInv_quantityCol->setCurrentIndex(-1); + ui->m_quantityCol->setCurrentIndex(-1); break; case ColumnType: - ui->comboBoxInv_typeCol->setCurrentIndex(-1); + ui->m_typeCol->setCurrentIndex(-1); break; case ColumnSymbol: - ui->comboBoxInv_symbolCol->setCurrentIndex(-1); + ui->m_symbolCol->setCurrentIndex(-1); break; case ColumnName: - ui->comboBoxInv_nameCol->setCurrentIndex(-1); + ui->m_nameCol->setCurrentIndex(-1); break; default: - KMessageBox::sorry(m_wiz, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections.", comboBox), i18n("CSV import")); + KMessageBox::sorry(m_dlg, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections.", comboBox), i18n("CSV import")); + } +} + +bool InvestmentPage::validateActionType() +{ + for (int row = m_profile->m_startLine; row <= m_profile->m_endLine; ++row) { + MyMoneyStatement::Transaction tr; + // process quantity field + int col = m_profile->m_colTypeNum.value(ColumnQuantity); + tr.m_shares = m_imp->processQuantityField(m_profile, row, col); + + // process price field + col = m_profile->m_colTypeNum.value(ColumnPrice); + tr.m_price = m_imp->processPriceField(m_profile, row, col); + + // process amount field + col = m_profile->m_colTypeNum.value(ColumnAmount); + tr.m_amount = m_imp->processAmountField(m_profile, row, col); + + // process type field + col = m_profile->m_colTypeNum.value(ColumnType); + tr.m_eAction = m_imp->processActionTypeField(m_profile, row, col); + + switch(m_imp->validateActionType(tr)) { + case InvalidActionValues: + KMessageBox::sorry(m_dlg, + i18n("The values in the columns you have selected\ndo not match any expected investment type.\n" + "Please check the fields in the current transaction,\nand also your selections."), + i18n("CSV import")); + return false; + break; + case NoActionType: + { + bool unknownType = false; + if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone) + unknownType = true; + + QStringList colList; + QStringList colHeaders; + for (int col = 0; col < m_imp->m_file->m_columnCount; ++col) { + colHeaders.append(m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col, ColumnInvalid), QString(i18nc("Unused column", "Unused")))); + colList.append(m_imp->m_file->m_model->item(row, col)->text()); + } + QList validActionTypes = m_imp->createValidActionTypes(tr); + TransactionDlg* transactionDlg = new TransactionDlg(colList, colHeaders, m_profile->m_colTypeNum.value(ColumnType), validActionTypes); + if (transactionDlg->exec() == QDialog::Rejected) { + KMessageBox::information(m_dlg, + i18n("
No valid action type found for this transaction.
" + "
Please check the parameters supplied.
"), + i18n("CSV import")); + return false; + } else + tr.m_eAction = transactionDlg->getActionType(); + delete transactionDlg; + + if (unknownType) { // type was unknown so store it + col = m_profile->m_colTypeNum.value(ColumnType); + m_profile->m_transactionNames[tr.m_eAction].append(m_imp->m_file->m_model->item(row, col)->text()); // store action type + } + } + default: + break; + } } + m_imp->m_isActionTypeValidated = true; + return true; } -bool InvestmentPage::validateSelectedColumn(int col, columnTypeE type) +bool InvestmentPage::validateSelectedColumn(const int col, const columnTypeE type) { - if (m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... - m_colNumType.remove(m_colTypeNum[type]); // ...if true remove 'type' assigned to this column 'number' + if (m_profile->m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... + m_profile->m_colNumType.remove(m_profile->m_colTypeNum.value(type)); // ...if true remove 'type' assigned to this column 'number' bool ret = true; if (col == -1) { // user only wanted to reset his column so allow him - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - } else if (m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned - KMessageBox::information(m_wiz, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", - m_colTypeName.value(m_colNumType[col]))); - resetComboBox(m_colNumType[col]); + m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' + } else if (m_profile->m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned + KMessageBox::information(m_dlg, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", + m_dlg->m_colTypeName.value(m_profile->m_colNumType.value(col)))); + resetComboBox(m_profile->m_colNumType[col]); resetComboBox(type); ret = false; } else { - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - m_colNumType[col] = type; // assign new 'type' to this column 'number' + m_profile->m_colTypeNum[type] = col; // assign new column 'number' to this 'type' + m_profile->m_colNumType[col] = type; // assign new 'type' to this column 'number' } emit completeChanged(); return ret; } bool InvestmentPage::validateMemoComboBox() { - if (m_wiz->m_memoColList.count() == 0) + if (m_profile->m_memoColList.count() == 0) return true; - for (int i = 0; i < ui->comboBoxInv_memoCol->count(); ++i) + for (int i = 0; i < ui->m_memoCol->count(); ++i) { - QString txt = ui->comboBoxInv_memoCol->itemText(i); + QString txt = ui->m_memoCol->itemText(i); if (txt.contains(QChar(QLatin1Char('*')))) // check if text containing '*' belongs to valid column types - if (m_colNumType.value(i) != ColumnName && - m_colNumType.value(i) != ColumnType) { - ui->comboBoxInv_memoCol->setItemText(i, QString().setNum(i + 1)); - m_wiz->m_memoColList.removeOne(i); + if (m_profile->m_colNumType.value(i) != ColumnName && + m_profile->m_colNumType.value(i) != ColumnType) { + ui->m_memoCol->setItemText(i, QString::number(i + 1)); + m_profile->m_memoColList.removeOne(i); return false; } } return true; } -bool InvestmentPage::createValidActionTypes(QList &validActionTypes, MyMoneyStatement::Transaction &tr) -{ - if (tr.m_shares.isPositive() && - tr.m_price.isPositive() && - !tr.m_amount.isZero()) - validActionTypes << MyMoneyStatement::Transaction::eaReinvestDividend << - MyMoneyStatement::Transaction::eaBuy << - MyMoneyStatement::Transaction::eaSell; - else if (tr.m_shares.isZero() && - tr.m_price.isZero() && - !tr.m_amount.isZero()) - validActionTypes << MyMoneyStatement::Transaction::eaCashDividend << - MyMoneyStatement::Transaction::eaInterest; - else if (tr.m_shares.isPositive() && - tr.m_price.isZero() && - tr.m_amount.isZero()) - validActionTypes << MyMoneyStatement::Transaction::eaShrsin << - MyMoneyStatement::Transaction::eaShrsout; - else - return false; - return true; -} - -void InvestmentPage::storeActionType(MyMoneyStatement::Transaction::EAction &actionType, const QString &userType) -{ - switch(actionType) { - case MyMoneyStatement::Transaction::eaBuy: - m_buyList << userType; - break; - case MyMoneyStatement::Transaction::eaSell: - m_sellList << userType; - break; - case MyMoneyStatement::Transaction::eaReinvestDividend: - m_reinvdivList << userType; - break; - case MyMoneyStatement::Transaction::eaCashDividend: - m_divXList << userType; - break; - case MyMoneyStatement::Transaction::eaInterest: - m_intIncList << userType; - break; - case MyMoneyStatement::Transaction::eaShrsin: - m_shrsinList << userType; - break; - case MyMoneyStatement::Transaction::eaShrsout: - m_shrsoutList << userType; - break; - default: - break; - } -} - -bool InvestmentPage::validateActionType(MyMoneyStatement::Transaction &tr, QStringList& colList) -{ - QList validActionTypes; - if (!createValidActionTypes(validActionTypes, tr)) { - KMessageBox::sorry(m_wiz, i18n("The values in the columns you have selected\ndo not match any expected investment type.\n" - "Please check the fields in the current transaction,\nand also your selections.") - , i18n("CSV import")); - return false; - } - - if (!validActionTypes.contains(tr.m_eAction)) { - bool unknownType = false; - if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone) - unknownType = true; - - QStringList colHeaders; - for (int i = 0; i < colList.count(); ++i) - colHeaders << m_colTypeName.value(m_colNumType.value(i, ColumnInvalid), QString(i18nc("Unused column", "Unused"))); - - TransactionDlg* transactionDlg = new TransactionDlg(colList, colHeaders, m_colTypeNum.value(ColumnType), validActionTypes); - if (transactionDlg->exec() == QDialog::Rejected) { - KMessageBox::information(m_wiz, i18n("
No valid action type found for this transaction.
" - "
Please check the parameters supplied.
")); - return false; - } else - tr.m_eAction = transactionDlg->getActionType(); - delete transactionDlg; - - if (unknownType) // type was unknown so store it - storeActionType(tr.m_eAction, colList.value(m_colTypeNum[ColumnType])); - } - return true; -} - -MyMoneyStatement::Transaction::EAction InvestmentPage::processActionType(QString& type) -{ - // most frequent action - for (QStringList::const_iterator it = m_buyList.constBegin(); it != m_buyList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaBuy; - - // second most frequent action - for (QStringList::const_iterator it = m_sellList.constBegin(); it != m_sellList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaSell; - - for (QStringList::const_iterator it = m_reinvdivList.constBegin(); it != m_reinvdivList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaReinvestDividend; - - // needs to be after reinvdiv - for (QStringList::const_iterator it = m_divXList.constBegin(); it != m_divXList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaCashDividend; - - for (QStringList::const_iterator it = m_intIncList.constBegin(); it != m_intIncList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaInterest; - - for (QStringList::const_iterator it = m_shrsinList.constBegin(); it != m_shrsinList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaShrsin; - - for (QStringList::const_iterator it = m_shrsoutList.constBegin(); it != m_shrsoutList.constEnd(); ++it) - if (type.contains(*it, Qt::CaseInsensitive)) - return MyMoneyStatement::Transaction::eaShrsout; - - return MyMoneyStatement::Transaction::eaNone; -} - -bool InvestmentPage::sortSecurities(QSet& onlySymbols, QSet& onlyNames, QMap& mapSymbolName) -{ - QList securityList = MyMoneyFile::instance()->securityList(); - int symbolCol = m_colTypeNum.value(ColumnSymbol); - int nameCol = m_colTypeNum.value(ColumnName); - - // sort by availability of symbol and name - for (int row = m_wiz->m_startLine - 1; row < m_wiz->m_endLine; ++row) { - QString symbol; - QString name; - if (symbolCol != -1) - symbol = m_wiz->ui->tableWidget->item(row, symbolCol)->text().trimmed(); - if (nameCol != -1) - name = m_wiz->ui->tableWidget->item(row, nameCol)->text().trimmed(); - - if (!symbol.isEmpty() && !name.isEmpty()) - mapSymbolName.insert(symbol, name); - else if (!symbol.isEmpty()) - onlySymbols.insert(symbol); - else if (!name.isEmpty()) - onlyNames.insert(name); - else - return false; - } - - // try to find names for symbols - for (QSet::iterator symbol = onlySymbols.begin(); symbol != onlySymbols.end();) { - QList filteredSecurities; - for (QList::ConstIterator secKMM = securityList.constBegin(); secKMM != securityList.constEnd(); ++secKMM) { - if ((*symbol).compare((*secKMM).tradingSymbol(), Qt::CaseInsensitive) == 0) - filteredSecurities << (*secKMM); // gather all securities that by matched by symbol - } - - if (filteredSecurities.count() == 1) { // single security matched by the symbol so... - mapSymbolName.insert(*symbol, filteredSecurities.first().name()); - symbol = onlySymbols.erase(symbol); // ...it's no longer unknown - } else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the symbol - // TODO: Ask user which security should we match to - mapSymbolName.insert(*symbol, filteredSecurities.first().name()); - symbol = onlySymbols.erase(symbol); - } else // no security matched, so leave it as unknown - ++symbol; - } - - // try to find symbols for names - for (QSet::iterator name = onlyNames.begin(); name != onlyNames.end();) { - QList filteredSecurities; - for (QList::ConstIterator secKMM = securityList.constBegin(); secKMM != securityList.constEnd(); ++secKMM) { - if ((*name).compare((*secKMM).name(), Qt::CaseInsensitive) == 0) - filteredSecurities << (*secKMM); // gather all securities that by matched by name - } - - if (filteredSecurities.count() == 1) { // single security matched by the name so... - mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name); - name = onlyNames.erase(name); // ...it's no longer unknown - } else if (!filteredSecurities.isEmpty()) { // multiple securities matched by the name - // TODO: Ask user which security should we match to - mapSymbolName.insert(filteredSecurities.first().tradingSymbol(), *name); - name = onlySymbols.erase(name); - } else // no security matched, so leave it as unknown - ++name; - } - return true; -} bool InvestmentPage::validateSecurities() { if (m_securitiesDlg.isNull() && - m_mapSymbolName.isEmpty()) { + m_imp->m_mapSymbolName.isEmpty()) { QSet onlySymbols; QSet onlyNames; - sortSecurities(onlySymbols, onlyNames, m_mapSymbolName); + m_imp->sortSecurities(onlySymbols, onlyNames, m_imp->m_mapSymbolName); if (!onlySymbols.isEmpty() || !onlyNames.isEmpty()) { m_securitiesDlg = new SecuritiesDlg; @@ -766,7 +557,7 @@ for (int row = 0; row < symbolTable->rowCount(); ++row) { QString symbol = symbolTable->item(row, 1)->text(); QString name = symbolTable->item(row, 2)->text(); - m_mapSymbolName.insert(symbol, name); + m_imp->m_mapSymbolName.insert(symbol, name); } delete m_securitiesDlg; } @@ -777,186 +568,43 @@ bool InvestmentPage::validateSecurity() { - if (!m_securitySymbol.isEmpty() && - !m_securityName.isEmpty()) - m_mapSymbolName.insert(m_securitySymbol, m_securityName); + if (!m_profile->m_securitySymbol.isEmpty() && + !m_profile->m_securityName.isEmpty()) + m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); MyMoneyFile* file = MyMoneyFile::instance(); if (m_securityDlg.isNull() && - (m_mapSymbolName.isEmpty() || - !(m_dontAsk && m_wiz->m_skipSetup))) { + (m_imp->m_mapSymbolName.isEmpty() || + !(m_profile->m_dontAsk && m_dlg->m_skipSetup))) { m_securityDlg = new SecurityDlg; - m_securityDlg->initializeSecurities(m_securitySymbol, m_securityName); - m_securityDlg->ui->cbDontAsk->setChecked(m_dontAsk); + m_securityDlg->initializeSecurities(m_profile->m_securitySymbol, m_profile->m_securityName); + m_securityDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk); } if (!m_securityDlg.isNull()) { if (m_securityDlg->exec() == QDialog::Rejected) { return false; } else { QString securityID = m_securityDlg->security(); if (!securityID.isEmpty()) { - m_securityName = file->security(securityID).name(); - m_securitySymbol = file->security(securityID).tradingSymbol(); + m_profile->m_securityName = file->security(securityID).name(); + m_profile->m_securitySymbol = file->security(securityID).tradingSymbol(); } else { - m_securityName = m_securityDlg->name(); - m_securitySymbol = m_securityDlg->symbol(); + m_profile->m_securityName = m_securityDlg->name(); + m_profile->m_securitySymbol = m_securityDlg->symbol(); } - m_dontAsk = m_securityDlg->dontAsk(); - m_mapSymbolName.clear(); - m_mapSymbolName.insert(m_securitySymbol, m_securityName); // probably we should check if security with given symbol and name exists... + m_profile->m_dontAsk = m_securityDlg->dontAsk(); + m_imp->m_mapSymbolName.clear(); + m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); // probably we should check if security with given symbol and name exists... delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother } } - if (m_mapSymbolName.isEmpty()) + if (m_imp->m_mapSymbolName.isEmpty()) return false; return true; } -void InvestmentPage::saveSettings() -{ - KConfigGroup profileNamesGroup(m_wiz->m_config, "ProfileNames"); - profileNamesGroup.writeEntry("Invest", m_wiz->m_profileList); - profileNamesGroup.writeEntry("PriorInvest", m_wiz->m_profileList.indexOf(m_wiz->m_profileName)); - profileNamesGroup.config()->sync(); - - KConfigGroup profilesGroup(m_wiz->m_config, QStringLiteral("Invest-") + m_wiz->m_profileName); - profilesGroup.writeEntry("DateFormat", m_wiz->m_dateFormatIndex); - profilesGroup.writeEntry("FieldDelimiter", m_wiz->m_fieldDelimiterIndex); - profilesGroup.writeEntry("DecimalSymbol", m_wiz->m_decimalSymbolIndex); - profilesGroup.writeEntry("PriceFraction", ui->comboBoxInv_priceFraction->currentIndex()); - profilesGroup.writeEntry("StartLine", m_wiz->m_startLine - 1); - profilesGroup.writeEntry("TrailerLines", m_wiz->m_trailerLines); - // The strings in these resource file lists may be edited, - // or expanded in the file by the user, to suit his needs. - - profilesGroup.writeEntry("ShrsinParam", m_shrsinList); - profilesGroup.writeEntry("DivXParam", m_divXList); - profilesGroup.writeEntry("IntIncParam", m_intIncList); - profilesGroup.writeEntry("BrokerageParam", m_brokerageList); - profilesGroup.writeEntry("ReinvdivParam", m_reinvdivList); - profilesGroup.writeEntry("BuyParam", m_buyList); - profilesGroup.writeEntry("SellParam", m_sellList); - profilesGroup.writeEntry("RemoveParam", m_shrsoutList); - - if (m_wiz->m_inFileName.startsWith(QLatin1Literal("/home/"))) { // replace /home/user with ~/ for brevity - QFileInfo fileInfo = QFileInfo(m_wiz->m_inFileName); - if (fileInfo.isFile()) - m_wiz->m_inFileName = fileInfo.absolutePath(); - m_wiz->m_inFileName = QStringLiteral("~/") + m_wiz->m_inFileName.section(QChar(QLatin1Char('/')), 3); - } - - profilesGroup.writeEntry("Directory", m_wiz->m_inFileName); - profilesGroup.writeEntry("Encoding", m_wiz->m_encodeIndex); - profilesGroup.writeEntry("DateCol", ui->comboBoxInv_dateCol->currentIndex()); - profilesGroup.writeEntry("PayeeCol", ui->comboBoxInv_typeCol->currentIndex()); - - QList list = m_wiz->m_memoColList; - int posn = 0; - if ((posn = list.indexOf(-1)) > -1) { - list.removeOne(-1); - } - profilesGroup.writeEntry("MemoCol", list); - profilesGroup.writeEntry("QuantityCol", ui->comboBoxInv_quantityCol->currentIndex()); - profilesGroup.writeEntry("AmountCol", ui->comboBoxInv_amountCol->currentIndex()); - profilesGroup.writeEntry("PriceCol", ui->comboBoxInv_priceCol->currentIndex()); - if (ui->comboBoxInv_feeCol->currentIndex() < m_wiz->m_endColumn) - profilesGroup.writeEntry("FeeCol", ui->comboBoxInv_feeCol->currentIndex()); - else - profilesGroup.writeEntry("FeeCol", QString()); - profilesGroup.writeEntry("SymbolCol", ui->comboBoxInv_symbolCol->currentIndex()); - profilesGroup.writeEntry("NameCol", ui->comboBoxInv_nameCol->currentIndex()); - profilesGroup.writeEntry("FeeIsPercentage", int(ui->checkBoxInv_feeIsPercentage->isChecked())); - profilesGroup.writeEntry("FeeRate", ui->lineEdit_feeRate->text()); - profilesGroup.writeEntry("MinFee", ui->lineEdit_minFee->text()); - - profilesGroup.writeEntry("SecurityName", m_securityName); - profilesGroup.writeEntry("SecuritySymbol", m_securitySymbol); - profilesGroup.writeEntry("DontAsk", int(m_dontAsk)); - profilesGroup.config()->sync(); -} - -void InvestmentPage::readSettings(const KSharedConfigPtr& config) -{ - for (int i = 0; i < m_wiz->m_profileList.count(); ++i) { - if (m_wiz->m_profileList[i] != m_wiz->m_profileName) - continue; - KConfigGroup profilesGroup(config, QStringLiteral("Invest-") + m_wiz->m_profileList[i]); - - m_wiz->m_inFileName = profilesGroup.readEntry("Directory", QString()); - - QStringList list = profilesGroup.readEntry("BuyParam", QStringList()); - if (!list.isEmpty()) - m_buyList = list; - list = profilesGroup.readEntry("ShrsinParam", QStringList()); - if (!list.isEmpty()) - m_shrsinList = list; - list = profilesGroup.readEntry("DivXParam", QStringList()); - if (!list.isEmpty()) - m_divXList = list; - list = profilesGroup.readEntry("IntIncParam", QStringList()); - if (!list.isEmpty()) - m_intIncList = list; - list = profilesGroup.readEntry("BrokerageParam", QStringList()); - if (!list.isEmpty()) - m_brokerageList = list; - list = profilesGroup.readEntry("ReinvdivParam", QStringList()); - if (!list.isEmpty()) - m_reinvdivList = list; - list = profilesGroup.readEntry("SellParam", QStringList()); - if (!list.isEmpty()) - m_sellList = list; - list = profilesGroup.readEntry("RemoveParam", QStringList()); - if (!list.isEmpty()) - m_shrsoutList = list; - - m_priceFraction = profilesGroup.readEntry("PriceFraction", 2); - m_colTypeNum[ColumnDate] = profilesGroup.readEntry("DateCol", -1); - m_colTypeNum[ColumnType] = profilesGroup.readEntry("PayeeCol", -1); //use for type col. - m_colTypeNum[ColumnPrice] = profilesGroup.readEntry("PriceCol", -1); - m_colTypeNum[ColumnQuantity] = profilesGroup.readEntry("QuantityCol", -1); - m_colTypeNum[ColumnAmount] = profilesGroup.readEntry("AmountCol", -1); - m_colTypeNum[ColumnName] = profilesGroup.readEntry("NameCol", -1); - m_colTypeNum[ColumnFee] = profilesGroup.readEntry("FeeCol", -1); - m_colTypeNum[ColumnSymbol] = profilesGroup.readEntry("SymbolCol", -1); - m_colTypeNum[ColumnMemo] = -1; // initialize, otherwise random data may go here - m_feeIsPercentage = profilesGroup.readEntry("FeeIsPercentage", 0); - m_feeRate = profilesGroup.readEntry("FeeRate", QString()); - m_minFee = profilesGroup.readEntry("MinFee", QString()); - - m_wiz->m_memoColList = profilesGroup.readEntry("MemoCol", QList()); - m_wiz->m_dateFormatIndex = profilesGroup.readEntry("DateFormat", -1); - m_wiz->m_textDelimiterIndex = profilesGroup.readEntry("TextDelimiter", 0); - m_wiz->m_fieldDelimiterIndex = profilesGroup.readEntry("FieldDelimiter", -1); - m_wiz->m_decimalSymbolIndex = profilesGroup.readEntry("DecimalSymbol", -1); - - if (m_wiz->m_decimalSymbolIndex != -1 && m_wiz->m_decimalSymbolIndex != 2) { - m_wiz->m_parse->setDecimalSymbolIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setDecimalSymbol(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_parse->setThousandsSeparatorIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_decimalSymbol = m_wiz->m_parse->decimalSymbol(m_wiz->m_decimalSymbolIndex); - } else - m_wiz->m_decimalSymbol.clear(); - - m_wiz->m_parse->setFieldDelimiterIndex(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_parse->setTextDelimiterIndex(m_wiz->m_textDelimiterIndex); - m_wiz->m_fieldDelimiterCharacter = m_wiz->m_parse->fieldDelimiterCharacter(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_textDelimiterCharacter = m_wiz->m_parse->textDelimiterCharacter(m_wiz->m_textDelimiterIndex); - m_wiz->m_startLine = profilesGroup.readEntry("StartLine", 0) + 1; - m_wiz->m_trailerLines = profilesGroup.readEntry("TrailerLines", 0); - m_wiz->m_encodeIndex = profilesGroup.readEntry("Encoding", 0); - - m_securityName = profilesGroup.readEntry("SecurityName", QString()); - m_securitySymbol = profilesGroup.readEntry("SecuritySymbol", QString()); - m_dontAsk = profilesGroup.readEntry("DontAsk", 0); - break; - } -} - -void InvestmentPage::makeQIF(MyMoneyStatement& st, QFile& file) +void InvestmentPage::makeQIF(MyMoneyStatement &st, QFile &file) { QTextStream out(&file); @@ -1050,223 +698,3 @@ buffer.clear(); } } - -bool InvestmentPage::createStatement(MyMoneyStatement& st) -{ - if (!st.m_listTransactions.isEmpty()) // don't create statement if there is one - return true; - st.m_eType = MyMoneyStatement::etInvestment; - if (m_wiz->m_autodetect.value(CSVWizard::AutoAccountInvest)) - m_wiz->detectAccount(st); - - if (m_colTypeNum.value(ColumnFee) >= m_wiz->m_endColumn) // fee column has not been calculated so do it now - slotCalculateFee(); - - for (int line = m_wiz->m_startLine - 1; line < m_wiz->m_endLine; ++line) - if (!processInvestLine(m_wiz->m_lineList[line], st)) // parse fields - return false; - - for (QMap::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) { - MyMoneyStatement::Security security; - security.m_strSymbol = it.key(); - security.m_strName = it.value(); - st.m_listSecurities << security; - } - return true; -} - -bool InvestmentPage::processInvestLine(const QString &line, MyMoneyStatement &st) -{ - MyMoneyStatement::Transaction tr; - m_columnList = m_wiz->m_parse->parseLine(line); // split line into fields - if (m_columnList.count() < m_wiz->m_endColumn) { - if (!m_wiz->m_accept) { - QString row = QString::number(m_wiz->m_row); - int ret = KMessageBox::questionYesNoCancel(m_wiz, i18n("
Row number %1 does not have the expected number of columns.
" - "
This might not be a problem, but it may be a header line.
" - "
You may accept all similar items, or just this one, or cancel.
", - row), i18n("CSV import"), - KGuiItem(i18n("Accept All")), - KGuiItem(i18n("Accept This")), - KGuiItem(i18n("Cancel"))); - if (ret == KMessageBox::Cancel) - return false; - if (ret == KMessageBox::Yes) - m_wiz->m_accept = true; - } - } - - int neededFieldsCount = 0; - QString memo; - QString txt; - - for (int i = 0; i < m_columnList.count(); ++i) { - m_columnList[i].trimmed().remove(m_wiz->m_textDelimiterCharacter); - } - - // process date field - if (m_colTypeNum.value(ColumnDate) != -1) { - ++neededFieldsCount; - txt = m_columnList[m_colTypeNum[ColumnDate]]; - tr.m_datePosted = m_wiz->m_convertDate->convertDate(txt); // Date column - if (tr.m_datePosted == QDate()) { - KMessageBox::sorry(m_wiz, i18n("
An invalid date has been detected during import.
" - "
'%1'
" - "Please check that you have set the correct date format,\n" - "
and start and end lines.
" - , txt), i18n("CSV import")); - return false; - } - } - - // process quantity field - if (m_colTypeNum.value(ColumnQuantity) != -1) { - ++neededFieldsCount; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnQuantity]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnQuantity]]; - txt.remove(QRegularExpression(QStringLiteral("+-"))); // remove unwanted sings in quantity - - if (txt.isEmpty()) - tr.m_shares = MyMoneyMoney(); - else - tr.m_shares = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - } - - // process price field - if (m_colTypeNum.value(ColumnPrice) != -1) { - ++neededFieldsCount; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnPrice]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnPrice]]; - if (txt.isEmpty()) - tr.m_price = MyMoneyMoney(); - else { - tr.m_price = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - tr.m_price *= MyMoneyMoney(m_priceFractionValue); - } - } - - // process amount field - if (m_colTypeNum.value(ColumnAmount) != -1) { - ++neededFieldsCount; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnAmount]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnAmount]]; - if (txt.startsWith(QChar(QLatin1Char('(')))) { // check if brackets notation is used for negative numbers - txt.remove(QRegularExpression(QStringLiteral("[()]"))); - txt.prepend(QChar(QLatin1Char('-'))); - } - - if (txt.isEmpty()) - tr.m_amount = MyMoneyMoney(); - else - tr.m_amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - } - - // process type field - if (m_colTypeNum.value(ColumnType) != -1) { - ++neededFieldsCount; - txt = m_columnList[m_colTypeNum[ColumnType]]; - tr.m_eAction = processActionType(txt); - if (!validateActionType(tr, m_columnList))// check if price, amount, quantity is appropriate for action type - return false; - } - - // process fee field - if (m_colTypeNum.value(ColumnFee) != -1) { - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnFee]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnFee]]; - if (txt.startsWith(QChar(QLatin1Char('(')))) // check if brackets notation is used for negative numbers - txt.remove(QRegularExpression(QStringLiteral("[()]"))); - - if (txt.isEmpty()) - tr.m_fees = MyMoneyMoney(); - else { - MyMoneyMoney fee(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - if (m_feeIsPercentage && m_feeRate.isEmpty()) // fee is percent - fee *= tr.m_amount / MyMoneyMoney(100); // as percentage - fee.abs(); - tr.m_fees = fee; - } - } - - // process symbol and name field - if (m_colTypeNum.value(ColumnSymbol) != -1) - tr.m_strSymbol = m_columnList[m_colTypeNum[ColumnSymbol]]; - if (m_colTypeNum.value(ColumnName) != -1 && - tr.m_strSymbol.isEmpty()) { // case in which symbol field is empty - txt = m_columnList[m_colTypeNum[ColumnName]]; - tr.m_strSymbol = m_mapSymbolName.key(txt); // it's all about getting the right symbol - } else if (!m_securitySymbol.isEmpty()) - tr.m_strSymbol = m_securitySymbol; - else - return false; - tr.m_strSecurity = m_mapSymbolName.value(tr.m_strSymbol); // take name from prepared names to avoid potential name mismatch - - // process memo field - if (m_colTypeNum.value(ColumnMemo) != -1) - memo.append(m_columnList[m_colTypeNum[ColumnMemo]]); - - for (int i = 0; i < m_wiz->m_memoColList.count(); ++i) { - if (m_wiz->m_memoColList[i] != m_colTypeNum[ColumnMemo]) { - if (!memo.isEmpty()) - memo.append(QChar(QLatin1Char('\n'))); - if (m_wiz->m_memoColList[i] < m_columnList.count()) - memo.append(m_columnList[m_wiz->m_memoColList[i]]); - } - } - tr.m_strMemo = memo; - - tr.m_strInterestCategory.clear(); // no special category - tr.m_strBrokerageAccount.clear(); // no brokerage account auto-detection - - if (neededFieldsCount <= 3) { - QString errMsg = i18n("
The columns selected are invalid.
" - "There must an amount or quantity fields, symbol or security name, plus date and type field."); - if (m_wiz->m_skipSetup) - errMsg += i18n("
You possibly need to check the start and end line settings, or reset 'Skip setup'.
"); - KMessageBox::sorry(m_wiz, errMsg); - return false; - } - - MyMoneyStatement::Split s1; - s1.m_amount = tr.m_amount; - s1.m_strMemo = tr.m_strMemo; - MyMoneyStatement::Split s2 = s1; - s2.m_amount = MyMoneyMoney(-s1.m_amount); - s2.m_accountId = m_wiz->m_csvUtil->checkCategory(tr.m_strInterestCategory, s1.m_amount, s2.m_amount); - - // deduct fees from amount - if (tr.m_eAction == MyMoneyStatement::Transaction::eaCashDividend || - tr.m_eAction == MyMoneyStatement::Transaction::eaSell || - tr.m_eAction == MyMoneyStatement::Transaction::eaInterest) - tr.m_amount -= tr.m_fees; - - else if (tr.m_eAction == MyMoneyStatement::Transaction::eaBuy) { - if (tr.m_amount.isPositive()) - tr.m_amount = -tr.m_amount; //if broker doesn't use minus sings for buy transactions, set it manually here - tr.m_amount -= tr.m_fees; - } else if (tr.m_eAction == MyMoneyStatement::Transaction::eaNone) - tr.m_listSplits.append(s2); - - st.m_listTransactions.append(tr); // Add the transaction to the statement - return true; -} diff --git a/kmymoney/plugins/csvimport/investmentwizardpage.ui b/kmymoney/plugins/csvimport/investmentwizardpage.ui --- a/kmymoney/plugins/csvimport/investmentwizardpage.ui +++ b/kmymoney/plugins/csvimport/investmentwizardpage.ui @@ -107,7 +107,7 @@
- + 0 @@ -123,7 +123,7 @@ - + 0 @@ -138,31 +138,6 @@ 5 - - - 0.01 - - - - - 0.10 - - - - - 1 - - - - - 10 - - - - - 100 - - @@ -204,7 +179,7 @@
- + 0 @@ -233,7 +208,7 @@ - + 0 @@ -249,7 +224,7 @@ - + 0 @@ -265,7 +240,7 @@ - + 0 @@ -280,7 +255,7 @@ - + 0 @@ -338,7 +313,7 @@ - + 0 @@ -388,7 +363,7 @@ - + 0 @@ -404,7 +379,7 @@ - + 0 @@ -420,7 +395,7 @@ - + 0 @@ -438,7 +413,7 @@ - + 0 @@ -458,7 +433,7 @@ - + 0 @@ -481,7 +456,7 @@ - + 0 @@ -522,7 +497,7 @@ - + 0 @@ -535,7 +510,7 @@ - + 0 diff --git a/kmymoney/plugins/csvimport/priceswizardpage.h b/kmymoney/plugins/csvimport/priceswizardpage.h --- a/kmymoney/plugins/csvimport/priceswizardpage.h +++ b/kmymoney/plugins/csvimport/priceswizardpage.h @@ -23,78 +23,41 @@ #include #include -#include #include // ---------------------------------------------------------------------------- // KDE Includes -#include - // ---------------------------------------------------------------------------- // Project Includes #include +#include // ---------------------------------------------------------------------------- +class PricesProfile; class SecurityDlg; class CurrenciesDlg; -class CSVWizard; -class PricesPage; namespace Ui { class PricesPage; } -class PricesPage : public QWizardPage +class PricesPage : public CSVWizardPage { Q_OBJECT public: - explicit PricesPage(QDialog *parent = 0); + explicit PricesPage(CSVWizard *dlg, CSVImporter *imp); ~PricesPage(); - Ui::PricesPage *ui; + Ui::PricesPage *ui; QVBoxLayout *m_pageLayout; - CSVWizard* m_wiz; - typedef enum:uchar { ColumnDate, ColumnPrice, - ColumnEmpty = 0xFE, ColumnInvalid = 0xFF - } columnTypeE; - - QMap m_colTypeNum; - QMap m_colNumType; - QMap m_colTypeName; - - void saveSettings(); - void readSettings(const KSharedConfigPtr &config); - void setParent(CSVWizard* dlg); - - /** - * This method feeds file buffer in prices lines parser. - */ - bool createStatement(MyMoneyStatement& st); - private: - QPointer m_securityDlg; - QPointer m_currenciesDlg; - - QMap m_mapSymbolName; - - QStringList m_columnList; - - QString m_priceFractionValue; - QString m_fromCurrency; - QString m_toCurrency; - QString m_securityName; - QString m_securitySymbol; - - int m_priceFraction; - int m_dontAsk; - void initializePage(); bool isComplete() const; bool validatePage(); @@ -106,13 +69,7 @@ * This method is called column on prices page is selected. * It sets m_colTypeNum, m_colNumType and runs column validation. */ - bool validateSelectedColumn(int col, columnTypeE type); - - /** - * This method is called when the user clicks 'Import'. - * It will evaluate an input line and append it to a statement. - */ - bool processPriceLine(const QString& line, MyMoneyStatement& st); + bool validateSelectedColumn(const int col, const columnTypeE type); /** * This method ensures that there is security for price import. @@ -124,13 +81,16 @@ */ bool validateCurrencies(); -public slots: + PricesProfile *m_profile; + + QPointer m_securityDlg; + QPointer m_currenciesDlg; private slots: - void slotDateColSelected(int col); - void slotPriceColSelected(int col); - void slotFractionChanged(int col); - void slotClearColumns(); + void dateColSelected(int col); + void priceColSelected(int col); + void fractionChanged(int col); + void clearColumns(); }; #endif // PRICESWIZARDPAGE_H diff --git a/kmymoney/plugins/csvimport/priceswizardpage.cpp b/kmymoney/plugins/csvimport/priceswizardpage.cpp --- a/kmymoney/plugins/csvimport/priceswizardpage.cpp +++ b/kmymoney/plugins/csvimport/priceswizardpage.cpp @@ -21,44 +21,43 @@ // QT Includes #include +#include // ---------------------------------------------------------------------------- // KDE Includes #include -#include // ---------------------------------------------------------------------------- // Project Includes -#include "kmymoney.h" #include "mymoneyfile.h" -#include "mymoneymoney.h" +#include "csvimporter.h" #include "csvwizard.h" -#include "convdate.h" -#include "csvutil.h" #include "securitydlg.h" #include "currenciesdlg.h" #include "ui_priceswizardpage.h" -#include "ui_csvwizard.h" - // ---------------------------------------------------------------------------- -PricesPage::PricesPage(QDialog *parent) : QWizardPage(parent), ui(new Ui::PricesPage) +PricesPage::PricesPage(CSVWizard *dlg, CSVImporter *imp) : + CSVWizardPage(dlg, imp), + ui(new Ui::PricesPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); - connect(ui->button_clear, SIGNAL(clicked()), this, SLOT(slotClearColumns())); + connect(ui->button_clear, SIGNAL(clicked()), this, SLOT(clearColumns())); + + m_profile = dynamic_cast(m_imp->m_profile); // initialize column names - m_colTypeName.insert(ColumnPrice, i18n("Price")); - m_colTypeName.insert(ColumnDate, i18n("Date")); + m_dlg->m_colTypeName.insert(ColumnPrice, i18n("Price")); + m_dlg->m_colTypeName.insert(ColumnDate, i18n("Date")); } PricesPage::~PricesPage() @@ -68,386 +67,188 @@ delete m_currenciesDlg; } -void PricesPage::setParent(CSVWizard* dlg) -{ - m_wiz = dlg; -} - void PricesPage::initializeComboBoxes() { // disable prices widgets allowing their initialization - disconnect(ui->comboBoxPrc_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - disconnect(ui->comboBoxPrc_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPriceColSelected(int))); - disconnect(ui->comboBoxPrc_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFractionChanged(int))); + disconnect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + disconnect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int))); + disconnect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int))); // clear all existing items before adding new ones - ui->comboBoxPrc_dateCol->clear(); - ui->comboBoxPrc_priceCol->clear(); + ui->m_dateCol->clear(); + ui->m_priceCol->clear(); QStringList columnNumbers; - for (int i = 0; i < m_wiz->m_endColumn; ++i) { - columnNumbers << QString::number(i + 1); - } + for (int i = 0; i < m_imp->m_file->m_columnCount; ++i) + columnNumbers.append(QString::number(i + 1)); // populate comboboxes with col # values - ui->comboBoxPrc_dateCol->addItems(columnNumbers); - ui->comboBoxPrc_priceCol->addItems(columnNumbers); + ui->m_dateCol->addItems(columnNumbers); + ui->m_priceCol->addItems(columnNumbers); - slotClearColumns(); // all comboboxes are set to 0 so set them to -1 - connect(ui->comboBoxPrc_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDateColSelected(int))); - connect(ui->comboBoxPrc_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPriceColSelected(int))); - connect(ui->comboBoxPrc_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(slotFractionChanged(int))); + clearColumns(); // all comboboxes are set to 0 so set them to -1 + connect(ui->m_dateCol, SIGNAL(currentIndexChanged(int)), this, SLOT(dateColSelected(int))); + connect(ui->m_priceCol, SIGNAL(currentIndexChanged(int)), this, SLOT(priceColSelected(int))); + connect(ui->m_priceFraction, SIGNAL(currentIndexChanged(int)), this, SLOT(fractionChanged(int))); } void PricesPage::initializePage() { - if (ui->comboBoxPrc_dateCol->count() != m_wiz->m_endColumn) + if (ui->m_dateCol->count() != m_imp->m_file->m_columnCount) initializeComboBoxes(); - ui->comboBoxPrc_dateCol->setCurrentIndex(m_colTypeNum.value(ColumnDate)); - ui->comboBoxPrc_priceCol->setCurrentIndex(m_colTypeNum.value(ColumnPrice)); - ui->comboBoxPrc_priceFraction->setCurrentIndex(m_priceFraction); + ui->m_dateCol->setCurrentIndex(m_imp->m_profile->m_colTypeNum.value(ColumnDate)); + ui->m_priceCol->setCurrentIndex(m_imp->m_profile->m_colTypeNum.value(ColumnPrice)); + ui->m_priceFraction->blockSignals(true); + foreach (const auto priceFraction, m_imp->m_priceFractions) + ui->m_priceFraction->addItem(QString::number(priceFraction.toDouble(), 'g', 3)); + ui->m_priceFraction->blockSignals(false); + ui->m_priceFraction->setCurrentIndex(m_profile->m_priceFraction); + + QList layout; + layout << QWizard::Stretch << + QWizard::BackButton << + QWizard::NextButton << + QWizard::CancelButton; + wizard()->setButtonLayout(layout); } bool PricesPage::isComplete() const { - return ui->comboBoxPrc_dateCol->currentIndex() > -1 && - ui->comboBoxPrc_priceCol->currentIndex() > -1 && - ui->comboBoxPrc_priceFraction->currentIndex() > -1; + return ui->m_dateCol->currentIndex() > -1 && + ui->m_priceCol->currentIndex() > -1 && + ui->m_priceFraction->currentIndex() > -1; } bool PricesPage::validatePage() { - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) - return validateCurrencies(); - else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) - return validateSecurity(); - return false; + switch (m_imp->m_profile->type()) { + case ProfileCurrencyPrices: + return validateCurrencies(); + case ProfileStockPrices: + return validateSecurity(); + default: + return false; + } } -void PricesPage::slotDateColSelected(int col) +void PricesPage::dateColSelected(int col) { validateSelectedColumn(col, ColumnDate); } -void PricesPage::slotPriceColSelected(int col) +void PricesPage::priceColSelected(int col) { validateSelectedColumn(col, ColumnPrice); } -void PricesPage::slotFractionChanged(int col) +void PricesPage::fractionChanged(int col) { - m_priceFraction = col; - m_priceFractionValue = ui->comboBoxPrc_priceFraction->itemText(col); + m_profile->m_priceFraction = col; emit completeChanged(); } -void PricesPage::slotClearColumns() +void PricesPage::clearColumns() { - ui->comboBoxPrc_dateCol->setCurrentIndex(-1); - ui->comboBoxPrc_priceCol->setCurrentIndex(-1); - ui->comboBoxPrc_priceFraction->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); + ui->m_priceCol->setCurrentIndex(-1); + ui->m_priceFraction->setCurrentIndex(-1); } void PricesPage::resetComboBox(const columnTypeE comboBox) { switch (comboBox) { case ColumnDate: - ui->comboBoxPrc_dateCol->setCurrentIndex(-1); + ui->m_dateCol->setCurrentIndex(-1); break; case ColumnPrice: - ui->comboBoxPrc_priceCol->setCurrentIndex(-1); + ui->m_priceCol->setCurrentIndex(-1); break; default: - KMessageBox::sorry(m_wiz, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections.", comboBox), i18n("CSV import")); + KMessageBox::sorry(m_dlg, i18n("
Field name not recognised.
'%1'
Please re-enter your column selections.", comboBox), i18n("CSV import")); } } -bool PricesPage::validateSelectedColumn(int col, columnTypeE type) +bool PricesPage::validateSelectedColumn(const int col, const columnTypeE type) { - if (m_colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... - m_colNumType.remove(m_colTypeNum[type]); // ...if true remove 'type' assigned to this column 'number' + QMap &colTypeNum = m_imp->m_profile->m_colTypeNum; + QMap &colNumType = m_imp->m_profile->m_colNumType; + + if (colTypeNum.value(type) != -1) // check if this 'type' has any column 'number' assigned... + colNumType.remove(colTypeNum.value(type)); // ...if true remove 'type' assigned to this column 'number' bool ret = true; if (col == -1) { // user only wanted to reset his column so allow him - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - } else if (m_colNumType.contains(col)) { // if this column 'number' has already 'type' assigned - KMessageBox::information(m_wiz, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", - m_colTypeName.value(m_colNumType[col]))); - resetComboBox(m_colNumType[col]); + colTypeNum[type] = col; // assign new column 'number' to this 'type' + } else if (colNumType.contains(col)) { // if this column 'number' has already 'type' assigned + KMessageBox::information(m_dlg, i18n("The '%1' field already has this column selected.
Please reselect both entries as necessary.
", + m_dlg->m_colTypeName.value(colNumType.value(col)))); + resetComboBox(colNumType.value(col)); resetComboBox(type); ret = false; } else { - m_colTypeNum[type] = col; // assign new column 'number' to this 'type' - m_colNumType[col] = type; // assign new 'type' to this column 'number' + colTypeNum[type] = col; // assign new column 'number' to this 'type' + colNumType[col] = type; // assign new 'type' to this column 'number' } emit completeChanged(); return ret; } bool PricesPage::validateCurrencies() { if ((m_currenciesDlg.isNull() || - m_fromCurrency.isEmpty() || - m_fromCurrency.isEmpty()) && - !(m_dontAsk && m_wiz->m_skipSetup)) { + !m_imp->validateCurrencies(m_profile)) && + !(m_profile->m_dontAsk && m_dlg->m_skipSetup)) { m_currenciesDlg = new CurrenciesDlg; - m_currenciesDlg->initializeCurrencies(m_fromCurrency, m_toCurrency); - m_currenciesDlg->ui->cbDontAsk->setChecked(m_dontAsk); + m_currenciesDlg->initializeCurrencies(m_profile->m_currencySymbol, m_profile->m_securitySymbol); + m_currenciesDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk); } if (!m_currenciesDlg.isNull()) { if (m_currenciesDlg->exec() == QDialog::Rejected) { return false; } else { - m_fromCurrency = m_currenciesDlg->fromCurrency(); - m_toCurrency = m_currenciesDlg->toCurrency(); - m_dontAsk = m_currenciesDlg->dontAsk(); + m_profile->m_currencySymbol = m_currenciesDlg->fromCurrency(); + m_profile->m_securitySymbol = m_currenciesDlg->toCurrency(); + m_profile->m_dontAsk = m_currenciesDlg->dontAsk(); delete m_currenciesDlg; } } return true; } bool PricesPage::validateSecurity() { - if (!m_securitySymbol.isEmpty() && - !m_securityName.isEmpty()) - m_mapSymbolName.insert(m_securitySymbol, m_securityName); + if (m_imp->validateSecurity(m_profile)) + m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); MyMoneyFile* file = MyMoneyFile::instance(); if (m_securityDlg.isNull() && - (m_mapSymbolName.isEmpty() || - !(m_dontAsk && m_wiz->m_skipSetup))) { + (m_imp->m_mapSymbolName.isEmpty() || + !(m_profile->m_dontAsk && m_dlg->m_skipSetup))) { m_securityDlg = new SecurityDlg; - m_securityDlg->initializeSecurities(m_securitySymbol, m_securityName); - m_securityDlg->ui->cbDontAsk->setChecked(m_dontAsk); + m_securityDlg->initializeSecurities(m_profile->m_securitySymbol, m_profile->m_securityName); + m_securityDlg->ui->cbDontAsk->setChecked(m_profile->m_dontAsk); } if (!m_securityDlg.isNull()) { if (m_securityDlg->exec() == QDialog::Rejected) { return false; } else { QString securityID = m_securityDlg->security(); if (!securityID.isEmpty()) { - m_securityName = file->security(securityID).name(); - m_securitySymbol = file->security(securityID).tradingSymbol(); + m_profile->m_securityName = file->security(securityID).name(); + m_profile->m_securitySymbol = file->security(securityID).tradingSymbol(); } else { - m_securityName = m_securityDlg->name(); - m_securitySymbol = m_securityDlg->symbol(); + m_profile->m_securityName = m_securityDlg->name(); + m_profile->m_securitySymbol = m_securityDlg->symbol(); } - m_dontAsk = m_securityDlg->dontAsk(); - m_mapSymbolName.clear(); - m_mapSymbolName.insert(m_securitySymbol, m_securityName); // probably we should check if security with given symbol and name exists... - delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother + m_profile->m_dontAsk = m_securityDlg->dontAsk(); + m_imp->m_mapSymbolName.clear(); + m_imp->m_mapSymbolName.insert(m_profile->m_securitySymbol, m_profile->m_securityName); // probably we should check if security with given symbol and name exists... + delete m_securityDlg; // ...but KMM allows creating duplicates, so don't bother } } - if (m_mapSymbolName.isEmpty()) + if (m_imp->m_mapSymbolName.isEmpty()) return false; return true; } - -void PricesPage::saveSettings() -{ - KConfigGroup profileNamesGroup(m_wiz->m_config, "ProfileNames"); - profileNamesGroup.writeEntry("Prices", m_wiz->m_profileList); - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) - profileNamesGroup.writeEntry("PriorCPrices", m_wiz->m_profileList.indexOf(m_wiz->m_profileName)); - else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) - profileNamesGroup.writeEntry("PriorSPrices", m_wiz->m_profileList.indexOf(m_wiz->m_profileName)); - - profileNamesGroup.config()->sync(); - - KConfigGroup profilesGroup; - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) - profilesGroup = KConfigGroup(m_wiz->m_config, QStringLiteral("CPrices-") + m_wiz->m_profileName); - else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) - profilesGroup = KConfigGroup(m_wiz->m_config, QStringLiteral("SPrices-") + m_wiz->m_profileName); - - profilesGroup.writeEntry("DateFormat", m_wiz->m_dateFormatIndex); - profilesGroup.writeEntry("FieldDelimiter", m_wiz->m_fieldDelimiterIndex); - profilesGroup.writeEntry("DecimalSymbol", m_wiz->m_decimalSymbolIndex); - profilesGroup.writeEntry("PriceFraction", ui->comboBoxPrc_priceFraction->currentIndex()); - profilesGroup.writeEntry("StartLine", m_wiz->m_startLine - 1); - profilesGroup.writeEntry("TrailerLines", m_wiz->m_trailerLines); - - if (m_wiz->m_inFileName.startsWith(QLatin1Literal("/home/"))) { // replace /home/user with ~/ for brevity - QFileInfo fileInfo = QFileInfo(m_wiz->m_inFileName); - if (fileInfo.isFile()) - m_wiz->m_inFileName = fileInfo.absolutePath(); - m_wiz->m_inFileName = QStringLiteral("~/") + m_wiz->m_inFileName.section(QChar(QLatin1Char('/')), 3); - } - - profilesGroup.writeEntry("Directory", m_wiz->m_inFileName); - profilesGroup.writeEntry("Encoding", m_wiz->m_encodeIndex); - profilesGroup.writeEntry("DateCol", ui->comboBoxPrc_dateCol->currentIndex()); - profilesGroup.writeEntry("PriceCol", ui->comboBoxPrc_priceCol->currentIndex()); - - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) { - profilesGroup.writeEntry("FromCurrency", m_fromCurrency); - profilesGroup.writeEntry("ToCurrency", m_toCurrency); - } else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) { - profilesGroup.writeEntry("SecurityName", m_securityName); - profilesGroup.writeEntry("SecuritySymbol", m_securitySymbol); - } - profilesGroup.writeEntry("DontAsk", int(m_dontAsk)); - profilesGroup.config()->sync(); -} - -void PricesPage::readSettings(const KSharedConfigPtr& config) -{ - for (int i = 0; i < m_wiz->m_profileList.count(); ++i) { - if (m_wiz->m_profileList[i] != m_wiz->m_profileName) - continue; - KConfigGroup profilesGroup; - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) - profilesGroup = KConfigGroup(config, QStringLiteral("CPrices-") + m_wiz->m_profileList[i]); - else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) - profilesGroup = KConfigGroup(config, QStringLiteral("SPrices-") + m_wiz->m_profileList[i]); - - m_wiz->m_inFileName = profilesGroup.readEntry("Directory", QString()); - - m_priceFraction = profilesGroup.readEntry("PriceFraction", 2); - m_colTypeNum[ColumnDate] = profilesGroup.readEntry("DateCol", -1); - m_colTypeNum[ColumnPrice] = profilesGroup.readEntry("PriceCol", -1); - - m_wiz->m_dateFormatIndex = profilesGroup.readEntry("DateFormat", -1); - m_wiz->m_textDelimiterIndex = profilesGroup.readEntry("TextDelimiter", 0); - m_wiz->m_fieldDelimiterIndex = profilesGroup.readEntry("FieldDelimiter", -1); - m_wiz->m_decimalSymbolIndex = profilesGroup.readEntry("DecimalSymbol", -1); - - if (m_wiz->m_decimalSymbolIndex != -1 && m_wiz->m_decimalSymbolIndex != 2) { - m_wiz->m_parse->setDecimalSymbolIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setDecimalSymbol(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_parse->setThousandsSeparatorIndex(m_wiz->m_decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(m_wiz->m_decimalSymbolIndex); - - m_wiz->m_decimalSymbol = m_wiz->m_parse->decimalSymbol(m_wiz->m_decimalSymbolIndex); - } else - m_wiz->m_decimalSymbol.clear(); - - m_wiz->m_parse->setFieldDelimiterIndex(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_parse->setTextDelimiterIndex(m_wiz->m_textDelimiterIndex); - m_wiz->m_fieldDelimiterCharacter = m_wiz->m_parse->fieldDelimiterCharacter(m_wiz->m_fieldDelimiterIndex); - m_wiz->m_textDelimiterCharacter = m_wiz->m_parse->textDelimiterCharacter(m_wiz->m_textDelimiterIndex); - m_wiz->m_startLine = profilesGroup.readEntry("StartLine", 0) + 1; - m_wiz->m_trailerLines = profilesGroup.readEntry("TrailerLines", 0); - m_wiz->m_encodeIndex = profilesGroup.readEntry("Encoding", 0); - - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) { - m_fromCurrency = profilesGroup.readEntry("FromCurrency", QString()); - m_toCurrency = profilesGroup.readEntry("ToCurrency", QString()); - } else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) { - m_securityName = profilesGroup.readEntry("SecurityName", QString()); - m_securitySymbol = profilesGroup.readEntry("SecuritySymbol", QString()); - } - m_dontAsk = profilesGroup.readEntry("DontAsk", 0); - break; - } -} - -bool PricesPage::createStatement(MyMoneyStatement& st) -{ - if (!st.m_listPrices.isEmpty()) // don't create statement if there is one - return true; - st.m_eType = MyMoneyStatement::etNone; - - for (int line = m_wiz->m_startLine - 1; line < m_wiz->m_endLine; ++line) - if (!processPriceLine(m_wiz->m_lineList[line], st)) // parse fields - return false; - - for (QMap::const_iterator it = m_mapSymbolName.cbegin(); it != m_mapSymbolName.cend(); ++it) { - MyMoneyStatement::Security security; - security.m_strSymbol = it.key(); - security.m_strName = it.value(); - st.m_listSecurities << security; - } - return true; -} - -bool PricesPage::processPriceLine(const QString &line, MyMoneyStatement &st) -{ - MyMoneyStatement::Price pr; - - m_columnList = m_wiz->m_parse->parseLine(line); // split line into fields - if (m_columnList.count() < m_wiz->m_endColumn) { - if (!m_wiz->m_accept) { - QString row = QString::number(m_wiz->m_row); - int ret = KMessageBox::questionYesNoCancel(m_wiz, i18n("
Row number %1 does not have the expected number of columns.
" - "
This might not be a problem, but it may be a header line.
" - "
You may accept all similar items, or just this one, or cancel.
", - row), i18n("CSV import"), - KGuiItem(i18n("Accept All")), - KGuiItem(i18n("Accept This")), - KGuiItem(i18n("Cancel"))); - if (ret == KMessageBox::Cancel) - return false; - if (ret == KMessageBox::Yes) - m_wiz->m_accept = true; - } - } - - int neededFieldsCount = 0; - QString txt; - - for (int i = 0; i < m_columnList.count(); ++i) { - m_columnList[i].trimmed().remove(m_wiz->m_textDelimiterCharacter); - } - - // process date field - if (m_colTypeNum.value(ColumnDate) != -1) { - ++neededFieldsCount; - txt = m_columnList[m_colTypeNum[ColumnDate]]; - pr.m_date = m_wiz->m_convertDate->convertDate(txt); // Date column - if (pr.m_date == QDate()) { - KMessageBox::sorry(m_wiz, i18n("
An invalid date has been detected during import.
" - "
'%1'
" - "Please check that you have set the correct date format,\n" - "
and start and end lines.
" - , txt), i18n("CSV import")); - return false; - } - } - - // process price field - if (m_colTypeNum.value(ColumnPrice) != -1) { - ++neededFieldsCount; - if (m_wiz->m_decimalSymbolIndex == 2) { - int decimalSymbolIndex = m_wiz->m_decimalSymbolIndexMap.value(m_colTypeNum[ColumnPrice]); - m_wiz->m_parse->setDecimalSymbol(decimalSymbolIndex); - m_wiz->m_parse->setThousandsSeparator(decimalSymbolIndex); - } - - txt = m_columnList[m_colTypeNum[ColumnPrice]]; - if (txt.isEmpty()) - pr.m_amount = MyMoneyMoney(); - else { - pr.m_amount = MyMoneyMoney(m_wiz->m_parse->possiblyReplaceSymbol(txt)); - pr.m_amount *= MyMoneyMoney(m_priceFractionValue); - } - } - - if (m_wiz->m_profileType == CSVWizard::ProfileCurrencyPrices) { - if (m_fromCurrency.isEmpty() || m_toCurrency.isEmpty()) - return false; - pr.m_strSecurity = m_fromCurrency; - pr.m_strCurrency = m_toCurrency; - } - else if (m_wiz->m_profileType == CSVWizard::ProfileStockPrices) { - if (m_mapSymbolName.isEmpty()) - return false; - pr.m_strSecurity = m_mapSymbolName.first(); - } - - if (neededFieldsCount < 2) { - QString errMsg = i18n("
The columns selected are invalid.
" - "There must an amount or quantity fields, symbol or security name, plus date and type field."); - if (m_wiz->m_skipSetup) - errMsg += i18n("
You possibly need to check the start and end line settings, or reset 'Skip setup'.
"); - KMessageBox::sorry(m_wiz, errMsg); - return false; - } - - st.m_listPrices.append(pr); // Add price to the statement - return true; -} diff --git a/kmymoney/plugins/csvimport/priceswizardpage.ui b/kmymoney/plugins/csvimport/priceswizardpage.ui --- a/kmymoney/plugins/csvimport/priceswizardpage.ui +++ b/kmymoney/plugins/csvimport/priceswizardpage.ui @@ -90,7 +90,7 @@
- + 0 @@ -108,35 +108,10 @@ 5 - - - 0.01 - - - - - 0.10 - - - - - 1 - - - - - 10 - - - - - 100 - - - + Select column containing date field @@ -152,7 +127,7 @@ - + true @@ -174,7 +149,7 @@ - + 0 @@ -190,7 +165,7 @@ - + 0 @@ -206,7 +181,7 @@ - + 16777215 diff --git a/kmymoney/plugins/csvimport/rowswizardpage.ui b/kmymoney/plugins/csvimport/rowswizardpage.ui --- a/kmymoney/plugins/csvimport/rowswizardpage.ui +++ b/kmymoney/plugins/csvimport/rowswizardpage.ui @@ -100,7 +100,7 @@ - + Start line @@ -110,7 +110,7 @@ - + End line @@ -120,7 +120,7 @@ - + 0 @@ -137,7 +137,7 @@ - + 0 diff --git a/kmymoney/plugins/csvimport/separatorwizardpage.ui b/kmymoney/plugins/csvimport/separatorwizardpage.ui --- a/kmymoney/plugins/csvimport/separatorwizardpage.ui +++ b/kmymoney/plugins/csvimport/separatorwizardpage.ui @@ -101,7 +101,7 @@ - + Field Delimiter @@ -111,17 +111,14 @@ - - + + 0 0 - - -1 - comma (,) @@ -144,7 +141,7 @@ - + @@ -157,8 +154,8 @@ - - + + 0 @@ -169,9 +166,6 @@ Select field delimiter/separator character. This will reset any existing field choices. - - -1 - quote (") @@ -184,6 +178,16 @@ + + + + + + + Encoding + + + diff --git a/kmymoney/plugins/interfaces/kmmstatementinterface.h b/kmymoney/plugins/interfaces/kmmstatementinterface.h --- a/kmymoney/plugins/interfaces/kmmstatementinterface.h +++ b/kmymoney/plugins/interfaces/kmmstatementinterface.h @@ -51,7 +51,7 @@ /** * This method imports a MyMoneyStatement into the engine */ - bool import(const MyMoneyStatement& s); + bool import(const MyMoneyStatement& s, bool silent = false); /** * This method returns the account for a given @a key - @a value pair. diff --git a/kmymoney/plugins/interfaces/kmmstatementinterface.cpp b/kmymoney/plugins/interfaces/kmmstatementinterface.cpp --- a/kmymoney/plugins/interfaces/kmmstatementinterface.cpp +++ b/kmymoney/plugins/interfaces/kmmstatementinterface.cpp @@ -38,10 +38,10 @@ { } -bool KMyMoneyPlugin::KMMStatementInterface::import(const MyMoneyStatement& s) +bool KMyMoneyPlugin::KMMStatementInterface::import(const MyMoneyStatement& s, bool silent) { qDebug("KMyMoneyPlugin::KMMStatementInterface::import start"); - return m_app->slotStatementImport(s); + return m_app->slotStatementImport(s, silent); } const MyMoneyAccount& KMyMoneyPlugin::KMMStatementInterface::account(const QString& key, const QString& value) const diff --git a/kmymoney/plugins/statementinterface.h b/kmymoney/plugins/statementinterface.h --- a/kmymoney/plugins/statementinterface.h +++ b/kmymoney/plugins/statementinterface.h @@ -51,7 +51,7 @@ /** * This method imports a MyMoneyStatement into the engine */ - virtual bool import(const MyMoneyStatement& s) = 0; + virtual bool import(const MyMoneyStatement& s, bool silent = false) = 0; /** * This method returns the account for a given @a key - @a value pair.