From bf6ac57f516e51b6a8b9a04c27c99305e68f5ae5 Mon Sep 17 00:00:00 2001 From: Florian Griffon Date: Sat, 24 May 2025 01:04:29 +0300 Subject: [PATCH] feat: split ui in multiple files for maintainability --- .../DryBox/UI/{python_ui.py => main.py} | 119 +----------------- protocol_prototype/DryBox/UI/phone_client.py | 61 +++++++++ protocol_prototype/DryBox/UI/phone_state.py | 5 + .../DryBox/UI/waveform_widget.py | 46 +++++++ 4 files changed, 117 insertions(+), 114 deletions(-) rename protocol_prototype/DryBox/UI/{python_ui.py => main.py} (71%) create mode 100644 protocol_prototype/DryBox/UI/phone_client.py create mode 100644 protocol_prototype/DryBox/UI/phone_state.py create mode 100644 protocol_prototype/DryBox/UI/waveform_widget.py diff --git a/protocol_prototype/DryBox/UI/python_ui.py b/protocol_prototype/DryBox/UI/main.py similarity index 71% rename from protocol_prototype/DryBox/UI/python_ui.py rename to protocol_prototype/DryBox/UI/main.py index 67e5a7c..f9a45fc 100644 --- a/protocol_prototype/DryBox/UI/python_ui.py +++ b/protocol_prototype/DryBox/UI/main.py @@ -1,122 +1,14 @@ import sys import random -import socket from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QSizePolicy, QStyle ) -from PyQt5.QtCore import Qt, QTimer, QSize, QPointF, pyqtSignal, QThread -from PyQt5.QtGui import QPainter, QColor, QPen, QLinearGradient, QBrush, QIcon, QFont - -# --- Phone Client Thread --- -class PhoneClient(QThread): - data_received = pyqtSignal(bytes, int) # Include client_id - state_changed = pyqtSignal(str, str, int) # Include client_id - - def __init__(self, host, port, client_id): - super().__init__() - self.host = host - self.port = port - self.client_id = client_id - self.sock = None - self.running = True - - def run(self): - try: - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - self.sock.settimeout(15) - self.sock.connect((self.host, self.port)) - print(f"Client {self.client_id} connected to {self.host}:{self.port}") - while self.running: - try: - data = self.sock.recv(1024) - if not data: - print(f"Client {self.client_id} disconnected") - self.state_changed.emit("CALL_END", "", self.client_id) - break - decoded_data = data.decode('utf-8', errors='ignore').strip() - print(f"Client {self.client_id} received raw: {decoded_data}") - if decoded_data in ["RINGING", "CALL_END", "CALL_DROPPED", "IN_CALL"]: - self.state_changed.emit(decoded_data, "", self.client_id) - else: - self.data_received.emit(data, self.client_id) - print(f"Client {self.client_id} received audio: {decoded_data}") - except socket.timeout: - print(f"Client {self.client_id} timed out waiting for data") - continue - except Exception as e: - print(f"Client {self.client_id} error: {e}") - self.state_changed.emit("CALL_END", "", self.client_id) - break - except Exception as e: - print(f"Client {self.client_id} connection failed: {e}") - finally: - if self.sock: - self.sock.close() - - def send(self, message): - if self.sock and self.running: - try: - self.sock.send(message.encode()) - print(f"Client {self.client_id} sent: {message}") - except Exception as e: - print(f"Client {self.client_id} send error: {e}") - - def stop(self): - self.running = False - if self.sock: - self.sock.close() - -# --- Custom Waveform Widget --- -class WaveformWidget(QWidget): - def __init__(self, parent=None, dynamic=False): - super().__init__(parent) - self.dynamic = dynamic - self.setMinimumSize(200, 80) - self.setMaximumHeight(100) - self.waveform_data = [random.randint(10, 90) for _ in range(50)] - if self.dynamic: - self.timer = QTimer(self) - self.timer.timeout.connect(self.update_waveform) - self.timer.start(100) - - def update_waveform(self): - self.waveform_data = self.waveform_data[1:] + [random.randint(10, 90)] - self.update() - - def set_data(self, data): - amplitude = sum(byte for byte in data) % 90 + 10 - self.waveform_data = self.waveform_data[1:] + [amplitude] - self.update() - - def paintEvent(self, event): - painter = QPainter(self) - painter.setRenderHint(QPainter.Antialiasing) - painter.fillRect(self.rect(), QColor("#2D2D2D")) - gradient = QLinearGradient(0, 0, 0, self.height()) - gradient.setColorAt(0.0, QColor("#0078D4")) - gradient.setColorAt(1.0, QColor("#50E6A4")) - pen = QPen(QBrush(gradient), 2) - painter.setPen(pen) - bar_width = self.width() / len(self.waveform_data) - max_h = self.height() - 10 - for i, val in enumerate(self.waveform_data): - bar_height = (val / 100.0) * max_h - x = i * bar_width - y = (self.height() - bar_height) / 2 - painter.drawLine(QPointF(x + bar_width / 2, y), QPointF(x + bar_width / 2, y + bar_height)) - - def resizeEvent(self, event): - super().resizeEvent(event) - self.update() - -# --- Phone State --- -class PhoneState: - IDLE = 0 - CALLING = 1 - IN_CALL = 2 - RINGING = 3 +from PyQt5.QtCore import Qt, QTimer, QSize +from PyQt5.QtGui import QFont +from phone_client import PhoneClient +from waveform_widget import WaveformWidget +from phone_state import PhoneState class PhoneUI(QMainWindow): def __init__(self): @@ -182,7 +74,6 @@ class PhoneUI(QMainWindow): client.state_changed.connect(lambda state, num, cid=i: self.set_phone_state(cid, self.map_state(state), num)) client.start() - # Corrected lambda to handle 'checked' and capture phone_id phone_widget_container, phone_display, phone_button, phone_waveform, phone_status_label = self._create_phone_ui( f"Phone {i+1}", lambda checked, phone_id=i: self.phone_action(phone_id) ) diff --git a/protocol_prototype/DryBox/UI/phone_client.py b/protocol_prototype/DryBox/UI/phone_client.py new file mode 100644 index 0000000..18e398c --- /dev/null +++ b/protocol_prototype/DryBox/UI/phone_client.py @@ -0,0 +1,61 @@ +import socket +from PyQt5.QtCore import QThread, pyqtSignal + +class PhoneClient(QThread): + data_received = pyqtSignal(bytes, int) # Include client_id + state_changed = pyqtSignal(str, str, int) # Include client_id + + def __init__(self, host, port, client_id): + super().__init__() + self.host = host + self.port = port + self.client_id = client_id + self.sock = None + self.running = True + + def run(self): + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self.sock.settimeout(15) + self.sock.connect((self.host, self.port)) + print(f"Client {self.client_id} connected to {self.host}:{self.port}") + while self.running: + try: + data = self.sock.recv(1024) + if not data: + print(f"Client {self.client_id} disconnected") + self.state_changed.emit("CALL_END", "", self.client_id) + break + decoded_data = data.decode('utf-8', errors='ignore').strip() + print(f"Client {self.client_id} received raw: {decoded_data}") + if decoded_data in ["RINGING", "CALL_END", "CALL_DROPPED", "IN_CALL"]: + self.state_changed.emit(decoded_data, "", self.client_id) + else: + self.data_received.emit(data, self.client_id) + print(f"Client {self.client_id} received audio: {decoded_data}") + except socket.timeout: + print(f"Client {self.client_id} timed out waiting for data") + continue + except Exception as e: + print(f"Client {self.client_id} error: {e}") + self.state_changed.emit("CALL_END", "", self.client_id) + break + except Exception as e: + print(f"Client {self.client_id} connection failed: {e}") + finally: + if self.sock: + self.sock.close() + + def send(self, message): + if self.sock and self.running: + try: + self.sock.send(message.encode()) + print(f"Client {self.client_id} sent: {message}") + except Exception as e: + print(f"Client {self.client_id} send error: {e}") + + def stop(self): + self.running = False + if self.sock: + self.sock.close() \ No newline at end of file diff --git a/protocol_prototype/DryBox/UI/phone_state.py b/protocol_prototype/DryBox/UI/phone_state.py new file mode 100644 index 0000000..56d40c1 --- /dev/null +++ b/protocol_prototype/DryBox/UI/phone_state.py @@ -0,0 +1,5 @@ +class PhoneState: + IDLE = 0 + CALLING = 1 + IN_CALL = 2 + RINGING = 3 \ No newline at end of file diff --git a/protocol_prototype/DryBox/UI/waveform_widget.py b/protocol_prototype/DryBox/UI/waveform_widget.py new file mode 100644 index 0000000..9b26240 --- /dev/null +++ b/protocol_prototype/DryBox/UI/waveform_widget.py @@ -0,0 +1,46 @@ +import random +from PyQt5.QtWidgets import QWidget +from PyQt5.QtCore import QTimer, QSize, QPointF +from PyQt5.QtGui import QPainter, QColor, QPen, QLinearGradient, QBrush + +class WaveformWidget(QWidget): + def __init__(self, parent=None, dynamic=False): + super().__init__(parent) + self.dynamic = dynamic + self.setMinimumSize(200, 80) + self.setMaximumHeight(100) + self.waveform_data = [random.randint(10, 90) for _ in range(50)] + if self.dynamic: + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_waveform) + self.timer.start(100) + + def update_waveform(self): + self.waveform_data = self.waveform_data[1:] + [random.randint(10, 90)] + self.update() + + def set_data(self, data): + amplitude = sum(byte for byte in data) % 90 + 10 + self.waveform_data = self.waveform_data[1:] + [amplitude] + self.update() + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.fillRect(self.rect(), QColor("#2D2D2D")) + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, QColor("#0078D4")) + gradient.setColorAt(1.0, QColor("#50E6A4")) + pen = QPen(QBrush(gradient), 2) + painter.setPen(pen) + bar_width = self.width() / len(self.waveform_data) + max_h = self.height() - 10 + for i, val in enumerate(self.waveform_data): + bar_height = (val / 100.0) * max_h + x = i * bar_width + y = (self.height() - bar_height) / 2 + painter.drawLine(QPointF(x + bar_width / 2, y), QPointF(x + bar_width / 2, y + bar_height)) + + def resizeEvent(self, event): + super().resizeEvent(event) + self.update() \ No newline at end of file