URI: 
       tMerge branch 'master' of github.com:spesmilo/electrum - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 35838b088497d2e8a1d52d10d5fe70724188088d
   DIR parent 837e154d3928c0d96e2e50ef4425201c863a59db
  HTML Author: slush <info@bitcoin.cz>
       Date:   Mon, 10 Dec 2012 00:51:07 +0100
       
       Merge branch 'master' of github.com:spesmilo/electrum
       
       Diffstat:
         M README                              |       5 +++--
         M data/cleanlook/style.css            |      16 ++++++++++++++++
         A electrum.icns                       |       0 
         M lib/gui_lite.py                     |     192 ++++++++++++++++++-------------
         M lib/history_widget.py               |       9 ++++++---
         A lib/receiving_widget.py             |      72 +++++++++++++++++++++++++++++++
         M lib/util.py                         |      51 ++++++++++++++++++++++++++++++-
         M setup-release.py                    |       2 +-
         M setup.py                            |       1 +
       
       9 files changed, 262 insertions(+), 86 deletions(-)
       ---
   DIR diff --git a/README b/README
       t@@ -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
       +  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-v0.61-macosx.dmg
       +  sudo hdiutil create -fs HFS+ -volname "Electrum" -srcfolder dist/Electrum.app dist/electrum-VERSION-macosx.dmg
        
        
        == BROWSER CONFIGURATION ==
   DIR diff --git a/data/cleanlook/style.css b/data/cleanlook/style.css
       t@@ -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
        {
   DIR diff --git a/electrum.icns b/electrum.icns
       Binary files differ.
   DIR diff --git a/lib/gui_lite.py b/lib/gui_lite.py
       t@@ -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
       t@@ -27,8 +26,12 @@ import time
        import wallet
        import webbrowser
        import history_widget
       +import receiving_widget
        import util
       +import csv 
       +import datetime
        
       +from wallet import format_satoshis
        import gui_qt
        import shutil
        
       t@@ -91,7 +94,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):
       t@@ -105,8 +107,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()
       t@@ -130,9 +130,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()
       t@@ -175,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()
       t@@ -218,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)
       t@@ -232,15 +230,44 @@ 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"))
        
       -        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"))
       t@@ -254,6 +281,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"))
       t@@ -288,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)
       t@@ -308,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)
       t@@ -439,15 +484,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])
       t@@ -465,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):
       t@@ -642,66 +693,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()
       t@@ -732,6 +723,48 @@ 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:
       +                        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 = "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)
       +                    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.")
       +        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)
       t@@ -902,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:
   DIR diff --git a/lib/history_widget.py b/lib/history_widget.py
       t@@ -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)
        
   DIR diff --git a/lib/receiving_widget.py b/lib/receiving_widget.py
       t@@ -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()
       +
       +
       +            
       +
       +
       +
   DIR diff --git a/lib/util.py b/lib/util.py
       t@@ -1,8 +1,57 @@
        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):
       +  if from_date is None:
       +    return "Unknown"
       +
       +  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
   DIR diff --git a/setup-release.py b/setup-release.py
       t@@ -29,7 +29,7 @@ if sys.platform == 'darwin':
                app=[mainscript],
                options=dict(py2app=dict(argv_emulation=True,
                                         iconfile='electrum.icns',
       -                                 resources=["data/background.png", "data/style.css", "data/icons"])),
       +                                 resources=["data", "icons"])),
            )
        elif sys.platform == 'win32':
            extra_options = dict(
   DIR diff --git a/setup.py b/setup.py
       t@@ -59,6 +59,7 @@ setup(name = "Electrum",
                          'electrum.pyqrnative',
                          'electrum.qrscanner',
                          'electrum.history_widget',
       +                  'electrum.receiving_widget',
                          'electrum.simple_config',
                          'electrum.socks',
                          'electrum.bmp',