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