tracedsa 1.0.0__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.
- tracedsa/__init__.py +0 -0
- tracedsa/__main__.py +404 -0
- tracedsa/bins/linux/bst +0 -0
- tracedsa/bins/linux/circqueue +0 -0
- tracedsa/bins/linux/dll +0 -0
- tracedsa/bins/linux/heap +0 -0
- tracedsa/bins/linux/ll +0 -0
- tracedsa/bins/linux/queue +0 -0
- tracedsa/bins/linux/queuell +0 -0
- tracedsa/bins/linux/stack +0 -0
- tracedsa/bins/linux/stackll +0 -0
- tracedsa/bins/macos/bst +0 -0
- tracedsa/bins/macos/circqueue +0 -0
- tracedsa/bins/macos/dll +0 -0
- tracedsa/bins/macos/heap +0 -0
- tracedsa/bins/macos/ll +0 -0
- tracedsa/bins/macos/queue +0 -0
- tracedsa/bins/macos/queuell +0 -0
- tracedsa/bins/macos/stack +0 -0
- tracedsa/bins/macos/stackll +0 -0
- tracedsa/bins/windows/bst.exe +0 -0
- tracedsa/bins/windows/circqueue.exe +0 -0
- tracedsa/bins/windows/dll.exe +0 -0
- tracedsa/bins/windows/heap.exe +0 -0
- tracedsa/bins/windows/ll.exe +0 -0
- tracedsa/bins/windows/queue.exe +0 -0
- tracedsa/bins/windows/queuell.exe +0 -0
- tracedsa/bins/windows/stack.exe +0 -0
- tracedsa/bins/windows/stackll.exe +0 -0
- tracedsa/bridge.py +74 -0
- tracedsa/screens/__init__.py +0 -0
- tracedsa/screens/confirm_dialog.py +113 -0
- tracedsa/screens/help_screen.py +80 -0
- tracedsa/screens/info_screen.py +531 -0
- tracedsa/screens/menu.py +475 -0
- tracedsa/screens/splash.py +214 -0
- tracedsa/screens/trace_screen.py +518 -0
- tracedsa/widgets/__init__.py +0 -0
- tracedsa/widgets/ascii_array.py +103 -0
- tracedsa/widgets/ascii_heap.py +73 -0
- tracedsa/widgets/ascii_tree.py +75 -0
- tracedsa/widgets/ops_log.py +30 -0
- tracedsa-1.0.0.dist-info/METADATA +81 -0
- tracedsa-1.0.0.dist-info/RECORD +47 -0
- tracedsa-1.0.0.dist-info/WHEEL +4 -0
- tracedsa-1.0.0.dist-info/entry_points.txt +2 -0
- tracedsa-1.0.0.dist-info/licenses/LICENSE +21 -0
tracedsa/__init__.py
ADDED
|
File without changes
|
tracedsa/__main__.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from textual.app import App, ComposeResult
|
|
3
|
+
from textual.binding import Binding
|
|
4
|
+
|
|
5
|
+
from tracedsa.screens.splash import SplashScreen
|
|
6
|
+
from tracedsa.screens.confirm_dialog import ConfirmDialog
|
|
7
|
+
|
|
8
|
+
import platform
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from tracedsa.bridge import DSBridge
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SHORTCUTS = {
|
|
15
|
+
"global": [
|
|
16
|
+
("q", "Quit (with confirmation)", "All screens"),
|
|
17
|
+
("h / ?", "Show this help screen", "All screens"),
|
|
18
|
+
],
|
|
19
|
+
"splash": [
|
|
20
|
+
("enter / s", "Start application", "SplashScreen"),
|
|
21
|
+
],
|
|
22
|
+
"menu": [
|
|
23
|
+
("/", "Focus search bar to filter modules", "MainMenu"),
|
|
24
|
+
("escape", "Quit (with confirmation)", "MainMenu"),
|
|
25
|
+
],
|
|
26
|
+
"trace": [
|
|
27
|
+
("escape", "Return to main menu", "TraceWindow"),
|
|
28
|
+
("Tab", "Cycle through buttons and inputs", "TraceWindow"),
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TraceDSApp(App):
|
|
34
|
+
CSS_PATH = None
|
|
35
|
+
|
|
36
|
+
SHORTCUTS = SHORTCUTS
|
|
37
|
+
|
|
38
|
+
DEFAULT_CSS = """
|
|
39
|
+
/* === App & Screen === */
|
|
40
|
+
App {
|
|
41
|
+
background: #1a1a2e;
|
|
42
|
+
color: #e0e0e0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Screen {
|
|
46
|
+
background: #1a1a2e;
|
|
47
|
+
height: 100%;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Static, Label {
|
|
51
|
+
color: #e0e0e0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Container {
|
|
55
|
+
background: transparent;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* === Main Menu === */
|
|
59
|
+
#main_menu_container {
|
|
60
|
+
layout: vertical;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: 100%;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#header-section {
|
|
66
|
+
layout: vertical;
|
|
67
|
+
align: center middle;
|
|
68
|
+
width: 100%;
|
|
69
|
+
height: auto;
|
|
70
|
+
background: #16213e;
|
|
71
|
+
border: round #0f3460;
|
|
72
|
+
padding: 1 2;
|
|
73
|
+
margin-bottom: 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#title {
|
|
77
|
+
color: #ffffff;
|
|
78
|
+
text-style: bold;
|
|
79
|
+
text-align: center;
|
|
80
|
+
width: 100%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#search_input {
|
|
84
|
+
width: 40;
|
|
85
|
+
margin: 1 0;
|
|
86
|
+
background: #1a1a2e;
|
|
87
|
+
border: round #0f3460;
|
|
88
|
+
color: #e0e0e0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#search_input:focus {
|
|
92
|
+
border: round #00d4ff;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#search_input > .input--placeholder {
|
|
96
|
+
color: #666680;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#middle_section {
|
|
100
|
+
layout: vertical;
|
|
101
|
+
align: center middle;
|
|
102
|
+
width: 100%;
|
|
103
|
+
height: auto;
|
|
104
|
+
background: #16213e;
|
|
105
|
+
border: round #0f3460;
|
|
106
|
+
padding: 1 2;
|
|
107
|
+
margin-bottom: 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#random_art {
|
|
111
|
+
color: #00d4ff;
|
|
112
|
+
text-align: center;
|
|
113
|
+
width: 100%;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#fun_fact {
|
|
117
|
+
color: #e0e0e0;
|
|
118
|
+
text-align: center;
|
|
119
|
+
text-style: italic;
|
|
120
|
+
width: 100%;
|
|
121
|
+
margin-top: 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#button_section {
|
|
125
|
+
layout: vertical;
|
|
126
|
+
align: center middle;
|
|
127
|
+
width: 100%;
|
|
128
|
+
height: 1fr;
|
|
129
|
+
padding: 1 2;
|
|
130
|
+
overflow-y: auto;
|
|
131
|
+
overflow-x: hidden;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#button_section Horizontal {
|
|
135
|
+
margin-bottom: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* === Buttons === */
|
|
139
|
+
Button {
|
|
140
|
+
background: #16213e;
|
|
141
|
+
color: #e0e0e0;
|
|
142
|
+
border: round #0f3460;
|
|
143
|
+
text-style: bold;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
Button:hover {
|
|
147
|
+
background: #00d4ff;
|
|
148
|
+
color: #1a1a2e;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
Button:focus {
|
|
152
|
+
border: round #00d4ff;
|
|
153
|
+
text-style: bold underline;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
Button.primary {
|
|
157
|
+
color: #00d4ff;
|
|
158
|
+
border: round #00d4ff;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Button.primary:hover {
|
|
162
|
+
background: #00d4ff;
|
|
163
|
+
color: #1a1a2e;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
Button.success {
|
|
167
|
+
color: #ffffff;
|
|
168
|
+
border: round #0f3460;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Button.success:hover {
|
|
172
|
+
background: #00d4ff;
|
|
173
|
+
color: #1a1a2e;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Button.default {
|
|
177
|
+
color: #666680;
|
|
178
|
+
border: round #0f3460;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Button.default:hover {
|
|
182
|
+
color: #e0e0e0;
|
|
183
|
+
border: round #00d4ff;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* === Trace Window === */
|
|
187
|
+
#trace_window_container {
|
|
188
|
+
layout: vertical;
|
|
189
|
+
width: 100%;
|
|
190
|
+
height: 100%;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#trace_header {
|
|
194
|
+
layout: horizontal;
|
|
195
|
+
content-align: left middle;
|
|
196
|
+
width: 100%;
|
|
197
|
+
height: auto;
|
|
198
|
+
background: #16213e;
|
|
199
|
+
border: round #0f3460;
|
|
200
|
+
padding: 0 2;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#trace_header #back_button {
|
|
204
|
+
width: auto;
|
|
205
|
+
color: #666680;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#trace_header #back_button:hover {
|
|
209
|
+
color: #00d4ff;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#module_name {
|
|
213
|
+
color: #ffffff;
|
|
214
|
+
text-style: bold;
|
|
215
|
+
text-align: center;
|
|
216
|
+
width: 1fr;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#main_content {
|
|
220
|
+
layout: horizontal;
|
|
221
|
+
width: 100%;
|
|
222
|
+
height: 1fr;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#ascii_panel {
|
|
226
|
+
width: 60%;
|
|
227
|
+
height: 100%;
|
|
228
|
+
background: #16213e;
|
|
229
|
+
border: round #0f3460;
|
|
230
|
+
padding: 1 2;
|
|
231
|
+
overflow: auto;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#ascii_placeholder {
|
|
235
|
+
color: #666680;
|
|
236
|
+
text-align: center;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#ascii_panel Static {
|
|
240
|
+
width: 100%;
|
|
241
|
+
height: auto;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
ASCIIArray, ASCIIBranchTree, ASCIIHeap {
|
|
245
|
+
width: 100%;
|
|
246
|
+
height: 100%;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
#log_panel {
|
|
250
|
+
width: 40%;
|
|
251
|
+
height: 100%;
|
|
252
|
+
background: #16213e;
|
|
253
|
+
border: round #0f3460;
|
|
254
|
+
layout: vertical;
|
|
255
|
+
padding: 0 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
#log_header {
|
|
259
|
+
color: #00d4ff;
|
|
260
|
+
text-style: bold;
|
|
261
|
+
width: 100%;
|
|
262
|
+
text-align: center;
|
|
263
|
+
padding: 1 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#log_panel RichLog {
|
|
267
|
+
background: #1a1a2e;
|
|
268
|
+
color: #e0e0e0;
|
|
269
|
+
width: 100%;
|
|
270
|
+
height: 1fr;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
OpsLog {
|
|
274
|
+
width: 100%;
|
|
275
|
+
height: 1fr;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#button_container {
|
|
279
|
+
width: 100%;
|
|
280
|
+
height: auto;
|
|
281
|
+
background: #16213e;
|
|
282
|
+
border: round #0f3460;
|
|
283
|
+
padding: 1 2;
|
|
284
|
+
layout: horizontal;
|
|
285
|
+
overflow-x: auto;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
#button_container Horizontal {
|
|
289
|
+
width: auto;
|
|
290
|
+
margin-right: 1;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#button_container Button {
|
|
294
|
+
margin: 0 1;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.clear-log {
|
|
298
|
+
margin-left: 3;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* === Status Bar === */
|
|
302
|
+
#status_bar {
|
|
303
|
+
color: #666680;
|
|
304
|
+
text-style: italic;
|
|
305
|
+
width: 100%;
|
|
306
|
+
height: auto;
|
|
307
|
+
padding: 0 2;
|
|
308
|
+
background: #16213e;
|
|
309
|
+
border-top: solid #0f3460;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* === Inputs === */
|
|
313
|
+
Input {
|
|
314
|
+
background: #1a1a2e;
|
|
315
|
+
color: #e0e0e0;
|
|
316
|
+
border: round #0f3460;
|
|
317
|
+
width: 12;
|
|
318
|
+
margin-right: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
Input:focus {
|
|
322
|
+
border: round #00d4ff;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
Input > .input--placeholder {
|
|
326
|
+
color: #666680;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
#button_container Input {
|
|
330
|
+
width: 12;
|
|
331
|
+
max-width: 16;
|
|
332
|
+
margin: 0 1;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* === Scrollbar === */
|
|
336
|
+
ScrollBar {
|
|
337
|
+
background: #1a1a2e;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
ScrollBar:hover {
|
|
341
|
+
background: #16213e;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
ScrollBar > .scrollbar--thumb {
|
|
345
|
+
background: #00d4ff;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
ScrollBar > .scrollbar--thumb:hover {
|
|
349
|
+
background: #ffffff;
|
|
350
|
+
}
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
TITLE = "TraceDSA"
|
|
354
|
+
SUB_TITLE = "Data Structures Visualization"
|
|
355
|
+
|
|
356
|
+
BINDINGS = [
|
|
357
|
+
Binding("q", "show_confirm_quit", "Quit", priority=True),
|
|
358
|
+
Binding("TAB", "cycle_focus", "Cycle Focus")
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
def __init__(self):
|
|
362
|
+
super().__init__()
|
|
363
|
+
self.bridges = {}
|
|
364
|
+
|
|
365
|
+
def initialize_bridge(self, name: str) -> bool:
|
|
366
|
+
sysname = platform.system().lower()
|
|
367
|
+
binary_path = f"tracedsa/bins/{sysname}/{name}"
|
|
368
|
+
if os.path.exists(binary_path):
|
|
369
|
+
try:
|
|
370
|
+
self.bridges[name] = DSBridge(name)
|
|
371
|
+
return True
|
|
372
|
+
except Exception:
|
|
373
|
+
return False
|
|
374
|
+
return False
|
|
375
|
+
|
|
376
|
+
def get_bridge(self, name: str):
|
|
377
|
+
return self.bridges.get(name)
|
|
378
|
+
|
|
379
|
+
def compose(self) -> ComposeResult:
|
|
380
|
+
yield from []
|
|
381
|
+
|
|
382
|
+
def on_mount(self) -> None:
|
|
383
|
+
self.push_screen(SplashScreen())
|
|
384
|
+
|
|
385
|
+
def action_show_confirm_quit(self) -> None:
|
|
386
|
+
from tracedsa.screens.confirm_dialog import ConfirmDialog
|
|
387
|
+
if isinstance(self.screen, ConfirmDialog):
|
|
388
|
+
self.exit()
|
|
389
|
+
else:
|
|
390
|
+
self.push_screen(ConfirmDialog())
|
|
391
|
+
|
|
392
|
+
def action_quit(self) -> None:
|
|
393
|
+
for bridge in self.bridges.values():
|
|
394
|
+
bridge.close()
|
|
395
|
+
self.exit()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def main():
|
|
399
|
+
app = TraceDSApp()
|
|
400
|
+
app.run()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
if __name__ == "__main__":
|
|
404
|
+
main()
|
tracedsa/bins/linux/bst
ADDED
|
Binary file
|
|
Binary file
|
tracedsa/bins/linux/dll
ADDED
|
Binary file
|
tracedsa/bins/linux/heap
ADDED
|
Binary file
|
tracedsa/bins/linux/ll
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tracedsa/bins/macos/bst
ADDED
|
Binary file
|
|
Binary file
|
tracedsa/bins/macos/dll
ADDED
|
Binary file
|
tracedsa/bins/macos/heap
ADDED
|
Binary file
|
tracedsa/bins/macos/ll
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
tracedsa/bridge.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import platform
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
def get_binary(name):
|
|
6
|
+
system = platform.system().lower()
|
|
7
|
+
ext = ".exe" if system == "windows" else ""
|
|
8
|
+
folder = "macos" if system == "darwin" else system
|
|
9
|
+
base = os.path.dirname(__file__)
|
|
10
|
+
return os.path.join(base, "bins", folder, f"{name}{ext}")
|
|
11
|
+
|
|
12
|
+
class DSBridge:
|
|
13
|
+
def __init__(self, name):
|
|
14
|
+
self.name = name
|
|
15
|
+
self.proc = subprocess.Popen(
|
|
16
|
+
[get_binary(name)],
|
|
17
|
+
stdin=subprocess.PIPE,
|
|
18
|
+
stdout=subprocess.PIPE,
|
|
19
|
+
stderr=subprocess.PIPE,
|
|
20
|
+
text=True
|
|
21
|
+
)
|
|
22
|
+
ready_line = self.proc.stdout.readline().strip()
|
|
23
|
+
if not ready_line:
|
|
24
|
+
stderr_tail = self._read_stderr()
|
|
25
|
+
raise RuntimeError(
|
|
26
|
+
f"Binary '{name}' produced no output (crashed?). "
|
|
27
|
+
f"Stderr: {stderr_tail or '(empty)'}"
|
|
28
|
+
)
|
|
29
|
+
if ready_line != "READY":
|
|
30
|
+
raise RuntimeError(f"Expected READY from '{name}', got: {ready_line}")
|
|
31
|
+
|
|
32
|
+
def _read_stderr(self):
|
|
33
|
+
try:
|
|
34
|
+
return self.proc.stderr.read().strip()
|
|
35
|
+
except Exception:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
def is_alive(self) -> bool:
|
|
39
|
+
return self.proc.poll() is None
|
|
40
|
+
|
|
41
|
+
def send(self, command):
|
|
42
|
+
if not self.is_alive():
|
|
43
|
+
raise RuntimeError("Process terminated")
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
self.proc.stdin.write(command + "\n")
|
|
47
|
+
self.proc.stdin.flush()
|
|
48
|
+
except (BrokenPipeError, OSError) as e:
|
|
49
|
+
raise RuntimeError(f"Failed to send command: {e}")
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = self.proc.stdout.readline()
|
|
53
|
+
except (ValueError, OSError) as e:
|
|
54
|
+
raise RuntimeError(f"Failed to read response: {e}")
|
|
55
|
+
|
|
56
|
+
if not response:
|
|
57
|
+
stderr_tail = self._read_stderr()
|
|
58
|
+
detail = f" (stderr: {stderr_tail})" if stderr_tail else ""
|
|
59
|
+
raise RuntimeError(f"Binary '{self.name}' closed stdout — likely crashed{detail}")
|
|
60
|
+
|
|
61
|
+
return response.strip()
|
|
62
|
+
|
|
63
|
+
def close(self):
|
|
64
|
+
try:
|
|
65
|
+
if self.is_alive():
|
|
66
|
+
self.proc.stdin.write("EXIT\n")
|
|
67
|
+
self.proc.stdin.flush()
|
|
68
|
+
except (BrokenPipeError, OSError):
|
|
69
|
+
pass
|
|
70
|
+
finally:
|
|
71
|
+
try:
|
|
72
|
+
self.proc.terminate()
|
|
73
|
+
except ProcessLookupError:
|
|
74
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from textual.app import ComposeResult
|
|
2
|
+
from textual.containers import Container, Horizontal
|
|
3
|
+
from textual.widgets import Static, Button
|
|
4
|
+
from textual.screen import Screen
|
|
5
|
+
from textual.binding import Binding
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConfirmDialog(Screen):
|
|
9
|
+
|
|
10
|
+
DEFAULT_CSS = """
|
|
11
|
+
ConfirmDialog {
|
|
12
|
+
align: center middle;
|
|
13
|
+
background: rgba(0, 0, 0, 0.7);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#confirm-box {
|
|
17
|
+
width: 44;
|
|
18
|
+
height: auto;
|
|
19
|
+
padding: 2 4;
|
|
20
|
+
background: #16213e;
|
|
21
|
+
border: round #00d4ff;
|
|
22
|
+
align: center middle;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#confirm-title {
|
|
26
|
+
text-align: center;
|
|
27
|
+
width: 100%;
|
|
28
|
+
color: #ffffff;
|
|
29
|
+
text-style: bold;
|
|
30
|
+
margin-bottom: 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#confirm-subtitle {
|
|
34
|
+
text-align: center;
|
|
35
|
+
width: 100%;
|
|
36
|
+
color: #666680;
|
|
37
|
+
margin-bottom: 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#confirm-buttons {
|
|
41
|
+
align: center middle;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: auto;
|
|
44
|
+
margin-top: 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#confirm-y {
|
|
48
|
+
background: #16213e;
|
|
49
|
+
border: round #00d4ff;
|
|
50
|
+
color: #00d4ff;
|
|
51
|
+
width: 1;
|
|
52
|
+
margin-right: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#confirm-y:hover {
|
|
56
|
+
background: #00d4ff;
|
|
57
|
+
color: #1a1a2e;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#confirm-n {
|
|
61
|
+
background: #16213e;
|
|
62
|
+
border: round #666680;
|
|
63
|
+
color: #666680;
|
|
64
|
+
width: 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#confirm-n:hover {
|
|
68
|
+
background: #666680;
|
|
69
|
+
color: #1a1a2e;
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
BINDINGS = [
|
|
74
|
+
Binding("y", "confirm", "Yes"),
|
|
75
|
+
Binding("n", "cancel", "No"),
|
|
76
|
+
Binding("escape", "cancel", "Cancel"),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
def __init__(self, title: str = "Exit TraceDSA?", message: str = "All unsaved state will be lost.", on_confirm = None):
|
|
80
|
+
super().__init__()
|
|
81
|
+
self._title = title
|
|
82
|
+
self._message = message
|
|
83
|
+
self._on_confirm = on_confirm
|
|
84
|
+
|
|
85
|
+
def compose(self) -> ComposeResult:
|
|
86
|
+
yield Container(
|
|
87
|
+
Static(self._title, id="confirm-title"),
|
|
88
|
+
Static(self._message, id="confirm-subtitle"),
|
|
89
|
+
Horizontal(
|
|
90
|
+
Button("Yes [y]", id="confirm-y"),
|
|
91
|
+
Button("No [n]", id="confirm-n"),
|
|
92
|
+
id="confirm-buttons"
|
|
93
|
+
),
|
|
94
|
+
id="confirm-box"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def action_confirm(self) -> None:
|
|
98
|
+
if self._on_confirm:
|
|
99
|
+
self._on_confirm()
|
|
100
|
+
else:
|
|
101
|
+
self.app.exit()
|
|
102
|
+
|
|
103
|
+
def action_cancel(self) -> None:
|
|
104
|
+
self.app.pop_screen()
|
|
105
|
+
|
|
106
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
107
|
+
if event.button.id == "confirm-y":
|
|
108
|
+
if self._on_confirm:
|
|
109
|
+
self._on_confirm()
|
|
110
|
+
else:
|
|
111
|
+
self.app.exit()
|
|
112
|
+
else:
|
|
113
|
+
self.app.pop_screen()
|