Files
Teletext-Editor/src/teletext/ui.py
Daniel Dybing 088ad1a320 Feature: Enhanced Editing, Hex Inspector, Color Shortcuts
- Enhanced Editing: Auto-create packets, Enter key support.
- Cursor: Fixed visibility (composition mode), click-to-move, immediate redraw.
- Hex Inspector: Added Hex Value display and input panel.
- Shortcuts: Added Color Insert buttons.
- Fixes: Resolved Hamming encoding for save, fixed duplicate spaces bug, fixed IndentationError.
2025-12-28 21:57:44 +01:00

260 lines
9.1 KiB
Python

from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QListWidget, QListWidgetItem, QComboBox, QLabel, QLineEdit, QPushButton, QFileDialog, QMenuBar, QMenu, QMessageBox)
# ... (imports remain)
# ... (imports remain)
from PyQt6.QtGui import QAction, QKeyEvent
from PyQt6.QtCore import Qt
from .io import load_t42, save_t42
from .renderer import TeletextCanvas
from .models import TeletextService, Page, Packet
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Teletext Editor")
self.resize(1024, 768)
self.service = TeletextService()
self.current_page: Page = None
# UI Components
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QHBoxLayout(self.central_widget)
# Left Panel: Page List
left_layout = QVBoxLayout()
left_label = QLabel("Pages")
left_layout.addWidget(left_label)
self.page_list = QListWidget()
self.page_list.setFixedWidth(150)
self.page_list.itemClicked.connect(self.on_page_selected)
left_layout.addWidget(self.page_list)
# Hex Inspector
hex_layout = QVBoxLayout()
hex_label = QLabel("Hex Value:")
self.hex_input = QLineEdit()
self.hex_input.setMaxLength(2)
self.hex_input.setPlaceholderText("00")
self.hex_input.returnPressed.connect(self.on_hex_entered)
hex_layout.addWidget(hex_label)
hex_layout.addWidget(self.hex_input)
left_layout.addLayout(hex_layout)
left_layout.addStretch()
self.layout.addLayout(left_layout)
# Center Area Layout (Top Bar + Canvas)
center_layout = QVBoxLayout()
# Top Bar: Subpage Selector
top_bar = QHBoxLayout()
self.subpage_label = QLabel("Subpage:")
self.subpage_combo = QComboBox()
self.subpage_combo.setMinimumWidth(250)
self.subpage_combo.currentIndexChanged.connect(self.on_subpage_changed)
top_bar.addWidget(self.subpage_label)
top_bar.addWidget(self.subpage_combo)
top_bar.addStretch()
center_layout.addLayout(top_bar)
# Color Shortcuts
color_layout = QHBoxLayout()
colors = [
("Red", 0x01, "#FF0000"),
("Green", 0x02, "#00FF00"),
("Yellow", 0x03, "#FFFF00"),
("Blue", 0x04, "#0000FF"),
("Magenta", 0x05, "#FF00FF"),
("Cyan", 0x06, "#00FFFF"),
("White", 0x07, "#FFFFFF"),
]
for name, code, hex_color in colors:
btn = QPushButton(name)
btn.setStyleSheet(f"background-color: {hex_color}; font-weight: bold; color: black;")
btn.clicked.connect(lambda checked, c=code: self.insert_char(c))
color_layout.addWidget(btn)
color_layout.addStretch()
center_layout.addLayout(color_layout)
# Canvas
self.canvas = TeletextCanvas()
self.canvas.cursorChanged.connect(self.on_cursor_changed)
center_layout.addWidget(self.canvas, 1) # Expand
self.layout.addLayout(center_layout, 1)
# Menus
self.create_menus()
def create_menus(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("File")
open_action = QAction("Open T42...", self)
open_action.triggered.connect(self.open_file)
file_menu.addAction(open_action)
save_action = QAction("Save T42...", self)
save_action.triggered.connect(self.save_file)
file_menu.addAction(save_action)
exit_action = QAction("Exit", self)
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
view_menu = menu_bar.addMenu("View")
lang_menu = view_menu.addMenu("Language")
langs = ["English", "German", "Swedish/Finnish", "Italian", "French", "Portuguese/Spanish", "Turkish", "Romania"]
for i, lang in enumerate(langs):
action = QAction(lang, self)
action.setData(i)
action.triggered.connect(self.set_language)
lang_menu.addAction(action)
def set_language(self):
action = self.sender()
if action:
idx = action.data()
self.canvas.subset_idx = idx
self.canvas.redraw()
self.canvas.update()
def open_file(self):
fname, _ = QFileDialog.getOpenFileName(self, "Open T42", "", "Teletext Files (*.t42);;All Files (*)")
if fname:
try:
self.service = load_t42(fname)
self.populate_list()
QMessageBox.information(self, "Loaded", f"Loaded {len(self.service.pages)} pages.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to load file: {e}")
def save_file(self):
fname, _ = QFileDialog.getSaveFileName(self, "Save T42", "", "Teletext Files (*.t42);;All Files (*)")
if fname:
try:
save_t42(fname, self.service)
QMessageBox.information(self, "Saved", "File saved successfully.")
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to save file: {e}")
def populate_list(self):
self.page_list.clear()
# Group pages by Mag+PageNum
# We want unique list items
self.page_groups = {} # Key: (mag, page_num) -> List[Page]
for p in self.service.pages:
key = (p.magazine, p.page_number)
if key not in self.page_groups:
self.page_groups[key] = []
self.page_groups[key].append(p)
# Sort keys
sorted_keys = sorted(self.page_groups.keys())
for mag, pnum in sorted_keys:
label = f"{mag}{pnum:02d}"
item = QListWidgetItem(label)
item.setData(Qt.ItemDataRole.UserRole, (mag, pnum))
self.page_list.addItem(item)
def on_page_selected(self, item):
mag, pnum = item.data(Qt.ItemDataRole.UserRole)
pages = self.page_groups.get((mag, pnum), [])
# Populate Subpage Combo
self.subpage_combo.blockSignals(True)
self.subpage_combo.clear()
for i, p in enumerate(pages):
# Display format: Index or Subcode?
# Subcode is often 0000. Index 1/N is clearer for editing.
label = f"{i+1}/{len(pages)} (Sub {p.sub_code:04X})"
self.subpage_combo.addItem(label, p)
self.subpage_combo.blockSignals(False)
if pages:
self.subpage_combo.setCurrentIndex(0)
# Trigger update (manual because blockSignals)
self.on_subpage_changed(0)
def on_subpage_changed(self, index):
if index < 0: return
page = self.subpage_combo.itemData(index)
if isinstance(page, Page):
self.current_page = page
self.canvas.set_page(page)
self.canvas.setFocus()
def insert_char(self, char_code):
self.canvas.set_byte_at_cursor(char_code)
# Advance cursor
self.canvas.move_cursor(1, 0)
self.canvas.setFocus()
def on_cursor_changed(self, x, y, val):
self.hex_input.setText(f"{val:02X}")
def on_hex_entered(self):
text = self.hex_input.text()
try:
val = int(text, 16)
if 0 <= val <= 255:
# Update canvas input
# We can call handle_input with char, OR set byte directly.
# Direct byte set is safer for non-printable.
self.canvas.set_byte_at_cursor(val)
self.canvas.setFocus() # Return focus to canvas
except ValueError:
pass # Ignore invalid hex
# Input Handling (Editor Logic)
def keyPressEvent(self, event: QKeyEvent):
if not self.current_page:
return
key = event.key()
text = event.text()
# Navigation
if key == Qt.Key.Key_Up:
self.canvas.move_cursor(0, -1)
elif key == Qt.Key.Key_Down:
self.canvas.move_cursor(0, 1)
elif key == Qt.Key.Key_Left:
self.canvas.move_cursor(-1, 0)
elif key == Qt.Key.Key_Right:
self.canvas.move_cursor(1, 0)
elif key == Qt.Key.Key_Return or key == Qt.Key.Key_Enter:
# Move to start of next line
self.canvas.cursor_x = 0
self.canvas.move_cursor(0, 1)
else:
# Typing
# Filter non-printable
if text and len(text) == 1 and 32 <= ord(text) <= 126:
self.canvas.handle_input(text)
elif key == Qt.Key.Key_Backspace:
# Move back and delete
self.canvas.move_cursor(-1, 0)
self.canvas.handle_input(' ')
self.canvas.move_cursor(-1, 0) # Compensate for the auto-advance logic if any