выбор папки для базы блоков из GUI 193/head
authorfsb4000 <fsb4000@yandex.ru>
Sat, 4 Apr 2015 17:24:19 +0000 (23:24 +0600)
committerfsb4000 <fsb4000@yandex.ru>
Sat, 4 Apr 2015 17:24:19 +0000 (23:24 +0600)
novacoin-qt.pro
src/qt/bitcoin.cpp
src/qt/forms/intro.ui [new file with mode: 0644]
src/qt/guiutil.cpp
src/qt/intro.cpp [new file with mode: 0644]
src/qt/intro.h [new file with mode: 0644]

index de9d411..d43cf89 100644 (file)
@@ -183,6 +183,7 @@ QMAKE_CXXFLAGS_WARN_ON = -fdiagnostics-show-option -Wall -Wextra -Wno-ignored-qu
 # Input
 DEPENDPATH += src src/json src/qt
 HEADERS += src/qt/bitcoingui.h \
+    src/qt/intro.h \
     src/qt/transactiontablemodel.h \
     src/qt/addresstablemodel.h \
     src/qt/optionsdialog.h \
@@ -273,6 +274,7 @@ HEADERS += src/qt/bitcoingui.h \
     src/qt/multisigdialog.h
 
 SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
+    src/qt/intro.cpp \
     src/qt/transactiontablemodel.cpp \
     src/qt/addresstablemodel.cpp \
     src/qt/optionsdialog.cpp \
@@ -350,6 +352,7 @@ RESOURCES += \
     src/qt/bitcoin.qrc
 
 FORMS += \
+    src/qt/forms/intro.ui \
     src/qt/forms/coincontroldialog.ui \
     src/qt/forms/sendcoinsdialog.ui \
     src/qt/forms/addressbookpage.ui \
index e2b6904..f99ee0f 100644 (file)
@@ -11,6 +11,7 @@
 #include "init.h"
 #include "ui_interface.h"
 #include "qtipcserver.h"
+#include "intro.h"
 
 #include <QApplication>
 #include <QMessageBox>
@@ -21,6 +22,7 @@
 #include <QTranslator>
 #include <QSplashScreen>
 #include <QLibraryInfo>
+#include <QSettings>
 
 #if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED)
 #define _BITCOIN_QT_PLUGINS_INCLUDED
@@ -37,6 +39,39 @@ Q_IMPORT_PLUGIN(qtaccessiblewidgets)
 static BitcoinGUI *guiref;
 static QSplashScreen *splashref;
 
