Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion documentation/implementacoes.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ diretório debug/
|---|---|---|
| `main.py` | Janela principal: menus, botões, tabelas, ligação com a simulação | `MainFrame.java` |
| `simulation.py` | Controle da simulação e janelas de checkpoint | `MainFrame.java` (parte de simulação), `CheckpointController.java` |
| `theme.py` | Paletas dos temas claro/escuro, cores da grade e preferência salva | — |
| `router_matrix.py` | Desenho da grade e de cada roteador (imagens, setas, %) | `Roteador.java`, `UJPanelImagem.java`, `MainFrame.createNoCPanel` |
| `util/MPSoCConfig.py` | Leitura de `platform.cfg` e `services.cfg`, constantes e endereçamento | `util/MPSoCConfig.java` |
| `information/packet_information.py` | Um pacote do `traffic_router.txt` | `PacketInformation.java` |
Expand Down Expand Up @@ -239,6 +240,10 @@ Reaproveita o layout do `Roteador.py` (gerado de `Roteador.ui`) e acrescenta:

As imagens (pasta `images/`) são desenhadas pelo `_ImagePainter`, um *event filter* que pinta a imagem esticada sobre os `QWidget` vazios do `.ui`, substituindo o `UJPanelImagem` do Java. As imagens ficam em cache (`_pixmap_cache`).

Os PNGs das setas não têm canal alfa (fundo branco sólido). Ao carregar, `_white_to_alpha` converte o fundo e o serrilhado da borda em transparência, mantendo a cor da seta, para que elas funcionem sobre o fundo escuro. As imagens do corpo do roteador (`Router*.png`) são usadas como estão.

As cores da grade vêm de uma única folha de estilo aplicada no `RouterMatrixWidget` (`apply_theme(dark)`): fundo da área, texto do % da porta local e borda dos clusters seguem o tema; os rótulos dentro do corpo do roteador são sempre pretos, porque o corpo tem cor clara nos dois temas.

---

## 8. Simulação — `simulation.py`
Expand Down Expand Up @@ -334,7 +339,16 @@ Divide o tempo em janelas de **0,5 ms** (`window_size_ms`). `set_time(ticks)` co
5. Limpa o campo **Back To**.
6. Mostra *"The project has been successfully loaded!"*.

### 9.2 Mensagens
### 9.2 Tema claro/escuro

**Menu:** `Edit → Dark Mode` (Ctrl+T), item marcável.

- `theme.py` define as paletas do Fusion para os dois temas, incluindo as cores de itens **desabilitados** e do texto de *placeholder* (sem elas, botões desabilitados ficavam ilegíveis no modo escuro).
- `MinhaJanela.set_dark_mode(dark)` aplica a paleta na aplicação inteira, atualiza a marcação do menu e recolore a grade (`RouterMatrixWidget.apply_theme`). Uma grade criada depois (novo debug ou reset) já nasce com o tema atual.
- A escolha é salva com `QSettings` (organização `GAPH`, aplicação `MemphisGraphicalDebugger`) e restaurada ao abrir o programa.
- As janelas Communication Overview e Task Mapping mantêm suas cores fixas.

### 9.3 Mensagens

| Situação | Mensagem |
|---|---|
Expand Down
80 changes: 23 additions & 57 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
QVBoxLayout, QHBoxLayout, QScrollArea, QTableWidgetItem, QTableWidget, QLabel,
QHeaderView)

import theme
from ui_mainwindow import MainWindow
from commsUi import Ui_Form
from taskmap import Ui_TaskMap
Expand Down Expand Up @@ -40,16 +41,15 @@ def __init__(self):
# ---------------------------------------------------------
# Limpa a paleta fixa gerada pelo arquivo .ui para herdar a do App
self.setPalette(QPalette())
self.apply_light_theme(app)
self.is_dark_mode = False
# Cria uma ação para alternar o tema e adiciona no menu "Edit"
self.actionToggle_Theme = QAction("Alternar Tema (Dark/Light)", self)

# Ação marcável no menu "Edit"; o último tema escolhido é restaurado
self.actionToggle_Theme = QAction("Dark Mode", self)
self.actionToggle_Theme.setCheckable(True)
self.actionToggle_Theme.setShortcut(QKeySequence("Ctrl+T"))
self.menuEdit.addAction(self.actionToggle_Theme)
self.actionToggle_Theme.triggered.connect(self.toggle_theme)
self.actionToggle_Theme.toggled.connect(self.set_dark_mode)

# Garante que o aplicativo inicie no modo claro padrão do estilo Fusion
self.apply_light_theme(QApplication.instance())
self.set_dark_mode(theme.load_dark_mode())
# ---------------------------------------------------------

self.actionCommunication_Overview.setEnabled(False)
Expand Down Expand Up @@ -102,56 +102,21 @@ def __init__(self):
# LÓGICA DE MUDANÇA DE TEMA
# ==========================================
def toggle_theme(self):
app = QApplication.instance()
if not self.is_dark_mode:
self.apply_dark_theme(app)
self.is_dark_mode = True
else:
self.apply_light_theme(app)
self.is_dark_mode = False

def apply_dark_theme(self, app):
app.setStyle("Fusion")
dark_palette = QPalette()

# Configurando as cores do modo escuro
dark_palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))
dark_palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white)
dark_palette.setColor(QPalette.ColorRole.Base, QColor(35, 35, 35))
dark_palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53))
dark_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(25, 25, 25))
dark_palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white)
dark_palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white)
dark_palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53))
dark_palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white)
dark_palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
dark_palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
dark_palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
dark_palette.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.black)

# Aplica na aplicação inteira (afeta abas secundárias também)
app.setPalette(dark_palette)

def apply_light_theme(self, app):
app.setStyle("Fusion")
light_palette = QPalette()

# Força as cores do modo claro para sobrescrever o tema do Sistema Operacional
light_palette.setColor(QPalette.ColorRole.Window, QColor(240, 240, 240))
light_palette.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.black)
light_palette.setColor(QPalette.ColorRole.Base, Qt.GlobalColor.white)
light_palette.setColor(QPalette.ColorRole.AlternateBase, QColor(225, 225, 225))
light_palette.setColor(QPalette.ColorRole.ToolTipBase, Qt.GlobalColor.white)
light_palette.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.black)
light_palette.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.black)
light_palette.setColor(QPalette.ColorRole.Button, QColor(240, 240, 240))
light_palette.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.black)
light_palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
light_palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
light_palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
light_palette.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.white)

app.setPalette(light_palette)
self.set_dark_mode(not self.is_dark_mode)

def set_dark_mode(self, dark):
"""Aplica o tema na aplicação inteira e na grade de roteadores."""
self.is_dark_mode = dark
theme.apply_theme(dark)
theme.save_dark_mode(dark)

# Mantém a marcação do menu em sincronia sem disparar o sinal de novo
self.actionToggle_Theme.blockSignals(True)
self.actionToggle_Theme.setChecked(dark)
self.actionToggle_Theme.blockSignals(False)

if self.router_matrix_widget is not None:
self.router_matrix_widget.apply_theme(dark)

# ==========================================

Expand Down Expand Up @@ -226,6 +191,7 @@ def build_router_matrix(self):
self.mpconfig.cluster_y,
mpsoc_config=self.mpconfig,
)
self.router_matrix_widget.apply_theme(self.is_dark_mode)

# Primeira vez: cria um QScrollArea dentro do self.frame para
# comportar malhas maiores que a área visível da janela.
Expand Down
5 changes: 2 additions & 3 deletions pe_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@


