]>
Commit | Line | Data |
---|---|---|
0856c1a0 | 1 | #include "guiutil.h" |
e457b021 | 2 | #include "bitcoinaddressvalidator.h" |
db7f0234 WL |
3 | #include "walletmodel.h" |
4 | #include "bitcoinunits.h" | |
97521b52 | 5 | #include "util.h" |
5d6b3027 | 6 | #include "init.h" |
93b7af30 | 7 | #include "base58.h" |
8fe2308b | 8 | |
e457b021 | 9 | #include <QString> |
0856c1a0 | 10 | #include <QDateTime> |
e457b021 WL |
11 | #include <QDoubleValidator> |
12 | #include <QFont> | |
13 | #include <QLineEdit> | |
db7f0234 | 14 | #include <QUrl> |
e0734571 | 15 | #include <QTextDocument> // For Qt::escape |
c58e7d4e WL |
16 | #include <QAbstractItemView> |
17 | #include <QApplication> | |
18 | #include <QClipboard> | |
303a47c0 WL |
19 | #include <QFileDialog> |
20 | #include <QDesktopServices> | |
7e7bcce2 | 21 | #include <QThread> |
0856c1a0 | 22 | |
4d3dda5d | 23 | #include <boost/filesystem.hpp> |
67d4cbab | 24 | #include <boost/filesystem/fstream.hpp> |
4d3dda5d PK |
25 | |
26 | #ifdef WIN32 | |
27 | #ifdef _WIN32_WINNT | |
28 | #undef _WIN32_WINNT | |
29 | #endif | |
30 | #define _WIN32_WINNT 0x0501 | |
31 | #ifdef _WIN32_IE | |
32 | #undef _WIN32_IE | |
33 | #endif | |
34 | #define _WIN32_IE 0x0501 | |
35 | #define WIN32_LEAN_AND_MEAN 1 | |
36 | #ifndef NOMINMAX | |
37 | #define NOMINMAX | |
38 | #endif | |
39 | #include "shlwapi.h" | |
b0659760 MC |
40 | #include "shlobj.h" |
41 | #include "shellapi.h" | |
4d3dda5d PK |
42 | #endif |
43 | ||
86d56349 | 44 | namespace GUIUtil { |
45 | ||
46 | QString dateTimeStr(const QDateTime &date) | |
0856c1a0 | 47 | { |
86d56349 | 48 | return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm"); |
a99ac8d3 WL |
49 | } |
50 | ||
86d56349 | 51 | QString dateTimeStr(qint64 nTime) |
a99ac8d3 | 52 | { |
86d56349 | 53 | return dateTimeStr(QDateTime::fromTime_t((qint32)nTime)); |
0856c1a0 | 54 | } |
ef1b844e | 55 | |
86d56349 | 56 | QFont bitcoinAddressFont() |
ef1b844e WL |
57 | { |
58 | QFont font("Monospace"); | |
59 | font.setStyleHint(QFont::TypeWriter); | |
60 | return font; | |
61 | } | |
e457b021 | 62 | |
86d56349 | 63 | void setupAddressWidget(QLineEdit *widget, QWidget *parent) |
e457b021 WL |
64 | { |
65 | widget->setMaxLength(BitcoinAddressValidator::MaxAddressLength); | |
66 | widget->setValidator(new BitcoinAddressValidator(parent)); | |
67 | widget->setFont(bitcoinAddressFont()); | |
68 | } | |
69 | ||
86d56349 | 70 | void setupAmountWidget(QLineEdit *widget, QWidget *parent) |
e457b021 WL |
71 | { |
72 | QDoubleValidator *amountValidator = new QDoubleValidator(parent); | |
73 | amountValidator->setDecimals(8); | |
74 | amountValidator->setBottom(0.0); | |
75 | widget->setValidator(amountValidator); | |
5e1feddc | 76 | widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter); |
e457b021 | 77 | } |
db7f0234 | 78 | |
86d56349 | 79 | bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) |
db7f0234 | 80 | { |
fa2544e7 | 81 | if(uri.scheme() != QString("bitcoin")) |
db7f0234 WL |
82 | return false; |
83 | ||
93b7af30 PK |
84 | // check if the address is valid |
85 | CBitcoinAddress addressFromUri(uri.path().toStdString()); | |
86 | if (!addressFromUri.IsValid()) | |
87 | return false; | |
88 | ||
db7f0234 | 89 | SendCoinsRecipient rv; |
fa2544e7 | 90 | rv.address = uri.path(); |
cce89ead | 91 | rv.amount = 0; |
fa2544e7 | 92 | QList<QPair<QString, QString> > items = uri.queryItems(); |
cce89ead | 93 | for (QList<QPair<QString, QString> >::iterator i = items.begin(); i != items.end(); i++) |
c359ac91 | 94 | { |
cce89ead MC |
95 | bool fShouldReturnFalse = false; |
96 | if (i->first.startsWith("req-")) | |
c359ac91 | 97 | { |
cce89ead MC |
98 | i->first.remove(0, 4); |
99 | fShouldReturnFalse = true; | |
c359ac91 | 100 | } |
cce89ead MC |
101 | |
102 | if (i->first == "label") | |
103 | { | |
104 | rv.label = i->second; | |
105 | fShouldReturnFalse = false; | |
106 | } | |
107 | else if (i->first == "amount") | |
108 | { | |
109 | if(!i->second.isEmpty()) | |
110 | { | |
111 | if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount)) | |
112 | { | |
113 | return false; | |
114 | } | |
115 | } | |
116 | fShouldReturnFalse = false; | |
117 | } | |
118 | ||
119 | if (fShouldReturnFalse) | |
120 | return false; | |
db7f0234 WL |
121 | } |
122 | if(out) | |
123 | { | |
124 | *out = rv; | |
125 | } | |
126 | return true; | |
127 | } | |
e0734571 | 128 | |
86d56349 | 129 | bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) |
23b3cf9d WL |
130 | { |
131 | // Convert bitcoin:// to bitcoin: | |
132 | // | |
133 | // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host, | |
134 | // which will lowercase it (and thus invalidate the address). | |
fa2544e7 | 135 | if(uri.startsWith("bitcoin://")) |
23b3cf9d | 136 | { |
fa2544e7 | 137 | uri.replace(0, 10, "bitcoin:"); |
23b3cf9d | 138 | } |
fa2544e7 LD |
139 | QUrl uriInstance(uri); |
140 | return parseBitcoinURI(uriInstance, out); | |
23b3cf9d WL |
141 | } |
142 | ||
86d56349 | 143 | QString HtmlEscape(const QString& str, bool fMultiLine) |
e0734571 WL |
144 | { |
145 | QString escaped = Qt::escape(str); | |
146 | if(fMultiLine) | |
147 | { | |
148 | escaped = escaped.replace("\n", "<br>\n"); | |
149 | } | |
150 | return escaped; | |
151 | } | |
152 | ||
86d56349 | 153 | QString HtmlEscape(const std::string& str, bool fMultiLine) |
e0734571 WL |
154 | { |
155 | return HtmlEscape(QString::fromStdString(str), fMultiLine); | |
156 | } | |
c58e7d4e | 157 | |
86d56349 | 158 | void copyEntryData(QAbstractItemView *view, int column, int role) |
c58e7d4e WL |
159 | { |
160 | if(!view || !view->selectionModel()) | |
161 | return; | |
162 | QModelIndexList selection = view->selectionModel()->selectedRows(column); | |
163 | ||
164 | if(!selection.isEmpty()) | |
165 | { | |
166 | // Copy first item | |
167 | QApplication::clipboard()->setText(selection.at(0).data(role).toString()); | |
168 | } | |
169 | } | |
303a47c0 | 170 | |
86d56349 | 171 | QString getSaveFileName(QWidget *parent, const QString &caption, |
303a47c0 WL |
172 | const QString &dir, |
173 | const QString &filter, | |
174 | QString *selectedSuffixOut) | |
175 | { | |
176 | QString selectedFilter; | |
177 | QString myDir; | |
178 | if(dir.isEmpty()) // Default to user documents location | |
179 | { | |
180 | myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); | |
181 | } | |
182 | else | |
183 | { | |
184 | myDir = dir; | |
185 | } | |
186 | QString result = QFileDialog::getSaveFileName(parent, caption, myDir, filter, &selectedFilter); | |
187 | ||
188 | /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ | |
189 | QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); | |
190 | QString selectedSuffix; | |
191 | if(filter_re.exactMatch(selectedFilter)) | |
192 | { | |
193 | selectedSuffix = filter_re.cap(1); | |
194 | } | |
195 | ||
196 | /* Add suffix if needed */ | |
197 | QFileInfo info(result); | |
198 | if(!result.isEmpty()) | |
199 | { | |
200 | if(info.suffix().isEmpty() && !selectedSuffix.isEmpty()) | |
201 | { | |
202 | /* No suffix specified, add selected suffix */ | |
203 | if(!result.endsWith(".")) | |
204 | result.append("."); | |
205 | result.append(selectedSuffix); | |
206 | } | |
207 | } | |
208 | ||
209 | /* Return selected suffix if asked to */ | |
210 | if(selectedSuffixOut) | |
211 | { | |
212 | *selectedSuffixOut = selectedSuffix; | |
213 | } | |
214 | return result; | |
215 | } | |
216 | ||
86d56349 | 217 | Qt::ConnectionType blockingGUIThreadConnection() |
7e7bcce2 WL |
218 | { |
219 | if(QThread::currentThread() != QCoreApplication::instance()->thread()) | |
220 | { | |
221 | return Qt::BlockingQueuedConnection; | |
222 | } | |
223 | else | |
224 | { | |
225 | return Qt::DirectConnection; | |
226 | } | |
227 | } | |
86d56349 | 228 | |
229 | bool checkPoint(const QPoint &p, const QWidget *w) | |
230 | { | |
93b7af30 PK |
231 | QWidget *atW = qApp->widgetAt(w->mapToGlobal(p)); |
232 | if (!atW) return false; | |
233 | return atW->topLevelWidget() == w; | |
86d56349 | 234 | } |
235 | ||
236 | bool isObscured(QWidget *w) | |
237 | { | |
93b7af30 PK |
238 | return !(checkPoint(QPoint(0, 0), w) |
239 | && checkPoint(QPoint(w->width() - 1, 0), w) | |
240 | && checkPoint(QPoint(0, w->height() - 1), w) | |
241 | && checkPoint(QPoint(w->width() - 1, w->height() - 1), w) | |
242 | && checkPoint(QPoint(w->width() / 2, w->height() / 2), w)); | |
86d56349 | 243 | } |
244 | ||
4d3dda5d PK |
245 | void openDebugLogfile() |
246 | { | |
247 | boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; | |
248 | ||
9b1732ba | 249 | /* Open debug.log with the associated application */ |
4d3dda5d | 250 | if (boost::filesystem::exists(pathDebug)) |
9b1732ba | 251 | QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(pathDebug.string()))); |
4d3dda5d PK |
252 | } |
253 | ||
58b01afc PK |
254 | ToolTipToRichTextFilter::ToolTipToRichTextFilter(int size_threshold, QObject *parent) : |
255 | QObject(parent), size_threshold(size_threshold) | |
3793fa09 WL |
256 | { |
257 | ||
258 | } | |
259 | ||
260 | bool ToolTipToRichTextFilter::eventFilter(QObject *obj, QEvent *evt) | |
261 | { | |
262 | if(evt->type() == QEvent::ToolTipChange) | |
263 | { | |
264 | QWidget *widget = static_cast<QWidget*>(obj); | |
265 | QString tooltip = widget->toolTip(); | |
99fdc1d8 | 266 | if(tooltip.size() > size_threshold && !tooltip.startsWith("<qt/>") && !Qt::mightBeRichText(tooltip)) |
3793fa09 WL |
267 | { |
268 | // Prefix <qt/> to make sure Qt detects this as rich text | |
269 | // Escape the current message as HTML and replace \n by <br> | |
270 | tooltip = "<qt/>" + HtmlEscape(tooltip, true); | |
271 | widget->setToolTip(tooltip); | |
272 | return true; | |
273 | } | |
274 | } | |
275 | return QObject::eventFilter(obj, evt); | |
276 | } | |
277 | ||
67d4cbab WL |
278 | #ifdef WIN32 |
279 | boost::filesystem::path static StartupShortcutPath() | |
280 | { | |
281 | return GetSpecialFolderPath(CSIDL_STARTUP) / "Bitcoin.lnk"; | |
282 | } | |
283 | ||
284 | bool GetStartOnSystemStartup() | |
285 | { | |
286 | // check for Bitcoin.lnk | |
287 | return boost::filesystem::exists(StartupShortcutPath()); | |
288 | } | |
289 | ||
290 | bool SetStartOnSystemStartup(bool fAutoStart) | |
291 | { | |
292 | // If the shortcut exists already, remove it for updating | |
293 | boost::filesystem::remove(StartupShortcutPath()); | |
294 | ||
295 | if (fAutoStart) | |
296 | { | |
297 | CoInitialize(NULL); | |
298 | ||
299 | // Get a pointer to the IShellLink interface. | |
300 | IShellLink* psl = NULL; | |
301 | HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, | |
302 | CLSCTX_INPROC_SERVER, IID_IShellLink, | |
303 | reinterpret_cast<void**>(&psl)); | |
304 | ||
305 | if (SUCCEEDED(hres)) | |
306 | { | |
307 | // Get the current executable path | |
308 | TCHAR pszExePath[MAX_PATH]; | |
309 | GetModuleFileName(NULL, pszExePath, sizeof(pszExePath)); | |
310 | ||
311 | TCHAR pszArgs[5] = TEXT("-min"); | |
312 | ||
313 | // Set the path to the shortcut target | |
314 | psl->SetPath(pszExePath); | |
315 | PathRemoveFileSpec(pszExePath); | |
316 | psl->SetWorkingDirectory(pszExePath); | |
317 | psl->SetShowCmd(SW_SHOWMINNOACTIVE); | |
318 | psl->SetArguments(pszArgs); | |
319 | ||
320 | // Query IShellLink for the IPersistFile interface for | |
321 | // saving the shortcut in persistent storage. | |
322 | IPersistFile* ppf = NULL; | |
323 | hres = psl->QueryInterface(IID_IPersistFile, | |
324 | reinterpret_cast<void**>(&ppf)); | |
325 | if (SUCCEEDED(hres)) | |
326 | { | |
327 | WCHAR pwsz[MAX_PATH]; | |
328 | // Ensure that the string is ANSI. | |
329 | MultiByteToWideChar(CP_ACP, 0, StartupShortcutPath().string().c_str(), -1, pwsz, MAX_PATH); | |
330 | // Save the link by calling IPersistFile::Save. | |
331 | hres = ppf->Save(pwsz, TRUE); | |
332 | ppf->Release(); | |
333 | psl->Release(); | |
334 | CoUninitialize(); | |
335 | return true; | |
336 | } | |
337 | psl->Release(); | |
338 | } | |
339 | CoUninitialize(); | |
340 | return false; | |
341 | } | |
342 | return true; | |
343 | } | |
344 | ||
345 | #elif defined(LINUX) | |
346 | ||
347 | // Follow the Desktop Application Autostart Spec: | |
348 | // http://standards.freedesktop.org/autostart-spec/autostart-spec-latest.html | |
349 | ||
350 | boost::filesystem::path static GetAutostartDir() | |
351 | { | |
352 | namespace fs = boost::filesystem; | |
353 | ||
354 | char* pszConfigHome = getenv("XDG_CONFIG_HOME"); | |
355 | if (pszConfigHome) return fs::path(pszConfigHome) / "autostart"; | |
356 | char* pszHome = getenv("HOME"); | |
357 | if (pszHome) return fs::path(pszHome) / ".config" / "autostart"; | |
358 | return fs::path(); | |
359 | } | |
360 | ||
361 | boost::filesystem::path static GetAutostartFilePath() | |
362 | { | |
363 | return GetAutostartDir() / "bitcoin.desktop"; | |
364 | } | |
365 | ||
366 | bool GetStartOnSystemStartup() | |
367 | { | |
368 | boost::filesystem::ifstream optionFile(GetAutostartFilePath()); | |
369 | if (!optionFile.good()) | |
370 | return false; | |
371 | // Scan through file for "Hidden=true": | |
372 | std::string line; | |
373 | while (!optionFile.eof()) | |
374 | { | |
375 | getline(optionFile, line); | |
376 | if (line.find("Hidden") != std::string::npos && | |
377 | line.find("true") != std::string::npos) | |
378 | return false; | |
379 | } | |
380 | optionFile.close(); | |
381 | ||
382 | return true; | |
383 | } | |
384 | ||
385 | bool SetStartOnSystemStartup(bool fAutoStart) | |
386 | { | |
387 | if (!fAutoStart) | |
388 | boost::filesystem::remove(GetAutostartFilePath()); | |
389 | else | |
390 | { | |
391 | char pszExePath[MAX_PATH+1]; | |
392 | memset(pszExePath, 0, sizeof(pszExePath)); | |
393 | if (readlink("/proc/self/exe", pszExePath, sizeof(pszExePath)-1) == -1) | |
394 | return false; | |
395 | ||
396 | boost::filesystem::create_directories(GetAutostartDir()); | |
397 | ||
398 | boost::filesystem::ofstream optionFile(GetAutostartFilePath(), std::ios_base::out|std::ios_base::trunc); | |
399 | if (!optionFile.good()) | |
400 | return false; | |
401 | // Write a bitcoin.desktop file to the autostart directory: | |
402 | optionFile << "[Desktop Entry]\n"; | |
403 | optionFile << "Type=Application\n"; | |
404 | optionFile << "Name=Bitcoin\n"; | |
405 | optionFile << "Exec=" << pszExePath << " -min\n"; | |
406 | optionFile << "Terminal=false\n"; | |
407 | optionFile << "Hidden=false\n"; | |
408 | optionFile.close(); | |
409 | } | |
410 | return true; | |
411 | } | |
412 | #else | |
413 | ||
414 | // TODO: OSX startup stuff; see: | |
415 | // http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/CustomLogin.html | |
416 | ||
417 | bool GetStartOnSystemStartup() { return false; } | |
418 | bool SetStartOnSystemStartup(bool fAutoStart) { return false; } | |
419 | ||
420 | #endif | |
421 | ||
5d6b3027 PK |
422 | HelpMessageBox::HelpMessageBox(QWidget *parent) : |
423 | QMessageBox(parent) | |
424 | { | |
425 | header = tr("Bitcoin-Qt") + " " + tr("version") + " " + | |
426 | QString::fromStdString(FormatFullVersion()) + "\n\n" + | |
427 | tr("Usage:") + "\n" + | |
428 | " bitcoin-qt [" + tr("command-line options") + "] " + "\n"; | |
429 | ||
430 | coreOptions = QString::fromStdString(HelpMessage()); | |
431 | ||
432 | uiOptions = tr("UI options") + ":\n" + | |
433 | " -lang=<lang> " + tr("Set language, for example \"de_DE\" (default: system locale)") + "\n" + | |
434 | " -min " + tr("Start minimized") + "\n" + | |
435 | " -splash " + tr("Show splash screen on startup (default: 1)") + "\n"; | |
436 | ||
437 | setWindowTitle(tr("Bitcoin-Qt")); | |
438 | setTextFormat(Qt::PlainText); | |
439 | // setMinimumWidth is ignored for QMessageBox so put in nonbreaking spaces to make it wider. | |
440 | setText(header + QString(QChar(0x2003)).repeated(50)); | |
441 | setDetailedText(coreOptions + "\n" + uiOptions); | |
442 | } | |
443 | ||
444 | void HelpMessageBox::exec() | |
445 | { | |
446 | #if defined(WIN32) | |
447 | // On windows, show a message box, as there is no stderr in windowed applications | |
448 | QMessageBox::exec(); | |
449 | #else | |
450 | // On other operating systems, the expected action is to print the message to the console. | |
451 | QString strUsage = header + "\n" + coreOptions + "\n" + uiOptions; | |
452 | fprintf(stderr, "%s", strUsage.toStdString().c_str()); | |
453 | #endif | |
454 | } | |
455 | ||
86d56349 | 456 | } // namespace GUIUtil |
457 |