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.
- ankigammon/__init__.py +7 -0
- ankigammon/__main__.py +6 -0
- ankigammon/analysis/__init__.py +13 -0
- ankigammon/analysis/score_matrix.py +391 -0
- ankigammon/anki/__init__.py +6 -0
- ankigammon/anki/ankiconnect.py +216 -0
- ankigammon/anki/apkg_exporter.py +111 -0
- ankigammon/anki/card_generator.py +1325 -0
- ankigammon/anki/card_styles.py +1054 -0
- ankigammon/gui/__init__.py +8 -0
- ankigammon/gui/app.py +192 -0
- ankigammon/gui/dialogs/__init__.py +10 -0
- ankigammon/gui/dialogs/export_dialog.py +594 -0
- ankigammon/gui/dialogs/import_options_dialog.py +201 -0
- ankigammon/gui/dialogs/input_dialog.py +762 -0
- ankigammon/gui/dialogs/note_dialog.py +93 -0
- ankigammon/gui/dialogs/settings_dialog.py +420 -0
- ankigammon/gui/dialogs/update_dialog.py +373 -0
- ankigammon/gui/format_detector.py +377 -0
- ankigammon/gui/main_window.py +1611 -0
- ankigammon/gui/resources/down-arrow.svg +3 -0
- ankigammon/gui/resources/icon.icns +0 -0
- ankigammon/gui/resources/icon.ico +0 -0
- ankigammon/gui/resources/icon.png +0 -0
- ankigammon/gui/resources/style.qss +402 -0
- ankigammon/gui/resources.py +26 -0
- ankigammon/gui/update_checker.py +259 -0
- ankigammon/gui/widgets/__init__.py +8 -0
- ankigammon/gui/widgets/position_list.py +166 -0
- ankigammon/gui/widgets/smart_input.py +268 -0
- ankigammon/models.py +356 -0
- ankigammon/parsers/__init__.py +7 -0
- ankigammon/parsers/gnubg_match_parser.py +1094 -0
- ankigammon/parsers/gnubg_parser.py +468 -0
- ankigammon/parsers/sgf_parser.py +290 -0
- ankigammon/parsers/xg_binary_parser.py +1097 -0
- ankigammon/parsers/xg_text_parser.py +688 -0
- ankigammon/renderer/__init__.py +5 -0
- ankigammon/renderer/animation_controller.py +391 -0
- ankigammon/renderer/animation_helper.py +191 -0
- ankigammon/renderer/color_schemes.py +145 -0
- ankigammon/renderer/svg_board_renderer.py +791 -0
- ankigammon/settings.py +315 -0
- ankigammon/thirdparty/__init__.py +7 -0
- ankigammon/thirdparty/xgdatatools/__init__.py +17 -0
- ankigammon/thirdparty/xgdatatools/xgimport.py +160 -0
- ankigammon/thirdparty/xgdatatools/xgstruct.py +1032 -0
- ankigammon/thirdparty/xgdatatools/xgutils.py +118 -0
- ankigammon/thirdparty/xgdatatools/xgzarc.py +260 -0
- ankigammon/utils/__init__.py +13 -0
- ankigammon/utils/gnubg_analyzer.py +590 -0
- ankigammon/utils/gnuid.py +577 -0
- ankigammon/utils/move_parser.py +204 -0
- ankigammon/utils/ogid.py +326 -0
- ankigammon/utils/xgid.py +387 -0
- ankigammon-1.0.6.dist-info/METADATA +352 -0
- ankigammon-1.0.6.dist-info/RECORD +61 -0
- ankigammon-1.0.6.dist-info/WHEEL +5 -0
- ankigammon-1.0.6.dist-info/entry_points.txt +2 -0
- ankigammon-1.0.6.dist-info/licenses/LICENSE +21 -0
- 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
|
+
)
|