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