Merge pull request #2670 from laanwj/2013_05_datadir
qt: allow user to choose data directory
This commit is contained in:
commit
4eab2dcc81
6 changed files with 671 additions and 44 deletions
|
@ -214,7 +214,8 @@ HEADERS += src/qt/bitcoingui.h \
|
||||||
src/leveldb.h \
|
src/leveldb.h \
|
||||||
src/threadsafety.h \
|
src/threadsafety.h \
|
||||||
src/limitedmap.h \
|
src/limitedmap.h \
|
||||||
src/qt/splashscreen.h
|
src/qt/splashscreen.h \
|
||||||
|
src/qt/intro.h
|
||||||
|
|
||||||
SOURCES += src/qt/bitcoin.cpp \
|
SOURCES += src/qt/bitcoin.cpp \
|
||||||
src/qt/bitcoingui.cpp \
|
src/qt/bitcoingui.cpp \
|
||||||
|
@ -284,7 +285,8 @@ SOURCES += src/qt/bitcoin.cpp \
|
||||||
src/noui.cpp \
|
src/noui.cpp \
|
||||||
src/leveldb.cpp \
|
src/leveldb.cpp \
|
||||||
src/txdb.cpp \
|
src/txdb.cpp \
|
||||||
src/qt/splashscreen.cpp
|
src/qt/splashscreen.cpp \
|
||||||
|
src/qt/intro.cpp
|
||||||
|
|
||||||
RESOURCES += src/qt/bitcoin.qrc
|
RESOURCES += src/qt/bitcoin.qrc
|
||||||
|
|
||||||
|
@ -298,7 +300,8 @@ FORMS += src/qt/forms/sendcoinsdialog.ui \
|
||||||
src/qt/forms/sendcoinsentry.ui \
|
src/qt/forms/sendcoinsentry.ui \
|
||||||
src/qt/forms/askpassphrasedialog.ui \
|
src/qt/forms/askpassphrasedialog.ui \
|
||||||
src/qt/forms/rpcconsole.ui \
|
src/qt/forms/rpcconsole.ui \
|
||||||
src/qt/forms/optionsdialog.ui
|
src/qt/forms/optionsdialog.ui \
|
||||||
|
src/qt/forms/intro.ui
|
||||||
|
|
||||||
contains(USE_QRCODE, 1) {
|
contains(USE_QRCODE, 1) {
|
||||||
HEADERS += src/qt/qrcodedialog.h
|
HEADERS += src/qt/qrcodedialog.h
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "ui_interface.h"
|
#include "ui_interface.h"
|
||||||
#include "paymentserver.h"
|
#include "paymentserver.h"
|
||||||
#include "splashscreen.h"
|
#include "splashscreen.h"
|
||||||
|
#include "intro.h"
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#if QT_VERSION < 0x050000
|
#if QT_VERSION < 0x050000
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED)
|
#if defined(BITCOIN_NEED_QT_PLUGINS) && !defined(_BITCOIN_QT_PLUGINS_INCLUDED)
|
||||||
#define _BITCOIN_QT_PLUGINS_INCLUDED
|
#define _BITCOIN_QT_PLUGINS_INCLUDED
|
||||||
|
@ -110,6 +112,46 @@ static void handleRunawayException(std::exception *e)
|
||||||
exit(1);
|
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
|
#ifndef BITCOIN_QT_TEST
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -130,6 +172,22 @@ int main(int argc, char *argv[])
|
||||||
// Register meta types used for QMetaObject::invokeMethod
|
// Register meta types used for QMetaObject::invokeMethod
|
||||||
qRegisterMetaType< bool* >();
|
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
|
// 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:
|
// ... but do it after creating app, so QCoreApplication::arguments is initialized:
|
||||||
if (PaymentServer::ipcSendCommandLine())
|
if (PaymentServer::ipcSendCommandLine())
|
||||||
|
@ -142,53 +200,15 @@ int main(int argc, char *argv[])
|
||||||
// ... then bitcoin.conf:
|
// ... then bitcoin.conf:
|
||||||
if (!boost::filesystem::is_directory(GetDataDir(false)))
|
if (!boost::filesystem::is_directory(GetDataDir(false)))
|
||||||
{
|
{
|
||||||
// This message can not be translated, as translation is not initialized yet
|
QMessageBox::critical(0, QObject::tr("Bitcoin"),
|
||||||
// (which not yet possible because lang=XX can be overridden in bitcoin.conf in the data directory)
|
QObject::tr("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
|
||||||
QMessageBox::critical(0, "Bitcoin",
|
|
||||||
QString("Error: Specified data directory \"%1\" does not exist.").arg(QString::fromStdString(mapArgs["-datadir"])));
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
ReadConfigFile(mapArgs, mapMultiArgs);
|
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:
|
// ... then GUI settings:
|
||||||
OptionsModel optionsModel;
|
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
|
// Subscribe to global signals from core
|
||||||
uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox);
|
uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox);
|
||||||
uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee);
|
uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee);
|
||||||
|
|
266
src/qt/forms/intro.ui
Normal file
266
src/qt/forms/intro.ui
Normal file
|
@ -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>
|
|
@ -500,7 +500,8 @@ HelpMessageBox::HelpMessageBox(QWidget *parent) :
|
||||||
uiOptions = tr("UI options") + ":\n" +
|
uiOptions = tr("UI options") + ":\n" +
|
||||||
" -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" +
|
" -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" +
|
||||||
" -min " + tr("Start minimized") + "\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"));
|
setWindowTitle(tr("Bitcoin-Qt"));
|
||||||
setTextFormat(Qt::PlainText);
|
setTextFormat(Qt::PlainText);
|
||||||
|
|
270
src/qt/intro.cpp
Normal file
270
src/qt/intro.cpp
Normal file
|
@ -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;
|
||||||
|
}
|
67
src/qt/intro.h
Normal file
67
src/qt/intro.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue