class WindowController(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle(
            "Spongo - Projet M1 ISEN Yncréa Brest | Margaux DOUDET et Alexandre THOMAS"
        )
        self.setFixedSize(1280, 720)
        self.setWindowIcon(QIcon(":/img/spongo_icon.png"))

        self.stacked_widget = QStackedWidget()
        self.stacked_widget.setObjectName("page-container")
        self.setCentralWidget(self.stacked_widget)

        self._widgets = {
            "/menu": MenuController(),
            "/parameters": ParametersController(),
            "/analysis": AnalysisController(),
            "/history": HistoryController()
        }

        for w in self._widgets.values():
            self.stacked_widget.addWidget(w)
            w.changeWidget.connect(self._route)

        self._route("/menu")

        self.show()

    def closeEvent(self, event: QCloseEvent):
        ask_exit = self.stacked_widget.currentWidget().askExit()

        if ask_exit:
            event.accept()
        else:
            event.ignore()

    @Slot(str, object)
    def _route(self, route_name: str, parameters: object = None):
        next_widget = None

        for r, w in self._widgets.items():
            if r == route_name:
                next_widget = w
                break

        if next_widget is None:
            print("[WARNING] Unknown widget : %s" % str(next_widget))
            return

        current_widget = self.stacked_widget.currentWidget()
        current_widget.stop()

        self.stacked_widget.setCurrentWidget(next_widget)

        if route_name == "/analysis":
            next_widget.start(parameters[0], parameters[1])
        elif route_name == "/history":
            next_widget.start(parameters)
        else:
            next_widget.start()
Exemple #2
0
class MainWidget(QWidget):
    def __init__(self, parent=None):
        super(MainWidget, self).__init__(parent)
        self.initUI()

    def initUI(self):
        layout = QVBoxLayout(self)

        self.stack = QStackedWidget(parent=self)

        self.search = SearchWidget(parent=self)
        self.search.searchButton.clicked.connect(self.goSearch)
        self.back = BackWidget(parent=self)
        self.back.backButton.clicked.connect(self.goBack)

        self.stack.addWidget(self.search)
        self.stack.addWidget(self.back)

        layout.addWidget(self.stack)

    def goSearch(self):
        self.stack.setCurrentWidget(self.back)

    def goBack(self):
        self.stack.setCurrentWidget(self.search)
    def _set_progress_layout(self, progress_layout, widget, transfered, size,
                             state):
        is_current = state in self.CURRENT_TASK_STATES
        is_error = state in self.ERROR_STATES

        progress_background = QStackedWidget(widget)
        progress_background.setObjectName("progress_background")
        progress_bar = QProgressBar(progress_background)
        progress_bar.setObjectName("progress_bar")
        progress_bar.setMinimum(0)
        progress_bar.setMaximum(size if is_current and state != DOWNLOAD_FAILED
                                and self._paused_state == self.WORKING else 0)
        if is_current:
            progress_bar.setValue(transfered)
        progress_bar.setTextVisible(False)

        progress_label = QLabel(widget)
        progress_label.setObjectName("progress_label")

        self._set_progress_bar_style(progress_bar, progress_background,
                                     progress_label, state, is_current,
                                     is_error)

        progress_background.addWidget(progress_bar)
        progress_layout.addWidget(progress_background,
                                  alignment=Qt.AlignVCenter)

        progress_label.setFont(QFont('Noto Sans', 7 * self._dp))
        progress_layout.addWidget(progress_label)
        spacerItem = QSpacerItem(6, 10, QSizePolicy.Maximum,
                                 QSizePolicy.Minimum)
        progress_layout.addItem(spacerItem)
Exemple #4
0
class stackedExample(QWidget):
    def __init__(self):
        super(stackedExample, self).__init__()

        self.leftList = QListWidget()
        self.leftList.insertItem(0, 'Contact')
        self.leftList.insertItem(1, 'Personal')
        self.leftList.insertItem(2, 'Educational')

        self.stack_1 = QWidget()
        self.stack_2 = QWidget()
        self.stack_3 = QWidget()

        self.stack_1_UI()
        self.stack_2_UI()
        self.stack_3_UI()

        self.stack = QStackedWidget()
        self.stack.addWidget(self.stack_1)
        self.stack.addWidget(self.stack_2)
        self.stack.addWidget(self.stack_3)

        hbox = QHBoxLayout()
        hbox.addWidget(self.leftList)
        hbox.addWidget(self.stack)

        self.setLayout(hbox)
        self.leftList.currentRowChanged.connect(self.display)
        self.setGeometry(300, 50, 10, 10)
        self.setWindowTitle("Stack Widget Demo")
        self.show()

    def stack_1_UI(self):
        layout = QFormLayout()
        layout.addRow("Name", QLineEdit())
        layout.addRow("Address", QLineEdit())
        #self.setTabText(0,"Contact Details")
        self.stack_1.setLayout(layout)

    def stack_2_UI(self):
        layout = QFormLayout()
        sex = QHBoxLayout()
        sex.addWidget(QRadioButton("Male"))
        sex.addWidget(QRadioButton("Female"))
        layout.addRow(QLabel("Sex"), sex)
        layout.addRow("Date of Birth", QLineEdit())
        self.stack_2.setLayout(layout)

    def stack_3_UI(self):
        layout = QHBoxLayout()
        layout.addWidget(QLabel("subjects"))
        layout.addWidget(QCheckBox("Physics"))
        layout.addWidget(QCheckBox("Maths"))
        self.stack_3.setLayout(layout)

    def display(self, i):
        self.stack.setCurrentIndex(i)
Exemple #5
0
    def makeResultStack(self):
        stack = QStackedWidget()
        # refresh = QPushButton()
        # refresh.setIcon(QIcon.fromTheme('view-refresh'))
        # stack.addWidget(refresh)

        for i in self.state.interfaces:
            s = QScrollArea()
            stack.addWidget(s)
        return stack
Exemple #6
0
class UiMainWindow:
    def setupUi(self, main_window):
        main_window.setObjectName('main_window')
        main_window.setWindowTitle('qt_mvc Demo')
        main_window.resize(800, 600)

        # pyqtgraph configuration
        setConfigOption('background', 'w')
        setConfigOption('foreground', 'k')

        # Divide window into left (toolbar) and right(main) vertical layouts
        self.central_widget = QWidget(main_window)
        self.central_widget.setObjectName('centralwidget')
        self.central_layout = QHBoxLayout(self.central_widget)
        self.central_layout.setObjectName('centrallayout')
        self.left_bar_layout = QVBoxLayout()
        self.left_bar_layout.setObjectName('left_bar_layout')
        self.main_layout = QVBoxLayout()
        self.main_layout.setObjectName('main_layout')
        self.central_layout.addLayout(self.left_bar_layout, 0)
        self.central_layout.addLayout(self.main_layout, 1)

        # Populate left toolbar
        self.calctype = CalcTypeButtonGroup('Calc Type')
        self.calctype.setObjectName('calctype_menu')
        self.stack_model_selections = QStackedWidget()
        self.stack_model_selections.setObjectName('model_selection_stack')
        self.multiplet_menu = MultipletButtonGroup('Multiplet')
        self.multiplet_menu.setObjectName('multiplet_menu')
        self.abc_menu = ABC_ButtonGroup('Number of Spins')
        self.abc_menu.setObjectName('abc_menu')
        self.dnmr_menu = DNMR_ButtonGroup('DNMR')
        self.dnmr_menu.setObjectName('dnmr_menu')
        for menu in [self.multiplet_menu, self.abc_menu, self.dnmr_menu]:
            self.stack_model_selections.addWidget(menu)
        self.stack_model_selections.setCurrentWidget(self.multiplet_menu)
        self.left_bar_layout.addWidget(self.calctype, 0)
        self.left_bar_layout.addWidget(self.stack_model_selections, 0)
        self.left_bar_layout.addWidget(QWidget(), 1)

        # Add toolbars and plot area to main layout
        self.toolbars = toolbar_stack(main_window, main_window.view_state)
        self.plot = PlotWidget()
        self.plot.getViewBox().invertX(True)  # Reverse x axis "NMR style"
        self.main_layout.addWidget(self.toolbars, 0)
        self.main_layout.addWidget(self.plot, 1)

        main_window.setCentralWidget(self.central_widget)
class ControlWidget(QWidget):

    def __init__(self, status_bar):
        super().__init__()
        self.statusBar = status_bar
        self.initUI()

    def initUI(self):
        """
        Init app

        """
        self._stacked_widget = QStackedWidget()
        signal_controller = SignalSender()
        signal_controller.signal_newWindowIndx2MainWindow.connect(self.set_widget_by_index)
        signal_controller.signal_goto_settings.connect(self.set_widget_settings)

        self.grid = QGridLayout()
        self.grid.addWidget(self._stacked_widget)

        # Create log in widget
        self._log_in_widget = LogInWidget(self.statusBar, signal_controller)
        self._log_in_widget.set_index(self._stacked_widget.addWidget(self._log_in_widget))

        # Create file receiver
        self._file_receiver = FileReceiverWidget(self, signal_controller)
        self._file_receiver.set_index(self._stacked_widget.addWidget(self._file_receiver))

        # Create file sender
        self._file_sender = FileSenderWidget(self, signal_controller)
        self._file_sender.set_index(self._stacked_widget.addWidget(self._file_sender))

        # Create settings
        self._config_widget= ConfigWidget(self)
        self._config_widget.set_index(self._stacked_widget.addWidget(self._config_widget))

        self.setLayout(self.grid)
        self._stacked_widget.setCurrentIndex(self._log_in_widget.get_index())
        self.setWindowTitle("Main menu")

    def set_widget_settings(self):
        self.set_widget_by_index(self._config_widget.get_index())

    def set_widget_by_index(self, index: int):
        self._stacked_widget.setCurrentIndex(index)

    def get_status_bar(self):
        return self.statusBar
Exemple #8
0
    def _init_widgets(self):

        # contents
        contents = QListWidget()
        contents.setViewMode(QListView.IconMode)
        contents.setIconSize(QSize(96, 84))
        contents.setMovement(QListView.Static)
        contents.setMaximumWidth(128)
        contents.setSpacing(12)

        def item_changed(item: QListWidgetItem):
            pageno = item.data(1)  # type: Page
            pages.setCurrentIndex(pageno)

        contents.itemClicked.connect(item_changed)

        self._pages.append(Integration())
        self._pages.append(ThemeAndColors())

        pages = QStackedWidget()
        for idx, page in enumerate(self._pages):
            pages.addWidget(page)
            list_item = QListWidgetItem(page.NAME)
            list_item.setData(1, idx)
            contents.addItem(list_item)

        # buttons
        buttons = QDialogButtonBox(parent=self)
        buttons.setStandardButtons(QDialogButtonBox.StandardButton.Close
                                   | QDialogButtonBox.StandardButton.Ok)
        buttons.button(QDialogButtonBox.Ok).setText('Save')
        buttons.accepted.connect(self._on_ok_clicked)
        buttons.rejected.connect(self.close)

        # layout
        top_layout = QHBoxLayout()
        top_layout.addWidget(contents)
        top_layout.addWidget(pages)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addWidget(buttons)

        self.setLayout(main_layout)
Exemple #9
0
 def init_blocks_ui(self, data):
     blocks_label = QLabel('Блоки:', self)
     blocks_label.setAlignment(Qt.AlignCenter)
     blocks_label.setStyleSheet('font-weight: bold;')
     self.layout.addWidget(blocks_label)
     cb = QComboBox()
     blocks_stacked = QStackedWidget()
     cb.activated.connect(blocks_stacked.setCurrentIndex)
     cb.activated.connect(self.update_current_block_number)
     for block in data['blocks']:
         cb.addItem(block['name'])
         tab_widget = QTabWidget()
         description_label = QLabel(block['description'])
         description_label.setAlignment(Qt.AlignCenter)
         description_label.setStyleSheet('font-style: italic;')
         tab_widget.addTab(description_label, 'Описание')
         commands_label = QLabel('')
         commands_label.setAlignment(Qt.AlignCenter)
         for command in block['commands']:
             c = command['command'].replace('\\"', '"') \
                 .replace('\\[', '[') \
                 .replace('\\]', ']') \
                 .replace('\\{', '{') \
                 .replace('\\}', '}') \
                 .replace('\\(', '(') \
                 .replace('\\)', ')')
             if command['result_variable'] != '':
                 c = command['result_variable'] + ' = ' + c
             commands_label.setText(commands_label.text() + '\n' + c)
         commands_label.setText(commands_label.text() + '\n')
         commands_label.setAlignment(Qt.AlignCenter)
         tab_widget.addTab(commands_label, 'Команды')
         blocks_stacked.addWidget(tab_widget)
     self.layout.addWidget(cb)
     self.layout.addWidget(blocks_stacked)
     block_button = QPushButton('Выполнить выбранный блок')
     block_button.clicked.connect(self.start_execute_block)
     self.layout.addWidget(block_button)
     module_button = QPushButton('Выполнить модуль')
     module_button.clicked.connect(self.start_execute_module)
     self.layout.addWidget(module_button)
Exemple #10
0
def toolbar_stack(mainwindow, settings):
    stack_toolbars = QStackedWidget()
    stack_toolbars.setObjectName('toolbar_stack')

    for model, params in settings['multiplet'].items():
        if model == '1stOrd':
            toolbar = FirstOrderBar(mainwindow, model, params)
        else:
            toolbar = MultipletBar(mainwindow, model, params)
        # toolbar.setObjectName(f'multiplet_{model_name}_toolbar')
        stack_toolbars.addWidget(toolbar)
        mainwindow.toolbars[f'multiplet_{model}'] = toolbar

    for spins, params in settings['nspin'].items():
        # model = str(spins)  # need str so BaseToolbar name inits
        toolbar = SecondOrderBar(mainwindow, spins, params)
        stack_toolbars.addWidget(toolbar)
        mainwindow.toolbars[toolbar.objectName()] = toolbar

    for model, params in settings['dnmr'].items():
        toolbar = DNMR_Bar(mainwindow, model, params)
        stack_toolbars.addWidget(toolbar)
        mainwindow.toolbars[toolbar.objectName()] = toolbar
    stack_toolbars.setCurrentWidget(mainwindow.toolbars['multiplet_AB'])
    return stack_toolbars
Exemple #11
0
class KitchenManagerWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Meal Planner")

        # Menu bar
        self.menu = self.menuBar()
        self.file_menu = self.menu.addMenu("File")

        #Goto Meal Plan Action
        goto_meal_plan_action = QAction("Meal planner", self)
        goto_meal_plan_action.triggered.connect(self.goto_meal_planner_widget)

        # Goto Scraper Action
        goto_scraper_action = QAction("Scraper", self)
        goto_scraper_action.triggered.connect(self.goto_scraper_widget)

        # Goto Recipe Collection Action
        goto_recipe_collection_action = QAction("Recipe Collection", self)
        goto_recipe_collection_action.triggered.connect(
            self.goto_recipe_collection_widget)

        # Exit QAction
        exit_action = QAction("Exit", self)
        exit_action.setShortcut("Ctrl+Q")
        exit_action.triggered.connect(self.exit_app)

        self.file_menu.addAction(goto_meal_plan_action)
        self.file_menu.addAction(goto_recipe_collection_action)
        self.file_menu.addAction(goto_scraper_action)
        self.file_menu.addAction(exit_action)

        self.widget_scraper = scraper_widget.ScraperWidget()
        self.widget_meal_plan = meal_plan_widget.MealPlanWidget()
        self.widget_recipe_collection = recipe_collection_widget.RecipeCollectionWidget(
        )

        #Start on the scraper
        self.stacked_widgets = QStackedWidget()
        self.stacked_widgets.addWidget(self.widget_scraper)
        self.stacked_widgets.addWidget(self.widget_meal_plan)
        self.stacked_widgets.addWidget(self.widget_recipe_collection)
        self.stacked_widgets.setCurrentWidget(self.widget_recipe_collection)
        self.setCentralWidget(self.stacked_widgets)

    @Slot()
    def goto_recipe_collection_widget(self, checked):
        self.stacked_widgets.setCurrentWidget(self.widget_recipe_collection)

    @Slot()
    def goto_meal_planner_widget(self, checked):
        self.stacked_widgets.setCurrentWidget(self.widget_meal_plan)

    @Slot()
    def goto_scraper_widget(self, checked):
        self.stacked_widgets.setCurrentWidget(self.widget_scraper)

    @Slot()
    def exit_app(self, checked):
        QApplication.quit()
Exemple #12
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("Piano Manager")
        self.setFixedSize(800, 480)
        # self.showFullScreen()

        # Create Page Objects Here
        LogInPage = LogIn()
        SignUpPage = SignUp()
        InUsePage = InUse()

        # Connect pages to switch each others
        LogInPage.ui.ButtonRegister.clicked.connect(partial(
            self.switchPage, 1))
        LogInPage.ui.DButtonYes.clicked.connect(partial(self.switchPage, 2))
        SignUpPage.ui.buttonHome.clicked.connect(partial(self.switchPage, 0))
        InUsePage.ui.ButtonQuit.clicked.connect(partial(self.switchPage, 0))

        # Insert pages into QStackedWidget
        self.centralWidgets = QStackedWidget()
        self.centralWidgets.addWidget(LogInPage)  # index: 0
        self.centralWidgets.addWidget(SignUpPage)  # index: 1
        self.centralWidgets.addWidget(InUsePage)  # index: 2

        self.setCentralWidget(self.centralWidgets)

    def getCurWidget(self):
        return self.centralWidgets.currentWidget()

    def switchPage(self, idx):
        self.getCurWidget().clearPage()
        self.centralWidgets.setCurrentIndex(idx)
        self.getCurWidget().setPage()
Exemple #13
0
class Main_Widget(QWidget):
    def __init__(self, parent=None):
        super(Main_Widget, self).__init__(parent)
        self.init_UI()

    def init_UI(self):
        layout = QHBoxLayout()
        self.stack = QStackedWidget(parent=self)
        self.welcome = Welcome_Widget(parent=self)
        self.welcome.login.Button_Login.clicked.connect(
            self.check_user_details)
        self.user = User_Widget(parent=None)
        self.user.Button_Quit.clicked.connect(self.user_quit)
        self.stack.addWidget(self.welcome)
        self.stack.addWidget(self.user)
        layout.addWidget(self.stack)

    def check_user_details(self):
        self.stack.setCurrentWidget(self.user)

    def user_quit(self):
        self.stack.setCurrentWidget(self.welcome)
Exemple #14
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("음취헌 Piano Manager")
        self.setFixedSize(800, 480)

        page_in_use = InUse()
        page_log_in = LogIn()
        page_sign_up = SignUp()

        self.widget = QStackedWidget()
        self.widget.addWidget(page_log_in)  # index 0
        self.widget.addWidget(page_in_use)  # index 1
        self.widget.addWidget(page_sign_up)  # index 2
        self.setCentralWidget(self.widget)

        page_in_use.ui.button_quit.clicked.connect(partial(
            self.switch_page, 0))

        page_log_in.ui.button_register.clicked.connect(
            partial(self.switch_page, 2))
        page_log_in.ui.dialog_true.button_yes.clicked.connect(
            partial(self.switch_page, 1))

        page_sign_up.widget(4).ui.dialog_true.button_ok.clicked.connect(
            partial(self.switch_page, 0))
        for i in range(5):
            page_sign_up.widget(i).ui.button_home.clicked.connect(
                partial(self.switch_page, 0))

    def switch_page(self, idx):
        if idx == 1:
            self.widget.widget(1).set_page(self.widget.widget(0).get_contact())
            self.widget.currentWidget().clear_page()
            self.widget.setCurrentIndex(idx)
        else:
            self.widget.currentWidget().clear_page()
            self.widget.setCurrentIndex(idx)
            self.widget.currentWidget().set_page()
Exemple #15
0
class MainWindow(QWidget):
    widget_stack = None
    main_widget = None
    exercise_widget = None
    exercise_view_widget = None

    def __init__(self):
        QWidget.__init__(self)

        self.setup_window()

        self.widget_stack.setCurrentWidget(self.main_widget)

    def setup_window(self):
        self.widget_stack = QStackedWidget(self)
        self.main_widget = MainWidget()
        self.exercise_widget = ExerciseWidget()
        self.exercise_view_widget = ViewWidget()

        self.widget_stack.addWidget(self.main_widget)
        self.widget_stack.addWidget(self.exercise_widget)
        self.widget_stack.addWidget(self.exercise_view_widget)

        self.main_widget.start_exercise_file.connect(self.show_exercise_window)
        self.main_widget.open_exercise_file.connect(
            self.show_exercise_view_widget)
        self.exercise_view_widget.return_button.clicked.connect(
            self.show_main_widget)

    def resizeEvent(self, event):
        self.widget_stack.resize(self.size())

    @Slot()
    def show_main_widget(self):
        if self.widget_stack.currentWidget() == self.exercise_view_widget:
            self.exercise_view_widget.clear_widget()

        self.widget_stack.setCurrentWidget(self.main_widget)

    @Slot(str)
    def show_exercise_view_widget(self, file: str):
        self.widget_stack.setCurrentWidget(self.exercise_view_widget)
        self.exercise_view_widget.open_exercise_file(file)

    @Slot(str)
    def show_exercise_window(self, file: str):
        self.exercise_widget.set_file(file)
        self.exercise_widget.setup_exercise()
        self.exercise_widget.start()
        self.widget_stack.setCurrentWidget(self.exercise_widget)
Exemple #16
0
class SCOUTS(QMainWindow):
    """Main Window Widget for SCOUTS."""
    style = {
        'title': 'QLabel {font-size: 18pt; font-weight: 600}',
        'header': 'QLabel {font-size: 12pt; font-weight: 520}',
        'label': 'QLabel {font-size: 10pt}',
        'button': 'QPushButton {font-size: 10pt}',
        'md button': 'QPushButton {font-size: 12pt}',
        'run button': 'QPushButton {font-size: 18pt; font-weight: 600}',
        'line edit': 'QLineEdit {font-size: 10pt}',
        'checkbox': 'QCheckBox {font-size: 10pt}',
        'radio button': 'QRadioButton {font-size: 10pt}'
    }

    def __init__(self) -> None:
        """SCOUTS Constructor. Defines all aspects of the GUI."""

        # ###
        # ### Main Window setup
        # ###

        # Inherits from QMainWindow
        super().__init__()
        self.rootdir = get_project_root()
        self.threadpool = QThreadPool()
        # Sets values for QMainWindow
        self.setWindowTitle("SCOUTS")
        self.setWindowIcon(
            QIcon(
                os.path.abspath(os.path.join(self.rootdir, 'src',
                                             'scouts.ico'))))
        # Creates StackedWidget as QMainWindow's central widget
        self.stacked_pages = QStackedWidget(self)
        self.setCentralWidget(self.stacked_pages)
        # Creates Widgets for individual "pages" and adds them to the StackedWidget
        self.main_page = QWidget()
        self.samples_page = QWidget()
        self.gating_page = QWidget()
        self.pages = (self.main_page, self.samples_page, self.gating_page)
        for page in self.pages:
            self.stacked_pages.addWidget(page)
        # ## Sets widget at program startup
        self.stacked_pages.setCurrentWidget(self.main_page)

        # ###
        # ### MAIN PAGE
        # ###

        # Main page layout
        self.main_layout = QVBoxLayout(self.main_page)

        # Title section
        # Title
        self.title = QLabel(self.main_page)
        self.title.setText('SCOUTS - Single Cell Outlier Selector')
        self.title.setStyleSheet(self.style['title'])
        self.title.adjustSize()
        self.main_layout.addWidget(self.title)

        # ## Input section
        # Input header
        self.input_header = QLabel(self.main_page)
        self.input_header.setText('Input settings')
        self.input_header.setStyleSheet(self.style['header'])
        self.main_layout.addChildWidget(self.input_header)
        self.input_header.adjustSize()
        self.main_layout.addWidget(self.input_header)
        # Input frame
        self.input_frame = QFrame(self.main_page)
        self.input_frame.setFrameShape(QFrame.StyledPanel)
        self.input_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.input_frame)
        # Input button
        self.input_button = QPushButton(self.main_page)
        self.input_button.setStyleSheet(self.style['button'])
        self.set_icon(self.input_button, 'x-office-spreadsheet')
        self.input_button.setObjectName('input')
        self.input_button.setText(' Select input file (.xlsx or .csv)')
        self.input_button.clicked.connect(self.get_path)
        # Input path box
        self.input_path = QLineEdit(self.main_page)
        self.input_path.setObjectName('input_path')
        self.input_path.setStyleSheet(self.style['line edit'])
        # Go to sample naming page
        self.samples_button = QPushButton(self.main_page)
        self.samples_button.setStyleSheet(self.style['button'])
        self.set_icon(self.samples_button, 'preferences-other')
        self.samples_button.setText(' Name samples...')
        self.samples_button.clicked.connect(self.goto_samples_page)
        # Go to gating page
        self.gates_button = QPushButton(self.main_page)
        self.gates_button.setStyleSheet(self.style['button'])
        self.set_icon(self.gates_button, 'preferences-other')
        self.gates_button.setText(' Gating && outlier options...')
        self.gates_button.clicked.connect(self.goto_gates_page)
        # Add widgets above to input frame Layout
        self.input_frame.layout().addRow(self.input_button, self.input_path)
        self.input_frame.layout().addRow(self.samples_button)
        self.input_frame.layout().addRow(self.gates_button)

        # ## Analysis section
        # Analysis header
        self.analysis_header = QLabel(self.main_page)
        self.analysis_header.setText('Analysis settings')
        self.analysis_header.setStyleSheet(self.style['header'])
        self.analysis_header.adjustSize()
        self.main_layout.addWidget(self.analysis_header)
        # Analysis frame
        self.analysis_frame = QFrame(self.main_page)
        self.analysis_frame.setFrameShape(QFrame.StyledPanel)
        self.analysis_frame.setLayout(QVBoxLayout())
        self.main_layout.addWidget(self.analysis_frame)
        # Cutoff text
        self.cutoff_text = QLabel(self.main_page)
        self.cutoff_text.setText('Type of outlier to select:')
        self.cutoff_text.setToolTip(
            'Choose whether to select outliers using the cutoff value from a reference\n'
            'sample (OutR) or by using the cutoff value calculated for each sample\n'
            'individually (OutS)')
        self.cutoff_text.setStyleSheet(self.style['label'])
        # Cutoff button group
        self.cutoff_group = QButtonGroup(self)
        # Cutoff by sample
        self.cutoff_sample = QRadioButton(self.main_page)
        self.cutoff_sample.setText('OutS')
        self.cutoff_sample.setObjectName('sample')
        self.cutoff_sample.setStyleSheet(self.style['radio button'])
        self.cutoff_sample.setChecked(True)
        self.cutoff_group.addButton(self.cutoff_sample)
        # Cutoff by reference
        self.cutoff_reference = QRadioButton(self.main_page)
        self.cutoff_reference.setText('OutR')
        self.cutoff_reference.setObjectName('ref')
        self.cutoff_reference.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_reference)
        # Both cutoffs
        self.cutoff_both = QRadioButton(self.main_page)
        self.cutoff_both.setText('both')
        self.cutoff_both.setObjectName('sample ref')
        self.cutoff_both.setStyleSheet(self.style['radio button'])
        self.cutoff_group.addButton(self.cutoff_both)
        # Markers text
        self.markers_text = QLabel(self.main_page)
        self.markers_text.setStyleSheet(self.style['label'])
        self.markers_text.setText('Show results for:')
        self.markers_text.setToolTip(
            'Individual markers: for each marker, select outliers\n'
            'Any marker: select cells that are outliers for AT LEAST one marker'
        )
        # Markers button group
        self.markers_group = QButtonGroup(self)
        # Single marker
        self.single_marker = QRadioButton(self.main_page)
        self.single_marker.setText('individual markers')
        self.single_marker.setObjectName('single')
        self.single_marker.setStyleSheet(self.style['radio button'])
        self.single_marker.setChecked(True)
        self.markers_group.addButton(self.single_marker)
        # Any marker
        self.any_marker = QRadioButton(self.main_page)
        self.any_marker.setText('any marker')
        self.any_marker.setObjectName('any')
        self.any_marker.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.any_marker)
        # Both methods
        self.both_methods = QRadioButton(self.main_page)
        self.both_methods.setText('both')
        self.both_methods.setObjectName('single any')
        self.both_methods.setStyleSheet(self.style['radio button'])
        self.markers_group.addButton(self.both_methods)
        # Tukey text
        self.tukey_text = QLabel(self.main_page)
        self.tukey_text.setStyleSheet(self.style['label'])
        # Tukey button group
        self.tukey_text.setText('Tukey factor:')
        self.tukey_group = QButtonGroup(self)
        # Low Tukey value
        self.tukey_low = QRadioButton(self.main_page)
        self.tukey_low.setText('1.5')
        self.tukey_low.setStyleSheet(self.style['radio button'])
        self.tukey_low.setChecked(True)
        self.tukey_group.addButton(self.tukey_low)
        # High Tukey value
        self.tukey_high = QRadioButton(self.main_page)
        self.tukey_high.setText('3.0')
        self.tukey_high.setStyleSheet(self.style['radio button'])
        self.tukey_group.addButton(self.tukey_high)
        # Add widgets above to analysis frame layout
        self.analysis_frame.layout().addWidget(self.cutoff_text)
        self.cutoff_buttons = QHBoxLayout()
        for button in self.cutoff_group.buttons():
            self.cutoff_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.cutoff_buttons)
        self.analysis_frame.layout().addWidget(self.markers_text)
        self.markers_buttons = QHBoxLayout()
        for button in self.markers_group.buttons():
            self.markers_buttons.addWidget(button)
        self.analysis_frame.layout().addLayout(self.markers_buttons)
        self.analysis_frame.layout().addWidget(self.tukey_text)
        self.tukey_buttons = QHBoxLayout()
        for button in self.tukey_group.buttons():
            self.tukey_buttons.addWidget(button)
        self.tukey_buttons.addWidget(QLabel())  # aligns row with 2 buttons
        self.analysis_frame.layout().addLayout(self.tukey_buttons)

        # ## Output section
        # Output header
        self.output_header = QLabel(self.main_page)
        self.output_header.setText('Output settings')
        self.output_header.setStyleSheet(self.style['header'])
        self.output_header.adjustSize()
        self.main_layout.addWidget(self.output_header)
        # Output frame
        self.output_frame = QFrame(self.main_page)
        self.output_frame.setFrameShape(QFrame.StyledPanel)
        self.output_frame.setLayout(QFormLayout())
        self.main_layout.addWidget(self.output_frame)
        # Output button
        self.output_button = QPushButton(self.main_page)
        self.output_button.setStyleSheet(self.style['button'])
        self.set_icon(self.output_button, 'folder')
        self.output_button.setObjectName('output')
        self.output_button.setText(' Select output folder')
        self.output_button.clicked.connect(self.get_path)
        # Output path box
        self.output_path = QLineEdit(self.main_page)
        self.output_path.setStyleSheet(self.style['line edit'])
        # Generate CSV checkbox
        self.output_csv = QCheckBox(self.main_page)
        self.output_csv.setText('Export multiple text files (.csv)')
        self.output_csv.setStyleSheet(self.style['checkbox'])
        self.output_csv.setChecked(True)
        # Generate XLSX checkbox
        self.output_excel = QCheckBox(self.main_page)
        self.output_excel.setText('Export multiple Excel spreadsheets (.xlsx)')
        self.output_excel.setStyleSheet(self.style['checkbox'])
        self.output_excel.clicked.connect(self.enable_single_excel)
        # Generate single, large XLSX checkbox
        self.single_excel = QCheckBox(self.main_page)
        self.single_excel.setText(
            'Also save one multi-sheet Excel spreadsheet')
        self.single_excel.setToolTip(
            'After generating all Excel spreadsheets, SCOUTS combines them into '
            'a single\nExcel spreadsheet where each sheet corresponds to an output'
            'file from SCOUTS')
        self.single_excel.setStyleSheet(self.style['checkbox'])
        self.single_excel.setEnabled(False)
        self.single_excel.clicked.connect(self.memory_warning)
        # Add widgets above to output frame layout
        self.output_frame.layout().addRow(self.output_button, self.output_path)
        self.output_frame.layout().addRow(self.output_csv)
        self.output_frame.layout().addRow(self.output_excel)
        self.output_frame.layout().addRow(self.single_excel)

        # ## Run & help-quit section
        # Run button (stand-alone)
        self.run_button = QPushButton(self.main_page)
        self.set_icon(self.run_button, 'system-run')
        self.run_button.setText(' Run!')
        self.run_button.setStyleSheet(self.style['run button'])
        self.main_layout.addWidget(self.run_button)
        self.run_button.clicked.connect(self.run)
        # Help-quit frame (invisible)
        self.helpquit_frame = QFrame(self.main_page)
        self.helpquit_frame.setLayout(QHBoxLayout())
        self.helpquit_frame.layout().setMargin(0)
        self.main_layout.addWidget(self.helpquit_frame)
        # Help button
        self.help_button = QPushButton(self.main_page)
        self.set_icon(self.help_button, 'help-about')
        self.help_button.setText(' Help')
        self.help_button.setStyleSheet(self.style['md button'])
        self.help_button.clicked.connect(self.get_help)
        # Quit button
        self.quit_button = QPushButton(self.main_page)
        self.set_icon(self.quit_button, 'process-stop')
        self.quit_button.setText(' Quit')
        self.quit_button.setStyleSheet(self.style['md button'])
        self.quit_button.clicked.connect(self.close)
        # Add widgets above to help-quit layout
        self.helpquit_frame.layout().addWidget(self.help_button)
        self.helpquit_frame.layout().addWidget(self.quit_button)

        # ###
        # ### SAMPLES PAGE
        # ###

        # Samples page layout
        self.samples_layout = QVBoxLayout(self.samples_page)

        # ## Title section
        # Title
        self.samples_title = QLabel(self.samples_page)
        self.samples_title.setText('Name your samples')
        self.samples_title.setStyleSheet(self.style['title'])
        self.samples_title.adjustSize()
        self.samples_layout.addWidget(self.samples_title)
        # Subtitle
        self.samples_subtitle = QLabel(self.samples_page)
        string = (
            'Please name the samples to be analysed by SCOUTS.\n\nSCOUTS searches the first '
            'column of your data\nand locates the exact string as part of the sample name.'
        )
        self.samples_subtitle.setText(string)
        self.samples_subtitle.setStyleSheet(self.style['label'])
        self.samples_subtitle.adjustSize()
        self.samples_layout.addWidget(self.samples_subtitle)

        # ## Sample addition section
        # Sample addition frame
        self.samples_frame = QFrame(self.samples_page)
        self.samples_frame.setFrameShape(QFrame.StyledPanel)
        self.samples_frame.setLayout(QGridLayout())
        self.samples_layout.addWidget(self.samples_frame)
        # Sample name box
        self.sample_name = QLineEdit(self.samples_page)
        self.sample_name.setStyleSheet(self.style['line edit'])
        self.sample_name.setPlaceholderText('Sample name ...')
        # Reference check
        self.is_reference = QCheckBox(self.samples_page)
        self.is_reference.setText('Reference?')
        self.is_reference.setStyleSheet(self.style['checkbox'])
        # Add sample to table
        self.add_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Return"), self.add_sample_button,
                  self.write_to_sample_table)
        self.set_icon(self.add_sample_button, 'list-add')
        self.add_sample_button.setText(' Add sample (Enter)')
        self.add_sample_button.setStyleSheet(self.style['button'])
        self.add_sample_button.clicked.connect(self.write_to_sample_table)
        # Remove sample from table
        self.remove_sample_button = QPushButton(self.samples_page)
        QShortcut(QKeySequence("Delete"), self.remove_sample_button,
                  self.remove_from_sample_table)
        self.set_icon(self.remove_sample_button, 'list-remove')
        self.remove_sample_button.setText(' Remove sample (Del)')
        self.remove_sample_button.setStyleSheet(self.style['button'])
        self.remove_sample_button.clicked.connect(
            self.remove_from_sample_table)
        # Add widgets above to sample addition layout
        self.samples_frame.layout().addWidget(self.sample_name, 0, 0)
        self.samples_frame.layout().addWidget(self.is_reference, 1, 0)
        self.samples_frame.layout().addWidget(self.add_sample_button, 0, 1)
        self.samples_frame.layout().addWidget(self.remove_sample_button, 1, 1)

        # ## Sample table
        self.sample_table = QTableWidget(self.samples_page)
        self.sample_table.setColumnCount(2)
        self.sample_table.setHorizontalHeaderItem(0,
                                                  QTableWidgetItem('Sample'))
        self.sample_table.setHorizontalHeaderItem(
            1, QTableWidgetItem('Reference?'))
        self.sample_table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.sample_table.horizontalHeader().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.samples_layout.addWidget(self.sample_table)

        # ## Save & clear buttons
        # Save & clear frame (invisible)
        self.saveclear_frame = QFrame(self.samples_page)
        self.saveclear_frame.setLayout(QHBoxLayout())
        self.saveclear_frame.layout().setMargin(0)
        self.samples_layout.addWidget(self.saveclear_frame)
        # Clear samples button
        self.clear_samples = QPushButton(self.samples_page)
        self.set_icon(self.clear_samples, 'edit-delete')
        self.clear_samples.setText(' Clear table')
        self.clear_samples.setStyleSheet(self.style['md button'])
        self.clear_samples.clicked.connect(self.prompt_clear_data)
        # Save samples button
        self.save_samples = QPushButton(self.samples_page)
        self.set_icon(self.save_samples, 'document-save')
        self.save_samples.setText(' Save samples')
        self.save_samples.setStyleSheet(self.style['md button'])
        self.save_samples.clicked.connect(self.goto_main_page)
        # Add widgets above to save & clear layout
        self.saveclear_frame.layout().addWidget(self.clear_samples)
        self.saveclear_frame.layout().addWidget(self.save_samples)

        # ###
        # ### GATING PAGE
        # ###

        # Gating page layout
        self.gating_layout = QVBoxLayout(self.gating_page)

        # ## Title section
        # Title
        self.gates_title = QLabel(self.gating_page)
        self.gates_title.setText('Gating & outlier options')
        self.gates_title.setStyleSheet(self.style['title'])
        self.gates_title.adjustSize()
        self.gating_layout.addWidget(self.gates_title)

        # ## Gating options section
        # Gating header
        self.gate_header = QLabel(self.gating_page)
        self.gate_header.setText('Gating')
        self.gate_header.setStyleSheet(self.style['header'])
        self.gate_header.adjustSize()
        self.gating_layout.addWidget(self.gate_header)

        # Gating frame
        self.gate_frame = QFrame(self.gating_page)
        self.gate_frame.setFrameShape(QFrame.StyledPanel)
        self.gate_frame.setLayout(QFormLayout())
        self.gating_layout.addWidget(self.gate_frame)
        # Gating button group
        self.gating_group = QButtonGroup(self)
        # Do not gate samples
        self.no_gates = QRadioButton(self.gating_page)
        self.no_gates.setObjectName('no_gate')
        self.no_gates.setText("Don't gate samples")
        self.no_gates.setStyleSheet(self.style['radio button'])
        self.no_gates.setChecked(True)
        self.gating_group.addButton(self.no_gates)
        self.no_gates.clicked.connect(self.activate_gate)
        # CyToF gating
        self.cytof_gates = QRadioButton(self.gating_page)
        self.cytof_gates.setObjectName('cytof')
        self.cytof_gates.setText('Mass Cytometry gating')
        self.cytof_gates.setStyleSheet(self.style['radio button'])
        self.cytof_gates.setToolTip(
            'Exclude cells for which the average expression of all\n'
            'markers is below the selected value')
        self.gating_group.addButton(self.cytof_gates)
        self.cytof_gates.clicked.connect(self.activate_gate)
        # CyToF gating spinbox
        self.cytof_gates_value = QDoubleSpinBox(self.gating_page)
        self.cytof_gates_value.setMinimum(0)
        self.cytof_gates_value.setMaximum(1)
        self.cytof_gates_value.setValue(0.1)
        self.cytof_gates_value.setSingleStep(0.05)
        self.cytof_gates_value.setEnabled(False)
        # scRNA-Seq gating
        self.rnaseq_gates = QRadioButton(self.gating_page)
        self.rnaseq_gates.setText('scRNA-Seq gating')
        self.rnaseq_gates.setStyleSheet(self.style['radio button'])
        self.rnaseq_gates.setToolTip(
            'When calculating cutoff, ignore reads below the selected value')
        self.rnaseq_gates.setObjectName('rnaseq')
        self.gating_group.addButton(self.rnaseq_gates)
        self.rnaseq_gates.clicked.connect(self.activate_gate)
        # scRNA-Seq gating spinbox
        self.rnaseq_gates_value = QDoubleSpinBox(self.gating_page)
        self.rnaseq_gates_value.setMinimum(0)
        self.rnaseq_gates_value.setMaximum(10)
        self.rnaseq_gates_value.setValue(0)
        self.rnaseq_gates_value.setSingleStep(1)
        self.rnaseq_gates_value.setEnabled(False)
        # export gated population checkbox
        self.export_gated = QCheckBox(self.gating_page)
        self.export_gated.setText('Export gated cells as an output file')
        self.export_gated.setStyleSheet(self.style['checkbox'])
        self.export_gated.setEnabled(False)
        # Add widgets above to Gate frame layout
        self.gate_frame.layout().addRow(self.no_gates, QLabel())
        self.gate_frame.layout().addRow(self.cytof_gates,
                                        self.cytof_gates_value)
        self.gate_frame.layout().addRow(self.rnaseq_gates,
                                        self.rnaseq_gates_value)
        self.gate_frame.layout().addRow(self.export_gated, QLabel())

        # ## Outlier options section
        # Outlier header
        self.outlier_header = QLabel(self.gating_page)
        self.outlier_header.setText('Outliers')
        self.outlier_header.setStyleSheet(self.style['header'])
        self.outlier_header.adjustSize()
        self.gating_layout.addWidget(self.outlier_header)
        # Outlier frame
        self.outlier_frame = QFrame(self.gating_page)
        self.outlier_frame.setFrameShape(QFrame.StyledPanel)
        self.outlier_frame.setLayout(QVBoxLayout())
        self.gating_layout.addWidget(self.outlier_frame)
        # Top outliers information
        self.top_outliers = QLabel(self.gating_page)
        self.top_outliers.setStyleSheet(self.style['label'])
        self.top_outliers.setText(
            'By default, SCOUTS selects the top outliers from the population')
        self.top_outliers.setStyleSheet(self.style['label'])
        # Bottom outliers data
        self.bottom_outliers = QCheckBox(self.gating_page)
        self.bottom_outliers.setText('Include results for low outliers')
        self.bottom_outliers.setStyleSheet(self.style['checkbox'])
        # Non-outliers data
        self.not_outliers = QCheckBox(self.gating_page)
        self.not_outliers.setText('Include results for non-outliers')
        self.not_outliers.setStyleSheet(self.style['checkbox'])
        # Add widgets above to Gate frame layout
        self.outlier_frame.layout().addWidget(self.top_outliers)
        self.outlier_frame.layout().addWidget(self.bottom_outliers)
        self.outlier_frame.layout().addWidget(self.not_outliers)

        # ## Save/back button
        self.save_gates = QPushButton(self.gating_page)
        self.set_icon(self.save_gates, 'go-next')
        self.save_gates.setText(' Back to main menu')
        self.save_gates.setStyleSheet(self.style['md button'])
        self.gating_layout.addWidget(self.save_gates)
        self.save_gates.clicked.connect(self.goto_main_page)

        # ## Add empty label to take vertical space
        self.empty_label = QLabel(self.gating_page)
        self.empty_label.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)
        self.gating_layout.addWidget(self.empty_label)

    # ###
    # ### ICON SETTING
    # ###

    def set_icon(self, widget: QWidget, icon: str) -> None:
        """Associates an icon to a widget."""
        i = QIcon()
        i.addPixmap(
            QPixmap(
                os.path.abspath(
                    os.path.join(self.rootdir, 'src', 'default_icons',
                                 f'{icon}.svg'))))
        widget.setIcon(QIcon.fromTheme(icon, i))

    # ###
    # ### STACKED WIDGET PAGE SWITCHING
    # ###

    def goto_main_page(self) -> None:
        """Switches stacked widget pages to the main page."""
        self.stacked_pages.setCurrentWidget(self.main_page)

    def goto_samples_page(self) -> None:
        """Switches stacked widget pages to the samples table page."""
        self.stacked_pages.setCurrentWidget(self.samples_page)

    def goto_gates_page(self) -> None:
        """Switches stacked widget pages to the gating & other options page."""
        self.stacked_pages.setCurrentWidget(self.gating_page)

    # ###
    # ### MAIN PAGE GUI LOGIC
    # ###

    def get_path(self) -> None:
        """Opens a dialog box and sets the chosen file/folder path, depending on the caller widget."""
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        sender_name = self.sender().objectName()
        if sender_name == 'input':
            query, _ = QFileDialog.getOpenFileName(self,
                                                   "Select file",
                                                   "",
                                                   "All Files (*)",
                                                   options=options)
        elif sender_name == 'output':
            query = QFileDialog.getExistingDirectory(self,
                                                     "Select Directory",
                                                     options=options)
        else:
            return
        if query:
            getattr(self, f'{sender_name}_path').setText(query)

    def enable_single_excel(self) -> None:
        """Enables checkbox for generating a single Excel output."""
        if self.output_excel.isChecked():
            self.single_excel.setEnabled(True)
        else:
            self.single_excel.setEnabled(False)
            self.single_excel.setChecked(False)

    # ###
    # ### SAMPLE NAME/SAMPLE TABLE GUI LOGIC
    # ###

    def write_to_sample_table(self) -> None:
        """Writes data to sample table."""
        table = self.sample_table
        ref = 'no'
        sample = self.sample_name.text()
        if sample:
            for cell in range(table.rowCount()):
                item = table.item(cell, 0)
                if item.text() == sample:
                    self.same_sample()
                    return
            if self.is_reference.isChecked():
                for cell in range(table.rowCount()):
                    item = table.item(cell, 1)
                    if item.text() == 'yes':
                        self.more_than_one_reference()
                        return
                ref = 'yes'
            sample = QTableWidgetItem(sample)
            is_reference = QTableWidgetItem(ref)
            is_reference.setFlags(Qt.ItemIsEnabled)
            row_position = table.rowCount()
            table.insertRow(row_position)
            table.setItem(row_position, 0, sample)
            table.setItem(row_position, 1, is_reference)
            self.is_reference.setChecked(False)
            self.sample_name.setText('')

    def remove_from_sample_table(self) -> None:
        """Removes data from sample table."""
        table = self.sample_table
        rows = set(index.row() for index in table.selectedIndexes())
        for index in sorted(rows, reverse=True):
            self.sample_table.removeRow(index)

    def prompt_clear_data(self) -> None:
        """Prompts option to clear all data in the sample table."""
        if self.confirm_clear_data():
            table = self.sample_table
            while table.rowCount():
                self.sample_table.removeRow(0)

    # ###
    # ### GATING GUI LOGIC
    # ###

    def activate_gate(self) -> None:
        """Activates/deactivates buttons related to gating."""
        if self.sender().objectName() == 'no_gate':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(False)
            self.export_gated.setChecked(False)
        elif self.sender().objectName() == 'cytof':
            self.cytof_gates_value.setEnabled(True)
            self.rnaseq_gates_value.setEnabled(False)
            self.export_gated.setEnabled(True)
        elif self.sender().objectName() == 'rnaseq':
            self.cytof_gates_value.setEnabled(False)
            self.rnaseq_gates_value.setEnabled(True)
            self.export_gated.setEnabled(True)

    # ###
    # ### CONNECT SCOUTS TO ANALYTICAL MODULES
    # ###

    def run(self) -> None:
        """Runs SCOUTS as a Worker, based on user input in the GUI."""
        try:
            data = self.parse_input()
        except Exception as error:
            trace = traceback.format_exc()
            self.propagate_error((error, trace))
        else:
            data['widget'] = self
            worker = Worker(func=start_scouts, **data)
            worker.signals.started.connect(self.analysis_has_started)
            worker.signals.finished.connect(self.analysis_has_finished)
            worker.signals.success.connect(self.success_message)
            worker.signals.error.connect(self.propagate_error)
            self.threadpool.start(worker)

    def parse_input(self) -> Dict:
        """Returns user input on the GUI as a dictionary."""
        # Input and output
        input_dict = {
            'input_file': str(self.input_path.text()),
            'output_folder': str(self.output_path.text())
        }
        if not input_dict['input_file'] or not input_dict['output_folder']:
            raise NoIOPathError
        # Set cutoff by reference or by sample rule
        input_dict['cutoff_rule'] = self.cutoff_group.checkedButton(
        ).objectName()  # 'sample', 'ref', 'sample ref'
        # Outliers for each individual marker or any marker in row
        input_dict['marker_rule'] = self.markers_group.checkedButton(
        ).objectName()  # 'single', 'any', 'single any'
        # Tukey factor used for calculating cutoff
        input_dict['tukey_factor'] = float(
            self.tukey_group.checkedButton().text())  # '1.5', '3.0'
        # Output settings
        input_dict['export_csv'] = True if self.output_csv.isChecked(
        ) else False
        input_dict['export_excel'] = True if self.output_excel.isChecked(
        ) else False
        input_dict['single_excel'] = True if self.single_excel.isChecked(
        ) else False
        # Retrieve samples from sample table
        input_dict['sample_list'] = []
        for tuples in self.yield_samples_from_table():
            input_dict['sample_list'].append(tuples)
        if not input_dict['sample_list']:
            raise NoSampleError
        # Set gate cutoff (if any)
        input_dict['gating'] = self.gating_group.checkedButton().objectName(
        )  # 'no_gate', 'cytof', 'rnaseq'
        input_dict['gate_cutoff_value'] = None
        if input_dict['gating'] != 'no_gate':
            input_dict['gate_cutoff_value'] = getattr(
                self, f'{input_dict["gating"]}_gates_value').value()
        input_dict['export_gated'] = True if self.export_gated.isChecked(
        ) else False
        # Generate results for non-outliers
        input_dict['non_outliers'] = False
        if self.not_outliers.isChecked():
            input_dict['non_outliers'] = True
        # Generate results for bottom outliers
        input_dict['bottom_outliers'] = False
        if self.bottom_outliers.isChecked():
            input_dict['bottom_outliers'] = True
        # return dictionary with all gathered inputs
        return input_dict

    def yield_samples_from_table(
            self) -> Generator[Tuple[str, str], None, None]:
        """Yields sample names from the sample table."""
        table = self.sample_table
        for cell in range(table.rowCount()):
            sample_name = table.item(cell, 0).text()
            sample_type = table.item(cell, 1).text()
            yield sample_name, sample_type

    # ###
    # ### MESSAGE BOXES
    # ###

    def analysis_has_started(self) -> None:
        """Disables run button while SCOUTS analysis is underway."""
        self.run_button.setText(' Working...')
        self.run_button.setEnabled(False)

    def analysis_has_finished(self) -> None:
        """Enables run button after SCOUTS analysis has finished."""
        self.run_button.setEnabled(True)
        self.run_button.setText(' Run!')

    def success_message(self) -> None:
        """Info message box used when SCOUTS finished without errors."""
        title = "Analysis finished!"
        mes = "Your analysis has finished. No errors were reported."
        if self.stacked_pages.isEnabled() is True:
            QMessageBox.information(self, title, mes)

    def memory_warning(self) -> None:
        """Warning message box used when user wants to generate a single excel file."""
        if self.sender().isChecked():
            title = 'Memory warning!'
            mes = (
                "Depending on your dataset, this option can consume a LOT of memory and take"
                " a long time to process. Please make sure that your computer can handle it!"
            )
            QMessageBox.information(self, title, mes)

    def same_sample(self) -> None:
        """Error message box used when the user tries to input the same sample twice in the sample table."""
        title = 'Error: sample name already in table'
        mes = (
            "Sorry, you can't do this because this sample name is already in the table. "
            "Please select a different name.")
        QMessageBox.critical(self, title, mes)

    def more_than_one_reference(self) -> None:
        """Error message box used when the user tries to input two reference samples in the sample table."""
        title = "Error: more than one reference selected"
        mes = (
            "Sorry, you can't do this because there is already a reference column in the table. "
            "Please remove it before adding a reference.")
        QMessageBox.critical(self, title, mes)

    def confirm_clear_data(self) -> bool:
        """Question message box used to confirm user action of clearing sample table."""
        title = 'Confirm Action'
        mes = "Table will be cleared. Are you sure?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            return True
        return False

    # ###
    # ### EXCEPTIONS & ERRORS
    # ###

    def propagate_error(self, error: Tuple[Exception, str]) -> None:
        """Calls the appropriate error message box based on type of Exception raised."""
        if isinstance(error[0], NoIOPathError):
            self.no_io_path_error_message()
        elif isinstance(error[0], NoReferenceError):
            self.no_reference_error_message()
        elif isinstance(error[0], NoSampleError):
            self.no_sample_error_message()
        elif isinstance(error[0], PandasInputError):
            self.pandas_input_error_message()
        elif isinstance(error[0], SampleNamingError):
            self.sample_naming_error_message()
        else:
            self.generic_error_message(error)

    def no_io_path_error_message(self) -> None:
        """Message displayed when the user did not include an input file path, or an output folder path."""
        title = 'Error: no file/folder'
        message = ("Sorry, no input file and/or output folder was provided. "
                   "Please add the path to the necessary file/folder.")
        QMessageBox.critical(self, title, message)

    def no_reference_error_message(self) -> None:
        """Message displayed when the user wants to analyse cutoff based on a reference, but did not specify what
        sample corresponds to the reference."""
        title = "Error: No reference selected"
        message = (
            "Sorry, no reference sample was found on the sample list, but analysis was set to "
            "reference. Please add a reference sample, or change the rule for cutoff calculation."
        )
        QMessageBox.critical(self, title, message)

    def no_sample_error_message(self) -> None:
        """Message displayed when the user did not add any samples to the sample table."""
        title = "Error: No samples selected"
        message = (
            "Sorry, the analysis cannot be performed because no sample names were input. "
            "Please add your sample names.")
        QMessageBox.critical(self, title, message)

    def pandas_input_error_message(self) -> None:
        """Message displayed when the input file cannot be read (likely because it is not a Excel or csv file)."""
        title = 'Error: unexpected input file'
        message = (
            "Sorry, the input file could not be read. Please make sure that "
            "the data is save in a valid format (supported formats are: "
            ".csv, .xlsx).")
        QMessageBox.critical(self, title, message)

    def sample_naming_error_message(self) -> None:
        """Message displayed when none of the sample names passed by the user are found in the input DataFrame."""
        title = 'Error: sample names not in input file'
        message = (
            "Sorry, your sample names were not found in the input file. Please "
            "make sure that the names were typed correctly (case-sensitive).")
        QMessageBox.critical(self, title, message)

    def generic_error_message(self, error: Tuple[Exception, str]) -> None:
        """Error message box used to display any error message (including traceback) for any uncaught errors."""
        title = 'An error occurred!'
        name, trace = error
        QMessageBox.critical(self, title,
                             f"{str(name)}\n\nfull traceback:\n{trace}")

    def not_implemented_error_message(self) -> None:
        """Error message box used when the user accesses a functionality that hasn't been implemented yet."""
        title = "Not yet implemented"
        mes = "Sorry, this functionality has not been implemented yet."
        QMessageBox.critical(self, title, mes)

    # ###
    # ### HELP & QUIT
    # ###

    @staticmethod
    def get_help() -> None:
        """Opens SCOUTS documentation on the browser. Called when the user clicks the "help" button"""
        webbrowser.open('https://scouts.readthedocs.io/en/master/')

    def closeEvent(self, event: QEvent) -> None:
        """Defines the message box for when the user wants to quit SCOUTS."""
        title = 'Quit SCOUTS'
        mes = "Are you sure you want to quit?"
        reply = QMessageBox.question(self, title, mes,
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.stacked_pages.setEnabled(False)
            message = self.quit_message()
            waiter = Waiter(waiter_func=self.threadpool.activeThreadCount)
            waiter.signals.started.connect(message.show)
            waiter.signals.finished.connect(message.destroy)
            waiter.signals.finished.connect(sys.exit)
            self.threadpool.start(waiter)
        event.ignore()

    def quit_message(self) -> QDialog:
        """Displays a window while SCOUTS is exiting"""
        message = QDialog(self)
        message.setWindowTitle('Exiting SCOUTS')
        message.resize(300, 50)
        label = QLabel('SCOUTS is exiting, please wait...', message)
        label.setStyleSheet(self.style['label'])
        label.adjustSize()
        label.setAlignment(Qt.AlignCenter)
        label.move(int((message.width() - label.width()) / 2),
                   int((message.height() - label.height()) / 2))
        return message
Exemple #17
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.json_path = ''
        self.currDB = ''
        self.dc = dataController()
        self.createWidgetStack()
        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.resize(1000, 800)
        self.setWindowTitle("Logger")
        self.setCentralWidget(self.widget_stack)


    def createWidgetStack(self):
        self.widget_stack = QStackedWidget()
        self.centralWidget = MainWidget(self.dc)
        self.plotWidget =  plotMainWidget(self.dc)
        self.widget_stack.addWidget(self.centralWidget)
        self.widget_stack.addWidget(self.plotWidget)
        self.widget_stack.setCurrentIndex(0)

    def open(self):
        fileName =  QFileDialog.getOpenFileName(self)[0]
        if self.json_path == '':
            QMessageBox.warning(self, "Application", "load json file first\n")
            return
        
        if fileName != '':
            print(fileName)
            self.dc.initBinFile(fileName, self.json_path)
            self.centralWidget.initDomainCombo()
            self.createNewPlotView()  
            self.widget_stack.setCurrentIndex(0)

    def openJson(self):
        fileName =  QFileDialog.getOpenFileName(self)[0]
        if fileName:
            self.json_path = fileName
        self.widget_stack.setCurrentIndex(0)

    def openDB(self):
        fileName=  QFileDialog.getOpenFileName(self)[0]
        if fileName:
            self.dc.initDB(fileName)
            self.centralWidget.initDomainCombo()
            if fileName != self.currDB:
                self.createNewPlotView()            
                self.currDB = fileName 
        self.widget_stack.setCurrentIndex(0)    
    
    def createNewPlotView(self):
        self.widget_stack.removeWidget(self.plotWidget)
        self.plotWidget =  plotMainWidget(self.dc)
        self.widget_stack.addWidget(self.plotWidget)
    
    def saveToFile(self):
        path, filtr =  QFileDialog.getSaveFileName(self)
        if path:
            self.centralWidget.CW_exportToFile(path)
    
    def openChart(self):
        if not self.dc:
            QMessageBox.warning(self, "Application", "load json file first\n")
            return
        self.widget_stack.setCurrentIndex(1)
    
    def goBack(self):
       self.widget_stack.setCurrentIndex(0)


    def createActions(self):
        open_bin_file = os.path.join(icon_path,'bin.png')

        self.openAct = QAction( QIcon(open_bin_file),
                "&Open...", self, shortcut= QKeySequence.Open,
                statusTip="Open an existing file", triggered=self.open)

        open_json_file = os.path.join(icon_path,'json.png')

        self.openJSONAct =  QAction( QIcon(open_json_file),
                            "Open...", self, shortcut= QKeySequence.Open,
                            statusTip="Open JSON file", triggered=self.openJson)
        open_db= os.path.join(icon_path,'db.png')
        self.openDBact = QAction( QIcon(open_db),
                            "Open...", self, shortcut= QKeySequence.Open,
                            statusTip="Open DB", triggered=self.openDB)
        disk_icon_path= os.path.join(icon_path,'save.png')
        self.saveToFileAct = QAction( QIcon(disk_icon_path),
                            "Save to file...", self, shortcut= QKeySequence.Open,
                            statusTip="save to file", triggered=self.saveToFile)
        chart_icon_path= os.path.join(icon_path,'chart.png')
        self.openChartAct = QAction( QIcon(chart_icon_path),
                            "Open chart windows", self, shortcut= QKeySequence.Open,
                            statusTip="open chart windows", triggered=self.openChart)
        back_icon_path= os.path.join(icon_path,'back.png')
        self.goBackAct = QAction( QIcon(back_icon_path),
                            "Go Back", self, shortcut= QKeySequence.Open,
                            statusTip="Go back", triggered=self.goBack)
   
    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.openAct)
        self.fileMenu.addAction(self.openJSONAct)
        self.fileMenu.addAction(self.openDBact)
        self.fileMenu.addAction(self.saveToFileAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.openChartAct)
        self.fileMenu.addAction(self.goBackAct)
     

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.openAct)
        self.fileToolBar.addAction(self.openJSONAct)
        self.fileToolBar.addAction(self.openDBact)
        self.fileToolBar.addAction(self.saveToFileAct)
        self.fileToolBar.addAction(self.openChartAct)
        self.fileToolBar.addAction(self.goBackAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")
Exemple #18
0
class Application(QMainWindow):

    # Booleans indicating which window is opened
    __isTesting = False
    __isTraining = False
    __isCreation = False
    __isPreProcessingText = False
    __isPreProcessingImage = False
    __isChoice = False

    @Slot()
    def goToTraining(self):
        if self.__isCreation:
            self.__isCreation = False
            self.__isTraining = True
            self.__trainingWindow = TrainingWindow(
                self.__creationWindow.dataset, self.__choiceWindow.classes,
                (self.__creationWindow.modelList,
                 self.__creationWindow.networkName.text()), True)
            self.__trainingWindow.goBackButton.clicked.connect(
                self.backtoCreation)
            self.__windows.addWidget(self.__trainingWindow)
            self.__windows.setCurrentIndex(3)

        elif self.__isChoice:
            if not self.__choiceWindow.datasetLoaded:
                ret = QMessageBox.warning(self, "Dataset",
                                          "Veuillez charger un dataset",
                                          QMessageBox.Ok)
                return
            if not self.__choiceWindow.networkLoaded:
                ret = QMessageBox.warning(
                    self, "Reseau de neurones",
                    "Veuillez charger un reseau de neurones ou en creer un nouveau",
                    QMessageBox.Ok)
                return
            self.__isTraining = True
            self.__isChoice = False
            self.__trainingWindow = TrainingWindow(
                self.__choiceWindow.dataset, self.__choiceWindow.classes,
                (self.__choiceWindow.network, self.__choiceWindow.networkName),
                False)
            self.__trainingWindow.goBackButton.clicked.connect(
                self.backToChoice)
            self.__windows.addWidget(self.__trainingWindow)
            self.__windows.setCurrentIndex(2)

    @Slot()
    def goToTesting(self):
        self.__isTesting = True
        self.__testingWindow = TestingWindow()
        self.__windows.addWidget(self.__testingWindow)
        self.__windows.setCurrentIndex(1)
        self.__testingWindow.buttons[3].clicked.connect(self.backToMenu)

    @Slot()
    def goToCreation(self):
        if not self.__choiceWindow.datasetLoaded:
            ret = QMessageBox.warning(self, "Dataset",
                                      "Veuillez d'abord charger un dataset",
                                      QMessageBox.Ok)
            return
        self.__isChoice = False
        if self.__choiceWindow.networkLoaded:
            ret = QMessageBox.warning(self, "Reseau de neurones",
                                      "Un reseau de neurones est deja charge",
                                      QMessageBox.Ok)
            return
        self.__isCreation = True
        self.__creationWindow = CreationWindow(self.__choiceWindow.dataset)
        self.__windows.addWidget(self.__creationWindow)
        self.__windows.setCurrentIndex(2)
        self.__creationWindow.goBackButton.clicked.connect(self.backToChoice)
        self.__creationWindow.done.connect(self.goToTraining)

    @Slot()
    def goToChoice(self):
        self.__isChoice = True
        self.__choiceWindow = ChoiceWindow()
        self.__windows.addWidget(self.__choiceWindow)
        self.__windows.setCurrentIndex(1)
        self.__choiceWindow.buttons[2].clicked.connect(self.goToCreation)
        self.__choiceWindow.buttons[3].clicked.connect(self.goToTraining)
        self.__choiceWindow.buttons[4].clicked.connect(self.backToMenu)

    @Slot()
    def backtoCreation(self):
        self.__isCreation = True
        self.__isTraining = False
        self.__windows.setCurrentIndex(2)
        self.__windows.removeWidget(self.__trainingWindow)
        self.__trainingWindow.deleteLater()

    @Slot()
    def goToPreprocessingText(self):
        self.__isPreProcessingText = True
        self.__preProcessingTextWindow = PreprocessingTextWindow()
        self.__preProcessingTextWindow.pushButton_return.clicked.connect(
            self.backToMenu)
        self.__windows.addWidget(self.__preProcessingTextWindow)
        self.__windows.setCurrentIndex(1)

    @Slot()
    def goToPreprocessingImage(self):
        self.__isPreProcessingImage = True
        self.__preProcessingImageWindow = PreprocessingImageWindow()
        self.__preProcessingImageWindow.pushButton_return.clicked.connect(
            self.backToMenu)
        self.__windows.addWidget(self.__preProcessingImageWindow)
        self.__windows.setCurrentIndex(1)

    @Slot()
    def backToMenu(self):
        self.__windows.setCurrentIndex(0)
        if self.__isTesting:
            self.__isTesting = False
            self.__windows.removeWidget(self.__testingWindow)
            self.__testingWindow.deleteLater()
        elif self.__isPreProcessingText:
            self.__isPreProcessingText = False
            self.__windows.removeWidget(self.__preProcessingTextWindow)
            self.__preProcessingTextWindow.deleteLater()
        elif self.__isPreProcessingImage:
            self.__isPreProcessingImage = False
            self.__windows.removeWidget(self.__preProcessingImageWindow)
            self.__preProcessingImageWindow.deleteLater()
        elif self.__isChoice:
            self.__isChoice = False
            self.__windows.removeWidget(self.__choiceWindow)
            self.__choiceWindow.deleteLater()
            self.__choiceWindow = None

    @Slot()
    def backToChoice(self):
        self.__isChoice = True
        self.__windows.setCurrentIndex(1)
        if self.__isCreation:
            self.__isCreation = False
            self.__windows.removeWidget(self.__creationWindow)
            self.__creationWindow.deleteLater()
        elif self.__isTraining:
            self.__isTraining = False
            self.__windows.removeWidget(self.__trainingWindow)
            self.__trainingWindow.deleteLater()

    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)

        # Initializing menu window
        self.__menuWindow = MenuWindow()
        self.__windows = QStackedWidget(self)
        self.__windows.addWidget(self.__menuWindow)
        self.__windows.setCurrentIndex(0)
        self.setCentralWidget(self.__windows)

        # Connecting buttons
        self.__menuWindow.buttons[0].clicked.connect(
            self.goToPreprocessingImage)
        self.__menuWindow.buttons[1].clicked.connect(
            self.goToPreprocessingText)
        self.__menuWindow.buttons[2].clicked.connect(self.goToChoice)
        self.__menuWindow.buttons[3].clicked.connect(self.goToTesting)
        self.__menuWindow.buttons[4].clicked.connect(QApplication.quit)
Exemple #19
0
class Task_ledger(QWidget):
    def __init__(self, system, parent=None):
        super(Task_ledger, self).__init__(parent)
        self.stackedWidgetPage1 = QWidget()
        self.stackedWidgetPage2 = QWidget()
        self.stackedWidgetPage3 = QWidget()
        self.stackedWidgetPage4 = QWidget()
        self.dialog = dialog_logout.Logout_Dialog()

        self.system = system

    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.setFixedSize(1000, 600)
        Form.setWindowTitle("Task Ledger")

        self.stackedWidget = QStackedWidget(Form)
        self.stackedWidget.setGeometry(QRect(0, 0, 1000, 600))
        self.stackedWidget.setObjectName("stackedWidget")

        # Landing Page
        self.stackedWidgetPage1.setObjectName("stackedWidgetPage1")
        self.landing = landing.LandingPageUI(self.stackedWidgetPage1)
        self.landing.setGeometry(0, 0, 1000, 60)
        self.landing.setupUi(self.stackedWidgetPage1)
        self.stackedWidget.addWidget(self.stackedWidgetPage1)
        self.landing.pushButton.clicked.connect(self.goto_login)

        # Login Page
        self.stackedWidgetPage2.setObjectName("stackedWidgetPage2")
        self.login = login.LoginUI(self.stackedWidgetPage2)
        self.login.setupUi(self.stackedWidgetPage2)
        self.login.setGeometry(0, 0, 1000, 60)
        self.login.reg_label.clicked.connect(self.goto_reg)
        self.login.back.clicked.connect(self.goto_landing)
        self.stackedWidget.addWidget(self.stackedWidgetPage2)

        # register Page
        self.stackedWidgetPage3.setObjectName("stackedWidgetPage3")
        self.reg = reg.RegisterUI(self.stackedWidgetPage3)
        self.reg.setupUi(self.stackedWidgetPage3)
        self.reg.setGeometry(0, 0, 1000, 600)
        self.reg.back.clicked.connect(self.goto_login)
        self.stackedWidget.addWidget(self.stackedWidgetPage3)

        # main
        self.stackedWidgetPage4.setObjectName("stackedWidgetPage4")
        self.main = main.MainUI(self.stackedWidgetPage4)
        # Pass system object to the child Widget
        self.main.bind_system(self.system)
        self.main.setupUi(self.stackedWidgetPage4)
        self.main.setGeometry(0, 0, 1000, 600)
        self.main.navbar.log_out.clicked.connect(self.logout)
        self.stackedWidget.addWidget(self.stackedWidgetPage4)

        self.stackedWidget.setCurrentIndex(0)

    def logout(self):
        self.dialog.setupUi(self.dialog)
        self.dialog.okay.clicked.connect(self.goto_landing)
        self.dialog.okay.clicked.connect(self.dialog.close)
        self.dialog.no.clicked.connect(self.dialog.close)
        self.dialog.show()

    def goto_landing(self):
        self.stackedWidget.setCurrentIndex(0)

    def goto_login(self):
        self.stackedWidget.setCurrentIndex(1)

    def goto_reg(self):
        self.stackedWidget.setCurrentIndex(2)

    def goto_main(self):
        self.stackedWidget.setCurrentIndex(3)
