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