def __init__(self, cp: ControlPoint, game_model: GameModel) -> None:
        super().__init__()
        self.cp = cp
        self.game_model = game_model
        self.transfers: Dict[Type[UnitType, int]] = defaultdict(int)

        main_layout = QVBoxLayout()

        scroll_content = QWidget()
        task_box_layout = QGridLayout()

        unit_types = set(
            self.game_model.game.faction_for(player=True).ground_units)
        sorted_units = sorted(
            {u
             for u in unit_types if self.cp.base.total_units_of_type(u)},
            key=lambda u: u.name,
        )
        for row, unit_type in enumerate(sorted_units):
            self.add_unit_row(unit_type, task_box_layout, row)
        stretch = QVBoxLayout()
        stretch.addStretch()
        task_box_layout.addLayout(stretch, task_box_layout.count(), 0)

        scroll_content.setLayout(task_box_layout)
        scroll = QScrollArea()
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        scroll.setWidgetResizable(True)
        scroll.setWidget(scroll_content)
        main_layout.addWidget(scroll)
        self.setLayout(main_layout)
Example #2
0
    def load_game_list(self, game_layout: QGridLayout):
        while game_layout.count():
            child = game_layout.takeAt(0)
            if child.widget():
                child.widget().deleteLater()
        games = self.all_displays
        all_entries = iter_entry_points('zero_play.game_display')
        filtered_entries = self.filter_games(all_entries)
        for game_entry in filtered_entries:
            display_class = game_entry.load()
            display: GameDisplay = display_class()
            self.destroyed.connect(display.close)
            display.game_ended.connect(self.on_game_ended)  # type: ignore
            games.append(display)
        games.sort(key=attrgetter('start_state.game_name'))
        column_count = math.ceil(math.sqrt(len(games)))
        for i, display in enumerate(games):
            row = i // column_count
            column = i % column_count
            game_name = display.start_state.game_name
            game_button = QPushButton(game_name)
            game_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)

            # noinspection PyUnresolvedReferences
            game_button.clicked.connect(partial(self.show_game, display))
            game_layout.addWidget(game_button, row, column)

            self.ui.history_game.addItem(game_name, userData=display)

            if display.rules_path is not None:
                game_rules_action = self.ui.menu_rules.addAction(game_name)
                game_rules_action.triggered.connect(
                    partial(self.on_rules, display))
Example #3
0
class _ExecuteTab(QTabWidget):
    """Tab used to execute modules or shell commands on the selected bot."""

    def __init__(self, responses_tab, model):
        """
        :type responses_tab: _ResponsesTab
        """
        super(_ExecuteTab, self).__init__()

        self._model = model
        self._current_layout = None
        self._current_bot = None

        self._layout = QGridLayout()
        self._sub_layout = QVBoxLayout()
        self._module_view = ModuleView(responses_tab)

        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)
        self.set_empty_layout()

    def set_current_bot(self, bot):
        """Sets the connected bot this tab will interact with.

        :type bot: Bot
        """
        self._current_bot = bot

    def _clear_layout(self):
        while self._layout.count():
            child = self._layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

    def set_empty_layout(self):
        """Default layout shown when the user has not yet selected a row."""
        self._current_layout = "Empty"
        self._clear_layout()

        self._layout.addWidget(QLabel("Please select a bot in the table above."), 0, 0)

    def set_module_layout(self, module_name="screenshot"):
        """Sets the layout which can execute modules.

        :type module_name: str
        """
        self._current_layout = "Module"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Module")
        command_type_combobox.addItem("Shell")

        module_label = QLabel("Module name: ")
        module_combobox = QComboBox()

        for module_name in modules.get_names():
            module_combobox.addItem(module_name)

        module_combobox.currentTextChanged.connect(self._on_module_change)
        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)

        self._layout.setColumnStretch(1, 1)
        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(module_label, 1, 0)
        self._layout.addWidget(module_combobox, 1, 1)

        # Module layout
        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)
            input_fields.append(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_combobox.currentText(), input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

        self._on_module_change(module_combobox.currentText())

    def set_shell_layout(self):
        """Sets the layout which can execute shell commands."""
        self._current_layout = "Shell"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Shell")
        command_type_combobox.addItem("Module")

        command_label = QLabel("Command:")
        command_input = QLineEdit()

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        command_type_combobox.currentTextChanged.connect(self._on_command_type_change)
        run_button.pressed.connect(lambda: self._on_command_run(command_input))

        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(command_label, 1, 0)
        self._layout.addWidget(command_input, 1, 1)

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2)

    def _on_command_type_change(self, text):
        """Handles the command type combobox change event.

        :type text: str
        """
        if text == "Module":
            self.set_module_layout()
        else:
            self.set_shell_layout()

    def _on_module_change(self, module_name):
        """Handles module combobox changes.

        :type module_name: str
        """
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view, self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()
            input_fields.append(input_field)

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(module_name, input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)

    def display_info(self, text):
        """
        :type text: str
        """
        message_box = QMessageBox()

        message_box.setIcon(QMessageBox.Information)
        message_box.setWindowTitle("Information")
        message_box.setText(text)
        message_box.setStandardButtons(QMessageBox.Ok)
        message_box.exec_()

    def _on_module_run(self, module_name, input_fields):
        """Handles running modules.

        :type module_name: str
        :type input_fields: list
        """
        set_options = []

        for input_field in input_fields:
            set_options.append(input_field.text())

        module = modules.get_module(module_name)

        if not module:
            module = modules.load_module(module_name, self._module_view, self._model)

        successful, options = module.setup(set_options)

        if successful:
            if module_name == "remove_bot":
                code = loaders.get_remove_code(self._current_bot.loader_name)
            elif module_name == "update_bot":
                code = loaders.get_update_code(self._current_bot.loader_name)
            else:
                code = modules.get_code(module_name)

            if not options:
                options = {}

            options["module_name"] = module_name

            self._model.add_command(self._current_bot.uid, Command(
                CommandType.MODULE, code, options
            ))

            self.display_info("Module added to the queue of:\n {}@{}".format(
                self._current_bot.username, self._current_bot.hostname)
            )

    def _on_command_run(self, command_input):
        """Handles running commands.

        :type command_input: QLineEdit
        """
        if command_input.text().strip() == "":
            return

        self._model.add_command(self._current_bot.uid, Command(CommandType.SHELL, command_input.text().encode()))

        command_input.clear()
        self.display_info("Command added to the queue of:\n {}@{}".format(
            self._current_bot.username, self._current_bot.hostname
        ))
Example #4
0
class QWaitingForMissionResultWindow(QDialog):
    def __init__(
        self,
        game: Game,
        sim_controller: SimController,
        parent: Optional[QWidget] = None,
    ) -> None:
        super(QWaitingForMissionResultWindow, self).__init__(parent=parent)
        self.setWindowModality(QtCore.Qt.WindowModal)
        self.game = game
        self.sim_controller = sim_controller
        self.setWindowTitle("Waiting for mission completion.")
        self.setWindowIcon(QIcon("./resources/icon.png"))
        self.setMinimumHeight(570)

        self.initUi()
        DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(
            self.updateLayout)
        self.wait_thread = sim_controller.wait_for_debriefing(
            lambda debriefing: self.on_debriefing_update(debriefing))

    def initUi(self):
        self.layout = QGridLayout()

        header = QLabel(self)
        header.setGeometry(0, 0, 655, 106)
        pixmap = QPixmap("./resources/ui/conflict.png")
        header.setPixmap(pixmap)
        self.layout.addWidget(header, 0, 0)

        self.gridLayout = QGridLayout()

        jinja = Environment(
            loader=FileSystemLoader("resources/ui/templates"),
            autoescape=select_autoescape(
                disabled_extensions=("", ),
                default_for_string=True,
                default=True,
            ),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        self.instructions_text = QTextBrowser()
        self.instructions_text.setHtml(
            jinja.get_template("mission_start_EN.j2").render())
        self.instructions_text.setOpenExternalLinks(True)
        self.gridLayout.addWidget(self.instructions_text, 1, 0)

        progress = QLabel("")
        progress.setAlignment(QtCore.Qt.AlignCenter)
        progress_bar = QMovie("./resources/ui/loader.gif")
        progress.setMovie(progress_bar)

        self.actions = QGroupBox("Actions :")
        self.actions_layout = QHBoxLayout()
        self.actions.setLayout(self.actions_layout)

        self.manually_submit = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit.clicked.connect(self.submit_manually)
        self.actions_layout.addWidget(self.manually_submit)
        self.cancel = QPushButton("Abort mission")
        self.cancel.clicked.connect(self.close)
        self.actions_layout.addWidget(self.cancel)
        self.gridLayout.addWidget(self.actions, 2, 0)

        self.actions2 = QGroupBox("Actions :")
        self.actions2_layout = QHBoxLayout()
        self.actions2.setLayout(self.actions2_layout)
        self.manually_submit2 = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit2.clicked.connect(self.submit_manually)
        self.actions2_layout.addWidget(self.manually_submit2)
        self.cancel2 = QPushButton("Abort mission")
        self.cancel2.clicked.connect(self.close)
        self.actions2_layout.addWidget(self.cancel2)
        self.proceed = QPushButton("Accept results")
        self.proceed.setProperty("style", "btn-success")
        self.proceed.clicked.connect(self.process_debriefing)
        self.actions2_layout.addWidget(self.proceed)

        progress_bar.start()
        self.layout.addLayout(self.gridLayout, 1, 0)
        self.setLayout(self.layout)

    @staticmethod
    def add_update_row(description: str, count: int,
                       layout: QGridLayout) -> None:
        row = layout.rowCount()
        layout.addWidget(QLabel(f"<b>{description}</b>"), row, 0)
        layout.addWidget(QLabel(f"{count}"), row, 1)

    def updateLayout(self, debriefing: Debriefing) -> None:
        updateBox = QGroupBox("Mission status")
        update_layout = QGridLayout()
        updateBox.setLayout(update_layout)
        self.debriefing = debriefing

        self.add_update_row("Aircraft destroyed",
                            len(list(debriefing.air_losses.losses)),
                            update_layout)
        self.add_update_row(
            "Front line units destroyed",
            len(list(debriefing.front_line_losses)),
            update_layout,
        )
        self.add_update_row("Convoy units destroyed",
                            len(list(debriefing.convoy_losses)), update_layout)
        self.add_update_row(
            "Shipping cargo destroyed",
            len(list(debriefing.cargo_ship_losses)),
            update_layout,
        )
        self.add_update_row(
            "Airlift cargo destroyed",
            sum(len(loss.cargo) for loss in debriefing.airlift_losses),
            update_layout,
        )
        self.add_update_row(
            "Ground Objects destroyed",
            len(list(debriefing.ground_object_losses)),
            update_layout,
        )
        self.add_update_row(
            "Scenery Objects destroyed",
            len(list(debriefing.scenery_object_losses)),
            update_layout,
        )
        self.add_update_row("Base capture events",
                            len(debriefing.base_captures), update_layout)

        # Clear previous content of the window
        for i in reversed(range(self.gridLayout.count())):
            try:
                self.gridLayout.itemAt(i).widget().setParent(None)
            except:
                logging.exception("Failed to clear window")

        # Set new window content
        self.gridLayout.addWidget(updateBox, 0, 0)

        if not debriefing.state_data.mission_ended:
            self.gridLayout.addWidget(QLabel("<b>Mission is being played</b>"),
                                      1, 0)
            self.gridLayout.addWidget(self.actions, 2, 0)
        else:
            self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0)
            self.gridLayout.addWidget(self.actions2, 2, 0)

    def on_debriefing_update(self, debriefing: Debriefing) -> None:
        try:
            logging.info("On Debriefing update")
            logging.debug(debriefing)
            DebriefingFileWrittenSignal.get_instance().sendDebriefing(
                debriefing)
        except Exception:
            logging.exception("Got an error while sending debriefing")
        self.wait_thread = self.sim_controller.wait_for_debriefing(
            lambda d: self.on_debriefing_update(d))

    def process_debriefing(self):
        with logged_duration("Turn processing"):
            self.sim_controller.process_results(self.debriefing)
            self.game.pass_turn()

            GameUpdateSignal.get_instance().sendDebriefing(self.debriefing)
            GameUpdateSignal.get_instance().updateGame(self.game)
        self.close()

    def debriefing_directory_location(self) -> str:
        return os.path.join(base_path(), "liberation_debriefings")

    def closeEvent(self, evt):
        super(QWaitingForMissionResultWindow, self).closeEvent(evt)
        if self.wait_thread is not None:
            self.wait_thread.stop()

    def submit_manually(self):
        file = QFileDialog.getOpenFileName(self,
                                           "Select game file to open",
                                           filter="json(*.json)",
                                           dir=".")
        logging.debug("Processing manually submitted %s", file[0])
        self.on_debriefing_update(
            self.sim_controller.debrief_current_state(
                Path(file[0], force_end=True)))
