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,373 @@
|
|
|
1
|
+
"""Dialog for displaying version update information."""
|
|
2
|
+
|
|
3
|
+
import webbrowser
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import Qt
|
|
8
|
+
from PySide6.QtGui import QFont
|
|
9
|
+
from PySide6.QtWidgets import (
|
|
10
|
+
QDialog,
|
|
11
|
+
QVBoxLayout,
|
|
12
|
+
QHBoxLayout,
|
|
13
|
+
QLabel,
|
|
14
|
+
QPushButton,
|
|
15
|
+
QTextEdit,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UpdateDialog(QDialog):
|
|
20
|
+
"""Dialog to notify user about available updates."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, parent, release_info: Dict, current_version: str):
|
|
23
|
+
"""Initialize update dialog.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parent: Parent widget
|
|
27
|
+
release_info: Release information from GitHub API
|
|
28
|
+
current_version: Current application version
|
|
29
|
+
"""
|
|
30
|
+
super().__init__(parent)
|
|
31
|
+
self.release_info = release_info
|
|
32
|
+
self.current_version = current_version
|
|
33
|
+
self.snooze_hours = 24
|
|
34
|
+
self.user_action = None # 'update', 'snooze', or 'skip'
|
|
35
|
+
|
|
36
|
+
self.setWindowTitle("Update Available")
|
|
37
|
+
self.setMinimumWidth(500)
|
|
38
|
+
self.setMinimumHeight(400)
|
|
39
|
+
|
|
40
|
+
self._setup_ui()
|
|
41
|
+
|
|
42
|
+
def _setup_ui(self):
|
|
43
|
+
"""Set up the dialog UI."""
|
|
44
|
+
# Set dialog background to match app theme
|
|
45
|
+
self.setStyleSheet("""
|
|
46
|
+
QDialog {
|
|
47
|
+
background-color: #1e1e2e;
|
|
48
|
+
color: #cdd6f4;
|
|
49
|
+
}
|
|
50
|
+
""")
|
|
51
|
+
|
|
52
|
+
layout = QVBoxLayout()
|
|
53
|
+
layout.setSpacing(15)
|
|
54
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
55
|
+
|
|
56
|
+
# Header
|
|
57
|
+
header = QLabel(f"AnkiGammon {self.release_info['version']} is available")
|
|
58
|
+
header.setFont(QFont('Arial', 14, QFont.Bold))
|
|
59
|
+
header.setStyleSheet("color: #cdd6f4;")
|
|
60
|
+
layout.addWidget(header)
|
|
61
|
+
|
|
62
|
+
# Version comparison
|
|
63
|
+
version_label = QLabel(
|
|
64
|
+
f"You have: <b>{self.current_version}</b><br>"
|
|
65
|
+
f"Available: <b>{self.release_info['version']}</b>"
|
|
66
|
+
)
|
|
67
|
+
version_label.setStyleSheet("color: #a6adc8; margin-bottom: 10px;")
|
|
68
|
+
layout.addWidget(version_label)
|
|
69
|
+
|
|
70
|
+
# Release date (if available)
|
|
71
|
+
published = self.release_info.get('published_at', '')
|
|
72
|
+
if published:
|
|
73
|
+
try:
|
|
74
|
+
# Parse ISO date
|
|
75
|
+
pub_date = datetime.fromisoformat(published.replace('Z', '+00:00'))
|
|
76
|
+
date_str = pub_date.strftime('%B %d, %Y')
|
|
77
|
+
date_label = QLabel(f"Released: {date_str}")
|
|
78
|
+
date_label.setStyleSheet("color: #6c7086; font-size: 11px;")
|
|
79
|
+
layout.addWidget(date_label)
|
|
80
|
+
except (ValueError, AttributeError):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# Release notes section
|
|
84
|
+
notes_label = QLabel("What's new:")
|
|
85
|
+
notes_label.setFont(QFont('Arial', 11, QFont.Bold))
|
|
86
|
+
notes_label.setStyleSheet("margin-top: 10px; color: #cdd6f4;")
|
|
87
|
+
layout.addWidget(notes_label)
|
|
88
|
+
|
|
89
|
+
# Release notes text
|
|
90
|
+
notes_text = QTextEdit()
|
|
91
|
+
notes_text.setReadOnly(True)
|
|
92
|
+
notes_text.setPlainText(
|
|
93
|
+
self.release_info.get('release_notes', 'No release notes available.')
|
|
94
|
+
)
|
|
95
|
+
notes_text.setMinimumHeight(150)
|
|
96
|
+
notes_text.setStyleSheet("""
|
|
97
|
+
QTextEdit {
|
|
98
|
+
background-color: #313244;
|
|
99
|
+
color: #cdd6f4;
|
|
100
|
+
border: 1px solid #45475a;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
padding: 10px;
|
|
103
|
+
font-family: 'Consolas', 'Courier New', monospace;
|
|
104
|
+
font-size: 11px;
|
|
105
|
+
}
|
|
106
|
+
""")
|
|
107
|
+
layout.addWidget(notes_text)
|
|
108
|
+
|
|
109
|
+
layout.addStretch()
|
|
110
|
+
|
|
111
|
+
# Button layout
|
|
112
|
+
button_layout = QHBoxLayout()
|
|
113
|
+
button_layout.setSpacing(10)
|
|
114
|
+
|
|
115
|
+
# Skip button
|
|
116
|
+
skip_btn = QPushButton("Skip This Version")
|
|
117
|
+
skip_btn.clicked.connect(self._on_skip)
|
|
118
|
+
skip_btn.setCursor(Qt.PointingHandCursor)
|
|
119
|
+
skip_btn.setStyleSheet("""
|
|
120
|
+
QPushButton {
|
|
121
|
+
background-color: #45475a;
|
|
122
|
+
color: #cdd6f4;
|
|
123
|
+
border: 1px solid #585b70;
|
|
124
|
+
padding: 8px 16px;
|
|
125
|
+
border-radius: 4px;
|
|
126
|
+
font-weight: bold;
|
|
127
|
+
}
|
|
128
|
+
QPushButton:hover {
|
|
129
|
+
background-color: #585b70;
|
|
130
|
+
border: 1px solid #6c7086;
|
|
131
|
+
}
|
|
132
|
+
""")
|
|
133
|
+
button_layout.addWidget(skip_btn)
|
|
134
|
+
|
|
135
|
+
# Snooze button
|
|
136
|
+
snooze_btn = QPushButton("Remind Me Tomorrow")
|
|
137
|
+
snooze_btn.clicked.connect(self._on_snooze)
|
|
138
|
+
snooze_btn.setCursor(Qt.PointingHandCursor)
|
|
139
|
+
snooze_btn.setStyleSheet("""
|
|
140
|
+
QPushButton {
|
|
141
|
+
background-color: #45475a;
|
|
142
|
+
color: #cdd6f4;
|
|
143
|
+
border: 1px solid #585b70;
|
|
144
|
+
padding: 8px 16px;
|
|
145
|
+
border-radius: 4px;
|
|
146
|
+
font-weight: bold;
|
|
147
|
+
}
|
|
148
|
+
QPushButton:hover {
|
|
149
|
+
background-color: #585b70;
|
|
150
|
+
border: 1px solid #6c7086;
|
|
151
|
+
}
|
|
152
|
+
""")
|
|
153
|
+
button_layout.addWidget(snooze_btn)
|
|
154
|
+
|
|
155
|
+
button_layout.addStretch()
|
|
156
|
+
|
|
157
|
+
# Update button (prominent)
|
|
158
|
+
update_btn = QPushButton("Download Update")
|
|
159
|
+
update_btn.clicked.connect(self._on_update)
|
|
160
|
+
update_btn.setDefault(True)
|
|
161
|
+
update_btn.setCursor(Qt.PointingHandCursor)
|
|
162
|
+
update_btn.setStyleSheet("""
|
|
163
|
+
QPushButton {
|
|
164
|
+
background-color: #a6e3a1;
|
|
165
|
+
color: #1e1e2e;
|
|
166
|
+
border: none;
|
|
167
|
+
padding: 8px 20px;
|
|
168
|
+
border-radius: 4px;
|
|
169
|
+
font-weight: bold;
|
|
170
|
+
font-size: 12px;
|
|
171
|
+
}
|
|
172
|
+
QPushButton:hover {
|
|
173
|
+
background-color: #94e2d5;
|
|
174
|
+
}
|
|
175
|
+
QPushButton:pressed {
|
|
176
|
+
background-color: #89dceb;
|
|
177
|
+
}
|
|
178
|
+
""")
|
|
179
|
+
button_layout.addWidget(update_btn)
|
|
180
|
+
|
|
181
|
+
layout.addLayout(button_layout)
|
|
182
|
+
|
|
183
|
+
self.setLayout(layout)
|
|
184
|
+
|
|
185
|
+
def _on_update(self):
|
|
186
|
+
"""Handle download update button click."""
|
|
187
|
+
self.user_action = 'update'
|
|
188
|
+
download_url = self.release_info.get('download_url', '')
|
|
189
|
+
if download_url:
|
|
190
|
+
webbrowser.open(download_url)
|
|
191
|
+
self.accept()
|
|
192
|
+
|
|
193
|
+
def _on_snooze(self):
|
|
194
|
+
"""Handle snooze button click."""
|
|
195
|
+
self.user_action = 'snooze'
|
|
196
|
+
self.accept()
|
|
197
|
+
|
|
198
|
+
def _on_skip(self):
|
|
199
|
+
"""Handle skip version button click."""
|
|
200
|
+
self.user_action = 'skip'
|
|
201
|
+
self.accept()
|
|
202
|
+
|
|
203
|
+
def get_snooze_until(self) -> str:
|
|
204
|
+
"""Get the snooze-until timestamp.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
ISO format timestamp
|
|
208
|
+
"""
|
|
209
|
+
return (datetime.now() + timedelta(hours=self.snooze_hours)).isoformat()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class CheckingUpdateDialog(QDialog):
|
|
213
|
+
"""Simple dialog shown while checking for updates."""
|
|
214
|
+
|
|
215
|
+
def __init__(self, parent):
|
|
216
|
+
"""Initialize checking dialog.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
parent: Parent widget
|
|
220
|
+
"""
|
|
221
|
+
super().__init__(parent)
|
|
222
|
+
self.setWindowTitle("Checking for Updates")
|
|
223
|
+
self.setModal(True)
|
|
224
|
+
self.setMinimumWidth(300)
|
|
225
|
+
|
|
226
|
+
# Remove close button
|
|
227
|
+
self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)
|
|
228
|
+
|
|
229
|
+
# Set dialog background to match app theme
|
|
230
|
+
self.setStyleSheet("""
|
|
231
|
+
QDialog {
|
|
232
|
+
background-color: #1e1e2e;
|
|
233
|
+
color: #cdd6f4;
|
|
234
|
+
}
|
|
235
|
+
""")
|
|
236
|
+
|
|
237
|
+
layout = QVBoxLayout()
|
|
238
|
+
layout.setContentsMargins(30, 20, 30, 20)
|
|
239
|
+
|
|
240
|
+
label = QLabel("Checking for updates...")
|
|
241
|
+
label.setAlignment(Qt.AlignCenter)
|
|
242
|
+
label.setStyleSheet("color: #cdd6f4;")
|
|
243
|
+
layout.addWidget(label)
|
|
244
|
+
|
|
245
|
+
self.setLayout(layout)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class NoUpdateDialog(QDialog):
|
|
249
|
+
"""Dialog shown when no update is available."""
|
|
250
|
+
|
|
251
|
+
def __init__(self, parent, current_version: str):
|
|
252
|
+
"""Initialize no-update dialog.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
parent: Parent widget
|
|
256
|
+
current_version: Current application version
|
|
257
|
+
"""
|
|
258
|
+
super().__init__(parent)
|
|
259
|
+
self.setWindowTitle("No Updates Available")
|
|
260
|
+
self.setMinimumWidth(350)
|
|
261
|
+
|
|
262
|
+
# Set dialog background to match app theme
|
|
263
|
+
self.setStyleSheet("""
|
|
264
|
+
QDialog {
|
|
265
|
+
background-color: #1e1e2e;
|
|
266
|
+
color: #cdd6f4;
|
|
267
|
+
}
|
|
268
|
+
""")
|
|
269
|
+
|
|
270
|
+
layout = QVBoxLayout()
|
|
271
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
272
|
+
|
|
273
|
+
# Success icon and message
|
|
274
|
+
message = QLabel(f"✓ You're up to date!\n\nVersion {current_version} is the latest version.")
|
|
275
|
+
message.setAlignment(Qt.AlignCenter)
|
|
276
|
+
message.setFont(QFont('Arial', 11))
|
|
277
|
+
message.setStyleSheet("color: #a6e3a1; margin: 20px 0;")
|
|
278
|
+
layout.addWidget(message)
|
|
279
|
+
|
|
280
|
+
# OK button
|
|
281
|
+
button_layout = QHBoxLayout()
|
|
282
|
+
button_layout.addStretch()
|
|
283
|
+
|
|
284
|
+
ok_btn = QPushButton("OK")
|
|
285
|
+
ok_btn.clicked.connect(self.accept)
|
|
286
|
+
ok_btn.setDefault(True)
|
|
287
|
+
ok_btn.setMinimumWidth(100)
|
|
288
|
+
ok_btn.setCursor(Qt.PointingHandCursor)
|
|
289
|
+
ok_btn.setStyleSheet("""
|
|
290
|
+
QPushButton {
|
|
291
|
+
background-color: #89b4fa;
|
|
292
|
+
color: #1e1e2e;
|
|
293
|
+
border: none;
|
|
294
|
+
padding: 8px 16px;
|
|
295
|
+
border-radius: 4px;
|
|
296
|
+
font-weight: bold;
|
|
297
|
+
}
|
|
298
|
+
QPushButton:hover {
|
|
299
|
+
background-color: #74c7ec;
|
|
300
|
+
}
|
|
301
|
+
""")
|
|
302
|
+
button_layout.addWidget(ok_btn)
|
|
303
|
+
button_layout.addStretch()
|
|
304
|
+
|
|
305
|
+
layout.addLayout(button_layout)
|
|
306
|
+
|
|
307
|
+
self.setLayout(layout)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class UpdateCheckFailedDialog(QDialog):
|
|
311
|
+
"""Dialog shown when update check fails due to network error."""
|
|
312
|
+
|
|
313
|
+
def __init__(self, parent, current_version: str):
|
|
314
|
+
"""Initialize failed check dialog.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
parent: Parent widget
|
|
318
|
+
current_version: Current application version
|
|
319
|
+
"""
|
|
320
|
+
super().__init__(parent)
|
|
321
|
+
self.setWindowTitle("Update Check Failed")
|
|
322
|
+
self.setMinimumWidth(400)
|
|
323
|
+
|
|
324
|
+
# Set dialog background to match app theme
|
|
325
|
+
self.setStyleSheet("""
|
|
326
|
+
QDialog {
|
|
327
|
+
background-color: #1e1e2e;
|
|
328
|
+
color: #cdd6f4;
|
|
329
|
+
}
|
|
330
|
+
""")
|
|
331
|
+
|
|
332
|
+
layout = QVBoxLayout()
|
|
333
|
+
layout.setContentsMargins(20, 20, 20, 20)
|
|
334
|
+
|
|
335
|
+
# Error icon and message
|
|
336
|
+
message = QLabel(
|
|
337
|
+
f"⚠ Couldn't check for updates\n\n"
|
|
338
|
+
f"Unable to connect to the internet.\n\n"
|
|
339
|
+
f"Current version: {current_version}"
|
|
340
|
+
)
|
|
341
|
+
message.setAlignment(Qt.AlignCenter)
|
|
342
|
+
message.setFont(QFont('Arial', 11))
|
|
343
|
+
message.setStyleSheet("color: #f38ba8; margin: 20px 0;")
|
|
344
|
+
layout.addWidget(message)
|
|
345
|
+
|
|
346
|
+
# OK button
|
|
347
|
+
button_layout = QHBoxLayout()
|
|
348
|
+
button_layout.addStretch()
|
|
349
|
+
|
|
350
|
+
ok_btn = QPushButton("OK")
|
|
351
|
+
ok_btn.clicked.connect(self.accept)
|
|
352
|
+
ok_btn.setDefault(True)
|
|
353
|
+
ok_btn.setMinimumWidth(100)
|
|
354
|
+
ok_btn.setCursor(Qt.PointingHandCursor)
|
|
355
|
+
ok_btn.setStyleSheet("""
|
|
356
|
+
QPushButton {
|
|
357
|
+
background-color: #89b4fa;
|
|
358
|
+
color: #1e1e2e;
|
|
359
|
+
border: none;
|
|
360
|
+
padding: 8px 16px;
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
font-weight: bold;
|
|
363
|
+
}
|
|
364
|
+
QPushButton:hover {
|
|
365
|
+
background-color: #74c7ec;
|
|
366
|
+
}
|
|
367
|
+
""")
|
|
368
|
+
button_layout.addWidget(ok_btn)
|
|
369
|
+
button_layout.addStretch()
|
|
370
|
+
|
|
371
|
+
layout.addLayout(button_layout)
|
|
372
|
+
|
|
373
|
+
self.setLayout(layout)
|