QTfrontend/main.cpp
branchhedgeroid
changeset 15515 7030706266df
parent 14892 a414d37278df
--- a/QTfrontend/main.cpp	Sun Oct 28 15:18:26 2012 +0100
+++ b/QTfrontend/main.cpp	Fri Dec 06 22:20:53 2019 +0100
@@ -1,6 +1,6 @@
 /*
  * Hedgewars, a free turn based strategy game
- * Copyright (c) 2004-2012 Andrey Korotaev <unC0Rr@gmail.com>
+ * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@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
@@ -13,34 +13,52 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
 #include "HWApplication.h"
 
 #include <QTranslator>
 #include <QLocale>
-#include <QMessageBox>
-#include <QPlastiqueStyle>
 #include <QRegExp>
 #include <QMap>
 #include <QSettings>
 #include <QStringListModel>
 #include <QDate>
+#include <QDesktopWidget>
+#include <QLabel>
+#include <QLibraryInfo>
+#include <QStyle>
+#include <QStyleFactory>
 
 #include "hwform.h"
 #include "hwconsts.h"
 #include "newnetclient.h"
 
 #include "DataManager.h"
+#include "FileEngine.h"
+#include "MessageDialog.h"
+
+#include "SDLInteraction.h"
 
 #ifdef _WIN32
 #include <Shlobj.h>
-#endif
-#ifdef __APPLE__
+#elif defined __APPLE__
 #include "CocoaInitializer.h"
+
 #endif
 
+#ifdef Q_OS_WIN
+#include <QSplashScreen>
+#endif
+
+#include <QMessageBox>
+
+// Program resources
+#ifdef __APPLE__
+static CocoaInitializer * cocoaInit = NULL;
+#endif
+static FileEngineHandler * engine = NULL;
 
 //Determines the day of easter in year
 //from http://aa.usno.navy.mil/faq/docs/easter.php,adapted to C/C++
@@ -78,6 +96,10 @@
         season = SEASON_HWBDAY;
         years_since_foundation = date.year() - 2004;
     }
+    else if (date.month() == 4 && date.day() == 1)
+    {
+        season = SEASON_APRIL1;
+    }
     //Easter?
     else if (calculateEaster(date.year()) == date)
         season = SEASON_EASTER;
@@ -85,29 +107,114 @@
         season = SEASON_NONE;
 }
 
+
 bool checkForDir(const QString & dir)
 {
-    QDir tmpdir;
-    if (!tmpdir.exists(dir))
-        if (!tmpdir.mkdir(dir))
+    QDir tmpdir(dir);
+    if (!tmpdir.exists())
+        if (!tmpdir.mkpath(dir))
         {
-            QMessageBox directoryMsg(QApplication::activeWindow());
-            directoryMsg.setIcon(QMessageBox::Warning);
-            directoryMsg.setWindowTitle(QMessageBox::tr("Main - Error"));
-            directoryMsg.setText(QMessageBox::tr("Cannot create directory %1").arg(dir));
-            directoryMsg.setWindowModality(Qt::WindowModal);
-            directoryMsg.exec();
+            MessageDialog::ShowErrorMessage(HWApplication::tr("Cannot create directory %1").arg(dir));
             return false;
         }
     return true;
 }
 
-int main(int argc, char *argv[])
+// Guaranteed to be the last thing ran in the application's life time.
+// Closes resources that need to exist as long as possible.
+void closeResources(void)
+{
+#ifdef __APPLE__
+    if (cocoaInit != NULL)
+    {
+        delete cocoaInit;
+        cocoaInit = NULL;
+    }
+#endif
+    if (engine != NULL)
+    {
+        delete engine;
+        engine = NULL;
+    }
+}
+
+// Simple Message handler that suppresses Qt debug and info messages (qDebug, qInfo).
+// Used when printing command line help (--help) or related error to keep console clean.
+void restrictedMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+    Q_UNUSED(context)
+    QByteArray localMsg = msg.toLocal8Bit();
+    switch (type) {
+    case QtWarningMsg:
+    case QtCriticalMsg:
+    case QtFatalMsg:
+        fprintf(stderr, "%s\n", localMsg.constData());
+        break;
+    default:
+        break;
+    }
+}
+
+QString getUsage()
 {
+    return QString(
+"%1: hedgewars [%2...] [%3]\n"
+"\n"
+"%4:\n"
+"  --help              %5\n"
+"  --config-dir=PATH   %6\n"
+"  --data-dir=PATH     %7\n"
+"\n"
+"%8"
+"\n"
+).arg(
+//: “Usage” as in “how the command-line syntax works”. Shown when running “hedgewars --help” in command-line
+HWApplication::tr("Usage", "command-line")
+).arg(
+//: Name of a command-line argument, shown when running “hedgewars --help” in command-line. “OPTION” as in “command-line option”
+HWApplication::tr("OPTION", "command-line")
+).arg(
+//: Name of a command-line argument, shown when running “hedgewars --help” in command-line
+HWApplication::tr("CONNECTSTRING", "command-line")
+).arg(
+//: “Options” as in “command-line options”
+HWApplication::tr("Options", "command-line")
+).arg(HWApplication::tr("Display this help", "command-line"))
+.arg(HWApplication::tr("Custom path for configuration data and user data", "command-line"))
+.arg(HWApplication::tr("Custom path to the game data folder", "command-line"))
+.arg(HWApplication::tr("Hedgewars can use a %1 (e.g. \"%2\") to connect on start.", "command-line").arg(HWApplication::tr("CONNECTSTRING", "command-line")).arg(QString("hwplay://") + NETGAME_DEFAULT_SERVER));
+}
+
+int main(int argc, char *argv[]) {
+
+    // Since we're calling this first, closeResources() will be the last thing called after main() returns.
+    atexit(closeResources);
+
+#ifdef __APPLE__
+    cocoaInit = new CocoaInitializer(); // Creates the autoreleasepool preventing cocoa object leaks on OS X.
+#endif
+
     HWApplication app(argc, argv);
-
     app.setAttribute(Qt::AA_DontShowIconsInMenus,false);
 
+    // file engine, to be initialized later
+    engine = NULL;
+
+    /*
+    This is for messages frelated to translatable command-line arguments.
+    If it is non-zero, will print out a message after loading locale
+    and exit.
+    */
+    enum cmdMsgStateEnum {
+        cmdMsgNone,
+        cmdMsgHelp,
+        cmdMsgMalformedArg,
+        cmdMsgUnknownArg,
+    };
+    enum cmdMsgStateEnum cmdMsgState = cmdMsgNone;
+    QString cmdMsgStateStr;
+
+    // parse arguments
     QStringList arguments = app.arguments();
     QMap<QString, QString> parsedArgs;
     {
@@ -116,6 +223,7 @@
         {
             QString arg = *i;
 
+
             QRegExp opt("--(\\S+)=(.+)");
             if(opt.exactMatch(arg))
             {
@@ -124,30 +232,85 @@
             }
             else
             {
+                if(arg.startsWith("--")) {
+                    if(arg == "--help")
+                    {
+                        cmdMsgState = cmdMsgHelp;
+                        qInstallMessageHandler(restrictedMessageHandler);
+                    }
+                    else
+                    {
+                        // argument is something wrong
+                        cmdMsgState = cmdMsgMalformedArg;
+                        cmdMsgStateStr = arg;
+                        qInstallMessageHandler(restrictedMessageHandler);
+                        break;
+                    }
+                }
+
+                // if not starting with --, then always skip
+                // (because we can't determine if executable path/call or not - on windows)
                 ++i;
             }
         }
     }
 