Example #5
0
class MatchStatWindow(QDialog):
    def __init__(self, parent=None):
        super(MatchStatWindow, self).__init__(parent)
        self.setModal(True)
        self.setWindowTitle("Mérkőzés választása")
        self.resize(740, 600)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.merkozesek = QListWidget()
        self.merkozesek.setFixedHeight(200)
        self.merkozesek.setFixedWidth(730)
        self.merkozesek.itemDoubleClicked.connect(self.stat_game)
        self.layout.addWidget(self.merkozesek)
        self.szumma = MatchSumWidget(self)
        self.layout.addWidget(self.szumma)
        self.history = QWidget()
        self.history.setFixedWidth(690)
        self.history_layout = QGridLayout()
        self.history.setLayout(self.history_layout)
        scroll = QScrollArea()
        scroll.setWidget(self.history)
        scroll.setWidgetResizable(True)
        self.layout.addWidget(scroll)

        self.match_valasztas()

    def match_valasztas(self):
        matches = QSqlRelationalTableModel(db=db)
        matches.setTable("match_settings")
        matches.setSort(8, Qt.DescendingOrder)
        matches.select()
        self.merkozesek.clear()
        for i in  range(matches.rowCount()):
            self.merkozesek.addItem(str(matches.record(i).value(1)) + "\t" +
                                    str(matches.record(i).value(2)) + "\t" +
                                    str(int(matches.record(i).value(3)) + matches.record(i).value(6)) + "\t" +
                                    str(int(matches.record(i).value(3)) + matches.record(i).value(7)) + "\t" +
                                    str(matches.record(i).value(4)) + "\t" +
                                    str(matches.record(i).value(5)) + "\t" +
                                    str(matches.record(i).value(8))[:16] + "\t" +
                                    str(matches.record(i).value(0))
            )

    def stat_game(self):
        # Az átvett adatok:
        para = self.merkozesek.currentItem().text().rsplit("\t")
        # Összegyűjtjük egy listába a szükséges infókat
        self.get_adatok(para)
        self.szumma.change_data(self.adatok)
        for x in reversed(range(self.history_layout.count())):
            self.history_layout.itemAt(x).widget().deleteLater()
        # kiszedjük az adott meccs összes set és leg esetére a dobásokat
        sor = 0
        for s in range(1, self.adatok[8] + 1):
            # s: a set-ek száma
            if self.adatok[8] != 1:
                self.history_layout.addWidget(QLabel("Set: " + str(s)), sor, 0, 1, 2)
                sor += 1
            for l in range(1, self.adatok[7][s-1] + 1):
                sl1_model = QSqlQueryModel()
                p1_data_list = []
                p1_data_list.append(self.adatok[4])   # start_score1
                sl1_query = QSqlQuery(f"select * from dobas where match_id ={self.adatok[6]} and set_id={s} and leg_id={l} and player_id='{self.adatok[0]}'", db=db)
                sl1_model.setQuery(sl1_query)
                for i in range(sl1_model.rowCount()):
                    # Itt a model már tartalmazza a p1 összes dobását az adott leg-ben.
                    p1_data_row = []
                    p1_data_row.append(sl1_model.record(i).value(1))
                    p1_data_row.append(sl1_model.record(i).value(2))
                    p1_data_list.append(p1_data_row)
                self.history_layout.addWidget(PlayerLegWidget(self, p1_data_list), sor, 0, Qt.AlignTop)

                sl2_model = QSqlQueryModel()
                p2_data_list = []
                p2_data_list.append(self.adatok[5])  # start_score2
                sl2_query = QSqlQuery(f"select * from dobas where match_id ={self.adatok[6]} and set_id={s} and leg_id={l} and player_id='{self.adatok[2]}'", db=db)
                sl2_model.setQuery(sl2_query)
                for j in range(sl2_model.rowCount()):
                    p2_data_row = []
                    p2_data_row.append(sl2_model.record(j).value(1))
                    p2_data_row.append(sl2_model.record(j).value(2))
                    p2_data_list.append(p2_data_row)
                self.history_layout.addWidget(PlayerLegWidget(self, p2_data_list), sor, 1, Qt.AlignTop)
                sor += 1

    def get_adatok(self, para):
        print(para)
        # self.adatok[0]  : p1_id
        # self.adatok[1]  : name1
        # self.adatok[2]  : p2_id
        # self.adatok[3]  : name2
        # self.adatok[4]  : start_score1
        # self.adatok[5]  : start_score2
        # self.adatok[6]  : match
        # self.adatok[7]  : legs
        # self.adatok[8]  : sets
        # self.adatok[9]  : dátum

        self.adatok = []
        name1_id = int(para[0])
        self.adatok.append(name1_id)
        query_name1 = QSqlQuery(f"select player_name from players where player_id={name1_id}", db=db)
        query_name1.exec_()
        while query_name1.next():
            name1 = query_name1.value(0)
        self.adatok.append(name1)
        name2_id = int(para[1])
        self.adatok.append(name2_id)
        query_name2 = QSqlQuery(f"select player_name from players where player_id={name2_id}", db=db)
        query_name2.exec_()
        while query_name2.next():
            name2 = query_name2.value(0)
        self.adatok.append(name2)

        start_score1 = int(para[2])
        start_score2 = int(para[3])
        match = int(para[7])
        setek = int(para[5])
        self.adatok.append(start_score1)
        self.adatok.append(start_score2)
        self.adatok.append(match)
        # Kell a max set-number, ezt beállítani a sets változóba
        # Ciklussal minden set-ben megnézni a max leg-numbert, és ezeket append-elni a legs[]-hez
        # Végül leg, set sorrendben append-elni az adatokhoz
        legs = []
        sets = 0
        query2 = QSqlQuery(
            f"select max(set_id) as max_set from matches where match_id={match}", db=db)
        query2.exec_()
        while query2.next():
            sets = int(query2.value(0))

        for i in range(1, sets + 1):
            query = QSqlQuery(f"select max(leg_id) as max_leg from matches where match_id={match} and set_id={i}", db=db)
            query.exec_()
            while query.next():
                legs.append(int(query.value(0)))
                # sets.append(int(query.value(1)))

        self.adatok.append(legs)
        self.adatok.append(sets)

        datum = para[6][:16]
        self.adatok.append(datum)
        print(self.adatok)
