ceb92fa46aa6c0f3caddadd18444534f7de6d144
[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 from qrtextedit import QRTextEdit
22
23 import re
24 from decimal import Decimal
25 from electrum import bitcoin
26
27 RE_ADDRESS = '[1-9A-HJ-NP-Za-km-z]{26,}'
28 RE_ALIAS = '(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>'
29
30 frozen_style = "QWidget { background-color:none; border:none;}"
31 normal_style = "QTextEdit { }"
32
33 class PayToEdit(QRTextEdit):
34
35     def __init__(self, win):
36         QRTextEdit.__init__(self)
37         self.win = win
38         self.amount_edit = win.amount_e
39         self.document().contentsChanged.connect(self.update_size)
40         self.heightMin = 0
41         self.heightMax = 150
42         self.setMinimumHeight(27)
43         self.setMaximumHeight(27)
44         self.c = None
45         self.textChanged.connect(self.check_text)
46         self.outputs = []
47         self.is_pr = False
48         self.scan_f = self.win.pay_from_URI
49         self.update_size()
50         self.payto_address = None
51
52     def lock_amount(self):
53         self.amount_edit.setFrozen(True)
54
55     def unlock_amount(self):
56         self.amount_edit.setFrozen(False)
57
58     def setFrozen(self, b):
59         self.setReadOnly(b)
60         self.setStyleSheet(frozen_style if b else normal_style)
61
62     def setGreen(self):
63         self.is_pr = True
64         self.setStyleSheet("QWidget { background-color:#80ff80;}")
65
66     def setExpired(self):
67         self.is_pr = True
68         self.setStyleSheet("QWidget { background-color:#ffcccc;}")
69
70     def parse_address_and_amount(self, line):
71         x, y = line.split(',')
72         address = self.parse_address(x)
73         amount = self.parse_amount(y)
74         return address, amount
75
76
77     def parse_amount(self, x):
78         p = pow(10, self.amount_edit.decimal_point())
79         return int( p * Decimal(x.strip()))
80
81
82     def parse_address(self, line):
83         r = line.strip()
84         m = re.match('^'+RE_ALIAS+'$', r)
85         address = m.group(2) if m else r
86         assert bitcoin.is_address(address)
87         return address
88
89
90     def check_text(self):
91         if self.is_pr:
92             return
93
94         # filter out empty lines
95         lines = filter( lambda x: x, self.lines())
96         outputs = []
97         total = 0
98
99         self.payto_address = None
100
101         if len(lines) == 1:
102             try:
103                 self.payto_address = self.parse_address(lines[0])
104             except:
105                 pass
106
107             if self.payto_address:
108                 self.unlock_amount()
109                 return
110
111         for line in lines:
112             try:
113                 to_address, amount = self.parse_address_and_amount(line)
114             except:
115                 continue
116                 
117             outputs.append((to_address, amount))
118             total += amount
119
120         self.outputs = outputs
121         self.payto_address = None
122
123         if total:
124             self.amount_edit.setAmount(total)
125         else:
126             self.amount_edit.setText("")
127
128         self.amount_edit.textEdited.emit("")
129
130         if total or len(lines)>1:
131             self.lock_amount()
132         else:
133             self.unlock_amount()
134
135
136     def get_outputs(self):
137         if self.payto_address:
138             try:
139                 amount = self.amount_edit.get_amount()
140             except:
141                 amount = None
142
143             self.outputs = [(self.payto_address, amount)]
144
145         return self.outputs[:]
146
147
148     def lines(self):
149         return str(self.toPlainText()).split('\n')
150
151
152     def is_multiline(self):
153         return len(self.lines()) > 1
154
155
156     def update_size(self):
157         docHeight = self.document().size().height()
158         if self.heightMin <= docHeight <= self.heightMax:
159             self.setMinimumHeight(docHeight + 2)
160             self.setMaximumHeight(docHeight + 2)
161
162
163     def setCompleter(self, completer):
164         self.c = completer
165         self.c.setWidget(self)
166         self.c.setCompletionMode(QCompleter.PopupCompletion)
167         self.c.activated.connect(self.insertCompletion)
168
169
170     def insertCompletion(self, completion):
171         if self.c.widget() != self:
172             return
173         tc = self.textCursor()
174         extra = completion.length() - self.c.completionPrefix().length()
175         tc.movePosition(QTextCursor.Left)
176         tc.movePosition(QTextCursor.EndOfWord)
177         tc.insertText(completion.right(extra))
178         self.setTextCursor(tc)
179  
180
181     def textUnderCursor(self):
182         tc = self.textCursor()
183         tc.select(QTextCursor.WordUnderCursor)
184         return tc.selectedText()
185
186
187     def keyPressEvent(self, e):
188         if self.isReadOnly():
189             return
190
191         if self.c.popup().isVisible():
192             if e.key() in [Qt.Key_Enter, Qt.Key_Return]:
193                 e.ignore()
194                 return
195
196         if e.key() in [Qt.Key_Tab]:
197             e.ignore()
198             return
199
200         if e.key() in [Qt.Key_Down, Qt.Key_Up] and not self.is_multiline():
201             e.ignore()
202             return
203
204         isShortcut = (e.modifiers() and Qt.ControlModifier) and e.key() == Qt.Key_E
205
206         if not self.c or not isShortcut:
207             QTextEdit.keyPressEvent(self, e)
208
209
210         ctrlOrShift = e.modifiers() and (Qt.ControlModifier or Qt.ShiftModifier)
211         if self.c is None or (ctrlOrShift and e.text().isEmpty()):
212             return
213
214         eow = QString("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=")
215         hasModifier = (e.modifiers() != Qt.NoModifier) and not ctrlOrShift;
216         completionPrefix = self.textUnderCursor()
217
218         if not isShortcut and (hasModifier or e.text().isEmpty() or completionPrefix.length() < 1 or eow.contains(e.text().right(1)) ):
219             self.c.popup().hide()
220             return
221
222         if completionPrefix != self.c.completionPrefix():
223             self.c.setCompletionPrefix(completionPrefix);
224             self.c.popup().setCurrentIndex(self.c.completionModel().index(0, 0))
225
226         cr = self.cursorRect()
227         cr.setWidth(self.c.popup().sizeHintForColumn(0) + self.c.popup().verticalScrollBar().sizeHint().width())
228         self.c.complete(cr)
229
230