from electrum import bmp, pyqrnative
-from amountedit import AmountEdit
+from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget
self.decimal_point = config.get('decimal_point', 5)
self.num_zeros = int(config.get('num_zeros',0))
+ self.invoices = {}
set_language(config.get('language'))
tabs.addTab(self.create_send_tab(), _('Send') )
tabs.addTab(self.create_receive_tab(), _('Receive') )
tabs.addTab(self.create_contacts_tab(), _('Contacts') )
+ tabs.addTab(self.create_invoices_tab(), _('Invoices') )
tabs.addTab(self.create_console_tab(), _('Console') )
tabs.setMinimumSize(600, 400)
tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.wallet = wallet
self.update_wallet_format()
+ self.invoices = self.wallet.storage.get('invoices', {})
self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
self.current_account = self.wallet.storage.get("current_account", None)
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
tools_menu.addSeparator()
tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
- #tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
+ tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
tools_menu.addSeparator()
csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
self.update_receive_tab()
self.update_contacts_tab()
self.update_completions()
+ self.update_invoices_tab()
def create_history_tab(self):
def create_send_tab(self):
w = QWidget()
- grid = QGridLayout(w)
+ self.send_grid = grid = QGridLayout(w)
grid.setSpacing(8)
grid.setColumnMinimumWidth(3,300)
grid.setColumnStretch(5,1)
grid.setRowStretch(8, 1)
from paytoedit import PayToEdit
- self.payto_e = PayToEdit()
+ self.amount_e = BTCAmountEdit(self.get_decimal_point)
+ self.payto_e = PayToEdit(self.amount_e)
self.payto_help = HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)'))
grid.addWidget(QLabel(_('Pay to')), 1, 0)
grid.addWidget(self.payto_e, 1, 1, 1, 3)
self.payto_e.setCompleter(completer)
completer.setModel(self.completions)
- self.message_e = QLineEdit()
+ self.message_e = MyLineEdit()
self.message_help = HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.'))
grid.addWidget(QLabel(_('Description')), 2, 0)
grid.addWidget(self.message_e, 2, 1, 1, 3)
self.from_label = QLabel(_('From'))
grid.addWidget(self.from_label, 3, 0)
- self.from_list = QTreeWidget(self)
+ self.from_list = MyTreeWidget(self)
self.from_list.setColumnCount(2)
self.from_list.setColumnWidth(0, 350)
self.from_list.setColumnWidth(1, 50)
- self.from_list.setHeaderHidden (True)
+ self.from_list.setHeaderHidden(True)
self.from_list.setMaximumHeight(80)
+ self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.from_list.customContextMenuRequested.connect(self.from_list_menu)
grid.addWidget(self.from_list, 3, 1, 1, 3)
self.set_pay_from([])
- self.amount_e = AmountEdit(self.get_decimal_point)
self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
+ _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') \
+ '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
grid.addWidget(self.amount_e, 4, 1, 1, 2)
grid.addWidget(self.amount_help, 4, 3)
- self.fee_e = AmountEdit(self.get_decimal_point)
+ self.fee_e = BTCAmountEdit(self.get_decimal_point)
grid.addWidget(QLabel(_('Fee')), 5, 0)
grid.addWidget(self.fee_e, 5, 1, 1, 2)
grid.addWidget(HelpButton(
+ _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
+ _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 5, 3)
- run_hook('exchange_rate_button', grid)
-
self.send_button = EnterButton(_("Send"), self.do_send)
grid.addWidget(self.send_button, 6, 1)
self.payto_sig = QLabel('')
grid.addWidget(self.payto_sig, 7, 0, 1, 4)
- QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
- QShortcut(QKeySequence("Down"), w, w.focusNextChild)
+ #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
+ #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
w.setLayout(grid)
def entry_changed( is_fee ):
self.amount_e.is_shortcut = False
sendable = self.get_sendable_balance()
# there is only one output because we are completely spending inputs
- inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, self.get_payment_sources())
+ inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
fee = self.wallet.estimated_fee(inputs, 1)
amount = total - fee
self.amount_e.setText( self.format_amount(amount) )
if amount is None:
return
# assume that there will be 2 outputs (one for change)
- inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, self.get_payment_sources())
+ inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, 2, coins = self.get_coins())
if not is_fee:
self.fee_e.setText( self.format_amount( fee ) )
if inputs:
run_hook('create_send_tab', grid)
return w
+ def from_list_delete(self, item):
+ i = self.from_list.indexOfTopLevelItem(item)
+ self.pay_from.pop(i)
+ self.redraw_from_list()
+
+ def from_list_menu(self, position):
+ item = self.from_list.itemAt(position)
+ menu = QMenu()
+ menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
+ menu.exec_(self.from_list.viewport().mapToGlobal(position))
+
+ def set_pay_from(self, domain = None):
+ self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
+ self.redraw_from_list()
- def set_pay_from(self, l):
- self.pay_from = l
+ def redraw_from_list(self):
self.from_list.clear()
self.from_label.setHidden(len(self.pay_from) == 0)
self.from_list.setHidden(len(self.pay_from) == 0)
- for addr in self.pay_from:
- c, u = self.wallet.get_addr_balance(addr)
- balance = self.format_amount(c + u)
- self.from_list.addTopLevelItem(QTreeWidgetItem( [addr, balance] ))
+ def format(x):
+ h = x.get('prevout_hash')
+ return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
+
+ for item in self.pay_from:
+ self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
def update_completions(self):
l = []
if self.gui_object.payment_request:
outputs = self.gui_object.payment_request.outputs
- amount = self.gui_object.payment_request.get_amount()
-
else:
- r = unicode( self.payto_e.text() )
- r = r.strip()
-
- # label or alias, with address in brackets
- m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
- to_address = m.group(2) if m else r
- if not is_valid(to_address):
- QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
+ outputs = self.payto_e.get_outputs()
+
+ if not outputs:
+ QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
+ return
+
+ for addr, x in outputs:
+ if addr is None or not bitcoin.is_address(addr):
+ QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
return
-
- try:
- amount = self.amount_e.get_amount()
- except Exception:
+ if x is None:
QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
return
- outputs = [(to_address, amount)]
+ amount = sum(map(lambda x:x[1], outputs))
- try:
- fee = self.fee_e.get_amount()
- except Exception:
+ fee = self.fee_e.get_amount()
+ if fee is None:
QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
return
confirm_amount = self.config.get('confirm_amount', 100000000)
if amount >= confirm_amount:
- if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
+ o = '\n'.join(map(lambda x:x[0], outputs))
+ if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
return
confirm_fee = self.config.get('confirm_fee', 100000)
self.send_button.setDisabled(True)
# first, create an unsigned tx
- domain = self.get_payment_sources()
+ coins = self.get_coins()
try:
- tx = self.wallet.make_unsigned_transaction(outputs, fee, None, domain)
+ tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
tx.error = None
except Exception as e:
traceback.print_exc(file=sys.stdout)
if label:
self.wallet.set_label(tx.hash(), label)
- if not self.gui_object.payment_request:
- if not tx.is_complete() or self.config.get('show_before_broadcast'):
- self.show_transaction(tx)
- self.do_clear()
- self.send_button.setDisabled(False)
- return
+ if not tx.is_complete() or self.config.get('show_before_broadcast'):
+ self.show_transaction(tx)
+ self.do_clear()
+ self.send_button.setDisabled(False)
+ return
self.broadcast_transaction(tx)
def prepare_for_payment_request(self):
- style = "QWidget { background-color:none;border:none;}"
self.tabs.setCurrentIndex(1)
+ self.payto_e.is_pr = True
for e in [self.payto_e, self.amount_e, self.message_e]:
- e.setReadOnly(True)
- e.setStyleSheet(style)
+ e.setFrozen(True)
for h in [self.payto_help, self.amount_help, self.message_help]:
h.hide()
self.payto_e.setText(_("please wait..."))
return True
def payment_request_ok(self):
- self.payto_e.setText(self.gui_object.payment_request.domain)
- self.amount_e.setText(self.format_amount(self.gui_object.payment_request.get_amount()))
- self.message_e.setText(self.gui_object.payment_request.memo)
+ pr = self.gui_object.payment_request
+ pr_id = pr.get_id()
+ # save it
+ self.invoices[pr_id] = (pr.get_domain(), pr.get_amount())
+ self.wallet.storage.put('invoices', self.invoices)
+ self.update_invoices_tab()
+
+ self.payto_help.show()
+ self.payto_help.set_alt(lambda: self.show_pr_details(pr))
+
+ self.payto_e.setGreen()
+ self.payto_e.setText(pr.domain)
+ self.amount_e.setText(self.format_amount(pr.get_amount()))
+ self.message_e.setText(pr.memo)
def payment_request_error(self):
self.do_clear()
self.show_message(self.gui_object.payment_request.error)
-
+ self.gui_object.payment_request = None
def set_send(self, address, amount, label, message):
def do_clear(self):
+ self.payto_e.is_pr = False
self.payto_sig.setVisible(False)
for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
e.setText('')
- self.set_frozen(e,False)
- e.setStyleSheet("")
+ e.setFrozen(False)
+
for h in [self.payto_help, self.amount_help, self.message_help]:
h.show()
+ self.payto_help.set_alt(None)
self.set_pay_from([])
self.update_status()
- def set_frozen(self,entry,frozen):
- if frozen:
- entry.setReadOnly(True)
- entry.setFrame(False)
- palette = QPalette()
- palette.setColor(entry.backgroundRole(), QColor('lightgray'))
- entry.setPalette(palette)
- else:
- entry.setReadOnly(False)
- entry.setFrame(True)
- palette = QPalette()
- palette.setColor(entry.backgroundRole(), QColor('white'))
- entry.setPalette(palette)
def set_addrs_frozen(self,addrs,freeze):
buttons = QWidget()
vbox.addWidget(buttons)
- hbox = QHBoxLayout()
- hbox.setMargin(0)
- hbox.setSpacing(0)
- buttons.setLayout(hbox)
-
- return l,w,hbox
+ return l, w
def create_receive_tab(self):
- l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
+ l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
l.setContextMenuPolicy(Qt.CustomContextMenu)
l.customContextMenuRequested.connect(self.create_receive_menu)
l.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
self.receive_list = l
- self.receive_buttons_hbox = hbox
- hbox.addStretch(1)
return w
def create_contacts_tab(self):
- l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
+ l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
l.setContextMenuPolicy(Qt.CustomContextMenu)
l.customContextMenuRequested.connect(self.create_contact_menu)
for i,width in enumerate(self.column_widths['contacts']):
self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
self.contacts_list = l
- self.contacts_buttons_hbox = hbox
- hbox.addStretch(1)
return w
+ def create_invoices_tab(self):
+ l, w = self.create_list_tab([_('Requestor'), _('Amount'), _('Status')])
+ l.setContextMenuPolicy(Qt.CustomContextMenu)
+ l.customContextMenuRequested.connect(self.create_invoice_menu)
+ self.invoices_list = l
+ return w
+
+ def update_invoices_tab(self):
+ invoices = self.wallet.storage.get('invoices', {})
+ l = self.invoices_list
+ l.clear()
+
+ for item, value in invoices.items():
+ domain, amount = value
+ item = QTreeWidgetItem( [ domain, self.format_amount(amount), ""] )
+ l.addTopLevelItem(item)
+
+ l.setCurrentItem(l.topLevelItem(0))
+
+
+
def delete_imported_key(self, addr):
if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
self.wallet.delete_imported_key(addr)
if not self.wallet.is_watching_only():
menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
- #menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
+ menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
if self.wallet.is_imported(addr):
menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
def get_sendable_balance(self):
- return sum(sum(self.wallet.get_addr_balance(a)) for a in self.get_payment_sources())
+ return sum(map(lambda x:x['value'], self.get_coins()))
- def get_payment_sources(self):
+ def get_coins(self):
if self.pay_from:
return self.pay_from
else:
- return self.wallet.get_account_addresses(self.current_account)
+ domain = self.wallet.get_account_addresses(self.current_account)
+ for i in self.wallet.frozen_addresses:
+ if i in domain: domain.remove(i)
+ return self.wallet.get_unspent_coins(domain)
def send_from_addresses(self, addrs):
run_hook('create_contact_menu', menu, item)
menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
+ def delete_invoice(self, item):
+ self.invoices.pop(key)
+ self.wallet.storage.put('invoices', self.invoices)
+ self.update_invoices_tab()
+
+ def show_invoice(self, key):
+ from electrum.paymentrequest import PaymentRequest
+ domain, value = self.invoices[key]
+ pr = PaymentRequest(self.config)
+ pr.read_file(key)
+ pr.domain = domain
+ pr.verify()
+ self.show_pr_details(pr)
+
+ def show_pr_details(self, pr):
+ msg = 'Domain: ' + pr.domain
+ msg += '\nStatus: ' + pr.get_status()
+ msg += '\nMemo: ' + pr.memo
+ msg += '\nPayment URL: ' + pr.payment_url
+ msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[0] + ' ' + self.format_amount(x[1])+ self.base_unit(), pr.get_outputs()))
+ QMessageBox.information(self, 'Invoice', msg , 'OK')
+
+ def create_invoice_menu(self, position):
+ item = self.invoices_list.itemAt(position)
+ if not item:
+ return
+ k = self.invoices_list.indexOfTopLevelItem(item)
+ key = self.invoices.keys()[k]
+ menu = QMenu()
+ menu.addAction(_("Details"), lambda: self.show_invoice(key))
+ menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
+ menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
+
def update_receive_item(self, item):
item.setFont(0, QFont(MONOSPACE_FONT))
fee_label = QLabel(_('Transaction fee') + ':')
grid.addWidget(fee_label, 2, 0)
- fee_e = AmountEdit(self.get_decimal_point)
+ fee_e = BTCAmountEdit(self.get_decimal_point)
fee_e.setText(self.format_amount(self.wallet.fee).strip())
grid.addWidget(fee_e, 2, 1)
msg = _('Fee per kilobyte of transaction.') + ' ' \
# run the dialog
if not d.exec_(): return
- try:
- fee = self.fee_e.get_amount()
- except Exception:
+ fee = self.fee_e.get_amount()
+ if fee is None:
QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
return