Example #6
0
class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, ui_demo=False):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.grid_layout = QGridLayout()

        self.btnDetectClient.clicked.connect(self.detectClients)
        self.lineEditIpRange.returnPressed.connect(self.detectClients)
        self.btnSelectAllClients.clicked.connect(self.selectAllCLients)

        shortcut = QShortcut(QKeySequence(self.tr("Ctrl+A")), self)
        shortcut.activated.connect(self.selectAllCLients)

        self.btnUnselectClients.clicked.connect(self.unselectAllCLients)
        self.btnSelectExam.clicked.connect(self.selectExamByWizard)
        self.btnSelectExam.setEnabled(True)

        self.btnPrepareExam.clicked.connect(self.prepareExam)
        self.btnPrepareExam.setEnabled(False)

        self.btnGetExams.clicked.connect(self.retrieveExamFilesByWizard)
        self.btnGetExams.setEnabled(True)
        self.btnSaveExamLog.clicked.connect(self.saveExamLog)
        # self.btnSaveExamLog.setEnabled(False)

        self.actionBearbeiten.triggered.connect(self.openConfigDialog)
        self.actionAlle_Benutzer_benachrichtigen.triggered.connect(
            self.sendMessage)
        self.actionAlle_Clients_zur_cksetzen.triggered.connect(
            self.resetClients)
        self.actionAlle_Clients_rebooten.triggered.connect(
            self.rebootAllClients)
        self.actionAlle_Clients_herunterfahren.triggered.connect(
            self.shutdownAllClients)
        self.actionOnlineHelp.triggered.connect(self.openHelpUrl)
        self.actionOfflineHelp.triggered.connect(self.openHelpUrlOffline)
        self.actionSortClientByCandidateName.triggered.connect(
            self.sortButtonsByCandidateName)
        self.actionSortClientByComputerName.triggered.connect(
            self.sortButtonsByComputerName)
        self.actionVersionInfo.triggered.connect(self.showVersionInfo)
        self.actionDisplayIPs.triggered.connect(self.toggleClientIpDisplay)

        self.btnApplyCandidateNames.clicked.connect(self.applyCandidateNames)
        self.btnNameClients.clicked.connect(self.activateNameTab)

        self.btnBlockUsb.clicked.connect(self.blockUsb)
        self.btnBlockWebAccess.clicked.connect(self.blockWebAccess)

        self.appTitle = 'ECMan - Exam Client Manager'
        self.setWindowTitle(self.appTitle)

        self.logger = Logger(self.textEditLog)

        self.lb_directory = None
        self.advancedUi = False
        self.configure()
        self.show()
        if ui_demo is False:
            self.detectClients()

        else:
            self.clientFrame.setLayout(self.grid_layout)
            for i in range(6):
                self.addTestClient(i + 100)

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

    def sortButtonsByCandidateName(self):
        '''
        sort LbClient-widgets by candidate name
        :return: nothing
        '''
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]
        clients = sorted(clients,
                         key=lambda client: client.computer.candidateName)
        self.arrangeClientButtons(clients)

    def sortButtonsByComputerName(self):
        '''
        supposed to sort LbClient-widgets by candidate name
        :return: nothing
        '''
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]
        clients = sorted(clients,
                         key=lambda client: client.computer.getHostName())
        self.arrangeClientButtons(clients)

    def toggleClientIpDisplay(self):
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]
        for client in clients:
            client.toggleShowIp()

    def arrangeClientButtons(self, clients):
        try:
            for i in reversed(range(self.grid_layout.count())):
                self.grid_layout.removeItem(self.grid_layout.itemAt(i))
        except:
            pass

        self.clientFrame.setLayout(self.grid_layout)
        for button in clients:
            self.grid_layout.addWidget(button,
                                       self.grid_layout.count() / 4,
                                       self.grid_layout.count() % 4)

        self.clientFrame.setLayout(self.grid_layout)

    def activateNameTab(self):
        if self.textEditCandidates.toPlainText() == "":
            candidates = "\n".join(
                str(x + 1) for x in range(self.grid_layout.count()))
            self.textEditCandidates.setText(candidates)

        self.tabs.setCurrentWidget(self.tab_candidates)

    def openHelpUrl(self):
        webbrowser.open(
            self.config.get(
                "General",
                "wikiurl",
                fallback="https://github.com/greenorca/ECMan/wiki"))

    def openHelpUrlOffline(self):
        webbrowser.open("file://" + os.getcwd().replace("\\", "/") +
                        "/help/Home.html")

    def checkOldLogFiles(self):
        """
        dummy,
        """
        pass

    def blockUsb(self):
        block = self.btnBlockUsb.text() == "USB blockieren"
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
            if self.grid_layout.itemAt(i).widget().isSelected
        ]

        if block:
            for client in clients:
                client.blockUsbAccessThread()
            self.btnBlockUsb.setText("USB freigeben")
        else:
            for client in clients:
                client.allowUsbAccessThread()
            self.btnBlockUsb.setText("USB blockieren")

    def blockWebAccess(self):
        block = self.btnBlockWebAccess.text() == "Web blockieren"
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
            if self.grid_layout.itemAt(i).widget().isSelected
        ]
        if block:
            for client in clients:
                client.blockInternetAccessThread()

            self.btnBlockWebAccess.setText("Web freigeben")

        else:
            for client in clients:
                client.allowInternetAccessThread()

            self.btnBlockWebAccess.setText("Web blockieren")

    def resetClients(self):
        """
        resets remote files and configuration for all connected clients
        """

        items = ["Ja, Namen zurücksetzen", "Nein, Namen beibehalten"]
        item, ok = QInputDialog().getItem(
            self, "LB-Status zurücksetzen",
            "USB-Sticks und Internet werden freigeben.\nDaten im Benutzerverzeichnis werden NICHT gelöscht.\nKandidaten-Namen zurücksetzen? ",
            items, 0, False)
        if ok is False:
            return

        resetCandidateName = True if item.startswith("Ja") else False

        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]

        progressDialog = EcManProgressDialog(self, "Reset Clients")
        progressDialog.setMaxValue(self.grid_layout.count())
        progressDialog.resetValue()
        progressDialog.open()

        self.worker = ResetClientsWorker(clients, resetCandidateName)
        self.worker.updateProgressSignal.connect(progressDialog.incrementValue)
        self.worker.start()

    def closeEvent(self, event):
        """
        overrides closeEvent of base class, clean up

        """
        print("cleaning up...")
        #         try:* especially remove shares created on Windows hosts
        #             for share in self.sharenames:
        #                 Thread(target=self.runLocalPowerShellAsRoot("Remove-SmbShare -Name {} -Force".format(share))).start()
        #         except Exception:
        #             pass
        #
        print("done cleaning up...")
        super(MainWindow, self).closeEvent(event)

    def selectAllCLients(self):
        """
        marks / selects all connected client pcs
        """
        for i in range(self.grid_layout.count()):
            self.grid_layout.itemAt(i).widget().select()

    def unselectAllCLients(self):
        """
        unmarks / unselects all connected client pcs
        """
        for i in range(self.grid_layout.count()):
            self.grid_layout.itemAt(i).widget().unselect()

    def shutdownAllClients(self):
        for i in range(self.grid_layout.count()):
            self.grid_layout.itemAt(i).widget().shutdownClient()

    def rebootAllClients(self):
        for i in range(self.grid_layout.count()):
            self.grid_layout.itemAt(i).widget().computer.reboot()
            self.grid_layout.itemAt(i).widget().deleteLater()
        # TODO TEST sven
        #for i in range(self.grid_layout.count()):
        #    self.grid_layout.removeItem(self.grid_layout.itemAt(i))

    def sendMessage(self):

        message, ok = QInputDialog.getText(self, "Eingabe",
                                           "Nachricht an Kandidaten eingeben")
        if ok:
            for i in range(self.grid_layout.count()):
                QThreadPool.globalInstance().start(
                    SendMessageTask(
                        self.grid_layout.itemAt(i).widget(), message))

    def applyCandidateNames(self):
        """
        reads candidate names from respective textEditField (line by line)
        and applies these names to (random) client pcs
        """
        names = self.textEditCandidates.toPlainText().rstrip().splitlines()
        # cleanup and remove duplicate names
        names = [x.strip() for x in names]
        names = list(set(names))

        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]

        # select only the computers without candidate name
        if self.checkBox_OverwriteExisitingNames.checkState(
        ) != Qt.CheckState.Checked:
            clients = [
                x for x in clients if x.computer.getCandidateName() == ""
                or x.computer.getCandidateName() is None
            ]

        if len(names) > len(clients):
            self.showMessageBox(
                "Fehler",
                "Nicht genug Prüfungs-PCs für alle {} Kandidaten".format(
                    str(len(names))),
                messageType=QMessageBox.Warning)
            return

        progressDialog = EcManProgressDialog(
            self, "Fortschritt Kandidatennamen setzen")
        progressDialog.setMaxValue(len(names))
        progressDialog.resetValue()

        self.worker = SetCandidateNamesWorker(clients, names)
        self.worker.updateProgressSignal.connect(progressDialog.incrementValue)
        self.worker.start()

        progressDialog.open()
        self.tabs.setCurrentWidget(self.tab_pcs)

    def configure(self):
        """
        sets inial values for app
        """
        self.port = 5986
        self.server = None

        self.configFile = Path(str(Path.home()) + "/.ecman.conf")

        if not (self.configFile.exists()):
            result = self.openConfigDialog()

        if self.configFile.exists() or result == 1:
            self.readConfigFile()

        self.result_directory = ""

        self.sharenames = [
        ]  # dump all eventually created local Windows-Shares in this array
        self.debug = True
        # fetch own ip adddress
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        try:
            s.connect(('9.9.9.9', 1))  # connect() for UDP doesn't send packets
            self.ip_address = s.getsockname()[0]
            parts = self.ip_address.split(".")[0:-1]
            self.ipRange = ".".join(parts) + ".*"
            self.lineEditIpRange.setText(self.ipRange)
            self.appTitle = self.windowTitle() + " on " + self.ip_address
            self.setWindowTitle(self.appTitle)
        except Exception as ex:
            self.ipRange, ok = QInputDialog.getText(
                self, "Keine Verbindung zum Internet",
                "Möglicherweise gelingt der Sichtflug. Bitte geben Sie die lokale IP-Adresse ein:"
            )

            self.log("no connection to internet:" + str(ex))

    def readConfigFile(self):
        """
        reads config file into class variables
        """
        self.config = ConfigParser()
        self.config.read_file(open(str(self.configFile)))

        self.port = self.config.get("General", "winrm_port", fallback=5986)

        self.client_lb_user = self.config.get("Client",
                                              "lb_user",
                                              fallback="student")
        self.user = self.config.get("Client", "user", fallback="")
        self.passwd = self.config.get("Client", "pwd", fallback="")
        self.maxFiles = int(
            self.config.get("Client", "max_files", fallback="1000"))
        self.maxFileSize = int(
            self.config.get("Client", "max_filesize",
                            fallback="1000")) * 1024 * 1024  # thats MB now...

        self.advancedUi = self.config.get('General',
                                          'advanced_ui',
                                          fallback="False") == "True"
        pass

    def openConfigDialog(self):
        """
        opens configuration dialog
        """
        config_dialog = EcManConfigDialog(self)
        result = config_dialog.exec_()
        if result == 1:
            config_dialog.saveConfig()
            self.readConfigFile()
        return result

    def log(self, message):
        """
        basic logging functionality
        TODO: improve...
        """
        self.logger.log(message)

    def updateProgressBar(self, value):
        self.progressBar.setValue(value)
        if (value == self.progressBar.maximum()):
            self.enableButtons(True)
            self.progressBar.setEnabled(False)

    def enableButtons(self, enable):

        if type(enable) != bool:
            raise Exception("Invalid parameter, must be boolean")
        self.btnNameClients.setEnabled(enable)
        self.btnSelectAllClients.setEnabled(enable)
        self.btnUnselectClients.setEnabled(enable)
        self.btnPrepareExam.setEnabled(enable)
        self.btnGetExams.setEnabled(enable)
        self.btnSaveExamLog.setEnabled(enable)
        self.btnDetectClient.setEnabled(enable)
        self.btnBlockWebAccess.setEnabled(enable)
        self.btnBlockUsb.setEnabled(enable)

    def retrieveExamFilesByWizard(self):
        """
        retrieve exam files for all clients
        """
        # find all suitable clients (required array for later threading)
        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
            if self.grid_layout.itemAt(i).widget().isSelected
            and self.grid_layout.itemAt(i).widget().computer.state in [
                Computer.State.STATE_DEPLOYED, Computer.State.STATE_FINISHED,
                Computer.State.STATE_RETRIVAL_FAIL
            ]
        ]

        if len(clients) == 0:
            self.showMessageBox("Achtung",
                                "Keine Clients ausgewählt bzw. deployed")
            return

        unknownClients = [
            c for c in clients if c.computer.getCandidateName() == ""
            or c.computer.getCandidateName() == None
        ]

        for unknown in unknownClients:
            unknown.computer.state = Computer.State.STATE_RETRIVAL_FAIL
            unknown._colorizeWidgetByClientState()

        if unknownClients != []:
            choice = QMessageBox.critical(
                self, "Achtung",
                "{} clients ohne gültigen Kandidatennamen.<br>Rückholung für alle anderen fortsetzen?"
                .format(str(len(unknownClients))), QMessageBox.Yes,
                QMessageBox.No)

            if choice == QMessageBox.No:
                return

        clients = [c for c in clients if c not in unknownClients]

        retVal = QMessageBox.StandardButton.Yes
        if self.result_directory != "":

            items = [
                "Ergebnispfad neu auswählen",
                "Weiter mit bisherigem Verzeichnis"
            ]
            item, ok = QInputDialog().getItem(
                self, "Achtung",
                "LB-Ergebnisverzeichnis ist bereits ausgewählt.\nErneutes Abholen kann existierende Ergebnisse überschreiben.\nWas möchten Sie tun?",
                items, 0, False)

            if ok is False:
                return

        if self.result_directory == "" or item == "Ergebnispfad neu auswählen":

            if self.server is None or self.server.connect() is not True:
                self.server = self.getServerCredentialsByWizard()
                if self.server is None:
                    return

            wizard = EcShareWizard(
                parent=self,
                server=self.server,
                wizardType=EcShareWizard.TYPE_RESULT_DESTINATION,
                advanced_Ui=self.advancedUi)

            wizard.setModal(True)
            result = wizard.exec_()
            print("I'm done, wizard result=" + str(result))
            if result == 1:
                print("selected values: %s - %s - %s" %
                      (wizard.field("username"), wizard.field("servername"),
                       wizard.selectedPath))

                self.result_directory = wizard.selectedPath

            else:
                print("Abbruch, kein Zielverzeichnis ausgewählt")
                return

        self.result_directory = self.result_directory.replace("/", "#")
        self.log("save result files into: " +
                 self.result_directory.replace("#", "\\"))

        progressDialog = EcManProgressDialog(
            self, "Fortschritt Ergebnisse kopieren")
        progressDialog.setMaxValue(len(clients))
        progressDialog.resetValue()
        progressDialog.open()

        self.log("starting to retrieve files")

        self.worker = RetrieveResultsWorker(clients, self.server.user,
                                            self.server.password,
                                            self.server.domain,
                                            self.result_directory,
                                            self.maxFiles, self.maxFileSize)
        self.worker.updateProgressSignal.connect(progressDialog.incrementValue)
        self.worker.start()

        self.btnSaveExamLog.setEnabled(True)

    def prepareExam(self):
        self.copyFilesToClient()
        pass

    def copyFilesToClient(self):
        """
        copies selected exam folder to all connected clients that are selected and not in STATE_DEPLOYED or STATE_FINISHED
        """
        if self.lb_directory == None or self.lb_directory == "":
            self.showMessageBox("Fehler", "Kein Prüfungsordner ausgewählt")
            return

        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
            if self.grid_layout.itemAt(i).widget().isSelected
            and self.grid_layout.itemAt(i).widget().computer.state not in
            [Computer.State.STATE_DEPLOYED, Computer.State.STATE_FINISHED]
        ]

        if len([x for x in clients if x.computer.candidateName == None]) > 0:
            self.showMessageBox("Warnung",
                                "Bitte Kandidatenname für alle PCs vergeben")
            return

        if len(clients) == 0:
            self.showMessageBox(
                "Warnung",
                "Keine Client-PCs ausgewählt oder Prüfungen bereits aktiv")
            return

        progressDialog = EcManProgressDialog(
            self, "Fortschritt LB-Client-Deployment")
        progressDialog.setMaxValue(len(clients))
        progressDialog.resetValue()
        progressDialog.open()

        self.worker = CopyExamsWorker(
            clients,
            self.server.user,
            self.server.password,
            self.server.domain,
            src=self.lb_directory,
            reset=(self.checkBoxWipeHomedir.checkState() ==
                   Qt.CheckState.Checked))
        self.worker.updateProgressSignal.connect(progressDialog.incrementValue)
        self.worker.start()

    def detectClients(self):
        """
        starts portscan to search for winrm enabled clients
        """
        ip_range = self.lineEditIpRange.text()
        if not (ip_range.endswith('*')):
            self.showMessageBox(
                'Eingabefehler',
                'Gültiger IP-V4 Bereich endet mit * (z.B. 192.168.0.*)')
            return

        try:
            self.worker.exit()
        except Exception as ex:
            print("crashed on stopping existing scanner thread: " + str(ex))

        self.ipRange = ip_range
        self.progressBar.setEnabled(True)
        self.progressBar.setValue(0)

        self.enableButtons(enable=False)
        self.clientCount = 0
        # clear previous client buttons
        try:
            for i in reversed(range(self.grid_layout.count())):
                self.grid_layout.itemAt(i).widget().close()
                self.grid_layout.itemAt(i).widget().deleteLater()
        except:
            pass
        self.clientFrame.setLayout(self.grid_layout)

        self.progressBar.setMaximum(253)
        self.worker = ScannerWorker(self.ipRange, self.ip_address)
        self.worker.updateProgressSignal.connect(self.updateProgressBar)
        self.worker.addClientSignal.connect(self.addClient)
        self.worker.start()

    def addClient(self, ip):
        """
        populate GUI with newly received client ips
        param ip: only last byte required
        param scan: wether or not scan the Client (set to False only for GUI testing)
        """
        self.log("new client signal received: " + str(ip))
        self.clientCount += 1
        self.statusBar.showMessage(
            str(self.clientCount) + " clients detected", 0)

        clientIp = self.ipRange.replace("*", str(ip))
        button = LbClient(clientIp,
                          remoteAdminUser=self.user,
                          passwd=self.passwd,
                          candidateLogin=self.client_lb_user,
                          parentApp=self)
        button.setMinimumHeight(50)
        # button.installEventFilter(self)
        self.grid_layout.addWidget(button,
                                   self.grid_layout.count() / 4,
                                   self.grid_layout.count() % 4)
        self.clientFrame.setLayout(self.grid_layout)
        # QtGui.qApp.processEvents()

    def addTestClient(self, ip):
        """
        populate GUI with dummy buttons
        param ip: only last byte required
        """
        self.log("new client signal received: " + str(ip))
        clientIp = self.ipRange.replace("*", str(ip))
        button = LbClient(clientIp,
                          remoteAdminUser=self.user,
                          passwd=self.passwd,
                          candidateLogin=self.client_lb_user,
                          parentApp=self,
                          test=True)
        button.setMinimumHeight(50)
        # button.installEventFilter(self)
        self.grid_layout.addWidget(button,
                                   self.grid_layout.count() / 4,
                                   self.grid_layout.count() % 4)
        self.clientFrame.setLayout(self.grid_layout)
        # QtGui.qApp.processEvents()

    def getExamPath(self):
        return self.lb_directory

    def getServerCredentialsByWizard(self):
        """
        open server config and login dialog, returns server object or None
        """
        wizard = EcLoginWizard(parent=self,
                               username=self.config.get("General",
                                                        "username",
                                                        fallback=""),
                               domain=self.config.get("General",
                                                      "domain",
                                                      fallback=""),
                               servername=self.config.get("General",
                                                          "lb_server",
                                                          fallback=""))

        wizard.setModal(True)
        result = wizard.exec_()
        print("I'm done, wizard result=" + str(result))
        if result == 1:
            self.config["General"]["username"] = wizard.field("username")
            self.config["General"]["domain"] = wizard.field("domainname")
            self.config["General"]["servername"] = wizard.server.serverName

            self.saveConfig()
            return wizard.server

        return None

    def selectExamByWizard(self):
        """
        provides ability to select serverName share plus logon credentials and lb directory using a wizard
        """

        if self.server == None or self.server.connect() is False:
            self.server = self.getServerCredentialsByWizard()

        wizard = EcShareWizard(parent=self,
                               server=self.server,
                               wizardType=EcShareWizard.TYPE_LB_SELECTION,
                               advanced_Ui=self.advancedUi)

        wizard.setModal(True)
        result = wizard.exec_()
        print("I'm done, wizard result=" + str(result))
        if result == 1:
            self.lb_directory = wizard.selectedPath
            self.setWindowTitle(self.appTitle + " - LB-Verzeichnis::" +
                                self.lb_directory.split("/")[-1])
            self.log("setup LB directory: " + self.lb_directory.split("/")[-1])
            self.btnPrepareExam.setEnabled(True)
            self.lblExamName.setText(self.lb_directory)
        else:
            self.log("no valid share selected")

    def saveExamLog(self):
        """
        on demand, store all client logs as PDF
        """
        if self.result_directory == None or len(self.result_directory) == 0:
            self.showMessageBox(
                "Fehler",
                "Ergebnispfad für Prüfungsdaten nicht gesetzt.<br>Bitte zuerst Prüfungsdaten abholen.",
                QMessageBox.Error)
            return

        clients = [
            self.grid_layout.itemAt(i).widget()
            for i in range(self.grid_layout.count())
        ]
        for client in clients:
            lb_dataDirectory = client.computer.lb_dataDirectory.split("#")[-1]
            pdfFileName = "protocol_" + date.today(
            ).__str__() + "_" + client.computer.getCandidateName().replace(
                " ", "_") + ".pdf"
            LogfileHandler(client.computer.logfile_name, client.computer.getCandidateName()). \
                createPdf(pdfFileName)
            if not (self.server.connect()):
                self.showMessageBox(
                    "Fehler",
                    "Verbindung zum Server kann nicht aufgebaut werden.",
                    QMessageBox.Error)
                return

            with open(pdfFileName, "rb") as file:
                sharename = self.result_directory.replace("##",
                                                          "").split("#")[1]
                destination = "/".join(
                    self.result_directory.replace(
                        "##",
                        "").split("#")[2:]) + "/" + lb_dataDirectory + "/"
                self.server.conn.storeFile(sharename,
                                           destination + pdfFileName, file)

    def __runLocalPowerShellAsRoot(self, command):
        """
        maybe useful at some stage again
        # see https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecutew#parameters
        :param command:
        :return:
        """

        retval = ctypes.windll.shell32.ShellExecuteW(
            None,
            "runas",  # runas admin,
            "C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
            # file to run
            command,  # actual powershell command
            None,
            0)  # last param disables popup powershell window...

        if retval != 42:
            self.log("ReturnCode after creating smbShare: " + str(retval))
            subprocess.run([
                "C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\powershell.exe",
                "Get-SmbShare"
            ])
            raise Exception("ReturnCode after running powershell: " +
                            str(retval))

    def __openFile(self):
        fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')
        if fname[0]:
            f = open(fname[0], 'r')
        with f:
            data = f.read()
            doc = QTextDocument(data, None)
            self.textEditLog.setDocument(doc)

    def saveConfig(self):
        """
        write to file what's currently set in config
        """
        if not (self.configFile.exists()):
            self.configFile.touch()

        self.config.write(open(self.configFile, 'w'))

    def eventFilter(self, currentObject, event):
        """
        unused, define mouseover events (with tooltips) for LbClient widgets
        """
        if event.type() == QEvent.Enter:
            if isinstance(currentObject, LbClient):
                print("Mouseover event catched")
                currentObject.setOwnToolTip()
                return True
            else:
                self.log(str(type(currentObject)) + " not recognized")

                # elif event.type() == QEvent.Leave:
        #    pass
        return False

    def showMessageBox(self,
                       title,
                       message,
                       messageType=QMessageBox.Information):
        """
        convinence wrapper
        """
        msg = QMessageBox(messageType, title, message, parent=self)
        if messageType != QMessageBox.Information:
            msg.setStandardButtons(QMessageBox.Abort)
        return msg.exec_()

    def showVersionInfo(self):
        info = "<b>Offizielles Release:</b><br>" + version
        try:
            # latest release - make sure to save this file before building :-)
            modDate = time.localtime(os.path.getmtime(__file__))
            info = info + "<br><br><b>Diese Version:</b><br>" + time.strftime(
                "%Y-%m-%d", modDate)
        except:
            pass

        self.showMessageBox("ECMan - Version", info)
