Line data Source code
1 : // Copyright (c) 2011-2020 The Bitcoin Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <qt/guiutil.h>
6 :
7 : #include <qt/bitcoinaddressvalidator.h>
8 : #include <qt/bitcoinunits.h>
9 : #include <qt/qvalidatedlineedit.h>
10 : #include <qt/sendcoinsrecipient.h>
11 :
12 : #include <base58.h>
13 : #include <chainparams.h>
14 : #include <interfaces/node.h>
15 : #include <key_io.h>
16 : #include <policy/policy.h>
17 : #include <primitives/transaction.h>
18 : #include <protocol.h>
19 : #include <script/script.h>
20 : #include <script/standard.h>
21 : #include <util/system.h>
22 :
23 : #ifdef WIN32
24 : #ifndef NOMINMAX
25 : #define NOMINMAX
26 : #endif
27 : #include <shellapi.h>
28 : #include <shlobj.h>
29 : #include <shlwapi.h>
30 : #endif
31 :
32 : #include <QAbstractItemView>
33 : #include <QApplication>
34 : #include <QClipboard>
35 : #include <QDateTime>
36 : #include <QDesktopServices>
37 : #include <QDoubleValidator>
38 : #include <QFileDialog>
39 : #include <QFont>
40 : #include <QFontDatabase>
41 : #include <QFontMetrics>
42 : #include <QGuiApplication>
43 : #include <QKeyEvent>
44 : #include <QLineEdit>
45 : #include <QList>
46 : #include <QMenu>
47 : #include <QMouseEvent>
48 : #include <QProgressDialog>
49 : #include <QScreen>
50 : #include <QSettings>
51 : #include <QShortcut>
52 : #include <QSize>
53 : #include <QString>
54 : #include <QTextDocument> // for Qt::mightBeRichText
55 : #include <QThread>
56 : #include <QUrlQuery>
57 : #include <QtGlobal>
58 :
59 : #if defined(Q_OS_MAC)
60 :
61 : #include <QProcess>
62 :
63 : void ForceActivation();
64 : #endif
65 :
66 : namespace GUIUtil {
67 :
68 0 : QString dateTimeStr(const QDateTime &date)
69 : {
70 0 : return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
71 0 : }
72 :
73 0 : QString dateTimeStr(qint64 nTime)
74 : {
75 0 : return dateTimeStr(QDateTime::fromTime_t((qint32)nTime));
76 0 : }
77 :
78 0 : QFont fixedPitchFont()
79 : {
80 0 : return QFontDatabase::systemFont(QFontDatabase::FixedFont);
81 : }
82 :
83 : // Just some dummy data to generate a convincing random-looking (but consistent) address
84 : 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};
85 :
86 : // Generate a dummy address with invalid CRC, starting with the network prefix.
87 0 : static std::string DummyAddress(const CChainParams ¶ms)
88 : {
89 0 : std::vector<unsigned char> sourcedata = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS);
90 0 : sourcedata.insert(sourcedata.end(), dummydata, dummydata + sizeof(dummydata));
91 0 : for(int i=0; i<256; ++i) { // Try every trailing byte
92 0 : std::string s = EncodeBase58(sourcedata);
93 0 : if (!IsValidDestinationString(s)) {
94 0 : return s;
95 : }
96 0 : sourcedata[sourcedata.size()-1] += 1;
97 0 : }
98 0 : return "";
99 0 : }
100 :
101 0 : void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
102 : {
103 0 : parent->setFocusProxy(widget);
104 :
105 0 : widget->setFont(fixedPitchFont());
106 : // We don't want translators to use own addresses in translations
107 : // and this is the only place, where this address is supplied.
108 0 : widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)").arg(
109 0 : QString::fromStdString(DummyAddress(Params()))));
110 0 : widget->setValidator(new BitcoinAddressEntryValidator(parent));
111 0 : widget->setCheckValidator(new BitcoinAddressCheckValidator(parent));
112 0 : }
113 :
114 11 : bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
115 : {
116 : // return if URI is not valid or is no bitcoin: URI
117 11 : if(!uri.isValid() || uri.scheme() != QString("bitcoin"))
118 0 : return false;
119 :
120 11 : SendCoinsRecipient rv;
121 11 : rv.address = uri.path();
122 : // Trim any following forward slash which may have been added by the OS
123 11 : if (rv.address.endsWith("/")) {
124 0 : rv.address.truncate(rv.address.length() - 1);
125 : }
126 11 : rv.amount = 0;
127 :
128 11 : QUrlQuery uriQuery(uri);
129 11 : QList<QPair<QString, QString> > items = uriQuery.queryItems();
130 23 : for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++)
131 : {
132 : bool fShouldReturnFalse = false;
133 12 : if (i->first.startsWith("req-"))
134 : {
135 2 : i->first.remove(0, 4);
136 : fShouldReturnFalse = true;
137 2 : }
138 :
139 12 : if (i->first == "label")
140 : {
141 2 : rv.label = i->second;
142 : fShouldReturnFalse = false;
143 2 : }
144 12 : if (i->first == "message")
145 : {
146 3 : rv.message = i->second;
147 : fShouldReturnFalse = false;
148 3 : }
149 9 : else if (i->first == "amount")
150 : {
151 5 : if(!i->second.isEmpty())
152 : {
153 5 : if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount))
154 : {
155 2 : return false;
156 : }
157 : }
158 : fShouldReturnFalse = false;
159 3 : }
160 :
161 10 : if (fShouldReturnFalse)
162 1 : return false;
163 9 : }
164 8 : if(out)
165 : {
166 8 : *out = rv;
167 : }
168 8 : return true;
169 11 : }
170 :
171 1 : bool parseBitcoinURI(QString uri, SendCoinsRecipient *out)
172 : {
173 1 : QUrl uriInstance(uri);
174 1 : return parseBitcoinURI(uriInstance, out);
175 1 : }
176 :
177 0 : QString formatBitcoinURI(const SendCoinsRecipient &info)
178 : {
179 0 : bool bech_32 = info.address.startsWith(QString::fromStdString(Params().Bech32HRP() + "1"));
180 :
181 0 : QString ret = QString("bitcoin:%1").arg(bech_32 ? info.address.toUpper() : info.address);
182 : int paramCount = 0;
183 :
184 0 : if (info.amount)
185 : {
186 0 : ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, info.amount, false, BitcoinUnits::SeparatorStyle::NEVER));
187 : paramCount++;
188 0 : }
189 :
190 0 : if (!info.label.isEmpty())
191 : {
192 0 : QString lbl(QUrl::toPercentEncoding(info.label));
193 0 : ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl);
194 0 : paramCount++;
195 0 : }
196 :
197 0 : if (!info.message.isEmpty())
198 : {
199 0 : QString msg(QUrl::toPercentEncoding(info.message));
200 0 : ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg);
201 : paramCount++;
202 0 : }
203 :
204 : return ret;
205 0 : }
206 :
207 0 : bool isDust(interfaces::Node& node, const QString& address, const CAmount& amount)
208 : {
209 0 : CTxDestination dest = DecodeDestination(address.toStdString());
210 0 : CScript script = GetScriptForDestination(dest);
211 0 : CTxOut txOut(amount, script);
212 0 : return IsDust(txOut, node.getDustRelayFee());
213 0 : }
214 :
215 0 : QString HtmlEscape(const QString& str, bool fMultiLine)
216 : {
217 0 : QString escaped = str.toHtmlEscaped();
218 0 : if(fMultiLine)
219 : {
220 0 : escaped = escaped.replace("\n", "<br>\n");
221 0 : }
222 : return escaped;
223 0 : }
224 :
225 0 : QString HtmlEscape(const std::string& str, bool fMultiLine)
226 : {
227 0 : return HtmlEscape(QString::fromStdString(str), fMultiLine);
228 0 : }
229 :
230 0 : void copyEntryData(const QAbstractItemView *view, int column, int role)
231 : {
232 0 : if(!view || !view->selectionModel())
233 : return;
234 0 : QModelIndexList selection = view->selectionModel()->selectedRows(column);
235 :
236 0 : if(!selection.isEmpty())
237 : {
238 : // Copy first item
239 0 : setClipboard(selection.at(0).data(role).toString());
240 0 : }
241 0 : }
242 :
243 0 : QList<QModelIndex> getEntryData(const QAbstractItemView *view, int column)
244 : {
245 0 : if(!view || !view->selectionModel())
246 0 : return QList<QModelIndex>();
247 0 : return view->selectionModel()->selectedRows(column);
248 0 : }
249 :
250 0 : bool hasEntryData(const QAbstractItemView *view, int column, int role)
251 : {
252 0 : QModelIndexList selection = getEntryData(view, column);
253 0 : if (selection.isEmpty()) return false;
254 0 : return !selection.at(0).data(role).toString().isEmpty();
255 0 : }
256 :
257 0 : QString getDefaultDataDirectory()
258 : {
259 0 : return boostPathToQString(GetDefaultDataDir());
260 0 : }
261 :
262 0 : QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir,
263 : const QString &filter,
264 : QString *selectedSuffixOut)
265 : {
266 0 : QString selectedFilter;
267 0 : QString myDir;
268 0 : if(dir.isEmpty()) // Default to user documents location
269 : {
270 0 : myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
271 0 : }
272 : else
273 : {
274 0 : myDir = dir;
275 : }
276 : /* Directly convert path to native OS path separators */
277 0 : QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter));
278 :
279 : /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
280 0 : QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]");
281 0 : QString selectedSuffix;
282 0 : if(filter_re.exactMatch(selectedFilter))
283 : {
284 0 : selectedSuffix = filter_re.cap(1);
285 0 : }
286 :
287 : /* Add suffix if needed */
288 0 : QFileInfo info(result);
289 0 : if(!result.isEmpty())
290 : {
291 0 : if(info.suffix().isEmpty() && !selectedSuffix.isEmpty())
292 : {
293 : /* No suffix specified, add selected suffix */
294 0 : if(!result.endsWith("."))
295 0 : result.append(".");
296 0 : result.append(selectedSuffix);
297 : }
298 : }
299 :
300 : /* Return selected suffix if asked to */
301 0 : if(selectedSuffixOut)
302 : {
303 0 : *selectedSuffixOut = selectedSuffix;
304 0 : }
305 : return result;
306 0 : }
307 :
308 0 : QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir,
309 : const QString &filter,
310 : QString *selectedSuffixOut)
311 : {
312 0 : QString selectedFilter;
313 0 : QString myDir;
314 0 : if(dir.isEmpty()) // Default to user documents location
315 : {
316 0 : myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
317 0 : }
318 : else
319 : {
320 0 : myDir = dir;
321 : }
322 : /* Directly convert path to native OS path separators */
323 0 : QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent, caption, myDir, filter, &selectedFilter));
324 :
325 0 : if(selectedSuffixOut)
326 : {
327 : /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
328 0 : QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]");
329 0 : QString selectedSuffix;
330 0 : if(filter_re.exactMatch(selectedFilter))
331 : {
332 0 : selectedSuffix = filter_re.cap(1);
333 0 : }
334 0 : *selectedSuffixOut = selectedSuffix;
335 0 : }
336 : return result;
337 0 : }
338 :
339 0 : Qt::ConnectionType blockingGUIThreadConnection()
340 : {
341 0 : if(QThread::currentThread() != qApp->thread())
342 : {
343 0 : return Qt::BlockingQueuedConnection;
344 : }
345 : else
346 : {
347 0 : return Qt::DirectConnection;
348 : }
349 0 : }
350 :
351 0 : bool checkPoint(const QPoint &p, const QWidget *w)
352 : {
353 0 : QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p));
354 0 : if (!atW) return false;
355 0 : return atW->window() == w;
356 0 : }
357 :
358 0 : bool isObscured(QWidget *w)
359 : {
360 0 : return !(checkPoint(QPoint(0, 0), w)
361 0 : && checkPoint(QPoint(w->width() - 1, 0), w)
362 0 : && checkPoint(QPoint(0, w->height() - 1), w)
363 0 : && checkPoint(QPoint(w->width() - 1, w->height() - 1), w)
364 0 : && checkPoint(QPoint(w->width() / 2, w->height() / 2), w));
365 : }
366 :
367 0 : void bringToFront(QWidget* w)
368 : {
369 : #ifdef Q_OS_MAC
370 0 : ForceActivation();
371 : #endif
372 :
373 0 : if (w) {
374 : // activateWindow() (sometimes) helps with keyboard focus on Windows
375 0 : if (w->isMinimized()) {
376 0 : w->showNormal();
377 0 : } else {
378 0 : w->show();
379 : }
380 0 : w->activateWindow();
381 0 : w->raise();
382 0 : }
383 0 : }
384 :
385 0 : void handleCloseWindowShortcut(QWidget* w)
386 : {
387 0 : QObject::connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), w), &QShortcut::activated, w, &QWidget::close);
388 0 : }
389 :
390 0 : void openDebugLogfile()
391 : {
392 0 : fs::path pathDebug = GetDataDir() / "debug.log";
393 :
394 : /* Open debug.log with the associated application */
395 0 : if (fs::exists(pathDebug))
396 0 : QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug)));
397 0 : }
398 :
399 0 : bool openBitcoinConf()
400 : {
401 0 : fs::path pathConfig = GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
402 :
403 : /* Create the file */
404 0 : fsbridge::ofstream configFile(pathConfig, std::ios_base::app);
405 :
406 0 : if (!configFile.good())
407 0 : return false;
408 :
409 0 : configFile.close();
410 :
411 : /* Open bitcoin.conf with the associated application */
412 0 : bool res = QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig)));
413 : #ifdef Q_OS_MAC
414 : // Workaround for macOS-specific behavior; see #15409.
415 0 : if (!res) {
416 0 : res = QProcess::startDetached("/usr/bin/open", QStringList{"-t", boostPathToQString(pathConfig)});
417 0 : }
418 : #endif
419 :
420 0 : return res;
421 0 : }
422 :
423 0 : ToolTipToRichTextFilter::ToolTipToRichTextFilter(int _size_threshold, QObject *parent) :
424 0 : QObject(parent),
425 0 : size_threshold(_size_threshold)
426 0 : {
427 :
428 0 : }
429 :
430 0 : bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt)
431 : {
432 0 : if(evt->type() == QEvent::ToolTipChange)
433 : {
434 0 : QWidget *widget = static_cast<QWidget*>(obj);
435 0 : QString tooltip = widget->toolTip();
436 0 : if(tooltip.size() > size_threshold && !tooltip.startsWith("<qt") && !Qt::mightBeRichText(tooltip))
437 : {
438 : // Envelop with <qt></qt> to make sure Qt detects this as rich text
439 : // Escape the current message as HTML and replace \n by <br>
440 0 : tooltip = "<qt>" + HtmlEscape(tooltip, true) + "</qt>";
441 0 : widget->setToolTip(tooltip);
442 0 : return true;
443 : }
444 0 : }
445 0 : return QObject::eventFilter(obj, evt);
446 0 : }
447 :
448 0 : LabelOutOfFocusEventFilter::LabelOutOfFocusEventFilter(QObject* parent)
449 0 : : QObject(parent)
450 0 : {
451 0 : }
452 :
453 0 : bool LabelOutOfFocusEventFilter::eventFilter(QObject* watched, QEvent* event)
454 : {
455 0 : if (event->type() == QEvent::FocusOut) {
456 0 : auto focus_out = static_cast<QFocusEvent*>(event);
457 0 : if (focus_out->reason() != Qt::PopupFocusReason) {
458 0 : auto label = qobject_cast<QLabel*>(watched);
459 0 : if (label) {
460 0 : auto flags = label->textInteractionFlags();
461 0 : label->setTextInteractionFlags(Qt::NoTextInteraction);
462 0 : label->setTextInteractionFlags(flags);
463 0 : }
464 0 : }
465 0 : }
466 :
467 0 : return QObject::eventFilter(watched, event);
468 : }
469 :
470 0 : void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
471 : {
472 0 : connect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized);
473 0 : connect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
474 0 : }
475 :
476 : // We need to disconnect these while handling the resize events, otherwise we can enter infinite loops.
477 0 : void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals()
478 : {
479 0 : disconnect(tableView->horizontalHeader(), &QHeaderView::sectionResized, this, &TableViewLastColumnResizingFixer::on_sectionResized);
480 0 : disconnect(tableView->horizontalHeader(), &QHeaderView::geometriesChanged, this, &TableViewLastColumnResizingFixer::on_geometriesChanged);
481 0 : }
482 :
483 : // Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed.
484 : // Refactored here for readability.
485 0 : void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode)
486 : {
487 0 : tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode);
488 0 : }
489 :
490 0 : void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width)
491 : {
492 0 : tableView->setColumnWidth(nColumnIndex, width);
493 0 : tableView->horizontalHeader()->resizeSection(nColumnIndex, width);
494 0 : }
495 :
496 0 : int TableViewLastColumnResizingFixer::getColumnsWidth()
497 : {
498 : int nColumnsWidthSum = 0;
499 0 : for (int i = 0; i < columnCount; i++)
500 : {
501 0 : nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i);
502 : }
503 0 : return nColumnsWidthSum;
504 : }
505 :
506 0 : int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column)
507 : {
508 0 : int nResult = lastColumnMinimumWidth;
509 0 : int nTableWidth = tableView->horizontalHeader()->width();
510 :
511 0 : if (nTableWidth > 0)
512 : {
513 0 : int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column);
514 0 : nResult = std::max(nResult, nTableWidth - nOtherColsWidth);
515 0 : }
516 :
517 0 : return nResult;
518 0 : }
519 :
520 : // Make sure we don't make the columns wider than the table's viewport width.
521 0 : void TableViewLastColumnResizingFixer::adjustTableColumnsWidth()
522 : {
523 0 : disconnectViewHeadersSignals();
524 0 : resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex));
525 0 : connectViewHeadersSignals();
526 :
527 0 : int nTableWidth = tableView->horizontalHeader()->width();
528 0 : int nColsWidth = getColumnsWidth();
529 0 : if (nColsWidth > nTableWidth)
530 : {
531 0 : resizeColumn(secondToLastColumnIndex,getAvailableWidthForColumn(secondToLastColumnIndex));
532 0 : }
533 0 : }
534 :
535 : // Make column use all the space available, useful during window resizing.
536 0 : void TableViewLastColumnResizingFixer::stretchColumnWidth(int column)
537 : {
538 0 : disconnectViewHeadersSignals();
539 0 : resizeColumn(column, getAvailableWidthForColumn(column));
540 0 : connectViewHeadersSignals();
541 0 : }
542 :
543 : // When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
544 0 : void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize)
545 : {
546 0 : adjustTableColumnsWidth();
547 0 : int remainingWidth = getAvailableWidthForColumn(logicalIndex);
548 0 : if (newSize > remainingWidth)
549 : {
550 0 : resizeColumn(logicalIndex, remainingWidth);
551 0 : }
552 0 : }
553 :
554 : // When the table's geometry is ready, we manually perform the stretch of the "Message" column,
555 : // as the "Stretch" resize mode does not allow for interactive resizing.
556 0 : void TableViewLastColumnResizingFixer::on_geometriesChanged()
557 : {
558 0 : if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0)
559 : {
560 0 : disconnectViewHeadersSignals();
561 0 : resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex));
562 0 : connectViewHeadersSignals();
563 0 : }
564 0 : }
565 :
566 : /**
567 : * Initializes all internal variables and prepares the
568 : * the resize modes of the last 2 columns of the table and
569 : */
570 0 : TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) :
571 0 : QObject(parent),
572 0 : tableView(table),
573 0 : lastColumnMinimumWidth(lastColMinimumWidth),
574 0 : allColumnsMinimumWidth(allColsMinimumWidth)
575 0 : {
576 0 : columnCount = tableView->horizontalHeader()->count();
577 0 : lastColumnIndex = columnCount - 1;
578 0 : secondToLastColumnIndex = columnCount - 2;
579 0 : tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth);
580 0 : setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive);
581 0 : setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive);
582 0 : }
583 :
584 : #ifdef WIN32
585 : fs::path static StartupShortcutPath()
586 : {
587 : std::string chain = gArgs.GetChainName();
588 : if (chain == CBaseChainParams::MAIN)
589 : return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk";
590 : if (chain == CBaseChainParams::TESTNET) // Remove this special case when CBaseChainParams::TESTNET = "testnet4"
591 : return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin (testnet).lnk";
592 : return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Bitcoin (%s).lnk", chain);
593 : }
594 :
595 : bool GetStartOnSystemStartup()
596 : {
597 : // check for Bitcoin*.lnk
598 : return fs::exists(StartupShortcutPath());
599 : }
600 :
601 : bool SetStartOnSystemStartup(bool fAutoStart)
602 : {
603 : // If the shortcut exists already, remove it for updating
604 : fs::remove(StartupShortcutPath());
605 :
606 : if (fAutoStart)
607 : {
608 : CoInitialize(nullptr);
609 :
610 : // Get a pointer to the IShellLink interface.
611 : IShellLinkW* psl = nullptr;
612 : HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr,
613 : CLSCTX_INPROC_SERVER, IID_IShellLinkW,
614 : reinterpret_cast<void**>(&psl));
615 :
616 : if (SUCCEEDED(hres))
617 : {
618 : // Get the current executable path
619 : WCHAR pszExePath[MAX_PATH];
620 : GetModuleFileNameW(nullptr, pszExePath, ARRAYSIZE(pszExePath));
621 :
622 : // Start client minimized
623 : QString strArgs = "-min";
624 : // Set -testnet /-regtest options
625 : strArgs += QString::fromStdString(strprintf(" -chain=%s", gArgs.GetChainName()));
626 :
627 : // Set the path to the shortcut target
628 : psl->SetPath(pszExePath);
629 : PathRemoveFileSpecW(pszExePath);
630 : psl->SetWorkingDirectory(pszExePath);
631 : psl->SetShowCmd(SW_SHOWMINNOACTIVE);
632 : psl->SetArguments(strArgs.toStdWString().c_str());
633 :
634 : // Query IShellLink for the IPersistFile interface for
635 : // saving the shortcut in persistent storage.
636 : IPersistFile* ppf = nullptr;
637 : hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&ppf));
638 : if (SUCCEEDED(hres))
639 : {
640 : // Save the link by calling IPersistFile::Save.
641 : hres = ppf->Save(StartupShortcutPath().wstring().c_str(), TRUE);
642 : ppf->Release();
643 : psl->Release();
644 : CoUninitialize();
645 : return true;
646 : }
647 : psl->Release();
648 : }
649 : CoUninitialize();
650 : return false;
651 : }
652 : return true;
653 : }
654 : #elif defined(Q_OS_LINUX)
655 :
656 : // Follow the Desktop Application Autostart Spec:
657 : // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
658 :
659 : fs::path static GetAutostartDir()
660 : {
661 : char* pszConfigHome = getenv("XDG_CONFIG_HOME");
662 : if (pszConfigHome) return fs::path(pszConfigHome) / "autostart";
663 : char* pszHome = getenv("HOME");
664 : if (pszHome) return fs::path(pszHome) / ".config" / "autostart";
665 : return fs::path();
666 : }
667 :
668 : fs::path static GetAutostartFilePath()
669 : {
670 : std::string chain = gArgs.GetChainName();
671 : if (chain == CBaseChainParams::MAIN)
672 : return GetAutostartDir() / "bitcoin.desktop";
673 : return GetAutostartDir() / strprintf("bitcoin-%s.desktop", chain);
674 : }
675 :
676 : bool GetStartOnSystemStartup()
677 : {
678 : fsbridge::ifstream optionFile(GetAutostartFilePath());
679 : if (!optionFile.good())
680 : return false;
681 : // Scan through file for "Hidden=true":
682 : std::string line;
683 : while (!optionFile.eof())
684 : {
685 : getline(optionFile, line);
686 : if (line.find("Hidden") != std::string::npos &&
687 : line.find("true") != std::string::npos)
688 : return false;
689 : }
690 : optionFile.close();
691 :
692 : return true;
693 : }
694 :
695 : bool SetStartOnSystemStartup(bool fAutoStart)
696 : {
697 : if (!fAutoStart)
698 : fs::remove(GetAutostartFilePath());
699 : else
700 : {
701 : char pszExePath[MAX_PATH+1];
702 : ssize_t r = readlink("/proc/self/exe", pszExePath, sizeof(pszExePath) - 1);
703 : if (r == -1)
704 : return false;
705 : pszExePath[r] = '\0';
706 :
707 : fs::create_directories(GetAutostartDir());
708 :
709 : fsbridge::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc);
710 : if (!optionFile.good())
711 : return false;
712 : std::string chain = gArgs.GetChainName();
713 : // Write a bitcoin.desktop file to the autostart directory:
714 : optionFile << "[Desktop Entry]\n";
715 : optionFile << "Type=Application\n";
716 : if (chain == CBaseChainParams::MAIN)
717 : optionFile << "Name=Bitcoin\n";
718 : else
719 : optionFile << strprintf("Name=Bitcoin (%s)\n", chain);
720 : optionFile << "Exec=" << pszExePath << strprintf(" -min -chain=%s\n", chain);
721 : optionFile << "Terminal=false\n";
722 : optionFile << "Hidden=false\n";
723 : optionFile.close();
724 : }
725 : return true;
726 : }
727 :
728 : #else
729 :
730 0 : bool GetStartOnSystemStartup() { return false; }
731 0 : bool SetStartOnSystemStartup(bool fAutoStart) { return false; }
732 :
733 : #endif
734 :
735 0 : void setClipboard(const QString& str)
736 : {
737 0 : QApplication::clipboard()->setText(str, QClipboard::Clipboard);
738 0 : QApplication::clipboard()->setText(str, QClipboard::Selection);
739 0 : }
740 :
741 0 : fs::path qstringToBoostPath(const QString &path)
742 : {
743 0 : return fs::path(path.toStdString());
744 0 : }
745 :
746 0 : QString boostPathToQString(const fs::path &path)
747 : {
748 0 : return QString::fromStdString(path.string());
749 : }
750 :
751 0 : QString formatDurationStr(int secs)
752 : {
753 0 : QStringList strList;
754 0 : int days = secs / 86400;
755 0 : int hours = (secs % 86400) / 3600;
756 0 : int mins = (secs % 3600) / 60;
757 0 : int seconds = secs % 60;
758 :
759 0 : if (days)
760 0 : strList.append(QString(QObject::tr("%1 d")).arg(days));
761 0 : if (hours)
762 0 : strList.append(QString(QObject::tr("%1 h")).arg(hours));
763 0 : if (mins)
764 0 : strList.append(QString(QObject::tr("%1 m")).arg(mins));
765 0 : if (seconds || (!days && !hours && !mins))
766 0 : strList.append(QString(QObject::tr("%1 s")).arg(seconds));
767 :
768 0 : return strList.join(" ");
769 0 : }
770 :
771 0 : QString formatServicesStr(quint64 mask)
772 : {
773 0 : QStringList strList;
774 :
775 0 : for (const auto& flag : serviceFlagsToStr(mask)) {
776 0 : strList.append(QString::fromStdString(flag));
777 : }
778 :
779 0 : if (strList.size())
780 0 : return strList.join(" & ");
781 : else
782 0 : return QObject::tr("None");
783 0 : }
784 :
785 0 : QString formatPingTime(int64_t ping_usec)
786 : {
787 0 : 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));
788 0 : }
789 :
790 0 : QString formatTimeOffset(int64_t nTimeOffset)
791 : {
792 0 : return QString(QObject::tr("%1 s")).arg(QString::number((int)nTimeOffset, 10));
793 0 : }
794 :
795 0 : QString formatNiceTimeOffset(qint64 secs)
796 : {
797 : // Represent time from last generated block in human readable text
798 0 : QString timeBehindText;
799 : const int HOUR_IN_SECONDS = 60*60;
800 : const int DAY_IN_SECONDS = 24*60*60;
801 : const int WEEK_IN_SECONDS = 7*24*60*60;
802 : const int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
803 0 : if(secs < 60)
804 : {
805 0 : timeBehindText = QObject::tr("%n second(s)","",secs);
806 0 : }
807 0 : else if(secs < 2*HOUR_IN_SECONDS)
808 : {
809 0 : timeBehindText = QObject::tr("%n minute(s)","",secs/60);
810 0 : }
811 0 : else if(secs < 2*DAY_IN_SECONDS)
812 : {
813 0 : timeBehindText = QObject::tr("%n hour(s)","",secs/HOUR_IN_SECONDS);
814 0 : }
815 0 : else if(secs < 2*WEEK_IN_SECONDS)
816 : {
817 0 : timeBehindText = QObject::tr("%n day(s)","",secs/DAY_IN_SECONDS);
818 0 : }
819 0 : else if(secs < YEAR_IN_SECONDS)
820 : {
821 0 : timeBehindText = QObject::tr("%n week(s)","",secs/WEEK_IN_SECONDS);
822 0 : }
823 : else
824 : {
825 0 : qint64 years = secs / YEAR_IN_SECONDS;
826 0 : qint64 remainder = secs % YEAR_IN_SECONDS;
827 0 : timeBehindText = QObject::tr("%1 and %2").arg(QObject::tr("%n year(s)", "", years)).arg(QObject::tr("%n week(s)","", remainder/WEEK_IN_SECONDS));
828 0 : }
829 : return timeBehindText;
830 0 : }
831 :
832 0 : QString formatBytes(uint64_t bytes)
833 : {
834 0 : if(bytes < 1024)
835 0 : return QString(QObject::tr("%1 B")).arg(bytes);
836 0 : if(bytes < 1024 * 1024)
837 0 : return QString(QObject::tr("%1 KB")).arg(bytes / 1024);
838 0 : if(bytes < 1024 * 1024 * 1024)
839 0 : return QString(QObject::tr("%1 MB")).arg(bytes / 1024 / 1024);
840 :
841 0 : return QString(QObject::tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024);
842 0 : }
843 :
844 0 : qreal calculateIdealFontSize(int width, const QString& text, QFont font, qreal minPointSize, qreal font_size) {
845 0 : while(font_size >= minPointSize) {
846 0 : font.setPointSizeF(font_size);
847 0 : QFontMetrics fm(font);
848 0 : if (TextWidth(fm, text) < width) {
849 0 : break;
850 : }
851 0 : font_size -= 0.5;
852 0 : }
853 0 : return font_size;
854 0 : }
855 :
856 0 : void ClickableLabel::mouseReleaseEvent(QMouseEvent *event)
857 : {
858 0 : Q_EMIT clicked(event->pos());
859 0 : }
860 :
861 0 : void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event)
862 : {
863 0 : Q_EMIT clicked(event->pos());
864 0 : }
865 :
866 0 : bool ItemDelegate::eventFilter(QObject *object, QEvent *event)
867 : {
868 0 : if (event->type() == QEvent::KeyPress) {
869 0 : if (static_cast<QKeyEvent*>(event)->key() == Qt::Key_Escape) {
870 0 : Q_EMIT keyEscapePressed();
871 0 : }
872 : }
873 0 : return QItemDelegate::eventFilter(object, event);
874 : }
875 :
876 0 : void PolishProgressDialog(QProgressDialog* dialog)
877 : {
878 : #ifdef Q_OS_MAC
879 : // Workaround for macOS-only Qt bug; see: QTBUG-65750, QTBUG-70357.
880 0 : const int margin = TextWidth(dialog->fontMetrics(), ("X"));
881 0 : dialog->resize(dialog->width() + 2 * margin, dialog->height());
882 0 : dialog->show();
883 : #else
884 : Q_UNUSED(dialog);
885 : #endif
886 0 : }
887 :
888 0 : int TextWidth(const QFontMetrics& fm, const QString& text)
889 : {
890 : #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
891 0 : return fm.horizontalAdvance(text);
892 : #else
893 : return fm.width(text);
894 : #endif
895 : }
896 :
897 0 : void LogQtInfo()
898 : {
899 : #ifdef QT_STATIC
900 : const std::string qt_link{"static"};
901 : #else
902 0 : const std::string qt_link{"dynamic"};
903 : #endif
904 : #ifdef QT_STATICPLUGIN
905 : const std::string plugin_link{"static"};
906 : #else
907 0 : const std::string plugin_link{"dynamic"};
908 : #endif
909 0 : LogPrintf("Qt %s (%s), plugin=%s (%s)\n", qVersion(), qt_link, QGuiApplication::platformName().toStdString(), plugin_link);
910 0 : LogPrintf("System: %s, %s\n", QSysInfo::prettyProductName().toStdString(), QSysInfo::buildAbi().toStdString());
911 0 : for (const QScreen* s : QGuiApplication::screens()) {
912 0 : LogPrintf("Screen: %s %dx%d, pixel ratio=%.1f\n", s->name().toStdString(), s->size().width(), s->size().height(), s->devicePixelRatio());
913 0 : }
914 0 : }
915 :
916 0 : void PopupMenu(QMenu* menu, const QPoint& point, QAction* at_action)
917 : {
918 : // The qminimal plugin does not provide window system integration.
919 0 : if (QApplication::platformName() == "minimal") return;
920 0 : menu->popup(point, at_action);
921 0 : }
922 :
923 : } // namespace GUIUtil
|