Created
January 19, 2016 03:32
-
-
Save brandonrobertz/6096791b98b76b025410 to your computer and use it in GitHub Desktop.
Namecoin Core QT Rebase/Merge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include | |
index 96123e4..eeaabeb 100644 | |
--- a/src/Makefile.qt.include | |
+++ b/src/Makefile.qt.include | |
@@ -96,6 +96,8 @@ QT_FORMS_UI = \ | |
qt/forms/debugwindow.ui \ | |
qt/forms/sendcoinsdialog.ui \ | |
qt/forms/sendcoinsentry.ui \ | |
+ qt/forms/managenamespage.ui \ | |
+ qt/forms/configurenamedialog.ui \ | |
qt/forms/signverifymessagedialog.ui \ | |
qt/forms/transactiondescdialog.ui | |
@@ -111,12 +113,15 @@ QT_MOC_CPP = \ | |
qt/moc_clientmodel.cpp \ | |
qt/moc_coincontroldialog.cpp \ | |
qt/moc_coincontroltreewidget.cpp \ | |
+ qt/moc_configurenamedialog.cpp \ | |
qt/moc_csvmodelwriter.cpp \ | |
qt/moc_editaddressdialog.cpp \ | |
qt/moc_guiutil.cpp \ | |
qt/moc_intro.cpp \ | |
qt/moc_macdockiconhandler.cpp \ | |
qt/moc_macnotificationhandler.cpp \ | |
+ qt/moc_managenamespage.cpp \ | |
+ qt/moc_nametablemodel.cpp \ | |
qt/moc_notificator.cpp \ | |
qt/moc_openuridialog.cpp \ | |
qt/moc_optionsdialog.cpp \ | |
@@ -178,14 +183,17 @@ BITCOIN_QT_H = \ | |
qt/coincontroldialog.h \ | |
qt/coincontroltreewidget.h \ | |
qt/csvmodelwriter.h \ | |
+ qt/configurenamedialog.h \ | |
qt/editaddressdialog.h \ | |
qt/guiconstants.h \ | |
qt/guiutil.h \ | |
qt/intro.h \ | |
qt/macdockiconhandler.h \ | |
qt/macnotificationhandler.h \ | |
+ qt/managenamespage.h \ | |
qt/networkstyle.h \ | |
qt/notificator.h \ | |
+ qt/nametablemodel.h \ | |
qt/openuridialog.h \ | |
qt/optionsdialog.h \ | |
qt/optionsmodel.h \ | |
@@ -202,6 +210,9 @@ BITCOIN_QT_H = \ | |
qt/rpcconsole.h \ | |
qt/sendcoinsdialog.h \ | |
qt/sendcoinsentry.h \ | |
+ qt/managenamespage.h \ | |
+ qt/configurenamedialog.h \ | |
+ qt/nametablemodel.h \ | |
qt/signverifymessagedialog.h \ | |
qt/splashscreen.h \ | |
qt/trafficgraphwidget.h \ | |
@@ -311,6 +322,9 @@ BITCOIN_QT_CPP += \ | |
qt/recentrequeststablemodel.cpp \ | |
qt/sendcoinsdialog.cpp \ | |
qt/sendcoinsentry.cpp \ | |
+ qt/managenamespage.cpp \ | |
+ qt/configurenamedialog.cpp \ | |
+ qt/nametablemodel.cpp \ | |
qt/signverifymessagedialog.cpp \ | |
qt/transactiondesc.cpp \ | |
qt/transactiondescdialog.cpp \ | |
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp | |
index 62aa0f0..2a08a3a 100644 | |
--- a/src/qt/askpassphrasedialog.cpp | |
+++ b/src/qt/askpassphrasedialog.cpp | |
@@ -15,7 +15,7 @@ | |
#include <QPushButton> | |
AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget *parent) : | |
- QDialog(parent), | |
+ QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint), | |
ui(new Ui::AskPassphraseDialog), | |
mode(mode), | |
model(0), | |
diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc | |
index c899e95..27b66ed 100644 | |
--- a/src/qt/bitcoin.qrc | |
+++ b/src/qt/bitcoin.qrc | |
@@ -35,6 +35,7 @@ | |
<file alias="tx_input">res/icons/tx_input.png</file> | |
<file alias="tx_output">res/icons/tx_output.png</file> | |
<file alias="tx_inout">res/icons/tx_inout.png</file> | |
+ <file alias="tx_nameop">res/icons/tx_nameop.png</file> | |
<file alias="lock_closed">res/icons/lock_closed.png</file> | |
<file alias="lock_open">res/icons/lock_open.png</file> | |
<file alias="key">res/icons/key.png</file> | |
diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp | |
index d712705..3a792ff 100644 | |
--- a/src/qt/bitcoinaddressvalidator.cpp | |
+++ b/src/qt/bitcoinaddressvalidator.cpp | |
@@ -15,8 +15,8 @@ | |
- All lower-case letters except for 'l' | |
*/ | |
-BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent) : | |
- QValidator(parent) | |
+BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent, bool fAllowEmpty) : | |
+ QValidator(parent), allowEmpty(fAllowEmpty) | |
{ | |
} | |
@@ -25,7 +25,7 @@ QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &po | |
Q_UNUSED(pos); | |
// Empty address is "intermediate" input | |
- if (input.isEmpty()) | |
+ if (input.isEmpty() && !allowEmpty) | |
return QValidator::Intermediate; | |
// Correction | |
diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h | |
index 30d4a26..7c6ab63 100644 | |
--- a/src/qt/bitcoinaddressvalidator.h | |
+++ b/src/qt/bitcoinaddressvalidator.h | |
@@ -15,9 +15,12 @@ class BitcoinAddressEntryValidator : public QValidator | |
Q_OBJECT | |
public: | |
- explicit BitcoinAddressEntryValidator(QObject *parent); | |
+ explicit BitcoinAddressEntryValidator(QObject *parent, bool fAllowEmpty = false); | |
State validate(QString &input, int &pos) const; | |
+ | |
+private: | |
+ bool allowEmpty; | |
}; | |
/** Bitcoin address widget validator, checks for a valid bitcoin address. | |
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp | |
index 99efdf3..2789182 100644 | |
--- a/src/qt/bitcoingui.cpp | |
+++ b/src/qt/bitcoingui.cpp | |
@@ -8,6 +8,7 @@ | |
#include "clientmodel.h" | |
#include "guiconstants.h" | |
#include "guiutil.h" | |
+#include "managenamespage.h" | |
#include "networkstyle.h" | |
#include "notificator.h" | |
#include "openuridialog.h" | |
@@ -292,6 +293,17 @@ void BitcoinGUI::createActions() | |
historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); | |
tabGroup->addAction(historyAction); | |
+ manageNamesAction = new QAction(platformStyle->SingleColorIcon(":/icons/bitcoin"), tr("&Manage Names"), this); | |
+ manageNamesAction->setStatusTip(tr("Manage names registered via Namecoin")); | |
+ manageNamesAction->setToolTip(manageNamesAction->statusTip()); | |
+ manageNamesAction->setCheckable(true); | |
+ manageNamesAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_6)); | |
+ tabGroup->addAction(manageNamesAction); | |
+ | |
+ manageNamesMenuAction = new QAction(platformStyle->TextColorIcon(":/icons/bitcoin"), manageNamesAction->text(), this); | |
+ manageNamesMenuAction->setStatusTip(manageNamesAction->statusTip()); | |
+ manageNamesMenuAction->setToolTip(manageNamesMenuAction->statusTip()); | |
+ | |
#ifdef ENABLE_WALLET | |
// These showNormalIfMinimized are needed because Send Coins and Receive Coins | |
// can be triggered from the tray menu, and need to show the GUI to be useful. | |
@@ -307,6 +319,8 @@ void BitcoinGUI::createActions() | |
connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); | |
connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); | |
connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); | |
+ connect(manageNamesAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); | |
+ connect(manageNamesAction, SIGNAL(triggered()), this, SLOT(gotoManageNamesPage())); | |
#endif // ENABLE_WALLET | |
quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"), tr("E&xit"), this); | |
@@ -436,6 +450,7 @@ void BitcoinGUI::createToolBars() | |
toolbar->addAction(sendCoinsAction); | |
toolbar->addAction(receiveCoinsAction); | |
toolbar->addAction(historyAction); | |
+ toolbar->addAction(manageNamesAction); | |
overviewAction->setChecked(true); | |
} | |
} | |
@@ -561,6 +576,7 @@ void BitcoinGUI::createTrayIconMenu() | |
trayIconMenu->addSeparator(); | |
trayIconMenu->addAction(sendCoinsMenuAction); | |
trayIconMenu->addAction(receiveCoinsMenuAction); | |
+ trayIconMenu->addAction(manageNamesMenuAction); | |
trayIconMenu->addSeparator(); | |
trayIconMenu->addAction(signMessageAction); | |
trayIconMenu->addAction(verifyMessageAction); | |
@@ -656,6 +672,12 @@ void BitcoinGUI::gotoSendCoinsPage(QString addr) | |
if (walletFrame) walletFrame->gotoSendCoinsPage(addr); | |
} | |
+void BitcoinGUI::gotoManageNamesPage() | |
+{ | |
+ manageNamesAction->setChecked(true); | |
+ if (walletFrame) walletFrame->gotoManageNamesPage(); | |
+} | |
+ | |
void BitcoinGUI::gotoSignMessageTab(QString addr) | |
{ | |
if (walletFrame) walletFrame->gotoSignMessageTab(addr); | |
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h | |
index 871ca1b..57b8623 100644 | |
--- a/src/qt/bitcoingui.h | |
+++ b/src/qt/bitcoingui.h | |
@@ -96,6 +96,8 @@ private: | |
QAction *sendCoinsMenuAction; | |
QAction *usedSendingAddressesAction; | |
QAction *usedReceivingAddressesAction; | |
+ QAction *manageNamesAction; | |
+ QAction *manageNamesMenuAction; | |
QAction *signMessageAction; | |
QAction *verifyMessageAction; | |
QAction *aboutAction; | |
@@ -184,6 +186,8 @@ private Q_SLOTS: | |
void gotoReceiveCoinsPage(); | |
/** Switch to send coins page */ | |
void gotoSendCoinsPage(QString addr = ""); | |
+ /** Switch to manage names page */ | |
+ void gotoManageNamesPage(); | |
/** Show Sign/Verify Message dialog and switch to sign message tab */ | |
void gotoSignMessageTab(QString addr = ""); | |
diff --git a/src/qt/configurenamedialog.cpp b/src/qt/configurenamedialog.cpp | |
new file mode 100644 | |
index 0000000..1c1b911 | |
--- /dev/null | |
+++ b/src/qt/configurenamedialog.cpp | |
@@ -0,0 +1,150 @@ | |
+#include "configurenamedialog.h" | |
+#include "ui_configurenamedialog.h" | |
+ | |
+#include "guiutil.h" | |
+#include "addressbookpage.h" | |
+#include "walletmodel.h" | |
+#include "names/main.h" | |
+#include "wallet/wallet.h" | |
+#include "platformstyle.h" | |
+ | |
+#include <QMessageBox> | |
+#include <QClipboard> | |
+ | |
+ConfigureNameDialog::ConfigureNameDialog(const QString &_name, const QString &data, bool _firstUpdate, QWidget *parent) : | |
+ QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint), | |
+ name(_name), | |
+ firstUpdate(_firstUpdate), | |
+ ui(new Ui::ConfigureNameDialog) | |
+{ | |
+ ui->setupUi(this); | |
+ | |
+#ifdef Q_OS_MAC | |
+ ui->transferToLayout->setSpacing(4); | |
+#endif | |
+ | |
+ GUIUtil::setupAddressWidget(ui->transferTo, this, true); | |
+ | |
+ ui->labelName->setText(name); | |
+ ui->dataEdit->setText(data); | |
+ | |
+ returnData = data; | |
+ | |
+ if (name.startsWith("d/")) | |
+ ui->labelDomain->setText(name.mid(2) + ".bit"); | |
+ else | |
+ ui->labelDomain->setText(tr("(not a domain name)")); | |
+ | |
+ if (firstUpdate) | |
+ { | |
+ ui->labelTransferTo->hide(); | |
+ ui->labelTransferToHint->hide(); | |
+ ui->transferTo->hide(); | |
+ ui->addressBookButton->hide(); | |
+ ui->pasteButton->hide(); | |
+ ui->labelSubmitHint->setText( | |
+ tr("Name_firstupdate transaction will be queued and broadcasted when corresponding name_new is %1 blocks old") | |
+ .arg(MIN_FIRSTUPDATE_DEPTH)); | |
+ } | |
+ else | |
+ { | |
+ ui->labelSubmitHint->setText(tr("Name_update transaction will be issued immediately")); | |
+ setWindowTitle(tr("Update Name")); | |
+ } | |
+} | |
+ | |
+ConfigureNameDialog::~ConfigureNameDialog() | |
+{ | |
+ delete ui; | |
+} | |
+ | |
+void ConfigureNameDialog::accept() | |
+{ | |
+ if (!walletModel) | |
+ return; | |
+ | |
+ QString addr; | |
+ if (!firstUpdate) | |
+ { | |
+ if (!ui->transferTo->hasAcceptableInput()) | |
+ { | |
+ ui->transferTo->setValid(false); | |
+ return; | |
+ } | |
+ | |
+ addr = ui->transferTo->text(); | |
+ | |
+ if (addr != "" && !walletModel->validateAddress(addr)) | |
+ { | |
+ ui->transferTo->setValid(false); | |
+ return; | |
+ } | |
+ } | |
+ | |
+ WalletModel::UnlockContext ctx(walletModel->requestUnlock()); | |
+ if (!ctx.isValid()) | |
+ return; | |
+ | |
+ returnData = ui->dataEdit->text(); | |
+ QString err_msg; | |
+ try | |
+ { | |
+ if (firstUpdate) | |
+ err_msg = walletModel->nameFirstUpdatePrepare(name, returnData); | |
+ else | |
+ err_msg = walletModel->nameUpdate(name, returnData, addr); | |
+ } | |
+ catch (std::exception& e) | |
+ { | |
+ err_msg = e.what(); | |
+ } | |
+ | |
+ if (!err_msg.isEmpty()) | |
+ { | |
+ if (err_msg == "ABORTED") | |
+ return; | |
+ | |
+ QMessageBox::critical(this, tr("Name update error"), err_msg); | |
+ return; | |
+ } | |
+ | |
+ QDialog::accept(); | |
+} | |
+ | |
+void ConfigureNameDialog::reject() | |
+{ | |
+ QDialog::reject(); | |
+} | |
+ | |
+void ConfigureNameDialog::setModel(WalletModel *walletModel) | |
+{ | |
+ this->walletModel = walletModel; | |
+} | |
+ | |
+void ConfigureNameDialog::on_pasteButton_clicked() | |
+{ | |
+ // Paste text from clipboard into recipient field | |
+ ui->transferTo->setText(QApplication::clipboard()->text()); | |
+} | |
+ | |
+void ConfigureNameDialog::on_addressBookButton_clicked() | |
+{ | |
+ if (!walletModel) | |
+ return; | |
+ | |
+ // NOTE: we will need to push this into the parent class creation | |
+ // pretty sure this will result in blank/default options? | |
+ const PlatformStyle *platformStyle; | |
+ AddressBookPage dlg( | |
+ // platformStyle | |
+ platformStyle, | |
+ // mode | |
+ AddressBookPage::ForSelection, | |
+ // tab | |
+ AddressBookPage::SendingTab, | |
+ // *parent | |
+ this); | |
+ dlg.setModel(walletModel->getAddressTableModel()); | |
+ if (dlg.exec()) | |
+ ui->transferTo->setText(dlg.getReturnValue()); | |
+} | |
diff --git a/src/qt/configurenamedialog.h b/src/qt/configurenamedialog.h | |
new file mode 100644 | |
index 0000000..fcd4994 | |
--- /dev/null | |
+++ b/src/qt/configurenamedialog.h | |
@@ -0,0 +1,40 @@ | |
+#ifndef CONFIGURENAMEDIALOG_H | |
+#define CONFIGURENAMEDIALOG_H | |
+ | |
+#include <QDialog> | |
+ | |
+namespace Ui { | |
+ class ConfigureNameDialog; | |
+} | |
+ | |
+class WalletModel; | |
+ | |
+/** Dialog for editing an address and associated information. | |
+ */ | |
+class ConfigureNameDialog : public QDialog | |
+{ | |
+ Q_OBJECT | |
+ | |
+public: | |
+ | |
+ explicit ConfigureNameDialog(const QString &_name, const QString &data, bool _firstUpdate, QWidget *parent = 0); | |
+ ~ConfigureNameDialog(); | |
+ | |
+ void setModel(WalletModel *walletModel); | |
+ const QString &getReturnData() const { return returnData; } | |
+ | |
+public Q_SLOTS: | |
+ void accept(); | |
+ void reject(); | |
+ void on_addressBookButton_clicked(); | |
+ void on_pasteButton_clicked(); | |
+ | |
+private: | |
+ QString returnData; | |
+ Ui::ConfigureNameDialog *ui; | |
+ WalletModel *walletModel; | |
+ QString name; | |
+ bool firstUpdate; | |
+}; | |
+ | |
+#endif // CONFIGURENAMEDIALOG_H | |
diff --git a/src/qt/forms/configurenamedialog.ui b/src/qt/forms/configurenamedialog.ui | |
new file mode 100644 | |
index 0000000..9ca282e | |
--- /dev/null | |
+++ b/src/qt/forms/configurenamedialog.ui | |
@@ -0,0 +1,271 @@ | |
+<?xml version="1.0" encoding="UTF-8"?> | |
+<ui version="4.0"> | |
+ <class>ConfigureNameDialog</class> | |
+ <widget class="QDialog" name="ConfigureNameDialog"> | |
+ <property name="geometry"> | |
+ <rect> | |
+ <x>0</x> | |
+ <y>0</y> | |
+ <width>629</width> | |
+ <height>214</height> | |
+ </rect> | |
+ </property> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="windowTitle"> | |
+ <string>Configure Name</string> | |
+ </property> | |
+ <layout class="QVBoxLayout" name="verticalLayout"> | |
+ <item> | |
+ <layout class="QFormLayout" name="formLayout"> | |
+ <property name="fieldGrowthPolicy"> | |
+ <enum>QFormLayout::AllNonFixedFieldsGrow</enum> | |
+ </property> | |
+ <property name="labelAlignment"> | |
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |
+ </property> | |
+ <item row="1" column="0"> | |
+ <widget class="QLabel" name="label_2"> | |
+ <property name="text"> | |
+ <string>Name:</string> | |
+ </property> | |
+ <property name="buddy"> | |
+ <cstring>dataEdit</cstring> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="1" column="1"> | |
+ <widget class="QLabel" name="labelName"> | |
+ <property name="text"> | |
+ <string notr="true">TextLabel</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="4" column="0"> | |
+ <widget class="QLabel" name="label"> | |
+ <property name="text"> | |
+ <string>&Data:</string> | |
+ </property> | |
+ <property name="alignment"> | |
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> | |
+ </property> | |
+ <property name="buddy"> | |
+ <cstring>dataEdit</cstring> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="4" column="1"> | |
+ <widget class="QLineEdit" name="dataEdit"> | |
+ <property name="toolTip"> | |
+ <string>Enter JSON string that will be associated with the name</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="5" column="1"> | |
+ <widget class="QLabel" name="label_4"> | |
+ <property name="text"> | |
+ <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> | |
+<html><head><meta name="qrichtext" content="1" /><style type="text/css"> | |
+p, li { white-space: pre-wrap; } | |
+</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;"> | |
+<p style=" margin:0px; -qt-block-indent:0; text-indent:0px;">JSON string, e.g. {&quot;ns&quot;: [&quot;1.2.3.4&quot;, &quot;1.2.3.5&quot;]}</p> | |
+<p style=" margin:0px; -qt-block-indent:0; text-indent:0px;">See <a href="http://dot-bit.org/HowToRegisterAndConfigureBitDomains"><span style=" text-decoration: underline; color:#0000ff;">How to Register and Configure Bit Domains</span></a></p></body></html></string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="7" column="0"> | |
+ <widget class="QLabel" name="labelTransferTo"> | |
+ <property name="text"> | |
+ <string>&Transfer to:</string> | |
+ </property> | |
+ <property name="buddy"> | |
+ <cstring>transferTo</cstring> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="7" column="1"> | |
+ <layout class="QHBoxLayout" name="transferToLayout"> | |
+ <property name="spacing"> | |
+ <number>0</number> | |
+ </property> | |
+ <property name="sizeConstraint"> | |
+ <enum>QLayout::SetDefaultConstraint</enum> | |
+ </property> | |
+ <item> | |
+ <widget class="QValidatedLineEdit" name="transferTo"> | |
+ <property name="toolTip"> | |
+ <string>The Namecoin address to transfer domain to | |
+(e.g. N1KHAL5C1CRzy58NdJwp1tbLze3XrkFxx9). | |
+Leave empty, if not needed.</string> | |
+ </property> | |
+ <property name="maxLength"> | |
+ <number>34</number> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QToolButton" name="addressBookButton"> | |
+ <property name="toolTip"> | |
+ <string>Choose address from address book</string> | |
+ </property> | |
+ <property name="text"> | |
+ <string/> | |
+ </property> | |
+ <property name="icon"> | |
+ <iconset resource="../bitcoin.qrc"> | |
+ <normaloff>:/icons/address-book</normaloff>:/icons/address-book</iconset> | |
+ </property> | |
+ <property name="shortcut"> | |
+ <string>Alt+A</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QToolButton" name="pasteButton"> | |
+ <property name="toolTip"> | |
+ <string>Paste address from clipboard</string> | |
+ </property> | |
+ <property name="text"> | |
+ <string/> | |
+ </property> | |
+ <property name="icon"> | |
+ <iconset resource="../bitcoin.qrc"> | |
+ <normaloff>:/icons/editpaste</normaloff>:/icons/editpaste</iconset> | |
+ </property> | |
+ <property name="shortcut"> | |
+ <string>Alt+P</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ </layout> | |
+ </item> | |
+ <item row="6" column="1"> | |
+ <spacer name="verticalSpacer"> | |
+ <property name="orientation"> | |
+ <enum>Qt::Vertical</enum> | |
+ </property> | |
+ <property name="sizeHint" stdset="0"> | |
+ <size> | |
+ <width>20</width> | |
+ <height>16</height> | |
+ </size> | |
+ </property> | |
+ </spacer> | |
+ </item> | |
+ <item row="2" column="0"> | |
+ <widget class="QLabel" name="label_3"> | |
+ <property name="text"> | |
+ <string>Domain name:</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="2" column="1"> | |
+ <widget class="QLabel" name="labelDomain"> | |
+ <property name="text"> | |
+ <string notr="true">TextLabel</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item row="3" column="1"> | |
+ <spacer name="verticalSpacer_2"> | |
+ <property name="orientation"> | |
+ <enum>Qt::Vertical</enum> | |
+ </property> | |
+ <property name="sizeHint" stdset="0"> | |
+ <size> | |
+ <width>20</width> | |
+ <height>16</height> | |
+ </size> | |
+ </property> | |
+ </spacer> | |
+ </item> | |
+ <item row="8" column="1"> | |
+ <widget class="QLabel" name="labelTransferToHint"> | |
+ <property name="text"> | |
+ <string>(can be left empty)</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ </layout> | |
+ </item> | |
+ <item> | |
+ <layout class="QHBoxLayout" name="horizontalLayout"> | |
+ <item> | |
+ <widget class="QLabel" name="labelSubmitHint"> | |
+ <property name="text"> | |
+ <string notr="true">TextLabel</string> | |
+ </property> | |
+ <property name="alignment"> | |
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QDialogButtonBox" name="buttonBox"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="orientation"> | |
+ <enum>Qt::Horizontal</enum> | |
+ </property> | |
+ <property name="standardButtons"> | |
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ </layout> | |
+ </item> | |
+ </layout> | |
+ </widget> | |
+ <customwidgets> | |
+ <customwidget> | |
+ <class>QValidatedLineEdit</class> | |
+ <extends>QLineEdit</extends> | |
+ <header>../../src/qt/qvalidatedlineedit.h</header> | |
+ </customwidget> | |
+ </customwidgets> | |
+ <resources> | |
+ <include location="../bitcoin.qrc"/> | |
+ </resources> | |
+ <connections> | |
+ <connection> | |
+ <sender>buttonBox</sender> | |
+ <signal>accepted()</signal> | |
+ <receiver>ConfigureNameDialog</receiver> | |
+ <slot>accept()</slot> | |
+ <hints> | |
+ <hint type="sourcelabel"> | |
+ <x>248</x> | |
+ <y>254</y> | |
+ </hint> | |
+ <hint type="destinationlabel"> | |
+ <x>157</x> | |
+ <y>274</y> | |
+ </hint> | |
+ </hints> | |
+ </connection> | |
+ <connection> | |
+ <sender>buttonBox</sender> | |
+ <signal>rejected()</signal> | |
+ <receiver>ConfigureNameDialog</receiver> | |
+ <slot>reject()</slot> | |
+ <hints> | |
+ <hint type="sourcelabel"> | |
+ <x>316</x> | |
+ <y>260</y> | |
+ </hint> | |
+ <hint type="destinationlabel"> | |
+ <x>286</x> | |
+ <y>274</y> | |
+ </hint> | |
+ </hints> | |
+ </connection> | |
+ </connections> | |
+</ui> | |
diff --git a/src/qt/forms/managenamespage.ui b/src/qt/forms/managenamespage.ui | |
new file mode 100644 | |
index 0000000..3ac54c9 | |
--- /dev/null | |
+++ b/src/qt/forms/managenamespage.ui | |
@@ -0,0 +1,217 @@ | |
+<?xml version="1.0" encoding="UTF-8"?> | |
+<ui version="4.0"> | |
+ <class>ManageNamesPage</class> | |
+ <widget class="QDialog" name="ManageNamesPage"> | |
+ <property name="geometry"> | |
+ <rect> | |
+ <x>0</x> | |
+ <y>0</y> | |
+ <width>776</width> | |
+ <height>364</height> | |
+ </rect> | |
+ </property> | |
+ <layout class="QHBoxLayout" name="horizontalLayout"> | |
+ <item> | |
+ | |
+ <widget class="QFrame" name="frame2"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="frameShape"> | |
+ <enum>QFrame::StyledPanel</enum> | |
+ </property> | |
+ <property name="frameShadow"> | |
+ <enum>QFrame::Sunken</enum> | |
+ </property> | |
+ | |
+ | |
+ <layout class="QVBoxLayout" name="verticalLayout"> | |
+ <item> | |
+ <widget class="QLabel" name="label_4"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="text"> | |
+ <string>&New name:</string> | |
+ </property> | |
+ <property name="alignment"> | |
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> | |
+ </property> | |
+ <property name="buddy"> | |
+ <cstring>registerName</cstring> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QValidatedLineEdit" name="registerName"> | |
+ <property name="toolTip"> | |
+ <string>Enter a name or domain name (prefixed with d/) to be registered via Namecoin.</string> | |
+ </property> | |
+ <property name="text"> | |
+ <string>d/</string> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QLabel" name="label"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="text"> | |
+ <string><html><head/><body><p>Use <span style=" font-weight:600;">d/</span> prefix for domain names. E.g. <span style=" font-weight:600;">d/mysite</span> will register <span style=" font-weight:600;">mysite.bit</span></p><p>See <a href="http://dot-bit.org/Namespace:Domain_names"><span style=" text-decoration: underline; color:#0000ff;">Domain names</span></a> in Namecoin wiki for reference. Other prfixes can be used for miscellaneous purposes (not domain names).</p></body></html></string> | |
+ </property> | |
+ <property name="textFormat"> | |
+ <enum>Qt::RichText</enum> | |
+ </property> | |
+ <property name="wordWrap"> | |
+ <bool>true</bool> | |
+ </property> | |
+ <property name="openExternalLinks"> | |
+ <bool>true</bool> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QPushButton" name="submitNameButton"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="minimumSize"> | |
+ <size> | |
+ <width>150</width> | |
+ <height>0</height> | |
+ </size> | |
+ </property> | |
+ <property name="toolTip"> | |
+ <string>Confirm the new name action. Sends name_new transaction | |
+to the network and creates a pending name_firstupdate transaction.</string> | |
+ </property> | |
+ <property name="text"> | |
+ <string>&Submit</string> | |
+ </property> | |
+ <property name="icon"> | |
+ <iconset resource="../bitcoin.qrc"> | |
+ <normaloff>:/icons/send</normaloff>:/icons/send</iconset> | |
+ </property> | |
+ <property name="default"> | |
+ <bool>true</bool> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <spacer name="verticalSpacer"> | |
+ <property name="orientation"> | |
+ <enum>Qt::Vertical</enum> | |
+ </property> | |
+ <property name="sizeType"> | |
+ <enum>QSizePolicy::Maximum</enum> | |
+ </property> | |
+ <property name="sizeHint" stdset="0"> | |
+ <size> | |
+ <width>20</width> | |
+ <height>12</height> | |
+ </size> | |
+ </property> | |
+ </spacer> | |
+ </item> | |
+ <item> | |
+ <widget class="QLabel" name="label_5"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="text"> | |
+ <string>Your registered names:</string> | |
+ </property> | |
+ <property name="alignment"> | |
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> | |
+ </property> | |
+ <property name="buddy"> | |
+ <cstring>registerName</cstring> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QTableView" name="tableView"> | |
+ <property name="contextMenuPolicy"> | |
+ <enum>Qt::CustomContextMenu</enum> | |
+ </property> | |
+ <property name="toolTip"> | |
+ <string>Double-click name to configure</string> | |
+ </property> | |
+ <property name="tabKeyNavigation"> | |
+ <bool>false</bool> | |
+ </property> | |
+ <property name="alternatingRowColors"> | |
+ <bool>true</bool> | |
+ </property> | |
+ <property name="selectionMode"> | |
+ <enum>QAbstractItemView::SingleSelection</enum> | |
+ </property> | |
+ <property name="selectionBehavior"> | |
+ <enum>QAbstractItemView::SelectRows</enum> | |
+ </property> | |
+ <property name="sortingEnabled"> | |
+ <bool>true</bool> | |
+ </property> | |
+ <attribute name="verticalHeaderVisible"> | |
+ <bool>false</bool> | |
+ </attribute> | |
+ </widget> | |
+ </item> | |
+ <item> | |
+ <widget class="QPushButton" name="configureNameButton"> | |
+ <property name="sizePolicy"> | |
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> | |
+ <horstretch>0</horstretch> | |
+ <verstretch>0</verstretch> | |
+ </sizepolicy> | |
+ </property> | |
+ <property name="minimumSize"> | |
+ <size> | |
+ <width>150</width> | |
+ <height>0</height> | |
+ </size> | |
+ </property> | |
+ <property name="toolTip"> | |
+ <string>Configure name and submit update operation</string> | |
+ </property> | |
+ <property name="text"> | |
+ <string>&Configure Name...</string> | |
+ </property> | |
+ <property name="default"> | |
+ <bool>false</bool> | |
+ </property> | |
+ </widget> | |
+ </item> | |
+ </layout> | |
+ </widget> | |
+ </item> | |
+ </layout> | |
+ </widget> | |
+ <customwidgets> | |
+ <customwidget> | |
+ <class>QValidatedLineEdit</class> | |
+ <extends>QLineEdit</extends> | |
+ <header>../../src/qt/qvalidatedlineedit.h</header> | |
+ </customwidget> | |
+ </customwidgets> | |
+ <resources> | |
+ <include location="../bitcoin.qrc"/> | |
+ </resources> | |
+ <connections/> | |
+</ui> | |
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp | |
index 0f42d59..5bf12fd 100644 | |
--- a/src/qt/guiutil.cpp | |
+++ b/src/qt/guiutil.cpp | |
@@ -111,7 +111,7 @@ QFont fixedPitchFont() | |
#endif | |
} | |
-void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) | |
+void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent, bool fAllowEmpty /*= false*/) | |
{ | |
parent->setFocusProxy(widget); | |
@@ -121,7 +121,7 @@ void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) | |
// and this is the only place, where this address is supplied. | |
widget->setPlaceholderText(QObject::tr("Enter a Namecoin address (e.g. %1)").arg("N1KHAL5C1CRzy58NdJwp1tbLze3XrkFxx9")); | |
#endif | |
- widget->setValidator(new BitcoinAddressEntryValidator(parent)); | |
+ widget->setValidator(new BitcoinAddressEntryValidator(parent, fAllowEmpty)); | |
widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); | |
} | |
diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h | |
index 9267e0a..169c3ce 100644 | |
--- a/src/qt/guiutil.h | |
+++ b/src/qt/guiutil.h | |
@@ -41,7 +41,7 @@ namespace GUIUtil | |
QFont fixedPitchFont(); | |
// Set up widgets for address and amounts | |
- void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); | |
+ void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent, bool fAllowEmpty = false); | |
void setupAmountWidget(QLineEdit *widget, QWidget *parent); | |
// Parse "bitcoin:" URI into recipient object, return true on successful parsing | |
diff --git a/src/qt/managenamespage.cpp b/src/qt/managenamespage.cpp | |
new file mode 100644 | |
index 0000000..c993293 | |
--- /dev/null | |
+++ b/src/qt/managenamespage.cpp | |
@@ -0,0 +1,283 @@ | |
+#include "managenamespage.h" | |
+#include "ui_managenamespage.h" | |
+ | |
+#include "walletmodel.h" | |
+#include "nametablemodel.h" | |
+#include "csvmodelwriter.h" | |
+#include "guiutil.h" | |
+#include "base58.h" | |
+#include "main.h" | |
+#include "wallet/wallet.h" | |
+#include "ui_interface.h" | |
+#include "configurenamedialog.h" | |
+#include "platformstyle.h" | |
+ | |
+#include <QSortFilterProxyModel> | |
+#include <QMessageBox> | |
+#include <QMenu> | |
+ | |
+extern std::map<std::vector<unsigned char>, PreparedNameFirstUpdate> mapMyNameFirstUpdate; | |
+ | |
+ManageNamesPage::ManageNamesPage(const PlatformStyle *platformStyle, QWidget *parent) : | |
+ QDialog(parent), | |
+ ui(new Ui::ManageNamesPage), | |
+ model(0), | |
+ walletModel(0), | |
+ proxyModel(0) | |
+{ | |
+ ui->setupUi(this); | |
+ | |
+ // Context menu actions | |
+ QAction *copyNameAction = new QAction(tr("Copy &Name"), this); | |
+ QAction *copyValueAction = new QAction(tr("Copy &Value"), this); | |
+ QAction *configureNameAction = new QAction(tr("&Configure Name..."), this); | |
+ | |
+ // Build context menu | |
+ contextMenu = new QMenu(); | |
+ contextMenu->addAction(copyNameAction); | |
+ contextMenu->addAction(copyValueAction); | |
+ contextMenu->addAction(configureNameAction); | |
+ | |
+ // Connect signals for context menu actions | |
+ connect(copyNameAction, SIGNAL(triggered()), this, SLOT(onCopyNameAction())); | |
+ connect(copyValueAction, SIGNAL(triggered()), this, SLOT(onCopyValueAction())); | |
+ connect(configureNameAction, SIGNAL(triggered()), this, SLOT(on_configureNameButton_clicked())); | |
+ | |
+ connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); | |
+ connect(ui->tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(on_configureNameButton_clicked())); | |
+ ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); | |
+ | |
+ ui->registerName->installEventFilter(this); | |
+ ui->tableView->installEventFilter(this); | |
+} | |
+ | |
+ManageNamesPage::~ManageNamesPage() | |
+{ | |
+ delete ui; | |
+} | |
+ | |
+void ManageNamesPage::setModel(WalletModel *walletModel) | |
+{ | |
+ this->walletModel = walletModel; | |
+ model = walletModel->getNameTableModel(); | |
+ | |
+ proxyModel = new QSortFilterProxyModel(this); | |
+ proxyModel->setSourceModel(model); | |
+ proxyModel->setDynamicSortFilter(true); | |
+ proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); | |
+ proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); | |
+ | |
+ ui->tableView->setModel(proxyModel); | |
+ ui->tableView->sortByColumn(0, Qt::AscendingOrder); | |
+ | |
+ ui->tableView->horizontalHeader()->setHighlightSections(false); | |
+ | |
+ // Set column widths | |
+ // TODO: DONT FORGET THIS | |
+ //#pragma message("QT version: " QT_VERSION_STR) | |
+ ui->tableView->horizontalHeader()->resizeSection( | |
+ NameTableModel::Name, 320); | |
+ // ui->tableView->horizontalHeader()->setResizeMode( | |
+ // NameTableModel::Value, QHeaderView::Stretch); | |
+ ui->tableView->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch); | |
+ | |
+ | |
+ connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), | |
+ this, SLOT(selectionChanged())); | |
+ | |
+ selectionChanged(); | |
+} | |
+ | |
+void ManageNamesPage::on_submitNameButton_clicked() | |
+{ | |
+ if (!walletModel) | |
+ return; | |
+ | |
+ QString name = ui->registerName->text(); | |
+ | |
+ if (!walletModel->nameAvailable(name)) | |
+ { | |
+ QMessageBox::warning(this, tr("Name registration"), tr("Name not available")); | |
+ ui->registerName->setFocus(); | |
+ return; | |
+ } | |
+ | |
+ QString msg; | |
+ if (name.startsWith("d/")) | |
+ msg = tr("Are you sure you want to register domain name %1, which corresponds to domain %2?").arg(name).arg(name.mid(2) + ".bit"); | |
+ else | |
+ msg = tr("Are you sure you want to register non-domain name %1?").arg(name); | |
+ | |
+ if (QMessageBox::Yes != QMessageBox::question(this, tr("Confirm name registration"), | |
+ msg, | |
+ QMessageBox::Yes | QMessageBox::Cancel, | |
+ QMessageBox::Cancel)) | |
+ { | |
+ return; | |
+ } | |
+ | |
+ WalletModel::UnlockContext ctx(walletModel->requestUnlock()); | |
+ if (!ctx.isValid()) | |
+ return; | |
+ | |
+ QString err_msg; | |
+ | |
+ try | |
+ { | |
+ WalletModel::NameNewReturn res = walletModel->nameNew(name); | |
+ | |
+ if (res.ok) | |
+ { | |
+ ui->registerName->setText("d/"); | |
+ ui->submitNameButton->setDefault(true); | |
+ | |
+ int newRowIndex; | |
+ // FIXME: CT_NEW may have been sent from nameNew (via transaction). | |
+ // Currently updateEntry is modified so it does not complain | |
+ model->updateEntry(name, "", NameTableEntry::NAME_NEW, CT_NEW, &newRowIndex); | |
+ ui->tableView->selectRow(newRowIndex); | |
+ ui->tableView->setFocus(); | |
+ | |
+ ConfigureNameDialog dlg(name, "", true, this); | |
+ dlg.setModel(walletModel); | |
+ if (dlg.exec() == QDialog::Accepted) | |
+ { | |
+ LOCK(cs_main); | |
+ // if (mapMyNameFirstUpdate.count(vchFromString(name.toStdString())) != 0) | |
+ if (mapMyNameFirstUpdate.count(ValtypeFromString(name.toStdString())) != 0) | |
+ model->updateEntry(name, dlg.getReturnData(), NameTableEntry::NAME_NEW, CT_UPDATED); | |
+ else | |
+ { | |
+ // name_firstupdate could have been sent, while the user was editing the value | |
+ // Do nothing | |
+ } | |
+ } | |
+ | |
+ return; | |
+ } | |
+ | |
+ err_msg = res.err_msg; | |
+ } | |
+ catch (std::exception& e) | |
+ { | |
+ err_msg = e.what(); | |
+ } | |
+ | |
+ if (err_msg == "ABORTED") | |
+ return; | |
+ | |
+ QMessageBox::warning(this, tr("Name registration failed"), err_msg); | |
+} | |
+ | |
+bool ManageNamesPage::eventFilter(QObject *object, QEvent *event) | |
+{ | |
+ if (event->type() == QEvent::FocusIn) | |
+ { | |
+ if (object == ui->registerName) | |
+ { | |
+ ui->submitNameButton->setDefault(true); | |
+ ui->configureNameButton->setDefault(false); | |
+ } | |
+ else if (object == ui->tableView) | |
+ { | |
+ ui->submitNameButton->setDefault(false); | |
+ ui->configureNameButton->setDefault(true); | |
+ } | |
+ } | |
+ return QDialog::eventFilter(object, event); | |
+} | |
+ | |
+void ManageNamesPage::selectionChanged() | |
+{ | |
+ // Set button states based on selected tab and selection | |
+ QTableView *table = ui->tableView; | |
+ if(!table->selectionModel()) | |
+ return; | |
+ | |
+ if(table->selectionModel()->hasSelection()) | |
+ { | |
+ ui->configureNameButton->setEnabled(true); | |
+ } | |
+ else | |
+ { | |
+ ui->configureNameButton->setEnabled(false); | |
+ } | |
+} | |
+ | |
+void ManageNamesPage::contextualMenu(const QPoint &point) | |
+{ | |
+ QModelIndex index = ui->tableView->indexAt(point); | |
+ if (index.isValid()) | |
+ contextMenu->exec(QCursor::pos()); | |
+} | |
+ | |
+void ManageNamesPage::onCopyNameAction() | |
+{ | |
+ GUIUtil::copyEntryData(ui->tableView, NameTableModel::Name); | |
+} | |
+ | |
+void ManageNamesPage::onCopyValueAction() | |
+{ | |
+ GUIUtil::copyEntryData(ui->tableView, NameTableModel::Value); | |
+} | |
+ | |
+void ManageNamesPage::on_configureNameButton_clicked() | |
+{ | |
+ if(!ui->tableView->selectionModel()) | |
+ return; | |
+ QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows(NameTableModel::Name); | |
+ if(indexes.isEmpty()) | |
+ return; | |
+ | |
+ QModelIndex index = indexes.at(0); | |
+ | |
+ QString name = index.data(Qt::EditRole).toString(); | |
+ QString value = index.sibling(index.row(), NameTableModel::Value).data(Qt::EditRole).toString(); | |
+ | |
+ // std::vector<unsigned char> vchName = vchFromString(name.toStdString()); | |
+ const valtype vchName = ValtypeFromString (name.toStdString()); | |
+ bool fFirstUpdate = mapMyNameFirstUpdate.count(vchName) != 0; | |
+ | |
+ ConfigureNameDialog dlg(name, value, fFirstUpdate, this); | |
+ dlg.setModel(walletModel); | |
+ if (dlg.exec() == QDialog::Accepted && fFirstUpdate) | |
+ { | |
+ LOCK(cs_main); | |
+ // name_firstupdate could have been sent, while the user was editing the value | |
+ if (mapMyNameFirstUpdate.count(vchName) != 0) | |
+ model->updateEntry(name, dlg.getReturnData(), NameTableEntry::NAME_NEW, CT_UPDATED); | |
+ } | |
+} | |
+ | |
+void ManageNamesPage::exportClicked() | |
+{ | |
+ // CSV is currently the only supported format | |
+ // QString filename = GUIUtil::getSaveFileName( | |
+ // this, | |
+ // tr("Export Registered Names Data"), QString(), | |
+ // tr("Comma separated file (*.csv)")); | |
+ QString suffixOut = ""; | |
+ QString filename = GUIUtil::getSaveFileName( | |
+ this, | |
+ tr("Export Registered Names Data"), | |
+ QString(), | |
+ tr("Comma separated file (*.csv)"), | |
+ &suffixOut); | |
+ | |
+ if (filename.isNull()) | |
+ return; | |
+ | |
+ CSVModelWriter writer(filename); | |
+ | |
+ // name, column, role | |
+ writer.setModel(proxyModel); | |
+ writer.addColumn("Name", NameTableModel::Name, Qt::EditRole); | |
+ writer.addColumn("Value", NameTableModel::Value, Qt::EditRole); | |
+ writer.addColumn("Expires In", NameTableModel::ExpiresIn, Qt::EditRole); | |
+ | |
+ if(!writer.write()) | |
+ { | |
+ QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename), | |
+ QMessageBox::Abort, QMessageBox::Abort); | |
+ } | |
+} | |
diff --git a/src/qt/managenamespage.h b/src/qt/managenamespage.h | |
new file mode 100644 | |
index 0000000..05179f2 | |
--- /dev/null | |
+++ b/src/qt/managenamespage.h | |
@@ -0,0 +1,58 @@ | |
+#ifndef MANAGENAMESPAGE_H | |
+#define MANAGENAMESPAGE_H | |
+ | |
+#include "platformstyle.h" | |
+ | |
+#include <QDialog> | |
+ | |
+class WalletModel; | |
+class NameTableModel; | |
+ | |
+namespace Ui { | |
+ class ManageNamesPage; | |
+} | |
+ | |
+QT_BEGIN_NAMESPACE | |
+class QTableView; | |
+class QItemSelection; | |
+class QSortFilterProxyModel; | |
+class QMenu; | |
+class QModelIndex; | |
+QT_END_NAMESPACE | |
+ | |
+/** Page for managing names */ | |
+class ManageNamesPage : public QDialog | |
+{ | |
+ Q_OBJECT | |
+ | |
+public: | |
+ explicit ManageNamesPage(const PlatformStyle *platformStyle, QWidget *parent = 0); | |
+ ~ManageNamesPage(); | |
+ | |
+ void setModel(WalletModel *walletModel); | |
+ | |
+private: | |
+ Ui::ManageNamesPage *ui; | |
+ NameTableModel *model; | |
+ WalletModel *walletModel; | |
+ QSortFilterProxyModel *proxyModel; | |
+ QMenu *contextMenu; | |
+ | |
+public Q_SLOTS: | |
+ void exportClicked(); | |
+ | |
+private Q_SLOTS: | |
+ void on_submitNameButton_clicked(); | |
+ | |
+ bool eventFilter(QObject *object, QEvent *event); | |
+ void selectionChanged(); | |
+ | |
+ /** Spawn contextual menu (right mouse menu) for name table entry */ | |
+ void contextualMenu(const QPoint &point); | |
+ | |
+ void onCopyNameAction(); | |
+ void onCopyValueAction(); | |
+ void on_configureNameButton_clicked(); | |
+}; | |
+ | |
+#endif // MANAGENAMESPAGE_H | |
diff --git a/src/qt/nametablemodel.cpp b/src/qt/nametablemodel.cpp | |
new file mode 100644 | |
index 0000000..72ed0e4 | |
--- /dev/null | |
+++ b/src/qt/nametablemodel.cpp | |
@@ -0,0 +1,585 @@ | |
+#include "nametablemodel.h" | |
+ | |
+#include "guiutil.h" | |
+#include "walletmodel.h" | |
+#include "guiconstants.h" | |
+#include "wallet/wallet.h" | |
+#include "ui_interface.h" | |
+#include "platformstyle.h" | |
+ | |
+#include "main.h" | |
+#include "names/common.h" | |
+ | |
+#include <QTimer> | |
+#include <QObject> | |
+ | |
+extern std::map<std::vector<unsigned char>, PreparedNameFirstUpdate> mapMyNameFirstUpdate; | |
+ | |
+// ExpiresIn column is right-aligned as it contains numbers | |
+static int column_alignments[] = { | |
+ Qt::AlignLeft|Qt::AlignVCenter, // Name | |
+ Qt::AlignLeft|Qt::AlignVCenter, // Value | |
+ Qt::AlignRight|Qt::AlignVCenter // Expires in | |
+ }; | |
+ | |
+struct NameTableEntryLessThan | |
+{ | |
+ bool operator()(const NameTableEntry &a, const NameTableEntry &b) const | |
+ { | |
+ return a.name < b.name; | |
+ } | |
+ bool operator()(const NameTableEntry &a, const QString &b) const | |
+ { | |
+ return a.name < b; | |
+ } | |
+ bool operator()(const QString &a, const NameTableEntry &b) const | |
+ { | |
+ return a < b.name; | |
+ } | |
+}; | |
+ | |
+// Returns true if new height is better | |
+/*static*/ bool NameTableEntry::CompareHeight(int nOldHeight, int nNewHeight) | |
+{ | |
+ if (nOldHeight == NAME_NON_EXISTING) | |
+ return true; | |
+ | |
+ // We use optimistic way, assuming that unconfirmed transaction will eventually become confirmed, | |
+ // so we update the name in the table immediately. Ideally we need a separate way of displaying | |
+ // unconfirmed names (e.g. grayed out) | |
+ if (nNewHeight == NAME_UNCONFIRMED) | |
+ return true; | |
+ | |
+ // Here we rely on the fact that dummy height values are always negative | |
+ return nNewHeight > nOldHeight; | |
+} | |
+ | |
+// Private implementation | |
+class NameTablePriv | |
+{ | |
+public: | |
+ CWallet *wallet; | |
+ QList<NameTableEntry> cachedNameTable; | |
+ NameTableModel *parent; | |
+ | |
+ NameTablePriv(CWallet *wallet, NameTableModel *parent): | |
+ wallet(wallet), parent(parent) {} | |
+ | |
+ void refreshNameTable() | |
+ { | |
+ cachedNameTable.clear(); | |
+ | |
+ std::map< std::vector<unsigned char>, NameTableEntry > vNamesO; | |
+ | |
+ // NOTE: this came from name_scan in rpcnames.cpp ... might want to use | |
+ // that call and parse the results into NameTableEntrys | |
+ | |
+ { | |
+ LOCK(cs_main); | |
+ | |
+ // TODO: replace with code from name_list | |
+ valtype name; | |
+ CNameData data; | |
+ std::auto_ptr<CNameIterator> iter(pcoinsTip->IterateNames ()); | |
+ for (; iter->next (name, data); ) { | |
+ // res.push_back (getNameInfo (name, data)); | |
+ vNamesO[name] = NameTableEntry(ValtypeToString(name), | |
+ ValtypeToString(data.getValue ()), | |
+ data.getHeight ()); | |
+ } | |
+ } | |
+ | |
+ // NOTE: There used to be a CRITICAL_BLOCK here | |
+ // { | |
+ // CTxIndex txindex; | |
+ // uint256 hash; | |
+ // CTxDB txdb("r"); | |
+ // CTransaction tx; | |
+ | |
+ // std::vector<unsigned char> vchName; | |
+ // std::vector<unsigned char> vchValue; | |
+ // int nHeight; | |
+ | |
+ // BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, wallet->mapWallet) | |
+ // { | |
+ // hash = item.second.GetHash(); | |
+ // bool fConfirmed; | |
+ // // TODO: Maybe CMerkleTx::GetDepthInMainChain() would be faster? | |
+ // if (!txdb.ReadDiskTx(hash, tx, txindex)) | |
+ // { | |
+ // tx = item.second; | |
+ // fConfirmed = false; | |
+ // } | |
+ // else | |
+ // fConfirmed = true; | |
+ | |
+ // if (tx.nVersion != NAMECOIN_TX_VERSION) | |
+ // continue; | |
+ | |
+ // // name | |
+ // if (!GetNameOfTx(tx, vchName)) | |
+ // continue; | |
+ | |
+ // // value | |
+ // if (!GetValueOfNameTx(tx, vchValue)) | |
+ // continue; | |
+ | |
+ // if (!hooks->IsMine(wallet->mapWallet[tx.GetHash()])) | |
+ // continue; // Transferred | |
+ | |
+ // // height | |
+ // if (fConfirmed) | |
+ // { | |
+ // nHeight = GetTxPosHeight(txindex.pos); | |
+ // if (nHeight + GetDisplayExpirationDepth(nHeight) - pindexBest->nHeight <= 0) | |
+ // continue; // Expired | |
+ // } | |
+ // else | |
+ // nHeight = NameTableEntry::NAME_UNCONFIRMED; | |
+ | |
+ // // get last active name only | |
+ // std::map< std::vector<unsigned char>, NameTableEntry >::iterator mi = vNamesO.find(vchName); | |
+ // if (mi != vNamesO.end() && !NameTableEntry::CompareHeight(mi->second.nHeight, nHeight)) | |
+ // continue; | |
+ | |
+ // vNamesO[vchName] = NameTableEntry(ValtypeFromString(vchName), ValtypeFromString(vchValue), nHeight); | |
+ // } | |
+ // } | |
+ | |
+ // Add existing names | |
+ BOOST_FOREACH(const PAIRTYPE(std::vector<unsigned char>, NameTableEntry)& item, vNamesO) | |
+ cachedNameTable.append(item.second); | |
+ | |
+ // Add pending names (name_new) | |
+ BOOST_FOREACH(const PAIRTYPE(std::vector<unsigned char>, PreparedNameFirstUpdate)& item, mapMyNameFirstUpdate) | |
+ cachedNameTable.append( | |
+ NameTableEntry(ValtypeToString(item.first), | |
+ ValtypeToString(item.second.vchData), | |
+ NameTableEntry::NAME_NEW)); | |
+ | |
+ // qLowerBound() and qUpperBound() require our cachedNameTable list to be sorted in asc order | |
+ qSort(cachedNameTable.begin(), cachedNameTable.end(), NameTableEntryLessThan()); | |
+ } | |
+ | |
+ void refreshName(const std::vector<unsigned char> &inName) | |
+ { | |
+ LOCK(cs_main); | |
+ | |
+ // NameTableEntry nameObj(ValtypeToString(inName), | |
+ // std::string(""), | |
+ // NameTableEntry::NAME_NON_EXISTING); | |
+ NameTableEntry nameObj; | |
+ | |
+ CNameData data; | |
+ { | |
+ LOCK (cs_main); | |
+ if (!pcoinsTip->GetName (inName, data)) | |
+ { | |
+ // std::ostringstream msg; | |
+ // msg << "name not found: '" << ValtypeToString (inName) << "'"; | |
+ // throw JSONRPCError (RPC_WALLET_ERROR, msg.str ()); | |
+ // TODO: err msg here | |
+ printf( "refreshName, name not found"); | |
+ return; | |
+ } | |
+ | |
+ nameObj = NameTableEntry(ValtypeToString(inName), | |
+ ValtypeToString(data.getValue ()), | |
+ data.getHeight ()); | |
+ } | |
+ | |
+ // CRITICAL_BLOCK(wallet->cs_mapWallet) | |
+ // { | |
+ // CTxIndex txindex; | |
+ // uint256 hash; | |
+ // CTxDB txdb("r"); | |
+ // CTransaction tx; | |
+ | |
+ // std::vector<unsigned char> vchName; | |
+ // std::vector<unsigned char> vchValue; | |
+ // int nHeight; | |
+ | |
+ // BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, wallet->mapWallet) | |
+ // { | |
+ // hash = item.second.GetHash(); | |
+ // bool fConfirmed; | |
+ // if (!txdb.ReadDiskTx(hash, tx, txindex)) | |
+ // { | |
+ // tx = item.second; | |
+ // fConfirmed = false; | |
+ // } | |
+ // else | |
+ // fConfirmed = true; | |
+ | |
+ // if (tx.nVersion != NAMECOIN_TX_VERSION) | |
+ // continue; | |
+ | |
+ // // name | |
+ // if (!GetNameOfTx(tx, vchName) || vchName != inName) | |
+ // continue; | |
+ | |
+ // // value | |
+ // if (!GetValueOfNameTx(tx, vchValue)) | |
+ // { | |
+ // printf("refreshName(%s): skipping tx %s (GetValueOfNameTx returned false)\n", qPrintable(nameObj.name), hash.GetHex().c_str()); | |
+ // continue; | |
+ // } | |
+ | |
+ // if (!hooks->IsMine(wallet->mapWallet[tx.GetHash()])) | |
+ // { | |
+ // printf("refreshName(%s): skipping tx %s (transferred name)\n", qPrintable(nameObj.name), hash.GetHex().c_str()); | |
+ // continue; // Transferred | |
+ // } | |
+ | |
+ // // height | |
+ // if (fConfirmed) | |
+ // { | |
+ // nHeight = GetTxPosHeight(txindex.pos); | |
+ // if (nHeight + GetDisplayExpirationDepth(nHeight) - pindexBest->nHeight <= 0) | |
+ // { | |
+ // printf("refreshName(%s): tx %s, nHeight = %d - expired\n", qPrintable(nameObj.name), hash.GetHex().c_str(), nHeight); | |
+ // continue; // Expired | |
+ // } | |
+ // else | |
+ // { | |
+ // printf("refreshName(%s): tx %s, nHeight = %d\n", qPrintable(nameObj.name), hash.GetHex().c_str(), nHeight); | |
+ // } | |
+ // } | |
+ // else | |
+ // { | |
+ // printf("refreshName(%s): tx %s - unconfirmed\n", qPrintable(nameObj.name), hash.GetHex().c_str()); | |
+ // nHeight = NameTableEntry::NAME_UNCONFIRMED; | |
+ // } | |
+ | |
+ // // get last active name only | |
+ // if (!NameTableEntry::CompareHeight(nameObj.nHeight, nHeight)) | |
+ // { | |
+ // printf("refreshName(%s): tx %s - skipped (more recent transaction exists)\n", qPrintable(nameObj.name), hash.GetHex().c_str()); | |
+ // continue; | |
+ // } | |
+ | |
+ // nameObj.value = QString::fromStdString(ValtypeFromString(vchValue)); | |
+ // nameObj.nHeight = nHeight; | |
+ | |
+ // printf("refreshName(%s) found tx %s, nHeight=%d, value: %s\n", qPrintable(nameObj.name), hash.GetHex().c_str(), nameObj.nHeight, qPrintable(nameObj.value)); | |
+ // } | |
+ // } | |
+ | |
+ // Find name in model | |
+ QList<NameTableEntry>::iterator lower = qLowerBound( | |
+ cachedNameTable.begin(), cachedNameTable.end(), nameObj.name, NameTableEntryLessThan()); | |
+ QList<NameTableEntry>::iterator upper = qUpperBound( | |
+ cachedNameTable.begin(), cachedNameTable.end(), nameObj.name, NameTableEntryLessThan()); | |
+ bool inModel = (lower != upper); | |
+ | |
+ if (inModel) | |
+ { | |
+ // In model - update or delete | |
+ | |
+ if (nameObj.nHeight != NameTableEntry::NAME_NON_EXISTING) | |
+ { | |
+ printf("refreshName result : %s - refreshed in the table\n", qPrintable(nameObj.name)); | |
+ updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_UPDATED); | |
+ } | |
+ else | |
+ { | |
+ printf("refreshName result : %s - deleted from the table\n", qPrintable(nameObj.name)); | |
+ updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_DELETED); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ // Not in model - add or do nothing | |
+ | |
+ if (nameObj.nHeight != NameTableEntry::NAME_NON_EXISTING) | |
+ { | |
+ printf("refreshName result : %s - added to the table\n", qPrintable(nameObj.name)); | |
+ updateEntry(nameObj.name, nameObj.value, nameObj.nHeight, CT_NEW); | |
+ } | |
+ else | |
+ { | |
+ printf("refreshName result : %s - ignored (not in the table)\n", qPrintable(nameObj.name)); | |
+ } | |
+ } | |
+ } | |
+ | |
+ void updateEntry(const QString &name, const QString &value, int nHeight, int status, int *outNewRowIndex = NULL) | |
+ { | |
+ // Find name in model | |
+ QList<NameTableEntry>::iterator lower = qLowerBound( | |
+ cachedNameTable.begin(), cachedNameTable.end(), name, NameTableEntryLessThan()); | |
+ QList<NameTableEntry>::iterator upper = qUpperBound( | |
+ cachedNameTable.begin(), cachedNameTable.end(), name, NameTableEntryLessThan()); | |
+ int lowerIndex = (lower - cachedNameTable.begin()); | |
+ int upperIndex = (upper - cachedNameTable.begin()); | |
+ bool inModel = (lower != upper); | |
+ | |
+ switch(status) | |
+ { | |
+ case CT_NEW: | |
+ if (inModel) | |
+ { | |
+ if (outNewRowIndex) | |
+ { | |
+ *outNewRowIndex = parent->index(lowerIndex, 0).row(); | |
+ // HACK: ManageNamesPage uses this to ensure updating and get selected row, | |
+ // so we do not write warning into the log in this case | |
+ } | |
+ else { | |
+ // OutputDebugStringF("Warning: NameTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); | |
+ printf("Warning: NameTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); | |
+ } | |
+ break; | |
+ } | |
+ parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); | |
+ cachedNameTable.insert(lowerIndex, NameTableEntry(name, value, nHeight)); | |
+ parent->endInsertRows(); | |
+ if (outNewRowIndex) | |
+ *outNewRowIndex = parent->index(lowerIndex, 0).row(); | |
+ break; | |
+ case CT_UPDATED: | |
+ if (!inModel) | |
+ { | |
+ // OutputDebugStringF("Warning: NameTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); | |
+ printf("Warning: NameTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); | |
+ break; | |
+ } | |
+ lower->name = name; | |
+ lower->value = value; | |
+ lower->nHeight = nHeight; | |
+ parent->emitDataChanged(lowerIndex); | |
+ break; | |
+ case CT_DELETED: | |
+ if (!inModel) | |
+ { | |
+ // OutputDebugStringF("Warning: NameTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); | |
+ printf("Warning: NameTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); | |
+ break; | |
+ } | |
+ parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); | |
+ cachedNameTable.erase(lower, upper); | |
+ parent->endRemoveRows(); | |
+ break; | |
+ } | |
+ } | |
+ | |
+ int size() | |
+ { | |
+ return cachedNameTable.size(); | |
+ } | |
+ | |
+ NameTableEntry *index(int idx) | |
+ { | |
+ if (idx >= 0 && idx < cachedNameTable.size()) | |
+ { | |
+ return &cachedNameTable[idx]; | |
+ } | |
+ else | |
+ { | |
+ return NULL; | |
+ } | |
+ } | |
+}; | |
+ | |
+//NameTableModel::NameTableModel(CWallet *wallet, WalletModel *parent) : | |
+// QAbstractTableModel(parent), walletModel(parent), wallet(wallet), priv(0), cachedNumBlocks(0) | |
+NameTableModel::NameTableModel(const PlatformStyle *platformStyle, CWallet* wallet, WalletModel *parent): | |
+ QAbstractTableModel(parent), | |
+ wallet(wallet), | |
+ walletModel(parent), | |
+ priv(new NameTablePriv(wallet, this)), | |
+ platformStyle(platformStyle) | |
+{ | |
+ columns << tr("Name") << tr("Value") << tr("Expires in"); | |
+ priv = new NameTablePriv(wallet, this); | |
+ priv->refreshNameTable(); | |
+ | |
+ QTimer *timer = new QTimer(this); | |
+ connect(timer, SIGNAL(timeout()), this, SLOT(updateExpiration())); | |
+ timer->start(MODEL_UPDATE_DELAY); | |
+} | |
+ | |
+NameTableModel::~NameTableModel() | |
+{ | |
+ delete priv; | |
+} | |
+ | |
+void NameTableModel::updateExpiration() | |
+{ | |
+ int nBestHeight = chainActive.Height(); | |
+ if (nBestHeight != cachedNumBlocks) | |
+ { | |
+ LOCK(cs_main); | |
+ | |
+ cachedNumBlocks = nBestHeight; | |
+ // Blocks came in since last poll. | |
+ // Delete expired names | |
+ for (int i = 0, n = priv->size(); i < n; i++) | |
+ { | |
+ NameTableEntry *item = priv->index(i); | |
+ if (!item->HeightValid()) | |
+ continue; // Currently, unconfirmed names do not expire in the table | |
+ int nHeight = item->nHeight; | |
+ | |
+ // NOTE: the line below used to be: GetExpirationDepth(nHeight) | |
+ // I changed it to just nHeight for now | |
+ // int GetExpirationDepth(int nHeight) { | |
+ // if (nHeight < 24000) | |
+ // return 12000; | |
+ // if (nHeight < 48000) | |
+ // return nHeight - 12000; | |
+ // return 36000; | |
+ // } | |
+ if (nHeight + 36000 - nBestHeight <= 0) | |
+ { | |
+ priv->updateEntry(item->name, item->value, item->nHeight, CT_DELETED); | |
+ // Data array changed - restart scan | |
+ n = priv->size(); | |
+ i = -1; | |
+ } | |
+ } | |
+ // Invalidate expiration counter for all rows. | |
+ // Qt is smart enough to only actually request the data for the | |
+ // visible rows. | |
+ //emit | |
+ dataChanged(index(0, ExpiresIn), index(priv->size()-1, ExpiresIn)); | |
+ } | |
+} | |
+ | |
+void NameTableModel::updateTransaction(const QString &hash, int status) | |
+{ | |
+ uint256 hash256; | |
+ hash256.SetHex(hash.toStdString()); | |
+ | |
+ CTransaction tx; | |
+ | |
+ { | |
+ LOCK(wallet->cs_wallet); | |
+ | |
+ // Find transaction in wallet | |
+ std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash256); | |
+ if (mi == wallet->mapWallet.end()) | |
+ return; // Not our transaction | |
+ tx = mi->second; | |
+ } | |
+ | |
+ std::vector<unsigned char> vchName; | |
+ // TODO: write fn that takes tx and returns name_new, name_up, etc, constants | |
+ if (true /*!GetNameOfTx(tx, vchName)*/ ) | |
+ return; // Non-name transaction | |
+ | |
+ //printf("updateTransaction (%s, status=%d) calls refreshName (%s)\n", qPrintable(hash), status, vchFromString(vchName).c_str()); | |
+ // printf( | |
+ // "updateTransaction (%s, status=%d) calls refreshName (%s)\n", | |
+ // qPrintable(hash), status, ValtypeToString(vchName) | |
+ // ); | |
+ | |
+ priv->refreshName(vchName); | |
+} | |
+ | |
+int NameTableModel::rowCount(const QModelIndex &parent /*= QModelIndex()*/) const | |
+{ | |
+ Q_UNUSED(parent); | |
+ return priv->size(); | |
+} | |
+ | |
+int NameTableModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const | |
+{ | |
+ Q_UNUSED(parent); | |
+ return columns.length(); | |
+} | |
+ | |
+QVariant NameTableModel::data(const QModelIndex &index, int role) const | |
+{ | |
+ if (!index.isValid()) | |
+ return QVariant(); | |
+ | |
+ NameTableEntry *rec = static_cast<NameTableEntry*>(index.internalPointer()); | |
+ | |
+ if (role == Qt::DisplayRole || role == Qt::EditRole) | |
+ { | |
+ switch(index.column()) | |
+ { | |
+ case Name: | |
+ return rec->name; | |
+ case Value: | |
+ return rec->value; | |
+ case ExpiresIn: | |
+ if (!rec->HeightValid()) { | |
+ return QVariant(); | |
+ } | |
+ else { | |
+ int nBestHeight = chainActive.Height(); | |
+ // OG: return rec->nHeight + GetDisplayExpirationDepth(rec->nHeight) | |
+ // - pindexBest->nHeight; | |
+ return rec->nHeight + 36000 - nBestHeight; | |
+ } | |
+ } | |
+ } | |
+ return QVariant(); | |
+} | |
+ | |
+QVariant NameTableModel::headerData(int section, Qt::Orientation orientation, int role) const | |
+{ | |
+ if (orientation == Qt::Horizontal) | |
+ { | |
+ if (role == Qt::DisplayRole) | |
+ { | |
+ return columns[section]; | |
+ } | |
+ else if (role == Qt::TextAlignmentRole) | |
+ { | |
+ return column_alignments[section]; | |
+ } | |
+ else if (role == Qt::ToolTipRole) | |
+ { | |
+ switch(section) | |
+ { | |
+ case Name: | |
+ return tr("Name registered using Namecoin."); | |
+ case Value: | |
+ return tr("Data associated with the name."); | |
+ case ExpiresIn: | |
+ return tr("Number of blocks, after which the name will expire." | |
+ " Update name to renew it.\nEmpty cell means pending" | |
+ " (awaiting automatic name_firstupdate or awaiting " | |
+ "network confirmation)."); | |
+ } | |
+ } | |
+ } | |
+ return QVariant(); | |
+} | |
+ | |
+Qt::ItemFlags NameTableModel::flags(const QModelIndex &index) const | |
+{ | |
+ if (!index.isValid()) | |
+ return 0; | |
+ //NameTableEntry *rec = static_cast<NameTableEntry*>(index.internalPointer()); | |
+ | |
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled; | |
+} | |
+ | |
+QModelIndex NameTableModel::index(int row, int column, const QModelIndex &parent /* = QModelIndex()*/) const | |
+{ | |
+ Q_UNUSED(parent); | |
+ NameTableEntry *data = priv->index(row); | |
+ if (data) | |
+ { | |
+ return createIndex(row, column, priv->index(row)); | |
+ } | |
+ else | |
+ { | |
+ return QModelIndex(); | |
+ } | |
+} | |
+ | |
+void NameTableModel::updateEntry(const QString &name, const QString &value, int nHeight, int status, int *outNewRowIndex /*= NULL*/) | |
+{ | |
+ priv->updateEntry(name, value, nHeight, status, outNewRowIndex); | |
+} | |
+ | |
+void NameTableModel::emitDataChanged(int idx) | |
+{ | |
+ //emit | |
+ dataChanged(index(idx, 0), index(idx, columns.length()-1)); | |
+} | |
diff --git a/src/qt/nametablemodel.h b/src/qt/nametablemodel.h | |
new file mode 100644 | |
index 0000000..2e492e5 | |
--- /dev/null | |
+++ b/src/qt/nametablemodel.h | |
@@ -0,0 +1,81 @@ | |
+#ifndef NAMETABLEMODEL_H | |
+#define NAMETABLEMODEL_H | |
+ | |
+#include "bitcoinunits.h" | |
+ | |
+#include <QAbstractTableModel> | |
+#include <QStringList> | |
+ | |
+class PlatformStyle; | |
+class NameTablePriv; | |
+class CWallet; | |
+class WalletModel; | |
+ | |
+/** | |
+ Qt model for "Manage Names" page. | |
+ */ | |
+class NameTableModel : public QAbstractTableModel | |
+{ | |
+ Q_OBJECT | |
+ | |
+public: | |
+ explicit NameTableModel(const PlatformStyle *platformStyle, CWallet* wallet, WalletModel *parent = 0); | |
+ //explicit NameTableModel(CWallet *wallet, WalletModel *parent = 0); | |
+ ~NameTableModel(); | |
+ | |
+ enum ColumnIndex { | |
+ Name = 0, | |
+ Value = 1, | |
+ ExpiresIn = 2 | |
+ }; | |
+ | |
+ /** @name Methods overridden from QAbstractTableModel | |
+ @{*/ | |
+ int rowCount(const QModelIndex &parent = QModelIndex()) const; | |
+ int columnCount(const QModelIndex &parent = QModelIndex()) const; | |
+ QVariant data(const QModelIndex &index, int role) const; | |
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const; | |
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; | |
+ Qt::ItemFlags flags(const QModelIndex &index) const; | |
+ /*@}*/ | |
+ | |
+private: | |
+ const PlatformStyle *platformStyle; | |
+ CWallet *wallet; | |
+ WalletModel *walletModel; | |
+ QStringList columns; | |
+ NameTablePriv *priv; | |
+ int cachedNumBlocks; | |
+ | |
+ /** Notify listeners that data changed. */ | |
+ void emitDataChanged(int index); | |
+ | |
+public Q_SLOTS: | |
+ void updateEntry(const QString &name, const QString &value, int nHeight, int status, int *outNewRowIndex = NULL); | |
+ void updateExpiration(); | |
+ void updateTransaction(const QString &hash, int status); | |
+ | |
+ friend class NameTablePriv; | |
+}; | |
+ | |
+struct NameTableEntry | |
+{ | |
+ QString name; | |
+ QString value; | |
+ int nHeight; | |
+ | |
+ static const int NAME_NEW = -1; // Dummy nHeight value for not-yet-created names | |
+ static const int NAME_NON_EXISTING = -2; // Dummy nHeight value for unitinialized entries | |
+ static const int NAME_UNCONFIRMED = -3; // Dummy nHeight value for unconfirmed name transactions | |
+ | |
+ bool HeightValid() { return nHeight >= 0; } | |
+ static bool CompareHeight(int nOldHeight, int nNewHeight); // Returns true if new height is better | |
+ | |
+ NameTableEntry() : nHeight(NAME_NON_EXISTING) {} | |
+ NameTableEntry(const QString &name, const QString &value, int nHeight): | |
+ name(name), value(value), nHeight(nHeight) {} | |
+ NameTableEntry(const std::string &name, const std::string &value, int nHeight): | |
+ name(QString::fromStdString(name)), value(QString::fromStdString(value)), nHeight(nHeight) {} | |
+}; | |
+ | |
+#endif // NAMETABLEMODEL_H | |
diff --git a/src/qt/platformstyle.h b/src/qt/platformstyle.h | |
index 4e763e7..e6685c3 100644 | |
--- a/src/qt/platformstyle.h | |
+++ b/src/qt/platformstyle.h | |
@@ -52,4 +52,3 @@ private: | |
}; | |
#endif // BITCOIN_QT_PLATFORMSTYLE_H | |
- | |
diff --git a/src/qt/res/icons/tx_nameop.png b/src/qt/res/icons/tx_nameop.png | |
new file mode 100644 | |
index 0000000..e69de29 | |
diff --git a/src/qt/transactiondescdialog.cpp b/src/qt/transactiondescdialog.cpp | |
index fadaa98..4792187 100644 | |
--- a/src/qt/transactiondescdialog.cpp | |
+++ b/src/qt/transactiondescdialog.cpp | |
@@ -10,7 +10,7 @@ | |
#include <QModelIndex> | |
TransactionDescDialog::TransactionDescDialog(const QModelIndex &idx, QWidget *parent) : | |
- QDialog(parent), | |
+ QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint), | |
ui(new Ui::TransactionDescDialog) | |
{ | |
ui->setupUi(this); | |
diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp | |
index 5b16b10..73d3bc2 100644 | |
--- a/src/qt/transactionrecord.cpp | |
+++ b/src/qt/transactionrecord.cpp | |
@@ -83,9 +83,29 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * | |
{ | |
bool involvesWatchAddress = false; | |
isminetype fAllFromMe = ISMINE_SPENDABLE; | |
+ CAmount nCarriedOverCoin = 0; | |
BOOST_FOREACH(const CTxIn& txin, wtx.vin) | |
{ | |
isminetype mine = wallet->IsMine(txin); | |
+ if (mine != ISMINE_SPENDABLE) | |
+ { | |
+ // Check whether transaction input is name_* operation - in this case consider it ours | |
+ CTransaction txPrev; | |
+ // TODO: make sure this gets initialized to 0 | |
+ uint256 hash; | |
+ CTxDestination address; | |
+ if (GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), hash) && | |
+ txin.prevout.n < txPrev.vout.size() && | |
+ //hooks->ExtractAddress(txPrev.vout[txin.prevout.n].scriptPubKey, address) | |
+ ExtractDestination(txPrev.vout[txin.prevout.n].scriptPubKey, address) | |
+ ) | |
+ { | |
+ // This is our name transaction | |
+ // Accumulate the coin carried from name_new, because it is not actually spent | |
+ nCarriedOverCoin += txPrev.vout[txin.prevout.n].nValue; | |
+ mine = ISMINE_SPENDABLE; | |
+ } | |
+ } | |
if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; | |
if(fAllFromMe > mine) fAllFromMe = mine; | |
} | |
@@ -112,7 +132,7 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * | |
// | |
// Debit | |
// | |
- CAmount nTxFee = nDebit - wtx.GetValueOut(); | |
+ CAmount nTxFee = nDebit - (wtx.GetValueOut() - nCarriedOverCoin); | |
for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) | |
{ | |
@@ -128,13 +148,41 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * | |
continue; | |
} | |
+ CAmount nValue = txout.nValue; | |
+ | |
CTxDestination address; | |
+ CBitcoinAddress addrParsed; | |
+ | |
if (ExtractDestination(txout.scriptPubKey, address)) | |
{ | |
// Sent to Bitcoin Address | |
sub.type = TransactionRecord::SendToAddress; | |
sub.address = CBitcoinAddress(address).ToString(); | |
} | |
+ //else if (hooks->ExtractAddress(txout.scriptPubKey, address)) | |
+ else if (ExtractDestination(txout.scriptPubKey, address) && addrParsed.Set(address)) | |
+ { | |
+ sub.type = TransactionRecord::NameOp; | |
+ sub.address = addrParsed.ToString(); | |
+ | |
+ // Add carried coin (from name_new) | |
+ if (nCarriedOverCoin > 0) | |
+ { | |
+ // Note: we subtract nCarriedOverCoin equally from all name operations, | |
+ // until it becomes zero. It may fail for complex transactions, which | |
+ // update multiple names simultaneously (standard client never creates such transactions). | |
+ if (nValue >= nCarriedOverCoin) | |
+ { | |
+ nValue -= nCarriedOverCoin; | |
+ nCarriedOverCoin = 0; | |
+ } | |
+ else | |
+ { | |
+ nCarriedOverCoin -= nValue; | |
+ nValue = 0; | |
+ } | |
+ } | |
+ } | |
else | |
{ | |
// Sent to IP, or other non-address transaction like OP_EVAL | |
@@ -142,13 +190,13 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * | |
sub.address = mapValue["to"]; | |
} | |
- CAmount nValue = txout.nValue; | |
/* Add fee to first output */ | |
if (nTxFee > 0) | |
{ | |
nValue += nTxFee; | |
nTxFee = 0; | |
} | |
+ | |
sub.debit = -nValue; | |
parts.append(sub); | |
diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h | |
index a5bc375..540e6a1 100644 | |
--- a/src/qt/transactionrecord.h | |
+++ b/src/qt/transactionrecord.h | |
@@ -76,7 +76,8 @@ public: | |
SendToOther, | |
RecvWithAddress, | |
RecvFromOther, | |
- SendToSelf | |
+ SendToSelf, | |
+ NameOp, | |
}; | |
/** Number of confirmation recommended for accepting a transaction */ | |
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp | |
index 1647b2a..c2b8853 100644 | |
--- a/src/qt/transactiontablemodel.cpp | |
+++ b/src/qt/transactiontablemodel.cpp | |
@@ -377,6 +377,8 @@ QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const | |
return tr("Payment to yourself"); | |
case TransactionRecord::Generated: | |
return tr("Mined"); | |
+ case TransactionRecord::NameOp: | |
+ return tr("Name operation"); | |
default: | |
return QString(); | |
} | |
@@ -394,6 +396,8 @@ QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx | |
case TransactionRecord::SendToAddress: | |
case TransactionRecord::SendToOther: | |
return QIcon(":/icons/tx_output"); | |
+ case TransactionRecord::NameOp: | |
+ return QIcon(":/icons/tx_nameop"); | |
default: | |
return QIcon(":/icons/tx_inout"); | |
} | |
@@ -416,6 +420,7 @@ QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, b | |
case TransactionRecord::Generated: | |
return lookupAddress(wtx->address, tooltip) + watchAddress; | |
case TransactionRecord::SendToOther: | |
+ case TransactionRecord::NameOp: | |
return QString::fromStdString(wtx->address) + watchAddress; | |
case TransactionRecord::SendToSelf: | |
default: | |
@@ -506,7 +511,8 @@ QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const | |
{ | |
QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec); | |
if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther || | |
- rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress) | |
+ rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress || | |
+ rec->type==TransactionRecord::NameOp) | |
{ | |
tooltip += QString(" ") + formatTxToAddress(rec, true); | |
} | |
@@ -679,7 +685,8 @@ public: | |
TransactionNotification(uint256 hash, ChangeType status, bool showTransaction): | |
hash(hash), status(status), showTransaction(showTransaction) {} | |
- void invoke(QObject *ttm) | |
+ //void invoke(QObject *ttm, QObject *ntm) | |
+ void invoke(TransactionTableModel *ttm) //, QObject *ntm) | |
{ | |
QString strHash = QString::fromStdString(hash.GetHex()); | |
qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status); | |
@@ -687,6 +694,13 @@ public: | |
Q_ARG(QString, strHash), | |
Q_ARG(int, status), | |
Q_ARG(bool, showTransaction)); | |
+ //QMetaObject::invokeMethod(ntm, "updateTransaction", Qt::QueuedConnection, | |
+ // Q_ARG(QString, strHash), | |
+ // Q_ARG(int, status), | |
+ // Q_ARG(bool, showTransaction)); | |
+ // //Q_ARG(QString, strHash), | |
+ // //Q_ARG(int, status), | |
+ // //Q_ARG(bool, showTransaction)); | |
} | |
private: | |
uint256 hash; | |
@@ -712,6 +726,7 @@ static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet | |
vQueueNotifications.push_back(notification); | |
return; | |
} | |
+ // TODO: notify messages | |
notification.invoke(ttm); | |
} | |
diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp | |
index 4a9a198..c257951 100644 | |
--- a/src/qt/transactionview.cpp | |
+++ b/src/qt/transactionview.cpp | |
@@ -89,6 +89,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa | |
TransactionFilterProxy::TYPE(TransactionRecord::SendToOther)); | |
typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf)); | |
typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated)); | |
+ typeWidget->addItem(tr("Name operation"), TransactionFilterProxy::TYPE(TransactionRecord::NameOp)); | |
typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other)); | |
hlayout->addWidget(typeWidget); | |
diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp | |
index e4ca5e1..afc75a3 100644 | |
--- a/src/qt/walletframe.cpp | |
+++ b/src/qt/walletframe.cpp | |
@@ -134,6 +134,13 @@ void WalletFrame::gotoSendCoinsPage(QString addr) | |
i.value()->gotoSendCoinsPage(addr); | |
} | |
+void WalletFrame::gotoManageNamesPage() | |
+{ | |
+ QMap<QString, WalletView*>::const_iterator i; | |
+ for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) | |
+ i.value()->gotoManageNamesPage(); | |
+} | |
+ | |
void WalletFrame::gotoSignMessageTab(QString addr) | |
{ | |
WalletView *walletView = currentWalletView(); | |
diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h | |
index 9a5bc27..d557350 100644 | |
--- a/src/qt/walletframe.h | |
+++ b/src/qt/walletframe.h | |
@@ -59,6 +59,8 @@ public Q_SLOTS: | |
void gotoReceiveCoinsPage(); | |
/** Switch to send coins page */ | |
void gotoSendCoinsPage(QString addr = ""); | |
+ /** Switch to manage names page */ | |
+ void gotoManageNamesPage(); | |
/** Show Sign/Verify Message dialog and switch to sign message tab */ | |
void gotoSignMessageTab(QString addr = ""); | |
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp | |
index dc6eac1..d685d0e 100644 | |
--- a/src/qt/walletmodel.cpp | |
+++ b/src/qt/walletmodel.cpp | |
@@ -8,8 +8,9 @@ | |
#include "guiconstants.h" | |
#include "guiutil.h" | |
#include "paymentserver.h" | |
-#include "recentrequeststablemodel.h" | |
+#include "nametablemodel.h" | |
#include "transactiontablemodel.h" | |
+#include "recentrequeststablemodel.h" | |
#include "base58.h" | |
#include "keystore.h" | |
@@ -27,10 +28,12 @@ | |
#include <boost/foreach.hpp> | |
+std::map<std::vector<unsigned char>, PreparedNameFirstUpdate> mapMyNameFirstUpdate; | |
+std::map<uint160, std::vector<unsigned char> > mapMyNameHashes; | |
+ | |
WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : | |
QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), | |
- transactionTableModel(0), | |
- recentRequestsTableModel(0), | |
+ nameTableModel(0), transactionTableModel(0), recentRequestsTableModel(0), | |
cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), | |
cachedEncryptionStatus(Unencrypted), | |
cachedNumBlocks(0) | |
@@ -39,6 +42,7 @@ WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, Op | |
fForceCheckBalanceChanged = false; | |
addressTableModel = new AddressTableModel(wallet, this); | |
+ nameTableModel = new NameTableModel(platformStyle, wallet, this); | |
transactionTableModel = new TransactionTableModel(platformStyle, wallet, this); | |
recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); | |
@@ -274,7 +278,55 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact | |
CWalletTx *newTx = transaction.getTransaction(); | |
CReserveKey *keyChange = transaction.getPossibleKeyChange(); | |
- bool fCreated = wallet->CreateTransaction(vecSend, NULL, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); | |
+ // TODO: figure out what sign does | |
+ bool sign = false; | |
+ | |
+ // qt/walletmodel.cpp:277:135: error: no matching function for call to ‘ | |
+ //CWallet::CreateTransaction( | |
+ // std::vector<CRecipient>&, | |
+ // CWalletTx&, | |
+ // CReserveKey&, | |
+ // CAmount&, | |
+ // int&, | |
+ // std::string&, | |
+ // const CCoinControl*&)’ | |
+ | |
+ //bool CreateTransaction( | |
+ // const std::vector<CRecipient>& vecSend, | |
+ // const CTxIn* withInput, | |
+ // CWalletTx& wtxNew, | |
+ // CReserveKey& reservekey, | |
+ // CAmount& nFeeRet, | |
+ // int& nChangePosRet, | |
+ // std::string& strFailReason, | |
+ // const CCoinControl *coinControl = NULL, | |
+ // bool sign = true); | |
+ // qt/walletmodel.cpp: In member function ‘WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction&, const CCoinControl*)’: | |
+ // qt/walletmodel.cpp:277:135: error: no matching function for call to ‘CWallet::CreateTransaction(std::vector<CRecipient>&, CWalletTx&, CReserveKey&, CAmount&, int&, std::string&, const CCoinControl*&)’ | |
+ // bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); | |
+ | |
+ | |
+/* | |
+ bool fCreated = wallet->CreateTransaction( | |
+ vecSend, | |
+ *newTx, // TODO: inputTx | |
+ *keyChange, | |
+ nFeeRequired, | |
+ nChangePosRet, | |
+ strFailReason, | |
+ coinControl); | |
+*/ | |
+ bool fCreated = wallet->CreateTransaction( | |
+ vecSend, | |
+ NULL, | |
+ *newTx, | |
+ *keyChange, | |
+ nFeeRequired, | |
+ nChangePosRet, | |
+ strFailReason, | |
+ coinControl, | |
+ sign); | |
+ | |
transaction.setTransactionFee(nFeeRequired); | |
if (fSubtractFeeFromAmount && fCreated) | |
transaction.reassignAmounts(nChangePosRet); | |
@@ -380,6 +432,11 @@ AddressTableModel *WalletModel::getAddressTableModel() | |
return addressTableModel; | |
} | |
+NameTableModel *WalletModel::getNameTableModel() | |
+{ | |
+ return nameTableModel; | |
+} | |
+ | |
TransactionTableModel *WalletModel::getTransactionTableModel() | |
{ | |
return transactionTableModel; | |
@@ -668,3 +725,291 @@ bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t | |
else | |
return wallet->AddDestData(dest, key, sRequest); | |
} | |
+ | |
+bool WalletModel::nameAvailable(const QString &name) | |
+{ | |
+ const std::string strName = name.toStdString(); | |
+ | |
+ //std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ | |
+ CNameData data; | |
+ if (!pcoinsTip->GetName (ValtypeFromString(strName), data)) { | |
+ return false; | |
+ } | |
+ | |
+ // TODO: check to see if we own the name? we need to look | |
+ // and see where this gets called form. if it's only name_new | |
+ // then a does exist check should be enough. | |
+ return true; | |
+ | |
+ /* | |
+ std::vector<CNameIndex> vtxPos; | |
+ CNameDB dbName("r"); | |
+ if (!dbName.ReadName(vchName, vtxPos)) | |
+ return true; | |
+ | |
+ if (vtxPos.size() < 1) | |
+ return true; | |
+ | |
+ CDiskTxPos txPos = vtxPos[vtxPos.size() - 1].txPos; | |
+ CTransaction tx; | |
+ if (!tx.ReadFromDisk(txPos)) | |
+ return true; // This may indicate error, rather than name availability | |
+ | |
+ std::vector<unsigned char> vchValue; | |
+ int nHeight; | |
+ uint256 hash; | |
+ if (txPos.IsNull() || !GetValueOfTxPos(txPos, vchValue, hash, nHeight)) | |
+ return true; | |
+ | |
+ // TODO: should we subtract MIN_FIRSTUPDATE_DEPTH blocks? I think name_new may be possible when the previous registration is just about to expire | |
+ if(nHeight + GetDisplayExpirationDepth(nHeight) - pindexBest->nHeight <= 0) | |
+ return true; // Expired | |
+ */ | |
+ return true; | |
+} | |
+ | |
+WalletModel::NameNewReturn WalletModel::nameNew(const QString &name) | |
+{ | |
+ NameNewReturn ret; | |
+ | |
+ /* | |
+ std::string strName = name.toStdString(); | |
+ ret.vchName = std::vector<unsigned char>(strName.begin(), strName.end()); | |
+ | |
+ CWalletTx wtx; | |
+ wtx.nVersion = NAMECOIN_TX_VERSION; | |
+ | |
+ uint64 rand = GetRand((uint64)-1); | |
+ std::vector<unsigned char> vchRand = CBigNum(rand).getvch(); | |
+ std::vector<unsigned char> vchToHash(vchRand); | |
+ vchToHash.insert(vchToHash.end(), ret.vchName.begin(), ret.vchName.end()); | |
+ uint160 hash = Hash160(vchToHash); | |
+ | |
+ std::vector<unsigned char> strPubKey = wallet->GetKeyFromKeyPool(); | |
+ CScript scriptPubKeyOrig; | |
+ scriptPubKeyOrig.SetBitcoinAddress(strPubKey); | |
+ CScript scriptPubKey; | |
+ scriptPubKey << OP_NAME_NEW << hash << OP_2DROP; | |
+ scriptPubKey += scriptPubKeyOrig; | |
+ | |
+ CRITICAL_BLOCK(cs_main) | |
+ { | |
+ std::string strError = wallet->SendMoney(scriptPubKey, MIN_AMOUNT, wtx, true); | |
+ if (strError != "") | |
+ { | |
+ ret.ok = false; | |
+ ret.err_msg = QString::fromStdString(strError); | |
+ return ret; | |
+ } | |
+ ret.ok = true; | |
+ ret.hex = wtx.GetHash(); | |
+ ret.rand = rand; | |
+ ret.hash = hash; | |
+ | |
+ mapMyNames[ret.vchName] = ret.hex; | |
+ mapMyNameHashes[ret.hash] = ret.vchName; | |
+ mapMyNameFirstUpdate[ret.vchName].rand = ret.rand; | |
+ | |
+ strError = nameFirstUpdatePrepare(name, "").toStdString(); | |
+ if (strError != "") | |
+ { | |
+ printf("nameFirstUpdatePrepare for %s returned error: %s\n", strName.c_str(), strError.c_str()); | |
+ // We do not return this error, because we stored data to mapMyNameFirstUpdate and configure dialog should show up | |
+ } | |
+ } | |
+ */ | |
+ return ret; | |
+} | |
+ | |
+QString WalletModel::nameFirstUpdatePrepare(const QString &name, const QString &data) | |
+{ | |
+ std::string strName = name.toStdString (); | |
+ //std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ valtype vchName = ValtypeFromString (strName); | |
+ std::string strData = data.toStdString(); | |
+ //std::vector<unsigned char> vchValue(strData.begin(), strData.end()); | |
+ valtype vchData = ValtypeFromString (strData); | |
+ | |
+ LOCK(cs_main); | |
+ | |
+/* | |
+ //CRITICAL_BLOCK(cs_main); | |
+ { | |
+ std::map<std::vector<unsigned char>, uint256>::const_iterator it1 = mapMyNames.find(vchName); | |
+ | |
+ if (it1 == mapMyNames.end()) | |
+ return tr("Cannot find stored tx hash for name"); | |
+ std::map<std::vector<unsigned char>, PreparedNameFirstUpdate>::iterator it2 = mapMyNameFirstUpdate.find(vchName); | |
+ if (it2 == mapMyNameFirstUpdate.end()) | |
+ return tr("Cannot find stored rand value for name"); | |
+ | |
+ uint256 wtxInHash = it1->second; | |
+ uint64 rand = it2->second.rand; | |
+ | |
+ CWalletTx wtx; | |
+ std::string err_msg = nameFirstUpdateCreateTx(wtx, vchName, wtxInHash, rand, vchValue); | |
+ if (err_msg != "") | |
+ return QString::fromStdString(err_msg); | |
+ it2->second.vchData = vchValue; | |
+ it2->second.wtx = wtx; | |
+ | |
+ CRITICAL_BLOCK(wallet->cs_wallet) | |
+ wallet->WriteNameFirstUpdate(vchName, wtxInHash, rand, vchValue, wtx); | |
+ printf( | |
+ "Automatic name_firstupdate created for name %s, created tx: %s\n", | |
+ qPrintable(name), wtx.GetHash().GetHex().c_str()); | |
+ } | |
+*/ | |
+ return ""; | |
+} | |
+ | |
+void WalletModel::sendPendingNameFirstUpdates() | |
+{ | |
+ | |
+/* | |
+ CRITICAL_BLOCK(cs_main) | |
+ { | |
+ for (std::map<std::vector<unsigned char>, PreparedNameFirstUpdate>::iterator mi = mapMyNameFirstUpdate.begin(); | |
+ mi != mapMyNameFirstUpdate.end(); ) | |
+ { | |
+ const std::vector<unsigned char> &vchName = mi->first; | |
+ | |
+ std::map<std::vector<unsigned char>, uint256>::const_iterator it1 = mapMyNames.find(vchName); | |
+ if (it1 == mapMyNames.end()) | |
+ { | |
+ printf("Automatic name_firstupdate failed - no tx id for name %s\n", stringFromVch(vchName).c_str()); | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ continue; | |
+ } | |
+ uint256 wtxInHash = it1->second; | |
+ bool fSkip = false; | |
+ CRITICAL_BLOCK(wallet->cs_wallet) | |
+ CRITICAL_BLOCK(wallet->cs_wallet) | |
+ { | |
+ std::map<uint256, CWalletTx>::const_iterator it2 = wallet->mapWallet.find(wtxInHash); | |
+ if (it2 == wallet->mapWallet.end()) | |
+ { | |
+ printf("Automatic name_firstupdate failed - no wallet transaction for name %s (hash %s)\n", | |
+ stringFromVch(vchName).c_str(), | |
+ wtxInHash.GetHex().c_str()); | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ fSkip = true; | |
+ } | |
+ if (it2->second.GetDepthInMainChain() < MIN_FIRSTUPDATE_DEPTH) | |
+ { | |
+ mi++; | |
+ fSkip = true; | |
+ } | |
+ } | |
+ if (fSkip) | |
+ continue; | |
+ | |
+ printf("Sending automatic name_firstupdate for name %s\n", stringFromVch(vchName).c_str()); | |
+ | |
+ CWalletTx wtx = mi->second.wtx; | |
+ | |
+ // Currently we reserve the key when preparing firstupdate transaction. If the user changes | |
+ // name configuration before broadcasting the transaction, the key is forever left unused. CReserveKey dummyKey(NULL); | |
+ if (!wallet->CommitTransaction(wtx, dummyKey)) | |
+ { | |
+ printf("Automatic name_firstupdate failed. Name: %s, rand: %s, prevTx: %s, value: %s\n", | |
+ stringFromVch(vchName).c_str(), | |
+ HexStr(CBigNum(mi->second.rand).getvch()).c_str(), | |
+ wtxInHash.GetHex().c_str(), | |
+ stringFromVch(mi->second.vchData).c_str()); | |
+ } | |
+ else | |
+ { | |
+ // Report the rand value, so the user has a chance to resubmit name_firstupdate manually (e.g. if the network forks) | |
+ printf("Automatic name_firstupdate done. Name: %s, rand: %s, prevTx: %s, value: %s\n", | |
+ stringFromVch(vchName).c_str(), | |
+ HexStr(CBigNum(mi->second.rand).getvch()).c_str(), | |
+ wtxInHash.GetHex().c_str(), | |
+ stringFromVch(mi->second.vchData).c_str()); | |
+ } | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ } | |
+ } | |
+*/ | |
+} | |
+ | |
+QString WalletModel::nameUpdate(const QString &name, const QString &data, const QString &transferToAddress) | |
+{ | |
+ std::string strName = name.toStdString (); | |
+ // std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ valtype vchName = ValtypeFromString (strName); | |
+ | |
+ std::string strData = data.toStdString (); | |
+ // std::vector<unsigned char> vchValue(strData.begin(), strData.end()); | |
+ valtype vchData = ValtypeFromString (strData); | |
+ | |
+ CWalletTx wtx; | |
+ | |
+/* | |
+ wtx.nVersion = NAMECOIN_TX_VERSION; | |
+ CScript scriptPubKeyOrig; | |
+*/ | |
+ | |
+ | |
+ /* | |
+ if (transferToAddress != "") | |
+ { | |
+ std::string strAddress = transferToAddress.toStdString(); | |
+ uint160 hash160; | |
+ bool isValid = AddressToHash160(strAddress, hash160); | |
+ if (!isValid) | |
+ return tr("Invalid Namecoin address"); | |
+ scriptPubKeyOrig.SetBitcoinAddress(strAddress); | |
+ } | |
+ else | |
+ { | |
+ std::vector<unsigned char> strPubKey = wallet->GetKeyFromKeyPool(); | |
+ scriptPubKeyOrig.SetBitcoinAddress(strPubKey); | |
+ } | |
+ */ | |
+ if( transferToAddress != "") { | |
+ return tr("Not Implemented"); | |
+ } else { | |
+ // get key from wallet | |
+ // build pubkey | |
+ } | |
+ | |
+ /* | |
+ CScript scriptPubKey; | |
+ scriptPubKey << OP_NAME_UPDATE << vchName << vchValue << OP_2DROP << OP_DROP; | |
+ scriptPubKey += scriptPubKeyOrig; | |
+ | |
+ CRITICAL_BLOCK(cs_main) | |
+ CRITICAL_BLOCK(wallet->cs_mapWallet) | |
+ { | |
+ if (mapNamePending.count(vchName) && mapNamePending[vchName].size()) | |
+ { | |
+ error("name_update() : there are %d pending operations on that name, including %s", | |
+ mapNamePending[vchName].size(), | |
+ mapNamePending[vchName].begin()->GetHex().c_str()); | |
+ return tr("There are pending operations on that name"); | |
+ } | |
+ | |
+ CNameDB dbName("r"); | |
+ CTransaction tx; | |
+ if (!GetTxOfName(dbName, vchName, tx)) | |
+ return tr("Could not find a coin with this name"); | |
+ | |
+ uint256 wtxInHash = tx.GetHash(); | |
+ | |
+ if (!wallet->mapWallet.count(wtxInHash)) | |
+ { | |
+ error("name_update() : this coin is not in your wallet %s", | |
+ wtxInHash.GetHex().c_str()); | |
+ return tr("This coin is not in your wallet"); | |
+ } | |
+ | |
+ CWalletTx& wtxIn = wallet->mapWallet[wtxInHash]; | |
+ return QString::fromStdString(SendMoneyWithInputTx(scriptPubKey, MIN_AMOUNT, 0, wtxIn, wtx, true)); | |
+ } | |
+*/ | |
+} | |
diff --git a/src/qt/walletmodel.cpp.PATCH b/src/qt/walletmodel.cpp.PATCH | |
new file mode 100644 | |
index 0000000..25f7c5f | |
--- /dev/null | |
+++ b/src/qt/walletmodel.cpp.PATCH | |
@@ -0,0 +1,1047 @@ | |
+// Copyright (c) 2011-2015 The Bitcoin Core developers | |
+// Distributed under the MIT software license, see the accompanying | |
+// file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
+ | |
+#include "walletmodel.h" | |
+ | |
+#include "addresstablemodel.h" | |
+#include "guiconstants.h" | |
+#include "guiutil.h" | |
+#include "paymentserver.h" | |
+#include "recentrequeststablemodel.h" | |
+#include "nametablemodel.h" | |
+#include "transactiontablemodel.h" | |
+ | |
+#include "base58.h" | |
+#include "keystore.h" | |
+#include "main.h" | |
+#include "sync.h" | |
+//#include "namecoin.h" | |
+#include "names/common.h" | |
+#include "ui_interface.h" | |
+#include "wallet/wallet.h" | |
+#include "wallet/walletdb.h" // for BackupWallet | |
+ | |
+#include <stdint.h> | |
+ | |
+#include <QDebug> | |
+#include <QSet> | |
+#include <QTimer> | |
+ | |
+#include <boost/foreach.hpp> | |
+ | |
+std::map<std::vector<unsigned char>, PreparedNameFirstUpdate> mapMyNameFirstUpdate; | |
+std::map<uint160, std::vector<unsigned char> > mapMyNameHashes; | |
+ | |
+WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, OptionsModel *optionsModel, QObject *parent) : | |
+ QObject(parent), wallet(wallet), optionsModel(optionsModel), addressTableModel(0), | |
+ nameTableModel(0), transactionTableModel(0), | |
+ recentRequestsTableModel(0), | |
+ cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), | |
+ cachedEncryptionStatus(Unencrypted), | |
+ cachedNumBlocks(0) | |
+{ | |
+ fHaveWatchOnly = wallet->HaveWatchOnly(); | |
+ fForceCheckBalanceChanged = false; | |
+ | |
+ addressTableModel = new AddressTableModel(wallet, this); | |
+ nameTableModel = new NameTableModel(wallet, this); | |
+ transactionTableModel = new TransactionTableModel(platformStyle, wallet, this); | |
+ recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); | |
+ | |
+ // This timer will be fired repeatedly to update the balance | |
+ pollTimer = new QTimer(this); | |
+ connect(pollTimer, SIGNAL(timeout()), this, SLOT(pollBalanceChanged())); | |
+ pollTimer->start(MODEL_UPDATE_DELAY); | |
+ | |
+ subscribeToCoreSignals(); | |
+} | |
+ | |
+WalletModel::~WalletModel() | |
+{ | |
+ unsubscribeFromCoreSignals(); | |
+} | |
+ | |
+CAmount WalletModel::getBalance(const CCoinControl *coinControl) const | |
+{ | |
+ if (coinControl) | |
+ { | |
+ CAmount nBalance = 0; | |
+ std::vector<COutput> vCoins; | |
+ wallet->AvailableCoins(vCoins, true, coinControl); | |
+ BOOST_FOREACH(const COutput& out, vCoins) | |
+ if(out.fSpendable) | |
+ nBalance += out.tx->vout[out.i].nValue; | |
+ | |
+ return nBalance; | |
+ } | |
+ | |
+ return wallet->GetBalance(); | |
+} | |
+ | |
+CAmount WalletModel::getUnconfirmedBalance() const | |
+{ | |
+ return wallet->GetUnconfirmedBalance(); | |
+} | |
+ | |
+CAmount WalletModel::getImmatureBalance() const | |
+{ | |
+ return wallet->GetImmatureBalance(); | |
+} | |
+ | |
+bool WalletModel::haveWatchOnly() const | |
+{ | |
+ return fHaveWatchOnly; | |
+} | |
+ | |
+CAmount WalletModel::getWatchBalance() const | |
+{ | |
+ return wallet->GetWatchOnlyBalance(); | |
+} | |
+ | |
+CAmount WalletModel::getWatchUnconfirmedBalance() const | |
+{ | |
+ return wallet->GetUnconfirmedWatchOnlyBalance(); | |
+} | |
+ | |
+CAmount WalletModel::getWatchImmatureBalance() const | |
+{ | |
+ return wallet->GetImmatureWatchOnlyBalance(); | |
+} | |
+ | |
+void WalletModel::updateStatus() | |
+{ | |
+ EncryptionStatus newEncryptionStatus = getEncryptionStatus(); | |
+ | |
+ if(cachedEncryptionStatus != newEncryptionStatus) | |
+ Q_EMIT encryptionStatusChanged(newEncryptionStatus); | |
+} | |
+ | |
+void WalletModel::pollBalanceChanged() | |
+{ | |
+ // Get required locks upfront. This avoids the GUI from getting stuck on | |
+ // periodical polls if the core is holding the locks for a longer time - | |
+ // for example, during a wallet rescan. | |
+ TRY_LOCK(cs_main, lockMain); | |
+ if(!lockMain) | |
+ return; | |
+ TRY_LOCK(wallet->cs_wallet, lockWallet); | |
+ if(!lockWallet) | |
+ return; | |
+ | |
+ if(fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks) | |
+ { | |
+ fForceCheckBalanceChanged = false; | |
+ | |
+ // Balance and number of transactions might have changed | |
+ cachedNumBlocks = chainActive.Height(); | |
+ | |
+ checkBalanceChanged(); | |
+ if(transactionTableModel) | |
+ transactionTableModel->updateConfirmations(); | |
+ | |
+ if (!IsInitialBlockDownload()) | |
+ sendPendingNameFirstUpdates(); | |
+ } | |
+} | |
+ | |
+void WalletModel::checkBalanceChanged() | |
+{ | |
+ CAmount newBalance = getBalance(); | |
+ CAmount newUnconfirmedBalance = getUnconfirmedBalance(); | |
+ CAmount newImmatureBalance = getImmatureBalance(); | |
+ CAmount newWatchOnlyBalance = 0; | |
+ CAmount newWatchUnconfBalance = 0; | |
+ CAmount newWatchImmatureBalance = 0; | |
+ if (haveWatchOnly()) | |
+ { | |
+ newWatchOnlyBalance = getWatchBalance(); | |
+ newWatchUnconfBalance = getWatchUnconfirmedBalance(); | |
+ newWatchImmatureBalance = getWatchImmatureBalance(); | |
+ } | |
+ | |
+ if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance || cachedImmatureBalance != newImmatureBalance || | |
+ cachedWatchOnlyBalance != newWatchOnlyBalance || cachedWatchUnconfBalance != newWatchUnconfBalance || cachedWatchImmatureBalance != newWatchImmatureBalance) | |
+ { | |
+ cachedBalance = newBalance; | |
+ cachedUnconfirmedBalance = newUnconfirmedBalance; | |
+ cachedImmatureBalance = newImmatureBalance; | |
+ cachedWatchOnlyBalance = newWatchOnlyBalance; | |
+ cachedWatchUnconfBalance = newWatchUnconfBalance; | |
+ cachedWatchImmatureBalance = newWatchImmatureBalance; | |
+ Q_EMIT balanceChanged(newBalance, newUnconfirmedBalance, newImmatureBalance, | |
+ newWatchOnlyBalance, newWatchUnconfBalance, newWatchImmatureBalance); | |
+ } | |
+} | |
+ | |
+void WalletModel::sendPendingNameFirstUpdates() | |
+{ | |
+ CRITICAL_BLOCK(cs_main) | |
+ { | |
+ for (std::map<std::vector<unsigned char>, PreparedNameFirstUpdate>::iterator mi = mapMyNameFirstUpdate.begin(); | |
+ mi != mapMyNameFirstUpdate.end(); ) | |
+ { | |
+ const std::vector<unsigned char> &vchName = mi->first; | |
+ | |
+ std::map<std::vector<unsigned char>, uint256>::const_iterator it1 = mapMyNames.find(vchName); | |
+ if (it1 == mapMyNames.end()) | |
+ { | |
+ printf("Automatic name_firstupdate failed - no tx id for name %s\n", stringFromVch(vchName).c_str()); | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ continue; | |
+ } | |
+ uint256 wtxInHash = it1->second; | |
+ bool fSkip = false; | |
+ CRITICAL_BLOCK(wallet->cs_wallet) | |
+ { | |
+ std::map<uint256, CWalletTx>::const_iterator it2 = wallet->mapWallet.find(wtxInHash); | |
+ if (it2 == wallet->mapWallet.end()) | |
+ { | |
+ printf("Automatic name_firstupdate failed - no wallet transaction for name %s (hash %s)\n", | |
+ stringFromVch(vchName).c_str(), | |
+ wtxInHash.GetHex().c_str()); | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ fSkip = true; | |
+ } | |
+ if (it2->second.GetDepthInMainChain() < MIN_FIRSTUPDATE_DEPTH) | |
+ { | |
+ mi++; | |
+ fSkip = true; | |
+ } | |
+ } | |
+ if (fSkip) | |
+ continue; | |
+ | |
+ printf("Sending automatic name_firstupdate for name %s\n", stringFromVch(vchName).c_str()); | |
+ | |
+ CWalletTx wtx = mi->second.wtx; | |
+ | |
+ // Currently we reserve the key when preparing firstupdate transaction. If the user changes | |
+ // name configuration before broadcasting the transaction, the key is forever left unused. | |
+ CReserveKey dummyKey(NULL); | |
+ | |
+ if (!wallet->CommitTransaction(wtx, dummyKey)) | |
+ { | |
+ printf("Automatic name_firstupdate failed. Name: %s, rand: %s, prevTx: %s, value: %s\n", | |
+ stringFromVch(vchName).c_str(), | |
+ HexStr(CBigNum(mi->second.rand).getvch()).c_str(), | |
+ wtxInHash.GetHex().c_str(), | |
+ stringFromVch(mi->second.vchData).c_str()); | |
+ } | |
+ else | |
+ { | |
+ // Report the rand value, so the user has a chance to resubmit name_firstupdate manually (e.g. if the network forks) | |
+ printf("Automatic name_firstupdate done. Name: %s, rand: %s, prevTx: %s, value: %s\n", | |
+ stringFromVch(vchName).c_str(), | |
+ HexStr(CBigNum(mi->second.rand).getvch()).c_str(), | |
+ wtxInHash.GetHex().c_str(), | |
+ stringFromVch(mi->second.vchData).c_str()); | |
+ } | |
+ | |
+ wallet->EraseNameFirstUpdate(vchName); | |
+ mapMyNameFirstUpdate.erase(mi++); | |
+ } | |
+ } | |
+} | |
+ | |
+// Equivalent of name_firstupdate that does not send the transaction (the transaction is kept for 12 blocks). | |
+// This is needed because of wallet encryption (otherwise we could store just hash+rand+value and create transaction | |
+// on-the-fly after 12 blocks). | |
+// Must hold cs_main lock. | |
+std::string WalletModel::nameFirstUpdateCreateTx(CWalletTx &wtx, const std::vector<unsigned char> &vchName, uint256 wtxInHash, uint64 rand, const std::vector<unsigned char> &vchValue) | |
+{ | |
+ wtx.nVersion = NAMECOIN_TX_VERSION; | |
+ | |
+ if (mapNamePending.count(vchName) && mapNamePending[vchName].size()) | |
+ { | |
+ error("name_firstupdate() : there are %d pending operations on that name, including %s", | |
+ mapNamePending[vchName].size(), | |
+ mapNamePending[vchName].begin()->GetHex().c_str()); | |
+ return _("there are pending operations on that name"); | |
+ } | |
+ | |
+ { | |
+ CNameDB dbName("r"); | |
+ CTransaction tx; | |
+ if (GetTxOfName(dbName, vchName, tx)) | |
+ { | |
+ error("name_firstupdate() : this name is already active with tx %s", | |
+ tx.GetHash().GetHex().c_str()); | |
+ return _("this name is already active"); | |
+ } | |
+ } | |
+ | |
+ if (!wallet->mapWallet.count(wtxInHash)) | |
+ return _("previous transaction is not in the wallet"); | |
+ | |
+ std::vector<unsigned char> vchRand = CBigNum(rand).getvch(); | |
+ | |
+ std::vector<unsigned char> strPubKey = wallet->GetKeyFromKeyPool(); | |
+ CScript scriptPubKeyOrig; | |
+ scriptPubKeyOrig.SetBitcoinAddress(strPubKey); | |
+ CScript scriptPubKey; | |
+ scriptPubKey << OP_NAME_FIRSTUPDATE << vchName << vchRand << vchValue << OP_2DROP << OP_2DROP; | |
+ scriptPubKey += scriptPubKeyOrig; | |
+ | |
+ CWalletTx& wtxIn = wallet->mapWallet[wtxInHash]; | |
+ std::vector<unsigned char> vchHash; | |
+ bool found = false; | |
+ BOOST_FOREACH(CTxOut& out, wtxIn.vout) | |
+ { | |
+ std::vector<std::vector<unsigned char> > vvch; | |
+ int op; | |
+ if (DecodeNameScript(out.scriptPubKey, op, vvch)) { | |
+ if (op != OP_NAME_NEW) | |
+ return _("previous transaction wasn't a name_new"); | |
+ vchHash = vvch[0]; | |
+ found = true; | |
+ } | |
+ } | |
+ | |
+ if (!found) | |
+ return _("previous tx on this name is not a name tx"); | |
+ | |
+ std::vector<unsigned char> vchToHash(vchRand); | |
+ vchToHash.insert(vchToHash.end(), vchName.begin(), vchName.end()); | |
+ uint160 hash = Hash160(vchToHash); | |
+ if (uint160(vchHash) != hash) | |
+ return _("previous tx used a different random value"); | |
+ | |
+ int64 nNetFee = GetNetworkFee(pindexBest->nHeight); | |
+ // Round up to CENT | |
+ nNetFee += CENT - 1; | |
+ nNetFee = (nNetFee / CENT) * CENT; | |
+ | |
+ //return SendMoneyWithInputTx(scriptPubKey, MIN_AMOUNT, nNetFee, wtxIn, wtx, false); | |
+ | |
+ int64 nValue = MIN_AMOUNT; | |
+ | |
+ int nTxOut = IndexOfNameOutput(wtxIn); | |
+ CReserveKey reservekey(wallet); | |
+ int64 nFeeRequired; | |
+ std::vector< std::pair<CScript, int64> > vecSend; | |
+ vecSend.push_back(make_pair(scriptPubKey, nValue)); | |
+ | |
+ if (nNetFee) | |
+ { | |
+ CScript scriptFee; | |
+ scriptFee << OP_RETURN; | |
+ vecSend.push_back(make_pair(scriptFee, nNetFee)); | |
+ } | |
+ | |
+ if (!CreateTransactionWithInputTx(vecSend, wtxIn, nTxOut, wtx, reservekey, nFeeRequired)) | |
+ { | |
+ std::string strError; | |
+ if (nValue + nFeeRequired > wallet->GetBalance()) | |
+ strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str()); | |
+ else | |
+ strError = _("Error: Transaction creation failed "); | |
+ printf("nameFirstUpdateCreateTx() : %s", strError.c_str()); | |
+ return strError; | |
+ } | |
+ | |
+ // Note: currently we do not notify the user about the name_firstupdate fee: | |
+ // - it can be confusing, since name_firstupdate can be re-configured many times | |
+ // - canceling the fee will leave the configured name in inconsistent state: name_new without pending name_firstupdate may result in losing the hex value (rand) | |
+ //if (!uiInterface.ThreadSafeAskFee(nFeeRequired)) | |
+ // return "ABORTED"; | |
+ | |
+ // Take key pair from key pool so it won't be used again | |
+ reservekey.KeepKey(); | |
+ | |
+ if (!wtx.CheckTransaction()) | |
+ return "Error: CheckTransaction failed for transaction created by nameFirstUpdateCreateTx"; | |
+ | |
+ return ""; | |
+} | |
+ | |
+void WalletModel::updateTransaction() | |
+{ | |
+ // Balance and number of transactions might have changed | |
+ fForceCheckBalanceChanged = true; | |
+} | |
+ | |
+void WalletModel::updateAddressBook(const QString &address, const QString &label, | |
+ bool isMine, const QString &purpose, int status) | |
+{ | |
+ if(addressTableModel) | |
+ addressTableModel->updateEntry(address, label, isMine, purpose, status); | |
+} | |
+ | |
+void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) | |
+{ | |
+ fHaveWatchOnly = fHaveWatchonly; | |
+ Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); | |
+} | |
+ | |
+bool WalletModel::validateAddress(const QString &address) | |
+{ | |
+ CBitcoinAddress addressParsed(address.toStdString()); | |
+ return addressParsed.IsValid(); | |
+} | |
+ | |
+WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) | |
+{ | |
+ CAmount total = 0; | |
+ bool fSubtractFeeFromAmount = false; | |
+ QList<SendCoinsRecipient> recipients = transaction.getRecipients(); | |
+ std::vector<CRecipient> vecSend; | |
+ | |
+ if(recipients.empty()) | |
+ { | |
+ return OK; | |
+ } | |
+ | |
+ QSet<QString> setAddress; // Used to detect duplicates | |
+ int nAddresses = 0; | |
+ | |
+ // Pre-check input data for validity | |
+ Q_FOREACH(const SendCoinsRecipient &rcp, recipients) | |
+ { | |
+ if (rcp.fSubtractFeeFromAmount) | |
+ fSubtractFeeFromAmount = true; | |
+ | |
+ if (rcp.paymentRequest.IsInitialized()) | |
+ { // PaymentRequest... | |
+ CAmount subtotal = 0; | |
+ const payments::PaymentDetails& details = rcp.paymentRequest.getDetails(); | |
+ for (int i = 0; i < details.outputs_size(); i++) | |
+ { | |
+ const payments::Output& out = details.outputs(i); | |
+ if (out.amount() <= 0) continue; | |
+ subtotal += out.amount(); | |
+ const unsigned char* scriptStr = (const unsigned char*)out.script().data(); | |
+ CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); | |
+ CAmount nAmount = out.amount(); | |
+ CRecipient recipient = {scriptPubKey, nAmount, rcp.fSubtractFeeFromAmount}; | |
+ vecSend.push_back(recipient); | |
+ } | |
+ if (subtotal <= 0) | |
+ { | |
+ return InvalidAmount; | |
+ } | |
+ total += subtotal; | |
+ } | |
+ else | |
+ { // User-entered bitcoin address / amount: | |
+ if(!validateAddress(rcp.address)) | |
+ { | |
+ return InvalidAddress; | |
+ } | |
+ if(rcp.amount <= 0) | |
+ { | |
+ return InvalidAmount; | |
+ } | |
+ setAddress.insert(rcp.address); | |
+ ++nAddresses; | |
+ | |
+ CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); | |
+ CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; | |
+ vecSend.push_back(recipient); | |
+ | |
+ total += rcp.amount; | |
+ } | |
+ } | |
+ if(setAddress.size() != nAddresses) | |
+ { | |
+ return DuplicateAddress; | |
+ } | |
+ | |
+ CAmount nBalance = getBalance(coinControl); | |
+ | |
+ if(total > nBalance) | |
+ { | |
+ return AmountExceedsBalance; | |
+ } | |
+ | |
+ { | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ | |
+ transaction.newPossibleKeyChange(wallet); | |
+ | |
+ CAmount nFeeRequired = 0; | |
+ int nChangePosRet = -1; | |
+ std::string strFailReason; | |
+ | |
+ CWalletTx *newTx = transaction.getTransaction(); | |
+ CReserveKey *keyChange = transaction.getPossibleKeyChange(); | |
+ bool fCreated = wallet->CreateTransaction(vecSend, NULL, *newTx, *keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); | |
+ transaction.setTransactionFee(nFeeRequired); | |
+ if (fSubtractFeeFromAmount && fCreated) | |
+ transaction.reassignAmounts(nChangePosRet); | |
+ | |
+ if(!fCreated) | |
+ { | |
+ if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) | |
+ { | |
+ return SendCoinsReturn(AmountWithFeeExceedsBalance); | |
+ } | |
+ Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason), | |
+ CClientUIInterface::MSG_ERROR); | |
+ return TransactionCreationFailed; | |
+ } | |
+ | |
+ // reject absurdly high fee. (This can never happen because the | |
+ // wallet caps the fee at maxTxFee. This merely serves as a | |
+ // belt-and-suspenders check) | |
+ if (nFeeRequired > maxTxFee) | |
+ return AbsurdFee; | |
+ } | |
+ | |
+ 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(); | |
+ | |
+ Q_FOREACH(const SendCoinsRecipient &rcp, transaction.getRecipients()) | |
+ { | |
+ if (rcp.paymentRequest.IsInitialized()) | |
+ { | |
+ // Make sure any payment requests involved are still valid. | |
+ if (PaymentServer::verifyExpired(rcp.paymentRequest.getDetails())) { | |
+ return PaymentRequestExpired; | |
+ } | |
+ | |
+ // Store PaymentRequests in wtx.vOrderForm in wallet. | |
+ std::string key("PaymentRequest"); | |
+ std::string value; | |
+ rcp.paymentRequest.SerializeToString(&value); | |
+ newTx->vOrderForm.push_back(make_pair(key, value)); | |
+ } | |
+ else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) | |
+ newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString())); | |
+ } | |
+ | |
+ CReserveKey *keyChange = transaction.getPossibleKeyChange(); | |
+ if(!wallet->CommitTransaction(*newTx, *keyChange)) | |
+ return TransactionCommitFailed; | |
+ | |
+ CTransaction* t = (CTransaction*)newTx; | |
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | |
+ ssTx << *t; | |
+ transaction_array.append(&(ssTx[0]), ssTx.size()); | |
+ } | |
+ | |
+ // Add addresses / update labels that we've sent to to the address book, | |
+ // and emit coinsSent signal for each recipient | |
+ Q_FOREACH(const SendCoinsRecipient &rcp, transaction.getRecipients()) | |
+ { | |
+ // Don't touch the address book when we have a payment request | |
+ if (!rcp.paymentRequest.IsInitialized()) | |
+ { | |
+ std::string strAddress = rcp.address.toStdString(); | |
+ CTxDestination dest = CBitcoinAddress(strAddress).Get(); | |
+ std::string strLabel = rcp.label.toStdString(); | |
+ { | |
+ 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 | |
+ } | |
+ } | |
+ } | |
+ Q_EMIT coinsSent(wallet, rcp, transaction_array); | |
+ } | |
+ checkBalanceChanged(); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits | |
+ | |
+ return SendCoinsReturn(OK); | |
+} | |
+ | |
+bool WalletModel::nameAvailable(const QString &name) | |
+{ | |
+ std::string strName = name.toStdString(); | |
+ std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ | |
+ std::vector<CNameIndex> vtxPos; | |
+ CNameDB dbName("r"); | |
+ if (!dbName.ReadName(vchName, vtxPos)) | |
+ return true; | |
+ | |
+ if (vtxPos.size() < 1) | |
+ return true; | |
+ | |
+ CDiskTxPos txPos = vtxPos[vtxPos.size() - 1].txPos; | |
+ CTransaction tx; | |
+ if (!tx.ReadFromDisk(txPos)) | |
+ return true; // This may indicate error, rather than name availability | |
+ | |
+ std::vector<unsigned char> vchValue; | |
+ int nHeight; | |
+ uint256 hash; | |
+ if (txPos.IsNull() || !GetValueOfTxPos(txPos, vchValue, hash, nHeight)) | |
+ return true; | |
+ | |
+ // TODO: should we subtract MIN_FIRSTUPDATE_DEPTH blocks? I think name_new may be possible when the previous registration is just about to expire | |
+ if(nHeight + GetDisplayExpirationDepth(nHeight) - pindexBest->nHeight <= 0) | |
+ return true; // Expired | |
+ | |
+ return false; | |
+} | |
+ | |
+WalletModel::NameNewReturn WalletModel::nameNew(const QString &name) | |
+{ | |
+ NameNewReturn ret; | |
+ | |
+ std::string strName = name.toStdString(); | |
+ ret.vchName = std::vector<unsigned char>(strName.begin(), strName.end()); | |
+ | |
+ CWalletTx wtx; | |
+ wtx.nVersion = NAMECOIN_TX_VERSION; | |
+ | |
+ uint64 rand = GetRand((uint64)-1); | |
+ std::vector<unsigned char> vchRand = CBigNum(rand).getvch(); | |
+ std::vector<unsigned char> vchToHash(vchRand); | |
+ vchToHash.insert(vchToHash.end(), ret.vchName.begin(), ret.vchName.end()); | |
+ uint160 hash = Hash160(vchToHash); | |
+ | |
+ std::vector<unsigned char> strPubKey = wallet->GetKeyFromKeyPool(); | |
+ CScript scriptPubKeyOrig; | |
+ scriptPubKeyOrig.SetBitcoinAddress(strPubKey); | |
+ CScript scriptPubKey; | |
+ scriptPubKey << OP_NAME_NEW << hash << OP_2DROP; | |
+ scriptPubKey += scriptPubKeyOrig; | |
+ | |
+ CRITICAL_BLOCK(cs_main) | |
+ { | |
+ std::string strError = wallet->SendMoney(scriptPubKey, MIN_AMOUNT, wtx, true); | |
+ if (strError != "") | |
+ { | |
+ ret.ok = false; | |
+ ret.err_msg = QString::fromStdString(strError); | |
+ return ret; | |
+ } | |
+ ret.ok = true; | |
+ ret.hex = wtx.GetHash(); | |
+ ret.rand = rand; | |
+ ret.hash = hash; | |
+ | |
+ mapMyNames[ret.vchName] = ret.hex; | |
+ mapMyNameHashes[ret.hash] = ret.vchName; | |
+ mapMyNameFirstUpdate[ret.vchName].rand = ret.rand; | |
+ | |
+ strError = nameFirstUpdatePrepare(name, "").toStdString(); | |
+ if (strError != "") | |
+ { | |
+ printf("nameFirstUpdatePrepare for %s returned error: %s\n", strName.c_str(), strError.c_str()); | |
+ // We do not return this error, because we stored data to mapMyNameFirstUpdate and configure dialog should show up | |
+ } | |
+ } | |
+ return ret; | |
+} | |
+ | |
+QString WalletModel::nameFirstUpdatePrepare(const QString &name, const QString &data) | |
+{ | |
+ std::string strName = name.toStdString(); | |
+ std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ | |
+ std::string strData = data.toStdString(); | |
+ std::vector<unsigned char> vchValue(strData.begin(), strData.end()); | |
+ | |
+ CRITICAL_BLOCK(cs_main) | |
+ { | |
+ std::map<std::vector<unsigned char>, uint256>::const_iterator it1 = mapMyNames.find(vchName); | |
+ if (it1 == mapMyNames.end()) | |
+ return tr("Cannot find stored tx hash for name"); | |
+ | |
+ std::map<std::vector<unsigned char>, PreparedNameFirstUpdate>::iterator it2 = mapMyNameFirstUpdate.find(vchName); | |
+ if (it2 == mapMyNameFirstUpdate.end()) | |
+ return tr("Cannot find stored rand value for name"); | |
+ | |
+ uint256 wtxInHash = it1->second; | |
+ uint64 rand = it2->second.rand; | |
+ | |
+ CWalletTx wtx; | |
+ std::string err_msg = nameFirstUpdateCreateTx(wtx, vchName, wtxInHash, rand, vchValue); | |
+ if (err_msg != "") | |
+ return QString::fromStdString(err_msg); | |
+ it2->second.vchData = vchValue; | |
+ it2->second.wtx = wtx; | |
+ | |
+ CRITICAL_BLOCK(wallet->cs_wallet) | |
+ wallet->WriteNameFirstUpdate(vchName, wtxInHash, rand, vchValue, wtx); | |
+ printf("Automatic name_firstupdate created for name %s, created tx: %s\n", qPrintable(name), wtx.GetHash().GetHex().c_str()); | |
+ } | |
+ | |
+ return ""; | |
+} | |
+ | |
+QString WalletModel::nameUpdate(const QString &name, const QString &data, const QString &transferToAddress) | |
+{ | |
+ std::string strName = name.toStdString(); | |
+ std::vector<unsigned char> vchName(strName.begin(), strName.end()); | |
+ | |
+ std::string strData = data.toStdString(); | |
+ std::vector<unsigned char> vchValue(strData.begin(), strData.end()); | |
+ | |
+ CWalletTx wtx; | |
+ wtx.nVersion = NAMECOIN_TX_VERSION; | |
+ CScript scriptPubKeyOrig; | |
+ | |
+ if (transferToAddress != "") | |
+ { | |
+ std::string strAddress = transferToAddress.toStdString(); | |
+ uint160 hash160; | |
+ bool isValid = AddressToHash160(strAddress, hash160); | |
+ if (!isValid) | |
+ return tr("Invalid Namecoin address"); | |
+ scriptPubKeyOrig.SetBitcoinAddress(strAddress); | |
+ } | |
+ else | |
+ { | |
+ std::vector<unsigned char> strPubKey = wallet->GetKeyFromKeyPool(); | |
+ scriptPubKeyOrig.SetBitcoinAddress(strPubKey); | |
+ } | |
+ | |
+ CScript scriptPubKey; | |
+ scriptPubKey << OP_NAME_UPDATE << vchName << vchValue << OP_2DROP << OP_DROP; | |
+ scriptPubKey += scriptPubKeyOrig; | |
+ | |
+ CRITICAL_BLOCK(cs_main) | |
+ CRITICAL_BLOCK(wallet->cs_mapWallet) | |
+ { | |
+ if (mapNamePending.count(vchName) && mapNamePending[vchName].size()) | |
+ { | |
+ error("name_update() : there are %d pending operations on that name, including %s", | |
+ mapNamePending[vchName].size(), | |
+ mapNamePending[vchName].begin()->GetHex().c_str()); | |
+ return tr("There are pending operations on that name"); | |
+ } | |
+ | |
+ CNameDB dbName("r"); | |
+ CTransaction tx; | |
+ if (!GetTxOfName(dbName, vchName, tx)) | |
+ return tr("Could not find a coin with this name"); | |
+ | |
+ uint256 wtxInHash = tx.GetHash(); | |
+ | |
+ if (!wallet->mapWallet.count(wtxInHash)) | |
+ { | |
+ error("name_update() : this coin is not in your wallet %s", | |
+ wtxInHash.GetHex().c_str()); | |
+ return tr("This coin is not in your wallet"); | |
+ } | |
+ | |
+ CWalletTx& wtxIn = wallet->mapWallet[wtxInHash]; | |
+ return QString::fromStdString(SendMoneyWithInputTx(scriptPubKey, MIN_AMOUNT, 0, wtxIn, wtx, true)); | |
+ } | |
+} | |
+ | |
+OptionsModel *WalletModel::getOptionsModel() | |
+{ | |
+ return optionsModel; | |
+} | |
+ | |
+AddressTableModel *WalletModel::getAddressTableModel() | |
+{ | |
+ return addressTableModel; | |
+} | |
+ | |
+NameTableModel *WalletModel::getNameTableModel() | |
+{ | |
+ return nameTableModel; | |
+} | |
+ | |
+TransactionTableModel *WalletModel::getTransactionTableModel() | |
+{ | |
+ return transactionTableModel; | |
+} | |
+ | |
+RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() | |
+{ | |
+ return recentRequestsTableModel; | |
+} | |
+ | |
+WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const | |
+{ | |
+ if(!wallet->IsCrypted()) | |
+ { | |
+ return Unencrypted; | |
+ } | |
+ else if(wallet->IsLocked()) | |
+ { | |
+ return Locked; | |
+ } | |
+ else | |
+ { | |
+ return Unlocked; | |
+ } | |
+} | |
+ | |
+bool WalletModel::setWalletEncrypted(bool encrypted, const SecureString &passphrase) | |
+{ | |
+ if(encrypted) | |
+ { | |
+ // Encrypt | |
+ return wallet->EncryptWallet(passphrase); | |
+ } | |
+ else | |
+ { | |
+ // Decrypt -- TODO; not supported yet | |
+ return false; | |
+ } | |
+} | |
+ | |
+bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase) | |
+{ | |
+ if(locked) | |
+ { | |
+ // Lock | |
+ return wallet->Lock(); | |
+ } | |
+ else | |
+ { | |
+ // Unlock | |
+ return wallet->Unlock(passPhrase); | |
+ } | |
+} | |
+ | |
+bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass) | |
+{ | |
+ bool retval; | |
+ { | |
+ LOCK(wallet->cs_wallet); | |
+ wallet->Lock(); // Make sure wallet is locked before attempting pass change | |
+ retval = wallet->ChangeWalletPassphrase(oldPass, newPass); | |
+ } | |
+ return retval; | |
+} | |
+ | |
+bool WalletModel::backupWallet(const QString &filename) | |
+{ | |
+ return BackupWallet(*wallet, filename.toLocal8Bit().data()); | |
+} | |
+ | |
+// Handlers for core signals | |
+static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) | |
+{ | |
+ qDebug() << "NotifyKeyStoreStatusChanged"; | |
+ QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); | |
+} | |
+ | |
+static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, | |
+ const CTxDestination &address, const std::string &label, bool isMine, | |
+ const std::string &purpose, ChangeType 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, 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) | |
+{ | |
+ Q_UNUSED(wallet); | |
+ Q_UNUSED(hash); | |
+ Q_UNUSED(status); | |
+ QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection); | |
+} | |
+ | |
+static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress) | |
+{ | |
+ // emits signal "showProgress" | |
+ QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection, | |
+ Q_ARG(QString, QString::fromStdString(title)), | |
+ Q_ARG(int, nProgress)); | |
+} | |
+ | |
+static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly) | |
+{ | |
+ QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection, | |
+ Q_ARG(bool, fHaveWatchonly)); | |
+} | |
+ | |
+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, _6)); | |
+ wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | |
+ wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); | |
+ wallet->NotifyWatchonlyChanged.connect(boost::bind(NotifyWatchonlyChanged, this, _1)); | |
+} | |
+ | |
+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, _6)); | |
+ wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | |
+ wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); | |
+ wallet->NotifyWatchonlyChanged.disconnect(boost::bind(NotifyWatchonlyChanged, this, _1)); | |
+} | |
+ | |
+// WalletModel::UnlockContext implementation | |
+WalletModel::UnlockContext WalletModel::requestUnlock() | |
+{ | |
+ bool was_locked = getEncryptionStatus() == Locked; | |
+ if(was_locked) | |
+ { | |
+ // Request UI to unlock wallet | |
+ Q_EMIT requireUnlock(); | |
+ } | |
+ // If wallet is still locked, unlock was failed or cancelled, mark context as invalid | |
+ bool valid = getEncryptionStatus() != Locked; | |
+ | |
+ return UnlockContext(this, valid, was_locked); | |
+} | |
+ | |
+WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool relock): | |
+ wallet(wallet), | |
+ valid(valid), | |
+ relock(relock) | |
+{ | |
+} | |
+ | |
+WalletModel::UnlockContext::~UnlockContext() | |
+{ | |
+ if(valid && relock) | |
+ { | |
+ wallet->setWalletLocked(true); | |
+ } | |
+} | |
+ | |
+void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs) | |
+{ | |
+ // Transfer context; old object no longer relocks wallet | |
+ *this = rhs; | |
+ rhs.relock = false; | |
+} | |
+ | |
+bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const | |
+{ | |
+ return wallet->GetPubKey(address, vchPubKeyOut); | |
+} | |
+ | |
+bool WalletModel::havePrivKey(const CKeyID &address) const | |
+{ | |
+ return wallet->HaveKey(address); | |
+} | |
+ | |
+// returns a list of COutputs from COutPoints | |
+void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs) | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ BOOST_FOREACH(const COutPoint& outpoint, vOutpoints) | |
+ { | |
+ if (!wallet->mapWallet.count(outpoint.hash)) continue; | |
+ int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); | |
+ if (nDepth < 0) continue; | |
+ COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true); | |
+ vOutputs.push_back(out); | |
+ } | |
+} | |
+ | |
+bool WalletModel::isSpent(const COutPoint& outpoint) const | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ return wallet->IsSpent(outpoint.hash, outpoint.n); | |
+} | |
+ | |
+// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) | |
+void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const | |
+{ | |
+ std::vector<COutput> vCoins; | |
+ wallet->AvailableCoins(vCoins); | |
+ | |
+ LOCK2(cs_main, wallet->cs_wallet); // ListLockedCoins, mapWallet | |
+ std::vector<COutPoint> vLockedCoins; | |
+ wallet->ListLockedCoins(vLockedCoins); | |
+ | |
+ // add locked coins | |
+ BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins) | |
+ { | |
+ if (!wallet->mapWallet.count(outpoint.hash)) continue; | |
+ int nDepth = wallet->mapWallet[outpoint.hash].GetDepthInMainChain(); | |
+ if (nDepth < 0) continue; | |
+ COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, nDepth, true); | |
+ if (outpoint.n < out.tx->vout.size() && wallet->IsMine(out.tx->vout[outpoint.n]) == ISMINE_SPENDABLE) | |
+ vCoins.push_back(out); | |
+ } | |
+ | |
+ BOOST_FOREACH(const COutput& out, vCoins) | |
+ { | |
+ COutput cout = out; | |
+ | |
+ while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0])) | |
+ { | |
+ if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break; | |
+ cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0, true); | |
+ } | |
+ | |
+ CTxDestination address; | |
+ if(!out.fSpendable || !ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) | |
+ continue; | |
+ mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out); | |
+ } | |
+} | |
+ | |
+bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ return wallet->IsLockedCoin(hash, n); | |
+} | |
+ | |
+void WalletModel::lockCoin(COutPoint& output) | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ wallet->LockCoin(output); | |
+} | |
+ | |
+void WalletModel::unlockCoin(COutPoint& output) | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ wallet->UnlockCoin(output); | |
+} | |
+ | |
+void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts) | |
+{ | |
+ LOCK2(cs_main, wallet->cs_wallet); | |
+ wallet->ListLockedCoins(vOutpts); | |
+} | |
+ | |
+void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests) | |
+{ | |
+ LOCK(wallet->cs_wallet); | |
+ BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook) | |
+ BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata) | |
+ if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request | |
+ vReceiveRequests.push_back(item2.second); | |
+} | |
+ | |
+bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) | |
+{ | |
+ CTxDestination dest = CBitcoinAddress(sAddress).Get(); | |
+ | |
+ std::stringstream ss; | |
+ ss << nId; | |
+ std::string key = "rr" + ss.str(); // "rr" prefix = "receive request" in destdata | |
+ | |
+ LOCK(wallet->cs_wallet); | |
+ if (sRequest.empty()) | |
+ return wallet->EraseDestData(dest, key); | |
+ else | |
+ return wallet->AddDestData(dest, key, sRequest); | |
+} | |
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h | |
index 7a47eda..9011fab 100644 | |
--- a/src/qt/walletmodel.h | |
+++ b/src/qt/walletmodel.h | |
@@ -19,6 +19,7 @@ class AddressTableModel; | |
class OptionsModel; | |
class PlatformStyle; | |
class RecentRequestsTableModel; | |
+class NameTableModel; | |
class TransactionTableModel; | |
class WalletModelTransaction; | |
@@ -127,6 +128,7 @@ public: | |
OptionsModel *getOptionsModel(); | |
AddressTableModel *getAddressTableModel(); | |
+ NameTableModel *getNameTableModel(); | |
TransactionTableModel *getTransactionTableModel(); | |
RecentRequestsTableModel *getRecentRequestsTableModel(); | |
@@ -200,6 +202,35 @@ public: | |
void loadReceiveRequests(std::vector<std::string>& vReceiveRequests); | |
bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); | |
+ struct NameNewReturn | |
+ { | |
+ bool ok; | |
+ QString err_msg; | |
+ QString address; | |
+ std::vector<unsigned char> vchName; | |
+ uint256 hex; // Transaction hash in hex | |
+ uint64_t rand; // Secret number in hex | |
+ uint160 hash; // Hash of rand+name | |
+ }; | |
+ | |
+ bool nameAvailable(const QString &name); | |
+ | |
+ // Register new name | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ NameNewReturn nameNew(const QString &name); | |
+ | |
+ // Create pending name update | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ QString nameFirstUpdatePrepare(const QString &name, const QString &data); | |
+ | |
+ // Send pending name updates, if they are 12 blocks old | |
+ void sendPendingNameFirstUpdates(); | |
+ | |
+ // Update name | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ QString nameUpdate(const QString &name, const QString &data, const QString &transferToAddress); | |
+ | |
+ | |
private: | |
CWallet *wallet; | |
bool fHaveWatchOnly; | |
@@ -211,6 +242,7 @@ private: | |
AddressTableModel *addressTableModel; | |
TransactionTableModel *transactionTableModel; | |
+ NameTableModel *nameTableModel; | |
RecentRequestsTableModel *recentRequestsTableModel; | |
// Cache some values to be able to detect changes | |
@@ -229,6 +261,7 @@ private: | |
void unsubscribeFromCoreSignals(); | |
void checkBalanceChanged(); | |
+ | |
Q_SIGNALS: | |
// Signal that balance in wallet changed | |
void balanceChanged(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, | |
@@ -265,6 +298,8 @@ public Q_SLOTS: | |
void updateWatchOnlyFlag(bool fHaveWatchonly); | |
/* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ | |
void pollBalanceChanged(); | |
+ | |
+ | |
}; | |
#endif // BITCOIN_QT_WALLETMODEL_H | |
diff --git a/src/qt/walletmodel.h.PATCH b/src/qt/walletmodel.h.PATCH | |
new file mode 100644 | |
index 0000000..f206dd2 | |
--- /dev/null | |
+++ b/src/qt/walletmodel.h.PATCH | |
@@ -0,0 +1,303 @@ | |
+// Copyright (c) 2011-2015 The Bitcoin Core developers | |
+// Distributed under the MIT software license, see the accompanying | |
+// file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
+ | |
+#ifndef BITCOIN_QT_WALLETMODEL_H | |
+#define BITCOIN_QT_WALLETMODEL_H | |
+ | |
+#include "paymentrequestplus.h" | |
+#include "walletmodeltransaction.h" | |
+ | |
+#include "support/allocators/secure.h" | |
+ | |
+#include <map> | |
+#include <vector> | |
+ | |
+#include <QObject> | |
+ | |
+class AddressTableModel; | |
+class OptionsModel; | |
+class PlatformStyle; | |
+class RecentRequestsTableModel; | |
+class NameTableModel; | |
+class TransactionTableModel; | |
+class WalletModelTransaction; | |
+ | |
+class CCoinControl; | |
+class CKeyID; | |
+class COutPoint; | |
+class COutput; | |
+class CPubKey; | |
+class CWallet; | |
+class uint256; | |
+class CWalletTx; | |
+ | |
+QT_BEGIN_NAMESPACE | |
+class QTimer; | |
+QT_END_NAMESPACE | |
+ | |
+class SendCoinsRecipient | |
+{ | |
+public: | |
+ explicit SendCoinsRecipient() : amount(0), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) { } | |
+ explicit SendCoinsRecipient(const QString &addr, const QString &label, const CAmount& amount, const QString &message): | |
+ address(addr), label(label), amount(amount), message(message), fSubtractFeeFromAmount(false), nVersion(SendCoinsRecipient::CURRENT_VERSION) {} | |
+ | |
+ // If from an unauthenticated payment request, this is used for storing | |
+ // the addresses, e.g. address-A<br />address-B<br />address-C. | |
+ // Info: As we don't need to process addresses in here when using | |
+ // payment requests, we can abuse it for displaying an address list. | |
+ // Todo: This is a hack, should be replaced with a cleaner solution! | |
+ QString address; | |
+ QString label; | |
+ CAmount amount; | |
+ // If from a payment request, this is used for storing the memo | |
+ QString message; | |
+ | |
+ // If from a payment request, paymentRequest.IsInitialized() will be true | |
+ PaymentRequestPlus paymentRequest; | |
+ // Empty if no authentication or invalid signature/cert/etc. | |
+ QString authenticatedMerchant; | |
+ | |
+ bool fSubtractFeeFromAmount; // memory only | |
+ | |
+ static const int CURRENT_VERSION = 1; | |
+ int nVersion; | |
+ | |
+ ADD_SERIALIZE_METHODS; | |
+ | |
+ template <typename Stream, typename Operation> | |
+ inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { | |
+ std::string sAddress = address.toStdString(); | |
+ std::string sLabel = label.toStdString(); | |
+ std::string sMessage = message.toStdString(); | |
+ std::string sPaymentRequest; | |
+ if (!ser_action.ForRead() && paymentRequest.IsInitialized()) | |
+ paymentRequest.SerializeToString(&sPaymentRequest); | |
+ std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString(); | |
+ | |
+ READWRITE(this->nVersion); | |
+ nVersion = this->nVersion; | |
+ READWRITE(sAddress); | |
+ READWRITE(sLabel); | |
+ READWRITE(amount); | |
+ READWRITE(sMessage); | |
+ READWRITE(sPaymentRequest); | |
+ READWRITE(sAuthenticatedMerchant); | |
+ | |
+ if (ser_action.ForRead()) | |
+ { | |
+ address = QString::fromStdString(sAddress); | |
+ label = QString::fromStdString(sLabel); | |
+ message = QString::fromStdString(sMessage); | |
+ if (!sPaymentRequest.empty()) | |
+ paymentRequest.parse(QByteArray::fromRawData(sPaymentRequest.data(), sPaymentRequest.size())); | |
+ authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant); | |
+ } | |
+ } | |
+}; | |
+ | |
+/** Interface to Bitcoin wallet from Qt view code. */ | |
+class WalletModel : public QObject | |
+{ | |
+ Q_OBJECT | |
+ | |
+public: | |
+ explicit WalletModel(const PlatformStyle *platformStyle, CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); | |
+ ~WalletModel(); | |
+ | |
+ enum StatusCode // Returned by sendCoins | |
+ { | |
+ OK, | |
+ InvalidAmount, | |
+ InvalidAddress, | |
+ AmountExceedsBalance, | |
+ AmountWithFeeExceedsBalance, | |
+ DuplicateAddress, | |
+ TransactionCreationFailed, // Error returned when wallet is still locked | |
+ TransactionCommitFailed, | |
+ AbsurdFee, | |
+ PaymentRequestExpired | |
+ }; | |
+ | |
+ enum EncryptionStatus | |
+ { | |
+ Unencrypted, // !wallet->IsCrypted() | |
+ Locked, // wallet->IsCrypted() && wallet->IsLocked() | |
+ Unlocked // wallet->IsCrypted() && !wallet->IsLocked() | |
+ }; | |
+ | |
+ OptionsModel *getOptionsModel(); | |
+ AddressTableModel *getAddressTableModel(); | |
+ NameTableModel *getNameTableModel(); | |
+ TransactionTableModel *getTransactionTableModel(); | |
+ RecentRequestsTableModel *getRecentRequestsTableModel(); | |
+ | |
+ CAmount getBalance(const CCoinControl *coinControl = NULL) const; | |
+ CAmount getUnconfirmedBalance() const; | |
+ CAmount getImmatureBalance() const; | |
+ bool haveWatchOnly() const; | |
+ CAmount getWatchBalance() const; | |
+ CAmount getWatchUnconfirmedBalance() const; | |
+ CAmount getWatchImmatureBalance() const; | |
+ EncryptionStatus getEncryptionStatus() const; | |
+ | |
+ // Check address for validity | |
+ bool validateAddress(const QString &address); | |
+ | |
+ // Return status record for SendCoins, contains error id + information | |
+ struct SendCoinsReturn | |
+ { | |
+ SendCoinsReturn(StatusCode status = OK): | |
+ status(status) {} | |
+ StatusCode status; | |
+ }; | |
+ | |
+ // prepare transaction for getting txfee before sending coins | |
+ SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl = NULL); | |
+ | |
+ // Send coins to a list of recipients | |
+ SendCoinsReturn sendCoins(WalletModelTransaction &transaction); | |
+ | |
+ bool nameAvailable(const QString &name); | |
+ | |
+ struct NameNewReturn | |
+ { | |
+ bool ok; | |
+ QString err_msg; | |
+ std::vector<unsigned char> vchName; | |
+ uint256 hex; // Transaction hash in hex | |
+ uint64_t rand; // Secret number in hex | |
+ uint160 hash; // Hash of rand+name | |
+ }; | |
+ | |
+ // Register new name | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ NameNewReturn nameNew(const QString &name); | |
+ | |
+ // Create pending name update | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ QString nameFirstUpdatePrepare(const QString &name, const QString &data); | |
+ | |
+ // Send pending name updates, if they are 12 blocks old | |
+ void sendPendingNameFirstUpdates(); | |
+ | |
+ // Update name | |
+ // Requires unlocked wallet; can throw exception instead of returning error | |
+ QString nameUpdate(const QString &name, const QString &data, const QString &transferToAddress); | |
+ | |
+ // Wallet encryption | |
+ bool setWalletEncrypted(bool encrypted, const SecureString &passphrase); | |
+ // Passphrase only needed when unlocking | |
+ bool setWalletLocked(bool locked, const SecureString &passPhrase=SecureString()); | |
+ bool changePassphrase(const SecureString &oldPass, const SecureString &newPass); | |
+ // Wallet backup | |
+ bool backupWallet(const QString &filename); | |
+ | |
+ // RAI object for unlocking wallet, returned by requestUnlock() | |
+ class UnlockContext | |
+ { | |
+ public: | |
+ UnlockContext(WalletModel *wallet, bool valid, bool relock); | |
+ ~UnlockContext(); | |
+ | |
+ bool isValid() const { return valid; } | |
+ | |
+ // Copy operator and constructor transfer the context | |
+ UnlockContext(const UnlockContext& obj) { CopyFrom(obj); } | |
+ UnlockContext& operator=(const UnlockContext& rhs) { CopyFrom(rhs); return *this; } | |
+ private: | |
+ WalletModel *wallet; | |
+ bool valid; | |
+ mutable bool relock; // mutable, as it can be set to false by copying | |
+ | |
+ void CopyFrom(const UnlockContext& rhs); | |
+ }; | |
+ | |
+ UnlockContext requestUnlock(); | |
+ | |
+ bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; | |
+ bool havePrivKey(const CKeyID &address) const; | |
+ void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs); | |
+ bool isSpent(const COutPoint& outpoint) const; | |
+ void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const; | |
+ | |
+ bool isLockedCoin(uint256 hash, unsigned int n) const; | |
+ void lockCoin(COutPoint& output); | |
+ void unlockCoin(COutPoint& output); | |
+ void listLockedCoins(std::vector<COutPoint>& vOutpts); | |
+ | |
+ void loadReceiveRequests(std::vector<std::string>& vReceiveRequests); | |
+ bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest); | |
+ | |
+private: | |
+ CWallet *wallet; | |
+ bool fHaveWatchOnly; | |
+ bool fForceCheckBalanceChanged; | |
+ | |
+ // Wallet has an options model for wallet-specific options | |
+ // (transaction fee, for example) | |
+ OptionsModel *optionsModel; | |
+ | |
+ AddressTableModel *addressTableModel; | |
+ NameTableModel *nameTableModel; | |
+ TransactionTableModel *transactionTableModel; | |
+ RecentRequestsTableModel *recentRequestsTableModel; | |
+ | |
+ // Cache some values to be able to detect changes | |
+ CAmount cachedBalance; | |
+ CAmount cachedUnconfirmedBalance; | |
+ CAmount cachedImmatureBalance; | |
+ CAmount cachedWatchOnlyBalance; | |
+ CAmount cachedWatchUnconfBalance; | |
+ CAmount cachedWatchImmatureBalance; | |
+ EncryptionStatus cachedEncryptionStatus; | |
+ int cachedNumBlocks; | |
+ | |
+ QTimer *pollTimer; | |
+ | |
+ void subscribeToCoreSignals(); | |
+ void unsubscribeFromCoreSignals(); | |
+ void checkBalanceChanged(); | |
+ | |
+ std::string nameFirstUpdateCreateTx(CWalletTx &wtx, const std::vector<unsigned char> &vchName, uint256 wtxInHash, uint64_t rand, const std::vector<unsigned char> &vchValue); | |
+ | |
+Q_SIGNALS: | |
+ // Signal that balance in wallet changed | |
+ void balanceChanged(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, | |
+ const CAmount& watchOnlyBalance, const CAmount& watchUnconfBalance, const CAmount& watchImmatureBalance); | |
+ | |
+ // Encryption status of wallet changed | |
+ void encryptionStatusChanged(int status); | |
+ | |
+ // Signal emitted when wallet needs to be unlocked | |
+ // It is valid behaviour for listeners to keep the wallet locked after this signal; | |
+ // this means that the unlocking failed or was cancelled. | |
+ void requireUnlock(); | |
+ | |
+ // Fired when a message should be reported to the user | |
+ void message(const QString &title, const QString &message, unsigned int style); | |
+ | |
+ // Coins sent: from wallet, to recipient, in (serialized) transaction: | |
+ void coinsSent(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction); | |
+ | |
+ // Show progress dialog e.g. for rescan | |
+ void showProgress(const QString &title, int nProgress); | |
+ | |
+ // Watch-only address added | |
+ void notifyWatchonlyChanged(bool fHaveWatchonly); | |
+ | |
+public Q_SLOTS: | |
+ /* Wallet status might have changed */ | |
+ void updateStatus(); | |
+ /* New transaction, or transaction changed status */ | |
+ void updateTransaction(); | |
+ /* New, updated or removed address book entry */ | |
+ void updateAddressBook(const QString &address, const QString &label, bool isMine, const QString &purpose, int status); | |
+ /* Watch-only added */ | |
+ void updateWatchOnlyFlag(bool fHaveWatchonly); | |
+ /* Current, immature or unconfirmed balance might have changed - emit 'balanceChanged' if so */ | |
+ void pollBalanceChanged(); | |
+}; | |
+ | |
+#endif // BITCOIN_QT_WALLETMODEL_H | |
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp | |
index 6ce98ef..a636bc9 100644 | |
--- a/src/qt/walletview.cpp | |
+++ b/src/qt/walletview.cpp | |
@@ -18,6 +18,7 @@ | |
#include "transactiontablemodel.h" | |
#include "transactionview.h" | |
#include "walletmodel.h" | |
+#include "managenamespage.h" | |
#include "ui_interface.h" | |
@@ -55,6 +56,7 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): | |
receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); | |
sendCoinsPage = new SendCoinsDialog(platformStyle); | |
+ manageNamesPage = new ManageNamesPage(platformStyle); | |
usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); | |
usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); | |
@@ -63,6 +65,7 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): | |
addWidget(transactionsPage); | |
addWidget(receiveCoinsPage); | |
addWidget(sendCoinsPage); | |
+ addWidget(manageNamesPage); | |
// Clicking on a transaction on the overview pre-selects the transaction on the transaction history page | |
connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); | |
@@ -118,6 +121,7 @@ void WalletView::setWalletModel(WalletModel *walletModel) | |
overviewPage->setWalletModel(walletModel); | |
receiveCoinsPage->setModel(walletModel); | |
sendCoinsPage->setModel(walletModel); | |
+ manageNamesPage->setModel(walletModel); | |
usedReceivingAddressesPage->setModel(walletModel->getAddressTableModel()); | |
usedSendingAddressesPage->setModel(walletModel->getAddressTableModel()); | |
@@ -185,6 +189,17 @@ void WalletView::gotoSendCoinsPage(QString addr) | |
sendCoinsPage->setAddress(addr); | |
} | |
+void WalletView::gotoManageNamesPage() | |
+{ | |
+ setCurrentWidget(manageNamesPage); | |
+ | |
+/* FIXME: Move to tab itself; see 45155d3010a3bbbe3cfbba670538ae18b9772a39 | |
+ exportAction->setEnabled(true); | |
+ disconnect(exportAction, SIGNAL(triggered()), 0, 0); | |
+ connect(exportAction, SIGNAL(triggered()), manageNamesPage, SLOT(exportClicked())); | |
+*/ | |
+} | |
+ | |
void WalletView::gotoSignMessageTab(QString addr) | |
{ | |
// calls show() in showTab_SM() | |
diff --git a/src/qt/walletview.h b/src/qt/walletview.h | |
index dbb289f..b65f0ca 100644 | |
--- a/src/qt/walletview.h | |
+++ b/src/qt/walletview.h | |
@@ -16,6 +16,7 @@ class PlatformStyle; | |
class ReceiveCoinsDialog; | |
class SendCoinsDialog; | |
class SendCoinsRecipient; | |
+class ManageNamesPage; | |
class TransactionView; | |
class WalletModel; | |
class AddressBookPage; | |
@@ -62,6 +63,7 @@ private: | |
QWidget *transactionsPage; | |
ReceiveCoinsDialog *receiveCoinsPage; | |
SendCoinsDialog *sendCoinsPage; | |
+ ManageNamesPage *manageNamesPage; | |
AddressBookPage *usedSendingAddressesPage; | |
AddressBookPage *usedReceivingAddressesPage; | |
@@ -85,6 +87,10 @@ public Q_SLOTS: | |
/** Show Sign/Verify Message dialog and switch to verify message tab */ | |
void gotoVerifyMessageTab(QString addr = ""); | |
+ /* NMC SHIT */ | |
+ void gotoManageNamesPage(); | |
+ | |
+ | |
/** Show incoming transaction notification for new transactions. | |
The new items are those between start and end inclusive, under the given parent item. | |
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp | |
index 2c5b00a..939787c 100644 | |
--- a/src/wallet/wallet.cpp | |
+++ b/src/wallet/wallet.cpp | |
@@ -2489,6 +2489,32 @@ bool CWallet::DelAddressBook(const CTxDestination& address) | |
return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); | |
} | |
+//#ifdef GUI | |
+bool CWallet::WriteNameFirstUpdate(const std::vector<unsigned char>& vchName, | |
+ const uint256& hex, | |
+ const uint64_t& rand, | |
+ const std::vector<unsigned char>& vchData, | |
+ const CWalletTx &wtx) | |
+{ | |
+ /* | |
+ if (!fFileBacked) | |
+ return false; | |
+ return CWalletDB(strWalletFile).WriteNameFirstUpdate(vchName, hex, rand, vchData, wtx); | |
+ */ | |
+ return true; | |
+} | |
+ | |
+bool CWallet::EraseNameFirstUpdate(const std::vector<unsigned char>& vchName) | |
+{ | |
+ /* | |
+ if (!fFileBacked) | |
+ return false; | |
+ return CWalletDB(strWalletFile).EraseNameFirstUpdate(vchName); | |
+ */ | |
+ return true; | |
+} | |
+//#endif | |
+ | |
bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) | |
{ | |
if (fFileBacked) | |
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h | |
index f697cd1..c58d29f 100644 | |
--- a/src/wallet/wallet.h | |
+++ b/src/wallet/wallet.h | |
@@ -748,6 +748,15 @@ public: | |
bool DelAddressBook(const CTxDestination& address); | |
+//#ifdef GUI | |
+ bool WriteNameFirstUpdate(const std::vector<unsigned char>& vchName, | |
+ const uint256& hex, | |
+ const uint64_t& rand, | |
+ const std::vector<unsigned char>& vchData, | |
+ const CWalletTx &wtx); | |
+ bool EraseNameFirstUpdate(const std::vector<unsigned char>& vchName); | |
+//#endif | |
+ | |
void UpdatedTransaction(const uint256 &hashTx); | |
void Inventory(const uint256 &hash) | |
@@ -879,4 +888,14 @@ public: | |
} | |
}; | |
+//#ifdef GUI | |
+// Editable transaction, which is not broadcasted immediately (only after 12 blocks) | |
+struct PreparedNameFirstUpdate | |
+{ | |
+ uint64_t rand; | |
+ std::vector<unsigned char> vchData; | |
+ CWalletTx wtx; | |
+}; | |
+//#endif | |
+ | |
#endif // BITCOIN_WALLET_WALLET_H | |
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp | |
index 6751197..1c8151b 100644 | |
--- a/src/wallet/walletdb.cpp | |
+++ b/src/wallet/walletdb.cpp | |
@@ -21,6 +21,11 @@ | |
#include <boost/scoped_ptr.hpp> | |
#include <boost/thread.hpp> | |
+//#ifdef GUI | |
+extern std::map<std::vector<unsigned char>, PreparedNameFirstUpdate> mapMyNameFirstUpdate; | |
+extern std::map<uint160, std::vector<unsigned char> > mapMyNameHashes; // Name for name_new hash (to show name in transaction list) | |
+//#endif | |
+ | |
using namespace std; | |
static uint64_t nAccountingEntryNumber = 0; | |
@@ -176,6 +181,33 @@ bool CWalletDB::WriteMinVersion(int nVersion) | |
return Write(std::string("minversion"), nVersion); | |
} | |
+//#ifdef GUI | |
+bool CWalletDB::WriteNameFirstUpdate(const std::vector<unsigned char>& vchName, | |
+ const uint256& hex, | |
+ const uint64_t& rand, | |
+ const std::vector<unsigned char>& vchData, | |
+ const CWalletTx &wtx) | |
+{ | |
+ /* | |
+ CDataStream ssValue; | |
+ ssValue << hex << rand << vchData << wtx; | |
+ | |
+ nWalletDBUpdated++; | |
+ return Write(make_pair(string("name_firstupdate"), vchName), ssValue, true); | |
+ */ | |
+ return true; | |
+} | |
+ | |
+bool CWalletDB::EraseNameFirstUpdate(const std::vector<unsigned char>& vchName) | |
+{ | |
+ /* | |
+ nWalletDBUpdated++; | |
+ return Erase(make_pair(string("name_firstupdate"), vchName)); | |
+ */ | |
+ return true; | |
+} | |
+//#endif | |
+ | |
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account) | |
{ | |
account.SetNull(); | |
@@ -401,6 +433,23 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, | |
wss.fAnyUnordered = true; | |
pwallet->AddToWallet(wtx, true, NULL); | |
+ | |
+//#ifdef GUI | |
+/* TODO: implement this | |
+ int op, nOut; | |
+ std::vector<std::vector<unsigned char> > vvch; | |
+ if (DecodeNameTx(wtx, op, nOut, vvch) && op == OP_NAME_FIRSTUPDATE && vvch.size() == 3) | |
+ { | |
+ std::vector<unsigned char> &vchName = vvch[0]; | |
+ std::vector<unsigned char> &vchRand = vvch[1]; | |
+ | |
+ std::vector<unsigned char> vchToHash(vchRand); | |
+ vchToHash.insert(vchToHash.end(), vchName.begin(), vchName.end()); | |
+ uint160 hash = Hash160(vchToHash); | |
+ mapMyNameHashes[hash] = vchName; | |
+ } | |
+ */ | |
+//#endif | |
} | |
else if (strType == "acentry") | |
{ | |
@@ -565,6 +614,37 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, | |
if (pwallet->mapKeyMetadata.count(keyid) == 0) | |
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); | |
} | |
+//#ifdef GUI | |
+ else if (strType == "name_firstupdate") | |
+ { | |
+ std::vector<unsigned char> vchName; | |
+ uint256 wtxInHash; | |
+ | |
+ ssKey >> vchName; | |
+ PreparedNameFirstUpdate prep; | |
+ // Note: name, rand and data are stored unencrypted. Even if we encrypt them, | |
+ // they are recoverable from prep.wtx, which has to be unencrypted (so it can be | |
+ // auto-broadcasted, when name_new is 12 blocks old) | |
+ ssValue >> wtxInHash >> prep.rand >> prep.vchData >> prep.wtx; | |
+ | |
+ // TODO: look into the prep wtc structure and get the assignment right | |
+ //prep.wtx.pwallet = pwallet; | |
+ | |
+ // TODO: also would be good to check that name, rand, wtxInHash and value match with prep.wtx | |
+ // Note: wtxInHash IS NOT prep.wtx.GetHash(), it is the hash of previous name_new | |
+ | |
+ // TODO: reimpliment this hashmap | |
+ //mapMyNames[vchName] = wtxInHash; | |
+ //mapMyNameFirstUpdate[vchName] = prep; | |
+ | |
+ // TODO: CBigNum doesn't exist | |
+ //std::vector<unsigned char> vchRand = CBigNum(prep.rand).getvch(); | |
+ //std::vector<unsigned char> vchToHash(vchRand); | |
+ //vchToHash.insert(vchToHash.end(), vchName.begin(), vchName.end()); | |
+ //uint160 hash = Hash160(vchToHash); | |
+ //mapMyNameHashes[hash] = vchName; | |
+ } | |
+//#endif | |
else if (strType == "version") | |
{ | |
ssValue >> wss.nFileVersion; | |
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h | |
index 8da33de..c475861 100644 | |
--- a/src/wallet/walletdb.h | |
+++ b/src/wallet/walletdb.h | |
@@ -126,6 +126,16 @@ public: | |
CAmount GetAccountCreditDebit(const std::string& strAccount); | |
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries); | |
+//#ifdef GUI | |
+ bool WriteNameFirstUpdate(const std::vector<unsigned char>& vchName, | |
+ const uint256& hex, | |
+ const uint64_t& rand, | |
+ const std::vector<unsigned char>& vchData, | |
+ const CWalletTx &wtx); | |
+ bool EraseNameFirstUpdate(const std::vector<unsigned char>& vchName); | |
+//#endif | |
+ | |
+ | |
DBErrors ReorderTransactions(CWallet* pwallet); | |
DBErrors LoadWallet(CWallet* pwallet); | |
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment