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