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