Page MenuHomePhabricator

No OneTemporary

diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp
index bebf8b8ef..b41726fc8 100644
--- a/src/qt/editaddressdialog.cpp
+++ b/src/qt/editaddressdialog.cpp
@@ -1,145 +1,150 @@
// Copyright (c) 2011-2016 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 <qt/editaddressdialog.h>
#include <qt/forms/ui_editaddressdialog.h>
#include <qt/addresstablemodel.h>
#include <qt/guiutil.h>
#include <QDataWidgetMapper>
#include <QMessageBox>
EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent)
: QDialog(parent), ui(new Ui::EditAddressDialog), mapper(0), mode(_mode),
model(0) {
ui->setupUi(this);
GUIUtil::setupAddressWidget(ui->addressEdit, this);
switch (mode) {
case NewSendingAddress:
setWindowTitle(tr("New sending address"));
break;
case EditReceivingAddress:
setWindowTitle(tr("Edit receiving address"));
ui->addressEdit->setEnabled(false);
break;
case EditSendingAddress:
setWindowTitle(tr("Edit sending address"));
break;
}
mapper = new QDataWidgetMapper(this);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
+
+ GUIUtil::ItemDelegate *delegate = new GUIUtil::ItemDelegate(mapper);
+ connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this,
+ &EditAddressDialog::reject);
+ mapper->setItemDelegate(delegate);
}
EditAddressDialog::~EditAddressDialog() {
delete ui;
}
void EditAddressDialog::setModel(AddressTableModel *_model) {
this->model = _model;
if (!_model) {
return;
}
mapper->setModel(_model);
mapper->addMapping(ui->labelEdit, AddressTableModel::Label);
mapper->addMapping(ui->addressEdit, AddressTableModel::Address);
}
void EditAddressDialog::loadRow(int row) {
mapper->setCurrentIndex(row);
}
bool EditAddressDialog::saveCurrentRow() {
if (!model) {
return false;
}
switch (mode) {
case NewSendingAddress:
address = model->addRow(
AddressTableModel::Send, ui->labelEdit->text(),
ui->addressEdit->text(), model->GetDefaultAddressType());
break;
case EditReceivingAddress:
case EditSendingAddress:
if (mapper->submit()) {
address = ui->addressEdit->text();
}
break;
}
return !address.isEmpty();
}
void EditAddressDialog::accept() {
if (!model) {
return;
}
if (!saveCurrentRow()) {
switch (model->getEditStatus()) {
case AddressTableModel::OK:
// Failed with unknown reason. Just reject.
break;
case AddressTableModel::NO_CHANGES:
// No changes were made during edit operation. Just reject.
break;
case AddressTableModel::INVALID_ADDRESS:
QMessageBox::warning(this, windowTitle(),
tr("The entered address \"%1\" is not a "
"valid Bitcoin address.")
.arg(ui->addressEdit->text()),
QMessageBox::Ok, QMessageBox::Ok);
break;
case AddressTableModel::DUPLICATE_ADDRESS:
QMessageBox::warning(this, windowTitle(),
getDuplicateAddressWarning(),
QMessageBox::Ok, QMessageBox::Ok);
break;
case AddressTableModel::WALLET_UNLOCK_FAILURE:
QMessageBox::critical(this, windowTitle(),
tr("Could not unlock wallet."),
QMessageBox::Ok, QMessageBox::Ok);
break;
case AddressTableModel::KEY_GENERATION_FAILURE:
QMessageBox::critical(this, windowTitle(),
tr("New key generation failed."),
QMessageBox::Ok, QMessageBox::Ok);
break;
}
return;
}
QDialog::accept();
}
QString EditAddressDialog::getDuplicateAddressWarning() const {
QString dup_address = ui->addressEdit->text();
QString existing_label = model->labelForAddress(dup_address);
QString existing_purpose = model->purposeForAddress(dup_address);
if (existing_purpose == "receive" &&
(mode == NewSendingAddress || mode == EditSendingAddress)) {
return tr("Address \"%1\" already exists as a receiving address with "
"label "
"\"%2\" and so cannot be added as a sending address.")
.arg(dup_address)
.arg(existing_label);
}
return tr("The entered address \"%1\" is already in the address book with "
"label \"%2\".")
.arg(dup_address)
.arg(existing_label);
}
QString EditAddressDialog::getAddress() const {
return address;
}
void EditAddressDialog::setAddress(const QString &_address) {
this->address = _address;
ui->addressEdit->setText(_address);
}
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 4b478ea6d..e4946c324 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -1,973 +1,983 @@
// Copyright (c) 2011-2016 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 <qt/guiutil.h>
#include <cashaddrenc.h>
#include <chainparams.h>
#include <fs.h>
#include <interfaces/node.h>
#include <key_io.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <protocol.h>
#include <qt/bitcoinaddressvalidator.h>
#include <qt/bitcoinunits.h>
#include <qt/qvalidatedlineedit.h>
#include <qt/walletmodel.h>
#include <script/script.h>
#include <script/standard.h>
#include <util/strencodings.h>
#include <util/system.h>
#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 <shellapi.h>
#include <shlobj.h>
#include <shlwapi.h>
#endif
#include <boost/scoped_array.hpp>
#include <QAbstractItemView>
#include <QApplication>
#include <QClipboard>
#include <QDateTime>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDoubleValidator>
#include <QFileDialog>
#include <QFont>
+#include <QKeyEvent>
#include <QLineEdit>
#include <QMouseEvent>
#include <QSettings>
#include <QTextDocument> // for Qt::mightBeRichText
#include <QThread>
#include <QUrlQuery>
#if QT_VERSION >= 0x50200
#include <QFontDatabase>
#endif
#if defined(Q_OS_MAC)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <CoreServices/CoreServices.h>
void ForceActivation();
#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 fixedPitchFont() {
#if QT_VERSION >= 0x50200
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
#else
QFont font("Monospace");
font.setStyleHint(QFont::Monospace);
return font;
#endif
}
static std::string MakeAddrInvalid(std::string addr,
const CChainParams &params) {
if (addr.size() < 2) {
return "";
}
// Checksum is at the end of the address. Swapping chars to make it invalid.
std::swap(addr[addr.size() - 1], addr[addr.size() - 2]);
if (!IsValidDestinationString(addr, params)) {
return addr;
}
return "";
}
std::string DummyAddress(const CChainParams &params) {
// Just some dummy data to generate a convincing random-looking (but
// consistent) address
static const std::vector<uint8_t> dummydata = {
0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86,
0xb6, 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1};
const CTxDestination dstKey = CKeyID(uint160(dummydata));
return MakeAddrInvalid(EncodeCashAddr(dstKey, params), params);
}
// Addresses are stored in the database with the encoding that the client was
// configured with at the time of creation.
//
// This converts to cashaddr.
QString convertToCashAddr(const CChainParams &params, const QString &addr) {
if (!IsValidDestinationString(addr.toStdString(), params)) {
// We have something sketchy as input. Do not try to convert.
return addr;
}
CTxDestination dst = DecodeDestination(addr.toStdString(), params);
return QString::fromStdString(EncodeCashAddr(dst, params));
}
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) {
parent->setFocusProxy(widget);
widget->setFont(fixedPitchFont());
// We don't want translators to use own addresses in translations
// and this is the only place, where this address is supplied.
widget->setPlaceholderText(
QObject::tr("Enter a Bitcoin address (e.g. %1)")
.arg(QString::fromStdString(DummyAddress(Params()))));
widget->setValidator(
new BitcoinAddressEntryValidator(Params().CashAddrPrefix(), parent));
widget->setCheckValidator(new BitcoinAddressCheckValidator(parent));
}
bool parseBitcoinURI(const QString &scheme, const QUrl &uri,
SendCoinsRecipient *out) {
// return if URI has wrong scheme.
if (!uri.isValid() || uri.scheme() != scheme) {
return false;
}
SendCoinsRecipient rv;
rv.address = uri.scheme() + ":" + uri.path();
// Trim any following forward slash which may have been added by the OS
if (rv.address.endsWith("/")) {
rv.address.truncate(rv.address.length() - 1);
}
rv.amount = Amount::zero();
QUrlQuery uriQuery(uri);
QList<QPair<QString, QString>> items = uriQuery.queryItems();
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;
}
if (i->first == "message") {
rv.message = i->second;
fShouldReturnFalse = false;
} else if (i->first == "amount") {
if (!i->second.isEmpty()) {
if (!BitcoinUnits::parse(BitcoinUnits::BCH, i->second,
&rv.amount)) {
return false;
}
}
fShouldReturnFalse = false;
}
if (fShouldReturnFalse) {
return false;
}
}
if (out) {
*out = rv;
}
return true;
}
bool parseBitcoinURI(const QString &scheme, QString uri,
SendCoinsRecipient *out) {
//
// Cannot handle this later, because bitcoincash://
// will cause Qt to see the part after // as host,
// which will lower-case it (and thus invalidate the address).
if (uri.startsWith(scheme + "://", Qt::CaseInsensitive)) {
uri.replace(0, scheme.length() + 3, scheme + ":");
}
QUrl uriInstance(uri);
return parseBitcoinURI(scheme, uriInstance, out);
}
QString formatBitcoinURI(const SendCoinsRecipient &info) {
return formatBitcoinURI(Params(), info);
}
QString formatBitcoinURI(const CChainParams &params,
const SendCoinsRecipient &info) {
QString ret = convertToCashAddr(params, info.address);
int paramCount = 0;
if (info.amount != Amount::zero()) {
ret +=
QString("?amount=%1")
.arg(BitcoinUnits::format(BitcoinUnits::BCH, info.amount, false,
BitcoinUnits::separatorNever));
paramCount++;
}
if (!info.label.isEmpty()) {
QString lbl(QUrl::toPercentEncoding(info.label));
ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl);
paramCount++;
}
if (!info.message.isEmpty()) {
QString msg(QUrl::toPercentEncoding(info.message));
ret +=
QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg);
paramCount++;
}
return ret;
}
bool isDust(interfaces::Node &node, const QString &address, const Amount amount,
const CChainParams &chainParams) {
CTxDestination dest = DecodeDestination(address.toStdString(), chainParams);
CScript script = GetScriptForDestination(dest);
CTxOut txOut(amount, script);
return IsDust(txOut, node.getDustRelayFee());
}
QString HtmlEscape(const QString &str, bool fMultiLine) {
QString escaped = str.toHtmlEscaped();
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
setClipboard(selection.at(0).data(role).toString());
}
}
QList<QModelIndex> getEntryData(QAbstractItemView *view, int column) {
if (!view || !view->selectionModel()) {
return QList<QModelIndex>();
}
return view->selectionModel()->selectedRows(column);
}
QString getSaveFileName(QWidget *parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedSuffixOut) {
QString selectedFilter;
QString myDir;
// Default to user documents location
if (dir.isEmpty()) {
myDir =
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
} else {
myDir = dir;
}
/* Directly convert path to native OS path separators */
QString result = QDir::toNativeSeparators(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;
}
QString getOpenFileName(QWidget *parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedSuffixOut) {
QString selectedFilter;
QString myDir;
// Default to user documents location
if (dir.isEmpty()) {
myDir =
QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
} else {
myDir = dir;
}
/* Directly convert path to native OS path separators */
QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName(
parent, caption, myDir, filter, &selectedFilter));
if (selectedSuffixOut) {
/* 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);
}
*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 bringToFront(QWidget *w) {
#ifdef Q_OS_MAC
ForceActivation();
#endif
if (w) {
// activateWindow() (sometimes) helps with keyboard focus on Windows
if (w->isMinimized()) {
w->showNormal();
} else {
w->show();
}
w->activateWindow();
w->raise();
}
}
void openDebugLogfile() {
fs::path pathDebug = GetDataDir() / "debug.log";
/* Open debug.log with the associated application */
if (fs::exists(pathDebug)) {
QDesktopServices::openUrl(
QUrl::fromLocalFile(boostPathToQString(pathDebug)));
}
}
bool openBitcoinConf() {
fs::path pathConfig =
GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
/* Create the file */
fs::ofstream configFile(pathConfig, std::ios_base::app);
if (!configFile.good()) {
return false;
}
configFile.close();
/* Open bitcoin.conf with the associated application */
return QDesktopServices::openUrl(
QUrl::fromLocalFile(boostPathToQString(pathConfig)));
}
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)) {
// Envelop with <qt></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) + "</qt>";
widget->setToolTip(tooltip);
return true;
}
}
return QObject::eventFilter(obj, evt);
}
void TableViewLastColumnResizingFixer::connectViewHeadersSignals() {
connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this,
&TableViewLastColumnResizingFixer::on_sectionResized);
connect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged,
this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
}
// We need to disconnect these while handling the resize events, otherwise we
// can enter infinite loops.
void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() {
disconnect(tableView->horizontalHeader(), &QHeaderView::sectionResized,
this, &TableViewLastColumnResizingFixer::on_sectionResized);
disconnect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged,
this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
} // namespace GUIUtil
// Setup the resize mode, handles compatibility for Qt5 and below as the method
// signatures changed.
// Refactored here for readability.
void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(
int logicalIndex, QHeaderView::ResizeMode resizeMode) {
tableView->horizontalHeader()->setSectionResizeMode(logicalIndex,
resizeMode);
}
void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex,
int width) {
tableView->setColumnWidth(nColumnIndex, width);
tableView->horizontalHeader()->resizeSection(nColumnIndex, width);
}
int TableViewLastColumnResizingFixer::getColumnsWidth() {
int nColumnsWidthSum = 0;
for (int i = 0; i < columnCount; i++) {
nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i);
}
return nColumnsWidthSum;
}
int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) {
int nResult = lastColumnMinimumWidth;
int nTableWidth = tableView->horizontalHeader()->width();
if (nTableWidth > 0) {
int nOtherColsWidth =
getColumnsWidth() -
tableView->horizontalHeader()->sectionSize(column);
nResult = std::max(nResult, nTableWidth - nOtherColsWidth);
}
return nResult;
}
// Make sure we don't make the columns wider than the table's viewport width.
void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() {
disconnectViewHeadersSignals();
resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex));
connectViewHeadersSignals();
int nTableWidth = tableView->horizontalHeader()->width();
int nColsWidth = getColumnsWidth();
if (nColsWidth > nTableWidth) {
resizeColumn(secondToLastColumnIndex,
getAvailableWidthForColumn(secondToLastColumnIndex));
}
}
// Make column use all the space available, useful during window resizing.
void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) {
disconnectViewHeadersSignals();
resizeColumn(column, getAvailableWidthForColumn(column));
connectViewHeadersSignals();
}
// When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex,
int oldSize,
int newSize) {
adjustTableColumnsWidth();
int remainingWidth = getAvailableWidthForColumn(logicalIndex);
if (newSize > remainingWidth) {
resizeColumn(logicalIndex, remainingWidth);
}
}
// When the table's geometry is ready, we manually perform the stretch of the
// "Message" column,
// as the "Stretch" resize mode does not allow for interactive resizing.
void TableViewLastColumnResizingFixer::on_geometriesChanged() {
if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) !=
0) {
disconnectViewHeadersSignals();
resizeColumn(secondToLastColumnIndex,
getAvailableWidthForColumn(secondToLastColumnIndex));
connectViewHeadersSignals();
}
}
/**
* Initializes all internal variables and prepares the
* the resize modes of the last 2 columns of the table and
*/
TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(
QTableView *table, int lastColMinimumWidth, int allColsMinimumWidth,
QObject *parent)
: QObject(parent), tableView(table),
lastColumnMinimumWidth(lastColMinimumWidth),
allColumnsMinimumWidth(allColsMinimumWidth) {
columnCount = tableView->horizontalHeader()->count();
lastColumnIndex = columnCount - 1;
secondToLastColumnIndex = columnCount - 2;
tableView->horizontalHeader()->setMinimumSectionSize(
allColumnsMinimumWidth);
setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive);
setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive);
}
#ifdef WIN32
static fs::path StartupShortcutPath() {
std::string chain = gArgs.GetChainName();
if (chain == CBaseChainParams::MAIN) {
return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk";
}
// Remove this special case when CBaseChainParams::TESTNET = "testnet4"
if (chain == CBaseChainParams::TESTNET) {
return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk";
}
return GetSpecialFolderPath(CSIDL_STARTUP) /
strprintf("Bitcoin (%s).lnk", chain);
}
bool GetStartOnSystemStartup() {
// check for Bitcoin*.lnk
return fs::exists(StartupShortcutPath());
}
bool SetStartOnSystemStartup(bool fAutoStart) {
// If the shortcut exists already, remove it for updating
fs::remove(StartupShortcutPath());
if (fAutoStart) {
CoInitialize(nullptr);
// Get a pointer to the IShellLink interface.
IShellLink *psl = nullptr;
HRESULT hres =
CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
IID_IShellLink, reinterpret_cast<void **>(&psl));
if (SUCCEEDED(hres)) {
// Get the current executable path
TCHAR pszExePath[MAX_PATH];
GetModuleFileName(nullptr, pszExePath, sizeof(pszExePath));
// Start client minimized
QString strArgs = "-min";
// Set -testnet /-regtest options
strArgs += QString::fromStdString(strprintf(
" -testnet=%d -regtest=%d", gArgs.GetBoolArg("-testnet", false),
gArgs.GetBoolArg("-regtest", false)));
#ifdef UNICODE
boost::scoped_array<TCHAR> args(new TCHAR[strArgs.length() + 1]);
// Convert the QString to TCHAR*
strArgs.toWCharArray(args.get());
// Add missing '\0'-termination to string
args[strArgs.length()] = '\0';
#endif
// Set the path to the shortcut target
psl->SetPath(pszExePath);
PathRemoveFileSpec(pszExePath);
psl->SetWorkingDirectory(pszExePath);
psl->SetShowCmd(SW_SHOWMINNOACTIVE);
#ifndef UNICODE
psl->SetArguments(strArgs.toStdString().c_str());
#else
psl->SetArguments(args.get());
#endif
// Query IShellLink for the IPersistFile interface for
// saving the shortcut in persistent storage.
IPersistFile *ppf = nullptr;
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(Q_OS_LINUX)
// Follow the Desktop Application Autostart Spec:
// http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
static fs::path GetAutostartDir() {
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();
}
static fs::path GetAutostartFilePath() {
std::string chain = gArgs.GetChainName();
if (chain == CBaseChainParams::MAIN) {
return GetAutostartDir() / "bitcoin.desktop";
}
return GetAutostartDir() / strprintf("bitcoin-%s.lnk", chain);
}
bool GetStartOnSystemStartup() {
fs::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) {
fs::remove(GetAutostartFilePath());
} else {
char pszExePath[MAX_PATH + 1];
ssize_t r =
readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1);
if (r == -1) {
return false;
}
pszExePath[r] = '\0';
fs::create_directories(GetAutostartDir());
fs::ofstream optionFile(GetAutostartFilePath(),
std::ios_base::out | std::ios_base::trunc);
if (!optionFile.good()) {
return false;
}
std::string chain = gArgs.GetChainName();
// Write a bitcoin.desktop file to the autostart directory:
optionFile << "[Desktop Entry]\n";
optionFile << "Type=Application\n";
if (chain == CBaseChainParams::MAIN) {
optionFile << "Name=Bitcoin\n";
} else {
optionFile << strprintf("Name=Bitcoin (%s)\n", chain);
}
optionFile << "Exec=" << pszExePath
<< strprintf(" -min -testnet=%d -regtest=%d\n",
gArgs.GetBoolArg("-testnet", false),
gArgs.GetBoolArg("-regtest", false));
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
// NB: caller must release returned ref if it's not NULL
LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list,
CFURLRef findUrl);
LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list,
CFURLRef findUrl) {
LSSharedFileListItemRef foundItem = nullptr;
// loop through the list of startup items and try to find the bitcoin app
CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, nullptr);
for (int i = 0; !foundItem && i < CFArrayGetCount(listSnapshot); ++i) {
LSSharedFileListItemRef item =
(LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i);
UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction |
kLSSharedFileListDoNotMountVolumes;
CFURLRef currentItemURL = nullptr;
#if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && \
MAC_OS_X_VERSION_MAX_ALLOWED >= 10100
if (&LSSharedFileListItemCopyResolvedURL) {
currentItemURL = LSSharedFileListItemCopyResolvedURL(
item, resolutionFlags, nullptr);
}
#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && \
MAC_OS_X_VERSION_MIN_REQUIRED < 10100
else {
LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL,
nullptr);
}
#endif
#else
LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL,
nullptr);
#endif
if (currentItemURL && CFEqual(currentItemURL, findUrl)) {
// found
CFRetain(foundItem = item);
}
if (currentItemURL) {
CFRelease(currentItemURL);
}
}
CFRelease(listSnapshot);
return foundItem;
}
bool GetStartOnSystemStartup() {
CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
LSSharedFileListRef loginItems = LSSharedFileListCreate(
nullptr, kLSSharedFileListSessionLoginItems, nullptr);
LSSharedFileListItemRef foundItem =
findStartupItemInList(loginItems, bitcoinAppUrl);
// findStartupItemInList retains the item it returned, need to release
if (foundItem) {
CFRelease(foundItem);
}
CFRelease(loginItems);
CFRelease(bitcoinAppUrl);
return foundItem;
}
bool SetStartOnSystemStartup(bool fAutoStart) {
CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
LSSharedFileListRef loginItems = LSSharedFileListCreate(
nullptr, kLSSharedFileListSessionLoginItems, nullptr);
LSSharedFileListItemRef foundItem =
findStartupItemInList(loginItems, bitcoinAppUrl);
if (fAutoStart && !foundItem) {
// add bitcoin app to startup item list
LSSharedFileListInsertItemURL(loginItems,
kLSSharedFileListItemBeforeFirst, nullptr,
nullptr, bitcoinAppUrl, nullptr, nullptr);
} else if (!fAutoStart && foundItem) {
// remove item
LSSharedFileListItemRemove(loginItems, foundItem);
}
// findStartupItemInList retains the item it returned, need to release
if (foundItem) {
CFRelease(foundItem);
}
CFRelease(loginItems);
CFRelease(bitcoinAppUrl);
return true;
}
#pragma GCC diagnostic pop
#else
bool GetStartOnSystemStartup() {
return false;
}
bool SetStartOnSystemStartup(bool fAutoStart) {
return false;
}
#endif
void setClipboard(const QString &str) {
QApplication::clipboard()->setText(str, QClipboard::Clipboard);
QApplication::clipboard()->setText(str, QClipboard::Selection);
}
fs::path qstringToBoostPath(const QString &path) {
return fs::path(path.toStdString());
}
QString boostPathToQString(const fs::path &path) {
return QString::fromStdString(path.string());
}
QString formatDurationStr(int secs) {
QStringList strList;
int days = secs / 86400;
int hours = (secs % 86400) / 3600;
int mins = (secs % 3600) / 60;
int seconds = secs % 60;
if (days) {
strList.append(QString(QObject::tr("%1 d")).arg(days));
}
if (hours) {
strList.append(QString(QObject::tr("%1 h")).arg(hours));
}
if (mins) {
strList.append(QString(QObject::tr("%1 m")).arg(mins));
}
if (seconds || (!days && !hours && !mins)) {
strList.append(QString(QObject::tr("%1 s")).arg(seconds));
}
return strList.join(" ");
}
QString formatServicesStr(quint64 mask) {
QStringList strList;
// Don't display experimental service bits
for (uint64_t check = 1; check <= NODE_LAST_NON_EXPERIMENTAL_SERVICE_BIT;
check <<= 1) {
if (mask & check) {
switch (check) {
case NODE_NETWORK:
strList.append("NETWORK");
break;
case NODE_GETUTXO:
strList.append("GETUTXO");
break;
case NODE_BLOOM:
strList.append("BLOOM");
break;
case NODE_XTHIN:
strList.append("XTHIN");
break;
case NODE_BITCOIN_CASH:
strList.append("CASH");
break;
case NODE_NETWORK_LIMITED:
strList.append("LIMITED");
break;
default:
strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check));
}
}
}
if (strList.size()) {
return strList.join(" & ");
} else {
return QObject::tr("None");
}
}
QString formatPingTime(double dPingTime) {
return (dPingTime == std::numeric_limits<int64_t>::max() / 1e6 ||
dPingTime == 0)
? QObject::tr("N/A")
: QString(QObject::tr("%1 ms"))
.arg(QString::number((int)(dPingTime * 1000), 10));
}
QString formatTimeOffset(int64_t nTimeOffset) {
return QString(QObject::tr("%1 s"))
.arg(QString::number((int)nTimeOffset, 10));
}
QString formatNiceTimeOffset(qint64 secs) {
// Represent time from last generated block in human readable text
QString timeBehindText;
const int HOUR_IN_SECONDS = 60 * 60;
const int DAY_IN_SECONDS = 24 * 60 * 60;
const int WEEK_IN_SECONDS = 7 * 24 * 60 * 60;
// Average length of year in Gregorian calendar
const int YEAR_IN_SECONDS = 31556952;
if (secs < 60) {
timeBehindText = QObject::tr("%n second(s)", "", secs);
} else if (secs < 2 * HOUR_IN_SECONDS) {
timeBehindText = QObject::tr("%n minute(s)", "", secs / 60);
} else if (secs < 2 * DAY_IN_SECONDS) {
timeBehindText = QObject::tr("%n hour(s)", "", secs / HOUR_IN_SECONDS);
} else if (secs < 2 * WEEK_IN_SECONDS) {
timeBehindText = QObject::tr("%n day(s)", "", secs / DAY_IN_SECONDS);
} else if (secs < YEAR_IN_SECONDS) {
timeBehindText = QObject::tr("%n week(s)", "", secs / WEEK_IN_SECONDS);
} else {
qint64 years = secs / YEAR_IN_SECONDS;
qint64 remainder = secs % YEAR_IN_SECONDS;
timeBehindText = QObject::tr("%1 and %2")
.arg(QObject::tr("%n year(s)", "", years))
.arg(QObject::tr("%n week(s)", "",
remainder / WEEK_IN_SECONDS));
}
return timeBehindText;
}
QString formatBytes(uint64_t bytes) {
if (bytes < 1024) {
return QString(QObject::tr("%1 B")).arg(bytes);
}
if (bytes < 1024 * 1024) {
return QString(QObject::tr("%1 KB")).arg(bytes / 1024);
}
if (bytes < 1024 * 1024 * 1024) {
return QString(QObject::tr("%1 MB")).arg(bytes / 1024 / 1024);
}
return QString(QObject::tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
}
void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) {
Q_EMIT clicked(event->pos());
}
void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event) {
Q_EMIT clicked(event->pos());
}
+bool ItemDelegate::eventFilter(QObject *object, QEvent *event) {
+ if (event->type() == QEvent::KeyPress) {
+ if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Escape) {
+ Q_EMIT keyEscapePressed();
+ }
+ }
+ return QItemDelegate::eventFilter(object, event);
+}
+
} // namespace GUIUtil
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index c7cfe265f..c5ff66153 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -1,270 +1,282 @@
// Copyright (c) 2011-2016 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_GUIUTIL_H
#define BITCOIN_QT_GUIUTIL_H
#include <amount.h>
#include <fs.h>
#include <QEvent>
#include <QHeaderView>
+#include <QItemDelegate>
#include <QLabel>
#include <QMessageBox>
#include <QObject>
#include <QProgressBar>
#include <QString>
#include <QTableView>
class QValidatedLineEdit;
class SendCoinsRecipient;
class CChainParams;
class Config;
namespace interfaces {
class Node;
}
QT_BEGIN_NAMESPACE
class QAbstractItemView;
class QDateTime;
class QFont;
class QLineEdit;
class QUrl;
class QWidget;
QT_END_NAMESPACE
/**
* Utility functions used by the Bitcoin Qt UI.
*/
namespace GUIUtil {
// Create human-readable string from date
QString dateTimeStr(const QDateTime &datetime);
QString dateTimeStr(qint64 nTime);
// Return a monospace font
QFont fixedPitchFont();
// Generate an invalid, but convincing address.
std::string DummyAddress(const CChainParams &params);
// Convert any address into cashaddr
QString convertToCashAddr(const CChainParams &params, const QString &addr);
// Set up widget for address
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent);
// Parse "bitcoincash:" URI into recipient object, return true on successful
// parsing
bool parseBitcoinURI(const QString &scheme, const QUrl &uri,
SendCoinsRecipient *out);
bool parseBitcoinURI(const QString &scheme, QString uri,
SendCoinsRecipient *out);
QString formatBitcoinURI(const SendCoinsRecipient &info);
QString formatBitcoinURI(const CChainParams &params,
const SendCoinsRecipient &info);
// Returns true if given address+amount meets "dust" definition
bool isDust(interfaces::Node &node, const QString &address, const Amount amount,
const CChainParams &chainParams);
// HTML escaping for rich text controls
QString HtmlEscape(const QString &str, bool fMultiLine = false);
QString HtmlEscape(const std::string &str, bool fMultiLine = false);
/** Copy a field of the currently selected entry of a view to the clipboard.
Does nothing if nothing
is selected.
@param[in] column Data column to extract from the model
@param[in] role Data role to extract from the model
@see TransactionView::copyLabel, TransactionView::copyAmount,
TransactionView::copyAddress
*/
void copyEntryData(QAbstractItemView *view, int column,
int role = Qt::EditRole);
/** Return a field of the currently selected entry as a QString. Does nothing if
nothing
is selected.
@param[in] column Data column to extract from the model
@see TransactionView::copyLabel, TransactionView::copyAmount,
TransactionView::copyAddress
*/
QList<QModelIndex> getEntryData(QAbstractItemView *view, int column);
void setClipboard(const QString &str);
/** Get save filename, mimics QFileDialog::getSaveFileName, except that it
appends a default suffix
when no suffix is provided by the user.
@param[in] parent Parent window (or 0)
@param[in] caption Window caption (or empty, for default)
@param[in] dir Starting directory (or empty, to default to documents
directory)
@param[in] filter Filter specification such as "Comma Separated Files
(*.csv)"
@param[out] selectedSuffixOut Pointer to return the suffix (file type) that
was selected (or 0).
Can be useful when choosing the save file format based on suffix.
*/
QString getSaveFileName(QWidget *parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedSuffixOut);
/** Get open filename, convenience wrapper for QFileDialog::getOpenFileName.
@param[in] parent Parent window (or 0)
@param[in] caption Window caption (or empty, for default)
@param[in] dir Starting directory (or empty, to default to documents
directory)
@param[in] filter Filter specification such as "Comma Separated Files
(*.csv)"
@param[out] selectedSuffixOut Pointer to return the suffix (file type) that
was selected (or 0).
Can be useful when choosing the save file format based on suffix.
*/
QString getOpenFileName(QWidget *parent, const QString &caption,
const QString &dir, const QString &filter,
QString *selectedSuffixOut);
/** Get connection type to call object slot in GUI thread with invokeMethod. The
call will be blocking.
@returns If called from the GUI thread, return a Qt::DirectConnection.
If called from another thread, return a
Qt::BlockingQueuedConnection.
*/
Qt::ConnectionType blockingGUIThreadConnection();
// Determine whether a widget is hidden behind other windows
bool isObscured(QWidget *w);
// Activate, show and raise the widget
void bringToFront(QWidget *w);
// Open debug.log
void openDebugLogfile();
// Open the config file
bool openBitcoinConf();
/** Qt event filter that intercepts ToolTipChange events, and replaces the
* tooltip with a rich text representation if needed. This assures that Qt can
* word-wrap long tooltip messages. Tooltips longer than the provided size
* threshold (in characters) are wrapped.
*/
class ToolTipToRichTextFilter : public QObject {
Q_OBJECT
public:
explicit ToolTipToRichTextFilter(int size_threshold, QObject *parent = 0);
protected:
bool eventFilter(QObject *obj, QEvent *evt) override;
private:
int size_threshold;
};
/**
* Makes a QTableView last column feel as if it was being resized from its left
* border.
* Also makes sure the column widths are never larger than the table's viewport.
* In Qt, all columns are resizable from the right, but it's not intuitive
* resizing the last column from the right.
* Usually our second to last columns behave as if stretched, and when on
* stretch mode, columns aren't resizable interactively or programmatically.
*
* This helper object takes care of this issue.
*
*/
class TableViewLastColumnResizingFixer : public QObject {
Q_OBJECT
public:
TableViewLastColumnResizingFixer(QTableView *table, int lastColMinimumWidth,
int allColsMinimumWidth, QObject *parent);
void stretchColumnWidth(int column);
private:
QTableView *tableView;
int lastColumnMinimumWidth;
int allColumnsMinimumWidth;
int lastColumnIndex;
int columnCount;
int secondToLastColumnIndex;
void adjustTableColumnsWidth();
int getAvailableWidthForColumn(int column);
int getColumnsWidth();
void connectViewHeadersSignals();
void disconnectViewHeadersSignals();
void setViewHeaderResizeMode(int logicalIndex,
QHeaderView::ResizeMode resizeMode);
void resizeColumn(int nColumnIndex, int width);
private Q_SLOTS:
void on_sectionResized(int logicalIndex, int oldSize, int newSize);
void on_geometriesChanged();
};
bool GetStartOnSystemStartup();
bool SetStartOnSystemStartup(bool fAutoStart);
/* Convert QString to OS specific boost path through UTF-8 */
fs::path qstringToBoostPath(const QString &path);
/* Convert OS specific boost path to QString through UTF-8 */
QString boostPathToQString(const fs::path &path);
/* Convert seconds into a QString with days, hours, mins, secs */
QString formatDurationStr(int secs);
/* Format CNodeStats.nServices bitmask into a user-readable string */
QString formatServicesStr(quint64 mask);
/* Format a CNodeCombinedStats.dPingTime into a user-readable string or display
* N/A, if 0*/
QString formatPingTime(double dPingTime);
/* Format a CNodeCombinedStats.nTimeOffset into a user-readable string. */
QString formatTimeOffset(int64_t nTimeOffset);
QString formatNiceTimeOffset(qint64 secs);
QString formatBytes(uint64_t bytes);
class ClickableLabel : public QLabel {
Q_OBJECT
Q_SIGNALS:
/** Emitted when the label is clicked. The relative mouse coordinates of the
* click are passed to the signal.
*/
void clicked(const QPoint &point);
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
};
class ClickableProgressBar : public QProgressBar {
Q_OBJECT
Q_SIGNALS:
/** Emitted when the progressbar is clicked. The relative mouse coordinates
* of the click are passed to the signal.
*/
void clicked(const QPoint &point);
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
};
typedef ClickableProgressBar ProgressBar;
+class ItemDelegate : public QItemDelegate {
+ Q_OBJECT
+public:
+ ItemDelegate(QObject *parent) : QItemDelegate(parent) {}
+
+Q_SIGNALS:
+ void keyEscapePressed();
+
+private:
+ bool eventFilter(QObject *object, QEvent *event);
+};
} // namespace GUIUtil
#endif // BITCOIN_QT_GUIUTIL_H
diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp
index 85deae6c2..e32417b9d 100644
--- a/src/qt/optionsdialog.cpp
+++ b/src/qt/optionsdialog.cpp
@@ -1,405 +1,410 @@
// Copyright (c) 2011-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <qt/forms/ui_optionsdialog.h>
#include <qt/optionsdialog.h>
#include <interfaces/node.h>
#include <netbase.h>
#include <qt/bitcoinunits.h>
#include <qt/guiutil.h>
#include <qt/optionsmodel.h>
#include <txdb.h> // for -dbcache defaults
#include <validation.h> // for DEFAULT_SCRIPTCHECK_THREADS and MAX_SCRIPTCHECK_THREADS
#include <QDataWidgetMapper>
#include <QDir>
#include <QIntValidator>
#include <QLocale>
#include <QMessageBox>
#include <QSystemTrayIcon>
#include <QTimer>
OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet)
: QDialog(parent), ui(new Ui::OptionsDialog), model(0), mapper(0) {
ui->setupUi(this);
/* Main elements init */
ui->databaseCache->setMinimum(nMinDbCache);
ui->databaseCache->setMaximum(nMaxDbCache);
static const uint64_t GiB = 1024 * 1024 * 1024;
static const uint64_t nMinDiskSpace =
MIN_DISK_SPACE_FOR_BLOCK_FILES / GiB +
(MIN_DISK_SPACE_FOR_BLOCK_FILES % GiB)
? 1
: 0;
ui->pruneSize->setMinimum(nMinDiskSpace);
ui->threadsScriptVerif->setMinimum(-GetNumCores());
ui->threadsScriptVerif->setMaximum(MAX_SCRIPTCHECK_THREADS);
ui->pruneWarning->setVisible(false);
ui->pruneWarning->setStyleSheet("QLabel { color: red; }");
ui->pruneSize->setEnabled(false);
connect(ui->prune, &QPushButton::toggled, ui->pruneSize,
&QWidget::setEnabled);
/* Network elements init */
#ifndef USE_UPNP
ui->mapPortUpnp->setEnabled(false);
#endif
ui->proxyIp->setEnabled(false);
ui->proxyPort->setEnabled(false);
ui->proxyPort->setValidator(new QIntValidator(1, 65535, this));
ui->proxyIpTor->setEnabled(false);
ui->proxyPortTor->setEnabled(false);
ui->proxyPortTor->setValidator(new QIntValidator(1, 65535, this));
connect(ui->connectSocks, &QPushButton::toggled, ui->proxyIp,
&QWidget::setEnabled);
connect(ui->connectSocks, &QPushButton::toggled, ui->proxyPort,
&QWidget::setEnabled);
connect(ui->connectSocks, &QPushButton::toggled, this,
&OptionsDialog::updateProxyValidationState);
connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyIpTor,
&QWidget::setEnabled);
connect(ui->connectSocksTor, &QPushButton::toggled, ui->proxyPortTor,
&QWidget::setEnabled);
connect(ui->connectSocksTor, &QPushButton::toggled, this,
&OptionsDialog::updateProxyValidationState);
/* Window elements init */
#ifdef Q_OS_MAC
/* remove Window tab on Mac */
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWindow));
#endif
/* remove Wallet tab in case of -disablewallet */
if (!enableWallet) {
ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabWallet));
}
/* Display elements init */
QDir translations(":translations");
ui->bitcoinAtStartup->setToolTip(
ui->bitcoinAtStartup->toolTip().arg(tr(PACKAGE_NAME)));
ui->bitcoinAtStartup->setText(
ui->bitcoinAtStartup->text().arg(tr(PACKAGE_NAME)));
ui->openBitcoinConfButton->setToolTip(
ui->openBitcoinConfButton->toolTip().arg(tr(PACKAGE_NAME)));
ui->lang->setToolTip(ui->lang->toolTip().arg(tr(PACKAGE_NAME)));
ui->lang->addItem(QString("(") + tr("default") + QString(")"),
QVariant(""));
for (const QString &langStr : translations.entryList()) {
QLocale locale(langStr);
/** check if the locale name consists of 2 parts (language_country) */
if (langStr.contains("_")) {
/** display language strings as "native language - native country
* (locale name)", e.g. "Deutsch - Deutschland (de)" */
ui->lang->addItem(locale.nativeLanguageName() + QString(" - ") +
locale.nativeCountryName() + QString(" (") +
langStr + QString(")"),
QVariant(langStr));
} else {
/** display language strings as "native language (locale name)",
* e.g. "Deutsch (de)" */
ui->lang->addItem(locale.nativeLanguageName() + QString(" (") +
langStr + QString(")"),
QVariant(langStr));
}
}
ui->thirdPartyTxUrls->setPlaceholderText("https://example.com/tx/%s");
ui->unit->setModel(new BitcoinUnits(this));
/* Widget-to-option mapper */
mapper = new QDataWidgetMapper(this);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
mapper->setOrientation(Qt::Vertical);
+ GUIUtil::ItemDelegate *delegate = new GUIUtil::ItemDelegate(mapper);
+ connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this,
+ &OptionsDialog::reject);
+ mapper->setItemDelegate(delegate);
+
/* setup/change UI elements when proxy IPs are invalid/valid */
ui->proxyIp->setCheckValidator(new ProxyAddressValidator(parent));
ui->proxyIpTor->setCheckValidator(new ProxyAddressValidator(parent));
connect(ui->proxyIp, &QValidatedLineEdit::validationDidChange, this,
&OptionsDialog::updateProxyValidationState);
connect(ui->proxyIpTor, &QValidatedLineEdit::validationDidChange, this,
&OptionsDialog::updateProxyValidationState);
connect(ui->proxyPort, &QLineEdit::textChanged, this,
&OptionsDialog::updateProxyValidationState);
connect(ui->proxyPortTor, &QLineEdit::textChanged, this,
&OptionsDialog::updateProxyValidationState);
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
ui->hideTrayIcon->setChecked(true);
ui->hideTrayIcon->setEnabled(false);
ui->minimizeToTray->setChecked(false);
ui->minimizeToTray->setEnabled(false);
}
}
OptionsDialog::~OptionsDialog() {
delete ui;
}
void OptionsDialog::setModel(OptionsModel *_model) {
this->model = _model;
if (_model) {
/* check if client restart is needed and show persistent message */
if (_model->isRestartRequired()) showRestartWarning(true);
QString strLabel = _model->getOverriddenByCommandLine();
if (strLabel.isEmpty()) strLabel = tr("none");
ui->overriddenByCommandLineLabel->setText(strLabel);
mapper->setModel(_model);
setMapper();
mapper->toFirst();
updateDefaultProxyNets();
}
/* warn when one of the following settings changes by user action (placed
* here so init via mapper doesn't trigger them) */
/* Main */
connect(ui->prune, &QCheckBox::clicked, this,
&OptionsDialog::showRestartWarning);
connect(ui->prune, &QCheckBox::clicked, this,
&OptionsDialog::togglePruneWarning);
connect(ui->pruneSize,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&OptionsDialog::showRestartWarning);
connect(ui->databaseCache,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&OptionsDialog::showRestartWarning);
connect(ui->threadsScriptVerif,
static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
&OptionsDialog::showRestartWarning);
/* Wallet */
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this,
&OptionsDialog::showRestartWarning);
/* Network */
connect(ui->allowIncoming, &QCheckBox::clicked, this,
&OptionsDialog::showRestartWarning);
connect(ui->connectSocks, &QCheckBox::clicked, this,
&OptionsDialog::showRestartWarning);
connect(ui->connectSocksTor, &QCheckBox::clicked, this,
&OptionsDialog::showRestartWarning);
/* Display */
connect(
ui->lang,
static_cast<void (QValueComboBox::*)()>(&QValueComboBox::valueChanged),
[this] { showRestartWarning(); });
connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged,
[this] { showRestartWarning(); });
}
void OptionsDialog::setCurrentTab(OptionsDialog::Tab tab) {
QWidget *tab_widget = nullptr;
if (tab == OptionsDialog::Tab::TAB_NETWORK) {
tab_widget = ui->tabNetwork;
}
if (tab == OptionsDialog::Tab::TAB_MAIN) {
tab_widget = ui->tabMain;
}
if (tab_widget && ui->tabWidget->currentWidget() != tab_widget) {
ui->tabWidget->setCurrentWidget(tab_widget);
}
}
void OptionsDialog::setMapper() {
/* Main */
mapper->addMapping(ui->bitcoinAtStartup, OptionsModel::StartAtStartup);
mapper->addMapping(ui->threadsScriptVerif,
OptionsModel::ThreadsScriptVerif);
mapper->addMapping(ui->databaseCache, OptionsModel::DatabaseCache);
mapper->addMapping(ui->prune, OptionsModel::Prune);
mapper->addMapping(ui->pruneSize, OptionsModel::PruneSize);
/* Wallet */
mapper->addMapping(ui->spendZeroConfChange,
OptionsModel::SpendZeroConfChange);
mapper->addMapping(ui->coinControlFeatures,
OptionsModel::CoinControlFeatures);
/* Network */
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
mapper->addMapping(ui->allowIncoming, OptionsModel::Listen);
mapper->addMapping(ui->connectSocks, OptionsModel::ProxyUse);
mapper->addMapping(ui->proxyIp, OptionsModel::ProxyIP);
mapper->addMapping(ui->proxyPort, OptionsModel::ProxyPort);
mapper->addMapping(ui->connectSocksTor, OptionsModel::ProxyUseTor);
mapper->addMapping(ui->proxyIpTor, OptionsModel::ProxyIPTor);
mapper->addMapping(ui->proxyPortTor, OptionsModel::ProxyPortTor);
/* Window */
#ifndef Q_OS_MAC
if (QSystemTrayIcon::isSystemTrayAvailable()) {
mapper->addMapping(ui->hideTrayIcon, OptionsModel::HideTrayIcon);
mapper->addMapping(ui->minimizeToTray, OptionsModel::MinimizeToTray);
}
mapper->addMapping(ui->minimizeOnClose, OptionsModel::MinimizeOnClose);
#endif
/* Display */
mapper->addMapping(ui->lang, OptionsModel::Language);
mapper->addMapping(ui->unit, OptionsModel::DisplayUnit);
mapper->addMapping(ui->thirdPartyTxUrls, OptionsModel::ThirdPartyTxUrls);
}
void OptionsDialog::setOkButtonState(bool fState) {
ui->okButton->setEnabled(fState);
}
void OptionsDialog::on_resetButton_clicked() {
if (model) {
// confirmation dialog
QMessageBox::StandardButton btnRetVal = QMessageBox::question(
this, tr("Confirm options reset"),
tr("Client restart required to activate changes.") + "<br><br>" +
tr("Client will be shut down. Do you want to proceed?"),
QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
if (btnRetVal == QMessageBox::Cancel) return;
/* reset all options and close GUI */
model->Reset();
QApplication::quit();
}
}
void OptionsDialog::on_openBitcoinConfButton_clicked() {
/* explain the purpose of the config file */
QMessageBox::information(
this, tr("Configuration options"),
tr("The configuration file is used to specify advanced user options "
"which override GUI settings. Additionally, any command-line "
"options will override this configuration file."));
/* show an error if there was some problem opening the file */
if (!GUIUtil::openBitcoinConf()) {
QMessageBox::critical(
this, tr("Error"),
tr("The configuration file could not be opened."));
}
}
void OptionsDialog::on_okButton_clicked() {
mapper->submit();
accept();
updateDefaultProxyNets();
}
void OptionsDialog::on_cancelButton_clicked() {
reject();
}
void OptionsDialog::on_hideTrayIcon_stateChanged(int fState) {
if (fState) {
ui->minimizeToTray->setChecked(false);
ui->minimizeToTray->setEnabled(false);
} else {
ui->minimizeToTray->setEnabled(true);
}
}
void OptionsDialog::togglePruneWarning(bool enabled) {
ui->pruneWarning->setVisible(!ui->pruneWarning->isVisible());
}
void OptionsDialog::showRestartWarning(bool fPersistent) {
ui->statusLabel->setStyleSheet("QLabel { color: red; }");
if (fPersistent) {
ui->statusLabel->setText(
tr("Client restart required to activate changes."));
} else {
ui->statusLabel->setText(
tr("This change would require a client restart."));
// clear non-persistent status label after 10 seconds
// TODO: should perhaps be a class attribute, if we extend the use of
// statusLabel
QTimer::singleShot(10000, this, &OptionsDialog::clearStatusLabel);
}
}
void OptionsDialog::clearStatusLabel() {
ui->statusLabel->clear();
if (model && model->isRestartRequired()) {
showRestartWarning(true);
}
}
void OptionsDialog::updateProxyValidationState() {
QValidatedLineEdit *pUiProxyIp = ui->proxyIp;
QValidatedLineEdit *otherProxyWidget =
(pUiProxyIp == ui->proxyIpTor) ? ui->proxyIp : ui->proxyIpTor;
if (pUiProxyIp->isValid() &&
(!ui->proxyPort->isEnabled() || ui->proxyPort->text().toInt() > 0) &&
(!ui->proxyPortTor->isEnabled() ||
ui->proxyPortTor->text().toInt() > 0)) {
// Only enable ok button if both proxys are valid
setOkButtonState(otherProxyWidget->isValid());
clearStatusLabel();
} else {
setOkButtonState(false);
ui->statusLabel->setStyleSheet("QLabel { color: red; }");
ui->statusLabel->setText(tr("The supplied proxy address is invalid."));
}
}
void OptionsDialog::updateDefaultProxyNets() {
proxyType proxy;
std::string strProxy;
QString strDefaultProxyGUI;
model->node().getProxy(NET_IPV4, proxy);
strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort();
strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text();
(strProxy == strDefaultProxyGUI.toStdString())
? ui->proxyReachIPv4->setChecked(true)
: ui->proxyReachIPv4->setChecked(false);
model->node().getProxy(NET_IPV6, proxy);
strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort();
strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text();
(strProxy == strDefaultProxyGUI.toStdString())
? ui->proxyReachIPv6->setChecked(true)
: ui->proxyReachIPv6->setChecked(false);
model->node().getProxy(NET_ONION, proxy);
strProxy = proxy.proxy.ToStringIP() + ":" + proxy.proxy.ToStringPort();
strDefaultProxyGUI = ui->proxyIp->text() + ":" + ui->proxyPort->text();
(strProxy == strDefaultProxyGUI.toStdString())
? ui->proxyReachTor->setChecked(true)
: ui->proxyReachTor->setChecked(false);
}
ProxyAddressValidator::ProxyAddressValidator(QObject *parent)
: QValidator(parent) {}
QValidator::State ProxyAddressValidator::validate(QString &input,
int &pos) const {
Q_UNUSED(pos);
// Validate the proxy
CService serv(
LookupNumeric(input.toStdString().c_str(), DEFAULT_GUI_PROXY_PORT));
proxyType addrProxy = proxyType(serv, true);
if (addrProxy.IsValid()) return QValidator::Acceptable;
return QValidator::Invalid;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Apr 17, 03:22 (8 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5507112
Default Alt Text
(63 KB)

Event Timeline