You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

guiutil.cpp 31KB

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