class MainWindow(MainWindowGUI): """Wrap the GUI and add functional behaviour to it.""" def __init__(self): super().__init__() self.change_action.triggered.connect( lambda: self.table.edit_item(self.table.current_item())) self.copy_action.triggered.connect(self.copy_table_item_text) self.resize_connection = ReconnectingSignal( self.table.itemChanged, self.manage_item_changed, ) self.resize_connection.connect() self._current_route_type: t.Optional[type[ExactPlotRow] | type[NeutronPlotRow]] = None self._last_index = None self.restore_window() self.retranslate() self._current_row_index = 0 def copy_table_item_text(self) -> None: """Copy the text of the selected table item into the clipboard.""" if (item := self.table.current_item()) is not None: QtWidgets.QApplication.instance().clipboard().set_text(item.text())
class ShutDownWindow(ShutDownWindowGUI): """ Window displayed after the user reached a shut down. The user is given a choice to select a new journal to resome with, to quit, or to save their route. """ new_journal_signal = QtCore.Signal(Journal) def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) self._selected_journal = None self._journals = [] self.journal_changed_signal = ReconnectingSignal( self.journal_combo.currentIndexChanged, self._change_journal) self.journal_changed_signal.connect() self.new_journal_button.pressed.connect( lambda: self.new_journal_signal.emit(self._selected_journal)) self.quit_button.pressed.connect( QtWidgets.QApplication.instance().quit) self._populate_journal_combos() self.retranslate() def _populate_journal_combos(self) -> None: """ Populate the combo boxes with CMDR names referring to latest active journal files. The journals they're referring to are stored in `self._journals`. """ font_metrics = self.journal_combo.font_metrics() combo_items = [] self._journals = get_unique_cmdr_journals() for journal in self._journals: combo_items.append( font_metrics.elided_text( cmdr_display_name(journal.cmdr), QtCore.Qt.TextElideMode.ElideRight, 80, )) with self.journal_changed_signal.temporarily_disconnect(): self.journal_combo.clear() self.journal_combo.add_items(combo_items) def _change_journal(self, index: int) -> None: """Change the selected journal, enable/disable the button depending on its shut down state.""" journal = self._journals[index] self.new_journal_button.enabled = not journal.shut_down self._selected_journal = journal def change_event(self, event: QtCore.QEvent) -> None: """Retranslate the GUI when a language change occurs.""" if event.type() == QtCore.QEvent.LanguageChange: self.retranslate() def retranslate(self) -> None: """Retranslate text that is always on display.""" with self.journal_changed_signal.temporarily_disconnect(): super().retranslate()
def __init__( self, exception_handler: ExceptionHandler, theme_listener: WinThemeChangeListener, ): super().__init__() self.window = MainWindow() self.error_window = ErrorWindow(self.window) self.error_window.save_button.pressed.connect(self.save_route) Updater(self.window).check_update() exception_handler.triggered.connect(self.error_window.show) exception_handler.set_parent(self) self._theme_listener = theme_listener self._theme_listener.theme_changed.connect(self.set_theme_from_os) self._theme_listener.set_parent(self) self.window.show() self.window.about_action.triggered.connect(self.display_license_window) self.window.new_route_action.triggered.connect(self.new_route_window) self.window.settings_action.triggered.connect(self.display_settings) self.window.save_action.triggered.connect(self.save_route) self.window.table.doubleClicked.connect(self.get_index_row) self.plotter_state = PlotterState(self) self.plotter_state.new_system_signal.connect(self.new_system_callback) self.plotter_state.route_end_signal.connect( partial(self.new_system_callback, None)) self.plotter_state.shut_down_signal.connect( self.display_shut_down_window) self.apply_settings() self.edit_route_update_connection = ReconnectingSignal( self.window.table.itemChanged, self.update_route_from_edit) self.edit_route_update_connection.connect() if (not JOURNAL_PATH.exists() or not list(JOURNAL_PATH.glob("Journal.*.log")) or not (JOURNAL_PATH / "Status.json").exists()): # If the journal folder is missing, force the user to quit MissingJournalWindow(self.window).show() return self.fuel_warner = FuelWarn(self, self.window) self.warn_worker = StatusWorker(self) self.warn_worker.status_signal.connect(self.fuel_warner.warn) self.new_route_window() atexit.register(self.save_on_exit)
def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) self._selected_journal = None self._journals = [] self.journal_changed_signal = ReconnectingSignal( self.journal_combo.currentIndexChanged, self._change_journal) self.journal_changed_signal.connect() self.new_journal_button.pressed.connect( lambda: self.new_journal_signal.emit(self._selected_journal)) self.quit_button.pressed.connect( QtWidgets.QApplication.instance().quit) self._populate_journal_combos() self.retranslate()
def __init__(self): super().__init__() self.change_action.triggered.connect( lambda: self.table.edit_item(self.table.current_item())) self.copy_action.triggered.connect(self.copy_table_item_text) self.resize_connection = ReconnectingSignal( self.table.itemChanged, self.manage_item_changed, ) self.resize_connection.connect() self._current_route_type: t.Optional[type[ExactPlotRow] | type[NeutronPlotRow]] = None self._last_index = None self.restore_window() self.retranslate() self._current_row_index = 0
class Hub(QtCore.QObject): """Manage windows and communication between them and workers.""" def __init__( self, exception_handler: ExceptionHandler, theme_listener: WinThemeChangeListener, ): super().__init__() self.window = MainWindow() self.error_window = ErrorWindow(self.window) self.error_window.save_button.pressed.connect(self.save_route) Updater(self.window).check_update() exception_handler.triggered.connect(self.error_window.show) exception_handler.set_parent(self) self._theme_listener = theme_listener self._theme_listener.theme_changed.connect(self.set_theme_from_os) self._theme_listener.set_parent(self) self.window.show() self.window.about_action.triggered.connect(self.display_license_window) self.window.new_route_action.triggered.connect(self.new_route_window) self.window.settings_action.triggered.connect(self.display_settings) self.window.save_action.triggered.connect(self.save_route) self.window.table.doubleClicked.connect(self.get_index_row) self.plotter_state = PlotterState(self) self.plotter_state.new_system_signal.connect(self.new_system_callback) self.plotter_state.route_end_signal.connect( partial(self.new_system_callback, None)) self.plotter_state.shut_down_signal.connect( self.display_shut_down_window) self.apply_settings() self.edit_route_update_connection = ReconnectingSignal( self.window.table.itemChanged, self.update_route_from_edit) self.edit_route_update_connection.connect() if (not JOURNAL_PATH.exists() or not list(JOURNAL_PATH.glob("Journal.*.log")) or not (JOURNAL_PATH / "Status.json").exists()): # If the journal folder is missing, force the user to quit MissingJournalWindow(self.window).show() return self.fuel_warner = FuelWarn(self, self.window) self.warn_worker = StatusWorker(self) self.warn_worker.status_signal.connect(self.fuel_warner.warn) self.new_route_window() atexit.register(self.save_on_exit) def new_route_window(self) -> None: """Display the `NewRouteWindow` and connect its signals.""" logging.info("Displaying new route window.") route_window = NewRouteWindow(self.window) route_window.route_created_signal.connect(self.new_route) route_window.show() def update_route_from_edit(self, table_item: QtWidgets.QTableWidgetItem) -> None: """Edit the plotter's route with the new data in `table_item`.""" log.debug( f"Updating info from edited item at x={table_item.row()} y={table_item.column()}." ) self.plotter_state.route[table_item.row()][ table_item.column()] = table_item.data( QtCore.Qt.ItemDataRole.DisplayRole) if table_item.row() == self.plotter_state.route_index: self.plotter_state.route_index = self.plotter_state.route_index def new_system_callback(self, _: t.Any, index: int) -> None: """Ensure we don't edit the route when inactivating rows.""" with self.edit_route_update_connection.temporarily_disconnect(): self.window.set_current_row(index) def get_index_row(self, index: QtCore.QModelIndex) -> None: """Set the current route index to `index`'s row.""" log.debug("Setting route index after user interaction.") self.plotter_state.route_index = index.row() def new_route(self, journal: Journal, route: RouteList = None, route_index: int = None) -> None: """Create a new worker with `route`, populate the main table with it, and set the route index.""" if route is None: logging.debug("Using current plotter route.") route = self.plotter_state.route if route_index is None: logging.debug("Using current plotter index.") route_index = self.plotter_state.route_index if route_index >= len(route): route_index = len(route) - 1 logging.debug( f"Creating a new {type(route[0]).__name__} route with {route_index=}." ) self.plotter_state.journal = journal self.plotter_state.create_worker_with_route(route) if self.plotter_state.plotter is None: if settings.General.copy_mode: self.plotter_state.plotter = CopyPlotter() else: self.plotter_state.plotter = AhkPlotter() with self.edit_route_update_connection.temporarily_disconnect(): self.window.initialize_table(route) self.plotter_state.route_index = route_index self.window.scroll_to_index(route_index) if journal.location is not None: # may not have a location yet self.plotter_state.tail_worker.emit_next_system(journal.location) self.warn_worker.start() self.fuel_warner.set_journal(journal) def apply_settings(self) -> None: """Update the appearance and plotter with new settings.""" log.debug("Refreshing settings.") self.window.table.font = settings.Window.font if settings.Window.dark_mode is Theme.OS_THEME: dark = self._theme_listener.dark_theme else: dark = settings.Window.dark_mode is Theme.DARK_THEME set_theme(dark) if self.plotter_state.plotter is not None: current_sys = self.plotter_state.route[ self.plotter_state.route_index] if settings.General.copy_mode and not isinstance( self.plotter_state.plotter, CopyPlotter): self.plotter_state.plotter = CopyPlotter( start_system=current_sys.system) elif not settings.General.copy_mode and not isinstance( self.plotter_state.plotter, AhkPlotter): self.plotter_state.plotter = AhkPlotter( start_system=current_sys.system) else: self.plotter_state.plotter.refresh_settings() new_locale = babel.Locale.parse(settings.General.locale) if new_locale != auto_neutron.locale.get_active_locale(): auto_neutron.locale.set_active_locale(new_locale) app = QtWidgets.QApplication.instance() app.post_event(app, QtCore.QEvent(QtCore.QEvent.LanguageChange)) def display_settings(self) -> None: """Display the settings window and connect the applied signal to refresh appearance.""" log.info("Displaying settings window.") window = SettingsWindow(self.window) window.settings_applied.connect(self.apply_settings) window.show() def display_shut_down_window(self) -> None: """Display the shut down window and connect it to create a new route and save the current one.""" log.info("Displaying shut down window.") window = ShutDownWindow(self.window) window.new_journal_signal.connect(self.new_route) window.save_route_button.pressed.connect(self.save_route) window.show() def display_license_window(self) -> None: """Display the license window.""" log.info("Displaying license window.") window = LicenseWindow(self.window) window.show() def set_theme_from_os(self, dark: bool) -> None: """Set the current theme to the OS' theme, if the theme setting is set to follow the OS.""" if settings.Window.dark_mode is Theme.OS_THEME: set_theme(dark) def save_on_exit(self) -> None: """Save necessary settings when exiting.""" with delay_sync(): settings.Window.geometry = self.window.save_geometry() if settings.General.save_on_quit: self.save_route() def save_route(self) -> None: """If route auto saving is enabled, or force is True, save the route to the config directory.""" if self.plotter_state.route is not None: log.info("Saving route.") with open(get_config_dir() / ROUTE_FILE_NAME, "w", encoding="utf8", newline="") as out_file: route_type = type(self.plotter_state.route[0]) writer = csv.writer(out_file, quoting=csv.QUOTE_ALL) if route_type is NeutronPlotRow: writer.writerow([ "System Name", "Distance To Arrival", "Distance Remaining", "Neutron Star", "Jumps", ]) else: writer.writerow([ "System Name", "Distance", "Distance Remaining", "Fuel Left", "Fuel Used", "Refuel", "Neutron Star", ]) writer.writerows(row.to_csv() for row in self.plotter_state.route) settings.General.last_route_index = self.plotter_state.route_index
def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) self.selected_journal: Journal | None = None self._journals = list[Journal]() self._journal_worker: GameWorker | None = None self._status_hide_timer = QtCore.QTimer(self) self._status_hide_timer.single_shot_ = True self._status_hide_timer.timeout.connect(self._reset_status_text) self._status_has_hover = False self._status_scheduled_reset = False self._setup_status_widget() self._combo_boxes = [tab.journal_combo for tab in self.tabs] self._current_network_reply = None self._journal_connections = [] # region spansh tabs init self.spansh_neutron_tab.nearest_button.pressed.connect( self._display_nearest_window) self.spansh_exact_tab.nearest_button.pressed.connect( self._display_nearest_window) # Disable submit plot buttons and set them to be enabled when their respective from/to fields are filled self.spansh_neutron_tab.submit_button.enabled = False self.spansh_exact_tab.submit_button.enabled = False self.spansh_neutron_tab.source_edit.textChanged.connect( self._set_neutron_submit) self.spansh_neutron_tab.target_edit.textChanged.connect( self._set_neutron_submit) self.spansh_exact_tab.source_edit.textChanged.connect( self._set_exact_submit) self.spansh_exact_tab.target_edit.textChanged.connect( self._set_exact_submit) self.spansh_exact_tab.use_clipboard_checkbox.stateChanged.connect( self._set_exact_submit) self.spansh_neutron_tab.range_spin.value = 50 self.spansh_neutron_tab.efficiency_spin.value = 80 # default to 80% efficiency self.spansh_neutron_tab.cargo_slider.valueChanged.connect( self._recalculate_range) self.spansh_neutron_tab.submit_button.pressed.connect( self._submit_neutron) self.spansh_exact_tab.submit_button.pressed.connect(self._submit_exact) # endregion # region csv tab init self.csv_tab.path_popup_button.pressed.connect(self._path_select_popup) self.csv_tab.submit_button.pressed.connect(self._csv_submit) if settings.Paths.csv is not None: self.csv_tab.path_edit.text = str(settings.Paths.csv) # endregion self.last_route_tab.submit_button.pressed.connect( self._last_route_submit) self.combo_signals = [ ReconnectingSignal( combo_box.currentIndexChanged, self._sync_journal_combos, ) for combo_box in self._combo_boxes ] for signal in self.combo_signals: signal.connect() for tab in self.tabs: tab.refresh_button.pressed.connect(self._populate_journal_combos) tab.abort_button.pressed.connect(self._abort_request) self.tab_widget.currentChanged.connect(self._display_saved_route) self._route_displayed = False self._loaded_route: list[NeutronPlotRow] | None = None self.retranslate() self._populate_journal_combos()