Files
bearhub/bauh/view/qt/prepare.py

392 lines
14 KiB
Python

import datetime
import operator
import time
from functools import reduce
from typing import Tuple
from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal, QCoreApplication
from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy, QTableWidget, QHeaderView, QPushButton, QToolBar, \
QProgressBar, QApplication, QPlainTextEdit, QToolButton, QHBoxLayout
from bauh import __app_name__
from bauh.api.abstract.context import ApplicationContext
from bauh.api.abstract.controller import SoftwareManager
from bauh.api.abstract.handler import TaskManager
from bauh.view.qt import root, styles
from bauh.view.qt.colors import GREEN
from bauh.view.qt.components import new_spacer
from bauh.view.qt.qt_utils import centralize
from bauh.view.qt.thread import AnimateProgress
from bauh.view.util.translation import I18n
class Prepare(QThread, TaskManager):
signal_register = pyqtSignal(str, str, object)
signal_update = pyqtSignal(str, float, str)
signal_finished = pyqtSignal(str)
signal_started = pyqtSignal()
signal_ask_password = pyqtSignal()
signal_output = pyqtSignal(str, str)
def __init__(self, context: ApplicationContext, manager: SoftwareManager, i18n: I18n):
super(Prepare, self).__init__()
self.manager = manager
self.i18n = i18n
self.context = context
self.waiting_password = False
self.password_response = None
def ask_password(self) -> Tuple[str, bool]:
self.waiting_password = True
self.signal_ask_password.emit()
while self.waiting_password:
time.sleep(0.1) # waiting for user input
return self.password_response
def set_password_reply(self, password: str, valid: bool):
self.password_response = password, valid
self.waiting_password = False
def run(self):
root_pwd = None
if self.manager.requires_root('prepare', None):
root_pwd, ok = self.ask_password()
if not ok:
QCoreApplication.exit(1)
self.manager.prepare(self, root_pwd, None)
self.signal_started.emit()
def update_progress(self, task_id: str, progress: float, substatus: str):
self.signal_update.emit(task_id, progress, substatus)
def update_output(self, task_id: str, output: str):
self.signal_output.emit(task_id, output)
def register_task(self, id_: str, label: str, icon_path: str):
self.signal_register.emit(id_, label, icon_path)
def finish_task(self, task_id: str):
self.signal_finished.emit(task_id)
class CheckFinished(QThread):
signal_finished = pyqtSignal()
def __init__(self):
super(CheckFinished, self).__init__()
self.total = None
self.finished = None
def run(self):
time.sleep(3)
while True:
if self.total == self.finished:
break
time.sleep(0.01)
self.signal_finished.emit()
def update(self, total: int, finished: int):
if total is not None:
self.total = total
if finished is not None:
self.finished = finished
class EnableSkip(QThread):
signal_timeout = pyqtSignal()
def run(self):
ti = datetime.datetime.now()
while True:
if datetime.datetime.now() >= ti + datetime.timedelta(minutes=1.5):
self.signal_timeout.emit()
break
time.sleep(0.1)
class PreparePanel(QWidget, TaskManager):
signal_status = pyqtSignal(object, object)
signal_password_response = pyqtSignal(str, bool)
def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize, i18n: I18n, manage_window: QWidget):
super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.i18n = i18n
self.context = context
self.manage_window = manage_window
self.setWindowTitle('{} ({})'.format(__app_name__, self.i18n['prepare_panel.title.start'].lower()))
self.setMinimumWidth(screen_size.width() * 0.5)
self.setMinimumHeight(screen_size.height() * 0.35)
self.setMaximumHeight(screen_size.height() * 0.95)
self.setLayout(QVBoxLayout())
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.manager = manager
self.tasks = {}
self.output = {}
self.ntasks = 0
self.ftasks = 0
self.self_close = False
self.prepare_thread = Prepare(self.context, manager, self.i18n)
self.prepare_thread.signal_register.connect(self.register_task)
self.prepare_thread.signal_update.connect(self.update_progress)
self.prepare_thread.signal_finished.connect(self.finish_task)
self.prepare_thread.signal_started.connect(self.start)
self.prepare_thread.signal_ask_password.connect(self.ask_root_password)
self.prepare_thread.signal_output.connect(self.update_output)
self.signal_password_response.connect(self.prepare_thread.set_password_reply)
self.check_thread = CheckFinished()
self.signal_status.connect(self.check_thread.update)
self.check_thread.signal_finished.connect(self.finish)
self.skip_thread = EnableSkip()
self.skip_thread.signal_timeout.connect(self._enable_skip_button)
self.progress_thread = AnimateProgress()
self.progress_thread.signal_change.connect(self._change_progress)
self.label_top = QLabel()
self.label_top.setText("{}...".format(self.i18n['prepare_panel.title.start'].capitalize()))
self.label_top.setAlignment(Qt.AlignHCenter)
self.label_top.setStyleSheet("QLabel { font-size: 14px; font-weight: bold; }")
self.layout().addWidget(self.label_top)
self.layout().addWidget(QLabel())
self.table = QTableWidget()
self.table.setStyleSheet("QTableWidget { background-color: transparent; }")
self.table.setFocusPolicy(Qt.NoFocus)
self.table.setShowGrid(False)
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setVisible(False)
self.table.horizontalHeader().setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(['' for _ in range(4)])
self.layout().addWidget(self.table)
self.textarea_output = QPlainTextEdit(self)
self.textarea_output.resize(self.table.size())
self.textarea_output.setStyleSheet("background: black; color: white;")
self.layout().addWidget(self.textarea_output)
self.textarea_output.setVisible(False)
self.textarea_output.setReadOnly(True)
self.textarea_output.setMaximumHeight(100)
self.current_output_task = None
self.bottom_widget = QWidget()
self.bottom_widget.setLayout(QHBoxLayout())
self.bottom_widget.layout().addStretch()
bt_hide_output = QPushButton(self.i18n['prepare.bt_hide_details'])
bt_hide_output.setStyleSheet('QPushButton { text-decoration: underline; border: 0px; background: none } ')
bt_hide_output.clicked.connect(self.hide_output)
bt_hide_output.setCursor(QCursor(Qt.PointingHandCursor))
self.bottom_widget.layout().addWidget(bt_hide_output)
self.bottom_widget.layout().addStretch()
self.layout().addWidget(self.bottom_widget)
self.bottom_widget.setVisible(False)
self.bt_bar = QToolBar()
self.bt_close = QPushButton(self.i18n['close'].capitalize())
self.bt_close.setCursor(QCursor(Qt.PointingHandCursor))
self.bt_close.clicked.connect(self.close)
self.bt_close.setVisible(False)
self.ref_bt_close = self.bt_bar.addWidget(self.bt_close)
self.bt_bar.addWidget(new_spacer())
self.progress_bar = QProgressBar()
self.progress_bar.setStyleSheet(styles.PROGRESS_BAR)
self.progress_bar.setMaximumHeight(10 if QApplication.instance().style().objectName().lower() == 'windows' else 4)
self.progress_bar.setTextVisible(False)
self.progress_bar.setVisible(False)
self.ref_progress_bar = self.bt_bar.addWidget(self.progress_bar)
self.bt_bar.addWidget(new_spacer())
self.bt_skip = QPushButton(self.i18n['prepare_panel.bt_skip.label'].capitalize())
self.bt_skip.clicked.connect(self.finish)
self.bt_skip.setEnabled(False)
self.bt_bar.addWidget(self.bt_skip)
self.layout().addWidget(self.bt_bar)
def hide_output(self):
self.current_output_task = None
self.textarea_output.setVisible(False)
self.textarea_output.clear()
self.bottom_widget.setVisible(False)
self._resize_columns()
self.setFocus(Qt.NoFocusReason)
if not self.bt_bar.isVisible():
self.bt_bar.setVisible(True)
def ask_root_password(self):
root_pwd, ok = root.ask_root_password(self.context, self.i18n)
self.signal_password_response.emit(root_pwd, ok)
def _enable_skip_button(self):
self.bt_skip.setEnabled(True)
self.bt_skip.setCursor(QCursor(Qt.PointingHandCursor))
def _change_progress(self, value: int):
self.progress_bar.setValue(value)
def get_table_width(self) -> int:
return reduce(operator.add, [self.table.columnWidth(i) for i in range(self.table.columnCount())])
def _resize_columns(self):
header_horizontal = self.table.horizontalHeader()
for i in range(self.table.columnCount()):
header_horizontal.setSectionResizeMode(i, QHeaderView.ResizeToContents)
self.resize(self.get_table_width() * 1.05, self.sizeHint().height())
def show(self):
super(PreparePanel, self).show()
self.prepare_thread.start()
centralize(self)
def start(self):
self.ref_bt_close.setVisible(True)
self.check_thread.start()
self.skip_thread.start()
self.ref_progress_bar.setVisible(True)
self.progress_thread.start()
def closeEvent(self, QCloseEvent):
if not self.self_close:
QCoreApplication.exit()
def register_task(self, id_: str, label: str, icon_path: str):
self.ntasks += 1
self.table.setRowCount(self.ntasks)
task_row = self.ntasks - 1
icon_widget = QWidget()
icon_widget.setLayout(QHBoxLayout())
icon_widget.layout().setContentsMargins(10, 0, 10, 0)
bt_icon = QToolButton()
bt_icon.setEnabled(False)
bt_icon.setToolTip(self.i18n['prepare.bt_icon.no_output'])
bt_icon.setFixedSize(QSize(24, 24))
bt_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
if icon_path:
bt_icon.setIcon(QIcon(icon_path))
def _show_output():
lines = self.output[id_]
if lines:
self.current_output_task = id_
self.textarea_output.clear()
self.textarea_output.setVisible(True)
for l in lines:
self.textarea_output.appendPlainText(l)
self.bottom_widget.setVisible(True)
self.setFocus(Qt.NoFocusReason)
if self.bt_bar.isVisible():
self.bt_bar.setVisible(False)
bt_icon.clicked.connect(_show_output)
icon_widget.layout().addWidget(bt_icon)
self.table.setCellWidget(task_row, 0, icon_widget)
lb_status = QLabel(label)
lb_status.setMinimumWidth(50)
lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
lb_status.setStyleSheet("QLabel { font-weight: bold; }")
self.table.setCellWidget(task_row, 1, lb_status)
lb_sub = QLabel()
lb_sub.setContentsMargins(10, 0, 10, 0)
lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
lb_sub.setMinimumWidth(50)
self.table.setCellWidget(task_row, 2, lb_sub)
lb_progress = QLabel('{0:.2f}'.format(0) + '%')
lb_progress.setContentsMargins(10, 0, 10, 0)
lb_progress.setStyleSheet("QLabel { font-weight: bold; }")
lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.table.setCellWidget(task_row, 3, lb_progress)
self.tasks[id_] = {'bt_icon': bt_icon,
'lb_status': lb_status,
'lb_prog': lb_progress,
'progress': 0,
'lb_sub': lb_sub,
'finished': False}
self.signal_status.emit(self.ntasks, self.ftasks)
def update_progress(self, task_id: str, progress: float, substatus: str):
task = self.tasks[task_id]
if progress != task['progress']:
task['progress'] = progress
task['lb_prog'].setText('{0:.2f}'.format(progress) + '%')
if substatus:
task['lb_sub'].setText('( {} )'.format(substatus))
else:
task['lb_sub'].setText('')
self._resize_columns()
def update_output(self, task_id: str, output: str):
full_output = self.output.get(task_id)
if full_output is None:
full_output = []
self.output[task_id] = full_output
task = self.tasks[task_id]
task['bt_icon'].setEnabled(True)
task['bt_icon'].setCursor(QCursor(Qt.PointingHandCursor))
task['bt_icon'].setToolTip(self.i18n['prepare.bt_icon.output'])
full_output.append(output)
if self.current_output_task == task_id:
self.textarea_output.appendPlainText(output)
def finish_task(self, task_id: str):
task = self.tasks[task_id]
task['lb_sub'].setText('')
for key in ('lb_prog', 'lb_status'):
task[key].setStyleSheet('QLabel { color: %s; text-decoration: line-through; }' % GREEN)
task['finished'] = True
self._resize_columns()
self.ftasks += 1
self.signal_status.emit(self.ntasks, self.ftasks)
if self.ntasks == self.ftasks:
self.label_top.setText(self.i18n['ready'].capitalize())
def finish(self):
if self.isVisible():
self.manage_window.refresh_packages()
self.manage_window.show()
self.self_close = True
self.close()