LBRY-Vault/gui/qt/util.py
Neil Booth 607664e663 Fix contact editing.
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.
2015-08-29 21:22:08 +09:00

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_()