Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cebash/sakia
  • santiago/sakia
  • jonas/sakia
3 results
Show changes
Showing
with 2946 additions and 0 deletions
from PyQt5.QtCore import QObject, QCoreApplication
from .table_model import HistoryTableModel
from PyQt5.QtCore import Qt, QDateTime, QTime, pyqtSignal, QModelIndex
from sakia.errors import NoPeerAvailable
from duniterpy.api import errors
from sakia.data.entities import Identity
import logging
class TxHistoryModel(QObject):
"""
The model of Navigation component
"""
def __init__(
self,
parent,
app,
connection,
blockchain_service,
identities_service,
transactions_service,
sources_service,
):
"""
:param sakia.gui.txhistory.TxHistoryParent parent: the parent controller
:param sakia.app.Application app: the app
:param sakia.data.entities.Connection connection: the connection
:param sakia.services.BlockchainService blockchain_service: the blockchain service
:param sakia.services.IdentitiesService identities_service: the identities service
:param sakia.services.TransactionsService transactions_service: the identities service
:param sakia.services.SourcesService sources_service: the sources service
"""
super().__init__(parent)
self.app = app
self.connection = connection
self.blockchain_service = blockchain_service
self.identities_service = identities_service
self.transactions_service = transactions_service
self.sources_service = sources_service
self._model = None
def init_history_table_model(self, ts_from, ts_to):
"""
Generates a history table model
:param int ts_from: date from where to filter tx
:param int ts_to: date to where to filter tx
:return:
"""
self._model = HistoryTableModel(
self,
self.app,
self.connection,
ts_from,
ts_to,
self.identities_service,
self.transactions_service,
)
self._model.init_transfers()
self.app.new_transfer.connect(self._model.init_transfers)
self.app.new_dividend.connect(self._model.init_transfers)
self.app.transaction_state_changed.connect(self._model.change_transfer)
self.app.referential_changed.connect(self._model.modelReset)
return self._model
def change_page(self, page):
self._model.set_current_page(page)
def max_pages(self):
return self._model.pages()
def table_data(self, index):
"""
Gets available table data at given index
:param index:
:return: tuple containing (Identity, Transfer)
"""
if index.isValid() and index.row() < self.table_model.rowCount(QModelIndex()):
pubkeys = self._model.pubkeys(index.row())
identities_or_pubkeys = []
for pubkey in pubkeys:
identity = self.identities_service.get_identity(pubkey)
if identity:
identities_or_pubkeys.append(identity)
else:
identities_or_pubkeys.append(pubkey)
transfer = self._model.transfers_data[index.row()][
self._model.columns_types.index("raw_data")
]
return True, identities_or_pubkeys, transfer
return False, [], None
def minimum_maximum_datetime(self):
"""
Get minimum and maximum datetime
:rtype: Tuple[PyQt5.QtCore.QDateTime, PyQt5.QtCore.QDateTime]
:return: minimum and maximum datetime
"""
minimum_datetime = QDateTime()
minimum_datetime.setTime_t(1488322800) # 28 of february 2017 23:00
tomorrow_datetime = QDateTime().currentDateTime().addDays(1)
return minimum_datetime, tomorrow_datetime
async def received_amount(self, received_list):
"""
Converts a list of transactions to an amount
:param list received_list:
:return: the amount, localized
"""
amount = 0
for r in received_list:
amount += r.metadata["amount"]
localized_amount = await self.app.current_ref.instance(
amount, self.connection.currency, self.app
).localized(True, True)
return localized_amount
def localized_balance(self):
"""
Get the localized amount of the given tx history
:return: the localized amount of given account in given community
:rtype: int
"""
try:
amount = self.sources_service.amount(self.connection.pubkey)
localized_amount = self.app.current_ref.instance(
amount, self.connection.currency, self.app
).localized(True, True)
return localized_amount
except NoPeerAvailable as e:
logging.debug(str(e))
except errors.DuniterError as e:
logging.debug(str(e))
return QCoreApplication.translate("TxHistoryModel", "Loading...")
@property
def table_model(self):
return self._model
def notifications(self):
return self.app.parameters.notifications
import math
import attr
TX_HISTORY_REQUEST = """
SELECT
transactions.ts,
transactions.pubkey,
total_amount((transactions.amount * -1), transactions.amountbase) as amount,
transactions.comment ,
transactions.sha_hash,
transactions.written_on,
transactions.txid,
sources.conditions
FROM
transactions
LEFT JOIN sources ON sources.identifier = transactions.sha_hash and
(sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%")
WHERE
transactions.currency = ?
and transactions.pubkey = ?
AND transactions.ts >= ?
and transactions.ts <= ?
AND transactions.issuers LIKE "%{pubkey}%"
UNION ALL
SELECT
transactions.ts,
transactions.pubkey,
total_amount(transactions.amount, transactions.amountbase) as amount,
transactions.comment ,
transactions.sha_hash,
transactions.written_on,
transactions.txid,
sources.conditions
FROM
transactions
LEFT JOIN sources ON sources.identifier = transactions.sha_hash and
(sources.conditions LIKE "%&&%" OR sources.conditions LIKE "%||%")
WHERE
transactions.currency = ?
and transactions.pubkey = ?
AND transactions.ts >= ?
and transactions.ts <= ?
AND (transactions.receivers LIKE "%{pubkey}%"
OR transactions.sha_hash = ?)
UNION ALL
SELECT
dividends.timestamp as ts,
dividends.pubkey ,
total_amount(dividends.amount, dividends.base) as amount,
NULL as comment,
NULL as sha_hash,
dividends.block_number AS written_on,
0 as txid,
NULL
FROM
dividends
WHERE
dividends.currency = ?
and dividends.pubkey =?
AND dividends.timestamp >= ?
and dividends.timestamp <= ?
"""
PAGE_LENGTH = 50
@attr.s(frozen=True)
class TxHistorySqlAdapter:
_conn = attr.ib() # :type sqlite3.Connection
def _transfers_and_dividends(
self,
currency,
pubkey,
ts_from,
ts_to,
stopline_hash,
offset=0,
limit=1000,
sort_by="currency",
sort_order="ASC",
):
"""
Get all transfers in the database on a given currency from or to a pubkey
:param str pubkey: the criterions of the lookup
:rtype: List[sakia.data.entities.Transaction]
"""
request = (
TX_HISTORY_REQUEST
+ """
ORDER BY {sort_by} {sort_order}, txid {sort_order}
LIMIT {limit} OFFSET {offset}"""
).format(
offset=offset,
limit=limit,
sort_by=sort_by,
sort_order=sort_order,
pubkey=pubkey,
)
c = self._conn.execute(
request,
(
currency,
pubkey,
ts_from,
ts_to,
currency,
pubkey,
ts_from,
ts_to,
stopline_hash,
currency,
pubkey,
ts_from,
ts_to,
),
)
datas = c.fetchall()
if datas:
return datas
return []
def _transfers_and_dividends_count(
self, currency, pubkey, ts_from, ts_to, stopline_hash
):
"""
Get all transfers in the database on a given currency from or to a pubkey
:param str pubkey: the criterions of the lookup
:rtype: List[sakia.data.entities.Transaction]
"""
request = (
"""
SELECT COUNT(*)
FROM (
"""
+ TX_HISTORY_REQUEST
+ ")"
).format(pubkey=pubkey)
c = self._conn.execute(
request,
(
currency,
pubkey,
ts_from,
ts_to,
currency,
pubkey,
ts_from,
ts_to,
stopline_hash,
currency,
pubkey,
ts_from,
ts_to,
),
)
datas = c.fetchone()
if datas:
return datas[0]
return 0
def transfers_and_dividends(
self, currency, pubkey, page, ts_from, ts_to, stopline_hash, sort_by, sort_order
):
"""
Get all transfers and dividends from or to a given pubkey
:param str currency:
:param str pubkey:
:param int page:
:param int ts_from:
:param int ts_to:
:return: the list of Transaction entities
:rtype: List[sakia.data.entities.Transaction]
"""
return self._transfers_and_dividends(
currency,
pubkey,
ts_from,
ts_to,
stopline_hash,
offset=page * PAGE_LENGTH,
limit=PAGE_LENGTH,
sort_by=sort_by,
sort_order=sort_order,
)
def pages(self, currency, pubkey, ts_from, ts_to, stopline_hash):
"""
Get all transfers and dividends from or to a given pubkey
:param str currency:
:param str pubkey:
:param int page:
:param int ts_from:
:param int ts_to:
:return: the list of Transaction entities
:rtype: List[sakia.data.entities.Transaction]
"""
count = self._transfers_and_dividends_count(
currency, pubkey, ts_from, ts_to, stopline_hash
)
return int(count / PAGE_LENGTH)
import datetime
import logging
from PyQt5.QtCore import (
QAbstractTableModel,
Qt,
QVariant,
QSortFilterProxyModel,
QDateTime,
QLocale,
QModelIndex,
QT_TRANSLATE_NOOP,
QCoreApplication,
)
from PyQt5.QtGui import QFont, QColor
from sakia.data.entities import Transaction
from sakia.constants import MAX_CONFIRMATIONS
from sakia.data.processors import BlockchainProcessor
from .sql_adapter import TxHistorySqlAdapter
from sakia.data.repositories import TransactionsRepo, DividendsRepo, SourcesRepo
from sakia.data.entities.transaction import STOPLINE_HASH
class HistoryTableModel(QAbstractTableModel):
"""
A Qt abstract item model to display communities in a tree
"""
DIVIDEND = 32
columns_types = (
"date",
"pubkey",
"amount",
"comment",
"state",
"txid",
"pubkey",
"block_number",
"txhash",
"raw_data",
"conditions",
)
columns_to_sql = {
"date": "ts",
"pubkey": "pubkey",
"amount": "amount",
"comment": "comment",
}
columns_headers = (
QT_TRANSLATE_NOOP("HistoryTableModel", "Date"),
QT_TRANSLATE_NOOP("HistoryTableModel", "Public key"),
QT_TRANSLATE_NOOP("HistoryTableModel", "Amount"),
QT_TRANSLATE_NOOP("HistoryTableModel", "Comment"),
)
def __init__(
self,
parent,
app,
connection,
ts_from,
ts_to,
identities_service,
transactions_service,
):
"""
History of all transactions
:param PyQt5.QtWidgets.QWidget parent: parent widget
:param sakia.app.Application app: the main application
:param sakia.data.entities.Connection connection: the connection
:param sakia.services.IdentitiesService identities_service: the identities service
:param sakia.services.TransactionsService transactions_service: the transactions service
"""
super().__init__(parent)
self.app = app
self.connection = connection
self.blockchain_processor = BlockchainProcessor.instanciate(app)
self.identities_service = identities_service
self.sql_adapter = TxHistorySqlAdapter(self.app.db.conn)
self.transactions_repo = TransactionsRepo(self.app.db.conn)
self.dividends_repo = DividendsRepo(self.app.db.conn)
self.sources_repo = SourcesRepo(self.app.db.conn)
self.current_page = 0
self.ts_from = ts_from
self.ts_to = ts_to
self.main_column_id = HistoryTableModel.columns_types[0]
self.order = Qt.AscendingOrder
self.transfers_data = []
def set_period(self, ts_from, ts_to):
"""
Filter table by given timestamps
"""
logging.debug(
"Filtering from {0} to {1}".format(
datetime.datetime.fromtimestamp(ts_from).isoformat(" "),
datetime.datetime.fromtimestamp(ts_to).isoformat(" "),
)
)
self.ts_from = ts_from
self.ts_to = ts_to
self.init_transfers()
def set_current_page(self, page):
self.current_page = page - 1
self.init_transfers()
def pages(self):
return self.sql_adapter.pages(
self.app.currency,
self.connection.pubkey,
ts_from=self.ts_from,
ts_to=self.ts_to,
stopline_hash=STOPLINE_HASH,
)
def pubkeys(self, row):
return self.transfers_data[row][
HistoryTableModel.columns_types.index("pubkey")
].split("\n")
def transfers_and_dividends(self):
"""
Transfer
:rtype: List[sakia.data.entities.Transfer]
"""
return self.sql_adapter.transfers_and_dividends(
self.app.currency,
self.connection.pubkey,
page=self.current_page,
ts_from=self.ts_from,
ts_to=self.ts_to,
stopline_hash=STOPLINE_HASH,
sort_by=HistoryTableModel.columns_to_sql[self.main_column_id],
sort_order="ASC" if self.order == Qt.AscendingOrder else "DESC",
)
def change_transfer(self, transfer):
# capture sources associated with tx
sources = self.sources_repo.get_all(
currency=transfer.currency, identifier=transfer.sha_hash
)
# capture complex conditions
sources_conditions = [
source.conditions
for source in sources
if "&&" in source.conditions or "||" in source.conditions
]
transfer.conditions = (
sources_conditions[0] if len(sources_conditions) > 0 else None
)
for i, data in enumerate(self.transfers_data):
if (
data[HistoryTableModel.columns_types.index("txhash")]
== transfer.sha_hash
):
if transfer.state == Transaction.DROPPED:
self.beginRemoveRows(QModelIndex(), i, i)
self.transfers_data.pop(i)
self.endRemoveRows()
else:
if self.connection.pubkey in transfer.issuers:
self.transfers_data[i] = self.data_sent(transfer)
self.dataChanged.emit(
self.index(i, 0),
self.index(i, len(HistoryTableModel.columns_types)),
)
if self.connection.pubkey in transfer.receivers:
self.transfers_data[i] = self.data_received(transfer)
self.dataChanged.emit(
self.index(i, 0),
self.index(i, len(HistoryTableModel.columns_types)),
)
def data_stopline(self, transfer):
"""
Converts a transaction to table data
:param sakia.data.entities.Transaction transfer: the transaction
:return: data as tuple
"""
block_number = transfer.written_block
senders = []
for issuer in transfer.issuers:
identity = self.identities_service.get_identity(issuer)
if identity:
# todo: add an uid column to store and display the uid of the senders
# otherwise the "pubkey + uid" break the context menu to send money
# senders.append(issuer + " (" + identity.uid + ")")
senders.append(issuer)
else:
senders.append(issuer)
date_ts = transfer.timestamp
txid = transfer.txid
return (
date_ts,
"",
0,
QCoreApplication.translate(
"HistoryTableModel", "Transactions missing from history"
),
transfer.state,
txid,
transfer.issuers,
block_number,
transfer.sha_hash,
transfer,
)
def data_received(self, transfer):
"""
Converts a transaction to table data
:param sakia.data.entities.Transaction transfer: the transaction
:return: data as tuple
"""
block_number = transfer.written_block
amount = transfer.amount * 10 ** transfer.amount_base
senders = []
for issuer in transfer.issuers:
identity = self.identities_service.get_identity(issuer)
if identity:
# todo: add an uid column to store and display the uid of the receivers
# otherwise the "pubkey + uid" break the context menu to send money
# senders.append(issuer + " (" + identity.uid + ")")
senders.append(issuer)
else:
senders.append(issuer)
date_ts = transfer.timestamp
txid = transfer.txid
return (
date_ts,
"\n".join(senders),
amount,
transfer.comment,
transfer.state,
txid,
transfer.issuers,
block_number,
transfer.sha_hash,
transfer,
transfer.conditions,
)
def data_sent(self, transfer):
"""
Converts a transaction to table data
:param sakia.data.entities.Transaction transfer: the transaction
:return: data as tuple
"""
block_number = transfer.written_block
amount = transfer.amount * 10 ** transfer.amount_base * -1
receivers = []
for receiver in transfer.receivers:
identity = self.identities_service.get_identity(receiver)
if identity:
# todo: add an uid column to store and display the uid of the receivers
# otherwise the "pubkey + uid" break the context menu to send money
# receivers.append(receiver + " (" + identity.uid + ")")
receivers.append(receiver)
else:
receivers.append(receiver)
date_ts = transfer.timestamp
txid = transfer.txid
return (
date_ts,
"\n".join(receivers),
amount,
transfer.comment,
transfer.state,
txid,
transfer.receivers,
block_number,
transfer.sha_hash,
transfer,
transfer.conditions,
)
def data_dividend(self, dividend):
"""
Converts a transaction to table data
:param sakia.data.entities.Dividend dividend: the dividend
:return: data as tuple
"""
block_number = dividend.block_number
amount = dividend.amount * 10 ** dividend.base
identity = self.identities_service.get_identity(dividend.pubkey)
if identity:
# todo: add an uid column to store and display the uid of the receivers
# otherwise the "pubkey + uid" break the context menu to send money
# receiver = dividend.pubkey + " (" + identity.uid + ")"
receiver = dividend.pubkey
else:
receiver = dividend.pubkey
date_ts = dividend.timestamp
return (
date_ts,
receiver,
amount,
"",
HistoryTableModel.DIVIDEND,
0,
dividend.pubkey,
block_number,
"",
dividend,
dividend.conditions,
)
def init_transfers(self):
self.beginResetModel()
self.transfers_data = []
transfers_and_dividends = self.transfers_and_dividends()
for data in transfers_and_dividends:
if data[4]: # If data is transfer, it has a sha_hash column
transfer = self.transactions_repo.get_one(
currency=self.app.currency,
pubkey=self.connection.pubkey,
sha_hash=data[4],
)
transfer.conditions = data[7]
if transfer.state != Transaction.DROPPED:
if data[4] == STOPLINE_HASH:
self.transfers_data.append(self.data_stopline(transfer))
elif data[2] < 0:
self.transfers_data.append(self.data_sent(transfer))
else:
self.transfers_data.append(self.data_received(transfer))
else:
# else we get the dividend depending on the block number
dividend = self.dividends_repo.get_one(
currency=self.app.currency,
pubkey=self.connection.pubkey,
block_number=data[5],
)
dividend.conditions = data[7]
self.transfers_data.append(self.data_dividend(dividend))
self.endResetModel()
def rowCount(self, parent):
return len(self.transfers_data)
def columnCount(self, parent):
return 4
def sort(self, main_column, order):
self.main_column_id = self.columns_types[main_column]
self.order = order
self.init_transfers()
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
if HistoryTableModel.columns_types[section] == "amount":
dividend, base = self.blockchain_processor.last_ud(self.app.currency)
header = "{:}".format(
QCoreApplication.translate(
"HistoryTableModel", HistoryTableModel.columns_headers[section]
)
)
if self.app.current_ref.base_str(base):
header += " ({:})".format(self.app.current_ref.base_str(base))
return header
return QCoreApplication.translate(
"HistoryTableModel", HistoryTableModel.columns_headers[section]
)
def data(self, index, role):
row = index.row()
col = index.column()
if not index.isValid():
return QVariant()
source_data = self.transfers_data[row][col]
txhash_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("txhash")
]
state_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("state")
]
block_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("block_number")
]
conditions_data = self.transfers_data[row][
HistoryTableModel.columns_types.index("conditions")
]
if state_data == Transaction.VALIDATED and block_data:
current_confirmations = (
self.blockchain_processor.current_buid(self.app.currency).number
- block_data
)
else:
current_confirmations = 0
if role == Qt.DisplayRole:
if col == HistoryTableModel.columns_types.index("pubkey"):
return "<p>" + source_data.replace("\n", "<br>") + "</p>"
if col == HistoryTableModel.columns_types.index("date"):
if txhash_data == STOPLINE_HASH:
return ""
else:
ts = self.blockchain_processor.adjusted_ts(
self.connection.currency, source_data
)
return (
QLocale.toString(
QLocale(),
QDateTime.fromTime_t(ts).date(),
QLocale.dateFormat(QLocale(), QLocale.ShortFormat),
)
+ " BAT"
)
if col == HistoryTableModel.columns_types.index("amount"):
if txhash_data == STOPLINE_HASH:
return ""
else:
amount = self.app.current_ref.instance(
source_data, self.connection.currency, self.app, block_data
).diff_localized(False, False)
return amount
return source_data
if role == Qt.FontRole:
font = QFont()
if txhash_data == STOPLINE_HASH:
font.setItalic(True)
else:
if state_data == Transaction.AWAITING or (
state_data == Transaction.VALIDATED
and current_confirmations < MAX_CONFIRMATIONS
):
font.setItalic(True)
elif state_data == Transaction.REFUSED:
font.setItalic(True)
elif state_data == Transaction.TO_SEND:
font.setItalic(True)
else:
font.setItalic(False)
# if special lock conditions on a source coming from this transaction...
if conditions_data is not None:
font.setUnderline(True)
return font
if role == Qt.ForegroundRole:
color = None
if state_data == Transaction.REFUSED:
color = QColor(Qt.darkGray)
elif state_data == Transaction.TO_SEND:
color = QColor(Qt.blue)
if col == HistoryTableModel.columns_types.index("amount"):
if source_data < 0:
color = QColor(Qt.darkRed)
elif state_data == HistoryTableModel.DIVIDEND:
color = QColor(Qt.darkBlue)
if state_data == Transaction.AWAITING:
color = QColor("#ffb000")
if color:
if self.app.parameters.dark_theme:
return color.lighter(300)
else:
return color
if role == Qt.TextAlignmentRole:
if HistoryTableModel.columns_types.index("amount"):
return Qt.AlignRight | Qt.AlignVCenter
if col == HistoryTableModel.columns_types.index("date"):
return Qt.AlignCenter
if role == Qt.ToolTipRole:
if col == HistoryTableModel.columns_types.index("date"):
ts = self.blockchain_processor.adjusted_ts(
self.connection.currency, source_data
)
return QDateTime.fromTime_t(ts).toString(Qt.SystemLocaleLongDate)
if (
state_data == Transaction.VALIDATED
or state_data == Transaction.AWAITING
):
if current_confirmations >= MAX_CONFIRMATIONS:
return None
elif self.app.parameters.expert_mode:
return QCoreApplication.translate(
"HistoryTableModel", "{0} / {1} confirmations"
).format(current_confirmations, MAX_CONFIRMATIONS)
else:
confirmation = current_confirmations / MAX_CONFIRMATIONS * 100
confirmation = 100 if confirmation > 100 else confirmation
return QCoreApplication.translate(
"HistoryTableModel", "Confirming... {0} %"
).format(QLocale().toString(float(confirmation), "f", 0))
def flags(self, index):
return Qt.ItemIsSelectable | Qt.ItemIsEnabled
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxHistoryWidget</class>
<widget class="QWidget" name="TxHistoryWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>656</width>
<height>635</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupbox_balance">
<property name="title">
<string>Balance</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_balance">
<property name="font">
<font>
<pointsize>22</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>loading...</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_send">
<property name="text">
<string>Send money</string>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/payment_icon</normaloff>:/icons/payment_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>16</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Busy" name="busy_balance" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stacked_widget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page_history">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="topMargin">
<number>5</number>
</property>
<item>
<widget class="QDateTimeEdit" name="date_from">
<property name="displayFormat">
<string>dd/MM/yyyy</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDateTimeEdit" name="date_to">
<property name="displayFormat">
<string>dd/MM/yyyy</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_refresh">
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/refresh_icon</normaloff>:/icons/refresh_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="table_history">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#0000ff;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;to send &lt;/span&gt;&lt;span style=&quot; color:#ffb000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;pending &lt;/span&gt;&lt;span style=&quot; color:#808080;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;refused &lt;/span&gt;&lt;span style=&quot; color:#000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;not confirmed &lt;/span&gt;&lt;span style=&quot; color:#000000;&quot;&gt;&lt;/span&gt;validated &lt;span style=&quot; color:#000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot; text-decoration: underline;&quot;&gt;to unlock&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spin_page"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Busy</class>
<extends>QWidget</extends>
<header>sakia.gui.widgets</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections/>
</ui>
from PyQt5.QtCore import QDateTime, QEvent, QCoreApplication
from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView
from .delegate import TxHistoryDelegate
from .txhistory_uic import Ui_TxHistoryWidget
class TxHistoryView(QWidget, Ui_TxHistoryWidget):
"""
The view of TxHistory component
"""
def __init__(self, parent, transfer_view):
super().__init__(parent)
self.transfer_view = transfer_view
self.setupUi(self)
self.stacked_widget.insertWidget(1, transfer_view)
self.button_send.clicked.connect(
lambda c: self.stacked_widget.setCurrentWidget(self.transfer_view)
)
self.spin_page.setMinimum(1)
self.date_from.setDateTime(QDateTime().currentDateTime().addMonths(-1))
self.date_to.setDateTime(QDateTime().currentDateTime().addDays(1))
def get_time_frame(self):
"""
Get the time frame of date filters
:return: the timestamps of the date filters
"""
return self.date_from.dateTime().toTime_t(), self.date_to.dateTime().toTime_t()
def set_table_history_model(self, model):
"""
Define the table history model
:param QAbstractItemModel model:
:return:
"""
self.table_history.setModel(model)
self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_history.setSortingEnabled(True)
self.table_history.horizontalHeader().setSectionResizeMode(
QHeaderView.Interactive
)
self.table_history.setItemDelegate(TxHistoryDelegate())
self.table_history.resizeRowsToContents()
self.table_history.verticalHeader().setSectionResizeMode(
QHeaderView.ResizeToContents
)
def set_minimum_maximum_datetime(self, minimum, maximum):
"""
Configure minimum and maximum datetime in date filter
:param PyQt5.QtCore.QDateTime minimum: the minimum
:param PyQt5.QtCore.QDateTime maximum: the maximum
"""
self.date_from.setMinimumDateTime(minimum)
self.date_from.setMaximumDateTime(QDateTime().currentDateTime())
self.date_to.setMinimumDateTime(minimum)
self.date_to.setMaximumDateTime(maximum)
def set_max_pages(self, pages):
self.spin_page.setSuffix(
QCoreApplication.translate("TxHistoryView", " / {:} pages").format(
pages + 1
)
)
self.spin_page.setMaximum(pages + 1)
def set_balance(self, balance):
"""
Display given balance
:param balance: the given balance to display
:return:
"""
# set infos in label
self.label_balance.setText("{:}".format(balance))
def clear(self):
self.stacked_widget.setCurrentWidget(self.page_history)
def changeEvent(self, event):
"""
Intercepte LanguageChange event to translate UI
:param QEvent QEvent: Event
:return:
"""
if event.type() == QEvent.LanguageChange:
self.retranslateUi(self)
super().changeEvent(event)
from PyQt5.QtWidgets import QFrame, QSizePolicy
from PyQt5.QtCore import pyqtSignal
from .navigation_uic import Ui_Navigation
from sakia.models.generic_tree import GenericTreeModel
class NavigationView(QFrame, Ui_Navigation):
"""
The view of navigation panel
"""
current_view_changed = pyqtSignal(dict)
def __init__(self, parent):
super().__init__(parent)
self.setupUi(self)
self.tree_view.clicked.connect(self.handle_click)
self.tree_view.setItemsExpandable(False)
def set_model(self, model):
"""
Change the navigation pane
:param sakia.gui.navigation.model.NavigationModel
"""
self.tree_view.setModel(model.generic_tree())
self.tree_view.expandAll()
def add_widget(self, widget):
"""
Add a widget to the stacked_widget
:param PyQt5.QtWidgets.QWidget widget: the new widget
"""
self.stacked_widget.addWidget(widget)
return widget
def handle_click(self, index):
"""
Click on the navigation pane
:param PyQt5.QtCore.QModelIndex index: the index
:return:
"""
if index.isValid():
raw_data = self.tree_view.model().data(
index, GenericTreeModel.ROLE_RAW_DATA
)
if "widget" in raw_data:
widget = raw_data["widget"]
if self.stacked_widget.indexOf(widget) != -1:
self.stacked_widget.setCurrentWidget(widget)
self.current_view_changed.emit(raw_data)
def add_connection(self, raw_data):
self.tree_view.model().insert_node(raw_data)
self.tree_view.expandAll()
def update_connection(self, raw_data):
self.tree_view.model().modelReset.emit()
self.tree_view.expandAll()
import asyncio
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QDialog
from sakia import money
from sakia.data.entities import UserParameters
from .preferences_uic import Ui_PreferencesDialog
class PreferencesDialog(QDialog, Ui_PreferencesDialog):
"""
A dialog to get password.
"""
def __init__(self, app):
"""
Constructor
:param sakia.app.Application app:
"""
super().__init__()
self.setupUi(self)
self.app = app
for ref in money.Referentials:
self.combo_referential.addItem(
QCoreApplication.translate("Account", ref.translated_name())
)
self.combo_referential.setCurrentIndex(self.app.parameters.referential)
for lang in ("en", "fr", "de", "es", "it", "pl", "pt", "ru"):
self.combo_language.addItem(lang)
self.combo_language.setCurrentText(self.app.parameters.lang)
self.checkbox_expertmode.setChecked(self.app.parameters.expert_mode)
self.checkbox_maximize.setChecked(self.app.parameters.maximized)
self.checkbox_notifications.setChecked(self.app.parameters.notifications)
self.spinbox_digits_comma.setValue(self.app.parameters.digits_after_comma)
self.spinbox_digits_comma.setMaximum(12)
self.spinbox_digits_comma.setMinimum(1)
self.button_app.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(0))
self.button_display.clicked.connect(
lambda: self.stackedWidget.setCurrentIndex(1)
)
self.button_network.clicked.connect(
lambda: self.stackedWidget.setCurrentIndex(2)
)
self.checkbox_proxy.setChecked(self.app.parameters.enable_proxy)
self.spinbox_proxy_port.setEnabled(self.checkbox_proxy.isChecked())
self.edit_proxy_address.setEnabled(self.checkbox_proxy.isChecked())
self.checkbox_proxy.stateChanged.connect(self.handle_proxy_change)
self.spinbox_proxy_port.setMinimum(0)
self.spinbox_proxy_port.setMaximum(55636)
self.spinbox_proxy_port.setValue(self.app.parameters.proxy_port)
self.edit_proxy_address.setText(self.app.parameters.proxy_address)
self.edit_proxy_username.setText(self.app.parameters.proxy_user)
self.edit_proxy_password.setText(self.app.parameters.proxy_password)
self.checkbox_dark_theme.setChecked(self.app.parameters.dark_theme)
def handle_proxy_change(self):
self.spinbox_proxy_port.setEnabled(self.checkbox_proxy.isChecked())
self.edit_proxy_address.setEnabled(self.checkbox_proxy.isChecked())
def accept(self):
parameters = UserParameters(
profile_name=self.app.parameters.profile_name,
lang=self.combo_language.currentText(),
referential=self.combo_referential.currentIndex(),
expert_mode=self.checkbox_expertmode.isChecked(),
maximized=self.checkbox_maximize.isChecked(),
digits_after_comma=self.spinbox_digits_comma.value(),
notifications=self.checkbox_notifications.isChecked(),
enable_proxy=self.checkbox_proxy.isChecked(),
proxy_address=self.edit_proxy_address.text(),
proxy_port=self.spinbox_proxy_port.value(),
proxy_user=self.edit_proxy_username.text(),
proxy_password=self.edit_proxy_password.text(),
dark_theme=self.checkbox_dark_theme.isChecked(),
)
self.app.save_parameters(parameters)
# change UI translation
self.app.switch_language()
super().accept()
def reject(self):
super().reject()
def async_exec(self):
future = asyncio.Future()
self.finished.connect(lambda r: future.set_result(r))
self.open()
return future
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PreferencesDialog</class>
<widget class="QDialog" name="PreferencesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>711</width>
<height>326</height>
</rect>
</property>
<property name="windowTitle">
<string>Preferences</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>6</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<property name="leftMargin">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="button_app">
<property name="text">
<string>General</string>
</property>
<property name="icon">
<iconset resource="../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/settings_app_icon</normaloff>:/icons/settings_app_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>65</width>
<height>65</height>
</size>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_display">
<property name="text">
<string>Display</string>
</property>
<property name="icon">
<iconset resource="../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/settings_display_icon</normaloff>:/icons/settings_display_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>65</width>
<height>65</height>
</size>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_network">
<property name="text">
<string>Network</string>
</property>
<property name="icon">
<iconset resource="../../../res/icons/sakia.icons.qrc">
<normaloff>:/icons/settings_network_icon</normaloff>:/icons/settings_network_icon</iconset>
</property>
<property name="iconSize">
<size>
<width>65</width>
<height>65</height>
</size>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;General settings&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Default &amp;referential</string>
</property>
<property name="buddy">
<cstring>label_3</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_referential"/>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkbox_expertmode">
<property name="text">
<string>Enable expert mode</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_9">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Display settings&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Digits after commas </string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinbox_digits_comma"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_language"/>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkbox_maximize">
<property name="text">
<string>Maximize Window at Startup</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="topMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkbox_notifications">
<property name="text">
<string>Enable notifications</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="topMargin">
<number>6</number>
</property>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="checkbox_dark_theme">
<property name="text">
<string>Dark Theme compatibility</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:16pt; font-weight:600;&quot;&gt;Network settings&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_proxy">
<property name="text">
<string>Use a http proxy server</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Proxy server address</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_proxy_address"/>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinbox_proxy_port"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Proxy username</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_proxy_username"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Proxy password</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_proxy_password"/>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../res/icons/sakia.icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PreferencesDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PreferencesDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CertificationWidget</class>
<widget class="QWidget" name="CertificationWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>724</width>
<height>620</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QGroupBox" name="groupbox_identity">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Select your identity</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QComboBox" name="combo_connections"/>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Certifications stock</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_cert_stock">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_certified">
<property name="title">
<string>Certify user</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="identity_select_layout">
<item>
<widget class="QPushButton" name="button_import_identity">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Import identity document</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_process">
<property name="text">
<string>Process certification</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_cancel">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label">
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>Step 1. Check the key and user / Step 2. Accept the money licence / Step 3. Sign to confirm certification</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Licence</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QTextEdit" name="text_licence">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_confirm">
<property name="text">
<string>By going throught the process of creating a wallet, you accept the license above.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_accept">
<property name="text">
<string>I accept the above licence</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_cancel_licence">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QGroupBox" name="group_box_password">
<property name="title">
<string>Secret Key / Password</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QVBoxLayout" name="layout_password_input"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="enabled">
<bool>true</bool>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QCoreApplication
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
from sakia.decorators import asyncify
from sakia.gui.sub.search_user.controller import SearchUserController
from sakia.gui.sub.user_information.controller import UserInformationController
from sakia.gui.sub.password_input import PasswordInputController
from sakia.gui.widgets import dialogs
from sakia.data.entities import Identity
from .model import CertificationModel
from .view import CertificationView
import attr
@attr.s()
class CertificationController(QObject):
"""
The Certification view
"""
accepted = pyqtSignal()
rejected = pyqtSignal()
view = attr.ib()
model = attr.ib()
search_user = attr.ib()
user_information = attr.ib()
password_input = attr.ib()
def __attrs_post_init__(self):
super().__init__()
self.view.button_box.accepted.connect(self.accept)
self.view.button_box.rejected.connect(self.reject)
self.view.button_cancel.clicked.connect(self.reject)
self.view.button_cancel_licence.clicked.connect(self.reject)
self.view.combo_connections.currentIndexChanged.connect(self.change_connection)
@classmethod
def create(cls, parent, app):
"""
Instanciate a Certification component
:param sakia.gui.component.controller.ComponentController parent:
:param sakia.app.Application app: sakia application
:return: a new Certification controller
:rtype: CertificationController
"""
search_user = SearchUserController.create(None, app)
user_information = UserInformationController.create(None, app, None)
password_input = PasswordInputController.create(None, None)
view = CertificationView(
parent.view if parent else None,
search_user.view,
user_information.view,
password_input.view,
app,
)
model = CertificationModel(app)
view.set_label_confirm(app.currency)
certification = cls(view, model, search_user, user_information, password_input)
search_user.identity_selected.connect(certification.refresh_user_information)
password_input.password_changed.connect(certification.refresh)
user_information.identity_loaded.connect(certification.refresh)
view.set_keys(certification.model.available_connections())
view.identity_document_imported.connect(certification.load_identity_document)
return certification
@classmethod
def integrate_to_main_view(cls, parent, app, connection):
certification = cls.create(parent, app)
certification.view.combo_connections.setCurrentText(connection.title())
certification.view.groupbox_identity.hide()
return certification
@classmethod
def open_dialog(cls, parent, app, connection):
"""
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.core.Application app: the application
:param sakia.core.Account account: the account certifying the identity
:param sakia.core.Community community: the community
:return:
"""
dialog = QDialog(parent)
dialog.setWindowTitle(
QCoreApplication.translate("CertificationController", "Certification")
)
dialog.setLayout(QVBoxLayout(dialog))
certification = cls.create(parent, app)
certification.set_connection(connection)
certification.refresh()
dialog.layout().addWidget(certification.view)
certification.accepted.connect(dialog.accept)
certification.rejected.connect(dialog.reject)
return dialog.exec()
@classmethod
def certify_identity(cls, parent, app, connection, identity):
"""
Certify and identity
:param sakia.gui.component.controller.ComponentController parent: the parent
:param sakia.core.Application app: the application
:param sakia.data.entities.Connection connection: the connection
:param sakia.data.entities.Identity identity: the identity certified
:return:
"""
dialog = QDialog(parent)
dialog.setWindowTitle(
QCoreApplication.translate("CertificationController", "Certification")
)
dialog.setLayout(QVBoxLayout(dialog))
certification = cls.create(parent, app)
if connection:
certification.view.combo_connections.setCurrentText(connection.title())
certification.user_information.change_identity(identity)
certification.refresh()
dialog.layout().addWidget(certification.view)
certification.accepted.connect(dialog.accept)
certification.rejected.connect(dialog.reject)
return dialog.exec()
def change_connection(self, index):
self.model.set_connection(index)
self.password_input.set_connection(self.model.connection)
self.refresh()
def set_connection(self, connection):
if connection:
self.view.combo_connections.setCurrentText(connection.title())
self.password_input.set_connection(connection)
@asyncify
async def accept(self):
"""
Validate the dialog
"""
if not self.user_information.model.identity.member:
result = await dialogs.QAsyncMessageBox.question(
self.view,
"Certifying a non-member",
"""
Did you ensure that :<br>
<br/>
1°) <b>You know the person declaring owning this pubkey
well enough and to personally verify that it is the correct key you are going to certify.</b><br/>
2°) To physically meet her to ensure that it is the person you know who owns this pubkey.<br/>
3°) Or did you ensure by contacting her using multiple communications means,
like forum + mail + videoconferencing + phone (to recognize voice)<br/>
Because if one can hack 1 mail account or 1 forum account, it will be way more difficult to hack multiple
communication means and imitate the voice of the person.<br/>
<br/>
The 2°) is however preferable to the 3°)... whereas <b>1°) is mandatory in any case.</b><br/>
<br/>
<b>Reminder</b>: Certifying is not only uniquely ensuring that you met the person, its ensuring the {:} community
that you know her well enough and that you will know how to find a double account done by a person certified by you
using cross checking which will help to reveal the problem if needs to be.</br>""".format(
self.model.app.root_servers[self.model.app.currency]["display"]
),
)
if result == dialogs.QMessageBox.No:
return
self.view.button_box.setDisabled(True)
secret_key, password = self.password_input.get_salt_password()
QApplication.setOverrideCursor(Qt.WaitCursor)
result = await self.model.certify_identity(
secret_key, password, self.user_information.model.identity
)
#
if result[0]:
QApplication.restoreOverrideCursor()
await self.view.show_success(self.model.notification())
self.search_user.clear()
self.user_information.clear()
self.view.clear()
self.accepted.emit()
else:
await self.view.show_error(self.model.notification(), result[1])
QApplication.restoreOverrideCursor()
self.view.button_box.setEnabled(True)
def reject(self):
self.search_user.clear()
self.user_information.clear()
self.view.clear()
self.rejected.emit()
def refresh(self):
stock = self.model.get_cert_stock()
written, pending = self.model.nb_certifications()
days, hours, minutes, seconds = self.model.remaining_time()
self.view.display_cert_stock(written, pending, stock, days, hours, minutes)
if self.model.could_certify():
if written < stock or stock == 0:
if not self.user_information.model.identity:
self.view.set_button_process(
CertificationView.ButtonsState.SELECT_IDENTITY
)
elif days + hours + minutes > 0:
if days > 0:
remaining_localized = QCoreApplication.translate(
"CertificationController", "{days} days"
).format(days=days)
else:
remaining_localized = QCoreApplication.translate(
"CertificationController", "{hours}h {min}min"
).format(hours=hours, min=minutes)
self.view.set_button_process(
CertificationView.ButtonsState.REMAINING_TIME_BEFORE_VALIDATION,
remaining=remaining_localized,
)
else:
self.view.set_button_process(CertificationView.ButtonsState.OK)
if self.password_input.valid():
self.view.set_button_box(CertificationView.ButtonsState.OK)
else:
self.view.set_button_box(
CertificationView.ButtonsState.WRONG_PASSWORD
)
else:
self.view.set_button_process(
CertificationView.ButtonsState.NO_MORE_CERTIFICATION
)
else:
self.view.set_button_process(CertificationView.ButtonsState.NOT_A_MEMBER)
def load_identity_document(self, identity_doc):
"""
Load an identity document
:param duniterpy.documents.Identity identity_doc:
"""
identity = Identity(
currency=identity_doc.currency,
pubkey=identity_doc.pubkey,
uid=identity_doc.uid,
blockstamp=identity_doc.timestamp,
signature=identity_doc.signatures[0],
)
self.user_information.change_identity(identity)
def refresh_user_information(self):
"""
Refresh user information
"""
self.user_information.search_identity(self.search_user.model.identity())
from PyQt5.QtCore import QObject
from sakia.data.processors import (
IdentitiesProcessor,
CertificationsProcessor,
BlockchainProcessor,
ConnectionsProcessor,
)
from sakia.helpers import timestamp_to_dhms
import attr
@attr.s()
class CertificationModel(QObject):
"""
The model of Certification component
"""
app = attr.ib()
connection = attr.ib(default=None)
_connections_processor = attr.ib(default=None)
_certifications_processor = attr.ib(default=None)
_identities_processor = attr.ib(default=None)
_blockchain_processor = attr.ib(default=None)
def __attrs_post_init__(self):
super().__init__()
self._connections_processor = ConnectionsProcessor.instanciate(self.app)
self._certifications_processor = CertificationsProcessor.instanciate(self.app)
self._identities_processor = IdentitiesProcessor.instanciate(self.app)
self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
def change_connection(self, index):
"""
Change current currency
:param int index: index of the community in the account list
"""
self.connection = self.connections_repo.get_currencies()[index]
def get_cert_stock(self):
"""
:return: the certifications stock
:rtype: int
"""
return self._blockchain_processor.parameters(self.connection.currency).sig_stock
def remaining_time(self):
"""
Get remaining time as a tuple to display
:return: a tuple containing (days, hours, minutes, seconds)
:rtype: tuple[int]
"""
parameters = self._blockchain_processor.parameters(self.connection.currency)
blockchain_time = self._blockchain_processor.time(self.connection.currency)
remaining_time = self._certifications_processor.cert_issuance_delay(
self.connection.currency,
self.connection.pubkey,
parameters,
blockchain_time,
)
return timestamp_to_dhms(remaining_time)
def nb_certifications(self):
"""
Get
:return: a tuple containing (written valid certifications, pending certifications)
:rtype: tuple[int]
"""
certifications = self._certifications_processor.certifications_sent(
self.connection.currency, self.connection.pubkey
)
nb_certifications = len([c for c in certifications if c.written_on >= 0])
nb_cert_pending = len([c for c in certifications if c.written_on == -1])
return nb_certifications, nb_cert_pending
def could_certify(self):
"""
Check if the user could theorically certify
:return: true if the user can certifiy
:rtype: bool
"""
identity = self._identities_processor.get_identity(
self.connection.currency, self.connection.pubkey, self.connection.uid
)
current_block = self._blockchain_processor.current_buid(
self.connection.currency
)
return identity.member or current_block.number == 0
def available_connections(self):
return self._connections_processor.connections_with_uids()
def set_connection(self, index):
connections = self._connections_processor.connections_with_uids()
self.connection = connections[index]
def notification(self):
return self.app.parameters.notifications
async def certify_identity(self, secret_key, password, identity):
result = await self.app.documents_service.certify(
self.connection, secret_key, password, identity
)
if result[0]:
connection_identity = self._identities_processor.get_identity(
self.connection.currency, self.connection.pubkey, self.connection.uid
)
self.app.db.commit()
self.app.identity_changed.emit(connection_identity)
return result
from PyQt5.QtWidgets import QWidget, QDialogButtonBox, QFileDialog, QMessageBox
from PyQt5.QtCore import QT_TRANSLATE_NOOP, Qt, pyqtSignal, QCoreApplication, QObject
from sakia.app import Application
from .certification_uic import Ui_CertificationWidget
from sakia.gui.widgets import toast
from sakia.gui.widgets.dialogs import QAsyncMessageBox
from sakia.constants import G1_LICENSE
from duniterpy.documents import Identity, MalformedDocumentError
from enum import Enum
from ..password_input.view import PasswordInputView
from ..search_user.view import SearchUserView
from ..user_information.view import UserInformationView
class CertificationView(QWidget, Ui_CertificationWidget):
"""
The view of the certification component
"""
class ButtonsState(Enum):
NO_MORE_CERTIFICATION = 0
NOT_A_MEMBER = 1
REMAINING_TIME_BEFORE_VALIDATION = 2
OK = 3
SELECT_IDENTITY = 4
WRONG_PASSWORD = 5
_button_process_values = {
ButtonsState.NO_MORE_CERTIFICATION: (
False,
QT_TRANSLATE_NOOP("CertificationView", "No more certifications"),
),
ButtonsState.NOT_A_MEMBER: (
False,
QT_TRANSLATE_NOOP("CertificationView", "Not a member"),
),
ButtonsState.SELECT_IDENTITY: (
False,
QT_TRANSLATE_NOOP("CertificationView", "Please select an identity"),
),
ButtonsState.REMAINING_TIME_BEFORE_VALIDATION: (
True,
QT_TRANSLATE_NOOP(
"CertificationView", "&Ok (Not validated before {remaining})"
),
),
ButtonsState.OK: (
True,
QT_TRANSLATE_NOOP("CertificationView", "&Process Certification"),
),
}
_button_box_values = {
ButtonsState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok")),
ButtonsState.WRONG_PASSWORD: (
False,
QT_TRANSLATE_NOOP("CertificationView", "Please enter correct password"),
),
}
identity_document_imported = pyqtSignal(Identity)
def __init__(
self,
parent: QObject,
search_user_view: SearchUserView,
user_information_view: UserInformationView,
password_input_view: PasswordInputView,
app: Application,
):
"""
Init CertificationView
:param parent: QObject instance
:param search_user_view: SearchUserView instance
:param user_information_view: UserInformationView instance
:param password_input_view: PasswordInputView instance
:param app: Application instance
"""
super().__init__(parent)
self.setupUi(self)
self.app = app
self.search_user_view = search_user_view
self.user_information_view = user_information_view
self.password_input_view = password_input_view
self.identity_select_layout.insertWidget(0, search_user_view)
self.search_user_view.button_reset.hide()
self.layout_password_input.addWidget(password_input_view)
self.groupbox_certified.layout().addWidget(user_information_view)
self.button_import_identity.clicked.connect(self.import_identity_document)
self.button_process.clicked.connect(
lambda c: self.stackedWidget.setCurrentIndex(1)
)
self.button_accept.clicked.connect(
lambda c: self.stackedWidget.setCurrentIndex(2)
)
licence_text = QCoreApplication.translate("CertificationView", G1_LICENSE)
self.text_licence.setText(licence_text)
def clear(self):
self.stackedWidget.setCurrentIndex(0)
self.set_button_process(CertificationView.ButtonsState.SELECT_IDENTITY)
self.password_input_view.clear()
self.search_user_view.clear()
self.user_information_view.clear()
def set_keys(self, connections):
self.combo_connections.clear()
for c in connections:
self.combo_connections.addItem(c.title())
def set_selected_key(self, connection):
"""
:param sakia.data.entities.Connection connection:
"""
self.combo_connections.setCurrentText(connection.title())
def pubkey_value(self):
return self.edit_pubkey.text()
def import_identity_document(self):
file_name = QFileDialog.getOpenFileName(
self,
QCoreApplication.translate("CertificationView", "Import identity document"),
"",
QCoreApplication.translate(
"CertificationView", "Duniter documents (*.txt)"
),
)
if file_name and file_name[0]:
with open(file_name[0], "r") as open_file:
raw_text = open_file.read()
try:
identity_doc = Identity.from_signed_raw(raw_text)
self.identity_document_imported.emit(identity_doc)
except MalformedDocumentError as e:
QMessageBox.warning(
self,
QCoreApplication.translate(
"CertificationView", "Identity document"
),
QCoreApplication.translate(
"CertificationView",
"The imported file is not a correct identity document",
),
QMessageBox.Ok,
)
def set_label_confirm(self, currency):
self.label_confirm.setTextFormat(Qt.RichText)
self.label_confirm.setText(
"""<b>Vous confirmez engager votre responsabilité envers la communauté Duniter {:}
et acceptez de certifier le compte Duniter {:} sélectionné.<br/><br/>""".format(
self.app.root_servers[currency]["display"],
self.app.root_servers[currency]["display"],
)
)
async def show_success(self, notification):
if notification:
toast.display(
QCoreApplication.translate("CertificationView", "Certification"),
QCoreApplication.translate(
"CertificationView", "Success sending certification"
),
)
else:
await QAsyncMessageBox.information(
self,
QCoreApplication.translate("CertificationView", "Certification"),
QCoreApplication.translate(
"CertificationView", "Success sending certification"
),
)
async def show_error(self, notification, error_txt):
if notification:
toast.display(
QCoreApplication.translate("CertificationView", "Certification"),
QCoreApplication.translate(
"CertificationView",
"Could not broadcast certification: {0}".format(error_txt),
),
)
else:
await QAsyncMessageBox.critical(
self,
QCoreApplication.translate("CertificationView", "Certification"),
QCoreApplication.translate(
"CertificationView",
"Could not broadcast certification: {0}".format(error_txt),
),
)
def display_cert_stock(
self,
written,
pending,
stock,
remaining_days,
remaining_hours,
remaining_minutes,
):
"""
Display values in informations label
:param int written: number of written certifications
:param int pending: number of pending certifications
:param int stock: maximum certifications
:param int remaining_days:
:param int remaining_hours:
:param int remaining_minutes:
"""
cert_text = QCoreApplication.translate(
"CertificationView", "Certifications sent: {nb_certifications}/{stock}"
).format(nb_certifications=written, stock=stock)
if pending > 0:
cert_text += " (+{nb_cert_pending} certifications pending)".format(
nb_cert_pending=pending
)
if remaining_days > 0:
remaining_localized = QCoreApplication.translate(
"CertificationView", "{days} days"
).format(days=remaining_days)
else:
remaining_localized = QCoreApplication.translate(
"CertificationView", "{hours} hours and {min} min."
).format(hours=remaining_hours, min=remaining_minutes)
cert_text += "\n"
cert_text += QCoreApplication.translate(
"CertificationView",
"Remaining time before next certification validation: {0}".format(
remaining_localized
),
)
self.label_cert_stock.setText(cert_text)
def set_button_box(self, state, **kwargs):
"""
Set button box state
:param sakia.gui.certification.view.CertificationView.ButtonBoxState state: the state of te button box
:param dict kwargs: the values to replace from the text in the state
:return:
"""
button_box_state = CertificationView._button_box_values[state]
self.button_box.button(QDialogButtonBox.Ok).setEnabled(button_box_state[0])
self.button_box.button(QDialogButtonBox.Ok).setText(
QCoreApplication.translate("CertificationView", button_box_state[1]).format(
**kwargs
)
)
def set_button_process(self, state, **kwargs):
"""
Set button box state
:param sakia.gui.certification.view.CertificationView.ButtonBoxState state: the state of te button box
:param dict kwargs: the values to replace from the text in the state
:return:
"""
button_process_state = CertificationView._button_process_values[state]
self.button_process.setEnabled(button_process_state[0])
self.button_process.setText(
QCoreApplication.translate(
"CertificationView", button_process_state[1]
).format(**kwargs)
)
from .controller import PasswordInputController
import asyncio
from duniterpy.key import SigningKey
from .view import PasswordInputView
from sakia.helpers import detect_non_printable
from sakia.gui.widgets.dialogs import dialog_async_exec
from PyQt5.QtCore import QObject, pyqtSignal, QMetaObject, QCoreApplication
from PyQt5.QtWidgets import QDialog, QVBoxLayout
class PasswordInputController(QObject):
"""
A dialog to get password.
"""
password_changed = pyqtSignal(bool)
def __init__(self, view, connection):
"""
:param PasswordInputView view:
:param sakia.data.entities.Connection connection: a given connection
"""
super().__init__()
self.view = view
self._password = ""
self._secret_key = ""
self.connection = connection
self.remember = False
self.set_connection(connection)
def set_info_visible(self, visible):
self.view.label_info.setVisible(visible)
@classmethod
def create(cls, parent, connection):
view = PasswordInputView(parent.view if parent else None)
password_input = cls(view, connection)
view.edit_password.textChanged.connect(password_input.handle_password_change)
view.edit_secret_key.textChanged.connect(
password_input.handle_secret_key_change
)
return password_input
@classmethod
async def open_dialog(cls, parent, connection):
dialog = QDialog(parent.view)
dialog.setLayout(QVBoxLayout(dialog))
password_input = cls.create(parent, connection)
dialog.setWindowTitle(
QCoreApplication.translate(
"PasswordInputController", "Please enter your password"
)
)
dialog.layout().addWidget(password_input.view)
password_input.view.button_box.accepted.connect(dialog.accept)
password_input.view.button_box.rejected.connect(dialog.reject)
password_input.view.setParent(dialog)
password_input.view.button_box.show()
result = await dialog_async_exec(dialog)
if result == QDialog.Accepted:
return password_input.get_salt_password()
else:
return "", ""
def valid(self):
return self._password is not ""
def check_private_key(self, secret_key, password):
if detect_non_printable(secret_key):
self.view.error(
QCoreApplication.translate(
"PasswordInputController", "Non printable characters in secret key"
)
)
return False
if detect_non_printable(password):
self.view.error(
QCoreApplication.translate(
"PasswordInputController", "Non printable characters in password"
)
)
return False
if (
SigningKey.from_credentials(
secret_key, password, self.connection.scrypt_params
).pubkey
!= self.connection.pubkey
):
self.view.error(
QCoreApplication.translate(
"PasswordInputController",
"Wrong secret key or password. Cannot open the private key",
)
)
return False
return True
def handle_secret_key_change(self, secret_key):
self._secret_key = ""
if self.check_private_key(secret_key, self.view.edit_password.text()):
self.view.valid()
self._secret_key = secret_key
self._password = self.view.edit_password.text()
self.password_changed.emit(True)
else:
self.password_changed.emit(False)
def handle_password_change(self, password):
self._password = ""
if self.check_private_key(self.view.edit_secret_key.text(), password):
self.view.valid()
self._secret_key = self.view.edit_secret_key.text()
self._password = password
self.password_changed.emit(True)
else:
self.password_changed.emit(False)
def get_salt_password(self):
return self._secret_key, self._password
def set_connection(self, connection):
if connection:
self.connection = connection
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PasswordInputWidget</class>
<widget class="QWidget" name="PasswordInputWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>106</height>
</rect>
</property>
<property name="windowTitle">
<string>Please enter your password</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="edit_secret_key">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Please enter your secret key</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="edit_password">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Please enter your password</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_info">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
from PyQt5.QtWidgets import QWidget, QDialogButtonBox
from PyQt5.QtCore import QEvent, Qt, QCoreApplication
from .password_input_uic import Ui_PasswordInputWidget
class PasswordInputView(QWidget, Ui_PasswordInputWidget):
"""
The model of Navigation component
"""
def __init__(self, parent):
# construct from qtDesigner
super().__init__(parent)
self.setupUi(self)
self.button_box = QDialogButtonBox(self)
self.button_box.setOrientation(Qt.Horizontal)
self.button_box.setStandardButtons(
QDialogButtonBox.Cancel | QDialogButtonBox.Ok
)
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
self.layout().addWidget(self.button_box)
self.button_box.hide()
font = self.edit_secret_key.font()
font.setBold(False)
self.edit_secret_key.setFont(font)
self.edit_password.setFont(font)
def error(self, text):
self.label_info.setText(text)
self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
def clear(self):
self.edit_password.clear()
self.edit_secret_key.clear()
def valid(self):
self.label_info.setText(
QCoreApplication.translate("PasswordInputView", "Password is valid")
)
self.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
def changeEvent(self, event):
"""
Intercepte LanguageChange event to translate UI
:param QEvent QEvent: Event
:return:
"""
if event.type() == QEvent.LanguageChange:
self.retranslateUi(self)
return super(PasswordInputView, self).changeEvent(event)
from PyQt5.QtCore import pyqtSignal, QObject
from sakia.data.entities import Identity
from sakia.decorators import asyncify
from .model import SearchUserModel
from .view import SearchUserView
class SearchUserController(QObject):
"""
The navigation panel
"""
search_started = pyqtSignal()
search_ended = pyqtSignal()
identity_selected = pyqtSignal(Identity)
def __init__(self, parent, view, model):
"""
:param sakia.gui.agent.controller.AgentController parent: the parent
:param sakia.gui.search_user.view.SearchUserView view:
:param sakia.gui.search_user.model.SearchUserModel model:
"""
super().__init__(parent)
self.view = view
self.model = model
self.view.search_requested.connect(self.search)
self.view.node_selected.connect(self.select_node)
@classmethod
def create(cls, parent, app):
view = SearchUserView(parent.view if parent else None)
model = SearchUserModel(parent, app)
search_user = cls(parent, view, model)
view.set_auto_completion([c.displayed_text() for c in model.contacts()])
return search_user
@asyncify
async def search(self, text):
"""
Search for a user
:param text:
:return:
"""
if len(text) > 2:
await self.model.find_user(text)
user_nodes = self.model.user_nodes()
self.view.set_search_result(text, user_nodes)
def current_identity(self):
"""
:rtype: sakia.core.registry.Identity
"""
return self.model.identity()
def select_node(self, index):
"""
Select node in graph when item is selected in combobox
"""
if self.model.select_identity(index):
self.identity_selected.emit(self.model.identity())
def clear(self):
self.model.clear()
self.view.clear()