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

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