diff --git a/kmymoney/plugins/csv/import/csvimporter.cpp b/kmymoney/plugins/csv/import/csvimporter.cpp index 1b4a1019d..5c820b7d9 100644 --- a/kmymoney/plugins/csv/import/csvimporter.cpp +++ b/kmymoney/plugins/csv/import/csvimporter.cpp @@ -1,110 +1,111 @@ /* * Copyright 2010-2014 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz + * Copyright 2018 Thomas Baumgart * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "csvimporter.h" // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "core/csvimportercore.h" #include "csvwizard.h" #include "statementinterface.h" #include "viewinterface.h" CSVImporter::CSVImporter(QObject *parent, const QVariantList &args) : KMyMoneyPlugin::Plugin(parent, "csvimporter"/*must be the same as X-KDE-PluginInfo-Name*/) { Q_UNUSED(args); setComponentName("csvimporter", i18n("CSV importer")); setXMLFile("csvimporter.rc"); createActions(); // For information, announce that we have been loaded. qDebug("Plugins: csvimporter loaded"); } CSVImporter::~CSVImporter() { qDebug("Plugins: csvimporter unloaded"); } void CSVImporter::createActions() { const auto &kpartgui = QStringLiteral("file_import_csv"); auto importAction = actionCollection()->addAction(kpartgui); importAction->setText(i18n("CSV...")); connect(importAction, &QAction::triggered, this, &CSVImporter::startWizardRun); connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action(qPrintable(kpartgui)), &QAction::setEnabled); } QString CSVImporter::formatName() const { return QLatin1String("CSV"); } QString CSVImporter::formatFilenameFilter() const { return "*.csv"; } bool CSVImporter::isMyFormat(const QString& filename) const { // filename is considered a CSV file if it can be opened // and the filename ends in ".csv" (case does not matter). QFile f(filename); return filename.endsWith(QLatin1String(".csv"), Qt::CaseInsensitive) && f.open(QIODevice::ReadOnly | QIODevice::Text); } void CSVImporter::startWizardRun() { import(QString()); } bool CSVImporter::import(const QString& filename) { QPointer wizard = new CSVWizard(this); wizard->presetFilename(filename); auto rc = false; if ((wizard->exec() == QDialog::Accepted) && wizard) { rc = !statementInterface()->import(wizard->statement(), false).isEmpty(); } wizard->deleteLater(); return rc; } QString CSVImporter::lastError() const { return QString(); } K_PLUGIN_FACTORY_WITH_JSON(CSVImporterFactory, "csvimporter.json", registerPlugin();) #include "csvimporter.moc" diff --git a/kmymoney/plugins/csv/import/csvimporter.h b/kmymoney/plugins/csv/import/csvimporter.h index fccc0cf06..af87fdc69 100644 --- a/kmymoney/plugins/csv/import/csvimporter.h +++ b/kmymoney/plugins/csv/import/csvimporter.h @@ -1,96 +1,97 @@ /* * Copyright 2010-2014 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz + * Copyright 2018 Thomas Baumgart * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CSVIMPORTER_H #define CSVIMPORTER_H // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // QT Includes // Project Includes #include "kmymoneyplugin.h" class CSVImporter : public KMyMoneyPlugin::Plugin, public KMyMoneyPlugin::ImporterPlugin { Q_OBJECT Q_INTERFACES(KMyMoneyPlugin::ImporterPlugin) public: explicit CSVImporter(QObject *parent, const QVariantList &args); ~CSVImporter() override; /** * This method returns the english-language name of the format * this plugin imports, e.g. "CSV" * * @return QString Name of the format */ virtual QString formatName() const override; /** * This method returns the filename filter suitable for passing to * KFileDialog::setFilter(), e.g. "*.csv" which describes how * files of this format are likely to be named in the file system * * @return QString Filename filter string */ virtual QString formatFilenameFilter() const override; /** * This method returns whether this plugin is able to import * a particular file. * * @param filename Fully-qualified pathname to a file * * @return bool Whether the indicated file is importable by this plugin */ virtual bool isMyFormat(const QString& filename) const override; /** * Import a file * * @param filename File to import * * @return bool Whether the import was successful. */ virtual bool import(const QString& filename) override; /** * Returns the error result of the last import * * @return QString English-language name of the error encountered in the * last import, or QString() if it was successful. * */ virtual QString lastError() const override; private: bool m_silent; protected Q_SLOTS: void startWizardRun(); protected: void createActions(); }; #endif diff --git a/kmymoney/plugins/csv/import/csvwizard.cpp b/kmymoney/plugins/csv/import/csvwizard.cpp index 9066a96b6..77a186f6a 100644 --- a/kmymoney/plugins/csv/import/csvwizard.cpp +++ b/kmymoney/plugins/csv/import/csvwizard.cpp @@ -1,1118 +1,1119 @@ /* * Copyright 2015-2016 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz + * Copyright 2018 Thomas Baumgart * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "csvwizard.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "csvimporter.h" #include "core/csvutil.h" #include "core/convdate.h" #include "core/csvimportercore.h" #include "investmentwizardpage.h" #include "bankingwizardpage.h" #include "priceswizardpage.h" #include "icons/icons.h" #include "ui_csvwizard.h" #include "ui_introwizardpage.h" #include "ui_separatorwizardpage.h" #include "ui_rowswizardpage.h" #include "ui_formatswizardpage.h" using namespace Icons; CSVWizard::CSVWizard(CSVImporter *plugin) : ui(new Ui::CSVWizard), m_skipSetup(false), m_plugin(plugin), m_imp(new CSVImporterCore), m_wiz(new QWizard) { ui->setupUi(this); ui->tableView->setModel(m_imp->m_file->m_model); readWindowSize(CSVImporterCore::configFile()); m_wiz->setWizardStyle(QWizard::ClassicStyle); ui->horizontalLayout->addWidget(m_wiz); m_curId = -1; m_lastId = -1; m_wiz->installEventFilter(this); // event filter for escape key presses m_wiz->button(QWizard::BackButton)->setIcon(Icons::get(Icon::ArrowLeft)); m_wiz->button(QWizard::CancelButton)->setIcon(Icons::get(Icon::DialogCancel)); m_wiz->button(QWizard::FinishButton)->setIcon(Icons::get(Icon::KMyMoney)); m_wiz->button(QWizard::CustomButton1)->setIcon(Icons::get(Icon::FileArchiver)); m_wiz->button(QWizard::CustomButton2)->setIcon(Icons::get(Icon::InvestApplet)); m_wiz->button(QWizard::NextButton)->setIcon(Icons::get(Icon::ArrowRight)); 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_pageRows = new RowsPage(this, m_imp); m_wiz->setPage(PageRows, m_pageRows); m_pageFormats = new FormatsPage(this, m_imp); m_wiz->setPage(PageFormats, m_pageFormats); 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); connect(m_wiz->button(QWizard::FinishButton), &QAbstractButton::clicked, this, &CSVWizard::importClicked); connect(m_wiz->button(QWizard::CancelButton), &QAbstractButton::clicked, this, &CSVWizard::reject); connect(m_wiz->button(QWizard::CustomButton1), &QAbstractButton::clicked, this, &CSVWizard::fileDialogClicked); connect(m_wiz->button(QWizard::CustomButton2), &QAbstractButton::clicked, this, &CSVWizard::saveAsQIFClicked); connect(m_wiz, SIGNAL(currentIdChanged(int)), this, SLOT(slotIdChanged(int))); ui->tableView->setWordWrap(false); m_vScrollBar = ui->tableView->verticalScrollBar(); m_vScrollBar->setTracking(false); 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); m_wiz->setSideWidget(ui->wizardBox); show(); } CSVWizard::~CSVWizard() { delete m_imp; delete ui; } void CSVWizard::presetFilename(const QString& name) { m_fileName = name; } void CSVWizard::showStage() { QString str = ui->label_intro->text(); ui->label_intro->setText(QString::fromLatin1("%1").arg(str)); } void CSVWizard::readWindowSize(const KSharedConfigPtr& config) { KConfigGroup miscGroup(config, CSVImporterCore::m_confMiscName); m_initialWidth = miscGroup.readEntry(CSVImporterCore::m_miscSettingsConfName.value(ConfWidth), 800); m_initialHeight = miscGroup.readEntry(CSVImporterCore::m_miscSettingsConfName.value(ConfHeight), 400); } void CSVWizard::saveWindowSize(const KSharedConfigPtr& config) { KConfigGroup miscGroup(config, CSVImporterCore::m_confMiscName); m_initialHeight = this->geometry().height(); m_initialWidth = this->geometry().width(); miscGroup.writeEntry(CSVImporterCore::m_miscSettingsConfName.value(ConfWidth), m_initialWidth); miscGroup.writeEntry(CSVImporterCore::m_miscSettingsConfName.value(ConfHeight), m_initialHeight); miscGroup.sync(); } void CSVWizard::slotIdChanged(int id) { QString txt; m_lastId = m_curId; m_curId = id; if ((m_lastId == -1) || (m_curId == -1)) { return; } txt = m_stageLabels[m_lastId]->text(); txt.remove(QRegularExpression(QStringLiteral("[/]"))); m_stageLabels[m_lastId]->setText(txt); txt = m_stageLabels[m_curId]->text(); txt = QString::fromLatin1("%1").arg(txt); m_stageLabels[m_curId]->setText(txt); } void CSVWizard::clearColumnsBackground(const int col) { QList columnList; columnList << col; clearColumnsBackground(columnList); } void CSVWizard::clearColumnsBackground(const QList &columnList) { 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() { 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() { QStandardItemModel *model = m_imp->m_file->m_model; int rowCount = model->rowCount(); int colCount = model->columnCount(); QBrush brush; QBrush brushText; 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 j = 0; j < colCount; ++j) { model->item(i, j)->setBackground(brush); model->item(i, j)->setForeground(brushText); } } } void CSVWizard::updateWindowSize() { QTableView *table = this->ui->tableView; table->resizeColumnsToContents(); this->repaint(); QRect screen = QApplication::desktop()->availableGeometry(); //get available screen size QRect wizard = this->frameGeometry(); //get current wizard size 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(); } int newHeight = table->contentsMargins().top() + table->contentsMargins().bottom() + table->verticalHeader()->length() + table->horizontalHeader()->height() + (wizard.height() - table->height()); 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 newHeight += table->horizontalScrollBar()->height(); } // limit wizard size to screen size if (newHeight > screen.height()) newHeight = screen.height(); if (newWidth > screen.width()) newWidth = screen.width(); // 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; newWidth -= (wizard.width() - this->geometry().width()); // remove window frame newHeight -= (wizard.height() - this->geometry().height()); wizard.setWidth(newWidth); wizard.setHeight(newHeight); wizard.moveTo((screen.width() - wizard.width()) / 2, (screen.height() - wizard.height()) / 2); this->setGeometry(wizard); } 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; } } } return false; } void CSVWizard::saveSettings() const { m_imp->m_profile->m_lastUsedDirectory = m_imp->m_file->m_inFileName; m_imp->m_profile->writeSettings(CSVImporterCore::configFile()); m_imp->profilesAction(m_imp->m_profile->type(), ProfileAction::UpdateLastUsed, m_imp->m_profile->m_profileName, m_imp->m_profile->m_profileName); } void CSVWizard::slotClose() { saveSettings(); m_st = MyMoneyStatement(); // nothing imported accept(); } void CSVWizard::fileDialogClicked() { m_imp->profileFactory(m_pageIntro->m_profileType, m_pageIntro->ui->m_profiles->currentText()); bool profileExists = m_imp->m_profile->readSettings(CSVImporterCore::configFile()); if (!m_fileName.isEmpty()) { if (!m_imp->m_file->getInFileName(m_fileName)) { if (!m_imp->m_file->getInFileName(m_imp->m_profile->m_lastUsedDirectory)) { return; } } } else if (!m_imp->m_file->getInFileName(m_imp->m_profile->m_lastUsedDirectory)) return; saveWindowSize(CSVImporterCore::configFile()); m_imp->m_file->readFile(m_imp->m_profile); m_imp->m_file->setupParser(m_imp->m_profile); m_skipSetup = m_pageIntro->ui->m_skipSetup->isChecked(); switch(m_imp->m_profile->type()) { case Profile::Investment: if (!m_pageInvestment) { m_pageInvestment = new InvestmentPage(this, m_imp); m_wiz->setPage(CSVWizard::PageInvestment, m_pageInvestment); } break; case Profile::Banking: if (!m_pageBanking) { m_pageBanking = new BankingPage(this, m_imp); m_wiz->setPage(CSVWizard::PageBanking, m_pageBanking); } break; case Profile::StockPrices: case Profile::CurrencyPrices: if (!m_pagePrices) { m_pagePrices = new PricesPage(this, m_imp); m_wiz->setPage(CSVWizard::PagePrices, m_pagePrices); } break; default: return; } m_wiz->next(); // go to separator page if (m_skipSetup && profileExists) { // programmatically skip to last page while((m_wiz->currentPage() != m_pageFormats) && (m_wiz->nextId() != -1)) { m_wiz->next(); } } } void CSVWizard::importClicked() { switch (m_imp->m_profile->type()) { case Profile::Banking: if (!m_pageBanking->validateCreditDebit()) return; break; case Profile::Investment: if (!m_pageInvestment->validateActionType()) return; break; default: break; } saveSettings(); if (!m_imp->createStatement(m_st)) { m_st = MyMoneyStatement(); return; } accept(); } const MyMoneyStatement& CSVWizard::statement() const { return m_st; } void CSVWizard::saveAsQIFClicked() { switch (m_imp->m_profile->type()) { case Profile::Banking: if (!m_pageBanking->validateCreditDebit()) return; break; case Profile::Investment: if (!m_pageInvestment->validateActionType()) return; break; default: break; } bool isOK = m_imp->createStatement(m_st); if (!isOK || m_st.m_listTransactions.isEmpty()) return; 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; switch (m_imp->m_profile->type()) { case Profile::Banking: m_pageBanking->makeQIF(m_st, outFileName); break; case Profile::Investment: m_pageInvestment->makeQIF(m_st, outFileName); break; default: break; } } void CSVWizard::initializeComboBoxes(const QHash &columns) { QStringList columnNumbers; for (int i = 0; i < m_imp->m_file->m_columnCount; ++i) columnNumbers.append(QString::number(i + 1)); foreach (const auto column, columns) { // disable widgets allowing their initialization column->blockSignals(true); // clear all existing items before adding new ones column->clear(); // populate comboboxes with col # values column->addItems(columnNumbers); // all comboboxes are set to 0 so set them to -1 column->setCurrentIndex(-1); // enable widgets after their initialization column->blockSignals(false); } } //------------------------------------------------------------------------------------------------------- IntroPage::IntroPage(CSVWizard *dlg, CSVImporterCore *imp) : CSVWizardPage(dlg, imp), m_profileType(Profile::Banking), ui(new Ui::IntroPage) { ui->setupUi(this); } IntroPage::~IntroPage() { delete ui; } int IntroPage::nextId() const { 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, &QAbstractButton::clicked, this, &IntroPage::slotAddProfile); connect(ui->m_remove, &QAbstractButton::clicked, this, &IntroPage::slotRemoveProfile); connect(ui->m_rename, &QAbstractButton::clicked, this, &IntroPage::slotRenameProfile); connect(ui->m_profilesBank, &QAbstractButton::toggled, this, &IntroPage::slotBankRadioToggled); connect(ui->m_profilesInvest, &QAbstractButton::toggled, this, &IntroPage::slotInvestRadioToggled); connect(ui->m_profilesCurrencyPrices, &QAbstractButton::toggled, this, &IntroPage::slotCurrencyPricesRadioToggled); connect(ui->m_profilesStockPrices, &QAbstractButton::toggled, this, &IntroPage::slotStockPricesRadioToggled); 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() ) ); } m_dlg->ui->tableView->hide(); } bool IntroPage::validatePage() { return true; } void IntroPage::slotAddProfile() { profileChanged(ProfileAction::Add); } void IntroPage::slotRemoveProfile() { profileChanged(ProfileAction::Remove); } void IntroPage::slotRenameProfile() { profileChanged(ProfileAction::Rename); } void IntroPage::profileChanged(const ProfileAction action) { QString cbText = ui->m_profiles->currentText(); if (cbText.isEmpty()) // you cannot neither add nor remove empty name profile or rename to empty name return; int cbIndex = ui->m_profiles->currentIndex(); switch (action) { case ProfileAction::Rename: case ProfileAction::Add: { 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 ProfileAction::Remove: if (m_profiles.value(cbIndex) != cbText) // user changed name of the profile and tries to remove it return; break; default: break; } if (CSVImporterCore::profilesAction(m_profileType, action, m_profiles.value(cbIndex), cbText)) { switch (action) { case ProfileAction::Add: 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 ProfileAction::Remove: m_profiles.removeAt(cbIndex); ui->m_profiles->removeItem(cbIndex); KMessageBox::information(m_dlg, i18n("
Profile %1 has been removed.
", cbText)); break; case ProfileAction::Rename: 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; } } } void IntroPage::slotComboSourceIndexChanged(int idx) { if (idx == -1) { wizard()->button(QWizard::CustomButton1)->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->m_skipSetup->setEnabled(true); ui->m_remove->setEnabled(true); ui->m_rename->setEnabled(true); } } void IntroPage::profileTypeChanged(const Profile profileType, bool toggled) { if (!toggled) return; KConfigGroup profilesGroup(CSVImporterCore::configFile(), CSVImporterCore::m_confProfileNames); m_profileType = profileType; QString profileTypeStr; switch (m_profileType) { case Profile::Banking: ui->m_profilesInvest->setChecked(false); ui->m_profilesStockPrices->setChecked(false); ui->m_profilesCurrencyPrices->setChecked(false); break; case Profile::Investment: ui->m_profilesBank->setChecked(false); ui->m_profilesStockPrices->setChecked(false); ui->m_profilesCurrencyPrices->setChecked(false); break; case Profile::StockPrices: ui->m_profilesBank->setChecked(false); ui->m_profilesInvest->setChecked(false); ui->m_profilesCurrencyPrices->setChecked(false); break; case Profile::CurrencyPrices: ui->m_profilesBank->setChecked(false); ui->m_profilesInvest->setChecked(false); ui->m_profilesStockPrices->setChecked(false); break; default: break; } profileTypeStr = CSVImporterCore::m_profileConfPrefix.value(m_profileType); m_profiles = profilesGroup.readEntry(profileTypeStr, QStringList()); int priorProfile = profilesGroup.readEntry(CSVImporterCore::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(Profile::Banking, toggled); } void IntroPage::slotInvestRadioToggled(bool toggled) { profileTypeChanged(Profile::Investment, toggled); } void IntroPage::slotCurrencyPricesRadioToggled(bool toggled) { profileTypeChanged(Profile::CurrencyPrices, toggled); } void IntroPage::slotStockPricesRadioToggled(bool toggled) { profileTypeChanged(Profile::StockPrices, toggled); } SeparatorPage::SeparatorPage(CSVWizard *dlg, CSVImporterCore *imp) : CSVWizardPage(dlg, imp), ui(new Ui::SeparatorPage) { ui->setupUi(this); 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))); } SeparatorPage::~SeparatorPage() { delete ui; } void SeparatorPage::initializePage() { m_dlg->ui->tableView->show(); // comboboxes are preset to -1 and, in new profile case, can be set here to -1 as well ... // ... so block their signals until setting them ... ui->m_encoding->blockSignals(true); ui->m_fieldDelimiter->blockSignals(true); ui->m_textDelimiter->blockSignals(true); initializeEncodingCombobox(); ui->m_encoding->setCurrentIndex(ui->m_encoding->findData(m_imp->m_profile->m_encodingMIBEnum)); ui->m_fieldDelimiter->setCurrentIndex((int)m_imp->m_profile->m_fieldDelimiter); ui->m_textDelimiter->setCurrentIndex((int)m_imp->m_profile->m_textDelimiter); ui->m_encoding->blockSignals(false); ui->m_fieldDelimiter->blockSignals(false); ui->m_textDelimiter->blockSignals(false); // ... 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::initializeEncodingCombobox() { 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(); foreach (const auto codec, codecs) ui->m_encoding->addItem(codec->name(), codec->mibEnum()); } 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 && // 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 == (int)m_imp->m_profile->m_fieldDelimiter) return; m_imp->m_profile->m_fieldDelimiter = static_cast(int(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->m_fieldDelimiter->blockSignals(true); ui->m_fieldDelimiter->setCurrentIndex((int)m_imp->m_profile->m_fieldDelimiter); 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_textDelimiter = static_cast(index); m_imp->m_file->setupParser(m_imp->m_profile); emit completeChanged(); } bool SeparatorPage::isComplete() const { 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 Profile::Banking: if (m_imp->m_file->m_columnCount > 2) rc = true; break; case Profile::Investment: if (m_imp->m_file->m_columnCount > 3) rc = true; break; case Profile::CurrencyPrices: case Profile::StockPrices: if (m_imp->m_file->m_columnCount > 1) rc = true; break; default: break; } } return rc; } bool SeparatorPage::validatePage() { return true; } void SeparatorPage::cleanupPage() { // On completion with error force use of 'Back' button. // ...to allow resetting of 'Skip setup' m_dlg->m_pageIntro->initializePage(); // Need to show button(QWizard::CustomButton1) not 'NextButton' } RowsPage::RowsPage(CSVWizard *dlg, CSVImporterCore *imp) : CSVWizardPage(dlg, imp), ui(new Ui::RowsPage) { ui->setupUi(this); connect(ui->m_startLine, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int)));; connect(ui->m_endLine, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); } RowsPage::~RowsPage() { delete ui; } void RowsPage::initializePage() { ui->m_startLine->blockSignals(true); ui->m_endLine->blockSignals(true); 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); ui->m_startLine->blockSignals(false); ui->m_endLine->blockSignals(false); m_dlg->markUnwantedRows(); m_dlg->m_vScrollBar->setValue(m_imp->m_profile->m_startLine); QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } void RowsPage::cleanupPage() { m_dlg->clearBackground(); } int RowsPage::nextId() const { int ret; switch (m_imp->m_profile->type()) { case Profile::Banking: ret = CSVWizard::PageBanking; break; case Profile::Investment: ret = CSVWizard::PageInvestment; break; case Profile::StockPrices: case Profile::CurrencyPrices: ret = CSVWizard::PagePrices; break; default: ret = CSVWizard::PageRows; break; } return ret; } void RowsPage::startRowChanged(int val) { if (val > m_imp->m_file->m_rowCount) { ui->m_startLine->setValue(m_imp->m_file->m_rowCount - 1); return; } --val; if (val > m_imp->m_profile->m_endLine) { if (m_imp->m_profile->m_endLine <= m_imp->m_file->m_rowCount) ui->m_startLine->setValue(m_imp->m_profile->m_endLine + 1); return; } m_imp->m_profile->m_startLine = val; m_dlg->m_vScrollBar->setValue(val); m_dlg->markUnwantedRows(); } void RowsPage::endRowChanged(int val) { if (val > m_imp->m_file->m_rowCount) { ui->m_endLine->setValue(m_imp->m_file->m_rowCount - 1); return; } --val; if (val < m_imp->m_profile->m_startLine) { if (m_imp->m_profile->m_startLine <= m_imp->m_file->m_rowCount) ui->m_endLine->setValue(m_imp->m_profile->m_startLine + 1); return; } m_imp->m_profile->m_trailerLines = m_imp->m_file->m_rowCount - val; m_imp->m_profile->m_endLine = val; m_dlg->markUnwantedRows(); } FormatsPage::FormatsPage(CSVWizard *dlg, CSVImporterCore *imp) : CSVWizardPage(dlg, imp), ui(new Ui::FormatsPage), m_isDecimalSymbolOK(false), m_isDateFormatOK(false) { ui->setupUi(this); connect(ui->m_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); connect(ui->m_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); } FormatsPage::~FormatsPage() { delete ui; } void FormatsPage::initializePage() { m_isDecimalSymbolOK = false; m_isDateFormatOK = false; QList layout; layout << QWizard::Stretch << QWizard::CustomButton2 << QWizard::BackButton << QWizard::FinishButton << QWizard::CancelButton; wizard()->setButtonText(QWizard::FinishButton, i18n("Import CSV")); wizard()->setOption(QWizard::HaveCustomButton2, true); wizard()->setButtonText(QWizard::CustomButton2, i18n("Make QIF File")); wizard()->setButtonLayout(layout); wizard()->button(QWizard::CustomButton2)->setEnabled(false); wizard()->button(QWizard::FinishButton)->setEnabled(false); ui->m_thousandsDelimiter->setEnabled(false); ui->m_dateFormat->blockSignals(true); ui->m_dateFormat->setCurrentIndex((int)m_imp->m_profile->m_dateFormat); ui->m_dateFormat->blockSignals(false); emit ui->m_dateFormat->currentIndexChanged((int)m_imp->m_profile->m_dateFormat); // emit update signal manually regardless of change to combobox ui->m_decimalSymbol->blockSignals(true); if (m_imp->m_profile->m_decimalSymbol == DecimalSymbol::Auto && !m_imp->m_autodetect.value(AutoDecimalSymbol)) ui->m_decimalSymbol->setCurrentIndex(-1); else ui->m_decimalSymbol->setCurrentIndex((int)m_imp->m_profile->m_decimalSymbol); ui->m_decimalSymbol->blockSignals(false); emit ui->m_decimalSymbol->currentIndexChanged((int)m_imp->m_profile->m_decimalSymbol); // emit update signal manually regardless of change to combobox } void FormatsPage::decimalSymbolChanged(int index) { const QList columns = m_imp->getNumericalColumns(); switch (index) { case -1: if (!m_imp->m_autodetect.value(AutoDecimalSymbol)) { break; } // intentional fall through case 2: { ui->m_decimalSymbol->blockSignals(true); m_imp->m_profile->m_decimalSymbol = DecimalSymbol::Auto; 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->m_decimalSymbol->setCurrentIndex(-1); ui->m_thousandsDelimiter->setCurrentIndex(-1); } else if (index == -1) { // if detection went well and decimal symbol was unspecified then we'll be specifying it DecimalSymbol 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_decimalSymbol = firstDecSymbol; ui->m_decimalSymbol->setCurrentIndex((int)firstDecSymbol); ui->m_thousandsDelimiter->setCurrentIndex((int)firstDecSymbol); } else { // else set to auto m_imp->m_profile->m_decimalSymbol = DecimalSymbol::Auto; ui->m_decimalSymbol->setCurrentIndex((int)DecimalSymbol::Auto); ui->m_thousandsDelimiter->setCurrentIndex((int)DecimalSymbol::Auto); } } ui->m_decimalSymbol->blockSignals(false); break; } default: foreach (const auto column, columns) m_imp->m_decimalSymbolIndexMap.insert(column, static_cast(index)); ui->m_thousandsDelimiter->setCurrentIndex(index); m_imp->m_profile->m_decimalSymbol = static_cast(index); } m_isDecimalSymbolOK = validateDecimalSymbols(columns); emit completeChanged(); } bool FormatsPage::validateDecimalSymbols(const QList &columns) { bool isOK = true; foreach (const auto col, columns) { m_imp->m_file->m_parse->setDecimalSymbol(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_imp->m_file->m_parse->possiblyReplaceSymbol(rawNumber); if (!m_imp->m_file->m_parse->invalidConversion() || rawNumber.isEmpty()) { // empty strings are welcome item->setBackground(m_dlg->m_colorBrush); item->setForeground(m_dlg->m_colorBrushText); } else { isOK = false; 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(const int index) { if (index == -1) return; int col = m_imp->m_profile->m_colTypeNum.value(Column::Date); m_imp->m_profile->m_dateFormat = static_cast(index); m_imp->m_convertDate->setDateFormatIndex(static_cast(index)); m_isDateFormatOK = validateDateFormat(col); if (!m_isDateFormatOK) { KMessageBox::sorry(this, i18n("
There are invalid date formats in column '%1'.
" "
Please check your selections.
" , col + 1), i18n("CSV import")); } emit completeChanged(); } bool FormatsPage::validateDateFormat(const int col) { m_dlg->clearColumnsBackground(col); QDate emptyDate; bool isOK = true; 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 == emptyDate) { isOK = false; 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_dlg->m_colorBrush); item->setForeground(m_dlg->m_colorBrushText); } } return isOK; } bool FormatsPage::isComplete() const { const bool enable = m_isDecimalSymbolOK && m_isDateFormatOK; if (m_imp->m_profile->type() != Profile::StockPrices && m_imp->m_profile->type() != Profile::CurrencyPrices) wizard()->button(QWizard::CustomButton2)->setEnabled(enable); return enable; } void FormatsPage::cleanupPage() { QList columns = m_imp->getNumericalColumns(); columns.append(m_imp->m_profile->m_colTypeNum.value(Column::Date)); 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); } diff --git a/kmymoney/plugins/csv/import/csvwizard.h b/kmymoney/plugins/csv/import/csvwizard.h index 9bc1bb7ca..d5ae61e05 100644 --- a/kmymoney/plugins/csv/import/csvwizard.h +++ b/kmymoney/plugins/csv/import/csvwizard.h @@ -1,281 +1,282 @@ /* * Copyright 2015-2016 Allan Anderson * Copyright 2016-2018 Łukasz Wojniłowicz + * Copyright 2018 Thomas Baumgart * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CSVWIZARD_H #define CSVWIZARD_H // ---------------------------------------------------------------------------- // QT Includes #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "csvwizardpage.h" #include "mymoneystatement.h" class CSVImporter; class CSVImporterCore; class IntroPage; class SeparatorPage; class RowsPage; class BankingPage; class InvestmentPage; class PricesPage; class FormatsPage; namespace Ui { class CSVWizard; } class QScrollBar; class QComboBox; class QLabel; class CSVWizard : public QDialog { Q_OBJECT public: explicit CSVWizard(CSVImporter *plugin); ~CSVWizard(); enum wizardPageE { PageIntro, PageSeparator, PageRows, PageBanking, PageInvestment, PagePrices, PageFormats }; const MyMoneyStatement& statement() const; Ui::CSVWizard *ui; MyMoneyStatement m_st; QScrollBar *m_vScrollBar; IntroPage *m_pageIntro; int m_initialHeight; int m_initialWidth; QBrush m_clearBrush; QBrush m_clearBrushText; QBrush m_colorBrush; QBrush m_colorBrushText; QBrush m_errorBrush; QBrush m_errorBrushText; QMap m_colTypeName; bool m_skipSetup; void clearColumnsBackground(const int col); void clearColumnsBackground(const QList &columnList); void clearBackground(); void markUnwantedRows(); void importClicked(); /** * Called in order to adjust window size to suit the file, */ void updateWindowSize(); void initializeComboBoxes(const QHash &columns); void presetFilename(const QString& name); private: QList m_stageLabels; int m_curId; int m_lastId; SeparatorPage *m_pageSeparator; RowsPage *m_pageRows; QPointer m_pageBanking; QPointer m_pageInvestment; QPointer m_pagePrices; FormatsPage *m_pageFormats; CSVImporter* m_plugin; CSVImporterCore* m_imp; QWizard* m_wiz; QString m_fileName; void readWindowSize(const KSharedConfigPtr& config); void saveWindowSize(const KSharedConfigPtr& config); void showStage(); void saveSettings() const; bool eventFilter(QObject *object, QEvent *event) override; private Q_SLOTS: /** * This method is called when 'Exit' is clicked. The plugin settings will * be saved and the plugin will be terminated. */ void slotClose(); void slotIdChanged(int id); void fileDialogClicked(); void saveAsQIFClicked(); }; namespace Ui { class IntroPage; } class IntroPage : public CSVWizardPage { Q_OBJECT public: explicit IntroPage(CSVWizard *dlg, CSVImporterCore *imp); ~IntroPage(); void initializePage() override; Profile m_profileType; Ui::IntroPage *ui; Q_SIGNALS: void signalBankClicked(bool); void activated(int); void returnPressed(); private: QStringList m_profiles; bool validatePage() override; int nextId() const override; void profileChanged(const ProfileAction action); void profileTypeChanged(const Profile profileType, bool toggled); private Q_SLOTS: void slotAddProfile(); void slotRemoveProfile(); void slotRenameProfile(); void slotComboSourceIndexChanged(int idx); void slotBankRadioToggled(bool toggled); void slotInvestRadioToggled(bool toggled); void slotCurrencyPricesRadioToggled(bool toggled); void slotStockPricesRadioToggled(bool toggled); }; namespace Ui { class SeparatorPage; } class SeparatorPage : public CSVWizardPage { Q_OBJECT public: explicit SeparatorPage(CSVWizard *dlg, CSVImporterCore *imp); ~SeparatorPage(); private Q_SLOTS: void encodingChanged(const int index); void fieldDelimiterChanged(const int index); void textDelimiterChanged(const int index); Q_SIGNALS: void completeChanged(); private: Ui::SeparatorPage *ui; void initializeEncodingCombobox(); void initializePage() override; bool isComplete() const override; void cleanupPage() override; bool validatePage() override; }; namespace Ui { class RowsPage; } class RowsPage : public CSVWizardPage { Q_OBJECT public: explicit RowsPage(CSVWizard *dlg, CSVImporterCore *imp); ~RowsPage(); private Q_SLOTS: /** * This method is called when the user edits the startLine setting. */ void startRowChanged(int val); /** * This method is called when the user edits the lastLine setting. */ void endRowChanged(int val); private: Ui::RowsPage *ui; void initializePage() override; int nextId() const override; void cleanupPage() override; }; namespace Ui { class FormatsPage; } class FormatsPage : public CSVWizardPage { Q_OBJECT public: explicit FormatsPage(CSVWizard *dlg, CSVImporterCore *imp); ~FormatsPage(); private: Ui::FormatsPage *ui; bool m_isDecimalSymbolOK; bool m_isDateFormatOK; /** * This method is called when the user selects a new decimal symbol. The * UI is updated using the new symbol, and on importing, the new symbol * also will be used. */ bool validateDecimalSymbols(const QList &columns); /** * This method checks if all dates in date column are valid. */ bool validateDateFormat(const int index); void initializePage() override; bool isComplete() const override; void cleanupPage() override; Q_SIGNALS: void completeChanged(); private Q_SLOTS: void decimalSymbolChanged(int); void dateFormatChanged(const int index); }; #endif // CSVWIZARD_H