basebender 0.2.1__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.
@@ -0,0 +1,546 @@
1
+ """
2
+ This module provides the graphical user interface (GUI) for the BaseBender
3
+ tool.
4
+
5
+ It allows users to interactively rebase strings between different digit sets,
6
+ manage digit set presets, and view rebase results.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from typing import Any
13
+
14
+ from PySide6.QtCore import QEvent, QObject, QSize, Qt
15
+ from PySide6.QtSvgWidgets import QSvgWidget
16
+ from PySide6.QtWidgets import (
17
+ QApplication,
18
+ QCheckBox,
19
+ QComboBox,
20
+ QHBoxLayout,
21
+ QLabel,
22
+ QMainWindow,
23
+ QMenu,
24
+ QPushButton,
25
+ QSizePolicy,
26
+ QStatusBar,
27
+ QTextEdit,
28
+ QToolButton,
29
+ QVBoxLayout,
30
+ QWidget,
31
+ )
32
+
33
+ from basebender.rebaser.config_loader import load_ui_state, save_ui_state
34
+ from basebender.rebaser.digit_set_rebaser import DigitSetRebaser
35
+ from basebender.rebaser.digit_sets import get_predefined_digit_sets
36
+ from basebender.rebaser.generated import app_resources_rc
37
+ from basebender.rebaser.models import DigitSet
38
+
39
+ ICON_INPUT_QRC_PATH = ":/app/icons/input.svg"
40
+ ICON_OUTPUT_QRC_PATH = ":/app/icons/output.svg"
41
+ ICON_SOURCE_QRC_PATH = ":/app/icons/source.svg"
42
+ ICON_TARGET_QRC_PATH = ":/app/icons/target.svg"
43
+
44
+
45
+ TEXT_EDIT_FIXED_HEIGHT = 30
46
+
47
+
48
+ class MainWindow(QMainWindow):
49
+ """
50
+ Main window for the BaseBender GUI application.
51
+
52
+ Provides an interactive interface for rebaseing strings between different
53
+ digit sets.
54
+ """
55
+
56
+ def __init__(self) -> None:
57
+ super().__init__()
58
+ app_resources_rc.qInitResources()
59
+ self.setWindowTitle("BaseBender")
60
+ self.setGeometry(100, 100, 800, 600)
61
+
62
+ self.central_widget: QWidget = QWidget()
63
+ self.setCentralWidget(self.central_widget)
64
+ self.main_layout: QVBoxLayout = QVBoxLayout(self.central_widget)
65
+
66
+ self._setup_ui()
67
+ self._connect_signals()
68
+ self._load_initial_state()
69
+ self.status_bar: QStatusBar = self.statusBar() # Initialize status bar
70
+
71
+ def _load_svg_icon(self, qrc_path: str, size: int = 20) -> QSvgWidget:
72
+ """
73
+ Loads an SVG icon from the Qt Resource System (QRC).
74
+
75
+ Args:
76
+ qrc_path: The path to the SVG icon within the Qt Resource System.
77
+ size: The desired size (width and height) for the SVG icon.
78
+ Defaults to 20.
79
+ Returns:
80
+ A QSvgWidget instance displaying the loaded SVG icon.
81
+ """
82
+ # Create a QSvgWidget to render the SVG
83
+ svg_widget: QSvgWidget = QSvgWidget(qrc_path)
84
+ svg_widget.setFixedSize(QSize(size, size))
85
+ return svg_widget
86
+
87
+ def _setup_ui(self) -> None:
88
+ self._setup_input_area()
89
+ self._setup_digit_set_sections()
90
+ self._setup_rebase_controls()
91
+ self._setup_output_area()
92
+
93
+ def _setup_input_area(self) -> None:
94
+ input_layout = QHBoxLayout()
95
+ self.input_label = QLabel("Input String:")
96
+ input_icon = self._load_svg_icon(ICON_INPUT_QRC_PATH)
97
+ input_layout.addWidget(input_icon)
98
+ input_layout.addWidget(self.input_label)
99
+ input_layout.addStretch()
100
+ self.input_text_edit = QTextEdit()
101
+ self.input_text_edit.setPlaceholderText("Enter string to rebase...")
102
+ self.main_layout.addLayout(input_layout)
103
+ self.main_layout.addWidget(self.input_text_edit)
104
+
105
+ def _setup_digit_set_sections(self) -> None:
106
+ digit_set_layout = QHBoxLayout()
107
+ self.main_layout.addLayout(digit_set_layout)
108
+ self._setup_source_digit_set(digit_set_layout)
109
+ self._setup_target_digit_set(digit_set_layout)
110
+
111
+ def _setup_source_digit_set(self, parent_layout: QHBoxLayout) -> None:
112
+ group_layout = QVBoxLayout()
113
+ label_layout = QHBoxLayout()
114
+ label = QLabel("Input Digit Set:")
115
+ icon = self._load_svg_icon(ICON_SOURCE_QRC_PATH)
116
+ label_layout.addWidget(icon)
117
+ label_layout.addWidget(label)
118
+ label_layout.addStretch()
119
+
120
+ edit_layout = QHBoxLayout()
121
+ self.input_digit_set_text_edit = QTextEdit()
122
+ self.input_digit_set_text_edit.setFixedHeight(TEXT_EDIT_FIXED_HEIGHT)
123
+ self.input_digit_set_text_edit.setPlaceholderText(
124
+ "Enter input digit set or select preset (or leave empty for dynamic derivation)..."
125
+ )
126
+ self.input_digit_set_text_edit.setToolTip(
127
+ "Define the set of characters used in the input string. "
128
+ "Leave empty for dynamic derivation."
129
+ )
130
+ edit_layout.addWidget(self.input_digit_set_text_edit)
131
+
132
+ self.input_digit_set_tool_button = QToolButton()
133
+ self.input_digit_set_tool_button.setText("...")
134
+ self.input_digit_set_tool_button.setToolTip("Digit Set Manipulation Options")
135
+ self.input_digit_set_tool_button.setPopupMode(QToolButton.InstantPopup)
136
+ self.input_digit_set_tool_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
137
+ self._setup_digit_set_menu(
138
+ self.input_digit_set_tool_button, self.input_digit_set_text_edit
139
+ )
140
+ edit_layout.addWidget(self.input_digit_set_tool_button)
141
+
142
+ self.input_digit_set_preset_combo = QComboBox()
143
+ self.input_digit_set_preset_combo.addItem("Select Preset...")
144
+ self.input_digit_set_preset_combo.addItem("Derived from Input")
145
+ self._populate_digit_set_presets(self.input_digit_set_preset_combo)
146
+ self.input_digit_set_preset_combo.setToolTip(
147
+ "Select a predefined digit set or choose to derive from input."
148
+ )
149
+
150
+ group_layout.addLayout(label_layout)
151
+ group_layout.addLayout(edit_layout)
152
+ group_layout.addWidget(self.input_digit_set_preset_combo)
153
+ parent_layout.addLayout(group_layout)
154
+
155
+ def _setup_target_digit_set(self, parent_layout: QHBoxLayout) -> None:
156
+ group_layout = QVBoxLayout()
157
+ label_layout = QHBoxLayout()
158
+ label = QLabel("Output Digit Set:")
159
+ icon = self._load_svg_icon(ICON_TARGET_QRC_PATH)
160
+ label_layout.addWidget(icon)
161
+ label_layout.addWidget(label)
162
+ label_layout.addStretch()
163
+
164
+ edit_layout = QHBoxLayout()
165
+ self.output_digit_set_text_edit = QTextEdit()
166
+ self.output_digit_set_text_edit.setFixedHeight(TEXT_EDIT_FIXED_HEIGHT)
167
+ self.output_digit_set_text_edit.setPlaceholderText(
168
+ "Enter output digit set or select preset..."
169
+ )
170
+ self.output_digit_set_text_edit.setToolTip(
171
+ "Define the set of characters for the rebased output string."
172
+ )
173
+ edit_layout.addWidget(self.output_digit_set_text_edit)
174
+
175
+ self.output_digit_set_tool_button = QToolButton()
176
+ self.output_digit_set_tool_button.setText("...")
177
+ self.output_digit_set_tool_button.setToolTip("Digit Set Manipulation Options")
178
+ self.output_digit_set_tool_button.setPopupMode(QToolButton.InstantPopup)
179
+ self.output_digit_set_tool_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
180
+ self._setup_digit_set_menu(
181
+ self.output_digit_set_tool_button, self.output_digit_set_text_edit
182
+ )
183
+ edit_layout.addWidget(self.output_digit_set_tool_button)
184
+
185
+ self.output_digit_set_preset_combo = QComboBox()
186
+ self.output_digit_set_preset_combo.addItem("Select Preset...")
187
+ self._populate_digit_set_presets(self.output_digit_set_preset_combo)
188
+ self.output_digit_set_preset_combo.setToolTip(
189
+ "Select a predefined digit set for the output."
190
+ )
191
+
192
+ group_layout.addLayout(label_layout)
193
+ group_layout.addLayout(edit_layout)
194
+ group_layout.addWidget(self.output_digit_set_preset_combo)
195
+ parent_layout.addLayout(group_layout)
196
+
197
+ def _setup_rebase_controls(self) -> None:
198
+ rebase_control_layout = QHBoxLayout()
199
+ self.rebase_button = QPushButton("Rebase")
200
+ self.rebase_button.setToolTip("Perform the rebase operation based on current inputs.")
201
+ self.realtime_checkbox = QCheckBox("Real-time Rebase")
202
+ self.realtime_checkbox.setToolTip("Enable or disable automatic rebase as you type.")
203
+ rebase_control_layout.addWidget(self.rebase_button)
204
+ rebase_control_layout.addWidget(self.realtime_checkbox)
205
+ self.main_layout.addLayout(rebase_control_layout)
206
+
207
+ def _setup_output_area(self) -> None:
208
+ output_layout = QHBoxLayout()
209
+ self.output_label = QLabel("Rebased String:")
210
+ output_icon = self._load_svg_icon(ICON_OUTPUT_QRC_PATH)
211
+ output_layout.addWidget(output_icon)
212
+ output_layout.addWidget(self.output_label)
213
+ output_layout.addStretch()
214
+ self.output_text_edit = QTextEdit()
215
+ self.output_text_edit.setReadOnly(True)
216
+ self.output_text_edit.setPlaceholderText("Rebased output will appear here...")
217
+ self.output_text_edit.setToolTip("The result of the rebase operation.")
218
+ self.main_layout.addLayout(output_layout)
219
+ self.main_layout.addWidget(self.output_text_edit)
220
+
221
+ def _populate_digit_set_presets(self, combo_box: QComboBox) -> None:
222
+ """
223
+ Populates the given QComboBox with predefined digit set names.
224
+
225
+ Args:
226
+ combo_box: The QComboBox widget to populate.
227
+ """
228
+ digit_sets: dict[str, DigitSet] = get_predefined_digit_sets()
229
+ for name in sorted(digit_sets.keys()):
230
+ combo_box.addItem(name)
231
+
232
+ def _connect_signals(self) -> None:
233
+ """
234
+ Connects UI signals to their respective slots for interactive behavior.
235
+ """
236
+ self.rebase_button.clicked.connect(self._perform_rebase)
237
+ self.input_digit_set_preset_combo.currentIndexChanged.connect(
238
+ self._update_input_ds_from_preset
239
+ )
240
+ self.output_digit_set_preset_combo.currentIndexChanged.connect(
241
+ self._update_output_ds_from_preset
242
+ )
243
+ self.realtime_checkbox.stateChanged.connect(self._toggle_realtime_rebase)
244
+ self.input_text_edit.textChanged.connect(self._on_input_changed)
245
+ self.input_digit_set_text_edit.textChanged.connect(self._on_digit_set_changed)
246
+ self.input_digit_set_text_edit.textChanged.connect(self._handle_input_ds_dynamic_state)
247
+ # Connect focusInEvent for clearing dynamic state
248
+ self.input_digit_set_text_edit.installEventFilter(self)
249
+ self.output_digit_set_text_edit.textChanged.connect(self._on_digit_set_changed)
250
+
251
+ def _update_input_ds_from_preset(self, index: int) -> None:
252
+ """
253
+ Updates the input digit set text edit based on the selected preset
254
+ from the input digit set combo box.
255
+
256
+ Args:
257
+ index: The index of the selected item in the combo box.
258
+ """
259
+ if index > 0: # Skip "Select Preset..."
260
+ selected_name: str = self.input_digit_set_preset_combo.currentText()
261
+ if selected_name == "Derived from Input":
262
+ input_string_content: str = self.input_text_edit.toPlainText()
263
+
264
+ derived_digits = DigitSet.deduplicate_digits(input_string_content)
265
+
266
+ if derived_digits:
267
+ self.input_digit_set_text_edit.setText(derived_digits)
268
+ else:
269
+ self.input_digit_set_text_edit.clear()
270
+ self.input_digit_set_text_edit.setPlaceholderText(
271
+ "Enter input digit set or select preset (or leave empty "
272
+ "for dynamic derivation)..."
273
+ )
274
+ self.input_digit_set_text_edit.setStyleSheet("")
275
+
276
+ else:
277
+ digit_sets: dict[str, DigitSet] = get_predefined_digit_sets()
278
+ digit_set_obj: DigitSet | None = digit_sets.get(selected_name)
279
+
280
+ if digit_set_obj:
281
+ self.input_digit_set_text_edit.setText(digit_set_obj.digits)
282
+ else:
283
+ self.input_digit_set_text_edit.clear()
284
+ self.input_digit_set_text_edit.setPlaceholderText(
285
+ "Enter input digit set or select preset (or leave empty "
286
+ "for dynamic derivation)..."
287
+ )
288
+ self.input_digit_set_text_edit.setStyleSheet("")
289
+
290
+ def _update_output_ds_from_preset(self, index: int) -> None:
291
+ """
292
+ Updates the output digit set text edit based on the selected preset
293
+ from the output digit set combo box.
294
+
295
+ Args:
296
+ index: The index of the selected item in the combo box.
297
+ """
298
+ if index > 0:
299
+ selected_name: str = self.output_digit_set_preset_combo.currentText()
300
+
301
+ digit_sets: dict[str, DigitSet] = get_predefined_digit_sets()
302
+ digit_set_obj: DigitSet | None = digit_sets.get(selected_name)
303
+ if digit_set_obj:
304
+ self.output_digit_set_text_edit.setText(digit_set_obj.digits)
305
+ else:
306
+ self.output_digit_set_text_edit.clear()
307
+ self.output_digit_set_text_edit.setStyleSheet("")
308
+
309
+ def _toggle_realtime_rebase(self, state: int) -> None:
310
+ """
311
+ Toggles real-time rebase functionality based on the checkbox state.
312
+
313
+ Args:
314
+ state: The new state of the checkbox (Qt.Checked or Qt.Unchecked).
315
+ """
316
+ if state == Qt.Checked:
317
+ self.rebase_button.setEnabled(False)
318
+ self._perform_rebase() # Perform initial rebase
319
+ else:
320
+ self.rebase_button.setEnabled(True)
321
+
322
+ def _on_input_changed(self) -> None:
323
+ """
324
+ Handles changes in the input string text edit.
325
+ Triggers rebase if real-time rebase is enabled and updates dynamic state.
326
+ """
327
+ if self.realtime_checkbox.isChecked():
328
+ self._perform_rebase()
329
+ self._handle_input_ds_dynamic_state()
330
+
331
+ def _on_digit_set_changed(self) -> None:
332
+ """
333
+ Handles changes in the digit set text edits (input and output).
334
+ Triggers rebase if real-time rebase is enabled and updates dynamic state.
335
+ """
336
+ if self.realtime_checkbox.isChecked():
337
+ self._perform_rebase()
338
+ self._handle_input_ds_dynamic_state()
339
+
340
+ def _handle_input_ds_dynamic_state(self) -> None:
341
+ """
342
+ Manages the dynamic placeholder text and styling for the input digit set
343
+ text edit based on the content of the input string.
344
+
345
+ If the input digit set field is empty and the input string is not empty,
346
+ it derives and displays a "Derived:" placeholder. Otherwise, it resets
347
+ the placeholder and styling.
348
+ """
349
+ input_string_content: str = self.input_text_edit.toPlainText()
350
+ if not self.input_digit_set_text_edit.toPlainText(): # If input digit set field is empty
351
+ if input_string_content: # If input string is not empty
352
+ derived_digits = DigitSet.deduplicate_digits(input_string_content)
353
+
354
+ if derived_digits:
355
+ self.input_digit_set_text_edit.setPlaceholderText(f"Derived: {derived_digits}")
356
+ self.input_digit_set_text_edit.setStyleSheet("background-color: lightyellow;")
357
+ else:
358
+ self.input_digit_set_text_edit.setPlaceholderText(
359
+ "Enter input digit set or select preset (or leave empty "
360
+ "for dynamic derivation)..."
361
+ )
362
+ self.input_digit_set_text_edit.setStyleSheet("")
363
+ else:
364
+ self.input_digit_set_text_edit.setPlaceholderText(
365
+ "Enter input digit set or select preset (or leave empty "
366
+ "for dynamic derivation)..."
367
+ )
368
+ self.input_digit_set_text_edit.setStyleSheet("")
369
+ else:
370
+ self.input_digit_set_text_edit.setPlaceholderText(
371
+ "Enter input digit set or select preset (or leave empty for dynamic derivation)..."
372
+ )
373
+ self.input_digit_set_text_edit.setStyleSheet("")
374
+
375
+ def eventFilter(self, obj: QObject, event: QEvent) -> bool:
376
+ """
377
+ Filters events for specific UI elements, primarily to handle focus
378
+ events for the input digit set text edit.
379
+
380
+ Args:
381
+ obj: The object for which the event occurred.
382
+ event: The event that occurred.
383
+
384
+ Returns:
385
+ True if the event was handled, False otherwise.
386
+ """
387
+ if obj == self.input_digit_set_text_edit and event.type() == QEvent.FocusIn:
388
+ # If currently showing a derived placeholder
389
+ # and user focuses, clear it
390
+ if (
391
+ self.input_digit_set_text_edit.toPlainText() == ""
392
+ and self.input_digit_set_text_edit.placeholderText().startswith("Derived:")
393
+ ):
394
+ self.input_digit_set_text_edit.setPlaceholderText(
395
+ "Enter input digit set or select preset (or leave empty "
396
+ "for dynamic derivation)..."
397
+ )
398
+ self.input_digit_set_text_edit.setStyleSheet("")
399
+ if self.input_digit_set_preset_combo.currentText() == "Derived from Input":
400
+ self.input_digit_set_preset_combo.setCurrentIndex(0)
401
+ return super().eventFilter(obj, event)
402
+
403
+ def _perform_rebase(self) -> None:
404
+ """
405
+ Performs the rebase operation based on the current input string,
406
+ source digit set, and target digit set from the UI.
407
+ Displays the rebased string or an error message in the status bar.
408
+ """
409
+ input_string: str = self.input_text_edit.toPlainText()
410
+ input_digit_set_str: str | None = self.input_digit_set_text_edit.toPlainText()
411
+ output_digit_set_str: str | None = self.output_digit_set_text_edit.toPlainText()
412
+
413
+ input_digit_set_obj: DigitSet | None = None
414
+ output_digit_set_obj: DigitSet | None = None
415
+
416
+ # Determine input digit set object
417
+ if input_digit_set_str:
418
+ input_digit_set_obj = DigitSet(
419
+ name="Custom Input",
420
+ digits=input_digit_set_str,
421
+ source="gui_input",
422
+ )
423
+
424
+ # Determine output digit set object
425
+ if output_digit_set_str:
426
+ if len(set(output_digit_set_str)) == 1:
427
+ output_digit_set_obj = None
428
+ else:
429
+ output_digit_set_obj = DigitSet(
430
+ name="Custom Output",
431
+ digits=output_digit_set_str,
432
+ source="gui_input",
433
+ )
434
+
435
+ self.status_bar.clearMessage() # Clear any previous messages
436
+ self.output_text_edit.clear() # Clear output on new rebase attempt
437
+
438
+ try:
439
+ rebaser = DigitSetRebaser(
440
+ out_digit_set=output_digit_set_obj,
441
+ in_digit_set=input_digit_set_obj,
442
+ )
443
+ rebased_string: str = rebaser.rebase(input_string)
444
+ self.output_text_edit.setText(rebased_string)
445
+ self.status_bar.clearMessage()
446
+ except ValueError as exc:
447
+ error_message: str = f"Error: {exc}"
448
+ self.status_bar.showMessage(error_message)
449
+ print(error_message, file=sys.stderr)
450
+ except IndexError as exc:
451
+ error_message = f"Error: {exc}"
452
+ self.status_bar.showMessage(error_message)
453
+ print(error_message, file=sys.stderr)
454
+ except (
455
+ RuntimeError
456
+ ) as err: # Catching RuntimeError as an isolation point for unexpected errors.
457
+ error_message = f"An unexpected error occurred: {err}"
458
+ self.status_bar.showMessage(error_message)
459
+ print(error_message, file=sys.stderr)
460
+
461
+ def _setup_digit_set_menu(self, button: QToolButton, text_edit: QTextEdit) -> None:
462
+ """
463
+ Sets up the context menu for digit set text edits, providing options
464
+ like Clear, Sort, and Deduplicate.
465
+
466
+ Args:
467
+ button: The QToolButton to attach the menu to.
468
+ text_edit: The QTextEdit associated with the digit set.
469
+ """
470
+ menu = QMenu(self)
471
+
472
+ clear_action = menu.addAction("Clear")
473
+ clear_action.setToolTip("Clear the content of the digit set field.")
474
+ clear_action.triggered.connect(text_edit.clear)
475
+
476
+ sort_action = menu.addAction("Sort")
477
+ sort_action.setToolTip("Sort the characters in the digit set alphabetically.")
478
+ sort_action.triggered.connect(lambda: self._sort_digit_set_text(text_edit))
479
+
480
+ deduplicate_action = menu.addAction("Deduplicate")
481
+ deduplicate_action.setToolTip(
482
+ "Remove duplicate characters from the digit set, preserving order of first appearance."
483
+ )
484
+ deduplicate_action.triggered.connect(lambda: self._deduplicate_digit_set_text(text_edit))
485
+
486
+ button.setMenu(menu)
487
+
488
+ @staticmethod
489
+ def _sort_digit_set_text(text_edit: QTextEdit) -> None:
490
+ current_text = text_edit.toPlainText()
491
+ if current_text:
492
+ text_edit.setText(DigitSet.sorted_digits(current_text))
493
+
494
+ @staticmethod
495
+ def _deduplicate_digit_set_text(text_edit: QTextEdit) -> None:
496
+ current_text = text_edit.toPlainText()
497
+ if current_text:
498
+ text_edit.setText(DigitSet.deduplicate_digits(current_text))
499
+
500
+ def _load_initial_state(self) -> None:
501
+ """
502
+ Loads the saved UI state (input string, digit sets, real-time setting)
503
+ from the configuration file and applies it to the UI elements.
504
+ """
505
+ state: dict[str, Any] = load_ui_state()
506
+ if state:
507
+ self.input_text_edit.setText(state.get("last_input", ""))
508
+ self.input_digit_set_text_edit.setText(state.get("last_source_digit_set", ""))
509
+ self.output_digit_set_text_edit.setText(state.get("last_target_digit_set", ""))
510
+ self.realtime_checkbox.setChecked(state.get("realtime_enabled", False))
511
+ self._handle_input_ds_dynamic_state()
512
+
513
+ def closeEvent(self, event: QEvent) -> None:
514
+ """
515
+ Overrides the default close event to save the current UI state
516
+ before the application exits.
517
+
518
+ Args:
519
+ event: The close event.
520
+ """
521
+ state_data: dict[str, Any] = {
522
+ "last_input": self.input_text_edit.toPlainText(),
523
+ "last_source_digit_set": self.input_digit_set_text_edit.toPlainText(),
524
+ "last_target_digit_set": self.output_digit_set_text_edit.toPlainText(),
525
+ "realtime_enabled": self.realtime_checkbox.isChecked(),
526
+ }
527
+ save_ui_state(state_data)
528
+ event.accept()
529
+
530
+
531
+ def run_gui() -> None:
532
+ """
533
+ Runs the BaseBender GUI application.
534
+
535
+ Initializes the QApplication, creates and shows the MainWindow,
536
+ and starts the application's event loop.
537
+ """
538
+ app: QApplication = QApplication(sys.argv)
539
+ window: MainWindow = MainWindow()
540
+ window.show()
541
+ # sys.exit() is used here as it's the standard way to exit a Qt application's event loop.
542
+ sys.exit(app.exec())
543
+
544
+
545
+ if __name__ == "__main__":
546
+ run_gui()
@@ -0,0 +1,16 @@
1
+ # `src/rebaser/` Directory Structure
2
+
3
+ This directory contains the core rebase logic and related modules.
4
+
5
+ ## Folders:
6
+
7
+ * [`resources/`](src/rebaser/resources/STRUCTURE.md): Contains resources such as data files and icons.
8
+ * [`generated/`](src/rebaser/generated/STRUCTURE.md): Contains generated source files.
9
+
10
+ ## Files:
11
+
12
+ * [`__init__.py`](src/rebaser/__init__.py): Initializes the `rebaser` package.
13
+ * [`config_loader.py`](src/rebaser/config_loader.py): Handles tiered configuration loading for digit sets.
14
+ * [`digit_set_rebaser.py`](src/rebaser/digit_set_rebaser.py): Implements the core rebase logic.
15
+ * [`digit_sets.py`](src/rebaser/digit_sets.py): Provides access to pre-defined digit sets and discovery mechanisms.
16
+ * [`models.py`](src/rebaser/models.py): Defines data models used within the rebaser module.
@@ -0,0 +1,4 @@
1
+ """
2
+ This package contains the core logic for digit set rebase operations,
3
+ including digit set definitions, rebaser implementation, and configuration loading.
4
+ """