payment request: show status with help button
[electrum-nvc.git] / gui / qt / paytoedit.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from PyQt4.QtCore import *
20 from PyQt4.QtGui import *
21
22 import re
23 from decimal import Decimal
24 from electrum import bitcoin
25
26 RE_ADDRESS = '[1-9A-HJ-NP-Za-km-z]{26,}'
27 RE_ALIAS = '(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>'
28
29 frozen_style = "QWidget { background-color:none; border:none;}"
30 normal_style = "QTextEdit { }"
31
32 class PayToEdit(QTextEdit):
33
34     def __init__(self, amount_edit):
35         QTextEdit.__init__(self)
36         self.amount_edit = amount_edit
37         self.document().contentsChanged.connect(self.update_size)
38         self.heightMin = 0
39         self.heightMax = 150
40         self.setMinimumHeight(27)
41         self.setMaximumHeight(27)
42         self.c = None
43
44     def lock_amount(self):
45         self.amount_edit.setFrozen(True)
46
47     def unlock_amount(self):
48         self.amount_edit.setFrozen(False)
49
50     def setFrozen(self, b):
51         self.setReadOnly(b)
52         self.setStyleSheet(frozen_style if b else normal_style)
53
54     def setGreen(self):
55         self.setStyleSheet("QWidget { background-color:#00ff00;}")
56
57     def parse_address_and_amount(self, line):
58         x, y = line.split(',')
59         address = self.parse_address(x)
60         amount = self.parse_amount(y)
61         return address, amount
62
63
64     def parse_amount(self, x):
65         p = pow(10, self.amount_edit.decimal_point())
66         return int( p * Decimal(x.strip()))
67
68
69     def parse_address(self, line):
70         r = line.strip()
71         m = re.match('^'+RE_ALIAS+'$', r)
72         address = m.group(2) if m else r
73         assert bitcoin.is_address(address)
74         return address
75
76
77     def check_text(self):
78         # filter out empty lines
79         lines = filter( lambda x: x, self.lines())
80         outputs = []
81         total = 0
82
83         if len(lines) == 1:
84             try:
85                 self.payto_address = self.parse_address(lines[0])
86             except:
87                 self.payto_address = None
88
89             if self.payto_address:
90                 self.unlock_amount()
91                 return
92
93         for line in lines:
94             try:
95                 to_address, amount = self.parse_address_and_amount(line)
96             except:
97                 continue
98                 
99             outputs.append((to_address, amount))
100             total += amount
101
102         self.outputs = outputs
103         self.payto_address = None
104
105         if total:
106             self.amount_edit.setAmount(total)
107         else:
108             self.amount_edit.setText("")
109
110         if total or len(lines)>1:
111             self.lock_amount()
112         else:
113             self.unlock_amount()
114
115
116
117     def get_outputs(self):
118
119         if self.payto_address:
120             
121             if not bitcoin.is_address(self.payto_address):
122                 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
123                 return
124
125             try:
126                 amount = self.amount_edit.get_amount()
127             except Exception:
128                 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
129                 return
130
131             outputs = [(self.payto_address, amount)]
132             return outputs
133
134         return self.outputs
135
136
137     def lines(self):
138         return str(self.toPlainText()).split('\n')
139
140
141     def is_multiline(self):
142         return len(self.lines()) > 1
143
144
145     def update_size(self):
146         docHeight = self.document().size().height()
147         if self.heightMin <= docHeight <= self.heightMax:
148             self.setMinimumHeight(docHeight + 2)
149             self.setMaximumHeight(docHeight + 2)
150
151
152     def setCompleter(self, completer):
153         self.c = completer
154         self.c.setWidget(self)
155         self.c.setCompletionMode(QCompleter.PopupCompletion)
156         self.c.activated.connect(self.insertCompletion)
157
158
159     def insertCompletion(self, completion):
160         if self.c.widget() != self:
161             return
162         tc = self.textCursor()
163         extra = completion.length() - self.c.completionPrefix().length()
164         tc.movePosition(QTextCursor.Left)
165         tc.movePosition(QTextCursor.EndOfWord)
166         tc.insertText(completion.right(extra))
167         self.setTextCursor(tc)
168         self.check_text()
169  
170
171     def textUnderCursor(self):
172         tc = self.textCursor()
173         tc.select(QTextCursor.WordUnderCursor)
174         return tc.selectedText()
175
176
177     def keyPressEvent(self, e):
178         if self.c.popup().isVisible():
179             if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
180                 e.ignore()
181                 return
182
183         if e.key() in [Qt.Key_Tab]:
184             e.ignore()
185             return
186
187         if e.key() in [Qt.Key_Down, Qt.Key_Up] and not self.is_multiline():
188             e.ignore()
189             return
190
191         isShortcut = (e.modifiers() and Qt.ControlModifier) and e.key() == Qt.Key_E
192
193         if not self.c or not isShortcut:
194             QTextEdit.keyPressEvent(self, e)
195             self.check_text()
196
197
198         ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
199         if self.c is None or (ctrlOrShift and e.text().isEmpty()):
200             return
201
202         eow = QString("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=")
203         hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift;
204         completionPrefix = self.textUnderCursor()
205
206         if not isShortcut and (hasModifier or e.text().isEmpty() or completionPrefix.length() < 1 or eow.contains(e.text().right(1)) ):
207             self.c.popup().hide()
208             return
209
210         if completionPrefix != self.c.completionPrefix():
211             self.c.setCompletionPrefix(completionPrefix);
212             self.c.popup().setCurrentIndex(self.c.completionModel().index(0, 0))
213
214         cr = self.cursorRect()
215         cr.setWidth(self.c.popup().sizeHintForColumn(0) + self.c.popup().verticalScrollBar().sizeHint().width())
216         self.c.complete(cr)
217
218