Example #7
0
class StatComponent(QGroupBox):
    def __init__(self):
        super().__init__()

        self._series = {}
        self._legend_items = {}

        self.setTitle("Statistiques")
        self.setProperty("qss-var", "pb-0")

        self._total_label = QLabel()

        self._legend_layout = QGridLayout()
        self._legend_layout.setVerticalSpacing(0)
        self._legend_layout.setHorizontalSpacing(50)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self._total_label)
        main_layout.addLayout(self._legend_layout)
        main_layout.addSpacing(5)
        main_layout.addWidget(self._createLineChart(), 1)

        self.setLayout(main_layout)

    def reset(self, parameters: Parameters):
        self._parameters = parameters

        self._total_label.setText("Éponges détectées : 0")

        for i in reversed(range(self._legend_layout.count())):
            self._legend_layout.itemAt(i).widget().setParent(None)

        self._chart.removeAllSeries()
        self._legend_items = {}
        self._series = {}
        for i, m in parameters.selectedMorphotypes().items():
            self._series[i] = QtCharts.QLineSeries(self)
            self._series[i].setName(m.name())
            self._series[i].setColor(m.color())

            pen = QPen(m.color(), 3)
            pen.setCapStyle(Qt.RoundCap)
            self._series[i].setPen(pen)

            self._series[i].append(0, 0)
            self._chart.addSeries(self._series[i])

            self._legend_items[i] = ChartLegendItem(m.name(), m.color(), self)
            self._legend_items[i].toggled.connect(self._legendItemToggled)

        for i, k in enumerate(self._legend_items.keys()):
            row = i % 2 + 1
            col = i // 2 + 1

            self._legend_layout.addWidget(self._legend_items[k], row, col)

        axis_pen = QPen(Loader.QSSColor("@dark"))
        axis_pen.setWidth(2)

        grid_line_pen = QPen(Loader.QSSColor("@light-gray"))

        labels_font = QFont(Loader.QSSVariable("@font"))
        labels_font.setPointSize(10)

        self._chart.createDefaultAxes()

        for axis in (self._chart.axisX(), self._chart.axisY()):
            axis.setRange(0, 4)
            axis.setLinePen(axis_pen)
            axis.setGridLinePen(grid_line_pen)
            axis.setLabelFormat("%d")
            axis.setLabelsFont(labels_font)

    def _createLineChart(self) -> QtCharts.QChartView:
        self._chart = QtCharts.QChart()

        self._chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)

        self._chart.setTitle("Détections cumulées")
        self._chart.legend().setVisible(False)
        self._chart.setBackgroundBrush(QBrush(QColor("transparent")))
        self._chart.setMargins(QMargins(0, 0, 0, 0))

        title_font = QFont(Loader.QSSVariable("@font"))
        title_font.setPointSize(14)
        self._chart.setTitleFont(title_font)
        self._chart.setTitleBrush(QBrush(Loader.QSSColor("@dark")))

        chart_view = QtCharts.QChartView(self._chart)
        chart_view.setRenderHint(QPainter.Antialiasing)

        return chart_view

    def update(self, analysis: Analysis):
        self._analysis = analysis

        for class_id in self._parameters.selectedMorphotypes():
            last_idx = self._series[class_id].count() - 1

            new_x = self._series[class_id].at(last_idx).x() + 1
            new_y = analysis.cumulativeDetectionsFor(class_id)

            if last_idx > 1:
                if self._series[class_id].at(last_idx - 1).y(
                ) == new_y and self._series[class_id].at(
                        last_idx).y() == new_y:
                    self._series[class_id].replace(last_idx, new_x, new_y)
                    continue

            self._series[class_id].append(new_x, new_y)
            self._legend_items[class_id].setValue(str(new_y))

        self._total_label.setText("Éponges détectées : %d" %
                                  analysis.totalDetections())

        self._recalculateAxis()

    def _recalculateAxis(self):
        base_series = list(self._series.values())[0]

        last_idx = base_series.count() - 1
        x = base_series.at(last_idx).x()

        self._chart.axisX().setRange(0, max(x, 4))

        maxY = 0
        for k in self._series:
            if not self._legend_items[k].isChecked():
                continue

            maxY = max(maxY, self._analysis.cumulativeDetectionsFor(k))

        maxY = max(maxY, 4)

        # Add 5% to show the top series below the top line
        maxY = round(1.05 * maxY)

        self._chart.axisY().setRange(0, maxY)

    @Slot(bool)
    def _legendItemToggled(self, state: bool):
        for k, v in self._legend_items.items():
            if v == self.sender():
                self._series[k].setVisible(state)
                break

        self._recalculateAxis()
Example #8
0
class QBriefingWindow(QDialog):

    def __init__(self, gameEvent: Event):
        super(QBriefingWindow, self).__init__()
        self.gameEvent = gameEvent
        self.setWindowTitle("Briefing : " + str(gameEvent))
        self.setMinimumSize(200,200)
        self.setWindowIcon(CONST.EVENT_ICONS[self.gameEvent.__class__])
        self.setModal(True)
        self.game = self.gameEvent.game

        if self.gameEvent.attacker_name == self.game.player_name:
            self.base = self.gameEvent.from_cp.base
            self.playerFromCp = self.gameEvent.from_cp
        else:
            self.base = self.gameEvent.to_cp.base
            self.playerFromCp = self.gameEvent.to_cp

        self.scramble_entries = {k: {} for k in self.gameEvent.tasks}
        self.initUi()

    def initUi(self):

        self.layout = QVBoxLayout()

        self.depart_box = QGroupBox("Departure")
        self.depart_layout = QHBoxLayout()
        self.depart_box.setLayout(self.depart_layout)
        self.depart_from_label = QLabel("Depart from : ")
        self.depart_from = QComboBox()

        for i, cp in enumerate([b for b in self.game.theater.controlpoints if b.captured]):
            self.depart_from.addItem(str(cp.name), cp)
            if cp.name == self.playerFromCp.name:
                self.depart_from.setCurrentIndex(i)

        self.depart_from.currentTextChanged.connect(self.on_departure_cp_changed)
        self.depart_layout.addWidget(self.depart_from_label)
        self.depart_layout.addWidget(self.depart_from)

        # Mission Description
        self.gridLayout = QGridLayout()
        self.initUnitRows()
        self.scramble_box = QGroupBox("Units")
        self.scramble_box.setLayout(self.gridLayout)

        self.action_layout = QHBoxLayout()
        self.commit_button = QPushButton("Commit")
        self.back_button = QPushButton("Cancel")
        self.commit_button.clicked.connect(self.start)
        self.back_button.clicked.connect(self.close)
        self.action_layout.addWidget(self.commit_button)
        self.action_layout.addWidget(self.back_button)

        self.support_box = self.initSupportBox()
        self.layout.addWidget(QLabel("<h2>{} on {}</h2>".format(self.gameEvent, self.gameEvent.to_cp.name)))
        self.layout.addWidget(self.depart_box)
        self.layout.addWidget(self.scramble_box)
        self.layout.addWidget(self.support_box)
        self.layout.addWidget(QLabel("<b>Ready?</b>"))
        self.layout.addLayout(self.action_layout)
        self.setLayout(self.layout)

    def initUnitRows(self):

        row = 0

        def header(text, row):
            self.gridLayout.addWidget(QLabel("<b>" + text + "</b>"), row, 0, 1, 2)

        def scramble_row(task_type, unit_type, unit_count, client_slots: bool, row: int):
            unit_name = QLabel("{} ({})".format(db.unit_type_name(unit_type), unit_count))
            self.gridLayout.addWidget(unit_name, row, 0)

            scramble_entry = QSpinBox()
            self.gridLayout.addWidget(scramble_entry, row, 1)

            if client_slots:
                client_entry = QSpinBox()
                self.gridLayout.addWidget(client_entry, row, 2)
            else:
                client_entry = None

            self.scramble_entries[task_type][unit_type] = scramble_entry, client_entry

        # Table headers
        self.gridLayout.addWidget(QLabel("Amount"), row, 1)
        self.gridLayout.addWidget(QLabel("Client slots"), row, 2)
        row += 1

        for flight_task in self.gameEvent.tasks:
            header("{}:".format(self.gameEvent.flight_name(flight_task)), row)
            row += 1

            if flight_task == PinpointStrike:
                if not self.base.armor:
                    self.gridLayout.addWidget(QLabel("No units"), row, 1)
                    row += 1
                for t, c in self.base.armor.items():
                    scramble_row(flight_task, t, c, False, row)
                    row += 1
            else:
                if not self.base.aircraft:
                    self.gridLayout.addWidget(QLabel("No units"), row, 1)
                    row += 1
                for t, c in self.base.aircraft.items():
                    scramble_row(flight_task, t, c, t.flyable, row)
                    row += 1

        return self.gridLayout

    def initSupportBox(self):

        self.support_box = QGroupBox("Support")
        self.support_layout = QGridLayout()
        self.support_box.setLayout(self.support_layout)

        self.awacs_label = QLabel("AWACS ({}m)".format(AWACS_BUDGET_COST))
        self.awacs_checkbox = QCheckBox()

        self.ca_slot_label = QLabel("Combined Arms Slots")
        self.ca_slot_entry = QSpinBox()
        self.ca_slot_entry.setValue(0)
        self.ca_slot_entry.setMinimum(0)
        self.ca_slot_entry.setMaximum(32)

        self.support_layout.addWidget(self.awacs_label, 0, 0)
        self.support_layout.addWidget(self.awacs_checkbox, 0, 1)
        self.support_layout.addWidget(self.ca_slot_label, 1, 0)
        self.support_layout.addWidget(self.ca_slot_entry, 1, 1)
        return self.support_box


    def initWaitingForResults(self):

        layout = QVBoxLayout()

        layout.addWidget(QLabel("<b>You are clear for takeoff</b>"))
        layout.addWidget(QLabel("In DCS open and play the mission : "))
        layout.addWidget(QLabel("<i>liberation_nextturn</i>"))
        layout.addWidget(QLabel("or"))
        layout.addWidget(QLabel("<i>liberation_nextturn_quick</i>"))

        layout.addWidget(QLabel("<b>Then save the debriefing to folder :</b>"))
        layout.addWidget(QLabel("Then save the debriefing to the folder:"))
        layout.addWidget(QLabel("<i>" + self.debriefing_directory_location() + "</i>"))
        layout.addWidget(QLabel("Waiting for results..."))

        # layout.addWidget(QLabel("In DCS open and play the mission : "))
        # layout.addWidget(QLabel("<b>You are clear for takeoff</b>"))

        self.setLayout(layout)

        pass

    def debriefing_directory_location(self) -> str:
        return os.path.join(base_path(), "liberation_debriefings")

    def start(self):

        if self.awacs_checkbox.isChecked() == 1:
            self.gameEvent.is_awacs_enabled = True
            self.game.awacs_expense_commit()
        else:
            self.gameEvent.is_awacs_enabled = False

        ca_slot_entry_value = self.ca_slot_entry.value()
        try:
            ca_slots = int(ca_slot_entry_value and ca_slot_entry_value or "0")
        except:
            ca_slots = 0
        self.gameEvent.ca_slots = ca_slots


        # Resolve Departure CP
        self.gameEvent.departure_cp = self.depart_from.itemData(self.depart_from.currentIndex())


        flights = {k: {} for k in self.gameEvent.tasks}  # type: db.TaskForceDict
        units_scramble_counts = {}  # type: typing.Dict[typing.Type[UnitType], int]
        tasks_scramble_counts = {}  # type: typing.Dict[typing.Type[Task], int]
        tasks_clients_counts = {}  # type: typing.Dict[typing.Type[Task], int]

        def dampen_count(unit_type, count: int) -> int:
            nonlocal units_scramble_counts
            total_count = self.base.total_units_of_type(unit_type)

            total_scrambled = units_scramble_counts.get(unit_type, 0)
            dampened_value = count if count + total_scrambled < total_count else total_count - total_scrambled
            units_scramble_counts[unit_type] = units_scramble_counts.get(unit_type, 0) + dampened_value

            return dampened_value

        for task_type, dict in self.scramble_entries.items():
            for unit_type, (count_entry, clients_entry) in dict.items():
                try:
                    count = int(count_entry.value())
                except:
                    count = 0

                try:
                    clients_count = int(clients_entry and clients_entry.value() or 0)
                except:
                    clients_count = 0

                dampened_count = dampen_count(unit_type, count)
                tasks_clients_counts[task_type] = tasks_clients_counts.get(task_type, 0) + clients_count
                tasks_scramble_counts[task_type] = tasks_scramble_counts.get(task_type, 0) + dampened_count

                flights[task_type][unit_type] = dampened_count, clients_count

        for task in self.gameEvent.ai_banned_tasks:
            if tasks_clients_counts.get(task, 0) == 0 and tasks_scramble_counts.get(task, 0) > 0:
                self.showErrorMessage("Need at least one player in flight {}".format(self.gameEvent.flight_name(task)))
                return

        for task in self.gameEvent.player_banned_tasks:
            if tasks_clients_counts.get(task, 0) != 0:
                self.showErrorMessage("Players are not allowed on flight {}".format(self.gameEvent.flight_name(task)))
                return

        if self.game.is_player_attack(self.gameEvent):
            if isinstance(self.gameEvent, FrontlineAttackEvent):
                if self.base.total_armor == 0:
                    self.showErrorMessage("No ground vehicles available to attack!")
                    return

            self.gameEvent.player_attacking(flights)
        else:
            if isinstance(self.gameEvent, FrontlineAttackEvent):
                if self.gameEvent.to_cp.base.total_armor == 0:
                    self.showErrorMessage("No ground vehicles available to defend!")
                    return

            self.gameEvent.player_defending(flights)

        self.game.initiate_event(self.gameEvent)

        waiting = QWaitingForMissionResultWindow(self.gameEvent, self.game)
        waiting.show()

        self.close()

    def showErrorMessage(self, text):
        about = QMessageBox()
        about.setWindowTitle("Error")
        about.setIcon(QMessageBox.Icon.Critical)
        about.setText(text)
        about.exec_()

    def on_departure_cp_changed(self):

        selectedBase = self.depart_from.itemData(self.depart_from.currentIndex())

        for i, cp in enumerate([b for b in self.game.theater.controlpoints if b.captured]):
            if cp.name == selectedBase.name:
                self.base = cp.base
                self.playerFromCp = cp
                break

        # Clear current selection
        self.scramble_entries = {k: {} for k in self.gameEvent.tasks}

        # Clear the grid layout
        for i in reversed(range(self.gridLayout.count())):
            self.gridLayout.itemAt(i).widget().setParent(None)

        # Rebuild the grid layout, so that it correspond to the newly selected CP
        self.initUnitRows()
