diff --git a/README.md b/README.md
index ca0f226..1390f51 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://en.wikipedia.org/wiki/MIT_License)
[](https://github.com/sanderland/katrain/releases)
[](https://pepy.tech/project/katrain)
-[](https://github.com/sponsors/sanderland)
+[](https://github.com/sponsors/sanderland)
[](https://discord.com/channels/417022162348802048/629446365688365067)
@@ -27,9 +27,8 @@
-
-
+
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