You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
10 KiB

#!/usr/bin/env python3
"""
Compact Buffer Widget - One Line Display
=========================================
Minimal, compact widget showing everything in ONE line.
Shows: [Capacity] [Progress Bar] [Overflow]
Manual update - call update_display() when you want.
"""
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QLabel, QProgressBar
from PyQt6.QtCore import Qt
from .circular_buffer import (
CircularBufferHandle,
cb_capacity, cb_fill_bytes, cb_fill_pct, cb_overflows
)
def format_bytes(b: int) -> str:
"""
Format byte count as human-readable string.
Args:
b: Byte count
Returns:
Formatted string (e.g., "120.5KB", "400.0MB", "1.2GB")
"""
if b >= 1024**3: # GB
return f"{b / (1024**3):.1f}GB"
elif b >= 1024**2: # MB
return f"{b / (1024**2):.1f}MB"
elif b >= 1024: # KB
return f"{b / 1024:.1f}KB"
else:
return f"{b}B"
class CompactBufferWidget(QWidget):
"""
Compact one-line buffer display.
Layout: [Size Label] [===== Progress Bar =====] [Overflow]
Usage:
widget = CompactBufferWidget(buffer)
# Update manually:
widget.update_display()
"""
def __init__(self, buffer: CircularBufferHandle, parent=None):
super().__init__(parent)
self.buffer = buffer
# Horizontal layout (everything in ONE line)
layout = QHBoxLayout()
layout.setContentsMargins(2, 2, 2, 2) # Tight margins
layout.setSpacing(5)
self.setLayout(layout)
# Size label (left)
self.lbl_size = QLabel()
self.lbl_size.setMinimumWidth(80)
layout.addWidget(self.lbl_size)
# Progress bar (center - takes most space)
self.progress = QProgressBar()
self.progress.setRange(0, 100)
self.progress.setTextVisible(True)
self.progress.setFormat("%p%")
self.progress.setMaximumHeight(20) # Compact height
layout.addWidget(self.progress, stretch=1) # Stretch to fill
# Overflow label (right)
self.lbl_overflow = QLabel()
self.lbl_overflow.setMinimumWidth(100) # Wider for "Overflows: X"
self.lbl_overflow.setAlignment(Qt.AlignmentFlag.AlignRight)
layout.addWidget(self.lbl_overflow)
self.update_display()
def update_display(self):
"""Update display - call manually when needed."""
cap = cb_capacity(self.buffer)
fill = cb_fill_bytes(self.buffer)
pct = cb_fill_pct(self.buffer)
ovf = cb_overflows(self.buffer)
# Update labels with human-readable format
self.lbl_size.setText(f"{format_bytes(fill)}/{format_bytes(cap)}")
self.progress.setValue(pct)
# Overflow label - just show count (no color logic)
self.lbl_overflow.setText(f"Overflows: {ovf}")
if ovf > 0:
self.lbl_overflow.setStyleSheet("color: red; font-weight: bold;")
else:
self.lbl_overflow.setStyleSheet("color: gray;")
# =============================================================================
# Version with callback support
# =============================================================================
class CompactBufferWidgetWithCallback(QWidget):
"""
Compact widget WITH callback support.
Callback is called whenever update_display() is called.
Useful for logging, debugging, or triggering other actions.
Usage:
def my_callback(capacity, fill, percent, overflows):
print(f"Buffer: {fill}/{capacity} ({percent}%)")
widget = CompactBufferWidgetWithCallback(buffer, on_update=my_callback)
widget.update_display() # Callback is called!
"""
def __init__(self, buffer: CircularBufferHandle,
on_update=None, parent=None):
"""
Args:
buffer: CircularBufferHandle to monitor
on_update: Callback function(capacity, fill, percent, overflows)
parent: Parent widget
"""
super().__init__(parent)
self.buffer = buffer
self.on_update_callback = on_update
# Same layout as compact version
layout = QHBoxLayout()
layout.setContentsMargins(2, 2, 2, 2)
layout.setSpacing(5)
self.setLayout(layout)
self.lbl_size = QLabel()
self.lbl_size.setMinimumWidth(80)
layout.addWidget(self.lbl_size)
self.progress = QProgressBar()
self.progress.setRange(0, 100)
self.progress.setTextVisible(True)
self.progress.setFormat("%p%")
self.progress.setMaximumHeight(20)
layout.addWidget(self.progress, stretch=1)
self.lbl_overflow = QLabel()
self.lbl_overflow.setMinimumWidth(100) # Wider for "Overflows: X"
self.lbl_overflow.setAlignment(Qt.AlignmentFlag.AlignRight)
layout.addWidget(self.lbl_overflow)
self.update_display()
def update_display(self):
"""
Update display and call callback if set.
Callback signature:
callback(capacity: int, fill: int, percent: int, overflows: int)
"""
cap = cb_capacity(self.buffer)
fill = cb_fill_bytes(self.buffer)
pct = cb_fill_pct(self.buffer)
ovf = cb_overflows(self.buffer)
# Update UI with human-readable format
self.lbl_size.setText(f"{format_bytes(fill)}/{format_bytes(cap)}")
self.progress.setValue(pct)
# Overflow label - just show count
self.lbl_overflow.setText(f"Overflows: {ovf}")
if ovf > 0:
self.lbl_overflow.setStyleSheet("color: red; font-weight: bold;")
else:
self.lbl_overflow.setStyleSheet("color: gray;")
# Call callback if set
if self.on_update_callback:
try:
self.on_update_callback(cap, fill, pct, ovf)
except Exception as e:
print(f"Callback error: {e}")
# =============================================================================
# Demo
# =============================================================================
def demo():
"""Demo showing both widgets."""
import sys
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QVBoxLayout,
QWidget, QPushButton, QLabel, QGroupBox
)
from circular_buffer import cb_init, cb_write
class DemoWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Compact Buffer Widget Demo")
central = QWidget()
self.setCentralWidget(central)
layout = QVBoxLayout()
central.setLayout(layout)
# Title
title = QLabel("Compact One-Line Buffer Widgets")
title.setStyleSheet("font-size: 14px; font-weight: bold;")
layout.addWidget(title)
# ====== Widget 1: Without callback ======
group1 = QGroupBox("Widget 1: Basic (no callback)")
group1_layout = QVBoxLayout()
group1.setLayout(group1_layout)
status1, self.buffer1 = cb_init(100)
self.widget1 = CompactBufferWidget(self.buffer1)
group1_layout.addWidget(self.widget1)
layout.addWidget(group1)
# ====== Widget 2: With callback ======
group2 = QGroupBox("Widget 2: With Callback")
group2_layout = QVBoxLayout()
group2.setLayout(group2_layout)
status2, self.buffer2 = cb_init(100)
# Define callback
def on_buffer_update(cap, fill, pct, ovf):
print(f"Callback: {fill}/{cap}B ({pct}%) - Overflows: {ovf}")
self.widget2 = CompactBufferWidgetWithCallback(
self.buffer2,
on_update=on_buffer_update
)
group2_layout.addWidget(self.widget2)
layout.addWidget(group2)
# ====== Controls ======
controls = QGroupBox("Controls")
controls_layout = QVBoxLayout()
controls.setLayout(controls_layout)
btn_write_5 = QPushButton("Write 5 bytes to both")
btn_write_5.clicked.connect(self.write_5)
controls_layout.addWidget(btn_write_5)
btn_write_50 = QPushButton("Write 50 bytes to both")
btn_write_50.clicked.connect(self.write_50)
controls_layout.addWidget(btn_write_50)
btn_update = QPushButton("⟳ Update Displays")
btn_update.setStyleSheet("background-color: #4CAF50; color: white;")
btn_update.clicked.connect(self.update_all)
controls_layout.addWidget(btn_update)
layout.addWidget(controls)
# Instructions
instructions = QLabel(
"\n💡 Instructions:\n"
"1. Click 'Write X bytes' to add data\n"
"2. Click 'Update Displays' to refresh\n"
"3. Widget 2 prints callback info to console"
)
instructions.setStyleSheet("background-color: #f0f0f0; padding: 5px;")
layout.addWidget(instructions)
self.resize(500, 400)
def write_5(self):
cb_write(self.buffer1, b"HELLO")
cb_write(self.buffer2, b"HELLO")
print("Wrote 5 bytes - click Update to see")
def write_50(self):
cb_write(self.buffer1, b"X" * 50)
cb_write(self.buffer2, b"X" * 50)
print("Wrote 50 bytes - click Update to see")
def update_all(self):
print("\n--- Updating displays ---")
self.widget1.update_display()
self.widget2.update_display() # This calls the callback!
app = QApplication(sys.argv)
window = DemoWindow()
window.show()
print("\n" + "="*60)
print("COMPACT BUFFER WIDGET DEMO")
print("="*60)
print("Widget 1: Basic compact widget")
print("Widget 2: Same but with callback function")
print("\nClick buttons and watch:")
print(" • Compact one-line display")
print(" • Manual updates")
print(" • Callback prints to console")
print("="*60 + "\n")
sys.exit(app.exec())
if __name__ == "__main__":
demo()