ankigammon 1.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. ankigammon/__init__.py +7 -0
  2. ankigammon/__main__.py +6 -0
  3. ankigammon/analysis/__init__.py +13 -0
  4. ankigammon/analysis/score_matrix.py +391 -0
  5. ankigammon/anki/__init__.py +6 -0
  6. ankigammon/anki/ankiconnect.py +216 -0
  7. ankigammon/anki/apkg_exporter.py +111 -0
  8. ankigammon/anki/card_generator.py +1325 -0
  9. ankigammon/anki/card_styles.py +1054 -0
  10. ankigammon/gui/__init__.py +8 -0
  11. ankigammon/gui/app.py +192 -0
  12. ankigammon/gui/dialogs/__init__.py +10 -0
  13. ankigammon/gui/dialogs/export_dialog.py +594 -0
  14. ankigammon/gui/dialogs/import_options_dialog.py +201 -0
  15. ankigammon/gui/dialogs/input_dialog.py +762 -0
  16. ankigammon/gui/dialogs/note_dialog.py +93 -0
  17. ankigammon/gui/dialogs/settings_dialog.py +420 -0
  18. ankigammon/gui/dialogs/update_dialog.py +373 -0
  19. ankigammon/gui/format_detector.py +377 -0
  20. ankigammon/gui/main_window.py +1611 -0
  21. ankigammon/gui/resources/down-arrow.svg +3 -0
  22. ankigammon/gui/resources/icon.icns +0 -0
  23. ankigammon/gui/resources/icon.ico +0 -0
  24. ankigammon/gui/resources/icon.png +0 -0
  25. ankigammon/gui/resources/style.qss +402 -0
  26. ankigammon/gui/resources.py +26 -0
  27. ankigammon/gui/update_checker.py +259 -0
  28. ankigammon/gui/widgets/__init__.py +8 -0
  29. ankigammon/gui/widgets/position_list.py +166 -0
  30. ankigammon/gui/widgets/smart_input.py +268 -0
  31. ankigammon/models.py +356 -0
  32. ankigammon/parsers/__init__.py +7 -0
  33. ankigammon/parsers/gnubg_match_parser.py +1094 -0
  34. ankigammon/parsers/gnubg_parser.py +468 -0
  35. ankigammon/parsers/sgf_parser.py +290 -0
  36. ankigammon/parsers/xg_binary_parser.py +1097 -0
  37. ankigammon/parsers/xg_text_parser.py +688 -0
  38. ankigammon/renderer/__init__.py +5 -0
  39. ankigammon/renderer/animation_controller.py +391 -0
  40. ankigammon/renderer/animation_helper.py +191 -0
  41. ankigammon/renderer/color_schemes.py +145 -0
  42. ankigammon/renderer/svg_board_renderer.py +791 -0
  43. ankigammon/settings.py +315 -0
  44. ankigammon/thirdparty/__init__.py +7 -0
  45. ankigammon/thirdparty/xgdatatools/__init__.py +17 -0
  46. ankigammon/thirdparty/xgdatatools/xgimport.py +160 -0
  47. ankigammon/thirdparty/xgdatatools/xgstruct.py +1032 -0
  48. ankigammon/thirdparty/xgdatatools/xgutils.py +118 -0
  49. ankigammon/thirdparty/xgdatatools/xgzarc.py +260 -0
  50. ankigammon/utils/__init__.py +13 -0
  51. ankigammon/utils/gnubg_analyzer.py +590 -0
  52. ankigammon/utils/gnuid.py +577 -0
  53. ankigammon/utils/move_parser.py +204 -0
  54. ankigammon/utils/ogid.py +326 -0
  55. ankigammon/utils/xgid.py +387 -0
  56. ankigammon-1.0.6.dist-info/METADATA +352 -0
  57. ankigammon-1.0.6.dist-info/RECORD +61 -0
  58. ankigammon-1.0.6.dist-info/WHEEL +5 -0
  59. ankigammon-1.0.6.dist-info/entry_points.txt +2 -0
  60. ankigammon-1.0.6.dist-info/licenses/LICENSE +21 -0
  61. ankigammon-1.0.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,201 @@
