From 832857fc7f995b86b77261cc3f4204b57df66f4c Mon Sep 17 00:00:00 2001 From: fsb4000 Date: Sat, 4 Apr 2015 23:24:19 +0600 Subject: [PATCH] =?utf8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B1=D0=B0=D0=B7=D1=8B=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=B2=20=D0=B8=D0=B7=20GUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- novacoin-qt.pro | 3 + src/qt/bitcoin.cpp | 94 +++++++++------- src/qt/forms/intro.ui | 266 ++++++++++++++++++++++++++++++++++++++++++++ src/qt/guiutil.cpp | 28 +++++ src/qt/intro.cpp | 292 +++++++++++++++++++++++++++++++++++++++++++++++++ src/qt/intro.h | 73 ++++++++++++ 6 files changed, 715 insertions(+), 41 deletions(-) create mode 100644 src/qt/forms/intro.ui create mode 100644 src/qt/intro.cpp create mode 100644 src/qt/intro.h diff --git a/novacoin-qt.pro b/novacoin-qt.pro index de9d411..d43cf89 100644 --- a/novacoin-qt.pro +++ b/novacoin-qt.pro @@ -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 \ diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index e2b6904..f99ee0f 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -11,6 +11,7 @@ #include "init.h" #include "ui_interface.h" #include "qtipcserver.h" +#include "intro.h" #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #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 index 0000000..cfe6069 --- /dev/null +++ b/src/qt/forms/intro.ui @@ -0,0 +1,266 @@ + + + Intro + + + + 0 + 0 + 480 + 288 + + + + Welcome + + + + + + QLabel { font-style:italic; } + + + Welcome to novacoin-qt. + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 15 + + + + + + + + As this is the first time the program is launched, you can choose where novacoin-qt will store its data. + + + true + + + + + + + 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. + + + true + + + + + + + Use the default data directory + + + + + + + Use a custom data directory: + + + + + + + 0 + + + QLayout::SetDefaultConstraint + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 60 + 20 + + + + + + + + QLayout::SetDefaultConstraint + + + + + + + + + + + 0 + 0 + + + + + 30 + 16777215 + + + + … + + + false + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 1 + 0 + + + + + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + Qt::RichText + + + true + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Intro + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + Intro + reject() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 090339b..57f3355 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -26,6 +26,9 @@ #ifndef Q_MOC_RUN #include #include +#if BOOST_FILESYSTEM_VERSION >= 3 +#include +#endif #endif #ifdef WIN32 @@ -46,8 +49,33 @@ #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 index 0000000..fe31dd0 --- /dev/null +++ b/src/qt/intro.cpp @@ -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 + +#include +#include +#include + +/* 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 = "" + QDir::toNativeSeparators("/") + tr("name") + ""; + 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 index 0000000..c973561 --- /dev/null +++ b/src/qt/intro.h @@ -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 +#include +#include + +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 -- 1.7.1