PageRenderTime 45ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/src/qt/guiutil.cpp

https://bitbucket.org/gincoin-dev/gincoin-core
C++ | 1076 lines | 866 code | 129 blank | 81 comment | 147 complexity | 6dc958bfed75728e4688210f46a02835 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause
  1. // Copyright (c) 2011-2015 The Bitcoin Core developers
  2. // Copyright (c) 2014-2017 The Dash Core developers
  3. // Copyright (c) 2018 The Gincoin Core developers
  4. // Distributed under the MIT software license, see the accompanying
  5. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  6. #include "guiutil.h"
  7. #include "bitcoinaddressvalidator.h"
  8. #include "bitcoinunits.h"
  9. #include "qvalidatedlineedit.h"
  10. #include "walletmodel.h"
  11. #include "primitives/transaction.h"
  12. #include "init.h"
  13. #include "validation.h" // For minRelayTxFee
  14. #include "protocol.h"
  15. #include "script/script.h"
  16. #include "script/standard.h"
  17. #include "util.h"
  18. #ifdef WIN32
  19. #ifdef _WIN32_WINNT
  20. #undef _WIN32_WINNT
  21. #endif
  22. #define _WIN32_WINNT 0x0501
  23. #ifdef _WIN32_IE
  24. #undef _WIN32_IE
  25. #endif
  26. #define _WIN32_IE 0x0501
  27. #define WIN32_LEAN_AND_MEAN 1
  28. #ifndef NOMINMAX
  29. #define NOMINMAX
  30. #endif
  31. #include "shellapi.h"
  32. #include "shlobj.h"
  33. #include "shlwapi.h"
  34. #endif
  35. #include <boost/filesystem.hpp>
  36. #include <boost/filesystem/fstream.hpp>
  37. #if BOOST_FILESYSTEM_VERSION >= 3
  38. #include <boost/filesystem/detail/utf8_codecvt_facet.hpp>
  39. #endif
  40. #include <boost/scoped_array.hpp>
  41. #include <QAbstractItemView>
  42. #include <QApplication>
  43. #include <QClipboard>
  44. #include <QDateTime>
  45. #include <QDesktopServices>
  46. #include <QDesktopWidget>
  47. #include <QDoubleValidator>
  48. #include <QFileDialog>
  49. #include <QFont>
  50. #include <QLineEdit>
  51. #include <QSettings>
  52. #include <QTextDocument> // for Qt::mightBeRichText
  53. #include <QThread>
  54. #include <QMouseEvent>
  55. #if QT_VERSION < 0x050000
  56. #include <QUrl>
  57. #else
  58. #include <QUrlQuery>
  59. #endif
  60. #if QT_VERSION >= 0x50200
  61. #include <QFontDatabase>
  62. #endif
  63. #if BOOST_FILESYSTEM_VERSION >= 3
  64. static boost::filesystem::detail::utf8_codecvt_facet utf8;
  65. #endif
  66. #if defined(Q_OS_MAC)
  67. extern double NSAppKitVersionNumber;
  68. #if !defined(NSAppKitVersionNumber10_8)
  69. #define NSAppKitVersionNumber10_8 1187
  70. #endif
  71. #if !defined(NSAppKitVersionNumber10_9)
  72. #define NSAppKitVersionNumber10_9 1265
  73. #endif
  74. #endif
  75. namespace GUIUtil {
  76. QString dateTimeStr(const QDateTime &date)
  77. {
  78. return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
  79. }
  80. QString dateTimeStr(qint64 nTime)
  81. {
  82. return dateTimeStr(QDateTime::fromTime_t((qint32)nTime));
  83. }
  84. QFont fixedPitchFont()
  85. {
  86. #if QT_VERSION >= 0x50200
  87. return QFontDatabase::systemFont(QFontDatabase::FixedFont);
  88. #else
  89. QFont font("Monospace");
  90. #if QT_VERSION >= 0x040800
  91. font.setStyleHint(QFont::Monospace);
  92. #else
  93. font.setStyleHint(QFont::TypeWriter);
  94. #endif
  95. return font;
  96. #endif
  97. }
  98. void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
  99. {
  100. parent->setFocusProxy(widget);
  101. widget->setFont(fixedPitchFont());
  102. #if QT_VERSION >= 0x040700
  103. // We don't want translators to use own addresses in translations
  104. // and this is the only place, where this address is supplied.
  105. widget->setPlaceholderText(QObject::tr("Enter a Gincoin address (e.g. %1)").arg("XwnLY9Tf7Zsef8gMGL2fhWA9ZmMjt4KPwg"));
  106. #endif
  107. widget->setValidator(new BitcoinAddressEntryValidator(parent));
  108. widget->setCheckValidator(new BitcoinAddressCheckValidator(parent));
  109. }
  110. void setupAmountWidget(QLineEdit *widget, QWidget *parent)
  111. {
  112. QDoubleValidator *amountValidator = new QDoubleValidator(parent);
  113. amountValidator->setDecimals(8);
  114. amountValidator->setBottom(0.0);
  115. widget->setValidator(amountValidator);
  116. widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter);
  117. }
  118. bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
  119. {
  120. // return if URI is not valid or is no dash: URI
  121. if(!uri.isValid() || uri.scheme() != QString("gincoin"))
  122. return false;
  123. SendCoinsRecipient rv;
  124. rv.address = uri.path();
  125. // Trim any following forward slash which may have been added by the OS
  126. if (rv.address.endsWith("/")) {
  127. rv.address.truncate(rv.address.length() - 1);
  128. }
  129. rv.amount = 0;
  130. #if QT_VERSION < 0x050000
  131. QList<QPair<QString, QString> > items = uri.queryItems();
  132. #else
  133. QUrlQuery uriQuery(uri);
  134. QList<QPair<QString, QString> > items = uriQuery.queryItems();
  135. #endif
  136. rv.fUseInstantSend = false;
  137. for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++)
  138. {
  139. bool fShouldReturnFalse = false;
  140. if (i->first.startsWith("req-"))
  141. {
  142. i->first.remove(0, 4);
  143. fShouldReturnFalse = true;
  144. }
  145. if (i->first == "label")
  146. {
  147. rv.label = i->second;
  148. fShouldReturnFalse = false;
  149. }
  150. if (i->first == "IS")
  151. {
  152. if(i->second.compare(QString("1")) == 0)
  153. rv.fUseInstantSend = true;
  154. fShouldReturnFalse = false;
  155. }
  156. if (i->first == "message")
  157. {
  158. rv.message = i->second;
  159. fShouldReturnFalse = false;
  160. }
  161. else if (i->first == "amount")
  162. {
  163. if(!i->second.isEmpty())
  164. {
  165. if(!BitcoinUnits::parse(BitcoinUnits::GIN, i->second, &rv.amount))
  166. {
  167. return false;
  168. }
  169. }
  170. fShouldReturnFalse = false;
  171. }
  172. if (fShouldReturnFalse)
  173. return false;
  174. }
  175. if(out)
  176. {
  177. *out = rv;
  178. }
  179. return true;
  180. }
  181. bool parseBitcoinURI(QString uri, SendCoinsRecipient *out)
  182. {
  183. // Convert dash:// to dash:
  184. //
  185. // Cannot handle this later, because dash:// will cause Qt to see the part after // as host,
  186. // which will lower-case it (and thus invalidate the address).
  187. if(uri.startsWith("gincoin://", Qt::CaseInsensitive))
  188. {
  189. uri.replace(0, 7, "gincoin:");
  190. }
  191. QUrl uriInstance(uri);
  192. return parseBitcoinURI(uriInstance, out);
  193. }
  194. QString formatBitcoinURI(const SendCoinsRecipient &info)
  195. {
  196. QString ret = QString("gincoin:%1").arg(info.address);
  197. int paramCount = 0;
  198. if (info.amount)
  199. {
  200. ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::GIN, info.amount, false, BitcoinUnits::separatorNever));
  201. paramCount++;
  202. }
  203. if (!info.label.isEmpty())
  204. {
  205. QString lbl(QUrl::toPercentEncoding(info.label));
  206. ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl);
  207. paramCount++;
  208. }
  209. if (!info.message.isEmpty())
  210. {
  211. QString msg(QUrl::toPercentEncoding(info.message));
  212. ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg);
  213. paramCount++;
  214. }
  215. if(info.fUseInstantSend)
  216. {
  217. ret += QString("%1IS=1").arg(paramCount == 0 ? "?" : "&");
  218. paramCount++;
  219. }
  220. return ret;
  221. }
  222. bool isDust(const QString& address, const CAmount& amount)
  223. {
  224. CTxDestination dest = CBitcoinAddress(address.toStdString()).Get();
  225. CScript script = GetScriptForDestination(dest);
  226. CTxOut txOut(amount, script);
  227. return txOut.IsDust(::minRelayTxFee);
  228. }
  229. QString HtmlEscape(const QString& str, bool fMultiLine)
  230. {
  231. #if QT_VERSION < 0x050000
  232. QString escaped = Qt::escape(str);
  233. #else
  234. QString escaped = str.toHtmlEscaped();
  235. #endif
  236. escaped = escaped.replace(" ", "&nbsp;");
  237. if(fMultiLine)
  238. {
  239. escaped = escaped.replace("\n", "<br>\n");
  240. }
  241. return escaped;
  242. }
  243. QString HtmlEscape(const std::string& str, bool fMultiLine)
  244. {
  245. return HtmlEscape(QString::fromStdString(str), fMultiLine);
  246. }
  247. void copyEntryData(QAbstractItemView *view, int column, int role)
  248. {
  249. if(!view || !view->selectionModel())
  250. return;
  251. QModelIndexList selection = view->selectionModel()->selectedRows(column);
  252. if(!selection.isEmpty())
  253. {
  254. // Copy first item
  255. setClipboard(selection.at(0).data(role).toString());
  256. }
  257. }
  258. QList<QModelIndex> getEntryData(QAbstractItemView *view, int column)
  259. {
  260. if(!view || !view->selectionModel())
  261. return QList<QModelIndex>();
  262. return view->selectionModel()->selectedRows(column);
  263. }
  264. QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir,
  265. const QString &filter,
  266. QString *selectedSuffixOut)
  267. {
  268. QString selectedFilter;
  269. QString myDir;
  270. if(dir.isEmpty()) // Default to user documents location
  271. {
  272. #if QT_VERSION < 0x050000
  273. myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
  274. #else
  275. myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  276. #endif
  277. }
  278. else
  279. {
  280. myDir = dir;
  281. }
  282. /* Directly convert path to native OS path separators */
  283. QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter));
  284. /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
  285. QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]");
  286. QString selectedSuffix;
  287. if(filter_re.exactMatch(selectedFilter))
  288. {
  289. selectedSuffix = filter_re.cap(1);
  290. }
  291. /* Add suffix if needed */
  292. QFileInfo info(result);
  293. if(!result.isEmpty())
  294. {
  295. if(info.suffix().isEmpty() && !selectedSuffix.isEmpty())
  296. {
  297. /* No suffix specified, add selected suffix */
  298. if(!result.endsWith("."))
  299. result.append(".");
  300. result.append(selectedSuffix);
  301. }
  302. }
  303. /* Return selected suffix if asked to */
  304. if(selectedSuffixOut)
  305. {
  306. *selectedSuffixOut = selectedSuffix;
  307. }
  308. return result;
  309. }
  310. QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir,
  311. const QString &filter,
  312. QString *selectedSuffixOut)
  313. {
  314. QString selectedFilter;
  315. QString myDir;
  316. if(dir.isEmpty()) // Default to user documents location
  317. {
  318. #if QT_VERSION < 0x050000
  319. myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
  320. #else
  321. myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
  322. #endif
  323. }
  324. else
  325. {
  326. myDir = dir;
  327. }
  328. /* Directly convert path to native OS path separators */
  329. QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent, caption, myDir, filter, &selectedFilter));
  330. if(selectedSuffixOut)
  331. {
  332. /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */
  333. QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]");
  334. QString selectedSuffix;
  335. if(filter_re.exactMatch(selectedFilter))
  336. {
  337. selectedSuffix = filter_re.cap(1);
  338. }
  339. *selectedSuffixOut = selectedSuffix;
  340. }
  341. return result;
  342. }
  343. Qt::ConnectionType blockingGUIThreadConnection()
  344. {
  345. if(QThread::currentThread() != qApp->thread())
  346. {
  347. return Qt::BlockingQueuedConnection;
  348. }
  349. else
  350. {
  351. return Qt::DirectConnection;
  352. }
  353. }
  354. bool checkPoint(const QPoint &p, const QWidget *w)
  355. {
  356. QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p));
  357. if (!atW) return false;
  358. return atW->topLevelWidget() == w;
  359. }
  360. bool isObscured(QWidget *w)
  361. {
  362. return !(checkPoint(QPoint(0, 0), w)
  363. && checkPoint(QPoint(w->width() - 1, 0), w)
  364. && checkPoint(QPoint(0, w->height() - 1), w)
  365. && checkPoint(QPoint(w->width() - 1, w->height() - 1), w)
  366. && checkPoint(QPoint(w->width() / 2, w->height() / 2), w));
  367. }
  368. void openDebugLogfile()
  369. {
  370. boost::filesystem::path pathDebug = GetDataDir() / "debug.log";
  371. /* Open debug.log with the associated application */
  372. if (boost::filesystem::exists(pathDebug))
  373. QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug)));
  374. }
  375. void openConfigfile()
  376. {
  377. boost::filesystem::path pathConfig = GetConfigFile();
  378. /* Open gincoin.conf with the associated application */
  379. if (boost::filesystem::exists(pathConfig))
  380. QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig)));
  381. }
  382. void openMNConfigfile()
  383. {
  384. boost::filesystem::path pathConfig = GetMasternodeConfigFile();
  385. /* Open masternode.conf with the associated application */
  386. if (boost::filesystem::exists(pathConfig))
  387. QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathConfig)));
  388. }
  389. void showBackups()
  390. {
  391. boost::filesystem::path backupsDir = GetBackupsDir();
  392. /* Open folder with default browser */
  393. if (boost::filesystem::exists(backupsDir))
  394. QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(backupsDir)));
  395. }
  396. void SubstituteFonts(const QString& language)
  397. {
  398. #if defined(Q_OS_MAC)
  399. // Background:
  400. // OSX's default font changed in 10.9 and Qt is unable to find it with its
  401. // usual fallback methods when building against the 10.7 sdk or lower.
  402. // The 10.8 SDK added a function to let it find the correct fallback font.
  403. // If this fallback is not properly loaded, some characters may fail to
  404. // render correctly.
  405. //
  406. // The same thing happened with 10.10. .Helvetica Neue DeskInterface is now default.
  407. //
  408. // Solution: If building with the 10.7 SDK or lower and the user's platform
  409. // is 10.9 or higher at runtime, substitute the correct font. This needs to
  410. // happen before the QApplication is created.
  411. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8
  412. if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8)
  413. {
  414. if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)
  415. /* On a 10.9 - 10.9.x system */
  416. QFont::insertSubstitution(".Lucida Grande UI", "Lucida Grande");
  417. else
  418. {
  419. /* 10.10 or later system */
  420. if (language == "zh_CN" || language == "zh_TW" || language == "zh_HK") // traditional or simplified Chinese
  421. QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Heiti SC");
  422. else if (language == "ja") // Japanesee
  423. QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Songti SC");
  424. else
  425. QFont::insertSubstitution(".Helvetica Neue DeskInterface", "Lucida Grande");
  426. }
  427. }
  428. #endif
  429. #endif
  430. }
  431. ToolTipToRichTextFilter::ToolTipToRichTextFilter(int size_threshold, QObject *parent) :
  432. QObject(parent),
  433. size_threshold(size_threshold)
  434. {
  435. }
  436. bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt)
  437. {
  438. if(evt->type() == QEvent::ToolTipChange)
  439. {
  440. QWidget *widget = static_cast<QWidget*>(obj);
  441. QString tooltip = widget->toolTip();
  442. if(tooltip.size() > size_threshold && !tooltip.startsWith("<qt"))
  443. {
  444. // Escape the current message as HTML and replace \n by <br> if it's not rich text
  445. if(!Qt::mightBeRichText(tooltip))
  446. tooltip = HtmlEscape(tooltip, true);
  447. // Envelop with <qt></qt> to make sure Qt detects every tooltip as rich text
  448. // and style='white-space:pre' to preserve line composition
  449. tooltip = "<qt style='white-space:pre'>" + tooltip + "</qt>";
  450. widget->setToolTip(tooltip);
  451. return true;
  452. }
  453. }
  454. return QObject::eventFilter(obj, evt);
  455. }
  456. void TableViewLastColumnResizingFixer::connectViewHeadersSignals()
  457. {
  458. connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
  459. connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
  460. }
  461. // We need to disconnect these while handling the resize events, otherwise we can enter infinite loops.
  462. void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals()
  463. {
  464. disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int)));
  465. disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged()));
  466. }
  467. // Setup the resize mode, handles compatibility for Qt5 and below as the method signatures changed.
  468. // Refactored here for readability.
  469. void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode)
  470. {
  471. #if QT_VERSION < 0x050000
  472. tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode);
  473. #else
  474. tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode);
  475. #endif
  476. }
  477. void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width)
  478. {
  479. tableView->setColumnWidth(nColumnIndex, width);
  480. tableView->horizontalHeader()->resizeSection(nColumnIndex, width);
  481. }
  482. int TableViewLastColumnResizingFixer::getColumnsWidth()
  483. {
  484. int nColumnsWidthSum = 0;
  485. for (int i = 0; i < columnCount; i++)
  486. {
  487. nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i);
  488. }
  489. return nColumnsWidthSum;
  490. }
  491. int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column)
  492. {
  493. int nResult = lastColumnMinimumWidth;
  494. int nTableWidth = tableView->horizontalHeader()->width();
  495. if (nTableWidth > 0)
  496. {
  497. int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column);
  498. nResult = std::max(nResult, nTableWidth - nOtherColsWidth);
  499. }
  500. return nResult;
  501. }
  502. // Make sure we don't make the columns wider than the tables viewport width.
  503. void TableViewLastColumnResizingFixer::adjustTableColumnsWidth()
  504. {
  505. disconnectViewHeadersSignals();
  506. resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex));
  507. connectViewHeadersSignals();
  508. int nTableWidth = tableView->horizontalHeader()->width();
  509. int nColsWidth = getColumnsWidth();
  510. if (nColsWidth > nTableWidth)
  511. {
  512. resizeColumn(secondToLastColumnIndex,getAvailableWidthForColumn(secondToLastColumnIndex));
  513. }
  514. }
  515. // Make column use all the space available, useful during window resizing.
  516. void TableViewLastColumnResizingFixer::stretchColumnWidth(int column)
  517. {
  518. disconnectViewHeadersSignals();
  519. resizeColumn(column, getAvailableWidthForColumn(column));
  520. connectViewHeadersSignals();
  521. }
  522. // When a section is resized this is a slot-proxy for ajustAmountColumnWidth().
  523. void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize)
  524. {
  525. adjustTableColumnsWidth();
  526. int remainingWidth = getAvailableWidthForColumn(logicalIndex);
  527. if (newSize > remainingWidth)
  528. {
  529. resizeColumn(logicalIndex, remainingWidth);
  530. }
  531. }
  532. // When the tabless geometry is ready, we manually perform the stretch of the "Message" column,
  533. // as the "Stretch" resize mode does not allow for interactive resizing.
  534. void TableViewLastColumnResizingFixer::on_geometriesChanged()
  535. {
  536. if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0)
  537. {
  538. disconnectViewHeadersSignals();
  539. resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex));
  540. connectViewHeadersSignals();
  541. }
  542. }
  543. /**
  544. * Initializes all internal variables and prepares the
  545. * the resize modes of the last 2 columns of the table and
  546. */
  547. TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth, QObject *parent) :
  548. QObject(parent),
  549. tableView(table),
  550. lastColumnMinimumWidth(lastColMinimumWidth),
  551. allColumnsMinimumWidth(allColsMinimumWidth)
  552. {
  553. columnCount = tableView->horizontalHeader()->count();
  554. lastColumnIndex = columnCount - 1;
  555. secondToLastColumnIndex = columnCount - 2;
  556. tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth);
  557. setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive);
  558. setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive);
  559. }
  560. #ifdef WIN32
  561. boost::filesystem::path static StartupShortcutPath()
  562. {
  563. std::string chain = ChainNameFromCommandLine();
  564. if (chain == CBaseChainParams::MAIN)
  565. return GetSpecialFolderPath(CSIDL_STARTUP) / "Gincoin Core.lnk";
  566. if (chain == CBaseChainParams::TESTNET) // Remove this special case when CBaseChainParams::TESTNET = "testnet4"
  567. return GetSpecialFolderPath(CSIDL_STARTUP) / "Gincoin Core (testnet).lnk";
  568. return GetSpecialFolderPath(CSIDL_STARTUP) / strprintf("Gincoin Core (%s).lnk", chain);
  569. }
  570. bool GetStartOnSystemStartup()
  571. {
  572. // check for "Gincoin Core*.lnk"
  573. return boost::filesystem::exists(StartupShortcutPath());
  574. }
  575. bool SetStartOnSystemStartup(bool fAutoStart)
  576. {
  577. // If the shortcut exists already, remove it for updating
  578. boost::filesystem::remove(StartupShortcutPath());
  579. if (fAutoStart)
  580. {
  581. CoInitialize(NULL);
  582. // Get a pointer to the IShellLink interface.
  583. IShellLink* psl = NULL;
  584. HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL,
  585. CLSCTX_INPROC_SERVER, IID_IShellLink,
  586. reinterpret_cast<void**>(&psl));
  587. if (SUCCEEDED(hres))
  588. {
  589. // Get the current executable path
  590. TCHAR pszExePath[MAX_PATH];
  591. GetModuleFileName(NULL, pszExePath, sizeof(pszExePath));
  592. // Start client minimized
  593. QString strArgs = "-min";
  594. // Set -testnet /-regtest options
  595. strArgs += QString::fromStdString(strprintf(" -testnet=%d -regtest=%d", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false)));
  596. #ifdef UNICODE
  597. boost::scoped_array<TCHAR> args(new TCHAR[strArgs.length() + 1]);
  598. // Convert the QString to TCHAR*
  599. strArgs.toWCharArray(args.get());
  600. // Add missing '\0'-termination to string
  601. args[strArgs.length()] = '\0';
  602. #endif
  603. // Set the path to the shortcut target
  604. psl->SetPath(pszExePath);
  605. PathRemoveFileSpec(pszExePath);
  606. psl->SetWorkingDirectory(pszExePath);
  607. psl->SetShowCmd(SW_SHOWMINNOACTIVE);
  608. #ifndef UNICODE
  609. psl->SetArguments(strArgs.toStdString().c_str());
  610. #else
  611. psl->SetArguments(args.get());
  612. #endif
  613. // Query IShellLink for the IPersistFile interface for
  614. // saving the shortcut in persistent storage.
  615. IPersistFile* ppf = NULL;
  616. hres = psl->QueryInterface(IID_IPersistFile, reinterpret_cast<void**>(&ppf));
  617. if (SUCCEEDED(hres))
  618. {
  619. WCHAR pwsz[MAX_PATH];
  620. // Ensure that the string is ANSI.
  621. MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH);
  622. // Save the link by calling IPersistFile::Save.
  623. hres = ppf->Save(pwsz, TRUE);
  624. ppf->Release();
  625. psl->Release();
  626. CoUninitialize();
  627. return true;
  628. }
  629. psl->Release();
  630. }
  631. CoUninitialize();
  632. return false;
  633. }
  634. return true;
  635. }
  636. #elif defined(Q_OS_LINUX)
  637. // Follow the Desktop Application Autostart Spec:
  638. // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html
  639. boost::filesystem::path static GetAutostartDir()
  640. {
  641. namespace fs = boost::filesystem;
  642. char* pszConfigHome = getenv("XDG_CONFIG_HOME");
  643. if (pszConfigHome) return fs::path(pszConfigHome) / "autostart";
  644. char* pszHome = getenv("HOME");
  645. if (pszHome) return fs::path(pszHome) / ".config" / "autostart";
  646. return fs::path();
  647. }
  648. boost::filesystem::path static GetAutostartFilePath()
  649. {
  650. std::string chain = ChainNameFromCommandLine();
  651. if (chain == CBaseChainParams::MAIN)
  652. return GetAutostartDir() / "gincoincore.desktop";
  653. return GetAutostartDir() / strprintf("gincoincore-%s.lnk", chain);
  654. }
  655. bool GetStartOnSystemStartup()
  656. {
  657. boost::filesystem::ifstream optionFile(GetAutostartFilePath());
  658. if (!optionFile.good())
  659. return false;
  660. // Scan through file for "Hidden=true":
  661. std::string line;
  662. while (!optionFile.eof())
  663. {
  664. getline(optionFile, line);
  665. if (line.find("Hidden") != std::string::npos &&
  666. line.find("true") != std::string::npos)
  667. return false;
  668. }
  669. optionFile.close();
  670. return true;
  671. }
  672. bool SetStartOnSystemStartup(bool fAutoStart)
  673. {
  674. if (!fAutoStart)
  675. boost::filesystem::remove(GetAutostartFilePath());
  676. else
  677. {
  678. char pszExePath[MAX_PATH+1];
  679. memset(pszExePath, 0, sizeof(pszExePath));
  680. if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1)
  681. return false;
  682. boost::filesystem::create_directories(GetAutostartDir());
  683. boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc);
  684. if (!optionFile.good())
  685. return false;
  686. std::string chain = ChainNameFromCommandLine();
  687. // Write a dashcore.desktop file to the autostart directory:
  688. optionFile << "[Desktop Entry]\n";
  689. optionFile << "Type=Application\n";
  690. if (chain == CBaseChainParams::MAIN)
  691. optionFile << "Name=Gincoin Core\n";
  692. else
  693. optionFile << strprintf("Name=Gincoin Core (%s)\n", chain);
  694. optionFile << "Exec=" << pszExePath << strprintf(" -min -testnet=%d -regtest=%d\n", GetBoolArg("-testnet", false), GetBoolArg("-regtest", false));
  695. optionFile << "Terminal=false\n";
  696. optionFile << "Hidden=false\n";
  697. optionFile.close();
  698. }
  699. return true;
  700. }
  701. #elif defined(Q_OS_MAC)
  702. // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m
  703. #include <CoreFoundation/CoreFoundation.h>
  704. #include <CoreServices/CoreServices.h>
  705. LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl);
  706. LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl)
  707. {
  708. // loop through the list of startup items and try to find the Gincoin Core app
  709. CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, NULL);
  710. for(int i = 0; i < CFArrayGetCount(listSnapshot); i++) {
  711. LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i);
  712. UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes;
  713. CFURLRef currentItemURL = NULL;
  714. #if defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED >= 10100
  715. if(&LSSharedFileListItemCopyResolvedURL)
  716. currentItemURL = LSSharedFileListItemCopyResolvedURL(item, resolutionFlags, NULL);
  717. #if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED < 10100
  718. else
  719. LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
  720. #endif
  721. #else
  722. LSSharedFileListItemResolve(item, resolutionFlags, &currentItemURL, NULL);
  723. #endif
  724. if(currentItemURL && CFEqual(currentItemURL, findUrl)) {
  725. // found
  726. CFRelease(currentItemURL);
  727. return item;
  728. }
  729. if(currentItemURL) {
  730. CFRelease(currentItemURL);
  731. }
  732. }
  733. return NULL;
  734. }
  735. bool GetStartOnSystemStartup()
  736. {
  737. CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
  738. LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
  739. LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl);
  740. return !!foundItem; // return boolified object
  741. }
  742. bool SetStartOnSystemStartup(bool fAutoStart)
  743. {
  744. CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle());
  745. LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
  746. LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl);
  747. if(fAutoStart && !foundItem) {
  748. // add Gincoin Core app to startup item list
  749. LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, bitcoinAppUrl, NULL, NULL);
  750. }
  751. else if(!fAutoStart && foundItem) {
  752. // remove item
  753. LSSharedFileListItemRemove(loginItems, foundItem);
  754. }
  755. return true;
  756. }
  757. #else
  758. bool GetStartOnSystemStartup() { return false; }
  759. bool SetStartOnSystemStartup(bool fAutoStart) { return false; }
  760. #endif
  761. void migrateQtSettings()
  762. {
  763. // Migration (12.1)
  764. QSettings settings;
  765. if(!settings.value("fMigrationDone121", false).toBool()) {
  766. settings.remove("theme");
  767. settings.remove("nWindowPos");
  768. settings.remove("nWindowSize");
  769. settings.setValue("fMigrationDone121", true);
  770. }
  771. }
  772. void saveWindowGeometry(const QString& strSetting, QWidget *parent)
  773. {
  774. QSettings settings;
  775. settings.setValue(strSetting + "Pos", parent->pos());
  776. settings.setValue(strSetting + "Size", parent->size());
  777. }
  778. void restoreWindowGeometry(const QString& strSetting, const QSize& defaultSize, QWidget *parent)
  779. {
  780. QSettings settings;
  781. QPoint pos = settings.value(strSetting + "Pos").toPoint();
  782. QSize size = settings.value(strSetting + "Size", defaultSize).toSize();
  783. parent->resize(size);
  784. parent->move(pos);
  785. if ((!pos.x() && !pos.y()) || (QApplication::desktop()->screenNumber(parent) == -1))
  786. {
  787. QRect screen = QApplication::desktop()->screenGeometry();
  788. QPoint defaultPos = screen.center() -
  789. QPoint(defaultSize.width() / 2, defaultSize.height() / 2);
  790. parent->resize(defaultSize);
  791. parent->move(defaultPos);
  792. }
  793. }
  794. // Return name of current UI-theme or default theme if no theme was found
  795. QString getThemeName()
  796. {
  797. QSettings settings;
  798. QString theme = settings.value("theme", "").toString();
  799. if(!theme.isEmpty()){
  800. return theme;
  801. }
  802. return QString("light");
  803. }
  804. // Open CSS when configured
  805. QString loadStyleSheet()
  806. {
  807. QString styleSheet;
  808. QSettings settings;
  809. QString cssName;
  810. QString theme = settings.value("theme", "").toString();
  811. if(!theme.isEmpty()){
  812. cssName = QString(":/css/") + theme;
  813. }
  814. else {
  815. cssName = QString(":/css/light");
  816. settings.setValue("theme", "light");
  817. }
  818. QFile qFile(cssName);
  819. if (qFile.open(QFile::ReadOnly)) {
  820. styleSheet = QLatin1String(qFile.readAll());
  821. }
  822. return styleSheet;
  823. }
  824. void setClipboard(const QString& str)
  825. {
  826. QApplication::clipboard()->setText(str, QClipboard::Clipboard);
  827. QApplication::clipboard()->setText(str, QClipboard::Selection);
  828. }
  829. #if BOOST_FILESYSTEM_VERSION >= 3
  830. boost::filesystem::path qstringToBoostPath(const QString &path)
  831. {
  832. return boost::filesystem::path(path.toStdString(), utf8);
  833. }
  834. QString boostPathToQString(const boost::filesystem::path &path)
  835. {
  836. return QString::fromStdString(path.string(utf8));
  837. }
  838. #else
  839. #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older
  840. boost::filesystem::path qstringToBoostPath(const QString &path)
  841. {
  842. return boost::filesystem::path(path.toStdString());
  843. }
  844. QString boostPathToQString(const boost::filesystem::path &path)
  845. {
  846. return QString::fromStdString(path.string());
  847. }
  848. #endif
  849. QString formatDurationStr(int secs)
  850. {
  851. QStringList strList;
  852. int days = secs / 86400;
  853. int hours = (secs % 86400) / 3600;
  854. int mins = (secs % 3600) / 60;
  855. int seconds = secs % 60;
  856. if (days)
  857. strList.append(QString(QObject::tr("%1 d")).arg(days));
  858. if (hours)
  859. strList.append(QString(QObject::tr("%1 h")).arg(hours));
  860. if (mins)
  861. strList.append(QString(QObject::tr("%1 m")).arg(mins));
  862. if (seconds || (!days && !hours && !mins))
  863. strList.append(QString(QObject::tr("%1 s")).arg(seconds));
  864. return strList.join(" ");
  865. }
  866. QString formatServicesStr(quint64 mask)
  867. {
  868. QStringList strList;
  869. // Just scan the last 8 bits for now.
  870. for (int i = 0; i < 8; i++) {
  871. uint64_t check = 1 << i;
  872. if (mask & check)
  873. {
  874. switch (check)
  875. {
  876. case NODE_NETWORK:
  877. strList.append("NETWORK");
  878. break;
  879. case NODE_GETUTXO:
  880. strList.append("GETUTXO");
  881. break;
  882. case NODE_BLOOM:
  883. strList.append("BLOOM");
  884. break;
  885. default:
  886. strList.append(QString("%1[%2]").arg("UNKNOWN").arg(check));
  887. }
  888. }
  889. }
  890. if (strList.size())
  891. return strList.join(" & ");
  892. else
  893. return QObject::tr("None");
  894. }
  895. QString formatPingTime(double dPingTime)
  896. {
  897. return (dPingTime == std::numeric_limits<int64_t>::max()/1e6 || dPingTime == 0) ? QObject::tr("N/A") : QString(QObject::tr("%1 ms")).arg(QString::number((int)(dPingTime * 1000), 10));
  898. }
  899. QString formatTimeOffset(int64_t nTimeOffset)
  900. {
  901. return QString(QObject::tr("%1 s")).arg(QString::number((int)nTimeOffset, 10));
  902. }
  903. QString formatNiceTimeOffset(qint64 secs)
  904. {
  905. // Represent time from last generated block in human readable text
  906. QString timeBehindText;
  907. const int HOUR_IN_SECONDS = 60*60;
  908. const int DAY_IN_SECONDS = 24*60*60;
  909. const int WEEK_IN_SECONDS = 7*24*60*60;
  910. const int YEAR_IN_SECONDS = 31556952; // Average length of year in Gregorian calendar
  911. if(secs < 60)
  912. {
  913. timeBehindText = QObject::tr("%n second(s)","",secs);
  914. }
  915. else if(secs < 2*HOUR_IN_SECONDS)
  916. {
  917. timeBehindText = QObject::tr("%n minute(s)","",secs/60);
  918. }
  919. else if(secs < 2*DAY_IN_SECONDS)
  920. {
  921. timeBehindText = QObject::tr("%n hour(s)","",secs/HOUR_IN_SECONDS);
  922. }
  923. else if(secs < 2*WEEK_IN_SECONDS)
  924. {
  925. timeBehindText = QObject::tr("%n day(s)","",secs/DAY_IN_SECONDS);
  926. }
  927. else if(secs < YEAR_IN_SECONDS)
  928. {
  929. timeBehindText = QObject::tr("%n week(s)","",secs/WEEK_IN_SECONDS);
  930. }
  931. else
  932. {
  933. qint64 years = secs / YEAR_IN_SECONDS;
  934. qint64 remainder = secs % YEAR_IN_SECONDS;
  935. timeBehindText = QObject::tr("%1 and %2").arg(QObject::tr("%n year(s)", "", years)).arg(QObject::tr("%n week(s)","", remainder/WEEK_IN_SECONDS));
  936. }
  937. return timeBehindText;
  938. }
  939. void ClickableLabel::mouseReleaseEvent(QMouseEvent *event)
  940. {
  941. Q_EMIT clicked(event->pos());
  942. }
  943. void ClickableProgressBar::mouseReleaseEvent(QMouseEvent *event)
  944. {
  945. Q_EMIT clicked(event->pos());
  946. }
  947. } // namespace GUIUtil