]>
Commit | Line | Data |
---|---|---|
1 | // Copyright (c) 2011-2013 The Bitcoin developers | |
2 | // Distributed under the MIT/X11 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 "core.h" | |
13 | #include "init.h" | |
14 | #include "util.h" | |
15 | ||
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 | ||
34 | #include <boost/filesystem.hpp> | |
35 | #include <boost/filesystem/fstream.hpp> | |
36 | #if BOOST_FILESYSTEM_VERSION >= 3 | |
37 | #include <boost/filesystem/detail/utf8_codecvt_facet.hpp> | |
38 | #endif | |
39 | ||
40 | #include <QAbstractItemView> | |
41 | #include <QApplication> | |
42 | #include <QClipboard> | |
43 | #include <QDateTime> | |
44 | #include <QDesktopServices> | |
45 | #include <QDesktopWidget> | |
46 | #include <QDoubleValidator> | |
47 | #include <QFileDialog> | |
48 | #include <QFont> | |
49 | #include <QLineEdit> | |
50 | #include <QSettings> | |
51 | #include <QTextDocument> // for Qt::mightBeRichText | |
52 | #include <QThread> | |
53 | ||
54 | #if QT_VERSION < 0x050000 | |
55 | #include <QUrl> | |
56 | #else | |
57 | #include <QUrlQuery> | |
58 | #endif | |
59 | ||
60 | #if BOOST_FILESYSTEM_VERSION >= 3 | |
61 | static boost::filesystem::detail::utf8_codecvt_facet utf8; | |
62 | #endif | |
63 | ||
64 | namespace GUIUtil { | |
65 | ||
66 | QString dateTimeStr(const QDateTime &date) | |
67 | { | |
68 | return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); | |
69 | } | |
70 | ||
71 | QString dateTimeStr(qint64 nTime) | |
72 | { | |
73 | return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); | |
74 | } | |
75 | ||
76 | QFont bitcoinAddressFont() | |
77 | { | |
78 | QFont font("Monospace"); | |
79 | font.setStyleHint(QFont::TypeWriter); | |
80 | return font; | |
81 | } | |
82 | ||
83 | void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) | |
84 | { | |
85 | parent->setFocusProxy(widget); | |
86 | ||
87 | widget->setFont(bitcoinAddressFont()); | |
88 | #if QT_VERSION >= 0x040700 | |
89 | widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); | |
90 | #endif | |
91 | widget->setValidator(new BitcoinAddressEntryValidator(parent)); | |
92 | widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); | |
93 | } | |
94 | ||
95 | void setupAmountWidget(QLineEdit *widget, QWidget *parent) | |
96 | { | |
97 | QDoubleValidator *amountValidator = new QDoubleValidator(parent); | |
98 | amountValidator->setDecimals(8); | |
99 | amountValidator->setBottom(0.0); | |
100 | widget->setValidator(amountValidator); | |
101 | widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter); | |
102 | } | |
103 | ||
104 | bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) | |
105 | { | |
106 | // return if URI is not valid or is no bitcoin: URI | |
107 | if(!uri.isValid() || uri.scheme() != QString("bitcoin")) | |
108 | return false; | |
109 | ||
110 | SendCoinsRecipient rv; | |
111 | rv.address = uri.path(); | |
112 | rv.amount = 0; | |
113 | ||
114 | #if QT_VERSION < 0x050000 | |
115 | QList<QPair<QString, QString> > items = uri.queryItems(); | |
116 | #else | |
117 | QUrlQuery uriQuery(uri); | |
118 | QList<QPair<QString, QString> > items = uriQuery.queryItems(); | |
119 | #endif | |
120 | for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++) | |
121 | { | |
122 | bool fShouldReturnFalse = false; | |
123 | if (i->first.startsWith("req-")) | |
124 | { | |
125 | i->first.remove(0, 4); | |
126 | fShouldReturnFalse = true; | |
127 | } | |
128 | ||
129 | if (i->first == "label") | |
130 | { | |
131 | rv.label = i->second; | |
132 | fShouldReturnFalse = false; | |
133 | } | |
134 | if (i->first == "message") | |
135 | { | |
136 | rv.message = i->second; | |
137 | fShouldReturnFalse = false; | |
138 | } | |
139 | else if (i->first == "amount") | |
140 | { | |
141 | if(!i->second.isEmpty()) | |
142 | { | |
143 | if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount)) | |
144 | { | |
145 | return false; | |
146 | } | |
147 | } | |
148 | fShouldReturnFalse = false; | |
149 | } | |
150 | ||
151 | if (fShouldReturnFalse) | |
152 | return false; | |
153 | } | |
154 | if(out) | |
155 | { | |
156 | *out = rv; | |
157 | } | |
158 | return true; | |
159 | } | |
160 | ||
161 | bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) | |
162 | { | |
163 | // Convert bitcoin:// to bitcoin: | |
164 | // | |
165 | // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host, | |
166 | // which will lower-case it (and thus invalidate the address). | |
167 | if(uri.startsWith("bitcoin://", Qt::CaseInsensitive)) | |
168 | { | |
169 | uri.replace(0, 10, "bitcoin:"); | |
170 | } | |
171 | QUrl uriInstance(uri); | |
172 | return parseBitcoinURI(uriInstance, out); | |
173 | } | |
174 | ||
175 | QString formatBitcoinURI(const SendCoinsRecipient &info) | |
176 | { | |
177 | QString ret = QString("bitcoin:%1").arg(info.address); | |
178 | int paramCount = 0; | |
179 | ||
180 | if (info.amount) | |
181 | { | |
182 | ret += QString("?amount=%1").arg(BitcoinUnits::format(BitcoinUnits::BTC, info.amount)); | |
183 | paramCount++; | |
184 | } | |
185 | ||
186 | if (!info.label.isEmpty()) | |
187 | { | |
188 | QString lbl(QUrl::toPercentEncoding(info.label)); | |
189 | ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); | |
190 | paramCount++; | |
191 | } | |
192 | ||
193 | if (!info.message.isEmpty()) | |
194 | { | |
195 | QString msg(QUrl::toPercentEncoding(info.message));; | |
196 | ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); | |
197 | paramCount++; | |
198 | } | |
199 | ||
200 | return ret; | |
201 | } | |
202 | ||
203 | bool isDust(const QString& address, qint64 amount) | |
204 | { | |
205 | CTxDestination dest = CBitcoinAddress(address.toStdString()).Get(); | |
206 | CScript script; script.SetDestination(dest); | |
207 | CTxOut txOut(amount, script); | |
208 | return txOut.IsDust(CTransaction::nMinRelayTxFee); | |
209 | } | |
210 | ||
211 | QString HtmlEscape(const QString& str, bool fMultiLine) | |
212 | { | |
213 | #if QT_VERSION < 0x050000 | |
214 | QString escaped = Qt::escape(str); | |
215 | #else | |
216 | QString escaped = str.toHtmlEscaped(); | |
217 | #endif | |
218 | if(fMultiLine) | |
219 | { | |
220 | escaped = escaped.replace("\n", "<br>\n"); | |
221 | } | |
222 | return escaped; | |
223 | } | |
224 | ||
225 | QString HtmlEscape(const std::string& str, bool fMultiLine) | |
226 | { | |
227 | return HtmlEscape(QString::fromStdString(str), fMultiLine); | |
228 | } | |
229 | ||
230 | void copyEntryData(QAbstractItemView *view, int column, int role) | |
231 | { | |
232 | if(!view || !view->selectionModel()) | |
233 | return; | |
234 | QModelIndexList selection = view->selectionModel()->selectedRows(column); | |
235 | ||
236 | if(!selection.isEmpty()) | |
237 | { | |
238 | // Copy first item | |
239 | setClipboard(selection.at(0).data(role).toString()); | |
240 | } | |
241 | } | |
242 | ||
243 | QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, | |
244 | const QString &filter, | |
245 | QString *selectedSuffixOut) | |
246 | { | |
247 | QString selectedFilter; | |
248 | QString myDir; | |
249 | if(dir.isEmpty()) // Default to user documents location | |
250 | { | |
251 | #if QT_VERSION < 0x050000 | |
252 | myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); | |
253 | #else | |
254 | myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); | |
255 | #endif | |
256 | } | |
257 | else | |
258 | { | |
259 | myDir = dir; | |
260 | } | |
261 | /* Directly convert path to native OS path separators */ | |
262 | QString result = QDir::toNativeSeparators(QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter)); | |
263 | ||
264 | /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ | |
265 | QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); | |
266 | QString selectedSuffix; | |
267 | if(filter_re.exactMatch(selectedFilter)) | |
268 | { | |
269 | selectedSuffix = filter_re.cap(1); | |
270 | } | |
271 | ||
272 | /* Add suffix if needed */ | |
273 | QFileInfo info(result); | |
274 | if(!result.isEmpty()) | |
275 | { | |
276 | if(info.suffix().isEmpty() && !selectedSuffix.isEmpty()) | |
277 | { | |
278 | /* No suffix specified, add selected suffix */ | |
279 | if(!result.endsWith(".")) | |
280 | result.append("."); | |
281 | result.append(selectedSuffix); | |
282 | } | |
283 | } | |
284 | ||
285 | /* Return selected suffix if asked to */ | |
286 | if(selectedSuffixOut) | |
287 | { | |
288 | *selectedSuffixOut = selectedSuffix; | |
289 | } | |
290 | return result; | |
291 | } | |
292 | ||
293 | QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, | |
294 | const QString &filter, | |
295 | QString *selectedSuffixOut) | |
296 | { | |
297 | QString selectedFilter; | |
298 | QString myDir; | |
299 | if(dir.isEmpty()) // Default to user documents location | |
300 | { | |
301 | #if QT_VERSION < 0x050000 | |
302 | myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); | |
303 | #else | |
304 | myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); | |
305 | #endif | |
306 | } | |
307 | else | |
308 | { | |
309 | myDir = dir; | |
310 | } | |
311 | /* Directly convert path to native OS path separators */ | |
312 | QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent, caption, myDir, filter, &selectedFilter)); | |
313 | ||
314 | if(selectedSuffixOut) | |
315 | { | |
316 | /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ | |
317 | QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); | |
318 | QString selectedSuffix; | |
319 | if(filter_re.exactMatch(selectedFilter)) | |
320 | { | |
321 | selectedSuffix = filter_re.cap(1); | |
322 | } | |
323 | *selectedSuffixOut = selectedSuffix; | |
324 | } | |
325 | return result; | |
326 | } | |
327 | ||
328 | Qt::ConnectionType blockingGUIThreadConnection() | |
329 | { | |
330 | if(QThread::currentThread() != qApp->thread()) | |
331 | { | |
332 | return Qt::BlockingQueuedConnection; | |
333 | } | |
334 | else | |
335 | { | |
336 | return Qt::DirectConnection; | |
337 | } | |
338 | } | |
339 | ||
340 | bool checkPoint(const QPoint &p, const QWidget *w) | |
341 | { | |
342 | QWidget *atW = QApplication::widgetAt(w->mapToGlobal(p)); | |
343 | if (!atW) return false; | |
344 | return atW->topLevelWidget() == w; | |
345 | } | |
346 | ||
347 | bool isObscured(QWidget *w) | |
348 | { | |
349 | return !(checkPoint(QPoint(0, 0), w) | |
350 | && checkPoint(QPoint(w->width() - 1, 0), w) | |
351 | && checkPoint(QPoint(0, w->height() - 1), w) | |
352 | && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) | |
353 | && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); | |
354 | } | |
355 | ||
356 | void openDebugLogfile() | |
357 | { | |
358 | boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; | |
359 | ||
360 | /* Open debug.log with the associated application */ | |
361 | if (boost::filesystem::exists(pathDebug)) | |
362 | QDesktopServices::openUrl(QUrl::fromLocalFile(boostPathToQString(pathDebug))); | |
363 | } | |
364 | ||
365 | ToolTipToRichTextFilter::ToolTipToRichTextFilter(int size_threshold, QObject *parent) : | |
366 | QObject(parent), size_threshold(size_threshold) | |
367 | { | |
368 | ||
369 | } | |
370 | ||
371 | bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) | |
372 | { | |
373 | if(evt->type() == QEvent::ToolTipChange) | |
374 | { | |
375 | QWidget *widget = static_cast<QWidget*>(obj); | |
376 | QString tooltip = widget->toolTip(); | |
377 | if(tooltip.size() > size_threshold && !tooltip.startsWith("<qt") && !Qt::mightBeRichText(tooltip)) | |
378 | { | |
379 | // Envelop with <qt></qt> to make sure Qt detects this as rich text | |
380 | // Escape the current message as HTML and replace \n by <br> | |
381 | tooltip = "<qt>" + HtmlEscape(tooltip, true) + "</qt>"; | |
382 | widget->setToolTip(tooltip); | |
383 | return true; | |
384 | } | |
385 | } | |
386 | return QObject::eventFilter(obj, evt); | |
387 | } | |
388 | ||
389 | void TableViewLastColumnResizingFixer::connectViewHeadersSignals() | |
390 | { | |
391 | connect(tableView->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int))); | |
392 | connect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); | |
393 | } | |
394 | ||
395 | //we need to disconnect these while handling the resize events, otherwise we can enter infinite loops | |
396 | void TableViewLastColumnResizingFixer::disconnectViewHeadersSignals() | |
397 | { | |
398 | disconnect(tableView->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(on_sectionResized(int,int,int))); | |
399 | disconnect(tableView->horizontalHeader(), SIGNAL(geometriesChanged()), this, SLOT(on_geometriesChanged())); | |
400 | } | |
401 | ||
402 | //setup the resize mode, handles compatibility for QT5 and below as the method signatures changed. (refactored here for readability) | |
403 | void TableViewLastColumnResizingFixer::setViewHeaderResizeMode(int logicalIndex, QHeaderView::ResizeMode resizeMode) | |
404 | { | |
405 | #if QT_VERSION < 0x050000 | |
406 | tableView->horizontalHeader()->setResizeMode(logicalIndex, resizeMode); | |
407 | #else | |
408 | tableView->horizontalHeader()->setSectionResizeMode(logicalIndex, resizeMode); | |
409 | #endif | |
410 | } | |
411 | ||
412 | void TableViewLastColumnResizingFixer::resizeColumn(int nColumnIndex, int width) { | |
413 | tableView->setColumnWidth(nColumnIndex, width); | |
414 | tableView->horizontalHeader()->resizeSection(nColumnIndex, width); | |
415 | } | |
416 | ||
417 | int TableViewLastColumnResizingFixer::getColumnsWidth() | |
418 | { | |
419 | int nColumnsWidthSum = 0; | |
420 | for (int i = 0; i < columnCount; i++) | |
421 | { | |
422 | nColumnsWidthSum += tableView->horizontalHeader()->sectionSize(i); | |
423 | } | |
424 | return nColumnsWidthSum; | |
425 | } | |
426 | ||
427 | int TableViewLastColumnResizingFixer::getAvailableWidthForColumn(int column) | |
428 | { | |
429 | int nResult = lastColumnMinimumWidth; | |
430 | int nTableWidth = tableView->horizontalHeader()->width(); | |
431 | ||
432 | if (nTableWidth > 0) | |
433 | { | |
434 | int nOtherColsWidth = getColumnsWidth() - tableView->horizontalHeader()->sectionSize(column); | |
435 | nResult = std::max(nResult, nTableWidth - nOtherColsWidth); | |
436 | } | |
437 | ||
438 | return nResult; | |
439 | } | |
440 | ||
441 | //make sure we don't make the columns wider than the table's viewport's width. | |
442 | void TableViewLastColumnResizingFixer::adjustTableColumnsWidth() | |
443 | { | |
444 | disconnectViewHeadersSignals(); | |
445 | resizeColumn(lastColumnIndex, getAvailableWidthForColumn(lastColumnIndex)); | |
446 | connectViewHeadersSignals(); | |
447 | ||
448 | int nTableWidth = tableView->horizontalHeader()->width(); | |
449 | int nColsWidth = getColumnsWidth(); | |
450 | if (nColsWidth > nTableWidth) | |
451 | { | |
452 | resizeColumn(secondToLastColumnIndex,getAvailableWidthForColumn(secondToLastColumnIndex)); | |
453 | } | |
454 | } | |
455 | ||
456 | //make column use all the space available, useful during window resizing. | |
457 | void TableViewLastColumnResizingFixer::stretchColumnWidth(int column) { | |
458 | disconnectViewHeadersSignals(); | |
459 | resizeColumn(column, getAvailableWidthForColumn(column)); | |
460 | connectViewHeadersSignals(); | |
461 | } | |
462 | ||
463 | //when a section is resized this is a slot-proxy for ajustAmountColumnWidth() | |
464 | void TableViewLastColumnResizingFixer::on_sectionResized(int logicalIndex, int oldSize, int newSize) | |
465 | { | |
466 | adjustTableColumnsWidth(); | |
467 | int remainingWidth = getAvailableWidthForColumn(logicalIndex); | |
468 | if (newSize > remainingWidth) | |
469 | { | |
470 | resizeColumn(logicalIndex, remainingWidth); | |
471 | } | |
472 | } | |
473 | ||
474 | //when the table's geometry is ready, we manually perform the Stretch of the "Message" column | |
475 | //as the "Stretch" resize mode does not allow for interactive resizing. | |
476 | void TableViewLastColumnResizingFixer::on_geometriesChanged() | |
477 | { | |
478 | if ((getColumnsWidth() - this->tableView->horizontalHeader()->width()) != 0) | |
479 | { | |
480 | disconnectViewHeadersSignals(); | |
481 | resizeColumn(secondToLastColumnIndex, getAvailableWidthForColumn(secondToLastColumnIndex)); | |
482 | connectViewHeadersSignals(); | |
483 | } | |
484 | } | |
485 | ||
486 | /** | |
487 | * Initializes all internal variables and prepares the | |
488 | * the resize modes of the last 2 columns of the table and | |
489 | */ | |
490 | TableViewLastColumnResizingFixer::TableViewLastColumnResizingFixer(QTableView* table, int lastColMinimumWidth, int allColsMinimumWidth) : | |
491 | tableView(table), | |
492 | lastColumnMinimumWidth(lastColMinimumWidth), | |
493 | allColumnsMinimumWidth(allColsMinimumWidth) | |
494 | { | |
495 | columnCount = tableView->horizontalHeader()->count(); | |
496 | lastColumnIndex = columnCount - 1; | |
497 | secondToLastColumnIndex = columnCount - 2; | |
498 | tableView->horizontalHeader()->setMinimumSectionSize(allColumnsMinimumWidth); | |
499 | setViewHeaderResizeMode(secondToLastColumnIndex, QHeaderView::Interactive); | |
500 | setViewHeaderResizeMode(lastColumnIndex, QHeaderView::Interactive); | |
501 | } | |
502 | ||
503 | ||
504 | #ifdef WIN32 | |
505 | boost::filesystem::path static StartupShortcutPath() | |
506 | { | |
507 | return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; | |
508 | } | |
509 | ||
510 | bool GetStartOnSystemStartup() | |
511 | { | |
512 | // check for Bitcoin.lnk | |
513 | return boost::filesystem::exists(StartupShortcutPath()); | |
514 | } | |
515 | ||
516 | bool SetStartOnSystemStartup(bool fAutoStart) | |
517 | { | |
518 | // If the shortcut exists already, remove it for updating | |
519 | boost::filesystem::remove(StartupShortcutPath()); | |
520 | ||
521 | if (fAutoStart) | |
522 | { | |
523 | CoInitialize(NULL); | |
524 | ||
525 | // Get a pointer to the IShellLink interface. | |
526 | IShellLink* psl = NULL; | |
527 | HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, | |
528 | CLSCTX_INPROC_SERVER, IID_IShellLink, | |
529 | reinterpret_cast<void**>(&psl)); | |
530 | ||
531 | if (SUCCEEDED(hres)) | |
532 | { | |
533 | // Get the current executable path | |
534 | TCHAR pszExePath[MAX_PATH]; | |
535 | GetModuleFileName(NULL, pszExePath, sizeof(pszExePath)); | |
536 | ||
537 | TCHAR pszArgs[5] = TEXT("-min"); | |
538 | ||
539 | // Set the path to the shortcut target | |
540 | psl->SetPath(pszExePath); | |
541 | PathRemoveFileSpec(pszExePath); | |
542 | psl->SetWorkingDirectory(pszExePath); | |
543 | psl->SetShowCmd(SW_SHOWMINNOACTIVE); | |
544 | psl->SetArguments(pszArgs); | |
545 | ||
546 | // Query IShellLink for the IPersistFile interface for | |
547 | // saving the shortcut in persistent storage. | |
548 | IPersistFile* ppf = NULL; | |
549 | hres = psl->QueryInterface(IID_IPersistFile, | |
550 | reinterpret_cast<void**>(&ppf)); | |
551 | if (SUCCEEDED(hres)) | |
552 | { | |
553 | WCHAR pwsz[MAX_PATH]; | |
554 | // Ensure that the string is ANSI. | |
555 | MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); | |
556 | // Save the link by calling IPersistFile::Save. | |
557 | hres = ppf->Save(pwsz, TRUE); | |
558 | ppf->Release(); | |
559 | psl->Release(); | |
560 | CoUninitialize(); | |
561 | return true; | |
562 | } | |
563 | psl->Release(); | |
564 | } | |
565 | CoUninitialize(); | |
566 | return false; | |
567 | } | |
568 | return true; | |
569 | } | |
570 | ||
571 | #elif defined(LINUX) | |
572 | ||
573 | // Follow the Desktop Application Autostart Spec: | |
574 | // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html | |
575 | ||
576 | boost::filesystem::path static GetAutostartDir() | |
577 | { | |
578 | namespace fs = boost::filesystem; | |
579 | ||
580 | char* pszConfigHome = getenv("XDG_CONFIG_HOME"); | |
581 | if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; | |
582 | char* pszHome = getenv("HOME"); | |
583 | if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; | |
584 | return fs::path(); | |
585 | } | |
586 | ||
587 | boost::filesystem::path static GetAutostartFilePath() | |
588 | { | |
589 | return GetAutostartDir() / "bitcoin.desktop"; | |
590 | } | |
591 | ||
592 | bool GetStartOnSystemStartup() | |
593 | { | |
594 | boost::filesystem::ifstream optionFile(GetAutostartFilePath()); | |
595 | if (!optionFile.good()) | |
596 | return false; | |
597 | // Scan through file for "Hidden=true": | |
598 | std::string line; | |
599 | while (!optionFile.eof()) | |
600 | { | |
601 | getline(optionFile, line); | |
602 | if (line.find("Hidden") != std::string::npos && | |
603 | line.find("true") != std::string::npos) | |
604 | return false; | |
605 | } | |
606 | optionFile.close(); | |
607 | ||
608 | return true; | |
609 | } | |
610 | ||
611 | bool SetStartOnSystemStartup(bool fAutoStart) | |
612 | { | |
613 | if (!fAutoStart) | |
614 | boost::filesystem::remove(GetAutostartFilePath()); | |
615 | else | |
616 | { | |
617 | char pszExePath[MAX_PATH+1]; | |
618 | memset(pszExePath, 0, sizeof(pszExePath)); | |
619 | if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1) | |
620 | return false; | |
621 | ||
622 | boost::filesystem::create_directories(GetAutostartDir()); | |
623 | ||
624 | boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc); | |
625 | if (!optionFile.good()) | |
626 | return false; | |
627 | // Write a bitcoin.desktop file to the autostart directory: | |
628 | optionFile << "[Desktop Entry]\n"; | |
629 | optionFile << "Type=Application\n"; | |
630 | optionFile << "Name=Bitcoin\n"; | |
631 | optionFile << "Exec=" << pszExePath << " -min\n"; | |
632 | optionFile << "Terminal=false\n"; | |
633 | optionFile << "Hidden=false\n"; | |
634 | optionFile.close(); | |
635 | } | |
636 | return true; | |
637 | } | |
638 | ||
639 | ||
640 | #elif defined(Q_OS_MAC) | |
641 | // based on: https://github.com/Mozketo/LaunchAtLoginController/blob/master/LaunchAtLoginController.m | |
642 | ||
643 | #include <CoreFoundation/CoreFoundation.h> | |
644 | #include <CoreServices/CoreServices.h> | |
645 | ||
646 | LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl); | |
647 | LSSharedFileListItemRef findStartupItemInList(LSSharedFileListRef list, CFURLRef findUrl) | |
648 | { | |
649 | // loop through the list of startup items and try to find the bitcoin app | |
650 | CFArrayRef listSnapshot = LSSharedFileListCopySnapshot(list, NULL); | |
651 | for(int i = 0; i < CFArrayGetCount(listSnapshot); i++) { | |
652 | LSSharedFileListItemRef item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(listSnapshot, i); | |
653 | UInt32 resolutionFlags = kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes; | |
654 | CFURLRef currentItemURL = NULL; | |
655 | LSSharedFileListItemResolve(item, resolutionFlags, ¤tItemURL, NULL); | |
656 | if(currentItemURL && CFEqual(currentItemURL, findUrl)) { | |
657 | // found | |
658 | CFRelease(currentItemURL); | |
659 | return item; | |
660 | } | |
661 | if(currentItemURL) { | |
662 | CFRelease(currentItemURL); | |
663 | } | |
664 | } | |
665 | return NULL; | |
666 | } | |
667 | ||
668 | bool GetStartOnSystemStartup() | |
669 | { | |
670 | CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); | |
671 | LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); | |
672 | LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); | |
673 | return !!foundItem; // return boolified object | |
674 | } | |
675 | ||
676 | bool SetStartOnSystemStartup(bool fAutoStart) | |
677 | { | |
678 | CFURLRef bitcoinAppUrl = CFBundleCopyBundleURL(CFBundleGetMainBundle()); | |
679 | LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL); | |
680 | LSSharedFileListItemRef foundItem = findStartupItemInList(loginItems, bitcoinAppUrl); | |
681 | ||
682 | if(fAutoStart && !foundItem) { | |
683 | // add bitcoin app to startup item list | |
684 | LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst, NULL, NULL, bitcoinAppUrl, NULL, NULL); | |
685 | } | |
686 | else if(!fAutoStart && foundItem) { | |
687 | // remove item | |
688 | LSSharedFileListItemRemove(loginItems, foundItem); | |
689 | } | |
690 | return true; | |
691 | } | |
692 | #else | |
693 | ||
694 | bool GetStartOnSystemStartup() { return false; } | |
695 | bool SetStartOnSystemStartup(bool fAutoStart) { return false; } | |
696 | ||
697 | #endif | |
698 | ||
699 | void saveWindowGeometry(const QString& strSetting, QWidget *parent) | |
700 | { | |
701 | QSettings settings; | |
702 | settings.setValue(strSetting + "Pos", parent->pos()); | |
703 | settings.setValue(strSetting + "Size", parent->size()); | |
704 | } | |
705 | ||
706 | void restoreWindowGeometry(const QString& strSetting, const QSize& defaultSize, QWidget *parent) | |
707 | { | |
708 | QSettings settings; | |
709 | QPoint pos = settings.value(strSetting + "Pos").toPoint(); | |
710 | QSize size = settings.value(strSetting + "Size", defaultSize).toSize(); | |
711 | ||
712 | if (!pos.x() && !pos.y()) { | |
713 | QRect screen = QApplication::desktop()->screenGeometry(); | |
714 | pos.setX((screen.width() - size.width()) / 2); | |
715 | pos.setY((screen.height() - size.height()) / 2); | |
716 | } | |
717 | ||
718 | parent->resize(size); | |
719 | parent->move(pos); | |
720 | } | |
721 | ||
722 | void setClipboard(const QString& str) | |
723 | { | |
724 | QApplication::clipboard()->setText(str, QClipboard::Clipboard); | |
725 | QApplication::clipboard()->setText(str, QClipboard::Selection); | |
726 | } | |
727 | ||
728 | #if BOOST_FILESYSTEM_VERSION >= 3 | |
729 | boost::filesystem::path qstringToBoostPath(const QString &path) | |
730 | { | |
731 | return boost::filesystem::path(path.toStdString(), utf8); | |
732 | } | |
733 | ||
734 | QString boostPathToQString(const boost::filesystem::path &path) | |
735 | { | |
736 | return QString::fromStdString(path.string(utf8)); | |
737 | } | |
738 | #else | |
739 | #warning Conversion between boost path and QString can use invalid character encoding with boost_filesystem v2 and older | |
740 | boost::filesystem::path qstringToBoostPath(const QString &path) | |
741 | { | |
742 | return boost::filesystem::path(path.toStdString()); | |
743 | } | |
744 | ||
745 | QString boostPathToQString(const boost::filesystem::path &path) | |
746 | { | |
747 | return QString::fromStdString(path.string()); | |
748 | } | |
749 | #endif | |
750 | ||
751 | } // namespace GUIUtil |