File indexing completed on 2024-04-28 05:06:07

0001 /*
0002     SPDX-FileCopyrightText: 2001 Michael Edwardes <mte@users.sourceforge.net>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 
0007 #include <config-kmymoney.h>
0008 #include <config-kmymoney-version.h>
0009 
0010 // ----------------------------------------------------------------------------
0011 // QT Includes
0012 
0013 #include <QApplication>
0014 #include <QCommandLineParser>
0015 #include <QRegularExpression>
0016 #include <QRegularExpressionMatch>
0017 #include <QStandardPaths>
0018 #include <QStringList>
0019 #include <QWidget>
0020 #ifdef KMM_DBUS
0021 #include <QDBusConnection>
0022 #include <QDBusConnectionInterface>
0023 #include <QDBusInterface>
0024 #endif
0025 // ----------------------------------------------------------------------------
0026 // KDE Includes
0027 
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 
0031 // ----------------------------------------------------------------------------
0032 // Project Includes
0033 
0034 #include <alkimia/alkenvironment.h>
0035 
0036 #include "amountedit.h"
0037 #include "kcreditswindow.h"
0038 #include "kmymoney.h"
0039 #include "kmymoneysettings.h"
0040 #include "kmymoneyutils.h"
0041 #include "misc/webconnect.h"
0042 #include "mymoney/mymoneyfile.h"
0043 #include "mymoneyexception.h"
0044 #include "platformtools.h"
0045 
0046 #ifdef Q_OS_WIN
0047 #include <windows.h>
0048 #endif
0049 
0050 #ifdef KMM_DEBUG
0051 #include "mymoneyutils.h"
0052 #include "mymoneytracer.h"
0053 #endif
0054 
0055 #ifdef IMPORT_SQLCIPHER_PLUGIN
0056 Q_IMPORT_PLUGIN(QSQLCipherDriverPlugin)
0057 #endif
0058 
0059 bool timersOn = false;
0060 
0061 KMyMoneyApp* kmymoney;
0062 
0063 static int runKMyMoney(QApplication &a, const QUrl &file, bool noFile);
0064 static void migrateConfigFiles();
0065 
0066 int main(int argc, char *argv[])
0067 {
0068 #ifdef Q_OS_WIN
0069     // enable console logging on Windows
0070     if (AttachConsole(ATTACH_PARENT_PROCESS)) {
0071         freopen("CONOUT$", "w", stdout);
0072         freopen("CONOUT$", "w", stderr);
0073     }
0074 #endif
0075 
0076     /**
0077      * Create application first
0078      */
0079     QApplication app(argc, argv);
0080     KLocalizedString::setApplicationDomain("kmymoney");
0081 
0082     AlkEnvironment::checkForAppImageEnvironment(argv[0]);
0083 
0084     migrateConfigFiles();
0085 
0086     /**
0087      * construct and register about data
0088      */
0089     KAboutData aboutData(QStringLiteral("kmymoney"), i18n("KMyMoney"), QStringLiteral(VERSION));
0090     aboutData.setOrganizationDomain("kde.org");
0091     KAboutData::setApplicationData(aboutData);
0092 
0093     QStringList fileUrls;
0094     bool isNoCatchOption = false;
0095     bool isNoFileOption = false;
0096 
0097 #ifdef KMM_DEBUG
0098     bool isDumpActionsOption = false;
0099 #endif
0100 
0101     if (argc != 0) {
0102         /**
0103          * Create command line parser and feed it with known options
0104          */
0105         QCommandLineParser parser;
0106         aboutData.setupCommandLine(&parser);
0107 
0108         // language
0109         const QCommandLineOption langOption(QStringLiteral("lang"), i18n("language to be used"), "lang");
0110         parser.addOption(langOption);
0111 
0112         // no file
0113         const QCommandLineOption noFileOption(QStringLiteral("n"), i18n("do not open last used file"));
0114         parser.addOption(noFileOption);
0115 
0116         // timers
0117         const QCommandLineOption timersOption(QStringLiteral("timers"), i18n("enable performance timers"));
0118         parser.addOption(timersOption);
0119 
0120         // no catch
0121         const QCommandLineOption noCatchOption(QStringLiteral("nocatch"), i18n("do not globally catch uncaught exceptions"));
0122         parser.addOption(noCatchOption);
0123 
0124 #ifdef KMM_DEBUG
0125         // The following options are only available when compiled in debug mode
0126         // trace
0127         const QCommandLineOption traceOption(QStringLiteral("trace"), i18n("turn on program traces"));
0128         parser.addOption(traceOption);
0129 
0130         // dump actions
0131         const QCommandLineOption dumpActionsOption(QStringLiteral("dump-actions"), i18n("dump the names of all defined QAction objects to stdout and quit"));
0132         parser.addOption(dumpActionsOption);
0133 #endif
0134 
0135         // INSERT YOUR COMMANDLINE OPTIONS HERE
0136         // url to open
0137         parser.addPositionalArgument(QStringLiteral("url"), i18n("file to open"));
0138 
0139         /**
0140          * do the command line parsing (and handle --help and --version)
0141          */
0142         parser.process(QApplication::arguments());
0143 
0144         if (parser.isSet(QStringLiteral("author")) || parser.isSet(QStringLiteral("license"))) {
0145             aboutData = initializeCreditsData();
0146         }
0147 
0148         if (parser.isSet(QStringLiteral("lang"))) {
0149             QString language = parser.value(langOption);
0150             if (!language.isEmpty()) {
0151                 KLocalizedString::setLanguages(QStringList() << language);
0152             }
0153         }
0154 
0155         /**
0156          * handle standard options
0157          */
0158         aboutData.processCommandLine(&parser);
0159 
0160 #ifdef KMM_DEBUG
0161         if (parser.isSet(traceOption))
0162             MyMoneyTracer::on();
0163         timersOn = parser.isSet(timersOption);
0164         isDumpActionsOption = parser.isSet(dumpActionsOption);
0165 #endif
0166 
0167         isNoCatchOption = parser.isSet(noCatchOption);
0168         isNoFileOption = parser.isSet(noFileOption);
0169         fileUrls = parser.positionalArguments();
0170     }
0171 
0172     // create the singletons before we start memory checking
0173     // to avoid false error reports
0174     auto file = MyMoneyFile::instance();
0175     Q_UNUSED(file)
0176 
0177     KMyMoneyUtils::checkConstants();
0178 
0179     // setup the MyMoneyMoney locale settings according to the KDE settings
0180     MyMoneyMoney::setThousandSeparator(QLocale().groupSeparator());
0181     MyMoneyMoney::setDecimalSeparator(QLocale().decimalPoint());
0182 
0183     // setup format of negative values
0184     MyMoneyMoney::setNegativeSpaceSeparatesSymbol(false);
0185     MyMoneyMoney::setNegativePrefixCurrencySymbol(false);
0186     MyMoneyMoney::setNegativeMonetarySignPosition(static_cast<eMyMoney::Money::signPosition>(platformTools::currencySignPosition(true)));
0187     switch(platformTools::currencySymbolPosition(true)) {
0188     case platformTools::AfterQuantityMoneyWithSpace:
0189         MyMoneyMoney::setNegativeSpaceSeparatesSymbol(true);
0190     // intentional fall through
0191     case platformTools::AfterQuantityMoney:
0192         break;
0193 
0194     case platformTools::BeforeQuantityMoneyWithSpace:
0195         MyMoneyMoney::setNegativeSpaceSeparatesSymbol(true);
0196     // intentional fall through
0197     case platformTools::BeforeQuantityMoney:
0198         MyMoneyMoney::setNegativePrefixCurrencySymbol(true);
0199         break;
0200     }
0201     // setup format of positive values
0202     MyMoneyMoney::setPositiveSpaceSeparatesSymbol(false);
0203     MyMoneyMoney::setPositivePrefixCurrencySymbol(false);
0204     MyMoneyMoney::setPositiveMonetarySignPosition(static_cast<eMyMoney::Money::signPosition>(platformTools::currencySignPosition(false)));
0205     switch(platformTools::currencySymbolPosition(false)) {
0206     case platformTools::AfterQuantityMoneyWithSpace:
0207         MyMoneyMoney::setPositiveSpaceSeparatesSymbol(true);
0208     // intentional fall through
0209     case platformTools::AfterQuantityMoney:
0210         break;
0211     case platformTools::BeforeQuantityMoneyWithSpace:
0212         MyMoneyMoney::setPositiveSpaceSeparatesSymbol(true);
0213     // intentional fall through
0214     case platformTools::BeforeQuantityMoney:
0215         MyMoneyMoney::setPositivePrefixCurrencySymbol(true);
0216         break;
0217     }
0218 
0219     kmymoney = new KMyMoneyApp();
0220 
0221 #ifdef KMM_DEBUG
0222     if (isDumpActionsOption) {
0223         kmymoney->dumpActions();
0224 
0225         // Before we delete the application, we make sure that we destroy all
0226         // widgets by running the event loop for some time to catch all those
0227         // widgets that are requested to be destroyed using the deleteLater() method.
0228         //QApplication::eventLoop()->processEvents(QEventLoop::ExcludeUserInput, 10);
0229         QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 10);
0230 
0231         delete kmymoney;
0232         exit(0);
0233     }
0234 #endif
0235 
0236     QString fname;
0237     // in case a filename is provided we need to check if it is a local
0238     // file. In case the name does not start with "file://" or "./" or "/"
0239     // we need to prepend "./" to fake a relative filename. Otherwise, QUrl prepends
0240     // "http://" and uses the full path which will not work.
0241     // On MS-Windows we also need to check if the filename starts with a
0242     // drive letter or the backslash variants.
0243     //
0244     // The handling might be different on other OSes
0245     if (!fileUrls.isEmpty()) {
0246         fname = fileUrls.front();
0247         QFileInfo fi(fname);
0248         auto needLeadIn = fi.isFile();
0249 #ifdef Q_OS_WIN
0250         QRegularExpression exp("^[a-z]:", QRegularExpression::CaseInsensitiveOption);
0251         needLeadIn &= !exp.match(fname).hasMatch() //
0252                       && !fname.startsWith(QLatin1String(".\\")) //
0253                       && !fname.startsWith(QLatin1String("\\"));
0254 #endif
0255         needLeadIn &= !fname.startsWith(QLatin1String("file://")) //
0256                       && !fname.startsWith(QLatin1String("./")) //
0257                       && !fname.startsWith(QLatin1String("/"));
0258         if (needLeadIn) {
0259             fname.prepend(QLatin1String("./"));
0260         }
0261     }
0262 
0263     const QUrl url = QUrl::fromUserInput(fname, QLatin1String("."), QUrl::AssumeLocalFile);
0264     int rc = 0;
0265     if (isNoCatchOption) {
0266         qDebug("Running w/o global try/catch block");
0267         rc = runKMyMoney(app, url, isNoFileOption);
0268     } else {
0269         try {
0270             rc = runKMyMoney(app, url, isNoFileOption);
0271         } catch (const MyMoneyException &e) {
0272             KMessageBox::detailedError(0, i18n("Uncaught error. Please report the details to the developers"), QString::fromLatin1(e.what()));
0273             throw;
0274         }
0275     }
0276 
0277     return rc;
0278 }
0279 
0280 int runKMyMoney(QApplication &a, const QUrl &file, bool noFile)
0281 {
0282 
0283 #ifdef Q_OS_MAC
0284     kmymoney->setUnifiedTitleAndToolBarOnMac(true);
0285     kmymoney->setAttribute(Qt::WA_TranslucentBackground, true);
0286     kmymoney->setWindowFlag(Qt::MacWindowToolBarButtonHint);
0287 #endif
0288 
0289     bool instantQuit = false;
0290 
0291     /**
0292      * enable high dpi icons
0293      */
0294     a.setAttribute(Qt::AA_UseHighDpiPixmaps);
0295 
0296     if (kmymoney->webConnect()->isClient()) {
0297         // If the user launches a second copy of the app and includes a file to
0298         // open, they are probably attempting a "WebConnect" session.  In this case,
0299         // we'll check to make sure it's an importable file that's passed in, and if so, we'll
0300         // notify the primary instance of the file and kill ourselves.
0301 
0302         if (file.isValid()) {
0303             if (kmymoney->isImportableFile(file)) {
0304                 instantQuit = true;
0305                 kmymoney->webConnect()->loadFile(file);
0306             }
0307         }
0308     }
0309 
0310     // show the main window and force it to repaint
0311     kmymoney->show();
0312     qApp->processEvents();
0313 
0314     // now disable input and repaint again
0315     kmymoney->centralWidget()->setEnabled(false);
0316     qApp->processEvents();
0317 
0318     if (!instantQuit) {
0319         QString importfile;
0320         QUrl url;
0321         // make sure, we take the file provided on the command
0322         // line before we go and open the last one used
0323         if (file.isValid()) {
0324             // Check to see if this is an importable file, as opposed to a loadable
0325             // file.  If it is importable, what we really want to do is load the
0326             // last used file anyway and then immediately import this file.  This
0327             // implements a "web connect" session where there is not already an
0328             // instance of the program running.
0329 
0330             if (kmymoney->isImportableFile(file)) {
0331                 importfile = file.path();
0332                 url = QUrl::fromUserInput(kmymoney->readLastUsedFile());
0333             } else {
0334                 url = file;
0335             }
0336 
0337         } else {
0338             url = QUrl::fromUserInput(kmymoney->readLastUsedFile());
0339         }
0340 
0341         if (url.isValid() && !noFile) {
0342             kmymoney->slotFileOpenRecent(url);
0343 
0344         } else if (KMyMoneySettings::firstTimeRun()) {
0345             kmymoney->slotFileNew();
0346         }
0347 
0348         KMyMoneySettings::setFirstTimeRun(false);
0349 
0350         if (!importfile.isEmpty()) {
0351             kmymoney->webConnect(importfile, QByteArray());
0352         }
0353 
0354     } else {
0355         // the instantQuit flag is set, so we force the app to quit right away
0356         kmymoney->slotFileQuit();
0357     }
0358 
0359     kmymoney->centralWidget()->setEnabled(true);
0360 
0361     const int rc = a.exec();      //krazy:exclude=crashy
0362     return rc;
0363 }
0364 
0365 static void migrateConfigFiles()
0366 {
0367     const QString sMainConfigName(QStringLiteral("kmymoneyrc"));
0368     const QString sMainConfigSubdirectory(QStringLiteral("kmymoney/")); // all KMM config files should be in ~/.config/kmymoney/
0369     const QString sMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) +
0370                                     QLatin1Char('/') +
0371                                     sMainConfigSubdirectory;
0372 
0373     if (!QFile::exists(sMainConfigPath + sMainConfigName)) { // if main config file doesn't exist, then it's first run
0374 
0375         // it could be migration from KDE4 to KF5 so prepare list of configuration files to migrate
0376         QStringList sConfigNames
0377         {
0378             sMainConfigName,
0379             QStringLiteral("csvimporterrc"),
0380             QStringLiteral("printcheckpluginrc"),
0381             QStringLiteral("icalendarexportpluginrc"),
0382             QStringLiteral("kbankingrc"),
0383         };
0384 
0385         QFileInfo fileInfo(sMainConfigPath + sMainConfigName);
0386         QDir().mkpath(fileInfo.absolutePath());
0387         const QString sOldMainConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) +
0388                                            QLatin1Char('/');
0389 
0390         // some files have changed their names during switch to KF5, so prepare map for name replacements
0391         QMap<QString, QString> configNamesChange {
0392             {QStringLiteral("printcheckpluginrc"), QStringLiteral("checkprintingrc")},
0393             {QStringLiteral("icalendarexportpluginrc"), QStringLiteral("icalendarexporterrc")},
0394         };
0395 
0396         for (const auto& sConfigName : qAsConst(sConfigNames)) {
0397             const QString sOldConfigFilename = sOldMainConfigPath + sConfigName;
0398             const QString sNewConfigFilename = sMainConfigPath + configNamesChange.value(sConfigName, sConfigName);
0399             if (QFile::exists(sOldConfigFilename)) {
0400                 if (QFile::copy(sOldConfigFilename, sNewConfigFilename))
0401                     QFile::remove(sOldConfigFilename);
0402             }
0403         }
0404     }
0405     KConfig::setMainConfigName(sMainConfigSubdirectory + sMainConfigName); // otherwise it would be ~/.config/kmymoneyrc and not ~/.config/kmymoney/kmymoneyrc
0406 }