|
|
@ -7,15 +7,16 @@ import queue |
|
|
|
import traceback |
|
|
|
import os |
|
|
|
import webbrowser |
|
|
|
|
|
|
|
from decimal import Decimal |
|
|
|
from functools import partial, lru_cache |
|
|
|
from typing import NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any |
|
|
|
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any, |
|
|
|
Sequence, Iterable) |
|
|
|
|
|
|
|
from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, |
|
|
|
QPalette, QIcon, QFontMetrics, QShowEvent) |
|
|
|
from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, |
|
|
|
QCoreApplication, QItemSelectionModel, QThread, |
|
|
|
QSortFilterProxyModel, QSize, QLocale) |
|
|
|
QSortFilterProxyModel, QSize, QLocale, QAbstractItemModel) |
|
|
|
from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, |
|
|
|
QAbstractItemView, QVBoxLayout, QLineEdit, |
|
|
|
QStyle, QDialog, QGroupBox, QButtonGroup, QRadioButton, |
|
|
@ -466,6 +467,7 @@ def filename_field(parent, config, defaultname, select_msg): |
|
|
|
|
|
|
|
return vbox, filename_e, b1 |
|
|
|
|
|
|
|
|
|
|
|
class ElectrumItemDelegate(QStyledItemDelegate): |
|
|
|
def __init__(self, tv: 'MyTreeView'): |
|
|
|
super().__init__(tv) |
|
|
@ -477,7 +479,7 @@ class ElectrumItemDelegate(QStyledItemDelegate): |
|
|
|
new_text = editor.text() |
|
|
|
idx = QModelIndex(self.opened) |
|
|
|
row, col = idx.row(), idx.column() |
|
|
|
_prior_text, user_role = self.tv.text_txid_from_coordinate(row, col) |
|
|
|
_prior_text, user_role = self.tv.get_text_and_userrole_from_coordinate(row, col) |
|
|
|
# check that we didn't forget to set UserRole on an editable field |
|
|
|
assert user_role is not None, (row, col) |
|
|
|
self.tv.on_edited(idx, user_role, new_text) |
|
|
@ -488,9 +490,12 @@ class ElectrumItemDelegate(QStyledItemDelegate): |
|
|
|
self.opened = QPersistentModelIndex(idx) |
|
|
|
return super().createEditor(parent, option, idx) |
|
|
|
|
|
|
|
|
|
|
|
class MyTreeView(QTreeView): |
|
|
|
ROLE_CLIPBOARD_DATA = Qt.UserRole + 100 |
|
|
|
|
|
|
|
filter_columns: Iterable[int] |
|
|
|
|
|
|
|
def __init__(self, parent: 'ElectrumWindow', create_menu, *, |
|
|
|
stretch_column=None, editable_columns=None): |
|
|
|
super().__init__(parent) |
|
|
@ -536,10 +541,25 @@ class MyTreeView(QTreeView): |
|
|
|
def current_item_user_role(self, col) -> Any: |
|
|
|
idx = self.selectionModel().currentIndex() |
|
|
|
idx = idx.sibling(idx.row(), col) |
|
|
|
item = self.model().itemFromIndex(idx) |
|
|
|
item = self.item_from_index(idx) |
|
|
|
if item: |
|
|
|
return item.data(Qt.UserRole) |
|
|
|
|
|
|
|
def item_from_index(self, idx: QModelIndex) -> Optional[QStandardItem]: |
|
|
|
model = self.model() |
|
|
|
if isinstance(model, QSortFilterProxyModel): |
|
|
|
idx = model.mapToSource(idx) |
|
|
|
return model.sourceModel().itemFromIndex(idx) |
|
|
|
else: |
|
|
|
return model.itemFromIndex(idx) |
|
|
|
|
|
|
|
def original_model(self) -> QAbstractItemModel: |
|
|
|
model = self.model() |
|
|
|
if isinstance(model, QSortFilterProxyModel): |
|
|
|
return model.sourceModel() |
|
|
|
else: |
|
|
|
return model |
|
|
|
|
|
|
|
def set_current_idx(self, set_current: QPersistentModelIndex): |
|
|
|
if set_current: |
|
|
|
assert isinstance(set_current, QPersistentModelIndex) |
|
|
@ -551,8 +571,7 @@ class MyTreeView(QTreeView): |
|
|
|
if not isinstance(headers, dict): # convert to dict |
|
|
|
headers = dict(enumerate(headers)) |
|
|
|
col_names = [headers[col_idx] for col_idx in sorted(headers.keys())] |
|
|
|
model = self.model() |
|
|
|
model.setHorizontalHeaderLabels(col_names) |
|
|
|
self.original_model().setHorizontalHeaderLabels(col_names) |
|
|
|
self.header().setStretchLastSection(False) |
|
|
|
for col_idx in headers: |
|
|
|
sm = QHeaderView.Stretch if col_idx == self.stretch_column else QHeaderView.ResizeToContents |
|
|
@ -592,10 +611,9 @@ class MyTreeView(QTreeView): |
|
|
|
""" |
|
|
|
return False |
|
|
|
|
|
|
|
def text_txid_from_coordinate(self, row_num, column): |
|
|
|
assert not isinstance(self.model(), QSortFilterProxyModel) |
|
|
|
def get_text_and_userrole_from_coordinate(self, row_num, column): |
|
|
|
idx = self.model().index(row_num, column) |
|
|
|
item = self.model().itemFromIndex(idx) |
|
|
|
item = self.item_from_index(idx) |
|
|
|
user_role = item.data(Qt.UserRole) |
|
|
|
return item.text(), user_role |
|
|
|
|
|
|
@ -610,7 +628,7 @@ class MyTreeView(QTreeView): |
|
|
|
self.setRowHidden(row_num, QModelIndex(), False) |
|
|
|
return |
|
|
|
for column in self.filter_columns: |
|
|
|
txt, _ = self.text_txid_from_coordinate(row_num, column) |
|
|
|
txt, _ = self.get_text_and_userrole_from_coordinate(row_num, column) |
|
|
|
txt = txt.lower() |
|
|
|
if self.current_filter in txt: |
|
|
|
# the filter matched, but the date filter might apply |
|
|
@ -664,8 +682,8 @@ class MyTreeView(QTreeView): |
|
|
|
def add_copy_menu(self, menu: QMenu, idx) -> QMenu: |
|
|
|
cc = menu.addMenu(_("Copy")) |
|
|
|
for column in self.Columns: |
|
|
|
column_title = self.model().horizontalHeaderItem(column).text() |
|
|
|
item_col = self.model().itemFromIndex(idx.sibling(idx.row(), column)) |
|
|
|
column_title = self.original_model().horizontalHeaderItem(column).text() |
|
|
|
item_col = self.item_from_index(idx.sibling(idx.row(), column)) |
|
|
|
clipboard_data = item_col.data(self.ROLE_CLIPBOARD_DATA) |
|
|
|
if clipboard_data is None: |
|
|
|
clipboard_data = item_col.text().strip() |
|
|
@ -692,6 +710,26 @@ class MyTreeView(QTreeView): |
|
|
|
return defer |
|
|
|
|
|
|
|
|
|
|
|
class MySortModel(QSortFilterProxyModel): |
|
|
|
def __init__(self, parent, *, sort_role): |
|
|
|
super().__init__(parent) |
|
|
|
self._sort_role = sort_role |
|
|
|
|
|
|
|
def lessThan(self, source_left: QModelIndex, source_right: QModelIndex): |
|
|
|
item1 = self.sourceModel().itemFromIndex(source_left) |
|
|
|
item2 = self.sourceModel().itemFromIndex(source_right) |
|
|
|
data1 = item1.data(self._sort_role) |
|
|
|
data2 = item2.data(self._sort_role) |
|
|
|
if data1 is not None and data2 is not None: |
|
|
|
return data1 < data2 |
|
|
|
v1 = item1.text() |
|
|
|
v2 = item2.text() |
|
|
|
try: |
|
|
|
return Decimal(v1) < Decimal(v2) |
|
|
|
except: |
|
|
|
return v1 < v2 |
|
|
|
|
|
|
|
|
|
|
|
class ButtonsWidget(QWidget): |
|
|
|
|
|
|
|
def __init__(self): |
|
|
|