diff --git a/README.md b/README.md index ca0f226..1390f51 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![License:MIT](https://img.shields.io/pypi/l/katrain)](https://en.wikipedia.org/wiki/MIT_License) [![GitHub Downloads](https://img.shields.io/github/downloads/sanderland/katrain/total?color=%23336699&label=github%20downloads)](https://github.com/sanderland/katrain/releases) [![PyPI Downloads](https://pepy.tech/badge/katrain)](https://pepy.tech/project/katrain) -[![Github sponsors](https://img.shields.io/static/v1?label=sponsor&message=7&logo=GitHub&color=dcb424&link=https://github.com/sponsors/sanderland/)](https://github.com/sponsors/sanderland) +[![Github sponsors](https://img.shields.io/static/v1?label=sponsor&message=6&logo=GitHub&color=dcb424&link=https://github.com/sponsors/sanderland/)](https://github.com/sponsors/sanderland) [![Discord](https://img.shields.io/discord/417022162348802048?logo=discord)](https://discord.com/channels/417022162348802048/629446365688365067) @@ -27,9 +27,8 @@ EnglishGermanFrench -Spanish -
Russian +
ChineseKoreanJapanese diff --git a/katrain/__main__.py b/katrain/__main__.py index 9463fdb..de8e706 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -66,7 +66,7 @@ from katrain.core.constants import ( PLAYING_NORMAL, PLAYER_HUMAN, ) -from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup +from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup, SaveSGFPopup from katrain.core.base_katrain import KaTrainBase from katrain.core.engine import KataGoEngine from katrain.core.game import Game, IllegalMoveException, KaTrainSGF @@ -227,6 +227,7 @@ class KaTrainGui(Screen, KaTrainBase): def set_note(self, note): self.game.current_node.note = note + # The message loop is here to make sure moves happen in the right order, and slow operations don't hang the GUI def _message_loop_thread(self): while True: game, msg, args, kwargs = self.message_queue.get() @@ -253,16 +254,14 @@ class KaTrainGui(Screen, KaTrainBase): else: # game related actions self.message_queue.put([self.game.game_id, message, args, kwargs]) - def _do_new_game(self, move_tree=None, analyze_fast=False, loaded_from_file=None): + def _do_new_game(self, move_tree=None, analyze_fast=False, sgf_filename=None): self.idle_analysis = False mode = self.play_analyze_mode if (move_tree is not None and mode == MODE_PLAY) or (move_tree is None and mode == MODE_ANALYZE): self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze self.board_gui.animating_pv = None self.engine.on_new_game() # clear queries - self.game = Game( - self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast, loaded_from_file=loaded_from_file - ) + self.game = Game(self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast, sgf_filename=sgf_filename) if move_tree: for bw, player_info in self.players_info.items(): player_info.player_type = PLAYER_HUMAN @@ -371,7 +370,7 @@ class KaTrainGui(Screen, KaTrainBase): except ParseError as e: self.log(i18n._("Failed to load SGF").format(error=e), OUTPUT_ERROR) return - self._do_new_game(move_tree=move_tree, analyze_fast=fast, loaded_from_file=file) + self._do_new_game(move_tree=move_tree, analyze_fast=fast, sgf_filename=file) if not rewind: self.game.redo(999) @@ -387,9 +386,8 @@ class KaTrainGui(Screen, KaTrainBase): filename = popup_contents.filesel.filename self.fileselect_popup.dismiss() path, file = os.path.split(filename) - settings_path = self.config("general/sgf_load") - if path != settings_path: - self.log(f"Updating sgf load path default to {path}", OUTPUT_INFO) + if path != self.config("general/sgf_load"): + self.log(f"Updating sgf load path default to {path}", OUTPUT_DEBUG) self._config["general"]["sgf_load"] = path self.save_config("general") self.load_sgf_file(filename, popup_contents.fast.active, popup_contents.rewind.active) @@ -399,11 +397,39 @@ class KaTrainGui(Screen, KaTrainBase): self.fileselect_popup.open() self.fileselect_popup.content.filesel.ids.list_view._trigger_update() - def _do_output_sgf(self): + def _do_save_game(self, filename=None): + filename = filename or self.game.sgf_filename + if not filename: + return self("save-game-as") msg = self.game.write_sgf(self.config("general/sgf_save")) self.log(msg, OUTPUT_INFO) self.controls.set_status(msg, STATUS_INFO) + def _do_save_game_as_popup(self): + popup_contents = SaveSGFPopup() + save_game_popup = I18NPopup( + title_key="save sgf title", size=[dp(1200), dp(800)], content=popup_contents + ).__self__ + + def readfile(*_args): + filename = popup_contents.filesel.filename + if not filename.lower().endswith('.sgf'): + filename += '.sgf' + save_game_popup.dismiss() + path, file = os.path.split(filename) + if path != self.config("general/sgf_save"): + self.log(f"Updating sgf save path default to {path}", OUTPUT_DEBUG) + self._config["general"]["sgf_save"] = path + self.save_config("general") + self._do_save_game(filename) + + popup_contents.filesel.on_success = readfile + popup_contents.filesel.on_submit = readfile + save_game_popup.open() + popup_contents.filesel.ids.file_text = self.game.generate_filename() # TODO: ??? + popup_contents.filesel.path = os.path.abspath(os.path.expanduser(self.config("general/sgf_save", "."))) + # save_game_popup.content.filesel.ids.list_view._trigger_update() + def load_sgf_from_clipboard(self): clipboard = Clipboard.paste() if not clipboard: @@ -513,7 +539,9 @@ class KaTrainGui(Screen, KaTrainBase): elif keycode[1] == "l" and ctrl_pressed: self("analyze-sgf-popup") elif keycode[1] == "s" and ctrl_pressed: - self("output-sgf") + self("save-game") + elif keycode[1] == "d" and ctrl_pressed: + self("save-game-as-popup") elif keycode[1] == "c" and ctrl_pressed: Clipboard.copy(self.game.root.sgf()) self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO) diff --git a/katrain/core/game.py b/katrain/core/game.py index bb31576..4c2c945 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -47,7 +47,7 @@ class Game: move_tree: GameNode = None, analyze_fast=False, game_properties: Optional[Dict] = None, - loaded_from_file=None, + sgf_filename=None, ): self.katrain = katrain self._lock = threading.Lock() @@ -55,7 +55,7 @@ class Game: engine = {"B": engine, "W": engine} self.engines = engine self.game_id = datetime.strftime(datetime.now(), "%Y-%m-%d %H %M %S") - self.loaded_from_file = loaded_from_file + self.sgf_filename = sgf_filename if move_tree: self.root = move_tree @@ -295,14 +295,18 @@ class Game: + f"\ncaptures: {self.prisoner_count}" ) + def generate_filename(self): + player_names = {bw: re.sub(r"['<>:\"/\\|?*]", "", self.root.get_property("P" + bw, bw)) for bw in "BW"} + base_game_name = f"katrain_{player_names['B']} vs {player_names['W']}" + return f"{base_game_name} {self.game_id}" + def write_sgf( - self, path: str, trainer_config: Optional[Dict] = None, + self, file_name: str=None, trainer_config: Optional[Dict] = None, ): if trainer_config is None: trainer_config = self.katrain.config("trainer", {}) save_feedback = trainer_config.get("save_feedback", False) eval_thresholds = trainer_config["eval_thresholds"] - print(trainer_config) save_analysis = trainer_config.get("save_analysis", False) def player_name(player_info): @@ -324,20 +328,6 @@ class Game: if save_analysis: x_properties["KTV"] = ANALYSIS_FORMAT_VERSION self.root.properties = {**root_properties, **{k: [v] for k, v in x_properties.items()}} - player_names = {bw: re.sub(r"['<>:\"/\\|?*]", "", self.root.get_property("P" + bw, bw)) for bw in "BW"} - base_game_name = f"katrain_{player_names['B']} vs {player_names['W']}" - game_name = f"{base_game_name} {self.game_id}" - - if ( - self.loaded_from_file - and base_game_name in self.loaded_from_file - and PROGRAM_NAME in self.root.get_property("AP", "") - and self.loaded_from_file.endswith("sgf") - ): - file_name = self.loaded_from_file - else: - file_name = os.path.abspath(os.path.join(path, f"{game_name}.sgf")) - os.makedirs(os.path.dirname(file_name), exist_ok=True) show_dots_for = { bw: trainer_config.get("eval_show_ai", True) or self.katrain.players_info[bw].human for bw in "BW" @@ -348,6 +338,8 @@ class Game: eval_thresholds=eval_thresholds, save_analysis=save_analysis, ) + file_name = file_name or self.generate_filename() + os.makedirs(os.path.dirname(file_name), exist_ok=True) with open(file_name, "w", encoding="utf-8") as f: f.write(sgf) self.root.properties = root_properties diff --git a/katrain/gui.kv b/katrain/gui.kv index 480fb22..bdf1e73 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -950,9 +950,9 @@ on_action: root.katrain("save-game") MainMenuItem: text: i18n._('menu:save-as') - icon: 'img/Save-Game.png' - shortcut: 'Ctrl-Shift-S' - on_action: root.katrain("save-game-as") + icon: 'img/Save-Game-As.png' + shortcut: 'Ctrl-D' + on_action: root.katrain("save-game-as-popup") MainMenuItem: text: i18n._('menu:load') icon: 'img/Load-Game.png' diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 9698e0b..96d2978 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -669,13 +669,25 @@ class LoadSGFPopup(BoxLayout): super().__init__(**kwargs) app = MDApp.get_running_app() self.filesel.favorites = [ - (os.path.abspath(app.gui.config("general/sgf_load")), "Last Used Dir"), - (os.path.abspath(app.gui.config("general/sgf_save")), "SGF Save Dir"), + (os.path.abspath(app.gui.config("general/sgf_load")), "Last Load Dir"), + (os.path.abspath(app.gui.config("general/sgf_save")), "Last Save Dir"), ] self.filesel.path = os.path.abspath(os.path.expanduser(app.gui.config("general/sgf_load"))) self.filesel.select_string = i18n._("Load File") +class SaveSGFPopup(BoxLayout): + def __init__(self, **kwargs): + super().__init__(**kwargs) + app = MDApp.get_running_app() + self.filesel.favorites = [ + (os.path.abspath(app.gui.config("general/sgf_load")), "Last Load Dir"), + (os.path.abspath(app.gui.config("general/sgf_save")), "Last Save Dir"), + ] + self.filesel.path = os.path.abspath(os.path.expanduser(MDApp.get_running_app().gui.config("general/sgf_save"))) + self.filesel.select_string = i18n._("Save File") + + class ReAnalyzeGamePopup(BoxLayout): katrain = ObjectProperty(None) popup = ObjectProperty(None) diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 3459baa..0e03668 100644 Binary files a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index 2f460e6..4a0dba9 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -727,3 +727,18 @@ msgstr "Cache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index 5bd19c4..00a1586 100644 Binary files a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index bc1ecf7..98fa667 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -774,3 +774,18 @@ msgstr "" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 865aa1d..a8528ea 100644 Binary files a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 1485d38..b244f04 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -293,6 +293,18 @@ msgstr "Your SGF notes for this position here." msgid "load sgf title" msgstr "Load SGF File to Analyze" +# sgf load button +msgid "Load File" +msgstr "Load File" + +# sgf save as button +msgid "Save File" +msgstr "Save File" + +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + msgid "load sgf fast analysis" msgstr "Use fast analysis" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index ec95bfd..c4bd3c5 100644 Binary files a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 156e2a5..6424d3d 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -787,3 +787,18 @@ msgstr "Cache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 87282c1..e51cd5c 100644 Binary files a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 1123176..284dc3a 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -813,3 +813,18 @@ msgstr "Cache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo index f1c8e38..0f962aa 100644 Binary files a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po index a8e523f..b97431d 100644 --- a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po @@ -766,3 +766,18 @@ msgstr "Cache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 3c40c7a..0b1bcc4 100644 Binary files a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index a7d88ca..012114a 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -741,3 +741,18 @@ msgstr "ache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 34466ba..f44aec6 100644 Binary files a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 7397039..1b74dc9 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -775,3 +775,18 @@ msgstr "Cache analysis in SGF" #. TODO msgid "menu:save-as" msgstr "Save Game As..." + +#. TODO +# sgf load button +msgid "Load File" +msgstr "Load File" + +#. TODO +# sgf save popup +msgid "save sgf title" +msgstr "Save SGF File" + +#. TODO +# sgf save as button +msgid "Save File" +msgstr "Save File" diff --git a/katrain/img/Save-Game - As.png b/katrain/img/Save-Game - As.png new file mode 100644 index 0000000..4b0af4e Binary files /dev/null and b/katrain/img/Save-Game - As.png differ diff --git a/katrain/popups.kv b/katrain/popups.kv index 6cdca00..46965e2 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -181,13 +181,8 @@ orientation: 'horizontal' GridLayout: cols: 2 - rows: 5 + rows: 4 spacing: CP_SPACING - DescriptionLabel: - text: i18n._("general:sgf_save") - AnchorLayout: - LabelledPathInput: - input_property: "general/sgf_save" DescriptionLabel: text: i18n._('count down sound') AnchorLayout: @@ -622,3 +617,13 @@ filters: ["*.sgf","*.gib","*.ngf"] path: "." size_hint: 1,7 + +: + filesel: filesel + orientation: 'vertical' + I18NFileBrowser: + id: filesel + multiselect: False + filters: ["*.sgf","*.gib","*.ngf"] + path: "." + size_hint: 1,7 \ No newline at end of file