Exemple #20
0
class PageAllegroOptions(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self)
        self.parent = parent
        self.parent.addWidget(self)
        self.auto_login, self.auto_pwd, self.auto_email, self.auto_time = data.get_autofill()
        self.gridLayout = QGridLayout(self)
        self.gridLayout.setContentsMargins(0, 0, 0, 0)
        self.gridLayout.setSpacing(0)
        self.gridLayout.setColumnStretch(0, 3)
        self.gridLayout.setColumnStretch(1, 7)

        self.pushButton_auto = QPushButton(self)
        self.pushButton_auto.setText("Autofill")
        self.pushButton_auto.setStyleSheet(styles.btn_allegro_ops_auto)
        self.pushButton_auto.setCheckable(True)
        self.pushButton_auto.setChecked(True)
        self.pushButton_auto.clicked.connect(lambda: self.on_auto())
        self.gridLayout.addWidget(self.pushButton_auto, 0, 0, 1, 1)

        self.pushButton_help = QPushButton(self)
        self.pushButton_help.setText("Help")
        self.pushButton_help.setStyleSheet(styles.btn_allegro_ops_auto)
        self.pushButton_help.setCheckable(True)
        self.pushButton_help.setChecked(False)
        self.pushButton_help.clicked.connect(lambda: self.on_help())
        self.gridLayout.addWidget(self.pushButton_help, 1, 0, 1, 1)

        self.spacer_btn_d = QSpacerItem(40, 20, QSizePolicy.Expanding)
        self.gridLayout.addItem(self.spacer_btn_d, 2, 0, 1, 1)

        self.stackedWidget = QStackedWidget(self)
        self.stackedWidget.setStyleSheet("""QStackedWidget{background-color: #fff;}""")
        self.gridLayout.addWidget(self.stackedWidget, 0, 1, 3, 1)

        self.widget_auto = QWidget(self.stackedWidget)
        self.widget_auto.setStyleSheet("""QWidget{background-color: #fff;}""")
        self.stackedWidget.addWidget(self.widget_auto)

        self.widget_help = QWidget(self.stackedWidget)
        self.widget_help.setStyleSheet("""QWidget{background-color: #fff;}""")
        self.stackedWidget.addWidget(self.widget_help)

        self.gridLayout_help = QVBoxLayout(self.widget_help)
        self.gridLayout_help.setContentsMargins(50, 50, 50, 50)
        self.gridLayout_help.setSpacing(50)

        self.label_help = QLabel(self.widget_help)
        self.label_help.setStyleSheet(styles.label_lineEdit)
        self.label_help.setWordWrap(True)
        self.label_help.setText(styles.help_text)
        self.gridLayout_help.addWidget(self.label_help)

        self.spacer_help = QSpacerItem(40, 20, QSizePolicy.Expanding)
        self.gridLayout_help.addItem(self.spacer_help)

        self.gridLayout_auto = QGridLayout(self.widget_auto)
        self.gridLayout_auto.setContentsMargins(0, 0, 0, 0)
        self.gridLayout_auto.setSpacing(20)
        self.gridLayout_auto.setColumnStretch(0, 1)
        self.gridLayout_auto.setColumnStretch(1, 1)
        self.gridLayout_auto.setColumnStretch(2, 1)
        self.gridLayout_auto.setColumnStretch(3, 1)
        self.gridLayout_auto.setRowStretch(0, 3)
        self.gridLayout_auto.setRowStretch(1, 1)
        self.gridLayout_auto.setRowStretch(2, 1)
        self.gridLayout_auto.setRowStretch(3, 1)
        self.gridLayout_auto.setRowStretch(4, 1)
        self.gridLayout_auto.setRowStretch(5, 1)
        self.gridLayout_auto.setRowStretch(6, 3)

        self.lineEdit_login = QLineEdit(self.widget_auto)
        self.lineEdit_login.setMinimumSize(QSize(0, 60))
        self.lineEdit_login.setSizeIncrement(QSize(40, 40))
        self.lineEdit_login.setStyleSheet(styles.lineEdit_opt)
        self.gridLayout_auto.addWidget(self.lineEdit_login, 1, 2, 1, 1)
        self.lineEdit_login.setPlaceholderText("login or email of your account")
        self.lineEdit_login.setText(self.auto_login)

        self.lineEdit_password = QLineEdit(self.widget_auto)
        self.lineEdit_password.setMinimumSize(QSize(20, 60))
        self.lineEdit_password.setStyleSheet(styles.lineEdit_opt)
        self.lineEdit_password.setEchoMode(QLineEdit.Password)
        self.lineEdit_password.setReadOnly(False)
        self.gridLayout_auto.addWidget(self.lineEdit_password, 2, 2, 1, 1)
        self.lineEdit_password.setPlaceholderText("password of your account")
        self.lineEdit_password.setText(self.auto_pwd)

        self.lineEdit_email = QLineEdit(self.widget_auto)
        self.lineEdit_email.setMinimumSize(QSize(0, 60))
        self.lineEdit_email.setStyleSheet(styles.lineEdit_opt)
        self.lineEdit_email.setFrame(True)
        self.lineEdit_email.setEchoMode(QLineEdit.Normal)
        self.gridLayout_auto.addWidget(self.lineEdit_email, 3, 2, 1, 1)
        self.lineEdit_email.setPlaceholderText("email to which the notification will be sent")
        self.lineEdit_email.setText(self.auto_email)

        self.lineEdit_time = QLineEdit(self.widget_auto)
        self.lineEdit_time.setMinimumSize(QSize(0, 60))
        self.lineEdit_time.setStyleSheet(styles.lineEdit)
        self.gridLayout_auto.addWidget(self.lineEdit_time, 4, 2, 1, 1)
        self.lineEdit_time.setPlaceholderText("interval between refreshes")
        self.lineEdit_time.setText(str(self.auto_time))

        self.label_login = QLabel("Allegro login", self)
        self.label_login.setStyleSheet(styles.label_lineEdit)
        self.gridLayout_auto.addWidget(self.label_login, 1, 1, 1, 1)

        self.label_password = QLabel("Allegro password", self)
        self.label_password.setStyleSheet(styles.label_lineEdit)
        self.gridLayout_auto.addWidget(self.label_password, 2, 1, 1, 1)

        self.label_email = QLabel("Email to notificate", self)
        self.label_email.setStyleSheet(styles.label_lineEdit)
        self.gridLayout_auto.addWidget(self.label_email, 3, 1, 1, 1)

        self.label_time = QLabel("Refresh time[s]", self)
        self.label_time.setStyleSheet(styles.label_lineEdit)
        self.gridLayout_auto.addWidget(self.label_time, 4, 1, 1, 1)

        self.pushButton_set = QPushButton("Set", self.widget_auto)
        self.pushButton_set.clicked.connect(lambda: self.on_set())
        self.pushButton_set.setMinimumSize(QSize(0, 40))
        self.pushButton_set.setStyleSheet(styles.btn_dark)
        self.gridLayout_auto.addWidget(self.pushButton_set, 5, 2, 1, 1)

        self.spacer_auto_l = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.gridLayout_auto.addItem(self.spacer_auto_l, 1, 1, 1, 1)

        self.spacer_auto_r = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.gridLayout_auto.addItem(self.spacer_auto_r, 1, 3, 1, 1)

        self.spacer_auto_t = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.gridLayout_auto.addItem(self.spacer_auto_t, 0, 0, 1, 1)

        self.spacer_auto_b = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.gridLayout_auto.addItem(self.spacer_auto_b, 6, 0, 1, 1)

    def on_set(self):
        data.add_autofill(self.lineEdit_login.text(), self.lineEdit_password.text(), self.lineEdit_email.text(), int(self.lineEdit_time.text()))
        auto_login, auto_pwd, auto_email, auto_time = data.get_autofill()
        self.parent.pageAllegroAdd.lineEdit_login.setText(auto_login)
        self.parent.pageAllegroAdd.lineEdit_password.setText(auto_pwd)
        self.parent.pageAllegroAdd.lineEdit_email.setText(auto_email)
        self.parent.pageAllegroAdd.lineEdit_time.setText(str(auto_time))

    def on_auto(self):
        self.pushButton_auto.setChecked(True)
        self.pushButton_help.setChecked(False)
        self.stackedWidget.setCurrentIndex(0)

    def on_help(self):
        self.pushButton_auto.setChecked(False)
        self.pushButton_help.setChecked(True)
        self.stackedWidget.setCurrentIndex(1)
