tRework MyTreeWidget editing - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 544b829f6e43de5ab20d8a6c414d7689e13400d9 DIR parent c481e61417c2c0b9f0432bc662f3dc8453fdacd6 HTML Author: Neil Booth <kyuupichan@gmail.com> Date: Wed, 9 Sep 2015 07:38:54 +0900 Rework MyTreeWidget editing Gets rid of need for EditableItem class. New callback on_permit_edit to permit widgets to refuse editing. Restores popup menu on activating a non-editable column behaviour. Diffstat: M gui/qt/history_widget.py | 3 +-- M gui/qt/main_window.py | 24 +++++++++++++----------- M gui/qt/util.py | 94 +++++++++++++++++++------------ 3 files changed, 72 insertions(+), 49 deletions(-) --- DIR diff --git a/gui/qt/history_widget.py b/gui/qt/history_widget.py t@@ -32,7 +32,6 @@ class HistoryWidget(MyTreeWidget): self.refresh_headers() self.setColumnHidden(1, True) self.config = self.parent.config - self.setSortingEnabled(False) def refresh_headers(self): headers = ['', '', _('Date'), _('Description') , _('Amount'), t@@ -72,7 +71,7 @@ class HistoryWidget(MyTreeWidget): label, is_default_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 = EditableItem(entry) + item = QTreeWidgetItem(entry) item.setIcon(0, icon) for i in range(len(entry)): if i>3: DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py t@@ -855,7 +855,7 @@ class ElectrumWindow(QMainWindow, PrintError): requestor = req.get('name', '') amount_str = self.format_amount(amount) if amount else "" account = '' - item = EditableItem([date, account, address, '', message, amount_str, pr_tooltips.get(status,'')]) + 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) t@@ -921,7 +921,6 @@ class ElectrumWindow(QMainWindow, PrintError): self.from_label = QLabel(_('From')) grid.addWidget(self.from_label, 3, 0) self.from_list = MyTreeWidget(self, self.from_list_menu, ['','']) - self.from_list.setSortingEnabled(False) self.from_list.setHeaderHidden(True) self.from_list.setMaximumHeight(80) grid.addWidget(self.from_list, 3, 1, 1, 3) t@@ -1000,6 +999,7 @@ class ElectrumWindow(QMainWindow, PrintError): 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) t@@ -1406,14 +1406,15 @@ class ElectrumWindow(QMainWindow, PrintError): def create_addresses_tab(self): l = MyTreeWidget(self, self.create_receive_menu, [ _('Address'), _('Label'), _('Balance'), _('Tx')], 1) l.setSelectionMode(QAbstractItemView.ExtendedSelection) - l.setSortingEnabled(False) self.address_list = l 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.item_edited = self.contact_edited + l.setSortingEnabled(True) + l.on_edited = self.on_contact_edited + l.on_permit_edit = self.on_permit_contact_edit self.contacts_list = l return self.create_list_tab(l) t@@ -1427,7 +1428,7 @@ class ElectrumWindow(QMainWindow, PrintError): requestor = pr.get_requestor() exp = pr.get_expiration_date() date_str = util.format_time(exp) if exp else _('Never') - item = EditableItem([date_str, requestor, pr.memo, self.format_amount(pr.get_amount(), whitespaces=True), pr_tooltips.get(status,'')]) + 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)) t@@ -1551,7 +1552,11 @@ class ElectrumWindow(QMainWindow, PrintError): self.payto_e.setText(text) self.payto_e.setFocus() - def contact_edited(self, item, column, prior): + 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))) t@@ -1703,7 +1708,7 @@ class ElectrumWindow(QMainWindow, PrintError): label = self.wallet.labels.get(address,'') c, u, x = self.wallet.get_addr_balance(address) balance = self.format_amount(c + u + x) - item = EditableItem( [ address, label, balance, "%d"%num] ) + item = QTreeWidgetItem([address, label, balance, "%d"%num]) item.setFont(0, QFont(MONOSPACE_FONT)) item.setData(0, Qt.UserRole, address) item.setData(0, Qt.UserRole+1, True) # label can be edited t@@ -1729,10 +1734,7 @@ class ElectrumWindow(QMainWindow, PrintError): l.clear() for key in sorted(self.contacts.keys()): _type, value = self.contacts[key] - if _type == 'address': - item = EditableItem([key, value, _type]) - else: # openalias items shouldn't be editable - item = QTreeWidgetItem([key, value, _type]) + item = QTreeWidgetItem([key, value, _type]) item.setData(0, Qt.UserRole, key) l.addTopLevelItem(item) if key == current_key: DIR diff --git a/gui/qt/util.py b/gui/qt/util.py t@@ -283,19 +283,9 @@ def filename_field(parent, config, defaultname, select_msg): return vbox, filename_e, b1 -class EditableItem(QTreeWidgetItem): - def __init__(self, columns): - QTreeWidgetItem.__init__(self, columns) - self.setFlags(self.flags() | Qt.ItemIsEditable) - -class EditableItemDelegate(QStyledItemDelegate): +class ElectrumItemDelegate(QStyledItemDelegate): def createEditor(self, parent, option, index): - if index.column() not in self.parent().editable_columns: - return None - self.parent().editing = (self.parent().currentItem(), - index.column(), - unicode(index.data().toString())) - return QStyledItemDelegate.createEditor(self, parent, option, index) + return self.parent().createEditor(parent, option, index) class MyTreeWidget(QTreeWidget): t@@ -305,23 +295,20 @@ class MyTreeWidget(QTreeWidget): self.parent = parent self.stretch_column = stretch_column self.setContextMenuPolicy(Qt.CustomContextMenu) - self.itemActivated.connect(self.on_activated) self.customContextMenuRequested.connect(create_menu) + self.setUniformRowHeights(True) # extend the syntax for consistency self.addChild = self.addTopLevelItem self.insertChild = self.insertTopLevelItem # Control which columns are editable - self.editing = (None, None, None) + self.editor = None if editable_columns is None: editable_columns = [stretch_column] self.editable_columns = editable_columns - self.setEditTriggers(QAbstractItemView.DoubleClicked | - QAbstractItemView.EditKeyPressed) - self.setItemDelegate(EditableItemDelegate(self)) - self.itemChanged.connect(self.item_changed) + self.setItemDelegate(ElectrumItemDelegate(self)) + self.itemActivated.connect(self.on_activated) self.update_headers(headers) - self.setSortingEnabled(True) def update_headers(self, headers): self.setColumnCount(len(headers)) t@@ -331,26 +318,61 @@ class MyTreeWidget(QTreeWidget): sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents self.header().setResizeMode(col, sm) - def on_activated(self, item): - if not item: - return - for i in range(0,self.viewport().height()/5): - if self.itemAt(QPoint(0,i*5)) == item: - break + def editItem(self, item, column): + if column in self.editable_columns: + self.editing_itemcol = (item, column, unicode(item.text(column))) + # Calling setFlags causes on_changed events for some reason + item.setFlags(item.flags() | Qt.ItemIsEditable) + QTreeWidget.editItem(self, item, column) + item.setFlags(item.flags() & ~Qt.ItemIsEditable) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_F2: + self.on_activated(self.currentItem(), self.currentColumn()) else: - return - for j in range(0,30): - if self.itemAt(QPoint(0,i*5 + j)) != item: - break - self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1)) + QTreeWidget.keyPressEvent(self, event) - def item_changed(self, item, column): - '''Called only when the text actually changes''' - # Only pass user edits to item_edited() - if item == self.editing[0] and column == self.editing[1]: - self.item_edited(item, column, self.editing[2]) + def permit_edit(self, item, column): + return (column in self.editable_columns + and self.on_permit_edit(item, column)) + + def on_permit_edit(self, item, column): + return True - def item_edited(self, item, column, prior): + def on_activated(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 createEditor(self, parent, option, index): + self.editor = QStyledItemDelegate.createEditor(self.itemDelegate(), + parent, option, index) + self.editor.connect(self.editor, SIGNAL("editingFinished()"), + self.editing_finished) + return self.editor + + def editing_finished(self): + # Long-time QT bug - pressing Enter to finish editing signals + # editingFinished twice. If the item changed the sequence is + # Enter key: editingFinished, on_change, editingFinished + # Mouse: on_change, editingFinished + # This mess is the cleanest way to ensure we make the + # on_edited callback with the updated item + if self.editor: + (item, column, prior_text) = self.editing_itemcol + if self.editor.text() == prior_text: + self.editor = None # Unchanged - ignore any 2nd call + elif item.text(column) == prior_text: + pass # Buggy first call on Enter key, item not yet updated + else: + # What we want - the updated item + self.on_edited(*self.editing_itemcol) + self.editor = None + + def on_edited(self, item, column, prior): '''Called only when the text actually changes''' key = str(item.data(0, Qt.UserRole).toString()) text = unicode(item.text(column))