aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/qt/bitcoin.cpp100
-rw-r--r--src/qt/forms/intro.ui266
-rw-r--r--src/qt/guiutil.cpp3
-rw-r--r--src/qt/intro.cpp270
-rw-r--r--src/qt/intro.h67
5 files changed, 665 insertions, 41 deletions
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 76e88b36a7..b0c45d6863 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -15,6 +15,7 @@
#include "ui_interface.h"
#include "paymentserver.h"
#include "splashscreen.h"
+#include "intro.h"
#include <QMessageBox>
#if QT_VERSION < 0x050000
@@ -24,6 +25,7 @@
#include <QTimer>
#include <QTranslator>
#include <QLibraryInfo>
+#include <QSettings>
#if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED)
#define _BITCOIN_QT_PLUGINS_INCLUDED
@@ -110,6 +112,46 @@ static void handleRunawayException(std::exception *e)
exit(1);
}
+/** 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);
+}
+
#ifndef BITCOIN_QT_TEST
int main(int argc, char *argv[])
{
@@ -130,6 +172,22 @@ int main(int argc, char *argv[])
// Register meta types used for QMetaObject::invokeMethod
qRegisterMetaType< bool* >();
+ // Application identification (must be set before OptionsModel is initialized,
+ // as it is used to locate QSettings)
+ QApplication::setOrganizationName("Bitcoin");
+ QApplication::setOrganizationDomain("bitcoin.org");
+ if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet
+ QApplication::setApplicationName("Bitcoin-Qt-testnet");
+ else
+ QApplication::setApplicationName("Bitcoin-Qt");
+
+ // Now that QSettings are accessible, initialize translations
+ QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator;
+ initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator);
+
+ // User language is set up: pick a data directory
+ Intro::pickDataDirectory();
+
// Do this early as we don't want to bother initializing if we are just calling IPC
// ... but do it after creating app, so QCoreApplication::arguments is initialized:
if (PaymentServer::ipcSendCommandLine())
@@ -142,53 +200,15 @@ int main(int argc, char *argv[])
// ... 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, "Bitcoin",
- QString("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
+ QMessageBox::critical(0, QObject::tr("Bitcoin"),
+ 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)
- QApplication::setOrganizationName("Bitcoin");
- QApplication::setOrganizationDomain("bitcoin.org");
- if (GetBoolArg("-testnet", false)) // Separate UI settings for testnet
- QApplication::setApplicationName("Bitcoin-Qt-testnet");
- else
- QApplication::setApplicationName("Bitcoin-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
index 0000000000..0f6ae5a7d0
--- /dev/null
+++ b/src/qt/forms/intro.ui
@@ -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>674</width>
+ <height>363</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 Bitcoin-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 Bitcoin-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>Bitcoin-Qt will download and store a copy of the Bitcoin 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>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>Intro</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 88a6e7226e..3d1e91efdc 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -500,7 +500,8 @@ HelpMessageBox::HelpMessageBox(QWidget *parent) :
uiOptions = tr("UI options") + ":\n" +
" -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" +
" -min " + tr("Start minimized") + "\n" +
- " -splash " + tr("Show splash screen on startup (default: 1)") + "\n";
+ " -splash " + tr("Show splash screen on startup (default: 1)") + "\n" +
+ " -choosedatadir " + tr("Choose data directory on startup (default: 0)") + "\n";
setWindowTitle(tr("Bitcoin-Qt"));
setTextFormat(Qt::PlainText);
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
new file mode 100644
index 0000000000..51f3c812e4
--- /dev/null
+++ b/src/qt/intro.cpp
@@ -0,0 +1,270 @@
+#include "intro.h"
+#include "ui_intro.h"
+#include "util.h"
+
+#include <QFileDialog>
+#include <QSettings>
+#include <QMessageBox>
+
+#include <boost/filesystem.hpp>
+
+/* Minimum free space (in bytes) needed for data directory */
+static const uint64 GB_BYTES = 1000000000LL;
+static const uint64 BLOCK_CHAIN_SIZE = 10LL * 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 = fs::path(dataDirStr.toStdString());
+ uint64 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;
+ while(parentDir.has_parent_path() && !fs::exists(parentDir))
+ {
+ parentDir = parentDir.parent_path();
+ }
+
+ try {
+ freeBytesAvailable = fs::space(parentDir).available;
+ if(fs::exists(dataDir))
+ {
+ if(fs::is_directory(dataDir))
+ {
+ QString separator = QDir::toNativeSeparators("/");
+ replyStatus = ST_OK;
+ replyMessage = tr("Directory already exists. Add <code>%1name</code> 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(fs::filesystem_error &e)
+ {
+ /* 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 QString::fromStdString(GetDefaultDataDir().string());
+}
+
+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(dataDir.toStdString()) || GetBoolArg("-choosedatadir", false))
+ {
+ /* If current default data directory does not exist, let the user choose one */
+ Intro intro;
+ intro.setDataDirectory(dataDir);
+ while(true)
+ {
+ if(!intro.exec())
+ {
+ /* Cancel clicked */
+ exit(0);
+ }
+ dataDir = intro.getDataDirectory();
+ try {
+ fs::create_directory(dataDir.toStdString());
+ break;
+ } catch(fs::filesystem_error &e) {
+ QMessageBox::critical(0, QObject::tr("Bitcoin"),
+ QObject::tr("Error: Specified data directory \"%1\" can not be created.").arg(dataDir));
+ /* fall through, back to choosing screen */
+ }
+ }
+
+ settings.setValue("strDataDir", dataDir);
+ }
+ SoftSetArg("-datadir", dataDir.toStdString());
+}
+
+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 = QString::number(bytesAvailable/GB_BYTES) + tr("GB of free space available");
+ if(bytesAvailable < BLOCK_CHAIN_SIZE)
+ {
+ freeString += " " + tr("(of %1GB needed)").arg(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 = 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
index 0000000000..b246c65a82
--- /dev/null
+++ b/src/qt/intro.h
@@ -0,0 +1,67 @@
+#ifndef INTRO_H
+#define INTRO_H
+
+#include <QDialog>
+#include <QThread>
+#include <QMutex>
+
+namespace Ui {
+class Intro;
+}
+class FreespaceChecker;
+
+/** 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 // INTRO_H