From 108da45e53b104d28f65e587f741ca104ec3593d Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 5 Dec 2012 22:42:40 +0100 Subject: [PATCH 01/12] Added the option to export your transactions to a CSV file --- lib/gui_lite.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index 5563737b6..72e5fb8e4 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -28,7 +28,10 @@ import wallet import webbrowser import history_widget import util +import csv +import datetime +from wallet import format_satoshis import gui_qt import shutil @@ -254,6 +257,9 @@ class MiniWindow(QDialog): backup_wallet = extra_menu.addAction( _("&Create wallet backup")) backup_wallet.triggered.connect(self.backup_wallet) + export_csv = extra_menu.addAction( _("&Export transactions to CSV") ) + export_csv.triggered.connect(self.actuator.csv_transaction) + expert_gui = view_menu.addAction(_("&Classic GUI")) expert_gui.triggered.connect(expand_callback) themes_menu = view_menu.addMenu(_("&Themes")) @@ -732,6 +738,42 @@ class MiniActuator: w.exec_() w.destroy() + def csv_transaction(self): + try: + fileName = QFileDialog.getSaveFileName(QWidget(), 'Select file to export your wallet transactions to', os.path.expanduser('~/'), "*.csv") + if fileName: + with open(fileName, "w+") as csvfile: + transaction = csv.writer(csvfile) + transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"]) + for item in self.wallet.get_tx_history(): + tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item + if confirmations: + try: + time_string = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] + except [RuntimeError, TypeError, NameError] as reason: + print reason + time_string = "unknown" + else: + time_string = "pending" + + if value is not None: + value_string = format_satoshis(value, True, self.wallet.num_zeros) + else: + value_string = '--' + + if fee is not None: + fee_string = format_satoshis(fee, True, self.wallet.num_zeros) + else: + fee_string = '0' + + if tx_hash: + label, is_default_label = self.wallet.get_label(tx_hash) + balance_string = format_satoshis(balance, False, self.wallet.num_zeros) + transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string]) + QMessageBox.information(None,"CSV Export created", "Your CSV export has been succesfully created.") + except (IOError, os.error), reason: + QMessageBox.critical(None,"Unable to create csv", "Electrum was unable to produce a transaction export.\n" + str(reason)) + def send(self, address, amount, parent_window): """Send bitcoins to the target address.""" dest_address = self.fetch_destination(address) From c50103870e4fc26743da5a9770c29df74b70e14a Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 5 Dec 2012 22:55:15 +0100 Subject: [PATCH 02/12] Handle exceptions on parsing better --- lib/gui_lite.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index 72e5fb8e4..f1c8daddb 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -748,11 +748,12 @@ class MiniActuator: for item in self.wallet.get_tx_history(): tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item if confirmations: - try: - time_string = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] - except [RuntimeError, TypeError, NameError] as reason: - print reason - time_string = "unknown" + if timestamp is not None: + try: + time_string = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] + except [RuntimeError, TypeError, NameError] as reason: + time_string = "unknown" + pass else: time_string = "pending" From 9083be46f74564dcad526fc9cc9dd810351f7e5b Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 5 Dec 2012 23:04:16 +0100 Subject: [PATCH 03/12] There isn't always a tx hash..\? --- lib/gui_lite.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index f1c8daddb..e0e26ffa6 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -769,6 +769,9 @@ class MiniActuator: if tx_hash: label, is_default_label = self.wallet.get_label(tx_hash) + else: + label = "" + balance_string = format_satoshis(balance, False, self.wallet.num_zeros) transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string]) QMessageBox.information(None,"CSV Export created", "Your CSV export has been succesfully created.") From 9bf12079ce3b7481b56c9d66529dcf8f7ba5821e Mon Sep 17 00:00:00 2001 From: Maran Date: Wed, 5 Dec 2012 23:18:31 +0100 Subject: [PATCH 04/12] Added default time string --- lib/gui_lite.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index e0e26ffa6..d2f610639 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -750,10 +750,12 @@ class MiniActuator: if confirmations: if timestamp is not None: try: - time_string = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] + time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] except [RuntimeError, TypeError, NameError] as reason: time_string = "unknown" pass + else: + time_string = "unknown" else: time_string = "pending" From 674ffe27ff663cd8e758a38d1d180a7ba55db7e1 Mon Sep 17 00:00:00 2001 From: Maran Date: Sun, 9 Dec 2012 12:53:25 +0100 Subject: [PATCH 05/12] Remove server list from lite gui With all the new options for servers a simple menu item is not enough to properly implement it --- lib/gui_lite.py | 71 ------------------------------------------------- 1 file changed, 71 deletions(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index 5563737b6..480f05ba2 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -15,7 +15,6 @@ except ImportError: from decimal import Decimal as D -from interface import DEFAULT_SERVERS from util import get_resource_path as rsrc from i18n import _ import decimal @@ -91,7 +90,6 @@ class ElectrumGui(QObject): self.config = config self.check_qt_version() self.app = QApplication(sys.argv) - self.wallet.interface.register_callback('peers', self.server_list_changed) def check_qt_version(self): @@ -105,8 +103,6 @@ class ElectrumGui(QObject): def main(self, url): actuator = MiniActuator(self.wallet) - self.connect(self, SIGNAL("updateservers()"), - actuator.update_servers_list) # Should probably not modify the current path but instead # change the behaviour of rsrc(...) old_path = QDir.currentPath() @@ -130,9 +126,6 @@ class ElectrumGui(QObject): self.expert.update_wallet() self.app.exec_() - def server_list_changed(self): - self.emit(SIGNAL("updateservers()")) - def expand(self): """Hide the lite mode window and show pro-mode.""" self.mini.hide() @@ -237,10 +230,6 @@ class MiniWindow(QDialog): menubar = QMenuBar() electrum_menu = menubar.addMenu(_("&Bitcoin")) - servers_menu = electrum_menu.addMenu(_("&Servers")) - servers_group = QActionGroup(self) - self.actuator.set_servers_gui_stuff(servers_menu, servers_group) - self.actuator.populate_servers_menu() electrum_menu.addSeparator() brain_seed = electrum_menu.addAction(_("&BrainWallet Info")) @@ -642,66 +631,6 @@ class MiniActuator: """Change the wallet fiat currency country.""" self.wallet.config.set_key('conversion_currency',conversion_currency,True) - def set_servers_gui_stuff(self, servers_menu, servers_group): - self.servers_menu = servers_menu - self.servers_group = servers_group - - def populate_servers_menu(self): - interface = self.wallet.interface - if not interface.servers: - print "No servers loaded yet." - self.servers_list = [] - for server_string in DEFAULT_SERVERS: - host, port, protocol = server_string.split(':') - transports = [(protocol,port)] - self.servers_list.append((host, transports)) - else: - print "Servers loaded." - self.servers_list = interface.servers - server_names = [details[0] for details in self.servers_list] - current_server = interface.server.split(":")[0] - for server_name in server_names: - server_action = self.servers_menu.addAction(server_name) - server_action.setCheckable(True) - if server_name == current_server: - server_action.setChecked(True) - class SelectServerFunctor: - def __init__(self, server_name, server_selected): - self.server_name = server_name - self.server_selected = server_selected - def __call__(self, checked): - if checked: - # call server_selected - self.server_selected(self.server_name) - delegate = SelectServerFunctor(server_name, self.server_selected) - server_action.toggled.connect(delegate) - self.servers_group.addAction(server_action) - - def update_servers_list(self): - # Clear servers_group - for action in self.servers_group.actions(): - self.servers_group.removeAction(action) - self.populate_servers_menu() - - def server_selected(self, server_name): - match = [transports for (host, transports) in self.servers_list - if host == server_name] - assert len(match) == 1 - match = match[0] - # Default to TCP if available else use anything - # TODO: protocol should be selectable. - tcp_port = [port for (protocol, port) in match if protocol == "t"] - if len(tcp_port) == 0: - protocol = match[0][0] - port = match[0][1] - else: - protocol = "t" - port = tcp_port[0] - server_line = "%s:%s:%s" % (server_name, port, protocol) - - # Should this have exception handling? - self.wallet.interface.set_server(server_line, self.wallet.config.get("proxy")) - def copy_address(self, receive_popup): """Copy the wallet addresses into the client.""" addrs = [addr for addr in self.wallet.all_addresses() From 737a84e9511d4b8adab9bf7cd8df56b18a38b491 Mon Sep 17 00:00:00 2001 From: Maran Date: Sun, 9 Dec 2012 13:04:01 +0100 Subject: [PATCH 06/12] Update release scripts for OSX --- electrum.icns | Bin 0 -> 13898 bytes setup-release.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 electrum.icns diff --git a/electrum.icns b/electrum.icns new file mode 100644 index 0000000000000000000000000000000000000000..c6a2862ea1f938c6c3d3fd362c5b058bf94fa2bb GIT binary patch literal 13898 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 Date: Sun, 9 Dec 2012 13:04:57 +0100 Subject: [PATCH 07/12] Update osx binary instructions --- README | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README b/README index 97ee960a7..f34b4b057 100644 --- a/README +++ b/README @@ -33,10 +33,11 @@ On Mac OS X: # On port based installs sudo python setup-release.py py2app - # On brew installs - ARCHFLAGS="-arch i386 -arch x86_64" sudo /usr/bin/python setup-release.py py2app --includes sip - sudo hdiutil create -fs HFS+ -volname "Electrum" -srcfolder dist/Electrum.app dist/electrum-v0.61-macosx.dmg + # On brew installs + ARCHFLAGS="-arch i386 -arch x86_64" sudo python setup-release.py py2app --includes sip + + sudo hdiutil create -fs HFS+ -volname "Electrum" -srcfolder dist/Electrum.app dist/electrum-VERSION-macosx.dmg == BROWSER CONFIGURATION == From a32a6793595a993020edbe8b0cbed45aa058c290 Mon Sep 17 00:00:00 2001 From: Maran Date: Sun, 9 Dec 2012 13:39:05 +0100 Subject: [PATCH 08/12] Added age method that takes a timestamp and return a string of how long ago the date is --- lib/util.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/lib/util.py b/lib/util.py index 33cfaef41..393d2c96b 100644 --- a/lib/util.py +++ b/lib/util.py @@ -1,8 +1,54 @@ import os, sys import platform - +from datetime import datetime is_verbose = True +# Takes a timestamp and puts out a string with the approxomation of the age +def age(from_date, since_date = None, target_tz=None, include_seconds=False): + from_date = datetime.fromtimestamp(from_date) + if since_date is None: + since_date = datetime.now(target_tz) + + distance_in_time = since_date - from_date + distance_in_seconds = int(round(abs(distance_in_time.days * 86400 + distance_in_time.seconds))) + distance_in_minutes = int(round(distance_in_seconds/60)) + + + if distance_in_minutes <= 1: + if include_seconds: + for remainder in [5, 10, 20]: + if distance_in_seconds < remainder: + return "less than %s seconds ago" % remainder + if distance_in_seconds < 40: + return "half a minute" + elif distance_in_seconds < 60: + return "less than a minute ago" + else: + return "1 minute ago" + else: + if distance_in_minutes == 0: + return "less than a minute ago" + else: + return "1 minute ago" + elif distance_in_minutes < 45: + return "%s minutes ago" % distance_in_minutes + elif distance_in_minutes < 90: + return "about 1 hour ago" + elif distance_in_minutes < 1440: + return "about %d hours ago" % (round(distance_in_minutes / 60.0)) + elif distance_in_minutes < 2880: + return "1 day ago" + elif distance_in_minutes < 43220: + return "%d days ago" % (round(distance_in_minutes / 1440)) + elif distance_in_minutes < 86400: + return "about 1 month ago" + elif distance_in_minutes < 525600: + return "%d months ago" % (round(distance_in_minutes / 43200)) + elif distance_in_minutes < 1051200: + return "about 1 year ago" + else: + return "over %d years ago" % (round(distance_in_minutes / 525600)) + def set_verbosity(b): global is_verbose is_verbose = b From b564bedd52753119874b7278661c8f422fb283e2 Mon Sep 17 00:00:00 2001 From: Maran Date: Sun, 9 Dec 2012 13:39:28 +0100 Subject: [PATCH 09/12] Added date to the history overview of the lite GUI --- lib/gui_lite.py | 8 ++++++-- lib/history_widget.py | 9 ++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/gui_lite.py b/lib/gui_lite.py index 5cdec0bb3..c39aa9f03 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -434,15 +434,19 @@ class MiniWindow(QDialog): def update_completions(self, completions): self.address_completions.setStringList(completions) + def update_history(self, tx_history): - from util import format_satoshis + from util import format_satoshis, age + + self.history_list.empty() + for item in tx_history[-10:]: tx_hash, conf, is_mine, value, fee, balance, timestamp = item label = self.actuator.wallet.get_label(tx_hash)[0] #amount = D(value) / 10**8 v_str = format_satoshis(value, True) - self.history_list.append(label, v_str) + self.history_list.append(label, v_str, age(timestamp)) def acceptbit(self): self.actuator.acceptbit(self.quote_currencies[0]) diff --git a/lib/history_widget.py b/lib/history_widget.py index 3812c95fb..ce1f2cbe2 100644 --- a/lib/history_widget.py +++ b/lib/history_widget.py @@ -6,10 +6,13 @@ class HistoryWidget(QTreeWidget): def __init__(self, parent=None): QTreeWidget.__init__(self, parent) self.setColumnCount(2) - self.setHeaderLabels([_("Amount"), _("To / From")]) + self.setHeaderLabels([_("Amount"), _("To / From"), _("When")]) self.setIndentation(0) - def append(self, address, amount): - item = QTreeWidgetItem([amount, address]) + def empty(self): + self.clear() + + def append(self, address, amount, date): + item = QTreeWidgetItem([amount, address, date]) self.insertTopLevelItem(0, item) From 383948ab7113730d67b636bc5a353e69e9fc9eb7 Mon Sep 17 00:00:00 2001 From: Maran Date: Sun, 9 Dec 2012 13:50:13 +0100 Subject: [PATCH 10/12] Added unknown option in case none given --- lib/util.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/util.py b/lib/util.py index 393d2c96b..6ae8aaa71 100644 --- a/lib/util.py +++ b/lib/util.py @@ -5,6 +5,9 @@ is_verbose = True # Takes a timestamp and puts out a string with the approxomation of the age def age(from_date, since_date = None, target_tz=None, include_seconds=False): + if from_date is None: + return "Unknown" + from_date = datetime.fromtimestamp(from_date) if since_date is None: since_date = datetime.now(target_tz) From 32f3a42c04a1ae8729e08bf7ce67a1f01f7ac2b3 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 10 Dec 2012 00:34:29 +0100 Subject: [PATCH 11/12] Add receiving widget for lite gui --- lib/receiving_widget.py | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 lib/receiving_widget.py diff --git a/lib/receiving_widget.py b/lib/receiving_widget.py new file mode 100644 index 000000000..206f10e6f --- /dev/null +++ b/lib/receiving_widget.py @@ -0,0 +1,72 @@ +from PyQt4.QtGui import * +from PyQt4.QtCore import * +from i18n import _ + +class ReceivingWidget(QTreeWidget): + + def toggle_used(self): + if self.hide_used: + self.hide_used = False + self.setColumnHidden(2, False) + else: + self.hide_used = True + self.setColumnHidden(2, True) + self.update_list() + + def edit_label(self, item, column): + if column == 1 and item.isSelected(): + self.editing = True + item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + self.editItem(item, column) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled) + self.editing = False + + def update_label(self, item, column): + if self.editing: + return + else: + address = str(item.text(0)) + label = unicode( item.text(1) ) + self.owner.actuator.wallet.labels[address] = label + + def copy_address(self): + address = self.currentItem().text(0) + qApp.clipboard().setText(address) + + + def update_list(self): + + self.clear() + addresses = [addr for addr in self.owner.actuator.wallet.all_addresses() if not self.owner.actuator.wallet.is_change(addr)] + for address in addresses: + history = self.owner.actuator.wallet.history.get(address,[]) + + used = "No" + for tx_hash, tx_height in history: + tx = self.owner.actuator.wallet.transactions.get(tx_hash) + if tx: + used = "Yes" + + if(self.hide_used == True and used == "No") or self.hide_used == False: + label = self.owner.actuator.wallet.labels.get(address,'') + item = QTreeWidgetItem([address, label, used]) + self.insertTopLevelItem(0, item) + + def __init__(self, owner=None): + self.owner = owner + self.editing = False + + QTreeWidget.__init__(self, owner) + self.setColumnCount(3) + self.setHeaderLabels([_("Address"), _("Label"), _("Used")]) + self.setIndentation(0) + + self.hide_used = True + self.setColumnHidden(2, True) + self.update_list() + + + + + + From 639471239249ee9f9840b4c6489628d2ca1e2433 Mon Sep 17 00:00:00 2001 From: Maran Date: Mon, 10 Dec 2012 00:34:57 +0100 Subject: [PATCH 12/12] Implemented receiving addresses to lite gui --- data/cleanlook/style.css | 16 ++++++++++ lib/gui_lite.py | 65 ++++++++++++++++++++++++++++++++++++---- setup.py | 1 + 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/data/cleanlook/style.css b/data/cleanlook/style.css index ffc9bb49b..d7e21233a 100644 --- a/data/cleanlook/style.css +++ b/data/cleanlook/style.css @@ -34,6 +34,22 @@ MiniWindow QPushButton { min-height: 23px; padding: 2px; } +#receive_button +{ + color: #777; + border: 1px solid #CCC; + border-radius: 0px; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 white, stop: 1 #E6E6E6); + min-height: 25px; + min-width: 30px; +} +#receive_button[isActive=true] +{ + color: #575757; + background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 white, stop: 1 #D1D1D1); +} #address_input, #amount_input { diff --git a/lib/gui_lite.py b/lib/gui_lite.py index c39aa9f03..d27e687b5 100644 --- a/lib/gui_lite.py +++ b/lib/gui_lite.py @@ -26,6 +26,7 @@ import time import wallet import webbrowser import history_widget +import receiving_widget import util import csv import datetime @@ -171,10 +172,6 @@ class MiniWindow(QDialog): self.balance_label = BalanceLabel(self.change_quote_currency) self.balance_label.setObjectName("balance_label") - self.receive_button = QPushButton(_("&Receive")) - self.receive_button.setObjectName("receive_button") - self.receive_button.setDefault(True) - self.receive_button.clicked.connect(self.copy_address) # Bitcoin address code self.address_input = QLineEdit() @@ -214,12 +211,17 @@ class MiniWindow(QDialog): self.send_button.setDisabled(True); self.send_button.clicked.connect(self.send) + # Creating the receive button + self.receive_button = QPushButton(_("&Receive")) + self.receive_button.setObjectName("receive_button") + self.receive_button.setDefault(True) + main_layout = QGridLayout(self) main_layout.addWidget(self.balance_label, 0, 0) main_layout.addWidget(self.receive_button, 0, 1) - main_layout.addWidget(self.address_input, 1, 0, 1, -1) + main_layout.addWidget(self.address_input, 1, 0) main_layout.addWidget(self.amount_input, 2, 0) main_layout.addWidget(self.send_button, 2, 1) @@ -228,8 +230,41 @@ class MiniWindow(QDialog): self.history_list.setObjectName("history") self.history_list.hide() self.history_list.setAlternatingRowColors(True) - main_layout.addWidget(self.history_list, 3, 0, 1, -1) + main_layout.addWidget(self.history_list, 3, 0, 1, 2) + + + self.receiving = receiving_widget.ReceivingWidget(self) + self.receiving.setObjectName("receiving") + + # Add to the right side + self.receiving_box = QGroupBox(_("Select a receiving address")) + extra_layout = QGridLayout() + + # Checkbox to filter used addresses + hide_used = QCheckBox(_('Hide used addresses')) + hide_used.setChecked(True) + hide_used.stateChanged.connect(self.receiving.toggle_used) + + # Events for receiving addresses + self.receiving.clicked.connect(self.receiving.copy_address) + self.receiving.itemDoubleClicked.connect(self.receiving.edit_label) + self.receiving.itemChanged.connect(self.receiving.update_label) + + # Label + extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.\nDouble clicking the label will allow you to edit it.') ),0,0) + + extra_layout.addWidget(self.receiving, 1,0) + extra_layout.addWidget(hide_used, 2,0) + extra_layout.setColumnMinimumWidth(0,200) + + self.receiving_box.setLayout(extra_layout) + main_layout.addWidget(self.receiving_box,0,3,-1,3) + self.receiving_box.hide() + + self.receive_button.clicked.connect(self.toggle_receiving_layout) + + # Creating the menu bar menubar = QMenuBar() electrum_menu = menubar.addMenu(_("&Bitcoin")) @@ -283,6 +318,7 @@ class MiniWindow(QDialog): show_about = help_menu.addAction(_("&About")) show_about.triggered.connect(self.show_about) main_layout.setMenuBar(menubar) + self.main_layout = main_layout quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self) quit_shortcut.activated.connect(self.close) @@ -303,6 +339,20 @@ class MiniWindow(QDialog): self.setObjectName("main_window") self.show() + def toggle_receiving_layout(self): + if self.receiving_box.isVisible(): + self.receiving_box.hide() + self.receive_button.setProperty("isActive", False) + + qApp.style().unpolish(self.receive_button) + qApp.style().polish(self.receive_button) + else: + self.receiving_box.show() + self.receive_button.setProperty("isActive", 'true') + + qApp.style().unpolish(self.receive_button) + qApp.style().polish(self.receive_button) + def toggle_theme(self, theme_name): old_path = QDir.currentPath() self.actuator.change_theme(theme_name) @@ -464,8 +514,10 @@ class MiniWindow(QDialog): def show_history(self, toggle_state): if toggle_state: + self.main_layout.setRowMinimumHeight(3,200) self.history_list.show() else: + self.main_layout.setRowMinimumHeight(3,0) self.history_list.hide() def backup_wallet(self): @@ -883,6 +935,7 @@ class MiniDriver(QObject): tx_history = self.wallet.get_tx_history() self.window.update_history(tx_history) + if __name__ == "__main__": app = QApplication(sys.argv) with open(rsrc("style.css")) as style_file: diff --git a/setup.py b/setup.py index 3bff8c8a6..8f653a8e8 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ setup(name = "Electrum", 'electrum.pyqrnative', 'electrum.qrscanner', 'electrum.history_widget', + 'electrum.receiving_widget', 'electrum.simple_config', 'electrum.socks', 'electrum.bmp',