-    if(parsedArgs.contains("data-dir"))
+    if(cmdMsgState == cmdMsgNone)
     {
-        QFileInfo f(parsedArgs["data-dir"]);
-        if(!f.exists())
+        if(parsedArgs.contains("data-dir"))
+        {
+            QFileInfo f(parsedArgs["data-dir"]);
+            parsedArgs.remove("data-dir");
+            if(!f.exists())
+            {
+                qWarning() << "WARNING: Cannot open data-dir=" << f.absoluteFilePath();
+            }
+            *cDataDir = f.absoluteFilePath();
+            custom_data = true;
+        }
+
+        if(parsedArgs.contains("config-dir"))
+        {
+            QFileInfo f(parsedArgs["config-dir"]);
+            parsedArgs.remove("config-dir");
+            cfgdir->setPath(f.absoluteFilePath());
+            custom_config = true;
+        }
+        else
         {
-            qWarning() << "WARNING: Cannot open DATA_PATH=" << f.absoluteFilePath();
+            cfgdir->setPath(QDir::homePath());
+            custom_config = false;
+        }
+
+        if (!parsedArgs.isEmpty())
+        {
+            cmdMsgState = cmdMsgUnknownArg;
+            qInstallMessageHandler(restrictedMessageHandler);
         }
-        *cDataDir = f.absoluteFilePath();
-        custom_data = true;
+
+        // end of parameter parsing
+
+        // Select Qt style
+        QStyle* coreStyle;
+        coreStyle = QStyleFactory::create("Windows");
+        if(coreStyle != 0) {
+            QApplication::setStyle(coreStyle);
+            qDebug("Qt style set: Windows");
+        } else {
+            // Windows style should not be missing in Qt5 Base. If it does, something went terribly wrong!
+            qWarning("No Qt style could be set! Using the default one.");
+        }
     }
 
-    if(parsedArgs.contains("config-dir"))
+#ifdef Q_OS_WIN
+    // Splash screen for Windows
+    QPixmap pixmap(":/res/splash.png");
+    QSplashScreen splash(pixmap);
+    if(cmdMsgState == cmdMsgNone)
     {
-        QFileInfo f(parsedArgs["config-dir"]);
-        *cConfigDir = f.absoluteFilePath();
-        custom_config = true;
+        splash.show();
     }
-
-    app.setStyle(new QPlastiqueStyle);
+#endif
 
     QDateTime now = QDateTime::currentDateTime();
     srand(now.toTime_t());
@@ -157,23 +320,18 @@
 
     qRegisterMetaType<HWTeam>("HWTeam");
 
-    bindir->cd("bin"); // workaround over NSIS installer
+    bindir->cd(QCoreApplication::applicationDirPath());
 
-    if(cConfigDir->length() == 0)
-        cfgdir->setPath(cfgdir->homePath());
-    else
-        cfgdir->setPath(*cConfigDir);
-
-    if(cConfigDir->length() == 0)
+    if(custom_config == false)
     {
 #ifdef __APPLE__
         checkForDir(cfgdir->absolutePath() + "/Library/Application Support/Hedgewars");
         cfgdir->cd("Library/Application Support/Hedgewars");
 #elif defined _WIN32
-        char path[1024];
-        if(!SHGetFolderPathA(0, CSIDL_PERSONAL, NULL, 0, path))
+        wchar_t path[MAX_PATH];
+        if(SHGetFolderPathW(0, CSIDL_PERSONAL, NULL, 0, path) == S_OK)
         {
-            cfgdir->cd(path);
+            cfgdir->cd(QString::fromWCharArray(path));
             checkForDir(cfgdir->absolutePath() + "/Hedgewars");
             cfgdir->cd("Hedgewars");
         }
@@ -195,49 +353,121 @@
 
         // config/save paths
         checkForDir(cfgdir->absolutePath() + "/Demos");
+        checkForDir(cfgdir->absolutePath() + "/DrawnMaps");
         checkForDir(cfgdir->absolutePath() + "/Saves");
         checkForDir(cfgdir->absolutePath() + "/Screenshots");
         checkForDir(cfgdir->absolutePath() + "/Teams");
         checkForDir(cfgdir->absolutePath() + "/Logs");
         checkForDir(cfgdir->absolutePath() + "/Videos");
         checkForDir(cfgdir->absolutePath() + "/VideoTemp");
+        checkForDir(cfgdir->absolutePath() + "/VideoThumbnails");
     }
 
     datadir->cd(bindir->absolutePath());
     datadir->cd(*cDataDir);
