Page MenuHomePhabricator

No OneTemporary

diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index f47f506de..ec79a292a 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -1,966 +1,976 @@
// 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 <QAbstractItemView>
#include <QApplication>
#include <QClipboard>
#include <QDateTime>
#include <QDesktopServices>
#include <QDoubleValidator>
#include <QFileDialog>
#include <QFont>
#include <QFontDatabase>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMouseEvent>
#include <QSettings>
#include <QTextDocument> // for Qt::mightBeRichText
#include <QThread>
#include <QUrlQuery>
#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() {
return QFontDatabase::systemFont(QFontDatabase::FixedFont);
}
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 = PKHash(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 */
fsbridge::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)));
}
+QStringList splitSkipEmptyParts(const QString &s, const QString &separator) {
+ return s.split(separator,
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
+ Qt::SkipEmptyParts
+#else
+ QString::SkipEmptyParts
+#endif
+ );
+}
+
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.
IShellLinkW *psl = nullptr;
HRESULT hres =
CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
IID_IShellLinkW, reinterpret_cast<void **>(&psl));
if (SUCCEEDED(hres)) {
// Get the current executable path
WCHAR pszExePath[MAX_PATH];
GetModuleFileNameW(nullptr, pszExePath, ARRAYSIZE(pszExePath));
// Start client minimized
QString strArgs = "-min";
// Set -testnet /-regtest options
strArgs += QString::fromStdString(
strprintf(" -chain=%s", gArgs.GetChainName()));
// Set the path to the shortcut target
psl->SetPath(pszExePath);
PathRemoveFileSpecW(pszExePath);
psl->SetWorkingDirectory(pszExePath);
psl->SetShowCmd(SW_SHOWMINNOACTIVE);
psl->SetArguments(strArgs.toStdWString().c_str());
// 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)) {
// Save the link by calling IPersistFile::Save.
hres = ppf->Save(StartupShortcutPath().wstring().c_str(), 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() {
fsbridge::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());
fsbridge::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 -chain=%s\n", chain);
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(int64_t ping_usec) {
return (ping_usec == std::numeric_limits<int64_t>::max() || ping_usec == 0)
? QObject::tr("N/A")
: QString(QObject::tr("%1 ms"))
.arg(QString::number(int(ping_usec / 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);
}
bool ClickableLabel::hasPixmap() const {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
return !pixmap(Qt::ReturnByValue).isNull();
#else
return pixmap() != nullptr;
#endif
}
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);
}
int TextWidth(const QFontMetrics &fm, const QString &text) {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
return fm.horizontalAdvance(text);
#else
return fm.width(text);
#endif
}
} // namespace GUIUtil
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h
index 1ed565574..3a8e0fb05 100644
--- a/src/qt/guiutil.h
+++ b/src/qt/guiutil.h
@@ -1,297 +1,300 @@
// 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();
+// Split a QString using given separator, skipping the empty parts
+QStringList splitSkipEmptyParts(const QString &s, const QString &separator);
+
/** 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 CNodeStats.m_ping_usec into a user-readable string or display N/A,
* if 0.
*/
QString formatPingTime(int64_t ping_usec);
/* 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
public:
bool hasPixmap() const;
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);
};
/**
* Returns the distance in pixels appropriate for drawing a subsequent character
* after text.
*
* In Qt 5.12 and before the QFontMetrics::width() is used and it is deprecated
* since Qt 13.0. In Qt 5.11 the QFontMetrics::horizontalAdvance() was
* introduced.
*/
int TextWidth(const QFontMetrics &fm, const QString &text);
} // namespace GUIUtil
#endif // BITCOIN_QT_GUIUTIL_H
diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp
index 15fb530fa..470bbf3f0 100644
--- a/src/qt/optionsmodel.cpp
+++ b/src/qt/optionsmodel.cpp
@@ -1,577 +1,577 @@
// 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/optionsmodel.h>
#include <amount.h>
#include <interfaces/node.h>
#include <net.h>
#include <netbase.h>
#include <qt/bitcoinunits.h>
#include <qt/guiutil.h>
#include <qt/intro.h>
#include <txdb.h> // for -dbcache defaults
#include <validation.h> // For DEFAULT_SCRIPTCHECK_THREADS
#include <QNetworkProxy>
#include <QSettings>
#include <QStringList>
const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1";
static const QString GetDefaultProxyAddress();
OptionsModel::OptionsModel(interfaces::Node &node, QObject *parent,
bool resetSettings)
: QAbstractListModel(parent), m_node(node) {
Init(resetSettings);
}
void OptionsModel::addOverriddenOption(const std::string &option) {
strOverriddenByCommandLine +=
QString::fromStdString(option) + "=" +
QString::fromStdString(gArgs.GetArg(option, "")) + " ";
}
// Writes all missing QSettings with their default values
void OptionsModel::Init(bool resetSettings) {
if (resetSettings) {
Reset();
}
checkAndMigrate();
QSettings settings;
// Ensure restart flag is unset on client startup
setRestartRequired(false);
// These are Qt-only settings:
// Window
if (!settings.contains("fHideTrayIcon")) {
settings.setValue("fHideTrayIcon", false);
}
fHideTrayIcon = settings.value("fHideTrayIcon").toBool();
Q_EMIT hideTrayIconChanged(fHideTrayIcon);
if (!settings.contains("fMinimizeToTray")) {
settings.setValue("fMinimizeToTray", false);
}
fMinimizeToTray =
settings.value("fMinimizeToTray").toBool() && !fHideTrayIcon;
if (!settings.contains("fMinimizeOnClose")) {
settings.setValue("fMinimizeOnClose", false);
}
fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool();
// Display
if (!settings.contains("nDisplayUnit")) {
settings.setValue("nDisplayUnit", BitcoinUnits::BCH);
}
nDisplayUnit = settings.value("nDisplayUnit").toInt();
if (!settings.contains("strThirdPartyTxUrls")) {
settings.setValue("strThirdPartyTxUrls", "");
}
strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString();
if (!settings.contains("fCoinControlFeatures")) {
settings.setValue("fCoinControlFeatures", false);
}
fCoinControlFeatures =
settings.value("fCoinControlFeatures", false).toBool();
// These are shared with the core or have a command-line parameter
// and we want command-line parameters to overwrite the GUI settings.
//
// If setting doesn't exist create it with defaults.
//
// If gArgs.SoftSetArg() or gArgs.SoftSetBoolArg() return false we were
// overridden
// by command-line and show this in the UI.
// Main
if (!settings.contains("bPrune")) {
settings.setValue("bPrune", false);
}
if (!settings.contains("nPruneSize")) {
settings.setValue("nPruneSize", 2);
}
// Convert prune size to MB:
const uint64_t nPruneSizeMB = settings.value("nPruneSize").toInt() * 1000;
if (!m_node.softSetArg("-prune", settings.value("bPrune").toBool()
? std::to_string(nPruneSizeMB)
: "0")) {
addOverriddenOption("-prune");
}
if (!settings.contains("nDatabaseCache")) {
settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache);
}
if (!m_node.softSetArg(
"-dbcache",
settings.value("nDatabaseCache").toString().toStdString())) {
addOverriddenOption("-dbcache");
}
if (!settings.contains("nThreadsScriptVerif")) {
settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS);
}
if (!m_node.softSetArg(
"-par",
settings.value("nThreadsScriptVerif").toString().toStdString())) {
addOverriddenOption("-par");
}
if (!settings.contains("strDataDir")) {
settings.setValue("strDataDir", Intro::getDefaultDataDirectory());
}
// Wallet
#ifdef ENABLE_WALLET
if (!settings.contains("bSpendZeroConfChange")) {
settings.setValue("bSpendZeroConfChange", true);
}
if (!m_node.softSetBoolArg(
"-spendzeroconfchange",
settings.value("bSpendZeroConfChange").toBool())) {
addOverriddenOption("-spendzeroconfchange");
}
#endif
// Network
if (!settings.contains("fUseUPnP")) {
settings.setValue("fUseUPnP", DEFAULT_UPNP);
}
if (!m_node.softSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) {
addOverriddenOption("-upnp");
}
if (!settings.contains("fListen")) {
settings.setValue("fListen", DEFAULT_LISTEN);
}
if (!m_node.softSetBoolArg("-listen", settings.value("fListen").toBool())) {
addOverriddenOption("-listen");
}
if (!settings.contains("fUseProxy")) {
settings.setValue("fUseProxy", false);
}
if (!settings.contains("addrProxy")) {
settings.setValue("addrProxy", GetDefaultProxyAddress());
}
// Only try to set -proxy, if user has enabled fUseProxy
if (settings.value("fUseProxy").toBool() &&
!m_node.softSetArg(
"-proxy", settings.value("addrProxy").toString().toStdString())) {
addOverriddenOption("-proxy");
} else if (!settings.value("fUseProxy").toBool() &&
!gArgs.GetArg("-proxy", "").empty()) {
addOverriddenOption("-proxy");
}
if (!settings.contains("fUseSeparateProxyTor")) {
settings.setValue("fUseSeparateProxyTor", false);
}
if (!settings.contains("addrSeparateProxyTor")) {
settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress());
}
// Only try to set -onion, if user has enabled fUseSeparateProxyTor
if (settings.value("fUseSeparateProxyTor").toBool() &&
!m_node.softSetArg(
"-onion",
settings.value("addrSeparateProxyTor").toString().toStdString())) {
addOverriddenOption("-onion");
} else if (!settings.value("fUseSeparateProxyTor").toBool() &&
!gArgs.GetArg("-onion", "").empty()) {
addOverriddenOption("-onion");
}
// Display
if (!settings.contains("language")) {
settings.setValue("language", "");
}
if (!m_node.softSetArg(
"-lang", settings.value("language").toString().toStdString())) {
addOverriddenOption("-lang");
}
language = settings.value("language").toString();
}
/**
* Helper function to copy contents from one QSettings to another.
* By using allKeys this also covers nested settings in a hierarchy.
*/
static void CopySettings(QSettings &dst, const QSettings &src) {
for (const QString &key : src.allKeys()) {
dst.setValue(key, src.value(key));
}
}
/** Back up a QSettings to an ini-formatted file. */
static void BackupSettings(const fs::path &filename, const QSettings &src) {
qWarning() << "Backing up GUI settings to"
<< GUIUtil::boostPathToQString(filename);
QSettings dst(GUIUtil::boostPathToQString(filename), QSettings::IniFormat);
dst.clear();
CopySettings(dst, src);
}
void OptionsModel::Reset() {
QSettings settings;
// Backup old settings to chain-specific datadir for troubleshooting
BackupSettings(GetDataDir(true) / "guisettings.ini.bak", settings);
// Save the strDataDir setting
QString dataDir = Intro::getDefaultDataDirectory();
dataDir = settings.value("strDataDir", dataDir).toString();
// Remove all entries from our QSettings object
settings.clear();
// Set strDataDir
settings.setValue("strDataDir", dataDir);
// Set that this was reset
settings.setValue("fReset", true);
// default setting for OptionsModel::StartAtStartup - disabled
if (GUIUtil::GetStartOnSystemStartup()) {
GUIUtil::SetStartOnSystemStartup(false);
}
}
int OptionsModel::rowCount(const QModelIndex &parent) const {
return OptionIDRowCount;
}
struct ProxySetting {
bool is_set;
QString ip;
QString port;
};
static ProxySetting GetProxySetting(QSettings &settings, const QString &name) {
static const ProxySetting default_val = {
false, DEFAULT_GUI_PROXY_HOST,
QString("%1").arg(DEFAULT_GUI_PROXY_PORT)};
// Handle the case that the setting is not set at all
if (!settings.contains(name)) {
return default_val;
}
// contains IP at index 0 and port at index 1
QStringList ip_port =
- settings.value(name).toString().split(":", QString::SkipEmptyParts);
+ GUIUtil::splitSkipEmptyParts(settings.value(name).toString(), ":");
if (ip_port.size() == 2) {
return {true, ip_port.at(0), ip_port.at(1)};
} else { // Invalid: return default
return default_val;
}
}
static void SetProxySetting(QSettings &settings, const QString &name,
const ProxySetting &ip_port) {
settings.setValue(name, ip_port.ip + ":" + ip_port.port);
}
static const QString GetDefaultProxyAddress() {
return QString("%1:%2")
.arg(DEFAULT_GUI_PROXY_HOST)
.arg(DEFAULT_GUI_PROXY_PORT);
}
// read QSettings values and return them
QVariant OptionsModel::data(const QModelIndex &index, int role) const {
if (role == Qt::EditRole) {
QSettings settings;
switch (index.row()) {
case StartAtStartup:
return GUIUtil::GetStartOnSystemStartup();
case HideTrayIcon:
return fHideTrayIcon;
case MinimizeToTray:
return fMinimizeToTray;
case MapPortUPnP:
#ifdef USE_UPNP
return settings.value("fUseUPnP");
#else
return false;
#endif
case MinimizeOnClose:
return fMinimizeOnClose;
// default proxy
case ProxyUse:
return settings.value("fUseProxy", false);
case ProxyIP:
return GetProxySetting(settings, "addrProxy").ip;
case ProxyPort:
return GetProxySetting(settings, "addrProxy").port;
// separate Tor proxy
case ProxyUseTor:
return settings.value("fUseSeparateProxyTor", false);
case ProxyIPTor:
return GetProxySetting(settings, "addrSeparateProxyTor").ip;
case ProxyPortTor:
return GetProxySetting(settings, "addrSeparateProxyTor").port;
#ifdef ENABLE_WALLET
case SpendZeroConfChange:
return settings.value("bSpendZeroConfChange");
#endif
case DisplayUnit:
return nDisplayUnit;
case ThirdPartyTxUrls:
return strThirdPartyTxUrls;
case Language:
return settings.value("language");
case CoinControlFeatures:
return fCoinControlFeatures;
case Prune:
return settings.value("bPrune");
case PruneSize:
return settings.value("nPruneSize");
case DatabaseCache:
return settings.value("nDatabaseCache");
case ThreadsScriptVerif:
return settings.value("nThreadsScriptVerif");
case Listen:
return settings.value("fListen");
default:
return QVariant();
}
}
return QVariant();
}
// write QSettings values
bool OptionsModel::setData(const QModelIndex &index, const QVariant &value,
int role) {
bool successful = true; /* set to false on parse error */
if (role == Qt::EditRole) {
QSettings settings;
switch (index.row()) {
case StartAtStartup:
successful = GUIUtil::SetStartOnSystemStartup(value.toBool());
break;
case HideTrayIcon:
fHideTrayIcon = value.toBool();
settings.setValue("fHideTrayIcon", fHideTrayIcon);
Q_EMIT hideTrayIconChanged(fHideTrayIcon);
break;
case MinimizeToTray:
fMinimizeToTray = value.toBool();
settings.setValue("fMinimizeToTray", fMinimizeToTray);
break;
case MapPortUPnP: // core option - can be changed on-the-fly
settings.setValue("fUseUPnP", value.toBool());
m_node.mapPort(value.toBool());
break;
case MinimizeOnClose:
fMinimizeOnClose = value.toBool();
settings.setValue("fMinimizeOnClose", fMinimizeOnClose);
break;
// default proxy
case ProxyUse:
if (settings.value("fUseProxy") != value) {
settings.setValue("fUseProxy", value.toBool());
setRestartRequired(true);
}
break;
case ProxyIP: {
auto ip_port = GetProxySetting(settings, "addrProxy");
if (!ip_port.is_set || ip_port.ip != value.toString()) {
ip_port.ip = value.toString();
SetProxySetting(settings, "addrProxy", ip_port);
setRestartRequired(true);
}
} break;
case ProxyPort: {
auto ip_port = GetProxySetting(settings, "addrProxy");
if (!ip_port.is_set || ip_port.port != value.toString()) {
ip_port.port = value.toString();
SetProxySetting(settings, "addrProxy", ip_port);
setRestartRequired(true);
}
} break;
// separate Tor proxy
case ProxyUseTor:
if (settings.value("fUseSeparateProxyTor") != value) {
settings.setValue("fUseSeparateProxyTor", value.toBool());
setRestartRequired(true);
}
break;
case ProxyIPTor: {
auto ip_port =
GetProxySetting(settings, "addrSeparateProxyTor");
if (!ip_port.is_set || ip_port.ip != value.toString()) {
ip_port.ip = value.toString();
SetProxySetting(settings, "addrSeparateProxyTor", ip_port);
setRestartRequired(true);
}
} break;
case ProxyPortTor: {
auto ip_port =
GetProxySetting(settings, "addrSeparateProxyTor");
if (!ip_port.is_set || ip_port.port != value.toString()) {
ip_port.port = value.toString();
SetProxySetting(settings, "addrSeparateProxyTor", ip_port);
setRestartRequired(true);
}
} break;
#ifdef ENABLE_WALLET
case SpendZeroConfChange:
if (settings.value("bSpendZeroConfChange") != value) {
settings.setValue("bSpendZeroConfChange", value);
setRestartRequired(true);
}
break;
#endif
case DisplayUnit:
setDisplayUnit(value);
break;
case ThirdPartyTxUrls:
if (strThirdPartyTxUrls != value.toString()) {
strThirdPartyTxUrls = value.toString();
settings.setValue("strThirdPartyTxUrls",
strThirdPartyTxUrls);
setRestartRequired(true);
}
break;
case Language:
if (settings.value("language") != value) {
settings.setValue("language", value);
setRestartRequired(true);
}
break;
case CoinControlFeatures:
fCoinControlFeatures = value.toBool();
settings.setValue("fCoinControlFeatures", fCoinControlFeatures);
Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures);
break;
case Prune:
if (settings.value("bPrune") != value) {
settings.setValue("bPrune", value);
setRestartRequired(true);
}
break;
case PruneSize:
if (settings.value("nPruneSize") != value) {
settings.setValue("nPruneSize", value);
setRestartRequired(true);
}
break;
case DatabaseCache:
if (settings.value("nDatabaseCache") != value) {
settings.setValue("nDatabaseCache", value);
setRestartRequired(true);
}
break;
case ThreadsScriptVerif:
if (settings.value("nThreadsScriptVerif") != value) {
settings.setValue("nThreadsScriptVerif", value);
setRestartRequired(true);
}
break;
case Listen:
if (settings.value("fListen") != value) {
settings.setValue("fListen", value);
setRestartRequired(true);
}
break;
default:
break;
}
}
Q_EMIT dataChanged(index, index);
return successful;
}
/** Updates current unit in memory, settings and emits
* displayUnitChanged(newUnit) signal */
void OptionsModel::setDisplayUnit(const QVariant &value) {
if (!value.isNull()) {
QSettings settings;
nDisplayUnit = value.toInt();
settings.setValue("nDisplayUnit", nDisplayUnit);
Q_EMIT displayUnitChanged(nDisplayUnit);
}
}
bool OptionsModel::getProxySettings(QNetworkProxy &proxy) const {
// Directly query current base proxy, because
// GUI settings can be overridden with -proxy.
proxyType curProxy;
if (m_node.getProxy(NET_IPV4, curProxy)) {
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(QString::fromStdString(curProxy.proxy.ToStringIP()));
proxy.setPort(curProxy.proxy.GetPort());
return true;
} else {
proxy.setType(QNetworkProxy::NoProxy);
}
return false;
}
void OptionsModel::setRestartRequired(bool fRequired) {
QSettings settings;
return settings.setValue("fRestartRequired", fRequired);
}
bool OptionsModel::isRestartRequired() const {
QSettings settings;
return settings.value("fRestartRequired", false).toBool();
}
void OptionsModel::checkAndMigrate() {
// Migration of default values
// Check if the QSettings container was already loaded with this client
// version
QSettings settings;
static const char strSettingsVersionKey[] = "nSettingsVersion";
int settingsVersion = settings.contains(strSettingsVersionKey)
? settings.value(strSettingsVersionKey).toInt()
: 0;
if (settingsVersion < CLIENT_VERSION) {
// -dbcache was bumped from 100 to 300 in 0.13
// see https://github.com/bitcoin/bitcoin/pull/8273
// force people to upgrade to the new value if they are using 100MB
if (settingsVersion < 130000 && settings.contains("nDatabaseCache") &&
settings.value("nDatabaseCache").toLongLong() == 100) {
settings.setValue("nDatabaseCache", (qint64)nDefaultDbCache);
}
settings.setValue(strSettingsVersionKey, CLIENT_VERSION);
}
// Overwrite the 'addrProxy' setting in case it has been set to an illegal
// default value (see issue #12623; PR #12650).
if (settings.contains("addrProxy") &&
settings.value("addrProxy").toString().endsWith("%2")) {
settings.setValue("addrProxy", GetDefaultProxyAddress());
}
// Overwrite the 'addrSeparateProxyTor' setting in case it has been set to
// an illegal default value (see issue #12623; PR #12650).
if (settings.contains("addrSeparateProxyTor") &&
settings.value("addrSeparateProxyTor").toString().endsWith("%2")) {
settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress());
}
}
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp
index 876907b94..add38a8aa 100644
--- a/src/qt/transactionview.cpp
+++ b/src/qt/transactionview.cpp
@@ -1,727 +1,726 @@
// 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/transactionview.h>
#include <qt/addresstablemodel.h>
#include <qt/bitcoinunits.h>
#include <qt/csvmodelwriter.h>
#include <qt/editaddressdialog.h>
#include <qt/guiutil.h>
#include <qt/optionsmodel.h>
#include <qt/platformstyle.h>
#include <qt/transactiondescdialog.h>
#include <qt/transactionfilterproxy.h>
#include <qt/transactionrecord.h>
#include <qt/transactiontablemodel.h>
#include <qt/walletmodel.h>
#include <ui_interface.h>
#include <QComboBox>
#include <QDateTimeEdit>
#include <QDesktopServices>
#include <QDoubleValidator>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPoint>
#include <QScrollBar>
#include <QSignalMapper>
#include <QTableView>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
TransactionView::TransactionView(const PlatformStyle *platformStyle,
QWidget *parent)
: QWidget(parent), model(nullptr), transactionProxyModel(nullptr),
transactionView(nullptr), abandonAction(nullptr),
columnResizingFixer(nullptr) {
// Build filter row
setContentsMargins(0, 0, 0, 0);
QHBoxLayout *hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0, 0, 0, 0);
if (platformStyle->getUseExtraSpacing()) {
hlayout->setSpacing(5);
hlayout->addSpacing(26);
} else {
hlayout->setSpacing(0);
hlayout->addSpacing(23);
}
watchOnlyWidget = new QComboBox(this);
watchOnlyWidget->setFixedWidth(24);
watchOnlyWidget->addItem("", TransactionFilterProxy::WatchOnlyFilter_All);
watchOnlyWidget->addItem(platformStyle->SingleColorIcon(":/icons/eye_plus"),
"", TransactionFilterProxy::WatchOnlyFilter_Yes);
watchOnlyWidget->addItem(
platformStyle->SingleColorIcon(":/icons/eye_minus"), "",
TransactionFilterProxy::WatchOnlyFilter_No);
hlayout->addWidget(watchOnlyWidget);
dateWidget = new QComboBox(this);
if (platformStyle->getUseExtraSpacing()) {
dateWidget->setFixedWidth(121);
} else {
dateWidget->setFixedWidth(120);
}
dateWidget->addItem(tr("All"), All);
dateWidget->addItem(tr("Today"), Today);
dateWidget->addItem(tr("This week"), ThisWeek);
dateWidget->addItem(tr("This month"), ThisMonth);
dateWidget->addItem(tr("Last month"), LastMonth);
dateWidget->addItem(tr("This year"), ThisYear);
dateWidget->addItem(tr("Range..."), Range);
hlayout->addWidget(dateWidget);
typeWidget = new QComboBox(this);
if (platformStyle->getUseExtraSpacing()) {
typeWidget->setFixedWidth(121);
} else {
typeWidget->setFixedWidth(120);
}
typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
typeWidget->addItem(
tr("Received with"),
TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther));
typeWidget->addItem(
tr("Sent to"),
TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
TransactionFilterProxy::TYPE(TransactionRecord::SendToOther));
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(
TransactionRecord::SendToSelf));
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(
TransactionRecord::Generated));
typeWidget->addItem(tr("Other"),
TransactionFilterProxy::TYPE(TransactionRecord::Other));
hlayout->addWidget(typeWidget);
search_widget = new QLineEdit(this);
search_widget->setPlaceholderText(
tr("Enter address, transaction id, or label to search"));
hlayout->addWidget(search_widget);
amountWidget = new QLineEdit(this);
amountWidget->setPlaceholderText(tr("Min amount"));
if (platformStyle->getUseExtraSpacing()) {
amountWidget->setFixedWidth(97);
} else {
amountWidget->setFixedWidth(100);
}
amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
hlayout->addWidget(amountWidget);
// Delay before filtering transactions in ms
static const int input_filter_delay = 200;
QTimer *amount_typing_delay = new QTimer(this);
amount_typing_delay->setSingleShot(true);
amount_typing_delay->setInterval(input_filter_delay);
QTimer *prefix_typing_delay = new QTimer(this);
prefix_typing_delay->setSingleShot(true);
prefix_typing_delay->setInterval(input_filter_delay);
QVBoxLayout *vlayout = new QVBoxLayout(this);
vlayout->setContentsMargins(0, 0, 0, 0);
vlayout->setSpacing(0);
QTableView *view = new QTableView(this);
vlayout->addLayout(hlayout);
vlayout->addWidget(createDateRangeWidget());
vlayout->addWidget(view);
vlayout->setSpacing(0);
int width = view->verticalScrollBar()->sizeHint().width();
// Cover scroll bar width with spacing
if (platformStyle->getUseExtraSpacing()) {
hlayout->addSpacing(width + 2);
} else {
hlayout->addSpacing(width);
}
// Always show scroll bar
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->setTabKeyNavigation(false);
view->setContextMenuPolicy(Qt::CustomContextMenu);
view->installEventFilter(this);
transactionView = view;
// Actions
abandonAction = new QAction(tr("Abandon transaction"), this);
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this);
QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this);
QAction *copyTxPlainText =
new QAction(tr("Copy full transaction details"), this);
QAction *editLabelAction = new QAction(tr("Edit label"), this);
QAction *showDetailsAction =
new QAction(tr("Show transaction details"), this);
contextMenu = new QMenu(this);
contextMenu->addAction(copyAddressAction);
contextMenu->addAction(copyLabelAction);
contextMenu->addAction(copyAmountAction);
contextMenu->addAction(copyTxIDAction);
contextMenu->addAction(copyTxHexAction);
contextMenu->addAction(copyTxPlainText);
contextMenu->addAction(showDetailsAction);
contextMenu->addSeparator();
contextMenu->addAction(abandonAction);
contextMenu->addAction(editLabelAction);
mapperThirdPartyTxUrls = new QSignalMapper(this);
// Connect actions
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
const auto mappedStringEvent = &QSignalMapper::mappedString;
#else
const auto mappedStringEvent =
static_cast<void (QSignalMapper::*)(const QString &)>(
&QSignalMapper::mapped);
#endif
connect(mapperThirdPartyTxUrls, mappedStringEvent, this,
&TransactionView::openThirdPartyTxUrl);
connect(dateWidget,
static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
&TransactionView::chooseDate);
connect(typeWidget,
static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
&TransactionView::chooseType);
connect(watchOnlyWidget,
static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
&TransactionView::chooseWatchonly);
connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay,
static_cast<void (QTimer::*)()>(&QTimer::start));
connect(amount_typing_delay, &QTimer::timeout, this,
&TransactionView::changedAmount);
connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay,
static_cast<void (QTimer::*)()>(&QTimer::start));
connect(prefix_typing_delay, &QTimer::timeout, this,
&TransactionView::changedSearch);
connect(view, &QTableView::doubleClicked, this,
&TransactionView::doubleClicked);
connect(view, &QTableView::customContextMenuRequested, this,
&TransactionView::contextualMenu);
connect(abandonAction, &QAction::triggered, this,
&TransactionView::abandonTx);
connect(copyAddressAction, &QAction::triggered, this,
&TransactionView::copyAddress);
connect(copyLabelAction, &QAction::triggered, this,
&TransactionView::copyLabel);
connect(copyAmountAction, &QAction::triggered, this,
&TransactionView::copyAmount);
connect(copyTxIDAction, &QAction::triggered, this,
&TransactionView::copyTxID);
connect(copyTxHexAction, &QAction::triggered, this,
&TransactionView::copyTxHex);
connect(copyTxPlainText, &QAction::triggered, this,
&TransactionView::copyTxPlainText);
connect(editLabelAction, &QAction::triggered, this,
&TransactionView::editLabel);
connect(showDetailsAction, &QAction::triggered, this,
&TransactionView::showDetails);
// Double-clicking on a transaction on the transaction history page shows
// details
connect(this, &TransactionView::doubleClicked, this,
&TransactionView::showDetails);
}
void TransactionView::setModel(WalletModel *_model) {
this->model = _model;
if (_model) {
transactionProxyModel = new TransactionFilterProxy(this);
transactionProxyModel->setSourceModel(
_model->getTransactionTableModel());
transactionProxyModel->setDynamicSortFilter(true);
transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
transactionProxyModel->setSortRole(Qt::EditRole);
transactionView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
transactionView->setModel(transactionProxyModel);
transactionView->setAlternatingRowColors(true);
transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
transactionView->horizontalHeader()->setSortIndicator(
TransactionTableModel::Date, Qt::DescendingOrder);
transactionView->setSortingEnabled(true);
transactionView->verticalHeader()->hide();
transactionView->setColumnWidth(TransactionTableModel::Status,
STATUS_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Watchonly,
WATCHONLY_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Date,
DATE_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Type,
TYPE_COLUMN_WIDTH);
transactionView->setColumnWidth(TransactionTableModel::Amount,
AMOUNT_MINIMUM_COLUMN_WIDTH);
columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(
transactionView, AMOUNT_MINIMUM_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH,
this);
if (_model->getOptionsModel()) {
// Add third party transaction URLs to context menu
- QStringList listUrls =
- _model->getOptionsModel()->getThirdPartyTxUrls().split(
- "|", QString::SkipEmptyParts);
+ QStringList listUrls = GUIUtil::splitSkipEmptyParts(
+ _model->getOptionsModel()->getThirdPartyTxUrls(), "|");
for (int i = 0; i < listUrls.size(); ++i) {
QString host =
QUrl(listUrls[i].trimmed(), QUrl::StrictMode).host();
if (!host.isEmpty()) {
// use host as menu item label
QAction *thirdPartyTxUrlAction = new QAction(host, this);
if (i == 0) {
contextMenu->addSeparator();
}
contextMenu->addAction(thirdPartyTxUrlAction);
connect(thirdPartyTxUrlAction, &QAction::triggered,
mapperThirdPartyTxUrls,
static_cast<void (QSignalMapper::*)()>(
&QSignalMapper::map));
mapperThirdPartyTxUrls->setMapping(thirdPartyTxUrlAction,
listUrls[i].trimmed());
}
}
}
// show/hide column Watch-only
updateWatchOnlyColumn(_model->wallet().haveWatchOnly());
// Watch-only signal
connect(_model, &WalletModel::notifyWatchonlyChanged, this,
&TransactionView::updateWatchOnlyColumn);
}
}
void TransactionView::chooseDate(int idx) {
if (!transactionProxyModel) {
return;
}
const QDate currentDate = QDate::currentDate();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
const QDateTime startofDay = currentDate.startOfDay();
#else
const QDateTime startofDay = QDateTime(currentDate);
#endif
const QDateTime startOfWeek =
startofDay.addDays(-(currentDate.dayOfWeek() - 1));
const QDateTime startOfMonth = startofDay.addDays(-(currentDate.day() - 1));
const QDateTime startOfYear =
startofDay.addDays(-(currentDate.dayOfYear() - 1));
dateRangeWidget->setVisible(false);
switch (dateWidget->itemData(idx).toInt()) {
case All:
transactionProxyModel->setDateRange(
TransactionFilterProxy::MIN_DATE,
TransactionFilterProxy::MAX_DATE);
break;
case Today:
transactionProxyModel->setDateRange(
startofDay, TransactionFilterProxy::MAX_DATE);
break;
case ThisWeek: {
// Find last Monday
transactionProxyModel->setDateRange(
startOfWeek, TransactionFilterProxy::MAX_DATE);
} break;
case ThisMonth:
transactionProxyModel->setDateRange(
startOfMonth, TransactionFilterProxy::MAX_DATE);
break;
case LastMonth:
transactionProxyModel->setDateRange(startOfMonth.addMonths(-1),
startOfMonth);
break;
case ThisYear:
transactionProxyModel->setDateRange(
startOfYear, TransactionFilterProxy::MAX_DATE);
break;
case Range:
dateRangeWidget->setVisible(true);
dateRangeChanged();
break;
}
}
void TransactionView::chooseType(int idx) {
if (!transactionProxyModel) {
return;
}
transactionProxyModel->setTypeFilter(typeWidget->itemData(idx).toInt());
}
void TransactionView::chooseWatchonly(int idx) {
if (!transactionProxyModel) {
return;
}
transactionProxyModel->setWatchOnlyFilter(
static_cast<TransactionFilterProxy::WatchOnlyFilter>(
watchOnlyWidget->itemData(idx).toInt()));
}
void TransactionView::changedSearch() {
if (!transactionProxyModel) {
return;
}
transactionProxyModel->setSearchString(search_widget->text());
}
void TransactionView::changedAmount() {
if (!transactionProxyModel) {
return;
}
Amount amount_parsed = Amount::zero();
if (BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(),
amountWidget->text(), &amount_parsed)) {
transactionProxyModel->setMinAmount(amount_parsed);
} else {
transactionProxyModel->setMinAmount(Amount::zero());
}
}
void TransactionView::exportClicked() {
if (!model || !model->getOptionsModel()) {
return;
}
// CSV is currently the only supported format
QString filename = GUIUtil::getSaveFileName(
this, tr("Export Transaction History"), QString(),
tr("Comma separated file (*.csv)"), nullptr);
if (filename.isNull()) {
return;
}
CSVModelWriter writer(filename);
// name, column, role
writer.setModel(transactionProxyModel);
writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
if (model->wallet().haveWatchOnly()) {
writer.addColumn(tr("Watch-only"), TransactionTableModel::Watchonly);
}
writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
writer.addColumn(BitcoinUnits::getAmountColumnTitle(
model->getOptionsModel()->getDisplayUnit()),
0, TransactionTableModel::FormattedAmountRole);
writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);
if (!writer.write()) {
Q_EMIT message(tr("Exporting Failed"),
tr("There was an error trying to save the transaction "
"history to %1.")
.arg(filename),
CClientUIInterface::MSG_ERROR);
} else {
Q_EMIT message(
tr("Exporting Successful"),
tr("The transaction history was successfully saved to %1.")
.arg(filename),
CClientUIInterface::MSG_INFORMATION);
}
}
void TransactionView::contextualMenu(const QPoint &point) {
QModelIndex index = transactionView->indexAt(point);
QModelIndexList selection =
transactionView->selectionModel()->selectedRows(0);
if (selection.empty()) {
return;
}
// check if transaction can be abandoned, disable context menu action in
// case it doesn't
TxId txid;
txid.SetHex(selection.at(0)
.data(TransactionTableModel::TxHashRole)
.toString()
.toStdString());
abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(txid));
if (index.isValid()) {
contextMenu->popup(transactionView->viewport()->mapToGlobal(point));
}
}
void TransactionView::abandonTx() {
if (!transactionView || !transactionView->selectionModel()) {
return;
}
QModelIndexList selection =
transactionView->selectionModel()->selectedRows(0);
// get the hash from the TxHashRole (QVariant / QString)
QString hashQStr =
selection.at(0).data(TransactionTableModel::TxHashRole).toString();
TxId txid;
txid.SetHex(hashQStr.toStdString());
// Abandon the wallet transaction over the walletModel
model->wallet().abandonTransaction(txid);
// Update the table
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED,
false);
}
void TransactionView::copyAddress() {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::AddressRole);
}
void TransactionView::copyLabel() {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::LabelRole);
}
void TransactionView::copyAmount() {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::FormattedAmountRole);
}
void TransactionView::copyTxID() {
GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::TxIDRole);
}
void TransactionView::copyTxHex() {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::TxHexRole);
}
void TransactionView::copyTxPlainText() {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::TxPlainTextRole);
}
void TransactionView::editLabel() {
if (!transactionView->selectionModel() || !model) {
return;
}
QModelIndexList selection =
transactionView->selectionModel()->selectedRows();
if (!selection.isEmpty()) {
AddressTableModel *addressBook = model->getAddressTableModel();
if (!addressBook) {
return;
}
QString address =
selection.at(0).data(TransactionTableModel::AddressRole).toString();
if (address.isEmpty()) {
// If this transaction has no associated address, exit
return;
}
// Is address in address book? Address book can miss address when a
// transaction is sent from outside the UI.
int idx = addressBook->lookupAddress(address);
if (idx != -1) {
// Edit sending / receiving address
QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
// Determine type of address, launch appropriate editor dialog type
QString type =
modelIdx.data(AddressTableModel::TypeRole).toString();
EditAddressDialog dlg(type == AddressTableModel::Receive
? EditAddressDialog::EditReceivingAddress
: EditAddressDialog::EditSendingAddress,
this);
dlg.setModel(addressBook);
dlg.loadRow(idx);
dlg.exec();
} else {
// Add sending address
EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, this);
dlg.setModel(addressBook);
dlg.setAddress(address);
dlg.exec();
}
}
}
void TransactionView::showDetails() {
if (!transactionView->selectionModel()) {
return;
}
QModelIndexList selection =
transactionView->selectionModel()->selectedRows();
if (!selection.isEmpty()) {
TransactionDescDialog *dlg = new TransactionDescDialog(selection.at(0));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
}
}
void TransactionView::openThirdPartyTxUrl(QString url) {
if (!transactionView || !transactionView->selectionModel()) {
return;
}
QModelIndexList selection =
transactionView->selectionModel()->selectedRows(0);
if (!selection.isEmpty()) {
QDesktopServices::openUrl(QUrl::fromUserInput(
url.replace("%s", selection.at(0)
.data(TransactionTableModel::TxHashRole)
.toString())));
}
}
QWidget *TransactionView::createDateRangeWidget() {
dateRangeWidget = new QFrame();
dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
dateRangeWidget->setContentsMargins(1, 1, 1, 1);
QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
layout->setContentsMargins(0, 0, 0, 0);
layout->addSpacing(23);
layout->addWidget(new QLabel(tr("Range:")));
dateFrom = new QDateTimeEdit(this);
dateFrom->setDisplayFormat("dd/MM/yy");
dateFrom->setCalendarPopup(true);
dateFrom->setMinimumWidth(100);
dateFrom->setDate(QDate::currentDate().addDays(-7));
layout->addWidget(dateFrom);
layout->addWidget(new QLabel(tr("to")));
dateTo = new QDateTimeEdit(this);
dateTo->setDisplayFormat("dd/MM/yy");
dateTo->setCalendarPopup(true);
dateTo->setMinimumWidth(100);
dateTo->setDate(QDate::currentDate());
layout->addWidget(dateTo);
layout->addStretch();
// Hide by default
dateRangeWidget->setVisible(false);
// Notify on change
connect(dateFrom, &QDateTimeEdit::dateChanged, this,
&TransactionView::dateRangeChanged);
connect(dateTo, &QDateTimeEdit::dateChanged, this,
&TransactionView::dateRangeChanged);
return dateRangeWidget;
}
void TransactionView::dateRangeChanged() {
if (!transactionProxyModel) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
const QDateTime rangeFrom = dateFrom->date().startOfDay();
const QDateTime rangeTo = dateTo->date().endOfDay();
#else
const QDateTime rangeFrom = QDateTime(dateFrom->date());
const QDateTime rangeTo = QDateTime(dateTo->date()).addDays(1);
#endif
transactionProxyModel->setDateRange(rangeFrom, rangeTo);
}
void TransactionView::focusTransaction(const QModelIndex &idx) {
if (!transactionProxyModel) {
return;
}
QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
transactionView->scrollTo(targetIdx);
transactionView->setCurrentIndex(targetIdx);
transactionView->setFocus();
}
void TransactionView::focusTransaction(const uint256 &txid) {
if (!transactionProxyModel) {
return;
}
const QModelIndexList results =
this->model->getTransactionTableModel()->match(
this->model->getTransactionTableModel()->index(0, 0),
TransactionTableModel::TxHashRole,
QString::fromStdString(txid.ToString()), -1);
transactionView->setFocus();
transactionView->selectionModel()->clearSelection();
for (const QModelIndex &index : results) {
const QModelIndex targetIndex =
transactionProxyModel->mapFromSource(index);
transactionView->selectionModel()->select(
targetIndex,
QItemSelectionModel::Rows | QItemSelectionModel::Select);
// Called once per destination to ensure all results are in view, unless
// transactions are not ordered by (ascending or descending) date.
transactionView->scrollTo(targetIndex);
// scrollTo() does not scroll far enough the first time when
// transactions are ordered by ascending date.
if (index == results[0]) {
transactionView->scrollTo(targetIndex);
}
}
}
// We override the virtual resizeEvent of the QWidget to adjust tables column
// sizes as the tables width is proportional to the dialogs width.
void TransactionView::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
columnResizingFixer->stretchColumnWidth(TransactionTableModel::ToAddress);
}
// Need to override default Ctrl+C action for amount as default behaviour is
// just to copy DisplayRole text
bool TransactionView::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_C &&
ke->modifiers().testFlag(Qt::ControlModifier)) {
GUIUtil::copyEntryData(transactionView, 0,
TransactionTableModel::TxPlainTextRole);
return true;
}
}
return QWidget::eventFilter(obj, event);
}
// show/hide column Watch-only
void TransactionView::updateWatchOnlyColumn(bool fHaveWatchOnly) {
watchOnlyWidget->setVisible(fHaveWatchOnly);
transactionView->setColumnHidden(TransactionTableModel::Watchonly,
!fHaveWatchOnly);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Apr 27, 11:34 (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573415
Default Alt Text
(90 KB)

Event Timeline