Example #9
0
class QWaitingForMissionResultWindow(QDialog):
    def __init__(self, gameEvent: Event, game: Game,
                 unit_map: UnitMap) -> None:
        super(QWaitingForMissionResultWindow, self).__init__()
        self.setModal(True)
        self.gameEvent = gameEvent
        self.game = game
        self.unit_map = unit_map
        self.setWindowTitle("Waiting for mission completion.")
        self.setWindowIcon(QIcon("./resources/icon.png"))
        self.setMinimumHeight(570)

        self.initUi()
        DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(
            self.updateLayout)
        self.wait_thread = wait_for_debriefing(
            lambda debriefing: self.on_debriefing_update(debriefing),
            self.game, self.unit_map)

    def initUi(self):
        self.layout = QGridLayout()

        header = QLabel(self)
        header.setGeometry(0, 0, 655, 106)
        pixmap = QPixmap("./resources/ui/conflict.png")
        header.setPixmap(pixmap)
        self.layout.addWidget(header, 0, 0)

        self.gridLayout = QGridLayout()

        jinja = Environment(
            loader=FileSystemLoader("resources/ui/templates"),
            autoescape=select_autoescape(
                disabled_extensions=("", ),
                default_for_string=True,
                default=True,
            ),
            trim_blocks=True,
            lstrip_blocks=True,
        )
        self.instructions_text = QTextBrowser()
        self.instructions_text.setHtml(
            jinja.get_template("mission_start_EN.j2").render())
        self.instructions_text.setOpenExternalLinks(True)
        self.gridLayout.addWidget(self.instructions_text, 1, 0)

        progress = QLabel("")
        progress.setAlignment(QtCore.Qt.AlignCenter)
        progress_bar = QMovie("./resources/ui/loader.gif")
        progress.setMovie(progress_bar)

        self.actions = QGroupBox("Actions :")
        self.actions_layout = QHBoxLayout()
        self.actions.setLayout(self.actions_layout)

        self.manually_submit = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit.clicked.connect(self.submit_manually)
        self.actions_layout.addWidget(self.manually_submit)
        self.cancel = QPushButton("Abort mission")
        self.cancel.clicked.connect(self.close)
        self.actions_layout.addWidget(self.cancel)
        self.gridLayout.addWidget(self.actions, 2, 0)

        self.actions2 = QGroupBox("Actions :")
        self.actions2_layout = QHBoxLayout()
        self.actions2.setLayout(self.actions2_layout)
        self.manually_submit2 = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit2.clicked.connect(self.submit_manually)
        self.actions2_layout.addWidget(self.manually_submit2)
        self.cancel2 = QPushButton("Abort mission")
        self.cancel2.clicked.connect(self.close)
        self.actions2_layout.addWidget(self.cancel2)
        self.proceed = QPushButton("Accept results")
        self.proceed.setProperty("style", "btn-success")
        self.proceed.clicked.connect(self.process_debriefing)
        self.actions2_layout.addWidget(self.proceed)

        progress_bar.start()
        self.layout.addLayout(self.gridLayout, 1, 0)
        self.setLayout(self.layout)

    def updateLayout(self, debriefing: Debriefing) -> None:
        updateBox = QGroupBox("Mission status")
        updateLayout = QGridLayout()
        updateBox.setLayout(updateLayout)
        self.debriefing = debriefing

        updateLayout.addWidget(QLabel("<b>Aircraft destroyed</b>"), 0, 0)
        updateLayout.addWidget(
            QLabel(str(len(list(debriefing.air_losses.losses)))), 0, 1)

        updateLayout.addWidget(QLabel("<b>Front line units destroyed</b>"), 1,
                               0)
        updateLayout.addWidget(
            QLabel(str(len(list(debriefing.front_line_losses)))), 1, 1)

        updateLayout.addWidget(QLabel("<b>Other ground units destroyed</b>"),
                               2, 0)
        updateLayout.addWidget(
            QLabel(str(len(list(debriefing.ground_object_losses)))), 2, 1)

        updateLayout.addWidget(QLabel("<b>Buildings destroyed</b>"), 3, 0)
        updateLayout.addWidget(
            QLabel(str(len(list(debriefing.building_losses)))), 3, 1)

        updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 4, 0)
        updateLayout.addWidget(
            QLabel(str(len(debriefing.base_capture_events))), 4, 1)

        # Clear previous content of the window
        for i in reversed(range(self.gridLayout.count())):
            try:
                self.gridLayout.itemAt(i).widget().setParent(None)
            except:
                logging.exception("Failed to clear window")

        # Set new window content
        self.gridLayout.addWidget(updateBox, 0, 0)

        if not debriefing.state_data.mission_ended:
            self.gridLayout.addWidget(QLabel("<b>Mission is being played</b>"),
                                      1, 0)
            self.gridLayout.addWidget(self.actions, 2, 0)
        else:
            self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0)
            self.gridLayout.addWidget(self.actions2, 2, 0)

    def on_debriefing_update(self, debriefing: Debriefing) -> None:
        try:
            logging.info("On Debriefing update")
            logging.debug(debriefing)
            DebriefingFileWrittenSignal.get_instance().sendDebriefing(
                debriefing)
        except Exception:
            logging.exception("Got an error while sending debriefing")
        self.wait_thread = wait_for_debriefing(
            lambda d: self.on_debriefing_update(d), self.game, self.unit_map)

    def process_debriefing(self):
        self.game.finish_event(event=self.gameEvent,
                               debriefing=self.debriefing)
        self.game.pass_turn()

        GameUpdateSignal.get_instance().sendDebriefing(self.debriefing)
        GameUpdateSignal.get_instance().updateGame(self.game)
        self.close()

    def debriefing_directory_location(self) -> str:
        return os.path.join(base_path(), "liberation_debriefings")

    def closeEvent(self, evt):
        super(QWaitingForMissionResultWindow, self).closeEvent(evt)
        if self.wait_thread is not None:
            self.wait_thread.stop()

    def submit_manually(self):
        file = QFileDialog.getOpenFileName(self,
                                           "Select game file to open",
                                           filter="json(*.json)",
                                           dir=".")
        print(file)
        try:
            with open(file[0], "r") as json_file:
                json_data = json.load(json_file)
                json_data["mission_ended"] = True
                debriefing = Debriefing(json_data, self.game, self.unit_map)
                self.on_debriefing_update(debriefing)
        except Exception as e:
            logging.error(e)
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setText("Invalid file : " + file[0])
            msg.setWindowTitle("Invalid file.")
            msg.setStandardButtons(QMessageBox.Ok)
            msg.setWindowFlags(Qt.WindowStaysOnTopHint)
            msg.exec_()
            return
