1from typing import TYPE_CHECKING 2 3from kivy.app import App 4from kivy.clock import Clock 5from kivy.factory import Factory 6from kivy.properties import ObjectProperty 7from kivy.lang import Builder 8from decimal import Decimal 9from kivy.uix.popup import Popup 10 11from electrum.gui.kivy.i18n import _ 12from ...util import address_colors 13 14if TYPE_CHECKING: 15 from ...main_window import ElectrumWindow 16 17 18Builder.load_string(''' 19<AddressLabel@Label> 20 text_size: self.width, None 21 halign: 'left' 22 valign: 'top' 23 24<AddressItem@CardItem> 25 address: '' 26 memo: '' 27 amount: '' 28 status: '' 29 is_frozen: False 30 BoxLayout: 31 spacing: '8dp' 32 height: '32dp' 33 orientation: 'vertical' 34 Widget 35 AddressLabel: 36 text: root.address 37 shorten: True 38 Widget 39 AddressLabel: 40 text: 41 (("({}) ".format(_("Frozen")) if root.is_frozen else "") 42 + (root.amount if root.status == 'Funded' else root.status) + ' ' + root.memo) 43 color: .699, .699, .699, 1 44 font_size: '13sp' 45 shorten: True 46 Widget 47 48<AddressButton@Button>: 49 background_color: 1, .585, .878, 0 50 halign: 'center' 51 text_size: (self.width, None) 52 shorten: True 53 size_hint: 0.5, None 54 default_text: '' 55 text: self.default_text 56 padding: '5dp', '5dp' 57 height: '40dp' 58 text_color: self.foreground_color 59 disabled_color: 1, 1, 1, 1 60 foreground_color: 1, 1, 1, 1 61 canvas.before: 62 Color: 63 rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color 64 Rectangle: 65 size: self.size 66 pos: self.pos 67 68<AddressesDialog@Popup> 69 id: popup 70 title: _('Addresses') 71 message: '' 72 pr_status: 'Pending' 73 show_change: 0 74 show_used: 0 75 on_message: 76 self.update() 77 BoxLayout: 78 id:box 79 padding: '12dp', '12dp', '12dp', '12dp' 80 spacing: '12dp' 81 orientation: 'vertical' 82 BoxLayout: 83 spacing: '6dp' 84 height: self.minimum_height 85 size_hint: 1, None 86 orientation: 'horizontal' 87 AddressFilter: 88 opacity: 1 89 size_hint: 1, None 90 height: self.minimum_height 91 spacing: '5dp' 92 AddressButton: 93 id: search 94 text: {0:_('Receiving'), 1:_('Change'), 2:_('All')}[root.show_change] 95 on_release: 96 root.show_change = (root.show_change + 1) % 3 97 Clock.schedule_once(lambda dt: root.update()) 98 AddressFilter: 99 opacity: 1 100 size_hint: 1, None 101 height: self.minimum_height 102 spacing: '5dp' 103 AddressButton: 104 id: search 105 text: {0:_('All'), 1:_('Unused'), 2:_('Funded'), 3:_('Used'), 4:(_('Funded')+'|'+_('Unused'))}[root.show_used] 106 on_release: 107 root.show_used = (root.show_used + 1) % 5 108 Clock.schedule_once(lambda dt: root.update()) 109 AddressFilter: 110 opacity: 1 111 size_hint: 1, None 112 height: self.minimum_height 113 spacing: '5dp' 114 canvas.before: 115 Color: 116 rgba: 0.9, 0.9, 0.9, 1 117 AddressButton: 118 id: change 119 text: root.message if root.message else _('Search') 120 on_release: Clock.schedule_once(lambda dt: app.description_dialog(popup)) 121 RecycleView: 122 scroll_type: ['bars', 'content'] 123 bar_width: '15dp' 124 viewclass: 'AddressItem' 125 id: search_container 126 RecycleBoxLayout: 127 orientation: 'vertical' 128 default_size: None, dp(56) 129 default_size_hint: 1, None 130 size_hint_y: None 131 height: self.minimum_height 132 133<AddressPopup@Popup>: 134 address: '' 135 balance: '' 136 status: '' 137 script_type: '' 138 pk: '' 139 is_frozen: False 140 address_color: 1, 1, 1, 1 141 address_background_color: 0.3, 0.3, 0.3, 1 142 BoxLayout: 143 orientation: 'vertical' 144 ScrollView: 145 GridLayout: 146 cols: 1 147 height: self.minimum_height 148 size_hint_y: None 149 padding: '10dp' 150 spacing: '10dp' 151 TopLabel: 152 text: _('Address') 153 RefLabel: 154 color: root.address_color 155 background_color: root.address_background_color 156 data: root.address 157 name: _('Address') 158 GridLayout: 159 cols: 1 160 size_hint_y: None 161 height: self.minimum_height 162 spacing: '10dp' 163 BoxLabel: 164 text: _('Balance') 165 value: root.balance 166 BoxLabel: 167 text: _('Script type') 168 value: root.script_type 169 BoxLabel: 170 text: _('Status') 171 value: root.status 172 BoxLabel: 173 text: _('Frozen') 174 value: str(root.is_frozen) 175 TopLabel: 176 text: _('Private Key') 177 RefLabel: 178 data: root.pk 179 name: _('Private key') 180 on_touched: if not self.data: root.do_export(self) 181 Widget: 182 size_hint: 1, 0.1 183 BoxLayout: 184 size_hint: 1, None 185 height: '48dp' 186 Button: 187 size_hint: 0.5, None 188 height: '48dp' 189 text: _('Receive') 190 on_release: root.receive_at() 191 Button: 192 size_hint: 0.5, None 193 height: '48dp' 194 text: _('Freeze') if not root.is_frozen else _('Unfreeze') 195 on_release: root.freeze_address() 196 Button: 197 size_hint: 0.5, None 198 height: '48dp' 199 text: _('Close') 200 on_release: root.dismiss() 201''') 202 203 204 205class AddressPopup(Popup): 206 207 def __init__(self, parent, address, balance, status, **kwargs): 208 super(AddressPopup, self).__init__(**kwargs) 209 self.title = _('Address Details') 210 self.parent_dialog = parent 211 self.app = parent.app # type: ElectrumWindow 212 self.address = address 213 self.status = status 214 self.script_type = self.app.wallet.get_txin_type(self.address) 215 self.balance = self.app.format_amount_and_units(balance) 216 self.address_color, self.address_background_color = address_colors(self.app.wallet, address) 217 self.is_frozen = self.app.wallet.is_frozen_address(address) 218 219 def receive_at(self): 220 self.dismiss() 221 self.parent_dialog.dismiss() 222 self.app.switch_to('receive') 223 # retry until receive_screen is set 224 Clock.schedule_interval(lambda dt: bool(self.app.receive_screen.set_address(self.address) and False) if self.app.receive_screen else True, 0.1) 225 226 def do_export(self, pk_label): 227 self.app.export_private_keys(pk_label, self.address) 228 229 def freeze_address(self): 230 self.is_frozen = not self.is_frozen 231 self.app.wallet.set_frozen_state_of_addresses([self.address], freeze=self.is_frozen) 232 self.parent_dialog.update() 233 234 235class AddressesDialog(Factory.Popup): 236 237 def __init__(self, app: 'ElectrumWindow'): 238 Factory.Popup.__init__(self) 239 self.app = app 240 self.update() 241 242 def get_card(self, addr, balance, is_used, label): 243 ci = {} 244 ci['screen'] = self 245 ci['address'] = addr 246 ci['memo'] = label 247 ci['amount'] = self.app.format_amount_and_units(balance) 248 ci['status'] = _('Used') if is_used else _('Funded') if balance > 0 else _('Unused') 249 ci['is_frozen'] = self.app.wallet.is_frozen_address(addr) 250 return ci 251 252 def update(self): 253 wallet = self.app.wallet 254 if self.show_change == 0: 255 _list = wallet.get_receiving_addresses() 256 elif self.show_change == 1: 257 _list = wallet.get_change_addresses() 258 else: 259 _list = wallet.get_addresses() 260 search = self.message 261 container = self.ids.search_container 262 n = 0 263 cards = [] 264 for address in _list: 265 label = wallet.get_label(address) 266 balance = sum(wallet.get_addr_balance(address)) 267 is_used_and_empty = wallet.is_used(address) and balance == 0 268 if self.show_used == 1 and (balance or is_used_and_empty): 269 continue 270 if self.show_used == 2 and balance == 0: 271 continue 272 if self.show_used == 3 and not is_used_and_empty: 273 continue 274 if self.show_used == 4 and is_used_and_empty: 275 continue 276 card = self.get_card(address, balance, is_used_and_empty, label) 277 if search and not self.ext_search(card, search): 278 continue 279 cards.append(card) 280 n += 1 281 container.data = cards 282 if not n: 283 self.app.show_error('No address matching your search') 284 285 def show_item(self, obj): 286 address = obj.address 287 c, u, x = self.app.wallet.get_addr_balance(address) 288 balance = c + u + x 289 d = AddressPopup(self, address, balance, obj.status) 290 d.open() 291 292 def ext_search(self, card, search): 293 return card['memo'].find(search) >= 0 or card['amount'].find(search) >= 0 294