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