-    if(!datadir->cd("hedgewars/Data"))
+    if (!datadir->cd("Data"))
     {
-        QMessageBox missingMsg(QApplication::activeWindow());
-        missingMsg.setIcon(QMessageBox::Critical);
-        missingMsg.setWindowTitle(QMessageBox::tr("Main - Error"));
-        missingMsg.setText(QMessageBox::tr("Failed to open data directory:\n%1\n\n"
-                                           "Please check your installation!").
-                                            arg(datadir->absolutePath()+"/hedgewars/Data"));
-        missingMsg.setWindowModality(Qt::WindowModal);
-        missingMsg.exec();
+        MessageDialog::ShowFatalMessage(HWApplication::tr("Failed to open data directory:\n%1\n\nPlease check your installation!").arg(datadir->absolutePath()+"/Data"));
         return 1;
     }
 
-    DataManager & dataMgr = DataManager::instance();
+    bool isProbablyNewPlayer = false;
 
-    QTranslator Translator;
+    // setup PhysFS
+    engine = new FileEngineHandler(argv[0]);
+    engine->mount(datadir->absolutePath());
+    engine->mount(cfgdir->absolutePath() + "/Data");
+    engine->mount(cfgdir->absolutePath());
+    engine->setWriteDir(cfgdir->absolutePath());
+    engine->mountPacks();
+
+    QTranslator TranslatorHedgewars;
+    QTranslator TranslatorQt;
+    QSettings settings(DataManager::instance().settingsFileName(), QSettings::IniFormat);
+    settings.setIniCodec("UTF-8");
     {
-        QSettings settings(cfgdir->absolutePath() + "/hedgewars.ini", QSettings::IniFormat);
         QString cc = settings.value("misc/locale", QString()).toString();
-        if(cc.isEmpty())
+        if (cc.isEmpty())
+        {
             cc = QLocale::system().name();
+            qDebug("Detected system locale: %s", qPrintable(cc));
 
-        // load locale file into translator
-        Translator.load(
-            dataMgr.findFileForRead(
-                QString("Locale/hedgewars_" + cc)
-            )
-        );
-        app.installTranslator(&Translator);
+            // Fallback to current input locale if "C" locale is returned
+            if(cc == "C")
+                cc = HWApplication::inputMethod()->locale().name();
+        }
+        else
+        {
+            qDebug("Configured frontend locale: %s", qPrintable(cc));
+        }
+        QLocale::setDefault(cc);
+        QString defaultLocaleName = QLocale().name();
+        qDebug("Frontend uses locale: %s", qPrintable(defaultLocaleName));
+
+        if (defaultLocaleName != "C")
+        {
+            // Load locale files into translators
+            if (!TranslatorHedgewars.load(QLocale(), "hedgewars", "_", QString("physfs://Locale")))
+                qWarning("Failed to install Hedgewars translation (%s)", qPrintable(defaultLocaleName));
+            if (!TranslatorQt.load(QLocale(), "qt", "_", QString(QLibraryInfo::location(QLibraryInfo::TranslationsPath))))
+                qWarning("Failed to install Qt translation (%s)", qPrintable(defaultLocaleName));
+            app.installTranslator(&TranslatorHedgewars);
+            app.installTranslator(&TranslatorQt);
+        }
+        app.setLayoutDirection(QLocale().textDirection());
+
+        // Handle command line messages
+        switch(cmdMsgState)
+        {
+            case cmdMsgHelp:
+            {
+                printf("%s", getUsage().toUtf8().constData());
+                return 0;
+            }
+            case cmdMsgMalformedArg:
+            {
+                fprintf(stderr, "%s\n\n%s",
+                    HWApplication::tr("Malformed option argument: %1", "command-line").arg(cmdMsgStateStr).toUtf8().constData(),
+                    getUsage().toUtf8().constData());
+                return 1;
+            }
+            case cmdMsgUnknownArg:
+            {
+                foreach (const QString & key, parsedArgs.keys())
+                {
+                    fprintf(stderr, "%s\n",
+                        HWApplication::tr("Unknown option argument: %1", "command-line").arg(QString("--") + key).toUtf8().constData());
+                }
+                fprintf(stderr, "\n%s", getUsage().toUtf8().constData());
+                return 1;
+            }
+            default:
+            {
+                break;
+            }
+        }
+
+        // Heuristic to figure out if the user is (probably) a first-time player.
+        // If nickname is not set, then probably yes.
+        // The hidden setting firstLaunch is, if present, used to force HW to
+        // treat iself as if it were launched the first time.
+        QString nick = settings.value("net/nick", QString()).toString();
+        if (settings.contains("frontend/firstLaunch"))
+        {
+            isProbablyNewPlayer = settings.value("frontend/firstLaunch").toBool();
+        }
+        else
+        {
+            isProbablyNewPlayer = nick.isNull();
+        }
+
+        // Set firstLaunch to false to make sure we remember we have been launched before.
+        settings.setValue("frontend/firstLaunch", false);
     }
 
 #ifdef _WIN32
