commit 12dfccb3ab199c59d767f729f332c24f0ba1dde9
parent 0273936b07d21f09be7c91bb3b6640320fdd340c
Author: ThomasV <thomasv@electrum.org>
Date: Fri, 27 May 2016 09:56:53 +0200
Define TreeWidget subclasses for lists
* move class code in separate files
* make menu column-dependent (fixes #1734)
Diffstat:
8 files changed, 660 insertions(+), 483 deletions(-)
diff --git a/gui/qt/address_list.py b/gui/qt/address_list.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+import webbrowser
+
+from util import *
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, format_satoshis, format_time
+from electrum.plugins import run_hook
+from electrum.bitcoin import is_address
+
+
+class AddressList(MyTreeWidget):
+
+ def __init__(self, parent=None):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [ _('Address'), _('Label'), _('Balance'), _('Tx')], 1)
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ item = self.currentItem()
+ current_address = item.data(0, Qt.UserRole).toString() if item else None
+ self.clear()
+ accounts = self.wallet.get_accounts()
+ if self.parent.current_account is None:
+ account_items = sorted(accounts.items())
+ else:
+ account_items = [(self.parent.current_account, accounts.get(self.parent.current_account))]
+ for k, account in account_items:
+ if len(accounts) > 1:
+ name = self.wallet.get_account_name(k)
+ c, u, x = self.wallet.get_account_balance(k)
+ account_item = QTreeWidgetItem([ name, '', self.parent.format_amount(c + u + x), ''])
+ account_item.setExpanded(self.accounts_expanded.get(k, True))
+ account_item.setData(0, Qt.UserRole, k)
+ self.addTopLevelItem(account_item)
+ else:
+ account_item = self
+ sequences = [0,1] if account.has_change() else [0]
+ for is_change in sequences:
+ if len(sequences) > 1:
+ name = _("Receiving") if not is_change else _("Change")
+ seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
+ account_item.addChild(seq_item)
+ if not is_change:
+ seq_item.setExpanded(True)
+ else:
+ seq_item = account_item
+ used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
+ used_flag = False
+ addr_list = account.get_addresses(is_change)
+ for address in addr_list:
+ num = len(self.wallet.history.get(address,[]))
+ is_used = self.wallet.is_used(address)
+ label = self.wallet.labels.get(address,'')
+ c, u, x = self.wallet.get_addr_balance(address)
+ balance = self.parent.format_amount(c + u + x)
+ address_item = QTreeWidgetItem([address, label, balance, "%d"%num])
+ address_item.setFont(0, QFont(MONOSPACE_FONT))
+ address_item.setData(0, Qt.UserRole, address)
+ address_item.setData(0, Qt.UserRole+1, True) # label can be edited
+ if self.wallet.is_frozen(address):
+ address_item.setBackgroundColor(0, QColor('lightblue'))
+ if self.wallet.is_beyond_limit(address, account, is_change):
+ address_item.setBackgroundColor(0, QColor('red'))
+ if is_used:
+ if not used_flag:
+ seq_item.insertChild(0, used_item)
+ used_flag = True
+ used_item.addChild(address_item)
+ else:
+ seq_item.addChild(address_item)
+ if address == current_address:
+ self.setCurrentItem(address_item)
+ # add utxos
+ utxos = self.wallet.get_addr_utxo(address)
+ for x in utxos:
+ h = x.get('prevout_hash')
+ s = h + ":%d"%x.get('prevout_n')
+ label = self.wallet.get_label(h)
+ utxo_item = QTreeWidgetItem([s, label, self.parent.format_amount(x['value'])])
+ utxo_item.setFont(0, QFont(MONOSPACE_FONT))
+ address_item.addChild(utxo_item)
+
+ def create_menu(self, position):
+ selected = self.selectedItems()
+ multi_select = len(selected) > 1
+ addrs = [unicode(item.text(0)) for item in selected]
+ if not multi_select:
+ item = self.itemAt(position)
+ col = self.currentColumn()
+ if not item:
+ return
+ addr = addrs[0]
+ if not is_address(addr):
+ k = str(item.data(0,32).toString())
+ if k:
+ self.create_account_menu(position, k, item)
+ else:
+ item.setExpanded(not item.isExpanded())
+ return
+
+ menu = QMenu()
+ if not multi_select:
+ column_title = self.headerItem().text(col)
+ menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(item.text(col)))
+ if col in self.editable_columns:
+ menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, col))
+ menu.addAction(_("Request payment"), lambda: self.parent.receive_at(addr))
+ menu.addAction(_('History'), lambda: self.parent.show_address(addr))
+ menu.addAction(_('Public Keys'), lambda: self.parent.show_public_keys(addr))
+ if self.wallet.can_export():
+ menu.addAction(_("Private key"), lambda: self.parent.show_private_key(addr))
+ if not self.wallet.is_watching_only():
+ menu.addAction(_("Sign/verify message"), lambda: self.parent.sign_verify_message(addr))
+ menu.addAction(_("Encrypt/decrypt message"), lambda: self.parent.encrypt_message(addr))
+ if self.wallet.is_imported(addr):
+ menu.addAction(_("Remove from wallet"), lambda: self.parent.delete_imported_key(addr))
+ addr_URL = block_explorer_URL(self.config, 'addr', addr)
+ if addr_URL:
+ menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))
+
+ if any(not self.wallet.is_frozen(addr) for addr in addrs):
+ menu.addAction(_("Freeze"), lambda: self.parent.set_frozen_state(addrs, True))
+ if any(self.wallet.is_frozen(addr) for addr in addrs):
+ menu.addAction(_("Unfreeze"), lambda: self.parent.set_frozen_state(addrs, False))
+
+ def can_send(addr):
+ return not self.wallet.is_frozen(addr) and sum(self.wallet.get_addr_balance(addr)[:2])
+ if any(can_send(addr) for addr in addrs):
+ menu.addAction(_("Send From"), lambda: self.parent.send_from_addresses(addrs))
+
+ run_hook('receive_menu', menu, addrs, self.wallet)
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/gui/qt/contact_list.py b/gui/qt/contact_list.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, format_satoshis, format_time, age
+from electrum.plugins import run_hook
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+from util import MyTreeWidget, pr_tooltips, pr_icons
+
+
+class ContactList(MyTreeWidget):
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Name'), _('Value'), _('Type')], 1, [0, 1])
+ self.setSelectionMode(QAbstractItemView.ExtendedSelection)
+ self.setSortingEnabled(True)
+
+ def on_permit_edit(self, item, column):
+ # openalias items shouldn't be editable
+ return item.text(2) != "openalias"
+
+ def on_edited(self, item, column, prior):
+ if column == 0: # Remove old contact if renamed
+ self.parent.contacts.pop(prior)
+ self.parent.set_contact(unicode(item.text(0)), unicode(item.text(1)))
+
+ def create_menu(self, position):
+ menu = QMenu()
+ selected = self.selectedItems()
+ if not selected:
+ menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
+ else:
+ labels = [unicode(item.text(0)) for item in selected]
+ addrs = [unicode(item.text(1)) for item in selected]
+ types = [unicode(item.text(2)) for item in selected]
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = '\n'.join([unicode(item.text(column)) for item in selected])
+
+ menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
+ if column in self.editable_columns:
+ menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column))
+
+ menu.addAction(_("Pay to"), lambda: self.parent.payto_contacts(labels))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_contacts(labels))
+ URLs = []
+ for (addr, _type) in zip(addrs, types):
+ if _type == 'address':
+ URLs.append(block_explorer_URL(self.config, 'addr', addr))
+ if URLs:
+ menu.addAction(_("View on block explorer"),
+ lambda: map(webbrowser.open, URLs))
+
+ run_hook('create_contact_menu', menu, selected)
+ menu.exec_(self.viewport().mapToGlobal(position))
+
+ def on_update(self):
+ item = self.currentItem()
+ current_key = item.data(0, Qt.UserRole).toString() if item else None
+ self.clear()
+ for key in sorted(self.parent.contacts.keys()):
+ _type, value = self.parent.contacts[key]
+ item = QTreeWidgetItem([key, value, _type])
+ item.setData(0, Qt.UserRole, key)
+ self.addTopLevelItem(item)
+ if key == current_key:
+ self.setCurrentItem(item)
+ run_hook('update_contacts_tab', self)
diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+import webbrowser
+
+from util import *
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, format_satoshis, format_time
+from electrum.plugins import run_hook
+
+
+class HistoryList(MyTreeWidget):
+
+ def __init__(self, parent=None):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [], 3)
+ self.refresh_headers()
+ self.setColumnHidden(1, True)
+
+ def refresh_headers(self):
+ headers = ['', '', _('Date'), _('Description') , _('Amount'),
+ _('Balance')]
+ run_hook('history_tab_headers', headers)
+ self.update_headers(headers)
+
+ def get_icon(self, conf, timestamp):
+ time_str = _("unknown")
+ if conf > 0:
+ time_str = format_time(timestamp)
+ if conf == -1:
+ time_str = _('Not Verified')
+ icon = QIcon(":icons/unconfirmed.png")
+ elif conf == 0:
+ time_str = _('Unconfirmed')
+ icon = QIcon(":icons/unconfirmed.png")
+ elif conf < 6:
+ icon = QIcon(":icons/clock%d.png"%conf)
+ else:
+ icon = QIcon(":icons/confirmed.png")
+ return icon, time_str
+
+ def get_domain(self):
+ '''Replaced in address_dialog.py'''
+ return self.wallet.get_account_addresses(self.parent.current_account)
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ h = self.wallet.get_history(self.get_domain())
+
+ item = self.currentItem()
+ current_tx = item.data(0, Qt.UserRole).toString() if item else None
+ self.clear()
+ run_hook('history_tab_update_begin')
+ for tx in h:
+ tx_hash, conf, value, timestamp, balance = tx
+ if conf is None and timestamp is None:
+ continue # skip history in offline mode
+ icon, time_str = self.get_icon(conf, timestamp)
+ v_str = self.parent.format_amount(value, True, whitespaces=True)
+ balance_str = self.parent.format_amount(balance, whitespaces=True)
+ label = self.wallet.get_label(tx_hash)
+ entry = ['', tx_hash, time_str, label, v_str, balance_str]
+ run_hook('history_tab_update', tx, entry)
+ item = QTreeWidgetItem(entry)
+ item.setIcon(0, icon)
+ for i in range(len(entry)):
+ if i>3:
+ item.setTextAlignment(i, Qt.AlignRight)
+ if i!=2:
+ item.setFont(i, QFont(MONOSPACE_FONT))
+ if value < 0:
+ item.setForeground(3, QBrush(QColor("#BC1E1E")))
+ item.setForeground(4, QBrush(QColor("#BC1E1E")))
+ if tx_hash:
+ item.setData(0, Qt.UserRole, tx_hash)
+ self.insertTopLevelItem(0, item)
+ if current_tx == tx_hash:
+ self.setCurrentItem(item)
+
+ def update_item(self, tx_hash, conf, timestamp):
+ icon, time_str = self.get_icon(conf, timestamp)
+ items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
+ if items:
+ item = items[0]
+ item.setIcon(0, icon)
+ item.setText(2, time_str)
+
+ def create_menu(self, position):
+ self.selectedIndexes()
+ item = self.currentItem()
+ if not item:
+ return
+ column = self.currentColumn()
+ tx_hash = str(item.data(0, Qt.UserRole).toString())
+ if not tx_hash:
+ return
+ if column is 0:
+ column_title = "ID"
+ column_data = tx_hash
+ else:
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+
+ tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
+ conf, timestamp = self.wallet.get_confirmations(tx_hash)
+ tx = self.wallet.transactions.get(tx_hash)
+ is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
+ rbf = is_mine and (conf == 0) and tx and not tx.is_final()
+ menu = QMenu()
+
+ menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
+ if column in self.editable_columns:
+ menu.addAction(_("Edit %s")%column_title, lambda: self.editItem(item, column))
+
+ menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
+ if rbf:
+ menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx))
+ if tx_URL:
+ menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/gui/qt/history_widget.py b/gui/qt/history_widget.py
@@ -1,132 +0,0 @@
-#!/usr/bin/env python
-#
-# Electrum - lightweight Bitcoin client
-# Copyright (C) 2015 Thomas Voegtlin
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-
-import webbrowser
-
-from util import *
-from electrum.i18n import _
-from electrum.util import block_explorer_URL, format_satoshis, format_time
-from electrum.plugins import run_hook
-
-
-class HistoryWidget(MyTreeWidget):
-
- def __init__(self, parent=None):
- MyTreeWidget.__init__(self, parent, self.create_menu, [], 3)
- self.refresh_headers()
- self.setColumnHidden(1, True)
- self.config = self.parent.config
-
- def refresh_headers(self):
- headers = ['', '', _('Date'), _('Description') , _('Amount'),
- _('Balance')]
- run_hook('history_tab_headers', headers)
- self.update_headers(headers)
-
- def get_icon(self, conf, timestamp):
- time_str = _("unknown")
- if conf > 0:
- time_str = format_time(timestamp)
- if conf == -1:
- time_str = _('Not Verified')
- icon = QIcon(":icons/unconfirmed.png")
- elif conf == 0:
- time_str = _('Unconfirmed')
- icon = QIcon(":icons/unconfirmed.png")
- elif conf < 6:
- icon = QIcon(":icons/clock%d.png"%conf)
- else:
- icon = QIcon(":icons/confirmed.png")
- return icon, time_str
-
- def get_domain(self):
- '''Replaced in address_dialog.py'''
- return self.wallet.get_account_addresses(self.parent.current_account)
-
- def on_update(self):
- self.wallet = self.parent.wallet
- h = self.wallet.get_history(self.get_domain())
-
- item = self.currentItem()
- current_tx = item.data(0, Qt.UserRole).toString() if item else None
- self.clear()
- run_hook('history_tab_update_begin')
- for tx in h:
- tx_hash, conf, value, timestamp, balance = tx
- if conf is None and timestamp is None:
- continue # skip history in offline mode
- icon, time_str = self.get_icon(conf, timestamp)
- v_str = self.parent.format_amount(value, True, whitespaces=True)
- balance_str = self.parent.format_amount(balance, whitespaces=True)
- label = self.wallet.get_label(tx_hash)
- entry = ['', tx_hash, time_str, label, v_str, balance_str]
- run_hook('history_tab_update', tx, entry)
- item = QTreeWidgetItem(entry)
- item.setIcon(0, icon)
- for i in range(len(entry)):
- if i>3:
- item.setTextAlignment(i, Qt.AlignRight)
- if i!=2:
- item.setFont(i, QFont(MONOSPACE_FONT))
- if value < 0:
- item.setForeground(3, QBrush(QColor("#BC1E1E")))
- item.setForeground(4, QBrush(QColor("#BC1E1E")))
- if tx_hash:
- item.setData(0, Qt.UserRole, tx_hash)
- self.insertTopLevelItem(0, item)
- if current_tx == tx_hash:
- self.setCurrentItem(item)
-
- def update_item(self, tx_hash, conf, timestamp):
- icon, time_str = self.get_icon(conf, timestamp)
- items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
- if items:
- item = items[0]
- item.setIcon(0, icon)
- item.setText(2, time_str)
-
- def create_menu(self, position):
- self.selectedIndexes()
- item = self.currentItem()
- if not item:
- return
- tx_hash = str(item.data(0, Qt.UserRole).toString())
- if not tx_hash:
- return
- tx_URL = block_explorer_URL(self.config, 'tx', tx_hash)
- conf, timestamp = self.wallet.get_confirmations(tx_hash)
- tx = self.wallet.transactions.get(tx_hash)
- is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
- rbf = is_mine and (conf == 0) and tx and not tx.is_final()
- menu = QMenu()
- menu.addAction(_("Copy ID to Clipboard"), lambda: self.parent.app.clipboard().setText(tx_hash))
- menu.addAction(_("Details"), lambda: self.parent.show_transaction(tx))
- menu.addAction(_("Edit description"), lambda: self.editItem(item, self.editable_columns[0]))
- if rbf:
- menu.addAction(_("Increase fee"), lambda: self.parent.bump_fee_dialog(tx))
- if tx_URL:
- menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL))
- menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/gui/qt/invoice_list.py b/gui/qt/invoice_list.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+from util import *
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, format_satoshis, format_time
+from electrum.plugins import run_hook
+
+
+class InvoiceList(MyTreeWidget):
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')], 2)
+ self.setSortingEnabled(True)
+ self.header().setResizeMode(1, QHeaderView.Interactive)
+ self.setColumnWidth(1, 200)
+
+ def on_update(self):
+ inv_list = self.parent.invoices.sorted_list()
+ self.clear()
+ for pr in inv_list:
+ key = pr.get_id()
+ status = self.parent.invoices.get_status(key)
+ requestor = pr.get_requestor()
+ exp = pr.get_expiration_date()
+ date_str = format_time(exp) if exp else _('Never')
+ item = QTreeWidgetItem([date_str, requestor, pr.memo, self.parent.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')])
+ item.setIcon(4, QIcon(pr_icons.get(status)))
+ item.setData(0, Qt.UserRole, key)
+ item.setFont(1, QFont(MONOSPACE_FONT))
+ item.setFont(3, QFont(MONOSPACE_FONT))
+ self.addTopLevelItem(item)
+ self.setCurrentItem(self.topLevelItem(0))
+ self.setVisible(len(inv_list))
+ self.parent.invoices_label.setVisible(len(inv_list))
+
+ def create_menu(self, position):
+ item = self.itemAt(position)
+ if not item:
+ return
+ key = str(item.data(0, 32).toString())
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+ pr = self.parent.invoices.get(key)
+ status = self.parent.invoices.get_status(key)
+ menu = QMenu()
+ if column_data:
+ menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
+ menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))
+ if status == PR_UNPAID:
+ menu.addAction(_("Pay Now"), lambda: self.parent.do_pay_invoice(key))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(key))
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -88,26 +88,6 @@ class StatusBarButton(QPushButton):
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
-pr_icons = {
- PR_UNPAID:":icons/unpaid.png",
- PR_PAID:":icons/confirmed.png",
- PR_EXPIRED:":icons/expired.png"
-}
-
-pr_tooltips = {
- PR_UNPAID:_('Pending'),
- PR_PAID:_('Paid'),
- PR_EXPIRED:_('Expired')
-}
-
-expiration_values = [
- (_('1 hour'), 60*60),
- (_('1 day'), 24*60*60),
- (_('1 week'), 7*24*60*60),
- (_('Never'), None)
-]
-
-
class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
@@ -305,7 +285,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.update_buttons_on_seed()
self.update_console()
self.clear_receive_tab()
- self.receive_list.update()
+ self.request_list.update()
self.tabs.show()
try:
@@ -628,15 +608,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def update_tabs(self):
self.history_list.update()
- self.receive_list.update()
+ self.request_list.update()
self.address_list.update()
- self.contacts_list.update()
- self.invoices_list.update()
+ self.contact_list.update()
+ self.invoice_list.update()
self.update_completions()
def create_history_tab(self):
- from history_widget import HistoryWidget
- self.history_list = l = HistoryWidget(self)
+ from history_list import HistoryList
+ self.history_list = l = HistoryList(self)
return l
def show_address(self, addr):
@@ -711,14 +691,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
grid.addLayout(buttons, 4, 1, 1, 2)
self.receive_requests_label = QLabel(_('Requests'))
- self.receive_list = MyTreeWidget(self, self.receive_list_menu, [_('Date'), _('Account'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 4)
- self.receive_list.currentItemChanged.connect(self.receive_item_changed)
- self.receive_list.itemClicked.connect(self.receive_item_changed)
- self.receive_list.setSortingEnabled(True)
- self.receive_list.setColumnWidth(0, 180)
- self.receive_list.hideColumn(1)
- self.receive_list.hideColumn(2)
- self.receive_list.on_update = self.update_receive_tab
+
+ from request_list import RequestList
+ self.request_list = RequestList(self)
# layout
vbox_g = QVBoxLayout()
@@ -734,33 +709,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
vbox.addLayout(hbox)
vbox.addStretch(1)
vbox.addWidget(self.receive_requests_label)
- vbox.addWidget(self.receive_list)
- vbox.setStretchFactor(self.receive_list, 1000)
+ vbox.addWidget(self.request_list)
+ vbox.setStretchFactor(self.request_list, 1000)
return w
- def receive_item_changed(self, item):
- if item is None:
- return
- if not self.receive_list.isItemSelected(item):
- return
- addr = str(item.text(2))
- req = self.wallet.receive_requests[addr]
- expires = util.age(req['time'] + req['exp']) if req.get('exp') else _('Never')
- amount = req['amount']
- message = self.wallet.labels.get(addr, '')
- self.receive_address_e.setText(addr)
- self.receive_message_e.setText(message)
- self.receive_amount_e.setAmount(amount)
- self.expires_combo.hide()
- self.expires_label.show()
- self.expires_label.setText(expires)
- self.new_request_button.setEnabled(True)
def delete_payment_request(self, item):
addr = str(item.text(2))
self.wallet.remove_payment_request(addr, self.config)
- self.receive_list.update()
+ self.request_list.update()
self.clear_receive_tab()
def get_request_URI(self, addr):
@@ -778,17 +736,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
URI += "&name=" + req['name'] + "&sig="+sig
return str(URI)
- def receive_list_menu(self, position):
- item = self.receive_list.itemAt(position)
- addr = str(item.text(2))
- req = self.wallet.receive_requests[addr]
- menu = QMenu(self)
- menu.addAction(_("Copy Address"), lambda: self.view_and_paste(_('Address'), '', addr))
- menu.addAction(_("Copy URI"), lambda: self.view_and_paste('URI', '', self.get_request_URI(addr)))
- menu.addAction(_("Save as BIP70 file"), lambda: self.export_payment_request(addr))
- menu.addAction(_("Delete"), lambda: self.delete_payment_request(item))
- run_hook('receive_list_menu', menu, addr)
- menu.exec_(self.receive_list.viewport().mapToGlobal(position))
def sign_payment_request(self, addr):
alias = self.config.get('alias')
@@ -823,7 +770,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
req = self.wallet.make_payment_request(addr, amount, message, expiration)
self.wallet.add_payment_request(req, self.config)
self.sign_payment_request(addr)
- self.receive_list.update()
+ self.request_list.update()
self.address_list.update()
self.save_request_button.setEnabled(False)
@@ -901,52 +848,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.receive_address_e.setText(addr)
self.new_request_button.setEnabled(True)
- def update_receive_tab(self):
-
- # hide receive tab if no receive requests available
- b = len(self.wallet.receive_requests) > 0
- self.receive_list.setVisible(b)
- self.receive_requests_label.setVisible(b)
- if not b:
- self.expires_label.hide()
- self.expires_combo.show()
-
- # check if it is necessary to show the account
- self.receive_list.setColumnHidden(1, len(self.wallet.get_accounts()) == 1)
-
- # update the receive address if necessary
- current_address = self.receive_address_e.text()
- domain = self.wallet.get_account_addresses(self.current_account, include_change=False)
- addr = self.wallet.get_unused_address(self.current_account)
- if not current_address in domain and addr:
- self.set_receive_address(addr)
- self.new_request_button.setEnabled(addr != current_address)
-
- # clear the list and fill it again
- self.receive_list.clear()
- for req in self.wallet.get_sorted_requests(self.config):
- address = req['address']
- if address not in domain:
- continue
- timestamp = req.get('time', 0)
- amount = req.get('amount')
- expiration = req.get('exp', None)
- message = req.get('memo', '')
- date = format_time(timestamp)
- status = req.get('status')
- signature = req.get('sig')
- requestor = req.get('name', '')
- amount_str = self.format_amount(amount) if amount else ""
- account = ''
- item = QTreeWidgetItem([date, account, address, '', message, amount_str, pr_tooltips.get(status,'')])
- if signature is not None:
- item.setIcon(3, QIcon(":icons/seal.png"))
- item.setToolTip(3, 'signed by '+ requestor)
- if status is not PR_UNKNOWN:
- item.setIcon(6, QIcon(pr_icons.get(status)))
- self.receive_list.addTopLevelItem(item)
-
-
def update_receive_qr(self):
addr = str(self.receive_address_e.text())
amount = self.receive_amount_e.get_amount()
@@ -1101,12 +1002,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.fee_e.textChanged.connect(entry_changed)
self.invoices_label = QLabel(_('Invoices'))
- self.invoices_list = MyTreeWidget(self, self.invoices_list_menu,
- [_('Expires'), _('Requestor'), _('Description'), _('Amount'), _('Status')], 2)
- self.invoices_list.setSortingEnabled(True)
- self.invoices_list.header().setResizeMode(1, QHeaderView.Interactive)
- self.invoices_list.setColumnWidth(1, 200)
- self.invoices_list.on_update = self.update_invoices_list
+ from invoice_list import InvoiceList
+ self.invoice_list = InvoiceList(self)
vbox0 = QVBoxLayout()
vbox0.addLayout(grid)
@@ -1117,8 +1014,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
vbox.addLayout(hbox)
vbox.addStretch(1)
vbox.addWidget(self.invoices_label)
- vbox.addWidget(self.invoices_list)
- vbox.setStretchFactor(self.invoices_list, 1000)
+ vbox.addWidget(self.invoice_list)
+ vbox.setStretchFactor(self.invoice_list, 1000)
# Defer this until grid is parented to avoid ugly flash during startup
self.update_fee_edit()
@@ -1402,7 +1299,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if tx_desc is not None and tx.is_complete():
self.wallet.set_label(tx.hash(), tx_desc)
parent.show_message(_('Payment sent.') + '\n' + msg)
- self.invoices_list.update()
+ self.invoice_list.update()
self.do_clear()
else:
parent.show_error(msg)
@@ -1428,23 +1325,25 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.payto_e.setText(_("please wait..."))
return True
+ def delete_invoice(self, key):
+ self.invoices.remove(key)
+ self.invoice_list.update()
+
def payment_request_ok(self):
pr = self.payment_request
key = self.invoices.add(pr)
status = self.invoices.get_status(key)
- self.invoices_list.update()
+ self.invoice_list.update()
if status == PR_PAID:
self.show_message("invoice already paid")
self.do_clear()
self.payment_request = None
return
-
self.payto_e.is_pr = True
if not pr.has_expired():
self.payto_e.setGreen()
else:
self.payto_e.setExpired()
-
self.payto_e.setText(pr.get_requestor())
self.amount_e.setText(format_satoshis_plain(pr.get_amount(), self.decimal_point))
self.message_e.setText(pr.get_memo())
@@ -1524,42 +1423,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return w
def create_addresses_tab(self):
- l = MyTreeWidget(self, self.create_address_menu, [ _('Address'), _('Label'), _('Balance'), _('Tx')], 1)
- l.setSelectionMode(QAbstractItemView.ExtendedSelection)
- l.on_update = self.update_address_tab
- self.address_list = l
+ from address_list import AddressList
+ self.address_list = l = AddressList(self)
return self.create_list_tab(l)
def create_contacts_tab(self):
- l = MyTreeWidget(self, self.create_contact_menu, [_('Name'), _('Value'), _('Type')], 1, [0, 1])
- l.setSelectionMode(QAbstractItemView.ExtendedSelection)
- l.setSortingEnabled(True)
- l.on_edited = self.on_contact_edited
- l.on_permit_edit = self.on_permit_contact_edit
- l.on_update = self.update_contacts_tab
- self.contacts_list = l
+ from contact_list import ContactList
+ self.contact_list = l = ContactList(self)
return self.create_list_tab(l)
- def update_invoices_list(self):
- inv_list = self.invoices.sorted_list()
- l = self.invoices_list
- l.clear()
- for pr in inv_list:
- key = pr.get_id()
- status = self.invoices.get_status(key)
- requestor = pr.get_requestor()
- exp = pr.get_expiration_date()
- date_str = util.format_time(exp) if exp else _('Never')
- item = QTreeWidgetItem([date_str, requestor, pr.memo, self.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')])
- item.setIcon(4, QIcon(pr_icons.get(status)))
- item.setData(0, Qt.UserRole, key)
- item.setFont(1, QFont(MONOSPACE_FONT))
- item.setFont(3, QFont(MONOSPACE_FONT))
- l.addTopLevelItem(item)
- l.setCurrentItem(l.topLevelItem(0))
- self.invoices_list.setVisible(len(inv_list))
- self.invoices_label.setVisible(len(inv_list))
-
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)
@@ -1586,53 +1458,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
menu.addAction(_("View details"), lambda: self.show_account_details(k))
menu.exec_(self.address_list.viewport().mapToGlobal(position))
- def create_address_menu(self, position):
- selected = self.address_list.selectedItems()
- multi_select = len(selected) > 1
- addrs = [unicode(item.text(0)) for item in selected]
- if not multi_select:
- item = self.address_list.itemAt(position)
- if not item:
- return
- addr = addrs[0]
- if not is_valid(addr):
- k = str(item.data(0,32).toString())
- if k:
- self.create_account_menu(position, k, item)
- else:
- item.setExpanded(not item.isExpanded())
- return
- menu = QMenu()
- if not multi_select:
- menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
- menu.addAction(_("Request payment"), lambda: self.receive_at(addr))
- menu.addAction(_("Edit label"), lambda: self.address_list.editItem(item, self.address_list.editable_columns[0]))
- menu.addAction(_('History'), lambda: self.show_address(addr))
- menu.addAction(_('Public Keys'), lambda: self.show_public_keys(addr))
- if self.wallet.can_export():
- menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
- if not self.wallet.is_watching_only():
- menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_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))
- addr_URL = block_explorer_URL(self.config, 'addr', addr)
- if addr_URL:
- menu.addAction(_("View on block explorer"), lambda: webbrowser.open(addr_URL))
-
- if any(not self.wallet.is_frozen(addr) for addr in addrs):
- menu.addAction(_("Freeze"), lambda: self.set_frozen_state(addrs, True))
- if any(self.wallet.is_frozen(addr) for addr in addrs):
- menu.addAction(_("Unfreeze"), lambda: self.set_frozen_state(addrs, False))
-
- def can_send(addr):
- return not self.wallet.is_frozen(addr) and sum(self.wallet.get_addr_balance(addr)[:2])
- if any(can_send(addr) for addr in addrs):
- menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
-
- run_hook('receive_menu', menu, addrs, self.wallet)
- menu.exec_(self.address_list.viewport().mapToGlobal(position))
-
def get_coins(self):
if self.pay_from:
@@ -1669,22 +1494,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.payto_e.setText(text)
self.payto_e.setFocus()
- def on_permit_contact_edit(self, item, column):
- # openalias items shouldn't be editable
- return item.text(2) != "openalias"
-
- def on_contact_edited(self, item, column, prior):
- if column == 0: # Remove old contact if renamed
- self.contacts.pop(prior)
- self.set_contact(unicode(item.text(0)), unicode(item.text(1)))
-
def set_contact(self, label, address):
if not is_valid(address):
self.show_error(_('Invalid Address'))
- self.contacts_list.update() # Displays original unchanged value
+ self.contact_list.update() # Displays original unchanged value
return False
self.contacts[label] = ('address', address)
- self.contacts_list.update()
+ self.contact_list.update()
self.history_list.update()
self.update_completions()
return True
@@ -1696,33 +1512,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
for label in labels:
self.contacts.pop(label)
self.history_list.update()
- self.contacts_list.update()
+ self.contact_list.update()
self.update_completions()
- def create_contact_menu(self, position):
- menu = QMenu()
- selected = self.contacts_list.selectedItems()
- if not selected:
- menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
- else:
- labels = [unicode(item.text(0)) for item in selected]
- addrs = [unicode(item.text(1)) for item in selected]
- types = [unicode(item.text(2)) for item in selected]
- menu.addAction(_("Copy to Clipboard"), lambda:
- self.app.clipboard().setText('\n'.join(labels)))
- menu.addAction(_("Pay to"), lambda: self.payto_contacts(labels))
- menu.addAction(_("Delete"), lambda: self.delete_contacts(labels))
- URLs = []
- for (addr, _type) in zip(addrs, types):
- if _type == 'address':
- URLs.append(block_explorer_URL(self.config, 'addr', addr))
- if URLs:
- menu.addAction(_("View on block explorer"),
- lambda: map(webbrowser.open, URLs))
-
- run_hook('create_contact_menu', menu, selected)
- menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
-
def show_invoice(self, key):
pr = self.invoices.get(key)
@@ -1766,107 +1558,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
else:
self.payment_request_error()
-
- def invoices_list_menu(self, position):
- item = self.invoices_list.itemAt(position)
- if not item:
- return
- key = str(item.data(0, 32).toString())
- pr = self.invoices.get(key)
- status = self.invoices.get_status(key)
- menu = QMenu()
- menu.addAction(_("Details"), lambda: self.show_invoice(key))
- if status == PR_UNPAID:
- menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
- def delete_invoice(key):
- self.invoices.remove(key)
- self.invoices_list.update()
- menu.addAction(_("Delete"), lambda: delete_invoice(key))
- menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
-
-
- def update_address_tab(self):
- l = self.address_list
- item = l.currentItem()
- current_address = item.data(0, Qt.UserRole).toString() if item else None
- l.clear()
- accounts = self.wallet.get_accounts()
- if self.current_account is None:
- account_items = sorted(accounts.items())
- else:
- account_items = [(self.current_account, accounts.get(self.current_account))]
- for k, account in account_items:
- if len(accounts) > 1:
- name = self.wallet.get_account_name(k)
- c, u, x = self.wallet.get_account_balance(k)
- account_item = QTreeWidgetItem([ name, '', self.format_amount(c + u + x), ''])
- account_item.setExpanded(self.accounts_expanded.get(k, True))
- account_item.setData(0, Qt.UserRole, k)
- l.addTopLevelItem(account_item)
- else:
- account_item = l
- sequences = [0,1] if account.has_change() else [0]
- for is_change in sequences:
- if len(sequences) > 1:
- name = _("Receiving") if not is_change else _("Change")
- seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
- account_item.addChild(seq_item)
- if not is_change:
- seq_item.setExpanded(True)
- else:
- seq_item = account_item
- used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
- used_flag = False
- addr_list = account.get_addresses(is_change)
- for address in addr_list:
- num = len(self.wallet.history.get(address,[]))
- is_used = self.wallet.is_used(address)
- label = self.wallet.labels.get(address,'')
- c, u, x = self.wallet.get_addr_balance(address)
- balance = self.format_amount(c + u + x)
- address_item = QTreeWidgetItem([address, label, balance, "%d"%num])
- address_item.setFont(0, QFont(MONOSPACE_FONT))
- address_item.setData(0, Qt.UserRole, address)
- address_item.setData(0, Qt.UserRole+1, True) # label can be edited
- if self.wallet.is_frozen(address):
- address_item.setBackgroundColor(0, QColor('lightblue'))
- if self.wallet.is_beyond_limit(address, account, is_change):
- address_item.setBackgroundColor(0, QColor('red'))
- if is_used:
- if not used_flag:
- seq_item.insertChild(0, used_item)
- used_flag = True
- used_item.addChild(address_item)
- else:
- seq_item.addChild(address_item)
- if address == current_address:
- l.setCurrentItem(address_item)
- # add utxos
- utxos = self.wallet.get_addr_utxo(address)
- for x in utxos:
- h = x.get('prevout_hash')
- s = h + ":%d"%x.get('prevout_n')
- label = self.wallet.get_label(h)
- utxo_item = QTreeWidgetItem([s, label, self.format_amount(x['value'])])
- utxo_item.setFont(0, QFont(MONOSPACE_FONT))
- address_item.addChild(utxo_item)
-
-
- def update_contacts_tab(self):
- l = self.contacts_list
- item = l.currentItem()
- current_key = item.data(0, Qt.UserRole).toString() if item else None
- l.clear()
- for key in sorted(self.contacts.keys()):
- _type, value = self.contacts[key]
- item = QTreeWidgetItem([key, value, _type])
- item.setData(0, Qt.UserRole, key)
- l.addTopLevelItem(item)
- if key == current_key:
- l.setCurrentItem(item)
- run_hook('update_contacts_tab', l)
-
-
def create_console_tab(self):
from console import Console
self.console = console = Console()
@@ -1906,7 +1597,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.history_list.update()
self.update_status()
self.address_list.update()
- self.receive_list.update()
+ self.request_list.update()
def create_status_bar(self):
@@ -1993,13 +1684,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if i == 0:
self.history_list.filter(t, [2, 3, 4]) # Date, Description, Amount
elif i == 1:
- self.invoices_list.filter(t, [0, 1, 2, 3]) # Date, Requestor, Description, Amount
+ self.invoice_list.filter(t, [0, 1, 2, 3]) # Date, Requestor, Description, Amount
elif i == 2:
- self.receive_list.filter(t, [0, 1, 2, 3, 4]) # Date, Account, Address, Description, Amount
+ self.request_list.filter(t, [0, 1, 2, 3, 4]) # Date, Account, Address, Description, Amount
elif i == 3:
self.address_list.filter(t, [0,1, 2]) # Address, Label, Balance
elif i == 4:
- self.contacts_list.filter(t, [0, 1]) # Key, Value
+ self.contact_list.filter(t, [0, 1]) # Key, Value
def new_contact_dialog(self):
@@ -2795,7 +2486,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
raise Exception('Unknown base unit')
self.config.set_key('decimal_point', self.decimal_point, True)
self.history_list.update()
- self.receive_list.update()
+ self.request_list.update()
self.address_list.update()
for edit, amount in zip(edits, amounts):
edit.setAmount(amount)
diff --git a/gui/qt/request_list.py b/gui/qt/request_list.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+#
+# Electrum - lightweight Bitcoin client
+# Copyright (C) 2015 Thomas Voegtlin
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+
+from electrum.i18n import _
+from electrum.util import block_explorer_URL, format_satoshis, format_time, age
+from electrum.plugins import run_hook
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+from util import MyTreeWidget, pr_tooltips, pr_icons
+
+
+class RequestList(MyTreeWidget):
+
+ def __init__(self, parent):
+ MyTreeWidget.__init__(self, parent, self.create_menu, [_('Date'), _('Account'), _('Address'), '', _('Description'), _('Amount'), _('Status')], 4)
+ self.currentItemChanged.connect(self.item_changed)
+ self.itemClicked.connect(self.item_changed)
+ self.setSortingEnabled(True)
+ self.setColumnWidth(0, 180)
+ self.hideColumn(1)
+ self.hideColumn(2)
+
+ def item_changed(self, item):
+ if item is None:
+ return
+ if not self.isItemSelected(item):
+ return
+ addr = str(item.text(2))
+ req = self.wallet.receive_requests[addr]
+ expires = age(req['time'] + req['exp']) if req.get('exp') else _('Never')
+ amount = req['amount']
+ message = self.wallet.labels.get(addr, '')
+ self.parent.receive_address_e.setText(addr)
+ self.parent.receive_message_e.setText(message)
+ self.parent.receive_amount_e.setAmount(amount)
+ self.parent.expires_combo.hide()
+ self.parent.expires_label.show()
+ self.parent.expires_label.setText(expires)
+ self.parent.new_request_button.setEnabled(True)
+
+ def on_update(self):
+ self.wallet = self.parent.wallet
+ # hide receive tab if no receive requests available
+ b = len(self.wallet.receive_requests) > 0
+ self.setVisible(b)
+ self.parent.receive_requests_label.setVisible(b)
+ if not b:
+ self.parent.expires_label.hide()
+ self.parent.expires_combo.show()
+
+ # check if it is necessary to show the account
+ self.setColumnHidden(1, len(self.wallet.get_accounts()) == 1)
+
+ # update the receive address if necessary
+ current_address = self.parent.receive_address_e.text()
+ domain = self.wallet.get_account_addresses(self.parent.current_account, include_change=False)
+ addr = self.wallet.get_unused_address(self.parent.current_account)
+ if not current_address in domain and addr:
+ self.parent.set_receive_address(addr)
+ self.parent.new_request_button.setEnabled(addr != current_address)
+
+ # clear the list and fill it again
+ self.clear()
+ for req in self.wallet.get_sorted_requests(self.config):
+ address = req['address']
+ if address not in domain:
+ continue
+ timestamp = req.get('time', 0)
+ amount = req.get('amount')
+ expiration = req.get('exp', None)
+ message = req.get('memo', '')
+ date = format_time(timestamp)
+ status = req.get('status')
+ signature = req.get('sig')
+ requestor = req.get('name', '')
+ amount_str = self.parent.format_amount(amount) if amount else ""
+ account = ''
+ item = QTreeWidgetItem([date, account, address, '', message, amount_str, pr_tooltips.get(status,'')])
+ if signature is not None:
+ item.setIcon(3, QIcon(":icons/seal.png"))
+ item.setToolTip(3, 'signed by '+ requestor)
+ if status is not PR_UNKNOWN:
+ item.setIcon(6, QIcon(pr_icons.get(status)))
+ self.addTopLevelItem(item)
+
+
+ def create_menu(self, position):
+ item = self.itemAt(position)
+ addr = str(item.text(2))
+ req = self.wallet.receive_requests[addr]
+ column = self.currentColumn()
+ column_title = self.headerItem().text(column)
+ column_data = item.text(column)
+ menu = QMenu(self)
+ menu.addAction(_("Copy %s")%column_title, lambda: self.parent.app.clipboard().setText(column_data))
+ menu.addAction(_("Copy URI"), lambda: self.parent.view_and_paste('URI', '', self.parent.get_request_URI(addr)))
+ menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
+ menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(item))
+ run_hook('receive_list_menu', menu, addr)
+ menu.exec_(self.viewport().mapToGlobal(position))
diff --git a/gui/qt/util.py b/gui/qt/util.py
@@ -27,6 +27,28 @@ BLACK_FG = "QWidget {color:black;}"
dialogs = []
+from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
+
+pr_icons = {
+ PR_UNPAID:":icons/unpaid.png",
+ PR_PAID:":icons/confirmed.png",
+ PR_EXPIRED:":icons/expired.png"
+}
+
+pr_tooltips = {
+ PR_UNPAID:_('Pending'),
+ PR_PAID:_('Paid'),
+ PR_EXPIRED:_('Expired')
+}
+
+expiration_values = [
+ (_('1 hour'), 60*60),
+ (_('1 day'), 24*60*60),
+ (_('1 week'), 7*24*60*60),
+ (_('Never'), None)
+]
+
+
class Timer(QThread):
stopped = False
@@ -351,6 +373,7 @@ class MyTreeWidget(QTreeWidget):
editable_columns=None):
QTreeWidget.__init__(self, parent)
self.parent = parent
+ self.config = self.parent.config
self.stretch_column = stretch_column
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(create_menu)
@@ -366,7 +389,7 @@ class MyTreeWidget(QTreeWidget):
editable_columns = [stretch_column]
self.editable_columns = editable_columns
self.setItemDelegate(ElectrumItemDelegate(self))
- self.itemActivated.connect(self.on_activated)
+ self.itemDoubleClicked.connect(self.on_doubleclick)
self.update_headers(headers)
def update_headers(self, headers):
@@ -386,7 +409,7 @@ class MyTreeWidget(QTreeWidget):
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
def keyPressEvent(self, event):
- if event.key() == Qt.Key_F2:
+ if event.key() in [ Qt.Key_F2, Qt.Key_Return ] and self.editor is None:
self.on_activated(self.currentItem(), self.currentColumn())
else:
QTreeWidget.keyPressEvent(self, event)
@@ -398,13 +421,15 @@ class MyTreeWidget(QTreeWidget):
def on_permit_edit(self, item, column):
return True
- def on_activated(self, item, column):
+ def on_doubleclick(self, item, column):
if self.permit_edit(item, column):
self.editItem(item, column)
- else:
- pt = self.visualItemRect(item).bottomLeft()
- pt.setX(50)
- self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), pt)
+
+ def on_activated(self, item, column):
+ # on 'enter' we show the menu
+ pt = self.visualItemRect(item).bottomLeft()
+ pt.setX(50)
+ self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), pt)
def createEditor(self, parent, option, index):
self.editor = QStyledItemDelegate.createEditor(self.itemDelegate(),