# Cores fixas, independentes do tema (claro/escuro) do MainWindow — mesmo
# motivo do _ROUTER_STYLESHEET em router_matrix.py e do _SLAVE_STYLESHEET
# em slave_matrix.py: sem isso, o toggle Dark/Light recoloriria essas
# células via QApplication.setPalette().
# motivo do _SLAVE_STYLESHEET em slave_matrix.py: sem isso, o toggle
# Dark/Light recoloriria essas células via QApplication.setPalette().
_PE_STYLESHEET = """
QWidget#peWidget {
background-color: #ffffff;
Expand Down
109 changes: 77 additions & 32 deletions router_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@
import os

from PySide6.QtCore import QEvent, QObject, QRect, Qt, Signal
from PySide6.QtGui import QFont, QPainter, QPixmap
from PySide6.QtGui import QColor, QFont, QImage, QPainter, QPixmap
from PySide6.QtWidgets import QFrame, QGridLayout, QWidget

import theme
from Roteador import Ui_Form
from util.MPSoCConfig import MPSoCConfig

Expand All @@ -45,9 +46,43 @@
_pixmap_cache: dict[str, QPixmap] = {}


def _white_to_alpha(image: QImage) -> QImage:
"""
Remove o fundo branco das imagens das setas (os PNGs não têm canal
alfa), para que fiquem legíveis sobre o fundo escuro do modo escuro.