Exemple #21
0
class ColorPanel(QWidget):
    """
    颜色面板柱状条
    作者:feiyangqingyun(QQ:517216493) 2017-11-21
    译者:sunchuquin(QQ:1715216365) 2021-07-04
    """
    def __init__(self, parent: QWidget = None):
        super(ColorPanel, self).__init__(parent)
        self.resize(650, 450)

        g_layout = QVBoxLayout()

        self.stackedWidget = QStackedWidget()
        self.pageFader = QWidget()
        layout = QVBoxLayout()
        self.colorPanelFader = ColorPanelFader()
        layout.addWidget(self.colorPanelFader)
        layout.setContentsMargins(0, 0, 0, 0)
        self.pageFader.setLayout(layout)
        self.pageHSB = QWidget()
        layout = QHBoxLayout()
        self.colorPanelHSB = ColorPanelHSB()
        self.colorPanelBar = ColorPanelBar()
        self.colorPanelBar.setMinimumWidth(60)
        self.colorPanelBar.setMaximumWidth(60)
        layout.addWidget(self.colorPanelHSB)
        layout.addWidget(self.colorPanelBar)
        layout.setContentsMargins(0, 0, 0, 0)
        self.pageHSB.setLayout(layout)
        self.pageBtn = QWidget()
        layout = QVBoxLayout()
        self.colorPanelBtn = ColorPanelBtn()
        layout.addWidget(self.colorPanelBtn)
        layout.setContentsMargins(0, 0, 0, 0)
        self.pageBtn.setLayout(layout)
        self.stackedWidget.addWidget(self.pageFader)
        self.stackedWidget.addWidget(self.pageHSB)
        self.stackedWidget.addWidget(self.pageBtn)
        g_layout.addWidget(self.stackedWidget)

        self.widgetColor = QWidget()
        self.btnHue = ColorButton()
        self.btnSat = ColorButton()
        self.btnBright = ColorButton()
        self.btnCyan = ColorButton()
        self.btnMagenta = ColorButton()
        self.btnYellow = ColorButton()
        self.btnRed = ColorButton()
        self.btnGreen = ColorButton()
        self.btnBlue = ColorButton()
        self.widgetColor.setMinimumHeight(45)
        self.widgetColor.setMaximumHeight(45)
        layout = QHBoxLayout()
        layout.addWidget(self.btnHue)
        layout.addWidget(self.btnSat)
        layout.addWidget(self.btnBright)
        layout.addWidget(self.btnCyan)
        layout.addWidget(self.btnMagenta)
        layout.addWidget(self.btnYellow)
        layout.addWidget(self.btnRed)
        layout.addWidget(self.btnGreen)
        layout.addWidget(self.btnBlue)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)
        self.widgetColor.setLayout(layout)
        g_layout.addWidget(self.widgetColor)

        self.widgetPanel = QWidget()
        layout = QHBoxLayout()
        self.btnPanelFader = ColorButton()
        self.btnPanelHSB = ColorButton()
        self.btnPanelBtn = ColorButton()
        self.labColor = QLabel()
        self.labColor.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        layout.addWidget(self.btnPanelFader)
        layout.addWidget(self.btnPanelHSB)
        layout.addWidget(self.btnPanelBtn)
        layout.addWidget(self.labColor)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(1)
        self.widgetPanel.setLayout(layout)
        g_layout.addWidget(self.widgetPanel)
        self.setLayout(g_layout)

        self.initForm()

    def initForm(self) -> None:
        self.btnHue.borderColor = QColor(Qt.darkGray)
        self.btnSat.borderColor = QColor(Qt.darkGray)
        self.btnBright.borderColor = QColor(Qt.darkGray)
        self.btnHue.normalColor = QColor(Qt.darkGray).light(20)
        self.btnSat.normalColor = QColor(Qt.darkGray).light(20)
        self.btnBright.normalColor = QColor(Qt.darkGray).light(20)

        self.btnCyan.borderColor = QColor(Qt.cyan)
        self.btnMagenta.borderColor = QColor(Qt.magenta)
        self.btnYellow.borderColor = QColor(Qt.yellow)
        self.btnCyan.normalColor = QColor(Qt.cyan).light(50)
        self.btnMagenta.normalColor = QColor(Qt.magenta).light(50)
        self.btnYellow.normalColor = QColor(Qt.yellow).light(50)

        self.btnRed.borderColor = QColor(Qt.red)
        self.btnGreen.borderColor = QColor(Qt.green)
        self.btnBlue.borderColor = QColor(Qt.blue)
        self.btnRed.normalColor = QColor(Qt.red).light(50)
        self.btnGreen.normalColor = QColor(Qt.green).light(50)
        self.btnBlue.normalColor = QColor(Qt.blue).light(50)

        self.btnPanelFader.text = "颜色滑块面板"
        self.btnPanelHSB.text = "颜色选取面板"
        self.btnPanelBtn.text = "颜色按钮面板"

        self.btnPanelFader.normalColor = QColor("#16A085")
        self.btnPanelHSB.normalColor = QColor("#C0392B")
        self.btnPanelBtn.normalColor = QColor("#27AE60")

        font: QFont = QFont()
        font.setPixelSize(15)
        font.setBold(True)
        self.btnPanelFader.textFont = font
        self.btnPanelHSB.textFont = font
        self.btnPanelBtn.textFont = font

        self.colorPanelFader.colorChanged.connect(self.colorChangedFader)
        self.colorPanelHSB.colorChanged.connect(self.colorChangedHSB)
        self.colorPanelBar.colorChanged.connect(self.colorChangedBar)
        self.colorPanelBtn.colorChanged.connect(self.colorChangedBtn)

        self.widgetColor.setEnabled(False)
        self.stackedWidget.setCurrentIndex(0)
        self.colorPanelBar.staticMode = False
        self.colorChangedBar(QColor(Qt.red), 0, 100)

        self.btnPanelFader.clicked.connect(self.buttonClicked)
        self.btnPanelHSB.clicked.connect(self.buttonClicked)
        self.btnPanelBtn.clicked.connect(self.buttonClicked)

    def colorChangedFader(self, color: QColor, hue: float, sat: float,
                          bright: float) -> None:
        self.btnHue.text = "Hue\n%0.1f" % round(hue, 1)
        self.btnSat.text = "Sat\n%0.1f" % round(sat, 1)
        self.btnBright.text = "Bright\n%0.1f" % round(bright, 1)

        self.setColor(color)

    def colorChangedHSB(self, color: QColor, hue: float, sat: float) -> None:
        self.colorPanelBar.topColor = color
        self.colorPanelBar.borderColor = color

        self.btnHue.text = "Hue\n%0.1f" % round(hue, 1)
        self.btnSat.text = "Sat\n%0.1f" % round(sat, 1)
        self.btnBright.text = "Bright\n%0.1f" % round(
            self.colorPanelBar.percent, 1)

        c: QColor = QColor.fromHsvF(hue / 360, sat / 100,
                                    self.colorPanelBar.percent / 100)
        self.setColor(c)

    def colorChangedBar(self, color: QColor, value: float,
                        percent: float) -> None:
        if self.colorPanelHSB.isVisible():
            self.colorPanelHSB.percent = percent

        hue: float = color.hue()
        hue = 360 if hue < 0 else hue
        sat: float = color.saturationF() * 100

        if not self.colorPanelBar.isVisible():
            self.btnHue.text = "Hue\n%0.1f" % round(hue, 1)
            self.btnSat.text = "Sat\n%0.1f" % round(sat, 1)

        self.btnBright.text = "Bright\n%0.1f" % round(percent, 1)

        self.setColor(color)

    def colorChangedBtn(self, color: QColor) -> None:
        self.colorChangedBar(color, 0, 100)

    def setColor(self, color: QColor) -> None:
        # 根据背景色自动计算合适的前景色
        gray: float = (0.299 * color.red() + 0.587 * color.green() +
                       0.114 * color.blue()) / 255
        textColor: QColor = QColor(Qt.black) if gray > 0.5 else QColor(
            Qt.white)
        self.labColor.setText(color.name().upper())
        self.labColor.setStyleSheet(
            "QLabel{font:25px;color:%s;background:%s;}" %
            (textColor.name(), color.name()))

        percentRed: float = color.redF() * 100
        percentGreen: float = color.greenF() * 100
        percentBlue: float = color.blueF() * 100

        self.btnCyan.text = "Cyan\n%0.1f%%" % round(100 - percentRed, 1)
        self.btnMagenta.text = "Magenta\n%0.1f%%" % round(
            100 - percentGreen, 1)
        self.btnYellow.text = "Yellow\n%0.1f%%" % round(100 - percentBlue, 1)

        self.btnRed.text = "Red\n%0.1f%%" % round(percentRed, 1)
        self.btnGreen.text = "Green\n%0.1f%%" % round(percentGreen, 1)
        self.btnBlue.text = "Blue\n%0.1f%%" % round(percentBlue, 1)

    def buttonClicked(self) -> None:
        btn: ColorButton = self.sender()
        if btn == self.btnPanelFader:
            self.stackedWidget.setCurrentIndex(0)
        elif btn == self.btnPanelHSB:
            self.stackedWidget.setCurrentIndex(1)
        elif btn == self.btnPanelBtn:
            self.stackedWidget.setCurrentIndex(2)
