Exemple #1
0
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())
Exemple #2
0
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()
Exemple #3
0
    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)
Exemple #4
0
    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()
Exemple #5
0
    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
Exemple #6
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()