be77b637fc
This adds an introduction screen that is shown when the client is first started in which the user can choose a data directory. It is also possible to force the intro screen to appear using command line argument `-choosedatadir`. The user is warned that the client will download and store 10Gb of data. The intro screen shows how much space is available on the device that contains the chosen directory and warns if this is less than the 10Gb. To make it possible to translate the introduction dialog, the initialization sequence is changed so that translations are loaded before the data directory. This has the by-effect that it is no longer possible to specify a language in bitcoin.conf inside the data directory.
532 lines
16 KiB
C++
532 lines
16 KiB
C++
#include <QApplication>
|
|
|
|
#include "guiutil.h"
|
|
|
|
#include "bitcoinaddressvalidator.h"
|
|
#include "walletmodel.h"
|
|
#include "bitcoinunits.h"
|
|
|
|
#include "util.h"
|
|
#include "init.h"
|
|
|
|
#include <QDateTime>
|
|
#include <QDoubleValidator>
|
|
#include <QFont>
|
|
#include <QLineEdit>
|
|
#if QT_VERSION >= 0x050000
|
|
#include <QUrlQuery>
|
|
#else
|
|
#include <QUrl>
|
|
#endif
|
|
#include <QTextDocument> // for Qt::mightBeRichText
|
|
#include <QAbstractItemView>
|
|
#include <QClipboard>
|
|
#include <QFileDialog>
|
|
#include <QDesktopServices>
|
|
#include <QThread>
|
|
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/filesystem/fstream.hpp>
|
|
|
|
#ifdef WIN32
|
|
#ifdef _WIN32_WINNT
|
|
#undef _WIN32_WINNT
|
|
#endif
|
|
#define _WIN32_WINNT 0x0501
|
|
#ifdef _WIN32_IE
|
|
#undef _WIN32_IE
|
|
#endif
|
|
#define _WIN32_IE 0x0501
|
|
#define WIN32_LEAN_AND_MEAN 1
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include "shlwapi.h"
|
|
#include "shlobj.h"
|
|
#include "shellapi.h"
|
|
#endif
|
|
|
|
namespace GUIUtil {
|
|
|
|
QString dateTimeStr(const QDateTime &date)
|
|
{
|
|
return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
|
|
}
|
|
|
|
QString dateTimeStr(qint64 nTime)
|
|
{
|
|
return dateTimeStr(QDateTime::fromTime_t((qint32)nTime));
|
|
}
|
|
|
|
QFont bitcoinAddressFont()
|
|
{
|
|
QFont font("Monospace");
|
|
font.setStyleHint(QFont::TypeWriter);
|
|
return font;
|
|
}
|
|
|
|
void setupAddressWidget(QLineEdit *widget, QWidget *parent)
|
|
{
|
|
widget->setMaxLength(BitcoinAddressValidator::MaxAddressLength);
|
|
widget->setValidator(new BitcoinAddressValidator(parent));
|
|
widget->setFont(bitcoinAddressFont());
|
|
}
|
|
|
|
void setupAmountWidget(QLineEdit *widget, QWidget *parent)
|
|
{
|
|
QDoubleValidator *amountValidator = new QDoubleValidator(parent);
|
|
amountValidator->setDecimals(8);
|
|
amountValidator->setBottom(0.0);
|
|
widget->setValidator(amountValidator);
|
|
widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
|
|
}
|
|
|
|
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
|
|
{
|
|
// return if URI is not valid or is no bitcoin URI
|
|
if(!uri.isValid() || uri.scheme() != QString("bitcoin"))
|
|
return false;
|
|
|
|
SendCoinsRecipient rv;
|
|
rv.address = uri.path();
|
|
rv.amount = 0;
|
|
|
|
#if QT_VERSION < 0x050000
|
|
QList<QPair<QString, QString> > items = uri.queryItems();
|
|
#else
|
|
QUrlQuery uriQuery(uri);
|
|
QList<QPair<QString, QString> > items = uriQuery.queryItems();
|
|
#endif
|
|
for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++)
|
|
{
|
|
bool fShouldReturnFalse = false;
|
|
if (i->first.startsWith("req-"))
|
|
{
|
|
i->first.remove(0, 4);
|
|
fShouldReturnFalse = true;
|
|
}
|
|
|
|
if (i->first == "label")
|
|
{
|
|
rv.label = i->second;
|
|
fShouldReturnFalse = false;
|
|
}
|
|
else if (i->first == "amount")
|
|
{
|
|
if(!i->second.isEmpty())
|
|
{
|
|
if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
fShouldReturnFalse = false;
|
|
}
|
|
|
|
if (fShouldReturnFalse)
|
|
return false;
|
|
}
|
|
if(out)
|
|
{
|
|
*out = rv;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool parseBitcoinURI(QString uri, SendCoinsRecipient *out)
|
|
{
|
|
// Convert bitcoin:// to bitcoin:
|
|
//
|
|
// Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host,
|
|
// which will lower-case it (and thus invalidate the address).
|
|
if(uri.startsWith("bitcoin://"))
|
|
{
|
|
uri.replace(0, 10, "bitcoin:");
|
|
}
|
|
QUrl uriInstance(uri);
|
|
return parseBitcoinURI(uriInstance, out);
|
|
}
|
|
|
|
QString HtmlEscape(const QString& str, bool fMultiLine)
|
|
{
|
|
#if QT_VERSION < 0x050000
|
|
QString escaped = Qt::escape(str);
|
|
#else
|
|
QString escaped = str.toHtmlEscaped();
|
|
#endif
|
|
if(fMultiLine)
|
|
{
|
|
escaped = escaped.replace("\n", "<br>\n");
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
QString HtmlEscape(const std::string& str, bool fMultiLine)
|
|
{
|
|
return HtmlEscape(QString::fromStdString(str), fMultiLine);
|
|
}
|
|
|
|
void copyEntryData(QAbstractItemView *view, int column, int role)
|
|
{
|
|
if(!view || !view->selectionModel())
|
|
return;
|
|
QModelIndexList selection = view->selectionModel()->selectedRows(column);
|
|
|
|
if(!selection.isEmpty())
|
|
{
|
|
// Copy first item (global clipboard)
|
|
QApplication::clipboard()->setText(selection.at(0).data(role).toString(), QClipboard::Clipboard);
|
|
// Copy first item (global mouse selection for e.g. X11 - NOP on Windows)
|
|
QApplication::clipboard()->setText(selection.at(0).data(role).toString(), QClipboard::Selection);
|
|
}
|
|
}
|
|
|
|
QString getSaveFileName(QWidget *parent, const QString &caption,
|
|
const QString &dir,
|
|
const QString &filter,
|
|
QString *selectedSuffixOut)
|
|
{
|
|
QString selectedFilter;
|
|
QString myDir;
|
|
if(dir.isEmpty()) // Default to user documents location
|
|
{
|
|
#if QT_VERSION < 0x050000
|
|
myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
|
|
#else
|
|
myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
myDir = dir;
|
|
}
|
|
QString result = QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter);
|
|
|
|
/* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
|
|
QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]");
|
|
QString selectedSuffix;
|
|
if(filter_re.exactMatch(selectedFilter))
|
|
{
|
|
selectedSuffix = filter_re.cap(1);
|
|
}
|
|
|
|
/* Add suffix if needed */
|
|
QFileInfo info(result);
|
|
if(!result.isEmpty())
|
|
{
|
|
if(info.suffix().isEmpty() && !selectedSuffix.isEmpty())
|
|
{
|
|
/* No suffix specified, add selected suffix */
|
|
if(!result.endsWith("."))
|
|
result.append(".");
|
|
result.append(selectedSuffix);
|
|
}
|
|
}
|
|
|
|
/* Return selected suffix if asked to */
|
|
if(selectedSuffixOut)
|
|
{
|
|
*selectedSuffixOut = selectedSuffix;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Qt::ConnectionType blockingGUIThreadConnection()
|
|
{
|
|
if(QThread::currentThread() != qApp->thread())
|
|
{
|
|
return Qt::BlockingQueuedConnection;
|
|
}
|
|
else
|
|
{
|
|
return Qt::DirectConnection;
|
|
}
|
|
}
|
|
|
|
bool checkPoint(const QPoint &p, const QWidget *w)
|
|
{
|
|
QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p));
|
|
if (!atW) return false;
|
|
return atW->topLevelWidget() == w;
|
|
}
|
|
|
|
bool isObscured(QWidget *w)
|
|
{
|
|
return !(checkPoint(QPoint(0, 0), w)
|
|
&& checkPoint(QPoint(w->width() - 1, 0), w)
|
|
&& checkPoint(QPoint(0, w->height() - 1), w)
|
|
&& checkPoint(QPoint(w->width() - 1, w->height() - 1), w)
|
|
&& checkPoint(QPoint(w->width() / 2, w->height() / 2), w));
|
|
}
|
|
|
|
void openDebugLogfile()
|
|
{
|
|
boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
|
|
|
|
/* Open debug.log with the associated application */
|
|
if (boost::filesystem::exists(pathDebug))
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(pathDebug.string())));
|
|
}
|
|
|
|
ToolTipToRichTextFilter::ToolTipToRichTextFilter(int size_threshold, QObject *parent) :
|
|
QObject(parent), size_threshold(size_threshold)
|
|
{
|
|
|
|
}
|
|
|
|
bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt)
|
|
{
|
|
if(evt->type() == QEvent::ToolTipChange)
|
|
{
|
|
QWidget *widget = static_cast<QWidget*>(obj);
|
|
QString tooltip = widget->toolTip();
|
|
if(tooltip.size() > size_threshold && !tooltip.startsWith("<qt/>") && !Qt::mightBeRichText(tooltip))
|
|
{
|
|
// Prefix <qt/> to make sure Qt detects this as rich text
|
|
// Escape the current message as HTML and replace \n by <br>
|
|
tooltip = "<qt/>" + HtmlEscape(tooltip, true);
|
|
widget->setToolTip(tooltip);
|
|
return true;
|
|
}
|
|
}
|
|
return QObject::eventFilter(obj, evt);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
boost::filesystem::path static StartupShortcutPath()
|
|
{
|
|
return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk";
|
|
}
|
|
|
|
bool GetStartOnSystemStartup()
|
|
{
|
|
// check for Bitcoin.lnk
|
|
return boost::filesystem::exists(StartupShortcutPath());
|
|
}
|
|
|
|
bool SetStartOnSystemStartup(bool fAutoStart)
|
|
{
|
|
// If the shortcut exists already, remove it for updating
|
|
boost::filesystem::remove(StartupShortcutPath());
|
|
|
|
if (fAutoStart)
|
|
{
|
|
CoInitialize(NULL);
|
|
|
|
// Get a pointer to the IShellLink interface.
|
|
IShellLink* psl = NULL;
|
|
HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL,
|
|
CLSCTX_INPROC_SERVER, IID_IShellLink,
|
|
reinterpret_cast<void**>(&psl));
|
|
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
// Get the current executable path
|
|
TCHAR pszExePath[MAX_PATH];
|
|
GetModuleFileName(NULL, pszExePath, sizeof(pszExePath));
|
|
|
|
TCHAR pszArgs[5] = TEXT("-min");
|
|
|
|
// Set the path to the shortcut target
|
|
psl->SetPath(pszExePath);
|
|
PathRemoveFileSpec(pszExePath);
|
|
psl->SetWorkingDirectory(pszExePath);
|
|
psl->SetShowCmd(SW_SHOWMINNOACTIVE);
|
|
psl->SetArguments(pszArgs);
|
|
|
|
// Query IShellLink for the IPersistFile interface for
|
|
// saving the shortcut in persistent storage.
|
|
IPersistFile* ppf = NULL;
|
|
hres = psl->QueryInterface(IID_IPersistFile,
|
|
reinterpret_cast<void**>(&ppf));
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
WCHAR pwsz[MAX_PATH];
|
|
// Ensure that the string is ANSI.
|
|
MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH);
|
|
// Save the link by calling IPersistFile::Save.
|
|
hres = ppf->Save(pwsz, TRUE);
|
|
ppf->Release();
|
|
psl->Release();
|
|
CoUninitialize();
|
|
return true;
|
|
}
|
|
psl->Release();
|
|
}
|
|
CoUninitialize();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#elif defined(LINUX)
|
|
|
|
// Follow the Desktop Application Autostart Spec:
|
|
// http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
|
|
|
|
boost::filesystem::path static GetAutostartDir()
|
|
{
|
|
namespace fs = boost::filesystem;
|
|
|
|
char* pszConfigHome = getenv("XDG_CONFIG_HOME");
|
|
if (pszConfigHome) return fs::path(pszConfigHome) / "autostart";
|
|
char* pszHome = getenv("HOME");
|
|
if (pszHome) return fs::path(pszHome) / ".config" / "autostart";
|
|
return fs::path();
|
|
}
|
|
|
|
boost::filesystem::path static GetAutostartFilePath()
|
|
{
|
|
return GetAutostartDir() / "bitcoin.desktop";
|
|
}
|
|
|
|
bool GetStartOnSystemStartup()
|
|
{
|
|
boost::filesystem::ifstream optionFile(GetAutostartFilePath());
|
|
if (!optionFile.good())
|
|
return false;
|
|
// Scan through file for "Hidden=true":
|
|
std::string line;
|
|
while (!optionFile.eof())
|
|
{
|
|
getline(optionFile, line);
|
|
if (line.find("Hidden") != std::string::npos &&
|
|
line.find("true") != std::string::npos)
|
|
return false;
|
|
}
|
|
optionFile.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SetStartOnSystemStartup(bool fAutoStart)
|
|
{
|
|
if (!fAutoStart)
|
|
boost::filesystem::remove(GetAutostartFilePath());
|
|
else
|
|
{
|
|
char pszExePath[MAX_PATH+1];
|
|
memset(pszExePath, 0, sizeof(pszExePath));
|
|
if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1)
|
|
return false;
|
|
|
|
boost::filesystem::create_directories(GetAutostartDir());
|
|
|
|
boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
|
|
if (!optionFile.good())
|
|
return false;
|
|
// Write a bitcoin.desktop file to the autostart directory:
|
|
optionFile << "[Desktop Entry]\n";
|
|
optionFile << "Type=Application\n";
|
|
optionFile << "Name=Bitcoin\n";
|
|
optionFile << "Exec=" << pszExePath << " -min\n";
|
|
optionFile << "Terminal=false\n";
|
|
optionFile << "Hidden=false\n";
|
|
optionFile.close();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
#elif defined(Q_OS_MAC)
|
|
// based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <CoreServices/CoreServices.h>
|
|
|
|
LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl);
|
|
LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl)
|
|
{
|
|
// loop through the list of startup items and try to find the bitcoin app
|
|
CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, NULL);
|
|
for(int i = 0; i < CFArrayGetCount(listSnapshot); i++) {
|
|
LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i);
|
|
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
|
|
CFURLRef currentItemURL = NULL;
|
|
LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL);
|
|
if(currentItemURL && CFEqual(currentItemURL, findUrl)) {
|
|
// found
|
|
CFRelease(currentItemURL);
|
|
return item;
|
|
}
|
|
if(currentItemURL) {
|
|
CFRelease(currentItemURL);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool GetStartOnSystemStartup()
|
|
{
|
|
CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
|
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
|
|
LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl);
|
|
return !!foundItem; // return boolified object
|
|
}
|
|
|
|
bool SetStartOnSystemStartup(bool fAutoStart)
|
|
{
|
|
CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
|
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
|
|
LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl);
|
|
|
|
if(fAutoStart && !foundItem) {
|
|
// add bitcoin app to startup item list
|
|
LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, bitcoinAppUrl, NULL, NULL);
|
|
}
|
|
else if(!fAutoStart && foundItem) {
|
|
// remove item
|
|
LSSharedFileListItemRemove(loginItems, foundItem);
|
|
}
|
|
return true;
|
|
}
|
|
#else
|
|
|
|
bool GetStartOnSystemStartup() { return false; }
|
|
bool SetStartOnSystemStartup(bool fAutoStart) { return false; }
|
|
|
|
#endif
|
|
|
|
HelpMessageBox::HelpMessageBox(QWidget *parent) :
|
|
QMessageBox(parent)
|
|
{
|
|
header = tr("Bitcoin-Qt") + " " + tr("version") + " " +
|
|
QString::fromStdString(FormatFullVersion()) + "\n\n" +
|
|
tr("Usage:") + "\n" +
|
|
" bitcoin-qt [" + tr("command-line options") + "] " + "\n";
|
|
|
|
coreOptions = QString::fromStdString(HelpMessage());
|
|
|
|
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" +
|
|
" -choosedatadir " + tr("Choose data directory on startup (default: 0)") + "\n";
|
|
|
|
setWindowTitle(tr("Bitcoin-Qt"));
|
|
setTextFormat(Qt::PlainText);
|
|
// setMinimumWidth is ignored for QMessageBox so put in non-breaking spaces to make it wider.
|
|
setText(header + QString(QChar(0x2003)).repeated(50));
|
|
setDetailedText(coreOptions + "\n" + uiOptions);
|
|
}
|
|
|
|
void HelpMessageBox::printToConsole()
|
|
{
|
|
// On other operating systems, the expected action is to print the message to the console.
|
|
QString strUsage = header + "\n" + coreOptions + "\n" + uiOptions;
|
|
fprintf(stdout, "%s", strUsage.toStdString().c_str());
|
|
}
|
|
|
|
void HelpMessageBox::showOrPrint()
|
|
{
|
|
#if defined(WIN32)
|
|
// On Windows, show a message box, as there is no stderr/stdout in windowed applications
|
|
exec();
|
|
#else
|
|
// On other operating systems, print help text to console
|
|
printToConsole();
|
|
#endif
|
|
}
|
|
|
|
} // namespace GUIUtil
|