-    // Win32 registry setup (used for xfire detection etc. - don't set it if we're running in "portable" mode with a custom config dir)
+    // Win32 registry setup (used for external software detection etc.
+    // don't set it if running in "portable" mode with a custom config dir)
     if(!custom_config)
     {
         QSettings registry_hklm("HKEY_LOCAL_MACHINE", QSettings::NativeFormat);
@@ -245,24 +475,30 @@
         registry_hklm.setValue("Software/Hedgewars/Path", bindir->absolutePath().replace("/", "\\"));
     }
 #endif
-#ifdef __APPLE__
-    // this creates the autoreleasepool that prevents leaking
-    CocoaInitializer initializer;
-#endif
+
+    SDLInteraction::instance();
 
     QString style = "";
     QString fname;
 
-    checkSeason();
-    //For each season, there is an extra stylesheet
-    //Todo: change background for easter and birthday
-    //(simply replace res/BackgroundBirthday.png and res/BackgroundEaster.png
-    //with an appropriate background
+    bool holidaySilliness = settings.value("misc/holidaySilliness", true).toBool();
+    if(holidaySilliness)
+        checkSeason();
+    else
+        season = SEASON_NONE;
+
+    // For each season, there is an extra stylesheet.
+    // TODO: change background for easter
+    // (simply replace res/BackgroundEaster.png
+    // with an appropriate background).
     switch (season)
     {
         case SEASON_CHRISTMAS :
             fname = "christmas.css";
             break;
+        case SEASON_APRIL1 :
+            fname = "april1.css";
+            break;
         case SEASON_EASTER :
             fname = "easter.css";
             break;
@@ -271,10 +507,11 @@
             break;
         default :
             fname = "qt.css";
+            break;
     }
 
     // load external stylesheet if there is any
-    QFile extFile(dataMgr.findFileForRead("css/" + fname));
+    QFile extFile("physfs://css/" + fname);
 
     QFile resFile(":/res/css/" + fname);
 
@@ -283,7 +520,34 @@
     if (file.open(QIODevice::ReadOnly | QIODevice::Text))
         style.append(file.readAll());
 
+    qWarning("Starting Hedgewars %s-r%d (%s)", qPrintable(*cVersionString), cRevisionString->toInt(), qPrintable(*cHashString));
+
     app.form = new HWForm(NULL, style);
+#ifdef Q_OS_WIN
+    if(cmdMsgState == cmdMsgNone)
+        splash.finish(app.form);
+#endif
     app.form->show();
+
+    // Show welcome message for (suspected) first-time player and
+    // point towards the Training menu.
+    if(isProbablyNewPlayer) {
+        QMessageBox questionTutorialMsg(app.form);
+        questionTutorialMsg.setIcon(QMessageBox::Question);
+        questionTutorialMsg.setWindowTitle(QMessageBox::tr("Welcome to Hedgewars"));
+        questionTutorialMsg.setText(QMessageBox::tr("Welcome to Hedgewars!\n\nYou seem to be new around here. Would you like to play some training missions first to learn the basics of Hedgewars?"));
+        questionTutorialMsg.setTextFormat(Qt::PlainText);
+        questionTutorialMsg.setWindowModality(Qt::WindowModal);
+        questionTutorialMsg.addButton(QMessageBox::Yes);
+        questionTutorialMsg.addButton(QMessageBox::No);
+
+        int answer = questionTutorialMsg.exec();
+        if (answer == QMessageBox::Yes) {
+            app.form->GoToTraining();
+        }
+    }
+
+    if (app.urlString)
+        app.fakeEvent();
     return app.exec();
 }