class ChannelArithmeticDialog(ieb.ImarisExtensionBase):
    """
    Channel Arithmetic and Beyond
    =============================
    `View on GitHub <https://github.com/niaid/imaris_extensions>`_

    This program enables one to specify arithmetic expressions which are used to
    create new channels. The basic arithmetic operations are supported: +,-,*,/,**.
    More advanced operations that run short `SimpleITK <https://simpleitk.org/>`_
    code snippets are also supported.

    Channels are referenced using square brackets and the channel index, starting
    at **zero**. To apply an expression to all channels, use the channel index 'i'.

    When creating a single new channel, the arithmetic expression consists of literal
    channel numbers, one can select a name and color for the new channel. When
    creating multiple new channels, the arithmetic expression is applied to all channels,
    the postfix '_modified' is appended to the original channel names and the original
    color is copied over. Note that for all channels created by the program the
    channel description will include the arithmetic expression used to create that
    channel. This transparently supports your efforts to conduct reproducible
    research.

    Because an Imaris image has a specific pixel type (8, 16, 32 bit unsigned integer
    and 32 bit floating point) all computations are performed using a 32 bit floating
    point representation and then clamped to the range of the image's pixel type.

    The program allows you to use the same expression on multiple files. In this
    case literal channel values are limited by the number of shared channels. Thus,
    if working with two files one with three channels and one with four channels,
    the valid literal channel values are limited to 0, 1, 2. We cannot use 3 as it does not
    exist in all files. On the other hand, if our autofluorescence channel is one
    of these channels, e.g. channel 0, we can subtract it from all channels in
    both files, `[i]-[0]`.

    Basic Examples
    --------------

    Multiply channels zero and three:

    .. code-block:: Python

      [0]*[3]

    Multiply channels zero and three and subtract the result from channel four:

    .. code-block:: Python

      [4] - ([0]*[3])

    Duplicate all channels:

    .. code-block:: Python

      [i]

    Subtract channel zero from all channels:

    .. code-block:: Python

      [i]-[0]


    Advanced Examples
    -----------------

    Threshold channel one using a value of 100, resulting image is binary
    values in {0,1}:

    .. code-block:: Python

      [1]>100

    Threshold a specific channel to create a binary result using the Otsu
    filter:

    .. code-block:: Python

      sitk.OtsuThreshold([1], 0, 1)

    Threshold a specific channel retaining the values above the threshold:

    .. code-block:: Python

      sitk.Cast([1]>100, sitk.sitkFloat32)*[1]

    Threshold a specific channel, get all connected components, then
    sort the components according to size, discarding those smaller than a minimum
    size and create a binary mask corresponding to the largest component, which is
    the first label(second largest component label is 2 etc.)

    .. code-block:: Python

      sitk.RelabelComponent(sitk.ConnectedComponent([1]>100), minimumObjectSize = 50)==1

    Create a binary mask representing the colocalization of two channels,
    intensity values below 20 are considred noise:

    .. code-block:: Python

      ([1]>20)*([2]>20)

    Create a binary mask representing the colocalization of two channels.
    We are interested in all pixels in channel 2 that have a value above 20
    and that are less than 1.0um away from pixels in channel 1 that have a value
    above 100 (**note**: this operation yields different results when run using
    a slice-by-slice approach vs. a volumetric approach):

    .. code-block:: Python

        (sitk.Cast([2]>20, sitk.sitkFloat32) *
         sitk.Abs(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)))<=1.0

    Create a binary mask using thresholding and then perform morphological
    closing (dilation followed by erosion) with distance maps, useful
    for filling holes:

    .. code-block:: Python

      sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < 1.0, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<-1.0

    Create a binary mask using thresholding and then perform morphological
    opening (erosion followed by dilation) with distance maps, useful
    for removing small islands:

    .. code-block:: Python

      sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < -0.2, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<0.2
    """  # noqa

    def __init__(self):
        super(ChannelArithmeticDialog, self).__init__()
        # Channel indexes in the arithmetic calculator are denoted using a
        # regular expression: one or more digits in square brackets (e.g. [1234]).
        # First digit is zero and nothing afterwards or first digit is in [1-9] and
        # there are possibly more digits afterwards.
        # Starting index is zero.
        self.channel_pattern = re.compile(r"\[(0|[1-9]\d*)\]")

        # Use QT's global threadpool, documentation says: "This global thread pool
        # automatically maintains an optimal number of threads based on the
        # number of cores in the CPU."
        self.threadpool = QThreadPool.globalInstance()

        # Configure the help dialog.
        self.help_dialog = HelpDialog(w=700, h=500)
        self.help_dialog.setWindowTitle("Channel Arithmetic Help")
        self.help_dialog.set_rst_text(
            inspect.getdoc(self), pygments_css_file_name="pygments_dark.css")

        self.__create_gui()
        self.setWindowTitle("Channel Arithmetic")
        self.processing_error = False

        self.show()

    def __create_gui(self):
        menu_bar = self.menuBar()
        # Force menubar to be displayed in the application on OSX/Linux, otherwise it
        # is displayed in the system menubar
        menu_bar.setNativeMenuBar(False)
        self.help_button = QPushButton("Help")
        self.help_button.clicked.connect(self.help_dialog.show)
        menu_bar.setCornerWidget(self.help_button, Qt.TopLeftCorner)

        central_widget = QWidget(self)
        gui_layout = QVBoxLayout()
        central_widget.setLayout(gui_layout)
        self.setCentralWidget(central_widget)

        select_files_widget = self.__create_select_files_widget()
        arithmetic_widget = self.__create_arithmetic_widget()

        self.stack = QStackedWidget(self)
        self.stack.addWidget(select_files_widget)
        self.stack.addWidget(arithmetic_widget)
        gui_layout.addWidget(self.stack)

        self.status_bar = self.statusBar()

    def closeEvent(self, event):
        """
        Override the closeEvent method so that clicking the 'x' button also
        closes all of the dialogs.
        """
        self.help_dialog.close()
        event.accept()

    def __create_arithmetic_widget(self):
        wid = QWidget(self)
        arithmetic_layout = QVBoxLayout()
        wid.setLayout(arithmetic_layout)

        self.valid_indexes_label = QLabel("")
        arithmetic_layout.addWidget(self.valid_indexes_label)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignLeft)
        layout.addWidget(QLabel("Enter new channel arithmetic expression:"))
        arithmetic_layout.addLayout(layout)

        self.arithmetic_expression_text_edit = QTextEdit()
        arithmetic_layout.addWidget(self.arithmetic_expression_text_edit)

        self.slice_by_slice_checkbox = QCheckBox(
            "Slice by slice (smaller memory footprint).")
        arithmetic_layout.addWidget(self.slice_by_slice_checkbox)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("New channel name:"))
        self.new_channel_name_line_edit = QLineEdit()
        layout.addWidget(self.new_channel_name_line_edit)
        arithmetic_layout.addLayout(layout)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("New channel color:"))
        self.new_channel_color_button = QPushButton()
        self.new_channel_color_button.clicked.connect(
            self.__select_color_callback)
        layout.addWidget(self.new_channel_color_button)
        arithmetic_layout.addLayout(layout)

        self.apply_button = QPushButton("Apply")
        self.apply_button.clicked.connect(self.__channel_arithmetic_wrapper)
        arithmetic_layout.addWidget(self.apply_button)

        progress_wid = QWidget()
        self.progress_grid_layout = QGridLayout()
        progress_wid.setLayout(self.progress_grid_layout)
        scroll_area = QScrollArea()
        scroll_area.setWidget(progress_wid)
        scroll_area.setWidgetResizable(True)
        arithmetic_layout.addWidget(scroll_area)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignLeft)
        self.processing_prev_button = QPushButton("Prev")
        self.processing_prev_button.clicked.connect(
            lambda: self.stack.setCurrentIndex(0))
        layout.addWidget(self.processing_prev_button)
        arithmetic_layout.addLayout(layout)

        return wid

    def __configure_and_show_arithmetic_widget(self):
        file_names = self.input_files_edit.toPlainText().split("\n")
        num_channels = []
        problematic_images = []
        for file_name in file_names:
            try:
                meta_data = sio.read_metadata(file_name)
                num_channels.append(len(meta_data["channels_information"]))
            except Exception:
                problematic_images.append(file_name)
        if problematic_images:
            self._error_function(
                "Problem encountered reading the following file(s):\n" +
                "\n".join(problematic_images))
            return
        self.max_channel_index = min(num_channels) - 1
        self.valid_indexes_label.setText(
            f"Valid channel indexes: 0...{self.max_channel_index}, i")
        self.arithmetic_expression_text_edit.clear()
        self.slice_by_slice_checkbox.setChecked(False)
        self.new_channel_name_line_edit.clear()

        # Remove all widgets from layout, done in reverse order because
        # removing from the begining shifts the rest of the items
        for i in reversed(range(self.progress_grid_layout.count())):
            self.progress_grid_layout.itemAt(i).widget().setParent(None)

        for i, file_name in enumerate(file_names):
            self.progress_grid_layout.addWidget(
                QLabel(os.path.basename(file_name)), i, 0)
            progress_bar = QProgressBar()
            progress_bar.setMaximum(100)
            self.progress_grid_layout.addWidget(progress_bar, i, 1)

        self.stack.setCurrentIndex(1)

    def __create_select_files_widget(self):
        wid = QWidget()
        input_layout = QVBoxLayout()
        wid.setLayout(input_layout)

        layout = QHBoxLayout()
        layout.addWidget(QLabel("File names:"))
        layout.setAlignment(Qt.AlignLeft)
        button = QPushButton("Browse")
        button.setToolTip("Select input files for arithmetic operation.")
        button.clicked.connect(self.__browse_select_input_callback)
        layout.addWidget(button)
        input_layout.addLayout(layout)

        self.input_files_edit = QTextEdit()
        self.input_files_edit.setReadOnly(True)
        input_layout.addWidget(self.input_files_edit)

        layout = QHBoxLayout()
        layout.setAlignment(Qt.AlignRight)
        self.input_files_next_button = QPushButton("Next")
        self.input_files_next_button.setEnabled(False)
        self.input_files_next_button.clicked.connect(
            self.__configure_and_show_arithmetic_widget)
        layout.addWidget(self.input_files_next_button)
        input_layout.addLayout(layout)

        return wid

    def __browse_select_input_callback(self):
        file_names, _ = QFileDialog.getOpenFileNames(
            self,
            "QFileDialog.getOpenFileNames()",
            "",
            "Imaris Images (*.ims);;All Files (*)",
        )
        if file_names:
            self.input_files_edit.setText("\n".join(file_names))
            self.input_files_next_button.setEnabled(True)

    def __select_color_callback(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.new_channel_color_button.setStyleSheet(
                f"background-color :rgb({color.red()},{color.green()},{color.blue()})"
            )

    def __channel_arithmetic_wrapper(self):
        # Get the arithmetic expression after removing all whitespace
        arithmetic_expression = "".join(
            self.arithmetic_expression_text_edit.toPlainText().split())
        color = self.new_channel_color_button.palette().button().color()

        if arithmetic_expression:
            # Get the explicit channel indexes that appear in the expression and
            # check that they are in the valid range.
            channel_indexes = re.findall(self.channel_pattern,
                                         arithmetic_expression)
            invalid_channels = [
                ci for ci in channel_indexes
                if int(ci) not in range(self.max_channel_index + 1)
            ]
            if invalid_channels:
                self._error_function(
                    "The following channels specified in the arithmetic expression"
                    +
                    f" are outside the valid range [0,{self.max_channel_index}]: "
                    + ", ".join(invalid_channels))
                return

            # Disable the UI interaction during computation
            self.arithmetic_expression_text_edit.setReadOnly(True)
            self.slice_by_slice_checkbox.setEnabled(False)
            self.new_channel_name_line_edit.setReadOnly(True)
            self.new_channel_color_button.setEnabled(False)
            self.apply_button.setEnabled(False)
            self.processing_prev_button.setEnabled(False)

            QApplication.setOverrideCursor(Qt.WaitCursor)
            file_names = self.input_files_edit.toPlainText().split("\n")
            self.num_threads_left = len(file_names)
            for i, input_file_name in enumerate(file_names):
                # Configure and perform computation in another thread.
                arithmetic_calculator = ArithmeticCalculator(
                    self.channel_pattern)
                arithmetic_calculator.signals.finished.connect(
                    self.__arithmetic_finished)
                arithmetic_calculator.signals.processing_error.connect(
                    self._processing_error_function)
                arithmetic_calculator.signals.progress_signal.connect(
                    self.progress_grid_layout.itemAtPosition(
                        i, 1).widget().setValue)
                arithmetic_calculator.signals.update_state_signal.connect(
                    self.status_bar.showMessage)
                arithmetic_calculator.input_file_name = input_file_name
                arithmetic_calculator.arithmetic_expression = arithmetic_expression
                arithmetic_calculator.new_channel_color = [
                    color.red() / 255.0,
                    color.green() / 255.0,
                    color.blue() / 255.0,
                ]
                arithmetic_calculator.new_channel_alpha = color.alpha() / 255.0
                arithmetic_calculator.new_channel_name = (
                    self.new_channel_name_line_edit.text().strip())
                arithmetic_calculator.slice_by_slice = (
                    self.slice_by_slice_checkbox.isChecked())
                self.threadpool.start(arithmetic_calculator)
        else:
            self._error_function(
                "No action taken: arithmetic expression not set.")

    def __arithmetic_finished(self):
        self.num_threads_left = self.num_threads_left - 1
        if self.num_threads_left == 0:
            QApplication.restoreOverrideCursor()
            self.status_bar.clearMessage()
            for i in range(self.progress_grid_layout.rowCount()):
                self.progress_grid_layout.itemAtPosition(
                    i, 1).widget().setValue(0)
            # Enable the UI interaction after computation
            self.arithmetic_expression_text_edit.setReadOnly(False)
            self.slice_by_slice_checkbox.setEnabled(True)
            self.new_channel_name_line_edit.setReadOnly(False)
            self.new_channel_color_button.setEnabled(True)
            self.apply_button.setEnabled(True)
            self.processing_prev_button.setEnabled(True)

            # Inform the user that the calculations completed. If processing errors
            # occured then the desired operation may not have happened, but the
            # calculation was completed.
            QMessageBox().information(self, "Message",
                                      "Calculation completed.")
            self.processing_error = False
Example #11
0
class ObjectToolBox(QWidget):
    object_icon_clicked: SignalInstance = Signal(ObjectIcon)
    object_placed: SignalInstance = Signal(ObjectIcon)

    def __init__(self, parent: Optional[QWidget] = None):
        super(ObjectToolBox, self).__init__(parent)

        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)

        self._layout = QGridLayout(self)
        self._layout.setAlignment(Qt.AlignCenter)

        self._layout.setAlignment(Qt.AlignHCenter)

    def add_object(self,
                   level_object: Union[EnemyItem, LevelObject],
                   index: int = -1):
        icon = ObjectIcon(level_object)

        icon.clicked.connect(self._on_icon_clicked)
        icon.object_placed.connect(lambda: self.object_placed.emit(icon))

        if index == -1:
            index = self._layout.count()

        self._layout.addWidget(icon, index // 2, index % 2)

    def add_from_object_set(self,
                            object_set_index: int,
                            graphic_set_index: int = -1):
        if graphic_set_index == -1:
            graphic_set_index = object_set_index

        factory = LevelObjectFactory(object_set_index,
                                     graphic_set_index,
                                     0, [],
                                     vertical_level=False,
                                     size_minimal=True)

        object_ids = list(range(0x00, 0x10)) + list(
            range(0x10, MAX_ID_VALUE, 0x10))

        for domain, obj_index in product(range(MAX_DOMAIN + 1), object_ids):
            level_object = factory.from_properties(domain=domain,
                                                   object_index=obj_index,
                                                   x=0,
                                                   y=0,
                                                   length=None,
                                                   index=0)

            if not isinstance(level_object,
                              LevelObject) or level_object.name in [
                                  "MSG_NOTHING", "MSG_CRASH"
                              ]:
                continue

            self.add_object(level_object)

    def add_from_enemy_set(self, object_set_index: int):
        factory = EnemyItemFactory(object_set_index, 0)

        for obj_index in range(MAX_ENEMY_ITEM_ID + 1):
            enemy_item = factory.from_properties(obj_index, x=0, y=0)

            if enemy_item.name in ["MSG_NOTHING", "MSG_CRASH"]:
                continue

            self.add_object(enemy_item)

    def clear(self):
        self._extract_objects()

    def _on_icon_clicked(self):
        self.object_icon_clicked.emit(self.sender())

    @property
    def draw_background_color(self):
        return self._layout.itemAt(0).draw_background_color

    @draw_background_color.setter
    def draw_background_color(self, value):
        for index in range(self._layout.count()):
            self._layout.itemAt(index).draw_background_color = value

    def has_object(self, level_object):
        return self.index_of_object(level_object) != -1

    def index_of_object(self, level_object):
        for index in range(self._layout.count()):
            if self._layout.itemAtPosition(index // 2, index %
                                           2).widget().object == level_object:
                return index
        else:
            return -1

    def _extract_objects(self):
        objects = []

        while True:
            item = self._layout.takeAt(0)

            if item is None:
                break
            else:
                objects.append(item.widget().object)
                item.widget().deleteLater()

        return objects

    def place_at_front(self, level_object):
        objects = self._extract_objects()

        if level_object in objects:
            objects.remove(level_object)

        objects.insert(0, level_object)

        assert self._layout.count() == 0

        for obj in objects:
            self.add_object(obj)
Example #12
0
class CodePreview_Widget(QWidget):
    def __init__(self):
        super(CodePreview_Widget, self).__init__()

        self.text_edit = CodePreview_TextEdit()
        self.node_instance = None
        self.buttons_obj_dict = {}
        self.active_class_index = -1
        self.edited_codes = {}

        settings_layout = QHBoxLayout()

        info_and_SH_layout = QVBoxLayout()

        # info label
        info_label = QLabel('Click on edit for more info!')
        info_label.setFont(QFont('Poppins', 8))
        info_and_SH_layout.addWidget(info_label)

        # syntax highlighting
        self.syntax_highlighting_check_box = QCheckBox('syntax highlighting (alpha)')
        self.syntax_highlighting_check_box.toggled.connect(self.syntax_highlighting_toggled)
        self.syntax_highlighting_check_box.setChecked(True)
        info_and_SH_layout.addWidget(self.syntax_highlighting_check_box)

        settings_layout.addLayout(info_and_SH_layout)

        # class radio buttons widget
        self.class_selection_layout = QGridLayout()

        settings_layout.addLayout(self.class_selection_layout)
        settings_layout.setAlignment(self.class_selection_layout, Qt.AlignRight)

        # edit source code buttons
        edit_buttons_layout = QVBoxLayout()
        self.edit_code_button = QPushButton('edit')
        self.edit_code_button.setMaximumWidth(100)
        self.edit_code_button.clicked.connect(self.edit_code_button_clicked)
        self.override_code_button = QPushButton('override')
        self.override_code_button.setMaximumWidth(100)
        self.override_code_button.setEnabled(False)
        self.override_code_button.clicked.connect(self.override_code_button_clicked)
        self.reset_code_button = QPushButton('reset')
        self.reset_code_button.setMaximumWidth(206)
        self.reset_code_button.setEnabled(False)
        self.reset_code_button.clicked.connect(self.reset_code_button_clicked)
        edit_buttons_top_layout = QHBoxLayout()
        edit_buttons_top_layout.addWidget(self.edit_code_button)
        edit_buttons_top_layout.addWidget(self.override_code_button)
        edit_buttons_layout.addLayout(edit_buttons_top_layout)
        edit_buttons_layout.addWidget(self.reset_code_button)

        settings_layout.addLayout(edit_buttons_layout)


        main_layout = QVBoxLayout()
        main_layout.addLayout(settings_layout)
        main_layout.addWidget(self.text_edit)
        self.setLayout(main_layout)

        self.set_new_NI(None)

    def set_new_NI(self, ni):
        self.disable_editing()

        self.rebuild_class_selection(ni)
        self.update_edit_status()

        self.node_instance = ni

        if ni is None:  # no NI selected
            self.text_edit.set_code('')
            self.edit_code_button.setEnabled(False)
            self.override_code_button.setEnabled(False)
            self.reset_code_button.setEnabled(False)
            return
        self.edit_code_button.setEnabled(True)

        self.update_code()

    def update_code(self):

        self.disable_editing()

        if self.active_class_index == -1 or self.node_instance is None:
            return

        if self.get_current_code_obj() not in self.edited_codes:
            self.text_edit.set_code(inspect.getsource(self.get_current_code_class()))
            self.reset_code_button.setEnabled(False)
        else:
            self.text_edit.set_code(self.edited_codes[self.get_current_code_obj()])
            self.reset_code_button.setEnabled(True)

    def get_current_code_class(self):
        return self.get_current_code_obj().__class__

    def get_current_code_obj(self):
        return list(self.buttons_obj_dict.values())[self.active_class_index]

    def rebuild_class_selection(self, obj):
        # clear layout
        for i in range(self.class_selection_layout.count()):
            item = self.class_selection_layout.itemAt(0)
            widget = item.widget()
            widget.hide()
            self.class_selection_layout.removeItem(item)

        self.buttons_obj_dict = {}
        self.active_class_index = -1

        if find_type_in_object(obj, NodeInstance):
            # NI class
            node_inst_class_RB = QRadioButton('NodeInstance')
            node_inst_class_RB.toggled.connect(self.class_RB_toggled)
            self.buttons_obj_dict[node_inst_class_RB] = obj
            self.class_selection_layout.addWidget(node_inst_class_RB, 0, 0)

            # main_widget class
            if obj.main_widget is not None:
                main_widget_class_RB = QRadioButton('MainWidget')
                main_widget_class_RB.toggled.connect(self.class_RB_toggled)
                self.buttons_obj_dict[main_widget_class_RB] = obj.main_widget
                self.class_selection_layout.addWidget(main_widget_class_RB, 1, 0)

            # data input widgets
            row_count = 0
            for inp in obj.inputs:
                if inp.widget is not None:
                    inp_widget_class_RB = QRadioButton('Input '+str(obj.inputs.index(inp)))
                    inp_widget_class_RB.toggled.connect(self.class_RB_toggled)
                    self.buttons_obj_dict[inp_widget_class_RB] = inp.widget
                    self.class_selection_layout.addWidget(inp_widget_class_RB, row_count, 1)
                    row_count += 1

            node_inst_class_RB.setChecked(True)

    def update_edit_status(self):
        for o in list(self.buttons_obj_dict.keys()):
            if self.edited_codes.keys().__contains__(self.buttons_obj_dict[o]):
                o.setStyleSheet('color: #3B9CD9;')
                f = o.font()
                f.setBold(True)
                o.setFont(f)
            else:
                o.setStyleSheet('color: white;')
                f = o.font()
                f.setBold(False)
                o.setFont(f)

    def class_RB_toggled(self, checked):
        if checked:
            self.active_class_index = list(self.buttons_obj_dict.keys()).index(self.sender())
            self.update_code()

    def syntax_highlighting_toggled(self):
        if self.syntax_highlighting_check_box.isChecked():
            self.text_edit.enable_highlighting()
        else:
            self.text_edit.disable_highlighting()

    def edit_code_button_clicked(self):
        info_dialog = EditSourceCode_Dialog(self)
        accepted = info_dialog.exec_()
        if accepted:
            self.enable_editing()

    def enable_editing(self):
        self.text_edit.enable_editing()
        self.override_code_button.setEnabled(True)

    def disable_editing(self):
        self.text_edit.disable_editing()
        self.override_code_button.setEnabled(False)

    def override_code_button_clicked(self):
        new_code = self.text_edit.get_code()
        override_code(self.get_current_code_obj(), new_code)
        self.disable_editing()

        self.edited_codes[self.get_current_code_obj()] = new_code
        self.reset_code_button.setEnabled(True)
        self.update_edit_status()

    def reset_code_button_clicked(self):
        code = inspect.getsource(self.get_current_code_class())
        override_code(self.get_current_code_obj(), code)
        del self.edited_codes[self.get_current_code_obj()]
        self.update_code()
        self.update_edit_status()
Example #13
0
class CommandWidget(TabWidgetExtension, QWidget):
    """Output for running queue"""

    # log state
    __log = False
    insertTextSignal = Signal(str, dict)
    updateCommandSignal = Signal(str)
    cliButtonsSateSignal = Signal(bool)
    cliValidateSignal = Signal(bool)
    resetSignal = Signal()

    def __init__(self, parent=None, proxyModel=None, controlQueue=None, log=None):
        super(CommandWidget, self).__init__(parent=parent, tabWidgetChild=self)

        self.__log = log
        self.__output = None
        self.__rename = None
        self.__tab = None

        # self.oCommand = MKVCommand()
        self.algorithm = None
        self.oCommand = MKVCommandParser()
        self.controlQueue = controlQueue
        self.parent = parent
        self.proxyModel = proxyModel
        self.model = proxyModel.sourceModel()
        self.outputWindow = QOutputTextWidget(self)
        self.log = log

        self._initControls()
        self._initUI()
        self._initHelper()

    def _initControls(self):
        #
        # command line
        #
        self.frmCmdLine = QFormLayout()
        btnPasteClipboard = QPushButtonWidget(
            Text.txt0164,
            function=lambda: qtRunFunctionInThread(self.pasteClipboard),
            margins="  ",
            toolTip=Text.txt0165,
        )
        self.cmdLine = QLineEdit()
        self.cmdLine.setValidator(
            ValidateCommand(self, self.cliValidateSignal, log=self.log)
        )
        self.frmCmdLine.addRow(btnPasteClipboard, self.cmdLine)
        self.frmCmdLine.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        self.command = QWidget()
        self.command.setLayout(self.frmCmdLine)

        #
        # Button group definition
        #
        self.btnGroup = QGroupBox()
        self.btnGrid = QGridLayout()

        btnAddCommand = QPushButtonWidget(
            Text.txt0160,
            function=lambda: self.addCommand(JobStatus.Waiting),
            margins="  ",
            toolTip=Text.txt0161,
        )
        btnRename = QPushButtonWidget(
            Text.txt0182,
            function=self.parent.renameWidget.setAsCurrentTab,
            margins="  ",
            toolTip=Text.txt0183,
        )
        btnAddQueue = QPushButtonWidget(
            Text.txt0166,
            function=lambda: self.addCommand(JobStatus.AddToQueue),
            margins="  ",
            toolTip=Text.txt0167,
        )
        btnStartQueue = QPushButtonWidget(
            Text.txt0126,
            function=self.parent.jobsQueue.run,
            margins="  ",
            toolTip=Text.txt0169,
        )
        btnAnalysis = QPushButtonWidget(
            Text.txt0170,
            function=lambda: qtRunFunctionInThread(
                runAnalysis,
                command=self.cmdLine.text(),
                output=self.output,
                log=self.log,
            ),
            margins="  ",
            toolTip=Text.txt0171,
        )
        btnShowCommands = QPushButtonWidget(
            Text.txt0172,
            function=lambda: qtRunFunctionInThread(
                showCommands,
                output=self.output,
                command=self.cmdLine.text(),
                oCommand=self.oCommand,
                log=self.log,
            ),
            margins="  ",
            toolTip=Text.txt0173,
        )
        btnCheckFiles = QPushButtonWidget(
            Text.txt0174,
            function=lambda: qtRunFunctionInThread(
                checkFiles,
                output=self.output,
                command=self.cmdLine.text(),
                oCommand=self.oCommand,
                log=self.log,
            ),
            margins="  ",
            toolTip=Text.txt0175,
        )
        btnClear = QPushButtonWidget(
            Text.txt0176,
            function=self.clearOutputWindow,
            margins="  ",
            toolTip=Text.txt0177,
        )
        btnReset = QPushButtonWidget(
            Text.txt0178,
            function=self.reset,
            margins="  ",
            toolTip=Text.txt0179,
        )

        self.btnGrid.addWidget(btnAddCommand, 0, 0)
        self.btnGrid.addWidget(btnRename, 0, 1)
        self.btnGrid.addWidget(btnAddQueue, 1, 0)
        self.btnGrid.addWidget(btnStartQueue, 1, 1)
        self.btnGrid.addWidget(HorizontalLine(), 2, 0, 1, 2)
        self.btnGrid.addWidget(btnAnalysis, 3, 0)
        self.btnGrid.addWidget(btnShowCommands, 3, 1)
        self.btnGrid.addWidget(btnCheckFiles, 4, 0)
        self.btnGrid.addWidget(HorizontalLine(), 5, 0, 1, 2)
        self.btnGrid.addWidget(btnClear, 6, 0)
        self.btnGrid.addWidget(btnReset, 6, 1)
        self.btnGroup.setLayout(self.btnGrid)

        self.btnGroupBox = QGroupBox()
        self.btnHBox = QHBoxLayout()

        self.lblAlgorithm = QLabelWidget(
            Text.txt0094,
            textSuffix=":  ",
        )
        self.rbZero = QRadioButton("0", self)
        self.rbOne = QRadioButton("1", self)
        self.rbTwo = QRadioButton("2", self)

        btnDefaultAlgorithm = QPushButtonWidget(
            Text.txt0092,
            function=self.setDefaultAlgorithm,
            margins="  ",
            toolTip=Text.txt0093,
        )

        self.radioButtons = [self.rbZero, self.rbOne, self.rbTwo]

        self.btnHBox.addWidget(self.lblAlgorithm)
        self.btnHBox.addWidget(self.rbZero)
        self.btnHBox.addWidget(self.rbOne)
        self.btnHBox.addWidget(self.rbTwo)
        self.btnHBox.addWidget(btnDefaultAlgorithm)
        self.btnGroupBox.setLayout(self.btnHBox)

    def _initUI(self):

        grid = QGridLayout()
        grid.addWidget(self.command, 0, 0, 1, 2)
        grid.addWidget(self.btnGroupBox, 1, 0)
        grid.addWidget(self.btnGroup, 2, 0)
        grid.addWidget(self.outputWindow, 2, 1, 10, 1)

        self.setLayout(grid)

    def _initHelper(self):
        #
        # Signal interconnections
        #

        # local button state connect to related state
        self.parent.jobsQueue.addQueueItemSignal.connect(
            lambda: self.jobStartQueueState(True)
        )
        self.parent.jobsQueue.queueEmptiedSignal.connect(
            lambda: self.jobStartQueueState(False)
        )

        # job related
        self.parent.jobsQueue.runJobs.startSignal.connect(lambda: self.jobStatus(True))
        self.parent.jobsQueue.runJobs.finishedSignal.connect(
            lambda: self.jobStatus(False)
        )

        # map insertText signal to outputWidget one
        self.insertText = self.outputWindow.insertTextSignal

        # command
        self.updateCommandSignal.connect(self.updateCommand)
        self.cliButtonsSateSignal.connect(self.cliButtonsState)
        self.cliValidateSignal.connect(self.cliValidate)

        #
        # button state
        #

        # Command related
        # self.frmCmdLine.itemAt(0, QFormLayout.LabelRole).widget().setEnabled(False)
        self.cliButtonsState(False)
        self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False)

        # Clear buttons related
        self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False)
        self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False)

        # connect text windows textChanged to clearButtonState function
        self.outputWindow.textChanged.connect(self.clearButtonState)

        # connect command line textChanged to analysisButtonState function
        self.cmdLine.textChanged.connect(self.analysisButtonState)

        # Job Queue related
        self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False)

        # Job Added to Queue
        self.parent.jobsQueue.addQueueItemSignal.connect(self.printJobIDAdded)

        #
        # Misc
        #
        self.cmdLine.setClearButtonEnabled(True)  # button at end of line to clear it

        # Algorithm radio buttons
        self.rbZero.toggled.connect(lambda: self.toggledRadioButton(self.rbZero))
        self.rbOne.toggled.connect(lambda: self.toggledRadioButton(self.rbOne))
        self.rbTwo.toggled.connect(lambda: self.toggledRadioButton(self.rbTwo))

        self.setDefaultAlgorithm()

    @classmethod
    def classLog(cls, setLogging=None):
        """
        get/set logging at class level
        every class instance will log
        unless overwritten

        Args:
            setLogging (bool):
                - True class will log
                - False turn off logging
                - None returns current Value

        Returns:
            bool:

            returns the current value set
        """

        if setLogging is not None:
            if isinstance(setLogging, bool):
                cls.__log = setLogging

        return cls.__log

    @property
    def log(self):
        """
        class property can be used to override the class global
        logging setting

        Returns:
            bool:

            True if logging is enable False otherwise
        """
        if self.__log is not None:
            return self.__log

        return CommandWidget.classLog()

    @log.setter
    def log(self, value):
        """set instance log variable"""

        if isinstance(value, bool) or value is None:
            self.__log = value
            # No variable used so for now use class log
            ValidateCommand.classLog(value)
            self.outputWindow.log = value

    @property
    def output(self):
        return self.__output

    @output.setter
    def output(self, value):
        self.__output = value

    @property
    def rename(self):
        return self.__rename

    @rename.setter
    def rename(self, value):
        if isinstance(value, object):
            self.__rename = value

    @Slot(list)
    def applyRename(self, renameFiles):

        if self.oCommand:
            self.oCommand.renameOutputFiles(renameFiles)

    @Slot(bool)
    def cliButtonsState(self, validateOK):
        """
        cliButtonsState change enabled status for buttons related with command line

        Args:
            validateOK (bool): True to enable, False to disable
        """

        for b in [
            _Button.ADDCOMMAND,
            _Button.RENAME,
            _Button.ADDQUEUE,
            _Button.SHOWCOMMANDS,
            _Button.CHECKFILES,
        ]:
            button = self.btnGrid.itemAt(b).widget()
            button.setEnabled(validateOK)

    @Slot(bool)
    def cliValidate(self, validateOK):
        """
        cliValidate Slot used by ValidateCommnad

        Args:
            validateOK (bool): True if command line is Ok.  False otherwise.
        """

        if validateOK:
            self.output.command.emit(
                "Command looks ok.\n", {LineOutput.AppendEnd: True}
            )
        else:
            if self.cmdLine.text() != "":
                self.output.command.emit("Bad command.\n", {LineOutput.AppendEnd: True})

        self.cliButtonsState(validateOK)
        self.updateObjCommnad(validateOK)

    @Slot(bool)
    def jobStartQueueState(self, state):

        if state and not isThreadRunning(config.WORKERTHREADNAME):
            self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(True)
        else:
            self.btnGrid.itemAt(_Button.STARTQUEUE).widget().setEnabled(False)

    @Slot(bool)
    def updateObjCommnad(self, valid):
        """Update the command object"""

        if valid:
            self.oCommand.command = self.cmdLine.text()
            if self.rename is not None:
                self.rename.setFilesSignal.emit(self.oCommand)
                self.rename.applyFileRenameSignal.connect(self.applyRename)
        else:
            self.oCommand.command = ""
            if self.rename is not None:
                self.rename.clear()

    @Slot(str)
    def updateCommand(self, command):
        """Update command input widget"""

        self.cmdLine.clear()
        self.cmdLine.setText(command)
        self.cmdLine.setCursorPosition(0)

    @Slot(int)
    def updateAlgorithm(self, algorithm):

        if 0 <= algorithm < len(self.radioButtons):
            self.radioButtons[algorithm].setChecked(True)

    @Slot(bool)
    def jobStatus(self, running):
        """
        jobStatus receive Signals for job start/end

        Args:
            running (bool): True if job started. False if ended.
        """

        if running:
            self.jobStartQueueState(False)
            palette = QPalette()
            color = checkColor(
                QColor(42, 130, 218), config.data.get(config.ConfigKey.DarkMode)
            )
            palette.setColor(QPalette.WindowText, color)
            self.parent.jobsLabel.setPalette(palette)
        else:
            palette = QPalette()
            color = checkColor(None, config.data.get(config.ConfigKey.DarkMode))
            palette.setColor(QPalette.WindowText, color)
            self.parent.jobsLabel.setPalette(palette)

    def addCommand(self, status):
        """
        addCommand add command row in jobs table

        Args:
            status (JobStatus): Status for job to be added should be either
                                JobStatus.Waiting or JobStatus.AddToQueue
        """

        totalJobs = self.model.rowCount()
        command = self.cmdLine.text()
        # [cell value, tooltip, obj]
        data = [
            ["", "", self.algorithm],
            [status, "Status code", None],
            [command, command, self.oCommand],
        ]
        self.model.insertRows(totalJobs, 1, data=data)
        self.cmdLine.clear()

    def analysisButtonState(self):
        """Set clear button state"""

        if self.cmdLine.text() != "":
            self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(True)
        else:
            self.btnGrid.itemAt(_Button.ANALYSIS).widget().setEnabled(False)

    def clearButtonState(self):
        """Set clear button state"""

        if self.outputWindow.toPlainText() != "":
            self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(True)
        else:
            self.btnGrid.itemAt(_Button.CLEAR).widget().setEnabled(False)

    def clearOutputWindow(self):
        """
        clearOutputWindow clear the command output window
        """

        language = config.data.get(config.ConfigKey.Language)
        bAnswer = False

        # Clear output window?
        title = "Clear output"
        msg = "¿" if language == "es" else ""
        msg += "Clear output window" + "?"
        bAnswer = yesNoDialog(self, msg, title)

        if bAnswer:
            self.outputWindow.clear()

    def printJobIDAdded(self, index):

        jobID = self.model.dataset[index.row(), index.column()]

        self.output.command.emit(
            f"Job: {jobID} added to Queue...\n", {LineOutput.AppendEnd: True}
        )

    def pasteClipboard(self):
        """Paste clipboard to command QLineEdit"""

        clip = QApplication.clipboard().text()

        if clip:
            self.output.command.emit(
                "Checking command...\n", {LineOutput.AppendEnd: True}
            )
            self.update()
            self.updateCommandSignal.emit(clip)

    def reset(self):
        """
        reset program status
        """

        language = config.data.get(config.ConfigKey.Language)

        if not isThreadRunning(config.WORKERTHREADNAME):

            language = config.data.get(config.ConfigKey.Language)
            bAnswer = False

            # Clear output window?
            title = "Reset"
            msg = "¿" if language == "es" else ""
            msg += "Reset Application" + "?"
            bAnswer = yesNoDialog(self, msg, title)

            if bAnswer:
                self.cmdLine.clear()
                self.outputWindow.clear()
                self.output.jobOutput.clear()
                self.output.errorOutput.clear()
                self.resetSignal.emit()

        else:
            messageBox(self, "Reset", "Jobs are running..")

    def resetButtonState(self):
        """Set clear button state"""

        if self.output.jobOutput.toPlainText() != "":
            self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(True)
        else:
            self.btnGrid.itemAt(_Button.RESET).widget().setEnabled(False)

    def setDefaultAlgorithm(self):
        #
        # Algorithm
        #
        if config.data.get(config.ConfigKey.Algorithm) is not None:
            currentAlgorithm = config.data.get(config.ConfigKey.Algorithm)
            self.radioButtons[currentAlgorithm].setChecked(True)

    def setLanguage(self):
        """
        setLanguage language use in buttons/labels to be called by MainWindow
        """

        for index in range(self.frmCmdLine.rowCount()):
            widget = self.frmCmdLine.itemAt(index, QFormLayout.LabelRole).widget()
            if isinstance(widget, QPushButtonWidget):
                widget.setLanguage()
                # widget.setText("  " + _(widget.originalText) + "  ")
                # widget.setToolTip(_(widget.toolTip))

        for index in range(self.btnHBox.count()):
            widget = self.btnHBox.itemAt(index).widget()
            if isinstance(
                widget,
                (
                    QLabelWidget,
                    QPushButtonWidget,
                ),
            ):
                widget.setLanguage()

        for index in range(self.btnGrid.count()):
            widget = self.btnGrid.itemAt(index).widget()
            if isinstance(widget, QPushButtonWidget):
                widget.setLanguage()
                # widget.setText("  " + _(widget.originalText) + "  ")
                # widget.setToolTip(_(widget.toolTip))

    def toggledRadioButton(self, rButton):
        for index, rb in enumerate(self.radioButtons):
            if rb.isChecked():
                self.algorithm = index