1
+ """
2
+ Import options dialog for XG file imports.
3
+ """
4
+
5
+ from typing import Optional
6
+ from PySide6.QtWidgets import (
7
+ QDialog, QVBoxLayout, QFormLayout,
8
+ QCheckBox, QDoubleSpinBox, QGroupBox,
9
+ QLabel, QDialogButtonBox
10
+ )
11
+ from PySide6.QtCore import Qt, Signal
12
+ from PySide6.QtGui import QShowEvent
13
+
14
+ from ankigammon.settings import Settings
15
+
16
+
17
+ class ImportOptionsDialog(QDialog):
18
+ """
19
+ Dialog for configuring XG import filtering options.
20
+
21
+ Allows users to filter imported positions by:
22
+ - Error threshold (only import mistakes above this threshold)
23
+ - Player selection (import mistakes from X, O, or both)
24
+
25
+ Signals:
26
+ options_accepted(float, bool, bool): Emitted when user accepts
27
+ Args: (threshold, include_player_x, include_player_o)
28
+ """
29
+
30
+ options_accepted = Signal(float, bool, bool)
31
+
32
+ def __init__(
33
+ self,
34
+ settings: Settings,
35
+ player1_name: Optional[str] = None,
36
+ player2_name: Optional[str] = None,
37
+ parent: Optional[QDialog] = None
38
+ ):
39
+ super().__init__(parent)
40
+ self.settings = settings
41
+ self.player1_name = player1_name or "Player 1"
42
+ self.player2_name = player2_name or "Player 2"
43
+
44
+ self.setWindowTitle("Import Options")
45
+ self.setModal(True)
46
+ self.setMinimumWidth(450)
47
+
48
+ self._setup_ui()
49
+ self._load_settings()
50
+ self._update_ok_button_state()
51
+
52
+ def _setup_ui(self):
53
+ """Initialize the user interface."""
54
+ layout = QVBoxLayout(self)
55
+
56
+ # Error threshold group
57
+ threshold_group = self._create_threshold_group()
58
+ layout.addWidget(threshold_group)
59
+
60
+ # Player selection group
61
+ player_group = self._create_player_group()
62
+ layout.addWidget(player_group)
63
+
64
+ # Dialog buttons
65
+ self.button_box = QDialogButtonBox(
66
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel
67
+ )
68
+ self.button_box.accepted.connect(self.accept)
69
+ self.button_box.rejected.connect(self.reject)
70
+
71
+ # Add cursor pointers to buttons
72
+ for button in self.button_box.buttons():
73
+ button.setCursor(Qt.PointingHandCursor)
74
+
75
+ layout.addWidget(self.button_box)
76
+
77
+ def _create_threshold_group(self) -> QGroupBox:
78
+ """Create error threshold settings group."""
79
+ group = QGroupBox("Error Threshold")
80
+ form = QFormLayout(group)
81
+
82
+ # Threshold spinbox
83
+ self.spin_threshold = QDoubleSpinBox()
84
+ self.spin_threshold.setMinimum(0.000)
85
+ self.spin_threshold.setMaximum(1.000)
86
+ self.spin_threshold.setSingleStep(0.001)
87
+ self.spin_threshold.setDecimals(3)
88
+ self.spin_threshold.setValue(0.080)
89
+ self.spin_threshold.setCursor(Qt.PointingHandCursor)
90
+ form.addRow("Minimum Error:", self.spin_threshold)
91
+
92
+ return group
93
+
94
+ def _create_player_group(self) -> QGroupBox:
95
+ """Create player selection group."""
96
+ group = QGroupBox("Player Selection")
97
+ form = QFormLayout(group)
98
+
99
+ # Player checkboxes (XG file player 1 = internal Player.O, player 2 = Player.X)
100
+ self.chk_player_o = QCheckBox(self.player1_name)
101
+ self.chk_player_o.setCursor(Qt.PointingHandCursor)
102
+ self.chk_player_o.stateChanged.connect(self._update_ok_button_state)
103
+ form.addRow(self.chk_player_o)
104
+
105
+ # Player 2 checkbox
106
+ self.chk_player_x = QCheckBox(self.player2_name)
107
+ self.chk_player_x.setCursor(Qt.PointingHandCursor)
108
+ self.chk_player_x.stateChanged.connect(self._update_ok_button_state)
109
+ form.addRow(self.chk_player_x)
110
+
111
+ # Warning label
112
+ self.lbl_warning = QLabel("")
113
+ self.lbl_warning.setStyleSheet(
114
+ "color: #f38ba8; font-size: 11px; margin-top: 8px; min-height: 20px;"
115
+ )
116
+ form.addRow(self.lbl_warning)
117
+
118
+ return group
119
+
120
+ def showEvent(self, event: QShowEvent):
121
+ """Reload settings when dialog is about to be shown."""
122
+ super().showEvent(event)
123
+ # Reload settings every time the dialog is shown to ensure we have
124
+ # the latest values (in case a previous dialog in the import sequence updated them)
125
+ self._load_settings()
126
+
127
+ def _load_settings(self):
128
+ """Load current settings into widgets, matching by player name."""
129
+ self.spin_threshold.setValue(self.settings.import_error_threshold)
130
+
131
+ selected_names = self.settings.import_selected_player_names
132
+ selected_names_lower = [name.lower() for name in selected_names]
133
+
134
+ if self.player1_name.lower() in selected_names_lower:
135
+ self.chk_player_o.setChecked(True)
136
+ elif not selected_names:
137
+ # No history - use legacy position-based settings
138
+ self.chk_player_o.setChecked(self.settings.import_include_player_o)
139
+ else:
140
+ # Has history but name doesn't match
141
+ self.chk_player_o.setChecked(False)
142
+
143
+ if self.player2_name.lower() in selected_names_lower:
144
+ self.chk_player_x.setChecked(True)
145
+ elif not selected_names:
146
+ # No history - use legacy position-based settings
147
+ self.chk_player_x.setChecked(self.settings.import_include_player_x)
148
+ else:
149
+ # Has history but name doesn't match
150
+ self.chk_player_x.setChecked(False)
151
+
152
+ def _update_ok_button_state(self):
153
+ """Enable/disable OK button based on player selection."""
154
+ at_least_one_selected = (
155
+ self.chk_player_x.isChecked() or self.chk_player_o.isChecked()
156
+ )
157
+
158
+ ok_button = self.button_box.button(QDialogButtonBox.Ok)
159
+ ok_button.setEnabled(at_least_one_selected)
160
+
161
+ if at_least_one_selected:
162
+ self.lbl_warning.setText("")
163
+ else:
164
+ self.lbl_warning.setText("At least one player must be selected")
165
+
166
+ def accept(self):
167
+ """Save settings and emit options."""
168
+ self.settings.import_error_threshold = self.spin_threshold.value()
169
+
170
+ selected_names = []
171
+ if self.chk_player_o.isChecked():
172
+ selected_names.append(self.player1_name)
173
+ if self.chk_player_x.isChecked():
174
+ selected_names.append(self.player2_name)
175
+
176
+ self.settings.import_selected_player_names = selected_names
177
+
178
+ # Also update legacy position-based settings for backward compatibility
179
+ self.settings.import_include_player_x = self.chk_player_x.isChecked()
180
+ self.settings.import_include_player_o = self.chk_player_o.isChecked()
181
+
182
+ self.options_accepted.emit(
183
+ self.spin_threshold.value(),
184
+ self.chk_player_x.isChecked(),
185
+ self.chk_player_o.isChecked()
186
+ )
187
+
188
+ super().accept()
189
+
190
+ def get_options(self) -> tuple[float, bool, bool]:
191
+ """
192
+ Get the selected import options.
193
+
194
+ Returns:
195
+ Tuple of (threshold, include_player_x, include_player_o)
196
+ """
197
+ return (
198
+ self.spin_threshold.value(),
199
+ self.chk_player_x.isChecked(),
200
+ self.chk_player_o.isChecked()
201
+ )