File indexing completed on 2024-04-28 05:06:07
0001 /* 0002 SPDX-FileCopyrightText: 2000-2003 Michael Edwardes <mte@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2000-2003 Javier Campos Morales <javi_c@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2000-2003 Felix Rodriguez <frodriguez@users.sourceforge.net> 0005 SPDX-FileCopyrightText: 2000-2003 John C <thetacoturtle@users.sourceforge.net> 0006 SPDX-FileCopyrightText: 2000-2003 Thomas Baumgart <ipwizard@users.sourceforge.net> 0007 SPDX-FileCopyrightText: 2000-2003 Kevin Tambascio <ktambascio@users.sourceforge.net> 0008 SPDX-FileCopyrightText: 2017 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com> 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "kmymoneyutils.h" 0013 0014 // ---------------------------------------------------------------------------- 0015 // QT Includes 0016 0017 #include <QAbstractButton> 0018 #include <QApplication> 0019 #include <QBitArray> 0020 #include <QFileInfo> 0021 #include <QGroupBox> 0022 #include <QIcon> 0023 #include <QList> 0024 #include <QPainter> 0025 #include <QPixmap> 0026 #include <QPixmapCache> 0027 #include <QRegularExpression> 0028 #include <QRegularExpressionMatch> 0029 #include <QTemporaryFile> 0030 #include <QWidget> 0031 #include <QWizard> 0032 #include <amountedit.h> 0033 #include <creditdebitedit.h> 0034 0035 // ---------------------------------------------------------------------------- 0036 // KDE Headers 0037 0038 #include <KColorScheme> 0039 #include <KGuiItem> 0040 #include <KIO/StatJob> 0041 #include <KIO/StoredTransferJob> 0042 #include <KLazyLocalizedString> 0043 #include <KLocalizedString> 0044 #include <KMessageBox> 0045 #include <KStandardGuiItem> 0046 #include <KXmlGuiWindow> 0047 #include <kio_version.h> 0048 0049 // ---------------------------------------------------------------------------- 0050 // Project Includes 0051 0052 #include "mymoneymoney.h" 0053 #include "mymoneyexception.h" 0054 #include "mymoneytransactionfilter.h" 0055 #include "mymoneyfile.h" 0056 #include "mymoneyaccount.h" 0057 #include "mymoneysecurity.h" 0058 #include "mymoneyschedule.h" 0059 #include "mymoneypayee.h" 0060 #include "mymoneytag.h" 0061 #include "mymoneyprice.h" 0062 #include "mymoneystatement.h" 0063 #include "mymoneyforecast.h" 0064 #include "mymoneysplit.h" 0065 #include "mymoneytransaction.h" 0066 #include "kmymoneysettings.h" 0067 #include "icons.h" 0068 #include "storageenums.h" 0069 #include "mymoneyenums.h" 0070 #include "kmymoneyplugin.h" 0071 #include "statusmodel.h" 0072 #include "journalmodel.h" 0073 #include "splitmodel.h" 0074 #include "accountsmodel.h" 0075 0076 #include "kmmyesno.h" 0077 0078 using namespace Icons; 0079 0080 const QString KMyMoneyUtils::paymentMethodToString(eMyMoney::Schedule::PaymentType paymentType) 0081 { 0082 return i18n(MyMoneySchedule::paymentMethodToString(paymentType)); 0083 } 0084 0085 const QString KMyMoneyUtils::weekendOptionToString(eMyMoney::Schedule::WeekendOption weekendOption) 0086 { 0087 return i18n(MyMoneySchedule::weekendOptionToString(weekendOption).toLatin1()); 0088 } 0089 0090 const QString KMyMoneyUtils::scheduleTypeToString(eMyMoney::Schedule::Type type) 0091 { 0092 return i18nc("Scheduled transaction type", MyMoneySchedule::scheduleTypeToString(type).toLatin1()); 0093 } 0094 0095 KGuiItem KMyMoneyUtils::scheduleNewGuiItem() 0096 { 0097 KGuiItem splitGuiItem(i18n("&New Schedule..."), 0098 Icons::get(Icon::DocumentNew), 0099 i18n("Create a new schedule."), 0100 i18n("Use this to create a new schedule.")); 0101 0102 return splitGuiItem; 0103 } 0104 0105 KGuiItem KMyMoneyUtils::accountsFilterGuiItem() 0106 { 0107 KGuiItem splitGuiItem(i18n("&Filter"), 0108 Icons::get(Icon::Filter), 0109 i18n("Filter out accounts"), 0110 i18n("Use this to filter out accounts")); 0111 0112 return splitGuiItem; 0113 } 0114 0115 const char* homePageItems[] = { 0116 kli18n("Scheduled payments").untranslatedText(), 0117 kli18n("Preferred accounts").untranslatedText(), 0118 kli18n("Payment accounts").untranslatedText(), 0119 kli18n("Favorite reports").untranslatedText(), 0120 kli18n("Forecast (schedule)").untranslatedText(), 0121 kli18n("Net worth forecast").untranslatedText(), 0122 kli18n("Forecast (history)").untranslatedText(), // unused, s.a. KSettingsHome::slotLoadItems() 0123 kli18n("Assets and Liabilities").untranslatedText(), 0124 kli18n("Budget").untranslatedText(), 0125 kli18n("CashFlow").untranslatedText(), 0126 // insert new items above this comment 0127 0, 0128 }; 0129 0130 const QString KMyMoneyUtils::homePageItemToString(const int idx) 0131 { 0132 QString rc; 0133 if (abs(idx) > 0 && abs(idx) < static_cast<int>(sizeof(homePageItems) / sizeof(homePageItems[0]))) { 0134 rc = i18n(homePageItems[abs(idx-1)]); 0135 } 0136 return rc; 0137 } 0138 0139 int KMyMoneyUtils::stringToHomePageItem(const QString& txt) 0140 { 0141 int idx = 0; 0142 for (idx = 0; homePageItems[idx] != 0; ++idx) { 0143 if (txt == i18n(homePageItems[idx])) 0144 return idx + 1; 0145 } 0146 return 0; 0147 } 0148 0149 bool KMyMoneyUtils::appendCorrectFileExt(QString& str, const QString& strExtToUse) 0150 { 0151 bool rc = false; 0152 0153 if (!str.isEmpty()) { 0154 //find last . deliminator 0155 int nLoc = str.lastIndexOf('.'); 0156 if (nLoc != -1) { 0157 QString strExt, strTemp; 0158 strTemp = str.left(nLoc + 1); 0159 strExt = str.right(str.length() - (nLoc + 1)); 0160 if (strExt.indexOf(strExtToUse, 0, Qt::CaseInsensitive) == -1) { 0161 // if the extension given contains a period, we remove ours 0162 if (strExtToUse.indexOf('.') != -1) 0163 strTemp = strTemp.left(strTemp.length() - 1); 0164 //append extension to make complete file name 0165 strTemp.append(strExtToUse); 0166 str = strTemp; 0167 rc = true; 0168 } 0169 } else { 0170 str.append(QLatin1Char('.')); 0171 str.append(strExtToUse); 0172 rc = true; 0173 } 0174 } 0175 return rc; 0176 } 0177 0178 void KMyMoneyUtils::checkConstants() 0179 { 0180 // TODO: port to kf5 0181 #if 0 0182 Q_ASSERT(static_cast<int>(KLocale::ParensAround) == static_cast<int>(MyMoneyMoney::ParensAround)); 0183 Q_ASSERT(static_cast<int>(KLocale::BeforeQuantityMoney) == static_cast<int>(MyMoneyMoney::BeforeQuantityMoney)); 0184 Q_ASSERT(static_cast<int>(KLocale::AfterQuantityMoney) == static_cast<int>(MyMoneyMoney::AfterQuantityMoney)); 0185 Q_ASSERT(static_cast<int>(KLocale::BeforeMoney) == static_cast<int>(MyMoneyMoney::BeforeMoney)); 0186 Q_ASSERT(static_cast<int>(KLocale::AfterMoney) == static_cast<int>(MyMoneyMoney::AfterMoney)); 0187 #endif 0188 } 0189 0190 QString KMyMoneyUtils::getStylesheet(QString baseStylesheet) 0191 { 0192 if (baseStylesheet.isEmpty()) 0193 baseStylesheet = QStandardPaths::locate(QStandardPaths::AppConfigLocation, "html/kmymoney.css"); 0194 0195 QColor tcolor = KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText).color(); 0196 QColor link = KColorScheme(QPalette::Active).foreground(KColorScheme::LinkText).color(); 0197 0198 QString css; 0199 css += QString(".row-even, .item0 { background-color: %1; color: %2 }\n") 0200 .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground1).name(), tcolor.name()); 0201 css += QString(".row-odd, .item1 { background-color: %1; color: %2 }\n") 0202 .arg(KMyMoneySettings::schemeColor(SchemeColor::ListBackground2).name(), tcolor.name()); 0203 css += QString(".negativetext { color: %1; }\n").arg(KMyMoneySettings::schemeColor(SchemeColor::Negative).name()); 0204 css += QString("a { color: %1; }\n").arg(link.name()); 0205 0206 QFile cssFile(baseStylesheet); 0207 if (cssFile.open(QIODevice::ReadOnly)) { 0208 QTextStream cssStream(&cssFile); 0209 css += cssStream.readAll(); 0210 cssFile.close(); 0211 } 0212 0213 return css; 0214 } 0215 0216 const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t) 0217 { 0218 MyMoneySplit investmentAccountSplit; 0219 const auto splits = t.splits(); 0220 for (const auto& split : splits) { 0221 if (!split.accountId().isEmpty()) { 0222 auto acc = MyMoneyFile::instance()->account(split.accountId()); 0223 if (acc.isInvest()) { 0224 return split; 0225 } 0226 // if we have a reference to an investment account, we remember it here 0227 if (acc.accountType() == eMyMoney::Account::Type::Investment) 0228 investmentAccountSplit = split; 0229 } 0230 } 0231 // if we haven't found a stock split, we see if we've seen 0232 // an investment account on the way. If so, we return it. 0233 if (!investmentAccountSplit.id().isEmpty()) 0234 return investmentAccountSplit; 0235 0236 // if none was found, we return an empty split. 0237 return MyMoneySplit(); 0238 } 0239 0240 KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t) 0241 { 0242 if (!stockSplit(t).id().isEmpty()) 0243 return InvestmentTransaction; 0244 0245 if (t.splitCount() < 2) { 0246 return Unknown; 0247 } else if (t.splitCount() > 2) { 0248 // FIXME check for loan transaction here 0249 return SplitTransaction; 0250 } 0251 QString ida, idb; 0252 const auto & splits = t.splits(); 0253 if (splits.size() > 0) 0254 ida = splits[0].accountId(); 0255 if (splits.size() > 1) 0256 idb = splits[1].accountId(); 0257 if (ida.isEmpty() || idb.isEmpty()) 0258 return Unknown; 0259 0260 MyMoneyAccount a, b; 0261 a = MyMoneyFile::instance()->account(ida); 0262 b = MyMoneyFile::instance()->account(idb); 0263 if ((a.accountGroup() == eMyMoney::Account::Type::Asset 0264 || a.accountGroup() == eMyMoney::Account::Type::Liability) 0265 && (b.accountGroup() == eMyMoney::Account::Type::Asset 0266 || b.accountGroup() == eMyMoney::Account::Type::Liability)) 0267 return Transfer; 0268 return Normal; 0269 } 0270 0271 void KMyMoneyUtils::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances) 0272 { 0273 try { 0274 MyMoneyForecast::calculateAutoLoan(schedule, transaction, balances); 0275 } catch (const MyMoneyException &e) { 0276 KMessageBox::detailedError(0, i18n("Unable to load schedule details"), QString::fromLatin1(e.what())); 0277 } 0278 } 0279 0280 QString KMyMoneyUtils::nextCheckNumber(const MyMoneyAccount& acc) 0281 { 0282 return getAdjacentNumber(acc.value("lastNumberUsed"), 1); 0283 } 0284 0285 QString KMyMoneyUtils::nextFreeCheckNumber(const MyMoneyAccount& acc) 0286 { 0287 auto file = MyMoneyFile::instance(); 0288 auto num = acc.value("lastNumberUsed"); 0289 0290 if (num.isEmpty()) 0291 num = QStringLiteral("1"); 0292 0293 // now check if this number has been used already 0294 if (file->checkNoUsed(acc.id(), num)) { 0295 // if a number has been entered which is immediately prior to 0296 // an existing number, the next new number produced would clash 0297 // so need to look ahead for free next number 0298 // we limit that to a number of tries which depends on the 0299 // number of splits in that account (we can't have more) 0300 MyMoneyTransactionFilter filter(acc.id()); 0301 QList<MyMoneyTransaction> transactions; 0302 file->transactionList(transactions, filter); 0303 const int maxNumber = transactions.count(); 0304 for (int i = 0; i < maxNumber; i++) { 0305 if (file->checkNoUsed(acc.id(), num)) { 0306 // increment and try again 0307 num = getAdjacentNumber(num); 0308 } else { 0309 // found a free number 0310 break; 0311 } 0312 } 0313 } 0314 return num; 0315 } 0316 0317 QString KMyMoneyUtils::getAdjacentNumber(const QString& number, int offset) 0318 { 0319 // make sure the offset is either -1 or 1 0320 offset = (offset >= 0) ? 1 : -1; 0321 0322 // +-#1--+ +#2++-#3-++-#4--+ 0323 static const QRegularExpression exp(QString("(.*\\D)?(0*)(\\d+)(\\D.*)?")); 0324 QRegularExpressionMatch match = exp.match(number); 0325 if (match.hasMatch()) { 0326 return QStringLiteral("%1%2%3%4").arg(match.captured(1), match.captured(2), QString::number(match.captured(3).toULong() + offset), match.captured(4)); 0327 } 0328 return QStringLiteral("1"); 0329 } 0330 0331 QString KMyMoneyUtils::reconcileStateToString(eMyMoney::Split::State flag, bool text) 0332 { 0333 QString txt; 0334 const QModelIndex idx = MyMoneyFile::instance()->statusModel()->index(static_cast<int>(flag), 0); 0335 if (idx.isValid()) { 0336 txt = idx.data(text ? eMyMoney::Model::SplitReconcileStatusRole : eMyMoney::Model::SplitReconcileFlagRole).toString(); 0337 } 0338 return txt; 0339 } 0340 0341 MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule) 0342 { 0343 MyMoneyTransaction t = schedule.transaction(); 0344 0345 try { 0346 if (schedule.type() == eMyMoney::Schedule::Type::LoanPayment) { 0347 calculateAutoLoan(schedule, t, QMap<QString, MyMoneyMoney>()); 0348 } 0349 } catch (const MyMoneyException &e) { 0350 qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), e.what()); 0351 } 0352 0353 t.clearId(); 0354 t.setEntryDate(QDate()); 0355 return t; 0356 } 0357 0358 KXmlGuiWindow* KMyMoneyUtils::mainWindow() 0359 { 0360 const auto widgetList = QApplication::topLevelWidgets(); 0361 for (QWidget* widget : qAsConst(widgetList)) { 0362 KXmlGuiWindow* result = dynamic_cast<KXmlGuiWindow*>(widget); 0363 if (result) 0364 return result; 0365 } 0366 return 0; 0367 } 0368 0369 void KMyMoneyUtils::updateWizardButtons(QWizard* wizard) 0370 { 0371 // setup text on buttons 0372 wizard->setButtonText(QWizard::NextButton, i18nc("Go to next page of the wizard", "&Next")); 0373 wizard->setButtonText(QWizard::BackButton, KStandardGuiItem::back().text()); 0374 0375 // setup icons 0376 wizard->button(QWizard::FinishButton)->setIcon(KStandardGuiItem::ok().icon()); 0377 wizard->button(QWizard::CancelButton)->setIcon(KStandardGuiItem::cancel().icon()); 0378 wizard->button(QWizard::NextButton)->setIcon(KStandardGuiItem::forward(KStandardGuiItem::UseRTL).icon()); 0379 wizard->button(QWizard::BackButton)->setIcon(KStandardGuiItem::back(KStandardGuiItem::UseRTL).icon()); 0380 } 0381 0382 void KMyMoneyUtils::dissectInvestmentTransaction(const QModelIndex &investSplitIdx, QModelIndex &assetAccountSplitIdx, SplitModel* feeSplitModel, SplitModel* interestSplitModel, MyMoneySecurity &security, MyMoneySecurity ¤cy, eMyMoney::Split::InvestmentTransactionType &transactionType) 0383 { 0384 // clear split models 0385 feeSplitModel->unload(); 0386 interestSplitModel->unload(); 0387 0388 assetAccountSplitIdx = QModelIndex(); // set to none to check later if it was assigned 0389 const auto file = MyMoneyFile::instance(); 0390 0391 // collect the splits. split references the stock account and should already 0392 // be set up. assetAccountSplit references the corresponding asset account (maybe 0393 // empty), feeSplits is the list of all expenses and interestSplits 0394 // the list of all incomes 0395 auto idx = MyMoneyFile::baseModel()->mapToBaseSource(investSplitIdx); 0396 const auto list = idx.model()->match(idx.model()->index(0, 0), eMyMoney::Model::JournalTransactionIdRole, 0397 idx.data(eMyMoney::Model::JournalTransactionIdRole), 0398 -1, // all splits 0399 Qt::MatchFlags(Qt::MatchExactly | Qt::MatchCaseSensitive | Qt::MatchRecursive)); 0400 for (const auto& splitIdx : list) { 0401 auto accIdx = file->accountsModel()->indexById(splitIdx.data(eMyMoney::Model::SplitAccountIdRole).toString()); 0402 const auto accountGroup = accIdx.data(eMyMoney::Model::AccountGroupRole).value<eMyMoney::Account::Type>(); 0403 if (splitIdx.row() == idx.row()) { 0404 security = file->security(accIdx.data(eMyMoney::Model::AccountCurrencyIdRole).toString()); 0405 } else if (accountGroup == eMyMoney::Account::Type::Expense) { 0406 feeSplitModel->appendSplit(file->journalModel()->itemByIndex(splitIdx).split()); 0407 } else if (accountGroup == eMyMoney::Account::Type::Income) { 0408 interestSplitModel->appendSplit(file->journalModel()->itemByIndex(splitIdx).split()); 0409 } else { 0410 if (!assetAccountSplitIdx.isValid()) { // first asset Account should be our requested brokerage account 0411 assetAccountSplitIdx = splitIdx; 0412 } else if (idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>().isNegative()) { // the rest (if present) is handled as fee or interest 0413 feeSplitModel->appendSplit(file->journalModel()->itemByIndex(splitIdx).split()); 0414 } else if (idx.data(eMyMoney::Model::SplitValueRole).value<MyMoneyMoney>().isPositive()) { 0415 interestSplitModel->appendSplit(file->journalModel()->itemByIndex(splitIdx).split()); 0416 } 0417 } 0418 } 0419 0420 // determine transaction type 0421 transactionType = idx.data(eMyMoney::Model::TransactionInvestementType).value<eMyMoney::Split::InvestmentTransactionType>(); 0422 0423 currency.setTradingSymbol("???"); 0424 try { 0425 currency = file->security(file->journalModel()->itemByIndex(idx).transaction().commodity()); 0426 } catch (const MyMoneyException &) { 0427 } 0428 } 0429 0430 void KMyMoneyUtils::processPriceList(const MyMoneyStatement &st) 0431 { 0432 auto file = MyMoneyFile::instance(); 0433 QHash<QString, MyMoneySecurity> secBySymbol; 0434 QHash<QString, MyMoneySecurity> secByName; 0435 0436 const auto securityList = file->securityList(); 0437 for (const auto& sec : securityList) { 0438 secBySymbol[sec.tradingSymbol()] = sec; 0439 secByName[sec.name()] = sec; 0440 } 0441 0442 for (const auto& stPrice : st.m_listPrices) { 0443 auto currency = file->baseCurrency().id(); 0444 QString security; 0445 0446 if (!stPrice.m_strCurrency.isEmpty()) { 0447 security = stPrice.m_strSecurity; 0448 currency = stPrice.m_strCurrency; 0449 } else if (secBySymbol.contains(stPrice.m_strSecurity)) { 0450 security = secBySymbol[stPrice.m_strSecurity].id(); 0451 currency = file->security(file->security(security).tradingCurrency()).id(); 0452 } else if (secByName.contains(stPrice.m_strSecurity)) { 0453 security = secByName[stPrice.m_strSecurity].id(); 0454 currency = file->security(file->security(security).tradingCurrency()).id(); 0455 } else 0456 return; 0457 0458 MyMoneyPrice price(security, 0459 currency, 0460 stPrice.m_date, 0461 stPrice.m_amount, stPrice.m_sourceName.isEmpty() ? i18n("Prices Importer") : stPrice.m_sourceName); 0462 file->addPrice(price); 0463 } 0464 } 0465 0466 void KMyMoneyUtils::deleteSecurity(const MyMoneySecurity& security, QWidget* parent) 0467 { 0468 QString msg, msg2; 0469 QString dontAsk, dontAsk2; 0470 if (security.isCurrency()) { 0471 msg = i18n("<p>Do you really want to remove the currency <b>%1</b> from the file?</p>", security.name()); 0472 msg2 = i18n("<p>All exchange rates for currency <b>%1</b> will be lost.</p><p>Do you still want to continue?</p>", security.name()); 0473 dontAsk = "DeleteCurrency"; 0474 dontAsk2 = "DeleteCurrencyRates"; 0475 } else { 0476 msg = i18n("<p>Do you really want to remove the %1 <b>%2</b> from the file?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name()); 0477 msg2 = i18n("<p>All price quotes for %1 <b>%2</b> will be lost.</p><p>Do you still want to continue?</p>", MyMoneySecurity::securityTypeToString(security.securityType()), security.name()); 0478 dontAsk = "DeleteSecurity"; 0479 dontAsk2 = "DeleteSecurityPrices"; 0480 } 0481 if (KMessageBox::questionTwoActions(parent, msg, i18n("Delete security"), KMMYesNo::yes(), KMMYesNo::no(), dontAsk) == KMessageBox::PrimaryAction) { 0482 MyMoneyFileTransaction ft; 0483 auto file = MyMoneyFile::instance(); 0484 0485 QBitArray skip((int)eStorage::Reference::Count); 0486 skip.fill(true); 0487 skip.clearBit((int)eStorage::Reference::Price); 0488 if (file->isReferenced(security, skip)) { 0489 if (KMessageBox::questionTwoActions(parent, msg2, i18n("Delete prices"), KMMYesNo::yes(), KMMYesNo::no(), dontAsk2) == KMessageBox::PrimaryAction) { 0490 try { 0491 QString secID = security.id(); 0492 const auto priceList = file->priceList(); 0493 for (const auto& priceEntry : priceList) { 0494 const MyMoneyPrice& price = priceEntry.first(); 0495 if (price.from() == secID || price.to() == secID) 0496 file->removePrice(price); 0497 } 0498 ft.commit(); 0499 ft.restart(); 0500 } catch (const MyMoneyException &) { 0501 qDebug("Cannot delete price"); 0502 return; 0503 } 0504 } else 0505 return; 0506 } 0507 try { 0508 if (security.isCurrency()) 0509 file->removeCurrency(security); 0510 else 0511 file->removeSecurity(security); 0512 ft.commit(); 0513 } catch (const MyMoneyException &) { 0514 } 0515 } 0516 } 0517 0518 bool KMyMoneyUtils::fileExists(const QUrl &url) 0519 { 0520 bool fileExists = false; 0521 if (url.isValid()) { 0522 if (url.isLocalFile() || url.scheme().isEmpty()) { 0523 QFileInfo check_file(url.toLocalFile()); 0524 fileExists = check_file.exists() && check_file.isFile(); 0525 0526 } else { 0527 auto statjob = KIO::statDetails(url, KIO::StatJob::SourceSide, KIO::StatNoDetails); 0528 bool noerror = statjob->exec(); 0529 if (noerror) { 0530 // We want a file 0531 fileExists = !statjob->statResult().isDir(); 0532 } 0533 statjob->kill(); 0534 } 0535 } 0536 return fileExists; 0537 } 0538 0539 QString KMyMoneyUtils::downloadFile(const QUrl &url) 0540 { 0541 QString filename; 0542 KIO::StoredTransferJob *transferjob = KIO::storedGet (url); 0543 // KJobWidgets::setWindow(transferjob, this); 0544 if (! transferjob->exec()) { 0545 KMessageBox::detailedError(nullptr, 0546 i18n("Error while loading file '%1'.", url.url()), 0547 transferjob->errorString(), 0548 i18n("File access error")); 0549 return filename; 0550 } 0551 0552 QTemporaryFile file; 0553 file.setAutoRemove(false); 0554 file.open(); 0555 file.write(transferjob->data()); 0556 filename = file.fileName(); 0557 file.close(); 0558 return filename; 0559 } 0560 0561 std::tuple<bool, QString> KMyMoneyUtils::newPayee(const QString& newnameBase) 0562 { 0563 bool doit = true; 0564 QString id; 0565 0566 if (newnameBase != i18n("New Payee")) { 0567 // Ask the user if that is what he intended to do? 0568 const auto msg = i18n("<qt>Do you want to add <b>%1</b> as payee/receiver?</qt>", newnameBase); 0569 0570 if (KMessageBox::questionTwoActions(nullptr, msg, i18n("New payee/receiver"), KMMYesNo::yes(), KMMYesNo::no(), "NewPayee") 0571 == KMessageBox::SecondaryAction) { 0572 doit = false; 0573 // we should not keep the 'no' setting because that can confuse people like 0574 // I have seen in some usability tests. So we just delete it right away. 0575 KSharedConfigPtr kconfig = KSharedConfig::openConfig(); 0576 if (kconfig) { 0577 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewPayee")); 0578 } 0579 } 0580 } 0581 0582 if (doit) { 0583 MyMoneyFileTransaction ft; 0584 try { 0585 QString newname(newnameBase); 0586 // adjust name until a unique name has been created 0587 int count = 0; 0588 0589 for (;;) { 0590 try { 0591 const auto payee = MyMoneyFile::instance()->payeeByName(newname); 0592 if (payee.id().isEmpty()) 0593 break; 0594 newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count); 0595 } catch (const MyMoneyException &) { 0596 break; 0597 } 0598 } 0599 0600 MyMoneyPayee p; 0601 p.setName(newname); 0602 p.setMatchData(eMyMoney::Payee::MatchType::NameExact, true, QStringList()); 0603 MyMoneyFile::instance()->addPayee(p); 0604 id = p.id(); 0605 ft.commit(); 0606 } catch (const MyMoneyException &e) { 0607 KMessageBox::detailedError(nullptr, i18n("Unable to add payee"), QString::fromLatin1(e.what())); 0608 doit = false; 0609 } 0610 } 0611 return std::make_tuple(doit, id); 0612 } 0613 0614 std::tuple<bool, QString> KMyMoneyUtils::newTag(const QString& newnameBase) 0615 { 0616 bool doit = true; 0617 QString id; 0618 0619 if (newnameBase != i18n("New Tag")) { 0620 // Ask the user if that is what he intended to do? 0621 const auto msg = i18n("<qt>Do you want to add <b>%1</b> as tag?</qt>", newnameBase); 0622 0623 if (KMessageBox::questionTwoActions(nullptr, msg, i18n("New tag"), KMMYesNo::yes(), KMMYesNo::no(), "NewTag") == KMessageBox::SecondaryAction) { 0624 doit = false; 0625 // we should not keep the 'no' setting because that can confuse people like 0626 // I have seen in some usability tests. So we just delete it right away. 0627 KSharedConfigPtr kconfig = KSharedConfig::openConfig(); 0628 if (kconfig) { 0629 kconfig->group(QLatin1String("Notification Messages")).deleteEntry(QLatin1String("NewTag")); 0630 } 0631 } 0632 } 0633 0634 if (doit) { 0635 MyMoneyFileTransaction ft; 0636 try { 0637 QString newname(newnameBase); 0638 // adjust name until a unique name has been created 0639 int count = 0; 0640 for (;;) { 0641 try { 0642 if (MyMoneyFile::instance()->tagByName(newname).id().isEmpty()) { 0643 break; 0644 } 0645 newname = QString::fromLatin1("%1 [%2]").arg(newnameBase).arg(++count); 0646 } catch (const MyMoneyException &) { 0647 break; 0648 } 0649 } 0650 0651 MyMoneyTag ta; 0652 ta.setName(newname); 0653 MyMoneyFile::instance()->addTag(ta); 0654 id = ta.id(); 0655 ft.commit(); 0656 } catch (const MyMoneyException &e) { 0657 KMessageBox::detailedError(nullptr, i18n("Unable to add tag"), QString::fromLatin1(e.what())); 0658 } 0659 } 0660 return std::make_tuple(doit, id); 0661 } 0662 0663 void KMyMoneyUtils::newInstitution(MyMoneyInstitution& institution) 0664 { 0665 auto file = MyMoneyFile::instance(); 0666 0667 MyMoneyFileTransaction ft; 0668 0669 try { 0670 file->addInstitution(institution); 0671 ft.commit(); 0672 0673 } catch (const MyMoneyException &e) { 0674 KMessageBox::information(nullptr, i18n("Cannot add institution: %1", QString::fromLatin1(e.what()))); 0675 } 0676 } 0677 0678 QDebug KMyMoneyUtils::debug() 0679 { 0680 return qDebug() << QDateTime::currentDateTime().toString(QStringLiteral("HH:mm:ss.zzz")); 0681 } 0682 0683 MyMoneyForecast KMyMoneyUtils::forecast() 0684 { 0685 MyMoneyForecast forecast; 0686 0687 // override object defaults with those of the application 0688 forecast.setForecastCycles(KMyMoneySettings::forecastCycles()); 0689 forecast.setAccountsCycle(KMyMoneySettings::forecastAccountCycle()); 0690 forecast.setHistoryStartDate(QDate::currentDate().addDays(-forecast.forecastCycles()*forecast.accountsCycle())); 0691 forecast.setHistoryEndDate(QDate::currentDate().addDays(-1)); 0692 forecast.setForecastDays(KMyMoneySettings::forecastDays()); 0693 forecast.setBeginForecastDay(KMyMoneySettings::beginForecastDay()); 0694 forecast.setForecastMethod(KMyMoneySettings::forecastMethod()); 0695 forecast.setHistoryMethod(KMyMoneySettings::historyMethod()); 0696 forecast.setIncludeFutureTransactions(KMyMoneySettings::includeFutureTransactions()); 0697 forecast.setIncludeScheduledTransactions(KMyMoneySettings::includeScheduledTransactions()); 0698 0699 return forecast; 0700 } 0701 0702 bool KMyMoneyUtils::canUpdateAllAccounts() 0703 { 0704 const auto file = MyMoneyFile::instance(); 0705 auto rc = false; 0706 0707 QList<MyMoneyAccount> accList; 0708 file->accountList(accList); 0709 QList<MyMoneyAccount>::const_iterator it_a; 0710 auto it_p = pPlugins.online.constEnd(); 0711 for (it_a = accList.constBegin(); (it_p == pPlugins.online.constEnd()) && (it_a != accList.constEnd()); ++it_a) { 0712 if ((*it_a).hasOnlineMapping()) { 0713 // check if provider is available 0714 it_p = pPlugins.online.constFind((*it_a).onlineBankingSettings().value("provider").toLower()); 0715 if (it_p != pPlugins.online.constEnd()) { 0716 QStringList protocols; 0717 (*it_p)->protocols(protocols); 0718 if (!protocols.isEmpty()) { 0719 rc = true; 0720 break; 0721 } 0722 } 0723 } 0724 } 0725 return rc; 0726 } 0727 0728 void KMyMoneyUtils::showStatementImportResult(const QStringList& resultMessages, uint statementCount) 0729 { 0730 KMessageBox::informationList(nullptr, 0731 i18np("One statement has been processed with the following results:", 0732 "%1 statements have been processed with the following results:", 0733 statementCount), 0734 !resultMessages.isEmpty() ? 0735 resultMessages : 0736 QStringList { i18np("No new transaction has been imported.", "No new transactions have been imported.", statementCount) }, 0737 i18n("Statement import statistics")); 0738 } 0739 0740 QString KMyMoneyUtils::normalizeNumericString(const qreal& val, const QLocale& loc, const char f, const int prec) 0741 { 0742 static const QRegularExpression trailingZeroesRegex("0+$"); 0743 return loc.toString(val, f, prec).remove(loc.groupSeparator()).remove(trailingZeroesRegex).remove(QRegularExpression("\\" + loc.decimalPoint() + "$")); 0744 } 0745 0746 QStringList KMyMoneyUtils::tabOrder(const QString& name, const QStringList& defaultTabOrder) 0747 { 0748 KSharedConfigPtr config = KSharedConfig::openConfig(); 0749 KConfigGroup grp = config->group(QLatin1String("TabOrder")); 0750 return grp.readEntry(name, defaultTabOrder); 0751 } 0752 0753 #if 0 0754 static QString widgetName(QWidget* w) 0755 { 0756 return w->objectName().isEmpty() ? w->metaObject()->className() : w->objectName(); 0757 } 0758 0759 static void dumpFocusChain(QWidget* w, QWidget* end, int additionalTabstops = 0, bool forward = true) 0760 { 0761 QString txt; 0762 int loopValid = 80; 0763 int trailing = -1; 0764 do { 0765 const auto policy = w->focusPolicy(); 0766 if (policy == Qt::TabFocus || policy == Qt::StrongFocus ) { 0767 if (!txt.isEmpty()) { 0768 txt += forward ? QLatin1String(" -> ") : QLatin1String(" <- "); 0769 } 0770 txt += widgetName(w); 0771 --loopValid; 0772 w = forward ? w->nextInFocusChain() : w->previousInFocusChain(); 0773 if (w == end && (trailing < 0)) { 0774 trailing = additionalTabstops+1; 0775 loopValid = trailing; 0776 } 0777 } 0778 --trailing; 0779 } while ((loopValid > 0) && (trailing != 0)); 0780 0781 if (end) { 0782 txt += forward ? QLatin1String(" -> ") : QLatin1String(" <- "); 0783 txt += widgetName(w); 0784 } 0785 0786 qDebug() << txt; 0787 } 0788 #endif 0789 0790 void KMyMoneyUtils::setupTabOrder(QWidget* parent, const QStringList& tabOrder) 0791 { 0792 const auto widgetCount = tabOrder.count(); 0793 if (widgetCount > 0) { 0794 auto prev = parent->findChild<QWidget*>(tabOrder.at(0)); 0795 for (int i = 1; (prev != nullptr) && (i < widgetCount); ++i) { 0796 const auto next = parent->findChild<QWidget*>(tabOrder.at(i)); 0797 if (next) { 0798 parent->setTabOrder(prev, next); 0799 prev = next; 0800 } else { 0801 qDebug() << tabOrder.at(i) << "not found :("; 0802 } 0803 } 0804 } 0805 } 0806 0807 void KMyMoneyUtils::storeTabOrder(const QString& name, const QStringList& tabOrder) 0808 { 0809 KSharedConfigPtr config = KSharedConfig::openConfig(); 0810 KConfigGroup grp = config->group(QLatin1String("TabOrder")); 0811 grp.writeEntry(name, tabOrder); 0812 } 0813 0814 bool KMyMoneyUtils::tabFocusHelper(QWidget* topLevelWidget, bool next) 0815 { 0816 const auto reason = next ? Qt::TabFocusReason : Qt::BacktabFocusReason; 0817 const auto tabOrder = topLevelWidget->property("kmm_currenttaborder").toStringList(); 0818 0819 if (tabOrder.isEmpty()) 0820 return false; 0821 0822 auto focusWidget = topLevelWidget->focusWidget(); 0823 0824 enum firstOrLastVisible { 0825 FirstVisible, 0826 LastVisible, 0827 }; 0828 0829 auto findFirstOrLastVisible = [&](firstOrLastVisible type) { 0830 const int ofs = (type == FirstVisible) ? 1 : -1; 0831 int idx = (type == FirstVisible) ? 0 : tabOrder.count() - 1; 0832 for (; idx >= 0 && idx < tabOrder.count(); idx += ofs) { 0833 auto w = topLevelWidget->findChild<QWidget*>(tabOrder.at(idx)); 0834 // in case of embedded transaction editors, we may search 0835 // for a widget that is known to the parent 0836 if (!w) { 0837 auto parent = topLevelWidget->parentWidget(); 0838 while (true) { 0839 w = parent->findChild<QWidget*>(tabOrder.at(idx)); 0840 if (!w && qobject_cast<QGroupBox*>(parent)) { 0841 parent = parent->parentWidget(); 0842 continue; 0843 } 0844 break; 0845 } 0846 } 0847 if (w && w->isVisible() && w->isEnabled()) { 0848 return w; 0849 } 0850 } 0851 return static_cast<QWidget*>(nullptr); 0852 }; 0853 0854 auto selectWidget = [&](QWidget* w) { 0855 if (w) { 0856 // if we point to a constructed widget (e.g. ButtonBox) we 0857 // need to select the last widget if going backward 0858 if (reason == Qt::BacktabFocusReason && !w->findChildren<QWidget*>().isEmpty()) { 0859 auto parent = w; 0860 while (w->nextInFocusChain()->parentWidget() == parent) { 0861 w = w->nextInFocusChain(); 0862 } 0863 } 0864 w->setFocus(reason); 0865 } 0866 }; 0867 0868 auto adjustToContainer = [&](const char* containerClass, const char* widgetClass) { 0869 if (focusWidget->qt_metacast(widgetClass) && focusWidget->parentWidget()->qt_metacast(containerClass) && (reason == Qt::BacktabFocusReason)) { 0870 if (focusWidget->previousInFocusChain() == focusWidget->parentWidget()) { 0871 focusWidget = focusWidget->parentWidget(); 0872 } 0873 } 0874 }; 0875 0876 // In case of a CreditDebitEdit widget and we leave from the left backwards, 0877 // we need to adjust the widget to point to the container widget. 0878 adjustToContainer("CreditDebitEdit", "AmountEdit"); 0879 // In case of a QDialogButtonBox widget and we leave from the left backwards, 0880 // we need to adjust the widget to point to the container widget. 0881 // adjustToContainer("QDialogButtonBox", "QPushButton"); 0882 0883 if ((reason == Qt::BacktabFocusReason) && (findFirstOrLastVisible(FirstVisible) == focusWidget)) { 0884 selectWidget(findFirstOrLastVisible(LastVisible)); 0885 return true; 0886 0887 } else if ((reason == Qt::TabFocusReason) && (findFirstOrLastVisible(LastVisible) == focusWidget)) { 0888 selectWidget(findFirstOrLastVisible(FirstVisible)); 0889 return true; 0890 } 0891 return false; 0892 }