+/** Set up translations */
+static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator)
+{
+    QSettings settings;
+    // Get desired locale (e.g. "de_DE")
+    // 1) System default language
+    QString lang_territory = QLocale::system().name();
+    // 2) Language from QSettings
+    QString lang_territory_qsettings = settings.value("language", "").toString();
+    if(!lang_territory_qsettings.isEmpty())
+        lang_territory = lang_territory_qsettings;
+    // 3) -lang command line argument
+    lang_territory = QString::fromStdString(GetArg("-lang", lang_territory.toStdString()));
+    // Convert to "de" only by truncating "_DE"
+    QString lang = lang_territory;
+    lang.truncate(lang_territory.lastIndexOf('_'));
+    // Load language files for configured locale:
+    // - First load the translator for the base language, without territory
+    // - Then load the more specific locale translator
+    // Load e.g. qt_de.qm
+    if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
+        QApplication::installTranslator(&qtTranslatorBase);
+    // Load e.g. qt_de_DE.qm
+    if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
+        QApplication::installTranslator(&qtTranslator);
+    // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc)
+    if (translatorBase.load(lang, ":/translations/"))
+        QApplication::installTranslator(&translatorBase);
+    // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc)
+    if (translator.load(lang_territory, ":/translations/"))
+        QApplication::installTranslator(&translator);
+}
+
 static void ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style)
 {
     // Message from network thread
@@ -125,62 +160,39 @@ int main(int argc, char *argv[])
     Q_INIT_RESOURCE(bitcoin);
     QApplication app(argc, argv);
 
-    // Install global event filter that makes sure that long tooltips can be word-wrapped
-    app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
+    // Application identification (must be set before OptionsModel is initialized,
+    // as it is used to locate QSettings)
+    app.setOrganizationName("NovaCoin");
+    app.setOrganizationDomain("novacoin.su");
+    if(GetBoolArg("-testnet")) // Separate UI settings for testnet
+        app.setApplicationName("NovaCoin-Qt-testnet");
+    else
+        app.setApplicationName("NovaCoin-Qt");
+    // Now that QSettings are accessible, initialize translations
+    QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
+    initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
 
     // Command-line options take precedence:
     ParseParameters(argc, argv);
 
+    // User language is set up: pick a data directory
+    Intro::pickDataDirectory();
+
+    // Install global event filter that makes sure that long tooltips can be word-wrapped
+    app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app));
+
     // ... then bitcoin.conf:
     if (!boost::filesystem::is_directory(GetDataDir(false)))
     {
-        // This message can not be translated, as translation is not initialized yet
-        // (which not yet possible because lang=XX can be overridden in bitcoin.conf in the data directory)
         QMessageBox::critical(0, "NovaCoin",
-                              QString("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
+                              QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
         return 1;
     }
     ReadConfigFile(mapArgs, mapMultiArgs);
 
-    // Application identification (must be set before OptionsModel is initialized,
-    // as it is used to locate QSettings)
-    app.setOrganizationName("NovaCoin");
-    app.setOrganizationDomain("novacoin.su");
-    if(GetBoolArg("-testnet")) // Separate UI settings for testnet
-        app.setApplicationName("NovaCoin-Qt-testnet");
-    else
-        app.setApplicationName("NovaCoin-Qt");
-
     // ... then GUI settings:
     OptionsModel optionsModel;
 
-    // Get desired locale (e.g. "de_DE") from command line or use system locale
-    QString lang_territory = QString::fromStdString(GetArg("-lang", QLocale::system().name().toStdString()));
-    QString lang = lang_territory;
-    // Convert to "de" only by truncating "_DE"
-    lang.truncate(lang_territory.lastIndexOf('_'));
-
-    QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
-    // Load language files for configured locale:
-    // - First load the translator for the base language, without territory
-    // - Then load the more specific locale translator
-
-    // Load e.g. qt_de.qm
-    if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
-        app.installTranslator(&qtTranslatorBase);
-
-    // Load e.g. qt_de_DE.qm
-    if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
-        app.installTranslator(&qtTranslator);
-
-    // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc)
-    if (translatorBase.load(lang, ":/translations/"))
-        app.installTranslator(&translatorBase);
-
-    // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc)
-    if (translator.load(lang_territory, ":/translations/"))
-        app.installTranslator(&translator);
-
     // Subscribe to global signals from core
     uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox);
     uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee);
diff --git a/src/qt/forms/intro.ui b/src/qt/forms/intro.ui
new file mode 100644 (file)
index 0000000..cfe6069
--- /dev/null
@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Intro</class>
+ <widget class="QDialog" name="Intro">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>480</width>
+    <height>288</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Welcome</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="styleSheet">
+      <string notr="true">QLabel { font-style:italic; }</string>
+     </property>
+     <property name="text">
+      <string>Welcome to novacoin-qt.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer_4">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Minimum</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>15</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_4">
+     <property name="text">
+      <string>As this is the first time the program is launched, you can choose where novacoin-qt will store its data.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="sizeWarningLabel">
+     <property name="text">
+      <string>novacoin-qt will download and store a copy of the Novacoin block chain. At least %1GB of data will be stored in this directory, and it will grow over time. The wallet will also be stored in this directory.</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="dataDirDefault">
+     <property name="text">
+      <string>Use the default data directory</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QRadioButton" name="dataDirCustom">
+     <property name="text">
+      <string>Use a custom data directory:</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeType">
+        <enum>QSizePolicy::Fixed</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>60</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <property name="sizeConstraint">
+        <enum>QLayout::SetDefaultConstraint</enum>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <widget class="QLineEdit" name="dataDirectory"/>
+         </item>
+         <item>
+          <widget class="QPushButton" name="ellipsisButton">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>30</width>
+             <height>16777215</height>
+            </size>
+           </property>
+           <property name="text">
+            <string notr="true">…</string>
+           </property>
+           <property name="autoDefault">
+            <bool>false</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Fixed</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>5</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="freeSpace">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>1</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Fixed</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>5</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="errorMessage">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::RichText</enum>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>Intro</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>Intro</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>20</x>
+     <y>20</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index 090339b..57f3355 100644 (file)
@@ -26,6 +26,9 @@
 #ifndef Q_MOC_RUN
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
+#if BOOST_FILESYSTEM_VERSION >= 3
+#include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
+#endif
 #endif
 
 #ifdef WIN32
 #include "shellapi.h"
 #endif
 
