3 # Let's do some dep checking and handle missing ones gracefully
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7 from PyQt4.Qt import Qt
8 import PyQt4.QtCore as QtCore
11 print "You need to have PyQT installed to run Electrum in graphical mode."
12 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
15 from decimal import Decimal as D
16 from electrum.util import get_resource_path as rsrc
17 from electrum.bitcoin import is_valid
18 from electrum.i18n import _
25 from electrum.wallet import Wallet, WalletStorage
28 import receiving_widget
29 from electrum import util
32 from electrum.version import ELECTRUM_VERSION as electrum_version
33 from electrum.util import format_satoshis, age
35 from main_window import ElectrumWindow
40 bitcoin = lambda v: v * 100000000
42 def IconButton(filename, parent=None):
43 pixmap = QPixmap(filename)
45 return QPushButton(icon, "", parent)
48 def resize_line_edit_width(line_edit, text_input):
49 metrics = QFontMetrics(qApp.font())
50 # Create an extra character to add some space on the end
52 line_edit.setMinimumWidth(metrics.width(text_input))
54 def load_theme_name(theme_path):
56 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
57 return name_cfg_file.read().rstrip("\n").strip()
62 def theme_dirs_from_prefix(prefix):
63 if not os.path.exists(prefix):
66 for potential_theme in os.listdir(prefix):
67 theme_full_path = os.path.join(prefix, potential_theme)
68 theme_css = os.path.join(theme_full_path, "style.css")
69 if not os.path.exists(theme_css):
71 theme_name = load_theme_name(theme_full_path)
72 if theme_name is None:
74 theme_paths[theme_name] = prefix, potential_theme
77 def load_theme_paths():
79 prefixes = (util.local_data_dir(), util.appdata_dir())
80 for prefix in prefixes:
81 theme_paths.update(theme_dirs_from_prefix(prefix))
87 class TransactionWindow(QDialog):
90 label = unicode(self.label_edit.text())
91 self.parent.wallet.labels[self.tx_id] = label
93 super(TransactionWindow, self).accept()
95 def __init__(self, transaction_id, parent):
96 super(TransactionWindow, self).__init__()
98 self.tx_id = str(transaction_id)
103 self.setWindowTitle(_("Transaction successfully sent"))
105 self.layout = QGridLayout(self)
106 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
107 self.layout.addWidget(QLabel(history_label))
109 self.label_edit = QLineEdit()
110 self.label_edit.setPlaceholderText(_("Transaction label"))
111 self.label_edit.setObjectName("label_input")
112 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
113 self.label_edit.setFocusPolicy(Qt.ClickFocus)
114 self.layout.addWidget(self.label_edit)
116 self.save_button = QPushButton(_("Save"))
117 self.layout.addWidget(self.save_button)
118 self.save_button.clicked.connect(self.set_label)
122 class MiniWindow(QDialog):
124 def __init__(self, actuator, expand_callback, config):
125 super(MiniWindow, self).__init__()
127 self.actuator = actuator
129 self.btc_balance = None
130 self.use_exchanges = ["Blockchain", "CoinDesk"]
131 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
132 self.actuator.set_configured_currency(self.set_quote_currency)
133 self.actuator.set_configured_exchange(self.set_exchange)
135 # Needed because price discovery is done in a different thread
136 # which needs to be sent back to this main one to update the GUI
137 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
139 self.balance_label = BalanceLabel(self.change_quote_currency, self)
140 self.balance_label.setObjectName("balance_label")
143 # Bitcoin address code
144 self.address_input = QLineEdit()
145 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
146 self.address_input.setObjectName("address_input")
148 self.address_input.setFocusPolicy(Qt.ClickFocus)
150 self.address_input.textChanged.connect(self.address_field_changed)
151 resize_line_edit_width(self.address_input,
152 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
154 self.address_completions = QStringListModel()
155 address_completer = QCompleter(self.address_input)
156 address_completer.setCaseSensitivity(False)
157 address_completer.setModel(self.address_completions)
158 self.address_input.setCompleter(address_completer)
160 address_layout = QHBoxLayout()
161 address_layout.addWidget(self.address_input)
163 self.amount_input = QLineEdit()
164 self.amount_input.setPlaceholderText(_("... and amount") + " (%s)"%self.actuator.g.base_unit())
165 self.amount_input.setObjectName("amount_input")
167 self.amount_input.setFocusPolicy(Qt.ClickFocus)
168 # This is changed according to the user's displayed balance
169 self.amount_validator = QDoubleValidator(self.amount_input)
170 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
171 self.amount_validator.setDecimals(8)
172 self.amount_input.setValidator(self.amount_validator)
174 # This removes the very ugly OSX highlighting, please leave this in :D
175 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
176 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
177 self.amount_input.textChanged.connect(self.amount_input_changed)
179 #if self.actuator.g.wallet.seed:
180 self.send_button = QPushButton(_("&Send"))
182 # self.send_button = QPushButton(_("&Create"))
184 self.send_button.setObjectName("send_button")
185 self.send_button.setDisabled(True);
186 self.send_button.clicked.connect(self.send)
188 # Creating the receive button
189 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
190 self.switch_button.setMaximumWidth(25)
191 self.switch_button.setFlat(True)
192 self.switch_button.clicked.connect(expand_callback)
194 main_layout = QGridLayout(self)
196 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
197 main_layout.addWidget(self.switch_button, 0, 3)
199 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
200 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
201 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
203 self.send_button.setMaximumWidth(125)
205 self.history_list = history_widget.HistoryWidget()
206 self.history_list.setObjectName("history")
207 self.history_list.hide()
208 self.history_list.setAlternatingRowColors(True)
210 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
212 self.receiving = receiving_widget.ReceivingWidget(self)
213 self.receiving.setObjectName("receiving")
215 # Add to the right side
216 self.receiving_box = QGroupBox(_("Select a receiving address"))
217 extra_layout = QGridLayout()
219 # Checkbox to filter used addresses
220 hide_used = QCheckBox(_('Hide used addresses'))
221 hide_used.setChecked(True)
222 hide_used.stateChanged.connect(self.receiving.toggle_used)
224 # Events for receiving addresses
225 self.receiving.clicked.connect(self.receiving.copy_address)
226 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
227 self.receiving.itemChanged.connect(self.receiving.update_label)
231 extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.') + '\n' + _('Double clicking the label will allow you to edit it.') ),0,0)
233 extra_layout.addWidget(self.receiving, 1,0)
234 extra_layout.addWidget(hide_used, 2,0)
235 extra_layout.setColumnMinimumWidth(0,200)
237 self.receiving_box.setLayout(extra_layout)
238 main_layout.addWidget(self.receiving_box,0,4,-1,3)
239 self.receiving_box.hide()
241 self.main_layout = main_layout
243 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
244 quit_shortcut.activated.connect(self.close)
245 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
246 close_shortcut.activated.connect(self.close)
248 g = self.config.get("winpos-lite",[4, 25, 351, 149])
249 self.setGeometry(g[0], g[1], g[2], g[3])
251 show_hist = self.config.get("gui_show_history",False)
252 self.show_history(show_hist)
253 show_hist = self.config.get("gui_show_receiving",False)
254 self.toggle_receiving_layout(show_hist)
256 self.setWindowIcon(QIcon(":icons/electrum.png"))
257 self.setWindowTitle("Electrum")
258 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
259 self.layout().setSizeConstraint(QLayout.SetFixedSize)
260 self.setObjectName("main_window")
263 def context_menu(self):
265 themes_menu = view_menu.addMenu(_("&Themes"))
266 selected_theme = self.actuator.selected_theme()
267 theme_group = QActionGroup(self)
268 for theme_name in self.actuator.theme_names():
269 theme_action = themes_menu.addAction(theme_name)
270 theme_action.setCheckable(True)
271 if selected_theme == theme_name:
272 theme_action.setChecked(True)
273 class SelectThemeFunctor:
274 def __init__(self, theme_name, toggle_theme):
275 self.theme_name = theme_name
276 self.toggle_theme = toggle_theme
277 def __call__(self, checked):
279 self.toggle_theme(self.theme_name)
280 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
281 theme_action.toggled.connect(delegate)
282 theme_group.addAction(theme_action)
283 view_menu.addSeparator()
285 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
286 show_receiving.setCheckable(True)
287 show_receiving.toggled.connect(self.toggle_receiving_layout)
288 show_receiving.setChecked(self.config.get("gui_show_receiving",False))
290 show_history = view_menu.addAction(_("Show History"))
291 show_history.setCheckable(True)
292 show_history.toggled.connect(self.show_history)
293 show_history.setChecked(self.config.get("gui_show_history",False))
299 def toggle_theme(self, theme_name):
300 self.actuator.change_theme(theme_name)
301 # Recompute style globally
302 qApp.style().unpolish(self)
303 qApp.style().polish(self)
305 def closeEvent(self, event):
307 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
308 self.actuator.g.closeEvent(event)
311 def set_payment_fields(self, dest_address, amount):
312 self.address_input.setText(dest_address)
313 self.address_field_changed(dest_address)
314 self.amount_input.setText(amount)
319 def deactivate(self):
322 def set_exchange(self, use_exchange):
323 if use_exchange not in self.use_exchanges:
325 self.use_exchanges.remove(use_exchange)
326 self.use_exchanges.insert(0, use_exchange)
327 self.refresh_balance()
329 def set_quote_currency(self, currency):
330 """Set and display the fiat currency country."""
331 if currency not in self.quote_currencies:
333 self.quote_currencies.remove(currency)
334 self.quote_currencies.insert(0, currency)
335 self.refresh_balance()
337 def change_quote_currency(self, forward=True):
339 self.quote_currencies = \
340 self.quote_currencies[1:] + self.quote_currencies[0:1]
342 self.quote_currencies = \
343 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
344 self.actuator.set_config_currency(self.quote_currencies[0])
345 self.refresh_balance()
347 def refresh_balance(self):
348 if self.btc_balance is None:
349 # Price has been discovered before wallet has been loaded
350 # and server connect... so bail.
352 self.set_balances(self.btc_balance)
353 self.amount_input_changed(self.amount_input.text())
355 def set_balances(self, btc_balance):
356 """Set the bitcoin balance and update the amount label accordingly."""
357 self.btc_balance = btc_balance
358 quote_text = self.create_quote_text(btc_balance)
360 quote_text = "(%s)" % quote_text
362 amount = self.actuator.g.format_amount(btc_balance)
363 unit = self.actuator.g.base_unit()
365 self.balance_label.set_balance_text(amount, unit, quote_text)
366 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
368 def amount_input_changed(self, amount_text):
369 """Update the number of bitcoins displayed."""
370 self.check_button_status()
373 amount = D(str(amount_text)) * (10**self.actuator.g.decimal_point)
374 except decimal.InvalidOperation:
375 self.balance_label.show_balance()
377 quote_text = self.create_quote_text(amount)
379 self.balance_label.set_amount_text(quote_text)
380 self.balance_label.show_amount()
382 self.balance_label.show_balance()
384 def create_quote_text(self, btc_balance):
385 """Return a string copy of the amount fiat currency the
386 user has in bitcoins."""
387 from electrum.plugins import run_hook
389 run_hook('get_fiat_balance_text', btc_balance, r)
393 if self.actuator.send(self.address_input.text(),
394 self.amount_input.text(), self):
395 self.address_input.setText("")
396 self.amount_input.setText("")
398 def check_button_status(self):
399 """Check that the bitcoin address is valid and that something
400 is entered in the amount before making the send button clickable."""
402 value = D(str(self.amount_input.text())) * (10**self.actuator.g.decimal_point)
403 except decimal.InvalidOperation:
405 # self.address_input.property(...) returns a qVariant, not a bool.
406 # The == is needed to properly invoke a comparison.
407 if (self.address_input.property("isValid") == True and
408 value is not None and 0 < value <= self.btc_balance):
409 self.send_button.setDisabled(False)
411 self.send_button.setDisabled(True)
413 def address_field_changed(self, address):
414 # label or alias, with address in brackets
415 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
418 address = match2.group(2)
419 self.address_input.setText(address)
421 if is_valid(address):
422 self.check_button_status()
423 self.address_input.setProperty("isValid", True)
424 self.recompute_style(self.address_input)
426 self.send_button.setDisabled(True)
427 self.address_input.setProperty("isValid", False)
428 self.recompute_style(self.address_input)
430 if len(address) == 0:
431 self.address_input.setProperty("isValid", None)
432 self.recompute_style(self.address_input)
434 def recompute_style(self, element):
435 self.style().unpolish(element)
436 self.style().polish(element)
438 def copy_address(self):
439 receive_popup = ReceivePopup(self.receive_button)
440 self.actuator.copy_address(receive_popup)
442 def update_completions(self, completions):
443 self.address_completions.setStringList(completions)
446 def update_history(self, tx_history):
448 self.history_list.empty()
450 for item in tx_history[-10:]:
451 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
452 label = self.actuator.g.wallet.get_label(tx_hash)[0]
453 v_str = self.actuator.g.format_amount(value, True)
454 self.history_list.append(label, v_str, age(timestamp))
457 def the_website(self):
458 webbrowser.open("http://electrum.org")
461 def toggle_receiving_layout(self, toggle_state):
463 self.receiving_box.show()
465 self.receiving_box.hide()
466 self.config.set_key("gui_show_receiving", toggle_state)
468 def show_history(self, toggle_state):
470 self.main_layout.setRowMinimumHeight(3,200)
471 self.history_list.show()
473 self.main_layout.setRowMinimumHeight(3,0)
474 self.history_list.hide()
475 self.config.set_key("gui_show_history", toggle_state)
477 class BalanceLabel(QLabel):
483 def __init__(self, change_quote_currency, parent=None):
484 super(QLabel, self).__init__(_("Connecting..."), parent)
485 self.change_quote_currency = change_quote_currency
486 self.state = self.SHOW_CONNECTING
487 self.balance_text = ""
488 self.amount_text = ""
491 def mousePressEvent(self, event):
492 """Change the fiat currency selection if window background is clicked."""
493 if self.state != self.SHOW_CONNECTING:
494 if event.button() == Qt.LeftButton:
495 self.change_quote_currency()
497 position = event.globalPos()
498 menu = self.parent.context_menu()
502 def set_balance_text(self, amount, unit, quote_text):
503 """Set the amount of bitcoins in the gui."""
504 if self.state == self.SHOW_CONNECTING:
505 self.state = self.SHOW_BALANCE
507 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
508 + " <span style='font-size: 10pt'>%s</span>" % unit \
509 + " <span style='font-size: 10pt'>%s</span>" % quote_text
511 if self.state == self.SHOW_BALANCE:
512 self.setText(self.balance_text)
514 def set_amount_text(self, quote_text):
515 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
516 if self.state == self.SHOW_AMOUNT:
517 self.setText(self.amount_text)
519 def show_balance(self):
520 if self.state == self.SHOW_AMOUNT:
521 self.state = self.SHOW_BALANCE
522 self.setText(self.balance_text)
524 def show_amount(self):
525 if self.state == self.SHOW_BALANCE:
526 self.state = self.SHOW_AMOUNT
527 self.setText(self.amount_text)
529 def ok_cancel_buttons(dialog):
530 row_layout = QHBoxLayout()
531 row_layout.addStretch(1)
532 ok_button = QPushButton(_("OK"))
533 row_layout.addWidget(ok_button)
534 ok_button.clicked.connect(dialog.accept)
535 cancel_button = QPushButton(_("Cancel"))
536 row_layout.addWidget(cancel_button)
537 cancel_button.clicked.connect(dialog.reject)
540 class PasswordDialog(QDialog):
542 def __init__(self, parent):
543 super(QDialog, self).__init__(parent)
547 self.password_input = QLineEdit()
548 self.password_input.setEchoMode(QLineEdit.Password)
550 main_layout = QVBoxLayout(self)
551 message = _('Please enter your password')
552 main_layout.addWidget(QLabel(message))
556 grid.addWidget(QLabel(_('Password')), 1, 0)
557 grid.addWidget(self.password_input, 1, 1)
558 main_layout.addLayout(grid)
560 main_layout.addLayout(ok_cancel_buttons(self))
561 self.setLayout(main_layout)
566 return unicode(self.password_input.text())
568 class ReceivePopup(QDialog):
570 def leaveEvent(self, event):
573 def setup(self, address):
574 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
575 address_display = QLineEdit(address)
576 address_display.setReadOnly(True)
577 resize_line_edit_width(address_display, address)
579 main_layout = QVBoxLayout(self)
580 main_layout.addWidget(label)
581 main_layout.addWidget(address_display)
583 self.setMouseTracking(True)
584 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
585 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
586 Qt.MSWindowsFixedSizeDialogHint)
587 self.layout().setSizeConstraint(QLayout.SetFixedSize)
588 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
589 #self.setAlignment(Qt.AlignCenter)
592 parent = self.parent()
593 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
594 self.move(top_left_pos)
595 center_mouse_pos = self.mapToGlobal(self.rect().center())
596 QCursor.setPos(center_mouse_pos)
600 """Initialize the definitions relating to themes and
601 sending/receiving bitcoins."""
604 def __init__(self, main_window):
605 """Retrieve the gui theme used in previous session."""
607 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
608 self.themes = load_theme_paths()
611 def load_theme(self):
612 """Load theme retrieved from wallet file."""
614 theme_prefix, theme_path = self.themes[self.theme_name]
616 util.print_error("Theme not found!", self.theme_name)
618 full_theme_path = "%s/%s/style.css" % (theme_prefix, theme_path)
619 with open(full_theme_path) as style_file:
620 qApp.setStyleSheet(style_file.read())
622 def theme_names(self):
624 return sorted(self.themes.keys())
626 def selected_theme(self):
628 return self.theme_name
630 def change_theme(self, theme_name):
632 self.theme_name = theme_name
633 self.g.config.set_key('litegui_theme',theme_name)
636 def set_configured_exchange(self, set_exchange):
637 use_exchange = self.g.config.get('use_exchange')
638 if use_exchange is not None:
639 set_exchange(use_exchange)
641 def set_configured_currency(self, set_quote_currency):
642 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
643 the GUI to what it was set to in the wallet."""
644 currency = self.g.config.get('currency')
645 # currency can be none when Electrum is used for the first
646 # time and no setting has been created yet.
647 if currency is not None:
648 set_quote_currency(currency)
650 def set_config_exchange(self, conversion_exchange):
651 self.g.config.set_key('exchange',conversion_exchange,True)
652 self.g.update_status()
654 def set_config_currency(self, conversion_currency):
655 """Change the wallet fiat currency country."""
656 self.g.config.set_key('currency',conversion_currency,True)
657 self.g.update_status()
659 def copy_address(self, receive_popup):
660 """Copy the wallet addresses into the client."""
661 addrs = [addr for addr in self.g.wallet.addresses(True)
662 if not self.g.wallet.is_change(addr)]
663 # Select most recent addresses from gap limit
664 addrs = addrs[-self.g.wallet.gap_limit:]
665 copied_address = random.choice(addrs)
666 qApp.clipboard().setText(copied_address)
667 receive_popup.setup(copied_address)
668 receive_popup.popup()
670 def waiting_dialog(self, f):
675 w.setWindowTitle('Electrum')
676 l = QLabel(_('Sending transaction, please wait.'))
685 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
690 def send(self, address, amount, parent_window):
691 """Send bitcoins to the target address."""
692 dest_address = self.fetch_destination(address)
694 if dest_address is None or not is_valid(dest_address):
695 QMessageBox.warning(parent_window, _('Error'),
696 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
699 amount = D(unicode(amount)) * (10*self.g.decimal_point)
700 print "amount", amount
703 if self.g.wallet.use_encryption:
704 password_dialog = PasswordDialog(parent_window)
705 password = password_dialog.run()
713 if amount < bitcoin(1) / 10:
715 fee = bitcoin(1) / 1000
718 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
719 except Exception as error:
720 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
724 h = self.g.wallet.send_tx(tx)
726 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
728 status, message = self.g.wallet.receive_tx(h, tx)
732 dumpf = tempfile.NamedTemporaryFile(delete=False)
735 print "Dumped error tx to", dumpf.name
736 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
739 TransactionWindow(message, self)
741 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
743 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
744 with open(fileName,'w') as f:
745 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
746 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
747 except Exception as e:
748 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
751 def fetch_destination(self, address):
752 recipient = unicode(address).strip()
755 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
758 # label or alias, with address in brackets
759 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
764 self.g.wallet.get_alias(recipient, True,
765 self.show_message, self.question)
768 return match2.group(2)
776 class MiniDriver(QObject):
783 def __init__(self, main_window, mini_window):
784 super(QObject, self).__init__()
787 self.network = main_window.network
788 self.window = mini_window
791 self.network.register_callback('updated',self.update_callback)
792 self.network.register_callback('connected', self.update_callback)
793 self.network.register_callback('disconnected', self.update_callback)
798 self.connect(self, SIGNAL("updatesignal()"), self.update)
799 self.update_callback()
801 # This is a hack to workaround that Qt does not like changing the
802 # window properties from this other thread before the runloop has
804 def update_callback(self):
805 self.emit(SIGNAL("updatesignal()"))
810 elif not self.network.interface:
812 elif not self.network.interface.is_connected:
815 if self.g.wallet is None:
817 elif not self.g.wallet.up_to_date:
821 self.update_balance()
822 self.update_completions()
823 self.update_history()
824 self.window.receiving.update_list()
827 def initializing(self):
828 if self.state == self.INITIALIZING:
830 self.state = self.INITIALIZING
831 self.window.deactivate()
833 def connecting(self):
834 if self.state == self.CONNECTING:
836 self.state = self.CONNECTING
837 self.window.deactivate()
839 def synchronizing(self):
840 if self.state == self.SYNCHRONIZING:
842 self.state = self.SYNCHRONIZING
843 self.window.deactivate()
846 if self.state == self.READY:
848 self.state = self.READY
849 self.window.activate()
851 def update_balance(self):
852 conf_balance, unconf_balance = self.g.wallet.get_balance()
853 balance = D(conf_balance + unconf_balance)
854 self.window.set_balances(balance)
856 def update_completions(self):
858 for addr, label in self.g.wallet.labels.items():
859 if addr in self.g.wallet.addressbook:
860 completions.append("%s <%s>" % (label, addr))
861 self.window.update_completions(completions)
863 def update_history(self):
864 tx_history = self.g.wallet.get_tx_history()
865 self.window.update_history(tx_history)
868 if __name__ == "__main__":
869 app = QApplication(sys.argv)
870 with open(rsrc("style.css")) as style_file:
871 app.setStyleSheet(style_file.read())
873 sys.exit(app.exec_())