settings_dialog.py (25202B)
1 #!/usr/bin/env python 2 # 3 # Electrum - lightweight Bitcoin client 4 # Copyright (C) 2012 thomasv@gitorious 5 # 6 # Permission is hereby granted, free of charge, to any person 7 # obtaining a copy of this software and associated documentation files 8 # (the "Software"), to deal in the Software without restriction, 9 # including without limitation the rights to use, copy, modify, merge, 10 # publish, distribute, sublicense, and/or sell copies of the Software, 11 # and to permit persons to whom the Software is furnished to do so, 12 # subject to the following conditions: 13 # 14 # The above copyright notice and this permission notice shall be 15 # included in all copies or substantial portions of the Software. 16 # 17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 # SOFTWARE. 25 26 import ast 27 from typing import Optional, TYPE_CHECKING 28 29 from PyQt5.QtCore import Qt 30 from PyQt5.QtWidgets import (QComboBox, QTabWidget, 31 QSpinBox, QFileDialog, QCheckBox, QLabel, 32 QVBoxLayout, QGridLayout, QLineEdit, 33 QPushButton, QWidget, QHBoxLayout) 34 35 from electrum.i18n import _ 36 from electrum import util, coinchooser, paymentrequest 37 from electrum.util import base_units_list 38 39 from .util import (ColorScheme, WindowModalDialog, HelpLabel, Buttons, 40 CloseButton) 41 42 from electrum.i18n import languages 43 from electrum import qrscanner 44 45 if TYPE_CHECKING: 46 from electrum.simple_config import SimpleConfig 47 from .main_window import ElectrumWindow 48 49 50 class SettingsDialog(WindowModalDialog): 51 52 def __init__(self, parent: 'ElectrumWindow', config: 'SimpleConfig'): 53 WindowModalDialog.__init__(self, parent, _('Preferences')) 54 self.config = config 55 self.window = parent 56 self.need_restart = False 57 self.fx = self.window.fx 58 self.wallet = self.window.wallet 59 60 vbox = QVBoxLayout() 61 tabs = QTabWidget() 62 gui_widgets = [] 63 tx_widgets = [] 64 oa_widgets = [] 65 66 # language 67 lang_help = _('Select which language is used in the GUI (after restart).') 68 lang_label = HelpLabel(_('Language') + ':', lang_help) 69 lang_combo = QComboBox() 70 lang_combo.addItems(list(languages.values())) 71 lang_keys = list(languages.keys()) 72 lang_cur_setting = self.config.get("language", '') 73 try: 74 index = lang_keys.index(lang_cur_setting) 75 except ValueError: # not in list 76 index = 0 77 lang_combo.setCurrentIndex(index) 78 if not self.config.is_modifiable('language'): 79 for w in [lang_combo, lang_label]: w.setEnabled(False) 80 def on_lang(x): 81 lang_request = list(languages.keys())[lang_combo.currentIndex()] 82 if lang_request != self.config.get('language'): 83 self.config.set_key("language", lang_request, True) 84 self.need_restart = True 85 lang_combo.currentIndexChanged.connect(on_lang) 86 gui_widgets.append((lang_label, lang_combo)) 87 88 nz_help = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"') 89 nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help) 90 nz = QSpinBox() 91 nz.setMinimum(0) 92 nz.setMaximum(self.config.decimal_point) 93 nz.setValue(self.config.num_zeros) 94 if not self.config.is_modifiable('num_zeros'): 95 for w in [nz, nz_label]: w.setEnabled(False) 96 def on_nz(): 97 value = nz.value() 98 if self.config.num_zeros != value: 99 self.config.num_zeros = value 100 self.config.set_key('num_zeros', value, True) 101 self.window.history_list.update() 102 self.window.address_list.update() 103 nz.valueChanged.connect(on_nz) 104 gui_widgets.append((nz_label, nz)) 105 106 use_rbf = bool(self.config.get('use_rbf', True)) 107 use_rbf_cb = QCheckBox(_('Use Replace-By-Fee')) 108 use_rbf_cb.setChecked(use_rbf) 109 use_rbf_cb.setToolTip( 110 _('If you check this box, your transactions will be marked as non-final,') + '\n' + \ 111 _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \ 112 _('Note that some merchants do not accept non-final transactions until they are confirmed.')) 113 def on_use_rbf(x): 114 self.config.set_key('use_rbf', bool(x)) 115 batch_rbf_cb.setEnabled(bool(x)) 116 use_rbf_cb.stateChanged.connect(on_use_rbf) 117 tx_widgets.append((use_rbf_cb, None)) 118 119 batch_rbf_cb = QCheckBox(_('Batch RBF transactions')) 120 batch_rbf_cb.setChecked(bool(self.config.get('batch_rbf', False))) 121 batch_rbf_cb.setEnabled(use_rbf) 122 batch_rbf_cb.setToolTip( 123 _('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \ 124 _('This will save fees.')) 125 def on_batch_rbf(x): 126 self.config.set_key('batch_rbf', bool(x)) 127 batch_rbf_cb.stateChanged.connect(on_batch_rbf) 128 tx_widgets.append((batch_rbf_cb, None)) 129 130 # lightning 131 lightning_widgets = [] 132 133 help_gossip = _("""If this option is enabled, Electrum will download the network 134 channels graph and compute payment path locally, instead of using trampoline payments. """) 135 gossip_cb = QCheckBox(_("Download network graph")) 136 gossip_cb.setToolTip(help_gossip) 137 gossip_cb.setChecked(bool(self.config.get('use_gossip', False))) 138 def on_gossip_checked(x): 139 use_gossip = bool(x) 140 self.config.set_key('use_gossip', use_gossip) 141 if use_gossip: 142 self.window.network.start_gossip() 143 else: 144 self.window.network.run_from_another_thread( 145 self.window.network.stop_gossip()) 146 util.trigger_callback('ln_gossip_sync_progress') 147 # FIXME: update all wallet windows 148 util.trigger_callback('channels_updated', self.wallet) 149 gossip_cb.stateChanged.connect(on_gossip_checked) 150 lightning_widgets.append((gossip_cb, None)) 151 152 help_local_wt = _("""If this option is checked, Electrum will 153 run a local watchtower and protect your channels even if your wallet is not 154 open. For this to work, your computer needs to be online regularly.""") 155 local_wt_cb = QCheckBox(_("Run a local watchtower")) 156 local_wt_cb.setToolTip(help_local_wt) 157 local_wt_cb.setChecked(bool(self.config.get('run_local_watchtower', False))) 158 def on_local_wt_checked(x): 159 self.config.set_key('run_local_watchtower', bool(x)) 160 local_wt_cb.stateChanged.connect(on_local_wt_checked) 161 lightning_widgets.append((local_wt_cb, None)) 162 163 help_persist = _("""If this option is checked, Electrum will persist after 164 you close all your wallet windows, and the Electrum icon will be visible in the taskbar. 165 Use this if you want your local watchtower to keep running after you close your wallet.""") 166 persist_cb = QCheckBox(_("Persist after all windows are closed")) 167 persist_cb.setToolTip(help_persist) 168 persist_cb.setChecked(bool(self.config.get('persist_daemon', False))) 169 def on_persist_checked(x): 170 self.config.set_key('persist_daemon', bool(x)) 171 persist_cb.stateChanged.connect(on_persist_checked) 172 lightning_widgets.append((persist_cb, None)) 173 174 help_remote_wt = _("""To use a remote watchtower, enter the corresponding URL here""") 175 remote_wt_cb = QCheckBox(_("Use a remote watchtower")) 176 remote_wt_cb.setToolTip(help_remote_wt) 177 remote_wt_cb.setChecked(bool(self.config.get('use_watchtower', False))) 178 def on_remote_wt_checked(x): 179 self.config.set_key('use_watchtower', bool(x)) 180 self.watchtower_url_e.setEnabled(bool(x)) 181 remote_wt_cb.stateChanged.connect(on_remote_wt_checked) 182 watchtower_url = self.config.get('watchtower_url') 183 self.watchtower_url_e = QLineEdit(watchtower_url) 184 self.watchtower_url_e.setEnabled(self.config.get('use_watchtower', False)) 185 def on_wt_url(): 186 url = self.watchtower_url_e.text() or None 187 watchtower_url = self.config.set_key('watchtower_url', url) 188 self.watchtower_url_e.editingFinished.connect(on_wt_url) 189 lightning_widgets.append((remote_wt_cb, self.watchtower_url_e)) 190 191 msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\ 192 + _('The following alias providers are available:') + '\n'\ 193 + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\ 194 + 'For more information, see https://openalias.org' 195 alias_label = HelpLabel(_('OpenAlias') + ':', msg) 196 alias = self.config.get('alias','') 197 self.alias_e = QLineEdit(alias) 198 self.set_alias_color() 199 self.alias_e.editingFinished.connect(self.on_alias_edit) 200 oa_widgets.append((alias_label, self.alias_e)) 201 202 # units 203 units = base_units_list 204 msg = (_('Base unit of your wallet.') 205 + '\n1 BTC = 1000 mBTC. 1 mBTC = 1000 bits. 1 bit = 100 sat.\n' 206 + _('This setting affects the Send tab, and all balance related fields.')) 207 unit_label = HelpLabel(_('Base unit') + ':', msg) 208 unit_combo = QComboBox() 209 unit_combo.addItems(units) 210 unit_combo.setCurrentIndex(units.index(self.window.base_unit())) 211 def on_unit(x, nz): 212 unit_result = units[unit_combo.currentIndex()] 213 if self.window.base_unit() == unit_result: 214 return 215 edits = self.window.amount_e, self.window.receive_amount_e 216 amounts = [edit.get_amount() for edit in edits] 217 self.config.set_base_unit(unit_result) 218 nz.setMaximum(self.config.decimal_point) 219 self.window.update_tabs() 220 for edit, amount in zip(edits, amounts): 221 edit.setAmount(amount) 222 self.window.update_status() 223 unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz)) 224 gui_widgets.append((unit_label, unit_combo)) 225 226 system_cameras = qrscanner._find_system_cameras() 227 qr_combo = QComboBox() 228 qr_combo.addItem("Default","default") 229 for camera, device in system_cameras.items(): 230 qr_combo.addItem(camera, device) 231 #combo.addItem("Manually specify a device", config.get("video_device")) 232 index = qr_combo.findData(self.config.get("video_device")) 233 qr_combo.setCurrentIndex(index) 234 msg = _("Install the zbar package to enable this.") 235 qr_label = HelpLabel(_('Video Device') + ':', msg) 236 qr_combo.setEnabled(qrscanner.libzbar is not None) 237 on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), True) 238 qr_combo.currentIndexChanged.connect(on_video_device) 239 gui_widgets.append((qr_label, qr_combo)) 240 241 colortheme_combo = QComboBox() 242 colortheme_combo.addItem(_('Light'), 'default') 243 colortheme_combo.addItem(_('Dark'), 'dark') 244 index = colortheme_combo.findData(self.config.get('qt_gui_color_theme', 'default')) 245 colortheme_combo.setCurrentIndex(index) 246 colortheme_label = QLabel(_('Color theme') + ':') 247 def on_colortheme(x): 248 self.config.set_key('qt_gui_color_theme', colortheme_combo.itemData(x), True) 249 self.need_restart = True 250 colortheme_combo.currentIndexChanged.connect(on_colortheme) 251 gui_widgets.append((colortheme_label, colortheme_combo)) 252 253 updatecheck_cb = QCheckBox(_("Automatically check for software updates")) 254 updatecheck_cb.setChecked(bool(self.config.get('check_updates', False))) 255 def on_set_updatecheck(v): 256 self.config.set_key('check_updates', v == Qt.Checked, save=True) 257 updatecheck_cb.stateChanged.connect(on_set_updatecheck) 258 gui_widgets.append((updatecheck_cb, None)) 259 260 filelogging_cb = QCheckBox(_("Write logs to file")) 261 filelogging_cb.setChecked(bool(self.config.get('log_to_file', False))) 262 def on_set_filelogging(v): 263 self.config.set_key('log_to_file', v == Qt.Checked, save=True) 264 self.need_restart = True 265 filelogging_cb.stateChanged.connect(on_set_filelogging) 266 filelogging_cb.setToolTip(_('Debug logs can be persisted to disk. These are useful for troubleshooting.')) 267 gui_widgets.append((filelogging_cb, None)) 268 269 preview_cb = QCheckBox(_('Advanced preview')) 270 preview_cb.setChecked(bool(self.config.get('advanced_preview', False))) 271 preview_cb.setToolTip(_("Open advanced transaction preview dialog when 'Pay' is clicked.")) 272 def on_preview(x): 273 self.config.set_key('advanced_preview', x == Qt.Checked) 274 preview_cb.stateChanged.connect(on_preview) 275 tx_widgets.append((preview_cb, None)) 276 277 usechange_cb = QCheckBox(_('Use change addresses')) 278 usechange_cb.setChecked(self.window.wallet.use_change) 279 if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False) 280 def on_usechange(x): 281 usechange_result = x == Qt.Checked 282 if self.window.wallet.use_change != usechange_result: 283 self.window.wallet.use_change = usechange_result 284 self.window.wallet.db.put('use_change', self.window.wallet.use_change) 285 multiple_cb.setEnabled(self.window.wallet.use_change) 286 usechange_cb.stateChanged.connect(on_usechange) 287 usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.')) 288 tx_widgets.append((usechange_cb, None)) 289 290 def on_multiple(x): 291 multiple = x == Qt.Checked 292 if self.wallet.multiple_change != multiple: 293 self.wallet.multiple_change = multiple 294 self.wallet.db.put('multiple_change', multiple) 295 multiple_change = self.wallet.multiple_change 296 multiple_cb = QCheckBox(_('Use multiple change addresses')) 297 multiple_cb.setEnabled(self.wallet.use_change) 298 multiple_cb.setToolTip('\n'.join([ 299 _('In some cases, use up to 3 change addresses in order to break ' 300 'up large coin amounts and obfuscate the recipient address.'), 301 _('This may result in higher transactions fees.') 302 ])) 303 multiple_cb.setChecked(multiple_change) 304 multiple_cb.stateChanged.connect(on_multiple) 305 tx_widgets.append((multiple_cb, None)) 306 307 def fmt_docs(key, klass): 308 lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")] 309 return '\n'.join([key, "", " ".join(lines)]) 310 311 choosers = sorted(coinchooser.COIN_CHOOSERS.keys()) 312 if len(choosers) > 1: 313 chooser_name = coinchooser.get_name(self.config) 314 msg = _('Choose coin (UTXO) selection method. The following are available:\n\n') 315 msg += '\n\n'.join(fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items()) 316 chooser_label = HelpLabel(_('Coin selection') + ':', msg) 317 chooser_combo = QComboBox() 318 chooser_combo.addItems(choosers) 319 i = choosers.index(chooser_name) if chooser_name in choosers else 0 320 chooser_combo.setCurrentIndex(i) 321 def on_chooser(x): 322 chooser_name = choosers[chooser_combo.currentIndex()] 323 self.config.set_key('coin_chooser', chooser_name) 324 chooser_combo.currentIndexChanged.connect(on_chooser) 325 tx_widgets.append((chooser_label, chooser_combo)) 326 327 def on_unconf(x): 328 self.config.set_key('confirmed_only', bool(x)) 329 conf_only = bool(self.config.get('confirmed_only', False)) 330 unconf_cb = QCheckBox(_('Spend only confirmed coins')) 331 unconf_cb.setToolTip(_('Spend only confirmed inputs.')) 332 unconf_cb.setChecked(conf_only) 333 unconf_cb.stateChanged.connect(on_unconf) 334 tx_widgets.append((unconf_cb, None)) 335 336 def on_outrounding(x): 337 self.config.set_key('coin_chooser_output_rounding', bool(x)) 338 enable_outrounding = bool(self.config.get('coin_chooser_output_rounding', True)) 339 outrounding_cb = QCheckBox(_('Enable output value rounding')) 340 outrounding_cb.setToolTip( 341 _('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' + 342 _('This might improve your privacy somewhat.') + '\n' + 343 _('If enabled, at most 100 satoshis might be lost due to this, per transaction.')) 344 outrounding_cb.setChecked(enable_outrounding) 345 outrounding_cb.stateChanged.connect(on_outrounding) 346 tx_widgets.append((outrounding_cb, None)) 347 348 block_explorers = sorted(util.block_explorer_info().keys()) 349 BLOCK_EX_CUSTOM_ITEM = _("Custom URL") 350 if BLOCK_EX_CUSTOM_ITEM in block_explorers: # malicious translation? 351 block_explorers.remove(BLOCK_EX_CUSTOM_ITEM) 352 block_explorers.append(BLOCK_EX_CUSTOM_ITEM) 353 msg = _('Choose which online block explorer to use for functions that open a web browser') 354 block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg) 355 block_ex_combo = QComboBox() 356 block_ex_custom_e = QLineEdit(self.config.get('block_explorer_custom') or '') 357 block_ex_combo.addItems(block_explorers) 358 block_ex_combo.setCurrentIndex( 359 block_ex_combo.findText(util.block_explorer(self.config) or BLOCK_EX_CUSTOM_ITEM)) 360 def showhide_block_ex_custom_e(): 361 block_ex_custom_e.setVisible(block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM) 362 showhide_block_ex_custom_e() 363 def on_be_combo(x): 364 if block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM: 365 on_be_edit() 366 else: 367 be_result = block_explorers[block_ex_combo.currentIndex()] 368 self.config.set_key('block_explorer_custom', None, False) 369 self.config.set_key('block_explorer', be_result, True) 370 showhide_block_ex_custom_e() 371 block_ex_combo.currentIndexChanged.connect(on_be_combo) 372 def on_be_edit(): 373 val = block_ex_custom_e.text() 374 try: 375 val = ast.literal_eval(val) # to also accept tuples 376 except: 377 pass 378 self.config.set_key('block_explorer_custom', val) 379 block_ex_custom_e.editingFinished.connect(on_be_edit) 380 block_ex_hbox = QHBoxLayout() 381 block_ex_hbox.setContentsMargins(0, 0, 0, 0) 382 block_ex_hbox.setSpacing(0) 383 block_ex_hbox.addWidget(block_ex_combo) 384 block_ex_hbox.addWidget(block_ex_custom_e) 385 block_ex_hbox_w = QWidget() 386 block_ex_hbox_w.setLayout(block_ex_hbox) 387 tx_widgets.append((block_ex_label, block_ex_hbox_w)) 388 389 # Fiat Currency 390 hist_checkbox = QCheckBox() 391 hist_capgains_checkbox = QCheckBox() 392 fiat_address_checkbox = QCheckBox() 393 ccy_combo = QComboBox() 394 ex_combo = QComboBox() 395 396 def update_currencies(): 397 if not self.window.fx: return 398 currencies = sorted(self.fx.get_currencies(self.fx.get_history_config())) 399 ccy_combo.clear() 400 ccy_combo.addItems([_('None')] + currencies) 401 if self.fx.is_enabled(): 402 ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency())) 403 404 def update_history_cb(): 405 if not self.fx: return 406 hist_checkbox.setChecked(self.fx.get_history_config()) 407 hist_checkbox.setEnabled(self.fx.is_enabled()) 408 409 def update_fiat_address_cb(): 410 if not self.fx: return 411 fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config()) 412 413 def update_history_capgains_cb(): 414 if not self.fx: return 415 hist_capgains_checkbox.setChecked(self.fx.get_history_capital_gains_config()) 416 hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked()) 417 418 def update_exchanges(): 419 if not self.fx: return 420 b = self.fx.is_enabled() 421 ex_combo.setEnabled(b) 422 if b: 423 h = self.fx.get_history_config() 424 c = self.fx.get_currency() 425 exchanges = self.fx.get_exchanges_by_ccy(c, h) 426 else: 427 exchanges = self.fx.get_exchanges_by_ccy('USD', False) 428 ex_combo.blockSignals(True) 429 ex_combo.clear() 430 ex_combo.addItems(sorted(exchanges)) 431 ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange())) 432 ex_combo.blockSignals(False) 433 434 def on_currency(hh): 435 if not self.fx: return 436 b = bool(ccy_combo.currentIndex()) 437 ccy = str(ccy_combo.currentText()) if b else None 438 self.fx.set_enabled(b) 439 if b and ccy != self.fx.ccy: 440 self.fx.set_currency(ccy) 441 update_history_cb() 442 update_exchanges() 443 self.window.update_fiat() 444 445 def on_exchange(idx): 446 exchange = str(ex_combo.currentText()) 447 if self.fx and self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name(): 448 self.fx.set_exchange(exchange) 449 450 def on_history(checked): 451 if not self.fx: return 452 self.fx.set_history_config(checked) 453 update_exchanges() 454 self.window.history_model.refresh('on_history') 455 if self.fx.is_enabled() and checked: 456 self.fx.trigger_update() 457 update_history_capgains_cb() 458 459 def on_history_capgains(checked): 460 if not self.fx: return 461 self.fx.set_history_capital_gains_config(checked) 462 self.window.history_model.refresh('on_history_capgains') 463 464 def on_fiat_address(checked): 465 if not self.fx: return 466 self.fx.set_fiat_address_config(checked) 467 self.window.address_list.refresh_headers() 468 self.window.address_list.update() 469 470 update_currencies() 471 update_history_cb() 472 update_history_capgains_cb() 473 update_fiat_address_cb() 474 update_exchanges() 475 ccy_combo.currentIndexChanged.connect(on_currency) 476 hist_checkbox.stateChanged.connect(on_history) 477 hist_capgains_checkbox.stateChanged.connect(on_history_capgains) 478 fiat_address_checkbox.stateChanged.connect(on_fiat_address) 479 ex_combo.currentIndexChanged.connect(on_exchange) 480 481 fiat_widgets = [] 482 fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo)) 483 fiat_widgets.append((QLabel(_('Source')), ex_combo)) 484 fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox)) 485 fiat_widgets.append((QLabel(_('Show capital gains in history')), hist_capgains_checkbox)) 486 fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')), fiat_address_checkbox)) 487 488 tabs_info = [ 489 (gui_widgets, _('General')), 490 (tx_widgets, _('Transactions')), 491 (lightning_widgets, _('Lightning')), 492 (fiat_widgets, _('Fiat')), 493 (oa_widgets, _('OpenAlias')), 494 ] 495 for widgets, name in tabs_info: 496 tab = QWidget() 497 tab_vbox = QVBoxLayout(tab) 498 grid = QGridLayout() 499 for a,b in widgets: 500 i = grid.rowCount() 501 if b: 502 if a: 503 grid.addWidget(a, i, 0) 504 grid.addWidget(b, i, 1) 505 else: 506 grid.addWidget(a, i, 0, 1, 2) 507 tab_vbox.addLayout(grid) 508 tab_vbox.addStretch(1) 509 tabs.addTab(tab, name) 510 511 vbox.addWidget(tabs) 512 vbox.addStretch(1) 513 vbox.addLayout(Buttons(CloseButton(self))) 514 self.setLayout(vbox) 515 516 def set_alias_color(self): 517 if not self.config.get('alias'): 518 self.alias_e.setStyleSheet("") 519 return 520 if self.window.alias_info: 521 alias_addr, alias_name, validated = self.window.alias_info 522 self.alias_e.setStyleSheet((ColorScheme.GREEN if validated else ColorScheme.RED).as_stylesheet(True)) 523 else: 524 self.alias_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True)) 525 526 def on_alias_edit(self): 527 self.alias_e.setStyleSheet("") 528 alias = str(self.alias_e.text()) 529 self.config.set_key('alias', alias, True) 530 if alias: 531 self.window.fetch_alias()