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