diff options
Diffstat (limited to 'src/qt')
48 files changed, 1529 insertions, 438 deletions
diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am new file mode 100644 index 0000000000..e7ae0a905f --- /dev/null +++ b/src/qt/Makefile.am @@ -0,0 +1,172 @@ +include $(top_srcdir)/src/Makefile.include + +AM_CPPFLAGS = $(INCLUDES) -I$(top_builddir)/src/obj \ + -I$(top_srcdir)/src/leveldb/include -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/leveldb/helpers -I$(top_builddir)/src/qt \ + -I$(top_builddir)/src/qt/forms $(BOOST_INCLUDES) $(PROTOBUF_CFLAGS) \ + $(QR_CFLAGS) $(BDB_CPPFLAGS) +AM_LDFLAGS = $(PTHREAD_CFLAGS) +bin_PROGRAMS = bitcoin-qt +noinst_LIBRARIES = libbitcoinqt.a +SUBDIRS = $(BUILD_TEST_QT) +DIST_SUBDIRS = test + +# bitcoin qt core # +QT_TS = locale/bitcoin_ach.ts locale/bitcoin_af_ZA.ts locale/bitcoin_ar.ts \ + locale/bitcoin_be_BY.ts locale/bitcoin_bg.ts locale/bitcoin_bs.ts \ + locale/bitcoin_ca_ES.ts locale/bitcoin_ca.ts locale/bitcoin_cs.ts \ + locale/bitcoin_cy.ts locale/bitcoin_da.ts locale/bitcoin_de.ts \ + locale/bitcoin_el_GR.ts locale/bitcoin_en.ts locale/bitcoin_eo.ts \ + locale/bitcoin_es_CL.ts locale/bitcoin_es.ts locale/bitcoin_et.ts \ + locale/bitcoin_eu_ES.ts locale/bitcoin_fa_IR.ts locale/bitcoin_fa.ts \ + locale/bitcoin_fi.ts locale/bitcoin_fr_CA.ts locale/bitcoin_fr.ts \ + locale/bitcoin_gu_IN.ts locale/bitcoin_he.ts locale/bitcoin_hi_IN.ts \ + locale/bitcoin_hr.ts locale/bitcoin_hu.ts locale/bitcoin_id_ID.ts \ + locale/bitcoin_it.ts locale/bitcoin_ja.ts locale/bitcoin_kk_KZ.ts \ + locale/bitcoin_ko_KR.ts locale/bitcoin_la.ts locale/bitcoin_lt.ts \ + locale/bitcoin_lv_LV.ts locale/bitcoin_ms_MY.ts locale/bitcoin_nb.ts \ + locale/bitcoin_nl.ts locale/bitcoin_pl.ts locale/bitcoin_pt_BR.ts \ + locale/bitcoin_pt_PT.ts locale/bitcoin_ro_RO.ts locale/bitcoin_ru.ts \ + locale/bitcoin_sk.ts locale/bitcoin_sq.ts locale/bitcoin_sr.ts \ + locale/bitcoin_sv.ts locale/bitcoin_th_TH.ts locale/bitcoin_tr.ts \ + locale/bitcoin_uk.ts locale/bitcoin_vi.ts locale/bitcoin_vi_VN.ts \ + locale/bitcoin_zh_CN.ts locale/bitcoin_zh_TW.ts + +QT_FORMS_UI = forms/aboutdialog.ui forms/addressbookpage.ui \ + forms/askpassphrasedialog.ui forms/editaddressdialog.ui forms/intro.ui \ + forms/optionsdialog.ui forms/overviewpage.ui forms/qrcodedialog.ui \ + forms/rpcconsole.ui forms/sendcoinsdialog.ui forms/sendcoinsentry.ui \ + forms/signverifymessagedialog.ui forms/transactiondescdialog.ui + +QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ + moc_addresstablemodel.cpp moc_askpassphrasedialog.cpp \ + moc_bitcoinaddressvalidator.cpp moc_bitcoinamountfield.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_optionsmodel.cpp moc_overviewpage.cpp moc_paymentserver.cpp \ + moc_qrcodedialog.cpp moc_qvalidatedlineedit.cpp moc_qvaluecombobox.cpp \ + moc_rpcconsole.cpp moc_sendcoinsdialog.cpp moc_sendcoinsentry.cpp \ + moc_signverifymessagedialog.cpp moc_splashscreen.cpp moc_trafficgraphwidget.cpp moc_transactiondesc.cpp \ + moc_transactiondescdialog.cpp moc_transactionfilterproxy.cpp \ + moc_transactiontablemodel.cpp moc_transactionview.cpp moc_walletframe.cpp \ + moc_walletmodel.cpp moc_walletstack.cpp moc_walletview.cpp + +BITCOIN_MM = macdockiconhandler.mm macnotificationhandler.mm +QR_CPP = qrcodedialog.cpp + +QT_MOC = intro.moc overviewpage.moc rpcconsole.moc + +QT_QRC_CPP = qrc_bitcoin.cpp +QT_QRC = bitcoin.qrc + +PROTOBUF_CC = paymentrequest.pb.cc +PROTOBUF_H = paymentrequest.pb.h +PROTOBUF_PROTO = paymentrequest.proto + +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 \ + optionsmodel.h overviewpage.h paymentrequestplus.h paymentserver.h \ + qrcodedialog.h qvalidatedlineedit.h qvaluecombobox.h rpcconsole.h \ + sendcoinsdialog.h sendcoinsentry.h signverifymessagedialog.h splashscreen.h \ + trafficgraphwidget.h transactiondescdialog.h transactiondesc.h transactionfilterproxy.h \ + transactionrecord.h transactiontablemodel.h transactionview.h walletframe.h \ + walletmodel.h walletmodeltransaction.h walletstack.h walletview.h + +RES_ICONS = res/icons/bitcoin.png res/icons/address-book.png \ + res/icons/quit.png res/icons/send.png res/icons/toolbar.png \ + res/icons/connect0_16.png res/icons/connect1_16.png \ + res/icons/connect2_16.png res/icons/connect3_16.png \ + res/icons/connect4_16.png res/icons/transaction0.png \ + res/icons/transaction2.png res/icons/clock1.png res/icons/clock2.png \ + res/icons/clock3.png res/icons/clock4.png res/icons/clock5.png \ + res/icons/configure.png res/icons/receive.png res/icons/editpaste.png \ + res/icons/editcopy.png res/icons/add.png res/icons/bitcoin_testnet.png \ + res/icons/toolbar_testnet.png res/icons/edit.png res/icons/history.png \ + res/icons/overview.png res/icons/export.png res/icons/synced.png \ + res/icons/remove.png res/icons/tx_mined.png res/icons/tx_input.png \ + res/icons/tx_output.png res/icons/tx_inout.png res/icons/lock_closed.png \ + res/icons/lock_open.png res/icons/key.png res/icons/filesave.png \ + res/icons/qrcode.png res/icons/debugwindow.png res/icons/bitcoin.ico \ + res/icons/bitcoin_testnet.ico + +BITCOIN_QT_CPP = aboutdialog.cpp addressbookpage.cpp \ + addresstablemodel.cpp askpassphrasedialog.cpp bitcoinaddressvalidator.cpp \ + bitcoinamountfield.cpp bitcoin.cpp bitcoingui.cpp \ + bitcoinunits.cpp clientmodel.cpp csvmodelwriter.cpp editaddressdialog.cpp \ + guiutil.cpp intro.cpp monitoreddatamapper.cpp notificator.cpp \ + optionsdialog.cpp optionsmodel.cpp overviewpage.cpp paymentrequestplus.cpp \ + paymentserver.cpp qvalidatedlineedit.cpp qvaluecombobox.cpp \ + rpcconsole.cpp sendcoinsdialog.cpp sendcoinsentry.cpp \ + signverifymessagedialog.cpp splashscreen.cpp trafficgraphwidget.cpp transactiondesc.cpp \ + transactiondescdialog.cpp transactionfilterproxy.cpp transactionrecord.cpp \ + transactiontablemodel.cpp transactionview.cpp walletframe.cpp \ + walletmodel.cpp walletmodeltransaction.cpp walletstack.cpp walletview.cpp + +RES_IMAGES = res/images/about.png res/images/splash.png \ + res/images/splash_testnet.png + +RES_MOVIES = res/movies/update_spinner.mng + +BITCOIN_RC = res/bitcoin-qt-res.rc + +libbitcoinqt_a_CPPFLAGS = $(AM_CPPFLAGS) $(QT_INCLUDES) \ + -I$(top_srcdir)/src/qt/forms $(QT_DBUS_INCLUDES) +libbitcoinqt_a_SOURCES = $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(QT_FORMS_UI) \ + $(QT_QRC) $(QT_TS) $(PROTOBUF_PROTO) $(RES_ICONS) $(RES_IMAGES) $(RES_MOVIES) + +nodist_libbitcoinqt_a_SOURCES = $(QT_MOC_CPP) $(QT_MOC) $(PROTOBUF_CC) \ + $(PROTOBUF_H) $(QT_QRC_CPP) + +BUILT_SOURCES = $(nodist_libbitcoinqt_a_SOURCES) + +#Generating these with a half-written protobuf header leads to wacky results. +#This makes sure it's done. +$(QT_MOC): $(PROTOBUF_H) +$(QT_MOC_CPP): $(PROTOBUF_H) + +if TARGET_DARWIN + libbitcoinqt_a_SOURCES += $(BITCOIN_MM) +endif +if TARGET_WINDOWS + libbitcoinqt_a_SOURCES += $(BITCOIN_RC) +endif +if USE_QRCODE + libbitcoinqt_a_SOURCES += $(QR_CPP) +endif +# + +# bitcoin-qt binary # +bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(QT_INCLUDES) \ + -I$(top_srcdir)/src/qt/forms +bitcoin_qt_SOURCES = bitcoin.cpp +bitcoin_qt_LDADD = libbitcoinqt.a $(LIBBITCOIN) $(LIBLEVELDB) $(LIBMEMENV) \ + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) + +# forms/foo.h -> forms/ui_foo.h +QT_FORMS_H=$(join $(dir $(QT_FORMS_UI)),$(addprefix ui_, $(notdir $(QT_FORMS_UI:.ui=.h)))) + +#locale/foo.ts -> locale/foo.qm +QT_QM=$(QT_TS:.ts=.qm) + +.PHONY: FORCE +.SECONDARY: $(QT_QM) + +bitcoinstrings.cpp: FORCE + $(MAKE) -C $(top_srcdir)/src qt/bitcoinstrings.cpp + +translate: bitcoinstrings.cpp $(QT_FORMS_UI) $(QT_FORMS_UI) $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(BITCOIN_MM) $(QR_CPP) + @test -n $(LUPDATE) || echo "lupdate is required for updating translations" + @$(LUPDATE) $^ -locations relative -no-obsolete -ts locale/bitcoin_en.ts + +$(QT_QRC_CPP): $(QT_QRC) $(QT_QM) $(QT_FORMS_H) $(RES_ICONS) $(RES_IMAGES) $(RES_MOVIES) $(PROTOBUF_H) + @cd $(abs_srcdir); test -f $(RCC) && $(RCC) -name bitcoin -o $(abs_builddir)/$@ $< || \ + echo error: could not build $@ + $(SED) -i.bak -e '/^\*\*.*Created:/d' $@ && rm $@.bak + $(SED) -i.bak -e '/^\*\*.*by:/d' $@ && rm $@.bak + +CLEANFILES = $(BUILT_SOURCES) $(QT_QM) $(QT_FORMS_H) *.gcda *.gcno diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 8906174d7d..5b8d44481e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -1,3 +1,7 @@ +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" +#endif + #include "addressbookpage.h" #include "ui_addressbookpage.h" diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index dcc70222cc..921c4443a9 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -7,6 +7,7 @@ #include "base58.h" #include <QFont> +#include <QDebug> const QString AddressTableModel::Send = "S"; const QString AddressTableModel::Receive = "R"; @@ -15,7 +16,8 @@ struct AddressTableEntry { enum Type { Sending, - Receiving + Receiving, + Hidden /* QSortFilterProxyModel will filter these out */ }; Type type; @@ -43,6 +45,20 @@ struct AddressTableEntryLessThan } }; +/* Determine address type from address purpose */ +static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine) +{ + AddressTableEntry::Type addressType = AddressTableEntry::Hidden; + // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all. + if (strPurpose == "send") + addressType = AddressTableEntry::Sending; + else if (strPurpose == "receive") + addressType = AddressTableEntry::Receiving; + else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess + addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending); + return addressType; +} + // Private implementation class AddressTablePriv { @@ -62,17 +78,9 @@ public: BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook) { const CBitcoinAddress& address = item.first; - - AddressTableEntry::Type addressType; - const std::string& strPurpose = item.second.purpose; - if (strPurpose == "send") addressType = AddressTableEntry::Sending; - else if (strPurpose == "receive") addressType = AddressTableEntry::Receiving; - else if (strPurpose == "unknown") { - bool fMine = IsMine(*wallet, address.Get()); - addressType = (fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending); - } - else continue; // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all. - + bool fMine = IsMine(*wallet, address.Get()); + AddressTableEntry::Type addressType = translateTransactionType( + QString::fromStdString(item.second.purpose), fMine); const std::string& strName = item.second.name; cachedAddressTable.append(AddressTableEntry(addressType, QString::fromStdString(strName), @@ -80,10 +88,12 @@ public: } } // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order + // Even though the map is already sorted this re-sorting step is needed because the originating map + // is sorted by binary address, not by base58() address. qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); } - void updateEntry(const QString &address, const QString &label, bool isMine, int status) + void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status) { // Find address / label in model QList<AddressTableEntry>::iterator lower = qLowerBound( @@ -93,14 +103,14 @@ public: int lowerIndex = (lower - cachedAddressTable.begin()); int upperIndex = (upper - cachedAddressTable.begin()); bool inModel = (lower != upper); - AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; + AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine); switch(status) { case CT_NEW: if(inModel) { - OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); + qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_NOW, but entry is already in model"; break; } parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); @@ -110,7 +120,7 @@ public: case CT_UPDATED: if(!inModel) { - OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); + qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_UPDATED, but entry is not in model"; break; } lower->type = newEntryType; @@ -120,7 +130,7 @@ public: case CT_DELETED: if(!inModel) { - OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); + qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_DELETED, but entry is not in model"; break; } parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); @@ -322,10 +332,11 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &par } } -void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status) +void AddressTableModel::updateEntry(const QString &address, + const QString &label, bool isMine, const QString &purpose, int status) { // Update address book model from Bitcoin core - priv->updateEntry(address, label, isMine, status); + priv->updateEntry(address, label, isMine, purpose, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) @@ -355,18 +366,21 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con else if(type == Receive) { // Generate a new address to associate with given label - WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - if(!ctx.isValid()) - { - // Unlock wallet failed or was cancelled - editStatus = WALLET_UNLOCK_FAILURE; - return QString(); - } CPubKey newKey; - if(!wallet->GetKeyFromPool(newKey, true)) + if(!wallet->GetKeyFromPool(newKey)) { - editStatus = KEY_GENERATION_FAILURE; - return QString(); + WalletModel::UnlockContext ctx(walletModel->requestUnlock()); + if(!ctx.isValid()) + { + // Unlock wallet failed or was cancelled + editStatus = WALLET_UNLOCK_FAILURE; + return QString(); + } + if(!wallet->GetKeyFromPool(newKey)) + { + editStatus = KEY_GENERATION_FAILURE; + return QString(); + } } strAddress = CBitcoinAddress(newKey.GetID()).ToString(); } diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 48baff5e54..6f532087fe 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -85,7 +85,7 @@ signals: public slots: /* Update address list from core. */ - void updateEntry(const QString &address, const QString &label, bool isMine, int status); + void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status); friend class AddressTablePriv; }; diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index f165c11cb1..2b7671f209 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -16,6 +16,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget *parent) : fCapsLock(false) { ui->setupUi(this); + ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE); ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE); ui->passEdit3->setMaxLength(MAX_PASSPHRASE_SIZE); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index e7cf440044..78693971da 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -62,7 +62,7 @@ static bool ThreadSafeMessageBox(const std::string& message, const std::string& } else { - printf("%s: %s\n", caption.c_str(), message.c_str()); + LogPrintf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); return false; } @@ -91,7 +91,7 @@ static void InitMessage(const std::string &message) splashref->showMessage(QString::fromStdString(message), Qt::AlignBottom|Qt::AlignHCenter, QColor(55,55,55)); qApp->processEvents(); } - printf("init message: %s\n", message.c_str()); + LogPrintf("init message: %s\n", message.c_str()); } /* @@ -155,12 +155,15 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans #if QT_VERSION < 0x050000 void DebugMessageHandler(QtMsgType type, const char * msg) { - OutputDebugStringF("%s\n", msg); + Q_UNUSED(type); + LogPrint("qt", "Bitcoin-Qt: %s\n", msg); } #else void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg) { - OutputDebugStringF("%s\n", qPrintable(msg)); + Q_UNUSED(type); + Q_UNUSED(context); + LogPrint("qt", "Bitcoin-Qt: %s\n", qPrintable(msg)); } #endif @@ -232,10 +235,16 @@ int main(int argc, char *argv[]) PaymentServer* paymentServer = new PaymentServer(&app); // User language is set up: pick a data directory - Intro::pickDataDirectory(); + Intro::pickDataDirectory(TestNet()); // Install global event filter that makes sure that long tooltips can be word-wrapped app.installEventFilter(new GUIUtil::ToolTipToRichTextFilter(TOOLTIP_WRAP_THRESHOLD, &app)); + // Install qDebug() message handler to route to debug.log +#if QT_VERSION < 0x050000 + qInstallMsgHandler(DebugMessageHandler); +#else + qInstallMessageHandler(DebugMessageHandler); +#endif // ... now GUI settings: OptionsModel optionsModel; @@ -255,13 +264,6 @@ int main(int argc, char *argv[]) return 1; } - // Install qDebug() message handler to route to debug.log: -#if QT_VERSION < 0x050000 - qInstallMsgHandler(DebugMessageHandler); -#else - qInstallMessageHandler(DebugMessageHandler); -#endif - SplashScreen splash(QPixmap(), 0); if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) { @@ -300,7 +302,8 @@ int main(int argc, char *argv[]) optionsModel.Upgrade(); // Must be done after AppInit2 PaymentServer::LoadRootCAs(); - paymentServer->initNetManager(optionsModel); + paymentServer->setOptionsModel(&optionsModel); + paymentServer->initNetManager(); if (splashref) splash.finish(&window); diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h index 2b6a59367d..b7f4dfee96 100644 --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -3,8 +3,8 @@ #include <QValidator> -/** Base48 entry widget validator. - Corrects near-miss characters and refuses characters that are no part of base48. +/** Base58 entry widget validator. + Corrects near-miss characters and refuses characters that are not part of base58. */ class BitcoinAddressValidator : public QValidator { diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index d9d4e3b23d..37b8743eff 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -130,9 +130,10 @@ void BitcoinAmountField::setValue(qint64 value) setText(BitcoinUnits::format(currentUnit, value)); } -void BitcoinAmountField::setReadOnly(bool fReadeOnly) +void BitcoinAmountField::setReadOnly(bool fReadOnly) { - // TODO ... + amount->setReadOnly(fReadOnly); + unit->setEnabled(!fReadOnly); } void BitcoinAmountField::unitChanged(int idx) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index ad32c9ea68..23a221120f 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -157,6 +157,8 @@ BitcoinGUI::BitcoinGUI(bool fIsTestnet, QWidget *parent) : rpcConsole = new RPCConsole(this); connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show())); + // prevents an oben debug window from becoming stuck/unusable on client shutdown + connect(quitAction, SIGNAL(triggered()), rpcConsole, SLOT(hide())); // Install event filter to be able to catch status tip events (QEvent::StatusTip) this->installEventFilter(this); @@ -233,7 +235,11 @@ void BitcoinGUI::createActions(bool fIsTestnet) aboutAction = new QAction(QIcon(":/icons/bitcoin_testnet"), tr("&About Bitcoin"), this); aboutAction->setStatusTip(tr("Show information about Bitcoin")); aboutAction->setMenuRole(QAction::AboutRole); +#if QT_VERSION < 0x050000 aboutQtAction = new QAction(QIcon(":/trolltech/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this); +#else + aboutQtAction = new QAction(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png"), tr("About &Qt"), this); +#endif aboutQtAction->setStatusTip(tr("Show information about Qt")); aboutQtAction->setMenuRole(QAction::AboutQtRole); optionsAction = new QAction(QIcon(":/icons/options"), tr("&Options..."), this); @@ -590,21 +596,28 @@ void BitcoinGUI::message(const QString &title, const QString &message, unsigned int nMBoxIcon = QMessageBox::Information; int nNotifyIcon = Notificator::Information; - // Override title based on style QString msgType; - switch (style) { - case CClientUIInterface::MSG_ERROR: - msgType = tr("Error"); - break; - case CClientUIInterface::MSG_WARNING: - msgType = tr("Warning"); - break; - case CClientUIInterface::MSG_INFORMATION: - msgType = tr("Information"); - break; - default: - msgType = title; // Use supplied title + + // Prefer supplied title over style based title + if (!title.isEmpty()) { + msgType = title; } + else { + switch (style) { + case CClientUIInterface::MSG_ERROR: + msgType = tr("Error"); + break; + case CClientUIInterface::MSG_WARNING: + msgType = tr("Warning"); + break; + case CClientUIInterface::MSG_INFORMATION: + msgType = tr("Information"); + break; + default: + break; + } + } + // Append title to "Bitcoin - " if (!msgType.isEmpty()) strTitle += " - " + msgType; @@ -625,7 +638,7 @@ void BitcoinGUI::message(const QString &title, const QString &message, unsigned if (!(buttons = (QMessageBox::StandardButton)(style & CClientUIInterface::BTN_MASK))) buttons = QMessageBox::Ok; - QMessageBox mBox((QMessageBox::Icon)nMBoxIcon, strTitle, message, buttons); + QMessageBox mBox((QMessageBox::Icon)nMBoxIcon, strTitle, message, buttons, this); int r = mBox.exec(); if (ret != NULL) *ret = r == QMessageBox::Ok; @@ -670,9 +683,12 @@ void BitcoinGUI::closeEvent(QCloseEvent *event) void BitcoinGUI::askFee(qint64 nFeeRequired, bool *payFee) { + if (!clientModel || !clientModel->getOptionsModel()) + return; + QString strMessage = tr("This transaction is over the size limit. You can still send it for a fee of %1, " "which goes to the nodes that process your transaction and helps to support the network. " - "Do you want to pay the fee?").arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nFeeRequired)); + "Do you want to pay the fee?").arg(BitcoinUnits::formatWithUnit(clientModel->getOptionsModel()->getDisplayUnit(), nFeeRequired)); QMessageBox::StandardButton retval = QMessageBox::question( this, tr("Confirm transaction fee"), strMessage, QMessageBox::Yes|QMessageBox::Cancel, QMessageBox::Yes); @@ -741,13 +757,9 @@ void BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient) walletFrame->handlePaymentRequest(recipient); } -void BitcoinGUI::showPaymentACK(QString msg) +void BitcoinGUI::showPaymentACK(const QString& msg) { -#if QT_VERSION < 0x050000 - message(tr("Payment acknowledged"), Qt::escape(msg), CClientUIInterface::MODAL); -#else - message(tr("Payment acknowledged"), msg.toHtmlEscaped(), CClientUIInterface::MODAL); -#endif + message(tr("Payment acknowledged"), GUIUtil::HtmlEscape(msg), CClientUIInterface::MODAL); } void BitcoinGUI::setEncryptionStatus(int status) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index fc25e867fc..e2dd5dc6bc 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -154,7 +154,7 @@ public slots: void askFee(qint64 nFeeRequired, bool *payFee); void handlePaymentRequest(const SendCoinsRecipient& recipient); - void showPaymentACK(QString msg); + void showPaymentACK(const QString& msg); /** Show incoming transaction notification for new transactions. */ void incomingTransaction(const QString& date, int unit, qint64 amount, const QString& type, const QString& address); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 5cf4dd8111..212fa6974a 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -13,6 +13,7 @@ #include <QDateTime> #include <QTimer> +#include <QDebug> static const int64 nClientStartupTime = GetTime(); @@ -23,9 +24,8 @@ ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : numBlocksAtStartup(-1), pollTimer(0) { pollTimer = new QTimer(this); - pollTimer->setInterval(MODEL_UPDATE_DELAY); - pollTimer->start(); connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer())); + pollTimer->start(MODEL_UPDATE_DELAY); subscribeToCoreSignals(); } @@ -42,7 +42,7 @@ int ClientModel::getNumConnections() const int ClientModel::getNumBlocks() const { - return nBestHeight; + return chainActive.Height(); } int ClientModel::getNumBlocksAtStartup() @@ -51,19 +51,27 @@ int ClientModel::getNumBlocksAtStartup() return numBlocksAtStartup; } +quint64 ClientModel::getTotalBytesRecv() const +{ + return CNode::GetTotalBytesRecv(); +} + +quint64 ClientModel::getTotalBytesSent() const +{ + return CNode::GetTotalBytesSent(); +} + QDateTime ClientModel::getLastBlockDate() const { - if (pindexBest) - return QDateTime::fromTime_t(pindexBest->GetBlockTime()); - else if(!isTestNet()) - return QDateTime::fromTime_t(1231006505); // Genesis block's time + if (chainActive.Tip()) + return QDateTime::fromTime_t(chainActive.Tip()->GetBlockTime()); else - return QDateTime::fromTime_t(1296688602); // Genesis block's time (testnet) + return QDateTime::fromTime_t(Params().GenesisBlock().nTime); // Genesis block's time of current network } double ClientModel::getVerificationProgress() const { - return Checkpoints::GuessVerificationProgress(pindexBest); + return Checkpoints::GuessVerificationProgress(chainActive.Tip()); } void ClientModel::updateTimer() @@ -85,6 +93,8 @@ void ClientModel::updateTimer() // ensure we return the maximum of newNumBlocksOfPeers and newNumBlocks to not create weird displays in the GUI emit numBlocksChanged(newNumBlocks, std::max(newNumBlocksOfPeers, newNumBlocks)); } + + emit bytesChanged(getTotalBytesRecv(), getTotalBytesSent()); } void ClientModel::updateNumConnections(int numConnections) @@ -180,14 +190,14 @@ static void NotifyBlocksChanged(ClientModel *clientmodel) static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) { - // Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections); + // Too noisy: qDebug() << "NotifyNumConnectionsChanged : " + QString::number(newNumConnections); QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, Q_ARG(int, newNumConnections)); } static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status) { - OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status); + qDebug() << "NotifyAlertChanged : " + QString::fromStdString(hash.GetHex()) + " status=" + QString::number(status); QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(hash.GetHex())), Q_ARG(int, status)); diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index f9d491aa50..925f20acd9 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -35,6 +35,9 @@ public: int getNumBlocks() const; int getNumBlocksAtStartup(); + quint64 getTotalBytesRecv() const; + quint64 getTotalBytesSent() const; + double getVerificationProgress() const; QDateTime getLastBlockDate() const; @@ -60,8 +63,8 @@ private: int cachedNumBlocks; int cachedNumBlocksOfPeers; - bool cachedReindexing; - bool cachedImporting; + bool cachedReindexing; + bool cachedImporting; int numBlocksAtStartup; @@ -74,6 +77,7 @@ signals: void numConnectionsChanged(int count); void numBlocksChanged(int count, int countOfPeers); void alertsChanged(const QString &warnings); + void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut); //! Asynchronous message notification void message(const QString &title, const QString &message, unsigned int style); diff --git a/src/qt/csvmodelwriter.cpp b/src/qt/csvmodelwriter.cpp index 8a50bbab3f..ad8e0d618a 100644 --- a/src/qt/csvmodelwriter.cpp +++ b/src/qt/csvmodelwriter.cpp @@ -85,4 +85,3 @@ bool CSVModelWriter::write() return file.error() == QFile::NoError; } - diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index bb53021cfd..1e4335c645 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -461,7 +461,7 @@ <customwidgets> <customwidget> <class>BitcoinAmountField</class> - <extends>QSpinBox</extends> + <extends>QLineEdit</extends> <header>bitcoinamountfield.h</header> </customwidget> <customwidget> diff --git a/src/qt/forms/qrcodedialog.ui b/src/qt/forms/qrcodedialog.ui index 52e9db3762..1cec9066f8 100644 --- a/src/qt/forms/qrcodedialog.ui +++ b/src/qt/forms/qrcodedialog.ui @@ -186,7 +186,7 @@ <customwidgets> <customwidget> <class>BitcoinAmountField</class> - <extends>QSpinBox</extends> + <extends>QLineEdit</extends> <header>bitcoinamountfield.h</header> </customwidget> </customwidgets> diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui index d1d8ab42a0..54c41ffb67 100644 --- a/src/qt/forms/rpcconsole.ui +++ b/src/qt/forms/rpcconsole.ui @@ -445,10 +445,271 @@ </item> </layout> </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>&Network Traffic</string> + </attribute> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="TrafficGraphWidget" name="trafficGraph" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QSlider" name="sldGraphRange"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>288</number> + </property> + <property name="pageStep"> + <number>12</number> + </property> + <property name="value"> + <number>6</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblGraphRange"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnClearTrafficGraph"> + <property name="text"> + <string>&Clear</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Totals</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="Line" name="line"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>10</width> + <height>0</height> + </size> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>In:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblBytesIn"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="Line" name="line_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>10</width> + <height>0</height> + </size> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Out:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblBytesOut"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>407</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> </widget> </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>TrafficGraphWidget</class> + <extends>QWidget</extends> + <header>trafficgraphwidget.h</header> + <container>1</container> + <slots> + <slot>clear()</slot> + </slots> + </customwidget> + </customwidgets> <resources> <include location="../bitcoin.qrc"/> </resources> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 6e17565ab0..2a00fc5455 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -28,7 +28,7 @@ <height>165</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1"> <property name="margin"> <number>0</number> </property> diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index a2ef9a0a38..5c6afd6c71 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -10,19 +10,13 @@ <height>150</height> </rect> </property> - <property name="windowTitle"> - <string>StackedWidget</string> - </property> <property name="autoFillBackground"> <bool>false</bool> </property> <property name="currentIndex"> - <number>1</number> + <number>0</number> </property> <widget class="QFrame" name="SendCoinsInsecure"> - <property name="windowTitle"> - <string>Form</string> - </property> <property name="frameShape"> <enum>QFrame::StyledPanel</enum> </property> @@ -34,7 +28,7 @@ <number>12</number> </property> <item row="5" column="0"> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="amountLabel"> <property name="text"> <string>A&mount:</string> </property> @@ -47,7 +41,7 @@ </widget> </item> <item row="3" column="0"> - <widget class="QLabel" name="label_2"> + <widget class="QLabel" name="payToLabel"> <property name="text"> <string>Pay &To:</string> </property> @@ -63,7 +57,7 @@ <widget class="BitcoinAmountField" name="payAmount"/> </item> <item row="4" column="0"> - <widget class="QLabel" name="label_4"> + <widget class="QLabel" name="labellLabel"> <property name="text"> <string>&Label:</string> </property> @@ -592,9 +586,6 @@ </disabled> </palette> </property> - <property name="windowTitle"> - <string>SecureSend</string> - </property> <property name="autoFillBackground"> <bool>true</bool> </property> @@ -609,7 +600,7 @@ <number>12</number> </property> <item row="4" column="0"> - <widget class="QLabel" name="label_s4"> + <widget class="QLabel" name="memoLabel_s"> <property name="text"> <string>Memo:</string> </property> @@ -622,7 +613,7 @@ </widget> </item> <item row="5" column="0"> - <widget class="QLabel" name="label_s1"> + <widget class="QLabel" name="amountLabel_s"> <property name="text"> <string>A&mount:</string> </property> @@ -630,12 +621,12 @@ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> <property name="buddy"> - <cstring>payAmount</cstring> + <cstring>payAmount_s</cstring> </property> </widget> </item> <item row="3" column="0"> - <widget class="QLabel" name="label_s2"> + <widget class="QLabel" name="payToLabel_s"> <property name="text"> <string>Pay &To:</string> </property> @@ -649,15 +640,9 @@ </item> <item row="5" column="2"> <widget class="BitcoinAmountField" name="payAmount_s"> - <property name="enabled"> - <bool>false</bool> - </property> <property name="acceptDrops"> <bool>false</bool> </property> - <property name="readOnly"> - <bool>true</bool> - </property> </widget> </item> <item row="3" column="2"> @@ -667,14 +652,17 @@ </property> <item> <widget class="QLabel" name="payTo_s"> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> </widget> </item> </layout> </item> <item row="4" column="2"> - <widget class="QLabel" name="memo_s"> - <property name="text"> - <string>message from merchant</string> + <widget class="QLabel" name="memoTextLabel_s"> + <property name="textFormat"> + <enum>Qt::PlainText</enum> </property> </widget> </item> diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index 99db141c94..4a02ff89e7 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -142,7 +142,7 @@ QString Intro::getDefaultDataDirectory() return QString::fromStdString(GetDefaultDataDir().string()); } -void Intro::pickDataDirectory() +void Intro::pickDataDirectory(bool fIsTestnet) { namespace fs = boost::filesystem;; QSettings settings; @@ -160,6 +160,11 @@ void Intro::pickDataDirectory() /* If current default data directory does not exist, let the user choose one */ Intro intro; intro.setDataDirectory(dataDir); + if (!fIsTestnet) + intro.setWindowIcon(QIcon(":icons/bitcoin")); + else + intro.setWindowIcon(QIcon(":icons/bitcoin_testnet")); + while(true) { if(!intro.exec()) diff --git a/src/qt/intro.h b/src/qt/intro.h index 788799b7b0..8b09847abd 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -31,7 +31,7 @@ public: * @note do NOT call global GetDataDir() before calling this function, this * will cause the wrong path to be cached. */ - static void pickDataDirectory(); + static void pickDataDirectory(bool fIsTestnet); /** * Determine default data directory for operating system. diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 7d419b6bd9..4d1a8574d0 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -67,7 +67,7 @@ This product includes software developed by the OpenSSL Project for use in the O <translation>&New Address</translation> </message> <message> - <location filename="../addressbookpage.cpp" line="+63"/> + <location filename="../addressbookpage.cpp" line="+67"/> <source>These are your Bitcoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you.</source> <translation>These are your Bitcoin addresses for receiving payments. You may want to give a different one to each sender so you can keep track of who is paying you.</translation> </message> @@ -165,7 +165,7 @@ This product includes software developed by the OpenSSL Project for use in the O <context> <name>AddressTableModel</name> <message> - <location filename="../addresstablemodel.cpp" line="+144"/> + <location filename="../addresstablemodel.cpp" line="+164"/> <source>Label</source> <translation>Label</translation> </message> @@ -203,7 +203,7 @@ This product includes software developed by the OpenSSL Project for use in the O <translation>Repeat new passphrase</translation> </message> <message> - <location filename="../askpassphrasedialog.cpp" line="+33"/> + <location filename="../askpassphrasedialog.cpp" line="+34"/> <source>Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>.</source> <translation>Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>.</translation> </message> @@ -324,7 +324,7 @@ This product includes software developed by the OpenSSL Project for use in the O <context> <name>BitcoinGUI</name> <message> - <location filename="../bitcoingui.cpp" line="+254"/> + <location filename="../bitcoingui.cpp" line="+255"/> <source>Sign &message...</source> <translation>Sign &message...</translation> </message> @@ -623,12 +623,12 @@ This product includes software developed by the OpenSSL Project for use in the O <translation>Information</translation> </message> <message> - <location line="+70"/> + <location line="+73"/> <source>This transaction is over the size limit. You can still send it for a fee of %1, which goes to the nodes that process your transaction and helps to support the network. Do you want to pay the fee?</source> <translation>This transaction is over the size limit. You can still send it for a fee of %1, which goes to the nodes that process your transaction and helps to support the network. Do you want to pay the fee?</translation> </message> <message> - <location line="-140"/> + <location line="-143"/> <source>Up to date</source> <translation>Up to date</translation> </message> @@ -638,7 +638,7 @@ This product includes software developed by the OpenSSL Project for use in the O <translation>Catching up...</translation> </message> <message> - <location line="+113"/> + <location line="+116"/> <source>Confirm transaction fee</source> <translation>Confirm transaction fee</translation> </message> @@ -666,18 +666,22 @@ Address: %4 </translation> </message> <message> - <location line="+33"/> - <location line="+23"/> + <location line="+34"/> <source>URI handling</source> <translation>URI handling</translation> </message> <message> - <location line="-23"/> - <location line="+23"/> + <location line="+0"/> <source>URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters.</source> <translation>URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters.</translation> </message> <message> + <location line="+27"/> + <location line="+2"/> + <source>Payment acknowledged</source> + <translation type="unfinished"></translation> + </message> + <message> <location line="+17"/> <source>Wallet is <b>encrypted</b> and currently <b>unlocked</b></source> <translation>Wallet is <b>encrypted</b> and currently <b>unlocked</b></translation> @@ -696,7 +700,7 @@ Address: %4 <context> <name>ClientModel</name> <message> - <location filename="../clientmodel.cpp" line="+105"/> + <location filename="../clientmodel.cpp" line="+106"/> <source>Network Alert</source> <translation>Network Alert</translation> </message> @@ -800,7 +804,7 @@ Address: %4 <context> <name>GUIUtil::HelpMessageBox</name> <message> - <location filename="../guiutil.cpp" line="+517"/> + <location filename="../guiutil.cpp" line="+525"/> <location line="+13"/> <source>Bitcoin-Qt</source> <translation>Bitcoin-Qt</translation> @@ -879,7 +883,7 @@ Address: %4 <translation>Use a custom data directory:</translation> </message> <message> - <location filename="../intro.cpp" line="+100"/> + <location filename="../intro.cpp" line="+105"/> <source>Error</source> <translation>Error</translation> </message> @@ -1067,7 +1071,7 @@ Address: %4 <translation>&Apply</translation> </message> <message> - <location filename="../optionsdialog.cpp" line="+54"/> + <location filename="../optionsdialog.cpp" line="+58"/> <source>default</source> <translation>default</translation> </message> @@ -1177,29 +1181,68 @@ Address: %4 <context> <name>PaymentServer</name> <message> - <location filename="../paymentserver.cpp" line="+108"/> - <source>Cannot start bitcoin: click-to-pay handler</source> - <translation>Cannot start bitcoin: click-to-pay handler</translation> + <location filename="../paymentserver.cpp" line="+450"/> + <location line="+41"/> + <source>Payment request error</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+1"/> + <source>Insecure requests to custom payment scripts unsupported</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+38"/> + <source>Refund from</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+46"/> + <location line="+28"/> + <location line="+17"/> + <source>Network request error</source> + <translation type="unfinished"></translation> </message> </context> <context> <name>QObject</name> <message> - <location filename="../bitcoin.cpp" line="+92"/> + <location filename="../bitcoin.cpp" line="+111"/> + <location line="+5"/> <location filename="../intro.cpp" line="-32"/> <source>Bitcoin</source> <translation>Bitcoin</translation> </message> <message> - <location line="+1"/> + <location line="-4"/> <source>Error: Specified data directory "%1" does not exist.</source> <translation>Error: Specified data directory "%1" does not exist.</translation> </message> <message> + <location line="+4"/> + <source>Error: Invalid combination of -regtest and -testnet.</source> + <translation type="unfinished"></translation> + </message> + <message> <location filename="../intro.cpp" line="+1"/> <source>Error: Specified data directory "%1" can not be created.</source> <translation>Error: Specified data directory "%1" can not be created.</translation> </message> + <message> + <location filename="../paymentserver.cpp" line="-175"/> + <source>Requested payment amount (%1) too small</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+126"/> + <source>Error communicating with %1: %2</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+29"/> + <source>Bad response from server %1</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>QRCodeDialog</name> @@ -1410,13 +1453,15 @@ Address: %4 <name>SendCoinsDialog</name> <message> <location filename="../forms/sendcoinsdialog.ui" line="+14"/> - <location filename="../sendcoinsdialog.cpp" line="+128"/> + <location filename="../sendcoinsdialog.cpp" line="+138"/> <location line="+5"/> <location line="+5"/> <location line="+5"/> <location line="+6"/> <location line="+5"/> - <location line="+5"/> + <location line="+50"/> + <location line="+145"/> + <location line="+9"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -1461,25 +1506,19 @@ Address: %4 <translation>S&end</translation> </message> <message> - <location filename="../sendcoinsdialog.cpp" line="-62"/> - <location line="+2"/> - <source><b>%1</b> to %2 (%3)</source> - <translation><b>%1</b> to %2 (%3)</translation> - </message> - <message> - <location line="+6"/> + <location filename="../sendcoinsdialog.cpp" line="-170"/> <source>Confirm send coins</source> <translation>Confirm send coins</translation> </message> <message> - <location line="+1"/> - <source>Are you sure you want to send %1?</source> - <translation>Are you sure you want to send %1?</translation> + <location line="-97"/> + <source>to</source> + <translation type="unfinished">to</translation> </message> <message> - <location line="+0"/> - <source> and </source> - <translation> and </translation> + <location line="+15"/> + <source><b>%1</b> to %2</source> + <translation type="unfinished"></translation> </message> <message> <location line="+23"/> @@ -1512,36 +1551,63 @@ Address: %4 <translation>Error: Transaction creation failed!</translation> </message> <message> - <location line="+5"/> + <location line="+15"/> + <source>Are you sure you want to send?</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+9"/> + <source>added as transaction fee</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+6"/> + <source>Total Amount %1</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+20"/> <source>Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.</source> <translation>Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.</translation> </message> + <message> + <location line="+145"/> + <source>Payment request expired</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+9"/> + <source>Invalid payment address %1</source> + <translation type="unfinished"></translation> + </message> </context> <context> <name>SendCoinsEntry</name> <message> - <location filename="../forms/sendcoinsentry.ui" line="+14"/> + <location filename="../forms/sendcoinsentry.ui" line="+24"/> <source>Form</source> <translation>Form</translation> </message> <message> <location line="+15"/> + <location line="+588"/> <source>A&mount:</source> <translation>A&mount:</translation> </message> <message> - <location line="+13"/> + <location line="-575"/> + <location line="+588"/> <source>Pay &To:</source> <translation>Pay &To:</translation> </message> <message> - <location line="+34"/> + <location line="-554"/> <source>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> <translation>The address to send the payment to (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> </message> <message> <location line="+60"/> - <location filename="../sendcoinsentry.cpp" line="+26"/> + <location filename="../sendcoinsentry.cpp" line="+28"/> <source>Enter a label for this address to add it to your address book</source> <translation>Enter a label for this address to add it to your address book</translation> </message> @@ -1551,7 +1617,12 @@ Address: %4 <translation>&Label:</translation> </message> <message> - <location line="+28"/> + <location line="-54"/> + <source>StackedWidget</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+82"/> <source>Choose address from address book</source> <translation>Choose address from address book</translation> </message> @@ -1576,6 +1647,21 @@ Address: %4 <translation>Remove this recipient</translation> </message> <message> + <location line="+466"/> + <source>SecureSend</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+18"/> + <source>Memo:</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+63"/> + <source>message from merchant</source> + <translation type="unfinished"></translation> + </message> + <message> <location filename="../sendcoinsentry.cpp" line="+1"/> <source>Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</source> <translation>Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)</translation> @@ -1708,7 +1794,7 @@ Address: %4 <translation>Enter Bitcoin signature</translation> </message> <message> - <location line="+82"/> + <location line="+85"/> <location line="+81"/> <source>The entered address is invalid.</source> <translation>The entered address is invalid.</translation> @@ -1777,7 +1863,7 @@ Address: %4 <context> <name>SplashScreen</name> <message> - <location filename="../splashscreen.cpp" line="+22"/> + <location filename="../splashscreen.cpp" line="+23"/> <source>The Bitcoin developers</source> <translation>The Bitcoin developers</translation> </message> @@ -1790,7 +1876,7 @@ Address: %4 <context> <name>TransactionDesc</name> <message> - <location filename="../transactiondesc.cpp" line="+20"/> + <location filename="../transactiondesc.cpp" line="+22"/> <source>Open until %1</source> <translation>Open until %1</translation> </message> @@ -1866,12 +1952,12 @@ Address: %4 <location line="+12"/> <location line="+45"/> <location line="+17"/> - <location line="+30"/> + <location line="+45"/> <source>Credit</source> <translation>Credit</translation> </message> <message numerus="yes"> - <location line="-102"/> + <location line="-117"/> <source>matures in %n more block(s)</source> <translation> <numerusform>matures in %n more block</numerusform> @@ -1887,12 +1973,12 @@ Address: %4 <location line="+44"/> <location line="+8"/> <location line="+15"/> - <location line="+30"/> + <location line="+45"/> <source>Debit</source> <translation>Debit</translation> </message> <message> - <location line="-39"/> + <location line="-54"/> <source>Transaction fee</source> <translation>Transaction fee</translation> </message> @@ -1917,7 +2003,12 @@ Address: %4 <translation>Transaction ID</translation> </message> <message> - <location line="+3"/> + <location line="+13"/> + <source>Merchant</source> + <translation type="unfinished"></translation> + </message> + <message> + <location line="+5"/> <source>Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.</source> <translation>Generated coins must mature 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to "not accepted" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.</translation> </message> @@ -1952,7 +2043,7 @@ Address: %4 <translation>false</translation> </message> <message> - <location line="-209"/> + <location line="-224"/> <source>, has not been successfully broadcast yet</source> <translation>, has not been successfully broadcast yet</translation> </message> @@ -1986,7 +2077,7 @@ Address: %4 <context> <name>TransactionTableModel</name> <message> - <location filename="../transactiontablemodel.cpp" line="+225"/> + <location filename="../transactiontablemodel.cpp" line="+227"/> <source>Date</source> <translation>Date</translation> </message> @@ -2279,7 +2370,7 @@ Address: %4 <context> <name>WalletModel</name> <message> - <location filename="../walletmodel.cpp" line="+193"/> + <location filename="../walletmodel.cpp" line="+218"/> <source>Send Coins</source> <translation>Send Coins</translation> </message> @@ -2297,7 +2388,7 @@ Address: %4 <translation>Export the data in the current tab to a file</translation> </message> <message> - <location line="+197"/> + <location line="+198"/> <source>Backup Wallet</source> <translation>Backup Wallet</translation> </message> diff --git a/src/qt/notificator.h b/src/qt/notificator.h index d1fe37fea5..6c9a46bcf7 100644 --- a/src/qt/notificator.h +++ b/src/qt/notificator.h @@ -1,6 +1,10 @@ #ifndef NOTIFICATOR_H #define NOTIFICATOR_H +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" +#endif + #include <QObject> #include <QIcon> diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index b2451aea31..7ccda6cdd4 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -1,3 +1,7 @@ +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" +#endif + #include "optionsdialog.h" #include "ui_optionsdialog.h" diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index d93a60e1bc..95efc58320 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -1,3 +1,7 @@ +#if defined(HAVE_CONFIG_H) +#include "bitcoin-config.h" +#endif + #include "optionsmodel.h" #include "bitcoinunits.h" diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp index 289ddabb92..f6a898ff7c 100644 --- a/src/qt/paymentrequestplus.cpp +++ b/src/qt/paymentrequestplus.cpp @@ -24,18 +24,18 @@ bool PaymentRequestPlus::parse(const QByteArray& data) { bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size()); if (!parseOK) { - qDebug() << "Error parsing payment request"; + qDebug() << "PaymentRequestPlus::parse : Error parsing payment request"; return false; } if (paymentRequest.payment_details_version() > 1) { - qDebug() << "Received up-version payment details, version=" << paymentRequest.payment_details_version(); + qDebug() << "PaymentRequestPlus::parse : Received up-version payment details, version=" << paymentRequest.payment_details_version(); return false; } parseOK = details.ParseFromString(paymentRequest.serialized_payment_details()); if (!parseOK) { - qDebug() << "Error parsing payment details"; + qDebug() << "PaymentRequestPlus::parse : Error parsing payment details"; paymentRequest.Clear(); return false; } @@ -75,17 +75,18 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c digestAlgorithm = EVP_sha1(); } else if (paymentRequest.pki_type() == "none") { - if (fDebug) qDebug() << "PaymentRequest: pki_type == none"; + if (fDebug) + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: pki_type == none"; return false; } else { - qDebug() << "PaymentRequest: unknown pki_type " << paymentRequest.pki_type().c_str(); + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: unknown pki_type " << QString::fromStdString(paymentRequest.pki_type()); return false; } payments::X509Certificates certChain; if (!certChain.ParseFromString(paymentRequest.pki_data())) { - qDebug() << "PaymentRequest: error parsing pki_data"; + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: error parsing pki_data"; return false; } @@ -95,12 +96,12 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size()); QSslCertificate qCert(certData, QSsl::Der); if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) { - qDebug() << "PaymentRequest: certificate expired or not yet active: " << qCert; + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: certificate expired or not yet active: " << qCert; return false; } #if QT_VERSION >= 0x050000 if (qCert.isBlacklisted()) { - qDebug() << "PaymentRequest: certificate blacklisted: " << qCert; + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: certificate blacklisted: " << qCert; return false; } #endif @@ -110,7 +111,7 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c certs.push_back(cert); } if (certs.empty()) { - qDebug() << "PaymentRequest: empty certificate chain"; + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: empty certificate chain"; return false; } @@ -126,7 +127,7 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c // load the signing cert into it and verify. X509_STORE_CTX *store_ctx = X509_STORE_CTX_new(); if (!store_ctx) { - qDebug() << "PaymentRequest: error creating X509_STORE_CTX"; + qDebug() << "PaymentRequestPlus::getMerchant : Payment request: error creating X509_STORE_CTX"; return false; } @@ -151,7 +152,7 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c // Valid cert; check signature: payments::PaymentRequest rcopy(paymentRequest); // Copy rcopy.set_signature(std::string("")); - std::string data_to_verify; // Everything but the signature + std::string data_to_verify; // Everything but the signature rcopy.SerializeToString(&data_to_verify); EVP_MD_CTX ctx; @@ -171,14 +172,14 @@ bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) c merchant = website; } else { - throw SSLVerifyError("Bad certificate, missing common name"); + throw SSLVerifyError("Bad certificate, missing common name."); } // TODO: detect EV certificates and set merchant = business name instead of unfriendly NID_commonName ? } catch (SSLVerifyError& err) { fResult = false; - qDebug() << "PaymentRequestPlus::getMerchant SSL err: " << err.what(); + qDebug() << "PaymentRequestPlus::getMerchant : SSL error: " << err.what(); } if (website) diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index a9f71315a9..96ba997943 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -44,10 +44,11 @@ #include "wallet.h" #include "walletmodel.h" -using namespace boost; - const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); +const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest"; +const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack"; +const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment"; X509_STORE* PaymentServer::certStore = NULL; void PaymentServer::freeCertStore() @@ -71,14 +72,14 @@ static QString ipcServerName() // Append a simple hash of the datadir // Note that GetDataDir(true) returns a different path // for -testnet versus main net - QString ddir(GetDataDir(true).string().c_str()); + QString ddir(QString::fromStdString(GetDataDir(true).string())); name.append(QString::number(qHash(ddir))); return name; } // -// We store payment URLs and requests received before +// We store payment URIs and requests received before // the main GUI window is up and ready to ask the user // to send payment. @@ -87,12 +88,12 @@ static QList<QString> savedPaymentRequests; static void ReportInvalidCertificate(const QSslCertificate& cert) { if (fDebug) { - qDebug() << "Invalid certificate: " << cert.subjectInfo(QSslCertificate::CommonName); + qDebug() << "ReportInvalidCertificate : Payment server found an invalid certificate: " << cert.subjectInfo(QSslCertificate::CommonName); } } // -// Load openSSL's list of root certificate authorities +// Load OpenSSL's list of root certificate authorities // void PaymentServer::LoadRootCAs(X509_STORE* _store) { @@ -147,7 +148,7 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) const unsigned char *data = (const unsigned char *)certData.data(); X509* x509 = d2i_X509(0, &data, certData.size()); - if (x509 && X509_STORE_add_cert( PaymentServer::certStore, x509)) + if (x509 && X509_STORE_add_cert(PaymentServer::certStore, x509)) { // Note: X509_STORE_free will free the X509* objects when // the PaymentServer is destroyed @@ -160,7 +161,7 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) } } if (fDebug) - qDebug() << "PaymentServer: loaded " << nRootCerts << " root certificates"; + qDebug() << "PaymentServer::LoadRootCAs : Loaded " << nRootCerts << " root certificates"; // Project for another day: // Fetch certificate revocation lists, and add them to certStore. @@ -219,7 +220,7 @@ bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) } else { - qDebug() << "Payment request file does not exist: " << argv[i]; + qDebug() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << argv[i]; // Printing to debug.log is about the best we can do here, the // GUI hasn't started yet so we can't pop up a message box. } @@ -248,8 +249,7 @@ bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) return fResult; } -PaymentServer::PaymentServer(QObject* parent, - bool startLocalServer) : QObject(parent), saveURIs(true) +PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(parent), saveURIs(true) { // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. @@ -269,7 +269,7 @@ PaymentServer::PaymentServer(QObject* parent, uriServer = new QLocalServer(this); if (!uriServer->listen(name)) - qDebug() << "Cannot start bitcoin: click-to-pay handler"; + qDebug() << "PaymentServer::PaymentServer : Cannot start bitcoin: click-to-pay handler"; else connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); } @@ -284,12 +284,12 @@ PaymentServer::~PaymentServer() } // -// OSX-specific way of handling bitcoin uris and +// OSX-specific way of handling bitcoin: URIs and // PaymentRequest mime types // bool PaymentServer::eventFilter(QObject *, QEvent *event) { - // clicking on bitcoin: URLs creates FileOpen events on the Mac: + // clicking on bitcoin: URIs creates FileOpen events on the Mac: if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event); @@ -303,18 +303,20 @@ bool PaymentServer::eventFilter(QObject *, QEvent *event) return false; } -void PaymentServer::initNetManager(const OptionsModel& options) +void PaymentServer::initNetManager() { + if (!optionsModel) + return; if (netManager != NULL) delete netManager; // netManager is used to fetch paymentrequests given in bitcoin: URI's netManager = new QNetworkAccessManager(this); - // Use proxy settings from options: + // Use proxy settings from optionsModel: QString proxyIP; quint16 proxyPort; - if (options.getProxySettings(proxyIP, proxyPort)) + if (optionsModel->getProxySettings(proxyIP, proxyPort)) { QNetworkProxy proxy; proxy.setType(QNetworkProxy::Socks5Proxy); @@ -352,22 +354,23 @@ void PaymentServer::handleURIOrFile(const QString& s) if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: { #if QT_VERSION >= 0x050000 - QUrlQuery url((QUrl(s))); + QUrlQuery uri((QUrl(s))); #else - QUrl url(s); + QUrl uri(s); #endif - if (url.hasQueryItem("request")) + if (uri.hasQueryItem("request")) { - QByteArray temp; temp.append(url.queryItemValue("request")); + QByteArray temp; temp.append(uri.queryItemValue("request")); QString decoded = QUrl::fromPercentEncoding(temp); QUrl fetchUrl(decoded, QUrl::StrictMode); - if (fDebug) qDebug() << "PaymentServer::fetchRequest " << fetchUrl; + if (fDebug) + qDebug() << "PaymentServer::handleURIOrFile : fetchRequest(" << fetchUrl << ")"; if (fetchUrl.isValid()) fetchRequest(fetchUrl); else - qDebug() << "PaymentServer: invalid url: " << fetchUrl; + qDebug() << "PaymentServer::handleURIOrFile : Invalid url: " << fetchUrl; return; } @@ -416,13 +419,13 @@ bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPl QFile f(filename); if (!f.open(QIODevice::ReadOnly)) { - qDebug() << "PaymentServer::readPaymentRequest fail to open " << filename; + qDebug() << "PaymentServer::readPaymentRequest : Failed to open " << filename; return false; } if (f.size() > MAX_PAYMENT_REQUEST_SIZE) { - qDebug() << "PaymentServer::readPaymentRequest " << filename << " too large"; + qDebug() << "PaymentServer::readPaymentRequest : " << filename << " too large"; return false; } @@ -431,18 +434,20 @@ bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPl return request.parse(data); } -bool -PaymentServer::processPaymentRequest(PaymentRequestPlus& request, - QList<SendCoinsRecipient>& recipients) +bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, QList<SendCoinsRecipient>& recipients) { + if (!optionsModel) + return false; + QList<std::pair<CScript,qint64> > sendingTos = request.getPayTo(); qint64 totalAmount = 0; foreach(const PAIRTYPE(CScript, qint64)& sendingTo, sendingTos) { CTxOut txOut(sendingTo.second, sendingTo.first); if (txOut.IsDust(CTransaction::nMinRelayTxFee)) { QString message = QObject::tr("Requested payment amount (%1) too small") - .arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendingTo.second)); - qDebug() << message; + .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)); + + qDebug() << "PaymentServer::processPaymentRequest : " << message; emit reportError(tr("Payment request error"), message, CClientUIInterface::MODAL); return false; } @@ -455,7 +460,8 @@ PaymentServer::processPaymentRequest(PaymentRequestPlus& request, if (request.getMerchant(PaymentServer::certStore, recipients[0].authenticatedMerchant)) { recipients[0].paymentRequest = request; recipients[0].amount = totalAmount; - if (fDebug) qDebug() << "PaymentRequest from " << recipients[0].authenticatedMerchant; + if (fDebug) + qDebug() << "PaymentServer::processPaymentRequest : Payment request from " << recipients[0].authenticatedMerchant; } else { recipients.clear(); @@ -466,23 +472,20 @@ PaymentServer::processPaymentRequest(PaymentRequestPlus& request, recipients.append(SendCoinsRecipient()); recipients[i].amount = sendingTo.second; QString memo = QString::fromStdString(request.getDetails().memo()); -#if QT_VERSION < 0x050000 - recipients[i].label = Qt::escape(memo); -#else - recipients[i].label = memo.toHtmlEscaped(); -#endif + recipients[i].label = GUIUtil::HtmlEscape(memo); CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { if (i == 0) // Tie request to first pay-to, we don't want multiple ACKs recipients[i].paymentRequest = request; recipients[i].address = QString::fromStdString(CBitcoinAddress(dest).ToString()); - if (fDebug) qDebug() << "PaymentRequest, insecure " << recipients[i].address; + if (fDebug) + qDebug() << "PaymentServer::processPaymentRequest : Payment request, insecure " << recipients[i].address; } else { // Insecure payments to custom bitcoin addresses are not supported // (there is no good way to tell the user where they are paying in a way // they'd have a chance of understanding). - emit reportError(tr("Payment request error"), + emit reportError(tr("Payment request error"), tr("Insecure requests to custom payment scripts unsupported"), CClientUIInterface::MODAL); return false; @@ -493,18 +496,17 @@ PaymentServer::processPaymentRequest(PaymentRequestPlus& request, return true; } -void -PaymentServer::fetchRequest(const QUrl& url) +void PaymentServer::fetchRequest(const QUrl& url) { QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest"); netRequest.setUrl(url); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); + netRequest.setRawHeader("Accept", BITCOIN_REQUEST_MIMETYPE); netManager->get(netRequest); } -void -PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction) +void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction) { const payments::PaymentDetails& details = recipient.paymentRequest.getDetails(); if (!details.has_payment_url()) @@ -513,8 +515,9 @@ PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QB QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, "PaymentACK"); netRequest.setUrl(QString::fromStdString(details.payment_url())); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoin-payment"); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BITCOIN_PAYMENTACK_CONTENTTYPE); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); + netRequest.setRawHeader("Accept", BITCOIN_PAYMENTACK_MIMETYPE); payments::Payment payment; payment.set_merchant_data(details.merchant_data()); @@ -531,7 +534,7 @@ PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QB } else { CPubKey newKey; - if (wallet->GetKeyFromPool(newKey, false)) { + if (wallet->GetKeyFromPool(newKey)) { CKeyID keyID = newKey.GetID(); wallet->SetAddressBook(keyID, strAccount, "refund"); @@ -542,7 +545,7 @@ PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QB else { // This should never happen, because sending coins should have just unlocked the wallet // and refilled the keypool - qDebug() << "Error getting refund key, refund_to not set"; + qDebug() << "PaymentServer::fetchPaymentACK : Error getting refund key, refund_to not set"; } } @@ -554,12 +557,11 @@ PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QB } else { // This should never happen, either: - qDebug() << "Error serializing payment message"; + qDebug() << "PaymentServer::fetchPaymentACK : Error serializing payment message"; } } -void -PaymentServer::netRequestFinished(QNetworkReply* reply) +void PaymentServer::netRequestFinished(QNetworkReply* reply) { reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) @@ -567,7 +569,7 @@ PaymentServer::netRequestFinished(QNetworkReply* reply) QString message = QObject::tr("Error communicating with %1: %2") .arg(reply->request().url().toString()) .arg(reply->errorString()); - qDebug() << message; + qDebug() << "PaymentServer::netRequestFinished : " << message; emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); return; } @@ -585,7 +587,7 @@ PaymentServer::netRequestFinished(QNetworkReply* reply) } } else - qDebug() << "PaymentServer::netRequestFinished: error processing PaymentRequest"; + qDebug() << "PaymentServer::netRequestFinished : Error processing payment request"; return; } else if (requestType == "PaymentACK") @@ -595,7 +597,7 @@ PaymentServer::netRequestFinished(QNetworkReply* reply) { QString message = QObject::tr("Bad response from server %1") .arg(reply->request().url().toString()); - qDebug() << message; + qDebug() << "PaymentServer::netRequestFinished : " << message; emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); } else { @@ -604,13 +606,19 @@ PaymentServer::netRequestFinished(QNetworkReply* reply) } } -void -PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs) +void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs) { + Q_UNUSED(reply); + QString errString; foreach (const QSslError& err, errs) { - qDebug() << err; + qDebug() << "PaymentServer::reportSslErrors : " << err; errString += err.errorString() + "\n"; } emit reportError(tr("Network request error"), errString, CClientUIInterface::MODAL); } + +void PaymentServer::setOptionsModel(OptionsModel *optionsModel) +{ + this->optionsModel = optionsModel; +} diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 7c6b2eabf0..f9d827204b 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -17,7 +17,7 @@ // received at or during startup in a list. // // When startup is finished and the main window is -// show, a signal is sent to slot uiReady(), which +// shown, a signal is sent to slot uiReady(), which // emits a receivedURL() signal for any payment // requests that happened during startup. // @@ -36,6 +36,8 @@ class CWallet; class OptionsModel; + +QT_BEGIN_NAMESPACE class QApplication; class QByteArray; class QLocalServer; @@ -43,6 +45,7 @@ class QNetworkAccessManager; class QNetworkReply; class QSslError; class QUrl; +QT_END_NAMESPACE class PaymentServer : public QObject { @@ -56,8 +59,8 @@ public: // will be called so we startup in the right mode. static bool ipcSendCommandLine(int argc, char *argv[]); - PaymentServer(QObject* parent, // parent should be QApplication object - bool startLocalServer=true); + // parent should be QApplication object + PaymentServer(QObject* parent, bool startLocalServer = true); ~PaymentServer(); // Load root certificate authorities. Pass NULL (default) @@ -65,18 +68,21 @@ public: // or, if that's not set, to use the system default root certificates. // If you pass in a store, you should not X509_STORE_free it: it will be // freed either at exit or when another set of CAs are loaded. - static void LoadRootCAs(X509_STORE* store=NULL); + static void LoadRootCAs(X509_STORE* store = NULL); // Return certificate store static X509_STORE* getCertStore() { return certStore; } - // Setup networking (options is used to get proxy settings) - void initNetManager(const OptionsModel& options); + // Setup networking + void initNetManager(); // Constructor registers this on the parent QApplication to // receive QEvent::FileOpen events bool eventFilter(QObject *object, QEvent *event); + // OptionsModel is used for getting proxy settings and display unit + void setOptionsModel(OptionsModel *optionsModel); + signals: // Fired when a valid payment request is received void receivedPaymentRequest(SendCoinsRecipient); @@ -106,12 +112,15 @@ private: void handleURIOrFile(const QString& s); void fetchRequest(const QUrl& url); - bool saveURIs; // true during startup + bool saveURIs; // true during startup QLocalServer* uriServer; - static X509_STORE* certStore; // Trusted root certificates + + static X509_STORE* certStore; // Trusted root certificates static void freeCertStore(); - QNetworkAccessManager* netManager; // Used to fetch payment requests + QNetworkAccessManager* netManager; // Used to fetch payment requests + + OptionsModel *optionsModel; }; #endif // PAYMENTSERVER_H diff --git a/src/qt/res/bitcoin-qt.rc b/src/qt/res/bitcoin-qt-res.rc index 3e3672a835..3e3672a835 100644 --- a/src/qt/res/bitcoin-qt.rc +++ b/src/qt/res/bitcoin-qt-res.rc diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 8953c36579..e7dcdf62a1 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -22,6 +22,8 @@ const int CONSOLE_HISTORY = 50; const QSize ICON_SIZE(24, 24); +const int INITIAL_TRAFFIC_GRAPH_MINS = 30; + const struct { const char *url; const char *source; @@ -204,6 +206,7 @@ RPCConsole::RPCConsole(QWidget *parent) : ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION)); startExecutor(); + setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); clear(); } @@ -253,7 +256,8 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) void RPCConsole::setClientModel(ClientModel *model) { - this->clientModel = model; + clientModel = model; + ui->trafficGraph->setClientModel(model); if(model) { // Keep up to date with client @@ -263,6 +267,9 @@ void RPCConsole::setClientModel(ClientModel *model) setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers()); connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); + updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent()); + connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); + // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientName->setText(model->clientName()); @@ -431,3 +438,49 @@ void RPCConsole::on_showCLOptionsButton_clicked() GUIUtil::HelpMessageBox help; help.exec(); } + +void RPCConsole::on_sldGraphRange_valueChanged(int value) +{ + const int multiplier = 5; // each position on the slider represents 5 min + int mins = value * multiplier; + setTrafficGraphRange(mins); +} + +QString RPCConsole::FormatBytes(quint64 bytes) +{ + if(bytes < 1024) + return QString(tr("%1 B")).arg(bytes); + if(bytes < 1024 * 1024) + return QString(tr("%1 KB")).arg(bytes / 1024); + if(bytes < 1024 * 1024 * 1024) + return QString(tr("%1 MB")).arg(bytes / 1024 / 1024); + + return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); +} + +void RPCConsole::setTrafficGraphRange(int mins) +{ + ui->trafficGraph->setGraphRangeMins(mins); + if(mins < 60) { + ui->lblGraphRange->setText(QString(tr("%1 m")).arg(mins)); + } else { + int hours = mins / 60; + int minsLeft = mins % 60; + if(minsLeft == 0) { + ui->lblGraphRange->setText(QString(tr("%1 h")).arg(hours)); + } else { + ui->lblGraphRange->setText(QString(tr("%1 h %2 m")).arg(hours).arg(minsLeft)); + } + } +} + +void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) +{ + ui->lblBytesIn->setText(FormatBytes(totalBytesIn)); + ui->lblBytesOut->setText(FormatBytes(totalBytesOut)); +} + +void RPCConsole::on_btnClearTrafficGraph_clicked() +{ + ui->trafficGraph->clear(); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 3c38b4b8de..af92b55770 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -37,6 +37,12 @@ private slots: void on_openDebugLogfileButton_clicked(); /** display messagebox with program parameters (same as bitcoin-qt --help) */ void on_showCLOptionsButton_clicked(); + /** change the time range of the network traffic graph */ + void on_sldGraphRange_valueChanged(int value); + /** update traffic statistics */ + void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut); + /** clear traffic graph */ + void on_btnClearTrafficGraph_clicked(); public slots: void clear(); @@ -55,6 +61,9 @@ signals: void cmdRequest(const QString &command); private: + static QString FormatBytes(quint64 bytes); + void setTrafficGraphRange(int mins); + Ui::RPCConsole *ui; ClientModel *clientModel; QStringList history; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 9086f6614e..3fd4a26e76 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -63,12 +63,12 @@ SendCoinsDialog::~SendCoinsDialog() void SendCoinsDialog::on_sendButton_clicked() { + if(!model || !model->getOptionsModel()) + return; + QList<SendCoinsRecipient> recipients; bool valid = true; - if(!model) - return; - for(int i = 0; i < ui->entries->count(); ++i) { SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); @@ -94,40 +94,37 @@ void SendCoinsDialog::on_sendButton_clicked() QStringList formatted; foreach(const SendCoinsRecipient &rcp, recipients) { - QString amount = BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, rcp.amount); + // generate bold amount string + QString amount = "<b>" + BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount); + amount.append("</b>"); + // generate monospace address string + QString address = "<span style='font-family: monospace;'>" + rcp.address; + address.append("</span>"); + + QString recipientElement; + if (rcp.authenticatedMerchant.isEmpty()) { - QString address = rcp.address; -#if QT_VERSION < 0x050000 - QString to = Qt::escape(rcp.label); -#else - QString to = rcp.label.toHtmlEscaped(); -#endif - formatted.append(tr("<b>%1</b> to %2 (%3)").arg(amount, to, address)); + if(rcp.label.length() > 0) // label with address + { + recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label)); + recipientElement.append(QString(" (%1)").arg(address)); + } + else // just address + { + recipientElement = tr("%1 to %2").arg(amount, address); + } } - else + else // just merchant { -#if QT_VERSION < 0x050000 - QString merchant = Qt::escape(rcp.authenticatedMerchant); -#else - QString merchant = rcp.authenticatedMerchant.toHtmlEscaped(); -#endif - formatted.append(tr("<b>%1</b> to %2").arg(amount, merchant)); + recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant)); } + + formatted.append(recipientElement); } fNewRecipientAllowed = false; - QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), - tr("Are you sure you want to send %1?").arg(formatted.join(tr(" and "))), - QMessageBox::Yes|QMessageBox::Cancel, - QMessageBox::Cancel); - - if(retval != QMessageBox::Yes) - { - fNewRecipientAllowed = true; - return; - } WalletModel::UnlockContext ctx(model->requestUnlock()); if(!ctx.isValid()) @@ -137,50 +134,94 @@ void SendCoinsDialog::on_sendButton_clicked() return; } - WalletModel::SendCoinsReturn sendstatus = model->sendCoins(recipients); - switch(sendstatus.status) + // prepare transaction for getting txFee earlier + WalletModelTransaction currentTransaction(recipients); + WalletModel::SendCoinsReturn prepareStatus = model->prepareTransaction(currentTransaction); + + QString strSendCoins = tr("Send Coins"); + switch(prepareStatus.status) { case WalletModel::InvalidAddress: - QMessageBox::warning(this, tr("Send Coins"), - tr("The recipient address is not valid, please recheck."), - QMessageBox::Ok, QMessageBox::Ok); + QMessageBox::warning(this, strSendCoins, + tr("The recipient address is not valid, please recheck.")); break; case WalletModel::InvalidAmount: - QMessageBox::warning(this, tr("Send Coins"), - tr("The amount to pay must be larger than 0."), - QMessageBox::Ok, QMessageBox::Ok); + QMessageBox::warning(this, strSendCoins, + tr("The amount to pay must be larger than 0.")); break; case WalletModel::AmountExceedsBalance: - QMessageBox::warning(this, tr("Send Coins"), - tr("The amount exceeds your balance."), - QMessageBox::Ok, QMessageBox::Ok); + QMessageBox::warning(this, strSendCoins, + tr("The amount exceeds your balance.")); break; case WalletModel::AmountWithFeeExceedsBalance: - QMessageBox::warning(this, tr("Send Coins"), + QMessageBox::warning(this, strSendCoins, tr("The total exceeds your balance when the %1 transaction fee is included."). - arg(BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, sendstatus.fee)), - QMessageBox::Ok, QMessageBox::Ok); + arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee()))); break; case WalletModel::DuplicateAddress: - QMessageBox::warning(this, tr("Send Coins"), - tr("Duplicate address found, can only send to each address once per send operation."), - QMessageBox::Ok, QMessageBox::Ok); + QMessageBox::warning(this, strSendCoins, + tr("Duplicate address found, can only send to each address once per send operation.")); break; case WalletModel::TransactionCreationFailed: - QMessageBox::warning(this, tr("Send Coins"), - tr("Error: Transaction creation failed!"), - QMessageBox::Ok, QMessageBox::Ok); + QMessageBox::warning(this, strSendCoins, + tr("Error: Transaction creation failed!")); break; case WalletModel::TransactionCommitFailed: - QMessageBox::warning(this, tr("Send Coins"), - tr("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."), - QMessageBox::Ok, QMessageBox::Ok); - break; + case WalletModel::OK: case WalletModel::Aborted: // User aborted, nothing to do + default: + break; + } + + if(prepareStatus.status != WalletModel::OK) { + fNewRecipientAllowed = true; + return; + } + + qint64 txFee = currentTransaction.getTransactionFee(); + QString questionString = tr("Are you sure you want to send?"); + questionString.append("<br /><br />%1"); + + if(txFee > 0) + { + // append fee string if a fee is required + questionString.append("<hr /><span style='color:#aa0000;'>"); + questionString.append(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee)); + questionString.append("</span> "); + questionString.append(tr("added as transaction fee")); + } + if(txFee > 0 || recipients.count() > 1) + { + // add total amount string if there are more then one recipients or a fee is required + questionString.append("<hr />"); + questionString.append(tr("Total Amount %1").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTotalTransactionAmount()+txFee))); + } + + QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm send coins"), + questionString.arg(formatted.join("<br />")), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + + if(retval != QMessageBox::Yes) + { + fNewRecipientAllowed = true; + return; + } + + // now send the prepared transaction + WalletModel::SendCoinsReturn sendstatus = model->sendCoins(currentTransaction); + switch(sendstatus.status) + { + case WalletModel::TransactionCommitFailed: + QMessageBox::warning(this, strSendCoins, + tr("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here.")); break; case WalletModel::OK: accept(); break; + case WalletModel::Aborted: // User aborted, nothing to do + default: + break; } fNewRecipientAllowed = true; } @@ -310,22 +351,22 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) { + QString strSendCoins = tr("Send Coins"); if (!rv.authenticatedMerchant.isEmpty()) { // Expired payment request? const payments::PaymentDetails& details = rv.paymentRequest.getDetails(); if (details.has_expires() && (int64)details.expires() < GetTime()) { - QMessageBox::warning(this, tr("Send Coins"), - tr("Payment request expired")); + QMessageBox::warning(this, strSendCoins, + tr("Payment request expired")); return false; } } else { CBitcoinAddress address(rv.address.toStdString()); if (!address.IsValid()) { - QString strAddress(address.ToString().c_str()); - QMessageBox::warning(this, tr("Send Coins"), - tr("Invalid payment address %1").arg(strAddress)); + QMessageBox::warning(this, strSendCoins, + tr("Invalid payment address %1").arg(rv.address)); return false; } } @@ -338,18 +379,14 @@ void SendCoinsDialog::setBalance(qint64 balance, qint64 unconfirmedBalance, qint { Q_UNUSED(unconfirmedBalance); Q_UNUSED(immatureBalance); - if(!model || !model->getOptionsModel()) - return; - int unit = model->getOptionsModel()->getDisplayUnit(); - ui->labelBalance->setText(BitcoinUnits::formatWithUnit(unit, balance)); + if(model && model->getOptionsModel()) + { + ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance)); + } } void SendCoinsDialog::updateDisplayUnit() { - if(model && model->getOptionsModel()) - { - // Update labelBalance with the current balance and the current unit - ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->getBalance())); - } + setBalance(model->getBalance(), 0, 0); } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index e75a003ba1..f4bffedc9b 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -10,6 +10,7 @@ namespace Ui { class WalletModel; class SendCoinsEntry; class SendCoinsRecipient; +class OptionsModel; QT_BEGIN_NAMESPACE class QUrl; diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 75610f199e..188b8860a9 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -60,12 +60,7 @@ void SendCoinsEntry::on_addressBookButton_clicked() void SendCoinsEntry::on_payTo_textChanged(const QString &address) { - if(!model) - return; - // Fill in label from address book, if address has an associated label - QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); - if(!associatedLabel.isEmpty()) - ui->addAsLabel->setText(associatedLabel); + updateLabel(address); } void SendCoinsEntry::setModel(WalletModel *model) @@ -85,10 +80,17 @@ void SendCoinsEntry::setRemoveEnabled(bool enabled) void SendCoinsEntry::clear() { + // clear UI elements for insecure payments ui->payTo->clear(); ui->addAsLabel->clear(); ui->payAmount->clear(); + // and the ones for secure payments just to be sure + ui->payTo_s->clear(); + ui->memoTextLabel_s->clear(); + ui->payAmount_s->clear(); + ui->payTo->setFocus(); + // update the display unit, to not use the default ("BTC") updateDisplayUnit(); } @@ -106,8 +108,8 @@ bool SendCoinsEntry::validate() if (!recipient.authenticatedMerchant.isEmpty()) return retval; - if(!ui->payTo->hasAcceptableInput() || - (model && !model->validateAddress(ui->payTo->text()))) + if (!ui->payTo->hasAcceptableInput() || + (model && !model->validateAddress(ui->payTo->text()))) { ui->payTo->setValid(false); retval = false; @@ -154,18 +156,20 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; - ui->payTo->setText(value.address); - ui->addAsLabel->setText(value.label); - ui->payAmount->setValue(value.amount); - - if (!recipient.authenticatedMerchant.isEmpty()) + if (recipient.authenticatedMerchant.isEmpty()) + { + ui->payTo->setText(recipient.address); + ui->addAsLabel->setText(recipient.label); + ui->payAmount->setValue(recipient.amount); + } + else { - const payments::PaymentDetails& details = value.paymentRequest.getDetails(); + const payments::PaymentDetails& details = recipient.paymentRequest.getDetails(); - ui->payTo_s->setText(value.authenticatedMerchant); - ui->memo_s->setTextFormat(Qt::PlainText); - ui->memo_s->setText(QString::fromStdString(details.memo())); - ui->payAmount_s->setValue(value.amount); + ui->payTo_s->setText(recipient.authenticatedMerchant); + ui->memoTextLabel_s->setText(QString::fromStdString(details.memo())); + ui->payAmount_s->setValue(recipient.amount); + ui->payAmount_s->setReadOnly(true); setCurrentWidget(ui->SendCoinsSecure); } } @@ -195,3 +199,19 @@ void SendCoinsEntry::updateDisplayUnit() ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); } } + +bool SendCoinsEntry::updateLabel(const QString &address) +{ + if(!model) + return false; + + // Fill in label from address book, if address has an associated label + QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); + if(!associatedLabel.isEmpty()) + { + ui->addAsLabel->setText(associatedLabel); + return true; + } + + return false; +} diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 9c7bfe9521..66d9752909 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -33,7 +33,8 @@ public: void setValue(const SendCoinsRecipient &value); void setAddress(const QString &address); - /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://bugreports.qt-project.org/browse/QTBUG-10907). + /** Set up the tab chain manually, as Qt messes up the tab chain by default in some cases + * (issue https://bugreports.qt-project.org/browse/QTBUG-10907). */ QWidget *setupTabChain(QWidget *prev); @@ -57,6 +58,8 @@ private: SendCoinsRecipient recipient; Ui::SendCoinsEntry *ui; WalletModel *model; + + bool updateLabel(const QString &address); }; #endif // SENDCOINSENTRY_H diff --git a/src/qt/test/Makefile.am b/src/qt/test/Makefile.am new file mode 100644 index 0000000000..f8fe97462b --- /dev/null +++ b/src/qt/test/Makefile.am @@ -0,0 +1,26 @@ +include $(top_srcdir)/src/Makefile.include + +AM_CPPFLAGS = $(INCLUDES) -I$(top_builddir)/src/obj \ + -I$(top_srcdir)/src/leveldb/include -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/leveldb/helpers -I$(top_srcdir)/src/qt \ + -I$(top_builddir)/src/qt $(BOOST_INCLUDES) $(PROTOBUF_CFLAGS) \ + $(QR_CFLAGS) $(BDB_CPPFLAGS) +AM_LDFLAGS = $(PTHREAD_CFLAGS) +bin_PROGRAMS = test_bitcoin-qt +TESTS = test_bitcoin-qt + +TEST_QT_MOC_CPP = moc_uritests.cpp moc_paymentservertests.cpp + +TEST_QT_H = uritests.h paymentservertests.h paymentrequestdata.h + +BUILT_SOURCES = $(TEST_QT_MOC_CPP) + +test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(QT_INCLUDES) $(QT_TEST_INCLUDES) +test_bitcoin_qt_SOURCES = test_main.cpp uritests.cpp paymentservertests.cpp $(TEST_QT_H) +nodist_test_bitcoin_qt_SOURCES = $(TEST_QT_MOC_CPP) +test_bitcoin_qt_LDADD = $(LIBBITCOINQT) $(LIBBITCOIN) $(LIBLEVELDB) \ + $(LIBMEMENV) $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) \ + $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) + +CLEANFILES = $(BUILT_SOURCES) *.gcda *.gcno + diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 2e26ab0c9b..34079e94ff 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -2,6 +2,7 @@ #include <QDebug> #include <QTemporaryFile> #include <QVariant> +#include <QFileOpenEvent> #include <openssl/x509.h> #include <openssl/x509_vfy.h> @@ -58,7 +59,8 @@ void PaymentServerTests::paymentServerTests() X509_STORE* caStore = X509_STORE_new(); X509_STORE_add_cert(caStore, parse_b64der_cert(caCert_BASE64)); PaymentServer::LoadRootCAs(caStore); - server->initNetManager(optionsModel); + server->setOptionsModel(&optionsModel); + server->initNetManager(); server->uiReady(); // Now feed PaymentRequests to server, and observe signals it produces: diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp new file mode 100644 index 0000000000..d49bc31f3e --- /dev/null +++ b/src/qt/trafficgraphwidget.cpp @@ -0,0 +1,169 @@ +#include "trafficgraphwidget.h" +#include "clientmodel.h" + +#include <QPainter> +#include <QColor> +#include <QTimer> + +#include <cmath> + +#define DESIRED_SAMPLES 800 + +#define XMARGIN 10 +#define YMARGIN 10 + +TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : + QWidget(parent), + timer(0), + fMax(0.0f), + nMins(0), + vSamplesIn(), + vSamplesOut(), + nLastBytesIn(0), + nLastBytesOut(0), + clientModel(0) +{ + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(updateRates())); +} + +void TrafficGraphWidget::setClientModel(ClientModel *model) +{ + clientModel = model; + if(model) { + nLastBytesIn = model->getTotalBytesRecv(); + nLastBytesOut = model->getTotalBytesSent(); + } +} + +int TrafficGraphWidget::getGraphRangeMins() const +{ + return nMins; +} + +void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue<float> &samples) +{ + int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; + int sampleCount = samples.size(), x = XMARGIN + w, y; + if(sampleCount > 0) { + path.moveTo(x, YMARGIN + h); + for(int i = 0; i < sampleCount; ++i) { + x = XMARGIN + w - w * i / DESIRED_SAMPLES; + y = YMARGIN + h - (int)(h * samples.at(i) / fMax); + path.lineTo(x, y); + } + path.lineTo(x, YMARGIN + h); + } +} + +void TrafficGraphWidget::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.fillRect(rect(), Qt::black); + + if(fMax <= 0.0f) return; + + QColor axisCol(Qt::gray); + int h = height() - YMARGIN * 2; + painter.setPen(axisCol); + painter.drawLine(XMARGIN, YMARGIN + h, width() - XMARGIN, YMARGIN + h); + + // decide what order of magnitude we are + int base = floor(log10(fMax)); + float val = pow(10.0f, base); + + const QString units = tr("KB/s"); + // draw lines + painter.setPen(axisCol); + painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax, QString("%1 %2").arg(val).arg(units)); + for(float y = val; y < fMax; y += val) { + int yy = YMARGIN + h - h * y / fMax; + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + // if we drew 3 or fewer lines, break them up at the next lower order of magnitude + if(fMax / val <= 3.0f) { + axisCol = axisCol.darker(); + val = pow(10.0f, base - 1); + painter.setPen(axisCol); + painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax, QString("%1 %2").arg(val).arg(units)); + int count = 1; + for(float y = val; y < fMax; y += val, count++) { + // don't overwrite lines drawn above + if(count % 10 == 0) + continue; + int yy = YMARGIN + h - h * y / fMax; + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + } + + if(!vSamplesIn.empty()) { + QPainterPath p; + paintPath(p, vSamplesIn); + painter.fillPath(p, QColor(0, 255, 0, 128)); + painter.setPen(Qt::green); + painter.drawPath(p); + } + if(!vSamplesOut.empty()) { + QPainterPath p; + paintPath(p, vSamplesOut); + painter.fillPath(p, QColor(255, 0, 0, 128)); + painter.setPen(Qt::red); + painter.drawPath(p); + } +} + +void TrafficGraphWidget::updateRates() +{ + if(!clientModel) return; + + quint64 bytesIn = clientModel->getTotalBytesRecv(), + bytesOut = clientModel->getTotalBytesSent(); + float inRate = (bytesIn - nLastBytesIn) / 1024.0f * 1000 / timer->interval(); + float outRate = (bytesOut - nLastBytesOut) / 1024.0f * 1000 / timer->interval(); + vSamplesIn.push_front(inRate); + vSamplesOut.push_front(outRate); + nLastBytesIn = bytesIn; + nLastBytesOut = bytesOut; + + while(vSamplesIn.size() > DESIRED_SAMPLES) { + vSamplesIn.pop_back(); + } + while(vSamplesOut.size() > DESIRED_SAMPLES) { + vSamplesOut.pop_back(); + } + + float tmax = 0.0f; + foreach(float f, vSamplesIn) { + if(f > tmax) tmax = f; + } + foreach(float f, vSamplesOut) { + if(f > tmax) tmax = f; + } + fMax = tmax; + update(); +} + +void TrafficGraphWidget::setGraphRangeMins(int mins) +{ + nMins = mins; + int msecsPerSample = nMins * 60 * 1000 / DESIRED_SAMPLES; + timer->stop(); + timer->setInterval(msecsPerSample); + + clear(); +} + +void TrafficGraphWidget::clear() +{ + timer->stop(); + + vSamplesOut.clear(); + vSamplesIn.clear(); + fMax = 0.0f; + + if(clientModel) { + nLastBytesIn = clientModel->getTotalBytesRecv(); + nLastBytesOut = clientModel->getTotalBytesSent(); + } + timer->start(); +} diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h new file mode 100644 index 0000000000..b31d1d5b0a --- /dev/null +++ b/src/qt/trafficgraphwidget.h @@ -0,0 +1,44 @@ +#ifndef TRAFFICGRAPHWIDGET_H +#define TRAFFICGRAPHWIDGET_H + +#include <QWidget> +#include <QQueue> + +class ClientModel; + +QT_BEGIN_NAMESPACE +class QPaintEvent; +class QTimer; +QT_END_NAMESPACE + +class TrafficGraphWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrafficGraphWidget(QWidget *parent = 0); + void setClientModel(ClientModel *model); + int getGraphRangeMins() const; + +protected: + void paintEvent(QPaintEvent *); + +public slots: + void updateRates(); + void setGraphRangeMins(int mins); + void clear(); + +private: + void paintPath(QPainterPath &path, QQueue<float> &samples); + + QTimer *timer; + float fMax; + int nMins; + QQueue<float> vSamplesIn; + QQueue<float> vSamplesOut; + quint64 nLastBytesIn; + quint64 nLastBytesOut; + ClientModel *clientModel; +}; + +#endif // TRAFFICGRAPHWIDGET_H diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 25ff3623c0..93fc8cab22 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -8,6 +8,7 @@ #include "ui_interface.h" #include "base58.h" #include "paymentserver.h" +#include "transactionrecord.h" #include <string> @@ -16,7 +17,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) if (!IsFinalTx(wtx)) { if (wtx.nLockTime < LOCKTIME_THRESHOLD) - return tr("Open for %n more block(s)", "", wtx.nLockTime - nBestHeight + 1); + return tr("Open for %n more block(s)", "", wtx.nLockTime - chainActive.Height() + 1); else return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.nLockTime)); } @@ -32,7 +33,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) } } -QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) +QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int unit) { QString strHTML; @@ -129,7 +130,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) nUnmatured += wallet->GetCredit(txout); strHTML += "<b>" + tr("Credit") + ":</b> "; if (wtx.IsInMainChain()) - strHTML += BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; + strHTML += BitcoinUnits::formatWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; else strHTML += "(" + tr("not accepted") + ")"; strHTML += "<br>"; @@ -139,7 +140,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // // Credit // - strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet) + "<br>"; + strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet) + "<br>"; } else { @@ -175,7 +176,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) } } - strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -txout.nValue) + "<br>"; + strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -txout.nValue) + "<br>"; } if (fAllToMe) @@ -183,13 +184,13 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // Payment to self int64 nChange = wtx.GetChange(); int64 nValue = nCredit - nChange; - strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nValue) + "<br>"; - strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nValue) + "<br>"; + strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nValue) + "<br>"; + strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nValue) + "<br>"; } int64 nTxFee = nDebit - GetValueOut(wtx); if (nTxFee > 0) - strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -nTxFee) + "<br>"; + strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -nTxFee) + "<br>"; } else { @@ -198,14 +199,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) // BOOST_FOREACH(const CTxIn& txin, wtx.vin) if (wallet->IsMine(txin)) - strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -wallet->GetDebit(txin)) + "<br>"; + strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin)) + "<br>"; BOOST_FOREACH(const CTxOut& txout, wtx.vout) if (wallet->IsMine(txout)) - strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, wallet->GetCredit(txout)) + "<br>"; + strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout)) + "<br>"; } } - strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, nNet, true) + "<br>"; + strHTML += "<b>" + tr("Net amount") + ":</b> " + BitcoinUnits::formatWithUnit(unit, nNet, true) + "<br>"; // // Message @@ -215,7 +216,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) strHTML += "<br><b>" + tr("Comment") + ":</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "<br>"; - strHTML += "<b>" + tr("Transaction ID") + ":</b> " + wtx.GetHash().ToString().c_str() + "<br>"; + strHTML += "<b>" + tr("Transaction ID") + ":</b> " + TransactionRecord::formatSubTxId(wtx.GetHash(), vout) + "<br>"; // // PaymentRequest info: @@ -225,7 +226,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) if (r.first == "PaymentRequest") { PaymentRequestPlus req; - req.parse(QByteArray::fromRawData(r.second.c_str(), r.second.size())); + req.parse(QByteArray::fromRawData(r.second.data(), r.second.size())); QString merchant; if (req.getMerchant(PaymentServer::getCertStore(), merchant)) strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>"; @@ -243,10 +244,10 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) strHTML += "<hr><br>" + tr("Debug information") + "<br><br>"; BOOST_FOREACH(const CTxIn& txin, wtx.vin) if(wallet->IsMine(txin)) - strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, -wallet->GetDebit(txin)) + "<br>"; + strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, -wallet->GetDebit(txin)) + "<br>"; BOOST_FOREACH(const CTxOut& txout, wtx.vout) if(wallet->IsMine(txout)) - strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, wallet->GetCredit(txout)) + "<br>"; + strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatWithUnit(unit, wallet->GetCredit(txout)) + "<br>"; strHTML += "<br><b>" + tr("Transaction") + ":</b><br>"; strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true); @@ -274,7 +275,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " "; strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); } - strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC, vout.nValue); + strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatWithUnit(unit, vout.nValue); strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) ? tr("true") : tr("false")) + "</li>"; } } diff --git a/src/qt/transactiondesc.h b/src/qt/transactiondesc.h index cb0dda5b58..8b3684e961 100644 --- a/src/qt/transactiondesc.h +++ b/src/qt/transactiondesc.h @@ -14,7 +14,7 @@ class TransactionDesc: public QObject Q_OBJECT public: - static QString toHTML(CWallet *wallet, CWalletTx &wtx); + static QString toHTML(CWallet *wallet, CWalletTx &wtx, int vout, int unit); private: TransactionDesc() {} diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index e954508769..162908a9a4 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -160,14 +160,14 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) idx); status.confirmed = wtx.IsConfirmed(); status.depth = wtx.GetDepthInMainChain(); - status.cur_num_blocks = nBestHeight; + status.cur_num_blocks = chainActive.Height(); if (!IsFinalTx(wtx)) { if (wtx.nLockTime < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; - status.open_for = wtx.nLockTime - nBestHeight + 1; + status.open_for = wtx.nLockTime - chainActive.Height() + 1; } else { @@ -221,11 +221,16 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) bool TransactionRecord::statusUpdateNeeded() { - return status.cur_num_blocks != nBestHeight; + return status.cur_num_blocks != chainActive.Height(); } -std::string TransactionRecord::getTxID() +QString TransactionRecord::getTxID() const { - return hash.ToString() + strprintf("-%03d", idx); + return formatSubTxId(hash, idx); +} + +QString TransactionRecord::formatSubTxId(const uint256 &hash, int vout) +{ + return QString::fromStdString(hash.ToString() + strprintf("-%03d", vout)); } diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index d760d47c89..480e7a7f2c 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -4,6 +4,7 @@ #include "uint256.h" #include <QList> +#include <QString> class CWallet; class CWalletTx; @@ -117,7 +118,10 @@ public: TransactionStatus status; /** Return the unique identifier for this transaction (part) */ - std::string getTxID(); + QString getTxID() const; + + /** Format subtransaction id */ + static QString formatSubTxId(const uint256 &hash, int vout); /** Update status from core wallet tx. */ diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index baf1e16483..6f7a5933ab 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -17,14 +17,15 @@ #include <QTimer> #include <QIcon> #include <QDateTime> +#include <QDebug> // Amount column is right-aligned it contains numbers static int column_alignments[] = { - Qt::AlignLeft|Qt::AlignVCenter, - Qt::AlignLeft|Qt::AlignVCenter, - Qt::AlignLeft|Qt::AlignVCenter, - Qt::AlignLeft|Qt::AlignVCenter, - Qt::AlignRight|Qt::AlignVCenter + Qt::AlignLeft|Qt::AlignVCenter, /* status */ + Qt::AlignLeft|Qt::AlignVCenter, /* date */ + Qt::AlignLeft|Qt::AlignVCenter, /* type */ + Qt::AlignLeft|Qt::AlignVCenter, /* address */ + Qt::AlignRight|Qt::AlignVCenter /* amount */ }; // Comparison operator for sort/binary search of model tx list @@ -48,11 +49,12 @@ struct TxLessThan class TransactionTablePriv { public: - TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent): - wallet(wallet), - parent(parent) + TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent) : + wallet(wallet), + parent(parent) { } + CWallet *wallet; TransactionTableModel *parent; @@ -66,7 +68,7 @@ public: */ void refreshWallet() { - OutputDebugStringF("refreshWallet\n"); + qDebug() << "TransactionTablePriv::refreshWallet"; cachedWallet.clear(); { LOCK(wallet->cs_wallet); @@ -85,7 +87,7 @@ public: */ void updateWallet(const uint256 &hash, int status) { - OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status); + qDebug() << "TransactionTablePriv::updateWallet : " + QString::fromStdString(hash.ToString()) + " " + QString::number(status); { LOCK(wallet->cs_wallet); @@ -113,20 +115,21 @@ public: status = CT_DELETED; /* In model, but want to hide, treat as deleted */ } - OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n", - inWallet, inModel, lowerIndex, upperIndex, showTransaction, status); + qDebug() << " inWallet=" + QString::number(inWallet) + " inModel=" + QString::number(inModel) + + " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) + + " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status); switch(status) { case CT_NEW: if(inModel) { - OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n"); + qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model"; break; } if(!inWallet) { - OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n"); + qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is not in wallet"; break; } if(showTransaction) @@ -150,7 +153,7 @@ public: case CT_DELETED: if(!inModel) { - OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n"); + qDebug() << "TransactionTablePriv::updateWallet : Warning: Got CT_DELETED, but transaction is not in model"; break; } // Removed -- remove entire transaction from table @@ -200,19 +203,18 @@ public: } } - QString describe(TransactionRecord *rec) + QString describe(TransactionRecord *rec, int unit) { { LOCK(wallet->cs_wallet); std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash); if(mi != wallet->mapWallet.end()) { - return TransactionDesc::toHTML(wallet, mi->second); + return TransactionDesc::toHTML(wallet, mi->second, rec->idx, unit); } } return QString(""); } - }; TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent): @@ -248,9 +250,9 @@ void TransactionTableModel::updateTransaction(const QString &hash, int status) void TransactionTableModel::updateConfirmations() { - if(nBestHeight != cachedNumBlocks) + if(chainActive.Height() != cachedNumBlocks) { - cachedNumBlocks = nBestHeight; + cachedNumBlocks = chainActive.Height(); // Blocks came in since last poll. // Invalidate status (number of confirmations) and (possibly) description // for all rows. Qt is smart enough to only actually request the data for the @@ -561,7 +563,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const case DateRole: return QDateTime::fromTime_t(static_cast<uint>(rec->time)); case LongDescriptionRole: - return priv->describe(rec); + return priv->describe(rec, walletModel->getOptionsModel()->getDisplayUnit()); case AddressRole: return QString::fromStdString(rec->address); case LabelRole: @@ -569,7 +571,7 @@ QVariant TransactionTableModel::data(const QModelIndex &index, int role) const case AmountRole: return rec->credit + rec->debit; case TxIDRole: - return QString::fromStdString(rec->getTxID()); + return rec->getTxID(); case ConfirmedRole: // Return True if transaction counts for balance return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 99a6647a65..8d6a1b387e 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -56,12 +56,9 @@ bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient) void WalletFrame::showOutOfSyncWarning(bool fShow) { - if (!walletStack) { - QMessageBox box; - box.setText("walletStack is null"); - box.exec(); + if (!walletStack) return; - } + walletStack->showOutOfSyncWarning(fShow); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 61357647b7..417bac9928 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -5,12 +5,12 @@ #include "transactiontablemodel.h" #include "ui_interface.h" -#include "wallet.h" #include "walletdb.h" // for BackupWallet #include "base58.h" #include <QSet> #include <QTimer> +#include <QDebug> WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), @@ -73,10 +73,10 @@ void WalletModel::updateStatus() void WalletModel::pollBalanceChanged() { - if(nBestHeight != cachedNumBlocks) + if(chainActive.Height() != cachedNumBlocks) { // Balance and number of transactions might have changed - cachedNumBlocks = nBestHeight; + cachedNumBlocks = chainActive.Height(); checkBalanceChanged(); } } @@ -112,10 +112,11 @@ void WalletModel::updateTransaction(const QString &hash, int status) } } -void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) +void WalletModel::updateAddressBook(const QString &address, const QString &label, + bool isMine, const QString &purpose, int status) { if(addressTableModel) - addressTableModel->updateEntry(address, label, isMine, status); + addressTableModel->updateEntry(address, label, isMine, purpose, status); } bool WalletModel::validateAddress(const QString &address) @@ -124,11 +125,11 @@ bool WalletModel::validateAddress(const QString &address) return addressParsed.IsValid(); } -WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipient> &recipients) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction) { qint64 total = 0; + QList<SendCoinsRecipient> recipients = transaction.getRecipients(); std::vector<std::pair<CScript, int64> > vecSend; - QByteArray transaction; if(recipients.empty()) { @@ -142,7 +143,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie foreach(const SendCoinsRecipient &rcp, recipients) { if (rcp.paymentRequest.IsInitialized()) - { // PaymentRequest... + { // PaymentRequest... int64 subtotal = 0; const payments::PaymentDetails& details = rcp.paymentRequest.getDetails(); for (int i = 0; i < details.outputs_size(); i++) @@ -192,81 +193,97 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie if((total + nTransactionFee) > getBalance()) { - return SendCoinsReturn(AmountWithFeeExceedsBalance, nTransactionFee); + transaction.setTransactionFee(nTransactionFee); + return SendCoinsReturn(AmountWithFeeExceedsBalance); } { LOCK2(cs_main, wallet->cs_wallet); - CReserveKey keyChange(wallet); + transaction.newPossibleKeyChange(wallet); int64 nFeeRequired = 0; std::string strFailReason; - CWalletTx wtx; - bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); + + CWalletTx *newTx = transaction.getTransaction(); + CReserveKey *keyChange = transaction.getPossibleKeyChange(); + bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason); + transaction.setTransactionFee(nFeeRequired); if(!fCreated) { if((total + nFeeRequired) > wallet->GetBalance()) { - return SendCoinsReturn(AmountWithFeeExceedsBalance, nFeeRequired); + return SendCoinsReturn(AmountWithFeeExceedsBalance); } emit message(tr("Send Coins"), QString::fromStdString(strFailReason), CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } + } + + return SendCoinsReturn(OK); +} + +WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &transaction) +{ + QByteArray transaction_array; /* store serialized transaction */ + + { + LOCK2(cs_main, wallet->cs_wallet); + CWalletTx *newTx = transaction.getTransaction(); + // Store PaymentRequests in wtx.vOrderForm in wallet. - foreach(const SendCoinsRecipient &rcp, recipients) + foreach(const SendCoinsRecipient &rcp, transaction.getRecipients()) { if (rcp.paymentRequest.IsInitialized()) { std::string key("PaymentRequest"); std::string value; rcp.paymentRequest.SerializeToString(&value); - wtx.vOrderForm.push_back(make_pair(key, value)); + newTx->vOrderForm.push_back(make_pair(key, value)); } - } - - if(!uiInterface.ThreadSafeAskFee(nFeeRequired)) - { - return Aborted; } - if(!wallet->CommitTransaction(wtx, keyChange)) - { + + CReserveKey *keyChange = transaction.getPossibleKeyChange(); + if(!wallet->CommitTransaction(*newTx, *keyChange)) return TransactionCommitFailed; - } - CTransaction* t = (CTransaction*)&wtx; + CTransaction* t = (CTransaction*)newTx; CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << *t; - transaction.append(&(ssTx[0]), ssTx.size()); + transaction_array.append(&(ssTx[0]), ssTx.size()); } // Add addresses / update labels that we've sent to to the address book, - // and emit coinsSent signal - foreach(const SendCoinsRecipient &rcp, recipients) + // and emit coinsSent signal for each recipient + foreach(const SendCoinsRecipient &rcp, transaction.getRecipients()) { - std::string strAddress = rcp.address.toStdString(); - CTxDestination dest = CBitcoinAddress(strAddress).Get(); - std::string strLabel = rcp.label.toStdString(); + // Don't touch the address book when we have a secure payment-request + if (rcp.authenticatedMerchant.isEmpty()) { - LOCK(wallet->cs_wallet); - - std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest); - - // Check if we have a new address or an updated label - if (mi == wallet->mapAddressBook.end()) - { - wallet->SetAddressBook(dest, strLabel, "send"); - } - else if (mi->second.name != strLabel) + std::string strAddress = rcp.address.toStdString(); + CTxDestination dest = CBitcoinAddress(strAddress).Get(); + std::string strLabel = rcp.label.toStdString(); { - wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + LOCK(wallet->cs_wallet); + + std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest); + + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end()) + { + wallet->SetAddressBook(dest, strLabel, "send"); + } + else if (mi->second.name != strLabel) + { + wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + } } } - emit coinsSent(wallet, rcp, transaction); + emit coinsSent(wallet, rcp, transaction_array); } - return SendCoinsReturn(OK, 0); + return SendCoinsReturn(OK); } OptionsModel *WalletModel::getOptionsModel() @@ -347,25 +364,34 @@ bool WalletModel::backupWallet(const QString &filename) // Handlers for core signals static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) { - OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + qDebug() << "NotifyKeyStoreStatusChanged"; QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); } -static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status) +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, + const CTxDestination &address, const std::string &label, bool isMine, + const std::string &purpose, ChangeType status) { - OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status); + QString strAddress = QString::fromStdString(CBitcoinAddress(address).ToString()); + QString strLabel = QString::fromStdString(label); + QString strPurpose = QString::fromStdString(purpose); + + qDebug() << "NotifyAddressBookChanged : " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(CBitcoinAddress(address).ToString())), - Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(QString, strAddress), + Q_ARG(QString, strLabel), Q_ARG(bool, isMine), + Q_ARG(QString, strPurpose), Q_ARG(int, status)); } static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) { - OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + QString strHash = QString::fromStdString(hash.GetHex()); + + qDebug() << "NotifyTransactionChanged : " + strHash + " status= " + QString::number(status); QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(QString, strHash), Q_ARG(int, status)); } @@ -373,7 +399,7 @@ void WalletModel::subscribeToCoreSignals() { // Connect signals to wallet wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); } @@ -381,7 +407,7 @@ void WalletModel::unsubscribeFromCoreSignals() { // Disconnect signals from wallet wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); - wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5, _6)); wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 8cba10f5d2..6abcdaf8cb 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -4,12 +4,15 @@ #include <QObject> #include "allocators.h" /* for SecureString */ +#include "wallet.h" +#include "walletmodeltransaction.h" #include "paymentrequestplus.h" class OptionsModel; class AddressTableModel; class TransactionTableModel; class CWallet; +class WalletModelTransaction; QT_BEGIN_NAMESPACE class QTimer; @@ -74,15 +77,16 @@ public: // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { - SendCoinsReturn(StatusCode status, - qint64 fee=0): - status(status), fee(fee) {} + SendCoinsReturn(StatusCode status): + status(status) {} StatusCode status; - qint64 fee; // is used in case status is "AmountWithFeeExceedsBalance" }; + // prepare transaction for getting txfee before sending coins + SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction); + // Send coins to a list of recipients - SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients); + SendCoinsReturn sendCoins(WalletModelTransaction &transaction); // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); @@ -165,7 +169,7 @@ public slots: /* New transaction, or transaction changed status */ void updateTransaction(const QString &hash, int status); /* New, updated or removed address book entry */ - void updateAddressBook(const QString &address, const QString &label, bool isMine, int status); + void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status); /* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ void pollBalanceChanged(); }; diff --git a/src/qt/walletmodeltransaction.cpp b/src/qt/walletmodeltransaction.cpp new file mode 100644 index 0000000000..706ed60b77 --- /dev/null +++ b/src/qt/walletmodeltransaction.cpp @@ -0,0 +1,56 @@ +#include "walletmodeltransaction.h" + +WalletModelTransaction::WalletModelTransaction(const QList<SendCoinsRecipient> &recipients) : + recipients(recipients), + walletTransaction(0), + keyChange(0), + fee(0) +{ + walletTransaction = new CWalletTx(); +} + +WalletModelTransaction::~WalletModelTransaction() +{ + delete keyChange; + delete walletTransaction; +} + +QList<SendCoinsRecipient> WalletModelTransaction::getRecipients() +{ + return recipients; +} + +CWalletTx *WalletModelTransaction::getTransaction() +{ + return walletTransaction; +} + +qint64 WalletModelTransaction::getTransactionFee() +{ + return fee; +} + +void WalletModelTransaction::setTransactionFee(qint64 newFee) +{ + fee = newFee; +} + +qint64 WalletModelTransaction::getTotalTransactionAmount() +{ + qint64 totalTransactionAmount = 0; + foreach(const SendCoinsRecipient &rcp, recipients) + { + totalTransactionAmount += rcp.amount; + } + return totalTransactionAmount; +} + +void WalletModelTransaction::newPossibleKeyChange(CWallet *wallet) +{ + keyChange = new CReserveKey(wallet); +} + +CReserveKey *WalletModelTransaction::getPossibleKeyChange() +{ + return keyChange; +} diff --git a/src/qt/walletmodeltransaction.h b/src/qt/walletmodeltransaction.h new file mode 100644 index 0000000000..c4848fb12d --- /dev/null +++ b/src/qt/walletmodeltransaction.h @@ -0,0 +1,37 @@ +#ifndef WALLETMODELTRANSACTION_H +#define WALLETMODELTRANSACTION_H + +#include "walletmodel.h" + +class SendCoinsRecipient; + +/** Data model for a walletmodel transaction. */ +class WalletModelTransaction +{ +public: + explicit WalletModelTransaction(const QList<SendCoinsRecipient> &recipients); + ~WalletModelTransaction(); + + QList<SendCoinsRecipient> getRecipients(); + + CWalletTx *getTransaction(); + + void setTransactionFee(qint64 newFee); + qint64 getTransactionFee(); + + qint64 getTotalTransactionAmount(); + + void newPossibleKeyChange(CWallet *wallet); + CReserveKey *getPossibleKeyChange(); + +private: + const QList<SendCoinsRecipient> recipients; + CWalletTx *walletTransaction; + CReserveKey *keyChange; + qint64 fee; + +public slots: + +}; + +#endif // WALLETMODELTRANSACTION_H |