Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711315
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
90 KB
Subscribers
None
View Options
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 ¶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 = 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 ¶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 */
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, ¤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(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 ¶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();
+// 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
Details
Attached
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)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment