예제 #1
0
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.setCursor(QCursor(Qt.WaitCursor))
        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.setCursor(QCursor(Qt.WaitCursor))
        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.progress_bar.setCursor(QCursor(Qt.WaitCursor))
        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_skip.setCursor(QCursor(Qt.WaitCursor))
        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.setCursor(QCursor(Qt.WaitCursor))
        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.setCursor(Qt.WaitCursor)
        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_status.setCursor(Qt.WaitCursor)
        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.setCursor(Qt.WaitCursor)
        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.begin_refresh_packages()
            self.manage_window.show()
            self.self_close = True
            self.close()
예제 #2
0
class ScreenshotsDialog(QDialog):

    MAX_HEIGHT = 600
    MAX_WIDTH = 800

    def __init__(self, pkg: PackageView, http_client: HttpClient,
                 icon_cache: MemoryCache, i18n: I18n,
                 screenshots: List[QPixmap], logger: logging.Logger):
        super(ScreenshotsDialog, self).__init__()
        self.setWindowTitle(str(pkg))
        self.screenshots = screenshots
        self.logger = logger
        self.loaded_imgs = []
        self.download_threads = []
        self.resize(1280, 720)
        self.i18n = i18n
        self.http_client = http_client
        self.progress_bar = QProgressBar()
        self.progress_bar.setMaximumHeight(10 if QApplication.instance().style(
        ).objectName().lower() == 'windows' else 6)
        self.progress_bar.setTextVisible(False)
        self.thread_progress = AnimateProgress()
        self.thread_progress.signal_change.connect(self._update_progress)
        self.thread_progress.start()

        # THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED
        #
        # icon_data = icon_cache.get(pkg.model.icon_url)
        #
        # if icon_data and icon_data.get('icon'):
        #     self.setWindowIcon(icon_data.get('icon'))
        # else:
        #     self.setWindowIcon(QIcon(pkg.model.get_type_icon_path()))
        self.setWindowIcon(QIcon(pkg.model.get_type_icon_path()))
        self.setLayout(QVBoxLayout())

        self.img = QLabel()
        self.layout().addWidget(self.img)

        self.bottom_bar = QToolBar()

        self.bt_back = QPushButton(
            ' < ' + self.i18n['screenshots.bt_back.label'].capitalize())
        self.bt_back.clicked.connect(self.back)
        self.ref_bt_back = self.bottom_bar.addWidget(self.bt_back)
        self.bottom_bar.addWidget(new_spacer(50))

        self.img_label = QLabel()
        self.img_label.setStyleSheet(
            'QLabel { font-weight: bold; text-align: center }')
        self.ref_img_label = self.bottom_bar.addWidget(self.img_label)
        self.ref_img_label.setVisible(False)
        self.ref_progress_bar = self.bottom_bar.addWidget(self.progress_bar)
        self.bottom_bar.addWidget(new_spacer(50))

        self.bt_next = QPushButton(
            self.i18n['screenshots.bt_next.label'].capitalize() + ' > ')
        self.bt_next.clicked.connect(self.next)
        self.ref_bt_next = self.bottom_bar.addWidget(self.bt_next)

        self.layout().addWidget(self.bottom_bar)

        self.img_idx = 0

        for idx, s in enumerate(self.screenshots):
            t = Thread(target=self._download_img, args=(idx, s), daemon=True)
            t.start()

        self._load_img()

    def _update_progress(self, val: int):
        self.progress_bar.setValue(val)

    def _load_img(self):
        if len(self.loaded_imgs) > self.img_idx:
            img = self.loaded_imgs[self.img_idx]

            if isinstance(img, QPixmap):
                self.img_label.setText('')
                self.img.setPixmap(img)
            else:
                self.img_label.setText(img)
                self.img.setPixmap(QPixmap())

            self.thread_progress.stop = True
            self.ref_progress_bar.setVisible(False)
            self.ref_img_label.setVisible(True)
        else:
            self.img.setPixmap(QPixmap())
            self.ref_img_label.setVisible(False)
            self.ref_progress_bar.setVisible(True)
            self.thread_progress.start()

        if len(self.screenshots) == 1:
            self.ref_bt_back.setVisible(False)
            self.ref_bt_next.setVisible(False)
        else:
            self.ref_bt_back.setEnabled(self.img_idx != 0)
            self.ref_bt_next.setEnabled(
                self.img_idx != len(self.screenshots) - 1)

        self.adjustSize()
        qt_utils.centralize(self)

    def _download_img(self, idx: int, url: str):
        self.logger.info('Downloading image [{}] from {}'.format(idx, url))
        res = self.http_client.get(url)

        if res:
            if not res.content:
                self.logger.warning('Image [{}] from {} has no content'.format(
                    idx, url))
                self.loaded_imgs.append(
                    self.i18n['screenshots.download.no_content'])
                self._load_img()
            else:
                self.logger.info(
                    'Image [{}] successfully downloaded'.format(idx))
                pixmap = QPixmap()
                pixmap.loadFromData(res.content)

                if pixmap.size().height() > self.MAX_HEIGHT or pixmap.size(
                ).width() > self.MAX_WIDTH:
                    pixmap = pixmap.scaled(self.MAX_WIDTH, self.MAX_HEIGHT,
                                           Qt.KeepAspectRatio,
                                           Qt.SmoothTransformation)

                self.loaded_imgs.append(pixmap)

                if self.img_idx == idx:
                    self._load_img()
        else:
            self.logger.info("Could not retrieve image [{}] from {}".format(
                idx, url))
            self.loaded_imgs.append(
                self.i18n['screenshots.download.no_response'])
            self._load_img()

    def back(self):
        self.img_idx -= 1
        self._load_img()

    def next(self):
        self.img_idx += 1
        self._load_img()
예제 #3
0
class PreparePanel(QWidget, TaskManager):

    signal_status = pyqtSignal(int)
    signal_password_response = pyqtSignal(bool, str)

    def __init__(self, context: ApplicationContext, manager: SoftwareManager,
                 screen_size: QSize, i18n: I18n, manage_window: QWidget,
                 app_config: dict):
        super(PreparePanel,
              self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
        self.i18n = i18n
        self.context = context
        self.app_config = app_config
        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.added_tasks = 0
        self.ftasks = 0
        self.started_at = None
        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.setCursor(QCursor(Qt.WaitCursor))
        self.label_top.setText("{}...".format(
            self.i18n['prepare_panel.title.start'].capitalize()))
        self.label_top.setObjectName('prepare_status')
        self.label_top.setAlignment(Qt.AlignHCenter)
        self.layout().addWidget(self.label_top)
        self.layout().addWidget(QLabel())

        self.table = QTableWidget()
        self.table.setObjectName('tasks')
        self.table.setCursor(QCursor(Qt.WaitCursor))
        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.table.horizontalScrollBar().setCursor(
            QCursor(Qt.PointingHandCursor))
        self.table.verticalScrollBar().setCursor(QCursor(
            Qt.PointingHandCursor))
        self.layout().addWidget(self.table)

        self.textarea_details = QPlainTextEdit(self)
        self.textarea_details.setObjectName('task_details')
        self.textarea_details.setProperty('console', 'true')
        self.textarea_details.resize(self.table.size())
        self.layout().addWidget(self.textarea_details)
        self.textarea_details.setVisible(False)
        self.textarea_details.setReadOnly(True)
        self.textarea_details.setMaximumHeight(100)
        self.current_output_task = None

        self.bottom_widget = QWidget()
        self.bottom_widget.setLayout(QHBoxLayout())
        self.bottom_widget.layout().addStretch()

        bt_hide_details = QPushButton(self.i18n['prepare.bt_hide_details'])
        bt_hide_details.setObjectName('bt_hide_details')
        bt_hide_details.clicked.connect(self.hide_output)
        bt_hide_details.setCursor(QCursor(Qt.PointingHandCursor))
        self.bottom_widget.layout().addWidget(bt_hide_details)
        self.bottom_widget.layout().addStretch()
        self.layout().addWidget(self.bottom_widget)
        self.bottom_widget.setVisible(False)

        self.bt_bar = QCustomToolbar()
        self.bt_close = QPushButton(self.i18n['close'].capitalize())
        self.bt_close.setObjectName('bt_cancel')
        self.bt_close.setCursor(QCursor(Qt.PointingHandCursor))
        self.bt_close.clicked.connect(self.close)
        self.bt_close.setVisible(False)
        self.bt_bar.add_widget(self.bt_close)
        self.bt_bar.add_widget(new_spacer())

        self.progress_bar = QProgressBar()
        self.progress_bar.setObjectName('prepare_progress')
        self.progress_bar.setTextVisible(False)
        self.progress_bar.setVisible(False)
        self.progress_bar.setCursor(QCursor(Qt.WaitCursor))
        self.bt_bar.add_widget(self.progress_bar)
        self.bt_bar.add_widget(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_skip.setCursor(QCursor(Qt.WaitCursor))
        self.bt_bar.add_widget(self.bt_skip)

        self.layout().addWidget(self.bt_bar)

    def hide_output(self):
        self.current_output_task = None
        self.textarea_details.setVisible(False)
        self.textarea_details.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):
        valid, root_pwd = RootDialog.ask_password(self.context, self.i18n)
        self.signal_password_response.emit(valid, root_pwd)

    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(int(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, tasks: int):
        self.started_at = time.time()
        self.check_thread.total = tasks
        self.check_thread.start()
        self.skip_thread.start()

        self.progress_thread.start()

        self.bt_close.setVisible(True)
        self.progress_bar.setVisible(True)

    def closeEvent(self, ev: QCloseEvent):
        if not self.self_close:
            QCoreApplication.exit()

    def register_task(self, id_: str, label: str, icon_path: str):
        self.added_tasks += 1
        self.table.setRowCount(self.added_tasks)
        task_row = self.added_tasks - 1

        icon_widget = QWidget()
        icon_widget.setProperty('container', 'true')
        icon_widget.setLayout(QHBoxLayout())
        icon_widget.layout().setContentsMargins(10, 0, 10, 0)

        bt_icon = QToolButton()
        bt_icon.setObjectName('bt_task')
        bt_icon.setCursor(QCursor(Qt.WaitCursor))
        bt_icon.setEnabled(False)
        bt_icon.setToolTip(self.i18n['prepare.bt_icon.no_output'])

        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_details.clear()
                self.textarea_details.setVisible(True)

                for l in lines:
                    self.textarea_details.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.setObjectName('task_status')
        lb_status.setProperty('status', 'running')
        lb_status.setCursor(Qt.WaitCursor)
        lb_status.setMinimumWidth(50)
        lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_status_col = 1
        self.table.setCellWidget(task_row, lb_status_col, lb_status)

        lb_progress = QLabel('{0:.2f}'.format(0) + '%')
        lb_progress.setObjectName('task_progress')
        lb_progress.setProperty('status', 'running')
        lb_progress.setCursor(Qt.WaitCursor)
        lb_progress.setContentsMargins(10, 0, 10, 0)
        lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_progress_col = 2

        self.table.setCellWidget(task_row, lb_progress_col, lb_progress)

        lb_sub = QLabel()
        lb_sub.setObjectName('task_substatus')
        lb_sub.setCursor(Qt.WaitCursor)
        lb_sub.setContentsMargins(10, 0, 10, 0)
        lb_sub.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_sub.setMinimumWidth(50)
        self.table.setCellWidget(task_row, 3, lb_sub)

        self.tasks[id_] = {
            'bt_icon': bt_icon,
            'lb_status': lb_status,
            'lb_status_col': lb_status_col,
            'lb_prog': lb_progress,
            'lb_prog_col': lb_progress_col,
            'progress': 0,
            'lb_sub': lb_sub,
            'finished': False,
            'row': task_row
        }

    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_details.appendPlainText(output)

    def finish_task(self, task_id: str):
        task = self.tasks[task_id]

        for key in ('lb_prog', 'lb_status', 'lb_sub'):
            label = task[key]
            label.setProperty('status', 'done')
            label.style().unpolish(label)
            label.style().polish(label)
            label.update()

        task['finished'] = True
        self._resize_columns()

        self.ftasks += 1
        self.signal_status.emit(self.ftasks)

    def finish(self):
        now = time.time()
        self.context.logger.info(
            "{0} tasks finished in {1:.9f} seconds".format(
                self.ftasks, (now - self.started_at)))
        if self.isVisible():
            self.manage_window.show()

            if self.app_config['boot']['load_apps']:
                self.manage_window.begin_refresh_packages()
            else:
                self.manage_window.load_without_packages()

            self.self_close = True
            self.close()
예제 #4
0
class ScreenshotsDialog(QDialog):
    def __init__(self, pkg: PackageView, http_client: HttpClient,
                 icon_cache: MemoryCache, i18n: I18n,
                 screenshots: List[QPixmap], logger: logging.Logger):
        super(ScreenshotsDialog, self).__init__()
        self.setWindowTitle(str(pkg))
        self.screenshots = screenshots
        self.logger = logger
        self.loaded_imgs = []
        self.download_threads = []
        self.i18n = i18n
        self.http_client = http_client
        self.progress_bar = QProgressBar()
        self.progress_bar.setObjectName('progress_screenshots')
        self.progress_bar.setCursor(QCursor(Qt.WaitCursor))
        self.progress_bar.setMaximumHeight(10 if QApplication.instance().style(
        ).objectName().lower() == 'windows' else 6)
        self.progress_bar.setTextVisible(False)
        self.thread_progress = AnimateProgress()
        self.thread_progress.signal_change.connect(self._update_progress)
        self.thread_progress.start()

        # THERE ARE CRASHES WITH SOME RARE ICONS ( like insomnia ). IT CAN BE A QT BUG. IN THE MEANTIME, ONLY THE TYPE ICON WILL BE RENDERED
        #
        # icon_data = icon_cache.get(pkg.model.icon_url)
        #
        # if icon_data and icon_data.get('icon'):
        #     self.setWindowIcon(icon_data.get('icon'))
        # else:
        #     self.setWindowIcon(QIcon(pkg.model.get_type_icon_path()))
        self.setWindowIcon(QIcon(pkg.model.get_type_icon_path()))
        self.setLayout(QVBoxLayout())

        self.layout().addWidget(new_spacer())
        self.img = QLabel()
        self.img.setObjectName('image')
        self.layout().addWidget(self.img)
        self.layout().addWidget(new_spacer())

        self.container_buttons = QWidget()
        self.container_buttons.setObjectName('buttons_container')
        self.container_buttons.setSizePolicy(QSizePolicy.Minimum,
                                             QSizePolicy.Fixed)
        self.container_buttons.setContentsMargins(0, 0, 0, 0)
        self.container_buttons.setLayout(QHBoxLayout())

        self.bt_back = QPushButton(
            ' < ' + self.i18n['screenshots.bt_back.label'].capitalize())
        self.bt_back.setObjectName('back')
        self.bt_back.setProperty('control', 'true')
        self.bt_back.setCursor(QCursor(Qt.PointingHandCursor))
        self.bt_back.clicked.connect(self.back)
        self.container_buttons.layout().addWidget(self.bt_back)
        self.container_buttons.layout().addWidget(new_spacer())

        self.container_buttons.layout().addWidget(self.progress_bar)
        self.container_buttons.layout().addWidget(new_spacer())

        self.bt_next = QPushButton(
            self.i18n['screenshots.bt_next.label'].capitalize() + ' > ')
        self.bt_next.setObjectName('next')
        self.bt_next.setProperty('control', 'true')
        self.bt_next.setCursor(QCursor(Qt.PointingHandCursor))
        self.bt_next.clicked.connect(self.next)
        self.container_buttons.layout().addWidget(self.bt_next)

        self.layout().addWidget(self.container_buttons)

        self.img_idx = 0
        self.max_img_width = 800
        self.max_img_height = 600

        for idx, s in enumerate(self.screenshots):
            t = Thread(target=self._download_img, args=(idx, s), daemon=True)
            t.start()

        self.resize(self.max_img_width + 5, self.max_img_height + 5)
        self._load_img()
        qt_utils.centralize(self)

    def _update_progress(self, val: int):
        self.progress_bar.setValue(val)

    def _load_img(self):
        if len(self.loaded_imgs) > self.img_idx:
            img = self.loaded_imgs[self.img_idx]

            if isinstance(img, QPixmap):
                self.img.setText('')
                self.img.setPixmap(img)
            else:
                self.img.setText(img)
                self.img.setPixmap(QPixmap())

            self.img.unsetCursor()
            self.thread_progress.stop = True
            self.progress_bar.setVisible(False)
        else:
            self.img.setPixmap(QPixmap())
            self.img.setCursor(QCursor(Qt.WaitCursor))
            self.img.setText('{} {}/{}...'.format(
                self.i18n['screenshots.image.loading'], self.img_idx + 1,
                len(self.screenshots)))
            self.progress_bar.setVisible(True)
            self.thread_progress.start()

        if len(self.screenshots) == 1:
            self.bt_back.setVisible(False)
            self.bt_next.setVisible(False)
        else:
            self.bt_back.setEnabled(self.img_idx != 0)
            self.bt_next.setEnabled(self.img_idx != len(self.screenshots) - 1)

    def _download_img(self, idx: int, url: str):
        self.logger.info('Downloading image [{}] from {}'.format(idx, url))
        res = self.http_client.get(url)

        if res:
            if not res.content:
                self.logger.warning('Image [{}] from {} has no content'.format(
                    idx, url))
                self.loaded_imgs.append(
                    self.i18n['screenshots.download.no_content'])
                self._load_img()
            else:
                self.logger.info(
                    'Image [{}] successfully downloaded'.format(idx))
                pixmap = QPixmap()
                pixmap.loadFromData(res.content)

                if pixmap.size().height() > self.max_img_height or pixmap.size(
                ).width() > self.max_img_width:
                    pixmap = pixmap.scaled(self.max_img_width,
                                           self.max_img_height,
                                           Qt.KeepAspectRatio,
                                           Qt.SmoothTransformation)

                self.loaded_imgs.append(pixmap)

                if self.img_idx == idx:
                    self._load_img()
        else:
            self.logger.info("Could not retrieve image [{}] from {}".format(
                idx, url))
            self.loaded_imgs.append(
                self.i18n['screenshots.download.no_response'])
            self._load_img()

    def back(self):
        self.img_idx -= 1
        self._load_img()

    def next(self):
        self.img_idx += 1
        self._load_img()
예제 #5
0
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__()
        self.setWindowFlag(Qt.WindowCloseButtonHint, False)
        self.i18n = i18n
        self.context = context
        self.manage_window = manage_window
        self.setWindowTitle('{} ({})'.format(
            self.i18n['prepare_panel.title.start'].capitalize(), __app_name__))
        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.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.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)

        toolbar = QToolBar()
        self.bt_close = QPushButton(self.i18n['close'].capitalize())
        self.bt_close.clicked.connect(self.close)
        self.bt_close.setVisible(False)
        self.ref_bt_close = toolbar.addWidget(self.bt_close)

        toolbar.addWidget(new_spacer())
        self.progress_bar = QProgressBar()
        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 = toolbar.addWidget(self.progress_bar)
        toolbar.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)
        toolbar.addWidget(self.bt_skip)

        self.layout().addWidget(toolbar)

    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)

    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

        lb_icon = QLabel()
        lb_icon.setContentsMargins(10, 0, 10, 0)
        lb_icon.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

        if icon_path:
            lb_icon.setPixmap(QIcon(icon_path).pixmap(14, 14))

        self.table.setCellWidget(task_row, 0, lb_icon)

        lb_status = QLabel(label)
        lb_status.setMinimumWidth(50)
        lb_status.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        lb_status.setStyleSheet("QLabel { color: blue; 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 { color: blue; font-weight: bold; }")
        lb_progress.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)

        self.table.setCellWidget(task_row, 3, lb_progress)

        self.tasks[id_] = {
            '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 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: green; text-decoration: line-through; }')

        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()
예제 #6
0
파일: window.py 프로젝트: jayvdb/bauh
class ManageWindow(QWidget):
    __BASE_HEIGHT__ = 400

    signal_user_res = pyqtSignal(bool)
    signal_table_update = pyqtSignal()

    def __init__(self,
                 i18n: dict,
                 icon_cache: MemoryCache,
                 manager: SoftwareManager,
                 disk_cache: bool,
                 download_icons: bool,
                 screen_size,
                 suggestions: bool,
                 display_limit: int,
                 config: Configuration,
                 context: ApplicationContext,
                 notifications: bool,
                 tray_icon=None):
        super(ManageWindow, self).__init__()
        self.i18n = i18n
        self.manager = manager
        self.tray_icon = tray_icon
        self.working = False  # restrict the number of threaded actions
        self.pkgs = []  # packages current loaded in the table
        self.pkgs_available = []  # all packages loaded in memory
        self.pkgs_installed = []  # cached installed packages
        self.display_limit = display_limit
        self.icon_cache = icon_cache
        self.disk_cache = disk_cache
        self.download_icons = download_icons
        self.screen_size = screen_size
        self.config = config
        self.context = context
        self.notifications = notifications

        self.icon_app = QIcon(resource.get_path('img/logo.svg'))
        self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
        self.setWindowIcon(self.icon_app)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.toolbar_top = QToolBar()
        self.toolbar_top.addWidget(new_spacer())

        self.label_status = QLabel()
        self.label_status.setText('')
        self.label_status.setStyleSheet("font-weight: bold")
        self.toolbar_top.addWidget(self.label_status)

        self.toolbar_search = QToolBar()
        self.toolbar_search.setStyleSheet("spacing: 0px;")
        self.toolbar_search.setContentsMargins(0, 0, 0, 0)

        label_pre_search = QLabel()
        label_pre_search.setStyleSheet(
            "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pre_search)

        self.input_search = QLineEdit()
        self.input_search.setMaxLength(20)
        self.input_search.setFrame(False)
        self.input_search.setPlaceholderText(
            self.i18n['window_manage.input_search.placeholder'] + "...")
        self.input_search.setToolTip(
            self.i18n['window_manage.input_search.tooltip'])
        self.input_search.setStyleSheet(
            "QLineEdit { background-color: white; color: gray; spacing: 0; height: 30px; font-size: 12px; width: 300px}"
        )
        self.input_search.returnPressed.connect(self.search)
        self.toolbar_search.addWidget(self.input_search)

        label_pos_search = QLabel()
        label_pos_search.setPixmap(QPixmap(
            resource.get_path('img/search.svg')))
        label_pos_search.setStyleSheet(
            "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pos_search)

        self.ref_toolbar_search = self.toolbar_top.addWidget(
            self.toolbar_search)
        self.toolbar_top.addWidget(new_spacer())
        self.layout.addWidget(self.toolbar_top)

        self.toolbar = QToolBar()
        self.toolbar.setStyleSheet(
            'QToolBar {spacing: 4px; margin-top: 15px; margin-bottom: 5px}')

        self.checkbox_updates = QCheckBox()
        self.checkbox_updates.setText(self.i18n['updates'].capitalize())
        self.checkbox_updates.stateChanged.connect(self._handle_updates_filter)
        self.ref_checkbox_updates = self.toolbar.addWidget(
            self.checkbox_updates)

        self.checkbox_only_apps = QCheckBox()
        self.checkbox_only_apps.setText(
            self.i18n['manage_window.checkbox.only_apps'])
        self.checkbox_only_apps.setChecked(True)
        self.checkbox_only_apps.stateChanged.connect(
            self._handle_filter_only_apps)
        self.ref_checkbox_only_apps = self.toolbar.addWidget(
            self.checkbox_only_apps)

        self.any_type_filter = 'any'
        self.cache_type_filter_icons = {}
        self.combo_filter_type = QComboBox()
        self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px}')
        self.combo_filter_type.setEditable(True)
        self.combo_filter_type.lineEdit().setReadOnly(True)
        self.combo_filter_type.lineEdit().setAlignment(Qt.AlignCenter)
        self.combo_filter_type.activated.connect(self._handle_type_filter)
        self.combo_filter_type.addItem(
            load_icon(resource.get_path('img/logo.svg'), 14),
            self.i18n[self.any_type_filter].capitalize(), self.any_type_filter)
        self.ref_combo_filter_type = self.toolbar.addWidget(
            self.combo_filter_type)

        self.input_name_filter = InputFilter(self.apply_filters_async)
        self.input_name_filter.setMaxLength(10)
        self.input_name_filter.setPlaceholderText(
            self.i18n['manage_window.name_filter.placeholder'] + '...')
        self.input_name_filter.setToolTip(
            self.i18n['manage_window.name_filter.tooltip'])
        self.input_name_filter.setStyleSheet(
            "QLineEdit { background-color: white; color: gray;}")
        self.input_name_filter.setFixedWidth(130)
        self.ref_input_name_filter = self.toolbar.addWidget(
            self.input_name_filter)

        self.toolbar.addWidget(new_spacer())

        self.bt_installed = QPushButton()
        self.bt_installed.setToolTip(
            self.i18n['manage_window.bt.installed.tooltip'])
        self.bt_installed.setIcon(QIcon(resource.get_path('img/disk.png')))
        self.bt_installed.setText(
            self.i18n['manage_window.bt.installed.text'].capitalize())
        self.bt_installed.clicked.connect(self._show_installed)
        self.bt_installed.setStyleSheet(toolbar_button_style('#A94E0A'))
        self.ref_bt_installed = self.toolbar.addWidget(self.bt_installed)

        self.bt_refresh = QPushButton()
        self.bt_refresh.setToolTip(i18n['manage_window.bt.refresh.tooltip'])
        self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
        self.bt_refresh.setText(self.i18n['manage_window.bt.refresh.text'])
        self.bt_refresh.setStyleSheet(toolbar_button_style('#2368AD'))
        self.bt_refresh.clicked.connect(
            lambda: self.refresh_apps(keep_console=False))
        self.ref_bt_refresh = self.toolbar.addWidget(self.bt_refresh)

        self.bt_upgrade = QPushButton()
        self.bt_upgrade.setToolTip(i18n['manage_window.bt.upgrade.tooltip'])
        self.bt_upgrade.setIcon(QIcon(resource.get_path('img/app_update.svg')))
        self.bt_upgrade.setText(i18n['manage_window.bt.upgrade.text'])
        self.bt_upgrade.setStyleSheet(toolbar_button_style('#20A435'))
        self.bt_upgrade.clicked.connect(self.update_selected)
        self.ref_bt_upgrade = self.toolbar.addWidget(self.bt_upgrade)

        self.layout.addWidget(self.toolbar)

        self.table_apps = AppsTable(self,
                                    self.icon_cache,
                                    disk_cache=self.disk_cache,
                                    download_icons=self.download_icons)
        self.table_apps.change_headers_policy()

        self.layout.addWidget(self.table_apps)

        toolbar_console = QToolBar()

        self.checkbox_console = QCheckBox()
        self.checkbox_console.setText(
            self.i18n['manage_window.checkbox.show_details'])
        self.checkbox_console.stateChanged.connect(self._handle_console)
        self.checkbox_console.setVisible(False)
        self.ref_checkbox_console = toolbar_console.addWidget(
            self.checkbox_console)

        toolbar_console.addWidget(new_spacer())

        self.label_displayed = QLabel()
        toolbar_console.addWidget(self.label_displayed)

        self.layout.addWidget(toolbar_console)

        self.textarea_output = QPlainTextEdit(self)
        self.textarea_output.resize(self.table_apps.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.toolbar_substatus = QToolBar()
        self.toolbar_substatus.addWidget(new_spacer())
        self.label_substatus = QLabel()
        self.toolbar_substatus.addWidget(self.label_substatus)
        self.toolbar_substatus.addWidget(new_spacer())
        self.layout.addWidget(self.toolbar_substatus)
        self._change_label_substatus('')

        self.thread_update = self._bind_async_action(
            UpdateSelectedApps(self.manager, self.i18n),
            finished_call=self._finish_update_selected)
        self.thread_refresh = self._bind_async_action(
            RefreshApps(self.manager),
            finished_call=self._finish_refresh_apps,
            only_finished=True)
        self.thread_uninstall = self._bind_async_action(
            UninstallApp(self.manager, self.icon_cache),
            finished_call=self._finish_uninstall)
        self.thread_get_info = self._bind_async_action(
            GetAppInfo(self.manager), finished_call=self._finish_get_info)
        self.thread_get_history = self._bind_async_action(
            GetAppHistory(self.manager, self.i18n),
            finished_call=self._finish_get_history)
        self.thread_search = self._bind_async_action(
            SearchPackages(self.manager),
            finished_call=self._finish_search,
            only_finished=True)
        self.thread_downgrade = self._bind_async_action(
            DowngradeApp(self.manager, self.i18n),
            finished_call=self._finish_downgrade)
        self.thread_suggestions = self._bind_async_action(
            FindSuggestions(man=self.manager),
            finished_call=self._finish_search,
            only_finished=True)
        self.thread_run_app = self._bind_async_action(
            LaunchApp(self.manager),
            finished_call=self._finish_run_app,
            only_finished=False)
        self.thread_custom_action = self._bind_async_action(
            CustomAction(manager=self.manager),
            finished_call=self._finish_custom_action)

        self.thread_apply_filters = ApplyFilters()
        self.thread_apply_filters.signal_finished.connect(
            self._finish_apply_filters_async)
        self.thread_apply_filters.signal_table.connect(
            self._update_table_and_upgrades)
        self.signal_table_update.connect(
            self.thread_apply_filters.stop_waiting)

        self.thread_install = InstallPackage(manager=self.manager,
                                             disk_cache=self.disk_cache,
                                             icon_cache=self.icon_cache,
                                             locale_keys=self.i18n)
        self._bind_async_action(self.thread_install,
                                finished_call=self._finish_install)

        self.thread_animate_progress = AnimateProgress()
        self.thread_animate_progress.signal_change.connect(
            self._update_progress)

        self.thread_verify_models = VerifyModels()
        self.thread_verify_models.signal_updates.connect(
            self._notify_model_data_change)

        self.toolbar_bottom = QToolBar()
        self.toolbar_bottom.setIconSize(QSize(16, 16))
        self.toolbar_bottom.setStyleSheet('QToolBar { spacing: 3px }')

        self.toolbar_bottom.addWidget(new_spacer())

        self.progress_bar = QProgressBar()
        self.progress_bar.setMaximumHeight(10 if QApplication.instance().style(
        ).objectName().lower() == 'windows' else 4)

        self.progress_bar.setTextVisible(False)
        self.ref_progress_bar = self.toolbar_bottom.addWidget(
            self.progress_bar)

        self.toolbar_bottom.addWidget(new_spacer())

        self.combo_styles = StylesComboBox(
            parent=self, i18n=i18n, show_panel_after_restart=bool(tray_icon))
        self.combo_styles.setStyleSheet('QComboBox {font-size: 12px;}')
        self.ref_combo_styles = self.toolbar_bottom.addWidget(
            self.combo_styles)

        bt_settings = IconButton(
            icon_path=resource.get_path('img/app_settings.svg'),
            action=self._show_settings_menu,
            background='#12ABAB',
            tooltip=self.i18n['manage_window.bt_settings.tooltip'])
        self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings)

        self.layout.addWidget(self.toolbar_bottom)

        qt_utils.centralize(self)

        self.filter_only_apps = True
        self.type_filter = self.any_type_filter
        self.filter_updates = False
        self._maximized = False
        self.progress_controll_enabled = True
        self.recent_installation = False

        self.dialog_about = None
        self.first_refresh = suggestions

        self.thread_warnings = ListWarnings(man=manager, locale_keys=i18n)
        self.thread_warnings.signal_warnings.connect(self._show_warnings)

    def set_tray_icon(self, tray_icon):
        self.tray_icon = tray_icon
        self.combo_styles.show_panel_after_restart = bool(tray_icon)

    def _update_process_progress(self, val: int):
        if self.progress_controll_enabled:
            self.thread_animate_progress.set_progress(val)

    def apply_filters_async(self):
        self.label_status.setText(self.i18n['manage_window.status.filtering'] +
                                  '...')

        self.ref_toolbar_search.setVisible(False)

        if self.ref_input_name_filter.isVisible():
            self.input_name_filter.setReadOnly(True)

        self.thread_apply_filters.filters = self._gen_filters()
        self.thread_apply_filters.pkgs = self.pkgs_available
        self.thread_apply_filters.start()

    def _update_table_and_upgrades(self, pkgs_info: dict):
        self._update_table(pkgs_info=pkgs_info, signal=True)
        self.update_bt_upgrade(pkgs_info)

    def _finish_apply_filters_async(self, success: bool):
        self.label_status.setText('')
        self.ref_toolbar_search.setVisible(True)

        if self.ref_input_name_filter.isVisible():
            self.input_name_filter.setReadOnly(False)

    def _bind_async_action(self,
                           action: AsyncAction,
                           finished_call,
                           only_finished: bool = False) -> AsyncAction:
        action.signal_finished.connect(finished_call)

        if not only_finished:
            action.signal_confirmation.connect(self._ask_confirmation)
            action.signal_output.connect(self._update_action_output)
            action.signal_message.connect(self._show_message)
            action.signal_status.connect(self._change_label_status)
            action.signal_substatus.connect(self._change_label_substatus)
            action.signal_progress.connect(self._update_process_progress)

            self.signal_user_res.connect(action.confirm)

        return action

    def _ask_confirmation(self, msg: dict):
        self.thread_animate_progress.pause()
        diag = ConfirmationDialog(title=msg['title'],
                                  body=msg['body'],
                                  locale_keys=self.i18n,
                                  components=msg['components'],
                                  confirmation_label=msg['confirmation_label'],
                                  deny_label=msg['deny_label'])
        res = diag.is_confirmed()
        self.thread_animate_progress.animate()
        self.signal_user_res.emit(res)

    def _show_message(self, msg: dict):
        self.thread_animate_progress.pause()
        dialog.show_message(title=msg['title'],
                            body=msg['body'],
                            type_=msg['type'])
        self.thread_animate_progress.animate()

    def _show_warnings(self, warnings: List[str]):
        if warnings:
            dialog.show_message(title=self.i18n['warning'].capitalize(),
                                body='<p>{}</p>'.format(
                                    '<br/><br/>'.join(warnings)),
                                type_=MessageType.WARNING)

    def show(self):
        super(ManageWindow, self).show()
        if not self.thread_warnings.isFinished():
            self.thread_warnings.start()

    def verify_warnings(self):
        self.thread_warnings.start()

    def _show_installed(self):
        if self.pkgs_installed:
            self.finish_action()
            self.ref_bt_upgrade.setVisible(True)
            self.ref_checkbox_only_apps.setVisible(True)
            self.input_search.setText('')
            self.input_name_filter.setText('')
            self.update_pkgs(new_pkgs=None, as_installed=True)

    def _show_about(self):
        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.i18n)

        self.dialog_about.show()

    def _handle_updates_filter(self, status: int):
        self.filter_updates = status == 2
        self.apply_filters_async()

    def _handle_filter_only_apps(self, status: int):
        self.filter_only_apps = status == 2
        self.apply_filters_async()

    def _handle_type_filter(self, idx: int):
        self.type_filter = self.combo_filter_type.itemData(idx)
        self.apply_filters_async()

    def _notify_model_data_change(self):
        self.table_apps.fill_async_data()

    def changeEvent(self, e: QEvent):
        if isinstance(e, QWindowStateChangeEvent):
            self._maximized = self.isMaximized()
            policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
            self.table_apps.change_headers_policy(policy)

    def closeEvent(self, event):

        if self.tray_icon:
            event.ignore()
            self.hide()
            self._handle_console_option(False)

    def _handle_console(self, checked: bool):

        if checked:
            self.textarea_output.show()
        else:
            self.textarea_output.hide()

    def _handle_console_option(self, enable: bool):

        if enable:
            self.textarea_output.clear()

        self.ref_checkbox_console.setVisible(enable)
        self.checkbox_console.setChecked(False)
        self.textarea_output.hide()

    def refresh_apps(self,
                     keep_console: bool = True,
                     top_app: PackageView = None,
                     pkg_types: Set[Type[SoftwarePackage]] = None):
        self.recent_installation = False
        self.type_filter = None
        self.input_search.clear()

        if not keep_console:
            self._handle_console_option(False)

        self.ref_checkbox_updates.setVisible(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self._begin_action(self.i18n['manage_window.status.refreshing'],
                           keep_bt_installed=False,
                           clear_filters=True)

        self.thread_refresh.app = top_app  # the app will be on top when refresh happens
        self.thread_refresh.pkg_types = pkg_types
        self.thread_refresh.start()

    def _finish_refresh_apps(self, res: dict, as_installed: bool = True):
        self.finish_action()
        self.ref_checkbox_only_apps.setVisible(bool(res['installed']))
        self.ref_bt_upgrade.setVisible(True)
        self.update_pkgs(res['installed'],
                         as_installed=as_installed,
                         types=res['types'])
        self.first_refresh = False

    def uninstall_app(self, app: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('uninstall', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.uninstalling'], app.model.name))

        self.thread_uninstall.app = app
        self.thread_uninstall.root_password = pwd
        self.thread_uninstall.start()

    def run_app(self, app: PackageView):
        self._begin_action(
            self.i18n['manage_window.status.running_app'].format(
                app.model.name))
        self.thread_run_app.app = app
        self.thread_run_app.start()

    def _finish_uninstall(self, pkgv: PackageView):
        self.finish_action()

        if pkgv:
            if self._can_notify_user():
                util.notify_user('{} ({}) {}'.format(pkgv.model.name,
                                                     pkgv.model.get_type(),
                                                     self.i18n['uninstalled']))

            self.refresh_apps(pkg_types={pkgv.model.__class__})
        else:
            if self._can_notify_user():
                util.notify_user('{}: {}'.format(
                    pkgv.model.name,
                    self.i18n['notification.uninstall.failed']))

            self.checkbox_console.setChecked(True)

    def _can_notify_user(self):
        return self.notifications and (self.isHidden() or self.isMinimized())

    def _finish_downgrade(self, res: dict):
        self.finish_action()

        if res['success']:
            if self._can_notify_user():
                util.notify_user('{} {}'.format(res['app'],
                                                self.i18n['downgraded']))

            self.refresh_apps(pkg_types={res['app'].model.__class__})

            if self.tray_icon:
                self.tray_icon.verify_updates(notify_user=False)
        else:
            if self._can_notify_user():
                util.notify_user(self.i18n['notification.downgrade.failed'])

            self.checkbox_console.setChecked(True)

    def _change_label_status(self, status: str):
        self.label_status.setText(status)

    def _change_label_substatus(self, substatus: str):
        self.label_substatus.setText('<p>{}</p>'.format(substatus))
        if not substatus:
            self.toolbar_substatus.hide()
        elif not self.toolbar_substatus.isVisible():
            self.toolbar_substatus.show()

    def _update_table(self, pkgs_info: dict, signal: bool = False):
        self.pkgs = pkgs_info['pkgs_displayed']

        self.table_apps.update_pkgs(
            self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0)

        if not self._maximized:
            self.table_apps.change_headers_policy(QHeaderView.Stretch)
            self.table_apps.change_headers_policy()
            self.resize_and_center(accept_lower_width=len(self.pkgs) > 0)
            self.label_displayed.setText('{} / {}'.format(
                len(self.pkgs), len(self.pkgs_available)))
        else:
            self.label_displayed.setText('')

        if signal:
            self.signal_table_update.emit()

    def update_bt_upgrade(self, pkgs_info: dict = None):
        show_bt_upgrade = False

        if not pkgs_info or pkgs_info['not_installed'] == 0:
            for app_v in (pkgs_info['pkgs_displayed']
                          if pkgs_info else self.pkgs):
                if app_v.update_checked:
                    show_bt_upgrade = True
                    break

        self.ref_bt_upgrade.setVisible(show_bt_upgrade)

    def change_update_state(self,
                            pkgs_info: dict,
                            trigger_filters: bool = True):
        self.update_bt_upgrade(pkgs_info)

        if pkgs_info['updates'] > 0:

            if pkgs_info['not_installed'] == 0:
                if not self.ref_checkbox_updates.isVisible():
                    self.ref_checkbox_updates.setVisible(True)

                if not self.filter_updates:
                    self._change_checkbox(self.checkbox_updates, True,
                                          'filter_updates', trigger_filters)

            if pkgs_info['napp_updates'] > 0 and self.filter_only_apps:
                self._change_checkbox(self.checkbox_only_apps, False,
                                      'filter_only_apps', trigger_filters)
        else:
            self._change_checkbox(self.checkbox_updates, False,
                                  'filter_updates', trigger_filters)

            self.ref_checkbox_updates.setVisible(False)

    def _change_checkbox(self,
                         checkbox: QCheckBox,
                         checked: bool,
                         attr: str = None,
                         trigger: bool = True):
        if not trigger:
            checkbox.blockSignals(True)

        checkbox.setChecked(checked)

        if not trigger:
            setattr(self, attr, checked)
            checkbox.blockSignals(False)

    def _gen_filters(self,
                     updates: int = 0,
                     ignore_updates: bool = False) -> dict:
        return {
            'only_apps':
            self.filter_only_apps,
            'type':
            self.type_filter,
            'updates':
            False if ignore_updates else self.filter_updates,
            'name':
            self.input_name_filter.get_text().lower()
            if self.input_name_filter.get_text() else None,
            'display_limit':
            self.display_limit if updates <= 0 else None
        }

    def update_pkgs(self,
                    new_pkgs: List[SoftwarePackage],
                    as_installed: bool,
                    types: Set[type] = None,
                    ignore_updates: bool = False):
        self.input_name_filter.setText('')
        pkgs_info = commons.new_pkgs_info()
        filters = self._gen_filters(ignore_updates)

        if new_pkgs is not None:
            old_installed = None

            if as_installed:
                old_installed = self.pkgs_installed
                self.pkgs_installed = []

            for pkg in new_pkgs:
                app_model = PackageView(model=pkg)
                commons.update_info(app_model, pkgs_info)
                commons.apply_filters(app_model, filters, pkgs_info)

            if old_installed and types:
                for pkgv in old_installed:
                    if not pkgv.model.__class__ in types:
                        commons.update_info(pkgv, pkgs_info)
                        commons.apply_filters(pkgv, filters, pkgs_info)

        else:  # use installed
            for pkgv in self.pkgs_installed:
                commons.update_info(pkgv, pkgs_info)
                commons.apply_filters(pkgv, filters, pkgs_info)

        if pkgs_info['apps_count'] == 0:
            if self.first_refresh:
                self._begin_search('')
                self.thread_suggestions.start()
                return
            else:
                self._change_checkbox(self.checkbox_only_apps,
                                      False,
                                      'filter_only_apps',
                                      trigger=False)
                self.checkbox_only_apps.setCheckable(False)
        else:
            self.checkbox_only_apps.setCheckable(True)
            self._change_checkbox(self.checkbox_only_apps,
                                  True,
                                  'filter_only_apps',
                                  trigger=False)

        self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)
        self._apply_filters(pkgs_info, ignore_updates=ignore_updates)
        self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False)

        self.pkgs_available = pkgs_info['pkgs']

        if as_installed:
            self.pkgs_installed = pkgs_info['pkgs']

        self.pkgs = pkgs_info['pkgs_displayed']

        if self.pkgs:
            self.ref_input_name_filter.setVisible(True)

        self._update_type_filters(pkgs_info['available_types'])

        self._update_table(pkgs_info=pkgs_info)

        if new_pkgs:
            self.thread_verify_models.apps = self.pkgs
            self.thread_verify_models.start()

        if self.pkgs_installed:
            self.ref_bt_installed.setVisible(not as_installed
                                             and not self.recent_installation)

        self.resize_and_center(accept_lower_width=self.pkgs_installed)

    def _apply_filters(self, pkgs_info: dict, ignore_updates: bool):
        pkgs_info['pkgs_displayed'] = []
        filters = self._gen_filters(updates=pkgs_info['updates'],
                                    ignore_updates=ignore_updates)
        for pkgv in pkgs_info['pkgs']:
            commons.apply_filters(pkgv, filters, pkgs_info)

    def _update_type_filters(self, available_types: dict = None):

        if available_types is None:
            self.ref_combo_filter_type.setVisible(
                self.combo_filter_type.count() > 1)
        else:
            self.type_filter = self.any_type_filter

            if available_types and len(available_types) > 1:
                if self.combo_filter_type.count() > 1:
                    for _ in range(self.combo_filter_type.count() - 1):
                        self.combo_filter_type.removeItem(1)

                for app_type, icon_path in available_types.items():
                    icon = self.cache_type_filter_icons.get(app_type)

                    if not icon:
                        icon = load_icon(icon_path, 14)
                        self.cache_type_filter_icons[app_type] = icon

                    self.combo_filter_type.addItem(icon, app_type.capitalize(),
                                                   app_type)

                self.ref_combo_filter_type.setVisible(True)
            else:
                self.ref_combo_filter_type.setVisible(False)

    def resize_and_center(self, accept_lower_width: bool = True):
        if self.pkgs:
            new_width = reduce(operator.add, [
                self.table_apps.columnWidth(i)
                for i in range(self.table_apps.columnCount())
            ])

            if self.ref_bt_upgrade.isVisible(
            ) or self.ref_bt_settings.isVisible():
                new_width *= 1.07
        else:
            new_width = self.toolbar_top.width()

        if accept_lower_width or new_width > self.width():
            self.resize(new_width, self.height())

            if self.ref_bt_upgrade.isVisible(
            ) and self.bt_upgrade.visibleRegion().isEmpty():
                self.adjustSize()

        qt_utils.centralize(self)

    def update_selected(self):
        if self.pkgs:
            requires_root = False

            to_update = []

            for app_v in self.pkgs:
                if app_v.update_checked:
                    to_update.append(app_v)

                    if self.manager.requires_root('update', app_v.model):
                        requires_root = True

            if to_update and dialog.ask_confirmation(
                    title=self.i18n['manage_window.upgrade_all.popup.title'],
                    body=self.i18n['manage_window.upgrade_all.popup.body'],
                    locale_keys=self.i18n,
                    widgets=[
                        UpdateToggleButton(
                            None, self, self.i18n, clickable=False)
                    ]):
                pwd = None

                if not is_root() and requires_root:
                    pwd, ok = ask_root_password(self.i18n)

                    if not ok:
                        return

                self._handle_console_option(True)
                self.progress_controll_enabled = len(to_update) == 1
                self._begin_action(self.i18n['manage_window.status.upgrading'])
                self.thread_update.apps_to_update = to_update
                self.thread_update.root_password = pwd
                self.thread_update.start()

    def _finish_update_selected(self, res: dict):
        self.finish_action()

        if res['success']:
            if self._can_notify_user():
                util.notify_user('{} {}'.format(
                    res['updated'],
                    self.i18n['notification.update_selected.success']))

            self.refresh_apps(pkg_types=res['types'])

            if self.tray_icon:
                self.tray_icon.verify_updates()
        else:
            if self._can_notify_user():
                util.notify_user(
                    self.i18n['notification.update_selected.failed'])

            self.ref_bt_upgrade.setVisible(True)
            self.checkbox_console.setChecked(True)

    def _update_action_output(self, output: str):
        self.textarea_output.appendPlainText(output)

    def _begin_action(self,
                      action_label: str,
                      keep_search: bool = False,
                      keep_bt_installed: bool = True,
                      clear_filters: bool = False):
        self.ref_input_name_filter.setVisible(False)
        self.ref_combo_filter_type.setVisible(False)
        self.ref_bt_settings.setVisible(False)
        self.thread_animate_progress.stop = False
        self.thread_animate_progress.start()
        self.ref_progress_bar.setVisible(True)
        self.ref_combo_styles.setVisible(False)

        self.label_status.setText(action_label + "...")
        self.ref_bt_upgrade.setVisible(False)
        self.ref_bt_refresh.setVisible(False)
        self.checkbox_only_apps.setEnabled(False)
        self.table_apps.setEnabled(False)
        self.checkbox_updates.setEnabled(False)

        if not keep_bt_installed:
            self.ref_bt_installed.setVisible(False)
        elif self.ref_bt_installed.isVisible():
            self.ref_bt_installed.setEnabled(False)

        if keep_search:
            self.ref_toolbar_search.setVisible(True)
        else:
            self.ref_toolbar_search.setVisible(False)

        if clear_filters:
            self._update_type_filters({})
        else:
            self.combo_filter_type.setEnabled(False)

    def finish_action(self):
        self.ref_combo_styles.setVisible(True)
        self.thread_animate_progress.stop = True
        self.thread_animate_progress.wait()
        self.ref_progress_bar.setVisible(False)
        self.progress_bar.setValue(0)
        self.progress_bar.setTextVisible(False)

        self._change_label_substatus('')
        self.ref_bt_settings.setVisible(True)

        self.ref_bt_refresh.setVisible(True)
        self.checkbox_only_apps.setEnabled(True)
        self.table_apps.setEnabled(True)
        self.input_search.setEnabled(True)
        self.label_status.setText('')
        self.label_substatus.setText('')
        self.ref_toolbar_search.setVisible(True)
        self.ref_toolbar_search.setEnabled(True)
        self.combo_filter_type.setEnabled(True)
        self.checkbox_updates.setEnabled(True)
        self.progress_controll_enabled = True

        if self.pkgs:
            self.ref_input_name_filter.setVisible(True)
            self.update_bt_upgrade()
            self._update_type_filters()

            if self.ref_bt_installed.isVisible():
                self.ref_bt_installed.setEnabled(True)

    def downgrade(self, pkgv: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('downgrade', pkgv.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.downgrading'], pkgv.model.name))

        self.thread_downgrade.app = pkgv
        self.thread_downgrade.root_password = pwd
        self.thread_downgrade.start()

    def get_app_info(self, pkg: dict):
        self._handle_console_option(False)
        self._begin_action(self.i18n['manage_window.status.info'])

        self.thread_get_info.app = pkg
        self.thread_get_info.start()

    def get_app_history(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.i18n['manage_window.status.history'])

        self.thread_get_history.app = app
        self.thread_get_history.start()

    def _finish_get_info(self, app_info: dict):
        self.finish_action()
        dialog_info = InfoDialog(app=app_info,
                                 icon_cache=self.icon_cache,
                                 locale_keys=self.i18n,
                                 screen_size=self.screen_size)
        dialog_info.exec_()

    def _finish_get_history(self, res: dict):
        self.finish_action()

        if res.get('error'):
            self._handle_console_option(True)
            self.textarea_output.appendPlainText(res['error'])
            self.checkbox_console.setChecked(True)
        else:
            dialog_history = HistoryDialog(res['history'], self.icon_cache,
                                           self.i18n)
            dialog_history.exec_()

    def _begin_search(self, word):
        self._handle_console_option(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self.ref_checkbox_updates.setVisible(False)
        self.filter_updates = False
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.searching'], word if word else ''),
                           clear_filters=True)

    def search(self):
        word = self.input_search.text().strip()
        if word:
            self._begin_search(word)
            self.thread_search.word = word
            self.thread_search.start()

    def _finish_search(self, res: dict):
        self.finish_action()

        if not res['error']:
            self.ref_bt_upgrade.setVisible(False)
            self.update_pkgs(res['pkgs_found'],
                             as_installed=False,
                             ignore_updates=True)
        else:
            dialog.show_message(title=self.i18n['warning'].capitalize(),
                                body=self.i18n[res['error']],
                                type_=MessageType.WARNING)

    def install(self, pkg: PackageView):
        pwd = None
        requires_root = self.manager.requires_root('install', pkg.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.i18n['manage_window.status.installing'], pkg.model.name))

        self.thread_install.pkg = pkg
        self.thread_install.root_password = pwd
        self.thread_install.start()

    def _finish_install(self, res: dict):
        self.input_search.setText('')
        self.finish_action()

        console_output = self.textarea_output.toPlainText()

        if console_output:
            log_path = '/tmp/bauh/logs/install/{}/{}'.format(
                res['pkg'].model.get_type(), res['pkg'].model.name)
            try:
                Path(log_path).mkdir(parents=True, exist_ok=True)

                log_file = log_path + '/{}.log'.format(int(time.time()))
                with open(log_file, 'w+') as f:
                    f.write(console_output)

                self.textarea_output.appendPlainText(
                    self.i18n['console.install_logs.path'].format(
                        '"{}"'.format(log_file)))
            except:
                self.textarea_output.appendPlainText(
                    "[warning] Could not write install log file to '{}'".
                    format(log_path))

        if res['success']:
            self.recent_installation = True
            if self._can_notify_user():
                util.notify_user(msg='{} ({}) {}'.format(
                    res['pkg'].model.name, res['pkg'].model.get_type(),
                    self.i18n['installed']))

            self._finish_refresh_apps({
                'installed': [res['pkg'].model],
                'total': 1,
                'types': None
            })
            self.ref_bt_installed.setVisible(False)
            self.ref_checkbox_only_apps.setVisible(False)
        else:
            if self._can_notify_user():
                util.notify_user('{}: {}'.format(
                    res['pkg'].model.name,
                    self.i18n['notification.install.failed']))

            self.checkbox_console.setChecked(True)

    def _update_progress(self, value: int):
        self.progress_bar.setValue(value)

    def _finish_run_app(self, success: bool):
        self.finish_action()

    def execute_custom_action(self, pkg: PackageView, action: PackageAction):
        pwd = None

        if not is_root() and action.requires_root:
            pwd, ok = ask_root_password(self.i18n)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(self.i18n[action.i18n_status_key],
                                          pkg.model.name))

        self.thread_custom_action.pkg = pkg
        self.thread_custom_action.root_password = pwd
        self.thread_custom_action.custom_action = action
        self.thread_custom_action.start()

    def _finish_custom_action(self, res: dict):
        self.finish_action()
        if res['success']:
            self.refresh_apps(pkg_types={res['pkg'].model.__class__})
        else:
            self.checkbox_console.setChecked(True)

    def show_gems_selector(self):
        gem_panel = GemSelectorPanel(window=self,
                                     manager=self.manager,
                                     i18n=self.i18n,
                                     config=self.config,
                                     show_panel_after_restart=bool(
                                         self.tray_icon))
        gem_panel.show()

    def _show_settings_menu(self):
        menu_row = QMenu()

        if isinstance(self.manager, GenericSoftwareManager):
            action_gems = QAction(self.i18n['manage_window.settings.gems'])
            action_gems.setIcon(self.icon_app)

            action_gems.triggered.connect(self.show_gems_selector)
            menu_row.addAction(action_gems)

        action_about = QAction(self.i18n['manage_window.settings.about'])
        action_about.setIcon(QIcon(resource.get_path('img/about.svg')))
        action_about.triggered.connect(self._show_about)
        menu_row.addAction(action_about)

        menu_row.adjustSize()
        menu_row.popup(QCursor.pos())
        menu_row.exec_()
예제 #7
0
class ManageWindow(QWidget):
    __BASE_HEIGHT__ = 400

    def __init__(self,
                 locale_keys: dict,
                 icon_cache: Cache,
                 manager: ApplicationManager,
                 disk_cache: bool,
                 download_icons: bool,
                 screen_size,
                 suggestions: bool,
                 tray_icon=None):
        super(ManageWindow, self).__init__()
        self.locale_keys = locale_keys
        self.manager = manager
        self.tray_icon = tray_icon
        self.working = False  # restrict the number of threaded actions
        self.apps = []
        self.label_flatpak = None
        self.icon_cache = icon_cache
        self.disk_cache = disk_cache
        self.download_icons = download_icons
        self.screen_size = screen_size

        self.icon_flathub = QIcon(resource.get_path('img/logo.svg'))
        self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
        self.setWindowIcon(self.icon_flathub)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.toolbar_top = QToolBar()
        self.toolbar_top.addWidget(self._new_spacer())

        self.label_status = QLabel()
        self.label_status.setText('')
        self.label_status.setStyleSheet("font-weight: bold")
        self.toolbar_top.addWidget(self.label_status)

        self.toolbar_search = QToolBar()
        self.toolbar_search.setStyleSheet("spacing: 0px;")
        self.toolbar_search.setContentsMargins(0, 0, 0, 0)

        label_pre_search = QLabel()
        label_pre_search.setStyleSheet(
            "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pre_search)

        self.input_search = QLineEdit()
        self.input_search.setMaxLength(20)
        self.input_search.setFrame(False)
        self.input_search.setPlaceholderText(
            self.locale_keys['window_manage.input_search.placeholder'] + "...")
        self.input_search.setToolTip(
            self.locale_keys['window_manage.input_search.tooltip'])
        self.input_search.setStyleSheet(
            "QLineEdit { background-color: white; color: gray; spacing: 0;}")
        self.input_search.returnPressed.connect(self.search)
        self.toolbar_search.addWidget(self.input_search)

        label_pos_search = QLabel()
        label_pos_search.setPixmap(QPixmap(
            resource.get_path('img/search.svg')))
        label_pos_search.setStyleSheet(
            "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;"
        )
        self.toolbar_search.addWidget(label_pos_search)

        self.ref_toolbar_search = self.toolbar_top.addWidget(
            self.toolbar_search)
        self.toolbar_top.addWidget(self._new_spacer())
        self.layout.addWidget(self.toolbar_top)

        toolbar = QToolBar()

        self.checkbox_updates = QCheckBox()
        self.checkbox_updates.setText(self.locale_keys['updates'].capitalize())
        self.checkbox_updates.stateChanged.connect(self._handle_updates_filter)
        self.ref_checkbox_updates = toolbar.addWidget(self.checkbox_updates)

        self.checkbox_only_apps = QCheckBox()
        self.checkbox_only_apps.setText(
            self.locale_keys['manage_window.checkbox.only_apps'])
        self.checkbox_only_apps.setChecked(True)
        self.checkbox_only_apps.stateChanged.connect(
            self._handle_filter_only_apps)
        self.ref_checkbox_only_apps = toolbar.addWidget(
            self.checkbox_only_apps)

        self.extra_filters = QWidget()
        self.extra_filters.setLayout(QHBoxLayout())
        toolbar.addWidget(self.extra_filters)

        toolbar.addWidget(self._new_spacer())

        self.bt_refresh = QToolButton()
        self.bt_refresh.setToolTip(
            locale_keys['manage_window.bt.refresh.tooltip'])
        self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
        self.bt_refresh.clicked.connect(
            lambda: self.refresh_apps(keep_console=False))
        toolbar.addWidget(self.bt_refresh)

        self.bt_upgrade = QToolButton()
        self.bt_upgrade.setToolTip(
            locale_keys['manage_window.bt.upgrade.tooltip'])
        self.bt_upgrade.setIcon(
            QIcon(resource.get_path('img/update_green.svg')))
        self.bt_upgrade.setEnabled(False)
        self.bt_upgrade.clicked.connect(self.update_selected)
        self.ref_bt_upgrade = toolbar.addWidget(self.bt_upgrade)

        self.layout.addWidget(toolbar)

        self.table_apps = AppsTable(self,
                                    self.icon_cache,
                                    disk_cache=self.disk_cache,
                                    download_icons=self.download_icons)
        self.table_apps.change_headers_policy()

        self.layout.addWidget(self.table_apps)

        toolbar_console = QToolBar()

        self.checkbox_console = QCheckBox()
        self.checkbox_console.setText(
            self.locale_keys['manage_window.checkbox.show_details'])
        self.checkbox_console.stateChanged.connect(self._handle_console)
        self.checkbox_console.setVisible(False)
        self.ref_checkbox_console = toolbar_console.addWidget(
            self.checkbox_console)

        toolbar_console.addWidget(self._new_spacer())
        self.layout.addWidget(toolbar_console)

        self.textarea_output = QPlainTextEdit(self)
        self.textarea_output.resize(self.table_apps.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.thread_update = UpdateSelectedApps(self.manager)
        self.thread_update.signal_output.connect(self._update_action_output)
        self.thread_update.signal_finished.connect(
            self._finish_update_selected)
        self.thread_update.signal_status.connect(
            self._change_updating_app_status)

        self.thread_refresh = RefreshApps(self.manager)
        self.thread_refresh.signal.connect(self._finish_refresh_apps)

        self.thread_uninstall = UninstallApp(self.manager, self.icon_cache)
        self.thread_uninstall.signal_output.connect(self._update_action_output)
        self.thread_uninstall.signal_finished.connect(self._finish_uninstall)

        self.thread_downgrade = DowngradeApp(self.manager, self.locale_keys)
        self.thread_downgrade.signal_output.connect(self._update_action_output)
        self.thread_downgrade.signal_finished.connect(self._finish_downgrade)

        self.thread_get_info = GetAppInfo(self.manager)
        self.thread_get_info.signal_finished.connect(self._finish_get_info)

        self.thread_get_history = GetAppHistory(self.manager, self.locale_keys)
        self.thread_get_history.signal_finished.connect(
            self._finish_get_history)

        self.thread_search = SearchApps(self.manager)
        self.thread_search.signal_finished.connect(self._finish_search)

        self.thread_install = InstallApp(manager=self.manager,
                                         disk_cache=self.disk_cache,
                                         icon_cache=self.icon_cache,
                                         locale_keys=self.locale_keys)
        self.thread_install.signal_output.connect(self._update_action_output)
        self.thread_install.signal_finished.connect(self._finish_install)

        self.thread_animate_progress = AnimateProgress()
        self.thread_animate_progress.signal_change.connect(
            self._update_progress)

        self.thread_verify_models = VerifyModels()
        self.thread_verify_models.signal_updates.connect(
            self._notify_model_data_change)

        self.thread_refresh_app = RefreshApp(manager=self.manager)
        self.thread_refresh_app.signal_finished.connect(self._finish_refresh)
        self.thread_refresh_app.signal_output.connect(
            self._update_action_output)

        self.thread_suggestions = FindSuggestions(man=self.manager)
        self.thread_suggestions.signal_finished.connect(self._finish_search)

        self.toolbar_bottom = QToolBar()
        self.toolbar_bottom.setIconSize(QSize(16, 16))

        self.label_updates = QLabel()
        self.ref_label_updates = self.toolbar_bottom.addWidget(
            self.label_updates)

        self.toolbar_bottom.addWidget(self._new_spacer())

        self.progress_bar = QProgressBar()
        self.progress_bar.setTextVisible(False)
        self.ref_progress_bar = self.toolbar_bottom.addWidget(
            self.progress_bar)

        self.toolbar_bottom.addWidget(self._new_spacer())

        bt_about = QToolButton()
        bt_about.setStyleSheet('QToolButton { border: 0px; }')
        bt_about.setIcon(QIcon(resource.get_path('img/about.svg')))
        bt_about.clicked.connect(self._show_about)
        bt_about.setToolTip(self.locale_keys['manage_window.bt_about.tooltip'])
        self.ref_bt_about = self.toolbar_bottom.addWidget(bt_about)

        self.layout.addWidget(self.toolbar_bottom)

        self.centralize()

        self.filter_only_apps = True
        self.filter_types = set()
        self.filter_updates = False
        self._maximized = False

        self.dialog_about = None
        self.first_refresh = suggestions

        self.thread_warnings = ListWarnings(man=manager,
                                            locale_keys=locale_keys)
        self.thread_warnings.signal_warnings.connect(self._show_warnings)

    def _show_warnings(self, warnings: List[str]):
        if warnings:
            for warning in warnings:
                dialog.show_warning(
                    title=self.locale_keys['warning'].capitalize(),
                    body=warning)

    def show(self):
        super(ManageWindow, self).show()
        if not self.thread_warnings.isFinished():
            self.thread_warnings.start()

    def _show_about(self):
        if self.dialog_about is None:
            self.dialog_about = AboutDialog(self.locale_keys)

        self.dialog_about.show()

    def _handle_updates_filter(self, status: int):
        self.filter_updates = status == 2
        self.apply_filters()

    def _handle_filter_only_apps(self, status: int):
        self.filter_only_apps = status == 2
        self.apply_filters()

    def _handle_type_filter(self, status: int, app_type: str):

        if status == 2:
            self.filter_types.add(app_type)
        elif app_type in self.filter_types:
            self.filter_types.remove(app_type)

        self.apply_filters()

    def _notify_model_data_change(self):
        self.table_apps.fill_async_data()

    def _new_spacer(self):
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        return spacer

    def changeEvent(self, e: QEvent):
        if isinstance(e, QWindowStateChangeEvent):
            self._maximized = self.isMaximized()
            policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
            self.table_apps.change_headers_policy(policy)

    def closeEvent(self, event):

        if self.tray_icon:
            event.ignore()
            self.hide()
            self._handle_console_option(False)

    def _handle_console(self, checked: bool):

        if checked:
            self.textarea_output.show()
        else:
            self.textarea_output.hide()

    def _handle_console_option(self, enable: bool):

        if enable:
            self.textarea_output.clear()

        self.ref_checkbox_console.setVisible(enable)
        self.checkbox_console.setChecked(False)
        self.textarea_output.hide()

    def refresh_apps(self, keep_console: bool = True):
        self.filter_types.clear()
        self.input_search.clear()

        if not keep_console:
            self._handle_console_option(False)

        self.ref_checkbox_updates.setVisible(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self._begin_action(self.locale_keys['manage_window.status.refreshing'],
                           clear_filters=True)
        self.thread_refresh.start()

    def _finish_refresh_apps(self, apps: List[Application]):
        self.finish_action()
        self.ref_checkbox_only_apps.setVisible(True)
        self.ref_bt_upgrade.setVisible(True)
        self.update_apps(apps)
        self.first_refresh = False

    def uninstall_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('uninstall', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.uninstalling'],
            app.model.base_data.name))

        self.thread_uninstall.app = app
        self.thread_uninstall.root_password = pwd
        self.thread_uninstall.start()

    def refresh(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('refresh', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.refreshing_app'],
            app.model.base_data.name))

        self.thread_refresh_app.app = app
        self.thread_refresh_app.root_password = pwd
        self.thread_refresh_app.start()

    def _finish_uninstall(self, app: ApplicationView):
        self.finish_action()

        if app:
            if self._can_notify_user():
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['uninstalled']))

            self.refresh_apps()
        else:
            if self._can_notify_user():
                system.notify_user('{}: {}'.format(
                    app.model.base_data.name,
                    self.locale_keys['notification.uninstall.failed']))

            self.checkbox_console.setChecked(True)

    def _can_notify_user(self):
        return self.isHidden() or self.isMinimized()

    def _finish_downgrade(self, success: bool):
        self.finish_action()

        if success:
            if self._can_notify_user():
                app = self.table_apps.get_selected_app()
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['downgraded']))

            self.refresh_apps()

            if self.tray_icon:
                self.tray_icon.verify_updates(notify_user=False)
        else:
            if self._can_notify_user():
                system.notify_user(
                    self.locale_keys['notification.downgrade.failed'])

            self.checkbox_console.setChecked(True)

    def _finish_refresh(self, success: bool):
        self.finish_action()

        if success:
            self.refresh_apps()
        else:
            self.checkbox_console.setChecked(True)

    def _change_updating_app_status(self, app_name: str):
        self.label_status.setText('{} {}...'.format(
            self.locale_keys['manage_window.status.upgrading'], app_name))

    def apply_filters(self):
        if self.apps:
            visible_apps = len(self.apps)
            for idx, app_v in enumerate(self.apps):
                hidden = self.filter_only_apps and app_v.model.is_library()

                if not hidden and self.filter_types is not None:
                    hidden = app_v.model.get_type() not in self.filter_types

                if not hidden and self.filter_updates:
                    hidden = not app_v.model.update

                self.table_apps.setRowHidden(idx, hidden)
                app_v.visible = not hidden
                visible_apps -= 1 if hidden else 0

            self.change_update_state(change_filters=False)

            if not self._maximized:
                self.table_apps.change_headers_policy(QHeaderView.Stretch)
                self.table_apps.change_headers_policy()
                self.resize_and_center(accept_lower_width=visible_apps > 0)

    def change_update_state(self, change_filters: bool = True):
        enable_bt_update = False
        app_updates, library_updates, not_installed = 0, 0, 0

        for app_v in self.apps:
            if app_v.model.update:
                if app_v.model.runtime:
                    library_updates += 1
                else:
                    app_updates += 1

            if not app_v.model.installed:
                not_installed += 1

        for app_v in self.apps:
            if not_installed == 0 and app_v.visible and app_v.update_checked:
                enable_bt_update = True
                break

        self.bt_upgrade.setEnabled(enable_bt_update)

        total_updates = app_updates + library_updates

        if total_updates > 0:
            self.label_updates.setPixmap(
                QPixmap(resource.get_path('img/exclamation.svg')).scaled(
                    16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation))
            self.label_updates.setToolTip('{}: {} ( {} {} | {} {} )'.format(
                self.locale_keys['manage_window.label.updates'], total_updates,
                app_updates,
                self.locale_keys['manage_window.checkbox.only_apps'].lower(),
                library_updates, self.locale_keys['others'].lower()))

            if not_installed == 0:
                if not self.ref_checkbox_updates.isVisible():
                    self.ref_checkbox_updates.setVisible(True)

                if change_filters and not self.checkbox_updates.isChecked():
                    self.checkbox_updates.setChecked(True)

            if change_filters and library_updates > 0 and self.checkbox_only_apps.isChecked(
            ):
                self.checkbox_only_apps.setChecked(False)
        else:
            self.checkbox_updates.setChecked(False)
            self.ref_checkbox_updates.setVisible(False)
            self.label_updates.setPixmap(QPixmap())

    def centralize(self):
        geo = self.frameGeometry()
        screen = QApplication.desktop().screenNumber(
            QApplication.desktop().cursor().pos())
        center_point = QApplication.desktop().screenGeometry(screen).center()
        geo.moveCenter(center_point)
        self.move(geo.topLeft())

    def update_apps(self,
                    apps: List[Application],
                    update_check_enabled: bool = True):
        self.apps = []

        napps = 0  # number of apps (not libraries)
        available_types = set()

        if apps:
            for app in apps:
                app_model = ApplicationView(
                    model=app,
                    visible=(not app.is_library())
                    or not self.checkbox_only_apps.isChecked())
                available_types.add(app.get_type())
                napps += 1 if not app.is_library() else 0
                self.apps.append(app_model)

        if napps == 0:

            if self.first_refresh:
                self._begin_search('')
                self.thread_suggestions.start()
                return
            else:
                self.checkbox_only_apps.setChecked(False)
                self.checkbox_only_apps.setCheckable(False)
        else:
            self.checkbox_only_apps.setCheckable(True)
            self.checkbox_only_apps.setChecked(True)

        self._update_type_filters(available_types)
        self.table_apps.update_apps(self.apps,
                                    update_check_enabled=update_check_enabled)
        self.apply_filters()
        self.change_update_state()
        self.resize_and_center()

        self.thread_verify_models.apps = self.apps
        self.thread_verify_models.start()

    def _update_type_filters(self, available_types: Set[str]):

        self.filter_types = available_types

        filters_layout = self.extra_filters.layout()
        for i in reversed(range(filters_layout.count())):
            filters_layout.itemAt(i).widget().setParent(None)

        if available_types:
            for app_type in sorted(list(available_types)):
                checkbox_app_type = QCheckBox()
                checkbox_app_type.setChecked(True)
                checkbox_app_type.setText(app_type.capitalize())

                def handle_click(status: int, filter_type: str = app_type):
                    self._handle_type_filter(status, filter_type)

                checkbox_app_type.stateChanged.connect(handle_click)
                filters_layout.addWidget(checkbox_app_type)

    def resize_and_center(self, accept_lower_width: bool = True):
        new_width = reduce(operator.add, [
            self.table_apps.columnWidth(i)
            for i in range(len(self.table_apps.column_names))
        ]) * 1.05

        if accept_lower_width or new_width > self.width():
            self.resize(new_width, self.height())

        self.centralize()

    def update_selected(self):
        if self.apps:

            to_update = [
                app_v for app_v in self.apps
                if app_v.visible and app_v.update_checked
            ]

            if to_update:
                if dialog.ask_confirmation(
                        title=self.
                        locale_keys['manage_window.upgrade_all.popup.title'],
                        body=self.
                        locale_keys['manage_window.upgrade_all.popup.body'],
                        locale_keys=self.locale_keys):
                    self._handle_console_option(True)

                    self._begin_action(
                        self.locale_keys['manage_window.status.upgrading'])
                    self.thread_update.apps_to_update = to_update
                    self.thread_update.start()

    def _finish_update_selected(self, success: bool, updated: int):
        self.finish_action()

        if success:
            if self._can_notify_user():
                system.notify_user('{} {}'.format(
                    updated,
                    self.locale_keys['notification.update_selected.success']))

            self.refresh_apps()

            if self.tray_icon:
                self.tray_icon.verify_updates()
        else:
            if self._can_notify_user():
                system.notify_user(
                    self.locale_keys['notification.update_selected.failed'])

            self.bt_upgrade.setEnabled(True)
            self.checkbox_console.setChecked(True)

    def _update_action_output(self, output: str):
        self.textarea_output.appendPlainText(output)

    def _begin_action(self,
                      action_label: str,
                      keep_search: bool = False,
                      clear_filters: bool = False):
        self.ref_bt_about.setVisible(False)
        self.ref_label_updates.setVisible(False)
        self.thread_animate_progress.stop = False
        self.thread_animate_progress.start()
        self.ref_progress_bar.setVisible(True)

        self.label_status.setText(action_label + "...")
        self.bt_upgrade.setEnabled(False)
        self.bt_refresh.setEnabled(False)
        self.checkbox_only_apps.setEnabled(False)
        self.table_apps.setEnabled(False)
        self.checkbox_updates.setEnabled(False)

        if keep_search:
            self.ref_toolbar_search.setVisible(True)
        else:
            self.ref_toolbar_search.setVisible(False)

        if clear_filters:
            self._update_type_filters(set())
        else:
            self.extra_filters.setEnabled(False)

    def finish_action(self):
        self.ref_bt_about.setVisible(True)
        self.ref_progress_bar.setVisible(False)
        self.ref_label_updates.setVisible(True)
        self.thread_animate_progress.stop = True
        self.progress_bar.setValue(0)
        self.bt_refresh.setEnabled(True)
        self.checkbox_only_apps.setEnabled(True)
        self.table_apps.setEnabled(True)
        self.input_search.setEnabled(True)
        self.label_status.setText('')
        self.ref_toolbar_search.setVisible(True)
        self.ref_toolbar_search.setEnabled(True)
        self.extra_filters.setEnabled(True)
        self.checkbox_updates.setEnabled(True)

    def downgrade_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root(
            'downgrade',
            self.table_apps.get_selected_app().model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.downgrading'],
            app.model.base_data.name))

        self.thread_downgrade.app = app
        self.thread_downgrade.root_password = pwd
        self.thread_downgrade.start()

    def get_app_info(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.locale_keys['manage_window.status.info'])

        self.thread_get_info.app = app
        self.thread_get_info.start()

    def get_app_history(self, app: dict):
        self._handle_console_option(False)
        self._begin_action(self.locale_keys['manage_window.status.history'])

        self.thread_get_history.app = app
        self.thread_get_history.start()

    def _finish_get_info(self, app_info: dict):
        self.finish_action()
        self.change_update_state(change_filters=False)
        dialog_info = InfoDialog(app=app_info,
                                 icon_cache=self.icon_cache,
                                 locale_keys=self.locale_keys,
                                 screen_size=self.screen_size)
        dialog_info.exec_()

    def _finish_get_history(self, app: dict):
        self.finish_action()
        self.change_update_state(change_filters=False)

        if app.get('error'):
            self._handle_console_option(True)
            self.textarea_output.appendPlainText(app['error'])
            self.checkbox_console.setChecked(True)
        else:
            dialog_history = HistoryDialog(app, self.icon_cache,
                                           self.locale_keys)
            dialog_history.exec_()

    def _begin_search(self, word):
        self._handle_console_option(False)
        self.ref_checkbox_only_apps.setVisible(False)
        self.ref_checkbox_updates.setVisible(False)
        self.filter_updates = False
        self._begin_action('{}{}'.format(
            self.locale_keys['manage_window.status.searching'],
            '"{}"'.format(word) if word else ''),
                           clear_filters=True)

    def search(self):

        word = self.input_search.text().strip()

        if word:
            self._begin_search(word)
            self.thread_search.word = word
            self.thread_search.start()

    def _finish_search(self, apps_found: List[Application]):
        self.finish_action()
        self.ref_bt_upgrade.setVisible(False)
        self.update_apps(apps_found, update_check_enabled=False)

    def install_app(self, app: ApplicationView):
        pwd = None
        requires_root = self.manager.requires_root('install', app.model)

        if not is_root() and requires_root:
            pwd, ok = ask_root_password(self.locale_keys)

            if not ok:
                return

        self._handle_console_option(True)
        self._begin_action('{} {}'.format(
            self.locale_keys['manage_window.status.installing'],
            app.model.base_data.name))

        self.thread_install.app = app
        self.thread_install.root_password = pwd
        self.thread_install.start()

    def _finish_install(self, app: ApplicationView):
        self.input_search.setText('')
        self.finish_action()

        if app:
            if self._can_notify_user():
                system.notify_user('{} ({}) {}'.format(
                    app.model.base_data.name, app.model.get_type(),
                    self.locale_keys['installed']))

            self.refresh_apps()
        else:
            if self._can_notify_user():
                system.notify_user('{}: {}'.format(
                    app.model.base_data.name,
                    self.locale_keys['notification.install.failed']))

            self.checkbox_console.setChecked(True)

    def _update_progress(self, value: int):
        self.progress_bar.setValue(value)