mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 17:47:31 +00:00
This fixes some bugs in contact editing: - a changed address is now checked for validity. Shows error if invalid and restores prior value - the changes are saved, before they were dropped - adding a new contact switches to the contacts tab, it used to switch to the address tab As an enhancement, the contact name, as well as its address, can be edited and updated. Finally, the platform edit key can also be used to edit, in adition to double-clicking. This is typically the F2 key.
432 lines
14 KiB
Python
432 lines
14 KiB
Python
from electrum.i18n import _
|
|
from PyQt4.QtGui import *
|
|
from PyQt4.QtCore import *
|
|
import os.path
|
|
import time
|
|
import traceback
|
|
import sys
|
|
import threading
|
|
import platform
|
|
|
|
if platform.system() == 'Windows':
|
|
MONOSPACE_FONT = 'Lucida Console'
|
|
elif platform.system() == 'Darwin':
|
|
MONOSPACE_FONT = 'Monaco'
|
|
else:
|
|
MONOSPACE_FONT = 'monospace'
|
|
|
|
GREEN_BG = "QWidget {background-color:#80ff80;}"
|
|
RED_BG = "QWidget {background-color:#ffcccc;}"
|
|
RED_FG = "QWidget {color:red;}"
|
|
BLUE_FG = "QWidget {color:blue;}"
|
|
BLACK_FG = "QWidget {color:black;}"
|
|
|
|
|
|
class WaitingDialog(QThread):
|
|
def __init__(self, parent, message, run_task, on_success=None, on_complete=None):
|
|
QThread.__init__(self)
|
|
self.parent = parent
|
|
self.d = QDialog(parent)
|
|
self.d.setWindowTitle('Please wait')
|
|
l = QLabel(message)
|
|
vbox = QVBoxLayout(self.d)
|
|
vbox.addWidget(l)
|
|
self.run_task = run_task
|
|
self.on_success = on_success
|
|
self.on_complete = on_complete
|
|
self.d.connect(self.d, SIGNAL('done'), self.close)
|
|
self.d.show()
|
|
|
|
def run(self):
|
|
self.error = None
|
|
try:
|
|
self.result = self.run_task()
|
|
except BaseException as e:
|
|
traceback.print_exc(file=sys.stdout)
|
|
self.error = str(e)
|
|
self.d.emit(SIGNAL('done'))
|
|
|
|
def close(self):
|
|
self.d.accept()
|
|
if self.error:
|
|
QMessageBox.warning(self.parent, _('Error'), self.error, _('OK'))
|
|
else:
|
|
if self.on_success:
|
|
if type(self.result) is not tuple:
|
|
self.result = (self.result,)
|
|
self.on_success(*self.result)
|
|
|
|
if self.on_complete:
|
|
self.on_complete()
|
|
|
|
|
|
class Timer(QThread):
|
|
def run(self):
|
|
while True:
|
|
self.emit(SIGNAL('timersignal'))
|
|
time.sleep(0.5)
|
|
|
|
|
|
class EnterButton(QPushButton):
|
|
def __init__(self, text, func):
|
|
QPushButton.__init__(self, text)
|
|
self.func = func
|
|
self.clicked.connect(func)
|
|
|
|
def keyPressEvent(self, e):
|
|
if e.key() == Qt.Key_Return:
|
|
apply(self.func,())
|
|
|
|
|
|
class ThreadedButton(QPushButton):
|
|
def __init__(self, text, func, on_success=None, before=None):
|
|
QPushButton.__init__(self, text)
|
|
self.before = before
|
|
self.run_task = func
|
|
self.on_success = on_success
|
|
self.clicked.connect(self.do_exec)
|
|
self.connect(self, SIGNAL('done'), self.done)
|
|
self.connect(self, SIGNAL('error'), self.on_error)
|
|
|
|
def done(self):
|
|
if self.on_success:
|
|
self.on_success()
|
|
self.setEnabled(True)
|
|
|
|
def on_error(self):
|
|
QMessageBox.information(None, _("Error"), self.error)
|
|
self.setEnabled(True)
|
|
|
|
def do_func(self):
|
|
self.setEnabled(False)
|
|
try:
|
|
self.result = self.run_task()
|
|
except BaseException as e:
|
|
traceback.print_exc(file=sys.stdout)
|
|
self.error = str(e.message)
|
|
self.emit(SIGNAL('error'))
|
|
return
|
|
self.emit(SIGNAL('done'))
|
|
|
|
def do_exec(self):
|
|
if self.before:
|
|
self.before()
|
|
t = threading.Thread(target=self.do_func)
|
|
t.setDaemon(True)
|
|
t.start()
|
|
|
|
|
|
class HelpLabel(QLabel):
|
|
|
|
def __init__(self, text, help_text):
|
|
QLabel.__init__(self, text)
|
|
self.help_text = help_text
|
|
self.app = QCoreApplication.instance()
|
|
self.font = QFont()
|
|
|
|
def mouseReleaseEvent(self, x):
|
|
QMessageBox.information(self, 'Help', self.help_text, 'OK')
|
|
|
|
def enterEvent(self, event):
|
|
self.font.setUnderline(True)
|
|
self.setFont(self.font)
|
|
self.app.setOverrideCursor(QCursor(Qt.PointingHandCursor))
|
|
return QLabel.enterEvent(self, event)
|
|
|
|
def leaveEvent(self, event):
|
|
self.font.setUnderline(False)
|
|
self.setFont(self.font)
|
|
self.app.setOverrideCursor(QCursor(Qt.ArrowCursor))
|
|
return QLabel.leaveEvent(self, event)
|
|
|
|
|
|
class HelpButton(QPushButton):
|
|
def __init__(self, text):
|
|
QPushButton.__init__(self, '?')
|
|
self.help_text = text
|
|
self.setFocusPolicy(Qt.NoFocus)
|
|
self.setFixedWidth(20)
|
|
self.clicked.connect(self.onclick)
|
|
|
|
def onclick(self):
|
|
QMessageBox.information(self, 'Help', self.help_text, 'OK')
|
|
|
|
class Buttons(QHBoxLayout):
|
|
def __init__(self, *buttons):
|
|
QHBoxLayout.__init__(self)
|
|
self.addStretch(1)
|
|
for b in buttons:
|
|
self.addWidget(b)
|
|
|
|
class CloseButton(QPushButton):
|
|
def __init__(self, dialog):
|
|
QPushButton.__init__(self, _("Close"))
|
|
self.clicked.connect(dialog.close)
|
|
self.setDefault(True)
|
|
|
|
class CopyButton(QPushButton):
|
|
def __init__(self, text_getter, app):
|
|
QPushButton.__init__(self, _("Copy"))
|
|
self.clicked.connect(lambda: app.clipboard().setText(text_getter()))
|
|
|
|
class CopyCloseButton(QPushButton):
|
|
def __init__(self, text_getter, app, dialog):
|
|
QPushButton.__init__(self, _("Copy and Close"))
|
|
self.clicked.connect(lambda: app.clipboard().setText(text_getter()))
|
|
self.clicked.connect(dialog.close)
|
|
self.setDefault(True)
|
|
|
|
class OkButton(QPushButton):
|
|
def __init__(self, dialog, label=None):
|
|
QPushButton.__init__(self, label or _("OK"))
|
|
self.clicked.connect(dialog.accept)
|
|
self.setDefault(True)
|
|
|
|
class CancelButton(QPushButton):
|
|
def __init__(self, dialog, label=None):
|
|
QPushButton.__init__(self, label or _("Cancel"))
|
|
self.clicked.connect(dialog.reject)
|
|
|
|
|
|
def line_dialog(parent, title, label, ok_label, default=None):
|
|
dialog = QDialog(parent)
|
|
dialog.setMinimumWidth(500)
|
|
dialog.setWindowTitle(title)
|
|
dialog.setModal(1)
|
|
l = QVBoxLayout()
|
|
dialog.setLayout(l)
|
|
l.addWidget(QLabel(label))
|
|
txt = QLineEdit()
|
|
if default:
|
|
txt.setText(default)
|
|
l.addWidget(txt)
|
|
l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
|
|
if dialog.exec_():
|
|
return unicode(txt.text())
|
|
|
|
def text_dialog(parent, title, label, ok_label, default=None):
|
|
from qrtextedit import ScanQRTextEdit
|
|
dialog = QDialog(parent)
|
|
dialog.setMinimumWidth(500)
|
|
dialog.setWindowTitle(title)
|
|
dialog.setModal(1)
|
|
l = QVBoxLayout()
|
|
dialog.setLayout(l)
|
|
l.addWidget(QLabel(label))
|
|
txt = ScanQRTextEdit()
|
|
if default:
|
|
txt.setText(default)
|
|
l.addWidget(txt)
|
|
l.addLayout(Buttons(CancelButton(dialog), OkButton(dialog, ok_label)))
|
|
if dialog.exec_():
|
|
return unicode(txt.toPlainText())
|
|
|
|
def question(msg):
|
|
return QMessageBox.question(None, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
|
|
|
|
def address_field(addresses):
|
|
hbox = QHBoxLayout()
|
|
address_e = QLineEdit()
|
|
if addresses:
|
|
address_e.setText(addresses[0])
|
|
def func():
|
|
i = addresses.index(str(address_e.text())) + 1
|
|
i = i % len(addresses)
|
|
address_e.setText(addresses[i])
|
|
button = QPushButton(_('Address'))
|
|
button.clicked.connect(func)
|
|
hbox.addWidget(button)
|
|
hbox.addWidget(address_e)
|
|
return hbox, address_e
|
|
|
|
|
|
def filename_field(parent, config, defaultname, select_msg):
|
|
|
|
vbox = QVBoxLayout()
|
|
vbox.addWidget(QLabel(_("Format")))
|
|
gb = QGroupBox("format", parent)
|
|
b1 = QRadioButton(gb)
|
|
b1.setText(_("CSV"))
|
|
b1.setChecked(True)
|
|
b2 = QRadioButton(gb)
|
|
b2.setText(_("json"))
|
|
vbox.addWidget(b1)
|
|
vbox.addWidget(b2)
|
|
|
|
hbox = QHBoxLayout()
|
|
|
|
directory = config.get('io_dir', unicode(os.path.expanduser('~')))
|
|
path = os.path.join( directory, defaultname )
|
|
filename_e = QLineEdit()
|
|
filename_e.setText(path)
|
|
|
|
def func():
|
|
text = unicode(filename_e.text())
|
|
_filter = "*.csv" if text.endswith(".csv") else "*.json" if text.endswith(".json") else None
|
|
p = unicode( QFileDialog.getSaveFileName(None, select_msg, text, _filter))
|
|
if p:
|
|
filename_e.setText(p)
|
|
|
|
button = QPushButton(_('File'))
|
|
button.clicked.connect(func)
|
|
hbox.addWidget(button)
|
|
hbox.addWidget(filename_e)
|
|
vbox.addLayout(hbox)
|
|
|
|
def set_csv(v):
|
|
text = unicode(filename_e.text())
|
|
text = text.replace(".json",".csv") if v else text.replace(".csv",".json")
|
|
filename_e.setText(text)
|
|
|
|
b1.clicked.connect(lambda: set_csv(True))
|
|
b2.clicked.connect(lambda: set_csv(False))
|
|
|
|
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):
|
|
def createEditor(self, parent, option, index):
|
|
if index.column() not in self.parent().editable_columns:
|
|
return None
|
|
self.parent().prior_text = unicode(index.data().toString())
|
|
return QStyledItemDelegate.createEditor(self, parent, option, index)
|
|
|
|
class MyTreeWidget(QTreeWidget):
|
|
|
|
def __init__(self, parent, create_menu, headers, stretch_column=None,
|
|
editable_columns=None):
|
|
QTreeWidget.__init__(self, parent)
|
|
self.parent = parent
|
|
self.setColumnCount(len(headers))
|
|
self.setHeaderLabels(headers)
|
|
self.header().setStretchLastSection(False)
|
|
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
self.itemActivated.connect(self.on_activated)
|
|
self.customContextMenuRequested.connect(create_menu)
|
|
# extend the syntax for consistency
|
|
self.addChild = self.addTopLevelItem
|
|
self.insertChild = self.insertTopLevelItem
|
|
|
|
# Control which columns are editable
|
|
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)
|
|
|
|
# stretch
|
|
for i in range(len(headers)):
|
|
self.header().setResizeMode(i, QHeaderView.Stretch if i == stretch_column else QHeaderView.ResizeToContents)
|
|
self.setSortingEnabled(True)
|
|
|
|
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
|
|
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))
|
|
|
|
def item_changed(self, item, column):
|
|
'''Called only when the text actually changes'''
|
|
self.item_edited(item, column, self.prior_text)
|
|
|
|
def item_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))
|
|
self.parent.wallet.set_label(key, text)
|
|
if text:
|
|
item.setForeground(column, QBrush(QColor('black')))
|
|
else:
|
|
text = self.parent.wallet.get_default_label(key)
|
|
item.setText(column, text)
|
|
item.setForeground(column, QBrush(QColor('gray')))
|
|
self.parent.update_history_tab()
|
|
self.parent.update_completions()
|
|
|
|
def get_leaves(self, root):
|
|
child_count = root.childCount()
|
|
if child_count == 0:
|
|
yield root
|
|
for i in range(child_count):
|
|
item = root.child(i)
|
|
for x in self.get_leaves(item):
|
|
yield x
|
|
|
|
def filter(self, p, columns):
|
|
p = unicode(p).lower()
|
|
for item in self.get_leaves(self.invisibleRootItem()):
|
|
item.setHidden(all([unicode(item.text(column)).lower().find(p) == -1
|
|
for column in columns]))
|
|
|
|
|
|
class ButtonsWidget(QWidget):
|
|
|
|
def __init__(self):
|
|
super(QWidget, self).__init__()
|
|
self.buttons = []
|
|
|
|
def resizeButtons(self):
|
|
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
|
|
x = self.rect().right() - frameWidth
|
|
y = self.rect().bottom() - frameWidth
|
|
for button in self.buttons:
|
|
sz = button.sizeHint()
|
|
x -= sz.width()
|
|
button.move(x, y - sz.height())
|
|
|
|
def addButton(self, icon_name, on_click, tooltip):
|
|
button = QToolButton(self)
|
|
button.setIcon(QIcon(icon_name))
|
|
button.setStyleSheet("QToolButton { border: none; hover {border: 1px} pressed {border: 1px} padding: 0px; }")
|
|
button.setVisible(True)
|
|
button.setToolTip(tooltip)
|
|
button.clicked.connect(on_click)
|
|
self.buttons.append(button)
|
|
return button
|
|
|
|
def addCopyButton(self, app):
|
|
self.app = app
|
|
f = lambda: self.app.clipboard().setText(str(self.text()))
|
|
self.addButton(":icons/copy.png", f, _("Copy to Clipboard"))
|
|
|
|
class ButtonsLineEdit(QLineEdit, ButtonsWidget):
|
|
def __init__(self, text=None):
|
|
QLineEdit.__init__(self, text)
|
|
self.buttons = []
|
|
|
|
def resizeEvent(self, e):
|
|
o = QLineEdit.resizeEvent(self, e)
|
|
self.resizeButtons()
|
|
return o
|
|
|
|
class ButtonsTextEdit(QPlainTextEdit, ButtonsWidget):
|
|
def __init__(self, text=None):
|
|
QPlainTextEdit.__init__(self, text)
|
|
self.setText = self.setPlainText
|
|
self.text = self.toPlainText
|
|
self.buttons = []
|
|
|
|
def resizeEvent(self, e):
|
|
o = QPlainTextEdit.resizeEvent(self, e)
|
|
self.resizeButtons()
|
|
return o
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication([])
|
|
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done", _('OK')))
|
|
t.start()
|
|
app.exec_()
|