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