1 from PyQt4.QtCore import *
2 from PyQt4.QtGui import *
9 def IconButton(filename, parent=None):
10 pixmap = QPixmap(filename)
12 return QPushButton(icon, "", parent)
16 def __init__(self, wallet):
18 self.app = QApplication(sys.argv)
19 with open("data/style.css") as style_file:
20 self.app.setStyleSheet(style_file.read())
23 actuator = MiniActuator(self.wallet)
24 mini = MiniWindow(actuator)
25 driver = MiniDriver(self.wallet, mini)
26 sys.exit(self.app.exec_())
28 class MiniWindow(QDialog):
30 def __init__(self, actuator):
31 super(MiniWindow, self).__init__()
33 self.actuator = actuator
35 accounts_button = IconButton("data/icons/accounts.png")
36 accounts_button.setObjectName("accounts_button")
38 self.accounts_selector = QMenu()
39 accounts_button.setMenu(self.accounts_selector)
41 interact_button = IconButton("data/icons/interact.png")
42 interact_button.setObjectName("interact_button")
45 report_action = app_menu.addAction(_("&Report Bug"))
46 about_action = app_menu.addAction(_("&About Electrum"))
47 app_menu.addSeparator()
48 quit_action = app_menu.addAction(_("&Quit"))
49 interact_button.setMenu(app_menu)
51 self.connect(report_action, SIGNAL("triggered()"),
53 self.connect(about_action, SIGNAL("triggered()"), self.show_about)
54 self.connect(quit_action, SIGNAL("triggered()"), self.close)
56 expand_button = IconButton("data/icons/expand.png")
57 expand_button.setObjectName("expand_button")
59 self.balance_label = BalanceLabel()
60 self.balance_label.setObjectName("balance_label")
62 copy_button = QPushButton(_("&Copy Address"))
63 copy_button.setObjectName("copy_button")
64 copy_button.setDefault(True)
65 self.connect(copy_button, SIGNAL("clicked()"),
66 self.actuator.copy_address)
69 self.address_input = TextedLineEdit(_("Enter a Bitcoin address..."))
70 self.address_input.setObjectName("address_input")
71 self.connect(self.address_input, SIGNAL("textChanged(QString)"),
72 self.address_field_changed)
73 metrics = QFontMetrics(qApp.font())
74 self.address_input.setMinimumWidth(
75 metrics.width("1E4vM9q25xsyDwWwdqHUWnwshdWC9PykmL"))
77 self.valid_address = QCheckBox()
78 self.valid_address.setObjectName("valid_address")
79 self.valid_address.setEnabled(False)
80 self.valid_address.setChecked(False)
82 address_layout = QHBoxLayout()
83 address_layout.addWidget(self.address_input)
84 address_layout.addWidget(self.valid_address)
86 self.amount_input = TextedLineEdit(_("... and amount"))
87 self.amount_input.setObjectName("amount_input")
88 # This is changed according to the user's displayed balance
89 self.amount_validator = QDoubleValidator(self.amount_input)
90 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
91 self.amount_validator.setRange(0, 0)
92 self.amount_validator.setDecimals(2)
93 self.amount_input.setValidator(self.amount_validator)
95 amount_layout = QHBoxLayout()
96 amount_layout.addWidget(self.amount_input)
97 amount_layout.addStretch()
99 send_button = QPushButton(_("&Send"))
100 send_button.setObjectName("send_button")
101 self.connect(send_button, SIGNAL("clicked()"), self.send)
103 main_layout = QGridLayout(self)
104 main_layout.addWidget(accounts_button, 0, 0)
105 main_layout.addWidget(interact_button, 1, 0)
106 main_layout.addWidget(expand_button, 2, 0)
108 main_layout.addWidget(self.balance_label, 0, 1)
109 main_layout.addWidget(copy_button, 0, 2)
111 main_layout.addLayout(address_layout, 1, 1, 1, -1)
113 main_layout.addLayout(amount_layout, 2, 1)
114 main_layout.addWidget(send_button, 2, 2)
116 self.setWindowTitle("Electrum")
117 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
118 self.layout().setSizeConstraint(QLayout.SetFixedSize)
119 self.setObjectName("main_window")
122 def closeEvent(self, event):
123 super(MiniWindow, self).closeEvent(event)
129 def deactivate(self):
132 def set_balances(self, btc_balance, quote_balance, quote_currency):
133 self.balance_label.set_balances( \
134 btc_balance, quote_balance, quote_currency)
135 self.amount_validator.setRange(0, btc_balance)
136 main_account_info = \
137 "Checking - %s BTC (%s %s)" % (btc_balance,
138 quote_balance, quote_currency)
139 self.setWindowTitle("Electrum - %s" % main_account_info)
140 self.accounts_selector.clear()
141 self.accounts_selector.addAction("%s" % main_account_info)
144 self.actuator.send(self.address_input.text(),
145 self.amount_input.text(), self)
147 def address_field_changed(self, address):
148 if self.actuator.is_valid(address):
149 self.valid_address.setChecked(True)
151 self.valid_address.setChecked(False)
153 def show_about(self):
154 QMessageBox.about(self, "Electrum",
155 "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.")
157 def show_report_bug(self):
158 QMessageBox.information(self, "Electrum - Reporting Bugs",
159 "Email bug reports to %s@%s.net" % ("genjix", "riseup"))
161 class BalanceLabel(QLabel):
163 def __init__(self, parent=None):
164 super(QLabel, self).__init__("Connecting...", parent)
166 def set_balances(self, btc_balance, quote_balance, quote_currency):
167 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)
168 self.setText(label_text)
170 class TextedLineEdit(QLineEdit):
172 def __init__(self, inactive_text, parent=None):
173 super(QLineEdit, self).__init__(parent)
174 self.inactive_text = inactive_text
175 self.become_inactive()
177 def mousePressEvent(self, event):
178 if self.isReadOnly():
180 QLineEdit.mousePressEvent(self, event)
182 def focusOutEvent(self, event):
183 if self.text() == "":
184 self.become_inactive()
185 QLineEdit.focusOutEvent(self, event)
187 def focusInEvent(self, event):
188 if self.isReadOnly():
190 QLineEdit.focusInEvent(self, event)
192 def become_inactive(self):
193 self.setText(self.inactive_text)
194 self.setReadOnly(True)
195 self.recompute_style()
197 def become_active(self):
199 self.setReadOnly(False)
200 self.recompute_style()
202 def recompute_style(self):
203 qApp.style().unpolish(self)
204 qApp.style().polish(self)
205 # also possible but more expensive:
206 #qApp.setStyleSheet(qApp.styleSheet())
210 def __init__(self, wallet):
213 def copy_address(self):
214 addrs = [addr for addr in self.wallet.all_addresses()
215 if not self.wallet.is_change(addr)]
216 qApp.clipboard().setText(random.choice(addrs))
218 def send(self, address, amount, parent_window):
219 recipient = unicode(address).strip()
222 match1 = re.match(ALIAS_REGEXP, r)
223 # label or alias, with address in brackets
224 match2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
228 self.wallet.get_alias(recipient, True,
229 self.show_message, self.question)
233 dest_address = match2.group(2)
235 dest_address = recipient
237 if not self.wallet.is_valid(dest_address):
238 QMessageBox.warning(parent_window, _('Error'),
239 _('Invalid Bitcoin Address') + ':\n' + dest_address, _('OK'))
242 convert_amount = lambda amount: \
243 int(decimal.Decimal(unicode(amount)) * 100000000)
245 amount = convert_amount(amount)
247 if self.wallet.use_encryption:
248 password = self.password_dialog()
255 tx = self.wallet.mktx(dest_address, amount, "", password, fee)
256 except BaseException, e:
257 self.show_message(str(e))
260 status, msg = self.wallet.sendtx( tx )
262 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
264 self.update_contacts_tab()
266 QMessageBox.warning(self, _('Error'), msg, _('OK'))
268 def is_valid(self, address):
269 return self.wallet.is_valid(address)
271 class MiniDriver(QObject):
278 def __init__(self, wallet, window):
279 super(QObject, self).__init__()
284 self.wallet.gui_callback = self.update_callback
289 self.connect(self, SIGNAL("updatesignal()"), self.update)
291 # This is a hack to workaround that Qt does not like changing the
292 # window properties from this other thread before the runloop has
294 def update_callback(self):
295 self.emit(SIGNAL("updatesignal()"))
298 if not self.wallet.interface:
300 elif not self.wallet.interface.is_connected:
302 elif not self.wallet.blocks == -1:
304 elif not self.wallet.is_up_to_date:
309 if self.wallet.up_to_date:
310 self.update_balance()
312 def initializing(self):
313 if self.state == self.INITIALIZING:
315 self.state = self.INITIALIZING
316 self.window.deactivate()
318 def connecting(self):
319 if self.state == self.CONNECTING:
321 self.state = self.CONNECTING
322 self.window.deactivate()
324 def synchronizing(self):
325 if self.state == self.SYNCHRONIZING:
327 self.state = self.SYNCHRONIZING
328 self.window.deactivate()
331 if self.state == self.READY:
333 self.state = self.READY
334 self.window.activate()
336 def update_balance(self):
337 conf_balance, unconf_balance = self.wallet.get_balance()
338 balance = conf_balance if unconf_balance is None else unconf_balance
339 self.window.set_balances(balance, balance * 6, 'EUR')
341 if __name__ == "__main__":
342 app = QApplication(sys.argv)
343 with open("data/style.css") as style_file:
344 app.setStyleSheet(style_file.read())
346 sys.exit(app.exec_())