diff --git a/kmymoney/mymoney/onlinejob.cpp b/kmymoney/mymoney/onlinejob.cpp index 6e1e97652..6bb9841b6 100644 --- a/kmymoney/mymoney/onlinejob.cpp +++ b/kmymoney/mymoney/onlinejob.cpp @@ -1,323 +1,323 @@ /* This file is part of KMyMoney, A Personal Finance Manager by KDE Copyright (C) 2013 Christian Dávid This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "onlinejob.h" #include "onlinejob_p.h" #include #include #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "tasks/onlinetask.h" #include "onlinejobadministration.h" #include "mymoneystoragenames.h" using namespace MyMoneyStorageNodes; onlineJob::onlineJob() : MyMoneyObject(*new onlineJobPrivate), m_task(0) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } -onlineJob::onlineJob(onlineTask* task, const QString &id) : +onlineJob::onlineJob(onlineTask* onlinetask, const QString &id) : MyMoneyObject(*new onlineJobPrivate, id), - m_task(task) + m_task(onlinetask) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } -onlineJob::onlineJob(onlineTask* task) : +onlineJob::onlineJob(onlineTask* onlinetask) : MyMoneyObject(*new onlineJobPrivate, QString()), - m_task(task) + m_task(onlinetask) { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; } onlineJob::onlineJob(onlineJob const& other) : MyMoneyObject(*new onlineJobPrivate(*other.d_func()), other.id()), m_task(0) { copyPointerFromOtherJob(other); } onlineJob::onlineJob(const QString &id, const onlineJob& other) : MyMoneyObject(*new onlineJobPrivate(*other.d_func()), id), m_task() { Q_D(onlineJob); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_messageList = QList(); d->m_locked = false; copyPointerFromOtherJob(other); } onlineJob::onlineJob(const QDomElement& element) : MyMoneyObject(*new onlineJobPrivate, element, true) { Q_D(onlineJob); d->m_messageList = QList(); d->m_locked = false; d->m_jobSend = QDateTime::fromString(element.attribute(d->getAttrName(OnlineJob::Attribute::Send)), Qt::ISODate); d->m_jobBankAnswerDate = QDateTime::fromString(element.attribute(d->getAttrName(OnlineJob::Attribute::BankAnswerDate)), Qt::ISODate); QString state = element.attribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState)); if (state == d->getAttrName(OnlineJob::Attribute::AbortedByUser)) d->m_jobBankAnswerState = abortedByUser; else if (state == d->getAttrName(OnlineJob::Attribute::AcceptedByBank)) d->m_jobBankAnswerState = acceptedByBank; else if (state == d->getAttrName(OnlineJob::Attribute::RejectedByBank)) d->m_jobBankAnswerState = rejectedByBank; else if (state == d->getAttrName(OnlineJob::Attribute::SendingError)) d->m_jobBankAnswerState = sendingError; else d->m_jobBankAnswerState = noBankAnswer; QDomElement taskElem = element.firstChildElement(d->getElName(OnlineJob::Element::OnlineTask)); m_task = onlineJobAdministration::instance()->createOnlineTaskByXml(taskElem.attribute(d->getAttrName(OnlineJob::Attribute::IID)), taskElem); } void onlineJob::copyPointerFromOtherJob(const onlineJob &other) { if (!other.isNull()) m_task = other.constTask()->clone(); } void onlineJob::reset() { Q_D(onlineJob); clearId(); d->m_jobSend = QDateTime(); d->m_jobBankAnswerDate = QDateTime(); d->m_jobBankAnswerState = noBankAnswer; d->m_locked = false; } onlineJob::~onlineJob() { delete m_task; } onlineTask* onlineJob::task() { if (m_task == 0) throw emptyTask(__FILE__, __LINE__); return m_task; } const onlineTask* onlineJob::task() const { if (m_task == 0) throw emptyTask(__FILE__, __LINE__); return m_task; } const onlineTask* onlineJob::constTask() const { return task(); } QString onlineJob::taskIid() const { try { return task()->taskName(); } catch (const emptyTask&) { } return QString(); } QString onlineJob::responsibleAccount() const { try { return task()->responsibleAccount(); } catch (const emptyTask&) { } return QString(); } MyMoneyAccount onlineJob::responsibleMyMoneyAccount() const { QString accountId = responsibleAccount(); if (!accountId.isEmpty()) return MyMoneyFile::instance()->account(accountId); return MyMoneyAccount(); } bool onlineJob::setLock(bool enable) { Q_D(onlineJob); d->m_locked = enable; return true; } bool onlineJob::isLocked() const { Q_D(const onlineJob); return d->m_locked; } bool onlineJob::isEditable() const { Q_D(const onlineJob); return (!isLocked() && sendDate().isNull() && (d->m_jobBankAnswerState == noBankAnswer || d->m_jobBankAnswerState == sendingError)); } bool onlineJob::isNull() const { return (m_task == 0); } void onlineJob::setJobSend(const QDateTime &dateTime) { Q_D(onlineJob); d->m_jobSend = dateTime; } void onlineJob::setJobSend() { setJobSend(QDateTime::currentDateTime()); } void onlineJob::setBankAnswer(const onlineJob::sendingState state, const QDateTime &dateTime) { Q_D(onlineJob); d->m_jobBankAnswerState = state; d->m_jobBankAnswerDate = dateTime; } void onlineJob::setBankAnswer(const onlineJob::sendingState state) { setBankAnswer(state, QDateTime::currentDateTime()); } QDateTime onlineJob::bankAnswerDate() const { Q_D(const onlineJob); return d->m_jobBankAnswerDate; } onlineJob::sendingState onlineJob::bankAnswerState() const { Q_D(const onlineJob); return d->m_jobBankAnswerState; } void onlineJob::addJobMessage(const onlineJobMessage& message) { Q_D(onlineJob); d->m_messageList.append(message); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode, const QDateTime& timestamp) { Q_D(onlineJob); onlineJobMessage logMessage(type, sender, message, timestamp); logMessage.setSenderErrorCode(errorCode); d->m_messageList.append(logMessage); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message, const QString& errorCode) { addJobMessage(type, sender, message, errorCode, QDateTime::currentDateTime()); } void onlineJob::addJobMessage(const eMyMoney::OnlineJob::MessageType& type, const QString& sender, const QString& message) { addJobMessage(type, sender, message, QString(), QDateTime::currentDateTime()); } QList onlineJob::jobMessageList() const { Q_D(const onlineJob); return d->m_messageList; } /** @todo give life */ void onlineJob::writeXML(QDomDocument &document, QDomElement &parent) const { auto el = document.createElement(nodeNames[nnOnlineJob]); Q_D(const onlineJob); d->writeBaseXML(document, el); if (!d->m_jobSend.isNull()) el.setAttribute(d->getAttrName(OnlineJob::Attribute::Send), d->m_jobSend.toString(Qt::ISODate)); if (!d->m_jobBankAnswerDate.isNull()) el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerDate), d->m_jobBankAnswerDate.toString(Qt::ISODate)); switch (d->m_jobBankAnswerState) { case abortedByUser: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::AbortedByUser)); break; case acceptedByBank: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::AcceptedByBank)); break; case rejectedByBank: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::RejectedByBank)); break; case sendingError: el.setAttribute(d->getAttrName(OnlineJob::Attribute::BankAnswerState), d->getAttrName(OnlineJob::Attribute::SendingError)); break; case noBankAnswer: default: void(); } QDomElement taskEl = document.createElement(d->getElName(OnlineJob::Element::OnlineTask)); taskEl.setAttribute(d->getAttrName(OnlineJob::Attribute::IID), taskIid()); try { task()->writeXML(document, taskEl); // throws execption if there is no task el.appendChild(taskEl); // only append child if there is something to append } catch (const emptyTask&) { } parent.appendChild(el); } bool onlineJob::isValid() const { if (m_task != 0) return m_task->isValid(); return false; } QDateTime onlineJob::sendDate() const { Q_D(const onlineJob); return d->m_jobSend; } bool onlineJob::hasReferenceTo(const QString& id) const { if (m_task != 0) return m_task->hasReferenceTo(id); return false; } diff --git a/kmymoney/mymoney/payeeidentifier/payeeidentifier.cpp b/kmymoney/mymoney/payeeidentifier/payeeidentifier.cpp index 1e7c866fb..8931d77fc 100644 --- a/kmymoney/mymoney/payeeidentifier/payeeidentifier.cpp +++ b/kmymoney/mymoney/payeeidentifier/payeeidentifier.cpp @@ -1,159 +1,159 @@ /* * This file is part of KMyMoney, A Personal Finance Manager by KDE * Copyright (C) 2014 Christian Dávid * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "payeeidentifier.h" #include #include #include "payeeidentifierdata.h" payeeIdentifier::payeeIdentifier() : m_id(0), m_payeeIdentifier(0) { } payeeIdentifier::payeeIdentifier(const payeeIdentifier& other) : m_id(other.m_id), m_payeeIdentifier(0) { if (other.m_payeeIdentifier != 0) m_payeeIdentifier = other.m_payeeIdentifier->clone(); } -payeeIdentifier::payeeIdentifier(payeeIdentifierData*const data) +payeeIdentifier::payeeIdentifier(payeeIdentifierData*const identifierdata) : m_id(0), - m_payeeIdentifier(data) + m_payeeIdentifier(identifierdata) { } -payeeIdentifier::payeeIdentifier(const payeeIdentifier::id_t& id, payeeIdentifierData*const data) +payeeIdentifier::payeeIdentifier(const payeeIdentifier::id_t& id, payeeIdentifierData*const identifierdata) : m_id(id), - m_payeeIdentifier(data) + m_payeeIdentifier(identifierdata) { } -payeeIdentifier::payeeIdentifier(const QString& id, payeeIdentifierData*const data) +payeeIdentifier::payeeIdentifier(const QString& id, payeeIdentifierData*const identifierdata) : m_id(id.mid(5).toUInt()), - m_payeeIdentifier(data) + m_payeeIdentifier(identifierdata) { bool ok = false; // hopefully the compiler optimizes this away if compiled in non-debug mode Q_ASSERT(id.mid(5).toUInt(&ok) && ok); } payeeIdentifier::payeeIdentifier(const payeeIdentifier::id_t& id, const payeeIdentifier& other) : m_id(id), m_payeeIdentifier(0) { if (other.m_payeeIdentifier != 0) m_payeeIdentifier = other.m_payeeIdentifier->clone(); } QString payeeIdentifier::idString() const { if (m_id == 0) return QString(); return QLatin1String("IDENT") + QString::number(m_id).rightJustified(6, '0'); } payeeIdentifier::~payeeIdentifier() { delete m_payeeIdentifier; } payeeIdentifierData* payeeIdentifier::operator->() { if (m_payeeIdentifier == 0) throw empty(__FILE__, __LINE__); return m_payeeIdentifier; } const payeeIdentifierData* payeeIdentifier::operator->() const { if (m_payeeIdentifier == 0) throw empty(__FILE__, __LINE__); return m_payeeIdentifier; } payeeIdentifierData* payeeIdentifier::data() { return operator->(); } const payeeIdentifierData* payeeIdentifier::data() const { return operator->(); } bool payeeIdentifier::isValid() const { if (m_payeeIdentifier != 0) return m_payeeIdentifier->isValid(); return false; } QString payeeIdentifier::iid() const { if (m_payeeIdentifier != 0) return m_payeeIdentifier->payeeIdentifierId(); return QString(); } payeeIdentifier& payeeIdentifier::operator=(const payeeIdentifier & other) { if (this == &other) return *this; m_id = other.m_id; if (other.m_payeeIdentifier == 0) m_payeeIdentifier = 0; else m_payeeIdentifier = other.m_payeeIdentifier->clone(); return *this; } bool payeeIdentifier::operator==(const payeeIdentifier& other) { if (m_id != other.m_id) return false; if (isNull() || other.isNull()) { if (!isNull() || !other.isNull()) return false; return true; } return (*data() == *(other.data())); } void payeeIdentifier::writeXML(QDomDocument& document, QDomElement& parent, const QString& elemenName) const { // Important: type must be set before calling m_payeeIdentifier->writeXML() // the plugin for unavailable plugins must be able to set type itself QDomElement elem = document.createElement(elemenName); if (m_id != 0) elem.setAttribute("id", m_id); if (!isNull()) { elem.setAttribute("type", m_payeeIdentifier->payeeIdentifierId()); m_payeeIdentifier->writeXML(document, elem); } parent.appendChild(elem); } diff --git a/kmymoney/plugins/sql/mymoneystoragesql.cpp b/kmymoney/plugins/sql/mymoneystoragesql.cpp index eab7d98cf..fd7e9ba53 100644 --- a/kmymoney/plugins/sql/mymoneystoragesql.cpp +++ b/kmymoney/plugins/sql/mymoneystoragesql.cpp @@ -1,2836 +1,2836 @@ /*************************************************************************** mymoneystoragesql.cpp --------------------- begin : 11 November 2005 copyright : (C) 2005 by Tony Bloomfield email : tonybloom@users.sourceforge.net : Fernando Vilas : Christian Dávid (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneystoragesql_p.h" // ---------------------------------------------------------------------------- // System Includes // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes //************************ Constructor/Destructor ***************************** MyMoneyStorageSql::MyMoneyStorageSql(MyMoneyStorageMgr *storage, const QUrl &url) : QSqlDatabase(QUrlQuery(url).queryItemValue("driver")), d_ptr(new MyMoneyStorageSqlPrivate(this)) { Q_D(MyMoneyStorageSql); d->m_storage = storage; } MyMoneyStorageSql::~MyMoneyStorageSql() { try { close(true); } catch (const MyMoneyException& e) { qDebug() << "Caught Exception in MMStorageSql dtor: " << e.what(); } Q_D(MyMoneyStorageSql); delete d; } uint MyMoneyStorageSql::currentVersion() const { Q_D(const MyMoneyStorageSql); return (d->m_db.currentVersion()); } int MyMoneyStorageSql::open(const QUrl &url, int openMode, bool clear) { Q_D(MyMoneyStorageSql); try { int rc = 0; d->m_driver = MyMoneyDbDriver::create(QUrlQuery(url).queryItemValue("driver")); //get the input options QStringList options = QUrlQuery(url).queryItemValue("options").split(','); - d->m_loadAll = true || // force loading whole database into memory since unification of storages - options.contains("loadAll")/*|| m_mode == 0*/; + d->m_loadAll = true; // force loading whole database into memory since unification of storages + // options.contains("loadAll")/*|| m_mode == 0*/; d->m_override = options.contains("override"); // create the database connection QString dbName = url.path().remove(0, 1); // remove separator slash setDatabaseName(dbName); setHostName(url.host()); setUserName(url.userName()); setPassword(url.password()); if (QUrlQuery(url).queryItemValue("driver").contains("QMYSQL")) { setConnectOptions("MYSQL_OPT_RECONNECT=1"); } switch (openMode) { case QIODevice::ReadOnly: // OpenDatabase menu entry (or open last file) case QIODevice::ReadWrite: // Save menu entry with database open // this may be a sqlite file opened from the recently used list // but which no longer exists. In that case, open will work but create an empty file. // This is not what the user's after; he may accuse KMM of deleting all his data! if (d->m_driver->requiresExternalFile()) { if (!d->fileExists(dbName)) { rc = 1; break; } } if (!QSqlDatabase::open()) { d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening database"); rc = 1; } else { rc = d->createTables(); // check all tables are present, create if not } break; case QIODevice::WriteOnly: // SaveAs Database - if exists, must be empty, if not will create // Try to open the database. // If that fails, try to create the database, then try to open it again. d->m_newDatabase = true; if (!QSqlDatabase::open()) { if (!d->createDatabase(url)) { rc = 1; } else { if (!QSqlDatabase::open()) { d->buildError(QSqlQuery(*this), Q_FUNC_INFO, "opening new database"); rc = 1; } else { rc = d->createTables(); } } } else { rc = d->createTables(); if (rc == 0) { if (clear) { d->clean(); } else { rc = d->isEmpty(); } } } break; default: qWarning("%s", qPrintable(QString("%1 - unknown open mode %2").arg(Q_FUNC_INFO).arg(openMode))); } if (rc != 0) return (rc); // bypass logon check if we are creating a database if (d->m_newDatabase) return(0); // check if the database is locked, if not lock it d->readFileInfo(); if (!d->m_logonUser.isEmpty() && (!d->m_override)) { d->m_error = i18n("Database apparently in use\nOpened by %1 on %2 at %3.\nOpen anyway?", d->m_logonUser, d->m_logonAt.date().toString(Qt::ISODate), d->m_logonAt.time().toString("hh.mm.ss")); qDebug("%s", qPrintable(d->m_error)); close(false); rc = -1; // retryable error } else { d->m_logonUser = url.userName() + '@' + url.host(); d->m_logonAt = QDateTime::currentDateTime(); d->writeFileInfo(); } return(rc); } catch (const QString& s) { qDebug("%s", qPrintable(s)); return (1); } } void MyMoneyStorageSql::close(bool logoff) { Q_D(MyMoneyStorageSql); if (QSqlDatabase::isOpen()) { if (logoff) { MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->m_logonUser.clear(); d->writeFileInfo(); } QSqlDatabase::close(); QSqlDatabase::removeDatabase(connectionName()); } } ulong MyMoneyStorageSql::getRecCount(const QString& table) const { Q_D(const MyMoneyStorageSql); QSqlQuery q(*const_cast (this)); q.prepare(QString("SELECT COUNT(*) FROM %1;").arg(table)); if ((!q.exec()) || (!q.next())) { // krazy:exclude=crashy d->buildError(q, Q_FUNC_INFO, "error retrieving record count"); qFatal("Error retrieving record count"); // definitely shouldn't happen } return ((ulong) q.value(0).toULongLong()); } ////////////////////////////////////////////////////////////////// bool MyMoneyStorageSql::readFile() { Q_D(MyMoneyStorageSql); d->m_displayStatus = true; try { d->readFileInfo(); d->readInstitutions(); if (d->m_loadAll) { readPayees(); } else { QList user; user.append(QString("USER")); readPayees(user); } readTags(); d->readCurrencies(); d->readSecurities(); d->readAccounts(); if (d->m_loadAll) { d->readTransactions(); } else { if (d->m_preferred.filterSet().singleFilter.accountFilter) readTransactions(d->m_preferred); } d->readSchedules(); d->readPrices(); d->readReports(); d->readBudgets(); //FIXME - ?? if (m_mode == 0) //m_storage->rebuildAccountBalances(); // this seems to be nonsense, but it clears the dirty flag // as a side-effect. d->m_storage->setLastModificationDate(d->m_storage->lastModificationDate()); // FIXME?? if (m_mode == 0) m_storage = NULL; // make sure the progress bar is not shown any longer d->signalProgress(-1, -1); d->m_displayStatus = false; //MyMoneySqlQuery::traceOn(); return true; } catch (const QString &) { return false; } // this seems to be nonsense, but it clears the dirty flag // as a side-effect. } // The following is called from 'SaveAsDatabase' bool MyMoneyStorageSql::writeFile() { Q_D(MyMoneyStorageSql); // initialize record counts and hi ids d->m_institutions = d->m_accounts = d->m_payees = d->m_tags = d->m_transactions = d->m_splits = d->m_securities = d->m_prices = d->m_currencies = d->m_schedules = d->m_reports = d->m_kvps = d->m_budgets = 0; d->m_hiIdInstitutions = d->m_hiIdPayees = d->m_hiIdTags = d->m_hiIdAccounts = d->m_hiIdTransactions = d->m_hiIdSchedules = d->m_hiIdSecurities = d->m_hiIdReports = d->m_hiIdBudgets = 0; d->m_onlineJobs = d->m_payeeIdentifier = 0; d->m_displayStatus = true; try { MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->writeInstitutions(); d->writePayees(); d->writeTags(); d->writeAccounts(); d->writeTransactions(); d->writeSchedules(); d->writeSecurities(); d->writePrices(); d->writeCurrencies(); d->writeReports(); d->writeBudgets(); d->writeOnlineJobs(); d->writeFileInfo(); // this seems to be nonsense, but it clears the dirty flag // as a side-effect. //m_storage->setLastModificationDate(m_storage->lastModificationDate()); // FIXME?? if (m_mode == 0) m_storage = NULL; // make sure the progress bar is not shown any longer d->signalProgress(-1, -1); d->m_displayStatus = false; // this seems to be nonsense, but it clears the dirty flag // as a side-effect. d->m_storage->setLastModificationDate(d->m_storage->lastModificationDate()); return true; } catch (const QString &) { return false; } } QString MyMoneyStorageSql::lastError() const { Q_D(const MyMoneyStorageSql); return d->m_error; } // --------------- SQL Transaction (commit unit) handling ----------------------------------- void MyMoneyStorageSql::startCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); if (d->m_commitUnitStack.isEmpty()) { if (!transaction()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "starting commit unit") + ' ' + callingFunction); } d->m_commitUnitStack.push(callingFunction); } bool MyMoneyStorageSql::endCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); // for now, we don't know if there were any changes made to the data so // we expect the data to have changed. This assumption causes some unnecessary // repaints of the UI here and there, but for now it's ok. If we can determine // that the commit() really changes the data, we can return that information // as value of this method. bool rc = true; if (d->m_commitUnitStack.isEmpty()) { throw MYMONEYEXCEPTION("Empty commit unit stack while trying to commit"); } if (callingFunction != d->m_commitUnitStack.top()) qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(d->m_commitUnitStack.top()))); d->m_commitUnitStack.pop(); if (d->m_commitUnitStack.isEmpty()) { //qDebug() << "Committing with " << QSqlQuery::refCount() << " queries"; if (!commit()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "ending commit unit")); } return rc; } void MyMoneyStorageSql::cancelCommitUnit(const QString& callingFunction) { Q_D(MyMoneyStorageSql); if (d->m_commitUnitStack.isEmpty()) return; if (callingFunction != d->m_commitUnitStack.top()) qDebug("%s", qPrintable(QString("%1 - %2 s/be %3").arg(Q_FUNC_INFO).arg(callingFunction).arg(d->m_commitUnitStack.top()))); d->m_commitUnitStack.clear(); if (!rollback()) throw MYMONEYEXCEPTION(d->buildError(QSqlQuery(), callingFunction, "cancelling commit unit") + ' ' + callingFunction); } ///////////////////////////////////////////////////////////////////// void MyMoneyStorageSql::fillStorage() { Q_D(MyMoneyStorageSql); // if (!m_transactionListRead) // make sure we have loaded everything d->readTransactions(); // if (!m_payeeListRead) readPayees(); } //------------------------------ Write SQL routines ---------------------------------------- // **** Institutions **** void MyMoneyStorageSql::addInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].insertString()); QList iList; iList << inst; d->writeInstitutionList(iList , q); ++d->m_institutions; d->writeFileInfo(); } void MyMoneyStorageSql::modifyInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].updateString()); QVariantList kvpList; kvpList << inst.id(); d->deleteKeyValuePairs("OFXSETTINGS", kvpList); QList iList; iList << inst; d->writeInstitutionList(iList , q); d->writeFileInfo(); } void MyMoneyStorageSql::removeInstitution(const MyMoneyInstitution& inst) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << inst.id(); d->deleteKeyValuePairs("OFXSETTINGS", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmInstitutions"].deleteString()); q.bindValue(":id", inst.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Institution"))); // krazy:exclude=crashy --d->m_institutions; d->writeFileInfo(); } void MyMoneyStorageSql::addPayee(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].insertString()); d->writePayee(payee, q); ++d->m_payees; QVariantList identIds; QList idents = payee.payeeIdentifiers(); // Store ids which have to be stored in the map table identIds.reserve(idents.count()); foreach (payeeIdentifier ident, idents) { try { // note: this changes ident addPayeeIdentifier(ident); identIds.append(ident.idString()); } catch (payeeIdentifier::empty&) { } } if (!identIds.isEmpty()) { // Create lists for batch processing QVariantList order; QVariantList payeeIdList; order.reserve(identIds.size()); payeeIdList.reserve(identIds.size()); for (int i = 0; i < identIds.size(); ++i) { order << i; payeeIdList << payee.id(); } q.prepare("INSERT INTO kmmPayeesPayeeIdentifier (payeeId, identifierId, userOrder) VALUES(?, ?, ?)"); q.bindValue(0, payeeIdList); q.bindValue(1, identIds); q.bindValue(2, order); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers"))); // krazy:exclude=crashy } d->writeFileInfo(); } void MyMoneyStorageSql::modifyPayee(MyMoneyPayee payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].updateString()); d->writePayee(payee, q); // Get a list of old identifiers first q.prepare("SELECT identifierId FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payee's identifiers (getting old values failed)"))); // krazy:exclude=crashy QStringList oldIdentIds; oldIdentIds.reserve(q.numRowsAffected()); while (q.next()) oldIdentIds << q.value(0).toString(); // Add new and modify old payeeIdentifiers foreach (payeeIdentifier ident, payee.payeeIdentifiers()) { if (ident.idString().isEmpty()) { payeeIdentifier oldIdent(ident); addPayeeIdentifier(ident); // addPayeeIdentifier could fail (throws an exception then) only remove old // identifier if new one is stored correctly payee.removePayeeIdentifier(oldIdent); payee.addPayeeIdentifier(ident); } else { modifyPayeeIdentifier(ident); payee.modifyPayeeIdentifier(ident); oldIdentIds.removeAll(ident.idString()); } } // Remove identifiers which are not used anymore foreach (QString idToRemove, oldIdentIds) { payeeIdentifier ident(fetchPayeeIdentifier(idToRemove)); removePayeeIdentifier(ident); } // Update relation table q.prepare("DELETE FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payee's identifiers (delete from mapping table)"))); // krazy:exclude=crashy // Get list again because modifiyPayeeIdentifier which is used above may change the id QList idents(payee.payeeIdentifiers()); QVariantList order; QVariantList payeeIdList; QVariantList identIdList; order.reserve(idents.size()); payeeIdList.reserve(idents.size()); identIdList.reserve(idents.size()); { QList::const_iterator end = idents.constEnd(); int i = 0; for (QList::const_iterator iter = idents.constBegin(); iter != end; ++iter, ++i) { order << i; payeeIdList << payee.id(); identIdList << iter->idString(); } } q.prepare("INSERT INTO kmmPayeesPayeeIdentifier (payeeId, userOrder, identifierId) VALUES(?, ?, ?)"); q.bindValue(0, payeeIdList); q.bindValue(1, order); q.bindValue(2, identIdList); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing payee's identifiers during modify"))); // krazy:exclude=crashy d->writeFileInfo(); } void MyMoneyStorageSql::modifyUserInfo(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayees"].updateString()); d->writePayee(payee, q, true); d->writeFileInfo(); } void MyMoneyStorageSql::removePayee(const MyMoneyPayee& payee) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); // Get identifiers first so we know which to delete q.prepare("SELECT identifierId FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("removing payee's identifiers (getting old values failed)"))); // krazy:exclude=crashy QStringList identIds; while (q.next()) identIds << q.value(0).toString(); QMap idents = fetchPayeeIdentifiers(identIds); foreach (payeeIdentifier ident, idents) { removePayeeIdentifier(ident); } // Delete entries from mapping table q.prepare("DELETE FROM kmmPayeesPayeeIdentifier WHERE payeeId = ?"); q.bindValue(0, payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("removing payee's identifiers (delete from mapping table)"))); // krazy:exclude=crashy // Delete payee q.prepare(d->m_db.m_tables["kmmPayees"].deleteString()); q.bindValue(":id", payee.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Payee"))); // krazy:exclude=crashy --d->m_payees; d->writeFileInfo(); } // **** Tags **** void MyMoneyStorageSql::addTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].insertString()); d->writeTag(tag, q); ++d->m_tags; d->writeFileInfo(); } void MyMoneyStorageSql::modifyTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].updateString()); d->writeTag(tag, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeTag(const MyMoneyTag& tag) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTags"].deleteString()); q.bindValue(":id", tag.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Tag"))); // krazy:exclude=crashy --d->m_tags; d->writeFileInfo(); } // **** Accounts **** void MyMoneyStorageSql::addAccount(const MyMoneyAccount& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].insertString()); QList aList; aList << acc; d->writeAccountList(aList, q); ++d->m_accounts; d->writeFileInfo(); } void MyMoneyStorageSql::modifyAccount(const MyMoneyAccount& acc) { QList aList; aList << acc; modifyAccountList(aList); } void MyMoneyStorageSql::modifyAccountList(const QList& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].updateString()); QVariantList kvpList; foreach (const MyMoneyAccount& a, acc) { kvpList << a.id(); } d->deleteKeyValuePairs("ACCOUNT", kvpList); d->deleteKeyValuePairs("ONLINEBANKING", kvpList); d->writeAccountList(acc, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeAccount(const MyMoneyAccount& acc) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << acc.id(); d->deleteKeyValuePairs("ACCOUNT", kvpList); d->deleteKeyValuePairs("ONLINEBANKING", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmAccounts"].deleteString()); q.bindValue(":id", acc.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Account"))); // krazy:exclude=crashy --d->m_accounts; d->writeFileInfo(); } // **** Transactions and Splits **** void MyMoneyStorageSql::addTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // add the transaction and splits QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmTransactions"].insertString()); d->writeTransaction(tx.id(), tx, q, "N"); ++d->m_transactions; QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); ++d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); // in the fileinfo record, update lastMod, txCount, next TxId d->writeFileInfo(); } void MyMoneyStorageSql::modifyTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // remove the splits of the old tx from the count table QSqlQuery q(*this); q.prepare("SELECT accountId FROM kmmSplits WHERE transactionId = :txId;"); q.bindValue(":txId", tx.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, "retrieving old splits")); while (q.next()) { QString id = q.value(0).toString(); --d->m_transactionCountMap[id]; } // add the transaction and splits q.prepare(d->m_db.m_tables["kmmTransactions"].updateString()); d->writeTransaction(tx.id(), tx, q, "N"); QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); ++d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); //writeSplits(tx.id(), "N", tx.splits()); // in the fileinfo record, update lastMod d->writeFileInfo(); } void MyMoneyStorageSql::removeTransaction(const MyMoneyTransaction& tx) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->deleteTransaction(tx.id()); --d->m_transactions; QList aList; // for each split account, update lastMod date, balance, txCount foreach (const MyMoneySplit& it_s, tx.splits()) { MyMoneyAccount acc = d->m_storage->account(it_s.accountId()); --d->m_transactionCountMap[acc.id()]; aList << acc; } modifyAccountList(aList); // in the fileinfo record, update lastModDate, txCount d->writeFileInfo(); } // **** Schedules **** void MyMoneyStorageSql::addSchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSchedules"].insertString()); d->writeSchedule(sched, q, true); ++d->m_schedules; d->writeFileInfo(); } void MyMoneyStorageSql::modifySchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSchedules"].updateString()); d->writeSchedule(sched, q, false); d->writeFileInfo(); } void MyMoneyStorageSql::removeSchedule(const MyMoneySchedule& sched) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); d->deleteSchedule(sched.id()); --d->m_schedules; d->writeFileInfo(); } // **** Securities **** void MyMoneyStorageSql::addSecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].insertString()); d->writeSecurity(sec, q); ++d->m_securities; d->writeFileInfo(); } void MyMoneyStorageSql::modifySecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << sec.id(); d->deleteKeyValuePairs("SECURITY", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].updateString()); d->writeSecurity(sec, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeSecurity(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QVariantList kvpList; kvpList << sec.id(); d->deleteKeyValuePairs("SECURITY", kvpList); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmSecurities"].deleteString()); q.bindValue(":id", kvpList); if (!q.execBatch()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Security"))); --d->m_securities; d->writeFileInfo(); } // **** Prices **** void MyMoneyStorageSql::addPrice(const MyMoneyPrice& p) { Q_D(MyMoneyStorageSql); if (d->m_readingPrices) return; // the app always calls addPrice, whether or not there is already one there MyMoneyDbTransaction t(*this, Q_FUNC_INFO); bool newRecord = false; QSqlQuery q(*this); QString s = d->m_db.m_tables["kmmPrices"].selectAllString(false); s += " WHERE fromId = :fromId AND toId = :toId AND priceDate = :priceDate;"; q.prepare(s); q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("finding Price"))); // krazy:exclude=crashy if (q.next()) { q.prepare(d->m_db.m_tables["kmmPrices"].updateString()); } else { q.prepare(d->m_db.m_tables["kmmPrices"].insertString()); ++d->m_prices; newRecord = true; } q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); q.bindValue(":price", p.rate(QString()).toString()); const MyMoneySecurity sec = d->m_storage->security(p.to()); q.bindValue(":priceFormatted", p.rate(QString()).formatMoney("", sec.pricePrecision())); q.bindValue(":priceSource", p.source()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing Price"))); // krazy:exclude=crashy if (newRecord) d->writeFileInfo(); } void MyMoneyStorageSql::removePrice(const MyMoneyPrice& p) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPrices"].deleteString()); q.bindValue(":fromId", p.from()); q.bindValue(":toId", p.to()); q.bindValue(":priceDate", p.date().toString(Qt::ISODate)); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Price"))); // krazy:exclude=crashy --d->m_prices; d->writeFileInfo(); } // **** Currencies **** void MyMoneyStorageSql::addCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].insertString()); d->writeCurrency(sec, q); ++d->m_currencies; d->writeFileInfo(); } void MyMoneyStorageSql::modifyCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].updateString()); d->writeCurrency(sec, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeCurrency(const MyMoneySecurity& sec) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmCurrencies"].deleteString()); q.bindValue(":ISOcode", sec.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Currency"))); // krazy:exclude=crashy --d->m_currencies; d->writeFileInfo(); } void MyMoneyStorageSql::addReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmReportConfig"].insertString()); d->writeReport(rep, q); ++d->m_reports; d->writeFileInfo(); } void MyMoneyStorageSql::modifyReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmReportConfig"].updateString()); d->writeReport(rep, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeReport(const MyMoneyReport& rep) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("DELETE FROM kmmReportConfig WHERE id = :id"); q.bindValue(":id", rep.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Report"))); // krazy:exclude=crashy --d->m_reports; d->writeFileInfo(); } void MyMoneyStorageSql::addBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].insertString()); d->writeBudget(bud, q); ++d->m_budgets; d->writeFileInfo(); } void MyMoneyStorageSql::modifyBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].updateString()); d->writeBudget(bud, q); d->writeFileInfo(); } void MyMoneyStorageSql::removeBudget(const MyMoneyBudget& bud) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmBudgetConfig"].deleteString()); q.bindValue(":id", bud.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting Budget"))); // krazy:exclude=crashy --d->m_budgets; d->writeFileInfo(); } void MyMoneyStorageSql::addOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("INSERT INTO kmmOnlineJobs (id, type, jobSend, bankAnswerDate, state, locked) VALUES(:id, :type, :jobSend, :bankAnswerDate, :state, :locked);"); d->writeOnlineJob(job, q); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy ++d->m_onlineJobs; try { // Save online task d->insertStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { } } void MyMoneyStorageSql::modifyOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); Q_ASSERT(!job.id().isEmpty()); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery query(*this); query.prepare(QLatin1String( "UPDATE kmmOnlineJobs SET " " type = :type, " " jobSend = :jobSend, " " bankAnswerDate = :bankAnswerDate, " " state = :state, " " locked = :locked " " WHERE id = :id" )); d->writeOnlineJob(job, query); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("writing onlineJob"))); // krazy:exclude=crashy try { // Modify online task d->updateStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { // If there is no task attached this is fine as well } } void MyMoneyStorageSql::removeOnlineJob(const onlineJob& job) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // Remove onlineTask first, because it could have a contraint // which could block the removal of the onlineJob try { // Remove task d->deleteStorableObject(*job.constTask(), job.id()); } catch (onlineJob::emptyTask&) { } QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmOnlineJobs"].deleteString()); q.bindValue(":id", job.id()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting onlineJob"))); // krazy:exclude=crashy --d->m_onlineJobs; } void MyMoneyStorageSql::addPayeeIdentifier(payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); ident = payeeIdentifier(incrementPayeeIdentfierId(), ident); QSqlQuery q(*this); q.prepare("INSERT INTO kmmPayeeIdentifier (id, type) VALUES(:id, :type)"); d->writePayeeIdentifier(ident, q); ++d->m_payeeIdentifier; try { d->insertStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } } void MyMoneyStorageSql::modifyPayeeIdentifier(const payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); QSqlQuery q(*this); q.prepare("SELECT type FROM kmmPayeeIdentifier WHERE id = ?"); q.bindValue(0, ident.idString()); if (!q.exec() || !q.next()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("modifying payeeIdentifier"))); // krazy:exclude=crashy bool typeChanged = (q.value(0).toString() != ident.iid()); if (typeChanged) { // Delete old identifier if type changed const payeeIdentifier oldIdent(fetchPayeeIdentifier(ident.idString())); try { d->deleteStorableObject(*oldIdent.data(), ident.idString()); } catch (payeeIdentifier::empty&) { // Note: this should not happen because the ui does not offer a way to change // the type of an payeeIdentifier if it was not correctly loaded. throw MYMONEYEXCEPTION(QLatin1String("Could not modify payeeIdentifier '") + ident.idString() + QLatin1String("' because type changed and could not remove identifier of old type. Maybe a plugin is missing?") ); // krazy:exclude=crashy } } q.prepare("UPDATE kmmPayeeIdentifier SET type = :type WHERE id = :id"); d->writePayeeIdentifier(ident, q); try { if (typeChanged) d->insertStorableObject(*ident.data(), ident.idString()); else d->updateStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } } void MyMoneyStorageSql::removePayeeIdentifier(const payeeIdentifier& ident) { Q_D(MyMoneyStorageSql); MyMoneyDbTransaction t(*this, Q_FUNC_INFO); // Remove first, the table could have a contraint which prevents removal // of row in kmmPayeeIdentifier try { d->deleteStorableObject(*ident.data(), ident.idString()); } catch (payeeIdentifier::empty&) { } QSqlQuery q(*this); q.prepare(d->m_db.m_tables["kmmPayeeIdentifier"].deleteString()); q.bindValue(":id", ident.idString()); if (!q.exec()) throw MYMONEYEXCEPTION(d->buildError(q, Q_FUNC_INFO, QString("deleting payeeIdentifier"))); // krazy:exclude=crashy --d->m_payeeIdentifier; } // **** Key/value pairs **** //******************************** read SQL routines ************************************** /*void MyMoneyStorageSql::setVersion (const QString& version) { m_dbVersion = version.section('.', 0, 0).toUInt(); m_minorVersion = version.section('.', 1, 1).toUInt(); // Okay, I made a cockup by forgetting to include a fixversion in the database // design, so we'll use the minor version as fix level (similar to VERSION // and FIXVERSION in XML file format). A second mistake was setting minor version to 1 // in the first place, so we need to subtract one on reading and add one on writing (sigh)!! m_storage->setFileFixVersion( m_minorVersion - 1); }*/ QMap MyMoneyStorageSql::fetchInstitutions(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int institutionsNb = (idList.isEmpty() ? d->m_institutions : idList.size()); d->signalProgress(0, institutionsNb, QObject::tr("Loading institutions...")); int progress = 0; QMap iList; ulong lastId = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmInstitutions"]; QSqlQuery sq(*const_cast (this)); sq.prepare("SELECT id from kmmAccounts where institutionId = :id"); QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" id = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Institution"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int managerCol = t.fieldNumber("manager"); int routingCodeCol = t.fieldNumber("routingCode"); int addressStreetCol = t.fieldNumber("addressStreet"); int addressCityCol = t.fieldNumber("addressCity"); int addressZipcodeCol = t.fieldNumber("addressZipcode"); int telephoneCol = t.fieldNumber("telephone"); while (query.next()) { MyMoneyInstitution inst; QString iid = GETSTRING(idCol); inst.setName(GETSTRING(nameCol)); inst.setManager(GETSTRING(managerCol)); inst.setSortcode(GETSTRING(routingCodeCol)); inst.setStreet(GETSTRING(addressStreetCol)); inst.setCity(GETSTRING(addressCityCol)); inst.setPostcode(GETSTRING(addressZipcodeCol)); inst.setTelephone(GETSTRING(telephoneCol)); // get list of subaccounts sq.bindValue(":id", iid); if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Institution AccountList"))); // krazy:exclude=crashy QStringList aList; while (sq.next()) aList.append(sq.value(0).toString()); foreach (const QString& it, aList) inst.addAccountId(it); iList[iid] = MyMoneyInstitution(iid, inst); ulong id = MyMoneyUtils::extractId(iid); if (id > lastId) lastId = id; d->signalProgress(++progress, 0); } return iList; } QMap MyMoneyStorageSql::fetchInstitutions() const { return fetchInstitutions(QStringList(), false); } void MyMoneyStorageSql::readPayees(const QString& id) { QList list; list.append(id); readPayees(list); } void MyMoneyStorageSql::readPayees(const QList& pid) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadPayees(fetchPayees(pid)); } catch (const MyMoneyException &) { } // if (pid.isEmpty()) m_payeeListRead = true; } void MyMoneyStorageSql::readPayees() { readPayees(QList()); } QMap MyMoneyStorageSql::fetchPayees(const QStringList& idList, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int payeesNb = (idList.isEmpty() ? d->m_payees : idList.size()); d->signalProgress(0, payeesNb, QObject::tr("Loading payees...")); } int progress = 0; QMap pList; QSqlQuery query(*const_cast (this)); QString queryString = QLatin1String("SELECT kmmPayees.id AS id, kmmPayees.name AS name, kmmPayees.reference AS reference, " " kmmPayees.email AS email, kmmPayees.addressStreet AS addressStreet, kmmPayees.addressCity AS addressCity, kmmPayees.addressZipcode AS addressZipcode, " " kmmPayees.addressState AS addressState, kmmPayees.telephone AS telephone, kmmPayees.notes AS notes, " " kmmPayees.defaultAccountId AS defaultAccountId, kmmPayees.matchData AS matchData, kmmPayees.matchIgnoreCase AS matchIgnoreCase, " " kmmPayees.matchKeys AS matchKeys, " " kmmPayeesPayeeIdentifier.identifierId AS identId " " FROM ( SELECT * FROM kmmPayees "); if (!idList.isEmpty()) { // Create WHERE clause if needed queryString += QLatin1String(" WHERE id IN ("); queryString += QString("?, ").repeated(idList.length()); queryString.chop(2); // remove ", " from end queryString += QLatin1Char(')'); } queryString += QLatin1String( " ) kmmPayees " " LEFT OUTER JOIN kmmPayeesPayeeIdentifier ON kmmPayees.Id = kmmPayeesPayeeIdentifier.payeeId " // The order is used below " ORDER BY kmmPayees.id, kmmPayeesPayeeIdentifier.userOrder;"); query.prepare(queryString); if (!idList.isEmpty()) { // Bind values QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Payee"))); // krazy:exclude=crashy const QSqlRecord record = query.record(); const int idCol = record.indexOf("id"); const int nameCol = record.indexOf("name"); const int referenceCol = record.indexOf("reference"); const int emailCol = record.indexOf("email"); const int addressStreetCol = record.indexOf("addressStreet"); const int addressCityCol = record.indexOf("addressCity"); const int addressZipcodeCol = record.indexOf("addressZipcode"); const int addressStateCol = record.indexOf("addressState"); const int telephoneCol = record.indexOf("telephone"); const int notesCol = record.indexOf("notes"); const int defaultAccountIdCol = record.indexOf("defaultAccountId"); const int matchDataCol = record.indexOf("matchData"); const int matchIgnoreCaseCol = record.indexOf("matchIgnoreCase"); const int matchKeysCol = record.indexOf("matchKeys"); const int identIdCol = record.indexOf("identId"); if (query.next()) { while (query.isValid()) { QString pid; QString boolChar; MyMoneyPayee payee; uint type; bool ignoreCase; QString matchKeys; pid = GETSTRING(idCol); payee.setName(GETSTRING(nameCol)); payee.setReference(GETSTRING(referenceCol)); payee.setEmail(GETSTRING(emailCol)); payee.setAddress(GETSTRING(addressStreetCol)); payee.setCity(GETSTRING(addressCityCol)); payee.setPostcode(GETSTRING(addressZipcodeCol)); payee.setState(GETSTRING(addressStateCol)); payee.setTelephone(GETSTRING(telephoneCol)); payee.setNotes(GETSTRING(notesCol)); payee.setDefaultAccountId(GETSTRING(defaultAccountIdCol)); type = GETINT(matchDataCol); ignoreCase = (GETSTRING(matchIgnoreCaseCol) == "Y"); matchKeys = GETSTRING(matchKeysCol); payee.setMatchData(static_cast(type), ignoreCase, matchKeys); // Get payeeIdentifier ids QStringList identifierIds; do { identifierIds.append(GETSTRING(identIdCol)); } while (query.next() && GETSTRING(idCol) == pid); // as long as the payeeId is unchanged // Fetch and save payeeIdentifier if (!identifierIds.isEmpty()) { QList< ::payeeIdentifier > identifier = fetchPayeeIdentifiers(identifierIds).values(); payee.resetPayeeIdentifiers(identifier); } if (pid == "USER") d->m_storage->setUser(payee); else pList[pid] = MyMoneyPayee(pid, payee); if (d->m_displayStatus) d->signalProgress(++progress, 0); } } return pList; } QMap MyMoneyStorageSql::fetchPayees() const { return fetchPayees(QStringList(), false); } void MyMoneyStorageSql::readTags(const QString& id) { QList list; list.append(id); readTags(list); } void MyMoneyStorageSql::readTags(const QList& pid) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadTags(fetchTags(pid)); d->readFileInfo(); } catch (const MyMoneyException &) { } // if (pid.isEmpty()) m_tagListRead = true; } void MyMoneyStorageSql::readTags() { readTags(QList()); } QMap MyMoneyStorageSql::fetchOnlineJobs(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); Q_UNUSED(forUpdate); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) d->signalProgress(0, idList.isEmpty() ? d->m_onlineJobs : idList.size(), QObject::tr("Loading online banking data...")); // Create query QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare("SELECT id, type, jobSend, bankAnswerDate, state, locked FROM kmmOnlineJobs;"); } else { QString queryIdSet = QString("?, ").repeated(idList.length()); queryIdSet.chop(2); query.prepare(QLatin1String("SELECT id, type, jobSend, bankAnswerDate, state, locked FROM kmmOnlineJobs WHERE id IN (") + queryIdSet + QLatin1String(");")); QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading onlineJobs"))); // krazy:exclude=crashy // Create onlineJobs int progress = 0; QMap jobList; while (query.next()) { const QString& id = query.value(0).toString(); onlineTask *const task = onlineJobAdministration::instance()->createOnlineTaskFromSqlDatabase(query.value(1).toString(), id, *this); onlineJob job = onlineJob(task, id); job.setJobSend(query.value(2).toDateTime()); onlineJob::sendingState state; const QString stateString = query.value(4).toString(); if (stateString == "acceptedByBank") state = onlineJob::acceptedByBank; else if (stateString == "rejectedByBank") state = onlineJob::rejectedByBank; else if (stateString == "abortedByUser") state = onlineJob::abortedByUser; else if (stateString == "sendingError") state = onlineJob::sendingError; else // includes: stateString == "noBankAnswer" state = onlineJob::noBankAnswer; job.setBankAnswer(state, query.value(4).toDateTime()); job.setLock(query.value(5).toString() == QLatin1String("Y") ? true : false); jobList.insert(job.id(), job); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return jobList; } QMap MyMoneyStorageSql::fetchOnlineJobs() const { return fetchOnlineJobs(QStringList(), false); } payeeIdentifier MyMoneyStorageSql::fetchPayeeIdentifier(const QString& id) const { QMap list = fetchPayeeIdentifiers(QStringList(id)); QMap::const_iterator iter = list.constFind(id); if (iter == list.constEnd()) throw MYMONEYEXCEPTION(QLatin1String("payeeIdentifier with id '") + id + QLatin1String("' not found.")); // krazy:exclude=crashy return *iter; } QMap< QString, payeeIdentifier > MyMoneyStorageSql::fetchPayeeIdentifiers(const QStringList& idList) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); // Create query QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare("SELECT id, type FROM kmmPayeeIdentifier;"); } else { QString queryIdSet = QString("?, ").repeated(idList.length()); queryIdSet.chop(2); // remove ", " from end query.prepare(QLatin1String("SELECT id, type FROM kmmPayeeIdentifier WHERE id IN (") + queryIdSet + QLatin1String(");")); QStringList::const_iterator end = idList.constEnd(); for (QStringList::const_iterator iter = idList.constBegin(); iter != end; ++iter) { query.addBindValue(*iter); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading payee identifiers"))); // krazy:exclude=crashy QMap identList; while (query.next()) { const QString id = query.value(0).toString(); identList.insert(id, payeeIdentifierLoader::instance()->createPayeeIdentifierFromSqlDatabase(*this, query.value(1).toString(), id)); } return identList; } QMap< QString, payeeIdentifier > MyMoneyStorageSql::fetchPayeeIdentifiers() const { return fetchPayeeIdentifiers(QStringList()); } QMap MyMoneyStorageSql::fetchTags(const QStringList& idList, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int tagsNb = (idList.isEmpty() ? d->m_tags : idList.size()); d->signalProgress(0, tagsNb, QObject::tr("Loading tags...")); } else { // if (m_tagListRead) return; } int progress = 0; QMap taList; //ulong lastId; const MyMoneyDbTable& t = d->m_db.m_tables["kmmTags"]; QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare(t.selectAllString()); } else { QString whereClause = " where ("; QString itemConnector = ""; foreach (const QString& it, idList) { whereClause.append(QString("%1id = '%2'").arg(itemConnector).arg(it)); itemConnector = " or "; } whereClause += ')'; query.prepare(t.selectAllString(false) + whereClause); } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Tag"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int notesCol = t.fieldNumber("notes"); int tagColorCol = t.fieldNumber("tagColor"); int closedCol = t.fieldNumber("closed"); while (query.next()) { QString pid; QString boolChar; MyMoneyTag tag; pid = GETSTRING(idCol); tag.setName(GETSTRING(nameCol)); tag.setNotes(GETSTRING(notesCol)); tag.setClosed((GETSTRING(closedCol) == "Y")); tag.setTagColor(QColor(GETSTRING(tagColorCol))); taList[pid] = MyMoneyTag(pid, tag); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return taList; } QMap MyMoneyStorageSql::fetchTags() const { return fetchTags(QStringList(), false); } QMap MyMoneyStorageSql::fetchAccounts(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int accountsNb = (idList.isEmpty() ? d->m_accounts : idList.size()); d->signalProgress(0, accountsNb, QObject::tr("Loading accounts...")); int progress = 0; QMap accList; QStringList kvpAccountList(idList); const MyMoneyDbTable& t = d->m_db.m_tables["kmmAccounts"]; QSqlQuery query(*const_cast (this)); QSqlQuery sq(*const_cast (this)); QString childQueryString = "SELECT id, parentId FROM kmmAccounts WHERE "; QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE id IN ("; childQueryString += " parentId IN ("; QString inString; for (int i = 0; i < idList.count(); ++i) { inString += QString(":id%1, ").arg(i); } inString = inString.left(inString.length() - 2) + ')'; queryString += inString; childQueryString += inString; } else { childQueryString += " NOT parentId IS NULL"; } queryString += " ORDER BY id"; childQueryString += " ORDER BY parentid, id"; if (forUpdate) { queryString += d->m_driver->forUpdateString(); childQueryString += d->m_driver->forUpdateString(); } query.prepare(queryString); sq.prepare(childQueryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); sq.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Account"))); // krazy:exclude=crashy if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading subAccountList"))); // krazy:exclude=crashy // Reserve enough space for all values. Approximate it with the size of the // idList in case the db doesn't support reporting the size of the // resultset to the caller. //FIXME: this is for if/when there is a QHash conversion //accList.reserve(q.size() > 0 ? q.size() : idList.size()); static const int idCol = t.fieldNumber("id"); static const int institutionIdCol = t.fieldNumber("institutionId"); static const int parentIdCol = t.fieldNumber("parentId"); static const int lastReconciledCol = t.fieldNumber("lastReconciled"); static const int lastModifiedCol = t.fieldNumber("lastModified"); static const int openingDateCol = t.fieldNumber("openingDate"); static const int accountNumberCol = t.fieldNumber("accountNumber"); static const int accountTypeCol = t.fieldNumber("accountType"); static const int accountNameCol = t.fieldNumber("accountName"); static const int descriptionCol = t.fieldNumber("description"); static const int currencyIdCol = t.fieldNumber("currencyId"); static const int balanceCol = t.fieldNumber("balance"); static const int transactionCountCol = t.fieldNumber("transactionCount"); while (query.next()) { QString aid; QString balance; MyMoneyAccount acc; aid = GETSTRING(idCol); acc.setInstitutionId(GETSTRING(institutionIdCol)); acc.setParentAccountId(GETSTRING(parentIdCol)); acc.setLastReconciliationDate(GETDATE_D(lastReconciledCol)); acc.setLastModified(GETDATE_D(lastModifiedCol)); acc.setOpeningDate(GETDATE_D(openingDateCol)); acc.setNumber(GETSTRING(accountNumberCol)); acc.setAccountType(static_cast(GETINT(accountTypeCol))); acc.setName(GETSTRING(accountNameCol)); acc.setDescription(GETSTRING(descriptionCol)); acc.setCurrencyId(GETSTRING(currencyIdCol)); acc.setBalance(MyMoneyMoney(GETSTRING(balanceCol))); const_cast (this)->d_func()->m_transactionCountMap[aid] = (ulong) GETULL(transactionCountCol); // Process any key value pair if (idList.empty()) kvpAccountList.append(aid); accList.insert(aid, MyMoneyAccount(aid, acc)); if (acc.value("PreferredAccount") == "Yes") { const_cast (this)->d_func()->m_preferred.addAccount(aid); } d->signalProgress(++progress, 0); } QMap::Iterator it_acc; QMap::Iterator accListEnd = accList.end(); while (sq.next()) { it_acc = accList.find(sq.value(1).toString()); if (it_acc != accListEnd && it_acc.value().id() == sq.value(1).toString()) { while (sq.isValid() && it_acc != accListEnd && it_acc.value().id() == sq.value(1).toString()) { it_acc.value().addAccountId(sq.value(0).toString()); if (!sq.next()) break; } sq.previous(); } } //TODO: There should be a better way than this. What's below is O(n log n) or more, // where it may be able to be done in O(n), if things are just right. // The operator[] call in the loop is the most expensive call in this function, according // to several profile runs. QHash kvpResult = d->readKeyValuePairs("ACCOUNT", kvpAccountList); QHash ::const_iterator kvp_end = kvpResult.constEnd(); for (QHash ::const_iterator it_kvp = kvpResult.constBegin(); it_kvp != kvp_end; ++it_kvp) { accList[it_kvp.key()].setPairs(it_kvp.value().pairs()); } kvpResult = d->readKeyValuePairs("ONLINEBANKING", kvpAccountList); kvp_end = kvpResult.constEnd(); for (QHash ::const_iterator it_kvp = kvpResult.constBegin(); it_kvp != kvp_end; ++it_kvp) { accList[it_kvp.key()].setOnlineBankingSettings(it_kvp.value()); } return accList; } QMap MyMoneyStorageSql::fetchAccounts() const { return fetchAccounts(QStringList(), false); } QMap MyMoneyStorageSql::fetchBalance(const QStringList& idList, const QDate& date) const { Q_D(const MyMoneyStorageSql); QMap returnValue; QSqlQuery query(*const_cast (this)); QString queryString = "SELECT action, shares, accountId, postDate " "FROM kmmSplits WHERE txType = 'N'"; if (idList.count() > 0) { queryString += "AND accountId in ("; for (int i = 0; i < idList.count(); ++i) { queryString += QString(":id%1, ").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } // SQLite stores dates as YYYY-MM-DDTHH:mm:ss with 0s for the time part. This makes // the <= operator misbehave when the date matches. To avoid this, add a day to the // requested date and use the < operator. if (date.isValid() && !date.isNull()) queryString += QString(" AND postDate < '%1'").arg(date.addDays(1).toString(Qt::ISODate)); queryString += " ORDER BY accountId, postDate;"; //DBG(queryString); query.prepare(queryString); int i = 0; foreach (const QString& bindVal, idList) { query.bindValue(QString(":id%1").arg(i), bindVal); ++i; } if (!query.exec()) // krazy:exclude=crashy throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("fetching balance"))); QString id; QString oldId; MyMoneyMoney temp; while (query.next()) { id = query.value(2).toString(); // If the old ID does not match the new ID, then the account being summed has changed. // Write the balance into the returnValue map and update the oldId to the current one. if (id != oldId) { if (!oldId.isEmpty()) { returnValue.insert(oldId, temp); temp = 0; } oldId = id; } if (MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares) == query.value(0).toString()) temp *= MyMoneyMoney(query.value(1).toString()); else temp += MyMoneyMoney(query.value(1).toString()); } // Do not forget the last id in the list. returnValue.insert(id, temp); // Return the map. return returnValue; } void MyMoneyStorageSql::readTransactions(const MyMoneyTransactionFilter& filter) { Q_D(MyMoneyStorageSql); try { d->m_storage->loadTransactions(fetchTransactions(filter)); } catch (const MyMoneyException &) { throw; } } QMap MyMoneyStorageSql::fetchTransactions(const QString& tidList, const QString& dateClause, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); // if (m_transactionListRead) return; // all list already in memory if (d->m_displayStatus) { int transactionsNb = (tidList.isEmpty() ? d->m_transactions : tidList.size()); d->signalProgress(0, transactionsNb, QObject::tr("Loading transactions...")); } int progress = 0; // m_payeeList.clear(); QString whereClause = " WHERE txType = 'N' "; if (! tidList.isEmpty()) { whereClause += " AND id IN " + tidList; } if (!dateClause.isEmpty()) whereClause += " and " + dateClause; const MyMoneyDbTable& t = d->m_db.m_tables["kmmTransactions"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(false) + whereClause + " ORDER BY id;"); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Transaction"))); // krazy:exclude=crashy const MyMoneyDbTable& ts = d->m_db.m_tables["kmmSplits"]; whereClause = " WHERE txType = 'N' "; if (! tidList.isEmpty()) { whereClause += " AND transactionId IN " + tidList; } if (!dateClause.isEmpty()) whereClause += " and " + dateClause; QSqlQuery qs(*const_cast (this)); QString splitQuery = ts.selectAllString(false) + whereClause + " ORDER BY transactionId, splitId;"; qs.prepare(splitQuery); if (!qs.exec()) throw MYMONEYEXCEPTION(d->buildError(qs, Q_FUNC_INFO, "reading Splits")); // krazy:exclude=crashy QString splitTxId = "ZZZ"; MyMoneySplit s; if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } QMap txMap; QStringList txList; int idCol = t.fieldNumber("id"); int postDateCol = t.fieldNumber("postDate"); int memoCol = t.fieldNumber("memo"); int entryDateCol = t.fieldNumber("entryDate"); int currencyIdCol = t.fieldNumber("currencyId"); int bankIdCol = t.fieldNumber("bankId"); while (query.next()) { MyMoneyTransaction tx; QString txId = GETSTRING(idCol); tx.setPostDate(GETDATE_D(postDateCol)); tx.setMemo(GETSTRING(memoCol)); tx.setEntryDate(GETDATE_D(entryDateCol)); tx.setCommodity(GETSTRING(currencyIdCol)); tx.setBankID(GETSTRING(bankIdCol)); // skip all splits while the transaction id of the split is less than // the transaction id of the current transaction. Don't forget to check // for the ZZZ flag for the end of the list. while (txId < splitTxId && splitTxId != "ZZZ") { if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } } // while the split transaction id matches the current transaction id, // add the split to the current transaction. Set the ZZZ flag if // all splits for this transaction have been read. while (txId == splitTxId) { tx.addSplit(s); if (qs.next()) { splitTxId = qs.value(0).toString(); d->readSplit(s, qs); } else { splitTxId = "ZZZ"; } } // Process any key value pair if (! txId.isEmpty()) { txList.append(txId); tx = MyMoneyTransaction(txId, tx); txMap.insert(tx.uniqueSortKey(), tx); } } // get the kvps QHash kvpMap = d->readKeyValuePairs("TRANSACTION", txList); QMap::Iterator txMapEnd = txMap.end(); for (QMap::Iterator i = txMap.begin(); i != txMapEnd; ++i) { i.value().setPairs(kvpMap[i.value().id()].pairs()); if (d->m_displayStatus) d->signalProgress(++progress, 0); } if ((tidList.isEmpty()) && (dateClause.isEmpty())) { //qDebug("setting full list read"); } return txMap; } QMap MyMoneyStorageSql::fetchTransactions(const QString& tidList) const { return fetchTransactions(tidList, QString(), false); } QMap MyMoneyStorageSql::fetchTransactions() const { return fetchTransactions(QString(), QString(), false); } QMap MyMoneyStorageSql::fetchTransactions(const MyMoneyTransactionFilter& filter) const { Q_D(const MyMoneyStorageSql); // analyze the filter // if (m_transactionListRead) return; // all list already in memory // if the filter is restricted to certain accounts/categories // check if we already have them all in memory QStringList accounts; QString inQuery; filter.accounts(accounts); filter.categories(accounts); // QStringList::iterator it; // bool allAccountsLoaded = true; // for (it = accounts.begin(); it != accounts.end(); ++it) { // if (m_accountsLoaded.find(*it) == m_accountsLoaded.end()) { // allAccountsLoaded = false; // break; // } // } // if (allAccountsLoaded) return; /* Some filter combinations do not lend themselves to implementation * in SQL, or are likely to require such extensive reading of the database * as to make it easier to just read everything into memory. */ bool canImplementFilter = true; MyMoneyMoney m1, m2; if (filter.amountFilter(m1, m2)) { d->alert("Amount Filter Set"); canImplementFilter = false; } QString n1, n2; if (filter.numberFilter(n1, n2)) { d->alert("Number filter set"); canImplementFilter = false; } int t1; if (filter.firstType(t1)) { d->alert("Type filter set"); canImplementFilter = false; } // int s1; // if (filter.firstState(s1)) { // alert("State filter set"); // canImplementFilter = false; // } QRegExp t2; if (filter.textFilter(t2)) { d->alert("text filter set"); canImplementFilter = false; } MyMoneyTransactionFilter::FilterSet s = filter.filterSet(); if (s.singleFilter.validityFilter) { d->alert("Validity filter set"); canImplementFilter = false; } if (!canImplementFilter) { QMap transactionList = fetchTransactions(); QMap::ConstIterator it_t; std::remove_if(transactionList.begin(), transactionList.end(), FilterFail(filter)); return transactionList; } bool splitFilterActive = false; // the split filter is active if we are selecting on fields in the split table // get start and end dates QDate start = filter.fromDate(); QDate end = filter.toDate(); // not entirely sure if the following is correct, but at best, saves a lot of reads, at worst // it only causes us to read a few more transactions that strictly necessary (I think...) if (start == MyMoneyStorageSqlPrivate::m_startDate) start = QDate(); bool txFilterActive = ((start != QDate()) || (end != QDate())); // and this for fields in the transaction table QString whereClause = ""; QString subClauseconnector = " where txType = 'N' and "; // payees QStringList payees; if (filter.payees(payees)) { QString itemConnector = "payeeId in ("; QString payeesClause = ""; foreach (const QString& it, payees) { payeesClause.append(QString("%1'%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!payeesClause.isEmpty()) { whereClause += subClauseconnector + payeesClause + ')'; subClauseconnector = " and "; } splitFilterActive = true; } //tags QStringList tags; if (filter.tags(tags)) { QString itemConnector = "splitId in ( SELECT splitId from kmmTagSplits where kmmTagSplits.transactionId = kmmSplits.transactionId and tagId in ("; QString tagsClause = ""; foreach (const QString& it, tags) { tagsClause.append(QString("%1'%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!tagsClause.isEmpty()) { whereClause += subClauseconnector + tagsClause + ')'; subClauseconnector = " and "; } splitFilterActive = true; } // accounts and categories if (!accounts.isEmpty()) { splitFilterActive = true; QString itemConnector = "accountId in ("; QString accountsClause = ""; foreach (const QString& it, accounts) { accountsClause.append(QString("%1 '%2'") .arg(itemConnector).arg(it)); itemConnector = ", "; } if (!accountsClause.isEmpty()) { whereClause += subClauseconnector + accountsClause + ')'; subClauseconnector = " and ("; } } // split states QList splitStates; if (filter.states(splitStates)) { splitFilterActive = true; QString itemConnector = " reconcileFlag IN ("; QString statesClause = ""; foreach (int it, splitStates) { statesClause.append(QString(" %1 '%2'").arg(itemConnector) .arg(d->splitState(TransactionFilter::State(it)))); itemConnector = ','; } if (!statesClause.isEmpty()) { whereClause += subClauseconnector + statesClause + ')'; subClauseconnector = " and ("; } } // I've given up trying to work out the logic. we keep getting the wrong number of close brackets int obc = whereClause.count('('); int cbc = whereClause.count(')'); if (cbc > obc) { qDebug() << "invalid where clause " << whereClause; qFatal("aborting"); } while (cbc < obc) { whereClause.append(')'); cbc++; } // if the split filter is active, but the where clause and the date filter is empty // it means we already have all the transactions for the specified filter // in memory, so just exit if ((splitFilterActive) && (whereClause.isEmpty()) && (!txFilterActive)) { qDebug("all transactions already in storage"); return fetchTransactions(); } // if we have neither a split filter, nor a tx (date) filter // it's effectively a read all if ((!splitFilterActive) && (!txFilterActive)) { //qDebug("reading all transactions"); return fetchTransactions(); } // build a date clause for the transaction table QString dateClause; QString connector = ""; if (end != QDate()) { dateClause = QString("(postDate < '%1')").arg(end.addDays(1).toString(Qt::ISODate)); connector = " and "; } if (start != QDate()) { dateClause += QString("%1 (postDate >= '%2')").arg(connector).arg(start.toString(Qt::ISODate)); } // now get a list of transaction ids // if we have only a date filter, we need to build the list from the tx table // otherwise we need to build from the split table if (splitFilterActive) { inQuery = QString("(select distinct transactionId from kmmSplits %1)").arg(whereClause); } else { inQuery = QString("(select distinct id from kmmTransactions where %1)").arg(dateClause); txFilterActive = false; // kill off the date filter now } return fetchTransactions(inQuery, dateClause); //FIXME: if we have an accounts-only filter, recalc balances on loaded accounts } ulong MyMoneyStorageSql::transactionCount(const QString& aid) const { Q_D(const MyMoneyStorageSql); if (aid.isEmpty()) return d->m_transactions; else return d->m_transactionCountMap[aid]; } QHash MyMoneyStorageSql::transactionCountMap() const { Q_D(const MyMoneyStorageSql); return d->m_transactionCountMap; } bool MyMoneyStorageSql::isReferencedByTransaction(const QString& id) const { Q_D(const MyMoneyStorageSql); //FIXME-ALEX should I add sub query for kmmTagSplits here? QSqlQuery q(*const_cast (this)); q.prepare("SELECT COUNT(*) FROM kmmTransactions " "INNER JOIN kmmSplits ON kmmTransactions.id = kmmSplits.transactionId " "WHERE kmmTransactions.currencyId = :ID OR kmmSplits.payeeId = :ID " "OR kmmSplits.accountId = :ID OR kmmSplits.costCenterId = :ID"); q.bindValue(":ID", id); if ((!q.exec()) || (!q.next())) { // krazy:exclude=crashy d->buildError(q, Q_FUNC_INFO, "error retrieving reference count"); qFatal("Error retrieving reference count"); // definitely shouldn't happen } return (0 != q.value(0).toULongLong()); } QMap MyMoneyStorageSql::fetchSchedules(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int schedulesNb = (idList.isEmpty() ? d->m_schedules : idList.size()); d->signalProgress(0, schedulesNb, QObject::tr("Loading schedules...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmSchedules"]; QSqlQuery query(*const_cast (this)); QMap sList; //ulong lastId = 0; const MyMoneyDbTable& ts = d->m_db.m_tables["kmmSplits"]; QSqlQuery qs(*const_cast (this)); qs.prepare(ts.selectAllString(false) + " WHERE transactionId = :id ORDER BY splitId;"); QSqlQuery sq(*const_cast (this)); sq.prepare("SELECT payDate from kmmSchedulePaymentHistory where schedId = :id"); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" id = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } queryString += " ORDER BY id"; if (forUpdate) queryString += d->m_driver->forUpdateString(); query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Schedules"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int typeCol = t.fieldNumber("type"); int occurrenceCol = t.fieldNumber("occurence"); // krazy:exclude=spelling int occurrenceMultiplierCol = t.fieldNumber("occurenceMultiplier"); // krazy:exclude=spelling int paymentTypeCol = t.fieldNumber("paymentType"); int startDateCol = t.fieldNumber("startDate"); int endDateCol = t.fieldNumber("endDate"); int fixedCol = t.fieldNumber("fixed"); int lastDayInMonthCol = t.fieldNumber("lastDayInMonth"); int autoEnterCol = t.fieldNumber("autoEnter"); int lastPaymentCol = t.fieldNumber("lastPayment"); int weekendOptionCol = t.fieldNumber("weekendOption"); int nextPaymentDueCol = t.fieldNumber("nextPaymentDue"); while (query.next()) { MyMoneySchedule s; QString boolChar; QString sId = GETSTRING(idCol); s.setName(GETSTRING(nameCol)); s.setType(static_cast(GETINT(typeCol))); s.setOccurrencePeriod(static_cast(GETINT(occurrenceCol))); s.setOccurrenceMultiplier(GETINT(occurrenceMultiplierCol)); s.setPaymentType(static_cast(GETINT(paymentTypeCol))); s.setStartDate(GETDATE_D(startDateCol)); s.setEndDate(GETDATE_D(endDateCol)); boolChar = GETSTRING(fixedCol); s.setFixed(boolChar == "Y"); boolChar = GETSTRING(lastDayInMonthCol); s.setLastDayInMonth(boolChar == "Y"); boolChar = GETSTRING(autoEnterCol); s.setAutoEnter(boolChar == "Y"); s.setLastPayment(GETDATE_D(lastPaymentCol)); s.setWeekendOption(static_cast(GETINT(weekendOptionCol))); QDate nextPaymentDue = GETDATE_D(nextPaymentDueCol); // convert simple occurrence to compound occurrence int mult = s.occurrenceMultiplier(); Schedule::Occurrence occ = s.occurrencePeriod(); MyMoneySchedule::simpleToCompoundOccurrence(mult, occ); s.setOccurrencePeriod(occ); s.setOccurrenceMultiplier(mult); // now assign the id to the schedule MyMoneySchedule _s(sId, s); s = _s; // read the associated transaction // m_payeeList.clear(); const MyMoneyDbTable& transactionTable = d->m_db.m_tables["kmmTransactions"]; QSqlQuery q2(*const_cast (this)); q2.prepare(transactionTable.selectAllString(false) + " WHERE id = :id;"); q2.bindValue(":id", s.id()); if (!q2.exec()) throw MYMONEYEXCEPTION(d->buildError(q2, Q_FUNC_INFO, QString("reading Scheduled Transaction"))); // krazy:exclude=crashy QSqlRecord rec = q2.record(); if (!q2.next()) throw MYMONEYEXCEPTION(d->buildError(q2, Q_FUNC_INFO, QString("retrieving scheduled transaction"))); MyMoneyTransaction tx(s.id(), MyMoneyTransaction()); tx.setPostDate(d->GETDATE(q2.value(transactionTable.fieldNumber("postDate")).toString())); tx.setMemo(q2.value(transactionTable.fieldNumber("memo")).toString()); tx.setEntryDate(d->GETDATE(q2.value(transactionTable.fieldNumber("entryDate")).toString())); tx.setCommodity(q2.value(transactionTable.fieldNumber("currencyId")).toString()); tx.setBankID(q2.value(transactionTable.fieldNumber("bankId")).toString()); qs.bindValue(":id", s.id()); if (!qs.exec()) throw MYMONEYEXCEPTION(d->buildError(qs, Q_FUNC_INFO, "reading Scheduled Splits")); // krazy:exclude=crashy while (qs.next()) { MyMoneySplit sp; d->readSplit(sp, qs); tx.addSplit(sp); } // if (!m_payeeList.isEmpty()) // readPayees(m_payeeList); // Process any key value pair tx.setPairs(d->readKeyValuePairs("TRANSACTION", s.id()).pairs()); // If the transaction doesn't have a post date, setTransaction will reject it. // The old way of handling things was to store the next post date in the schedule object // and set the transaction post date to QDate(). // For compatibility, if this is the case, copy the next post date from the schedule object // to the transaction object post date. if (!tx.postDate().isValid()) { tx.setPostDate(nextPaymentDue); } s.setTransaction(tx); // read in the recorded payments sq.bindValue(":id", s.id()); if (!sq.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading schedule payment history"))); // krazy:exclude=crashy while (sq.next()) s.recordPayment(sq.value(0).toDate()); sList[s.id()] = s; //FIXME: enable when schedules have KVPs. // s.setPairs(readKeyValuePairs("SCHEDULE", s.id()).pairs()); //ulong id = MyMoneyUtils::extractId(s.id().data()); //if(id > lastId) // lastId = id; d->signalProgress(++progress, 0); } return sList; } QMap MyMoneyStorageSql::fetchSchedules() const { return fetchSchedules(QStringList(), false); } QMap MyMoneyStorageSql::fetchSecurities(const QStringList& /*idList*/, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); d->signalProgress(0, d->m_securities, QObject::tr("Loading securities...")); int progress = 0; QMap sList; ulong lastId = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmSecurities"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(false) + " ORDER BY id;"); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Securities"))); // krazy:exclude=crashy int idCol = t.fieldNumber("id"); int nameCol = t.fieldNumber("name"); int symbolCol = t.fieldNumber("symbol"); int typeCol = t.fieldNumber("type"); int roundingMethodCol = t.fieldNumber("roundingMethod"); int smallestAccountFractionCol = t.fieldNumber("smallestAccountFraction"); int pricePrecisionCol = t.fieldNumber("pricePrecision"); int tradingCurrencyCol = t.fieldNumber("tradingCurrency"); int tradingMarketCol = t.fieldNumber("tradingMarket"); while (query.next()) { MyMoneySecurity e; QString eid; eid = GETSTRING(idCol); e.setName(GETSTRING(nameCol)); e.setTradingSymbol(GETSTRING(symbolCol)); e.setSecurityType(static_cast(GETINT(typeCol))); e.setRoundingMethod(static_cast(GETINT(roundingMethodCol))); int saf = GETINT(smallestAccountFractionCol); int pp = GETINT(pricePrecisionCol); e.setTradingCurrency(GETSTRING(tradingCurrencyCol)); e.setTradingMarket(GETSTRING(tradingMarketCol)); if (e.tradingCurrency().isEmpty()) e.setTradingCurrency(d->m_storage->pairs()["kmm-baseCurrency"]); if (saf == 0) saf = 100; if (pp == 0 || pp > 10) pp = 4; e.setSmallestAccountFraction(saf); e.setPricePrecision(pp); // Process any key value pairs e.setPairs(d->readKeyValuePairs("SECURITY", eid).pairs()); //tell the storage objects we have a new security object. // FIXME: Adapt to new interface make sure, to take care of the currencies as well // see MyMoneyStorageXML::readSecurites() MyMoneySecurity security(eid, e); sList[security.id()] = security; ulong id = MyMoneyUtils::extractId(security.id()); if (id > lastId) lastId = id; d->signalProgress(++progress, 0); } return sList; } QMap MyMoneyStorageSql::fetchSecurities() const { return fetchSecurities(QStringList(), false); } MyMoneyPrice MyMoneyStorageSql::fetchSinglePrice(const QString& fromId, const QString& toId, const QDate& date_, bool exactDate, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); const MyMoneyDbTable& t = d->m_db.m_tables["kmmPrices"]; static const int priceDateCol = t.fieldNumber("priceDate"); static const int priceCol = t.fieldNumber("price"); static const int priceSourceCol = t.fieldNumber("priceSource"); QSqlQuery query(*const_cast (this)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. // See balance query for why the date logic seems odd. QString queryString = t.selectAllString(false) + " WHERE fromId = :fromId AND toId = :toId AND priceDate < :priceDate "; if (exactDate) queryString += "AND priceDate > :exactDate "; queryString += "ORDER BY priceDate DESC;"; query.prepare(queryString); QDate date(date_); if (!date.isValid()) date = QDate::currentDate(); query.bindValue(":fromId", fromId); query.bindValue(":toId", toId); query.bindValue(":priceDate", date.addDays(1).toString(Qt::ISODate)); if (exactDate) query.bindValue(":exactDate", date.toString(Qt::ISODate)); if (! query.exec()) return MyMoneyPrice(); // krazy:exclude=crashy if (query.next()) { return MyMoneyPrice(fromId, toId, GETDATE_D(priceDateCol), MyMoneyMoney(GETSTRING(priceCol)), GETSTRING(priceSourceCol)); } return MyMoneyPrice(); } MyMoneyPriceList MyMoneyStorageSql::fetchPrices(const QStringList& fromIdList, const QStringList& toIdList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int pricesNb = (fromIdList.isEmpty() ? d->m_prices : fromIdList.size()); d->signalProgress(0, pricesNb, QObject::tr("Loading prices...")); int progress = 0; const_cast (this)->d_func()->m_readingPrices = true; MyMoneyPriceList pList; const MyMoneyDbTable& t = d->m_db.m_tables["kmmPrices"]; QSqlQuery query(*const_cast (this)); QString queryString = t.selectAllString(false); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! fromIdList.empty()) { queryString += " WHERE ("; for (int i = 0; i < fromIdList.count(); ++i) { queryString += QString(" fromId = :fromId%1 OR").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } if (! toIdList.empty()) { queryString += " AND ("; for (int i = 0; i < toIdList.count(); ++i) { queryString += QString(" toId = :toId%1 OR").arg(i); } queryString = queryString.left(queryString.length() - 2) + ')'; } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! fromIdList.empty()) { QStringList::ConstIterator bindVal = fromIdList.constBegin(); for (int i = 0; bindVal != fromIdList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":fromId%1").arg(i), *bindVal); } } if (! toIdList.empty()) { QStringList::ConstIterator bindVal = toIdList.constBegin(); for (int i = 0; bindVal != toIdList.constEnd(); ++i, ++bindVal) { query.bindValue(QString(":toId%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Prices"))); // krazy:exclude=crashy static const int fromIdCol = t.fieldNumber("fromId"); static const int toIdCol = t.fieldNumber("toId"); static const int priceDateCol = t.fieldNumber("priceDate"); static const int priceCol = t.fieldNumber("price"); static const int priceSourceCol = t.fieldNumber("priceSource"); while (query.next()) { QString from = GETSTRING(fromIdCol); QString to = GETSTRING(toIdCol); QDate date = GETDATE_D(priceDateCol); pList [MyMoneySecurityPair(from, to)].insert(date, MyMoneyPrice(from, to, date, MyMoneyMoney(GETSTRING(priceCol)), GETSTRING(priceSourceCol))); d->signalProgress(++progress, 0); } const_cast (this)->d_func()->m_readingPrices = false; return pList; } MyMoneyPriceList MyMoneyStorageSql::fetchPrices() const { return fetchPrices(QStringList(), QStringList(), false); } QMap MyMoneyStorageSql::fetchCurrencies(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int currenciesNb = (idList.isEmpty() ? d->m_currencies : idList.size()); d->signalProgress(0, currenciesNb, QObject::tr("Loading currencies...")); int progress = 0; QMap cList; const MyMoneyDbTable& t = d->m_db.m_tables["kmmCurrencies"]; QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); // Use bind variables, instead of just inserting the values in the queryString, // so that values containing a ':' will work. if (! idList.empty()) { queryString += " WHERE"; for (int i = 0; i < idList.count(); ++i) queryString += QString(" isocode = :id%1 OR").arg(i); queryString = queryString.left(queryString.length() - 2); } queryString += " ORDER BY ISOcode"; if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (! idList.empty()) { QStringList::ConstIterator bindVal = idList.constBegin(); for (int i = 0; bindVal != idList.end(); ++i, ++bindVal) { query.bindValue(QString(":id%1").arg(i), *bindVal); } } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading Currencies"))); // krazy:exclude=crashy int ISOcodeCol = t.fieldNumber("ISOcode"); int nameCol = t.fieldNumber("name"); int typeCol = t.fieldNumber("type"); int symbol1Col = t.fieldNumber("symbol1"); int symbol2Col = t.fieldNumber("symbol2"); int symbol3Col = t.fieldNumber("symbol3"); int smallestCashFractionCol = t.fieldNumber("smallestCashFraction"); int smallestAccountFractionCol = t.fieldNumber("smallestAccountFraction"); int pricePrecisionCol = t.fieldNumber("pricePrecision"); while (query.next()) { QString id; MyMoneySecurity c; QChar symbol[3]; id = GETSTRING(ISOcodeCol); c.setName(GETSTRING(nameCol)); c.setSecurityType(static_cast(GETINT(typeCol))); symbol[0] = QChar(GETINT(symbol1Col)); symbol[1] = QChar(GETINT(symbol2Col)); symbol[2] = QChar(GETINT(symbol3Col)); c.setSmallestCashFraction(GETINT(smallestCashFractionCol)); c.setSmallestAccountFraction(GETINT(smallestAccountFractionCol)); c.setPricePrecision(GETINT(pricePrecisionCol)); c.setTradingSymbol(QString(symbol, 3).trimmed()); cList[id] = MyMoneySecurity(id, c); d->signalProgress(++progress, 0); } return cList; } QMap MyMoneyStorageSql::fetchCurrencies() const { return fetchCurrencies(QStringList(), false); } QMap MyMoneyStorageSql::fetchReports(const QStringList& /*idList*/, bool /*forUpdate*/) const { Q_D(const MyMoneyStorageSql); d->signalProgress(0, d->m_reports, QObject::tr("Loading reports...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmReportConfig"]; QSqlQuery query(*const_cast (this)); query.prepare(t.selectAllString(true)); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading reports"))); // krazy:exclude=crashy int xmlCol = t.fieldNumber("XML"); QMap rList; while (query.next()) { QDomDocument dom; dom.setContent(GETSTRING(xmlCol), false); QDomNode child = dom.firstChild(); child = child.firstChild(); MyMoneyReport report; if (report.read(child.toElement())) rList[report.id()] = report; d->signalProgress(++progress, 0); } return rList; } QMap MyMoneyStorageSql::fetchReports() const { return fetchReports(QStringList(), false); } QMap MyMoneyStorageSql::fetchBudgets(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); int budgetsNb = (idList.isEmpty() ? d->m_budgets : idList.size()); d->signalProgress(0, budgetsNb, QObject::tr("Loading budgets...")); int progress = 0; const MyMoneyDbTable& t = d->m_db.m_tables["kmmBudgetConfig"]; QSqlQuery query(*const_cast (this)); QString queryString(t.selectAllString(false)); if (! idList.empty()) { queryString += " WHERE id = '" + idList.join("' OR id = '") + '\''; } if (forUpdate) queryString += d->m_driver->forUpdateString(); queryString += ';'; query.prepare(queryString); if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading budgets"))); // krazy:exclude=crashy QMap budgets; int xmlCol = t.fieldNumber("XML"); while (query.next()) { QDomDocument dom; dom.setContent(GETSTRING(xmlCol), false); QDomNode child = dom.firstChild(); child = child.firstChild(); MyMoneyBudget budget(child.toElement()); budgets.insert(budget.id(), budget); d->signalProgress(++progress, 0); } return budgets; } QMap MyMoneyStorageSql::fetchBudgets() const { return fetchBudgets(QStringList(), false); } ulong MyMoneyStorageSql::getNextBudgetId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdBudgets>(QLatin1String("kmmBudgetConfig"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextAccountId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdAccounts>(QLatin1String("kmmAccounts"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextInstitutionId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdInstitutions>(QLatin1String("kmmInstitutions"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextPayeeId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdPayees>(QLatin1String("kmmPayees"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextTagId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdTags>(QLatin1String("kmmTags"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextReportId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdReports>(QLatin1String("kmmReportConfig"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextScheduleId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdSchedules>(QLatin1String("kmmSchedules"), QLatin1String("id"), 3); } ulong MyMoneyStorageSql::getNextSecurityId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdSecurities>(QLatin1String("kmmSecurities"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextTransactionId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdTransactions>(QLatin1String("kmmTransactions"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextOnlineJobId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdOnlineJobs>(QLatin1String("kmmOnlineJobs"), QLatin1String("id"), 1); } ulong MyMoneyStorageSql::getNextPayeeIdentifierId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdPayeeIdentifier>(QLatin1String("kmmPayeeIdentifier"), QLatin1String("id"), 5); } ulong MyMoneyStorageSql::getNextCostCenterId() const { Q_D(const MyMoneyStorageSql); return d->getNextId<&MyMoneyStorageSqlPrivate::m_hiIdCostCenter>(QLatin1String("kmmCostCenterIdentifier"), QLatin1String("id"), 5); } ulong MyMoneyStorageSql::incrementBudgetId() { Q_D(MyMoneyStorageSql); d->m_hiIdBudgets = getNextBudgetId() + 1; return (d->m_hiIdBudgets - 1); } /** * @warning This method uses getNextAccountId() internaly. The database is not informed which can cause issues * when the database is accessed concurrently. Then maybe a single id is used twice but the RDBMS will detect the * issue and KMyMoney crashes. This issue can only occour when two instances of KMyMoney access the same database. * But in this unlikley case MyMoneyStorageSql will have a lot more issues, I think. */ ulong MyMoneyStorageSql::incrementAccountId() { Q_D(MyMoneyStorageSql); d->m_hiIdAccounts = getNextAccountId() + 1; return (d->m_hiIdAccounts - 1); } ulong MyMoneyStorageSql::incrementInstitutionId() { Q_D(MyMoneyStorageSql); d->m_hiIdInstitutions = getNextInstitutionId() + 1; return (d->m_hiIdInstitutions - 1); } ulong MyMoneyStorageSql::incrementPayeeId() { Q_D(MyMoneyStorageSql); d->m_hiIdPayees = getNextPayeeId() + 1; return (d->m_hiIdPayees - 1); } ulong MyMoneyStorageSql::incrementTagId() { Q_D(MyMoneyStorageSql); d->m_hiIdTags = getNextTagId() + 1; return (d->m_hiIdTags - 1); } ulong MyMoneyStorageSql::incrementReportId() { Q_D(MyMoneyStorageSql); d->m_hiIdReports = getNextReportId() + 1; return (d->m_hiIdReports - 1); } ulong MyMoneyStorageSql::incrementScheduleId() { Q_D(MyMoneyStorageSql); d->m_hiIdSchedules = getNextScheduleId() + 1; return (d->m_hiIdSchedules - 1); } ulong MyMoneyStorageSql::incrementSecurityId() { Q_D(MyMoneyStorageSql); d->m_hiIdSecurities = getNextSecurityId() + 1; return (d->m_hiIdSecurities - 1); } ulong MyMoneyStorageSql::incrementTransactionId() { Q_D(MyMoneyStorageSql); d->m_hiIdTransactions = getNextTransactionId() + 1; return (d->m_hiIdTransactions - 1); } ulong MyMoneyStorageSql::incrementOnlineJobId() { Q_D(MyMoneyStorageSql); d->m_hiIdOnlineJobs = getNextOnlineJobId() + 1; return (d->m_hiIdOnlineJobs - 1); } ulong MyMoneyStorageSql::incrementPayeeIdentfierId() { Q_D(MyMoneyStorageSql); d->m_hiIdPayeeIdentifier = getNextPayeeIdentifierId() + 1; return (d->m_hiIdPayeeIdentifier - 1); } ulong MyMoneyStorageSql::incrementCostCenterId() { Q_D(MyMoneyStorageSql); d->m_hiIdCostCenter = getNextCostCenterId() + 1; return (d->m_hiIdCostCenter - 1); } void MyMoneyStorageSql::loadAccountId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdAccounts = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadTransactionId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdTransactions = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadPayeeId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdPayees = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadTagId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdTags = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadInstitutionId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdInstitutions = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadScheduleId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdSchedules = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadSecurityId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdSecurities = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadReportId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdReports = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadBudgetId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdBudgets = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadOnlineJobId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdOnlineJobs = id; d->writeFileInfo(); } void MyMoneyStorageSql::loadPayeeIdentifierId(ulong id) { Q_D(MyMoneyStorageSql); d->m_hiIdPayeeIdentifier = id; d->writeFileInfo(); } //**************************************************** void MyMoneyStorageSql::setProgressCallback(void(*callback)(int, int, const QString&)) { Q_D(MyMoneyStorageSql); d->m_progressCallback = callback; } void MyMoneyStorageSql::readFile(QIODevice* s, MyMoneyStorageMgr* storage) { Q_UNUSED(s); Q_UNUSED(storage) } void MyMoneyStorageSql::writeFile(QIODevice* s, MyMoneyStorageMgr* storage) { Q_UNUSED(s); Q_UNUSED(storage) } // **************************** Error display routine ******************************* QDate MyMoneyStorageSqlPrivate::m_startDate = QDate(1900, 1, 1); void MyMoneyStorageSql::setStartDate(const QDate& startDate) { MyMoneyStorageSqlPrivate::m_startDate = startDate; } QMap< QString, MyMoneyCostCenter > MyMoneyStorageSql::fetchCostCenters(const QStringList& idList, bool forUpdate) const { Q_D(const MyMoneyStorageSql); Q_UNUSED(forUpdate); MyMoneyDbTransaction trans(const_cast (*this), Q_FUNC_INFO); if (d->m_displayStatus) { int costCenterNb = (idList.isEmpty() ? 100 : idList.size()); d->signalProgress(0, costCenterNb, QObject::tr("Loading cost center...")); } int progress = 0; QMap costCenterList; //ulong lastId; const MyMoneyDbTable& t = d->m_db.m_tables["kmmCostCenter"]; QSqlQuery query(*const_cast (this)); if (idList.isEmpty()) { query.prepare(t.selectAllString()); } else { QString whereClause = " where ("; QString itemConnector = ""; foreach (const QString& it, idList) { whereClause.append(QString("%1id = '%2'").arg(itemConnector).arg(it)); itemConnector = " or "; } whereClause += ')'; query.prepare(t.selectAllString(false) + whereClause); } if (!query.exec()) throw MYMONEYEXCEPTION(d->buildError(query, Q_FUNC_INFO, QString("reading CostCenter"))); // krazy:exclude=crashy const int idCol = t.fieldNumber("id"); const int nameCol = t.fieldNumber("name"); while (query.next()) { MyMoneyCostCenter costCenter; QString pid = GETSTRING(idCol); costCenter.setName(GETSTRING(nameCol)); costCenterList[pid] = MyMoneyCostCenter(pid, costCenter); if (d->m_displayStatus) d->signalProgress(++progress, 0); } return costCenterList; } QMap< QString, MyMoneyCostCenter > MyMoneyStorageSql::fetchCostCenters() const { return fetchCostCenters(QStringList(), false); } diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp index 1c25c5d60..5afaf1b50 100644 --- a/kmymoney/views/kpayeesview.cpp +++ b/kmymoney/views/kpayeesview.cpp @@ -1,722 +1,722 @@ /*************************************************************************** kpayeesview.cpp --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kpayeesview_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include "ui_kpayeesview.h" #include "kmymoneyviewbase_p.h" #include "kpayeeidentifierview.h" #include "mymoneypayee.h" #include "mymoneyexception.h" #include "mymoneyfile.h" #include "mymoneyaccount.h" #include "mymoneymoney.h" #include "mymoneytransactionfilter.h" #include "kmymoneysettings.h" #include "models.h" #include "accountsmodel.h" #include "mymoneysecurity.h" #include "mymoneycontact.h" #include "mymoneyprice.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "icons/icons.h" #include "transaction.h" #include "widgetenums.h" #include "mymoneyenums.h" #include "modelenums.h" #include "menuenums.h" using namespace Icons; // *** KPayeesView Implementation *** KPayeesView::KPayeesView(QWidget *parent) : KMyMoneyViewBase(*new KPayeesViewPrivate(this), parent) { connect(pActions[eMenu::Action::NewPayee], &QAction::triggered, this, &KPayeesView::slotNewPayee); connect(pActions[eMenu::Action::RenamePayee], &QAction::triggered, this, &KPayeesView::slotRenamePayee); connect(pActions[eMenu::Action::DeletePayee], &QAction::triggered, this, &KPayeesView::slotDeletePayee); connect(pActions[eMenu::Action::MergePayee], &QAction::triggered, this, &KPayeesView::slotMergePayee); } KPayeesView::~KPayeesView() { } void KPayeesView::slotChooseDefaultAccount() { Q_D(KPayeesView); MyMoneyFile* file = MyMoneyFile::instance(); QMap account_count; KMyMoneyRegister::RegisterItem* item = d->ui->m_register->firstItem(); while (item) { //only walk through selectable items. eg. transactions and not group markers if (item->isSelectable()) { auto t = dynamic_cast(item); if (!t) return; MyMoneySplit s = t->transaction().splitByPayee(d->m_payee.id()); const MyMoneyAccount& acc = file->account(s.accountId()); QString txt; if (s.action() != MyMoneySplit::actionName(eMyMoney::Split::Action::Amortization) && acc.accountType() != eMyMoney::Account::Type::AssetLoan && !file->isTransfer(t->transaction()) && t->transaction().splitCount() == 2) { MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false); if (account_count.contains(s0.accountId())) { account_count[s0.accountId()]++; } else { account_count[s0.accountId()] = 1; } } } item = item->nextItem(); } QMap::Iterator most_frequent, iter; most_frequent = account_count.begin(); for (iter = account_count.begin(); iter != account_count.end(); ++iter) { if (iter.value() > most_frequent.value()) { most_frequent = iter; } } if (most_frequent != account_count.end()) { d->ui->checkEnableDefaultCategory->setChecked(true); d->ui->comboDefaultCategory->setSelected(most_frequent.key()); d->setDirty(true); } } void KPayeesView::slotClosePayeeIdentifierSource() { Q_D(KPayeesView); if (!d->m_needLoad) d->ui->payeeIdentifiers->closeSource(); } void KPayeesView::slotSelectByVariant(const QVariantList& variant, eView::Intent intent) { switch (intent) { case eView::Intent::ShowPayee: if (variant.count() == 3) slotSelectPayeeAndTransaction(variant.at(0).toString(), variant.at(1).toString(), variant.at(2).toString()); break; default: break; } } void KPayeesView::slotStartRename(QListWidgetItem* item) { Q_D(KPayeesView); d->m_allowEditing = true; d->ui->m_payeesList->editItem(item); } // This variant is only called when a single payee is selected and renamed. void KPayeesView::slotRenameSinglePayee(QListWidgetItem* p) { Q_D(KPayeesView); //if there is no current item selected, exit if (d->m_allowEditing == false || !d->ui->m_payeesList->currentItem() || p != d->ui->m_payeesList->currentItem()) return; //qDebug() << "[KPayeesView::slotRenamePayee]"; // create a copy of the new name without appended whitespaces QString new_name = p->text(); if (d->m_payee.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a payee with the new name try { // this function call will throw an exception, if the payee // hasn't been found. MyMoneyFile::instance()->payeeByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A payee with the name '%1' already exists. It is not advisable to have " "multiple payees with the same identification name. Are you sure you would like " "to rename the payee?", new_name)) != KMessageBox::Yes) { p->setText(d->m_payee.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } d->m_payee.setName(new_name); d->m_newName = new_name; MyMoneyFile::instance()->modifyPayee(d->m_payee); // the above call to modifyPayee will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation d->ensurePayeeVisible(d->m_payee.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { p->setText(new_name); } } void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev) { Q_D(KPayeesView); Q_UNUSED(cur); Q_UNUSED(prev); d->m_allowEditing = false; } void KPayeesView::slotSelectPayee() { Q_D(KPayeesView); // check if the content of a currently selected payee was modified // and ask to store the data if (d->isDirty()) { if (KMessageBox::questionYesNo(this, i18n("Do you want to save the changes for %1?", d->m_newName), i18n("Save changes")) == KMessageBox::Yes) { d->m_inSelection = true; slotUpdatePayee(); d->m_inSelection = false; } } // make sure we always clear the selected list when listing again d->m_selectedPayeesList.clear(); // loop over all payees and count the number of payees, also // obtain last selected payee d->selectedPayees(d->m_selectedPayeesList); updatePayeeActions(d->m_selectedPayeesList); emit selectObjects(d->m_selectedPayeesList); if (d->m_selectedPayeesList.isEmpty()) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_balanceLabel->hide(); d->ui->m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons d->ui->m_renameButton->setEnabled(false); d->ui->m_mergeButton->setEnabled(false); d->clearItemData(); d->m_payee = MyMoneyPayee(); d->ui->m_syncAddressbook->setEnabled(false); return; // make sure we don't access an undefined payee } d->ui->m_deleteButton->setEnabled(true); //re-enable delete button d->ui->m_syncAddressbook->setEnabled(true); // if we have multiple payees selected, clear and disable the payee information if (d->m_selectedPayeesList.count() > 1) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_renameButton->setEnabled(false); // disable also the rename button d->ui->m_mergeButton->setEnabled(true); d->ui->m_balanceLabel->hide(); d->clearItemData(); } else { d->ui->m_mergeButton->setEnabled(false); d->ui->m_renameButton->setEnabled(true); } // otherwise we have just one selected, enable payee information widget d->ui->m_tabWidget->setEnabled(true); d->ui->m_balanceLabel->show(); // as of now we are updating only the last selected payee, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { d->m_payee = d->m_selectedPayeesList[0]; d->m_newName = d->m_payee.name(); d->ui->addressEdit->setEnabled(true); d->ui->addressEdit->setText(d->m_payee.address()); d->ui->postcodeEdit->setEnabled(true); d->ui->postcodeEdit->setText(d->m_payee.postcode()); d->ui->telephoneEdit->setEnabled(true); d->ui->telephoneEdit->setText(d->m_payee.telephone()); d->ui->emailEdit->setEnabled(true); d->ui->emailEdit->setText(d->m_payee.email()); d->ui->notesEdit->setText(d->m_payee.notes()); QStringList keys; bool ignorecase = false; MyMoneyPayee::payeeMatchType type = d->m_payee.matchData(ignorecase, keys); d->ui->matchTypeCombo->setCurrentIndex(d->ui->matchTypeCombo->findData(type)); d->ui->matchKeyEditList->clear(); d->ui->matchKeyEditList->insertStringList(keys); d->ui->checkMatchIgnoreCase->setChecked(ignorecase); d->ui->checkEnableDefaultCategory->setChecked(d->m_payee.defaultAccountEnabled()); d->ui->comboDefaultCategory->setSelected(d->m_payee.defaultAccountId()); d->ui->payeeIdentifiers->setSource(d->m_payee); slotPayeeDataChanged(); d->showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of payee: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); d->ui->m_register->clear(); d->m_selectedPayeesList.clear(); d->m_payee = MyMoneyPayee(); } d->m_allowEditing = true; } void KPayeesView::slotKeyListChanged() { Q_D(KPayeesView); bool rc = false; bool ignorecase = false; QStringList keys; d->m_payee.matchData(ignorecase, keys); if (d->ui->matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { rc |= (keys != d->ui->matchKeyEditList->items()); } d->setDirty(rc); } void KPayeesView::slotPayeeDataChanged() { Q_D(KPayeesView); bool rc = false; if (d->ui->m_tabWidget->isEnabled()) { rc |= ((d->m_payee.email().isEmpty() != d->ui->emailEdit->text().isEmpty()) || (!d->ui->emailEdit->text().isEmpty() && d->m_payee.email() != d->ui->emailEdit->text())); rc |= ((d->m_payee.address().isEmpty() != d->ui->addressEdit->toPlainText().isEmpty()) || (!d->ui->addressEdit->toPlainText().isEmpty() && d->m_payee.address() != d->ui->addressEdit->toPlainText())); rc |= ((d->m_payee.postcode().isEmpty() != d->ui->postcodeEdit->text().isEmpty()) || (!d->ui->postcodeEdit->text().isEmpty() && d->m_payee.postcode() != d->ui->postcodeEdit->text())); rc |= ((d->m_payee.telephone().isEmpty() != d->ui->telephoneEdit->text().isEmpty()) || (!d->ui->telephoneEdit->text().isEmpty() && d->m_payee.telephone() != d->ui->telephoneEdit->text())); rc |= ((d->m_payee.name().isEmpty() != d->m_newName.isEmpty()) || (!d->m_newName.isEmpty() && d->m_payee.name() != d->m_newName)); rc |= ((d->m_payee.notes().isEmpty() != d->ui->notesEdit->toPlainText().isEmpty()) || (!d->ui->notesEdit->toPlainText().isEmpty() && d->m_payee.notes() != d->ui->notesEdit->toPlainText())); bool ignorecase = false; QStringList keys; MyMoneyPayee::payeeMatchType type = d->m_payee.matchData(ignorecase, keys); rc |= (static_cast(type) != d->ui->matchTypeCombo->currentData().toUInt()); d->ui->checkMatchIgnoreCase->setEnabled(false); d->ui->matchKeyEditList->setEnabled(false); if (d->ui->matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) { d->ui->checkMatchIgnoreCase->setEnabled(true); // if we turn matching on, we default to 'ignore case' // TODO maybe make the default a user option if (type == MyMoneyPayee::matchDisabled && d->ui->matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) d->ui->checkMatchIgnoreCase->setChecked(true); rc |= (ignorecase != d->ui->checkMatchIgnoreCase->isChecked()); if (d->ui->matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { d->ui->matchKeyEditList->setEnabled(true); rc |= (keys != d->ui->matchKeyEditList->items()); } } rc |= (d->ui->checkEnableDefaultCategory->isChecked() != d->m_payee.defaultAccountEnabled()); if (d->ui->checkEnableDefaultCategory->isChecked()) { d->ui->comboDefaultCategory->setEnabled(true); d->ui->labelDefaultCategory->setEnabled(true); // this is only going to understand the first in the list of selected accounts if (d->ui->comboDefaultCategory->getSelected().isEmpty()) { rc |= !d->m_payee.defaultAccountId().isEmpty(); } else { QString temp = d->ui->comboDefaultCategory->getSelected(); rc |= (temp.isEmpty() != d->m_payee.defaultAccountId().isEmpty()) || (!d->m_payee.defaultAccountId().isEmpty() && temp != d->m_payee.defaultAccountId()); } } else { d->ui->comboDefaultCategory->setEnabled(false); d->ui->labelDefaultCategory->setEnabled(false); } rc |= (d->m_payee.payeeIdentifiers() != d->ui->payeeIdentifiers->identifiers()); } d->setDirty(rc); } void KPayeesView::slotUpdatePayee() { Q_D(KPayeesView); if (d->isDirty()) { MyMoneyFileTransaction ft; d->setDirty(false); try { d->m_payee.setName(d->m_newName); d->m_payee.setAddress(d->ui->addressEdit->toPlainText()); d->m_payee.setPostcode(d->ui->postcodeEdit->text()); d->m_payee.setTelephone(d->ui->telephoneEdit->text()); d->m_payee.setEmail(d->ui->emailEdit->text()); d->m_payee.setNotes(d->ui->notesEdit->toPlainText()); d->m_payee.setMatchData(static_cast(d->ui->matchTypeCombo->currentData().toUInt()), d->ui->checkMatchIgnoreCase->isChecked(), d->ui->matchKeyEditList->items()); d->m_payee.setDefaultAccountId(); d->m_payee.resetPayeeIdentifiers(d->ui->payeeIdentifiers->identifiers()); if (d->ui->checkEnableDefaultCategory->isChecked()) { QString temp; if (!d->ui->comboDefaultCategory->getSelected().isEmpty()) { temp = d->ui->comboDefaultCategory->getSelected(); d->m_payee.setDefaultAccountId(temp); } } MyMoneyFile::instance()->modifyPayee(d->m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KPayeesView::slotSyncAddressBook() { Q_D(KPayeesView); if (d->m_payeeRows.isEmpty()) { // empty list means no syncing is pending... foreach (auto item, d->ui->m_payeesList->selectedItems()) { d->m_payeeRows.append(d->ui->m_payeesList->row(item)); // ...so initialize one } d->ui->m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update // d->ui->m_syncAddressbook->setEnabled(false); // disallow concurrent syncs } if (d->m_payeeRows.count() <= d->m_payeeRow) { if (auto item = dynamic_cast(d->ui->m_payeesList->currentItem())) { // update ui if something is selected d->m_payee = item->payee(); d->ui->addressEdit->setText(d->m_payee.address()); d->ui->postcodeEdit->setText(d->m_payee.postcode()); d->ui->telephoneEdit->setText(d->m_payee.telephone()); } d->m_payeeRows.clear(); // that means end of sync d->m_payeeRow = 0; return; } if (auto item = dynamic_cast(d->ui->m_payeesList->item(d->m_payeeRows.at(d->m_payeeRow)))) d->m_payee = item->payee(); ++d->m_payeeRow; d->m_contact->fetchContact(d->m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched } void KPayeesView::slotContactFetched(const ContactData &identity) { Q_D(KPayeesView); if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched QString txt; if (!identity.street.isEmpty()) txt.append(identity.street + '\n'); if (!identity.locality.isEmpty()) { txt.append(identity.locality); if (!identity.postalCode.isEmpty()) txt.append(' ' + identity.postalCode + '\n'); else txt.append('\n'); } if (!identity.country.isEmpty()) txt.append(identity.country + '\n'); if (!txt.isEmpty() && d->m_payee.address().compare(txt) != 0) d->m_payee.setAddress(txt); if (!identity.postalCode.isEmpty() && d->m_payee.postcode().compare(identity.postalCode) != 0) d->m_payee.setPostcode(identity.postalCode); if (!identity.phoneNumber.isEmpty() && d->m_payee.telephone().compare(identity.phoneNumber) != 0) d->m_payee.setTelephone(identity.phoneNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyPayee(d->m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } slotSyncAddressBook(); // process next payee } void KPayeesView::slotSendMail() { Q_D(KPayeesView); QRegularExpression re(".+@.+"); if (re.match(d->m_payee.email()).hasMatch()) QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + d->m_payee.email(), QUrl::TolerantMode)); } void KPayeesView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KPayeesView); QTimer::singleShot(0, d->m_searchWidget, SLOT(setFocus())); } break; case eView::Action::ClosePayeeIdentifierSource: slotClosePayeeIdentifierSource(); break; default: break; } } void KPayeesView::refresh() { Q_D(KPayeesView); if (isVisible()) { if (d->m_inSelection) { QTimer::singleShot(0, this, SLOT(refresh())); } else { d->loadPayees(); d->m_needsRefresh = false; } } else { d->m_needsRefresh = true; } } void KPayeesView::showEvent(QShowEvent* event) { Q_D(KPayeesView); if (d->m_needLoad) d->init(); emit customActionRequested(View::Payees, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); QList list; d->selectedPayees(list); emit selectObjects(list); } void KPayeesView::updatePayeeActions(const QList &payees) { pActions[eMenu::Action::NewPayee]->setEnabled(true); const auto payeesCount = payees.count(); auto b = payeesCount == 1 ? true : false; pActions[eMenu::Action::RenamePayee]->setEnabled(b); b = payeesCount > 1 ? true : false; pActions[eMenu::Action::MergePayee]->setEnabled(b); b = payeesCount == 0 ? false : true; pActions[eMenu::Action::DeletePayee]->setEnabled(b); } void KPayeesView::slotSelectTransaction() { Q_D(KPayeesView); auto list = d->ui->m_register->selectedItems(); if (!list.isEmpty()) { const auto t = dynamic_cast(list[0]); if (t) emit selectByVariant(QVariantList {QVariant(t->split().accountId()), QVariant(t->transaction().id()) }, eView::Intent::ShowTransaction); } } void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) { Q_D(KPayeesView); if (!isVisible()) return; try { // clear filter d->m_searchWidget->clear(); d->m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = d->ui->m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { if (auto item = dynamic_cast(*payeesIt)) item->setSelected(false); ++payeesIt; } // find the payee in the list QListWidgetItem* it; for (int i = 0; i < d->ui->m_payeesList->count(); ++i) { it = d->ui->m_payeesList->item(i); auto item = dynamic_cast(it); if (item && item->payee().id() == payeeId) { d->ui->m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter); d->ui->m_payeesList->setCurrentItem(it); // active item and deselect all others d->ui->m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the payee selection is updated and transactions are updated accordingly slotSelectPayee(); - KMyMoneyRegister::RegisterItem *item = 0; - for (int i = 0; i < d->ui->m_register->rowCount(); ++i) { - item = d->ui->m_register->itemAtRow(i); - if (auto t = dynamic_cast(item)) { + KMyMoneyRegister::RegisterItem *registerItem = 0; + for (i = 0; i < d->ui->m_register->rowCount(); ++i) { + registerItem = d->ui->m_register->itemAtRow(i); + if (auto t = dynamic_cast(registerItem)) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { - d->ui->m_register->selectItem(item); - d->ui->m_register->ensureItemVisible(item); + d->ui->m_register->selectItem(registerItem); + d->ui->m_register->ensureItemVisible(registerItem); break; } } } - // quit out of for() loop + // quit out of outer for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", qPrintable(e.what())); } } void KPayeesView::slotShowPayeesMenu(const QPoint& /*p*/) { Q_D(KPayeesView); if (dynamic_cast(d->ui->m_payeesList->currentItem())) { slotSelectPayee(); pMenus[eMenu::Menu::Payee]->exec(QCursor::pos()); } } void KPayeesView::slotHelp() { KHelpClient::invokeHelp("details.payees"); } void KPayeesView::slotChangeFilter(int index) { Q_D(KPayeesView); //update the filter type then reload the payees list d->m_payeeFilterType = index; d->loadPayees(); } void KPayeesView::slotNewPayee() { QString id; KMyMoneyUtils::newPayee(i18n("New Payee"), id); slotSelectPayeeAndTransaction(id); } void KPayeesView::slotRenamePayee() { Q_D(KPayeesView); if (d->ui->m_payeesList->currentItem() && d->ui->m_payeesList->selectedItems().count() == 1) { slotStartRename(d->ui->m_payeesList->currentItem()); } } void KPayeesView::slotDeletePayee() { Q_D(KPayeesView); if (d->m_selectedPayeesList.isEmpty()) return; // shouldn't happen // get confirmation from user QString prompt; if (d->m_selectedPayeesList.size() == 1) prompt = i18n("

Do you really want to remove the payee %1?

", d->m_selectedPayeesList.front().name()); else prompt = i18n("Do you really want to remove all selected payees?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Payee")) == KMessageBox::No) return; d->payeeReassign(KPayeeReassignDlg::TypeDelete); } void KPayeesView::slotMergePayee() { Q_D(KPayeesView); if (d->m_selectedPayeesList.size() < 1) return; // shouldn't happen if (KMessageBox::questionYesNo(this, i18n("

Do you really want to merge the selected payees?"), i18n("Merge Payees")) == KMessageBox::No) return; if (d->payeeReassign(KPayeeReassignDlg::TypeMerge)) // clean selection since we just deleted the selected payees d->m_selectedPayeesList.clear(); } diff --git a/kmymoney/views/ktagsview.cpp b/kmymoney/views/ktagsview.cpp index cb01828f9..d57e94033 100644 --- a/kmymoney/views/ktagsview.cpp +++ b/kmymoney/views/ktagsview.cpp @@ -1,758 +1,758 @@ /*************************************************************************** ktagsview.cpp ------------- begin : Sat Oct 13 2012 copyright : (C) 2012 by Alessandro Russo (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "ktagsview.h" #include "ktagsview_p.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyexception.h" #include "mymoneymoney.h" #include "mymoneyprice.h" #include "kmymoneysettings.h" #include "ktagreassigndlg.h" #include "kmymoneyutils.h" #include "kmymoneymvccombo.h" #include "mymoneysecurity.h" #include "mymoneysplit.h" #include "mymoneytransaction.h" #include "mymoneyschedule.h" #include "transaction.h" #include "menuenums.h" using namespace Icons; /* -------------------------------------------------------------------------*/ /* KTransactionPtrVector */ /* -------------------------------------------------------------------------*/ // *** KTagsView Implementation *** KTagsView::KTagsView(QWidget *parent) : KMyMoneyViewBase(*new KTagsViewPrivate(this), parent) { typedef void(KTagsView::*KTagsViewFunc)(); const QHash actionConnections { {eMenu::Action::NewTag, &KTagsView::slotNewTag}, {eMenu::Action::RenameTag, &KTagsView::slotRenameTag}, {eMenu::Action::DeleteTag, &KTagsView::slotDeleteTag} }; for (auto a = actionConnections.cbegin(); a != actionConnections.cend(); ++a) connect(pActions[a.key()], &QAction::triggered, this, a.value()); } KTagsView::~KTagsView() { } void KTagsView::executeCustomAction(eView::Action action) { switch(action) { case eView::Action::Refresh: refresh(); break; case eView::Action::SetDefaultFocus: { Q_D(KTagsView); QTimer::singleShot(0, d->m_searchWidget, SLOT(setFocus())); } break; default: break; } } void KTagsView::refresh() { Q_D(KTagsView); if (isVisible()) { if (d->m_inSelection) QTimer::singleShot(0, this, SLOT(refresh())); else loadTags(); d->m_needsRefresh = false; } else { d->m_needsRefresh = true; } } void KTagsView::slotStartRename(QListWidgetItem* item) { Q_D(KTagsView); d->m_allowEditing = true; d->ui->m_tagsList->editItem(item); } // This variant is only called when a single tag is selected and renamed. void KTagsView::slotRenameSingleTag(QListWidgetItem* ta) { Q_D(KTagsView); //if there is no current item selected, exit if (d->m_allowEditing == false || !d->ui->m_tagsList->currentItem() || ta != d->ui->m_tagsList->currentItem()) return; //qDebug() << "[KTagsView::slotRenameTag]"; // create a copy of the new name without appended whitespaces auto new_name = ta->text(); if (d->m_tag.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a tag with the new name try { // this function call will throw an exception, if the tag // hasn't been found. MyMoneyFile::instance()->tagByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A tag with the name '%1' already exists. It is not advisable to have " "multiple tags with the same identification name. Are you sure you would like " "to rename the tag?", new_name)) != KMessageBox::Yes) { ta->setText(d->m_tag.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } d->m_tag.setName(new_name); d->m_newName = new_name; MyMoneyFile::instance()->modifyTag(d->m_tag); // the above call to modifyTag will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation ensureTagVisible(d->m_tag.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { ta->setText(new_name); } } void KTagsView::ensureTagVisible(const QString& id) { Q_D(KTagsView); for (int i = 0; i < d->ui->m_tagsList->count(); ++i) { KTagListItem* ta = dynamic_cast(d->ui->m_tagsList->item(0)); if (ta && ta->tag().id() == id) { d->ui->m_tagsList->scrollToItem(ta, QAbstractItemView::PositionAtCenter); d->ui->m_tagsList->setCurrentItem(ta); // active item and deselect all others d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KTagsView::selectedTags(QList& tagsList) const { Q_D(const KTagsView); QList selectedItems = d->ui->m_tagsList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*itemsIt); if (item) tagsList << item->tag(); ++itemsIt; } } void KTagsView::slotSelectTag(QListWidgetItem* cur, QListWidgetItem* prev) { Q_D(KTagsView); Q_UNUSED(cur); Q_UNUSED(prev); d->m_allowEditing = false; } void KTagsView::slotSelectTag() { Q_D(KTagsView); // check if the content of a currently selected tag was modified // and ask to store the data if (d->ui->m_updateButton->isEnabled()) { if (KMessageBox::questionYesNo(this, QString("%1").arg( i18n("Do you want to save the changes for %1?", d->m_newName)), i18n("Save changes")) == KMessageBox::Yes) { d->m_inSelection = true; slotUpdateTag(); d->m_inSelection = false; } } // loop over all tags and count the number of tags, also // obtain last selected tag QList tagsList; selectedTags(tagsList); slotSelectTags(tagsList); if (tagsList.isEmpty()) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_balanceLabel->hide(); d->ui->m_deleteButton->setEnabled(false); //disable delete and rename button d->ui->m_renameButton->setEnabled(false); clearItemData(); d->m_tag = MyMoneyTag(); return; // make sure we don't access an undefined tag } d->ui->m_deleteButton->setEnabled(true); //re-enable delete button // if we have multiple tags selected, clear and disable the tag information if (tagsList.count() > 1) { d->ui->m_tabWidget->setEnabled(false); // disable tab widget d->ui->m_renameButton->setEnabled(false); // disable also the rename button d->ui->m_balanceLabel->hide(); clearItemData(); } else d->ui->m_renameButton->setEnabled(true); // otherwise we have just one selected, enable tag information widget and renameButton d->ui->m_tabWidget->setEnabled(true); d->ui->m_balanceLabel->show(); // as of now we are updating only the last selected tag, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { d->m_tag = tagsList[0]; d->m_newName = d->m_tag.name(); d->ui->m_colorbutton->setEnabled(true); d->ui->m_colorbutton->setColor(d->m_tag.tagColor()); d->ui->m_closed->setEnabled(true); d->ui->m_closed->setChecked(d->m_tag.isClosed()); d->ui->m_notes->setEnabled(true); d->ui->m_notes->setText(d->m_tag.notes()); slotTagDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of tag: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); d->ui->m_register->clear(); d->m_tag = MyMoneyTag(); } d->m_allowEditing = true; } void KTagsView::clearItemData() { Q_D(KTagsView); d->ui->m_colorbutton->setColor(QColor()); d->ui->m_closed->setChecked(false); d->ui->m_notes->setText(QString()); showTransactions(); } void KTagsView::showTransactions() { Q_D(KTagsView); MyMoneyMoney balance; auto file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order d->ui->m_register->setSortOrder(KMyMoneySettings::sortSearchView()); // clear the register d->ui->m_register->clear(); if (d->m_tag.id().isEmpty() || !d->ui->m_tabWidget->isEnabled()) { d->ui->m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; filter.addTag(d->m_tag.id()); filter.setDateFilter(KMyMoneySettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(d->m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = d->m_transactionList.constBegin(); it != d->m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(d->ui->m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers d->ui->m_register->addGroupMarkers(); // sort the transactions according to the sort setting d->ui->m_register->sortItems(); // remove trailing and adjacent markers d->ui->m_register->removeUnwantedGroupMarkers(); d->ui->m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here d->ui->m_register->setUpdatesEnabled(true); d->ui->m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KTagsView::slotTagDataChanged() { Q_D(KTagsView); auto rc = false; if (d->ui->m_tabWidget->isEnabled()) { rc |= ((d->m_tag.tagColor().isValid() != d->ui->m_colorbutton->color().isValid()) || (d->ui->m_colorbutton->color().isValid() && d->m_tag.tagColor() != d->ui->m_colorbutton->color())); rc |= (d->ui->m_closed->isChecked() != d->m_tag.isClosed()); rc |= ((d->m_tag.notes().isEmpty() != d->ui->m_notes->toPlainText().isEmpty()) || (!d->ui->m_notes->toPlainText().isEmpty() && d->m_tag.notes() != d->ui->m_notes->toPlainText())); } d->ui->m_updateButton->setEnabled(rc); } void KTagsView::slotUpdateTag() { Q_D(KTagsView); if (d->ui->m_updateButton->isEnabled()) { MyMoneyFileTransaction ft; d->ui->m_updateButton->setEnabled(false); try { d->m_tag.setName(d->m_newName); d->m_tag.setTagColor(d->ui->m_colorbutton->color()); d->m_tag.setClosed(d->ui->m_closed->isChecked()); d->m_tag.setNotes(d->ui->m_notes->toPlainText()); MyMoneyFile::instance()->modifyTag(d->m_tag); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to modify tag"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KTagsView::showEvent(QShowEvent* event) { Q_D(KTagsView); if (d->m_needLoad) d->init(); emit customActionRequested(View::Tags, eView::Action::AboutToShow); if (d->m_needsRefresh) refresh(); // don't forget base class implementation QWidget::showEvent(event); QList list; selectedTags(list); slotSelectTags(list); } void KTagsView::updateTagActions(const QList& tags) { pActions[eMenu::Action::NewTag]->setEnabled(true); const auto tagsCount = tags.count(); auto b = tagsCount == 1 ? true : false; pActions[eMenu::Action::RenameTag]->setEnabled(b); b = tagsCount >= 1 ? true : false; pActions[eMenu::Action::DeleteTag]->setEnabled(b); } void KTagsView::loadTags() { Q_D(KTagsView); if (d->m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = d->ui->m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) isSelected[item->tag().id()] = true; ++tagsIt; } // keep current selected item KTagListItem *currentItem = static_cast(d->ui->m_tagsList->currentItem()); if (currentItem) id = currentItem->tag().id(); d->m_allowEditing = false; // clear the list d->m_searchWidget->clear(); d->m_searchWidget->updateSearch(); d->ui->m_tagsList->clear(); d->ui->m_register->clear(); currentItem = 0; QListlist = file->tagList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (d->m_tagFilterType == (int)eView::Tag::All || (d->m_tagFilterType == (int)eView::Tag::Referenced && file->isReferenced(*it)) || (d->m_tagFilterType == (int)eView::Tag::Unused && !file->isReferenced(*it)) || (d->m_tagFilterType == (int)eView::Tag::Opened && !(*it).isClosed()) || (d->m_tagFilterType == (int)eView::Tag::Closed && (*it).isClosed())) { KTagListItem* item = new KTagListItem(d->ui->m_tagsList, *it); if (item->tag().id() == id) currentItem = item; if (isSelected[item->tag().id()]) item->setSelected(true); } } d->ui->m_tagsList->sortItems(); if (currentItem) { d->ui->m_tagsList->setCurrentItem(currentItem); d->ui->m_tagsList->scrollToItem(currentItem); } slotSelectTag(0, 0); d->m_allowEditing = true; } void KTagsView::slotSelectTransaction() { Q_D(KTagsView); QList list = d->ui->m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit selectByVariant(QVariantList {QVariant(t->split().accountId()), QVariant(t->transaction().id())}, eView::Intent::ShowTransaction); } } void KTagsView::slotSelectTagAndTransaction(const QString& tagId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; Q_D(KTagsView); try { // clear filter d->m_searchWidget->clear(); d->m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = d->ui->m_tagsList->selectedItems(); QList::const_iterator tagsIt = selectedItems.constBegin(); while (tagsIt != selectedItems.constEnd()) { KTagListItem* item = dynamic_cast(*tagsIt); if (item) item->setSelected(false); ++tagsIt; } // find the tag in the list QListWidgetItem* it; for (int i = 0; i < d->ui->m_tagsList->count(); ++i) { it = d->ui->m_tagsList->item(i); KTagListItem* item = dynamic_cast(it); if (item && item->tag().id() == tagId) { d->ui->m_tagsList->scrollToItem(it, QAbstractItemView::PositionAtCenter); d->ui->m_tagsList->setCurrentItem(it); // active item and deselect all others d->ui->m_tagsList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the tag selection is updated and transactions are updated accordingly slotSelectTag(); - KMyMoneyRegister::RegisterItem *item = 0; - for (int i = 0; i < d->ui->m_register->rowCount(); ++i) { - item = d->ui->m_register->itemAtRow(i); - KMyMoneyRegister::Transaction* t = dynamic_cast(item); + KMyMoneyRegister::RegisterItem *registerItem = 0; + for (i = 0; i < d->ui->m_register->rowCount(); ++i) { + registerItem = d->ui->m_register->itemAtRow(i); + KMyMoneyRegister::Transaction* t = dynamic_cast(registerItem); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { - d->ui->m_register->selectItem(item); - d->ui->m_register->ensureItemVisible(item); + d->ui->m_register->selectItem(registerItem); + d->ui->m_register->ensureItemVisible(registerItem); break; } } } - // quit out of for() loop + // quit out of outer for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KTagsView::slotSelectTagAndTransaction %s", qPrintable(e.what())); } } void KTagsView::slotSelectTagAndTransaction(const QString& tagId) { slotSelectTagAndTransaction(tagId, QString(), QString()); } void KTagsView::slotShowTagsMenu(const QPoint& /*ta*/) { Q_D(KTagsView); auto item = dynamic_cast(d->ui->m_tagsList->currentItem()); if (item) { slotSelectTag(); pMenus[eMenu::Menu::Tag]->exec(QCursor::pos()); } } void KTagsView::slotHelp() { KHelpClient::invokeHelp("details.tags.attributes"); //FIXME-ALEX update help file } void KTagsView::slotChangeFilter(int index) { Q_D(KTagsView); //update the filter type then reload the tags list d->m_tagFilterType = index; loadTags(); } void KTagsView::slotSelectTags(const QList& list) { Q_D(KTagsView); d->m_selectedTags = list; updateTagActions(list); } void KTagsView::slotNewTag() { QString id; KMyMoneyUtils::newTag(i18n("New Tag"), id); slotSelectTagAndTransaction(id); } void KTagsView::slotRenameTag() { Q_D(KTagsView); if (d->ui->m_tagsList->currentItem() && d->ui->m_tagsList->selectedItems().count() == 1) { slotStartRename(d->ui->m_tagsList->currentItem()); } } void KTagsView::slotDeleteTag() { Q_D(KTagsView); if (d->m_selectedTags.isEmpty()) return; // shouldn't happen const auto file = MyMoneyFile::instance(); // first create list with all non-selected tags QList remainingTags = file->tagList(); QList::iterator it_ta; for (it_ta = remainingTags.begin(); it_ta != remainingTags.end();) { if (d->m_selectedTags.contains(*it_ta)) { it_ta = remainingTags.erase(it_ta); } else { ++it_ta; } } // get confirmation from user QString prompt; if (d->m_selectedTags.size() == 1) prompt = i18n("

Do you really want to remove the tag %1?

", d->m_selectedTags.front().name()); else prompt = i18n("Do you really want to remove all selected tags?"); if (KMessageBox::questionYesNo(this, prompt, i18n("Remove Tag")) == KMessageBox::No) return; MyMoneyFileTransaction ft; try { // create a transaction filter that contains all tags selected for removal MyMoneyTransactionFilter f = MyMoneyTransactionFilter(); for (QList::const_iterator it = d->m_selectedTags.constBegin(); it != d->m_selectedTags.constEnd(); ++it) { f.addTag((*it).id()); } // request a list of all transactions that still use the tags in question auto translist = file->transactionList(f); // qDebug() << "[KTagsView::slotDeleteTag] " << translist.count() << " transaction still assigned to tags"; // now get a list of all schedules that make use of one of the tags QList used_schedules; foreach (const auto schedule, file->scheduleList()) { // loop over all splits in the transaction of the schedule foreach (const auto split, schedule.transaction().splits()) { for (auto i = 0; i < split.tagIdList().size(); ++i) { // is the tag in the split to be deleted? if (d->tagInList(d->m_selectedTags, split.tagIdList()[i])) { used_schedules.push_back(schedule); // remember this schedule break; } } } } // qDebug() << "[KTagsView::slotDeleteTag] " << used_schedules.count() << " schedules use one of the selected tags"; MyMoneyTag newTag; // if at least one tag is still referenced, we need to reassign its transactions first if (!translist.isEmpty() || !used_schedules.isEmpty()) { // show error message if no tags remain //FIXME-ALEX Tags are optional so we can delete all of them and simply delete every tagId from every transaction if (remainingTags.isEmpty()) { KMessageBox::sorry(this, i18n("At least one transaction/scheduled transaction is still referenced by a tag. " "Currently you have all tags selected. However, at least one tag must remain so " "that the transaction/scheduled transaction can be reassigned.")); return; } // show transaction reassignment dialog auto dlg = new KTagReassignDlg(this); KMyMoneyMVCCombo::setSubstringSearchForChildren(dlg, !KMyMoneySettings::stringMatchFromStart()); auto tag_id = dlg->show(remainingTags); delete dlg; // and kill the dialog if (tag_id.isEmpty()) //FIXME-ALEX Let the user choose to not reassign a to-be deleted tag to another one. return; // the user aborted the dialog, so let's abort as well newTag = file->tag(tag_id); // TODO : check if we have a report that explicitively uses one of our tags // and issue an appropriate warning try { // now loop over all transactions and reassign tag for (auto& transaction : translist) { // create a copy of the splits list in the transaction // loop over all splits for (auto& split : transaction.splits()) { QList tagIdList = split.tagIdList(); for (int i = 0; i < tagIdList.size(); ++i) { // if the split is assigned to one of the selected tags, we need to modify it if (d->tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } split.setTagIdList(tagIdList); // first modify tag list in current split // then modify the split in our local copy of the transaction list transaction.modifySplit(split); // this does not modify the list object 'splits'! } // for - Splits file->modifyTransaction(transaction); // modify the transaction in the MyMoney object } // for - Transactions // now loop over all schedules and reassign tags for (auto& schedule : used_schedules) { // create copy of transaction in current schedule auto trans = schedule.transaction(); // create copy of lists of splits for (auto& split : trans.splits()) { QList tagIdList = split.tagIdList(); for (auto i = 0; i < tagIdList.size(); ++i) { if (d->tagInList(d->m_selectedTags, tagIdList[i])) { tagIdList.removeAt(i); if (tagIdList.indexOf(tag_id) == -1) tagIdList.append(tag_id); i = -1; // restart from the first element } } split.setTagIdList(tagIdList); trans.modifySplit(split); // does not modify the list object 'splits'! } // for - Splits // store transaction in current schedule schedule.setTransaction(trans); file->modifySchedule(schedule); // modify the schedule in the MyMoney engine } // for - Schedules } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to reassign tag of transaction/split"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } // if !translist.isEmpty() // now loop over all selected tags and remove them for (QList::iterator it = d->m_selectedTags.begin(); it != d->m_selectedTags.end(); ++it) { file->removeTag(*it); } ft.commit(); // If we just deleted the tags, they sure don't exist anymore slotSelectTags(QList()); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(this, i18n("Unable to remove tag(s)"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } diff --git a/kmymoney/views/ledgerdelegate.cpp b/kmymoney/views/ledgerdelegate.cpp index 56d9bf7ed..9ac44cb19 100644 --- a/kmymoney/views/ledgerdelegate.cpp +++ b/kmymoney/views/ledgerdelegate.cpp @@ -1,798 +1,798 @@ /*************************************************************************** ledgerdelegate.cpp ------------------- begin : Sat Aug 8 2015 copyright : (C) 2015 by Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "ledgerdelegate.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include // ---------------------------------------------------------------------------- // Project Includes #include "ledgerview.h" #include "ledgermodel.h" #include "newtransactioneditor.h" static unsigned char attentionSign[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, 0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D, 0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49, 0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64, 0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E, 0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F, 0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00, 0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D, 0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7, 0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77, 0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10, 0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55, 0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC, 0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8, 0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90, 0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0, 0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A, 0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF, 0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD, 0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84, 0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5, 0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A, 0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C, 0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5, 0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54, 0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1, 0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C, 0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C, 0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07, 0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27, 0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D, 0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32, 0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60, 0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B, 0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21, 0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A, 0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F, 0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99, 0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04, 0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C, 0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8, 0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01, 0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5, 0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18, 0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05, 0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C, 0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15, 0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB, 0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD, 0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA, 0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05, 0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE, 0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6, 0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C, 0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E, 0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06, 0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC, 0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC, 0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65, 0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0, 0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3, 0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA, 0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C, 0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD, 0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1, 0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F, 0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE, 0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA, 0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13, 0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C, 0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66, 0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63, 0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; QColor LedgerDelegate::m_erroneousColor = QColor(Qt::red); QColor LedgerDelegate::m_importedColor = QColor(Qt::yellow); QColor LedgerDelegate::m_separatorColor = QColor(0xff, 0xf2, 0x9b); class LedgerSeparatorDate : public LedgerSeparator { public: LedgerSeparatorDate(eLedgerModel::Role role); virtual ~LedgerSeparatorDate() {} - virtual bool rowHasSeparator(const QModelIndex& index) const; - virtual QString separatorText(const QModelIndex& index) const; - virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const; + virtual bool rowHasSeparator(const QModelIndex& index) const override; + virtual QString separatorText(const QModelIndex& index) const override; + virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override; protected: QString getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const; QMap m_entries; }; class LedgerSeparatorOnlineBalance : public LedgerSeparatorDate { public: LedgerSeparatorOnlineBalance(eLedgerModel::Role role); virtual ~LedgerSeparatorOnlineBalance() {} - virtual bool rowHasSeparator(const QModelIndex& index) const; - virtual QString separatorText(const QModelIndex& index) const; - virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const; + virtual bool rowHasSeparator(const QModelIndex& index) const override; + virtual QString separatorText(const QModelIndex& index) const override; + virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override; void setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction); private: QString m_balanceAmount; }; QDate LedgerSeparator::firstFiscalDate; bool LedgerSeparator::showFiscalDate = true; bool LedgerSeparator::showFancyDate = true; void LedgerSeparator::setFirstFiscalDate(int firstMonth, int firstDay) { firstFiscalDate = QDate(QDate::currentDate().year(), firstMonth, firstDay); if (QDate::currentDate() < firstFiscalDate) firstFiscalDate = firstFiscalDate.addYears(-1); } QModelIndex LedgerSeparator::nextIndex(const QModelIndex& index) const { const int nextRow = index.row() + 1; if (index.isValid() && (nextRow < index.model()->rowCount(QModelIndex()))) { const QAbstractItemModel* model = index.model(); return model->index(nextRow, 0, QModelIndex()); } return QModelIndex(); } LedgerSeparatorDate::LedgerSeparatorDate(eLedgerModel::Role role) : LedgerSeparator(role) { const QDate today = QDate::currentDate(); const QDate thisMonth(today.year(), today.month(), 1); const QDate lastMonth = thisMonth.addMonths(-1); const QDate yesterday = today.addDays(-1); // a = QDate::dayOfWeek() todays weekday (1 = Monday, 7 = Sunday) // b = QLocale().firstDayOfWeek() first day of week (1 = Monday, 7 = Sunday) int weekStartOfs = today.dayOfWeek() - QLocale().firstDayOfWeek(); if (weekStartOfs < 0) { weekStartOfs = 7 + weekStartOfs; } const QDate thisWeek = today.addDays(-weekStartOfs); const QDate lastWeek = thisWeek.addDays(-7); const QDate thisYear(today.year(), 1, 1); m_entries[thisYear] = i18n("This year"); m_entries[lastMonth] = i18n("Last month"); m_entries[thisMonth] = i18n("This month"); m_entries[lastWeek] = i18n("Last week"); m_entries[thisWeek] = i18n("This week"); m_entries[yesterday] = i18n("Yesterday"); m_entries[today] = i18n("Today"); m_entries[today.addDays(1)] = i18n("Future transactions"); m_entries[thisWeek.addDays(7)] = i18n("Next week"); m_entries[thisMonth.addMonths(1)] = i18n("Next month"); if (showFiscalDate && firstFiscalDate.isValid()) { m_entries[firstFiscalDate] = i18n("Current fiscal year"); m_entries[firstFiscalDate.addYears(-1)] = i18n("Previous fiscal year"); m_entries[firstFiscalDate.addYears(1)] = i18n("Next fiscal year"); } } QString LedgerSeparatorDate::getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const { Q_ASSERT(index.isValid()); Q_ASSERT(nextIndex.isValid()); Q_ASSERT(index.model() == nextIndex.model()); const QAbstractItemModel* model = index.model(); QString rc; if(!m_entries.isEmpty()) { if (model->data(index, (int)m_role).toDate() != model->data(nextIndex, (int)m_role).toDate()) { const QDate key = model->data(index, (int)m_role).toDate(); const QDate endKey = model->data(nextIndex, (int)m_role).toDate(); QMap::const_iterator it = m_entries.upperBound(key); while((it != m_entries.cend()) && (it.key() <= endKey)) { rc = *it; ++it; } } } return rc; } bool LedgerSeparatorDate::rowHasSeparator(const QModelIndex& index) const { bool rc = false; if(!m_entries.isEmpty()) { QModelIndex nextIdx = nextIndex(index); if(nextIdx.isValid() ) { const QString id = nextIdx.model()->data(nextIdx, (int)eLedgerModel::Role::TransactionSplitId).toString(); // For a new transaction the id is completely empty, for a split view the transaction // part is filled but the split id is empty and the string ends with a dash // and we never draw a separator in front of that row if(!id.isEmpty() && !id.endsWith('-')) { rc = !getEntry(index, nextIdx).isEmpty(); } } } return rc; } QString LedgerSeparatorDate::separatorText(const QModelIndex& index) const { QModelIndex nextIdx = nextIndex(index); if(nextIdx.isValid()) { return getEntry(index, nextIdx); } return QString(); } void LedgerSeparatorDate::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const { Q_UNUSED(index); KColorScheme::adjustBackground(palette, KColorScheme::ActiveBackground, QPalette::Base, KColorScheme::Button, KSharedConfigPtr()); } LedgerSeparatorOnlineBalance::LedgerSeparatorOnlineBalance(eLedgerModel::Role role) : LedgerSeparatorDate(role) { // we don't need the standard values m_entries.clear(); } void LedgerSeparatorOnlineBalance::setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction) { m_entries.clear(); if (date.isValid()) { m_balanceAmount = amount.formatMoney(fraction); m_entries[date] = i18n("Online statement balance: %1", m_balanceAmount); } } bool LedgerSeparatorOnlineBalance::rowHasSeparator(const QModelIndex& index) const { bool rc = false; if(!m_entries.isEmpty()) { QModelIndex nextIdx = nextIndex(index); const QAbstractItemModel* model = index.model(); const QDate date = model->data(index, (int)m_role).toDate(); // only a real transaction can have an online balance separator if(model->data(index, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty()) { // if this is not the last entry and not a schedule? if(nextIdx.isValid()) { // index points to the last entry of a date rc = (date != model->data(nextIdx, (int)m_role).toDate()); if (!rc) { // in case it's the same date, we need to check if this is the last real transaction // and the next one is a scheduled transaction if(!model->data(nextIdx, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty() ) { rc = (date <= m_entries.firstKey()); } } else { // check if this the spot for the online balance data rc &= ((date <= m_entries.firstKey()) && (model->data(nextIdx, (int)m_role).toDate() > m_entries.firstKey())); } } else { rc = (date <= m_entries.firstKey()); } } } return rc; } QString LedgerSeparatorOnlineBalance::separatorText(const QModelIndex& index) const { if(rowHasSeparator(index)) { return m_entries.first(); } return QString(); } void LedgerSeparatorOnlineBalance::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const { const QAbstractItemModel* model = index.model(); QModelIndex amountIndex = model->index(index.row(), (int)eLedgerModel::Column::Balance); QString amount = model->data(amountIndex).toString(); KColorScheme::BackgroundRole role = KColorScheme::PositiveBackground; if (!m_entries.isEmpty()) { if(amount != m_balanceAmount) { role = KColorScheme::NegativeBackground; } } KColorScheme::adjustBackground(palette, role, QPalette::Base, KColorScheme::Button, KSharedConfigPtr()); } class LedgerDelegate::Private { public: Private() : m_editor(0) , m_view(0) , m_editorRow(-1) , m_separator(0) , m_onlineBalanceSeparator(0) {} ~Private() { delete m_separator; } inline bool displaySeparator(const QModelIndex& index) const { return m_separator && m_separator->rowHasSeparator(index); } inline bool displayOnlineBalanceSeparator(const QModelIndex& index) const { return m_onlineBalanceSeparator && m_onlineBalanceSeparator->rowHasSeparator(index); } NewTransactionEditor* m_editor; LedgerView* m_view; int m_editorRow; LedgerSeparator* m_separator; LedgerSeparatorOnlineBalance* m_onlineBalanceSeparator; }; LedgerDelegate::LedgerDelegate(LedgerView* parent) : QStyledItemDelegate(parent) , d(new Private) { d->m_view = parent; } LedgerDelegate::~LedgerDelegate() { delete d; } void LedgerDelegate::setSortRole(eLedgerModel::Role role) { delete d->m_separator; delete d->m_onlineBalanceSeparator; d->m_separator = 0; d->m_onlineBalanceSeparator = 0; switch(role) { case eLedgerModel::Role::PostDate: d->m_separator = new LedgerSeparatorDate(role); d->m_onlineBalanceSeparator = new LedgerSeparatorOnlineBalance(role); break; default: qDebug() << "LedgerDelegate::setSortRole role" << (int)role << "not implemented"; break; } } void LedgerDelegate::setErroneousColor(const QColor& color) { m_erroneousColor = color; } void LedgerDelegate::setOnlineBalance(const QDate& date, const MyMoneyMoney& amount, int fraction) { if(d->m_onlineBalanceSeparator) { d->m_onlineBalanceSeparator->setSeparatorData(date, amount, fraction); } } QWidget* LedgerDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); if(index.isValid()) { if(d->m_view->selectionModel()->selectedRows().count() > 1) { qDebug() << "Editing multiple transactions at once is not yet supported"; /** * @todo replace the following three lines with the creation of a special * editor that can handle multiple transactions at once */ d->m_editor = 0; LedgerDelegate* const that = const_cast(this); emit that->closeEditor(d->m_editor, NoHint); } else { d->m_editor = new NewTransactionEditor(parent, d->m_view->accountId()); } if(d->m_editor) { d->m_editorRow = index.row(); connect(d->m_editor, SIGNAL(done()), this, SLOT(endEdit())); emit sizeHintChanged(index); } } else { qFatal("LedgerDelegate::createEditor(): we should never end up here"); } return d->m_editor; } int LedgerDelegate::editorRow() const { return d->m_editorRow; } void LedgerDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // never change the background of the cell the mouse is hovering over opt.state &= ~QStyle::State_MouseOver; // show the focus only on the detail column opt.state &= ~QStyle::State_HasFocus; // if selected, always show as active, so that the // background does not change when the editor is shown if (opt.state & QStyle::State_Selected) { opt.state |= QStyle::State_Active; } painter->save(); QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent()); const bool editWidgetIsVisible = d->m_view && d->m_view->indexWidget(index); const bool rowHasSeparator = d->displaySeparator(index); const bool rowHasOnlineBalance = d->displayOnlineBalanceSeparator(index); // Background QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin); const int lineHeight = opt.fontMetrics.lineSpacing() + 2; if (rowHasSeparator) { // don't draw over the separator space opt.rect.setHeight(opt.rect.height() - lineHeight ); } if (rowHasOnlineBalance) { // don't draw over the online balance space opt.rect.setHeight(opt.rect.height() - lineHeight ); } style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); QPalette::ColorGroup cg; // Do not paint text if the edit widget is shown if (!editWidgetIsVisible) { if(view && (index.column() == (int)eLedgerModel::Column::Detail)) { if(view->currentIndex().row() == index.row()) { opt.state |= QStyle::State_HasFocus; } } const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin); const bool selected = opt.state & QStyle::State_Selected; QStringList lines; if(index.column() == (int)eLedgerModel::Column::Detail) { lines << index.model()->data(index, (int)eLedgerModel::Role::PayeeName).toString(); if(selected) { lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString(); lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString(); } else { if(lines.at(0).isEmpty()) { lines.clear(); lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString(); } if(lines.at(0).isEmpty()) { lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString(); } } lines.removeAll(QString()); } const bool erroneous = index.model()->data(index, (int)eLedgerModel::Role::Erroneous).toBool(); // draw the text items if(!opt.text.isEmpty() || !lines.isEmpty()) { // check if it is a scheduled transaction and display it as inactive if(!index.model()->data(index, (int)eLedgerModel::Role::ScheduleId).toString().isEmpty()) { opt.state &= ~QStyle::State_Enabled; } cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (selected) { // always use the normal palette since the background is also in normal painter->setPen(opt.palette.color(QPalette::ColorGroup(QPalette::Normal), QPalette::HighlightedText)); } else if (erroneous) { painter->setPen(m_erroneousColor); } else { painter->setPen(opt.palette.color(cg, QPalette::Text)); } if (opt.state & QStyle::State_Editing) { painter->setPen(opt.palette.color(cg, QPalette::Text)); painter->drawRect(textArea.adjusted(0, 0, -1, -1)); } // collect data for the various columns if(index.column() == (int)eLedgerModel::Column::Detail) { for(int i = 0; i < lines.count(); ++i) { painter->drawText(textArea.adjusted(0, lineHeight * i, 0, 0), opt.displayAlignment, lines[i]); } } else { painter->drawText(textArea, opt.displayAlignment, opt.text); } } // draw the focus rect if(opt.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(opt); o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget); o.state |= QStyle::State_KeyboardFocusChange; o.state |= QStyle::State_Item; cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget); } // draw the attention mark if((index.column() == (int)eLedgerModel::Column::Detail) && erroneous) { QPixmap attention; attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0); style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention); } } // draw a separator if any if (rowHasOnlineBalance) { opt.rect.setY(opt.rect.y() + opt.rect.height()); opt.rect.setHeight(lineHeight); d->m_onlineBalanceSeparator->adjustBackgroundScheme(opt.palette, index); opt.backgroundBrush = opt.palette.base(); // never draw it as selected but always enabled opt.state &= ~QStyle::State_Selected; style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); // when the editor is shown, the row has only a single column // so we need to paint the separator if we get here in this casee bool needPaint = editWidgetIsVisible; if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) { needPaint = true; // adjust the rect to cover all columns if(view && view->viewport()) { opt.rect.setX(0); opt.rect.setWidth(view->viewport()->width()); } } if(needPaint) { painter->setPen(opt.palette.color(QPalette::Normal, QPalette::Text)); painter->drawText(opt.rect, Qt::AlignCenter, d->m_onlineBalanceSeparator->separatorText(index)); } } if (rowHasSeparator) { opt.rect.setY(opt.rect.y() + opt.rect.height()); opt.rect.setHeight(lineHeight); d->m_separator->adjustBackgroundScheme(opt.palette, index); opt.backgroundBrush = opt.palette.base(); // never draw it as selected but always enabled opt.state &= ~QStyle::State_Selected; style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); // when the editor is shown, the row has only a single column // so we need to paint the separator if we get here in this casee bool needPaint = editWidgetIsVisible; if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) { needPaint = true; // adjust the rect to cover all columns if(view && view->viewport()) { opt.rect.setX(0); opt.rect.setWidth(view->viewport()->width()); } } if(needPaint) { painter->setPen(opt.palette.foreground().color()); painter->drawText(opt.rect, Qt::AlignCenter, d->m_separator->separatorText(index)); } } painter->restore(); } QSize LedgerDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { bool fullDisplay = false; if(d->m_view) { QModelIndex currentIndex = d->m_view->currentIndex(); if(currentIndex.isValid()) { QString currentId = currentIndex.model()->data(currentIndex, (int)eLedgerModel::Role::TransactionSplitId).toString(); QString myId = index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString(); fullDisplay = (currentId == myId); } } QSize size; QStyleOptionViewItem opt = option; int rows = 1; initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin); const int lineHeight = opt.fontMetrics.lineSpacing(); if(index.isValid()) { // check if we are showing the edit widget // const QAbstractItemView *view = qobject_cast(opt.widget); if (d->m_view) { QModelIndex editIndex = d->m_view->model()->index(index.row(), 0); if(editIndex.isValid()) { QWidget* editor = d->m_view->indexWidget(editIndex); if(editor) { size = editor->minimumSizeHint(); if(d->displaySeparator(index)) { // don't draw over the separator space size += QSize(0, lineHeight + margin); } if(d->displayOnlineBalanceSeparator(index)) { // don't draw over the separator space size += QSize(0, lineHeight + margin); } return size; } } } } size = QSize(100, lineHeight + 2*margin); if(fullDisplay) { auto payee = index.data((int)eLedgerModel::Role::PayeeName).toString(); auto counterAccount = index.data((int)eLedgerModel::Role::CounterAccount).toString(); auto memo = index.data((int)eLedgerModel::Role::SingleLineMemo).toString(); rows = (payee.length() > 0 ? 1 : 0) + (counterAccount.length() > 0 ? 1 : 0) + (memo.length() > 0 ? 1 : 0); // make sure we show at least one row if(!rows) { rows = 1; } // leave a few pixels as margin for each space between rows size.setHeight((size.height() * rows) - (margin * (rows - 1))); } if (d->m_separator && d->m_separator->rowHasSeparator(index)) { size.setHeight(size.height() + lineHeight + margin); } if (d->m_onlineBalanceSeparator && d->m_onlineBalanceSeparator->rowHasSeparator(index)) { size.setHeight(size.height() + lineHeight + margin); } return size; } void LedgerDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin); const int lineHeight = option.fontMetrics.lineSpacing(); int ofs = 8; if(d->m_view) { if(d->m_view->verticalScrollBar()->isVisible()) { ofs += d->m_view->verticalScrollBar()->width(); } } QRect r(option.rect); if (option.widget) r.setWidth(option.widget->width() - ofs); if(d->displaySeparator(index)) { // consider the separator space r.setHeight(r.height() - lineHeight - margin); } if(d->displayOnlineBalanceSeparator(index)) { // consider the separator space r.setHeight(r.height() - lineHeight - margin); } editor->setGeometry(r); editor->update(); } void LedgerDelegate::endEdit() { if(d->m_editor) { if(d->m_editor->accepted()) { emit commitData(d->m_editor); } emit closeEditor(d->m_editor, NoHint); d->m_editorRow = -1; } } /** * This eventfilter seems to do nothing but it prevents that selecting a * different row with the mouse closes the editor */ bool LedgerDelegate::eventFilter(QObject* o, QEvent* event) { return QAbstractItemDelegate::eventFilter(o, event); } void LedgerDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const { NewTransactionEditor* editor = qobject_cast(editWidget); if(editor) { editor->loadTransaction(index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString()); } } void LedgerDelegate::setModelData(QWidget* editWidget, QAbstractItemModel* model, const QModelIndex& index) const { Q_UNUSED(model) Q_UNUSED(index) NewTransactionEditor* editor = qobject_cast(editWidget); if(editor) { editor->saveTransaction(); } } diff --git a/kmymoney/views/splitdelegate.cpp b/kmymoney/views/splitdelegate.cpp index 9576f0746..e5861be5b 100644 --- a/kmymoney/views/splitdelegate.cpp +++ b/kmymoney/views/splitdelegate.cpp @@ -1,457 +1,457 @@ /*************************************************************************** splitdelegate.cpp ------------------- begin : Wed Apr 6 2016 copyright : (C) 2016 by Thomas Baumgart email : Thomas Baumgart ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "splitdelegate.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "ledgerview.h" #include "models.h" #include "accountsmodel.h" #include "ledgermodel.h" #include "splitmodel.h" #include "newspliteditor.h" #include "mymoneyaccount.h" #include "modelenums.h" using namespace eLedgerModel; QColor SplitDelegate::m_erroneousColor = QColor(Qt::red); QColor SplitDelegate::m_importedColor = QColor(Qt::yellow); class SplitDelegate::Private { public: Private() : m_editor(0) , m_editorRow(-1) , m_showValuesInverted(false) {} NewSplitEditor* m_editor; int m_editorRow; bool m_showValuesInverted; }; SplitDelegate::SplitDelegate(QObject* parent) : QStyledItemDelegate(parent) , d(new Private) { } SplitDelegate::~SplitDelegate() { delete d; } void SplitDelegate::setErroneousColor(const QColor& color) { m_erroneousColor = color; } QWidget* SplitDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); if(index.isValid()) { Q_ASSERT(parent); LedgerView* view = qobject_cast(parent->parentWidget()); Q_ASSERT(view != 0); if(view->selectionModel()->selectedRows().count() > 1) { qDebug() << "Editing multiple splits at once is not yet supported"; /** * @todo replace the following three lines with the creation of a special * editor that can handle multiple splits at once or show a message to the user * that this is not possible */ d->m_editor = 0; SplitDelegate* const that = const_cast(this); emit that->closeEditor(d->m_editor, NoHint); } else { d->m_editor = new NewSplitEditor(parent, view->accountId()); } if(d->m_editor) { d->m_editorRow = index.row(); connect(d->m_editor, SIGNAL(done()), this, SLOT(endEdit())); emit sizeHintChanged(index); } } else { qFatal("SplitDelegate::createEditor(): we should never end up here"); } return d->m_editor; } int SplitDelegate::editorRow() const { return d->m_editorRow; } void SplitDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // never change the background of the cell the mouse is hovering over opt.state &= ~QStyle::State_MouseOver; // show the focus only on the detail column opt.state &= ~QStyle::State_HasFocus; if(index.column() == (int)Column::Detail) { QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent()); if(view) { if(view->currentIndex().row() == index.row()) { opt.state |= QStyle::State_HasFocus; } } } painter->save(); // Background auto bgOpt = opt; // if selected, always show as active, so that the // background does not change when the editor is shown if (opt.state & QStyle::State_Selected) { bgOpt.state |= QStyle::State_Active; } QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &bgOpt, painter, opt.widget); // Do not paint text if the edit widget is shown const LedgerView *view = qobject_cast(opt.widget); if (view && view->indexWidget(index)) { painter->restore(); return; } const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin); QStringList lines; if(index.column() == (int)Column::Detail) { lines << index.model()->data(index, (int)Role::Account).toString(); lines << index.model()->data(index, (int)Role::SingleLineMemo).toString(); lines.removeAll(QString()); } // draw the text items if(!opt.text.isEmpty() || !lines.isEmpty()) { QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (opt.state & QStyle::State_Selected) { painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(opt.palette.color(cg, QPalette::Text)); } if (opt.state & QStyle::State_Editing) { painter->setPen(opt.palette.color(cg, QPalette::Text)); painter->drawRect(textArea.adjusted(0, 0, -1, -1)); } // collect data for the various columns if(index.column() == (int)Column::Detail) { for(int i = 0; i < lines.count(); ++i) { painter->drawText(textArea.adjusted(0, (opt.fontMetrics.lineSpacing() + 5) * i, 0, 0), opt.displayAlignment, lines[i]); } } else { painter->drawText(textArea, opt.displayAlignment, opt.text); } } // draw the focus rect if(opt.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(opt); o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget); o.state |= QStyle::State_KeyboardFocusChange; o.state |= QStyle::State_Item; QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Window); style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget); } #if 0 if((index.column() == LedgerModel::DetailColumn) && erroneous) { QPixmap attention; attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0); style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention); } #endif painter->restore(); #if 0 const QHeaderView* horizontalHeader = view->horizontalHeader(); const QHeaderView* verticalHeader = view->verticalHeader(); const QWidget* viewport = view->viewport(); const bool showGrid = view->showGrid() && !view->indexWidget(index); const int gridSize = showGrid ? 1 : 0; const int gridHint = style->styleHint(QStyle::SH_Table_GridLineColor, &option, view); const QColor gridColor = static_cast(gridHint); const QPen gridPen = QPen(gridColor, 0, view->gridStyle()); const bool rightToLeft = view->isRightToLeft(); const int viewportOffset = horizontalHeader->offset(); // QStyledItemDelegate::paint(painter, opt, index); if(!horizontalHeader->isSectionHidden(LedgerModel::DateColumn)) { QDate postDate = index.data(LedgerModel::PostDateRole).toDate(); if(postDate.isValid()) { int ofs = horizontalHeader->sectionViewportPosition(LedgerModel::DateColumn) + viewportOffset; QRect oRect = opt.rect; opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop; opt.rect.setLeft(opt.rect.left()+ofs); opt.rect.setTop(opt.rect.top()+margin); opt.rect.setWidth(horizontalHeader->sectionSize(LedgerModel::DateColumn)); opt.text = KGlobal::locale()->formatDate(postDate, QLocale::ShortFormat); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); opt.rect = oRect; } } if(!horizontalHeader->isSectionHidden(LedgerModel::DetailColumn)) { QString payee = index.data(LedgerModel::PayeeRole).toString(); QString counterAccount = index.data(LedgerModel::CounterAccountRole).toString(); QString txt = payee; if(payee.length() > 0) txt += '\n'; txt += counterAccount; int ofs = horizontalHeader->sectionViewportPosition(LedgerModel::DetailColumn) + viewportOffset; QRect oRect = opt.rect; opt.displayAlignment = Qt::AlignLeft | Qt::AlignTop; opt.rect.setLeft(opt.rect.left()+ofs); opt.rect.setTop(opt.rect.top()+margin); opt.rect.setWidth(horizontalHeader->sectionSize(LedgerModel::DetailColumn)); opt.text = txt; style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); opt.rect = oRect; } #if 0 opt.features |= QStyleOptionViewItemV2::HasDisplay; QString txt = QString("%1").arg(index.isValid() ? "true" : "false"); if(index.isValid()) txt += QString(" %1 - %2").arg(index.row()).arg(view->verticalHeader()->sectionViewportPosition(index.row())); opt.text = displayText(txt, opt.locale); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); #endif // paint grid if(showGrid) { painter->save(); QPen old = painter->pen(); painter->setPen(gridPen); // qDebug() << "Paint grid for" << index.row() << "in" << opt.rect; for(int i=0; i < horizontalHeader->count(); ++i) { if(!horizontalHeader->isSectionHidden(i)) { int ofs = horizontalHeader->sectionViewportPosition(i) + viewportOffset; if(!rightToLeft) { ofs += horizontalHeader->sectionSize(i) - gridSize; } if(ofs-viewportOffset < viewport->width()) { // I have no idea, why I need to paint the grid for the selected row and the one below // but it was the only way to get this working correctly. Otherwise the grid was missing // while moving the mouse over the view from bottom to top. painter->drawLine(opt.rect.x()+ofs, opt.rect.y(), opt.rect.x()+ofs, opt.rect.height()); painter->drawLine(opt.rect.x()+ofs, opt.rect.y()+verticalHeader->sectionSize(index.row()), opt.rect.x()+ofs, opt.rect.height()); } } } painter->setPen(old); painter->restore(); } #endif } QSize SplitDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { bool fullDisplay = false; LedgerView* view = qobject_cast(parent()); if(view) { QModelIndex currentIndex = view->currentIndex(); if(currentIndex.isValid()) { QString currentId = currentIndex.model()->data(currentIndex, (int)Role::TransactionSplitId).toString(); QString myId = index.model()->data(index, (int)Role::TransactionSplitId).toString(); fullDisplay = (currentId == myId); } } QSize size; QStyleOptionViewItem opt = option; if(index.isValid()) { // check if we are showing the edit widget - const QAbstractItemView *view = qobject_cast(opt.widget); - if (view) { - QModelIndex editIndex = view->model()->index(index.row(), 0); + const QAbstractItemView *viewWidget = qobject_cast(opt.widget); + if (viewWidget) { + QModelIndex editIndex = viewWidget->model()->index(index.row(), 0); if(editIndex.isValid()) { - QWidget* editor = view->indexWidget(editIndex); + QWidget* editor = viewWidget->indexWidget(editIndex); if(editor) { size = editor->minimumSizeHint(); return size; } } } } int rows = 1; if(fullDisplay) { initStyleOption(&opt, index); auto payee = index.data((int)Role::PayeeName).toString(); auto account = index.data((int)Role::Account).toString(); auto memo = index.data((int)Role::SingleLineMemo).toString(); rows = (payee.length() > 0 ? 1 : 0) + (account.length() > 0 ? 1 : 0) + (memo.length() > 0 ? 1 : 0); // make sure we show at least one row if(!rows) { rows = 1; } } // leave a 5 pixel margin for each row size = QSize(100, (opt.fontMetrics.lineSpacing() + 5) * rows); return size; } void SplitDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); QStyleOptionViewItem opt = option; int ofs = 8; const LedgerView* view = qobject_cast(opt.widget); if(view) { if(view->verticalScrollBar()->isVisible()) { ofs += view->verticalScrollBar()->width(); } } QRect r(opt.rect); r.setWidth(opt.widget->width() - ofs); editor->setGeometry(r); editor->update(); } void SplitDelegate::endEdit() { if(d->m_editor) { if(d->m_editor->accepted()) { emit commitData(d->m_editor); } emit closeEditor(d->m_editor, NoHint); d->m_editorRow = -1; } } void SplitDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const { const SplitModel* model = qobject_cast(index.model()); NewSplitEditor* editor = qobject_cast(editWidget); if(model && editor) { editor->setShowValuesInverted(d->m_showValuesInverted); editor->setMemo(model->data(index, (int)Role::Memo).toString()); editor->setAccountId(model->data(index, (int)Role::AccountId).toString()); editor->setAmount(model->data(index, (int)Role::SplitShares).value()); editor->setCostCenterId(model->data(index, (int)Role::CostCenterId).toString()); editor->setNumber(model->data(index, (int)Role::Number).toString()); } } void SplitDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { NewSplitEditor* splitEditor = qobject_cast< NewSplitEditor* >(editor); if(splitEditor) { model->setData(index, splitEditor->number(), (int)Role::Number); model->setData(index, splitEditor->memo(), (int)Role::Memo); model->setData(index, splitEditor->accountId(), (int)Role::AccountId); model->setData(index, splitEditor->costCenterId(), (int)Role::CostCenterId); model->setData(index, QVariant::fromValue(splitEditor->amount()), (int)Role::SplitShares); model->setData(index, QVariant::fromValue(splitEditor->amount()), (int)Role::SplitValue); const QString transactionCommodity = model->data(index, (int)Role::TransactionCommodity).toString(); QModelIndex accIndex = Models::instance()->accountsModel()->accountById(splitEditor->accountId()); if(accIndex.isValid()) { MyMoneyAccount acc = Models::instance()->accountsModel()->data(accIndex, (int)eAccountsModel::Role::Account).value(); if(transactionCommodity != acc.currencyId()) { #if 0 /// @todo call KCurrencyConversionDialog and update the model data MyMoneyMoney value; model->setData(index, QVariant::fromValue(value), SplitModel::SplitValueRole); #endif } } else { qWarning() << "Unable to get account index in SplitDelegate::setModelData"; } // the following forces to send a dataChanged signal model->setData(index, QVariant(), (int)Role::EmitDataChanged); // in case this was a new split, we nned to create a new empty one SplitModel* splitModel = qobject_cast(model); if(splitModel) { splitModel->addEmptySplitEntry(); } } } /** * This eventfilter seems to do nothing but it prevents that selecting a * different row with the mouse closes the editor */ bool SplitDelegate::eventFilter(QObject* o, QEvent* event) { return QAbstractItemDelegate::eventFilter(o, event); } void SplitDelegate::setShowValuesInverted(bool inverse) { d->m_showValuesInverted = inverse; } bool SplitDelegate::showValuesInverted() { return d->m_showValuesInverted; } diff --git a/kmymoney/widgets/kaccounttemplateselector.cpp b/kmymoney/widgets/kaccounttemplateselector.cpp index 5e1bccec0..79f6a2ce7 100644 --- a/kmymoney/widgets/kaccounttemplateselector.cpp +++ b/kmymoney/widgets/kaccounttemplateselector.cpp @@ -1,288 +1,288 @@ /*************************************************************************** kaccounttemplateselector.cpp - description ------------------- begin : Tue Feb 5 2008 copyright : (C) 2008 by Thomas Baumgart email : Thomas Baumgart (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kaccounttemplateselector.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "ui_kaccounttemplateselector.h" #include class KAccountTemplateSelectorPrivate { Q_DISABLE_COPY(KAccountTemplateSelectorPrivate) public: KAccountTemplateSelectorPrivate() : ui(new Ui::KAccountTemplateSelector), id(0) { } ~KAccountTemplateSelectorPrivate() { delete ui; } #ifndef KMM_DESIGNER QTreeWidgetItem* hierarchyItem(const QString& parent, const QString& name) { if (!m_templateHierarchy.contains(parent) || m_templateHierarchy[parent] == 0) { QRegExp exp("(.*):(.*)"); if (exp.indexIn(parent) != -1) m_templateHierarchy[parent] = hierarchyItem(exp.cap(1), exp.cap(2)); } QTreeWidgetItem *item = new QTreeWidgetItem(m_templateHierarchy[parent]); item->setText(0, name); return item; } void loadHierarchy() { m_templateHierarchy.clear(); QTreeWidgetItemIterator it(ui->m_groupList, QTreeWidgetItemIterator::Selected); QTreeWidgetItem* it_v; while ((it_v = *it) != 0) { m_templates[it_v->data(0, Qt::UserRole).toInt()].hierarchy(m_templateHierarchy); ++it; } // I need to think about this some more. The code works and shows // the current account hierarchy. It might be useful, to show // existing accounts dimmed and the new ones in bold or so. #if 0 // add the hierarchy from the MyMoneyFile object QList aList; QList::const_iterator it_a; auto file = MyMoneyFile::instance(); file->accountList(aList); if (aList.count() > 0) { m_templateHierarchy[file->accountToCategory(file->asset().id(), true)] = 0; m_templateHierarchy[file->accountToCategory(file->liability().id(), true)] = 0; m_templateHierarchy[file->accountToCategory(file->income().id(), true)] = 0; m_templateHierarchy[file->accountToCategory(file->expense().id(), true)] = 0; m_templateHierarchy[file->accountToCategory(file->equity().id(), true)] = 0; } for (it_a = aList.begin(); it_a != aList.end(); ++it_a) { m_templateHierarchy[file->accountToCategory((*it_a).id(), true)] = 0; } #endif ui->m_accountList->clear(); QRegExp exp("(.*):(.*)"); - for (QMap::iterator it_m = m_templateHierarchy.begin(); it_m != m_templateHierarchy.end(); ++it_m) { - if (exp.indexIn(it_m.key()) == -1) { - (*it_m) = new QTreeWidgetItem(ui->m_accountList); - (*it_m)->setText(0, it_m.key()); + for (QMap::iterator it_h = m_templateHierarchy.begin(); it_h != m_templateHierarchy.end(); ++it_h) { + if (exp.indexIn(it_h.key()) == -1) { + (*it_h) = new QTreeWidgetItem(ui->m_accountList); + (*it_h)->setText(0, it_h.key()); } else { - (*it_m) = hierarchyItem(exp.cap(1), exp.cap(2)); + (*it_h) = hierarchyItem(exp.cap(1), exp.cap(2)); } - (*it_m)->setExpanded(true); + (*it_h)->setExpanded(true); } ui->m_description->clear(); if (ui->m_groupList->currentItem()) { ui->m_description->setText(m_templates[ui->m_groupList->currentItem()->data(0, Qt::UserRole).toInt()].longDescription()); } } QList selectedTemplates() const { QList list; QTreeWidgetItemIterator it(ui->m_groupList, QTreeWidgetItemIterator::Selected); QTreeWidgetItem* it_v; while ((it_v = *it) != 0) { list << m_templates[it_v->data(0, Qt::UserRole).toInt()]; ++it; } return list; } #endif public: Ui::KAccountTemplateSelector *ui; QMap m_templateHierarchy; #ifndef KMM_DESIGNER QMap m_templates; // a map of country name or country name (language name) -> localeId (lang_country) so be careful how you use it QMap countries; QString currentLocaleId; QMap::iterator it_m; QStringList dirlist; int id; #endif }; KAccountTemplateSelector::KAccountTemplateSelector(QWidget* parent) : QWidget(parent), d_ptr(new KAccountTemplateSelectorPrivate) { Q_D(KAccountTemplateSelector); d->ui->setupUi(this); d->ui->m_accountList->header()->hide(); d->ui->m_groupList->setSelectionMode(QAbstractItemView::ExtendedSelection); connect(d->ui->m_groupList, &QTreeWidget::itemSelectionChanged, this, &KAccountTemplateSelector::slotLoadHierarchy); // kick off loading of account template data QTimer::singleShot(0, this, SLOT(slotLoadTemplateList())); } KAccountTemplateSelector::~KAccountTemplateSelector() { Q_D(KAccountTemplateSelector); delete d; } void KAccountTemplateSelector::slotLoadTemplateList() { #ifndef KMM_DESIGNER Q_D(KAccountTemplateSelector); QStringList dirs; // get list of template subdirs and scan them for the list of subdirs d->dirlist = QStandardPaths::locateAll(QStandardPaths::DataLocation, "templates", QStandardPaths::LocateDirectory); QStringList::iterator it; for (it = d->dirlist.begin(); it != d->dirlist.end(); ++it) { QDir dir(*it); dirs = dir.entryList(QStringList("*"), QDir::Dirs); QStringList::iterator it_d; for (it_d = dirs.begin(); it_d != dirs.end(); ++it_d) { // we don't care about . and .. if ((*it_d) == ".." || (*it_d) == "." || (*it_d) == "C") continue; QLocale templateLocale(*it_d); if (templateLocale.language() != QLocale::C) { QString country = QLocale().countryToString(templateLocale.country()); QString lang = QLocale().languageToString(templateLocale.language()); if (d->countries.contains(country)) { if (d->countries[country] != *it_d) { QString otherName = d->countries[country]; QLocale otherTemplateLocale(otherName); QString otherCountry = QLocale().countryToString(otherTemplateLocale.country()); QString otherLang = QLocale().languageToString(otherTemplateLocale.language()); d->countries.remove(country); d->countries[QString("%1 (%2)").arg(otherCountry, otherLang)] = otherName; d->countries[QString("%1 (%2)").arg(country, lang)] = *it_d; // retain the item corresponding to the current locale if (QLocale().country() == templateLocale.country()) { d->currentLocaleId = *it_d; } } } else { d->countries[country] = *it_d; // retain the item corresponding to the current locale if (QLocale().country() == templateLocale.country()) { d->currentLocaleId = *it_d; } } } else { qDebug("'%s/%s' not scanned", qPrintable(*it), qPrintable(*it_d)); } } } // now that we know, what we can get at max, we scan everything // and parse the templates into memory d->ui->m_groupList->clear(); d->m_templates.clear(); d->it_m = d->countries.begin(); d->id = 1; if (d->it_m != d->countries.end()) QTimer::singleShot(0, this, SLOT(slotLoadCountry())); else { d->loadHierarchy(); } #endif } void KAccountTemplateSelector::slotLoadCountry() { #ifndef KMM_DESIGNER Q_D(KAccountTemplateSelector); QTreeWidgetItem *parent = new QTreeWidgetItem(d->ui->m_groupList); parent->setText(0, d->it_m.key()); parent->setFlags(parent->flags() & ~Qt::ItemIsSelectable); for (QStringList::iterator it = d->dirlist.begin(); it != d->dirlist.end(); ++it) { QDir dir(QString("%1/%2").arg(*it).arg(*(d->it_m))); if (dir.exists()) { QStringList files = dir.entryList(QStringList("*"), QDir::Files); for (QStringList::iterator it_f = files.begin(); it_f != files.end(); ++it_f) { MyMoneyTemplate templ(QUrl::fromUserInput(QString("%1/%2").arg(dir.canonicalPath(), *it_f))); d->m_templates[d->id] = templ; QTreeWidgetItem *item = new QTreeWidgetItem(parent); item->setText(0, templ.title()); item->setText(1, templ.shortDescription()); item->setData(0, Qt::UserRole, QString("%1").arg(d->id)); ++d->id; } } } // make visible the templates of the current locale if (d->it_m.value() == d->currentLocaleId) { d->ui->m_groupList->setCurrentItem(parent); d->ui->m_groupList->expandItem(parent); d->ui->m_groupList->scrollToItem(parent, QTreeView::PositionAtTop); } ++d->it_m; if (d->it_m != d->countries.end()) QTimer::singleShot(0, this, SLOT(slotLoadCountry())); else { d->loadHierarchy(); } #endif } void KAccountTemplateSelector::slotLoadHierarchy() { #ifndef KMM_DESIGNER Q_D(KAccountTemplateSelector); d->loadHierarchy(); #endif } QList KAccountTemplateSelector::selectedTemplates() const { #ifndef KMM_DESIGNER Q_D(const KAccountTemplateSelector); return d->selectedTemplates(); #else return QList(); #endif }