class QWaitingForMissionResultWindow(QDialog):
    def __init__(self, gameEvent: Event, game: Game):
        super(QWaitingForMissionResultWindow, self).__init__()
        self.setModal(True)
        self.gameEvent = gameEvent
        self.game = game
        self.setWindowTitle("Waiting for mission completion.")
        self.setWindowIcon(QIcon("./resources/icon.png"))
        self.setMinimumHeight(570)

        self.initUi()
        DebriefingFileWrittenSignal.get_instance().debriefingReceived.connect(
            self.updateLayout)
        self.wait_thread = wait_for_debriefing(
            lambda debriefing: self.on_debriefing_udpate(debriefing),
            self.game)

    def initUi(self):
        self.layout = QGridLayout()

        header = QLabel(self)
        header.setGeometry(0, 0, 655, 106)
        pixmap = QPixmap("./resources/ui/conflict.png")
        header.setPixmap(pixmap)
        self.layout.addWidget(header, 0, 0)

        self.gridLayout = QGridLayout()
        TEXT = "" + \
                "<b>You are clear for takeoff</b>" + \
                "" + \
                "<h2>For Singleplayer :</h2>\n" + \
                "In DCS, open the Mission Editor, and load the file : \n" + \
                "<i>liberation_nextturn</i>\n" + \
                "<p>Then once the mission is loaded in ME, in menu \"Flight\",\n" + \
                "click on FLY Mission to launch.</p>\n" + \
                "" + \
                "<h2>For Multiplayer :</h2>" + \
                "In DCS, open the Mission Editor, and load the file : " + \
                "<i>liberation_nextturn</i>" + \
                "<p>Click on File/Save. Then exit the mission editor, and go to Multiplayer.</p>" + \
                "<p>Then host a server with the mission, and tell your friends to join !</p>" + \
                "<i>(The step in the mission editor is important, and fix a game breaking bug.)</i>" + \
                "<h2>Finishing</h2>" + \
                "<p>Once you have played the mission, click on the \"Accept Results\" button.</p>" + \
                "<p>If DCS Liberation does not detect mission end, use the manually submit button, and choose the state.json file.</p>"

        self.instructions_text = QTextEdit(TEXT)
        self.instructions_text.setReadOnly(True)
        self.gridLayout.addWidget(self.instructions_text, 1, 0)

        progress = QLabel("")
        progress.setAlignment(QtCore.Qt.AlignCenter)
        progress_bar = QMovie("./resources/ui/loader.gif")
        progress.setMovie(progress_bar)

        self.actions = QGroupBox("Actions :")
        self.actions_layout = QHBoxLayout()
        self.actions.setLayout(self.actions_layout)

        self.manually_submit = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit.clicked.connect(self.submit_manually)
        self.actions_layout.addWidget(self.manually_submit)
        self.cancel = QPushButton("Abort mission")
        self.cancel.clicked.connect(self.close)
        self.actions_layout.addWidget(self.cancel)
        self.gridLayout.addWidget(self.actions, 2, 0)

        self.actions2 = QGroupBox("Actions :")
        self.actions2_layout = QHBoxLayout()
        self.actions2.setLayout(self.actions2_layout)
        self.manually_submit2 = QPushButton("Manually Submit [Advanced users]")
        self.manually_submit2.clicked.connect(self.submit_manually)
        self.actions2_layout.addWidget(self.manually_submit2)
        self.cancel2 = QPushButton("Abort mission")
        self.cancel2.clicked.connect(self.close)
        self.actions2_layout.addWidget(self.cancel2)
        self.proceed = QPushButton("Accept results")
        self.proceed.setProperty("style", "btn-success")
        self.proceed.clicked.connect(self.process_debriefing)
        self.actions2_layout.addWidget(self.proceed)

        progress_bar.start()
        self.layout.addLayout(self.gridLayout, 1, 0)
        self.setLayout(self.layout)

    def updateLayout(self, debriefing):
        updateBox = QGroupBox("Mission status")
        updateLayout = QGridLayout()
        updateBox.setLayout(updateLayout)
        self.debriefing = debriefing

        updateLayout.addWidget(QLabel("<b>Aircraft destroyed</b>"), 0, 0)
        updateLayout.addWidget(QLabel(str(len(debriefing.killed_aircrafts))),
                               0, 1)

        updateLayout.addWidget(QLabel("<b>Ground units destroyed</b>"), 1, 0)
        updateLayout.addWidget(
            QLabel(str(len(debriefing.killed_ground_units))), 1, 1)

        #updateLayout.addWidget(QLabel("<b>Weapons fired</b>"), 2, 0)
        #updateLayout.addWidget(QLabel(str(len(debriefing.weapons_fired))), 2, 1)

        updateLayout.addWidget(QLabel("<b>Base Capture Events</b>"), 2, 0)
        updateLayout.addWidget(
            QLabel(str(len(debriefing.base_capture_events))), 2, 1)

        # Clear previous content of the window
        for i in reversed(range(self.gridLayout.count())):
            try:
                self.gridLayout.itemAt(i).widget().setParent(None)
            except:
                pass

        # Set new window content
        self.gridLayout.addWidget(updateBox, 0, 0)

        if not debriefing.mission_ended:
            self.gridLayout.addWidget(QLabel("<b>Mission is being played</b>"),
                                      1, 0)
            self.gridLayout.addWidget(self.actions, 2, 0)
        else:
            self.gridLayout.addWidget(QLabel("<b>Mission is over</b>"), 1, 0)
            self.gridLayout.addWidget(self.actions2, 2, 0)

    def on_debriefing_udpate(self, debriefing):
        try:
            logging.info("On Debriefing update")
            print(debriefing)
            DebriefingFileWrittenSignal.get_instance().sendDebriefing(
                debriefing)
        except Exception as e:
            logging.error("Got an error while sending debriefing")
            logging.error(e)
        self.wait_thread = wait_for_debriefing(
            lambda debriefing: self.on_debriefing_udpate(debriefing),
            self.game)

    def process_debriefing(self):
        self.game.finish_event(event=self.gameEvent,
                               debriefing=self.debriefing)
        self.game.pass_turn(ignored_cps=[
            self.gameEvent.to_cp,
        ])

        GameUpdateSignal.get_instance().sendDebriefing(self.game,
                                                       self.gameEvent,
                                                       self.debriefing)
        self.close()

    def debriefing_directory_location(self) -> str:
        return os.path.join(base_path(), "liberation_debriefings")

    def closeEvent(self, evt):
        super(QWaitingForMissionResultWindow, self).closeEvent(evt)
        if self.wait_thread is not None:
            self.wait_thread.stop()

    def submit_manually(self):
        file = QFileDialog.getOpenFileName(self,
                                           "Select game file to open",
                                           filter="json(*.json)",
                                           dir=".")
        print(file)
        try:
            with open(file[0], "r") as json_file:
                json_data = json.load(json_file)
                json_data["mission_ended"] = True
                debriefing = Debriefing(json_data, self.game)
                self.on_debriefing_udpate(debriefing)
        except Exception as e:
            logging.error(e)
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Information)
            msg.setText("Invalid file : " + file[0])
            msg.setWindowTitle("Invalid file.")
            msg.setStandardButtons(QMessageBox.Ok)
            msg.setWindowFlags(Qt.WindowStaysOnTopHint)
            msg.exec_()
            return
