feat: add exit confirmation dialog, system tray enhancements, and diagnostic options
This commit is contained in:
+14
@@ -36,6 +36,17 @@ public class CyberCoreSignerApplication {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Previne conflitos nativos de UI Automation/Acessibilidade do Glass no Windows 11
|
||||
if (System.getProperty("javafx.accessibility") == null) {
|
||||
System.setProperty("javafx.accessibility", "false");
|
||||
}
|
||||
if (System.getProperty("glass.accessible.force") == null) {
|
||||
System.setProperty("glass.accessible.force", "false");
|
||||
}
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
||||
logger.error("Exceção não tratada na thread '{}': {}", thread.getName(), throwable.getMessage(), throwable);
|
||||
});
|
||||
|
||||
parseArguments(args);
|
||||
printBanner();
|
||||
|
||||
@@ -67,6 +78,9 @@ public class CyberCoreSignerApplication {
|
||||
try {
|
||||
logger.info("Iniciando interface gráfica desktop...");
|
||||
CyberCoreDesktopApplication.setDevelopmentMode(developmentMode);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() ->
|
||||
logger.warn("JVM em encerramento (sinal externo, System.exit ou fim do aplicativo)."),
|
||||
"cybercore-shutdown-diagnostic"));
|
||||
Application.launch(CyberCoreDesktopApplication.class, args);
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Não foi possível inicializar ambiente gráfico ({}). Continuando em modo console.", t.getMessage());
|
||||
|
||||
+17
-2
@@ -33,12 +33,27 @@ public class CertificateManager {
|
||||
|
||||
/**
|
||||
* Cria e inicializa uma instância padrão do CertificateManager com os provedores nativos da plataforma.
|
||||
* Suporta chaves de diagnóstico para isolamento de problemas de middleware:
|
||||
* -Dcybercore.diag.no-wincert=true (não inicializa Windows Certificate Store)
|
||||
* -Dcybercore.diag.no-pkcs11=true (não inicializa PKCS#11 / DLLs nativas)
|
||||
*/
|
||||
public static CertificateManager createDefault() {
|
||||
CertificateManager manager = new CertificateManager();
|
||||
manager.registerProvider(new WindowsCertificateStoreProvider());
|
||||
|
||||
if (Boolean.getBoolean("cybercore.diag.no-wincert")) {
|
||||
logger.warn("Diagnóstico: Provedor Windows Certificate Store DESATIVADO via -Dcybercore.diag.no-wincert=true");
|
||||
} else {
|
||||
manager.registerProvider(new WindowsCertificateStoreProvider());
|
||||
}
|
||||
|
||||
manager.registerProvider(new Pkcs12CertificateProvider());
|
||||
manager.registerProvider(new Pkcs11CertificateProvider());
|
||||
|
||||
if (Boolean.getBoolean("cybercore.diag.no-pkcs11")) {
|
||||
logger.warn("Diagnóstico: Provedor PKCS#11 DESATIVADO via -Dcybercore.diag.no-pkcs11=true");
|
||||
} else {
|
||||
manager.registerProvider(new Pkcs11CertificateProvider());
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
|
||||
+30
@@ -63,4 +63,34 @@ class CertificateManagerTest {
|
||||
assertThat(byCnpj).isPresent();
|
||||
assertThat(byCnpj.get().id()).isEqualTo("cert-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deve desativar provider PKCS#11 quando chave de diagnóstico estiver ativa")
|
||||
void shouldRespectDiagnosticsNoPkcs11() {
|
||||
System.setProperty("cybercore.diag.no-pkcs11", "true");
|
||||
try {
|
||||
CertificateManager manager = CertificateManager.createDefault();
|
||||
assertThat(manager.getProviders())
|
||||
.extracting(CertificateProvider::getProviderId)
|
||||
.doesNotContain("provider-pkcs11")
|
||||
.contains("provider-a1");
|
||||
} finally {
|
||||
System.clearProperty("cybercore.diag.no-pkcs11");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Deve desativar provider Windows Certificate Store quando chave de diagnóstico estiver ativa")
|
||||
void shouldRespectDiagnosticsNoWinCert() {
|
||||
System.setProperty("cybercore.diag.no-wincert", "true");
|
||||
try {
|
||||
CertificateManager manager = CertificateManager.createDefault();
|
||||
assertThat(manager.getProviders())
|
||||
.extracting(CertificateProvider::getProviderId)
|
||||
.doesNotContain("provider-win-store")
|
||||
.contains("provider-a1");
|
||||
} finally {
|
||||
System.clearProperty("cybercore.diag.no-wincert");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-12
@@ -117,10 +117,14 @@ public class CyberCoreDesktopApplication extends Application {
|
||||
mainAppView.setOnValidateTimestamp(() -> new SignatureValidationDialog(primaryStage, true).show());
|
||||
mainAppView.setOnManageSeals(() -> new SealManagementDialog(primaryStage).show());
|
||||
mainAppView.setOnExit(() -> {
|
||||
logger.info("Encerrando CyberCore Signer pela interface gráfica.");
|
||||
primaryStage.close();
|
||||
Platform.exit();
|
||||
System.exit(0);
|
||||
logger.info("Ação 'Sair' acionada na interface gráfica.");
|
||||
if (systemTrayManager != null) {
|
||||
systemTrayManager.handleWindowCloseRequest();
|
||||
} else {
|
||||
primaryStage.close();
|
||||
Platform.exit();
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
|
||||
// Ações do Painel Lateral
|
||||
@@ -130,9 +134,21 @@ public class CyberCoreDesktopApplication extends Application {
|
||||
mainAppView.setOnCertificates(() -> new CertificatesManagerDialog(primaryStage, certificateManager).show());
|
||||
mainAppView.setOnAbout(() -> openAboutDialog(primaryStage));
|
||||
|
||||
// Exibe imediatamente a janela principal em primeiro plano para o usuário
|
||||
mainAppView.show();
|
||||
logger.info("Janela principal JavaFX exibida.");
|
||||
// Observabilidade do ciclo de vida da janela
|
||||
primaryStage.showingProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.info("Visibilidade da janela principal JavaFX alterada: visível={}", newVal));
|
||||
primaryStage.iconifiedProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.info("Estado de minimização da janela JavaFX alterado: minimizada={}", newVal));
|
||||
primaryStage.focusedProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.info("Foco da janela principal alterado: focada={}", newVal));
|
||||
primaryStage.xProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.debug("Janela principal movida: x={}, y={}", newVal, primaryStage.getY()));
|
||||
primaryStage.yProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.debug("Janela principal movida: x={}, y={}", primaryStage.getX(), newVal));
|
||||
primaryStage.widthProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.debug("Janela principal redimensionada: largura={}, altura={}", newVal, primaryStage.getHeight()));
|
||||
primaryStage.heightProperty().addListener((obs, oldVal, newVal) ->
|
||||
logger.debug("Janela principal redimensionada: largura={}, altura={}", primaryStage.getWidth(), newVal));
|
||||
|
||||
// Inicializa o servidor local do protocolo CCSP/1 para atender requisições do navegador
|
||||
initCcspServer(primaryStage);
|
||||
@@ -140,12 +156,22 @@ public class CyberCoreDesktopApplication extends Application {
|
||||
// Monitoramento em tempo real de conexão/remoção de Smart Cards e Tokens USB
|
||||
initSmartCardWatcher();
|
||||
|
||||
// Configura bandeja do sistema (System Tray) para operação contínua em segundo plano
|
||||
this.systemTrayManager = new SystemTrayManager(primaryStage, this::refreshCertificates);
|
||||
this.systemTrayManager.setup();
|
||||
|
||||
// Carrega os certificados instalados na máquina em segundo plano para não congelar a interface
|
||||
CompletableFuture.runAsync(this::refreshCertificates);
|
||||
// Carrega os certificados instalados na máquina do usuário de forma estável
|
||||
refreshCertificates();
|
||||
|
||||
// Exibe a janela principal em primeiro plano após os serviços estarem prontos
|
||||
mainAppView.show();
|
||||
logger.info("Janela principal JavaFX exibida.");
|
||||
|
||||
// Configura a bandeja (System Tray) somente depois que a janela JavaFX está visível,
|
||||
// para não inicializar o toolkit AWT concorrentemente com a criação da janela.
|
||||
if (Boolean.getBoolean("cybercore.no.tray")) {
|
||||
logger.warn("System Tray desativado via -Dcybercore.no.tray=true (diagnóstico).");
|
||||
} else {
|
||||
Platform.runLater(() -> this.systemTrayManager.setup());
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshCertificates() {
|
||||
@@ -473,12 +499,23 @@ public class CyberCoreDesktopApplication extends Application {
|
||||
this.ccspServer = new CcspServer(this.certificateManager, this.signingService, promptListener);
|
||||
this.ccspServer.start();
|
||||
logger.info("Servidor local CCSP/1 inicializado na porta {}", this.ccspServer.getPort());
|
||||
} catch (java.net.BindException be) {
|
||||
logger.warn("Porta 21325 já está ocupada por outra instância do CyberCore Signer em execução. O agente continuará ativo na interface gráfica.");
|
||||
} catch (Exception e) {
|
||||
logger.error("Falha ao inicializar servidor local CCSP/1: {}", e.getMessage(), e);
|
||||
if (e.getCause() instanceof java.net.BindException || (e.getMessage() != null && e.getMessage().contains("Address already in use"))) {
|
||||
logger.warn("Porta 21325 já está ocupada por outra instância do CyberCore Signer em execução. O agente continuará ativo na interface gráfica.");
|
||||
} else {
|
||||
logger.error("Falha ao inicializar servidor local CCSP/1: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initSmartCardWatcher() {
|
||||
if (Boolean.getBoolean("cybercore.diag.no-smartcard-watcher")) {
|
||||
logger.warn("Diagnóstico: Monitor dinâmico de smart cards DESATIVADO via -Dcybercore.diag.no-smartcard-watcher=true");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.smartCardWatcher = new SmartCardWatcher();
|
||||
this.smartCardWatcher.addListener(new SmartCardWatcher.CardEventListener() {
|
||||
|
||||
+55
-40
@@ -1,5 +1,6 @@
|
||||
package br.com.cybercore.signer.ui.tray;
|
||||
|
||||
import br.com.cybercore.signer.ui.view.ExitConfirmationDialog;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
@@ -15,8 +16,6 @@ import java.awt.PopupMenu;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -42,12 +41,19 @@ public class SystemTrayManager {
|
||||
/**
|
||||
* Inicializa o ícone e menu de contexto na bandeja do sistema, caso haja suporte.
|
||||
*/
|
||||
public synchronized boolean setup() {
|
||||
public boolean setup() {
|
||||
if (!SystemTray.isSupported()) {
|
||||
logger.info("SystemTray não é suportado pelo ambiente atual ou sistema operacional.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// O TrayIcon AWT é criado na thread de eventos do AWT, nunca na thread do JavaFX,
|
||||
// para evitar conflito entre os dois loops de mensagens nativos no Windows.
|
||||
java.awt.EventQueue.invokeLater(this::setupOnAwtThread);
|
||||
return true;
|
||||
}
|
||||
|
||||
private synchronized void setupOnAwtThread() {
|
||||
try {
|
||||
SystemTray tray = SystemTray.getSystemTray();
|
||||
Image iconImage = createTrayIconImage();
|
||||
@@ -78,69 +84,78 @@ public class SystemTrayManager {
|
||||
this.trayIcon = new TrayIcon(iconImage, "CyberCore Signer (CCSP/1 Ativo)", popup);
|
||||
this.trayIcon.setImageAutoSize(true);
|
||||
|
||||
// Clique com o botão esquerdo (single-click) restaura a janela imediatamente
|
||||
this.trayIcon.addMouseListener(new MouseAdapter() {
|
||||
// Duplo clique (ActionListener) e clique simples (MouseListener) restauram a janela
|
||||
this.trayIcon.addActionListener(e -> {
|
||||
logger.info("Ícone da bandeja acionado (duplo clique).");
|
||||
Platform.runLater(this::showStage);
|
||||
});
|
||||
this.trayIcon.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
public void mouseClicked(java.awt.event.MouseEvent e) {
|
||||
if (e.getButton() == java.awt.event.MouseEvent.BUTTON1) {
|
||||
logger.info("Ícone da bandeja acionado (clique simples).");
|
||||
Platform.runLater(SystemTrayManager.this::showStage);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Duplo clique ou acionamento nativo padrão também restaura a janela
|
||||
this.trayIcon.addActionListener(e -> Platform.runLater(this::showStage));
|
||||
|
||||
tray.add(this.trayIcon);
|
||||
this.initialized = true;
|
||||
|
||||
// Intercepta fechamento da janela para ocultar em vez de encerrar
|
||||
// Intercepta fechamento da janela (botão X) para confirmar ação com o usuário
|
||||
Platform.runLater(() -> {
|
||||
stage.setOnCloseRequest(event -> {
|
||||
event.consume();
|
||||
stage.hide();
|
||||
showMessage("CyberCore Signer", "O agente continua ativo na bandeja atendendo aplicações web.", TrayIcon.MessageType.INFO);
|
||||
handleWindowCloseRequest();
|
||||
});
|
||||
});
|
||||
|
||||
logger.info("System Tray configurado com sucesso.");
|
||||
return true;
|
||||
|
||||
} catch (AWTException e) {
|
||||
logger.warn("Falha ao registrar ícone no System Tray: {}", e.getMessage());
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Erro ao inicializar System Tray: {}", t.getMessage());
|
||||
return false;
|
||||
logger.warn("Erro ao inicializar System Tray: {}", t.getMessage(), t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exibe o diálogo de confirmação ao clicar no 'X' da janela principal,
|
||||
* permitindo minimizar para a bandeja, encerrar definitivamente ou cancelar.
|
||||
*/
|
||||
public void handleWindowCloseRequest() {
|
||||
try {
|
||||
ExitConfirmationDialog dialog = new ExitConfirmationDialog(stage);
|
||||
ExitConfirmationDialog.ExitChoice choice = dialog.showAndGetChoice();
|
||||
|
||||
switch (choice) {
|
||||
case MINIMIZE_TO_TRAY -> {
|
||||
logger.info("Usuário optou por minimizar o CyberCore Signer para a bandeja.");
|
||||
stage.hide();
|
||||
showMessage("CyberCore Signer", "O agente continua ativo na bandeja atendendo aplicações web.", TrayIcon.MessageType.INFO);
|
||||
}
|
||||
case EXIT_COMPLETELY -> {
|
||||
logger.info("Usuário optou por encerrar definitivamente o CyberCore Signer.");
|
||||
shutdown();
|
||||
}
|
||||
case CANCEL -> {
|
||||
logger.debug("Fechamento da janela cancelado pelo usuário.");
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
logger.warn("Erro ao exibir diálogo de fechamento, minimizando para bandeja por segurança: {}", t.getMessage());
|
||||
stage.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public void showStage() {
|
||||
if (stage == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!stage.isShowing()) {
|
||||
stage.show();
|
||||
}
|
||||
|
||||
if (stage.isIconified()) {
|
||||
stage.setIconified(false);
|
||||
}
|
||||
|
||||
if (stage != null) {
|
||||
logger.info("Restaurando janela principal (visível={}, minimizada={}).",
|
||||
stage.isShowing(), stage.isIconified());
|
||||
stage.show();
|
||||
stage.setIconified(false);
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
|
||||
// No Windows (Win32 / DWM), chamadas de toFront() partindo da bandeja do sistema
|
||||
// podem ser suprimidas por políticas de foco do SO (Foreground Lock Timeout).
|
||||
// A elevação momentânea via setAlwaysOnTop força o gerenciador de janelas a trazer
|
||||
// o assinador diretamente para o primeiro plano.
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.setAlwaysOnTop(false);
|
||||
|
||||
} catch (Throwable t) {
|
||||
logger.debug("Falha secundária ao restaurar stage da bandeja: {}", t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package br.com.cybercore.signer.ui.view;
|
||||
|
||||
import br.com.cybercore.signer.ui.components.SvgIcons;
|
||||
import br.com.cybercore.signer.ui.theme.ThemeManager;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* Diálogo modal de confirmação ao fechar a janela principal do CyberCore Signer.
|
||||
* Permite ao usuário escolher entre manter o agente ativo na bandeja do sistema
|
||||
* ou encerrar completamente a aplicação.
|
||||
*/
|
||||
public final class ExitConfirmationDialog {
|
||||
|
||||
public enum ExitChoice {
|
||||
MINIMIZE_TO_TRAY,
|
||||
EXIT_COMPLETELY,
|
||||
CANCEL
|
||||
}
|
||||
|
||||
private final Stage stage;
|
||||
private ExitChoice chosenAction = ExitChoice.CANCEL;
|
||||
|
||||
public ExitConfirmationDialog(Stage owner) {
|
||||
this.stage = new Stage();
|
||||
this.stage.initModality(Modality.APPLICATION_MODAL);
|
||||
if (owner != null) {
|
||||
this.stage.initOwner(owner);
|
||||
}
|
||||
this.stage.setTitle("CyberCore Signer — Encerrar ou Manter na Bandeja");
|
||||
this.stage.setResizable(false);
|
||||
this.stage.setMinWidth(520);
|
||||
this.stage.setMinHeight(190);
|
||||
|
||||
initLayout();
|
||||
}
|
||||
|
||||
private void initLayout() {
|
||||
VBox root = new VBox(22);
|
||||
root.setPadding(new Insets(24));
|
||||
root.getStyleClass().add("dialog-container");
|
||||
|
||||
// Cabeçalho com ícone e texto explicativo
|
||||
HBox header = new HBox(16);
|
||||
header.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
VBox titleBox = new VBox(4);
|
||||
Label title = new Label("Como você deseja proceder?");
|
||||
title.setStyle("-fx-font-size: 15px; -fx-font-weight: bold;");
|
||||
|
||||
Label subtitle = new Label(
|
||||
"Você pode manter o CyberCore Signer ativo na bandeja do sistema\n" +
|
||||
"para atender assinaturas de aplicações web, ou encerrá-lo por completo."
|
||||
);
|
||||
subtitle.setStyle("-fx-text-fill: -fx-text-secondary; -fx-font-size: 12px;");
|
||||
|
||||
titleBox.getChildren().addAll(title, subtitle);
|
||||
header.getChildren().addAll(SvgIcons.cyberShieldLogo(38), titleBox);
|
||||
|
||||
// Barra de Ações com os botões
|
||||
HBox actionsRow = new HBox(10);
|
||||
actionsRow.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
Button btnCancel = new Button("Cancelar");
|
||||
btnCancel.getStyleClass().addAll("btn-cancel", "btn-dialog");
|
||||
btnCancel.setOnAction(e -> {
|
||||
chosenAction = ExitChoice.CANCEL;
|
||||
stage.close();
|
||||
});
|
||||
|
||||
HBox spacer = new HBox();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
Button btnExit = new Button("Encerrar Definitivamente");
|
||||
btnExit.getStyleClass().addAll("btn-danger", "btn-dialog");
|
||||
btnExit.setOnAction(e -> {
|
||||
chosenAction = ExitChoice.EXIT_COMPLETELY;
|
||||
stage.close();
|
||||
});
|
||||
|
||||
Button btnMinimize = new Button("Minimizar para a Bandeja");
|
||||
btnMinimize.getStyleClass().addAll("btn-primary", "btn-dialog");
|
||||
btnMinimize.setDefaultButton(true);
|
||||
btnMinimize.setOnAction(e -> {
|
||||
chosenAction = ExitChoice.MINIMIZE_TO_TRAY;
|
||||
stage.close();
|
||||
});
|
||||
|
||||
actionsRow.getChildren().addAll(btnCancel, spacer, btnExit, btnMinimize);
|
||||
|
||||
root.getChildren().addAll(header, actionsRow);
|
||||
|
||||
Scene scene = new Scene(root);
|
||||
ThemeManager.applyTheme(scene);
|
||||
stage.setScene(scene);
|
||||
stage.setOnCloseRequest(e -> chosenAction = ExitChoice.CANCEL);
|
||||
}
|
||||
|
||||
public ExitChoice showAndGetChoice() {
|
||||
stage.showAndWait();
|
||||
return chosenAction;
|
||||
}
|
||||
}
|
||||
+23
-14
@@ -16,6 +16,8 @@ import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -27,6 +29,8 @@ import java.util.Objects;
|
||||
@SuppressWarnings("this-escape")
|
||||
public final class MainAppView {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MainAppView.class);
|
||||
|
||||
private final Stage stage;
|
||||
private final VBox root = new VBox(16);
|
||||
private final Button themeToggleBtn = new Button();
|
||||
@@ -136,6 +140,14 @@ public final class MainAppView {
|
||||
getClass().getResource(CyberCoreUiConstants.MAIN_CSS_PATH)
|
||||
).toExternalForm());
|
||||
|
||||
if (Boolean.getBoolean("cybercore.diag.no-effects")) {
|
||||
logger.warn("Diagnóstico: Efeitos/sombras visuais DESATIVADOS via -Dcybercore.diag.no-effects=true");
|
||||
var noEffectsUrl = getClass().getResource("/styles/no-effects.css");
|
||||
if (noEffectsUrl != null) {
|
||||
scene.getStylesheets().add(noEffectsUrl.toExternalForm());
|
||||
}
|
||||
}
|
||||
|
||||
// Configuração e inicialização do Tema
|
||||
ThemeManager.applyTheme(scene);
|
||||
updateThemeToggleButton();
|
||||
@@ -144,11 +156,17 @@ public final class MainAppView {
|
||||
|
||||
stage.setTitle(CyberCoreUiConstants.APP_NAME);
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.setMinWidth(780);
|
||||
stage.setMinHeight(480);
|
||||
stage.setMaxWidth(820);
|
||||
stage.setMaxHeight(520);
|
||||
|
||||
if (Boolean.getBoolean("cybercore.diag.plain-stage")) {
|
||||
logger.warn("Diagnóstico: Plain Stage ATIVADO via -Dcybercore.diag.plain-stage=true (janela redimensionável, sem restrições min/max)");
|
||||
stage.setResizable(true);
|
||||
} else {
|
||||
stage.setResizable(false);
|
||||
stage.setMinWidth(780);
|
||||
stage.setMinHeight(480);
|
||||
stage.setMaxWidth(820);
|
||||
stage.setMaxHeight(520);
|
||||
}
|
||||
}
|
||||
|
||||
private HBox createHeaderBar() {
|
||||
@@ -297,17 +315,8 @@ public final class MainAppView {
|
||||
|
||||
public void show() {
|
||||
stage.centerOnScreen();
|
||||
if (stage.isIconified()) {
|
||||
stage.setIconified(false);
|
||||
}
|
||||
stage.show();
|
||||
stage.toFront();
|
||||
stage.requestFocus();
|
||||
try {
|
||||
stage.setAlwaysOnTop(true);
|
||||
stage.setAlwaysOnTop(false);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* CyberCore Signer - Modo Diagnóstico: Remoção de sombras/efeitos visuais dinâmicos */
|
||||
|
||||
.cyber-action-card,
|
||||
.serpro-action-btn,
|
||||
.cyber-side-card,
|
||||
.serpro-side-btn,
|
||||
.cyber-header-bar,
|
||||
.theme-toggle-btn,
|
||||
.cyber-status-badge {
|
||||
-fx-effect: null !important;
|
||||
}
|
||||
|
||||
.theme-light .cyber-action-card:hover,
|
||||
.theme-light .serpro-action-btn:hover,
|
||||
.theme-dark .cyber-action-card:hover,
|
||||
.theme-dark .serpro-action-btn:hover,
|
||||
.cyber-side-card:hover,
|
||||
.serpro-side-btn:hover,
|
||||
.theme-toggle-btn:hover {
|
||||
-fx-effect: null !important;
|
||||
}
|
||||
Reference in New Issue
Block a user