Exemple #22
0
        main.removeWidget(oldWidget)
        oldWidget.destroy()
    main.addWidget(timerWidget)
    main.setCurrentIndex(TIMER_PAGE)
    main.setFixedSize(370, 200)


# Constants
MAIN_PAGE = 0
TIMER_PAGE = 1
SIT_TIMER = 0
STAND_TIMER = 1

# Create Qt App
app = QtWidgets.QApplication(sys.argv)
app.setApplicationDisplayName("Sit_Stand")
icon = QIcon(resource_path('./images/icon.png'))
app.setWindowIcon(icon)

# Create and Show Elements
main = QStackedWidget()
homeWidget = MainPage()

main.addWidget(homeWidget)
main.setGeometry(100, 100, 370, 100)
main.setFixedSize(370, 100)
main.show()

# Run Qt main loop
app.exec_()
Exemple #23
0
class ORIRMain(QFramelessWindow):
    def __init__(self):
        super(ORIRMain, self).__init__()
        self._m_navigation_bar = QSlideNavigationBar()
        self._m_stacked_widget = QStackedWidget()
        self._m_stacked_widget.setMouseTracking(True)
        self._layout = QHBoxLayout()

        self._init_navigation_bar()
        self._layout.addWidget(self._m_navigation_bar)
        self._layout.addWidget(self._m_stacked_widget)
        self._layout.setStretchFactor(self._m_navigation_bar, 1)
        self._layout.setStretchFactor(self._m_stacked_widget, 40)
        self.set_layout(None, self._layout)

        self._debug_page = ORIR_Debug()
        self._m_stacked_widget.addWidget(self._debug_page)
        self._loganalysis_page = ORIR_LogAnalysis()
        self._m_stacked_widget.addWidget(self._loganalysis_page)
        self._collectiondata_page = ORIR_CollectionData()
        self._m_stacked_widget.addWidget(self._collectiondata_page)

        self._m_tool_page = ORIR_Tool()
        self._m_stacked_widget.addWidget(self._m_tool_page)

        self._help_page = ORIR_Help()
        self._m_stacked_widget.addWidget(self._help_page)

        self._m_navigation_bar.itemClicked.connect(self._on_change_page)
        # self.set_window_icon('../resources/icons/battery.ico')
        self.setWindowFlags(Qt.FramelessWindowHint or Qt.WindowSystemMenuHint
                            or Qt.WindowMinMaxButtonsHint)

    def _on_update_status_bar(self, str):
        """
        更新状态栏
        :param str:要更新的信息
        :return:
        """
        self.set_status_text(str)

    def _on_change_page(self, index: int, item_name: str):
        """
        切换页面的槽函数
        :param index:
        :param item_name:
        :return:
        """
        self._m_stacked_widget.setCurrentIndex(index)

    def _init_navigation_bar(self):
        """
        初始化导航栏
        :return: None
        """
        self._m_navigation_bar.set_orientation(Qt.Vertical)
        self._m_navigation_bar.set_fixed(True)
        self._m_navigation_bar.add_item('前期调试')
        self._m_navigation_bar.add_item('日志分析')
        self._m_navigation_bar.add_item('采点工具')
        self._m_navigation_bar.add_item('辅助工具')
        self._m_navigation_bar.add_item('帮助')
        self._m_navigation_bar.set_item_line_style(
            QSlideNavigationBar.ItemLineStyle.ItemLeft)
        self._m_navigation_bar.set_item_line_color(QColor('red'))
        self._m_navigation_bar.setMaximumWidth(20)

    def closeEvent(self, event):
        # message为窗口标题
        # Are you sure to quit?窗口显示内容
        # QtGui.QMessageBox.Yes | QtGui.QMessageBox.No窗口按钮部件
        # QtGui.QMessageBox.No默认焦点停留在NO上
        reply = QMessageBox.question(self, 'Message', "确定退出?",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        # 判断返回结果处理相应事项
        if reply == QMessageBox.Yes:
            self._debug_page.close_all()
            self._m_tool_page.close_all()
            event.accept()
        else:
            event.ignore()
Exemple #24
0
class ExifViewer(QMainWindow):
    WINDOW_SIZE = {'width': 900, 'height': 600}

    def __init__(self, parent=None):
        super().__init__(parent)
        self.qw_tree = None
        self.qw_stack = None
        self.tree_type_to_stack_idx = None
        self.exif_reader = None

        self.setWindowTitle("exif viewer")
        self.resize(self.WINDOW_SIZE['width'], self.WINDOW_SIZE['height'])

    def make_viewer(self, exif_reader):
        self.exif_reader = exif_reader
        ifds = self.exif_reader.get_exif()

        self.qw_tree = QTreeWidget(self)
        qt_util_init_tree(self.qw_tree, header_list=['ifd_name'])

        self.qw_stack = QStackedWidget(self)
        self.tree_type_to_stack_idx = {}
        for tree_item_type, (ifd_name, ifd) in enumerate(ifds.items()):
            # -----------------------
            # treeにifd nameを書き込んでroot_treeに登録する.
            # -----------------------
            qw_tree_item = QTreeWidgetItem(['{} ifd'.format(ifd_name), ''],
                                           type=tree_item_type)
            self.qw_tree.addTopLevelItem(qw_tree_item)

            # -----------------------
            # text_editにexif情報を書き込んでstacked_widgetに登録する.
            # -----------------------
            qw_text_edit = QTextEdit(self)
            qt_util_init_text_edit(qw_text_edit)
            qt_util_append_text_edit(qw_text_edit, None, head=True)
            for _, tag in ifd.items():
                qt_util_append_text_edit(qw_text_edit,
                                         tag,
                                         head=False,
                                         display_max_len=50)
            qt_util_reset_text_edit_cursor(qw_text_edit)

            idx = self.qw_stack.addWidget(qw_text_edit)
            self.tree_type_to_stack_idx[tree_item_type] = idx
        else:
            self.qw_tree.itemClicked.connect(self._tree_item_clicked)

        # -----------------------
        # widgetをlayoutに登録する.
        # -----------------------
        widgets = [self.qw_tree, self.qw_stack]
        self._make_layout(widgets)
        self._make_toolbar()

        self.show()  # widgetを表示する.

    def _make_toolbar(self):
        # action_to_text = QAction(QIcon(os.path.join('icon', 'text.png')), 'save', self)
        save_icon = QApplication.style().standardIcon(
            QStyle.SP_DialogSaveButton)
        save_action = QAction(icon=save_icon, text='save', parent=self)
        save_action.triggered.connect(self._save_text)

        exit_icon = QApplication.style().standardIcon(
            QStyle.SP_DialogCloseButton)
        exit_action = QAction(icon=exit_icon, text='exit', parent=self)
        exit_action.triggered.connect(self._quit)

        # ツールバー作成
        self.toolbar = self.addToolBar('tool bar')
        self.toolbar.setIconSize(QSize(35, 35))
        self.toolbar.setFixedHeight(35)
        self.toolbar.addAction(save_action)
        self.toolbar.addAction(exit_action)

    def _make_layout(self, widgets):
        qw_splitter = QSplitter(self)
        for widget in widgets:
            qw_splitter.addWidget(widget)
        qw_splitter.setSizes(
            [self.WINDOW_SIZE['width'] * 0.1, self.WINDOW_SIZE['width'] * 0.9])

        # layout = QHBoxLayout()
        # layout.addWidget(qw_splitter)
        # qw = QWidget()
        # qw.setLayout(layout)
        # self.setCentralWidget(qw)

        # self.setWindowFlags(Qt.WindowStaysOnTopHint) # 常に手前に表示する.

        # MainWindowのCentral領域にWidgetを設定
        self.setCentralWidget(qw_splitter)

    @Slot()
    def _tree_item_clicked(self, item, column):
        self.qw_stack.setCurrentIndex(self.tree_type_to_stack_idx[item.type()])
        current_text_edit = self.qw_stack.currentWidget()
        qt_util_reset_text_edit_cursor(current_text_edit)

    @Slot()
    def _save_text(self):
        filename = QFileDialog.getSaveFileName(self, '名前を付けて保存')
        out_path = filename[0]
        if out_path:
            self.exif_reader.save_log(out_path)

    @Slot()
    def _quit(self):
        qApp.quit()
class UpdatePrompt(QDialog):
    def __init__(self):
        super().__init__()
        self.makeView()
        return

    def makeView(self):
        layout = QVBoxLayout()
        btnLayout = QHBoxLayout()
        self.centStack = QStackedWidget()
        self.updateButton = QPushButton('Update')
        self.cancelButton = QPushButton('Cancel')
        notifyLabel = QLabel('There are upgrades scheduled')
        self.inputBox = QLineEdit()
        self.outputBox = QTextBrowser()
        #refreshIcon = QIcon.fromTheme('process-working')
        self.refreshIcon = QMovie('assets/spin3.gif')
        refreshAnimation = QLabel()

        layout.addWidget(notifyLabel)
        layout.addWidget(self.centStack)
        layout.addWidget(self.inputBox)
        layout.addLayout(btnLayout)
        btnLayout.addWidget(self.cancelButton)
        btnLayout.addWidget(self.updateButton)

        self.centStack.addWidget(refreshAnimation)
        self.centStack.addWidget(self.outputBox)
        refreshAnimation.setMovie(self.refreshIcon)
        refreshAnimation.setAlignment(Qt.AlignCenter)
        self.refreshIcon.start()

        self.inputBox.setEchoMode(QLineEdit.Password)
        self.inputBox.setFocus()
        self.inputBox.returnPressed.connect(self.pkgUpdates)
        self.updateButton.clicked.connect(self.pkgUpdates)
        self.cancelButton.clicked.connect(self.cancelUpdates)
        self.updateButton.setDefault(True)

        self.centStack.setCurrentIndex(1)
        notifyLabel.setAlignment(Qt.AlignTop)
        self.outputBox.setReadOnly(True)
        #self.outputBox.setAlignment(Qt.AlignTop)
        self.setWindowTitle('Package Upgrades')
        self.setLayout(layout)
        self.resize(450, 250)
        return

    async def asetup(self, password):
        async with trio.open_nursery() as nursery:
            finishedState = trio.Event()
            nursery.start_soon(self.upProc, password, 'update', finishedState)
            #nursery.start_soon(self.KEAlive, finishedState)
        return

    async def upProc(self, password, cmd, finishedState):
        proc = await trio.open_process(['sudo', '-S', 'apt-get', cmd, '-y'],
                                       stdin=subprocess.PIPE,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.STDOUT)
        await proc.stdin.send_all((password + '\n').encode())

        while (proc.poll() == None):
            QCoreApplication.processEvents()
            await trio.sleep(0.1)

        result = ''
        result = await self.pullOutput(proc)
        self.appendToOutput(result)
        proc.terminate()

        if (cmd == 'update'):
            await self.upProc(password, 'upgrade', finishedState)
            finishedState.set()
        return

    async def pullOutput(self, proc):
        x = await proc.stdout.receive_some()
        x = x.decode()
        result = ''
        while (x != ''):
            QCoreApplication.processEvents()
            result = result + x
            x = await proc.stdout.receive_some()
            x = x.decode()
        return result

    async def KEAlive(self, finishedState):
        while finishedState.is_set():
            QCoreApplication.processEvents()
            trio.sleep(0.1)
        return

        return

    def appendToOutput(self, add):
        currentText = self.outputBox.toPlainText()
        self.outputBox.setText(currentText + 'Running updates\n' + add + '\n')
        print(add)
        return

    def pkgUpdates(self):
        self.centStack.setCurrentIndex(0)
        self.refreshIcon.start()
        QCoreApplication.processEvents()

        password = self.inputBox.text()

        if (password == ''):
            self.passError('The password field cannot be empty')
            return

        self.inputBox.clear()
        self.inputBox.setDisabled(True)
        self.updateButton.setDisabled(True)
        trio.run(self.asetup, password)
        self.centStack.setCurrentIndex(1)
        self.refreshIcon.stop()
        self.updateButton.setDisabled(False)
        self.inputBox.setDisabled(False)
        return

    def passError(self, s):
        passError = QDialog(self)
        msg = QLabel(s)
        layout = QVBoxLayout()
        layout.addWidget(msg)
        passError.setLayout(layout)

        okBtn = QPushButton('OK')
        okBtn.clicked.connect(passError.reject)
        layout.addWidget(okBtn)

        passError.exec_()
        return

    def cancelUpdates(self):
        #Needs way of closing subprocess during async run
        self.reject()
        return
Exemple #26
0
class MainWindowUi(QMainWindow):
    """sets up ui properties of MainWindowUi class"""
    def __init__(self) -> None:
        """inits MainWindow class

        configuring parameters of MainWindow class and inherits from QtWidget.QMainWindow
        loads .ui file sets up file and directory path vars, inits click events(menuebar, coboboxes, btns) and
        shows gui the first time

        Returns:
            None"""
        super(MainWindowUi, self).__init__()
        self.setWindowTitle("It_Hilfe")
        self.resize(820, 450)
        self.setWindowIcon(QIcon("./data/favicon2.png"))
        self.setMinimumSize(700, 250)

        self.file_path = None
        self.dir = None
        self.last_open_file_path = None
        self.last_open_file_dir = None
        self.initial_theme = None
        self.registered_devices = {}
        self.user_config_file = "./data/user_config.json"

        # setup stackedwidget
        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)
        self.setup_menubar()
        self.setup_p_view()
        self.setup_p_register()
        self.setup_p_create()
        self.setup_p_preferences()
        self.setup_signals()

        self.font = QFont()
        self.font.setPointSize(9)

        self.validate(self.set_user_preferences,
                      file_path=self.user_config_file,
                      schema=validate_json.ItHilfeUserPreferencesSchema,
                      forbidden=[""])

        # setup statusbar
        self.statusbar = self.statusBar()

        self.stacked_widget.setCurrentWidget(self.p_view)

    def setup_menubar(self) -> None:
        """inits menubar

        Returns:
            None"""
        self.menu_Bar = self.menuBar()

        menu_file = self.menu_Bar.addMenu("file")
        self.action_open = QAction("open")
        self.action_save = QAction("save")
        self.action_new = QAction("new")
        self.action_print = QAction("print")
        self.action_preferences = QAction("preferences")
        self.action_hide_menu_bar = QAction("hide menubar")
        self.action_print.setShortcut(QKeySequence("Ctrl+p"))
        self.action_open.setShortcut(QKeySequence("Ctrl+o"))
        self.action_save.setShortcut(QKeySequence("Ctrl+s"))
        self.action_hide_menu_bar.setShortcut(QKeySequence("Ctrl+h"))
        self.action_hide_menu_bar.setIcon(QIcon("./data/show_hide.ico"))
        self.action_print.setIcon(QIcon("./data/print2.ico"))
        self.action_open.setIcon(QIcon("./data/open.ico"))
        self.action_save.setIcon(QIcon("./data/save.ico"))
        self.action_new.setIcon(QIcon("./data/newfile.ico"))
        self.action_preferences.setIcon(QIcon("./data/preferences.ico"))

        menu_file.addAction(self.action_open)
        menu_file.addAction(self.action_save)
        menu_file.addAction(self.action_new)
        menu_file.addAction(self.action_print)
        menu_file.addAction(self.action_preferences)

        menu_edit = self.menu_Bar.addMenu("edit")
        self.action_register = QAction("register")
        self.action_register.setShortcut(QKeySequence("Ctrl+n"))
        self.action_register.setIcon(QIcon("./data/register.ico"))

        menu_edit.addAction(self.action_register)

        menu_view = self.menu_Bar.addMenu("view")
        menu_view.addAction(self.action_hide_menu_bar)

    def setup_p_view(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""
        self.p_view = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_view)

        self.model = QStandardItemModel(self.p_view)
        self.model.setHorizontalHeaderLabels(labels)

        self.filters = []
        source_model = self.model
        for filter_num in range(7):
            filter = QSortFilterProxyModel()
            filter.setSourceModel(source_model)
            filter.setFilterKeyColumn(filter_num)
            source_model = filter
            self.filters.append(filter)

        delegate = ComboDelegate()
        self.table = QtWidgets.QTableView(self.p_view)
        self.table.setModel(self.filters[-1])
        self.table.setItemDelegateForColumn(2, delegate)
        self.table.horizontalHeader().setStretchLastSection(True)
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.header = FilterHeader(self.table)
        self.header.set_filter_boxes()
        self.header.setMaximumHeight(50)
        self.table.setHorizontalHeader(self.header)

        self.bt_burger = QPushButton(self.p_view)
        self.bt_burger.setIcon(QIcon("./data/menu2.svg"))
        self.bt_burger.setIconSize(QSize(30, 30))
        self.bt_burger.setToolTip('slide out description')
        l_burger = QLabel("menu", self.p_view)

        self.bt_register_new = QPushButton(self.p_view)
        self.bt_register_new.setIcon(QIcon("./data/add.ico"))
        self.bt_register_new.setIconSize(QSize(30, 30))
        self.bt_register_new.setToolTip("register new")
        l_register_new = QLabel("register new", self.p_view)

        self.bt_delete_column = QPushButton(self.p_view)
        self.bt_delete_column.setIcon(QIcon("./data/remove.ico"))
        self.bt_delete_column.setIconSize(QSize(30, 30))
        self.bt_delete_column.setToolTip(
            "delete columns with min 1 cell selected")
        l_delete = QLabel("delete column", self.p_view)

        self.bt_hide_show_filter = QPushButton(self.p_view)
        self.bt_hide_show_filter.setIcon(QIcon("./data/show_hide.ico"))
        self.bt_hide_show_filter.setIconSize(QSize(30, 30))
        self.bt_hide_show_filter.setToolTip("hide/show filter input")
        l_hide_show = QLabel("hide/show", self.p_view)

        self.left_btn_frame = QFrame(self.p_view)
        self.left_btn_frame.setMaximumWidth(40)
        self.left_btn_frame.setContentsMargins(0, 0, 0, 0)

        self.left_menu_frame = QFrame(self.p_view)
        self.left_menu_frame.setMaximumWidth(0)
        self.left_menu_frame.setContentsMargins(0, 0, 0, 0)

        p_view_layout2 = QtWidgets.QVBoxLayout(self.left_btn_frame)
        p_view_layout2.addWidget(self.bt_burger)
        p_view_layout2.addWidget(self.bt_register_new)
        p_view_layout2.addWidget(self.bt_delete_column)
        p_view_layout2.addWidget(self.bt_hide_show_filter)
        p_view_layout2.setAlignment(Qt.AlignTop)
        p_view_layout2.setContentsMargins(0, 0, 0, 0)

        self.p_view_layout3 = QtWidgets.QVBoxLayout(self.left_menu_frame)
        self.p_view_layout3.addWidget(l_burger)
        self.p_view_layout3.addWidget(l_register_new)
        self.p_view_layout3.addWidget(l_delete)
        self.p_view_layout3.addWidget(l_hide_show)
        self.p_view_layout3.setAlignment(Qt.AlignTop | Qt.AlignCenter)
        self.p_view_layout3.setContentsMargins(0, 0, 0, 0)
        self.p_view_layout3.setSpacing(25)

        p_view_layout = QHBoxLayout(self.p_view)
        p_view_layout.setContentsMargins(0, 0, 0, 0)
        p_view_layout.addWidget(self.left_btn_frame)
        p_view_layout.addWidget(self.left_menu_frame)
        p_view_layout.addWidget(self.table)
        self.p_view.setLayout(p_view_layout)

        self.p_view.addAction(self.action_open)
        self.p_view.addAction(self.action_save)
        self.p_view.addAction(self.action_new)
        self.p_view.addAction(self.action_print)
        self.p_view.addAction(self.action_register)
        self.p_view.addAction(self.action_hide_menu_bar)

    def setup_p_register(self) -> None:
        """inits stacked widget page widgets

        Returns:
            None"""

        self.p_register = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_register)

        l_user = QtWidgets.QLabel("Username", self.p_register)
        self.in_username = QtWidgets.QLineEdit(self.p_register)
        l_devicename = QtWidgets.QLabel("Devicename", self.p_register)
        self.in_devicename = QtWidgets.QLineEdit(self.p_register)
        l_devicetype = QtWidgets.QLabel("DeviceType", self.p_register)
        self.in_combobox_devicetype = QtWidgets.QComboBox(self.p_register)
        l_os = QtWidgets.QLabel("OS", self.p_register)
        self.in_combobox_os = QtWidgets.QComboBox(self.p_register)
        l_comment = QtWidgets.QLabel("Comment", self.p_register)
        self.in_comment = QtWidgets.QTextEdit(self.p_register)
        self.bt_enter_register = QPushButton("register", self.p_register)
        self.bt_cancel_register = QPushButton("cancel", self.p_register)

        p_register_layout = QtWidgets.QVBoxLayout(self.p_register)
        p_register_layout.addWidget(l_user)
        p_register_layout.addWidget(self.in_username)
        p_register_layout.addWidget(l_devicename)
        p_register_layout.addWidget(self.in_devicename)
        p_register_layout.addWidget(l_devicetype)
        p_register_layout.addWidget(self.in_combobox_devicetype)
        p_register_layout.addWidget(l_os)
        p_register_layout.addWidget(self.in_combobox_os)
        p_register_layout.addWidget(l_comment)
        p_register_layout.addWidget(self.in_comment)
        p_register_layout.addWidget(self.bt_enter_register)
        p_register_layout.addWidget(self.bt_cancel_register)

    def setup_p_create(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""

        self.p_create = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_create)

        l_new_filepath = QtWidgets.QLabel("new filepath", self.p_create)
        self.bt_mod_new_path = QPushButton("mod filepath", self.p_create)
        self.in_new_filepath = QtWidgets.QLineEdit(self.p_create)
        l_new_filename = QtWidgets.QLabel("new filename", self.p_create)
        self.in_new_filename = QtWidgets.QLineEdit(self.p_create)
        self.bt_create = QPushButton("create", self.p_create)
        self.bt_cancel_create = QPushButton("cancel", self.p_create)

        p_create_layout = QtWidgets.QVBoxLayout(self.p_create)
        p_create_layout.addWidget(l_new_filepath)
        p_create_layout.addWidget(self.in_new_filepath)
        p_create_layout.addWidget(l_new_filename)
        p_create_layout.addWidget(self.in_new_filename)
        p_create_layout.addStretch(100)
        p_create_layout.addWidget(self.bt_mod_new_path)
        p_create_layout.addWidget(self.bt_create)
        p_create_layout.addWidget(self.bt_cancel_create)

    def setup_p_preferences(self) -> None:
        """inits setup_p_preferences stacked widget page widget

            Returns:
                None"""

        self.p_preferences = QWidget()
        self.p_preferences.resize(500, 250)
        self.p_preferences.setWindowTitle("preferences")
        self.list_Widget = QListWidget(self.p_preferences)
        self.list_Widget.addItems(["appearance", "about"])
        self.list_Widget.setMaximumWidth(100)

        self.stacked_widget_preferences = QStackedWidget(self.p_preferences)

        # setup appearance
        self.apperence_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.apperence_widget)
        self.in_combo_themes = QComboBox(self.apperence_widget)
        self.in_combo_themes.addItems(["dark_theme", "light_theme"])

        self.in_combo_theme_initial = QComboBox(self.apperence_widget)
        self.in_combo_theme_initial.addItems(["dark_theme", "light_theme"])

        self.text_size_slider = QSlider(QtCore.Qt.Orientation.Horizontal,
                                        self.apperence_widget)
        self.text_size_slider.setTickPosition(QSlider.TickPosition.TicksAbove)
        self.text_size_slider.setMaximum(15)
        self.text_size_slider.setMinimum(8)

        stacked_widget_preferences_layout = QGridLayout(self.apperence_widget)
        stacked_widget_preferences_layout.setAlignment(QtCore.Qt.AlignTop)
        stacked_widget_preferences_layout.addWidget(QLabel("theme"), 0, 0)
        stacked_widget_preferences_layout.addWidget(self.in_combo_themes, 0, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("initial theme"), 1,
                                                    0)
        stacked_widget_preferences_layout.addWidget(
            self.in_combo_theme_initial, 1, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("Fontsize"), 2, 0)
        stacked_widget_preferences_layout.addWidget(self.text_size_slider, 2,
                                                    1)

        self.about_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.about_widget)

        about_text_edit = QTextEdit(self.about_widget)
        about_text_edit.setText(
            "developed by Maurice Jarck\nwith kind support from Shuai Lou\n07.2020-04.2021"
        )
        about_text_edit.setEnabled(False)
        stacked_widget_about_layout = QGridLayout(self.about_widget)
        stacked_widget_about_layout.addWidget(about_text_edit)

        p_apperance_layout = QHBoxLayout(self.p_preferences)
        p_apperance_layout.addWidget(self.list_Widget)
        p_apperance_layout.addWidget(self.stacked_widget_preferences)

    def setup_signals(self) -> None:
        """connects signals

        Returns:
            None"""

        # header
        for filter, editor in zip(self.filters, self.header.editors):
            editor.textChanged.connect(filter.setFilterRegExp)

        # line edit
        self.in_new_filename.returnPressed.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))

        # comboboxes
        self.in_combobox_devicetype.addItems(
            ["choose here"] + [x.__name__ for x in valid_devices])
        self.in_combobox_devicetype.currentIndexChanged.connect(
            lambda: self.update_combobox(
                self.in_combobox_os, valid_devices[
                    self.in_combobox_devicetype.currentIndex() - 1].expected_OS
            ))
        self.in_combo_themes.currentIndexChanged.connect(
            lambda: self.change_theme(self.in_combo_themes.currentText()))
        self.in_combo_theme_initial.currentTextChanged.connect(lambda: setattr(
            self, "initial_theme", self.in_combo_theme_initial.currentText()))
        # btns
        self.bt_delete_column.clicked.connect(self.delete)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(37, 47, "height", self.header, b"maximumHeight"))
        self.bt_hide_show_filter.clicked.connect(self.header.hide_show)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(30, 44, "height", self.header, b"maximumHeight"))
        self.bt_register_new.clicked.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.bt_enter_register.clicked.connect(lambda: self.validate(
            self.register,
            line_edit_list=[self.in_username, self.in_devicename],
            combo_box_list=[self.in_combobox_devicetype, self.in_combobox_os],
            forbidden=list(self.registered_devices.keys()),
            checkfname=True))
        self.bt_create.clicked.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))
        self.bt_mod_new_path.clicked.connect(lambda: self.new(True))
        self.bt_burger.clicked.connect(lambda: self.toggle_hide_show_ani(
            0,
            66,
            "width",
            self.left_menu_frame,
            b"maximumWidth",
        ))
        # menu bar
        self.action_register.triggered.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.action_open.triggered.connect(self.get_open_file_path)
        self.action_save.triggered.connect(self.save)
        self.action_new.triggered.connect(lambda: self.new(True))
        self.action_print.triggered.connect(
            lambda: self.validate(self.print, data=False, checkfname=True))
        self.action_hide_menu_bar.triggered.connect(
            lambda: self.toggle_hide_show(self.menu_Bar))
        self.action_preferences.triggered.connect(self.p_preferences.show)
        # cancel
        self.bt_cancel_register.clicked.connect(lambda: self.cancel([
            self.in_username, self.in_devicename, self.in_combobox_os, self.
            in_comment
        ]))

        # list widget
        self.list_Widget.currentRowChanged.connect(
            lambda: self.stacked_widget_preferences.setCurrentIndex(
                self.list_Widget.currentIndex().row()))

        # slider
        self.text_size_slider.sliderMoved.connect(
            lambda: self.change_font_size(self.text_size_slider.value()))
        # self.text_size_slider.sliderMoved.connect(lambda: print(self.text_size_slider.value()))

    def change_theme(self, theme) -> None:
        """changes theme according to combobox selection

        Returns:
            None"""

        with open(f"./data/{theme}.css", "r") as file:
            stylesheed = " ".join(file.readlines())
            self.setStyleSheet(stylesheed)
            self.p_preferences.setStyleSheet(stylesheed)

        if self.in_combo_themes.currentText() == "dark_theme":

            self.left_btn_frame.setStyleSheet(
                u"background: #455364; border: 0px solid;")
            self.p_view_layout3.setSpacing(30)

        else:
            self.left_btn_frame.setStyleSheet(
                u"background: #ADADAD; border: 0px solid;")
            self.p_view_layout3.setSpacing(25)

        return self.in_combo_themes.currentText()

    def toggle_hide_show_ani(self, collapsed_val: int, expanded_val: int,
                             actual: str, to_animate, property: bytes):
        """interpolates over a defined range of vales and sets it to a given property of a given widget"""
        if getattr(to_animate, actual)() == expanded_val:
            destination = collapsed_val
        else:
            destination = expanded_val
        print(getattr(to_animate, actual)(), destination)
        self.ani = QPropertyAnimation(to_animate, property)
        self.ani.setDuration(300)
        self.ani.setStartValue(getattr(to_animate, actual)())
        self.ani.setEndValue(destination)
        self.ani.setEasingCurve(QEasingCurve.Linear)
        self.ani.start()

    def toggle_hide_show(self, widget: QWidget) -> None:
        """toggles visibiliy of a given widget
        Arg:
            widget: widget which is aimed to be hidden or shown
        Returs:
            None"""

        if widget.isVisible():
            widget.hide()
        else:
            widget.show()

    def reopen_last_file(self) -> None:
        """asks for reopening of the last opened file"""
        if self.last_open_file_path != "" or self.last_open_file_path is not None:
            reopen_dialog = QMessageBox.question(
                self.p_view, "reopen last file?",
                "Do you want to reopen the last edited file?",
                QMessageBox.Yes | QMessageBox.No)
            if reopen_dialog == QMessageBox.Yes:
                self.file_path = self.last_open_file_path
                self.load()

    def change_font_size(self, size: int) -> None:
        """changes all font sizes"""
        self.font.setPointSize(size)
        self.menu_Bar.setFont(self.font)
        self.header.setFont(self.font)
        self.table.setFont(self.font)
        self.p_preferences.setFont(self.font)

    def set_user_preferences(self) -> None:
        """Reads user_config file and sets its propertys"""
        with open(self.user_config_file, "r") as config_file:
            data = dict(json.load(config_file))

            self.last_open_file_path = data["last_open_file_path"]
            self.initial_theme = data['initial_theme']
            self.change_font_size(data['font_size'])
            self.text_size_slider.setValue(data['font_size'])
            self.in_combo_theme_initial.setCurrentText(self.initial_theme)
            self.in_combo_themes.setCurrentText(self.initial_theme)

            with open(f"./data/{self.initial_theme}.css") as file:
                style_sheed = " ".join(file.readlines())
                self.setStyleSheet(style_sheed)
                self.p_preferences.setStyleSheet(style_sheed)

            self.bt_burger.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_register_new.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_delete_column.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_hide_show_filter.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.left_menu_frame.setStyleSheet(u" border: 0px solid;")

            if self.initial_theme == "dark_theme":
                self.left_btn_frame.setStyleSheet(
                    u"background: #455364; border: 0px solid;")

            else:
                self.left_btn_frame.setStyleSheet(
                    u"background: #ADADAD; border: 0px solid;")

    def cancel(self, widgets: list) -> None:
        """click event for all cancel buttons

        shows fist page in stacked widget and clears all widgets in widgets

        Args:
               widgets: defines list containing widgets to clear, only widgets with method .clear() are possible

        Returns:
            None"""
        for widget in widgets:
            widget.clear()
        self.stacked_widget.setCurrentWidget(self.p_view)

    def update_combobox(self, box, data: list) -> None:
        """ clears combo box

        updates combobox so that old content not needed any more isnt displayed and adds 'choose here' dummy
        to ensure an index change will be made (updating next box depends on index change)
        Args:
            box: instance of pyqt5.QtWidgets.qComboBox
            data: data supposed to be inserted into combobox
        Returns:
            None"""

        box.clear()
        box.addItems(["choose here"] + data)

    def validate(self,
                 command,
                 file_path: str = None,
                 schema=None,
                 line_edit_list: list = None,
                 combo_box_list: list = None,
                 data=None,
                 forbidden: list = None,
                 checkfname: bool = None) -> None:
        """validates user input

        Args:
            command: function to be called after vailidation process if finished
            line_edit_list: contents pyqt5.QtWidgets.QlineEdit instances to be checked if empty or current text in forbidden or not in allowed
            combo_box_list: contents pyqt5.QtWidgets.qComboBox instances to be checked if nothing selected
            data: data to be passed into command function if needed
            forbidden: houses keys which are not allowed to be entered
            checkfname: check weather an file path exists or not

        Returns:
            None"""

        fails = 0
        if line_edit_list is not None:
            for x in line_edit_list:
                if x.text() == "":
                    x.setText("fill all fields")
                    fails += 1
                if forbidden is not None and x.text() in forbidden:
                    x.setText("in forbidden!!")
                    fails += 1
        if combo_box_list is not None:
            for combobox in combo_box_list:
                if combobox.currentText() == "":
                    self.statusbar.showMessage("all comboboxes must be filled")
                    fails += 1
        if checkfname is True and self.file_path is None:
            self.statusbar.showMessage(
                "no file path specified, visit Ctrl+o or menuebar/edit/open to fix"
            )
            fails += 1

        if file_path is not None:
            if forbidden is not None and file_path in forbidden:
                fails += 1
                self.statusbar.showMessage("select a file to continue")
            else:
                try:
                    validate_json.validate(file_path, schema)
                except ValidationError as e:
                    self.msg_box = QtWidgets.QMessageBox.critical(
                        self, "validation failed",
                        f"Invalid Json file, problem in: {e.messages}")
                    fails += 1
        if fails == 0:
            if data is None:
                command()
            else:
                command(data)
        else:
            message = f"problem\ncommand: {command.__name__}\nfails: {fails}"
            print(message)
            return message

    def register(self) -> None:
        """registers a new device and saves

        Returns:
            None"""
        logic.register(devname=self.in_devicename.text(),
                       devtype=[
                           device for device in valid_devices
                           if device.__name__ ==
                           self.in_combobox_devicetype.currentText()
                       ].pop(),
                       username=self.in_username.text(),
                       os=self.in_combobox_os.currentText(),
                       comment=self.in_comment.toPlainText(),
                       datetime=str(datetime.datetime.now()),
                       registered_devices=self.registered_devices)

        new_values = [
            self.in_devicename.text(),
            self.in_username.text(),
            self.in_combobox_os.currentText(),
            [
                device.__name__ for device in valid_devices if device.__name__
                == self.in_combobox_devicetype.currentText()
            ].pop(),
            self.in_comment.toPlainText(),
            str(datetime.datetime.now())
        ]
        row = [QStandardItem(str(item)) for item in new_values]
        self.model.appendRow(row)

        self.stacked_widget.setCurrentWidget(self.p_view)
        self.in_devicename.clear()
        self.in_username.clear()
        self.in_combobox_os.clear()
        self.in_comment.clear()
        self.save()

    def delete(self) -> None:
        """deletes all rows associated with min 1 slected cell
        Returns:
            None"""
        rows = sorted(set(index.row()
                          for index in self.table.selectedIndexes()),
                      reverse=True)
        qb = QMessageBox()
        answ = qb.question(self, 'delete rows',
                           f"Are you sure to delete {rows} rows?",
                           qb.Yes | qb.No)

        if answ == qb.Yes:
            for row in rows:
                self.registered_devices.pop(
                    str(self.model.index(row, 0).data()))
                self.model.removeRow(row)
            qb.information(self, 'notification', f"deleted {rows} row")
        else:
            qb.information(self, 'notification', "Nothing Changed")
        self.save()

    def get_open_file_path(self) -> None:
        """gets file-path and set it to self.file_path, extra step for json validation

        Returns:
            None"""

        self.file_path = \
            QFileDialog.getOpenFileName(self, "open file", f"{self.last_open_file_dir or 'c://'}",
                                        "json files (*json)")[0]
        self.validate(command=self.load,
                      file_path=self.file_path,
                      schema=validate_json.ItHilfeDataSchema,
                      forbidden=[""])

    def load(self) -> None:
        """opens json file and loads its content into registered devices

        Returns:
            None"""

        self.model.clear()
        self.registered_devices.clear()
        with open(self.file_path, "r") as file:
            data = dict(json.load(file))
            devices = data["devices"].values()
            self.last_open_file_dir = data["last_open_file_dir"]
            for value in devices:
                row = []
                for i, item in enumerate(value):
                    cell = QStandardItem(str(item))
                    row.append(cell)
                    if i == 0 or i == 3 or i == 5:
                        cell.setEditable(False)
                self.model.appendRow(row)

                new = [x for x in valid_devices
                       if x.__name__ == value[3]].pop(0)(value[0], value[1],
                                                         value[4], value[5])
                new.OS = value[2]
                self.registered_devices[value[0]] = new

        self.model.setHorizontalHeaderLabels(labels)
        self.statusbar.showMessage("")

        # auto complete
        for a in range(len(self.header.editors)):
            completer = QCompleter([
                self.model.data(self.model.index(x, a))
                for x in range(self.model.rowCount())
            ])
            completer.setCompletionMode(QCompleter.InlineCompletion)
            self.header.editors[a].setCompleter(completer)

    def save(self) -> None:
        """saves content fo self.registered_devices into specified json file

        Returns:
            None"""
        if not self.file_path:
            self.statusbar.showMessage(
                "no file path set all changes get lost if closed")
        else:
            with open(
                    self.file_path,
                    'w',
            ) as file:
                devices = {
                    k: [
                        v.name, v.user, v.OS, v.__class__.__name__, v.comment,
                        v.datetime
                    ]
                    for (k, v) in enumerate(self.registered_devices.values())
                }
                last_open_file_dir = "/".join(self.file_path.split("/")[:-1])
                resulting_dict = {
                    "devices": devices,
                    "last_open_file_dir": last_open_file_dir
                }
                json.dump(resulting_dict, file)
                self.statusbar.showMessage("saved file")

        with open(self.user_config_file, "w") as user_preferences_file:
            json.dump(
                {
                    "last_open_file_path": self.last_open_file_path,
                    "initial_theme": self.initial_theme,
                    "font_size": self.text_size_slider.value()
                }, user_preferences_file)

    def new(self, stage: bool, test: bool = False) -> None:
        """creates new csv file to save into

        stage is True: set filepath
        stage is False: set new name, save
        Args:
            stage: determines a which stage to execute this function

        Returns:
            None"""

        if stage is True:
            if not test:
                self.dir = QFileDialog.getExistingDirectory(
                    self, "select a folder", "c://")
            self.stacked_widget.setCurrentWidget(self.p_create)
            self.in_new_filepath.setText(self.dir)
            self.registered_devices.clear()

        else:
            self.file_path = self.dir + f"/{self.in_new_filename.text()}.json"
            self.save()
            self.stacked_widget.setCurrentWidget(self.p_view)

    def print(self, test: bool) -> None:
        """setup and preview pViewTable for paper printing

        Returns:
            None"""

        with open(self.file_path) as f:
            self.data = json.dumps(dict(json.load(f)),
                                   sort_keys=True,
                                   indent=6,
                                   separators=(".", "="))
        self.document = QtWidgets.QTextEdit()
        self.document.setText(self.data)

        if not test:
            printer = QPrinter()
            previewDialog = QPrintPreviewDialog(printer, self)
            previewDialog.paintRequested.connect(
                lambda: self.document.print_(printer))
            previewDialog.exec_()