Example #15
0
class QGroundObjectTemplateLayout(QGroupBox):
    close_dialog_signal = Signal()

    def __init__(
            self,
            game: Game,
            ground_object: TheaterGroundObject,
            layout: QTgoLayout,
            layout_changed_signal: Signal(QTgoLayout),
            current_group_value: int,
    ):
        super().__init__()
        # Connect to the signal to handle template updates
        self.game = game
        self.ground_object = ground_object
        self.layout_changed_signal = layout_changed_signal
        self.layout_model = layout
        self.layout_changed_signal.connect(self.load_for_layout)

        self.current_group_value = current_group_value

        self.buy_button = QPushButton("Buy")
        self.buy_button.setEnabled(False)
        self.buy_button.clicked.connect(self.buy_group)

        self.template_layout = QGridLayout()
        self.setLayout(self.template_layout)

        self.template_grid = QGridLayout()
        self.template_layout.addLayout(self.template_grid, 0, 0, 1, 2)
        self.template_layout.addWidget(self.buy_button, 1, 1)
        stretch = QVBoxLayout()
        stretch.addStretch()
        self.template_layout.addLayout(stretch, 2, 0)

        # Load Layout
        self.load_for_layout(self.layout_model)

    def load_for_layout(self, layout: QTgoLayout) -> None:
        self.layout_model = layout
        # Clean the current grid
        self.layout_model.groups = defaultdict(list)
        for id in range(self.template_grid.count()):
            self.template_grid.itemAt(id).widget().deleteLater()
        for group_name, groups in self.layout_model.layout.groups.items():
            self.add_theater_group(group_name, self.layout_model.force_group,
                                   groups)
        self.group_template_changed()

    @property
    def cost(self) -> int:
        return self.layout_model.price - self.current_group_value

    @property
    def affordable(self) -> bool:
        return self.cost <= self.game.blue.budget

    def add_theater_group(self, group_name: str, force_group: ForceGroup,
                          groups: list[TgoLayoutGroup]) -> None:
        group_box = QGroupBox(group_name)
        vbox_layout = QVBoxLayout()
        for group in groups:
            try:
                group_row = QTgoLayoutGroupRow(force_group, group)
            except LayoutException:
                continue
            self.layout_model.groups[group_name].append(group_row.group_layout)
            group_row.group_template_changed.connect(
                self.group_template_changed)
            vbox_layout.addWidget(group_row)
        group_box.setLayout(vbox_layout)
        self.template_grid.addWidget(group_box)

    def group_template_changed(self) -> None:
        price = self.layout_model.price
        self.buy_button.setText(
            f"Buy [${price}M][-${self.current_group_value}M]")
        self.buy_button.setEnabled(self.affordable)
        if self.buy_button.isEnabled():
            self.buy_button.setToolTip(f"Buy the group for ${self.cost}M")
        else:
            self.buy_button.setToolTip("Not enough money to buy this group")

    def buy_group(self) -> None:
        if not self.affordable:
            # Something went wrong. Buy button should be disabled!
            logging.error("Not enough money to buy the group")
            return

        # Change the heading of the new group to head to the conflict
        self.ground_object.heading = (
            self.game.theater.heading_to_conflict_from(
                self.ground_object.position) or self.ground_object.heading)
        self.game.blue.budget -= self.cost
        self.ground_object.groups = []
        for group_name, groups in self.layout_model.groups.items():
            for group in groups:
                self.layout_model.force_group.create_theater_group_for_tgo(
                    self.ground_object,
                    group.layout,
                    f"{self.ground_object.name} ({group_name})",
                    self.game,
                    group.dcs_unit_type,  # Forced Type
                    group.amount,  # Forced Amount
                )
        self.close_dialog_signal.emit()