A cor mais distante do branco é tomada como a cor da seta; os pixels
mais claros (fundo e serrilhado da borda) viram essa cor com opacidade
proporcional à distância do branco.
"""
image = image.convertToFormat(QImage.Format.Format_ARGB32)
width, height = image.width(), image.height()

def distance(color: QColor) -> int:
return 255 - min(color.red(), color.green(), color.blue())

pixels = [[image.pixelColor(x, y) for x in range(width)] for y in range(height)]
base = max((color for row in pixels for color in row), key=distance)
base_distance = distance(base) or 1

for y, row in enumerate(pixels):
for x, color in enumerate(row):
alpha = distance(color) / base_distance
if alpha < 1:
faded = QColor(base)
faded.setAlphaF(alpha)
image.setPixelColor(x, y, faded)

return image


def _pixmap(name: str) -> QPixmap:
if name not in _pixmap_cache:
_pixmap_cache[name] = QPixmap(os.path.join(IMAGES_DIR, name))
image = QImage(os.path.join(IMAGES_DIR, name))
# As imagens do corpo do roteador são opacas e não têm fundo branco
if not name.startswith("Router"):
image = _white_to_alpha(image)
_pixmap_cache[name] = QPixmap.fromImage(image)
return _pixmap_cache[name]


Expand Down Expand Up @@ -136,22 +171,30 @@ def eventFilter(self, obj, event):
return super().eventFilter(obj, event)


# Cores fixas do roteador, independentes do tema (claro/escuro) que o
# MainWindow aplica globalmente via QApplication.setPalette(). Sem isso,
# alternar o tema (Ctrl+T) recolore os widgets internos do roteador (portas,
# rótulos) porque eles não têm paleta própria e herdam a paleta do app,
# quebrando a visualização (fundo/texto ficam com a mesma cor, sumindo, ou
# os pequenos retângulos de porta ficam visíveis onde antes eram
# transparentes ao fundo).
_ROUTER_STYLESHEET = """
QWidget {
background-color: #ffffff;
color: #000000;
}
QLabel {
background-color: transparent;
}
"""
def _matrix_stylesheet(dark: bool) -> str:
"""
Folha de estilo da grade inteira (aplicada no RouterMatrixWidget e
herdada pelos roteadores). Os rótulos dentro do corpo do roteador são
sempre pretos, pois o corpo tem cor clara nos dois temas; o % da porta
local fica fora do corpo e acompanha o tema.
"""
colors = theme.matrix_colors(dark)
return f"""
#routerMatrix {{
background-color: {colors.canvas};
}}
#routerMatrix QLabel {{
background-color: transparent;
color: #000000;
}}
#routerMatrix QLabel#local_Label {{
color: {colors.text};
}}
#routerMatrix QFrame[clusterFrame="true"] {{
background-color: transparent;
border: 1px solid {colors.cluster_border};
}}
"""


class RouterWidget(QWidget, Ui_Form):
Expand Down Expand Up @@ -200,13 +243,12 @@ def __init__(self, pos_x: int, pos_y: int, parent=None,
for label_name in set(_PORT_LABELS.values()):
getattr(self, label_name).setFont(percent_font)

# Isola este roteador do tema global do app (ver comentário acima
# de _ROUTER_STYLESHEET): fixa cores próprias em toda a subárvore,
# que não são afetadas por um app.setPalette() posterior.
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
for child in self.findChildren(QWidget):
child.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
self.setStyleSheet(_ROUTER_STYLESHEET)
# O Roteador.ui fixa fundo branco no Form inteiro; as cores passam a
# vir da folha de estilo da matriz (_matrix_stylesheet), que segue o tema
self.setStyleSheet("")

# Mesma posição do Java: acima do corpo do roteador, sem cruzar a borda
self.local_Label.setGeometry(QRect(21, 16, 40, 13))

self.reset_arrows()

Expand Down Expand Up @@ -291,6 +333,10 @@ def __init__(self, mpsoc_x: int, mpsoc_y: int,

self.mpsoc_config = mpsoc_config

# Fundo e cores da grade seguem o tema (ver apply_theme)
self.setObjectName("routerMatrix")
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)

# mpsoc_x/mpsoc_y é o tamanho TOTAL da malha.
self.total_cols = max(1, mpsoc_x)
self.total_rows = max(1, mpsoc_y)
Expand Down Expand Up @@ -349,13 +395,8 @@ def _build_cluster(self, cluster_x: int, cluster_y: int,
cluster_frame.setFrameShape(QFrame.Shape.Box)
cluster_frame.setFrameShadow(QFrame.Shadow.Plain)
cluster_frame.setLineWidth(1)
# Borda do cluster com cor fixa, isolada do tema global (mesmo
# motivo do _ROUTER_STYLESHEET: sem isso, o toggle Dark/Light
# do MainWindow também recolore essa borda de forma inconsistente).
cluster_frame.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
cluster_frame.setStyleSheet(
"QFrame { background-color: #ffffff; border: 1px solid #808080; }"
)
# A cor da borda vem da folha de estilo da matriz (_matrix_stylesheet)
cluster_frame.setProperty("clusterFrame", True)

cluster_layout = QGridLayout(cluster_frame)
cluster_layout.setSpacing(0)
Expand Down Expand Up @@ -391,6 +432,10 @@ def get_router(self, x: int, y: int) -> RouterWidget | None:
"""Retorna o RouterWidget na posição global (x, y), se existir."""
return self.routers.get((x, y))

def apply_theme(self, dark: bool) -> None:
"""Aplica as cores do tema claro ou escuro na grade."""
self.setStyleSheet(_matrix_stylesheet(dark))

def get_router_by_address(self, router_address: int) -> RouterWidget | None:
"""Retorna o RouterWidget pelo endereço hamiltoniano, se existir."""
return self.routers_by_address.get(router_address)
Expand Down
6 changes: 3 additions & 3 deletions slave_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
from PySide6.QtWidgets import QGridLayout, QLabel, QSizePolicy, QVBoxLayout, QWidget


# Cores fixas, independentes do tema (claro/escuro) do MainWindow — mesmo
# motivo do _ROUTER_STYLESHEET em router_matrix.py: sem isso, o toggle
# Dark/Light recoloriaria essas células via QApplication.setPalette().
# Cores fixas, independentes do tema (claro/escuro) do MainWindow: sem
# isso, o toggle Dark/Light recoloriria essas células via
# QApplication.setPalette().
_SLAVE_STYLESHEET = """
QWidget#slaveWidget {
background-color: #0000cc;
Expand Down
Loading