fix indent
[electrum-nvc.git] / plugins / qrscanner.py
1 from electrum.util import print_error
2 from urlparse import urlparse, parse_qs
3 from PyQt4.QtGui import QPushButton, QMessageBox, QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel
4 from PyQt4.QtCore import Qt
5 from electrum_gui.i18n import _
6
7 import re
8 from electrum.bitcoin import MIN_RELAY_TX_FEE, Transaction, is_valid
9 from electrum_gui.qrcodewidget import QRCodeWidget
10 import electrum_gui.bmp
11 import json
12
13 try:
14     import zbar
15 except ImportError:
16     zbar = None
17
18 from electrum_gui import BasePlugin
19 class Plugin(BasePlugin):
20
21     def __init__(self, gui):
22         BasePlugin.__init__(self, gui, 'qrscans', 'QR scans', "QR Scans.\nInstall the zbar package (http://zbar.sourceforge.net/download.html) to enable this plugin")
23         self._is_available = self._init()
24         
25     def _init(self):
26         if not zbar:
27             return False
28         try:
29             proc = zbar.Processor()
30             proc.init()
31         except zbar.SystemError:
32             # Cannot open video device
33             return False
34
35         return True
36
37     def is_available(self):
38         return self._is_available
39
40     def create_send_tab(self, grid):
41         b = QPushButton(_("Scan QR code"))
42         b.clicked.connect(self.fill_from_qr)
43         grid.addWidget(b, 1, 5)
44         b2 = QPushButton(_("Scan TxQR"))
45         b2.clicked.connect(self.read_raw_qr)
46         
47         if not self.gui.wallet.seed:
48             b3 = QPushButton(_("Show unsigned TxQR"))
49             b3.clicked.connect(self.show_raw_qr)
50             grid.addWidget(b3, 7, 1)
51             grid.addWidget(b2, 7, 2)
52         else:
53             grid.addWidget(b2, 7, 1)
54
55
56     def scan_qr(self):
57         proc = zbar.Processor()
58         proc.init()
59         proc.visible = True
60
61         while True:
62             try:
63                 proc.process_one()
64             except:
65                 # User closed the preview window
66                 return {}
67
68             for r in proc.results:
69                 if str(r.type) != 'QRCODE':
70                     continue
71                 return r.data
72         
73     def show_raw_qr(self):
74         r = unicode( self.gui.payto_e.text() )
75         r = r.strip()
76
77         # label or alias, with address in brackets
78         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
79         to_address = m.group(2) if m else r
80
81         if not is_valid(to_address):
82             QMessageBox.warning(self.gui, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
83             return
84
85         try:
86             amount = self.gui.read_amount(unicode( self.gui.amount_e.text()))
87         except:
88             QMessageBox.warning(self.gui, _('Error'), _('Invalid Amount'), _('OK'))
89             return
90         try:
91             fee = self.gui.read_amount(unicode( self.gui.fee_e.text()))
92         except:
93             QMessageBox.warning(self.gui, _('Error'), _('Invalid Fee'), _('OK'))
94             return
95
96         try:
97             tx = self.gui.wallet.mktx( [(to_address, amount)], None, fee, account=self.gui.current_account)
98         except BaseException, e:
99             self.gui.show_message(str(e))
100             return
101
102         if tx.requires_fee(self.gui.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
103             QMessageBox.warning(self.gui, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
104             return
105
106         try:
107             out = {
108             "hex" : tx.hash(),
109             "complete" : "false"
110             }
111     
112             input_info = []
113
114         except BaseException, e:
115             self.gui.show_message(str(e))
116
117         try:
118             json_text = json.dumps(tx.as_dict()).replace(' ', '')
119             self.show_tx_qrcode(json_text, 'Unsigned Transaction')
120         except BaseException, e:
121             self.gui.show_message(str(e))
122
123     def show_tx_qrcode(self, data, title):
124         if not data: return
125         d = QDialog(self.gui)
126         d.setModal(1)
127         d.setWindowTitle(title)
128         d.setMinimumSize(250, 525)
129         vbox = QVBoxLayout()
130         qrw = QRCodeWidget(data)
131         vbox.addWidget(qrw, 0)
132         hbox = QHBoxLayout()
133         hbox.addStretch(1)
134
135         def print_qr(self):
136             filename = "qrcode.bmp"
137             electrum_gui.bmp.save_qrcode(qrw.qr, filename)
138             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
139
140         b = QPushButton(_("Save"))
141         hbox.addWidget(b)
142         b.clicked.connect(print_qr)
143
144         b = QPushButton(_("Close"))
145         hbox.addWidget(b)
146         b.clicked.connect(d.accept)
147         b.setDefault(True)
148
149         vbox.addLayout(hbox, 1)
150         d.setLayout(vbox)
151         d.exec_()
152
153     def read_raw_qr(self):
154     qrcode = self.scan_qr()
155     if qrcode:
156         tx_dict = self.gui.tx_dict_from_text(qrcode)
157         if tx_dict:
158             self.create_transaction_details_window(tx_dict)
159
160
161     def create_transaction_details_window(self, tx_dict):
162         tx = Transaction(tx_dict["hex"])
163             
164         dialog = QDialog(self.gui)
165         dialog.setMinimumWidth(500)
166         dialog.setWindowTitle(_('Process Offline transaction'))
167         dialog.setModal(1)
168
169         l = QGridLayout()
170         dialog.setLayout(l)
171
172         l.addWidget(QLabel(_("Transaction status:")), 3,0)
173         l.addWidget(QLabel(_("Actions")), 4,0)
174
175         if tx_dict["complete"] == False:
176             l.addWidget(QLabel(_("Unsigned")), 3,1)
177             if self.gui.wallet.seed :
178                 b = QPushButton("Sign transaction")
179                 input_info = json.loads(tx_dict["input_info"])
180                 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
181                 l.addWidget(b, 4, 1)
182             else:
183                 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
184         else:
185             l.addWidget(QLabel(_("Signed")), 3,1)
186         b = QPushButton("Broadcast transaction")
187             b.clicked.connect(lambda: self.gui.send_raw_transaction(tx, dialog))
188             l.addWidget(b,4,1)
189     
190         l.addWidget( self.gui.generate_transaction_information_widget(tx), 0,0,2,3)
191         closeButton = QPushButton(_("Close"))
192         closeButton.clicked.connect(lambda: dialog.done(0))
193         l.addWidget(closeButton, 4,2)
194
195         dialog.exec_()
196
197     def do_protect(self, func, args):
198         if self.gui.wallet.use_encryption:
199             password = self.gui.password_dialog()
200             if not password:
201                 return
202         else:
203             password = None
204             
205         if args != (False,):
206             args = (self,) + args + (password,)
207         else:
208             args = (self,password)
209         apply( func, args)
210
211     def protected(func):
212         return lambda s, *args: s.do_protect(func, args)
213
214     @protected
215     def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
216         try:
217             self.gui.wallet.signrawtransaction(tx, input_info, [], password)
218             txtext = json.dumps(tx.as_dict()).replace(' ', '')
219             self.show_tx_qrcode(txtext, 'Signed Transaction')
220         except BaseException, e:
221             self.gui.show_message(str(e))
222
223
224     def fill_from_qr(self):
225         qrcode = parse_uri(self.scan_qr())
226         if not qrcode:
227             return
228
229         if 'address' in qrcode:
230             self.gui.payto_e.setText(qrcode['address'])
231         if 'amount' in qrcode:
232             self.gui.amount_e.setText(str(qrcode['amount']))
233         if 'label' in qrcode:
234             self.gui.message_e.setText(qrcode['label'])
235         if 'message' in qrcode:
236             self.gui.message_e.setText("%s (%s)" % (self.gui.message_e.text(), qrcode['message']))
237                 
238
239
240
241 def parse_uri(uri):
242     if ':' not in uri:
243         # It's just an address (not BIP21)
244         return {'address': uri}
245
246     if '//' not in uri:
247         # Workaround for urlparse, it don't handle bitcoin: URI properly
248         uri = uri.replace(':', '://')
249         
250     uri = urlparse(uri)
251     result = {'address': uri.netloc} 
252     
253     if uri.path.startswith('?'):
254         params = parse_qs(uri.path[1:])
255     else:
256         params = parse_qs(uri.path)    
257
258     for k,v in params.items():
259         if k in ('amount', 'label', 'message'):
260             result[k] = v[0]
261         
262     return result    
263
264
265
266
267
268 if __name__ == '__main__':
269     # Run some tests
270     
271     assert(parse_uri('1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
272            {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
273
274     assert(parse_uri('bitcoin://1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
275            {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
276     
277     assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN') ==
278            {'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
279     
280     assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10') ==
281            {'amount': '10', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
282     
283     assert(parse_uri('bitcoin:1Marek48fwU7mugmSe186do2QpUkBnpzSN?amount=10&label=slush&message=Small%20tip%20to%20slush') ==
284            {'amount': '10', 'label': 'slush', 'message': 'Small tip to slush', 'address': '1Marek48fwU7mugmSe186do2QpUkBnpzSN'})
285     
286