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