1
0
Files
electrum/electrum/gui/qt/wizard/wallet.py

703 lines
27 KiB
Python

import os
from PyQt5.QtCore import Qt, QTimer, QRect
from PyQt5.QtGui import QPen, QPainter, QPalette
from PyQt5.QtWidgets import (QApplication, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget,
QFileDialog, QSlider, QGridLayout)
from electrum.bip32 import is_bip32_derivation, BIP32Node
from electrum.daemon import Daemon
from electrum.i18n import _
from electrum.keystore import bip44_derivation, bip39_to_seed
from electrum.storage import StorageReadWriteError
from electrum.util import WalletFileException, get_new_wallet_name
from electrum.wallet import wallet_types
from .wizard import QEAbstractWizard, WizardComponent
from electrum.logging import get_logger
from electrum import WalletStorage, mnemonic, keystore
from electrum.wizard import NewWalletWizard
from ..bip39_recovery_dialog import Bip39RecoveryDialog
from ..password_dialog import PasswordLayout, PW_NEW, MSG_ENTER_PASSWORD
from ..seed_dialog import SeedLayout, MSG_PASSPHRASE_WARN_ISSUE4566, KeysLayout
from ..util import ChoicesLayout, PasswordLineEdit, char_width_in_lineedit, WWLabel, InfoButton, font_height
WIF_HELP_TEXT = (_('WIF keys are typed in Electrum, based on script type.') + '\n\n' +
_('A few examples') + ':\n' +
'p2pkh:KxZcY47uGp9a... \t-> 1DckmggQM...\n' +
'p2wpkh-p2sh:KxZcY47uGp9a... \t-> 3NhNeZQXF...\n' +
'p2wpkh:KxZcY47uGp9a... \t-> bc1q3fjfk...')
class QENewWalletWizard(NewWalletWizard, QEAbstractWizard):
_logger = get_logger(__name__)
# createError = pyqtSignal([str], arguments=["error"])
# createSuccess = pyqtSignal()
def __init__(self, config: 'SimpleConfig', app: QApplication, daemon: Daemon, path, parent=None):
NewWalletWizard.__init__(self, daemon)
QEAbstractWizard.__init__(self, config, app, parent)
self._daemon = daemon
self._path = path
# attach gui classes to views
self.navmap_merge({
'wallet_name': { 'gui': WCWalletName },
'wallet_type': { 'gui': WCWalletType },
'keystore_type': { 'gui': WCKeystoreType },
'create_seed': { 'gui': WCCreateSeed },
'create_ext': { 'gui': WCCreateExt },
'confirm_seed': { 'gui': WCConfirmSeed },
'confirm_ext': { 'gui': WCConfirmExt },
'have_seed': { 'gui': WCHaveSeed },
'bip39_refine': { 'gui': WCBIP39Refine },
'have_master_key': { 'gui': 'WCHaveMasterKey' },
'multisig': { 'gui': WCMultisig },
'multisig_cosigner_keystore': { 'gui': 'WCCosignerKeystore' },
'multisig_cosigner_key': { 'gui': 'WCHaveMasterKey' },
'multisig_cosigner_seed': { 'gui': 'WCHaveSeed' },
'multisig_cosigner_bip39_refine': { 'gui': 'WCBIP39Refine' },
'imported': { 'gui': WCImport },
'wallet_password': { 'gui': WCWalletPassword }
})
# modify default flow, insert seed extension entry/confirm as separate views
self.navmap_merge({
'create_seed': {
'next': lambda d: 'create_ext' if d['seed_extend'] else 'confirm_seed'
},
'create_ext': {
'next': 'confirm_seed',
},
'confirm_seed': {
'next': lambda d: 'confirm_ext' if d['seed_extend'] else self.on_have_or_confirm_seed(d),
'accept': lambda d: None if d['seed_extend'] else self.maybe_master_pubkey(d),
},
'confirm_ext': {
'next': self.on_have_or_confirm_seed,
'accept': self.maybe_master_pubkey,
}
})
# pathChanged = pyqtSignal()
# @pyqtProperty(str, notify=pathChanged)
# def path(self):
# return self._path
#
# @path.setter
# def path(self, path):
# self._path = path
# self.pathChanged.emit()
#
def is_single_password(self):
# TODO: also take into account if possible with existing set of wallets. see qedaemon.py
return self._daemon.config.WALLET_USE_SINGLE_PASSWORD
# @pyqtSlot('QJSValue', result=bool)
# def hasDuplicateMasterKeys(self, js_data):
# self._logger.info('Checking for duplicate masterkeys')
# data = js_data.toVariant()
# return self.has_duplicate_masterkeys(data)
#
# @pyqtSlot('QJSValue', result=bool)
# def hasHeterogeneousMasterKeys(self, js_data):
# self._logger.info('Checking for heterogeneous masterkeys')
# data = js_data.toVariant()
# return self.has_heterogeneous_masterkeys(data)
#
# @pyqtSlot(str, str, result=bool)
# def isMatchingSeed(self, seed, seed_again):
# return mnemonic.is_matching_seed(seed=seed, seed_again=seed_again)
#
def create_storage(self, single_password: str = None):
self._logger.info('Creating wallet from wizard data')
# data = js_data.toVariant()
data = self._current.wizard_data
if self.is_single_password() and single_password:
data['encrypt'] = True
data['password'] = single_password
path = os.path.join(os.path.dirname(self._daemon.config.get_wallet_path()), data['wallet_name'])
try:
super().create_storage(path, data)
# minimally populate self after create
self._password = data['password']
# self.path = path
# self.createSuccess.emit()
return True
except Exception as e:
self._logger.error(f"createStorage errored: {e!r}")
return False
# self.createError.emit(str(e))
class WCWalletName(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Electrum wallet'))
path = wizard._path
if os.path.isdir(path):
raise Exception("wallet path cannot point to a directory")
hbox = QHBoxLayout()
hbox.addWidget(QLabel(_('Wallet') + ':'))
self.name_e = QLineEdit()
hbox.addWidget(self.name_e)
button = QPushButton(_('Choose...'))
hbox.addWidget(button)
self.layout().addLayout(hbox)
msg_label = WWLabel('')
self.layout().addWidget(msg_label)
hbox2 = QHBoxLayout()
pw_e = PasswordLineEdit('', self)
pw_e.setFixedWidth(17 * char_width_in_lineedit())
pw_label = QLabel(_('Password') + ':')
hbox2.addWidget(pw_label)
hbox2.addWidget(pw_e)
hbox2.addStretch()
self.layout().addLayout(hbox2)
self.layout().addSpacing(50)
vbox_create_new = QVBoxLayout()
vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignLeft)
button_create_new = QPushButton(_('Create New Wallet'))
button_create_new.setMinimumWidth(120)
vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignLeft)
widget_create_new = QWidget()
widget_create_new.setLayout(vbox_create_new)
vbox_create_new.setContentsMargins(0, 0, 0, 0)
self.layout().addWidget(widget_create_new)
temp_storage = None # type: Optional[WalletStorage]
wallet_folder = os.path.dirname(path)
def on_choose():
path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder)
if path:
self.name_e.setText(path)
def on_filename(filename):
# FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible
nonlocal temp_storage
temp_storage = None
msg = None
if filename:
path = os.path.join(wallet_folder, filename)
# wallet_from_memory = get_wallet_from_daemon(path)
wallet_from_memory = self.wizard._daemon.get_wallet(path)
try:
if wallet_from_memory:
temp_storage = wallet_from_memory.storage # type: Optional[WalletStorage]
else:
temp_storage = WalletStorage(path)
except (StorageReadWriteError, WalletFileException) as e:
msg = _('Cannot read file') + f'\n{repr(e)}'
except Exception as e:
self.logger.exception('')
msg = _('Cannot read file') + f'\n{repr(e)}'
else:
msg = ""
# self.next_button.setEnabled(temp_storage is not None)
self.valid = temp_storage is not None
user_needs_to_enter_password = False
if temp_storage:
if not temp_storage.file_exists():
msg =_("This file does not exist.") + '\n' \
+ _("Press 'Next' to create this wallet, or choose another file.")
elif not wallet_from_memory:
if temp_storage.is_encrypted_with_user_pw():
msg = _("This file is encrypted with a password.") + '\n' \
+ _('Enter your password or choose another file.')
user_needs_to_enter_password = True
elif temp_storage.is_encrypted_with_hw_device():
msg = _("This file is encrypted using a hardware device.") + '\n' \
+ _("Press 'Next' to choose device to decrypt.")
else:
msg = _("Press 'Next' to open this wallet.")
else:
msg = _("This file is already open in memory.") + "\n" \
+ _("Press 'Next' to create/focus window.")
if msg is None:
msg = _('Cannot read file')
msg_label.setText(msg)
widget_create_new.setVisible(bool(temp_storage and temp_storage.file_exists()))
if user_needs_to_enter_password:
pw_label.show()
pw_e.show()
pw_e.setFocus()
else:
pw_label.hide()
pw_e.hide()
button.clicked.connect(on_choose)
button_create_new.clicked.connect(
lambda: self.name_e.setText(get_new_wallet_name(wallet_folder))) # FIXME get_new_wallet_name might raise
self.name_e.textChanged.connect(on_filename)
self.name_e.setText(os.path.basename(path))
def apply(self):
self.wizard_data['wallet_name'] = self.name_e.text()
class WCWalletType(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Create new wallet'))
message = _('What kind of wallet do you want to create?')
wallet_kinds = [
('standard', _('Standard wallet')),
('2fa', _('Wallet with two-factor authentication')),
('multisig', _('Multi-signature wallet')),
('imported', _('Import Bitcoin addresses or private keys')),
]
choices = [pair for pair in wallet_kinds if pair[0] in wallet_types]
self.c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
self.clayout = ChoicesLayout(message, c_titles)
self.layout().addLayout(self.clayout.layout())
self._valid = True
def apply(self):
self.wizard_data['wallet_type'] = self.c_values[self.clayout.selected_index()]
class WCKeystoreType(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Keystore'))
message = _('Do you want to create a new seed, or to restore a wallet using an existing seed?')
choices = [
('createseed', _('Create a new seed')),
('haveseed', _('I already have a seed')),
('masterkey', _('Use a master key')),
('hardware', _('Use a hardware device'))
]
self.c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
self.clayout = ChoicesLayout(message, c_titles)
self.layout().addLayout(self.clayout.layout())
self._valid = True
def apply(self):
self.wizard_data['keystore_type'] = self.c_values[self.clayout.selected_index()]
class WCCreateSeed(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Wallet Seed'))
self._busy = True
self.seed_type = 'standard' if self.wizard.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit'
self.slayout = None
self.seed = None
def on_ready(self):
QTimer.singleShot(1, self.create_seed)
def apply(self):
if self.slayout:
self.wizard_data['seed'] = self.seed
self.wizard_data['seed_type'] = self.seed_type
self.wizard_data['seed_extend'] = self.slayout.is_ext
self.wizard_data['seed_variant'] = 'electrum'
self.wizard_data['seed_extra_words'] = '' # empty default
def create_seed(self):
self.busy = True
self.seed = mnemonic.Mnemonic('en').make_seed(seed_type=self.seed_type)
self.slayout = SeedLayout(
title=_('Your wallet generation seed is:'),
seed=self.seed,
options=['ext'],
msg=True,
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
self.busy = False
self.valid = True
class WCConfirmSeed(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Confirm Seed'))
message = ' '.join([
_('Your seed is important!'),
_('If you lose your seed, your money will be permanently lost.'),
_('To make sure that you have properly saved your seed, please retype it here.')
])
self.layout().addWidget(WWLabel(message))
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
self.next_button = Hack()
self.slayout = SeedLayout(
is_seed=lambda x: x == self.wizard_data['seed'],
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
def apply(self):
pass
class WCCreateExt(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Seed Extension'))
message = '\n'.join([
_('You may extend your seed with custom words.'),
_('Your seed extension must be saved together with your seed.'),
])
warning = '\n'.join([
_('Note that this is NOT your encryption password.'),
_('If you do not know what this is, leave this field empty.'),
])
self.ext_edit = SeedExtensionEdit(self, message=message, warning=warning)
self.ext_edit.textEdited.connect(self.on_text_edited)
self.layout().addWidget(self.ext_edit)
self.layout().addStretch(1)
def on_text_edited(self, text):
self.ext_edit.warn_issue4566 = self.wizard_data['keystore_type'] == 'haveseed' and \
self.wizard_data['seed_type'] == 'bip39'
self.valid = len(text) > 0
def apply(self):
self.wizard_data['seed_extra_words'] = self.ext_edit.text()
class WCConfirmExt(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Confirm Seed Extension'))
message = '\n'.join([
_('Your seed extension must be saved together with your seed.'),
_('Please type it here.'),
])
self.ext_edit = SeedExtensionEdit(self, message=message)
self.ext_edit.textEdited.connect(self.on_text_edited)
self.layout().addWidget(self.ext_edit)
self.layout().addStretch(1)
def on_text_edited(self, text):
self.valid = text == self.wizard_data['seed_extra_words']
def apply(self):
pass
class WCHaveSeed(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Enter Seed'))
self.layout().addWidget(WWLabel(_('Please enter your seed phrase in order to restore your wallet.')))
# TODO: SeedLayout assumes too much in parent, refactor SeedLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
self.next_button = Hack()
def on_ready(self):
options = ['ext'] if self.wizard_data['wallet_type'] == '2fa' else ['ext', 'bip39', 'slip39']
self.slayout = SeedLayout(
is_seed=self.is_seed,
options=options,
parent=self,
config=self.wizard.config,
)
self.layout().addLayout(self.slayout)
def is_seed(self, x):
if self.wizard_data['wallet_type'] == 'standard':
return mnemonic.is_seed(x)
else:
return mnemonic.seed_type(x) in ['standard', 'segwit']
def apply(self):
self.wizard_data['seed'] = self.slayout.get_seed()
self.wizard_data['seed_variant'] = self.slayout.seed_type
if self.slayout.seed_type == 'electrum':
self.wizard_data['seed_type'] = mnemonic.seed_type(self.slayout.get_seed())
else:
self.wizard_data['seed_type'] = self.slayout.seed_type
self.wizard_data['seed_extend'] = self.slayout.is_ext
self.wizard_data['seed_extra_words'] = '' # empty default
class WCBIP39Refine(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Script type and Derivation path'))
def on_ready(self):
if self.wizard_data['wallet_type'] == 'multisig':
raise Exception('NYI')
message1 = _('Choose the type of addresses in your wallet.')
message2 = ' '.join([
_('You can override the suggested derivation path.'),
_('If you are not sure what this is, leave this field unchanged.')
])
hide_choices = False
default_choice_idx = 2
choices = [
('standard', 'legacy (p2pkh)', bip44_derivation(0, bip43_purpose=44)),
('p2wpkh-p2sh', 'p2sh-segwit (p2wpkh-p2sh)', bip44_derivation(0, bip43_purpose=49)),
('p2wpkh', 'native segwit (p2wpkh)', bip44_derivation(0, bip43_purpose=84)),
]
passphrase = self.wizard_data['seed_extra_words'] if self.wizard_data['seed_extend'] else ''
root_seed = bip39_to_seed(self.wizard_data['seed'], passphrase)
def get_account_xpub(account_path):
root_node = BIP32Node.from_rootseed(root_seed, xtype="standard")
account_node = root_node.subkey_at_private_derivation(account_path)
account_xpub = account_node.to_xpub()
return account_xpub
if get_account_xpub:
button = QPushButton(_("Detect Existing Accounts"))
def on_account_select(account):
script_type = account["script_type"]
if script_type == "p2pkh":
script_type = "standard"
button_index = self.c_values.index(script_type)
button = self.clayout.group.buttons()[button_index]
button.setChecked(True)
self.derivation_path_edit.setText(account["derivation_path"])
button.clicked.connect(lambda: Bip39RecoveryDialog(self, get_account_xpub, on_account_select))
self.layout().addWidget(button, alignment=Qt.AlignLeft)
self.layout().addWidget(QLabel(_("Or")))
self.c_values = [x[0] for x in choices]
c_titles = [x[1] for x in choices]
c_default_text = [x[2] for x in choices]
def on_choice_click(clayout):
idx = clayout.selected_index()
self.derivation_path_edit.setText(c_default_text[idx])
self.clayout = ChoicesLayout(message1, c_titles, on_choice_click,
checked_index=default_choice_idx)
if not hide_choices:
self.layout().addLayout(self.clayout.layout())
self.layout().addWidget(WWLabel(message2))
self.derivation_path_edit = QLineEdit()
self.derivation_path_edit.textChanged.connect(self.validate)
on_choice_click(self.clayout) # set default value for derivation path
self.layout().addWidget(self.derivation_path_edit)
def validate(self):
self.valid = is_bip32_derivation(self.derivation_path_edit.text())
def apply(self):
self.wizard_data['script_type'] = self.c_values[self.clayout.selected_index()]
self.wizard_data['derivation_path'] = str(self.derivation_path_edit.text())
class WCMultisig(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Multi-Signature Wallet'))
def on_m(m):
m_label.setText(_('Require {0} signatures').format(m))
cw.set_m(m)
backup_warning_label.setVisible(cw.m != cw.n)
def on_n(n):
n_label.setText(_('From {0} cosigners').format(n))
cw.set_n(n)
m_edit.setMaximum(n)
backup_warning_label.setVisible(cw.m != cw.n)
backup_warning_label = WWLabel(_('Warning: to be able to restore a multisig wallet, '
'you should include the master public key for each cosigner '
'in all of your backups.'))
cw = CosignWidget(2, 2)
m_label = QLabel()
n_label = QLabel()
m_edit = QSlider(Qt.Horizontal, self)
m_edit.setMinimum(1)
m_edit.setMaximum(2)
m_edit.setValue(2)
m_edit.valueChanged.connect(on_m)
on_m(m_edit.value())
n_edit = QSlider(Qt.Horizontal, self)
n_edit.setMinimum(2)
n_edit.setMaximum(15)
n_edit.setValue(2)
n_edit.valueChanged.connect(on_n)
on_n(n_edit.value())
grid = QGridLayout()
grid.addWidget(n_label, 0, 0)
grid.addWidget(n_edit, 0, 1)
grid.addWidget(m_label, 1, 0)
grid.addWidget(m_edit, 1, 1)
self.layout().addWidget(cw)
self.layout().addWidget(WWLabel(_('Choose the number of signatures needed to unlock funds in your wallet:')))
self.layout().addLayout(grid)
self.layout().addSpacing(2 * char_width_in_lineedit())
self.layout().addWidget(backup_warning_label)
self.n_edit = n_edit
self.m_edit = m_edit
self._valid = True
def apply(self):
self.wizard_data['multisig_participants'] = int(self.n_edit.value())
self.wizard_data['multisig_signatures'] = int(self.m_edit.value())
self.wizard_data['multisig_cosigner_data'] = {}
class WCImport(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Import Bitcoin Addresses'))
message = _(
'Enter a list of Bitcoin addresses (this will create a watching-only wallet), or a list of private keys.')
# self.add_xpub_dialog(title=title, message=message, run_next=self.on_import,
# is_valid=v, allow_multi=True, show_wif_help=True)
header_layout = QHBoxLayout()
label = WWLabel(message)
label.setMinimumWidth(400)
header_layout.addWidget(label)
header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
# TODO: KeysLayout assumes too much in parent, refactor KeysLayout
# for now, fake parent.next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
def setToolTip(self2, b):
pass
self.next_button = Hack()
v = lambda x: keystore.is_address_list(x) or keystore.is_private_key_list(x, raise_on_error=True)
self.slayout = KeysLayout(parent=self, header_layout=header_layout, is_valid=v,
allow_multi=True, config=self.wizard.config)
self.layout().addLayout(self.slayout)
def apply(self):
text = self.slayout.get_text()
if keystore.is_address_list(text):
self.wizard_data['address_list'] = text
elif keystore.is_private_key_list(text):
self.wizard_data['private_key_list'] = text
class WCWalletPassword(WizardComponent):
def __init__(self, parent, wizard):
WizardComponent.__init__(self, parent, wizard, title=_('Wallet Password'))
# TODO: PasswordLayout assumes a button, refactor PasswordLayout
# for now, fake next_button.setEnabled
class Hack:
def setEnabled(self2, b):
self.valid = b
self.next_button = Hack()
self.pw_layout = PasswordLayout(
msg=MSG_ENTER_PASSWORD,
kind=PW_NEW,
OK_button=self.next_button,
# force_disable_encrypt_cb=force_disable_encrypt_cb
)
self.pw_layout.encrypt_cb.setChecked(True)
self.layout().addLayout(self.pw_layout.layout())
self.layout().addStretch(1)
def apply(self):
self.wizard_data['password'] = self.pw_layout.new_password()
self.wizard_data['encrypt'] = self.pw_layout.encrypt_cb.isChecked()
class SeedExtensionEdit(QWidget):
def __init__(self, parent, *, message: str = None, warning: str = None, warn_issue4566: bool = False):
super().__init__(parent)
self.warn_issue4566 = warn_issue4566
layout = QVBoxLayout()
self.setLayout(layout)
if message:
layout.addWidget(WWLabel(message))
self.line = QLineEdit()
layout.addWidget(self.line)
def f(text):
if self.warn_issue4566:
text_whitespace_normalised = ' '.join(text.split())
warn_issue4566_label.setVisible(text != text_whitespace_normalised)
self.line.textEdited.connect(f)
if warning:
layout.addWidget(WWLabel(warning))
warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566)
warn_issue4566_label.setVisible(False)
layout.addWidget(warn_issue4566_label)
# expose textEdited signal and text() func to widget
self.textEdited = self.line.textEdited
self.text = self.line.text
class CosignWidget(QWidget):
def __init__(self, m, n):
QWidget.__init__(self)
self.size = max(120, 9 * font_height())
self.R = QRect(0, 0, self.size, self.size)
self.setGeometry(self.R)
self.setMinimumHeight(self.size)
self.setMaximumHeight(self.size)
self.m = m
self.n = n
def set_n(self, n):
self.n = n
self.update()
def set_m(self, m):
self.m = m
self.update()
def paintEvent(self, event):
bgcolor = self.palette().color(QPalette.Background)
pen = QPen(bgcolor, 7, Qt.SolidLine)
qp = QPainter()
qp.begin(self)
qp.setPen(pen)
qp.setRenderHint(QPainter.Antialiasing)
qp.setBrush(Qt.gray)
for i in range(self.n):
alpha = int(16 * 360 * i/self.n)
alpha2 = int(16 * 360 * 1/self.n)
qp.setBrush(Qt.green if i < self.m else Qt.gray)
qp.drawPie(self.R, alpha, alpha2)
qp.end()