+#if BOOST_FILESYSTEM_VERSION >= 3
+static boost::filesystem::detail::utf8_codecvt_facet utf8;
+#endif
+
 namespace GUIUtil {
 
+#if BOOST_FILESYSTEM_VERSION >= 3
+boost::filesystem::path qstringToBoostPath(const QString &path)
+{
+    return boost::filesystem::path(path.toStdString(), utf8);
+}
+QString boostPathToQString(const boost::filesystem::path &path)
+{
+    return QString::fromStdString(path.string(utf8));
+}
+#else
+#warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older
+boost::filesystem::path qstringToBoostPath(const QString &path)
+{
+    return boost::filesystem::path(path.toStdString());
+}
+QString boostPathToQString(const boost::filesystem::path &path)
+{
+    return QString::fromStdString(path.string());
+}
+#endif
+
 QString dateTimeStr(const QDateTime &date)
 {
     return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
new file mode 100644 (file)
index 0000000..fe31dd0
--- /dev/null
@@ -0,0 +1,292 @@
+// Copyright (c) 2011-2014 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "intro.h"
+#include "ui_intro.h"
+
+#include "guiutil.h"
+
+#include "util.h"
+
+#include <boost/filesystem.hpp>
+
+#include <QFileDialog>
+#include <QSettings>
+#include <QMessageBox>
+
+/* Minimum free space (in bytes) needed for data directory */
+static const uint64_t GB_BYTES = 1000000000LL;
+static const uint64_t BLOCK_CHAIN_SIZE = 1LL * GB_BYTES;
+
+/* Check free space asynchronously to prevent hanging the UI thread.
+
+   Up to one request to check a path is in flight to this thread; when the check()
+   function runs, the current path is requested from the associated Intro object.
+   The reply is sent back through a signal.
+
+   This ensures that no queue of checking requests is built up while the user is
+   still entering the path, and that always the most recently entered path is checked as
+   soon as the thread becomes available.
+*/
+class FreespaceChecker : public QObject
+{
+    Q_OBJECT
+
+public:
+    FreespaceChecker(Intro *intro);
+
+    enum Status {
+        ST_OK,
+        ST_ERROR
+    };
+
+public slots:
+    void check();
+
+signals:
+    void reply(int status, const QString &message, quint64 available);
+
+private:
+    Intro *intro;
+};
+
+#include "intro.moc"
+
+FreespaceChecker::FreespaceChecker(Intro *intro)
+{
+    this->intro = intro;
+}
+
+void FreespaceChecker::check()
+{
+    namespace fs = boost::filesystem;
+    QString dataDirStr = intro->getPathToCheck();
+    fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
+    uint64_t freeBytesAvailable = 0;
+    int replyStatus = ST_OK;
+    QString replyMessage = tr("A new data directory will be created.");
+
+    /* Find first parent that exists, so that fs::space does not fail */
+    fs::path parentDir = dataDir;
+    fs::path parentDirOld = fs::path();
+    while(parentDir.has_parent_path() && !fs::exists(parentDir))
+    {
+        parentDir = parentDir.parent_path();
+
+        /* Check if we make any progress, break if not to prevent an infinite loop here */
+        if (parentDirOld == parentDir)
+            break;
+
+        parentDirOld = parentDir;
+    }
+
+    try {
+        freeBytesAvailable = fs::space(parentDir).available;
+        if(fs::exists(dataDir))
+        {
+            if(fs::is_directory(dataDir))
+            {
+                QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
+                replyStatus = ST_OK;
+                replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
+            } else {
+                replyStatus = ST_ERROR;
+                replyMessage = tr("Path already exists, and is not a directory.");
+            }
+        }
+    } catch (const fs::filesystem_error&)
+    {
+        /* Parent directory does not exist or is not accessible */
+        replyStatus = ST_ERROR;
+        replyMessage = tr("Cannot create data directory here.");
+    }
+    emit reply(replyStatus, replyMessage, freeBytesAvailable);
+}
+
+
+Intro::Intro(QWidget *parent) :
+    QDialog(parent),
+    ui(new Ui::Intro),
+    thread(0),
+    signalled(false)
+{
+    ui->setupUi(this);
+    ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
+    startThread();
+}
+
+Intro::~Intro()
+{
+    delete ui;
+    /* Ensure thread is finished before it is deleted */
+    emit stopThread();
+    thread->wait();
+}
+
+QString Intro::getDataDirectory()
+{
+    return ui->dataDirectory->text();
+}
+
+void Intro::setDataDirectory(const QString &dataDir)
+{
+    ui->dataDirectory->setText(dataDir);
+    if(dataDir == getDefaultDataDirectory())
+    {
+        ui->dataDirDefault->setChecked(true);
+        ui->dataDirectory->setEnabled(false);
+        ui->ellipsisButton->setEnabled(false);
+    } else {
+        ui->dataDirCustom->setChecked(true);
+        ui->dataDirectory->setEnabled(true);
+        ui->ellipsisButton->setEnabled(true);
+    }
+}
+
+QString Intro::getDefaultDataDirectory()
+{
+    return GUIUtil::boostPathToQString(GetDefaultDataDir());
+}
+
+void Intro::pickDataDirectory()
+{
+    namespace fs = boost::filesystem;
+    QSettings settings;
+    /* If data directory provided on command line, no need to look at settings
+       or show a picking dialog */
+    if(!GetArg("-datadir", "").empty())
+        return;
+    /* 1) Default data directory for operating system */
+    QString dataDir = getDefaultDataDirectory();
+    /* 2) Allow QSettings to override default dir */
+    dataDir = settings.value("strDataDir", dataDir).toString();
+
+    if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || GetBoolArg("-choosedatadir", false))
+    {
+        /* If current default data directory does not exist, let the user choose one */
+        Intro intro;
+        intro.setDataDirectory(dataDir);
+        intro.setWindowIcon(QIcon(":icons/bitcoin"));
+
+        while(true)
+        {
+            if(!intro.exec())
+            {
+                /* Cancel clicked */
+                exit(0);
+            }
+            dataDir = intro.getDataDirectory();
+            try {
+                boost::filesystem::create_directory(GUIUtil::qstringToBoostPath(dataDir));
+                break;
+            } catch (const fs::filesystem_error&) {
+                QMessageBox::critical(0, tr("Novacoin-qt"),
+                    tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
+                /* fall through, back to choosing screen */
+            }
+        }
+
+        settings.setValue("strDataDir", dataDir);
+    }
+    /* Only override -datadir if different from the default, to make it possible to
+     * override -datadir in the bitcoin.conf file in the default data directory
+     * (to be consistent with bitcoind behavior)
+     */
+    if(dataDir != getDefaultDataDirectory())
+        SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
+}
+
+void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
+{
+    switch(status)
+    {
+    case FreespaceChecker::ST_OK:
+        ui->errorMessage->setText(message);
+        ui->errorMessage->setStyleSheet("");
+        break;
+    case FreespaceChecker::ST_ERROR:
+        ui->errorMessage->setText(tr("Error") + ": " + message);
+        ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
+        break;
+    }
+    /* Indicate number of bytes available */
+    if(status == FreespaceChecker::ST_ERROR)
+    {
+        ui->freeSpace->setText("");
+    } else {
+        QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
+        if(bytesAvailable < BLOCK_CHAIN_SIZE)
+        {
+            freeString += " " + tr("(of %n GB needed)", "", BLOCK_CHAIN_SIZE/GB_BYTES);
+            ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
+        } else {
+            ui->freeSpace->setStyleSheet("");
+        }
+        ui->freeSpace->setText(freeString + ".");
+    }
+    /* Don't allow confirm in ERROR state */
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
+}
+
+void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
+{
+    /* Disable OK button until check result comes in */
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
+    checkPath(dataDirStr);
+}
+
+void Intro::on_ellipsisButton_clicked()
+{
+    QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
+    if(!dir.isEmpty())
+        ui->dataDirectory->setText(dir);
+}
+
+void Intro::on_dataDirDefault_clicked()
+{
+    setDataDirectory(getDefaultDataDirectory());
+}
+
+void Intro::on_dataDirCustom_clicked()
+{
+    ui->dataDirectory->setEnabled(true);
+    ui->ellipsisButton->setEnabled(true);
+}
+
+void Intro::startThread()
+{
+    thread = new QThread(this);
+    FreespaceChecker *executor = new FreespaceChecker(this);
+    executor->moveToThread(thread);
+
+    connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
+    connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
+    /*  make sure executor object is deleted in its own thread */
+    connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
+    connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
+
+    thread->start();
+}
+
+void Intro::checkPath(const QString &dataDir)
+{
+    mutex.lock();
+    pathToCheck = dataDir;
+    if(!signalled)
+    {
+        signalled = true;
+        emit requestCheck();
+    }
+    mutex.unlock();
+}
+
+QString Intro::getPathToCheck()
+{
+    QString retval;
+    mutex.lock();
+    retval = pathToCheck;
+    signalled = false; /* new request can be queued now */
+    mutex.unlock();
+    return retval;
+}
diff --git a/src/qt/intro.h b/src/qt/intro.h
new file mode 100644 (file)
index 0000000..c973561
--- /dev/null
@@ -0,0 +1,73 @@
+// Copyright (c) 2011-2013 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_QT_INTRO_H
+#define BITCOIN_QT_INTRO_H
+
+#include <QDialog>
+#include <QMutex>
+#include <QThread>
+
+class FreespaceChecker;
+
+namespace Ui {
+    class Intro;
+}
+
+/** Introduction screen (pre-GUI startup).
+  Allows the user to choose a data directory,
+  in which the wallet and block chain will be stored.
+ */
+class Intro : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit Intro(QWidget *parent = 0);
+    ~Intro();
+
+    QString getDataDirectory();
+    void setDataDirectory(const QString &dataDir);
+
+    /**
+     * Determine data directory. Let the user choose if the current one doesn't exist.
+     *
+     * @note do NOT call global GetDataDir() before calling this function, this
+     * will cause the wrong path to be cached.
+     */
+    static void pickDataDirectory();
+
+    /**
+     * Determine default data directory for operating system.
+     */
+    static QString getDefaultDataDirectory();
+
+signals:
+    void requestCheck();
+    void stopThread();
+
+public slots:
+    void setStatus(int status, const QString &message, quint64 bytesAvailable);
+
+private slots:
+    void on_dataDirectory_textChanged(const QString &arg1);
+    void on_ellipsisButton_clicked();
+    void on_dataDirDefault_clicked();
+    void on_dataDirCustom_clicked();
+
+private:
+    Ui::Intro *ui;
+    QThread *thread;
+    QMutex mutex;
+    bool signalled;
+    QString pathToCheck;
+
+    void startThread();
+    void checkPath(const QString &dataDir);
+    QString getPathToCheck();
+
+    friend class FreespaceChecker;
+};
+
+#endif // BITCOIN_QT_INTRO_H