See More

// Copyright (c) 2011-2020 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 #include #include #include #include #include #include #include #include #include #include #include #include #include to make sure Qt detects this as rich text // Escape the current message as HTML and replace \n by
tooltip = "" + HtmlEscape(tooltip, true) + ""; widget->setToolTip(tooltip); return true; } } return QObject::eventFilter(obj, evt); } LabelOutOfFocusEventFilter::LabelOutOfFocusEventFilter(QObject* parent) : QObject(parent) { } bool LabelOutOfFocusEventFilter::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::FocusOut) { auto focus_out = static_cast(event); if (focus_out->reason() != Qt::PopupFocusReason) { auto label = qobject_cast(watched); if (label) { auto flags = label->textInteractionFlags(); label->setTextInteractionFlags(Qt::NoTextInteraction); label->setTextInteractionFlags(flags); } } } return QObject::eventFilter(watched, event); } #ifdef WIN32 fs::path static StartupShortcutPath() { std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; if (chain == CBaseChainParams::TESTNET) // Remove this special case when CBaseChainParams::TESTNET = "testnet4" 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(&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(&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: // https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html fs::path static 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(); } fs::path static GetAutostartFilePath() { std::string chain = gArgs.GetChainName(); if (chain == CBaseChainParams::MAIN) return GetAutostartDir() / "bitcoin.desktop"; return GetAutostartDir() / strprintf("bitcoin-%s.desktop", 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; } #else bool GetStartOnSystemStartup() { return false; } bool SetStartOnSystemStartup(bool fAutoStart) { return false; } #endif void setClipboard(const QString& str) { QClipboard* clipboard = QApplication::clipboard(); clipboard->setText(str, QClipboard::Clipboard); if (clipboard->supportsSelection()) { 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 NetworkToQString(Network net) { switch (net) { case NET_UNROUTABLE: return QObject::tr("Unroutable"); case NET_IPV4: return "IPv4"; case NET_IPV6: return "IPv6"; case NET_ONION: return "Onion"; case NET_I2P: return "I2P"; case NET_CJDNS: return "CJDNS"; case NET_INTERNAL: return QObject::tr("Internal"); case NET_MAX: assert(false); } // no default case, so the compiler can warn about missing cases assert(false); } QString ConnectionTypeToQString(ConnectionType conn_type, bool prepend_direction) { QString prefix; if (prepend_direction) { prefix = (conn_type == ConnectionType::INBOUND) ? QObject::tr("Inbound") : QObject::tr("Outbound") + " "; } switch (conn_type) { case ConnectionType::INBOUND: return prefix; case ConnectionType::OUTBOUND_FULL_RELAY: return prefix + QObject::tr("Full Relay"); case ConnectionType::BLOCK_RELAY: return prefix + QObject::tr("Block Relay"); case ConnectionType::MANUAL: return prefix + QObject::tr("Manual"); case ConnectionType::FEELER: return prefix + QObject::tr("Feeler"); case ConnectionType::ADDR_FETCH: return prefix + QObject::tr("Address Fetch"); } // no default case, so the compiler can warn about missing cases assert(false); } 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(QObject::tr("%1 d").arg(days)); if (hours) strList.append(QObject::tr("%1 h").arg(hours)); if (mins) strList.append(QObject::tr("%1 m").arg(mins)); if (seconds || (!days && !hours && !mins)) strList.append(QObject::tr("%1 s").arg(seconds)); return strList.join(" "); } QString formatServicesStr(quint64 mask) { QStringList strList; for (const auto& flag : serviceFlagsToStr(mask)) { strList.append(QString::fromStdString(flag)); } if (strList.size()) return strList.join(", "); else return QObject::tr("None"); } QString formatPingTime(std::chrono::microseconds ping_time) { return (ping_time == std::chrono::microseconds::max() || ping_time == 0us) ? QObject::tr("N/A") : QObject::tr("%1 ms").arg(QString::number((int)(count_microseconds(ping_time) / 1000), 10)); } QString formatTimeOffset(int64_t nTimeOffset) { return 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; const int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar 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 < 1'000) return QObject::tr("%1 B").arg(bytes); if (bytes < 1'000'000) return QObject::tr("%1 kB").arg(bytes / 1'000); if (bytes < 1'000'000'000) return QObject::tr("%1 MB").arg(bytes / 1'000'000); return QObject::tr("%1 GB").arg(bytes / 1'000'000'000); } qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) { while(font_size >= minPointSize) { font.setPointSizeF(font_size); QFontMetrics fm(font); if (TextWidth(fm, text) < width) { break; } font_size -= 0.5; } return font_size; } ThemedLabel::ThemedLabel(const PlatformStyle* platform_style, QWidget* parent) : QLabel{parent}, m_platform_style{platform_style} { assert(m_platform_style); } void ThemedLabel::setThemedPixmap(const QString& image_filename, int width, int height) { m_image_filename = image_filename; m_pixmap_width = width; m_pixmap_height = height; updateThemedPixmap(); } void ThemedLabel::changeEvent(QEvent* e) { if (e->type() == QEvent::PaletteChange) { updateThemedPixmap(); } QLabel::changeEvent(e); } void ThemedLabel::updateThemedPixmap() { setPixmap(m_platform_style->SingleColorIcon(m_image_filename).pixmap(m_pixmap_width, m_pixmap_height)); } ClickableLabel::ClickableLabel(const PlatformStyle* platform_style, QWidget* parent) : ThemedLabel{platform_style, parent} { } 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(event)->key() == Qt::Key_Escape) { Q_EMIT keyEscapePressed(); } } return QItemDelegate::eventFilter(object, event); } void PolishProgressDialog(QProgressDialog* dialog) { #ifdef Q_OS_MAC // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357. const int margin = TextWidth(dialog->fontMetrics(), ("X")); dialog->resize(dialog->width() + 2 * margin, dialog->height()); #endif // QProgressDialog estimates the time the operation will take (based on time // for steps), and only shows itself if that estimate is beyond minimumDuration. // The default minimumDuration value is 4 seconds, and it could make users // think that the GUI is frozen. dialog->setMinimumDuration(0); } 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 } void LogQtInfo() { #ifdef QT_STATIC const std::string qt_link{"static"}; #else const std::string qt_link{"dynamic"}; #endif #ifdef QT_STATICPLUGIN const std::string plugin_link{"static"}; #else const std::string plugin_link{"dynamic"}; #endif LogPrintf("Qt %s (%s), plugin=%s (%s)\n", qVersion(), qt_link, QGuiApplication::platformName().toStdString(), plugin_link); const auto static_plugins = QPluginLoader::staticPlugins(); if (static_plugins.empty()) { LogPrintf("No static plugins.\n"); } else { LogPrintf("Static plugins:\n"); for (const QStaticPlugin& p : static_plugins) { QJsonObject meta_data = p.metaData(); const std::string plugin_class = meta_data.take(QString("className")).toString().toStdString(); const int plugin_version = meta_data.take(QString("version")).toInt(); LogPrintf(" %s, version %d\n", plugin_class, plugin_version); } } LogPrintf("Style: %s / %s\n", QApplication::style()->objectName().toStdString(), QApplication::style()->metaObject()->className()); LogPrintf("System: %s, %s\n", QSysInfo::prettyProductName().toStdString(), QSysInfo::buildAbi().toStdString()); for (const QScreen* s : QGuiApplication::screens()) { LogPrintf("Screen: %s %dx%d, pixel ratio=%.1f\n", s->name().toStdString(), s->size().width(), s->size().height(), s->devicePixelRatio()); } } void PopupMenu(QMenu* menu, const QPoint& point, QAction* at_action) { // The qminimal plugin does not provide window system integration. if (QApplication::platformName() == "minimal") return; menu->popup(point, at_action); } QDateTime StartOfDay(const QDate& date) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) return date.startOfDay(); #else return QDateTime(date); #endif } bool HasPixmap(const QLabel* label) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) return !label->pixmap(Qt::ReturnByValue).isNull(); #else return label->pixmap() != nullptr; #endif } QImage GetImage(const QLabel* label) { if (!HasPixmap(label)) { return QImage(); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) return label->pixmap(Qt::ReturnByValue).toImage(); #else return label->pixmap()->toImage(); #endif } QString MakeHtmlLink(const QString& source, const QString& link) { return QString(source).replace( link, QLatin1String("") + link + QLatin1String("")); } void PrintSlotException( const std::exception* exception, const QObject* sender, const QObject* receiver) { std::string description = sender->metaObject()->className(); description += "->"; description += receiver->metaObject()->className(); PrintExceptionContinue(exception, description.c_str()); } } // namespace GUIUtil