Initial commit: Core Teletext Editor functionality
This commit is contained in:
3
run_editor.sh
Executable file
3
run_editor.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/src
|
||||
./venv/bin/python src/main.py
|
||||
BIN
specification.pdf
Normal file
BIN
specification.pdf
Normal file
Binary file not shown.
13
src/main.py
Normal file
13
src/main.py
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from teletext.ui import MainWindow
|
||||
|
||||
def main():
|
||||
app = QApplication(sys.argv)
|
||||
window = MainWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
src/teletext/__pycache__/charsets.cpython-312.pyc
Normal file
BIN
src/teletext/__pycache__/charsets.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/teletext/__pycache__/io.cpython-312.pyc
Normal file
BIN
src/teletext/__pycache__/io.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/teletext/__pycache__/models.cpython-312.pyc
Normal file
BIN
src/teletext/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/teletext/__pycache__/renderer.cpython-312.pyc
Normal file
BIN
src/teletext/__pycache__/renderer.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/teletext/__pycache__/ui.cpython-312.pyc
Normal file
BIN
src/teletext/__pycache__/ui.cpython-312.pyc
Normal file
Binary file not shown.
60
src/teletext/charsets.py
Normal file
60
src/teletext/charsets.py
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
"""
|
||||
Teletext Character Sets (G0).
|
||||
Maps the specific code points (0x23, 0x24, 0x40, 0x5B-0x5E, 0x60, 0x7B-0x7E)
|
||||
to Unicode characters based on the National Option (3 bits).
|
||||
"""
|
||||
|
||||
# Default (English) - Option 000
|
||||
ENGLISH = {
|
||||
0x23: '#', 0x24: '$', 0x40: '@',
|
||||
0x5B: '[', 0x5C: '\\', 0x5D: ']', 0x5E: '^',
|
||||
0x5F: '_', 0x60: '`',
|
||||
0x7B: '{', 0x7C: '|', 0x7D: '}', 0x7E: '~'
|
||||
}
|
||||
|
||||
# Swedish/Finnish/Hungarian - Option 010 (2)
|
||||
SWEDISH_FINNISH = {
|
||||
0x23: '#', 0x24: '¤', 0x40: 'É',
|
||||
0x5B: 'Ä', 0x5C: 'Ö', 0x5D: 'Å', 0x5E: 'Ü',
|
||||
0x5F: '_', 0x60: 'é',
|
||||
0x7B: 'ä', 0x7C: 'ö', 0x7D: 'å', 0x7E: 'ü'
|
||||
}
|
||||
|
||||
# German - Option 001 (1)
|
||||
GERMAN = {
|
||||
0x23: '#', 0x24: '$', 0x40: '§',
|
||||
0x5B: 'Ä', 0x5C: 'Ö', 0x5D: 'Ü', 0x5E: '^',
|
||||
0x5F: '_', 0x60: '`',
|
||||
0x7B: 'ä', 0x7C: 'ö', 0x7D: 'ü', 0x7E: 'ß'
|
||||
}
|
||||
|
||||
# We can add more as needed.
|
||||
|
||||
SETS = [
|
||||
ENGLISH, # 000
|
||||
GERMAN, # 001
|
||||
SWEDISH_FINNISH, # 010
|
||||
ENGLISH, # Italian (011) - placeholder
|
||||
ENGLISH, # French (100) - placeholder
|
||||
ENGLISH, # Portuguese/Spanish (101) - placeholder
|
||||
ENGLISH, # Turkish (110) - placeholder
|
||||
ENGLISH, # Romania (111) - placeholder
|
||||
]
|
||||
|
||||
def get_char(byte_val, subset_idx):
|
||||
if subset_idx < 0 or subset_idx >= len(SETS):
|
||||
subset_idx = 0
|
||||
|
||||
mapping = SETS[subset_idx]
|
||||
|
||||
# If byte is in mapping, return mapped char.
|
||||
# Else return ASCII equivalent (for basic chars)
|
||||
|
||||
valid_byte = byte_val & 0x7F # Strip parity if present (though our packet data is 8-bit usually already stripping parity?)
|
||||
# Packet data we store is raw bytes. We should probably strip parity bit 7 before lookup.
|
||||
|
||||
if valid_byte in mapping:
|
||||
return mapping[valid_byte]
|
||||
|
||||
return chr(valid_byte)
|
||||
119
src/teletext/io.py
Normal file
119
src/teletext/io.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import os
|
||||
from typing import List
|
||||
from .models import Packet, Page, TeletextService
|
||||
|
||||
def load_t42(file_path: str) -> TeletextService:
|
||||
service = TeletextService()
|
||||
|
||||
with open(file_path, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(42)
|
||||
if not chunk:
|
||||
break
|
||||
if len(chunk) < 42:
|
||||
# Should not happen in a valid T42 stream, or we just ignore incomplete tail
|
||||
break
|
||||
|
||||
packet = Packet(chunk)
|
||||
service.all_packets.append(packet)
|
||||
|
||||
# Logic to group into pages.
|
||||
# This is non-trivial because packets for a page might be interleaved or sequential.
|
||||
# Standard implementation: Packets arrive in order. Row 0 starts a new page/subpage.
|
||||
|
||||
if packet.row == 0:
|
||||
# Start of a new page header.
|
||||
# Byte 2-9 of header contain Page Number, Subcode, Control bits etc.
|
||||
# We need to parse the header to identify the page.
|
||||
|
||||
# Header format (after Mag/Row):
|
||||
# Bytes: P1 P2 S1 S2 S3 S4 C1 C2 ...
|
||||
# All Hamming 8/4 encoded.
|
||||
|
||||
# For now, let's just create a new page entry for every Header we see,
|
||||
# or find the existing one if we want to support updates (but T42 usually is a stream capture).
|
||||
# If it's an editor file, it's likely sequential.
|
||||
|
||||
p_num, sub_code = parse_header(packet.data)
|
||||
|
||||
# Create new page
|
||||
new_page = Page(magazine=packet.magazine, page_number=p_num, sub_code=sub_code)
|
||||
new_page.packets.append(packet)
|
||||
service.pages.append(new_page)
|
||||
else:
|
||||
# Add to the "current" page of this magazine.
|
||||
# We need to track the current active page for each magazine.
|
||||
# A simplistic approach: add to the last page added that matches the magazine ??
|
||||
# Robust approach: Maintain a dict of current_pages_by_magazine.
|
||||
|
||||
# Let's find the last page in service that matches the packet's magazine
|
||||
# This is O(N) but N (pages) is small.
|
||||
target_page = None
|
||||
for p in reversed(service.pages):
|
||||
if p.magazine == packet.magazine:
|
||||
target_page = p
|
||||
break
|
||||
|
||||
if target_page:
|
||||
target_page.packets.append(packet)
|
||||
else:
|
||||
# Packet without a header? Orphaned. Just keep in all_packets
|
||||
pass
|
||||
|
||||
return service
|
||||
|
||||
def save_t42(file_path: str, service: TeletextService):
|
||||
with open(file_path, 'wb') as f:
|
||||
# User requirement: "without rearranging the order of the packets"
|
||||
# Implies we should iterate the original list.
|
||||
# However, if we edit data, we modify the Packet objects in place.
|
||||
# If we Add/Delete packets, we need to handle that.
|
||||
|
||||
for packet in service.all_packets:
|
||||
# Reconstruct the 42 bytes from the packet fields
|
||||
# The packet.data (bytearray) should be mutable and edited by the UI.
|
||||
# packet.original_data (first 2 bytes) + packet.data
|
||||
|
||||
# Note: If we changed Magazine or Row, we'd need to re-encode the first 2 bytes.
|
||||
# For now, assume we primarily edit content (bytes 2-41).
|
||||
|
||||
header = packet.original_data[:2] # Keep original address for now
|
||||
# TODO: regenerating header if Mag/Row changed
|
||||
|
||||
f.write(header + packet.data)
|
||||
|
||||
def decode_hamming_8_4(byte_val):
|
||||
return ((byte_val >> 1) & 1) | \
|
||||
(((byte_val >> 3) & 1) << 1) | \
|
||||
(((byte_val >> 5) & 1) << 2) | \
|
||||
(((byte_val >> 7) & 1) << 3)
|
||||
|
||||
def parse_header(data: bytearray):
|
||||
# Data is 40 bytes.
|
||||
# Bytes 0-7 are Page Num (2), Subcode (4), Control (2) - ALL Hamming encoded.
|
||||
|
||||
# 0: Page Units (PU)
|
||||
# 1: Page Tens (PT)
|
||||
|
||||
pu = decode_hamming_8_4(data[0])
|
||||
pt = decode_hamming_8_4(data[1])
|
||||
|
||||
page_num = (pt & 0xF) * 10 + (pu & 0xF)
|
||||
|
||||
# Subcode: S1, S2, S3, S4
|
||||
# S1 (low), S2, S3, S4 (high)
|
||||
|
||||
s1 = decode_hamming_8_4(data[2])
|
||||
s2 = decode_hamming_8_4(data[3])
|
||||
s3 = decode_hamming_8_4(data[4])
|
||||
s4 = decode_hamming_8_4(data[5])
|
||||
|
||||
# Subcode logic is a bit complex with specific bit mapping for "Time" vs "Subcode"
|
||||
# But usually just combining them gives the raw subcode value.
|
||||
# S1: bits 0-3
|
||||
# S2: bits 4-6 (bit 4 is C4) -> actually S2 has 3 bits of subcode + 1 control bit usually?
|
||||
# Let's simplify and just concat them for a unique identifier.
|
||||
|
||||
sub_code = s1 | (s2 << 4) | (s3 << 8) | (s4 << 12)
|
||||
|
||||
return page_num, sub_code
|
||||
93
src/teletext/models.py
Normal file
93
src/teletext/models.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
@dataclass
|
||||
class Packet:
|
||||
"""
|
||||
Represents a single Teletext packet (Row).
|
||||
In T42, we have 42 bytes:
|
||||
Byte 0-1: Magazine and Row Address (Hamming 8/4 encoding)
|
||||
Byte 2-41: Data bytes (40 bytes)
|
||||
"""
|
||||
original_data: bytes
|
||||
magazine: int = field(init=False)
|
||||
row: int = field(init=False)
|
||||
data: bytearray = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
if len(self.original_data) != 42:
|
||||
raise ValueError(f"Packet must be 42 bytes, got {len(self.original_data)}")
|
||||
|
||||
# Parse Magazine and Row from the first 2 bytes (Hamming 8/4)
|
||||
# MRAG (Magazine + Row Address Group)
|
||||
# Byte 0: P1 D1 P2 D2 P3 D3 P4 D4
|
||||
# Byte 1: P1 D1 P2 D2 P3 D3 P4 D4
|
||||
|
||||
b1 = self.original_data[0]
|
||||
b2 = self.original_data[1]
|
||||
|
||||
# De-interleave Hamming bits to get M (3 bits) and R (5 bits)
|
||||
# This is the "basic" interpretation.
|
||||
# For a robust editor we assume the input T42 is valid or we just store bytes.
|
||||
# But we need Mag/Row to organize pages.
|
||||
|
||||
# Decode Hamming 8/4 logic is complex to implementation from scratch correctly
|
||||
# without a reference, but usually D1, D2, D3, D4 are at bit positions 1, 3, 5, 7
|
||||
# (0-indexed, where 0 is LSB).
|
||||
# Let's perform a simple extraction assuming no bit errors for now.
|
||||
|
||||
def decode_hamming_8_4(byte_val):
|
||||
# Extract data bits: bits 1, 3, 5, 7
|
||||
return ((byte_val >> 1) & 1) | \
|
||||
(((byte_val >> 3) & 1) << 1) | \
|
||||
(((byte_val >> 5) & 1) << 2) | \
|
||||
(((byte_val >> 7) & 1) << 3)
|
||||
|
||||
d1 = decode_hamming_8_4(b1)
|
||||
d2 = decode_hamming_8_4(b2)
|
||||
|
||||
# Magazine is 3 bits (Logic is specific: Mag 8 is encoded as 0)
|
||||
# Row is 5 bits.
|
||||
|
||||
# According to Spec (ETSI EN 300 706):
|
||||
# b1 encoded: M1 M2 M3 R1
|
||||
# b2 encoded: R2 R3 R4 R5
|
||||
|
||||
self.magazine = (d1 & 0b0111)
|
||||
if self.magazine == 0:
|
||||
self.magazine = 8
|
||||
|
||||
row_low_bit = (d1 >> 3) & 1
|
||||
row_high_bits = d2
|
||||
self.row = (row_high_bits << 1) | row_low_bit
|
||||
|
||||
self.data = bytearray(self.original_data[2:])
|
||||
|
||||
@property
|
||||
def is_header(self):
|
||||
return self.row == 0
|
||||
|
||||
@dataclass
|
||||
class Page:
|
||||
"""
|
||||
Represents a Teletext Page (e.g., 100).
|
||||
Can have multiple subpages.
|
||||
"""
|
||||
magazine: int
|
||||
page_number: int # 00-99
|
||||
sub_code: int = 0 # Subpage code (0000 to 3F7F hex usually, simplest is 0-99 equivalent)
|
||||
packets: List[Packet] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def full_page_number(self):
|
||||
return f"{self.magazine}{self.page_number:02d}"
|
||||
|
||||
@dataclass
|
||||
class TeletextService:
|
||||
"""
|
||||
Container for all pages.
|
||||
"""
|
||||
pages: List[Page] = field(default_factory=list)
|
||||
# We also keep a flat list of all packets to preserve order on save
|
||||
all_packets: List[Packet] = field(default_factory=list)
|
||||
|
||||
274
src/teletext/renderer.py
Normal file
274
src/teletext/renderer.py
Normal file
@@ -0,0 +1,274 @@
|
||||
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
from PyQt6.QtGui import QPainter, QColor, QFont, QImage, QBrush, QPen
|
||||
from PyQt6.QtCore import Qt, QRect, QSize, pyqtSignal
|
||||
|
||||
from .models import Page, Packet
|
||||
from .charsets import get_char
|
||||
|
||||
# Teletext Palette
|
||||
COLORS = [
|
||||
QColor(0, 0, 0), # Black
|
||||
QColor(255, 0, 0), # Red
|
||||
QColor(0, 255, 0), # Green
|
||||
QColor(255, 255, 0), # Yellow
|
||||
QColor(0, 0, 255), # Blue
|
||||
QColor(255, 0, 255), # Magenta
|
||||
QColor(0, 255, 255), # Cyan
|
||||
QColor(255, 255, 255) # White
|
||||
]
|
||||
|
||||
class TeletextCanvas(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setMinimumSize(480, 500) # 40x12 * 25x20 approx
|
||||
self.page: Page = None
|
||||
self.subset_idx = 0 # Default English
|
||||
|
||||
# Teletext is 40 columns x 25 rows
|
||||
# We will render to a fixed size QImage and scale it
|
||||
self.cols = 40
|
||||
self.rows = 25
|
||||
self.cell_w = 12
|
||||
self.cell_h = 20
|
||||
self.img_w = self.cols * self.cell_w
|
||||
self.img_h = self.rows * self.cell_h
|
||||
|
||||
self.buffer = QImage(self.img_w, self.img_h, QImage.Format.Format_RGB32)
|
||||
self.buffer.fill(Qt.GlobalColor.black)
|
||||
|
||||
# Font for text
|
||||
self.font = QFont("Courier New", 14)
|
||||
self.font.setStyleHint(QFont.StyleHint.Monospace)
|
||||
self.font.setBold(True)
|
||||
|
||||
# Cursor state
|
||||
self.cursor_x = 0
|
||||
self.cursor_y = 0
|
||||
self.cursor_visible = True
|
||||
# Blinking cursor timer could be added, for now static inverted is fine or toggle on timer elsewhere
|
||||
|
||||
def set_cursor(self, x, y):
|
||||
self.cursor_x = max(0, min(self.cols - 1, x))
|
||||
self.cursor_y = max(0, min(self.rows - 1, y))
|
||||
self.update()
|
||||
|
||||
def move_cursor(self, dx, dy):
|
||||
self.cursor_x = max(0, min(self.cols - 1, self.cursor_x + dx))
|
||||
self.cursor_y = max(0, min(self.rows - 1, self.cursor_y + dy))
|
||||
self.update()
|
||||
|
||||
def set_page(self, page: Page):
|
||||
self.page = page
|
||||
self.cursor_x = 0
|
||||
self.cursor_y = 0
|
||||
self.redraw()
|
||||
self.update()
|
||||
|
||||
def handle_input(self, text):
|
||||
if not self.page:
|
||||
return
|
||||
|
||||
# Find packet for current row
|
||||
packet = None
|
||||
for p in self.page.packets:
|
||||
if p.row == self.cursor_y:
|
||||
packet = p
|
||||
break
|
||||
|
||||
if packet:
|
||||
# Edit the data
|
||||
# Data bytes start at index 0 of packet.data corresponding to Col 0?
|
||||
# Standard Teletext row is 40 chars. Packet data is 40 bytes.
|
||||
if 0 <= self.cursor_x < 40:
|
||||
# Get ASCII value
|
||||
# We need to Reverse Map chars to Teletext Bytes if we want full suppport
|
||||
# For now, just taking ord() for basic ASCII
|
||||
# TODO: reverse lookup for National Chars
|
||||
|
||||
# Check if text is a single char
|
||||
if len(text) == 1:
|
||||
byte_val = ord(text)
|
||||
# Simple filter
|
||||
if byte_val > 255: byte_val = 0x3F # ?
|
||||
|
||||
packet.data[self.cursor_x] = byte_val
|
||||
self.redraw()
|
||||
self.move_cursor(1, 0)
|
||||
else:
|
||||
# Create a packet if it doesn't exist for this row?
|
||||
# Creating new packets in a T42 stream is tricky (insertion).
|
||||
# For now, only edit existing rows.
|
||||
pass
|
||||
|
||||
def redraw(self):
|
||||
self.buffer.fill(Qt.GlobalColor.black)
|
||||
painter = QPainter(self.buffer)
|
||||
painter.setFont(self.font)
|
||||
|
||||
if not self.page:
|
||||
painter.setPen(Qt.GlobalColor.white)
|
||||
painter.drawText(10, 20, "No Page Loaded")
|
||||
painter.end()
|
||||
return
|
||||
|
||||
# Draw each packet
|
||||
# Initialize a grid of empty chars
|
||||
grid = [None] * 26 # 0-25
|
||||
|
||||
for p in self.page.packets:
|
||||
if 0 <= p.row <= 25:
|
||||
grid[p.row] = p
|
||||
|
||||
for r in range(25):
|
||||
packet = grid[r]
|
||||
self.draw_row(painter, r, packet)
|
||||
|
||||
painter.end()
|
||||
|
||||
def draw_row(self, painter, row, packet):
|
||||
# Default State at start of row
|
||||
fg = COLORS[7] # White
|
||||
bg = COLORS[0] # Black
|
||||
graphics_mode = False
|
||||
contiguous = True # Mosaic
|
||||
hold_graphics = False
|
||||
held_char = 0x20 # Space
|
||||
|
||||
y = row * self.cell_h
|
||||
|
||||
data = b''
|
||||
if packet:
|
||||
data = packet.data # 40 bytes
|
||||
else:
|
||||
data = b'\x20' * 40
|
||||
|
||||
# Header string for Row 0 columns 0-7
|
||||
header_prefix = ""
|
||||
if row == 0 and self.page:
|
||||
header_prefix = f"P{self.page.magazine}{self.page.page_number:02d}"
|
||||
# Pad to 8 chars
|
||||
header_prefix = header_prefix.ljust(8)
|
||||
|
||||
for c in range(40):
|
||||
x = c * self.cell_w
|
||||
|
||||
# Decide byte value
|
||||
if row == 0 and c < 8:
|
||||
# Use generated header prefix
|
||||
byte_val = ord(header_prefix[c])
|
||||
else:
|
||||
byte_val = data[c] if c < len(data) else 0x20
|
||||
|
||||
byte_val &= 0x7F # Strip parity
|
||||
|
||||
# Control Codes
|
||||
is_control = False
|
||||
|
||||
if byte_val < 0x20:
|
||||
is_control = True
|
||||
# Handle control codes
|
||||
if 0x00 <= byte_val <= 0x07: # Alpha Color
|
||||
fg = COLORS[byte_val]
|
||||
graphics_mode = False
|
||||
elif 0x10 <= byte_val <= 0x17: # Mosaic Color
|
||||
fg = COLORS[byte_val - 0x10]
|
||||
graphics_mode = True
|
||||
elif byte_val == 0x1C: # Black BG
|
||||
bg = COLORS[0]
|
||||
elif byte_val == 0x1D: # New BG
|
||||
bg = fg
|
||||
elif byte_val == 0x0C: # Normal Height
|
||||
pass
|
||||
elif byte_val == 0x0D: # Double Height
|
||||
pass # Not implemented yet
|
||||
elif byte_val == 0x19: # Contiguous Graphics
|
||||
contiguous = True
|
||||
elif byte_val == 0x1A: # Separated Graphics
|
||||
contiguous = False
|
||||
elif byte_val == 0x1E: # Hold Graphics
|
||||
hold_graphics = True
|
||||
elif byte_val == 0x1F: # Release Graphics
|
||||
hold_graphics = False
|
||||
|
||||
# Draw Background
|
||||
painter.fillRect(x, y, self.cell_w, self.cell_h, bg)
|
||||
|
||||
# Draw Foreground
|
||||
if is_control:
|
||||
# "Set-at" spacing attribute? Teletext control codes occupy a space
|
||||
# unless "Hold Graphics" replaces it with previous graphic char.
|
||||
if hold_graphics and graphics_mode:
|
||||
self.draw_mosaic(painter, x, y, held_char, fg, contiguous)
|
||||
else:
|
||||
# Draw space (nothing, since we filled BG)
|
||||
pass
|
||||
else:
|
||||
if graphics_mode:
|
||||
# Mosaic Graphics
|
||||
if (0x20 <= byte_val <= 0x3F) or (0x60 <= byte_val <= 0x7F):
|
||||
self.draw_mosaic(painter, x, y, byte_val, fg, contiguous)
|
||||
held_char = byte_val
|
||||
else:
|
||||
# Capital letter in graphics mode? Usually shows char?
|
||||
char = get_char(byte_val, self.subset_idx)
|
||||
painter.setPen(fg)
|
||||
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
|
||||
held_char = 0x20
|
||||
else:
|
||||
# Alphanumeric
|
||||
char = get_char(byte_val, self.subset_idx)
|
||||
painter.setPen(fg)
|
||||
painter.drawText(QRect(x, y, self.cell_w, self.cell_h), Qt.AlignmentFlag.AlignCenter, char)
|
||||
# Draw Cursor
|
||||
# Invert the cell at cursor position
|
||||
if self.cursor_visible and c == self.cursor_x and row == self.cursor_y:
|
||||
painter.setCompositionMode(QPainter.CompositionMode.RasterOp_NotSourceXorDestination)
|
||||
# XOR with white (creates inversion)
|
||||
painter.fillRect(x, y, self.cell_w, self.cell_h, QColor(255, 255, 255))
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
|
||||
|
||||
def draw_mosaic(self, painter, x, y, char_code, color, contiguous):
|
||||
val = char_code & 0x7F
|
||||
bits = 0
|
||||
if val >= 0x20:
|
||||
bits = val - 0x20
|
||||
|
||||
blocks = [
|
||||
(0, 0), (1, 0), # Top
|
||||
(0, 1), (1, 1), # Mid
|
||||
(0, 2), (1, 2) # Bot
|
||||
]
|
||||
|
||||
bit_mask = [1, 2, 4, 8, 16, 64] # 64 is bit 6
|
||||
|
||||
bw = self.cell_w / 2
|
||||
bh = self.cell_h / 3
|
||||
|
||||
if not contiguous:
|
||||
bw -= 1
|
||||
bh -= 1
|
||||
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.setBrush(QBrush(color))
|
||||
|
||||
for i in range(6):
|
||||
if bits & bit_mask[i]:
|
||||
bx = x + blocks[i][0] * (self.cell_w / 2)
|
||||
by = y + blocks[i][1] * (self.cell_h / 3)
|
||||
|
||||
if not contiguous:
|
||||
bx += 1
|
||||
by += 1
|
||||
|
||||
painter.drawRect(QRect(int(bx), int(by), int(bw), int(bh)))
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
target_rect = self.rect()
|
||||
scaled = self.buffer.scaled(target_rect.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.FastTransformation)
|
||||
|
||||
ox = (target_rect.width() - scaled.width()) // 2
|
||||
oy = (target_rect.height() - scaled.height()) // 2
|
||||
|
||||
painter.drawImage(ox, oy, scaled)
|
||||
152
src/teletext/ui.py
Normal file
152
src/teletext/ui.py
Normal file
@@ -0,0 +1,152 @@
|
||||
|
||||
import os
|
||||
from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QTreeWidget, QTreeWidgetItem, QFileDialog, QMenuBar, QMenu, QMessageBox)
|
||||
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 Tree
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderLabel("Pages")
|
||||
self.tree.setFixedWidth(200)
|
||||
self.tree.itemClicked.connect(self.on_page_selected)
|
||||
self.layout.addWidget(self.tree)
|
||||
|
||||
# Center: Teletext Canvas
|
||||
self.canvas = TeletextCanvas()
|
||||
self.layout.addWidget(self.canvas, 1) # Expand
|
||||
|
||||
# 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_tree()
|
||||
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_tree(self):
|
||||
self.tree.clear()
|
||||
|
||||
# Group by Magazine
|
||||
mags = {}
|
||||
for p in self.service.pages:
|
||||
if p.magazine not in mags:
|
||||
mags[p.magazine] = QTreeWidgetItem([f"Magazine {p.magazine}"])
|
||||
self.tree.addTopLevelItem(mags[p.magazine])
|
||||
|
||||
# Format: PPP-SS (Page-Subcode)
|
||||
# Create Item
|
||||
# Subcode is complicated to display "nicely" without decoding,
|
||||
# let's just show hex or raw for now if not standard 0000.
|
||||
|
||||
label = f"{p.page_number:02d} (Sub: {p.sub_code:04X})"
|
||||
item = QTreeWidgetItem([label])
|
||||
item.setData(0, Qt.ItemDataRole.UserRole, p)
|
||||
mags[p.magazine].addChild(item)
|
||||
|
||||
self.tree.expandAll()
|
||||
|
||||
def on_page_selected(self, item, column):
|
||||
page = item.data(0, Qt.ItemDataRole.UserRole)
|
||||
if isinstance(page, Page):
|
||||
self.current_page = page
|
||||
self.canvas.set_page(page)
|
||||
# Also set window focus to canvas or handle key events?
|
||||
self.canvas.setFocus()
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
BIN
test_out.t42
Normal file
BIN
test_out.t42
Normal file
Binary file not shown.
94
test_t42.py
Normal file
94
test_t42.py
Normal file
@@ -0,0 +1,94 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add src to path
|
||||
sys.path.append(os.path.join(os.getcwd(), 'src'))
|
||||
|
||||
from teletext.models import Packet, Page
|
||||
from teletext.io import load_t42, save_t42
|
||||
|
||||
def create_dummy_t42(filename):
|
||||
# Create a 42-byte packet
|
||||
# Byte 0: Mag 1, Row 0.
|
||||
# M=1 (001), R=0 (00000)
|
||||
# Encoded:
|
||||
# B1: M1 M2 M3 R1 -> 1 0 0 0. With Hamming: P1, D1(1), P2, D2(0), P3, D3(0), P4, D4(0)
|
||||
# D1=1 -> P1=1 (1,3,5,7 parity).
|
||||
# Actually let's use a simpler way or pre-calculated bytes for testing.
|
||||
# Magazine 1, Row 0 is often: 0x15 0x15 (example guess, need real hamming)
|
||||
|
||||
# Let's simple write 42 zero bytes, then set some manually to test "parsing" robustness
|
||||
# or just trust the load/save loop for raw data conservation.
|
||||
|
||||
# We'll create a "Header" packet (Row 0) and a "Content" packet (Row 1).
|
||||
|
||||
# Packet 1: Row 0.
|
||||
# We need to construct bytes that pass our minimal decoder.
|
||||
# decode_common: returns D1..D4 for bits 1,3,5,7.
|
||||
# Mag=1 => 001. R=0 => 00000.
|
||||
# B1 (Low row bits + Mag): M1, M2, M3, R1 -> 1, 0, 0, 0
|
||||
# D1=1, D2=0, D3=0, D4=0.
|
||||
# Byte value: x1x0x0x0.
|
||||
# B2 (High row bits): R2, R3, R4, R5 -> 0, 0, 0, 0
|
||||
# Byte value: x0x0x0x0.
|
||||
|
||||
# Let's arbitrarily set parity bits to 0 for this test as my decoder ignores them (it only reads D bits).
|
||||
# B1: 0 1 0 0 0 0 0 0 -> 0x02
|
||||
# B2: 0 0 0 0 0 0 0 0 -> 0x00
|
||||
|
||||
p1_data = bytearray(42)
|
||||
p1_data[0] = 0x02
|
||||
p1_data[1] = 0x00
|
||||
# Add some text in the rest
|
||||
p1_data[2:] = b'Header Packet' + b'\x00' * (40 - 13)
|
||||
|
||||
# Packet 2: Row 1.
|
||||
# M=1, R=1.
|
||||
# B1: M1 M2 M3 R1 -> 1 0 0 1
|
||||
# D1=1, D2=0, D3=0, D4=1.
|
||||
# Byte: x1x0x0x1 -> 0x82 (if bit 7 is D4).
|
||||
# Position: 0(P1) 1(D1-b0) 2(P2) 3(D2-b1) 4(P3) 5(D3-b2) 6(P4) 7(D4-b3)
|
||||
# My decoder keys off D1(bit1), D2(bit3), D3(bit5), D4(bit7).
|
||||
# So we want bits 1 and 7 set. 0x82 = 1000 0010. Correct.
|
||||
|
||||
p2_data = bytearray(42)
|
||||
p2_data[0] = 0x82
|
||||
p2_data[1] = 0x00 # Row high bits 0
|
||||
p2_data[2:] = b'Content Row 1' + b'\x00' * (40 - 13)
|
||||
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(p1_data)
|
||||
f.write(p2_data)
|
||||
|
||||
print(f"Created {filename}")
|
||||
|
||||
def test_load_save():
|
||||
fname = "test.t42"
|
||||
out_fname = "test_out.t42"
|
||||
|
||||
create_dummy_t42(fname)
|
||||
|
||||
service = load_t42(fname)
|
||||
print(f"Loaded {len(service.all_packets)} packets")
|
||||
print(f"Loaded {len(service.pages)} pages")
|
||||
|
||||
if len(service.pages) > 0:
|
||||
p = service.pages[0]
|
||||
print(f"Page 0: Mag {p.magazine} Num {p.page_number}")
|
||||
print(f"Packets in page: {len(p.packets)}")
|
||||
|
||||
save_t42(out_fname, service)
|
||||
|
||||
# Verify binary identity
|
||||
with open(fname, 'rb') as f1, open(out_fname, 'rb') as f2:
|
||||
b1 = f1.read()
|
||||
b2 = f2.read()
|
||||
if b1 == b2:
|
||||
print("SUCCESS: Output matches input")
|
||||
else:
|
||||
print("FAILURE: Output differs")
|
||||
print(f"In: {len(b1)}, Out: {len(b2)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_load_save()
|
||||
247
venv/bin/Activate.ps1
Normal file
247
venv/bin/Activate.ps1
Normal file
@@ -0,0 +1,247 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
70
venv/bin/activate
Normal file
70
venv/bin/activate
Normal file
@@ -0,0 +1,70 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# You cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||
if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
export VIRTUAL_ENV=$(cygpath /home/daniel/Documents/Projects/teletext_editor/venv)
|
||||
else
|
||||
# use the path as-is
|
||||
export VIRTUAL_ENV=/home/daniel/Documents/Projects/teletext_editor/venv
|
||||
fi
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1='(venv) '"${PS1:-}"
|
||||
export PS1
|
||||
VIRTUAL_ENV_PROMPT='(venv) '
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
27
venv/bin/activate.csh
Normal file
27
venv/bin/activate.csh
Normal file
@@ -0,0 +1,27 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV /home/daniel/Documents/Projects/teletext_editor/venv
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = '(venv) '"$prompt"
|
||||
setenv VIRTUAL_ENV_PROMPT '(venv) '
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
69
venv/bin/activate.fish
Normal file
69
venv/bin/activate.fish
Normal file
@@ -0,0 +1,69 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/). You cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV /home/daniel/Documents/Projects/teletext_editor/venv
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
set -gx VIRTUAL_ENV_PROMPT '(venv) '
|
||||
end
|
||||
8
venv/bin/pip
Executable file
8
venv/bin/pip
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/daniel/Documents/Projects/teletext_editor/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pip3
Executable file
8
venv/bin/pip3
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/daniel/Documents/Projects/teletext_editor/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pip3.12
Executable file
8
venv/bin/pip3.12
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/daniel/Documents/Projects/teletext_editor/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
8
venv/bin/pylupdate6
Executable file
8
venv/bin/pylupdate6
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/daniel/Documents/Projects/teletext_editor/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from PyQt6.lupdate.pylupdate import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
1
venv/bin/python
Symbolic link
1
venv/bin/python
Symbolic link
@@ -0,0 +1 @@
|
||||
python3
|
||||
1
venv/bin/python3
Symbolic link
1
venv/bin/python3
Symbolic link
@@ -0,0 +1 @@
|
||||
/usr/bin/python3
|
||||
1
venv/bin/python3.12
Symbolic link
1
venv/bin/python3.12
Symbolic link
@@ -0,0 +1 @@
|
||||
python3
|
||||
8
venv/bin/pyuic6
Executable file
8
venv/bin/pyuic6
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/home/daniel/Documents/Projects/teletext_editor/venv/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from PyQt6.uic.pyuic import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Bluetooth.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Bluetooth.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Concurrent.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Concurrent.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Core.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Core.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6DBus.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6DBus.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Designer.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Designer.so.6
Executable file
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-crypto.so.3
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-crypto.so.3
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-ssl.so.3
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-ssl.so.3
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va-drm.so.2
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va-drm.so.2
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va-x11.so.2
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va-x11.so.2
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va.so.2
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6FFmpegStub-va.so.2
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Gui.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Gui.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Help.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Help.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsAnimation.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsAnimation.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsFolderListModel.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsFolderListModel.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsPlatform.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsPlatform.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsQmlModels.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsQmlModels.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsSettings.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsSettings.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsSharedImage.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsSharedImage.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsWavefrontMesh.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6LabsWavefrontMesh.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Multimedia.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Multimedia.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6MultimediaQuick.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6MultimediaQuick.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6MultimediaWidgets.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6MultimediaWidgets.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Network.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Network.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Nfc.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Nfc.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6OpenGL.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6OpenGL.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6OpenGLWidgets.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6OpenGLWidgets.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Pdf.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Pdf.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PdfQuick.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PdfQuick.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PdfWidgets.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PdfWidgets.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Positioning.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Positioning.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PositioningQuick.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PositioningQuick.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PrintSupport.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6PrintSupport.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Qml.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Qml.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlMeta.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlMeta.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlModels.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlModels.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlWorkerScript.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QmlWorkerScript.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3D.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3D.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DAssetImport.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DAssetImport.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DAssetUtils.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DAssetUtils.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DEffects.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DEffects.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DGlslParser.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DGlslParser.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DHelpers.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DHelpers.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DHelpersImpl.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DHelpersImpl.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DIblBaker.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DIblBaker.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DParticles.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DParticles.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DPhysics.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DPhysics.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DPhysicsHelpers.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DPhysicsHelpers.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DRuntimeRender.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DRuntimeRender.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DSpatialAudio.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DSpatialAudio.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DUtils.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DUtils.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DXr.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6Quick3DXr.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Basic.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Basic.so.6
Executable file
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Fusion.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Fusion.so.6
Executable file
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Imagine.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Imagine.so.6
Executable file
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Impl.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickControls2Impl.so.6
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickDialogs2.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickDialogs2.so.6
Executable file
Binary file not shown.
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickDialogs2Utils.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickDialogs2Utils.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickEffects.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickEffects.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickLayouts.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickLayouts.so.6
Executable file
Binary file not shown.
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickParticles.so.6
Executable file
BIN
venv/lib/python3.12/site-packages/PyQt6/Qt6/lib/libQt6QuickParticles.so.6
Executable file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user