diff --git a/contrib/bitcoin-qt.pro b/contrib/bitcoin-qt.pro index 6adf7650d..6e31c2ed8 100644 --- a/contrib/bitcoin-qt.pro +++ b/contrib/bitcoin-qt.pro @@ -12,7 +12,8 @@ FORMS += \ ../src/qt/forms/addressbookpage.ui \ ../src/qt/forms/aboutdialog.ui \ ../src/qt/forms/receivecoinsdialog.ui \ - ../src/qt/forms/receiverequestdialog.ui + ../src/qt/forms/receiverequestdialog.ui \ + ../src/qt/forms/openuridialog.ui RESOURCES += \ ../src/qt/bitcoin.qrc diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index 3ede99dfa..a19cdc25a 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -33,6 +33,7 @@ QT_TS = locale/bitcoin_ach.ts locale/bitcoin_af_ZA.ts locale/bitcoin_ar.ts \ QT_FORMS_UI = forms/aboutdialog.ui forms/addressbookpage.ui \ forms/askpassphrasedialog.ui forms/editaddressdialog.ui forms/intro.ui \ + forms/openuridialog.ui \ forms/optionsdialog.ui forms/overviewpage.ui forms/receiverequestdialog.ui \ forms/receivecoinsdialog.ui \ forms/rpcconsole.ui forms/sendcoinsdialog.ui forms/sendcoinsentry.ui \ @@ -44,7 +45,9 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ moc_bitcoingui.cpp moc_bitcoinunits.cpp moc_clientmodel.cpp \ moc_csvmodelwriter.cpp moc_editaddressdialog.cpp moc_guiutil.cpp \ moc_intro.cpp moc_macdockiconhandler.cpp moc_macnotificationhandler.cpp \ - moc_monitoreddatamapper.cpp moc_notificator.cpp moc_optionsdialog.cpp \ + moc_monitoreddatamapper.cpp moc_notificator.cpp \ + moc_openuridialog.cpp \ + moc_optionsdialog.cpp \ moc_optionsmodel.cpp moc_overviewpage.cpp moc_paymentserver.cpp \ moc_receiverequestdialog.cpp moc_qvalidatedlineedit.cpp moc_qvaluecombobox.cpp \ moc_receivecoinsdialog.cpp \ @@ -69,7 +72,9 @@ BITCOIN_QT_H = aboutdialog.h addressbookpage.h addresstablemodel.h \ askpassphrasedialog.h bitcoinaddressvalidator.h bitcoinamountfield.h \ bitcoingui.h bitcoinunits.h clientmodel.h csvmodelwriter.h \ editaddressdialog.h guiconstants.h guiutil.h intro.h macdockiconhandler.h \ - macnotificationhandler.h monitoreddatamapper.h notificator.h optionsdialog.h \ + macnotificationhandler.h monitoreddatamapper.h notificator.h \ + openuridialog.h \ + optionsdialog.h \ optionsmodel.h overviewpage.h paymentrequestplus.h paymentserver.h \ receivecoinsdialog.h \ receiverequestdialog.h qvalidatedlineedit.h qvaluecombobox.h rpcconsole.h \ @@ -100,6 +105,7 @@ BITCOIN_QT_CPP = aboutdialog.cpp addressbookpage.cpp \ bitcoinamountfield.cpp bitcoin.cpp bitcoingui.cpp \ bitcoinunits.cpp clientmodel.cpp csvmodelwriter.cpp editaddressdialog.cpp \ guiutil.cpp intro.cpp monitoreddatamapper.cpp notificator.cpp \ + openuridialog.cpp \ optionsdialog.cpp optionsmodel.cpp overviewpage.cpp paymentrequestplus.cpp \ paymentserver.cpp qvalidatedlineedit.cpp qvaluecombobox.cpp \ receivecoinsdialog.cpp receiverequestdialog.cpp \ diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 2fa7979ea..5eb0cfed3 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -334,6 +334,8 @@ int main(int argc, char *argv[]) // bitcoin: URIs or payment requests: QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), &window, SLOT(handlePaymentRequest(SendCoinsRecipient))); + QObject::connect(&window, SIGNAL(receivedURI(QString)), + paymentServer, SLOT(handleURIOrFile(QString))); QObject::connect(&walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); QObject::connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index a1bb0ee2f..8b0aba1b5 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -15,6 +15,7 @@ #include "rpcconsole.h" #include "walletframe.h" #include "walletmodel.h" +#include "openuridialog.h" #ifdef Q_OS_MAC #include "macdockiconhandler.h" @@ -262,6 +263,9 @@ void BitcoinGUI::createActions(bool fIsTestnet) usedReceivingAddressesAction = new QAction(QIcon(":/icons/address-book"), tr("Used &receiving addresses..."), this); usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels")); + openAction = new QAction(QApplication::style()->standardIcon(QStyle::SP_FileIcon), tr("Open URI..."), this); + openAction->setStatusTip(tr("Open a bitcoin: URI or payment request")); + connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked())); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); @@ -274,6 +278,7 @@ void BitcoinGUI::createActions(bool fIsTestnet) connect(verifyMessageAction, SIGNAL(triggered()), this, SLOT(gotoVerifyMessageTab())); connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedSendingAddresses())); connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame, SLOT(usedReceivingAddresses())); + connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked())); } void BitcoinGUI::createMenuBar() @@ -288,6 +293,7 @@ void BitcoinGUI::createMenuBar() // Configure the menus QMenu *file = appMenuBar->addMenu(tr("&File")); + file->addAction(openAction); file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); @@ -445,6 +451,15 @@ void BitcoinGUI::aboutClicked() dlg.exec(); } +void BitcoinGUI::openClicked() +{ + OpenURIDialog dlg; + if(dlg.exec()) + { + emit receivedURI(dlg.getURI()); + } +} + void BitcoinGUI::gotoOverviewPage() { overviewAction->setChecked(true); @@ -720,23 +735,11 @@ void BitcoinGUI::dropEvent(QDropEvent *event) { if(event->mimeData()->hasUrls()) { - int nValidUrisFound = 0; - QList uris = event->mimeData()->urls(); - foreach(const QUrl &uri, uris) + foreach(const QUrl &uri, event->mimeData()->urls()) { - SendCoinsRecipient r; - if (GUIUtil::parseBitcoinURI(uri, &r) && walletFrame->handlePaymentRequest(r)) - nValidUrisFound++; + emit receivedURI(uri.toString()); } - - // if valid URIs were found - if (nValidUrisFound) - walletFrame->gotoSendCoinsPage(); - else - message(tr("URI handling"), tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), - CClientUIInterface::ICON_WARNING); } - event->acceptProposedAction(); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 2e3b3e74b..acbc38c89 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -87,6 +87,7 @@ private: QAction *changePassphraseAction; QAction *aboutQtAction; QAction *openRPCConsoleAction; + QAction *openAction; QSystemTrayIcon *trayIcon; Notificator *notificator; @@ -107,6 +108,10 @@ private: /** Create system tray menu (or setup the dock menu) */ void createTrayIconMenu(); +signals: + /** Signal raised when a URI was entered or dragged to the GUI */ + void receivedURI(const QString &uri); + public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); @@ -165,6 +170,8 @@ private slots: /** Handle tray icon clicked */ void trayIconActivated(QSystemTrayIcon::ActivationReason reason); #endif + /** Show open dialog */ + void openClicked(); /** Show window if hidden, unminimize when minimized, rise when obscured or show if hidden and fToggleHidden is true */ void showNormalIfMinimized(bool fToggleHidden = false); diff --git a/src/qt/forms/openuridialog.ui b/src/qt/forms/openuridialog.ui new file mode 100644 index 000000000..cd09ed024 --- /dev/null +++ b/src/qt/forms/openuridialog.ui @@ -0,0 +1,116 @@ + + + OpenURIDialog + + + + 0 + 0 + 564 + 109 + + + + Open URI + + + + + + Open payment request from URI or file + + + + + + + + + URI: + + + + + + + + + + + Select payment request file + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QValidatedLineEdit + QLineEdit +
qvalidatedlineedit.h
+
+
+ + + + buttonBox + accepted() + OpenURIDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OpenURIDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index e6a1138f1..85eeab2cb 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -278,6 +278,41 @@ QString getSaveFileName(QWidget *parent, const QString &caption, const QString & return result; } +QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, + const QString &filter, + QString *selectedSuffixOut) +{ + QString selectedFilter; + QString myDir; + if(dir.isEmpty()) // Default to user documents location + { +#if QT_VERSION < 0x050000 + myDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation); +#else + myDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); +#endif + } + else + { + myDir = dir; + } + /* Directly convert path to native OS path separators */ + QString result = QDir::toNativeSeparators(QFileDialog::getOpenFileName(parent, caption, myDir, filter, &selectedFilter)); + + if(selectedSuffixOut) + { + /* Extract first suffix from filter pattern "Description (*.foo)" or "Description (*.foo *.bar ...) */ + QRegExp filter_re(".* \\(\\*\\.(.*)[ \\)]"); + QString selectedSuffix; + if(filter_re.exactMatch(selectedFilter)) + { + selectedSuffix = filter_re.cap(1); + } + *selectedSuffixOut = selectedSuffix; + } + return result; +} + Qt::ConnectionType blockingGUIThreadConnection() { if(QThread::currentThread() != qApp->thread()) diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 8bd0eab9d..ddff2de4c 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -36,7 +36,6 @@ namespace GUIUtil void setupAmountWidget(QLineEdit *widget, QWidget *parent); // Parse "bitcoin:" URI into recipient object, return true on successful parsing - // See Bitcoin URI definition discussion here: https://bitcointalk.org/index.php?topic=33490.0 bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); QString formatBitcoinURI(const SendCoinsRecipient &info); @@ -70,6 +69,19 @@ namespace GUIUtil const QString &dir=QString(), const QString &filter=QString(), QString *selectedSuffixOut=0); + /** Get open filename, convenience wrapper for QFileDialog::getOpenFileName. + + @param[in] parent Parent window (or 0) + @param[in] caption Window caption (or empty, for default) + @param[in] dir Starting directory (or empty, to default to documents directory) + @param[in] filter Filter specification such as "Comma Separated Files (*.csv)" + @param[out] selectedSuffixOut Pointer to return the suffix (file type) that was selected (or 0). + Can be useful when choosing the save file format based on suffix. + */ + QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, + const QString &filter, + QString *selectedSuffixOut); + /** Get connection type to call object slot in GUI thread with invokeMethod. The call will be blocking. @returns If called from the GUI thread, return a Qt::DirectConnection. diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp new file mode 100644 index 000000000..803a3c9dd --- /dev/null +++ b/src/qt/openuridialog.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "openuridialog.h" +#include "ui_openuridialog.h" + +#include "guiutil.h" +#include "walletmodel.h" + +#include + +OpenURIDialog::OpenURIDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::OpenURIDialog) +{ + ui->setupUi(this); +#if QT_VERSION >= 0x040700 + ui->uriEdit->setPlaceholderText("bitcoin:"); +#endif +} + +OpenURIDialog::~OpenURIDialog() +{ + delete ui; +} + +QString OpenURIDialog::getURI() +{ + return ui->uriEdit->text(); +} + +void OpenURIDialog::accept() +{ + SendCoinsRecipient rcp; + if(GUIUtil::parseBitcoinURI(getURI(), &rcp)) + { + /* Only accept value URIs */ + QDialog::accept(); + } else { + ui->uriEdit->setValid(false); + } +} + +void OpenURIDialog::on_selectFileButton_clicked() +{ + QString filename = GUIUtil::getOpenFileName(this, tr("Select payment request file to open"), "", "", NULL); + if(filename.isEmpty()) + return; + QUrl fileUri = QUrl::fromLocalFile(filename); + ui->uriEdit->setText("bitcoin:?request=" + QUrl::toPercentEncoding(fileUri.toString())); +} diff --git a/src/qt/openuridialog.h b/src/qt/openuridialog.h new file mode 100644 index 000000000..3b9ff0a8e --- /dev/null +++ b/src/qt/openuridialog.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef OPENURIDIALOG_H +#define OPENURIDIALOG_H + +#include + +namespace Ui { +class OpenURIDialog; +} + +class OpenURIDialog : public QDialog +{ + Q_OBJECT + +public: + explicit OpenURIDialog(QWidget *parent = 0); + ~OpenURIDialog(); + + QString getURI(); + +protected slots: + void accept(); + +private slots: + void on_selectFileButton_clicked(); + +private: + Ui::OpenURIDialog *ui; +}; + +#endif // OPENURIDIALOG_H diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 65bf03435..febcbad62 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -105,6 +105,9 @@ public slots: // Submit Payment message to a merchant, get back PaymentACK: void fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction); + // Handle an incoming URI or file + void handleURIOrFile(const QString& s); + private slots: void handleURIConnection(); void netRequestFinished(QNetworkReply*); @@ -114,7 +117,6 @@ private slots: private: static bool readPaymentRequest(const QString& filename, PaymentRequestPlus& request); bool processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient); - void handleURIOrFile(const QString& s); void fetchRequest(const QUrl& url); bool saveURIs; // true during startup