894cbb31fef4017bb45b8bedec32d3321919ad97
[electrum-nvc.git] / lib / gui_lite.py
1 from PyQt4.QtCore import *
2 from PyQt4.QtGui import *
3 from i18n import _
4 import decimal
5 import random
6 import re
7 import sys
8
9 def IconButton(filename, parent=None):
10     pixmap = QPixmap(filename)
11     icon = QIcon(pixmap)
12     return QPushButton(icon, "", parent)
13
14 class ElectrumGui:
15
16     def __init__(self, wallet):
17         self.wallet = wallet
18         self.app = QApplication(sys.argv)
19         with open("data/style.css") as style_file:
20             self.app.setStyleSheet(style_file.read())
21
22     def main(self, url):
23         actuator = MiniActuator(self.wallet)
24         mini = MiniWindow(actuator)
25         driver = MiniDriver(self.wallet, mini)
26         sys.exit(self.app.exec_())
27
28 class MiniWindow(QDialog):
29
30     def __init__(self, actuator):
31         super(MiniWindow, self).__init__()
32
33         self.actuator = actuator
34
35         accounts_button = IconButton("data/icons/accounts.png")
36         accounts_button.setObjectName("accounts_button")
37
38         self.accounts_selector = QMenu()
39         accounts_button.setMenu(self.accounts_selector)
40
41         interact_button = IconButton("data/icons/interact.png")
42         interact_button.setObjectName("interact_button")
43
44         app_menu = QMenu()
45         interact_button.setMenu(app_menu)
46
47         expand_button = IconButton("data/icons/expand.png")
48         expand_button.setObjectName("expand_button")
49
50         self.balance_label = BalanceLabel()
51         self.balance_label.setObjectName("balance_label")
52
53         copy_button = QPushButton(_("&Copy Address"))
54         copy_button.setObjectName("copy_button")
55         copy_button.setDefault(True)
56         self.connect(copy_button, SIGNAL("clicked()"),
57                      self.actuator.copy_address)
58
59         # Use QCompleter
60         self.address_input = TextedLineEdit(_("Enter a Bitcoin address..."))
61         self.address_input.setObjectName("address_input")
62         self.connect(self.address_input, SIGNAL("textChanged(QString)"),
63                      self.address_field_changed)
64         metrics = QFontMetrics(qApp.font())
65         self.address_input.setMinimumWidth(
66             metrics.width("1E4vM9q25xsyDwWwdqHUWnwshdWC9PykmL"))
67
68         self.valid_address = QCheckBox()
69         self.valid_address.setObjectName("valid_address")
70         self.valid_address.setEnabled(False)
71         self.valid_address.setChecked(False)
72
73         address_layout = QHBoxLayout()
74         address_layout.addWidget(self.address_input)
75         address_layout.addWidget(self.valid_address)
76
77         self.amount_input = TextedLineEdit(_("... and amount"))
78         self.amount_input.setObjectName("amount_input")
79         # This is changed according to the user's displayed balance
80         self.amount_validator = QDoubleValidator(self.amount_input)
81         self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
82         self.amount_validator.setRange(0, 0)
83         self.amount_validator.setDecimals(2)
84         self.amount_input.setValidator(self.amount_validator)
85
86         amount_layout = QHBoxLayout()
87         amount_layout.addWidget(self.amount_input)
88         amount_layout.addStretch()
89
90         send_button = QPushButton(_("&Send"))
91         send_button.setObjectName("send_button")
92         self.connect(send_button, SIGNAL("clicked()"), self.send)
93
94         main_layout = QGridLayout(self)
95         main_layout.addWidget(accounts_button, 0, 0)
96         main_layout.addWidget(interact_button, 1, 0)
97         main_layout.addWidget(expand_button, 2, 0)
98
99         main_layout.addWidget(self.balance_label, 0, 1)
100         main_layout.addWidget(copy_button, 0, 2)
101
102         main_layout.addLayout(address_layout, 1, 1, 1, -1)
103
104         main_layout.addLayout(amount_layout, 2, 1)
105         main_layout.addWidget(send_button, 2, 2)
106
107         self.setWindowTitle("Electrum")
108         self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
109         self.layout().setSizeConstraint(QLayout.SetFixedSize)
110         self.setObjectName("main_window")
111         self.show()
112
113     def closeEvent(self, event):
114         super(MiniWindow, self).closeEvent(event)
115         qApp.quit()
116
117     def activate(self):
118         pass
119
120     def deactivate(self):
121         pass
122
123     def set_balances(self, btc_balance, quote_balance, quote_currency):
124         self.balance_label.set_balances( \
125             btc_balance, quote_balance, quote_currency)
126         self.amount_validator.setRange(0, btc_balance)
127         main_account_info = \
128             "Checking - %s BTC (%s %s)" % (btc_balance,
129                                            quote_balance, quote_currency)
130         self.setWindowTitle("Electrum - %s" % main_account_info)
131         self.accounts_selector.clear()
132         self.accounts_selector.addAction("%s" % main_account_info)
133
134     def send(self):
135         self.actuator.send(self.address_input.text(),
136                            self.amount_input.text(), self)
137
138     def address_field_changed(self, address):
139         if self.actuator.is_valid(address):
140             self.valid_address.setChecked(True)
141         else:
142             self.valid_address.setChecked(False)
143
144 class BalanceLabel(QLabel):
145
146     def __init__(self, parent=None):
147         super(QLabel, self).__init__("Connecting...", parent)
148
149     def set_balances(self, btc_balance, quote_balance, quote_currency):
150         label_text = "<span style='font-size: 16pt'>%s</span> <span style='font-size: 10pt'>BTC</span> <span style='font-size: 10pt'>(%s %s)</span>" % (btc_balance, quote_balance, quote_currency)
151         self.setText(label_text)
152
153 class TextedLineEdit(QLineEdit):
154
155     def __init__(self, inactive_text, parent=None):
156         super(QLineEdit, self).__init__(parent)
157         self.inactive_text = inactive_text
158         self.become_inactive()
159
160     def mousePressEvent(self, event):
161         if self.isReadOnly():
162             self.become_active()
163         QLineEdit.mousePressEvent(self, event)
164
165     def focusOutEvent(self, event):
166         if self.text() == "":
167             self.become_inactive()
168         QLineEdit.focusOutEvent(self, event)
169
170     def focusInEvent(self, event):
171         if self.isReadOnly():
172             self.become_active()
173         QLineEdit.focusInEvent(self, event)
174
175     def become_inactive(self):
176         self.setText(self.inactive_text)
177         self.setReadOnly(True)
178         self.recompute_style()
179
180     def become_active(self):
181         self.setText("")
182         self.setReadOnly(False)
183         self.recompute_style()
184
185     def recompute_style(self):
186         qApp.style().unpolish(self)
187         qApp.style().polish(self)
188         # also possible but more expensive:
189         #qApp.setStyleSheet(qApp.styleSheet())
190
191 class MiniActuator:
192
193     def __init__(self, wallet):
194         self.wallet = wallet
195
196     def copy_address(self):
197         addrs = [addr for addr in self.wallet.all_addresses()
198                  if not self.wallet.is_change(addr)]
199         qApp.clipboard().setText(random.choice(addrs))
200
201     def send(self, address, amount, parent_window):
202         recipient = unicode(address).strip()
203
204         # alias
205         match1 = re.match(ALIAS_REGEXP, r)
206         # label or alias, with address in brackets
207         match2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
208         
209         if match1:
210             dest_address = \
211                 self.wallet.get_alias(recipient, True, 
212                                       self.show_message, self.question)
213             if not dest_address:
214                 return
215         elif match2:
216             dest_address = match2.group(2)
217         else:
218             dest_address = recipient
219
220         if not self.wallet.is_valid(dest_address):
221             QMessageBox.warning(parent_window, _('Error'), 
222                 _('Invalid Bitcoin Address') + ':\n' + dest_address, _('OK'))
223             return
224
225         convert_amount = lambda amount: \
226             int(decimal.Decimal(unicode(amount)) * 100000000)
227
228         amount = convert_amount(amount)
229
230         if self.wallet.use_encryption:
231             password = self.password_dialog()
232             if not password:
233                 return
234         else:
235             password = None
236
237         try:
238             tx = self.wallet.mktx(dest_address, amount, "", password, fee)
239         except BaseException, e:
240             self.show_message(str(e))
241             return
242             
243         status, msg = self.wallet.sendtx( tx )
244         if status:
245             QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
246             self.do_clear()
247             self.update_contacts_tab()
248         else:
249             QMessageBox.warning(self, _('Error'), msg, _('OK'))
250
251     def is_valid(self, address):
252         return self.wallet.is_valid(address)
253
254 class MiniDriver(QObject):
255
256     INITIALIZING = 0
257     CONNECTING = 1
258     SYNCHRONIZING = 2
259     READY = 3
260
261     def __init__(self, wallet, window):
262         super(QObject, self).__init__()
263
264         self.wallet = wallet
265         self.window = window
266
267         self.wallet.gui_callback = self.update_callback
268
269         self.state = None
270
271         self.initializing()
272         self.connect(self, SIGNAL("updatesignal()"), self.update)
273
274     # This is a hack to workaround that Qt does not like changing the
275     # window properties from this other thread before the runloop has
276     # been called from.
277     def update_callback(self):
278         self.emit(SIGNAL("updatesignal()"))
279
280     def update(self):
281         if not self.wallet.interface:
282             self.initializing()
283         elif not self.wallet.interface.is_connected:
284             self.connecting()
285         elif not self.wallet.blocks == -1:
286             self.connecting()
287         elif not self.wallet.is_up_to_date:
288             self.synchronizing()
289         else:
290             self.ready()
291
292         if self.wallet.up_to_date:
293             self.update_balance()
294
295     def initializing(self):
296         if self.state == self.INITIALIZING:
297             return
298         self.state = self.INITIALIZING
299         self.window.deactivate()
300
301     def connecting(self):
302         if self.state == self.CONNECTING:
303             return
304         self.state = self.CONNECTING
305         self.window.deactivate()
306
307     def synchronizing(self):
308         if self.state == self.SYNCHRONIZING:
309             return
310         self.state = self.SYNCHRONIZING
311         self.window.deactivate()
312
313     def ready(self):
314         if self.state == self.READY:
315             return
316         self.state = self.READY
317         self.window.activate()
318
319     def update_balance(self):
320         conf_balance, unconf_balance = self.wallet.get_balance()
321         balance = conf_balance if unconf_balance is None else unconf_balance
322         self.window.set_balances(balance, balance * 6, 'EUR')
323
324 if __name__ == "__main__":
325     app = QApplication(sys.argv)
326     with open("data/style.css") as style_file:
327         app.setStyleSheet(style_file.read())
328     mini = MiniWindow()
329     sys.exit(app.exec_())
330