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)
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))
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 ))
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)))
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)
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)
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()
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()
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
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)
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()
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
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()