diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4f88a80 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,81 @@ +# ============================================================ +# STAGE 1: base — Python 3.12 + Qt6 system dependencies +# ============================================================ +FROM python:3.9-slim AS base + +ENV DEBIAN_FRONTEND=noninteractive + +RUN pip install --upgrade pip + +# Qt6/PyQt6 runtime dependencies (xcb platform plugin requirements) +RUN apt-get update && apt-get install -y --no-install-recommends \ + libgl1 \ + libglib2.0-0 \ + libfontconfig1 \ + libxkbcommon-x11-0 \ + libdbus-1-3 \ + libegl1 \ + libxcb-xinerama0 \ + libxcb-cursor0 \ + libxcb-shape0 \ + libxcb-icccm4 \ + libxcb-keysyms1 \ + libxcb-render-util0 \ + libxcb-image0 \ + libgssapi-krb5-2 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +ENV PYTHONPATH=/app/src + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY src/ ./src/ + +# ============================================================ +# STAGE 2: dev — GUI development with X11 forwarding +# ============================================================ +FROM base AS dev + +ENV DISPLAY=:0 +ENV QT_QPA_PLATFORM=xcb + +CMD ["python", "-m", "main"] + +# ============================================================ +# STAGE 3: test — Headless testing with Xvfb +# ============================================================ +FROM base AS test + +RUN apt-get update && apt-get install -y --no-install-recommends \ + xvfb \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir \ + "pytest>=7.0" \ + "pytest-qt>=4.2" + +ENV QT_QPA_PLATFORM=xcb + +COPY tests/ ./tests/ + +CMD ["xvfb-run", "--auto-servernum", \ + "--server-args=-screen 0 1920x1080x24", \ + "pytest", "tests/", "-v"] + +# ============================================================ +# STAGE 4: build — PyInstaller Linux binary +# ============================================================ +FROM base AS build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + binutils \ + && rm -rf /var/lib/apt/lists/* + +RUN pip install --no-cache-dir "pyinstaller>=6.0" + +CMD ["pyinstaller", "src/main.py", \ + "--name", "SignalViewer", \ + "--onefile", "--windowed", \ + "--distpath", "/app/dist"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b7d9cee --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: dev test build build-bin clean help + +help: ## Show available commands + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " make %-12s %s\n", $$1, $$2}' + +dev: ## Run the GUI app (X11 via WSLg) + docker compose run --rm dev + +test: ## Run tests headlessly (Xvfb) + docker compose run --rm test + +build: ## Build Linux binary → ./dist/SignalViewer + docker compose run --rm build + +build-bin: ## Build Linux binary without Docker → ./dist/SignalViewer + sudo apt install -y python3.12-venv + python3 -m venv .venv + .venv/bin/pip install -r requirements.txt + .venv/bin/pip install "pyinstaller>=6.0" + .venv/bin/pyinstaller src/app/main.py --name SignalViewer --onefile --windowed + +build-exe: ## Build Windows .exe → run build-exe.ps1 from PowerShell on Windows + @echo "ERROR: Cannot build a Windows .exe from WSL2/Linux." + @echo "Open PowerShell on Windows and run: .\\build-exe.ps1" + @exit 1 + +clean: ## Remove build artifacts and Docker images + rm -rf dist/ build/ *.spec + docker compose down --rmi local --volumes --remove-orphans diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..74759e9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + # GUI development — requires WSLg or VcXsrv for X11 display + dev: + build: + context: . + target: dev + environment: + - DISPLAY=${DISPLAY:-:0} + - QT_QPA_PLATFORM=xcb + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix:rw + - ./src:/app/src + network_mode: host + stdin_open: true + tty: true + + # Headless testing with virtual framebuffer + test: + build: + context: . + target: test + volumes: + - ./tests:/app/tests + - ./src:/app/src + + # PyInstaller build (produces Linux binary, NOT Windows .exe) + build: + build: + context: . + target: build + volumes: + - ./dist:/app/dist diff --git a/src/Utils/aboutDialog.py b/src/Utils/aboutDialog.py index d6aed63..7e7efc3 100644 --- a/src/Utils/aboutDialog.py +++ b/src/Utils/aboutDialog.py @@ -145,10 +145,11 @@ def setupUi(self, AboutDialog): self.linkTausand.setOpenExternalLinks(True) self.linkGitHub.setOpenExternalLinks(True) ##translateFuncion - self.descriptionLabel.setText(QCoreApplication.translate("AboutDialog", u"Tempico Software is a suite of tools build to ensure your experience with Tausand's time to digital converters.", None)) + # self.descriptionLabel.setText(QCoreApplication.translate("AboutDialog", u"Tempico Software is a suite of tools build to ensure your experience with Tausand's time to digital converters.", None)) self.softwareLabel.setText(QCoreApplication.translate("AboutDialog", u"Software Version: %s"%VERSION, None)) self.tempicoLabel.setText(QCoreApplication.translate("AboutDialog", u"PyTempico Version: %s"%PYTEMPICO_VERSION, None)) #self.imageLabel.setText(QCoreApplication.translate("AboutDialog", u"Picture Label", None)) + self.descriptionLabel.setText(QCoreApplication.translate("AboutDialog", u"My first experience as a full stack Python developer was in the GUI Python area. At the time, I had been working as a data scientist for almost two years, primarily focused on data analysis and modeling. Then, a friend of mine offer my profile to a Canadian geophysics company (Geophysics GPR International Inc.) that completely changed my trajectory.\nThe company had been developing a GUI project for almost six months, with the goal of processing seismic trace information vital for structural seismic analysis. The role was entirely new to me, not just in terms of the domain, but also in terms of what I thought Python was capable of. I had always associated Python with scripting, data pipelines, and machine learning workflows never with rich, interactive graphical interfaces.\nFor this project, I worked with Tkinter and Matplotlib as the core tools to build the interface. The final product was conceptually similar to SeisImager/2D, a well-known licensed software used in the geophysical industry for seismic refraction analysis and interpretation. Replicating that level of functionality using open-source Python libraries was both a challenge and an incredibly rewarding experience.\nOver the course of nearly a year, I gained a deep understanding of GUI development with Python — event-driven programming, dynamic plot rendering, data visualization, and user interaction design. Beyond the interface itself, I also developed a strong foundation in modeling physical phenomena, particularly in how seismic waves propagate through geological structures and how that data is interpreted visually.\nLooking back, this experience not only broadened my technical skill set but also gave me a genuine appreciation for how powerful and versatile Python truly is across different engineering disciplines.", None)) self.linkTausand.setText(QCoreApplication.translate("AboutDialog", u"Visit us at: %s "%tausand, None)) self.linkGitHub.setText(QCoreApplication.translate("AboutDialog", u"More information on Tempico Software implementation can be found at: %s"%pages, None)) self.pushButton.setText(QCoreApplication.translate("AboutDialog", u"Ok", None)) diff --git a/src/main.py b/src/main.py index c91572d..e82b0bc 100644 --- a/src/main.py +++ b/src/main.py @@ -230,10 +230,12 @@ def __init__(self, parent=None, *args): self.construct_start_stop_histogram(self.tab1) self.connectButton = QPushButton("Connect", self) self.disconnectButton = QPushButton("Disconnect", self) + self.about_me_button = QPushButton("About Me", parent=self) buttonLayout = QHBoxLayout() buttonLayout.addWidget(self.connectButton) buttonLayout.addWidget(self.disconnectButton) + buttonLayout.addWidget(self.about_me_button) # Crear un QWidget para contener los QTabWidget y los botones mainWidget = QWidget(self) @@ -263,6 +265,7 @@ def __init__(self, parent=None, *args): self.connectsentinel=0 self.connectButton.clicked.connect(self.open_dialog) self.disconnectButton.clicked.connect(self.disconnect_button_click) + self.about_me_button.clicked.connect(self.about_settings) self.sentinel2=0 self.sentinel3=0 self.sentinel4=0 @@ -1029,7 +1032,6 @@ def about_settings(self): settings_windows.setupUi(settings_windows_dialog) settings_windows_dialog.exec_() - #This function is not use for the Tempico Version 1.1 #TO DO: Comment the function for a future version def parameters_action(self):