diff --git a/kmymoney/mymoney/mymoneyreport.cpp b/kmymoney/mymoney/mymoneyreport.cpp index fe8afa6b8..0fc1b04e6 100644 --- a/kmymoney/mymoney/mymoneyreport.cpp +++ b/kmymoney/mymoney/mymoneyreport.cpp @@ -1,833 +1,833 @@ /*************************************************************************** mymoneyreport.cpp ------------------- begin : Sun July 4 2004 copyright : (C) 2004-2005 by Ace Jones email : acejones@users.sourceforge.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "mymoneyreport.h" // ---------------------------------------------------------------------------- // QT Includes #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" const QStringList MyMoneyReport::kRowTypeText = QString("none,assetliability,expenseincome,category,topcategory,account,tag,payee,month,week,topaccount,topaccount-account,equitytype,accounttype,institution,budget,budgetactual,schedule,accountinfo,accountloaninfo,accountreconcile,cashflow").split(','); const QStringList MyMoneyReport::kColumnTypeText = QString("none,months,bimonths,quarters,4,5,6,weeks,8,9,10,11,years").split(','); // if you add names here, don't forget to update the bitmap for EQueryColumns // and shift the bit for eQCend one position to the left const QStringList MyMoneyReport::kQueryColumnsText = QString("none,number,payee,category,tag,memo,account,reconcileflag,action,shares,price,performance,loan,balance,capitalgain").split(','); const MyMoneyReport::EReportType MyMoneyReport::kTypeArray[] = { eNoReport, ePivotTable, ePivotTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, eQueryTable, ePivotTable, ePivotTable, eInfoTable, eInfoTable, eInfoTable, eQueryTable, eQueryTable, eNoReport }; const QStringList MyMoneyReport::kDetailLevelText = QString("none,all,top,group,total,invalid").split(','); const QStringList MyMoneyReport::kChartTypeText = QString("none,line,bar,pie,ring,stackedbar,invalid").split(','); // This should live in mymoney/mymoneytransactionfilter.h const QStringList kTypeText = QString("all,payments,deposits,transfers,none").split(','); const QStringList kStateText = QString("all,notreconciled,cleared,reconciled,frozen,none").split(','); const QStringList kDateLockText = QString("alldates,untiltoday,currentmonth,currentyear,monthtodate,yeartodate,yeartomonth,lastmonth,lastyear,last7days,last30days,last3months,last6months,last12months,next7days,next30days,next3months,next6months,next12months,userdefined,last3tonext3months,last11Months,currentQuarter,lastQuarter,nextQuarter,currentFiscalYear,lastFiscalYear,today,next18months").split(','); const QStringList kDataLockText = QString("automatic,userdefined").split(','); const QStringList kAccountTypeText = QString("unknown,checkings,savings,cash,creditcard,loan,certificatedep,investment,moneymarket,asset,liability,currency,income,expense,assetloan,stock,equity,invalid").split(','); MyMoneyReport::MyMoneyReport() : m_name("Unconfigured Pivot Table Report"), m_detailLevel(eDetailNone), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(kTypeArray[eExpenseIncome]), m_rowType(eExpenseIncome), m_columnType(eMonths), m_columnsAreDays(false), m_queryColumns(eQCnone), m_dateLock(MyMoneyTransactionFilter::userDefined), - m_dataLock(MyMoneyReport::automatic), m_accountGroupFilter(false), m_chartType(eChartLine), m_chartDataLabels(true), m_chartCHGridLines(true), m_chartSVGridLines(true), m_chartByDefault(false), m_logYaxis(false), + m_dataLock(MyMoneyReport::automatic), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { m_chartLineWidth = m_lineWidth; } MyMoneyReport::MyMoneyReport(const QString& id, const MyMoneyReport& right) : MyMoneyObject(id), m_movingAverageDays(0), m_currentDateColumn(0) { *this = right; setId(id); } MyMoneyReport::MyMoneyReport(ERowType _rt, unsigned _ct, dateOptionE _dl, EDetailLevel _ss, const QString& _name, const QString& _comment) : m_name(_name), m_comment(_comment), m_detailLevel(_ss), m_convertCurrency(true), m_favorite(false), m_tax(false), m_investments(false), m_loans(false), m_reportType(kTypeArray[_rt]), m_rowType(_rt), m_columnsAreDays(false), m_queryColumns(eQCnone), m_dateLock(_dl), - m_dataLock(MyMoneyReport::automatic), m_accountGroupFilter(false), m_chartType(eChartLine), m_chartDataLabels(true), m_chartCHGridLines(true), m_chartSVGridLines(true), m_chartByDefault(false), m_logYaxis(false), + m_dataLock(MyMoneyReport::automatic), m_includeSchedules(false), m_includeTransfers(false), m_includeBudgetActuals(false), m_includeUnusedAccounts(false), m_showRowTotals(false), m_includeForecast(false), m_includeMovingAverage(false), m_movingAverageDays(0), m_includePrice(false), m_includeAveragePrice(false), m_mixedTime(false), m_currentDateColumn(0), m_skipZero(false) { //set initial values m_chartLineWidth = m_lineWidth; //set report type if (m_reportType == ePivotTable) m_columnType = static_cast(_ct); if (m_reportType == eQueryTable) m_queryColumns = static_cast(_ct); setDateFilter(_dl); //throw exception if the type is inconsistent if ((_rt > static_cast(sizeof(kTypeArray) / sizeof(kTypeArray[0]))) || (m_reportType == eNoReport)) throw MYMONEYEXCEPTION("Invalid report type"); //add the corresponding account groups if (_rt == MyMoneyReport::eAssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); m_showRowTotals = true; } if (_rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); m_showRowTotals = true; } //FIXME take this out once we have sorted out all issues regarding budget of assets and liabilities -- asoliverez@gmail.com if (_rt == MyMoneyReport::eBudget || _rt == MyMoneyReport::eBudgetActual) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } if (_rt == MyMoneyReport::eAccountInfo) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } //cash flow reports show splits for all account groups if (_rt == MyMoneyReport::eCashFlow) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } } MyMoneyReport::MyMoneyReport(const QDomElement& node) : MyMoneyObject(node), m_currentDateColumn(0) { // properly initialize the object before reading it *this = MyMoneyReport(); if (!read(node)) clearId(); } void MyMoneyReport::clear() { m_accountGroupFilter = false; m_accountGroups.clear(); MyMoneyTransactionFilter::clear(); } void MyMoneyReport::validDateRange(QDate& _db, QDate& _de) { _db = fromDate(); _de = toDate(); // if either begin or end date are invalid we have one of the following // possible date filters: // // a) begin date not set - first transaction until given end date // b) end date not set - from given date until last transaction // c) both not set - first transaction until last transaction // // If there is no transaction in the engine at all, we use the current // year as the filter criteria. if (!_db.isValid() || !_de.isValid()) { QList list = MyMoneyFile::instance()->transactionList(*this); QDate tmpBegin, tmpEnd; if (!list.isEmpty()) { qSort(list); // try to use the post dates tmpBegin = list.front().postDate(); tmpEnd = list.back().postDate(); // if the post dates are not valid try the entry dates if (!tmpBegin.isValid()) tmpBegin = list.front().entryDate(); if (!tmpEnd.isValid()) tmpEnd = list.back().entryDate(); } // make sure that we leave this function with valid dates no mather what if (!tmpBegin.isValid() || !tmpEnd.isValid() || tmpBegin > tmpEnd) { tmpBegin = QDate(QDate::currentDate().year(), 1, 1); // the first date in the file tmpEnd = QDate(QDate::currentDate().year(), 12, 31); // the last date in the file } if (!_db.isValid()) _db = tmpBegin; if (!_de.isValid()) _de = tmpEnd; } if (_db > _de) _db = _de; } void MyMoneyReport::setRowType(ERowType _rt) { m_rowType = _rt; m_reportType = kTypeArray[_rt]; m_accountGroupFilter = false; m_accountGroups.clear(); if (_rt == MyMoneyReport::eAssetLiability) { addAccountGroup(MyMoneyAccount::Asset); addAccountGroup(MyMoneyAccount::Liability); } if (_rt == MyMoneyReport::eExpenseIncome) { addAccountGroup(MyMoneyAccount::Expense); addAccountGroup(MyMoneyAccount::Income); } } bool MyMoneyReport::accountGroups(QList& list) const { bool result = m_accountGroupFilter; if (result) { QList::const_iterator it_group = m_accountGroups.begin(); while (it_group != m_accountGroups.end()) { list += (*it_group); ++it_group; } } return result; } void MyMoneyReport::addAccountGroup(MyMoneyAccount::accountTypeE type) { if (!m_accountGroups.isEmpty() && type != MyMoneyAccount::UnknownAccountType) { if (m_accountGroups.contains(type)) return; } m_accountGroupFilter = true; if (type != MyMoneyAccount::UnknownAccountType) m_accountGroups.push_back(type); } bool MyMoneyReport::includesAccountGroup(MyMoneyAccount::accountTypeE type) const { bool result = (! m_accountGroupFilter) || (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome) || m_accountGroups.contains(type); return result; } bool MyMoneyReport::includes(const MyMoneyAccount& acc) const { bool result = false; if (includesAccountGroup(acc.accountGroup())) { switch (acc.accountGroup()) { case MyMoneyAccount::Income: case MyMoneyAccount::Expense: if (isTax()) result = (acc.value("Tax") == "Yes") && includesCategory(acc.id()); else result = includesCategory(acc.id()); break; case MyMoneyAccount::Asset: case MyMoneyAccount::Liability: if (isLoansOnly()) result = acc.isLoan() && includesAccount(acc.id()); else if (isInvestmentsOnly()) result = acc.isInvest() && includesAccount(acc.id()); else if (isIncludingTransfers() && m_rowType == MyMoneyReport::eExpenseIncome) // If transfers are included, ONLY include this account if it is NOT // included in the report itself!! result = ! includesAccount(acc.id()); else result = includesAccount(acc.id()); break; default: result = includesAccount(acc.id()); } } return result; } void MyMoneyReport::write(QDomElement& e, QDomDocument *doc, bool anonymous) const { // No matter what changes, be sure to have a 'type' attribute. Only change // the major type if it becomes impossible to maintain compatibility with // older versions of the program as new features are added to the reports. // Feel free to change the minor type every time a change is made here. writeBaseXML(*doc, e); if (anonymous) { e.setAttribute("name", m_id); e.setAttribute("comment", QString(m_comment).fill('x')); } else { e.setAttribute("name", m_name); e.setAttribute("comment", m_comment); } e.setAttribute("group", m_group); e.setAttribute("convertcurrency", m_convertCurrency); e.setAttribute("favorite", m_favorite); e.setAttribute("tax", m_tax); e.setAttribute("investments", m_investments); e.setAttribute("loans", m_loans); e.setAttribute("rowtype", kRowTypeText[m_rowType]); e.setAttribute("datelock", kDateLockText[m_dateLock]); e.setAttribute("datalock", kDataLockText[m_dataLock]); e.setAttribute("includeschedules", m_includeSchedules); e.setAttribute("columnsaredays", m_columnsAreDays); e.setAttribute("includestransfers", m_includeTransfers); if (!m_budgetId.isEmpty()) e.setAttribute("budget", m_budgetId); e.setAttribute("includesactuals", m_includeBudgetActuals); e.setAttribute("includeunused", m_includeUnusedAccounts); e.setAttribute("includesforecast", m_includeForecast); e.setAttribute("includesprice", m_includePrice); e.setAttribute("includesaverageprice", m_includeAveragePrice); e.setAttribute("mixedtime", m_mixedTime); e.setAttribute("includesmovingaverage", m_includeMovingAverage); if (m_includeMovingAverage) e.setAttribute("movingaveragedays", m_movingAverageDays); if (m_chartType < 0 || m_chartType >= kChartTypeText.size()) { qDebug("m_chartType out of bounds with %d on report of type %d. Default to none.", m_chartType, m_reportType); e.setAttribute("charttype", kChartTypeText[0]); } else { e.setAttribute("charttype", kChartTypeText[m_chartType]); } e.setAttribute("chartdatalabels", m_chartDataLabels); e.setAttribute("chartchgridlines", m_chartCHGridLines); e.setAttribute("chartsvgridlines", m_chartSVGridLines); e.setAttribute("chartbydefault", m_chartByDefault); e.setAttribute("chartlinewidth", m_chartLineWidth); e.setAttribute("logYaxis", m_logYaxis); e.setAttribute("dataRangeStart", m_dataRangeStart); e.setAttribute("dataRangeEnd", m_dataRangeEnd); e.setAttribute("dataMajorTick", m_dataMajorTick); e.setAttribute("dataMinorTick", m_dataMinorTick); e.setAttribute("dataLock", m_dataLock); e.setAttribute("skipZero", m_skipZero); if (m_reportType == ePivotTable) { e.setAttribute("type", "pivottable 1.15"); e.setAttribute("detail", kDetailLevelText[m_detailLevel]); e.setAttribute("columntype", kColumnTypeText[m_columnType]); e.setAttribute("showrowtotals", m_showRowTotals); } else if (m_reportType == eQueryTable) { e.setAttribute("type", "querytable 1.14"); QStringList columns; unsigned qc = m_queryColumns; unsigned it_qc = eQCbegin; unsigned index = 1; while (it_qc != eQCend) { if (qc & it_qc) columns += kQueryColumnsText[index]; it_qc *= 2; index++; } e.setAttribute("querycolumns", columns.join(",")); } else if (m_reportType == eInfoTable) { e.setAttribute("type", "infotable 1.0"); e.setAttribute("detail", kDetailLevelText[m_detailLevel]); e.setAttribute("showrowtotals", m_showRowTotals); } // // Text Filter // QRegExp textfilter; if (textFilter(textfilter)) { QDomElement f = doc->createElement("TEXT"); f.setAttribute("pattern", textfilter.pattern()); f.setAttribute("casesensitive", (textfilter.caseSensitivity() == Qt::CaseSensitive) ? 1 : 0); f.setAttribute("regex", (textfilter.patternSyntax() == QRegExp::Wildcard) ? 1 : 0); f.setAttribute("inverttext", m_invertText); e.appendChild(f); } // // Type & State Filters // QList typelist; if (types(typelist) && ! typelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_type = typelist.constBegin(); while (it_type != typelist.constEnd()) { QDomElement p = doc->createElement("TYPE"); p.setAttribute("type", kTypeText[*it_type]); e.appendChild(p); ++it_type; } } QList statelist; if (states(statelist) && ! statelist.empty()) { // iterate over payees, and add each one QList::const_iterator it_state = statelist.constBegin(); while (it_state != statelist.constEnd()) { QDomElement p = doc->createElement("STATE"); p.setAttribute("state", kStateText[*it_state]); e.appendChild(p); ++it_state; } } // // Number Filter // QString nrFrom, nrTo; if (numberFilter(nrFrom, nrTo)) { QDomElement f = doc->createElement("NUMBER"); f.setAttribute("from", nrFrom); f.setAttribute("to", nrTo); e.appendChild(f); } // // Amount Filter // MyMoneyMoney from, to; if (amountFilter(from, to)) { // bool getAmountFilter(MyMoneyMoney&,MyMoneyMoney&); QDomElement f = doc->createElement("AMOUNT"); f.setAttribute("from", from.toString()); f.setAttribute("to", to.toString()); e.appendChild(f); } // // Payees Filter // QStringList payeelist; if (payees(payeelist)) { if (payeelist.empty()) { QDomElement p = doc->createElement("PAYEE"); e.appendChild(p); } else { // iterate over payees, and add each one QStringList::const_iterator it_payee = payeelist.constBegin(); while (it_payee != payeelist.constEnd()) { QDomElement p = doc->createElement("PAYEE"); p.setAttribute("id", *it_payee); e.appendChild(p); ++it_payee; } } } // // Tags Filter // QStringList taglist; if (tags(taglist)) { if (taglist.empty()) { QDomElement p = doc->createElement("TAG"); e.appendChild(p); } else { // iterate over tags, and add each one QStringList::const_iterator it_tag = taglist.constBegin(); while (it_tag != taglist.constEnd()) { QDomElement p = doc->createElement("TAG"); p.setAttribute("id", *it_tag); e.appendChild(p); ++it_tag; } } } // // Account Groups Filter // QList accountgrouplist; if (accountGroups(accountgrouplist)) { // iterate over accounts, and add each one QList::const_iterator it_group = accountgrouplist.constBegin(); while (it_group != accountgrouplist.constEnd()) { QDomElement p = doc->createElement("ACCOUNTGROUP"); p.setAttribute("group", kAccountTypeText[*it_group]); e.appendChild(p); ++it_group; } } // // Accounts Filter // QStringList accountlist; if (accounts(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("ACCOUNT"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Categories Filter // accountlist.clear(); if (categories(accountlist)) { // iterate over accounts, and add each one QStringList::const_iterator it_account = accountlist.constBegin(); while (it_account != accountlist.constEnd()) { QDomElement p = doc->createElement("CATEGORY"); p.setAttribute("id", *it_account); e.appendChild(p); ++it_account; } } // // Date Filter // if (m_dateLock == MyMoneyTransactionFilter::userDefined) { QDate dateFrom, dateTo; if (dateFilter(dateFrom, dateTo)) { QDomElement f = doc->createElement("DATES"); if (dateFrom.isValid()) f.setAttribute("from", dateFrom.toString(Qt::ISODate)); if (dateTo.isValid()) f.setAttribute("to", dateTo.toString(Qt::ISODate)); e.appendChild(f); } } } bool MyMoneyReport::read(const QDomElement& e) { // The goal of this reading method is 100% backward AND 100% forward // compatibility. Any report ever created with any version of KMyMoney // should be able to be loaded by this method (as long as it's one of the // report types supported in this version, of course) bool result = false; if ( "REPORT" == e.tagName() && ( (e.attribute("type").indexOf("pivottable 1.") == 0) || (e.attribute("type").indexOf("querytable 1.") == 0) || (e.attribute("type").indexOf("infotable 1.") == 0) ) ) { result = true; clear(); int i; m_name = e.attribute("name"); m_comment = e.attribute("comment", "Extremely old report"); //set report type if (!e.attribute("type").indexOf("pivottable")) { m_reportType = MyMoneyReport::ePivotTable; } else if (!e.attribute("type").indexOf("querytable")) { m_reportType = MyMoneyReport::eQueryTable; } else if (!e.attribute("type").indexOf("infotable")) { m_reportType = MyMoneyReport::eInfoTable; } else { m_reportType = MyMoneyReport::eNoReport; } // Removed the line that screened out loading reports that are called // "Default Report". It's possible for the user to change the comment // to this, and we'd hate for it to break as a result. m_group = e.attribute("group"); m_id = e.attribute("id"); //check for reports with older settings which didn't have the detail attribute if (e.hasAttribute("detail")) { i = kDetailLevelText.indexOf(e.attribute("detail", "all")); if (i != -1) m_detailLevel = static_cast(i); } else if (e.attribute("showsubaccounts", "0").toUInt()) { //set to show all accounts m_detailLevel = eDetailAll; } else { //set to show the top level account instead m_detailLevel = eDetailTop; } m_convertCurrency = e.attribute("convertcurrency", "1").toUInt(); m_favorite = e.attribute("favorite", "0").toUInt(); m_tax = e.attribute("tax", "0").toUInt(); m_investments = e.attribute("investments", "0").toUInt(); m_loans = e.attribute("loans", "0").toUInt(); m_includeSchedules = e.attribute("includeschedules", "0").toUInt(); m_columnsAreDays = e.attribute("columnsaredays", "0").toUInt(); m_includeTransfers = e.attribute("includestransfers", "0").toUInt(); if (e.hasAttribute("budget")) m_budgetId = e.attribute("budget"); m_includeBudgetActuals = e.attribute("includesactuals", "0").toUInt(); m_includeUnusedAccounts = e.attribute("includeunused", "0").toUInt(); m_includeForecast = e.attribute("includesforecast", "0").toUInt(); m_includePrice = e.attribute("includesprice", "0").toUInt(); m_includeAveragePrice = e.attribute("includesaverageprice", "0").toUInt(); m_mixedTime = e.attribute("mixedtime", "0").toUInt(); m_includeMovingAverage = e.attribute("includesmovingaverage", "0").toUInt(); m_skipZero = e.attribute("skipZero", "0").toUInt(); if (m_includeMovingAverage) m_movingAverageDays = e.attribute("movingaveragedays", "1").toUInt(); //only load chart data if it is a pivot table m_chartType = static_cast(0); if (m_reportType == ePivotTable) { i = kChartTypeText.indexOf(e.attribute("charttype")); if (i >= 0) m_chartType = static_cast(i); // if it is invalid, set to first type if (m_chartType >= eChartEnd) m_chartType = eChartLine; m_chartDataLabels = e.attribute("chartdatalabels", "1").toUInt(); m_chartCHGridLines = e.attribute("chartchgridlines", "1").toUInt(); m_chartSVGridLines = e.attribute("chartsvgridlines", "1").toUInt(); m_chartByDefault = e.attribute("chartbydefault", "0").toUInt(); m_chartLineWidth = e.attribute("chartlinewidth", QString(m_lineWidth)).toUInt(); m_logYaxis = e.attribute("logYaxis", "0").toUInt(); m_dataRangeStart = e.attribute("dataRangeStart", "0"); m_dataRangeEnd = e.attribute("dataRangeEnd", "0"); m_dataMajorTick = e.attribute("dataMajorTick", "0"); m_dataMinorTick = e.attribute("dataMinorTick", "0"); } else { m_chartDataLabels = true; m_chartCHGridLines = true; m_chartSVGridLines = true; m_chartByDefault = false; m_chartLineWidth = 1; m_logYaxis = false; } QString datelockstr = e.attribute("datelock", "userdefined"); // Handle the pivot 1.2/query 1.1 case where the values were saved as // numbers bool ok = false; i = datelockstr.toUInt(&ok); if (!ok) { i = kDateLockText.indexOf(datelockstr); if (i == -1) i = MyMoneyTransactionFilter::userDefined; } setDateFilter(static_cast(i)); QString datalockstr = e.attribute("dataLock", "userdefined"); ok = false; i = datalockstr.toUInt(&ok); if (!ok) { i = kDataLockText.indexOf(datalockstr); if (i == -1) i = MyMoneyReport::userDefined; } setDataFilter(static_cast(i)); i = kRowTypeText.indexOf(e.attribute("rowtype", "expenseincome")); if (i != -1) { setRowType(static_cast(i)); // recent versions of KMyMoney always showed a total column for // income/expense reports. We turn it on for backward compatibility // here. If the total column is turned off, the flag will be reset // in the next step if (i == eExpenseIncome) m_showRowTotals = true; } if (e.hasAttribute("showrowtotals")) m_showRowTotals = e.attribute("showrowtotals").toUInt(); i = kColumnTypeText.indexOf(e.attribute("columntype", "months")); if (i != -1) setColumnType(static_cast(i)); unsigned qc = 0; QStringList columns = e.attribute("querycolumns", "none").split(','); QStringList::const_iterator it_column = columns.constBegin(); while (it_column != columns.constEnd()) { i = kQueryColumnsText.indexOf(*it_column); if (i > 0) qc |= (1 << (i - 1)); ++it_column; } setQueryColumns(static_cast(qc)); QDomNode child = e.firstChild(); while (!child.isNull() && child.isElement()) { QDomElement c = child.toElement(); if ("TEXT" == c.tagName() && c.hasAttribute("pattern")) { setTextFilter(QRegExp(c.attribute("pattern"), c.attribute("casesensitive", "1").toUInt() ? Qt::CaseSensitive : Qt::CaseInsensitive, c.attribute("regex", "1").toUInt() ? QRegExp::Wildcard : QRegExp::RegExp), c.attribute("inverttext", "0").toUInt()); } if ("TYPE" == c.tagName() && c.hasAttribute("type")) { i = kTypeText.indexOf(c.attribute("type")); if (i != -1) addType(i); } if ("STATE" == c.tagName() && c.hasAttribute("state")) { i = kStateText.indexOf(c.attribute("state")); if (i != -1) addState(i); } if ("NUMBER" == c.tagName()) { setNumberFilter(c.attribute("from"), c.attribute("to")); } if ("AMOUNT" == c.tagName()) { setAmountFilter(MyMoneyMoney(c.attribute("from", "0/100")), MyMoneyMoney(c.attribute("to", "0/100"))); } if ("DATES" == c.tagName()) { QDate from, to; if (c.hasAttribute("from")) from = QDate::fromString(c.attribute("from"), Qt::ISODate); if (c.hasAttribute("to")) to = QDate::fromString(c.attribute("to"), Qt::ISODate); MyMoneyTransactionFilter::setDateFilter(from, to); } if ("PAYEE" == c.tagName()) { addPayee(c.attribute("id")); } if ("TAG" == c.tagName()) { addTag(c.attribute("id")); } if ("CATEGORY" == c.tagName() && c.hasAttribute("id")) { addCategory(c.attribute("id")); } if ("ACCOUNT" == c.tagName() && c.hasAttribute("id")) { addAccount(c.attribute("id")); } if ("ACCOUNTGROUP" == c.tagName() && c.hasAttribute("group")) { i = kAccountTypeText.indexOf(c.attribute("group")); if (i != -1) addAccountGroup(static_cast(i)); } child = child.nextSibling(); } } return result; } void MyMoneyReport::writeXML(QDomDocument& document, QDomElement& parent) const { QDomElement el = document.createElement("REPORT"); write(el, &document, false); parent.appendChild(el); } bool MyMoneyReport::hasReferenceTo(const QString& id) const { QStringList list; // collect all ids accounts(list); categories(list); payees(list); tags(list); return (list.contains(id) > 0); } int MyMoneyReport::m_lineWidth = 2; void MyMoneyReport::setLineWidth(int width) { m_lineWidth = width; } diff --git a/kmymoney/plugins/csvimport/csvwizard.cpp b/kmymoney/plugins/csvimport/csvwizard.cpp index e9ca8a6dc..25c9e07ea 100644 --- a/kmymoney/plugins/csvimport/csvwizard.cpp +++ b/kmymoney/plugins/csvimport/csvwizard.cpp @@ -1,1677 +1,1676 @@ /******************************************************************************* * csvwizard.cpp * -------------------- * begin : Thur Jan 01 2015 * copyright : (C) 2015 by Allan Anderson * email : agander93@gmail.com * copyright : (C) 2016 by Łukasz Wojniłowicz * email : lukasz.wojnilowicz@gmail.com ********************************************************************************/ /******************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ********************************************************************************/ #include "csvwizard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "convdate.h" #include "csvutil.h" #include "mymoneyfile.h" #include "ui_csvwizard.h" #include "ui_introwizardpage.h" #include "ui_separatorwizardpage.h" #include "ui_rowswizardpage.h" #include "ui_bankingwizardpage.h" #include "ui_formatswizardpage.h" #include "ui_investmentwizardpage.h" #include "ui_priceswizardpage.h" CSVWizard::CSVWizard() : ui(new Ui::CSVWizard), m_pageIntro(0), m_pageSeparator(0), m_pageBanking(0), m_pageInvestment(0) { ui->setupUi(this); m_parse = new Parse; m_convertDate = new ConvertDate; m_csvUtil = new CsvUtil; st = MyMoneyStatement(); m_curId = -1; m_lastId = -1; m_maxColumnCount = 0; m_importError = false; m_fileEndLine = 0; ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); m_wizard = new QWizard; m_wizard->setWizardStyle(QWizard::ClassicStyle); ui->horizontalLayout->addWidget(m_wizard); m_wizard->installEventFilter(this); // event filter for escape key presses m_wizard->button(QWizard::BackButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); m_wizard->button(QWizard::CancelButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"), QIcon::fromTheme(QStringLiteral("stop")))); m_wizard->button(QWizard::CustomButton2)->setIcon(QIcon::fromTheme(QStringLiteral("kmymoney"))); m_wizard->button(QWizard::FinishButton)->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"), QIcon::fromTheme(QStringLiteral("finish")))); m_wizard->button(QWizard::CustomButton1)->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); m_wizard->button(QWizard::CustomButton3)->setIcon(QIcon::fromTheme(QStringLiteral("invest-applet"))); m_wizard->button(QWizard::NextButton)->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); } void CSVWizard::init() { m_pageIntro = new IntroPage; m_wizard->setPage(PageIntro, m_pageIntro); m_pageIntro->setParent(this); m_pageSeparator = new SeparatorPage; m_wizard->setPage(PageSeparator, m_pageSeparator); m_pageSeparator->setParent(this); m_pageRows = new RowsPage; m_wizard->setPage(PageRows, m_pageRows); m_pageRows->setParent(this); m_pageFormats = new FormatsPage; m_wizard->setPage(PageFormats, m_pageFormats); m_pageFormats->setParent(this); m_stageLabels << ui->label_intro << ui->label_separators << ui->label_rows << ui->label_columns << ui->label_columns << ui->label_columns << ui->label_formats; m_pageFormats->setFinalPage(true); this->setAttribute(Qt::WA_DeleteOnClose, true); m_profileList.clear(); findCodecs(); m_config = KSharedConfig::openConfig(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "csvimporterrc"); validateConfigFile(m_config); readMiscSettings(m_config); connect(m_pageFormats, SIGNAL(statementReady(MyMoneyStatement&)), m_plugin, SLOT(slotGetStatement(MyMoneyStatement&))); connect(m_wizard->button(QWizard::FinishButton), SIGNAL(clicked()), this, SLOT(slotClose())); connect(m_wizard->button(QWizard::CancelButton), SIGNAL(clicked()), this, SLOT(close())); connect(m_wizard->button(QWizard::CustomButton1), SIGNAL(clicked()), this, SLOT(slotFileDialogClicked())); connect(m_wizard->button(QWizard::CustomButton2), SIGNAL(clicked()), m_pageFormats, SLOT(slotImportClicked())); connect(m_wizard->button(QWizard::CustomButton3), SIGNAL(clicked()), m_pageFormats, SLOT(slotSaveAsQIFClicked())); connect(m_wizard, SIGNAL(currentIdChanged(int)), this, SLOT(slotIdChanged(int))); ui->tableWidget->setWordWrap(false); m_vScrollBar = ui->tableWidget->verticalScrollBar(); m_vScrollBar->setTracking(false); m_dateFormats << "yyyy/MM/dd" << "MM/dd/yyyy" << "dd/MM/yyyy"; m_clearBrush = KColorScheme(QPalette::Normal).background(KColorScheme::NormalBackground); m_clearBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::NormalText); m_colorBrush = KColorScheme(QPalette::Normal).background(KColorScheme::PositiveBackground); m_colorBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::PositiveText); m_errorBrush = KColorScheme(QPalette::Normal).background(KColorScheme::NegativeBackground); m_errorBrushText = KColorScheme(QPalette::Normal).foreground(KColorScheme::NegativeText); int y = (QApplication::desktop()->height() - this->height()) / 2; int x = (QApplication::desktop()->width() - this->width()) / 2; move(x, y); show(); } CSVWizard::~CSVWizard() { delete ui; delete m_parse; delete m_convertDate; delete m_csvUtil; delete m_wizard; } void CSVWizard::showStage() { QString str = ui->label_intro->text(); ui->label_intro->setText("" + str + ""); } void CSVWizard::readMiscSettings(const KSharedConfigPtr& config) { KConfigGroup miscGroup(config, "Misc"); m_initialWidth = miscGroup.readEntry("Width", 800); m_initialHeight = miscGroup.readEntry("Height", 400); m_autodetect.clear(); m_autodetect.insert(CSVWizard::AutoFieldDelimiter, miscGroup.readEntry("AutoFieldDelimiter", true)); m_autodetect.insert(CSVWizard::AutoDecimalSymbol, miscGroup.readEntry("AutoDecimalSymbol", true)); m_autodetect.insert(CSVWizard::AutoDateFormat, miscGroup.readEntry("AutoDateFormat", true)); m_autodetect.insert(CSVWizard::AutoAccountInvest, miscGroup.readEntry("AutoAccountInvest", true)); m_autodetect.insert(CSVWizard::AutoAccountBank, miscGroup.readEntry("AutoAccountBank", true)); } void CSVWizard::saveWindowSize(const KSharedConfigPtr& config) { KConfigGroup miscGroup(config, "Misc"); m_initialHeight = this->geometry().height(); m_initialWidth = this->geometry().width(); miscGroup.writeEntry("Width", m_initialWidth); miscGroup.writeEntry("Height", m_initialHeight); miscGroup.sync(); } bool CSVWizard::updateConfigFile(const KSharedConfigPtr& config, const QList& kmmVer) { QString configFilePath = config.constData()->name(); QFile::copy(configFilePath, configFilePath + ".bak"); KConfigGroup profileNamesGroup(config, "ProfileNames"); QStringList bankProfiles = profileNamesGroup.readEntry("Bank", QStringList()); QStringList investProfiles = profileNamesGroup.readEntry("Invest", QStringList()); QStringList invalidBankProfiles = profileNamesGroup.readEntry("InvalidBank", QStringList()); // get profiles that was marked invalid during last update QStringList invalidInvestProfiles = profileNamesGroup.readEntry("InvalidInvest", QStringList()); QString bankPrefix = "Bank-"; QString investPrefix = "Invest-"; uint version = kmmVer[0]*100 + kmmVer[1]*10 + kmmVer[2]; // for kmm < 5.0.0 change 'BankNames' to 'ProfileNames' and remove 'MainWindow' group if (version < 500 && bankProfiles.isEmpty()) { KConfigGroup oldProfileNamesGroup(config, "BankProfiles"); bankProfiles = oldProfileNamesGroup.readEntry("BankNames", QStringList()); // profile names are under 'BankNames' entry for kmm < 5.0.0 bankPrefix = "Profiles-"; // needed to remove non-existent profiles in first run oldProfileNamesGroup.deleteGroup(); KConfigGroup oldMainWindowGroup(config, "MainWindow"); oldMainWindowGroup.deleteGroup(); } bool firstTry = false; if (invalidBankProfiles.isEmpty() && invalidInvestProfiles.isEmpty()) // if there is no invalid profiles then this might be first update try firstTry = true; bool ret = true; int invalidProfileResponse = QMessageBox::No; for (QStringList::Iterator profileName = bankProfiles.begin(); profileName != bankProfiles.end(); ++profileName) { KConfigGroup bankProfile(config, bankPrefix + *profileName); if (!bankProfile.exists() && !invalidBankProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference profileName = bankProfiles.erase(profileName); profileName--; continue; } // for kmm < 5.0.0 remove 'FileType' and 'ProfileName' and assign them to either "Bank=" or "Invest=" if (version < 500) { KConfigGroup oldBankProfile(config, "Profiles-" + *profileName); // if half of configuration is updated and the other one untouched this is needed QString oldProfileType = oldBankProfile.readEntry("FileType", QString()); KConfigGroup newProfile; if (oldProfileType == "Invest") { newProfile = KConfigGroup(config, "Invest-" + *profileName); investProfiles.append(*profileName); profileName = bankProfiles.erase(profileName); profileName--; } else if (oldProfileType == "Banking") newProfile = KConfigGroup(config, "Bank-" + *profileName); else { if (invalidProfileResponse != QMessageBox::YesToAll && invalidProfileResponse != QMessageBox::NoToAll) { if (!firstTry && !invalidBankProfiles.contains(*profileName)) // if it isn't first update run and profile isn't on the list of invalid ones then don't bother continue; invalidProfileResponse = QMessageBox::warning(m_wizard, i18n("CSV import"), i18n("
During update of %1
" "the profile type for %2 could not be recognized.
" "The profile cannot be used because of that.
" "Do you want to delete it?
", configFilePath, *profileName), QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll, QMessageBox::No ); } switch (invalidProfileResponse) { case QMessageBox::YesToAll: case QMessageBox::Yes: oldBankProfile.deleteGroup(); invalidBankProfiles.removeOne(*profileName); profileName = bankProfiles.erase(profileName); --profileName; break; case QMessageBox::NoToAll: case QMessageBox::No: if (!invalidBankProfiles.contains(*profileName)) // on user request: don't delete profile but keep eye on it invalidBankProfiles.append(*profileName); ret = false; break; } continue; } oldBankProfile.deleteEntry("FileType"); oldBankProfile.deleteEntry("ProfileName"); oldBankProfile.deleteEntry("DebitFlag"); oldBankProfile.deleteEntry("InvDirectory"); oldBankProfile.deleteEntry("CsvDirectory"); oldBankProfile.copyTo(&newProfile); oldBankProfile.deleteGroup(); newProfile.sync(); oldBankProfile.sync(); } } for (QStringList::Iterator profileName = investProfiles.begin(); profileName != investProfiles.end(); ++profileName) { KConfigGroup investProfile(config, investPrefix + *profileName); if (!investProfile.exists() && !invalidInvestProfiles.contains(*profileName)) { // if there is reference to profile but no profile then remove this reference investProfiles.erase(profileName); continue; } } profileNamesGroup.writeEntry("Bank", bankProfiles); // update profile names as some of them might have been changed profileNamesGroup.writeEntry("Invest", investProfiles); if (invalidBankProfiles.isEmpty()) // if no invalid profiles then we don't need this variable anymore profileNamesGroup.deleteEntry("InvalidBank"); else profileNamesGroup.writeEntry("InvalidBank", invalidBankProfiles); if (invalidInvestProfiles.isEmpty()) profileNamesGroup.deleteEntry("InvalidInvest"); else profileNamesGroup.writeEntry("InvalidInvest", invalidInvestProfiles); if (ret) QFile::remove(configFilePath + ".bak"); // remove backup if all is ok return ret; } void CSVWizard::validateConfigFile(const KSharedConfigPtr& config) { KConfigGroup profileNamesGroup(config, "ProfileNames"); if (!profileNamesGroup.exists()) { profileNamesGroup.writeEntry("Bank", QStringList()); profileNamesGroup.writeEntry("Invest", QStringList()); profileNamesGroup.writeEntry("SPrices", QStringList()); profileNamesGroup.writeEntry("PriorBank", int()); profileNamesGroup.writeEntry("PriorInvest", int()); profileNamesGroup.writeEntry("PriorSPrices", int()); profileNamesGroup.sync(); } KConfigGroup miscGroup(config, "Misc"); if (!miscGroup.exists()) { miscGroup.writeEntry("Height", "400"); miscGroup.writeEntry("Width", "800"); miscGroup.sync(); } QList kmmVer = miscGroup.readEntry("KMMVer", QList {0, 0, 0}); QList curKmmVer = QList {5, 0, 0}; if (curKmmVer != kmmVer) { if (updateConfigFile(config, kmmVer)) // write kmmVer only if there were no errors miscGroup.writeEntry("KMMVer", curKmmVer); } KConfigGroup securitiesGroup(config, "Securities"); if (!securitiesGroup.exists()) { securitiesGroup.writeEntry("SecurityNameList", QStringList()); securitiesGroup.sync(); } } void CSVWizard::setCodecList(const QList &list, QComboBox* comboBoxEncode) { comboBoxEncode->clear(); foreach (QTextCodec * codec, list) comboBoxEncode->addItem(codec->name(), codec->mibEnum()); } void CSVWizard::findCodecs() { QMap codecMap; QRegExp iso8859RegExp("ISO[- ]8859-([0-9]+).*"); foreach (int mib, QTextCodec::availableMibs()) { QTextCodec *codec = QTextCodec::codecForMib(mib); QString sortKey = codec->name().toUpper(); int rank; if (sortKey.startsWith("UTF-8")) { // krazy:exclude=strings rank = 1; } else if (sortKey.startsWith("UTF-16")) { // krazy:exclude=strings rank = 2; } else if (iso8859RegExp.exactMatch(sortKey)) { if (iso8859RegExp.cap(1).size() == 1) rank = 3; else rank = 4; } else { rank = 5; } sortKey.prepend(QChar('0' + rank)); codecMap.insert(sortKey, codec); } m_codecs = codecMap.values(); } void CSVWizard::slotIdChanged(int id) { QString txt; m_lastId = m_curId; m_curId = id; if ((m_lastId == -1) || (m_curId == -1)) { return; } txt = m_stageLabels[m_lastId]->text(); txt.remove(QRegExp("[/]")); m_stageLabels[m_lastId]->setText(txt); txt = m_stageLabels[m_curId]->text(); txt = "" + txt + ""; m_stageLabels[m_curId]->setText(txt); } void CSVWizard::clearColumnsBackground(int col) { QList columnList; columnList << col; clearColumnsBackground(columnList); } void CSVWizard::clearColumnsBackground(QList& columnList) { for (int row = m_startLine -1 ; row < m_endLine; ++row) { for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { QTableWidgetItem* item = ui->tableWidget->item(row, *col); item->setBackground(m_clearBrush); item->setForeground(m_clearBrushText); } } } void CSVWizard::clearBackground() { for (int row = 0; row < ui->tableWidget->rowCount(); ++row) { for (int col = 0; col < ui->tableWidget->columnCount(); ++col) { QTableWidgetItem *item = ui->tableWidget->item(row, col); item->setBackground(m_clearBrush); item->setForeground(m_clearBrushText); } } } void CSVWizard::markUnwantedRows() { int first = m_startLine - 1; int last = m_endLine - 1; // // highlight unwanted lines instead of not showing them. // QBrush brush; QBrush brushText; for (int row = 0; row < ui->tableWidget->rowCount(); row++) { if ((row < first) || (row > last)) { brush = m_errorBrush; brushText = m_errorBrushText; } else { brush = m_clearBrush; brushText = m_clearBrushText; } for (int col = 0; col < ui->tableWidget->columnCount(); col ++) { if (ui->tableWidget->item(row, col) != 0) { ui->tableWidget->item(row, col)->setBackground(brush); ui->tableWidget->item(row, col)->setForeground(brushText); } } } } QList CSVWizard::findAccounts(QList &accountTypes, QString& statementHeader) { MyMoneyFile* file = MyMoneyFile::instance(); QList accountList; file->accountList(accountList); QList filteredTypes; QList filteredAccounts; QList::iterator account; QRegExp filterOutChars = QRegExp("-., "); for (account = accountList.begin(); account != accountList.end(); ++account) { if (accountTypes.contains((*account).accountType()) && !(*account).isClosed()) filteredTypes << *account; } // filter out accounts whose names aren't in statements header for (account = filteredTypes.begin(); account != filteredTypes.end(); ++account) { QString txt = (*account).name(); txt = txt.replace(filterOutChars, ""); if (statementHeader.contains(txt, Qt::CaseInsensitive)) filteredAccounts << *account; } // if filtering returned more results, filter out accounts whose numbers aren't in statements header if (filteredAccounts.count() > 1) { for (account = filteredAccounts.begin(); account != filteredAccounts.end();) { QString txt = (*account).number(); txt = txt.replace(filterOutChars, ""); if (txt.isEmpty() || txt.length() < 3) { ++account; continue; } if (statementHeader.contains(txt, Qt::CaseInsensitive)) ++account; else account = filteredAccounts.erase(account); } } // if filtering by name and number didn't return nothing, then try filtering by number only if (filteredAccounts.isEmpty()) { for (account = filteredTypes.begin(); account != filteredTypes.end(); ++account) { QString txt = (*account).number(); txt = txt.replace(filterOutChars, ""); if (statementHeader.contains(txt, Qt::CaseInsensitive)) filteredAccounts << *account; } } return filteredAccounts; } bool CSVWizard::detectAccount(MyMoneyStatement& st) { QString statementHeader; for (int row = 0; row < m_startLine - 1; ++row) // concatenate header for better search statementHeader += m_lineList.value(row); QRegExp filterOutChars = QRegExp("-., "); statementHeader.replace(filterOutChars, ""); QList accounts; QList accountTypes; if (m_profileType == CSVWizard::ProfileBank) { accountTypes << MyMoneyAccount::Checkings << MyMoneyAccount::Savings << MyMoneyAccount::Liability << MyMoneyAccount::Checkings << MyMoneyAccount::Savings << MyMoneyAccount::Cash << MyMoneyAccount::CreditCard << MyMoneyAccount::Loan << MyMoneyAccount::Asset << MyMoneyAccount::Liability; accounts = findAccounts(accountTypes, statementHeader); } else if (m_profileType == CSVWizard::ProfileInvest) { accountTypes << MyMoneyAccount::Investment; // take investment accounts... accounts = findAccounts(accountTypes, statementHeader); //...and search them in statement header } if (accounts.count() == 1) { // set account in statement, if it was the only one match st.m_strAccountName = accounts.first().name(); st.m_strAccountNumber = accounts.first().number(); st.m_accountId = accounts.first().id(); switch (accounts.first().accountType()) { case MyMoneyAccount::Checkings: st.m_eType=MyMoneyStatement::etCheckings; break; case MyMoneyAccount::Savings: st.m_eType=MyMoneyStatement::etSavings; break; case MyMoneyAccount::Investment: st.m_eType=MyMoneyStatement::etInvestment; break; case MyMoneyAccount::CreditCard: st.m_eType=MyMoneyStatement::etCreditCard; break; default: st.m_eType=MyMoneyStatement::etNone; } return true; } return false; } bool CSVWizard::detectDecimalSymbol(const int col, int& symbol) { if (symbol != 2) return true; // get list of used currencies to remove them from col QList accountList; MyMoneyFile::instance()->accountList(accountList); QList accountTypes; accountTypes << MyMoneyAccount::Checkings << MyMoneyAccount::Savings << MyMoneyAccount::Liability << MyMoneyAccount::Checkings << MyMoneyAccount::Savings << MyMoneyAccount::Cash << MyMoneyAccount::CreditCard << MyMoneyAccount::Loan << MyMoneyAccount::Asset << MyMoneyAccount::Liability; QSet currencySymbols; for (QList::ConstIterator account = accountList.cbegin(); account != accountList.cend(); ++account) { if (accountTypes.contains((*account).accountType())) { // account must actually have currency property currencySymbols.insert((*account).currencyId()); // add currency id currencySymbols.insert(MyMoneyFile::instance()->currency((*account).currencyId()).tradingSymbol()); // add currency symbol } } QString filteredCurrencies = QStringList(currencySymbols.values()).join(""); QString pattern = QString("%1%2").arg(QLocale().currencySymbol()).arg(filteredCurrencies); QRegularExpression re("^[\\(+-]?\\d+[\\)]?$"); // matches '0' ; '+12' ; '-345' ; '(6789)' bool dotIsDecimalSeparator = false; bool commaIsDecimalSeparator = false; for (int row = m_startLine - 1; row < m_endLine; ++row) { QString txt = ui->tableWidget->item(row, col)->text(); if (txt.isEmpty()) // nothing to process, so go to next row continue; int dotPos = txt.lastIndexOf("."); // get last positions of decimal/thousand separator... int commaPos = txt.lastIndexOf(","); // ...to be able to determine which one is the last if (dotPos != -1 && commaPos != -1) { if (dotPos > commaPos && commaIsDecimalSeparator == false) // follwing case 1,234.56 dotIsDecimalSeparator = true; else if (dotPos < commaPos && dotIsDecimalSeparator == false) // follwing case 1.234,56 commaIsDecimalSeparator = true; else // follwing case 1.234,56 and somwhere earlier there was 1,234.56 so unresolvable conflict return false; } else if (dotPos != -1) { // follwing case 1.23 if (dotIsDecimalSeparator) // it's already know that dotIsDecimalSeparator continue; if (!commaIsDecimalSeparator) // if there is no conflict with comma as decimal separator dotIsDecimalSeparator = true; else { if (txt.count('.') > 1) // follwing case 1.234.567 so OK continue; else if (txt.length() - 4 == dotPos) // follwing case 1.234 and somwhere earlier there was 1.234,56 so OK continue; else // follwing case 1.23 and somwhere earlier there was 1,23 so unresolvable conflict return false; } } else if (commaPos != -1) { // follwing case 1,23 if (commaIsDecimalSeparator) // it's already know that commaIsDecimalSeparator continue; else if (!dotIsDecimalSeparator) // if there is no conflict with dot as decimal separator commaIsDecimalSeparator = true; else { if (txt.count(',') > 1) // follwing case 1,234,567 so OK continue; else if (txt.length() - 4 == commaPos) // follwing case 1,234 and somwhere earlier there was 1,234.56 so OK continue; else // follwing case 1,23 and somwhere earlier there was 1.23 so unresolvable conflict return false; } } else { // follwing case 123 txt.remove(QRegularExpression("[ " + QRegularExpression::escape(pattern) + ']')); QRegularExpressionMatch match = re.match(txt); if (match.hasMatch()) // if string is pure numerical then go forward... continue; else // ...if not then it's non-numerical garbage return false; } } if (dotIsDecimalSeparator) symbol = 0; else if (commaIsDecimalSeparator) symbol = 1; else { // whole column was empty, but we don't want to fail so take os decimal symbol if (QLocale().decimalPoint() == '.') symbol = 0; else symbol = 1; } return true; } int CSVWizard::getMaxColumnCount(QStringList &lineList, int &delimiter) { if (lineList.isEmpty()) return 0; QList delimiterIndexes; if (delimiter == -1) delimiterIndexes = QList{0, 1, 2, 3}; // include all delimiters to test or ... else delimiterIndexes = QList{delimiter}; // ... only the one specified int totalDelimiterCount[4] = {0}; // Total in file for each delimiter int thisDelimiterCount[4] = {0}; // Total in this line for each delimiter int colCount = 0; // Total delimiters in this line int possibleDelimiter = 0; int maxColumnCount = 0; for (int i = 0; i < lineList.count(); i++) { QString data = lineList[i]; for (QList::ConstIterator it = delimiterIndexes.constBegin(); it != delimiterIndexes.constEnd(); it++) { m_parse->setFieldDelimiterIndex(*it); colCount = m_parse->parseLine(data).count(); // parse each line using each delimiter if (colCount > thisDelimiterCount[*it]) thisDelimiterCount[*it] = colCount; if (thisDelimiterCount[*it] > maxColumnCount) maxColumnCount = thisDelimiterCount[*it]; totalDelimiterCount[*it] += colCount; if (totalDelimiterCount[*it] > totalDelimiterCount[possibleDelimiter]) possibleDelimiter = *it; } } delimiter = possibleDelimiter; m_parse->setFieldDelimiterIndex(delimiter); return maxColumnCount; } bool CSVWizard::getInFileName(QString& inFileName) { if (inFileName.isEmpty()) inFileName = QDir::homePath(); if(inFileName.startsWith("~/")) //expand Linux home directory inFileName.replace(0, 1, QDir::home().absolutePath()); QFileInfo fileInfo = QFileInfo(inFileName); if (fileInfo.isFile()) { // if it is file... if (fileInfo.exists()) // ...and exists... return true; // ...then all is OK... else { // ...but if not... fileInfo.setFile(fileInfo.absolutePath()); //...then set start directory to directory of that file... if (!fileInfo.exists()) //...and if it doesn't exist too... fileInfo.setFile(QDir::homePath()); //...then set start directory to home path } } QPointer dialog = new QFileDialog(this, QString(), fileInfo.absoluteFilePath(), i18n("CSV Files (*.csv)")); dialog->setOption(QFileDialog::DontUseNativeDialog, true); //otherwise we cannot add custom QComboBox dialog->setFileMode(QFileDialog::ExistingFile); QPointer label = new QLabel(i18n("Encoding")); dialog->layout()->addWidget(label); // Add encoding selection to FileDialog QPointer comboBoxEncode = new QComboBox(); setCodecList(m_codecs, comboBoxEncode); comboBoxEncode->setCurrentIndex(m_encodeIndex); connect(comboBoxEncode, SIGNAL(activated(int)), this, SLOT(encodingChanged(int))); dialog->layout()->addWidget(comboBoxEncode); QUrl url; if (dialog->exec() == QDialog::Accepted) url = dialog->selectedUrls().first(); else url.clear(); delete dialog; if (url.isEmpty()) return false; else if (url.isLocalFile()) inFileName = url.toLocalFile(); else { inFileName = QDir::tempPath(); if (!inFileName.endsWith(QDir::separator())) inFileName += QDir::separator(); inFileName += url.fileName(); qDebug() << "Source:" << url.toDisplayString() << "Destination:" << inFileName; KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromUserInput(inFileName), -1, KIO::Overwrite); KJobWidgets::setWindow(job, this); job->exec(); if (job->error()) { KMessageBox::detailedError(this, i18n("Error while loading file '%1'.", url.toDisplayString()), job->errorString(), i18n("File access error")); return false; } } if (inFileName.isEmpty()) return false; return true; } void CSVWizard::encodingChanged(int index) { m_encodeIndex = index; } void CSVWizard::readFile(const QString& fname) { if (!fname.isEmpty()) m_inFileName = fname; m_importError = false; QFile inFile(m_inFileName); inFile.open(QIODevice::ReadOnly); // allow a Carriage return -// QIODevice::Text QTextStream inStream(&inFile); QTextCodec* codec = QTextCodec::codecForMib(m_codecs.value(m_encodeIndex)->mibEnum()); inStream.setCodec(codec); QString buf = inStream.readAll(); inFile.close(); m_lineList = m_parse->parseFile(buf, 1, 0); // parse the buffer m_fileEndLine = m_parse->lastLine(); // won't work without above line if (m_fileEndLine > m_trailerLines) m_endLine = m_fileEndLine - m_trailerLines; else m_endLine = m_fileEndLine; // Ignore m_trailerLines as > file length. if (m_startLine > m_endLine) // Don't allow m_startLine > m_endLine m_startLine = m_endLine; } void CSVWizard::displayLines(const QStringList &lineList, Parse* parse) { ui->tableWidget->clear(); m_row = 0; QFont font(QApplication::font()); ui->tableWidget->setFont(font); ui->tableWidget->setRowCount(lineList.count()); ui->tableWidget->setColumnCount(m_maxColumnCount); for (int line = 0; line < lineList.count(); ++line) { QStringList columnList = parse->parseLine(lineList[line]); for (int col = 0; col < columnList.count(); ++col) { QTableWidgetItem *item = new QTableWidgetItem; // new item for tableWidget item->setText(columnList[col]); ui->tableWidget->setItem(m_row, col, item); // add item to tableWidget } if (columnList.count() < m_maxColumnCount) { // if 'header' area has less columns than 'data' area, then fill this area with whitespaces for nice effect with markUnwantedRows for (int col = columnList.count(); col < m_maxColumnCount; ++col) { QTableWidgetItem *item = new QTableWidgetItem; // new item for tableWidget item->setText(""); ui->tableWidget->setItem(m_row, col, item); // add item to tableWidget } } m_row ++; } for (int col = 0; col < ui->tableWidget->columnCount(); col ++) ui->tableWidget->resizeColumnToContents(col); } void CSVWizard::updateWindowSize() { QTableWidget *table = this->ui->tableWidget; table->resizeColumnsToContents(); layout()->invalidate(); layout()->activate(); QRect screen = QApplication::desktop()->availableGeometry(); //get available screen size QRect wizard = this->frameGeometry(); //get current wizard size int newWidth = table->verticalHeader()->width() + //take header, margins nad scrollbar into account table->contentsMargins().left() + table->contentsMargins().right(); if (table->verticalScrollBar()->isEnabled()) newWidth += table->verticalScrollBar()->width(); for(int i = 0; i < table->columnCount(); ++i) newWidth += table->columnWidth(i); //add up required column widths int newHeight = table->horizontalHeader()->height() + table->horizontalScrollBar()->height() + table->contentsMargins().top() + table->contentsMargins().bottom(); if (table->horizontalScrollBar()->isEnabled()) newHeight += table->horizontalScrollBar()->height(); if( this->ui->tableWidget->rowCount() > 0) newHeight += this->ui->tableWidget->rowCount() * table->rowHeight(0); //add up estimated row heights newWidth = wizard.width() + (newWidth - table->width()); newHeight = wizard.height() + (newHeight - table->height()); if (newWidth > screen.width()) //limit wizard size to screen size newWidth = screen.width(); if (newHeight > screen.height()) newHeight = screen.height(); if (newWidth < this->m_initialWidth) //don't shrink wizard if required size is less than initial newWidth = this->m_initialWidth; if (newHeight < this->m_initialHeight) newHeight = this->m_initialHeight; newWidth -= (wizard.width() - this->geometry().width()); // remove window frame newHeight -= (wizard.height() - this->geometry().height()); wizard.setWidth(newWidth); wizard.setHeight(newHeight); wizard.moveTo((screen.width() - wizard.width()) / 2, (screen.height() - wizard.height()) / 2); this->setGeometry(wizard); } void CSVWizard::slotFileDialogClicked() { saveWindowSize(m_config); if (!m_pageInvestment.isNull()) m_wizard->removePage(PageInvestment); delete m_pageInvestment; if (!m_pageBanking.isNull()) m_wizard->removePage(PageBanking); delete m_pageBanking; m_profileName = m_pageIntro->ui->combobox_source->currentText(); m_skipSetup = m_pageIntro->ui->checkBoxSkipSetup->isChecked(); m_accept = false; m_acceptAllInvalid = false; // Don't accept further invalid values. m_memoColList.clear(); if (m_profileType == CSVWizard::ProfileInvest) { m_pageInvestment = new InvestmentPage; m_wizard->setPage(PageInvestment, m_pageInvestment); m_pageInvestment->setParent(this); m_pageInvestment->readSettings(m_config); } else if (m_profileType == CSVWizard::ProfileBank) { m_pageBanking = new BankingPage; m_wizard->setPage(PageBanking, m_pageBanking); m_pageBanking->setParent(this); m_pageBanking->readSettings(m_config); } else if (m_profileType == CSVWizard::ProfileStockPrices || m_profileType == CSVWizard::ProfileCurrencyPrices) { m_pagePrices = new PricesPage; m_wizard->setPage(PagePrices, m_pagePrices); m_pagePrices->setParent(this); m_pagePrices->readSettings(m_config); } if (!getInFileName(m_inFileName)) return; readFile(m_inFileName); m_wizard->next(); //go to separator page if (m_skipSetup) for (int i = 0; i < 4; i++) //programmaticaly go through separator-, rows-, investment-/bank-, formatspage m_wizard->next(); } void CSVWizard::resizeEvent(QResizeEvent* ev) { if (ev->spontaneous()) { ev->ignore(); return; } } void CSVWizard::slotClose() { if (m_profileType == ProfileBank) m_pageBanking->saveSettings(); else if (m_profileType == ProfileInvest) m_pageInvestment->saveSettings(); else if (m_profileType == ProfileStockPrices || m_profileType == ProfileCurrencyPrices) m_pagePrices->saveSettings(); close(); } //------------------------------------------------------------------------------------------------------- IntroPage::IntroPage(QDialog *parent) : CSVWizardPage(parent), ui(new Ui::IntroPage), m_pageLayout(0) { ui->setupUi(this); } IntroPage::~IntroPage() { delete ui; } void IntroPage::setParent(CSVWizard* dlg) { CSVWizardPage::setParent(dlg); m_wizDlg->showStage(); wizard()->button(QWizard::CustomButton1)->setEnabled(false); } void IntroPage::slotAddProfile() { profileChanged(ProfileAdd); } void IntroPage::slotRemoveProfile() { profileChanged(ProfileRemove); } void IntroPage::slotRenameProfile() { profileChanged(ProfileRename); } void IntroPage::profileChanged(const profileActionsE& action) { int cbIndex = ui->combobox_source->currentIndex(); QString cbText = ui->combobox_source->currentText(); if (cbText.isEmpty()) // you cannot neither add nor remove empty name profile or rename to empty name return; QString profileTypeStr; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) profileTypeStr = "Bank"; else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) profileTypeStr = "Invest"; else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices) profileTypeStr = "SPrices"; else if (m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) profileTypeStr = "CPrices"; KConfigGroup profileNamesGroup(m_wizDlg->m_config, "ProfileNames"); KConfigGroup currentProfileName(m_wizDlg->m_config, profileTypeStr + '-' + cbText); if (action == ProfileRemove) { if (m_wizDlg->m_profileList.value(cbIndex) != cbText) return; m_wizDlg->m_profileList.removeAt(cbIndex); currentProfileName.deleteGroup(); ui->combobox_source->removeItem(cbIndex); KMessageBox::information(m_wizDlg->m_wizard, i18n("
Profile %1 has been removed.
", cbText)); } if (action == ProfileAdd || action == ProfileRename) { int dupIndex = m_wizDlg->m_profileList.indexOf(cbText, Qt::CaseInsensitive); if (dupIndex == cbIndex && cbIndex != -1) // if profile name wasn't changed then return return; else if (dupIndex != -1) { // profile with the same name already exists ui->combobox_source->setItemText(cbIndex, m_wizDlg->m_profileList.value(cbIndex)); KMessageBox::information(m_wizDlg->m_wizard, i18n("
Profile %1 already exists.
" "Please enter another name
", cbText)); return; } if (action == ProfileAdd) { m_wizDlg->m_profileList.append(cbText); ui->combobox_source->addItem(cbText); ui->combobox_source->setCurrentIndex(m_wizDlg->m_profileList.count() - 1); currentProfileName.writeEntry("Directory", QString()); KMessageBox::information(m_wizDlg->m_wizard, i18n("
Profile %1 has been added.
", cbText)); } else if (action == ProfileRename) { KConfigGroup oldProfileName(m_wizDlg->m_config, profileTypeStr + '-' + m_wizDlg->m_profileList.value(cbIndex)); oldProfileName.copyTo(¤tProfileName); oldProfileName.deleteGroup(); ui->combobox_source->setItemText(cbIndex, cbText); oldProfileName.sync(); KMessageBox::information(m_wizDlg->m_wizard, i18n("
Profile name has been renamed from %1 to %2.
", m_wizDlg->m_profileList.value(cbIndex), cbText)); m_wizDlg->m_profileList[cbIndex] = cbText; } } currentProfileName.sync(); profileNamesGroup.writeEntry(profileTypeStr, m_wizDlg->m_profileList); // update profiles list profileNamesGroup.sync(); } void IntroPage::slotComboSourceIndexChanged(int idx) { if (idx == -1) { wizard()->button(QWizard::CustomButton1)->setEnabled(false); ui->checkBoxSkipSetup->setEnabled(false); ui->buttonRemove->setEnabled(false); ui->buttonRename->setEnabled(false); } else { wizard()->button(QWizard::CustomButton1)->setEnabled(true); ui->checkBoxSkipSetup->setEnabled(true); ui->buttonRemove->setEnabled(true); ui->buttonRename->setEnabled(true); } } void IntroPage::profileTypeChanged(const CSVWizard::profileTypeE profileType, bool toggled) { if (!toggled) return; KConfigGroup profilesGroup(m_wizDlg->m_config, "ProfileNames"); m_wizDlg->m_profileType = profileType; QString profileTypeStr; int priorProfile; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { ui->radioButton_invest->setChecked(false); ui->radioButton_stockprices->setChecked(false); ui->radioButton_currencyprices->setChecked(false); profileTypeStr = "Bank"; } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { ui->radioButton_bank->setChecked(false); ui->radioButton_stockprices->setChecked(false); ui->radioButton_currencyprices->setChecked(false); profileTypeStr = "Invest"; } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices) { ui->radioButton_bank->setChecked(false); ui->radioButton_invest->setChecked(false); ui->radioButton_currencyprices->setChecked(false); profileTypeStr = "SPrices"; } else if (m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { ui->radioButton_bank->setChecked(false); ui->radioButton_invest->setChecked(false); ui->radioButton_stockprices->setChecked(false); profileTypeStr = "CPrices"; } m_wizDlg->m_profileList = profilesGroup.readEntry(profileTypeStr, QStringList()); priorProfile = profilesGroup.readEntry("Prior" + profileTypeStr, 0); ui->combobox_source->clear(); ui->combobox_source->addItems(m_wizDlg->m_profileList); ui->combobox_source->setCurrentIndex(priorProfile); ui->combobox_source->setEnabled(true); ui->buttonAdd->setEnabled(true); } void IntroPage::slotBankRadioToggled(bool toggled) { profileTypeChanged(CSVWizard::ProfileBank, toggled); } void IntroPage::slotInvestRadioToggled(bool toggled) { profileTypeChanged(CSVWizard::ProfileInvest, toggled); } void IntroPage::slotCurrencyPricesRadioToggled(bool toggled) { profileTypeChanged(CSVWizard::ProfileCurrencyPrices, toggled); } void IntroPage::slotStockPricesRadioToggled(bool toggled) { profileTypeChanged(CSVWizard::ProfileStockPrices, toggled); } void IntroPage::initializePage() { m_wizDlg->ui->tableWidget->clear(); m_wizDlg->ui->tableWidget->setColumnCount(0); m_wizDlg->ui->tableWidget->setRowCount(0); m_wizDlg->ui->tableWidget->verticalScrollBar()->setValue(0); m_wizDlg->ui->tableWidget->horizontalScrollBar()->setValue(0); wizard()->setButtonText(QWizard::CustomButton1, i18n("Select File")); wizard()->button(QWizard::CustomButton1)->setToolTip(i18n("A profile must be selected before selecting a file.")); QList layout; layout << QWizard::Stretch << QWizard::CustomButton1 << QWizard::CancelButton; wizard()->setButtonLayout(layout); m_wizDlg->m_importError = false; ui->combobox_source->lineEdit()->setClearButtonEnabled(true); connect(ui->combobox_source, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboSourceIndexChanged(int))); connect(ui->buttonAdd, SIGNAL(clicked()), this, SLOT(slotAddProfile())); connect(ui->buttonRemove, SIGNAL(clicked()), this, SLOT(slotRemoveProfile())); connect(ui->buttonRename, SIGNAL(clicked()), this, SLOT(slotRenameProfile())); connect(ui->radioButton_bank, SIGNAL(toggled(bool)), this, SLOT(slotBankRadioToggled(bool))); connect(ui->radioButton_invest, SIGNAL(toggled(bool)), this, SLOT(slotInvestRadioToggled(bool))); connect(ui->radioButton_currencyprices, SIGNAL(toggled(bool)), this, SLOT(slotCurrencyPricesRadioToggled(bool))); connect(ui->radioButton_stockprices, SIGNAL(toggled(bool)), this, SLOT(slotStockPricesRadioToggled(bool))); if (m_wizDlg->m_initialHeight == -1 || m_wizDlg->m_initialWidth == -1) { m_wizDlg->m_initialHeight = m_wizDlg->geometry().height(); m_wizDlg->m_initialWidth = m_wizDlg->geometry().width(); } else { //resize wizard to its initial size and center it m_wizDlg->setGeometry( QStyle::alignedRect( Qt::LeftToRight, Qt::AlignCenter, QSize(m_wizDlg->m_initialWidth, m_wizDlg->m_initialHeight), QApplication::desktop()->availableGeometry() ) ); } } bool IntroPage::validatePage() { return true; } int IntroPage::nextId() const { return CSVWizard::PageSeparator; } SeparatorPage::SeparatorPage(QDialog *parent) : CSVWizardPage(parent), ui(new Ui::SeparatorPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); } SeparatorPage::~SeparatorPage() { delete ui; } void SeparatorPage::initializePage() { ui->comboBox_fieldDelimiter->setCurrentIndex(m_wizDlg->m_fieldDelimiterIndex); ui->comboBox_textDelimiter->setCurrentIndex(m_wizDlg->m_textDelimiterIndex); connect(ui->comboBox_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); connect(ui->comboBox_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); emit ui->comboBox_fieldDelimiter->currentIndexChanged(m_wizDlg->m_fieldDelimiterIndex); emit ui->comboBox_textDelimiter->currentIndexChanged(m_wizDlg->m_textDelimiterIndex); QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } void SeparatorPage::textDelimiterChanged(const int index) { if (index < 0) ui->comboBox_textDelimiter->setCurrentIndex(0); // for now there is no better idea how to detect textDelimiter m_wizDlg->m_textDelimiterIndex = index; m_wizDlg->m_parse->setTextDelimiterIndex(index); m_wizDlg->m_parse->setTextDelimiterCharacter(index); m_wizDlg->m_textDelimiterCharacter = m_wizDlg->m_parse->textDelimiterCharacter(index); } void SeparatorPage::fieldDelimiterChanged(const int index) { if (index == -1 && !m_wizDlg->m_autodetect.value(CSVWizard::AutoFieldDelimiter)) return; m_wizDlg->m_fieldDelimiterIndex = index; m_wizDlg->m_maxColumnCount = m_wizDlg->getMaxColumnCount(m_wizDlg->m_lineList, m_wizDlg->m_fieldDelimiterIndex); // get column count, we get with this fieldDelimiter m_wizDlg->m_endColumn = m_wizDlg->m_maxColumnCount; m_wizDlg->m_parse->setFieldDelimiterIndex(m_wizDlg->m_fieldDelimiterIndex); m_wizDlg->m_parse->setFieldDelimiterCharacter(m_wizDlg->m_fieldDelimiterIndex); m_wizDlg->m_fieldDelimiterCharacter = m_wizDlg->m_parse->fieldDelimiterCharacter(m_wizDlg->m_fieldDelimiterIndex); if (index == -1) { ui->comboBox_fieldDelimiter->blockSignals(true); ui->comboBox_fieldDelimiter->setCurrentIndex(m_wizDlg->m_fieldDelimiterIndex); ui->comboBox_fieldDelimiter->blockSignals(false); } m_wizDlg->displayLines(m_wizDlg->m_lineList, m_wizDlg->m_parse); // refresh tableWidget with new fieldDelimiter set m_wizDlg->updateWindowSize(); emit completeChanged(); } bool SeparatorPage::isComplete() const { if (ui->comboBox_fieldDelimiter->currentIndex() != -1) { if (m_wizDlg->m_profileType == CSVWizard::ProfileBank && m_wizDlg->m_endColumn > 2) return true; else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest && m_wizDlg->m_endColumn > 3) return true; else if ((m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) && m_wizDlg->m_endColumn > 1) return true; } return false; } bool SeparatorPage::validatePage() { return true; } void SeparatorPage::cleanupPage() { // On completion with error force use of 'Back' button. // ...to allow resetting of 'Skip setup' disconnect(ui->comboBox_fieldDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(fieldDelimiterChanged(int))); disconnect(ui->comboBox_textDelimiter, SIGNAL(currentIndexChanged(int)), this, SLOT(textDelimiterChanged(int))); m_wizDlg->m_pageIntro->initializePage(); // Need to show button(QWizard::CustomButton1) not 'NextButton' } RowsPage::RowsPage(QDialog *parent) : CSVWizardPage(parent), ui(new Ui::RowsPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); } RowsPage::~RowsPage() { delete ui; } void RowsPage::initializePage() { disconnect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); disconnect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); ui->spinBox_skip->setMaximum(m_wizDlg->m_fileEndLine); ui->spinBox_skipToLast->setMaximum(m_wizDlg->m_fileEndLine); ui->spinBox_skip->setValue(m_wizDlg->m_startLine); ui->spinBox_skipToLast->setValue(m_wizDlg->m_endLine); m_wizDlg->markUnwantedRows(); m_wizDlg->m_vScrollBar->setValue(m_wizDlg->m_startLine - 1); connect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); connect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } void RowsPage::startRowChanged(int val) { if (val > m_wizDlg->m_fileEndLine) { ui->spinBox_skip->setValue(m_wizDlg->m_fileEndLine); return; } if (val > m_wizDlg->m_endLine) { ui->spinBox_skip->setValue(m_wizDlg->m_endLine); return; } m_wizDlg->m_startLine = val; if (!m_wizDlg->m_inFileName.isEmpty()) { m_wizDlg->m_vScrollBar->setValue(m_wizDlg->m_startLine - 1); m_wizDlg->markUnwantedRows(); } } void RowsPage::endRowChanged(int val) { if (val > m_wizDlg->m_fileEndLine) { ui->spinBox_skipToLast->setValue(m_wizDlg->m_fileEndLine); return; } if (val < m_wizDlg->m_startLine) { ui->spinBox_skipToLast->setValue(m_wizDlg->m_startLine); return; } m_wizDlg->m_trailerLines = m_wizDlg->m_fileEndLine - val; m_wizDlg->m_endLine = val; if (!m_wizDlg->m_inFileName.isEmpty()) { m_wizDlg->markUnwantedRows(); } } void RowsPage::cleanupPage() { disconnect(ui->spinBox_skip, SIGNAL(valueChanged(int)), this, SLOT(startRowChanged(int))); disconnect(ui->spinBox_skipToLast, SIGNAL(valueChanged(int)), this, SLOT(endRowChanged(int))); m_wizDlg->clearBackground(); } int RowsPage::nextId() const { - int ret; + int ret = CSVWizard::PageBanking; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) ret = CSVWizard::PageBanking; else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) ret = CSVWizard::PageInvestment; else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) ret = CSVWizard::PagePrices; return ret; - } FormatsPage::FormatsPage(QDialog *parent) : CSVWizardPage(parent), ui(new Ui::FormatsPage) { ui->setupUi(this); m_pageLayout = new QVBoxLayout; ui->horizontalLayout->insertLayout(0, m_pageLayout); } FormatsPage::~FormatsPage() { delete ui; } void FormatsPage::initializePage() { disconnect(ui->comboBox_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); disconnect(ui->comboBox_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); m_isDecimalSymbolOK = false; m_isDateFormatOK = false; QList layout; layout << QWizard::Stretch << QWizard::CustomButton3 << QWizard::CustomButton2 << QWizard::BackButton << QWizard::FinishButton << QWizard::CancelButton; wizard()->setOption(QWizard::HaveCustomButton2, true); wizard()->setButtonText(QWizard::CustomButton2, i18n("Import CSV")); wizard()->setOption(QWizard::HaveCustomButton3, true); wizard()->setButtonText(QWizard::CustomButton3, i18n("Make QIF File")); wizard()->setButtonLayout(layout); wizard()->button(QWizard::CustomButton2)->setEnabled(false); wizard()->button(QWizard::CustomButton3)->setEnabled(false); wizard()->button(QWizard::FinishButton)->setEnabled(false); ui->comboBox_thousandsDelimiter->setEnabled(false); ui->comboBox_dateFormat->setCurrentIndex(m_wizDlg->m_dateFormatIndex); // put before connect to not emit update signal connect(ui->comboBox_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); emit ui->comboBox_dateFormat->currentIndexChanged(m_wizDlg->m_dateFormatIndex); // emit update signal manually regardless of change to combobox ui->comboBox_decimalSymbol->setCurrentIndex(m_wizDlg->m_decimalSymbolIndex); // put before connect to not emit update signal connect(ui->comboBox_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); emit ui->comboBox_decimalSymbol->currentIndexChanged(m_wizDlg->m_decimalSymbolIndex); // emit update signal manually regardless of change to combobox if (m_wizDlg->m_skipSetup && wizard()->button(QWizard::CustomButton2)->isEnabled()) slotImportClicked(); } void FormatsPage::decimalSymbolChanged(int index) { switch (index) { case -1: if (!m_wizDlg->m_autodetect.value(CSVWizard::AutoDecimalSymbol)) return; case 2: m_wizDlg->m_decimalSymbolIndex = 2; m_wizDlg->m_decimalSymbol.clear(); break; default: m_wizDlg->m_parse->setDecimalSymbol(index); m_wizDlg->m_parse->setDecimalSymbolIndex(index); m_wizDlg->m_parse->setThousandsSeparator(index); m_wizDlg->m_parse->setThousandsSeparatorIndex(index); m_wizDlg->m_decimalSymbol = m_wizDlg->m_parse->decimalSymbol(index); m_wizDlg->m_decimalSymbolIndex = m_wizDlg->m_parse->decimalSymbolIndex(); } ui->comboBox_thousandsDelimiter->setCurrentIndex(m_wizDlg->m_decimalSymbolIndex); bool isOk = true; QList columnList; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { if (m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount) >= 0) { columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount); } else { columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDebit); columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnCredit); } } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnAmount); columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnPrice); columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnQuantity); if (m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee) != -1) columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee); } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { columnList << m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnPrice); } for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { int detectedSymbol = m_wizDlg->m_decimalSymbolIndex; if (!m_wizDlg->detectDecimalSymbol(*col, detectedSymbol)) { isOk = false; KMessageBox::sorry(this, i18n("
Autodetect could not detect your decimal symbol in column %1.
" "
Try manual selection to see problematic cells and correct your data.
", *col + 1), i18n("CSV import")); ui->comboBox_decimalSymbol->blockSignals(true); ui->comboBox_decimalSymbol->setCurrentIndex(-1); ui->comboBox_decimalSymbol->blockSignals(false); ui->comboBox_thousandsDelimiter->setCurrentIndex(-1); break; } m_wizDlg->m_decimalSymbolIndexMap.insert(*col, detectedSymbol); m_wizDlg->m_parse->setDecimalSymbol(detectedSymbol); m_wizDlg->m_parse->setThousandsSeparator(detectedSymbol); // separator list is in reverse so it's ok isOk &= validateDecimalSymbol(*col); } if (index == -1 && isOk) { // if detection went well and decimal symbol was unspeciffied then we'll be specifying it int prevDecimalSymbol = columnList.value(0); bool allSymbolsEqual = true; for (QList::const_iterator col = columnList.constBegin(); col < columnList.constEnd(); ++col) { if (m_wizDlg->m_decimalSymbolIndexMap.value(*col) != prevDecimalSymbol) allSymbolsEqual = false; } ui->comboBox_decimalSymbol->blockSignals(true); if (allSymbolsEqual) { // if symbol in all columns is equal then set it... ui->comboBox_decimalSymbol->setCurrentIndex(prevDecimalSymbol); ui->comboBox_thousandsDelimiter->setCurrentIndex(prevDecimalSymbol); } else { // else set to auto ui->comboBox_decimalSymbol->setCurrentIndex(2); ui->comboBox_thousandsDelimiter->setCurrentIndex(2); } ui->comboBox_decimalSymbol->blockSignals(false); } m_isDecimalSymbolOK = isOk; emit completeChanged(); } bool FormatsPage::validateDecimalSymbol(int col) { m_wizDlg->clearColumnsBackground(col); bool isOK = true; for (int row = m_wizDlg->m_startLine - 1; row < m_wizDlg->m_endLine; ++row) { QTableWidgetItem* item = m_wizDlg->ui->tableWidget->item(row, col); QString rawNumber = item->text(); m_wizDlg->m_parse->possiblyReplaceSymbol(rawNumber); if (!m_wizDlg->m_parse->invalidConversion() || rawNumber.isEmpty()) { // empty strings are welcome item->setBackground(m_wizDlg->m_colorBrush); item->setForeground(m_wizDlg->m_colorBrushText); } else { isOK = false; m_wizDlg->ui->tableWidget->scrollToItem(item, QAbstractItemView::EnsureVisible); item->setBackground(m_wizDlg->m_errorBrush); item->setForeground(m_wizDlg->m_errorBrushText); } } return isOK; } void FormatsPage::dateFormatChanged(int index) { if (index < 0) return; else { m_wizDlg->m_dateFormatIndex = index; m_wizDlg->m_date = m_wizDlg->m_dateFormats[m_wizDlg->m_dateFormatIndex]; } - int col; + int col = 0; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) col = m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDate); else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) col = m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnDate); else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) col = m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnDate); m_wizDlg->m_convertDate->setDateFormatIndex(index); m_isDateFormatOK = validateDateFormat(col); if (!m_isDateFormatOK) { KMessageBox::sorry(this, i18n("
There are invalid date formats in column '%1'.
" "
Please check your selections.
" , col + 1), i18n("CSV import")); } emit completeChanged(); } bool FormatsPage::validateDateFormat(int col) { m_wizDlg->clearColumnsBackground(col); bool isOK = true; for (int row = m_wizDlg->m_startLine - 1; row < m_wizDlg->m_endLine; ++row) { QTableWidgetItem* item = m_wizDlg->ui->tableWidget->item(row, col); QDate dat = m_wizDlg->m_convertDate->convertDate(item->text()); if (dat == QDate()) { isOK = false; m_wizDlg->ui->tableWidget->scrollToItem(item, QAbstractItemView::EnsureVisible); item->setBackground(m_wizDlg->m_errorBrush); item->setForeground(m_wizDlg->m_errorBrushText); } else { item->setBackground(m_wizDlg->m_colorBrush); item->setForeground(m_wizDlg->m_colorBrushText); } } return isOK; } void FormatsPage::slotImportClicked() { bool isOK = true; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) isOK = m_wizDlg->m_pageBanking->createStatement(m_wizDlg->st); else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) isOK = m_wizDlg->m_pageInvestment->createStatement(m_wizDlg->st); else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) isOK = m_wizDlg->m_pagePrices->createStatement(m_wizDlg->st); if (!isOK) { m_wizDlg->st = MyMoneyStatement(); // statement is invalid so erase it return; } m_wizDlg->hide(); //hide wizard so it will not cover accountselector emit m_wizDlg->statementReady(m_wizDlg->st); m_wizDlg->slotClose(); //close hidden window as it isn't needed anymore } void FormatsPage::slotSaveAsQIFClicked() { bool isOK = true; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) isOK = m_wizDlg->m_pageBanking->createStatement(m_wizDlg->st); else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) isOK = m_wizDlg->m_pageInvestment->createStatement(m_wizDlg->st); if (!isOK || m_wizDlg->st.m_listTransactions.isEmpty()) return; QString outFileName = m_wizDlg->m_inFileName; outFileName.truncate(m_wizDlg->m_inFileName.lastIndexOf('.')); outFileName += ".qif"; outFileName = QFileDialog::getSaveFileName(this, i18n("Save QIF"), outFileName, i18n("QIF Files (*.qif)")); if (outFileName.isEmpty()) return; QFile oFile(outFileName); oFile.open(QIODevice::WriteOnly); if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) m_wizDlg->m_pageBanking->makeQIF(m_wizDlg->st, oFile); else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) m_wizDlg->m_pageInvestment->makeQIF(m_wizDlg->st, oFile); oFile.close(); } bool FormatsPage::isComplete() const { const bool enable = m_isDecimalSymbolOK && m_isDateFormatOK; wizard()->button(QWizard::CustomButton2)->setEnabled(enable); if (m_wizDlg->m_profileType != CSVWizard::ProfileStockPrices && m_wizDlg->m_profileType != CSVWizard::ProfileCurrencyPrices) wizard()->button(QWizard::CustomButton3)->setEnabled(enable); return enable; } void FormatsPage::cleanupPage() { disconnect(ui->comboBox_dateFormat, SIGNAL(currentIndexChanged(int)), this, SLOT(dateFormatChanged(int))); disconnect(ui->comboBox_decimalSymbol, SIGNAL(currentIndexChanged(int)), this, SLOT(decimalSymbolChanged(int))); QList columnList; if (m_wizDlg->m_profileType == CSVWizard::ProfileBank) { columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDate); if (m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount) >= 0) columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnAmount); else columnList << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnDebit) << m_wizDlg->m_pageBanking->m_colTypeNum.value(BankingPage::ColumnCredit); } else if (m_wizDlg->m_profileType == CSVWizard::ProfileInvest) { columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnAmount) << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnPrice) << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnQuantity) << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnDate); if (m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee) != -1) columnList << m_wizDlg->m_pageInvestment->m_colTypeNum.value(InvestmentPage::ColumnFee); } else if (m_wizDlg->m_profileType == CSVWizard::ProfileStockPrices || m_wizDlg->m_profileType == CSVWizard::ProfileCurrencyPrices) { columnList << m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnPrice) << m_wizDlg->m_pagePrices->m_colTypeNum.value(PricesPage::ColumnDate); } m_wizDlg->clearColumnsBackground(columnList); m_wizDlg->st = MyMoneyStatement(); // any change on investment/banking page invalidates created statement QList layout; layout << QWizard::Stretch << QWizard::BackButton << QWizard::NextButton << QWizard::CancelButton; wizard()->setButtonLayout(layout); } void CSVWizard::closeEvent(QCloseEvent *event) { this->m_plugin->m_action->setEnabled(true); // reenable File->Import->CSV event->accept(); } bool CSVWizard::eventFilter(QObject *object, QEvent *event) { // prevent the QWizard part of CSVWizard window from closing on Escape key press if (object == this->m_wizard) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { close(); return true; } } } return false; } diff --git a/kmymoney/plugins/csvimport/currenciesdlg.cpp b/kmymoney/plugins/csvimport/currenciesdlg.cpp index f7495748c..f5de2e0c6 100644 --- a/kmymoney/plugins/csvimport/currenciesdlg.cpp +++ b/kmymoney/plugins/csvimport/currenciesdlg.cpp @@ -1,83 +1,84 @@ /******************************************************************************* * currenciesdlg.cpp * ------------------ * begin : Sat Jan 21 2017 * copyright : (C) 2017 by Łukasz Wojnilowicz * email : lukasz.wojnilowicz@gmail.com ********************************************************************************/ /******************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ********************************************************************************/ #include "currenciesdlg.h" #include "mymoneyfile.h" CurrenciesDlg::CurrenciesDlg() : ui(new Ui::CurrenciesDlg) { ui->setupUi(this); m_buttonOK = ui->buttonBox->button(QDialogButtonBox::Ok); m_buttonOK->setDefault(true); m_buttonOK->setShortcut(Qt::CTRL | Qt::Key_Return); m_buttonOK->setEnabled(false); connect(ui->cbFrom, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int))); connect(ui->cbTo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotIndexChanged(int))); } CurrenciesDlg::~CurrenciesDlg() { delete ui; } void CurrenciesDlg::initializeCurrencies(const QString &presetFromCurrency, const QString &presetToCurrency) { QList currencies = MyMoneyFile::instance()->currencyList(); ui->cbFrom->blockSignals(true); ui->cbTo->blockSignals(true); int presetFromIndex = -1; int presetToIndex = -1; for (QList::const_iterator currency = currencies.cbegin(); currency != currencies.cend(); ++currency) { QString name = (*currency).name(); QString id = (*currency).id(); QString symbol = (*currency).tradingSymbol(); if (id == presetFromCurrency) presetFromIndex = ui->cbFrom->count(); if (id == presetToCurrency) presetToIndex = ui->cbTo->count(); ui->cbFrom->addItem(name + QChar(' ') + QChar('(') + symbol + QChar(')'), QVariant(id)); ui->cbTo->addItem(name + QChar(' ') + QChar('(') + symbol + QChar(')'), QVariant(id)); } ui->cbFrom->blockSignals(false); ui->cbTo->blockSignals(false); ui->cbFrom->setCurrentIndex(presetFromIndex); ui->cbTo->setCurrentIndex(presetToIndex); emit ui->cbFrom->currentIndexChanged(presetFromIndex); // in case currentIndex == presetIndex and no signal would be emitted } QString CurrenciesDlg::fromCurrency() { return ui->cbFrom->currentData().toString(); } QString CurrenciesDlg::toCurrency() { return ui->cbTo->currentData().toString(); } int CurrenciesDlg::dontAsk() { return int(ui->cbDontAsk->isChecked()); } void CurrenciesDlg::slotIndexChanged(int index) { + Q_UNUSED(index); if (ui->cbFrom->currentIndex() != ui->cbTo->currentIndex() && ui->cbFrom->currentIndex() != -1 && ui->cbTo->currentIndex() != -1) m_buttonOK->setEnabled(true); else m_buttonOK->setEnabled(false); } diff --git a/kmymoney/reports/kreportchartview.cpp b/kmymoney/reports/kreportchartview.cpp index d1f0c510b..a37d18638 100644 --- a/kmymoney/reports/kreportchartview.cpp +++ b/kmymoney/reports/kreportchartview.cpp @@ -1,727 +1,727 @@ /*************************************************************************** kreportchartview.cpp ------------------- begin : Sun Aug 14 2005 copyright : (C) 2004-2005 by Ace Jones email : (C) 2017 Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kreportchartview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include #include #include #include #include #include #include #include #include #include #include #include #include "kmymoneyglobalsettings.h" #include #include using namespace reports; KReportChartView::KReportChartView(QWidget* parent) : KChart::Chart(parent), m_accountSeries(0), m_seriesTotals(0), m_numColumns(0), m_skipZero(0), m_backgroundBrush(KColorScheme(QPalette::Current).background()), m_foregroundBrush(KColorScheme(QPalette::Current).foreground()) { // ******************************************************************** // Set KMyMoney's Chart Parameter Defaults // ******************************************************************** //Set the background obtained from the color scheme BackgroundAttributes backAttr(backgroundAttributes()); backAttr.setBrush(m_backgroundBrush); backAttr.setVisible(true); setBackgroundAttributes(backAttr); //Line diagram KChart::LineDiagram* diagram = new KChart::LineDiagram; diagram->setModel(&m_model); this->coordinatePlane()->replaceDiagram(diagram); } void KReportChartView::drawPivotChart(const PivotGrid &grid, const MyMoneyReport &config, int numberColumns, const QStringList& columnHeadings, const QList& rowTypeList, const QStringList& columnTypeHeaderList) { //set the number of columns setNumColumns(numberColumns); //set skipZero m_skipZero = config.isSkippingZero(); //remove existing headers while (headerFooters().count() > 0) { HeaderFooter* delHeader = headerFooters().at(0); takeHeaderFooter(delHeader); delete delHeader; } //make sure the model is clear m_model.removeColumns(0, m_model.columnCount()); m_model.removeRows(0, m_model.rowCount()); //set the new header HeaderFooter* header = new HeaderFooter(this); header->setText(config.name()); header->setType(HeaderFooter::Header); header->setPosition(Position::North); TextAttributes headerTextAttr(header->textAttributes()); headerTextAttr.setPen(m_foregroundBrush.color()); header->setTextAttributes(headerTextAttr); addHeaderFooter(header); // whether to limit the chart to use series totals only. Used for reports which only // show one dimension (pie). setSeriesTotals(false); // whether series (rows) are accounts (true) or months (false). This causes a lot // of complexity in the charts. The problem is that circular reports work best with // an account in a COLUMN, while line/bar prefer it in a ROW. setAccountSeries(true); - CartesianAxis *xAxis; - KBalanceAxis *yAxis; + CartesianAxis *xAxis = 0; + KBalanceAxis *yAxis = 0; switch (config.chartType()) { case MyMoneyReport::eChartNone: case MyMoneyReport::eChartEnd: case MyMoneyReport::eChartLine: case MyMoneyReport::eChartBar: case MyMoneyReport::eChartStackedBar: { CartesianCoordinatePlane* cartesianPlane = new CartesianCoordinatePlane; replaceCoordinatePlane(cartesianPlane); // set-up axis type if (config.isLogYAxis()) cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Logarithmic); else cartesianPlane->setAxesCalcModeY(KChart::AbstractCoordinatePlane::Linear); // set-up grid GridAttributes ga = cartesianPlane->gridAttributes(Qt::Vertical); ga.setGridVisible(config.isChartCHGridLines()); ga.setGridStepWidth(locale().toDouble(config.dataMajorTick())); ga.setGridSubStepWidth(locale().toDouble(config.dataMinorTick())); cartesianPlane->setGridAttributes(Qt::Vertical, ga); ga = cartesianPlane->gridAttributes(Qt::Horizontal); ga.setGridVisible(config.isChartSVGridLines()); cartesianPlane->setGridAttributes(Qt::Horizontal, ga); // set-up data range cartesianPlane->setVerticalRange(qMakePair(locale().toDouble(config.dataRangeStart()), locale().toDouble(config.dataRangeEnd()))); //set-up x axis xAxis = new CartesianAxis(); xAxis->setPosition(CartesianAxis::Bottom); xAxis->setTitleText(i18n("Time")); TextAttributes xAxisTitleTextAttr(xAxis->titleTextAttributes()); xAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()); xAxisTitleTextAttr.setPen(m_foregroundBrush.color()); xAxis->setTitleTextAttributes(xAxisTitleTextAttr); TextAttributes xAxisTextAttr(xAxis->textAttributes()); xAxisTextAttr.setPen(m_foregroundBrush.color()); xAxis->setTextAttributes(xAxisTextAttr); RulerAttributes xAxisRulerAttr(xAxis->rulerAttributes()); xAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color()); xAxisRulerAttr.setShowRulerLine(true); xAxis->setRulerAttributes(xAxisRulerAttr); //set-up y axis yAxis = new KBalanceAxis(); yAxis->setPosition(CartesianAxis::Left); // TODO // if the chart shows prices and no balance // the axis title should be 'Price' if (config.isIncludingPrice()) yAxis->setTitleText(i18n("Price")); else yAxis->setTitleText(i18n("Balance")); TextAttributes yAxisTitleTextAttr(yAxis->titleTextAttributes()); yAxisTitleTextAttr.setMinimalFontSize(QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize()); yAxisTitleTextAttr.setPen(m_foregroundBrush.color()); yAxis->setTitleTextAttributes(yAxisTitleTextAttr); TextAttributes yAxisTextAttr(yAxis->textAttributes()); yAxisTextAttr.setPen(m_foregroundBrush.color()); yAxis->setTextAttributes(yAxisTextAttr); RulerAttributes yAxisRulerAttr(yAxis->rulerAttributes()); yAxisRulerAttr.setTickMarkPen(m_foregroundBrush.color()); yAxisRulerAttr.setShowRulerLine(true); yAxis->setRulerAttributes(yAxisRulerAttr); break; } case MyMoneyReport::eChartPie: case MyMoneyReport::eChartRing:{ PolarCoordinatePlane* polarPlane = new PolarCoordinatePlane; replaceCoordinatePlane(polarPlane); // set-up grid GridAttributes ga = polarPlane->gridAttributes(true); ga.setGridVisible(config.isChartCHGridLines()); polarPlane->setGridAttributes(true, ga); ga = polarPlane->gridAttributes(false); ga.setGridVisible(config.isChartSVGridLines()); polarPlane->setGridAttributes(false, ga); break; } } switch (config.chartType()) { case MyMoneyReport::eChartNone: case MyMoneyReport::eChartEnd: case MyMoneyReport::eChartLine: { KChart::LineDiagram* diagram = new KChart::LineDiagram; if (config.isSkippingZero()) { LineAttributes attributes = diagram->lineAttributes(); attributes.setMissingValuesPolicy(LineAttributes::MissingValuesAreBridged); diagram->setLineAttributes(attributes); } coordinatePlane()->replaceDiagram(diagram); diagram->addAxis(xAxis); diagram->addAxis(yAxis); break; } case MyMoneyReport::eChartBar: { KChart::BarDiagram* diagram = new KChart::BarDiagram; coordinatePlane()->replaceDiagram(diagram); diagram->addAxis(xAxis); diagram->addAxis(yAxis); break; } case MyMoneyReport::eChartStackedBar: { KChart::BarDiagram* diagram = new KChart::BarDiagram; diagram->setType(BarDiagram::Stacked); coordinatePlane()->replaceDiagram(diagram); diagram->addAxis(xAxis); diagram->addAxis(yAxis); break; } case MyMoneyReport::eChartPie: { KChart::PieDiagram* diagram = new KChart::PieDiagram; coordinatePlane()->replaceDiagram(diagram); setAccountSeries(false); setSeriesTotals(true); break; } case MyMoneyReport::eChartRing: { KChart::RingDiagram* diagram = new KChart::RingDiagram; coordinatePlane()->replaceDiagram(diagram); //chartView.params()->setRelativeRingThickness( true ); setAccountSeries(false); break; } } //get the diagram for later use AbstractDiagram* planeDiagram = coordinatePlane()->diagram(); //the palette - we set it here because it is a property of the diagram switch (KMyMoneySettings::chartsPalette()) { case 0: planeDiagram->useDefaultColors(); break; case 1: planeDiagram->useRainbowColors(); break; case 2: default: planeDiagram->useSubduedColors(); break; } //the legend will be used later Legend* legend = new Legend(planeDiagram, this); legend->setTitleText(i18nc("Chart legend title", "Legend")); const bool blocked = m_model.blockSignals(true); // don't emit dataChanged() signal during each drawPivotRowSet switch (config.detailLevel()) { case MyMoneyReport::eDetailNone: case MyMoneyReport::eDetailEnd: case MyMoneyReport::eDetailAll: { int rowNum = 0; // iterate over outer groups PivotGrid::const_iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { // iterate over inner groups PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { // // Rows // PivotInnerGroup::const_iterator it_row = (*it_innergroup).begin(); while (it_row != (*it_innergroup).end()) { //Do not include investments accounts in the chart because they are merely container of stock and other accounts if (it_row.key().accountType() != MyMoneyAccount::Investment) { //iterate row types for (int i = 0; i < rowTypeList.size(); ++i) { //skip the budget difference rowset if (rowTypeList[i] != eBudgetDiff) { QString legendText; //only show the column type in the header if there is more than one type if (rowTypeList.size() > 1) { legendText = QString(columnTypeHeaderList[i] + " - " + it_row.key().name()); } else { legendText = QString(it_row.key().name()); } //set the legend text legend->setText(rowNum, legendText); //clear to speed up drawPivotRowSet if (!config.isChartDataLabels()) legendText.clear(); //set the cell value and tooltip rowNum = drawPivotRowSet(rowNum, it_row.value(), rowTypeList[i], legendText, 1, numColumns() - 1); } } } ++it_row; } ++it_innergroup; } ++it_outergroup; } } break; case MyMoneyReport::eDetailTop: { int rowNum = 0; // iterate over outer groups PivotGrid::const_iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { // iterate over inner groups PivotOuterGroup::const_iterator it_innergroup = (*it_outergroup).begin(); while (it_innergroup != (*it_outergroup).end()) { //iterate row types for (int i = 0; i < rowTypeList.size(); ++i) { //skip the budget difference rowset if (rowTypeList[i] != eBudgetDiff) { QString legendText; //only show the column type in the header if there is more than one type if (rowTypeList.size() > 1) { legendText = QString(columnTypeHeaderList[i] + " - " + it_innergroup.key()); } else { legendText = QString(it_innergroup.key()); } //set the legend text legend->setText(rowNum, legendText); //clear to speed up drawPivotRowSet if (!config.isChartDataLabels()) legendText.clear(); //set the cell value and tooltip rowNum = drawPivotRowSet(rowNum, (*it_innergroup).m_total, rowTypeList[i], legendText, 1, numColumns() - 1); } } ++it_innergroup; } ++it_outergroup; } } break; case MyMoneyReport::eDetailGroup: { int rowNum = 0; // iterate over outer groups PivotGrid::const_iterator it_outergroup = grid.begin(); while (it_outergroup != grid.end()) { //iterate row types for (int i = 0; i < rowTypeList.size(); ++i) { //skip the budget difference rowset if (rowTypeList[i] != eBudgetDiff) { QString legendText; //only show the column type in the header if there is more than one type if (rowTypeList.size() > 1) { legendText = QString(columnTypeHeaderList[i] + " - " + it_outergroup.key()); } else { legendText = QString(it_outergroup.key()); } //set the legend text legend->setText(rowNum, legendText); //clear to speed up drawPivotRowSet if (!config.isChartDataLabels()) legendText.clear(); //set the cell value and tooltip rowNum = drawPivotRowSet(rowNum, (*it_outergroup).m_total, rowTypeList[i], legendText, 1, numColumns() - 1); } } ++it_outergroup; } //if selected, show totals too if (config.isShowingRowTotals()) { //iterate row types for (int i = 0; i < rowTypeList.size(); ++i) { //skip the budget difference rowset if (rowTypeList[i] != eBudgetDiff) { QString legendText; //only show the column type in the header if there is more than one type if (rowTypeList.size() > 1) { legendText = QString(columnTypeHeaderList[i] + " - " + i18nc("Total balance", "Total")); legendText = QString(i18nc("Total balance", "Total")); } else { } //set the legend text legend->setText(rowNum, legendText); //clear to speed up drawPivotRowSet if (!config.isChartDataLabels()) legendText.clear(); //set the cell value rowNum = drawPivotRowSet(rowNum, grid.m_total, rowTypeList[i], legendText, 1, numColumns() - 1); } } } } break; case MyMoneyReport::eDetailTotal: { int rowNum = 0; //iterate row types for (int i = 0; i < rowTypeList.size(); ++i) { //skip the budget difference rowset if (rowTypeList[i] != eBudgetDiff) { QString legendText; //only show the column type in the header if there is more than one type if (rowTypeList.size() > 1) { legendText = QString(columnTypeHeaderList[i] + " - " + i18nc("Total balance", "Total")); } else { legendText = QString(i18nc("Total balance", "Total")); } //set the legend legend->setText(rowNum, legendText); //clear to speed up drawPivotRowSet if (!config.isChartDataLabels()) legendText.clear(); if (config.isMixedTime() && (rowTypeList[i] == eActual || rowTypeList[i] == eForecast)) { if (rowTypeList[i] == eActual) { rowNum = drawPivotRowSet(rowNum, grid.m_total, rowTypeList[i], legendText, 1, config.currentDateColumn()); } else if (rowTypeList[i] == eForecast) { rowNum = drawPivotRowSet(rowNum, grid.m_total, rowTypeList[i], legendText, config.currentDateColumn(), numColumns() - 1); } else { rowNum = drawPivotRowSet(rowNum, grid.m_total, rowTypeList[i], legendText, 1, numColumns() - 1); } } else { //set cell value rowNum = drawPivotRowSet(rowNum, grid.m_total, rowTypeList[i], legendText, 1, numColumns() - 1); } } } } break; } m_model.blockSignals(blocked); // reenable dataChanged() signal if (config.chartType() == MyMoneyReport::eChartLine || config.chartType() == MyMoneyReport::eChartBar || config.chartType() == MyMoneyReport::eChartStackedBar) { // Set up X axis labels (ie "abscissa" to use the technical term) QStringList abscissaNames; if (accountSeries()) { // if not, we will set these up while putting in the chart values. int column = 1; while (column < numColumns()) { abscissaNames += QString(columnHeadings[column++]).replace(" ", " "); } qDebug() << "rows: " << m_model.rowCount() << "columns: " << m_model.columnCount(); m_model.setVerticalHeaderLabels(abscissaNames); } } //assign model to the diagram planeDiagram->setModel(&m_model); //set the legend basic attributes //this is done after adding the legend because the values are overridden when adding the legend to the chart for (uint i = static_cast(KMyMoneyGlobalSettings::maximumLegendItems()); i < legend->datasetCount(); ++i) { legend->setDatasetHidden(i, true); } legend->setTitleText(i18nc("Chart lines legend", "Legend")); legend->setUseAutomaticMarkerSize(false); FrameAttributes legendFrameAttr(legend->frameAttributes()); legendFrameAttr.setPen(m_foregroundBrush.color()); // leave some space between the content and the frame legendFrameAttr.setPadding(2); legend->setFrameAttributes(legendFrameAttr); legend->setPosition(Position::East); legend->setTextAlignment(Qt::AlignLeft); legend->setLegendStyle(KChart::Legend::MarkersAndLines); replaceLegend(legend); // set the text attributes after calling replaceLegend() otherwise fon sizes will get overwritten qreal generalFontSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSizeF(); if (generalFontSize == -1) generalFontSize = 8; // this is a fallback if the fontsize was specified in pixels TextAttributes legendTextAttr(legend->textAttributes()); legendTextAttr.setPen(m_foregroundBrush.color()); legendTextAttr.setFontSize(KChart::Measure(generalFontSize, KChartEnums::MeasureCalculationModeAbsolute)); legend->setTextAttributes(legendTextAttr); TextAttributes legendTitleTextAttr(legend->titleTextAttributes()); legendTitleTextAttr.setPen(m_foregroundBrush.color()); legendTitleTextAttr.setFontSize(KChart::Measure(generalFontSize + 4, KChartEnums::MeasureCalculationModeAbsolute)); legend->setTitleTextAttributes(legendTitleTextAttr); //this sets the line width only for line diagrams setLineWidth(config.chartLineWidth()); //set data value attributes //make sure to show only the required number of fractional digits on the labels of the graph DataValueAttributes dataValueAttr(planeDiagram->dataValueAttributes()); MarkerAttributes markerAttr(dataValueAttr.markerAttributes()); markerAttr.setVisible(true); markerAttr.setMarkerStyle(MarkerAttributes::MarkerCircle); markerAttr.setMarkerSize(QSize(8, 8)); dataValueAttr.setMarkerAttributes(markerAttr); TextAttributes dataValueTextAttr(dataValueAttr.textAttributes()); dataValueTextAttr.setPen(m_foregroundBrush.color()); dataValueAttr.setTextAttributes(dataValueTextAttr); dataValueAttr.setVisible(config.isChartDataLabels()); dataValueAttr.setDecimalDigits(KMyMoneyGlobalSettings::pricePrecision()); planeDiagram->setDataValueAttributes(dataValueAttr); planeDiagram->setAllowOverlappingDataValueTexts(true); if (qMin(static_cast(KMyMoneyGlobalSettings::maximumLegendItems()), legend->datasetCount()) < 2) { // the legend is needed only if at least two data sets are rendered removeLegend(); } // connect needLayoutPlanes, so dimension of chart can be known, so custom Y labels can be generated connect(coordinatePlane(), SIGNAL(needLayoutPlanes()), this, SLOT(slotNeedUpdate())); planeDiagram->update(); } void KReportChartView::slotNeedUpdate() { disconnect(coordinatePlane(), SIGNAL(needLayoutPlanes()), this, SLOT(slotNeedUpdate())); // this won't cause hang-up in KReportsView::slotConfigure QList grids = coordinatePlane()->gridDimensionsList(); if (grids.isEmpty()) // ring and pie charts have no dimensions return; if (grids.at(1).stepWidth == 0) // no labels? return; QChar separator = locale().groupSeparator(); QChar decimalPoint = locale().decimalPoint(); int precision = KMyMoneyGlobalSettings::pricePrecision(); QStringList labels; CartesianCoordinatePlane* cartesianplane = qobject_cast(coordinatePlane()); if (cartesianplane) { if (cartesianplane->axesCalcModeY() == KChart::AbstractCoordinatePlane::Logarithmic) { qreal labelValue = qFloor(log10(grids.at(1).start)); // first label is 10 to power of labelValue uchar labelCount = qFloor(log10(grids.at(1).end)) - qFloor(log10(grids.at(1).start)) + 1; for (uchar i = 0; i < labelCount; ++i) { labels.append(locale().toString(qPow(10.0, labelValue), 'f', precision).remove(separator).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + decimalPoint + "$"))); ++labelValue; // next label is 10 to power of previous exponent + 1 } } else { qreal labelValue = grids.at(1).start; // first label is start value qreal step = grids.at(1).stepWidth; uchar labelCount = qFloor((grids.at(1).end - grids.at(1).start) / grids.at(1).stepWidth) + 1; for (uchar i = 0; i < labelCount; ++i) { labels.append(locale().toString(labelValue, 'f', precision).remove(separator).remove(QRegularExpression("0+$")).remove(QRegularExpression("\\" + decimalPoint + "$"))); labelValue += step; // next label is previous value + step value } } } else return; // nothing but cartesian plane is handled KChart::LineDiagram* lineDiagram = qobject_cast(coordinatePlane()->diagram()); if (lineDiagram) lineDiagram->axes().at(1)->setLabels(labels); KChart::BarDiagram* barDiagram = qobject_cast(coordinatePlane()->diagram()); if (barDiagram) barDiagram->axes().at(1)->setLabels(labels); } unsigned KReportChartView::drawPivotRowSet(int rowNum, const PivotGridRowSet& rowSet, const ERowType rowType, const QString& legendText, int startColumn, int endColumn) { if (coordinatePlane()->diagram()->datasetDimension() != 1) return ++rowNum; //if endColumn is invalid, make it the same as numColumns if (endColumn == 0) endColumn = numColumns(); double value; QString toolTip; if (( accountSeries() && !seriesTotals()) || (!accountSeries() && seriesTotals())) { justifyModelSize(endColumn > numColumns() - 1 ? endColumn + 1 : numColumns(), rowNum + 1); } else { justifyModelSize(rowNum + 1, endColumn > numColumns() - 1 ? endColumn + 1 : numColumns()); } // Columns if (seriesTotals()) { value = rowSet[rowType].m_total.toDouble(); //set the tooltip if (!legendText.isEmpty()) toolTip = QString("

%1

%2
") .arg(legendText) .arg(value, 0, 'g', KMyMoneyGlobalSettings::pricePrecision()); //set the cell value if (accountSeries()) this->setDataCell(rowNum, 0, value, toolTip); else this->setDataCell(0, rowNum, value, toolTip); } else { int column = startColumn; while (column <= endColumn && column < numColumns()) { //if zero and set to skip, increase column and continue with next value if (m_skipZero && rowSet[rowType][column].isZero()) { ++column; continue; } else { value = rowSet[rowType][column].toDouble(); if (!legendText.isEmpty()) toolTip = QString("

%1

%2
") .arg(legendText) .arg(value, 0, 'g', KMyMoneyGlobalSettings::pricePrecision()); if (accountSeries()) this->setDataCell(column, rowNum, value, toolTip); else this->setDataCell(rowNum, column, value, toolTip); } ++column; } } return ++rowNum; } void KReportChartView::setDataCell(int row, int column, const double value, QString tip) { QMap data; data.insert(Qt::DisplayRole, QVariant(value)); if (!tip.isEmpty()) data.insert(Qt::ToolTipRole, QVariant(tip)); const QModelIndex index = m_model.index(row, column); m_model.setItemData(index, data); } /** * Justifies the model, so that the given rows and columns fit into it. */ void KReportChartView::justifyModelSize(int rows, int columns) { const int currentRows = m_model.rowCount(); const int currentCols = m_model.columnCount(); if (currentCols < columns) if (! m_model.insertColumns(currentCols, columns - currentCols)) qDebug() << "justifyModelSize: could not increase model size."; if (currentRows < rows) if (! m_model.insertRows(currentRows, rows - currentRows)) qDebug() << "justifyModelSize: could not increase model size."; Q_ASSERT(m_model.rowCount() >= rows); Q_ASSERT(m_model.columnCount() >= columns); } void KReportChartView::setLineWidth(const int lineWidth) { if (qobject_cast(coordinatePlane()->diagram())) { LineDiagram* lineDiagram = qobject_cast(coordinatePlane()->diagram()); QList pens; pens = lineDiagram->datasetPens(); for (int i = 0; i < pens.count(); ++i) { pens[i].setWidth(lineWidth); lineDiagram->setPen(i, pens.at(i)); } } } void KReportChartView::drawLimitLine(const double limit) { if (coordinatePlane()->diagram()->datasetDimension() != 1) return; // temporarily disconnect the view from the model to aovid update of view on // emission of the dataChanged() signal for each call of setDataCell(). // This speeds up the runtime of drawLimitLine() by a factor of // approx. 60 on my box (1831ms vs. 31ms). AbstractDiagram* planeDiagram = coordinatePlane()->diagram(); planeDiagram->setModel(0); //we get the current number of rows and we add one after that int row = m_model.rowCount(); justifyModelSize(m_numColumns, row + 1); for (int col = 0; col < m_numColumns; ++col) { setDataCell(col, row, limit); } planeDiagram->setModel(&m_model); //TODO: add format to the line } void KReportChartView::removeLegend() { Legend* chartLegend = Chart::legend(); delete chartLegend; } diff --git a/kmymoney/views/kpayeesview.cpp b/kmymoney/views/kpayeesview.cpp index fbe6fd04f..96a4fa072 100644 --- a/kmymoney/views/kpayeesview.cpp +++ b/kmymoney/views/kpayeesview.cpp @@ -1,970 +1,970 @@ /*************************************************************************** kpayeesview.cpp --------------- begin : Thu Jan 24 2002 copyright : (C) 2000-2002 by Michael Edwardes Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio Andreas Nicolai ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kpayeesview.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes #include #include #include #include #include // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "kmymoneyglobalsettings.h" #include "kmymoney.h" #include "models.h" #include "mymoneysecurity.h" // *** KPayeeListItem Implementation *** KPayeeListItem::KPayeeListItem(QListWidget *parent, const MyMoneyPayee& payee) : QListWidgetItem(parent, QListWidgetItem::UserType), m_payee(payee) { setText(payee.name()); // allow in column rename setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } KPayeeListItem::~KPayeeListItem() { } // *** KPayeesView Implementation *** KPayeesView::KPayeesView(QWidget *parent) : QWidget(parent), + m_contact(new MyMoneyContact(this)), m_needReload(false), m_inSelection(false), m_allowEditing(true), - m_payeeFilterType(0), - m_contact(new MyMoneyContact(this)) + m_payeeFilterType(0) { setupUi(this); m_filterProxyModel = new AccountNamesFilterProxyModel(this); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Asset); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Liability); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Income); m_filterProxyModel->addAccountGroup(MyMoneyAccount::Expense); m_filterProxyModel->setSourceModel(Models::instance()->accountsModel()); m_filterProxyModel->sort(0); comboDefaultCategory->setModel(m_filterProxyModel); matchTypeCombo->addItem(i18nc("@item No matching", "No matching"), MyMoneyPayee::matchDisabled); matchTypeCombo->addItem(i18nc("@item Match Payees name partially", "Match Payees name (partial)"), MyMoneyPayee::matchName); matchTypeCombo->addItem(i18nc("@item Match Payees name exactly", "Match Payees name (exact)"), MyMoneyPayee::matchNameExact); matchTypeCombo->addItem(i18nc("@item Search match in list", "Match on a name listed below"), MyMoneyPayee::matchKey); // create the searchline widget // and insert it into the existing layout m_searchWidget = new KListWidgetSearchLine(this, m_payeesList); m_searchWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); m_payeesList->setContextMenuPolicy(Qt::CustomContextMenu); m_listTopHLayout->insertWidget(0, m_searchWidget); //load the filter type m_filterBox->addItem(i18nc("@item Show all payees", "All")); m_filterBox->addItem(i18nc("@item Show only used payees", "Used")); m_filterBox->addItem(i18nc("@item Show only unused payees", "Unused")); m_filterBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); KGuiItem newButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("Creates a new payee"), i18n("Use this to create a new payee.")); KGuiItem::assign(m_newButton, newButtonItem); m_newButton->setToolTip(newButtonItem.toolTip()); KGuiItem renameButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("user-properties"), QIcon::fromTheme(QStringLiteral("text-editor"))), i18n("Rename the current selected payee"), i18n("Use this to start renaming the selected payee.")); KGuiItem::assign(m_renameButton, renameButtonItem); m_renameButton->setToolTip(renameButtonItem.toolTip()); KGuiItem deleteButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("list-remove-user")), i18n("Delete selected payee(s)"), i18n("Use this to delete the selected payee. You can also select " "multiple payees to be deleted.")); KGuiItem::assign(m_deleteButton, deleteButtonItem); m_deleteButton->setToolTip(deleteButtonItem.toolTip()); KGuiItem mergeButtonItem(QString(""), QIcon::fromTheme(QStringLiteral("merge")), i18n("Merge multiple selected payees"), i18n("Use this to merge multiple selected payees.")); KGuiItem::assign(m_mergeButton, mergeButtonItem); m_mergeButton->setToolTip(mergeButtonItem.toolTip()); KGuiItem updateButtonItem(i18nc("Update payee", "Update"), QIcon::fromTheme(QStringLiteral("dialog-ok"), QIcon::fromTheme(QStringLiteral("finish"))), i18n("Accepts the entered data and stores it"), i18n("Use this to accept the modified data.")); KGuiItem::assign(m_updateButton, updateButtonItem); KGuiItem syncButtonItem(i18nc("Sync payee", "Sync"), QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Fetches the payee's data from your addressbook."), i18n("Use this to fetch payee's data.")); KGuiItem::assign(m_syncAddressbook, syncButtonItem); KGuiItem sendMailButtonItem(i18nc("Send mail", "Send"), QIcon::fromTheme(QStringLiteral("mail-message"), QIcon::fromTheme(QStringLiteral("internet-mail"))), i18n("Creates new e-mail to your payee."), i18n("Use this to create new e-mail to your payee.")); KGuiItem::assign(m_sendMail, sendMailButtonItem); m_updateButton->setEnabled(false); m_syncAddressbook->setEnabled(false); #ifndef KMM_ADDRESSBOOK_FOUND m_syncAddressbook->hide(); #endif matchTypeCombo->setCurrentIndex(0); checkMatchIgnoreCase->setEnabled(false); checkEnableDefaultCategory->setChecked(false); labelDefaultCategory->setEnabled(false); comboDefaultCategory->setEnabled(false); QList cols; cols << KMyMoneyRegister::DateColumn; cols << KMyMoneyRegister::AccountColumn; cols << KMyMoneyRegister::DetailColumn; cols << KMyMoneyRegister::ReconcileFlagColumn; cols << KMyMoneyRegister::PaymentColumn; cols << KMyMoneyRegister::DepositColumn; m_register->setupRegister(MyMoneyAccount(), cols); m_register->setSelectionMode(QTableWidget::SingleSelection); m_register->setDetailsColumnType(KMyMoneyRegister::AccountFirst); m_balanceLabel->hide(); connect(m_contact, SIGNAL(contactFetched(ContactData)), this, SLOT(slotContactFetched(ContactData))); connect(m_payeesList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), this, SLOT(slotSelectPayee(QListWidgetItem*,QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemSelectionChanged()), this, SLOT(slotSelectPayee())); connect(m_payeesList, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(slotStartRename(QListWidgetItem*))); connect(m_payeesList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(slotRenamePayee(QListWidgetItem*))); connect(m_payeesList, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotOpenContextMenu(QPoint))); connect(m_renameButton, SIGNAL(clicked()), this, SLOT(slotRenameButtonCliked())); connect(m_deleteButton, SIGNAL(clicked()), kmymoney->action("payee_delete"), SLOT(trigger())); connect(m_mergeButton, SIGNAL(clicked()), kmymoney->action("payee_merge"), SLOT(trigger())); connect(m_newButton, SIGNAL(clicked()), this, SLOT(slotPayeeNew())); connect(addressEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(postcodeEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(telephoneEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(emailEdit, SIGNAL(textChanged(QString)), this, SLOT(slotPayeeDataChanged())); connect(notesEdit, SIGNAL(textChanged()), this, SLOT(slotPayeeDataChanged())); connect(matchKeyEditList, SIGNAL(changed()), this, SLOT(slotKeyListChanged())); connect(matchTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPayeeDataChanged())); connect(checkMatchIgnoreCase, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(checkEnableDefaultCategory, SIGNAL(toggled(bool)), this, SLOT(slotPayeeDataChanged())); connect(comboDefaultCategory, SIGNAL(accountSelected(QString)), this, SLOT(slotPayeeDataChanged())); connect(buttonSuggestACategory, SIGNAL(clicked()), this, SLOT(slotChooseDefaultAccount())); connect(m_updateButton, SIGNAL(clicked()), this, SLOT(slotUpdatePayee())); connect(m_syncAddressbook, SIGNAL(clicked()), this, SLOT(slotSyncAddressBook())); connect(m_helpButton, SIGNAL(clicked()), this, SLOT(slotHelp())); connect(m_sendMail, SIGNAL(clicked()), this, SLOT(slotSendMail())); connect(m_register, SIGNAL(editTransaction()), this, SLOT(slotSelectTransaction())); connect(MyMoneyFile::instance(), SIGNAL(dataChanged()), this, SLOT(slotLoadPayees())); connect(m_filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeFilter(int))); connect(payeeIdentifiers, SIGNAL(dataChanged()), this, SLOT(slotPayeeDataChanged())); // use the size settings of the last run (if any) KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); m_splitter->restoreState(grp.readEntry("KPayeesViewSplitterSize", QByteArray())); m_splitter->setChildrenCollapsible(false); //At start we haven't any payee selected m_tabWidget->setEnabled(false); // disable tab widget m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); m_payee = MyMoneyPayee(); // make sure we don't access an undefined payee clearItemData(); } KPayeesView::~KPayeesView() { // remember the splitter settings for startup KConfigGroup grp = KSharedConfig::openConfig()->group("Last Use Settings"); grp.writeEntry("KPayeesViewSplitterSize", m_splitter->saveState()); grp.sync(); } void KPayeesView::slotChooseDefaultAccount() { MyMoneyFile* file = MyMoneyFile::instance(); QMap account_count; KMyMoneyRegister::RegisterItem* item = m_register->firstItem(); while (item) { //only walk through selectable items. eg. transactions and not group markers if (item->isSelectable()) { KMyMoneyRegister::Transaction* t = dynamic_cast(item); MyMoneySplit s = t->transaction().splitByPayee(m_payee.id()); const MyMoneyAccount& acc = file->account(s.accountId()); QString txt; if (s.action() != MyMoneySplit::ActionAmortization && acc.accountType() != MyMoneyAccount::AssetLoan && !file->isTransfer(t->transaction()) && t->transaction().splitCount() == 2) { MyMoneySplit s0 = t->transaction().splitByAccount(s.accountId(), false); if (account_count.contains(s0.accountId())) { account_count[s0.accountId()]++; } else { account_count[s0.accountId()] = 1; } } } item = item->nextItem(); } QMap::Iterator most_frequent, iter; most_frequent = account_count.begin(); for (iter = account_count.begin(); iter != account_count.end(); ++iter) { if (iter.value() > most_frequent.value()) { most_frequent = iter; } } if (most_frequent != account_count.end()) { checkEnableDefaultCategory->setChecked(true); comboDefaultCategory->setSelected(most_frequent.key()); setDirty(); } } void KPayeesView::slotStartRename(QListWidgetItem* item) { m_allowEditing = true; m_payeesList->editItem(item); } void KPayeesView::slotRenameButtonCliked() { if (m_payeesList->currentItem() && m_payeesList->selectedItems().count() == 1) { slotStartRename(m_payeesList->currentItem()); } } // This variant is only called when a single payee is selected and renamed. void KPayeesView::slotRenamePayee(QListWidgetItem* p) { //if there is no current item selected, exit if (m_allowEditing == false || !m_payeesList->currentItem() || p != m_payeesList->currentItem()) return; //qDebug() << "[KPayeesView::slotRenamePayee]"; // create a copy of the new name without appended whitespaces QString new_name = p->text(); if (m_payee.name() != new_name) { MyMoneyFileTransaction ft; try { // check if we already have a payee with the new name try { // this function call will throw an exception, if the payee // hasn't been found. MyMoneyFile::instance()->payeeByName(new_name); // the name already exists, ask the user whether he's sure to keep the name if (KMessageBox::questionYesNo(this, i18n("A payee with the name '%1' already exists. It is not advisable to have " "multiple payees with the same identification name. Are you sure you would like " "to rename the payee?", new_name)) != KMessageBox::Yes) { p->setText(m_payee.name()); return; } } catch (const MyMoneyException &) { // all ok, the name is unique } m_payee.setName(new_name); m_newName = new_name; MyMoneyFile::instance()->modifyPayee(m_payee); // the above call to modifyPayee will reload the view so // all references and pointers to the view have to be // re-established. // make sure, that the record is visible even if it moved // out of sight due to the rename operation ensurePayeeVisible(m_payee.id()); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } else { p->setText(new_name); } } void KPayeesView::ensurePayeeVisible(const QString& id) { for (int i = 0; i < m_payeesList->count(); ++i) { KPayeeListItem* p = dynamic_cast(m_payeesList->item(0)); if (p && p->payee().id() == id) { m_payeesList->scrollToItem(p, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(p); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it break; } } } void KPayeesView::selectedPayees(QList& payeesList) const { QList selectedItems = m_payeesList->selectedItems(); QList::ConstIterator itemsIt = selectedItems.constBegin(); while (itemsIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*itemsIt); if (item) payeesList << item->payee(); ++itemsIt; } } void KPayeesView::slotSelectPayee(QListWidgetItem* cur, QListWidgetItem* prev) { Q_UNUSED(cur); Q_UNUSED(prev); m_allowEditing = false; } void KPayeesView::slotSelectPayee() { // check if the content of a currently selected payee was modified // and ask to store the data if (isDirty()) { QString question = QString("%1").arg(i18n("Do you want to save the changes for %1?", m_newName)); if (KMessageBox::questionYesNo(this, question, i18n("Save changes")) == KMessageBox::Yes) { m_inSelection = true; slotUpdatePayee(); m_inSelection = false; } } // make sure we always clear the selected list when listing again m_selectedPayeesList.clear(); // loop over all payees and count the number of payees, also // obtain last selected payee selectedPayees(m_selectedPayeesList); emit selectObjects(m_selectedPayeesList); if (m_selectedPayeesList.isEmpty()) { m_tabWidget->setEnabled(false); // disable tab widget m_balanceLabel->hide(); m_deleteButton->setEnabled(false); //disable delete, rename and merge buttons m_renameButton->setEnabled(false); m_mergeButton->setEnabled(false); clearItemData(); m_payee = MyMoneyPayee(); m_syncAddressbook->setEnabled(false); return; // make sure we don't access an undefined payee } m_deleteButton->setEnabled(true); //re-enable delete button m_syncAddressbook->setEnabled(true); // if we have multiple payees selected, clear and disable the payee information if (m_selectedPayeesList.count() > 1) { m_tabWidget->setEnabled(false); // disable tab widget m_renameButton->setEnabled(false); // disable also the rename button m_mergeButton->setEnabled(true); m_balanceLabel->hide(); clearItemData(); } else { m_mergeButton->setEnabled(false); m_renameButton->setEnabled(true); } // otherwise we have just one selected, enable payee information widget m_tabWidget->setEnabled(true); m_balanceLabel->show(); // as of now we are updating only the last selected payee, and until // selection mode of the QListView has been changed to Extended, this // will also be the only selection and behave exactly as before - Andreas try { m_payee = m_selectedPayeesList[0]; m_newName = m_payee.name(); addressEdit->setEnabled(true); addressEdit->setText(m_payee.address()); postcodeEdit->setEnabled(true); postcodeEdit->setText(m_payee.postcode()); telephoneEdit->setEnabled(true); telephoneEdit->setText(m_payee.telephone()); emailEdit->setEnabled(true); emailEdit->setText(m_payee.email()); notesEdit->setText(m_payee.notes()); QStringList keys; bool ignorecase = false; MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); matchTypeCombo->setCurrentIndex(matchTypeCombo->findData(type)); matchKeyEditList->clear(); matchKeyEditList->insertStringList(keys); checkMatchIgnoreCase->setChecked(ignorecase); checkEnableDefaultCategory->setChecked(m_payee.defaultAccountEnabled()); comboDefaultCategory->setSelected(m_payee.defaultAccountId()); payeeIdentifiers->setSource(m_payee); slotPayeeDataChanged(); showTransactions(); } catch (const MyMoneyException &e) { qDebug("exception during display of payee: %s at %s:%ld", qPrintable(e.what()), qPrintable(e.file()), e.line()); m_register->clear(); m_selectedPayeesList.clear(); m_payee = MyMoneyPayee(); } m_allowEditing = true; } void KPayeesView::clearItemData() { addressEdit->setText(QString()); postcodeEdit->setText(QString()); telephoneEdit->setText(QString()); emailEdit->setText(QString()); notesEdit->setText(QString()); showTransactions(); } void KPayeesView::showTransactions() { MyMoneyMoney balance; MyMoneyFile *file = MyMoneyFile::instance(); MyMoneySecurity base = file->baseCurrency(); // setup sort order m_register->setSortOrder(KMyMoneyGlobalSettings::sortSearchView()); // clear the register m_register->clear(); if (m_selectedPayeesList.isEmpty() || !m_tabWidget->isEnabled()) { m_balanceLabel->setText(i18n("Balance: %1", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); return; } // setup the list and the pointer vector MyMoneyTransactionFilter filter; for (QList::const_iterator it = m_selectedPayeesList.constBegin(); it != m_selectedPayeesList.constEnd(); ++it) filter.addPayee((*it).id()); filter.setDateFilter(KMyMoneyGlobalSettings::startDate().date(), QDate()); // retrieve the list from the engine file->transactionList(m_transactionList, filter); // create the elements for the register QList >::const_iterator it; QMap uniqueMap; MyMoneyMoney deposit, payment; int splitCount = 0; bool balanceAccurate = true; for (it = m_transactionList.constBegin(); it != m_transactionList.constEnd(); ++it) { const MyMoneySplit& split = (*it).second; MyMoneyAccount acc = file->account(split.accountId()); ++splitCount; uniqueMap[(*it).first.id()]++; KMyMoneyRegister::Register::transactionFactory(m_register, (*it).first, (*it).second, uniqueMap[(*it).first.id()]); // take care of foreign currencies MyMoneyMoney val = split.shares().abs(); if (acc.currencyId() != base.id()) { const MyMoneyPrice &price = file->price(acc.currencyId(), base.id()); // in case the price is valid, we use it. Otherwise, we keep // a flag that tells us that the balance is somewhat inaccurate if (price.isValid()) { val *= price.rate(base.id()); } else { balanceAccurate = false; } } if (split.shares().isNegative()) { payment += val; } else { deposit += val; } } balance = deposit - payment; // add the group markers m_register->addGroupMarkers(); // sort the transactions according to the sort setting m_register->sortItems(); // remove trailing and adjacent markers m_register->removeUnwantedGroupMarkers(); m_register->updateRegister(true); // we might end up here with updates disabled on the register so // make sure that we enable updates here m_register->setUpdatesEnabled(true); m_balanceLabel->setText(i18n("Balance: %1%2", balanceAccurate ? "" : "~", balance.formatMoney(file->baseCurrency().smallestAccountFraction()))); } void KPayeesView::slotKeyListChanged() { bool rc = false; bool ignorecase = false; QStringList keys; m_payee.matchData(ignorecase, keys); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { rc |= (keys != matchKeyEditList->items()); } setDirty(rc); } void KPayeesView::slotPayeeDataChanged() { bool rc = false; if (m_tabWidget->isEnabled()) { rc |= ((m_payee.email().isEmpty() != emailEdit->text().isEmpty()) || (!emailEdit->text().isEmpty() && m_payee.email() != emailEdit->text())); rc |= ((m_payee.address().isEmpty() != addressEdit->toPlainText().isEmpty()) || (!addressEdit->toPlainText().isEmpty() && m_payee.address() != addressEdit->toPlainText())); rc |= ((m_payee.postcode().isEmpty() != postcodeEdit->text().isEmpty()) || (!postcodeEdit->text().isEmpty() && m_payee.postcode() != postcodeEdit->text())); rc |= ((m_payee.telephone().isEmpty() != telephoneEdit->text().isEmpty()) || (!telephoneEdit->text().isEmpty() && m_payee.telephone() != telephoneEdit->text())); rc |= ((m_payee.name().isEmpty() != m_newName.isEmpty()) || (!m_newName.isEmpty() && m_payee.name() != m_newName)); rc |= ((m_payee.notes().isEmpty() != notesEdit->toPlainText().isEmpty()) || (!notesEdit->toPlainText().isEmpty() && m_payee.notes() != notesEdit->toPlainText())); bool ignorecase = false; QStringList keys; MyMoneyPayee::payeeMatchType type = m_payee.matchData(ignorecase, keys); rc |= (static_cast(type) != matchTypeCombo->currentData().toUInt()); checkMatchIgnoreCase->setEnabled(false); matchKeyEditList->setEnabled(false); if (matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) { checkMatchIgnoreCase->setEnabled(true); // if we turn matching on, we default to 'ignore case' // TODO maybe make the default a user option if (type == MyMoneyPayee::matchDisabled && matchTypeCombo->currentData().toUInt() != MyMoneyPayee::matchDisabled) checkMatchIgnoreCase->setChecked(true); rc |= (ignorecase != checkMatchIgnoreCase->isChecked()); if (matchTypeCombo->currentData().toUInt() == MyMoneyPayee::matchKey) { matchKeyEditList->setEnabled(true); rc |= (keys != matchKeyEditList->items()); } } rc |= (checkEnableDefaultCategory->isChecked() != m_payee.defaultAccountEnabled()); if (checkEnableDefaultCategory->isChecked()) { comboDefaultCategory->setEnabled(true); labelDefaultCategory->setEnabled(true); // this is only going to understand the first in the list of selected accounts if (comboDefaultCategory->getSelected().isEmpty()) { rc |= !m_payee.defaultAccountId().isEmpty(); } else { QString temp = comboDefaultCategory->getSelected(); rc |= (temp.isEmpty() != m_payee.defaultAccountId().isEmpty()) || (!m_payee.defaultAccountId().isEmpty() && temp != m_payee.defaultAccountId()); } } else { comboDefaultCategory->setEnabled(false); labelDefaultCategory->setEnabled(false); } rc |= (m_payee.payeeIdentifiers() != payeeIdentifiers->identifiers()); } setDirty(rc); } void KPayeesView::slotUpdatePayee() { if (isDirty()) { MyMoneyFileTransaction ft; setDirty(false); try { m_payee.setName(m_newName); m_payee.setAddress(addressEdit->toPlainText()); m_payee.setPostcode(postcodeEdit->text()); m_payee.setTelephone(telephoneEdit->text()); m_payee.setEmail(emailEdit->text()); m_payee.setNotes(notesEdit->toPlainText()); m_payee.setMatchData(static_cast(matchTypeCombo->currentData().toUInt()), checkMatchIgnoreCase->isChecked(), matchKeyEditList->items()); m_payee.setDefaultAccountId(); m_payee.resetPayeeIdentifiers(payeeIdentifiers->identifiers()); if (checkEnableDefaultCategory->isChecked()) { QString temp; if (!comboDefaultCategory->getSelected().isEmpty()) { temp = comboDefaultCategory->getSelected(); m_payee.setDefaultAccountId(temp); } } MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } } void KPayeesView::slotSyncAddressBook() { if (m_payeeRows.isEmpty()) { // empty list means no syncing is pending... foreach (auto item, m_payeesList->selectedItems()) { m_payeeRows.append(m_payeesList->row(item)); // ...so initialize one } m_payeesList->clearSelection(); // otherwise slotSelectPayee will be run after every payee update // m_syncAddressbook->setEnabled(false); // disallow concurent syncs } if (m_payeeRows.count() <= m_payeeRow) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { // update ui if something is selected m_payee = item->payee(); addressEdit->setText(m_payee.address()); postcodeEdit->setText(m_payee.postcode()); telephoneEdit->setText(m_payee.telephone()); } m_payeeRows.clear(); // that means end of sync m_payeeRow = 0; return; } KPayeeListItem* item = dynamic_cast(m_payeesList->item(m_payeeRows.at(m_payeeRow))); if (item) m_payee = item->payee(); ++m_payeeRow; m_contact->fetchContact(m_payee.email()); // search for payee's data in addressbook and receive it in slotContactFetched } void KPayeesView::slotContactFetched(const ContactData &identity) { if (!identity.email.isEmpty()) { // empty e-mail means no identity fetched QString txt; if (!identity.street.isEmpty()) txt.append(identity.street + "\n"); if (!identity.locality.isEmpty()) { txt.append(identity.locality); if (!identity.postalCode.isEmpty()) txt.append(' ' + identity.postalCode + "\n"); else txt.append("\n"); } if (!identity.country.isEmpty()) txt.append(identity.country + "\n"); if (!txt.isEmpty() && m_payee.address().compare(txt) != 0) m_payee.setAddress(txt); if (!identity.postalCode.isEmpty() && m_payee.postcode().compare(identity.postalCode) != 0) m_payee.setPostcode(identity.postalCode); if (!identity.phoneNumber.isEmpty() && m_payee.telephone().compare(identity.phoneNumber) != 0) m_payee.setTelephone(identity.phoneNumber); MyMoneyFileTransaction ft; try { MyMoneyFile::instance()->modifyPayee(m_payee); ft.commit(); } catch (const MyMoneyException &e) { KMessageBox::detailedSorry(0, i18n("Unable to modify payee"), i18n("%1 thrown in %2:%3", e.what(), e.file(), e.line())); } } slotSyncAddressBook(); // process next payee } void KPayeesView::slotSendMail() { QRegularExpression re(".+@.+"); if (re.match(m_payee.email()).hasMatch()) QDesktopServices::openUrl(QUrl(QStringLiteral("mailto:?to=") + m_payee.email(), QUrl::TolerantMode)); } void KPayeesView::showEvent(QShowEvent* event) { emit aboutToShow(); if (m_needReload) { loadPayees(); m_needReload = false; } // don't forget base class implementation QWidget::showEvent(event); QList list; selectedPayees(list); emit selectObjects(list); } void KPayeesView::slotLoadPayees() { if (isVisible()) { if (m_inSelection) QTimer::singleShot(0, this, SLOT(slotLoadPayees())); else loadPayees(); } else { m_needReload = true; } } void KPayeesView::loadPayees() { if (m_inSelection) return; QMap isSelected; QString id; MyMoneyFile* file = MyMoneyFile::instance(); // remember which items are selected in the list QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) isSelected[item->payee().id()] = true; ++payeesIt; } // keep current selected item KPayeeListItem *currentItem = static_cast(m_payeesList->currentItem()); if (currentItem) id = currentItem->payee().id(); m_allowEditing = false; // clear the list m_searchWidget->clear(); m_searchWidget->updateSearch(); m_payeesList->clear(); m_register->clear(); currentItem = 0; QListlist = file->payeeList(); QList::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if (m_payeeFilterType == eAllPayees || (m_payeeFilterType == eReferencedPayees && file->isReferenced(*it)) || (m_payeeFilterType == eUnusedPayees && !file->isReferenced(*it))) { KPayeeListItem* item = new KPayeeListItem(m_payeesList, *it); if (item->payee().id() == id) currentItem = item; if (isSelected[item->payee().id()]) item->setSelected(true); } } m_payeesList->sortItems(); if (currentItem) { m_payeesList->setCurrentItem(currentItem); m_payeesList->scrollToItem(currentItem); } m_filterProxyModel->invalidate(); comboDefaultCategory->expandAll(); slotSelectPayee(0, 0); m_allowEditing = true; } void KPayeesView::slotSelectTransaction() { QList list = m_register->selectedItems(); if (!list.isEmpty()) { KMyMoneyRegister::Transaction* t = dynamic_cast(list[0]); if (t) emit transactionSelected(t->split().accountId(), t->transaction().id()); } } void KPayeesView::slotSelectPayeeAndTransaction(const QString& payeeId, const QString& accountId, const QString& transactionId) { if (!isVisible()) return; try { // clear filter m_searchWidget->clear(); m_searchWidget->updateSearch(); // deselect all other selected items QList selectedItems = m_payeesList->selectedItems(); QList::const_iterator payeesIt = selectedItems.constBegin(); while (payeesIt != selectedItems.constEnd()) { KPayeeListItem* item = dynamic_cast(*payeesIt); if (item) item->setSelected(false); ++payeesIt; } // find the payee in the list QListWidgetItem* it; for (int i = 0; i < m_payeesList->count(); ++i) { it = m_payeesList->item(i); KPayeeListItem* item = dynamic_cast(it); if (item && item->payee().id() == payeeId) { m_payeesList->scrollToItem(it, QAbstractItemView::PositionAtCenter); m_payeesList->setCurrentItem(it); // active item and deselect all others m_payeesList->setCurrentRow(i, QItemSelectionModel::ClearAndSelect); // and select it //make sure the payee selection is updated and transactions are updated accordingly slotSelectPayee(); KMyMoneyRegister::RegisterItem *item = 0; for (int i = 0; i < m_register->rowCount(); ++i) { item = m_register->itemAtRow(i); KMyMoneyRegister::Transaction* t = dynamic_cast(item); if (t) { if (t->transaction().id() == transactionId && t->transaction().accountReferenced(accountId)) { m_register->selectItem(item); m_register->ensureItemVisible(item); break; } } } // quit out of for() loop break; } } } catch (const MyMoneyException &e) { qWarning("Unexpected exception in KPayeesView::slotSelectPayeeAndTransaction %s", qPrintable(e.what())); } } void KPayeesView::slotOpenContextMenu(const QPoint& /*p*/) { KPayeeListItem* item = dynamic_cast(m_payeesList->currentItem()); if (item) { slotSelectPayee(); emit openContextMenu(item->payee()); } } void KPayeesView::slotPayeeNew() { kmymoney->action("payee_new")->trigger(); } void KPayeesView::slotHelp() { KHelpClient::invokeHelp("details.payees"); } void KPayeesView::slotChangeFilter(int index) { //update the filter type then reload the payees list m_payeeFilterType = index; loadPayees(); } bool KPayeesView::isDirty() const { return m_updateButton->isEnabled(); } void KPayeesView::setDirty(bool dirty) { m_updateButton->setEnabled(dirty); } diff --git a/kmymoney/views/newspliteditor.ui b/kmymoney/views/newspliteditor.ui index 521ce9c80..a35e62577 100644 --- a/kmymoney/views/newspliteditor.ui +++ b/kmymoney/views/newspliteditor.ui @@ -1,252 +1,251 @@ NewSplitEditor 0 0 651 295 QFrame::StyledPanel QFrame::Raised Qt::Horizontal 40 20 Amount Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Credit 0 0 Debit true Qt::Horizontal 17 20 Enter Qt::ToolButtonTextBesideIcon Cancel Qt::ToolButtonTextBesideIcon Tags true QComboBox::InsertAfterCurrent Number Pay to true 0 0 Category Costcenter true Memo Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::Vertical 20 40 memoEdit tagsLabel payeeEdit numberLabel payToLabel accountCombo categoryLabel costCenterLabel numberEdit costCenterCombo memoLabel comboBox - verticalSpacer KMyMoneyAccountCombo QComboBox
kmymoneyaccountcombo.h
AmountEdit QLineEdit
amountedit.h
payeeEdit numberEdit accountCombo costCenterCombo comboBox memoEdit amountEditCredit amountEditDebit enterButton cancelButton