mirror of https://github.com/lukechilds/komodo.git
Wladimir J. van der Laan
11 years ago
6 changed files with 671 additions and 44 deletions
@ -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> |
@ -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; |
||||
|
} |
@ -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 new issue