35 #include <QAbstractButton>
36 #include <QAbstractItemView>
37 #include <QApplication>
40 #include <QDesktopServices>
42 #include <QDoubleValidator>
43 #include <QFileDialog>
45 #include <QFontDatabase>
46 #include <QFontMetrics>
47 #include <QGuiApplication>
48 #include <QJsonObject>
50 #include <QLatin1String>
55 #include <QMouseEvent>
56 #include <QPluginLoader>
57 #include <QProgressDialog>
63 #include <QTextDocument>
86 return QLocale::system().toString(date.date(), QLocale::ShortFormat) + QString(
" ") + date.toString(
"hh:mm");
91 return dateTimeStr(QDateTime::fromSecsSinceEpoch(nTime));
96 if (use_embedded_font) {
97 return {
"Roboto Mono"};
99 return QFontDatabase::systemFont(QFontDatabase::FixedFont);
103 static const uint8_t
dummydata[] = {0xeb,0x15,0x23,0x1d,0xfc,0xeb,0x60,0x92,0x58,0x86,0xb6,0x7d,0x06,0x52,0x99,0x92,0x59,0x15,0xae,0xb1,0x72,0xc0,0x66,0x47};
110 for(
int i=0; i<256; ++i) {
115 sourcedata[sourcedata.size()-1] += 1;
122 parent->setFocusProxy(widget);
127 widget->setPlaceholderText(QObject::tr(
"Enter a Bitcoin address (e.g. %1)").arg(
135 QObject::connect(
new QShortcut(shortcut, button), &QShortcut::activated, [button]() { button->animateClick(); });
141 if(!uri.isValid() || uri.scheme() != QString(
"bitcoin"))
147 if (rv.
address.endsWith(
"/")) {
152 QUrlQuery uriQuery(uri);
153 QList<QPair<QString, QString> > items = uriQuery.queryItems();
154 for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++)
156 bool fShouldReturnFalse =
false;
157 if (i->first.startsWith(
"req-"))
159 i->first.remove(0, 4);
160 fShouldReturnFalse =
true;
163 if (i->first ==
"label")
165 rv.
label = i->second;
166 fShouldReturnFalse =
false;
168 if (i->first ==
"message")
171 fShouldReturnFalse =
false;
173 else if (i->first ==
"amount")
175 if(!i->second.isEmpty())
182 fShouldReturnFalse =
false;
185 if (fShouldReturnFalse)
197 QUrl uriInstance(uri);
203 bool bech_32 = info.
address.startsWith(QString::fromStdString(
Params().Bech32HRP() +
"1"));
205 QString ret = QString(
"bitcoin:%1").arg(bech_32 ? info.
address.toUpper() : info.
address);
214 if (!info.
label.isEmpty())
216 QString lbl(QUrl::toPercentEncoding(info.
label));
217 ret += QString(
"%1label=%2").arg(paramCount == 0 ?
"?" :
"&").arg(lbl);
223 QString msg(QUrl::toPercentEncoding(info.
message));
224 ret += QString(
"%1message=%2").arg(paramCount == 0 ?
"?" :
"&").arg(msg);
235 CTxOut txOut(amount, script);
241 QString escaped = str.toHtmlEscaped();
244 escaped = escaped.replace(
"\n",
"<br>\n");
251 return HtmlEscape(QString::fromStdString(str), fMultiLine);
256 if(!view || !view->selectionModel())
258 QModelIndexList selection = view->selectionModel()->selectedRows(column);
260 if(!selection.isEmpty())
267 QList<QModelIndex>
getEntryData(
const QAbstractItemView *view,
int column)
269 if(!view || !view->selectionModel())
270 return QList<QModelIndex>();
271 return view->selectionModel()->selectedRows(column);
277 if (selection.isEmpty())
return false;
278 return !selection.at(0).data(role).toString().isEmpty();
283 const int id = QFontDatabase::addApplicationFont(file_name);
293 const QString &filter,
294 QString *selectedSuffixOut)
296 QString selectedFilter;
300 myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
310 QRegExp filter_re(
".* \\(\\*\\.(.*)[ \\)]");
311 QString selectedSuffix;
312 if(filter_re.exactMatch(selectedFilter))
314 selectedSuffix = filter_re.cap(1);
318 QFileInfo info(result);
319 if(!result.isEmpty())
321 if(info.suffix().isEmpty() && !selectedSuffix.isEmpty())
324 if(!result.endsWith(
"."))
326 result.append(selectedSuffix);
331 if(selectedSuffixOut)
333 *selectedSuffixOut = selectedSuffix;
339 const QString &filter,
340 QString *selectedSuffixOut)
342 QString selectedFilter;
346 myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
355 if(selectedSuffixOut)
358 QRegExp filter_re(
".* \\(\\*\\.(.*)[ \\)]");
359 QString selectedSuffix;
360 if(filter_re.exactMatch(selectedFilter))
362 selectedSuffix = filter_re.cap(1);
364 *selectedSuffixOut = selectedSuffix;
371 if(QThread::currentThread() != qApp->thread())
373 return Qt::BlockingQueuedConnection;
377 return Qt::DirectConnection;
383 QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p));
384 if (!atW)
return false;
385 return atW->window() == w;
393 &&
checkPoint(QPoint(w->width() - 1, w->height() - 1), w)
394 &&
checkPoint(QPoint(w->width() / 2, w->height() / 2), w));
405 if (w->isMinimized()) {
417 QObject::connect(
new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), w), &QShortcut::activated, w, &QWidget::close);
426 QDesktopServices::openUrl(QUrl::fromLocalFile(
PathToQString(pathDebug)));
434 std::ofstream configFile{pathConfig, std::ios_base::app};
436 if (!configFile.good())
442 bool res = QDesktopServices::openUrl(QUrl::fromLocalFile(
PathToQString(pathConfig)));
446 res = QProcess::startDetached(
"/usr/bin/open", QStringList{
"-t",
PathToQString(pathConfig)});
455 size_threshold(_size_threshold)
462 if(evt->type() == QEvent::ToolTipChange)
464 QWidget *widget =
static_cast<QWidget*
>(obj);
465 QString tooltip = widget->toolTip();
466 if(tooltip.size() >
size_threshold && !tooltip.startsWith(
"<qt") && !Qt::mightBeRichText(tooltip))
470 tooltip =
"<qt>" +
HtmlEscape(tooltip,
true) +
"</qt>";
471 widget->setToolTip(tooltip);
475 return QObject::eventFilter(obj, evt);
485 if (event->type() == QEvent::FocusOut) {
486 auto focus_out =
static_cast<QFocusEvent*
>(event);
487 if (focus_out->reason() != Qt::PopupFocusReason) {
488 auto label = qobject_cast<QLabel*>(watched);
490 auto flags = label->textInteractionFlags();
491 label->setTextInteractionFlags(Qt::NoTextInteraction);
492 label->setTextInteractionFlags(
flags);
497 return QObject::eventFilter(watched, event);
501 fs::path static StartupShortcutPath()
505 return GetSpecialFolderPath(CSIDL_STARTUP) /
"Bitcoin.lnk";
507 return GetSpecialFolderPath(CSIDL_STARTUP) /
"Bitcoin (testnet).lnk";
508 return GetSpecialFolderPath(CSIDL_STARTUP) /
strprintf(
"Bitcoin (%s).lnk", chain);
520 fs::remove(StartupShortcutPath());
524 CoInitialize(
nullptr);
527 IShellLinkW* psl =
nullptr;
528 HRESULT hres = CoCreateInstance(CLSID_ShellLink,
nullptr,
529 CLSCTX_INPROC_SERVER, IID_IShellLinkW,
530 reinterpret_cast<void**
>(&psl));
536 GetModuleFileNameW(
nullptr, pszExePath, ARRAYSIZE(pszExePath));
539 QString strArgs =
"-min";
544 psl->SetPath(pszExePath);
545 PathRemoveFileSpecW(pszExePath);
546 psl->SetWorkingDirectory(pszExePath);
547 psl->SetShowCmd(SW_SHOWMINNOACTIVE);
548 psl->SetArguments(strArgs.toStdWString().c_str());
552 IPersistFile* ppf =
nullptr;
553 hres = psl->QueryInterface(IID_IPersistFile,
reinterpret_cast<void**
>(&ppf));
557 hres = ppf->Save(StartupShortcutPath().wstring().c_str(), TRUE);
570 #elif defined(Q_OS_LINUX)
577 char* pszConfigHome = getenv(
"XDG_CONFIG_HOME");
578 if (pszConfigHome)
return fs::path(pszConfigHome) /
"autostart";
579 char* pszHome = getenv(
"HOME");
580 if (pszHome)
return fs::path(pszHome) /
".config" /
"autostart";
584 fs::path static GetAutostartFilePath()
588 return GetAutostartDir() /
"bitcoin.desktop";
589 return GetAutostartDir() /
strprintf(
"bitcoin-%s.desktop", chain);
594 std::ifstream optionFile{GetAutostartFilePath()};
595 if (!optionFile.good())
599 while (!optionFile.eof())
601 getline(optionFile, line);
602 if (line.find(
"Hidden") != std::string::npos &&
603 line.find(
"true") != std::string::npos)
614 fs::remove(GetAutostartFilePath());
618 ssize_t r = readlink(
"/proc/self/exe", pszExePath,
sizeof(pszExePath) - 1);
621 pszExePath[r] =
'\0';
625 std::ofstream optionFile{GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc};
626 if (!optionFile.good())
630 optionFile <<
"[Desktop Entry]\n";
631 optionFile <<
"Type=Application\n";
633 optionFile <<
"Name=Bitcoin\n";
635 optionFile <<
strprintf(
"Name=Bitcoin (%s)\n", chain);
636 optionFile <<
"Exec=" << pszExePath <<
strprintf(
" -min -chain=%s\n", chain);
637 optionFile <<
"Terminal=false\n";
638 optionFile <<
"Hidden=false\n";
653 QClipboard* clipboard = QApplication::clipboard();
654 clipboard->setText(str, QClipboard::Clipboard);
655 if (clipboard->supportsSelection()) {
656 clipboard->setText(str, QClipboard::Selection);
662 return fs::u8path(path.toStdString());
667 return QString::fromStdString(path.u8string());
688 if (prepend_direction) {
692 QObject::tr(
"Inbound") :
695 QObject::tr(
"Outbound") +
" ";
718 int days = secs / 86400;
719 int hours = (secs % 86400) / 3600;
720 int mins = (secs % 3600) / 60;
721 int seconds = secs % 60;
724 strList.append(QObject::tr(
"%1 d").arg(days));
726 strList.append(QObject::tr(
"%1 h").arg(hours));
728 strList.append(QObject::tr(
"%1 m").arg(mins));
729 if (seconds || (!days && !hours && !mins))
730 strList.append(QObject::tr(
"%1 s").arg(seconds));
732 return strList.join(
" ");
740 strList.append(QString::fromStdString(flag));
744 return strList.join(
", ");
746 return QObject::tr(
"None");
751 return (ping_time == std::chrono::microseconds::max() || ping_time == 0us) ?
753 QObject::tr(
"%1 ms").arg(QString::number((
int)(
count_microseconds(ping_time) / 1000), 10));
758 return QObject::tr(
"%1 s").arg(QString::number((
int)nTimeOffset, 10));
764 QString timeBehindText;
765 const int HOUR_IN_SECONDS = 60*60;
766 const int DAY_IN_SECONDS = 24*60*60;
767 const int WEEK_IN_SECONDS = 7*24*60*60;
768 const int YEAR_IN_SECONDS = 31556952;
771 timeBehindText = QObject::tr(
"%n second(s)",
"",secs);
773 else if(secs < 2*HOUR_IN_SECONDS)
775 timeBehindText = QObject::tr(
"%n minute(s)",
"",secs/60);
777 else if(secs < 2*DAY_IN_SECONDS)
779 timeBehindText = QObject::tr(
"%n hour(s)",
"",secs/HOUR_IN_SECONDS);
781 else if(secs < 2*WEEK_IN_SECONDS)
783 timeBehindText = QObject::tr(
"%n day(s)",
"",secs/DAY_IN_SECONDS);
785 else if(secs < YEAR_IN_SECONDS)
787 timeBehindText = QObject::tr(
"%n week(s)",
"",secs/WEEK_IN_SECONDS);
791 qint64 years = secs / YEAR_IN_SECONDS;
792 qint64 remainder = secs % YEAR_IN_SECONDS;
793 timeBehindText = QObject::tr(
"%1 and %2").arg(QObject::tr(
"%n year(s)",
"", years)).arg(QObject::tr(
"%n week(s)",
"", remainder/WEEK_IN_SECONDS));
795 return timeBehindText;
801 return QObject::tr("%1 B").arg(bytes);
802 if (bytes < 1'000
'000)
803 return QObject::tr("%1 kB").arg(bytes / 1'000);
804 if (bytes < 1
'000'000
'000)
805 return QObject::tr("%1 MB").arg(bytes / 1'000
'000);
807 return QObject::tr("%1 GB").arg(bytes / 1'000
'000'000);
811 while(font_size >= minPointSize) {
812 font.setPointSizeF(font_size);
813 QFontMetrics fm(font);
823 : QLabel{parent}, m_platform_style{platform_style}
838 if (e->type() == QEvent::PaletteChange) {
842 QLabel::changeEvent(e);
867 if (event->type() == QEvent::KeyPress) {
868 if (
static_cast<QKeyEvent*
>(event)->key() == Qt::Key_Escape) {
872 return QItemDelegate::eventFilter(
object, event);
879 const int margin =
TextWidth(dialog->fontMetrics(), (
"X"));
880 dialog->resize(dialog->width() + 2 * margin, dialog->height());
886 dialog->setMinimumDuration(0);
889 int TextWidth(
const QFontMetrics& fm,
const QString& text)
891 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
892 return fm.horizontalAdvance(text);
894 return fm.width(text);
901 const std::string qt_link{
"static"};
903 const std::string qt_link{
"dynamic"};
905 #ifdef QT_STATICPLUGIN
906 const std::string plugin_link{
"static"};
908 const std::string plugin_link{
"dynamic"};
910 LogPrintf(
"Qt %s (%s), plugin=%s (%s)\n", qVersion(), qt_link, QGuiApplication::platformName().toStdString(), plugin_link);
911 const auto static_plugins = QPluginLoader::staticPlugins();
912 if (static_plugins.empty()) {
916 for (
const QStaticPlugin& p : static_plugins) {
917 QJsonObject meta_data = p.metaData();
918 const std::string plugin_class = meta_data.take(QString(
"className")).toString().toStdString();
919 const int plugin_version = meta_data.take(QString(
"version")).toInt();
920 LogPrintf(
" %s, version %d\n", plugin_class, plugin_version);
924 LogPrintf(
"Style: %s / %s\n", QApplication::style()->objectName().toStdString(), QApplication::style()->metaObject()->className());
925 LogPrintf(
"System: %s, %s\n", QSysInfo::prettyProductName().toStdString(), QSysInfo::buildAbi().toStdString());
926 for (
const QScreen* s : QGuiApplication::screens()) {
927 LogPrintf(
"Screen: %s %dx%d, pixel ratio=%.1f\n", s->name().toStdString(), s->size().width(), s->size().height(), s->devicePixelRatio());
931 void PopupMenu(QMenu* menu,
const QPoint& point, QAction* at_action)
934 if (QApplication::platformName() ==
"minimal")
return;
935 menu->popup(point, at_action);
940 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
941 return date.startOfDay();
943 return QDateTime(date);
949 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
950 return !label->pixmap(Qt::ReturnByValue).isNull();
952 return label->pixmap() !=
nullptr;
962 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
963 return label->pixmap(Qt::ReturnByValue).toImage();
965 return label->pixmap()->toImage();
971 return QString(
source).replace(
973 QLatin1String(
"<a href=\"") + link + QLatin1String(
"\">") + link + QLatin1String(
"</a>"));
977 const std::exception* exception,
978 const QObject* sender,
979 const QObject* receiver)
981 std::string description = sender->metaObject()->className();
983 description += receiver->metaObject()->className();
989 dialog->setAttribute(Qt::WA_DeleteOnClose);
990 dialog->setWindowModality(Qt::ApplicationModal);