Exemple #27
0
class MainWindow(QMainWindow):  # Main window
    def __init__(self):
        super().__init__()
        self.setWindowTitle = 'DD烤肉机'
        self.resize(1870, 820)
        self.mainWidget = QWidget()
        self.mainLayout = QGridLayout()  # Grid layout
        self.mainLayout.setSpacing(10)
        self.mainWidget.setLayout(self.mainLayout)
        self.duration = 60000
        self.bitrate = 2000
        self.fps = 60

        self.initProcess = InitProcess()
        self.previewSubtitle = PreviewSubtitle()
        self.dnldWindow = YoutubeDnld()
        self.exportWindow = exportSubtitle()
        self.videoDecoder = VideoDecoder()
        self.exportWindow.exportArgs.connect(self.exportSubtitle)
        self.stack = QStackedWidget()
        self.stack.setFixedWidth(1300)
        self.mainLayout.addWidget(self.stack, 0, 0, 10, 8)
        buttonWidget = QWidget()
        buttonLayout = QHBoxLayout()
        buttonWidget.setLayout(buttonLayout)
        self.playButton = QPushButton('从本地打开')
        self.playButton.clicked.connect(self.open)
        self.playButton.setFixedWidth(400)
        self.playButton.setFixedHeight(75)
        self.dnldButton = QPushButton('Youtube下载器')
        self.dnldButton.clicked.connect(self.popDnld)
        self.dnldButton.setFixedWidth(400)
        self.dnldButton.setFixedHeight(75)
        buttonLayout.addWidget(self.playButton)
        buttonLayout.addWidget(self.dnldButton)
        self.stack.addWidget(buttonWidget)

        self.videoPath = ''
        self.videoWidth = 1920
        self.videoHeight = 1080
        self.globalInterval = 200
        self.setPlayer()
        self.setSubtitle()
        self.setToolBar()
        self.setCentralWidget(self.mainWidget)
        self.playStatus = False
        self.volumeStatus = True
        self.volumeValue = 100
        self.subSelectedTxt = ''
        self.subReplayTime = 1
        self.clipBoard = []
        self.grabKeyboard()
        self.show()

    def setPlayer(self):
        self.playerWidget = QGraphicsVideoItem()
        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        self.view.resize(1280, 730)
        self.scene.addItem(self.playerWidget)
        self.stack.addWidget(self.view)
        self.player = QMediaPlayer(self, QMediaPlayer.VideoSurface)
        self.player.setVideoOutput(self.playerWidget)
        self.view.installEventFilter(self)
        self.view.show()
        self.srtTextItemDict = {0: QGraphicsTextItem(), 1: QGraphicsTextItem(), 2: QGraphicsTextItem(), 3: QGraphicsTextItem(), 4: QGraphicsTextItem()}
        for _, srtTextItem in self.srtTextItemDict.items():
            self.scene.addItem(srtTextItem)

    def setSubtitle(self):
        self.subtitleDict = {x: {-1: [100, '']} for x in range(5)}
        self.subTimer = QTimer()
        self.subTimer.setInterval(100)
        self.subtitle = QTableWidget()
        self.subtitle.setAutoScroll(False)
        self.subtitle.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.mainLayout.addWidget(self.subtitle, 0, 8, 10, 12)
        self.subtitle.setColumnCount(5)
        self.subtitle.selectRow(0)
        self.subtitle.setHorizontalHeaderLabels(['%s' % (i + 1) for i in range(5)])
        self.subtitle.setVerticalHeaderLabels([cnt2Time2(i, self.globalInterval) for i in range(self.subtitle.rowCount())])
        for index in range(5):
            self.subtitle.setColumnWidth(index, 130)
        self.subtitle.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.subtitle.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.subtitle.horizontalHeader().sectionClicked.connect(self.addSubtitle)
        self.subtitle.doubleClicked.connect(self.releaseKeyboard)
        self.subtitle.cellChanged.connect(self.subEdit)
        self.subtitle.verticalHeader().sectionClicked.connect(self.subHeaderClick)
        self.subtitle.setContextMenuPolicy(Qt.CustomContextMenu)
        self.subtitle.customContextMenuRequested.connect(self.popTableMenu)
        self.initSubtitle()

    def initSubtitle(self):
        self.initProcess.show()
        self.subtitle.cellChanged.disconnect(self.subEdit)
        for x in range(self.subtitle.columnCount()):
            for y in range(self.subtitle.rowCount()):
                self.subtitle.setSpan(y, x, 1, 1)
        self.subtitle.setRowCount(self.duration // self.globalInterval + 1)
        for x in range(self.subtitle.columnCount()):
            for y in range(self.subtitle.rowCount()):
                self.subtitle.setItem(y, x, QTableWidgetItem(''))
                self.subtitle.item(y, x).setBackground(QBrush(QColor('#232629')))
        self.subtitle.setVerticalHeaderLabels([cnt2Time2(i, self.globalInterval) for i in range(self.subtitle.rowCount())])
        self.subtitle.cellChanged.connect(self.subEdit)
        self.initProcess.hide()

    def addSubtitle(self, index):
        subtitlePath = QFileDialog.getOpenFileName(self, "请选择字幕", None, "字幕文件 (*.srt *.vtt *.ass *.ssa)")[0]
        if subtitlePath:
            self.initProcess.show()
            self.subtitle.cellChanged.disconnect(self.subEdit)
            if subtitlePath.endswith('.ass') or subtitlePath.endswith('.ssa'):
                p = subprocess.Popen(['utils/ffmpeg.exe', '-y', '-i', subtitlePath, 'temp_sub.srt'])
                p.wait()
                subtitlePath = 'temp_sub.srt'
            subData = {}
            with open(subtitlePath, 'r', encoding='utf-8') as f:
                f = f.readlines()
            subText = ''
            YoutubeAutoSub = False
            for l in f:
                if '<c>' in l:
                    YoutubeAutoSub = True
                    break
            for cnt, l in enumerate(f):
                if '<c>' in l:
                    lineData = l.split('c>')
                    if len(lineData) > 3:
                        subText, start, _ = lineData[0].split('<')
                        start = calSubTime(start[:-1]) // self.globalInterval * self.globalInterval
                        if start not in self.subtitleDict[index]:
                            end = calSubTime(lineData[-3][1:-2]) // self.globalInterval * self.globalInterval
                            for i in range(len(lineData) // 2):
                                subText += lineData[i * 2 + 1][:-2]
                            subData[start] = [end - start, subText]
                    else:
                        subText, start, _ = lineData[0].split('<')
                        start = calSubTime(start[:-1]) // self.globalInterval * self.globalInterval
                        if start not in self.subtitleDict[index]:
                            subText += lineData[1][:-2]
                            subData[start] = [self.globalInterval, subText]
                elif '-->' in l and f[cnt + 2].strip() and '<c>' not in f[cnt + 2]:
                    subText = f[cnt + 2][:-1]
                    start = calSubTime(l[:12]) // self.globalInterval * self.globalInterval
                    if start not in self.subtitleDict[index]:
                        end = calSubTime(l[17:29]) // self.globalInterval * self.globalInterval
                        subData[start] = [end - start, subText]
                if '-->' in l and f[cnt + 1].strip() and not YoutubeAutoSub:
                    start = calSubTime(l[:12]) // self.globalInterval * self.globalInterval
                    if start not in self.subtitleDict[index]:
                        end = calSubTime(l[17:29]) // self.globalInterval * self.globalInterval
                        delta = end - start
                        if delta > 10:
                            if '<b>' in f[cnt + 1]:
                                subData[start] = [delta, f[cnt + 1].split('<b>')[1].split('<')[0]]
                            else:
                                subData[start] = [delta, f[cnt + 1][:-1]]
            self.subtitleDict[index].update(subData)
            maxRow = 0
            for _, v in self.subtitleDict.items():
                startMax = max(v.keys())
                rowCount = (startMax + v[startMax][0]) // self.globalInterval
                if rowCount > maxRow:
                    maxRow = rowCount
            if maxRow < self.duration // self.globalInterval + 1:
                maxRow = self.duration // self.globalInterval
            else:
                self.duration = maxRow * self.globalInterval
            self.subtitle.setRowCount(maxRow)
            self.subtitle.setVerticalHeaderLabels([cnt2Time2(i, self.globalInterval) for i in range(self.subtitle.rowCount())])
            for start, rowData in subData.items():
                startRow = start // self.globalInterval
                endRow = startRow + rowData[0] // self.globalInterval
                for row in range(startRow, endRow):
                    self.subtitle.setItem(row, index, QTableWidgetItem(rowData[1]))
                    self.subtitle.item(row, index).setBackground(QBrush(QColor('#35545d')))
                self.subtitle.setSpan(startRow, index, endRow - startRow, 1)
            self.refreshComboBox()
            self.subtitle.cellChanged.connect(self.subEdit)
            self.initProcess.hide()

    def subTimeOut(self):
        fontColor = self.previewSubtitle.fontColor
        fontSize = (self.previewSubtitle.fontSize + 5) / 2.5
        fontBold = self.previewSubtitle.bold
        fontItalic = self.previewSubtitle.italic
        fontShadowOffset = self.previewSubtitle.shadowOffset
        for _, srtTextItem in self.srtTextItemDict.items():
            srtTextItem.setDefaultTextColor(fontColor)
            font = QFont()
            font.setFamily("微软雅黑")
            font.setPointSize(fontSize)
            font.setBold(fontBold)
            font.setItalic(fontItalic)
            srtTextItem.setFont(font)
            srtTextShadow = QGraphicsDropShadowEffect()
            srtTextShadow.setOffset(fontShadowOffset)
            srtTextItem.setGraphicsEffect(srtTextShadow)
        try:
            selected = self.subtitle.selectionModel().selection().indexes()
            for x, i in enumerate(selected):
                if self.subtitle.item(i.row(), x):
                    txt = self.subtitle.item(i.row(), x).text()
                    if txt:
                        self.srtTextItemDict[x].setPlainText('#%s:' % (x + 1) + txt)
                        txtSize = self.srtTextItemDict[x].boundingRect().size()
                        posY = self.playerWidget.size().height() - txtSize.height() * (x + 1)
                        posX = (self.playerWidget.size().width() - txtSize.width()) / 2
                        self.srtTextItemDict[x].setPos(posX, posY)
                    else:
                        self.srtTextItemDict[x].setPlainText('')
                else:
                    self.srtTextItemDict[x].setPlainText('')
        except:
            pass

    def subHeaderClick(self, index):
        if self.player.duration():
            position = index * self.globalInterval
            self.player.setPosition(position)
            self.videoSlider.setValue(position * 1000 // self.player.duration())
            self.setTimeLabel()

    def subEdit(self, row, index):
        repeat = self.subtitle.rowSpan(row, index)
        self.setSubtitleDict(row, index, repeat, self.subtitle.item(row, index).text())
        self.subtitle.cellChanged.disconnect(self.subEdit)
        for cnt in range(repeat):
            if self.subtitle.item(row + cnt, index).text():
                self.subtitle.item(row, index).setBackground(QBrush(QColor('#35545d')))
            else:
                self.subtitle.item(row, index).setBackground(QBrush(QColor('#232629')))
        self.subtitle.cellChanged.connect(self.subEdit)

    def setSubtitleDict(self, row, index, num, text):
        self.subtitleDict[index][row * self.globalInterval] = [num * self.globalInterval, text]

    def popTableMenu(self, pos):
        self.subtitle.cellChanged.disconnect(self.subEdit)
        pos = QPoint(pos.x() + 55, pos.y() + 30)
        menu = QMenu()
        copy = menu.addAction('复制')
        paste = menu.addAction('粘贴')
        setSpan = menu.addAction('合并')
        clrSpan = menu.addAction('拆分')
        addSub = menu.addAction('导入字幕')
        cutSub = menu.addAction('裁剪字幕')
        action = menu.exec_(self.subtitle.mapToGlobal(pos))
        selected = self.subtitle.selectionModel().selection().indexes()
        yList = [selected[0].row(), selected[-1].row()]
        xSet = set()
        for i in range(len(selected)):
            xSet.add(selected[i].column())
        if action == copy:
            for x in xSet:
                self.clipBoard = []
                for y in range(yList[0], yList[1] + 1):
                    if self.subtitle.item(y, x):
                        self.clipBoard.append(self.subtitle.item(y, x).text())
                    else:
                        self.clipBoard.append('')
                break
        elif action == paste:
            self.subtitle.cellChanged.connect(self.subEdit)
            for x in xSet:
                for cnt, text in enumerate(self.clipBoard):
                    self.subtitle.setItem(yList[0] + cnt, x, QTableWidgetItem(text))
                    self.subtitleDict[x][(yList[0] + cnt) * self.globalInterval] = [self.globalInterval, text]
            self.subtitle.cellChanged.disconnect(self.subEdit)
        elif action == setSpan:
            for x in xSet:
                if not self.subtitle.item(yList[0], x):
                    firstItem = ''
                else:
                    firstItem = self.subtitle.item(yList[0], x).text()
                for y in range(yList[0], yList[1] + 1):
                    self.subtitle.setSpan(y, x, 1, 1)
                    self.subtitle.setItem(y, x, QTableWidgetItem(firstItem))
                    self.subtitle.item(y, x).setBackground(QBrush(QColor('#35545d')))
                    if y * self.globalInterval in self.subtitleDict[x]:
                        del self.subtitleDict[x][y * self.globalInterval]
            for x in xSet:
                self.subtitle.setSpan(yList[0], x, yList[1] - yList[0] + 1, 1)
            self.setSubtitleDict(yList[0], x, yList[1] - yList[0] + 1, firstItem)
        elif action == clrSpan:
            for x in xSet:
                if not self.subtitle.item(yList[0], x):
                    firstItem = ''
                else:
                    firstItem = self.subtitle.item(yList[0], x).text()
                for cnt, y in enumerate(range(yList[0], yList[1] + 1)):
                    self.subtitle.setSpan(y, x, 1, 1)
                    if not cnt:
                        self.subtitle.setItem(yList[0], x, QTableWidgetItem(firstItem))
                        if firstItem:
                            self.subtitle.item(y, x).setBackground(QBrush(QColor('#35545d')))
                        else:
                            self.subtitle.item(y, x).setBackground(QBrush(QColor('#232629')))
                    else:
                        self.subtitle.setItem(y, x, QTableWidgetItem(''))
                        self.subtitle.item(y, x).setBackground(QBrush(QColor('#232629')))
                    self.setSubtitleDict(yList[0], x, yList[1] - yList[0] + 1, firstItem)
                break
        elif action == addSub:
            self.subtitle.cellChanged.connect(self.subEdit)
            for x in xSet:
                self.addSubtitle(x)
            self.subtitle.cellChanged.disconnect(self.subEdit)
        elif action == cutSub:
            for x in xSet:
                start = yList[0] * self.globalInterval
                end = yList[1] * self.globalInterval
                self.exportSubWindow(start, end, x + 1)
        self.subtitle.cellChanged.connect(self.subEdit)

    def setToolBar(self):
        '''
        menu bar, file menu, play menu, tool bar.
        '''
        toolBar = QToolBar()
        self.setContextMenuPolicy(Qt.NoContextMenu)
        self.addToolBar(toolBar)
        fileMenu = self.menuBar().addMenu('&文件')
        openAction = QAction(QIcon.fromTheme('document-open'), '&打开...', self, shortcut=QKeySequence.Open, triggered=self.open)
        fileMenu.addAction(openAction)
        downloadAction = QAction(QIcon.fromTheme('document-open'), '&Youtube下载器', self, triggered=self.popDnld)
        fileMenu.addAction(downloadAction)
        exitAction = QAction(QIcon.fromTheme('application-exit'), '&退出', self, shortcut='Ctrl+Q', triggered=self.close)
        fileMenu.addAction(exitAction)

        playMenu = self.menuBar().addMenu('&功能')
        self.playIcon = self.style().standardIcon(QStyle.SP_MediaPlay)
        self.pauseIcon = self.style().standardIcon(QStyle.SP_MediaPause)
        self.playAction = toolBar.addAction(self.playIcon, '播放')
        self.playAction.triggered.connect(self.mediaPlay)
        self.volumeIcon = self.style().standardIcon(QStyle.SP_MediaVolume)
        self.volumeMuteIcon = self.style().standardIcon(QStyle.SP_MediaVolumeMuted)
        self.volumeAction = toolBar.addAction(self.volumeIcon, '静音')
        self.volumeAction.triggered.connect(self.volumeMute)
        previewAction = QAction(QIcon.fromTheme('document-open'), '&设置预览字幕', self, triggered=self.popPreview)
        playMenu.addAction(previewAction)

        decodeMenu = self.menuBar().addMenu('&输出')
        decodeAction = QAction(QIcon.fromTheme('document-open'), '&输出字幕及视频', self, triggered=self.decode)
        decodeMenu.addAction(decodeAction)

        self.volSlider = Slider()
        self.volSlider.setOrientation(Qt.Horizontal)
        self.volSlider.setMinimum(0)
        self.volSlider.setMaximum(100)
        self.volSlider.setFixedWidth(120)
        self.volSlider.setValue(self.player.volume())
        self.volSlider.setToolTip(str(self.volSlider.value()))
        self.volSlider.pointClicked.connect(self.setVolume)
        toolBar.addWidget(self.volSlider)

        self.videoPositionEdit = LineEdit('00:00')
        self.videoPositionEdit.setAlignment(Qt.AlignRight)
        self.videoPositionEdit.setFixedWidth(75)
        self.videoPositionEdit.setFont(QFont('Timers', 14))
        self.videoPositionEdit.clicked.connect(self.mediaPauseOnly)
        self.videoPositionEdit.editingFinished.connect(self.mediaPlayOnly)
        self.videoPositionLabel = QLabel(' / 00:00  ')
        self.videoPositionLabel.setFont(QFont('Timers', 14))
        toolBar.addWidget(QLabel('    '))
        toolBar.addWidget(self.videoPositionEdit)
        toolBar.addWidget(self.videoPositionLabel)

        self.timer = QTimer()
        self.timer.setInterval(100)
        self.videoSlider = Slider()
        self.videoSlider.setEnabled(False)
        self.videoSlider.setOrientation(Qt.Horizontal)
        self.videoSlider.setMinimum(0)
        self.videoSlider.setMaximum(1000)
        self.videoSlider.setFixedWidth(1000)
        self.videoSlider.sliderMoved.connect(self.timeStop)
        self.videoSlider.sliderReleased.connect(self.timeStart)
        self.videoSlider.pointClicked.connect(self.videoSliderClick)
        toolBar.addWidget(self.videoSlider)

        toolBar.addWidget(QLabel('   '))
        self.globalIntervalComBox = QComboBox()
        self.globalIntervalComBox.addItems(['间隔 100ms', '间隔 200ms', '间隔 500ms', '间隔 1s'])
        self.globalIntervalComBox.setCurrentIndex(1)
        self.globalIntervalComBox.currentIndexChanged.connect(self.setGlobalInterval)
        toolBar.addWidget(self.globalIntervalComBox)
        toolBar.addWidget(QLabel('  '))
        self.subEditComBox = QComboBox()
        self.refreshComboBox()
        toolBar.addWidget(self.subEditComBox)
        toolBar.addWidget(QLabel('  '))
        moveForward = QPushButton('- 1')
        moveForward.setFixedWidth(50)
        toolBar.addWidget(moveForward)
        toolBar.addWidget(QLabel('  '))
        moveAfterward = QPushButton('+ 1')
        moveAfterward.setFixedWidth(50)
        toolBar.addWidget(moveAfterward)
        toolBar.addWidget(QLabel('  '))
        clearSub = QPushButton('清空')
        clearSub.setFixedWidth(50)
        toolBar.addWidget(clearSub)
        toolBar.addWidget(QLabel('  '))
        outputSub = QPushButton('裁剪')
        outputSub.setFixedWidth(50)
        toolBar.addWidget(outputSub)
        moveForward.clicked.connect(self.moveForward)
        moveAfterward.clicked.connect(self.moveAfterward)
        clearSub.clicked.connect(self.clearSub)
        outputSub.clicked.connect(self.exportSubWindow)

    def setGlobalInterval(self, index):
        if not self.playStatus:
            self.mediaPlay()
        self.globalInterval = {0: 100, 1: 200, 2: 500, 3: 1000}[index]
        self.initSubtitle()
        self.initProcess.show()
        self.subtitle.cellChanged.disconnect(self.subEdit)
        for index, subData in self.subtitleDict.items():
            for start, rowData in subData.items():
                startRow = start // self.globalInterval
                deltaRow = rowData[0] // self.globalInterval
                if deltaRow:
                    endRow = startRow + deltaRow
                    for row in range(startRow, endRow):
                        self.subtitle.setItem(row, index, QTableWidgetItem(rowData[1]))
                        if row >= 0:
                            self.subtitle.item(row, index).setBackground(QBrush(QColor('#35545d')))
                    self.subtitle.setSpan(startRow, index, endRow - startRow, 1)
        self.subtitle.cellChanged.connect(self.subEdit)
        self.initProcess.hide()

    def moveForward(self):
        self.initProcess.show()
        self.subtitle.cellChanged.disconnect(self.subEdit)
        index = self.subEditComBox.currentIndex()
        for y in range(self.subtitle.rowCount()):
            self.subtitle.setSpan(y, index, 1, 1)
            self.subtitle.setItem(y, index, QTableWidgetItem(''))
            self.subtitle.item(y, index).setBackground(QBrush(QColor('#232629')))
        tmpDict = self.subtitleDict[index]
        self.subtitleDict[index] = {}
        for start, rowData in tmpDict.items():
            self.subtitleDict[index][start - self.globalInterval] = rowData
        for start, rowData in self.subtitleDict[index].items():
            startRow = start // self.globalInterval
            endRow = startRow + rowData[0] // self.globalInterval
            for row in range(startRow, endRow):
                self.subtitle.setItem(row, index, QTableWidgetItem(rowData[1]))
                self.subtitle.item(row, index).setBackground(QBrush(QColor('#35545d')))
            self.subtitle.setSpan(startRow, index, endRow - startRow, 1)
        self.subtitle.cellChanged.connect(self.subEdit)
        self.initProcess.hide()

    def moveAfterward(self):
        self.initProcess.show()
        self.subtitle.cellChanged.disconnect(self.subEdit)
        index = self.subEditComBox.currentIndex()
        for y in range(self.subtitle.rowCount()):
            self.subtitle.setSpan(y, index, 1, 1)
            self.subtitle.setItem(y, index, QTableWidgetItem(''))
            self.subtitle.item(y, index).setBackground(QBrush(QColor('#232629')))
        tmpDict = self.subtitleDict[index]
        self.subtitleDict[index] = {}
        for start, rowData in tmpDict.items():
            self.subtitleDict[index][start + self.globalInterval] = rowData
        for start, rowData in self.subtitleDict[index].items():
            startRow = start // self.globalInterval
            endRow = startRow + rowData[0] // self.globalInterval
            for row in range(startRow, endRow):
                self.subtitle.setItem(row, index, QTableWidgetItem(rowData[1]))
                self.subtitle.item(row, index).setBackground(QBrush(QColor('#35545d')))
            self.subtitle.setSpan(startRow, index, endRow - startRow, 1)
        self.subtitle.cellChanged.connect(self.subEdit)
        self.initProcess.hide()

    def clearSub(self):
        index = self.subEditComBox.currentIndex()
        reply = QMessageBox.information(self, '清空字幕', '清空第 %s 列字幕条?' % (index + 1), QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.initProcess.show()
            self.subtitle.cellChanged.disconnect(self.subEdit)
            self.subtitleDict[index] = {0: [self.globalInterval, '']}
            for i in range(self.subtitle.rowCount()):
                self.subtitle.setSpan(i, index, 1, 1)
                self.subtitle.setItem(i, index, QTableWidgetItem(''))
                self.subtitle.item(i, index).setBackground(QBrush(QColor('#232629')))
                self.subtitle.setHorizontalHeaderItem(index, QTableWidgetItem('%s' % (index + 1)))
            self.subtitle.cellChanged.connect(self.subEdit)
            self.initProcess.hide()

    def exportSubWindow(self, start=0, end=0, index=None):
        self.releaseKeyboard()
        self.exportWindow.hide()
        self.exportWindow.show()
        start = '00:00.0' if not start else self.splitTime(start)
        end = self.splitTime(self.duration) if not end else self.splitTime(end)
        if not index:
            index = self.subEditComBox.currentIndex() + 1
        self.exportWindow.setDefault(start, end, index)

    def exportSubtitle(self, exportArgs):
        start = calSubTime2(exportArgs[0])
        end = calSubTime2(exportArgs[1])
        subStart = calSubTime2(exportArgs[2])
        index = exportArgs[3] - 1
        subData = self.subtitleDict[index]
        rowList = sorted(subData.keys())
        exportRange = []
        for t in rowList:
            if t >= start and t <= end:
                exportRange.append(t)
        subNumber = 1
        with open(exportArgs[-1], 'w', encoding='utf-8') as exportFile:
            for t in exportRange:
                text = subData[t][1]
                if text:
                    start = ms2Time(t + subStart)
                    end = ms2Time(t + subStart + subData[t][0])
                    exportFile.write('%s\n%s --> %s\n%s\n\n' % (subNumber, start, end, text))
                    subNumber += 1
        QMessageBox.information(self, '导出字幕', '导出完成', QMessageBox.Yes)
        self.exportWindow.hide()

    def refreshComboBox(self):
        self.subEditComBox.clear()
        for i in range(self.subtitle.columnCount()):
            self.subEditComBox.addItem('字幕 ' + str(i + 1))

    def open(self):
        self.videoPath = QFileDialog.getOpenFileName(self, "请选择视频文件", None, "MP4格式 (*.mp4);;所有文件(*.*)")[0]
        if self.videoPath:
            cmd = ['utils/ffmpeg.exe', '-i', self.videoPath]
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            p.wait()
            for l in p.stdout.readlines():
                l = l.decode('utf8')
                if 'Duration' in l:
                    self.duration = calSubTime(l.split(' ')[3][:-1])
                if 'Stream' in l and 'DAR' in l:
                    self.videoWidth, self.videoHeight = map(int, l.split(' [')[0].split(' ')[-1].split('x'))
                    args = l.split(',')
                    for cnt, arg in enumerate(args):
                        if 'kb' in arg:
                            self.bitrate = int(arg.split('kb')[0])
                            self.fps = int(args[cnt + 1].split('fps')[0])
                            break
                    break
            self.initProcess.show()
            self.subtitle.cellChanged.disconnect(self.subEdit)
            self.subtitle.setRowCount(self.duration // self.globalInterval + 1)
            self.subtitle.setVerticalHeaderLabels([cnt2Time2(i, self.globalInterval) for i in range(self.subtitle.rowCount())])
            self.subtitle.cellChanged.connect(self.subEdit)
            self.initProcess.hide()
            url = QUrl.fromLocalFile(self.videoPath)
            self.stack.setCurrentIndex(1)
            self.playerWidget.setSize(QSizeF(1280, 720))
            self.player.setMedia(url)
            self.playStatus = True
            self.videoSlider.setEnabled(True)
            self.mediaPlay()
            self.timer.start()
            self.timer.timeout.connect(self.timeOut)
            self.subTimer.start()
            self.subTimer.timeout.connect(self.subTimeOut)

    def popDnld(self):
        self.releaseKeyboard()
        self.dnldWindow.hide()
        self.dnldWindow.show()

    def popPreview(self):
        self.releaseKeyboard()
        self.previewSubtitle.hide()
        self.previewSubtitle.show()

    def decode(self):
        self.releaseKeyboard()
        self.videoDecoder.setDefault(self.videoPath, self.videoWidth, self.videoHeight, self.duration, self.bitrate, self.fps, self.subtitleDict)
        self.videoDecoder.hide()
        self.videoDecoder.show()

    def mediaPlay(self):
        if self.playStatus:
            self.player.play()
            self.grabKeyboard()
            self.timeStart()
            self.playStatus = False
            self.playAction.setIcon(self.pauseIcon)
            self.playAction.setText('暂停')
        else:
            self.player.pause()
            self.timeStop()
            self.playStatus = True
            self.playAction.setIcon(self.playIcon)
            self.playAction.setText('播放')

    def mediaPlayOnly(self):
        self.grabKeyboard()
        try:
            timeText = self.videoPositionEdit.text().split(':')
            m, s = timeText[:2]
            if not m:
                m = '00'
            if not s:
                s = '00'
            if len(m) > 3:
                m = m[:3]
            if len(s) > 2:
                s = s[:2]
            if m.isdigit():
                m = int(m)
            if s.isdigit():
                s = int(s)
            if s > 60:
                s = 60
            total_m = self.player.duration() // 60000
            if m > total_m:
                m = total_m
            self.player.setPosition(m * 60000 + s * 1000)
            self.videoSlider.setValue(self.player.position() * 1000 / self.player.duration())
        except:
            pass
        self.videoPositionEdit.setReadOnly(True)
        self.timeStart()

    def mediaPauseOnly(self):
        self.releaseKeyboard()
        self.videoPositionEdit.setReadOnly(False)
        self.player.pause()
        self.timeStop()
        self.playStatus = True
        self.playAction.setIcon(self.playIcon)
        self.playAction.setText('播放')

    def splitTime(self, playTime):
        playTime = playTime // 1000
        m = str(playTime // 60)
        s = playTime % 60
        s = ('0%s' % s)[-2:]
        if len(m) > 2:
            t = '%3s:%2s' % (m, s)
        else:
            t = '%2s:%2s' % (m, s)
        return t

    def timeOut(self):
        row = self.player.position() // self.globalInterval
        self.subtitle.selectRow(row)
        self.subtitle.verticalScrollBar().setValue(row - 10)
        if self.dnldWindow.isHidden() or self.exportWindow.isHidden() or self.videoDecoder.isHidden():
            self.grabKeyboard()
        try:
            self.videoSlider.setValue(self.player.position() * 1000 / self.player.duration())
            self.setTimeLabel()
        except:
            pass

    def timeStop(self):
        self.timer.stop()

    def timeStart(self):
        self.timer.start()

    def videoSliderClick(self, p):
        self.videoSlider.setValue(p.x())
        self.player.setPosition(p.x() * self.player.duration() // 1000)
        self.setTimeLabel()

    def setVolume(self, p):
        self.volumeValue = p.x()
        if self.volumeValue > 100:
            self.volumeValue = 100
        if self.volumeValue < 0:
            self.volumeValue = 0
        self.volSlider.setValue(self.volumeValue)
        self.player.setVolume(self.volumeValue)
        self.volSlider.setToolTip(str(self.volSlider.value()))
        if self.volumeValue:
            self.volumeStatus = True
            self.volumeAction.setIcon(self.volumeIcon)
        else:
            self.volumeStatus = False
            self.volumeAction.setIcon(self.volumeMuteIcon)

    def volumeMute(self):
        if self.volumeStatus:
            self.volumeStatus = False
            self.old_volumeValue = self.player.volume()
            self.player.setVolume(0)
            self.volSlider.setValue(0)
            self.volumeAction.setIcon(self.volumeMuteIcon)
        else:
            self.volumeStatus = True
            self.player.setVolume(self.old_volumeValue)
            self.volSlider.setValue(self.old_volumeValue)
            self.volumeAction.setIcon(self.volumeIcon)

    def setTimeLabel(self):
        now = self.player.position()
        total = self.player.duration()
        now = self.splitTime(now)
        total = self.splitTime(total)
        self.videoPositionEdit.setText(now)
        self.videoPositionLabel.setText(' / %s  ' % total)

    def eventFilter(self, obj, event):
        if obj == self.view:
            if event.type() == QEvent.MouseButtonPress:
                self.mediaPlay()
        return QMainWindow.eventFilter(self, obj, event)

    def keyPressEvent(self, QKeyEvent):
        key = QKeyEvent.key()
        if key == Qt.Key_Left:
            if self.videoSlider.isEnabled():
                self.player.setPosition(self.player.position() - 5000)
                self.videoSlider.setValue(self.player.position() * 1000 / self.player.duration())
                self.setTimeLabel()
        elif key == Qt.Key_Right:
            if self.videoSlider.isEnabled():
                self.player.setPosition(self.player.position() + 5000)
                self.videoSlider.setValue(self.player.position() * 1000 / self.player.duration())
                self.setTimeLabel()
        elif key == Qt.Key_Up:
            self.volumeValue += 10
            if self.volumeValue > 100:
                self.volumeValue = 100
            self.volSlider.setValue(self.volumeValue)
            self.player.setVolume(self.volumeValue)
        elif key == Qt.Key_Down:
            self.volumeValue -= 10
            if self.volumeValue < 0:
                self.volumeValue = 0
            self.volSlider.setValue(self.volumeValue)
            self.player.setVolume(self.volumeValue)
        elif key == Qt.Key_Space:
            self.mediaPlay()
Exemple #28
0
class MainWindow(QMainWindow):
	def __init__(self):
		self.status_bar = None
		self.progress_bar = None

		super().__init__()
		self.create()

	def create(self):
		self.create_window()
		self.create_status_bar()
		self.create_menu()
		self.create_central_widgets()

	def create_window(self):
		self.setWindowTitle("Bluebonnet Computing")
		self.resize(1200,700)

	def create_status_bar(self):
		self.status_bar = self.statusBar()
		self.add_permanent_progress_bar()
		self.display_status("Welcome")

	def add_permanent_progress_bar(self):
		self.progress_bar = QProgressBar()
		progress_bar_label = QLabel("Status: ")

		#widgets are added proceduarally from left to right
		self.status_bar.addPermanentWidget(progress_bar_label)
		self.status_bar.addPermanentWidget(self.progress_bar)

	def display_status(self, message):
		timeout = 10000
		self.status_bar.showMessage(message, timeout=timeout)

	def create_menu(self):
		"""
		Create the main window menu bar on initialization
		"""
		#create menu bar
		self.menu = self.menuBar()

		### FILE ###

		#file menu
		self.file_menu = self.menu.addMenu("File")

		#home option in file
		home_action = QAction("Home", self)
		home_action.setShortcut("Ctrl+H")
		home_action.triggered.connect(self.menu_file_home)
		self.file_menu.addAction(home_action)

		#exit option in file
		exit_action = QAction("Exit", self)
		exit_action.setShortcut("Ctrl+Q")
		exit_action.triggered.connect(self.menu_file_exit)
		self.file_menu.addAction(exit_action)

		### TASK ###

		#task menu
		self.task_menu = self.menu.addMenu("Tasks")

		#reconcile statements option in tasks
		reconcile_action = QAction("Reconcile Statements", self)
		reconcile_action.setShortcut("Alt+R")
		reconcile_action.triggered.connect(self.menu_task_reconcile)
		self.task_menu.addAction(reconcile_action)

	def create_central_widgets(self):
		"""
		Application uses central widgets to load menu selections
		"""
		#create all widget instances to be used as central widgets
		self.launch_widget = launch()
		self.reconcile_widget = reconcile(self.progress_bar)

		#add above widget instances to a stack instance
		self.stacked_widgets = QStackedWidget()
		self.stacked_widgets.addWidget(self.launch_widget)
		self.stacked_widgets.addWidget(self.reconcile_widget)

		#on application start set the launch widget to display
		self.stacked_widgets.setCurrentWidget(self.launch_widget)
		self.setCentralWidget(self.stacked_widgets)

	@Slot()
	def menu_file_home(self):
		"""
		Helper for home_action object in self.create_menu
		Reset application to launch configuration
		"""
		self.stacked_widgets.setCurrentWidget(self.launch_widget)
		self.setCentralWidget(self.stacked_widgets)

	@Slot()
	def menu_file_exit(self):
		"""
		Helper for exit_action object in self.create_menu
		"""
		QApplication.quit()

	@Slot()
	def menu_task_reconcile(self):
		"""
		Helper for reconcile_action in self.create_menu
		"""
		self.stacked_widgets.setCurrentWidget(self.reconcile_widget)
		self.setCentralWidget(self.stacked_widgets)
Exemple #29
0
class MainWidget(QWidget):
    def __init__(self, parent: QWidget, model: Model) -> None:
        super().__init__(parent)

        logger.add(self.log)

        self.mainlayout = QVBoxLayout()
        self.setLayout(self.mainlayout)

        self.splitter = QSplitter(Qt.Vertical)

        self.stack = QStackedWidget()
        self.splitter.addWidget(self.stack)

        # mod list widget

        self.modlistwidget = QWidget()
        self.modlistlayout = QVBoxLayout()
        self.modlistlayout.setContentsMargins(0, 0, 0, 0)
        self.modlistwidget.setLayout(self.modlistlayout)
        self.stack.addWidget(self.modlistwidget)

        # search bar

        self.searchbar = QLineEdit()
        self.searchbar.setPlaceholderText('Search...')
        self.modlistlayout.addWidget(self.searchbar)

        # mod list

        self.modlist = ModList(self, model)
        self.modlistlayout.addWidget(self.modlist)

        self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e))

        # welcome message

        welcomelayout = QVBoxLayout()
        welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomewidget = QWidget()
        welcomewidget.setLayout(welcomelayout)
        welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent  # type: ignore
        welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent  # type: ignore
        welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent  # type: ignore
        welcomewidget.dropEvent = self.modlist.dropEvent  # type: ignore
        welcomewidget.setAcceptDrops(True)

        icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico')))
        iconpixmap = icon.pixmap(32, 32)
        icon = QLabel()
        icon.setPixmap(iconpixmap)
        icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        icon.setContentsMargins(4, 4, 4, 4)
        welcomelayout.addWidget(icon)

        welcome = QLabel('''<p><font>
            No mod installed yet.
            Drag a mod into this area to get started!
            </font></p>''')
        welcome.setAttribute(Qt.WA_TransparentForMouseEvents)
        welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        welcomelayout.addWidget(welcome)

        self.stack.addWidget(welcomewidget)

        # output log

        self.output = QTextEdit(self)
        self.output.setTextInteractionFlags(Qt.NoTextInteraction)
        self.output.setReadOnly(True)
        self.output.setContextMenuPolicy(Qt.NoContextMenu)
        self.output.setPlaceholderText('Program output...')
        self.splitter.addWidget(self.output)

        # TODO: enhancement: add a launch game icon
        # TODO: enhancement: show indicator if scripts have to be merged

        self.splitter.setStretchFactor(0, 1)
        self.splitter.setStretchFactor(1, 0)
        self.mainlayout.addWidget(self.splitter)

        if len(model):
            self.stack.setCurrentIndex(0)
            self.splitter.setSizes([self.splitter.size().height(), 50])
        else:
            self.stack.setCurrentIndex(1)
            self.splitter.setSizes([self.splitter.size().height(), 0])
        model.updateCallbacks.append(self.modelUpdateEvent)

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.modlist.setFocus()
            self.searchbar.setText('')
        elif event.matches(QKeySequence.Find):
            self.searchbar.setFocus()
        elif event.matches(QKeySequence.Paste):
            self.pasteEvent()
        super().keyPressEvent(event)

    def pasteEvent(self) -> None:
        clipboard = QApplication.clipboard().text().splitlines()
        if len(clipboard) == 1 and isValidNexusModsUrl(clipboard[0]):
            self.parentWidget().showDownloadModDialog()
        else:
            urls = [
                url for url in QApplication.clipboard().text().splitlines()
                if len(str(url.strip()))
            ]
            if all(
                    isValidModDownloadUrl(url) or isValidFileUrl(url)
                    for url in urls):
                asyncio.create_task(self.modlist.checkInstallFromURLs(urls))

    def modelUpdateEvent(self, model: Model) -> None:
        if len(model) > 0:
            if self.stack.currentIndex() != 0:
                self.stack.setCurrentIndex(0)
                self.repaint()
        else:
            if self.stack.currentIndex() != 1:
                self.stack.setCurrentIndex(1)
                self.repaint()

    def unhideOutput(self) -> None:
        if self.splitter.sizes()[1] < 10:
            self.splitter.setSizes([self.splitter.size().height(), 50])

    def unhideModList(self) -> None:
        if self.splitter.sizes()[0] < 10:
            self.splitter.setSizes([50, self.splitter.size().height()])

    def log(self, message: Any) -> None:
        # format log messages to user readable output
        settings = QSettings()

        record = message.record
        message = record['message']
        extra = record['extra']
        level = record['level'].name.lower()

        name = str(extra['name']
                   ) if 'name' in extra and extra['name'] is not None else ''
        path = str(extra['path']
                   ) if 'path' in extra and extra['path'] is not None else ''
        dots = bool(
            extra['dots']
        ) if 'dots' in extra and extra['dots'] is not None else False
        newline = bool(
            extra['newline']
        ) if 'newline' in extra and extra['newline'] is not None else False
        output = bool(
            extra['output']
        ) if 'output' in extra and extra['output'] is not None else bool(
            message)
        modlist = bool(
            extra['modlist']
        ) if 'modlist' in extra and extra['modlist'] is not None else False

        if level in ['debug'
                     ] and settings.value('debugOutput', 'False') != 'True':
            if newline:
                self.output.append(f'')
            return

        n = '<br>' if newline else ''
        d = '...' if dots else ''
        if len(name) and len(path):
            path = f' ({path})'

        if output:
            message = html.escape(message, quote=True)

            if level in ['success', 'error', 'warning']:
                message = f'<strong>{message}</strong>'
            if level in ['success']:
                message = f'<font color="#04c45e">{message}</font>'
            if level in ['error', 'critical']:
                message = f'<font color="#ee3b3b">{message}</font>'
            if level in ['warning']:
                message = f'<font color="#ff6500">{message}</font>'
            if level in ['debug', 'trace']:
                message = f'<font color="#aaa">{message}</font>'
                path = f'<font color="#aaa">{path}</font>' if path else ''
                d = f'<font color="#aaa">{d}</font>' if d else ''

            time = record['time'].astimezone(
                tz=None).strftime('%Y-%m-%d %H:%M:%S')
            message = f'<font color="#aaa">{time}</font> {message}'
            self.output.append(
                f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}'
            )
        else:
            self.output.append(f'')

        self.output.verticalScrollBar().setValue(
            self.output.verticalScrollBar().maximum())
        self.output.repaint()

        if modlist:
            self.unhideModList()
        if settings.value('unhideOutput', 'True') == 'True' and output:
            self.unhideOutput()
Exemple #30
0
class _ViewSwitcherWidget(QWidget):
    """Object that handles displaying of Enigma model wikis and images"""
    def __init__(self, master, regen_plug):
        """
        :param master: Qt parent object
        :param regen_plug: {callable} Regenerates settings view to new contents
        """
        super().__init__(master)

        layout = QHBoxLayout()
        self.setLayout(layout)

        # LIST OF AVAILABLE MODELS =============================================

        self.model_list = QListWidget()
        self.model_list.setMaximumWidth(150)
        self.model_list.setMinimumWidth(150)
        self.model_list.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        self.model_list.currentRowChanged.connect(self.select_model)

        self.regen_plug = regen_plug

        # STACKED MODEL VIEWS ==================================================

        self.stacked_wikis = QStackedWidget()
        for i, model in enumerate(VIEW_DATA):
            self.model_list.insertItem(i, model)
            description = VIEW_DATA[model]["description"]
            self.stacked_wikis.addWidget(
                _EnigmaViewWidget(self, model, description))
        self.total_models = len(VIEW_DATA)

        layout.addWidget(self.model_list)
        layout.addWidget(self.stacked_wikis)

        # Sets initially viewed
        self.currently_selected = None

    def select_model(self, i):
        """Called when the "Use this model" button is pressed
        :param i: {str} Newly selected model index
        """
        logging.info('Changing settings view to model "%s"' %
                     list(VIEW_DATA.keys())[i])
        model = list(VIEW_DATA.keys())[i]
        self.regen_plug(model)
        self.stacked_wikis.setCurrentIndex(i)

        self.model_list.blockSignals(True)
        self.model_list.setCurrentRow(i)
        self.model_list.blockSignals(False)

        self.currently_selected = model

    def highlight(self, i):
        """Highlights an option with Blue color, indicating that
        it is currently selected in EnigmaAPI
        :param i: {int} Index from list options
        """
        for index in range(self.total_models):
            item = self.model_list.item(index)
            item.setForeground(Qt.black)
            item.setToolTip(None)

        selected = self.model_list.item(i)
        selected.setBackground(Qt.gray)  # .setForeground(Qt.blue)
        selected.setToolTip("Currently used Enigma model")