Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13612295
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
63 KB
Subscribers
None
View Options
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 ¶ms) {
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 ¶ms) {
// 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 ¶ms, 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 ¶ms,
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, ¤tItemURL,
nullptr);
}
#endif
#else
LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL,
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 ¶ms);
// Convert any address into cashaddr
QString convertToCashAddr(const CChainParams ¶ms, 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 ¶ms,
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
Details
Attached
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)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment