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