diff --git a/src/main/java/com/toxicstoxm/LEDSuite/Constants.java b/src/main/java/com/toxicstoxm/LEDSuite/Constants.java index b19992db..cdd920e3 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/Constants.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/Constants.java @@ -57,6 +57,7 @@ public static final class Config { public static final String LOG_FILE_ENABLED = LOGGING_FILE_SECTION + SEPARATOR + "Enabled"; public static final String LOG_FILE_LOG_LEVEL_ALL = LOGGING_FILE_SECTION + SEPARATOR + "Log-Level-All"; public static final String LOG_FILE_MAX_FILES = LOGGING_FILE_SECTION + SEPARATOR + "Max-Files"; + public static final String MAX_STACK_TRACES_CACHED = LOGGING_SECTION + SEPARATOR + "Max-Stack-Traces-Cached"; public static final String CHECK_IPV4 = NETWORK_SECTION + SEPARATOR + "Check-IPv4"; public static final String NETWORK_COMMUNICATION_CLOCK = PERIODIC_SECTION + SEPARATOR + "Network-Communication-Clock"; diff --git a/src/main/java/com/toxicstoxm/LEDSuite/LEDSuite.java b/src/main/java/com/toxicstoxm/LEDSuite/LEDSuite.java index 1e1a4030..1dabb128 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/LEDSuite.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/LEDSuite.java @@ -9,9 +9,7 @@ import com.toxicstoxm.LEDSuite.logging.network.NetworkLogger; import com.toxicstoxm.LEDSuite.settings.LocalSettings; import com.toxicstoxm.LEDSuite.settings.ServerSettings; -import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteGuiRunnable; -import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteRunnable; -import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteScheduler; +import com.toxicstoxm.LEDSuite.task_scheduler.*; import com.toxicstoxm.LEDSuite.time.TickingSystem; import com.toxicstoxm.LEDSuite.time.TimeManager; import com.toxicstoxm.LEDSuite.ui.Window; @@ -26,6 +24,7 @@ import org.gnome.adw.Adw; import org.gnome.adw.Application; import org.gnome.adw.Toast; +import org.gnome.adw.ToastOverlay; import org.gnome.gio.ApplicationFlags; import picocli.CommandLine; @@ -40,7 +39,6 @@ @CommandLine.Command(name = "LEDSuite", description = "Simple front end application that lets you control decorative matrix's.") public class LEDSuite implements EventListener, Runnable { - @Getter private static LEDSuite instance; @Getter @@ -55,7 +53,7 @@ public class LEDSuite implements EventListener, Runnable { public static Window mainWindow; public static LEDSuiteScheduler ledSuiteScheduler; public static TickingSystem tickingSystem; - public static ResourceBundle messages; + private static ResourceBundle messages; @CommandLine.Option( names = {"-h", "--help"}, @@ -143,30 +141,6 @@ public class LEDSuite implements EventListener, Runnable { ) private int setStatusClockActive = -1; - /*@CommandLine.Option( - names = {"-w", "--write-log-to-file"}, - description = "Change if the log should be written to a log file for the current session." - ) - private boolean writeLogToFile; - - @CommandLine.Option( - names = {"-W", "--set-write-log-to-file"}, - description = "Permanently change if the log should be written to a log file." - ) - private boolean setWriteLogToFile; - - @CommandLine.Option( - names = {"--write-log-level-all"}, - description = "Change if all log levels should be written to the log file for the current session." - ) - private boolean writeLogLevelAll = true; - - @CommandLine.Option( - names = {"--set-write-log-level-all"}, - description = "Permanently change if all log levels should be written to the log file." - ) - private boolean setWriteLogLevelAll = false;*/ - @CommandLine.Option( names = {"--max-log-files"}, description = "Change the maximum number of log files allowed for the current session." @@ -191,12 +165,6 @@ public class LEDSuite implements EventListener, Runnable { ) private boolean getPaths; - /*@CommandLine.Option( - names = {"--libadwaita-args"}, - description = "Pass through arguments to libadwaita." - ) - private String[] libadwaitaArguments;*/ - @CommandLine.Option( names = {"--stack-trace-depth"}, description = "Change the maximum stack trace depth for the current session." @@ -208,17 +176,6 @@ public class LEDSuite implements EventListener, Runnable { ) private int setStackTraceDepth = -2; - /*@CommandLine.Option( - names = {"--color-code-log"}, - description = "Change if the log should be color coded for the current session." - ) - private boolean colorCodeLog; - @CommandLine.Option( - names = {"--set-color-code-log"}, - description = "Permanently change if the log should be color coded." - ) - private boolean setColorCodeLog;*/ - // main method public static void main(String[] args) { @@ -242,7 +199,7 @@ public static void main(String[] args) { // constructor method public LEDSuite() { instance = this; - // create new libadwaita application object + // create a new libadwaita application object app = new Application(Constants.Application.DOMAIN, ApplicationFlags.DEFAULT_FLAGS); app.setVersion(Constants.Application.VERSION); app.setApplicationId(Constants.Application.DOMAIN); @@ -312,9 +269,8 @@ public void logicInit() { } else { // if the config can't be created for some reason, a waring message is displayed in the console logger.warn("Config couldn't be created!"); - logger.warn("Please restart the application to prevent wierd behaviour!"); } - } + } else logger.debug("Found config file: " + config_file.getAbsolutePath()); // checking if the server side config file already exists if (!server_config_file.exists()) { // if the config file doesn't exist, a new one gets loaded from the internal resources folder with its default values using the configs saveDefaultConfig() function @@ -324,10 +280,9 @@ public void logicInit() { } else { // if the config can't be created for some reason, a waring message is displayed in the console logger.warn("Server config couldn't be created!"); - logger.warn("Please restart the application to prevent wierd behaviour!"); } - } - // check if log file already exists + } else logger.debug("Found server config file: " + server_config_file.getAbsolutePath()); + // check if a log file already exists if (!log_file.exists()) { Files.createDirectories(Path.of(log_file.getParent())); // if it does not exist, a new one will be created @@ -336,9 +291,9 @@ public void logicInit() { } else { // if there are any exceptions during the creation of a new log file, a warning message is displayed in the console logger.warn("Log file couldn't be created!"); - logger.warn("Please restart the application to prevent wierd behaviour!"); } } else { + logger.debug("Found log file: " + log_file.getAbsolutePath()); temp = getFile(log_file); logger.debug("Moving existing 'latest.log' to '" + temp.getName() + "'..."); logger.debug(log_file.renameTo(new File(temp.getAbsolutePath())) ? "Moving success!" : "Moving failed!"); @@ -355,10 +310,7 @@ public void logicInit() { } catch (IOException | NullPointerException e) { // if any exceptions occur during file creation / modification, an error is displayed in the console // additionally the program is halted to prevent any further issues or unexpected behavior - logger.error("Settings failed to load!"); - logger.warn("Application was halted!"); - logger.warn("If this keeps happening please open an issue on GitHub!"); - logger.warn("Please restart the application!"); + logger.fatal("Settings failed to load! " + logger.getErrorMessage(e)); getInstance().app.emitShutdown(); return; } @@ -369,12 +321,16 @@ public void logicInit() { eventManager.registerEvents(settings); eventManager.registerEvents(server_settings); + logger.debug("Loading settings from config files..."); // user settings are loaded from config files loadConfigsFromFile(); + logger.debug("Successfully loaded settings from config files!"); SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); eventManager.fireEvent(new Events.Startup("Starting application! Current date and time: " + df.format(new Date()))); + logger.debug("Processing CLI arguments..."); + argumentsSettings.copyImpl(settings, false); if (setLogLevel != null) { argumentsSettings.setLogLevel(setLogLevel.getValue()); @@ -418,20 +374,6 @@ public void logicInit() { argumentsSettings.setStatusRequestClockActive(statusClockActive); } - /*if (setWriteLogToFile != argumentsSettings.isLogFileEnabled()) { - argumentsSettings.setLogFileEnabled(setWriteLogToFile); - settings.setLogFileEnabled(setWriteLogToFile); - } else if (writeLogToFile != argumentsSettings.isLogFileEnabled()) { - argumentsSettings.setLogFileEnabled(writeLogToFile); - } - - if (setWriteLogLevelAll != argumentsSettings.isLogFileLogLevelAll()) { - argumentsSettings.setLogFileLogLevelAll(setWriteLogLevelAll); - settings.setLogFileLogLevelAll(setWriteLogLevelAll); - } else if (writeLogLevelAll != argumentsSettings.isLogFileLogLevelAll()) { - argumentsSettings.setLogFileLogLevelAll(writeLogLevelAll); - }*/ - if (setMaxLogFiles > -1 && setMaxLogFiles != argumentsSettings.getLogFileMaxFiles()) { if (setMaxLogFiles < 1) { logger.warn("Invalid value '" + setMaxLogFiles + "' for '--set-max-log-files', value must be a valid integer greater than 1!"); @@ -452,13 +394,6 @@ public void logicInit() { settings.setStackTraceDepth(setStackTraceDepth); } else if (stackTraceDepth > -2) argumentsSettings.setStackTraceDepth(stackTraceDepth); - /*if (setColorCodeLog != argumentsSettings.isLogColorCodingEnabled()) { - argumentsSettings.setLogColorCodingEnabled(setColorCodeLog); - settings.setLogColorCodingEnabled(setColorCodeLog); - } else if (colorCodeLog != argumentsSettings.isLogColorCodingEnabled()) { - argumentsSettings.setLogColorCodingEnabled(colorCodeLog); - }*/ - if (getPaths) { logger.log("Paths:"); logger.log(" -> Directories"); @@ -473,10 +408,40 @@ public void logicInit() { getInstance().app.emitShutdown(); } + new LEDSuiteRunnable() { + @Override + public void run() { + String input = ""; + Scanner inputListener = new Scanner(new InputStreamReader(System.in)); + while (start != 0) { + while (input.isBlank()) { + input = inputListener.nextLine(); + } + if (input.contains("s")) { + int length = input.length(); + if (length == 1) { + logger.printRecentStackTraces(1); + } else { + String count = input.replace("s", ""); + try { + logger.printRecentStackTraces(Integer.parseInt(count)); + } catch (NumberFormatException e) { + logger.warn("Invalid trace count: '" + count + "'! Must be a valid integer value!"); + } + } + } + input = ""; + } + } + }.runTaskAsynchronously(); + + logger.debug("Successfully processed CLI arguments!"); + logger.debug("Processing log files..."); // process log files // checks if log files need to be moved or deleted processLogFiles(log_file, temp); + logger.debug("Successfully processed log files!"); tickingSystem = new TickingSystem(); @@ -497,8 +462,6 @@ public void logicInit() { logger.info("Country: " + messages.getLocale().getDisplayCountry()); logger.info("Successfully loaded messages!"); - //System.out.println(messages.getString("greeting")); - // check for window os // app does not normally work on windows, since windows doesn't natively support libadwaita if (Constants.System.NAME.toLowerCase().contains("windows")) { @@ -506,6 +469,7 @@ public void logicInit() { logger.warn("You will be ignored if you open an issue for a windows only bug! You can fork the repo though and fix the bug yourself!"); } + logger.info("Initializing connection to server"); new LEDSuiteRunnable() { @Override public void run() { @@ -518,6 +482,7 @@ public void run() { } }.runTaskAsynchronously(); + logger.info("Requesting an initial status update from the server"); new LEDSuiteGuiRunnable() { @Override public void processGui() { @@ -596,12 +561,12 @@ private void processLogFiles(File log_file, File temp) { } // Handle errors and early returns } catch (IndexOutOfBoundsException e) { - logger.error("Error while handling log files! Error message: " + e.getMessage()); + logger.fatal("Error while handling log files! Error message: " + logger.getErrorMessage(e)); getInstance().app.emitShutdown(); } catch (NullPointerException | IllegalArgumentException e) { - logger.error("Error while handling log files! Error message: " + e.getMessage()); + logger.error("Error while handling log files! Error message: " + logger.getErrorMessage(e)); } catch (InterruptedException e) { - logger.debug(e.getMessage()); + logger.debug(logger.getErrorMessage(e)); } } } @@ -655,6 +620,7 @@ public void activate() { // registering event listener for the main window eventManager.registerEvents(mainWindow); // showing the main window on screen + logger.info("Showing application window"); mainWindow.present(); // trigger started to send a started message to console // calculating time elapsed during startup and displaying it in the console @@ -668,13 +634,13 @@ public static void started(String message) { try { Networking.Communication.sendYAMLDefaultHost(YAMLMessage.defaultStatusRequest().build()); } catch (ConfigurationException | YAMLSerializer.YAMLException e) { - LEDSuite.logger.error("Failed to send / get available animations list from the server!"); - LEDSuite.logger.displayError(e); + LEDSuite.logger.warn("Failed to send / get available animations list from the server!" + LEDSuite.logger.getErrorMessage(e)); } } // exiting program with specified status code public void exit(int status) { + start = 0; // firing new shutdown event if (eventManager != null) eventManager.fireEvent(new Events.Shutdown("Shutdown")); if (logger != null) LEDSuite.logger.info("Saving..."); @@ -686,7 +652,11 @@ public void exit(int status) { if (logger != null) LEDSuite.logger.info("Shutting down..."); if (logger != null) LEDSuite.logger.info("Goodbye!"); // displaying status code in the console - if (logger != null) LEDSuite.logger.info("Status code: " + status); + if (logger != null) { + if (status > 0) { + LEDSuite.logger.fatal("Error code: " + status); + } else LEDSuite.logger.info("Status code: " + status); + } // exiting program with the specified status code System.exit(status); } @@ -702,6 +672,7 @@ public static void sysBeep() { public void loadConfigsFromFile() { // parsing config and loading the values from storage (Default: ./LED-Cube-Control-Panel/config.yaml) // using Apache-Commons-Configuration2 and SnakeYaml + LEDSuite.logger.debug("Parsing config.yaml..."); try { // defining new YamlConfig object from apache-commons-configuration2 lib YAMLConfiguration yamlConfig = new YAMLConfiguration(); @@ -715,14 +686,13 @@ public void loadConfigsFromFile() { } catch (ConfigurationException e) { // if any errors occur during config parsing, an error is displayed in the console; // the program is halted to prevent any further unwanted behavior - LEDSuite.logger.error("Failed to parse config.yaml!"); - LEDSuite.logger.warn("Application was halted!"); - LEDSuite.logger.warn("If this keeps happening please open an issue on GitHub!"); - LEDSuite.logger.warn("Please restart the application!"); + LEDSuite.logger.fatal("Failed to parse config.yaml! " + logger.getErrorMessage(e)); getInstance().exit(1); return; } + LEDSuite.logger.debug("Successfully pares config.yaml!"); + LEDSuite.logger.debug("Parsing server-config.yaml..."); try { // defining new YamlConfig object from apache-commons-configuration2 lib YAMLConfiguration yamlConfig = new YAMLConfiguration(); @@ -736,18 +706,47 @@ public void loadConfigsFromFile() { } catch (ConfigurationException e) { // if any errors occur during config parsing, an error is displayed in the console // the program is halted to prevent any further unwanted behavior - LEDSuite.logger.error("Failed to parse server_config.yaml!"); - LEDSuite.logger.warn("Application was halted!"); - LEDSuite.logger.warn("If this keeps happening please open an issue on GitHub!"); - LEDSuite.logger.warn("Please restart the application!"); - getInstance().exit(1); + LEDSuite.logger.fatal("Failed to parse server_config.yaml! " + logger.getErrorMessage(e)); + getInstance().exit(2); } + LEDSuite.logger.debug("Successfully pares server-config.yaml!"); } public static LEDSuiteScheduler getScheduler() { return ledSuiteScheduler; } + public static String i18n(String key) { + if (!messages.containsKey(key)) { + logger.warn("Internationalized string for key '" + key + "' wasn't found!"); + return key; + } + return messages.getString(key); + } + + public static String i18n(String key, HashMap placeholders) { + String i18n = i18n(key); + if (i18n.contains("%")) { + for (Map.Entry entry : placeholders.entrySet()) { + i18n = i18n.replace(entry.getKey(), entry.getValue()); + } + } + return i18n; + } + + public static String i18n(String key, String placeholder, String replacement, String... placeholders) { + if (placeholders.length % 2 != 0) logger.warn("Placeholder without replacement found in: " + Arrays.toString(placeholders)); + HashMap placeholderTable = new HashMap<>(); + placeholderTable.put(placeholder, replacement); + for (int i = 0; i < placeholders.length; i++) { + placeholderTable.put(placeholders[i], placeholders[i++]); + } + return i18n(key, placeholderTable); + } + public static String[] i18n(String key, boolean ignoredList) { + return i18n(key).split("§"); + } + // listener function for reload event @EventHandler public void onReload(Events.Reload e) { @@ -794,7 +793,7 @@ public void onDataReceived(Events.DataIn e) { case reply -> { switch (yaml.getReplyType()) { case status -> eventManager.fireEvent(new Events.Status(StatusUpdate.fromYAMLMessage(yaml))); - case menu -> LEDSuite.logger.fatal("Redundancy catcher caught a menu reply while not expecting it!"); + case menu -> LEDSuite.logger.warn("Unexpected menu reply packet was detected!"); } } @@ -805,7 +804,7 @@ public void onDataReceived(Events.DataIn e) { ) ); } - logger.debug(id + "---------------------------------------------------------------"); + logger.verbose(id + "---------------------------------------------------------------"); } @EventHandler public void onDataSend(Events.DataOut e) { @@ -820,7 +819,7 @@ public void onDataSend(Events.DataOut e) { logger.error(id + "Failed to get internal network event id from YAML!"); logger.error(id + "Error message: " + ex.getMessage()); } - logger.debug(id + "Data stream direction: out"); + logger.verbose(id + "Data stream direction: out"); try { logger.verbose(id + "Data: " + YAMLSerializer.deserializeYAML(e.yaml())); } catch (YAMLSerializer.YAMLException ex) { @@ -834,8 +833,8 @@ public void onStatus(Events.Status e) { StatusUpdate status = e.statusUpdate(); TimeManager.ping("status"); String id = "[" + status.getNetworkEventID() + "] "; - logger.debug(id + "Received status update from server!"); - logger.debug(id + "Status: " + status); + logger.verbose(id + "Received status update from server!"); + logger.verbose(id + "Status: " + status); } @EventHandler public void onSend(Events.DataOut e) { @@ -859,7 +858,7 @@ public void onError(Events.Error e) { logger.debug(id + "Received error from server!"); logger.debug("id" + "Error: " + error); sysBeep(); - mainWindow.toastOverlay.addToast( + mainWindow.widgetCache.get(ToastOverlay.class, "toastOverlay").addToast( Toast.builder() .setTitle(error.humanReadable()) .setTimeout(Adw.DURATION_INFINITE) diff --git a/src/main/java/com/toxicstoxm/LEDSuite/cache/Cache.java b/src/main/java/com/toxicstoxm/LEDSuite/cache/Cache.java new file mode 100644 index 00000000..e87a4dc5 --- /dev/null +++ b/src/main/java/com/toxicstoxm/LEDSuite/cache/Cache.java @@ -0,0 +1,91 @@ +package com.toxicstoxm.LEDSuite.cache; + +import com.toxicstoxm.LEDSuite.LEDSuite; +import lombok.NonNull; + +import java.util.HashMap; + +/** + * This is a wrapper of the standard HashMap implementation. + *

+ * This class can be used instead of creating a lot of global variables. It allows for easy storage and retrieval of objects, + * with additional safety features such as automatic null-checks and class cast exception handling. + *

+ * + * @implNote This class extends {@link HashMap} and overrides the {@code put} method to include a warning if a key + * already exists in the cache. It also includes a custom {@code get} method that performs type checking and + * handles potential {@link ClassCastException}s. + * + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public class Cache extends HashMap { + + /** + * Associates the specified value with the specified key in this cache. If the cache previously contained a + * mapping for the key, the old value is replaced by the specified value. + *

+ * If the key already exists in the cache, a warning is logged. + *

+ * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + @Override + public V put(K key, V value) { + return put(key, value, false); + } + + /** + * Associates the specified value with the specified key in this cache, with an option to suppress warnings. + *

+ * If the cache previously contained a mapping for the key, the old value is replaced by the specified value. + * If the key already exists in the cache, a warning is logged unless the {@code suppressOverrideWarning} parameter is set to {@code true}. + *

+ * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @param suppressOverrideWarning if {@code true}, suppresses the warning when a key already exists in the cache + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key}. + * (A {@code null} return can also indicate that the map + * previously associated {@code null} with {@code key}.) + */ + public V put(K key, V value, boolean suppressOverrideWarning) { + if (containsKey(key)) { + if (!suppressOverrideWarning) { + LEDSuite.logger.warn("Cache already contains object for key: '" + key + "'!"); + } + } + return super.put(key, value); + } + + /** + * Retrieves the value associated with the specified key and casts it to the desired type. + *

+ * If the key does not exist or if the value cannot be cast to the desired type, {@code null} is returned, + * and a warning is logged in case of a {@link ClassCastException}. + *

+ * + * @param the type to which the value should be cast + * @param desiredType the {@link Class} object representing the desired type + * @param key the key whose associated value is to be returned + * @return the value mapped to the specified key cast to the desired type, or {@code null} if the key does not exist + * or if the cast fails + */ + public T get(@NonNull Class desiredType, @NonNull String key) { + if (!containsKey(key)) { + return null; + } + try { + return desiredType.cast(super.get(key)); + } catch (ClassCastException e) { + LEDSuite.logger.warn("Failed to get cache for Key: '" + key + "' and type: '" + desiredType.getName() + "'!"); + return null; + } + } +} diff --git a/src/main/java/com/toxicstoxm/LEDSuite/communication/network/Networking.java b/src/main/java/com/toxicstoxm/LEDSuite/communication/network/Networking.java index 54fb629a..0246aecf 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/communication/network/Networking.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/communication/network/Networking.java @@ -253,6 +253,9 @@ public static void sendFile(String serverIP4, int serverPort, String fileToSendP // loading the specified file to memory File fileToSend = new File(fileToSendPath); try { + // disabling periodic requests to prevent unwanted data injection + TimeManager.lock("keepalive"); + TimeManager.lock("status"); // send a file upload request to the server sendYAMLDefaultHost( YAMLMessage.builder() @@ -262,12 +265,18 @@ public static void sendFile(String serverIP4, int serverPort, String fileToSendP .setObjectNewValue(String.valueOf(fileToSend.length())) .build(), success -> { - // if the request was successful, send the file to the server using the sendFile() method - if (success) { - if (!sendFile(serverIP4, serverPort, progressTracker, fileToSend)) { - LEDSuite.logger.error("Failed to send file '" + fileToSendPath + "' to server '" + serverIP4 + ":" + serverPort + "'!"); + TimeManager.lock("mgr"); + new LEDSuiteRunnable() { + @Override + public void run() { + // if the request was successful, send the file to the server using the sendFile() method + if (success) { + if (!sendFile(serverIP4, serverPort, progressTracker, fileToSend)) { + LEDSuite.logger.warn("Failed to send file '" + fileToSendPath + "' to server '" + serverIP4 + ":" + serverPort + "'!"); + } + } } - } + }.runTaskLaterAsynchronously(100); } ); } catch (YAMLSerializer.TODOException | ConfigurationException | YAMLSerializer.InvalidReplyTypeException | @@ -276,7 +285,7 @@ public static void sendFile(String serverIP4, int serverPort, String fileToSendP } } - // send file to server using sockets + // send a file to server using sockets /** * @@ -314,11 +323,6 @@ public static boolean sendFile(String serverIPv4, int serverPort, ProgressTracke try { - // disabling periodic requests to prevent unwanted data injection - TimeManager.lock("keepalive"); - TimeManager.lock("status"); - TimeManager.lock("mgr"); - // getting current socket from network handler Socket socket = NetworkHandler.getServer(); LEDSuite.logger.verbose(id + "Successfully established connection!"); @@ -430,6 +434,7 @@ public static boolean sendFile(String serverIPv4, int serverPort, ProgressTracke // calculating transferred size in MB double temp1 = (double) transferredSize / (1024 * 1024); mbTransferredSize = (double) Math.round(temp1 * 1000) / 1000; + out.flush(); } // if the progress tracker object is not null and the upload has finished, @@ -451,12 +456,11 @@ public static boolean sendFile(String serverIPv4, int serverPort, ProgressTracke LEDSuite.logger.verbose(id + "Sending complete!"); } catch (IOException e) { if (track) progressTracker.setError(true); // inform the progress tracker of the occurred error - LEDSuite.logger.error(id + "Error occurred! Transmission terminated!"); - LEDSuite.logger.displayError(e); + LEDSuite.logger.warn(id + "Error occurred! Transmission terminated! " + LEDSuite.logger.getErrorMessage(e)); return false; } catch (NetworkException e) { if (track) progressTracker.setError(true); // inform the progress tracker of the occurred error - LEDSuite.logger.fatal(id + "Network error: " + e.getMessage()); + LEDSuite.logger.warn(id + "Network error! " + LEDSuite.logger.getErrorMessage(e)); } finally { // re enabling periodic requests TimeManager.release("keepalive"); @@ -606,8 +610,7 @@ public static void init(SuccessCallback callback) throws NetworkException { LEDSuite.logger.verbose("Successfully connected to server!"); } catch (IOException e) { // if connection fails, inform the caller function using the callback - LEDSuite.logger.fatal("Failed to initialize connection to server! Error: " + e.getMessage()); - LEDSuite.logger.displayError(e); + LEDSuite.logger.warn("Failed to initialize connection to server! " + LEDSuite.logger.getErrorMessage(e)); serverIsRebooting = false; if (callback != null) callback.getResult(false); return; @@ -621,7 +624,6 @@ public static void init(SuccessCallback callback) throws NetworkException { LEDSuite.logger.verbose("Network Handler: started network handle!"); } - // if manager is already running, cancel it if (mgr != null) mgr.cancel(); long keepalive = 500; @@ -638,7 +640,7 @@ public static void init(SuccessCallback callback) throws NetworkException { @Override public void run() { if (!TimeManager.call("mgr")) return; - // check if keepalive needs to be sent + // check if keepalive needs to be sent if (TimeManager.call("keepalive")) { LEDSuite.logger.verbose("Sending keepalive"); try { @@ -670,7 +672,7 @@ public void run() { if (!networkQueue.isEmpty() && isConnected()) { Map.Entry entry = networkQueue.firstEntry(); LEDSuite.logger.verbose("Handling request: " + entry.getKey()); - entry.getValue().runTaskAsynchronously(); + entry.getValue().runTask(); networkQueue.remove(entry.getKey()); } } @@ -750,8 +752,7 @@ public void run() { } } } catch (Exception e) { - LEDSuite.logger.fatal("Network Handler: master listener: Error: " + e.getMessage()); - LEDSuite.logger.displayError(e); + LEDSuite.logger.error("Network Handler: Error while running master listener! " + LEDSuite.logger.getErrorMessage(e)); } } @@ -818,8 +819,7 @@ public static void reboot() throws NetworkException { if (!success) throw new NetworkException("connection failed"); }); } catch (NetworkException e) { - LEDSuite.logger.fatal("Network Handler: reboot failed!"); - LEDSuite.logger.displayError(e); + LEDSuite.logger.warn("Network Handler: reboot failed! " + LEDSuite.logger.getErrorMessage(e)); throw new NetworkException("connection failed!"); } } @@ -885,7 +885,7 @@ public void onSettingChanged(Events.SettingChanged e) { ); } catch (YAMLSerializer.TODOException | ConfigurationException | YAMLSerializer.InvalidReplyTypeException | YAMLSerializer.InvalidPacketTypeException ex) { - LEDSuite.logger.error("Failed to send (1) settings change request!"); + LEDSuite.logger.warn("Failed to send (1) settings change request!"); } LEDSuite.logger.verbose("Successfully send (1) settings change request to server!"); } @@ -907,7 +907,7 @@ public void onSettingsChanged(Events.SettingsChanged e) { ); } catch (YAMLSerializer.TODOException | ConfigurationException | YAMLSerializer.InvalidReplyTypeException | YAMLSerializer.InvalidPacketTypeException ex) { - LEDSuite.logger.error("Failed to send (" + changedSettings.size() +") settings change request!"); + LEDSuite.logger.warn("Failed to send (" + changedSettings.size() +") settings change request!"); } LEDSuite.logger.verbose("Successfully send (" + changedSettings.size() +") settings change request to server!"); } @@ -1059,7 +1059,7 @@ public static boolean sendKeepalive(YAMLConfiguration yaml, boolean displayLog) */ public static void sendYAMLDefaultHost(YAMLConfiguration yaml) { if (!sendYAML(LEDSuite.server_settings.getIPv4(), LEDSuite.server_settings.getPort(), yaml, null)) { - LEDSuite.logger.error("Failed to send YAML message to server! Callback = false | ReplyHandler = false"); + LEDSuite.logger.warn("Failed to send YAML message to server!"); } } @@ -1075,7 +1075,7 @@ public static void sendYAMLDefaultHost(YAMLConfiguration yaml) { */ public static void sendYAMLDefaultHost(YAMLConfiguration yaml, FinishCallback callback) { if (!sendYAML(LEDSuite.server_settings.getIPv4(), LEDSuite.server_settings.getPort(), yaml, callback)) { - LEDSuite.logger.error("Failed to send YAML message with callback to server! Callback = true | ReplyHandler = false"); + LEDSuite.logger.warn("Failed to send YAML message to server!"); } } @@ -1092,7 +1092,7 @@ public static void sendYAMLDefaultHost(YAMLConfiguration yaml, FinishCallback ca */ public static void sendYAMLDefaultHost(YAMLConfiguration yaml, FinishCallback callback, LEDSuiteProcessor replyHandler) { if (!sendYAML(LEDSuite.server_settings.getIPv4(), LEDSuite.server_settings.getPort(), yaml, callback, replyHandler)) { - LEDSuite.logger.error("Failed to send YAML message with to server! Callback = true | ReplyHandler = true"); + LEDSuite.logger.warn("Failed to send YAML message to server!"); } } @@ -1144,8 +1144,7 @@ public static boolean sendYAML(String host, int port, YAMLConfiguration yaml, Fi if (callback != null) callback.onFinish(true); LEDSuite.logger.verbose("Successfully reconnected to previous server!"); } catch (NetworkException e) { - LEDSuite.logger.fatal(e.getMessage()); - LEDSuite.logger.error("Reconnection attempt to previous server failed!"); + LEDSuite.logger.warn("Reconnection attempt to previous server failed! " + LEDSuite.logger.getErrorMessage(e)); if (callback != null) callback.onFinish(false); return false; } @@ -1178,7 +1177,7 @@ public void run() { */ private static void sendYAMLMessage(String serverIP4, int serverPort, YAMLConfiguration yaml, FinishCallback callback, LEDSuiteProcessor replyHandle) { if (!sendYAMLMessage(serverIP4, serverPort, yaml, callback, replyHandle, true)) { - LEDSuite.logger.error("Failed to send YAML message to server!"); + LEDSuite.logger.warn("Failed to send YAML message to server!"); } } @@ -1301,6 +1300,7 @@ private static boolean sendYAMLMessage(String serverIP4, int serverPort, YAMLCon // sending yaml data to server if (displayLog) LEDSuite.logger.verbose(id + "Transmitting data..."); out.write(bytes); + out.flush(); if (displayLog) LEDSuite.logger.verbose(id + "Successfully transmitted data to server!"); if (displayLog) { @@ -1316,13 +1316,12 @@ private static boolean sendYAMLMessage(String serverIP4, int serverPort, YAMLCon } catch (NetworkException ex) { // if an error occurs, print an error message and give up if (displayLog) { - LEDSuite.logger.error(id + "Error occurred! Transmission terminated!"); - LEDSuite.logger.displayError(e); + LEDSuite.logger.warn(id + "Error occurred! Transmission terminated! " + LEDSuite.logger.getErrorMessage(e)); } err = true; } } catch (NetworkException e) { - if (displayLog) LEDSuite.logger.fatal(id + "Network error: " + e.getMessage()); + if (displayLog) LEDSuite.logger.warn(id + "Network error! " + LEDSuite.logger.getErrorMessage(e)); } finally { if (displayLog) LEDSuite.logger.verbose(id + "---------------------------------------------------------------"); @@ -1332,7 +1331,6 @@ private static boolean sendYAMLMessage(String serverIP4, int serverPort, YAMLCon err = !err; if (callb) callback.onFinish(err); return err; - } } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/event_handling/EventManager.java b/src/main/java/com/toxicstoxm/LEDSuite/event_handling/EventManager.java index 198ea653..f9d648a9 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/event_handling/EventManager.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/event_handling/EventManager.java @@ -42,7 +42,7 @@ public void registerEvents(EventListener eventListener) { // Add the method to the listeners map for the specific event type listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(new RegisteredListener(eventListener, method)); // Log the registration of the listener method - LEDSuite.logger.debug("Registering listener method: " + + LEDSuite.logger.verbose("Registering listener method: " + eventListener.toString().split("@")[0] + "." + method.getName() + @@ -58,7 +58,7 @@ public void registerEvents(EventListener eventListener) { * @since 1.0.0 */ public void unregisterEvents(EventListener eventListener) { - // Iterate over all entries in the listeners map + // Iterate over all entries in the listener map for (Map.Entry, List> entry : listeners.entrySet()) { // Get the list of registered listeners for the current event type List registeredListeners = entry.getValue(); @@ -82,7 +82,7 @@ public void fireEvent(Object event) { String id = tryToGetNetworkID(event); // Log the firing of the event - LEDSuite.logger.debug(id + "Firing event: " + event); + LEDSuite.logger.verbose(id + "Firing event: " + event); // Get the list of registered listeners for the event's class List registeredListeners = listeners.get(event.getClass()); // Temporary list to keep track of listeners to remove @@ -95,7 +95,7 @@ public void fireEvent(Object event) { // Invoke the listener method with the event Class eventType = registeredListener.method.getParameterTypes()[0]; // Log the calling of the listener method - LEDSuite.logger.debug("Calling listener method: " + + LEDSuite.logger.verbose("Calling listener method: " + registeredListener.eventListener.toString().split("@")[0] + "." + registeredListener.method.getName() + @@ -106,7 +106,6 @@ public void fireEvent(Object event) { // Log any exception that occurs during method invocation LEDSuite.logger.warn(id + "Error while trying to fire event: " + event); LEDSuite.logger.warn(id + "This warning can be ignored!"); - LEDSuite.logger.debug(id + "Stack trace: "); LEDSuite.logger.displayError(e); // Add the listener to the removal list toRemove.add(registeredListener); diff --git a/src/main/java/com/toxicstoxm/LEDSuite/logging/Logger.java b/src/main/java/com/toxicstoxm/LEDSuite/logging/Logger.java index d2f10f3a..08026697 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/logging/Logger.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/logging/Logger.java @@ -38,6 +38,7 @@ public class Logger { * @since 1.0.0 */ private final TreeMap cache; + private final Stack> traceCache; /** * Maximum length of the log message trace for alignment purposes. @@ -57,6 +58,7 @@ public Logger() { AnsiConsole.systemInstall(); // Initializes the cache to store log messages before writing them to the log file cache = new TreeMap<>(); + traceCache = new Stack<>(); // Sets initial maximum length for log message trace maxLength = 0; TimeManager.initTimeTracker("logger", 0); @@ -72,7 +74,7 @@ public void info(String message) { // Check if INFO level logging is enabled if (log_level.INFO.isEnabled()) { // Format and log the message with INFO level - cInfo("[INFO]: [" + Constants.Application.NAME + "] " + message); + _Info("[INFO] [" + Constants.Application.NAME + "] " + message); } } @@ -82,7 +84,7 @@ public void info(String message) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cInfo(String message) { + private void _Info(String message) { // Write the message to the log file writeLog(message); // Log to console with default color @@ -99,7 +101,7 @@ public void warn(String message) { // Check if WARN level logging is enabled if (log_level.WARN.isEnabled()) { // Format and log the message with WARN level - cWarn("[WARN]: [" + Constants.Application.NAME + "] " + message); + _warn("[WARN] [" + Constants.Application.NAME + "] " + message); } } @@ -109,7 +111,7 @@ public void warn(String message) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cWarn(String message) { + private void _warn(String message) { // Write the message to the log file writeLog(message); // Log to console with warning color @@ -137,7 +139,7 @@ public void error(String message, boolean visualFeedback) { // Check if ERROR level logging is enabled if (log_level.ERROR.isEnabled()) { // Format and log the message with ERROR level - cError("[ERROR]: [" + Constants.Application.NAME + "] " + message); + _error("[ERROR] [" + Constants.Application.NAME + "] " + message); } // Display visual feedback if requested if (visualFeedback) { @@ -174,13 +176,33 @@ public void displayError(Exception exception) { } } + public void printRecentStackTraces(int count) { + if (count < 0) { + warn("Invalid stack trace count '" + count + "'!"); + return; + } + Stack> recentTraces = new Stack<>(); + + for (int i = 0; i < Math.min(count, traceCache.size()); i++) { + recentTraces.push(traceCache.pop()); + } + + for (int j = 0; j < Math.min(count, recentTraces.size()); j++) { + stackTrace("#" + (count - j) + " most recent stackTrace ------------------------------------------------------------------------"); + for (StackTraceElement traceElement : recentTraces.pop()) { + stackTrace(traceElement.toString()); + } + stackTrace("#" + (count - j) + " most recent stackTrace ------------------------------------------------------------------------"); + } + } + /** * Internal method for logging an error message with ANSI color formatting. * * @param message The formatted message to log. * @since 1.0.0 */ - private void cError(String message) { + private void _error(String message) { // Write the message to the log file writeLog(message); // Log to console with error color @@ -208,7 +230,7 @@ public void fatal(String message, boolean visualFeedback) { // Check if FATAL level logging is enabled if (log_level.FATAL.isEnabled()) { // Format and log the message with FATAL level - cFatal("[FATAL]: [" + Constants.Application.NAME + "] " + message); + _fatal("[FATAL] [" + Constants.Application.NAME + "] " + message); } // Display visual feedback if requested if (visualFeedback) { @@ -222,7 +244,7 @@ public void fatal(String message, boolean visualFeedback) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cFatal(String message) { + private void _fatal(String message) { // Write the message to the log file writeLog(message); // Log to console with fatal color @@ -239,7 +261,7 @@ public void debug(String message) { // Check if DEBUG level logging is enabled if (log_level.DEBUG.isEnabled()) { // Format and log the message with DEBUG level - cDebug("[DEBUG]: [" + Constants.Application.NAME + "] " + message); + _debug("[DEBUG] [" + Constants.Application.NAME + "] " + message); } } @@ -249,7 +271,7 @@ public void debug(String message) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cDebug(String message) { + private void _debug(String message) { // Write the message to the log file writeLog(message); // Log to console with debug color @@ -266,7 +288,7 @@ public void verbose(String message) { // Check if VERBOSE level logging is enabled if (log_level.VERBOSE.isEnabled()) { // Format and log the message with VERBOSE level - cVerbose("[VERBOSE]: [" + Constants.Application.NAME + "] " + message); + _verbose("[VERBOSE] [" + Constants.Application.NAME + "] " + message); } } @@ -276,7 +298,7 @@ public void verbose(String message) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cVerbose(String message) { + private void _verbose(String message) { // Write the message to the log file writeLog(message); // Log to console with verbose color @@ -293,7 +315,7 @@ public void stackTrace(String message) { // Check if VERBOSE level logging is enabled if (log_level.STACKTRACE.isEnabled()) { // Format and log the message with VERBOSE level - cStackTrace("[STACKTRACE]: [" + Constants.Application.NAME + "] " + message); + _stackTrace("[STACKTRACE] [" + Constants.Application.NAME + "] " + message); } } @@ -303,7 +325,7 @@ public void stackTrace(String message) { * @param message The formatted message to log. * @since 1.0.0 */ - private void cStackTrace(String message) { + private void _stackTrace(String message) { // Write the message to the log file writeLog(message); // Log to console with verbose color @@ -321,7 +343,11 @@ private String attachMetadata(String message) { // Create a date formatter for the timestamp SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); // Attach the current timestamp and stack trace to the message - return "[" + df.format(new Date()) + "] " + getTrace() + message; + return "[" + df.format(new Date()) + "] " + ( + message.contains("STACKTRACE") ? + "" : + getTrace(false) + ) + message; } /** @@ -336,10 +362,14 @@ private String attachMetadata(Ansi message) { SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss"); // Attach the current timestamp and stack trace to the ANSI message return "[" + df.format(new Date()) + "] " + ( - LEDSuite.argumentsSettings.isLogColorCodingEnabled() ? - ansi().fgRgb(LEDSuite.argumentsSettings.getLogColors().get("TRACE")).a(getTrace()).reset() : - ansi().a(getTrace()).reset() - ) + message; + message.toString().contains("STACKTRACE") ? + "" : + ( + LEDSuite.argumentsSettings.isLogColorCodingEnabled() ? + ansi().fgRgb(LEDSuite.argumentsSettings.getLogColors().get("TRACE")).a(getTrace(true)).reset() : + ansi().a(getTrace(true)).reset() + ) + ) + message; } /** @@ -348,8 +378,10 @@ private String attachMetadata(Ansi message) { * @return The formatted stack trace information. * @since 1.0.0 */ - private String getTrace() { + private String getTrace(boolean cache) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (traceCache.size() >= LEDSuite.settings.getMaxStackTracesCached()) traceCache.removeLast(); + if (cache) traceCache.push(Arrays.stream(stackTrace).toList().subList(6, stackTrace.length)); int depth = 6; // Retrieve the stack trace of the current thread String s = stackTrace[depth].toString(); @@ -465,7 +497,7 @@ private void visualFeedback(String message) { private void visualFeedback(String message, int timeout) { // Check if the main window and toast overlay are available if (LEDSuite.mainWindow == null) return; - ToastOverlay toastOverlay = LEDSuite.mainWindow.toastOverlay; + ToastOverlay toastOverlay = LEDSuite.mainWindow.widgetCache.get(ToastOverlay.class, "toastOverlay"); if (toastOverlay != null) { // Create and display new toast with the message and timeout toastOverlay @@ -600,4 +632,8 @@ public static log_level interpret(int logLevel) { } } } + + public String getErrorMessage(Exception e) { + return e.getMessage() == null ? "Cause: unknown" : e.getMessage(); + } } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/logging/network/NetworkLogger.java b/src/main/java/com/toxicstoxm/LEDSuite/logging/network/NetworkLogger.java index b178af3b..488e1d62 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/logging/network/NetworkLogger.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/logging/network/NetworkLogger.java @@ -68,13 +68,13 @@ public UUID addEvent(UUID id, String description) { public void printEvents() { alignNetworkEvents(); // Align event descriptions boolean empty = networkEvents.isEmpty() && order.isEmpty(); - LEDSuite.logger.debug("-------------------- Network Events ---------------------------------------------------------------------------------------------------------"); - LEDSuite.logger.debug(empty ? "Couldn't find any network events!" : "Network event count: " + networkEvents.size()); + LEDSuite.logger.verbose("-------------------- Network Events ---------------------------------------------------------------------------------------------------------"); + LEDSuite.logger.verbose(empty ? "Couldn't find any network events!" : "Network event count: " + networkEvents.size()); for (Map.Entry entry : order.entrySet()) { // Print each event with its description and UUID - LEDSuite.logger.debug(networkEvents.get(entry.getValue()) + " " + entry.getValue()); + LEDSuite.logger.verbose(networkEvents.get(entry.getValue()) + " " + entry.getValue()); } - LEDSuite.logger.debug("----------------------------------------------------------------------------------------------------------------------------------------------"); + LEDSuite.logger.verbose("----------------------------------------------------------------------------------------------------------------------------------------------"); } /** diff --git a/src/main/java/com/toxicstoxm/LEDSuite/settings/CommentPreservation.java b/src/main/java/com/toxicstoxm/LEDSuite/settings/CommentPreservation.java index 4a63c4a1..d6648bd3 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/settings/CommentPreservation.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/settings/CommentPreservation.java @@ -49,10 +49,7 @@ public static TreeMap extractComments(String filePath) { lineNumber++; } } catch (Exception e) { - // Log the stack trace if an exception occurs - for (StackTraceElement s : e.getStackTrace()) { - LEDSuite.logger.error(s.toString()); - } + LEDSuite.logger.error("Failed to extract comments from YAML file! " + LEDSuite.logger.getErrorMessage(e)); } // Return the TreeMap containing the comments and their line numbers diff --git a/src/main/java/com/toxicstoxm/LEDSuite/settings/LocalSettings.java b/src/main/java/com/toxicstoxm/LEDSuite/settings/LocalSettings.java index 9b1f0268..68da8987 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/settings/LocalSettings.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/settings/LocalSettings.java @@ -54,6 +54,7 @@ public class LocalSettings extends Settings { ); private boolean LogColorCodingEnabled = true; private int StackTraceDepth = 11; + private int MaxStackTracesCached = 100; private LocalSettings backup; @@ -104,6 +105,7 @@ public void saveDefaultConfig() throws IOException, NullPointerException { */ @Override public void load(YAMLConfiguration config) { + super.load(config); LEDSuite.logger.debug("Loading config values to memory..."); try { // Setting values to parsed config values @@ -114,53 +116,53 @@ public void load(YAMLConfiguration config) { this.WindowDefWidth = config.getInt(Constants.Config.WINDOW_DEFAULT_WIDTH); this.WindowDefHeight = config.getInt(Constants.Config.WINDOW_DEFAULT_HEIGHT); } catch (ConversionException e) { - LEDSuite.logger.error("Error while parsing Window-Default-Height and Window-Default-Width! Not a valid Number!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing Window-Default-Height and Window-Default-Width! Not a valid Number!" + LEDSuite.logger.getErrorMessage(e)); } try { this.LogLevel = config.getInt(Constants.Config.LOG_LEVEL); } catch (ConversionException e) { - LEDSuite.logger.error("Error while parsing Log-Level! Not a valid Number!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing Log-Level! Not a valid Number!" + LEDSuite.logger.getErrorMessage(e)); } try { double temp = config.getDouble(Constants.Config.NETWORK_COMMUNICATION_CLOCK); this.NetworkingCommunicationClock = (int) (Math.round((temp * 1000))); } catch (ClassCastException | ConversionException e) { - LEDSuite.logger.error("Error while parsing NetworkingCommunicationClock! Not a valid time argument (seconds)!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing NetworkingCommunicationClock! Not a valid time argument (seconds)!" + LEDSuite.logger.getErrorMessage(e)); } try { LogFileMaxFiles = config.getInt(Constants.Config.LOG_FILE_MAX_FILES); } catch (ConversionException e) { - LEDSuite.logger.error("Error while parsing LogFileMaxFiles! Not a valid time argument (seconds)!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing LogFileMaxFiles! Not a valid time argument (seconds)!" + LEDSuite.logger.getErrorMessage(e)); } try { double temp = config.getDouble(Constants.Config.STATUS_REQUEST_CLOCK_ACTIVE); this.StatusRequestClockActive = (int) (Math.round((temp * 1000))); } catch (ClassCastException | ConversionException e) { - LEDSuite.logger.error("Error while parsing StatusRequestClockActive! Not a valid time argument (seconds)!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing StatusRequestClockActive! Not a valid time argument (seconds)!" + LEDSuite.logger.getErrorMessage(e)); } try { double temp = config.getDouble(Constants.Config.STATUS_REQUEST_CLOCK_PASSIVE); this.StatusRequestClockPassive = (int) (Math.round((temp * 1000))); } catch (ClassCastException | ConversionException e) { - LEDSuite.logger.error("Error while parsing StatusRequestClockPassive! Not a valid time argument (seconds)!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing StatusRequestClockPassive! Not a valid time argument (seconds)!" + LEDSuite.logger.getErrorMessage(e)); } try { this.StackTraceDepth = config.getInt(Constants.Config.STACK_TRACE_DEPTH); } catch (ClassCastException | ConversionException e) { - LEDSuite.logger.error("Error while parsing StackTraceDepth! This needs to be a numeric value!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); + LEDSuite.logger.warn("Error while parsing StackTraceDepth! This needs to be a numeric value!" + LEDSuite.logger.getErrorMessage(e)); + } + + + try { + this.MaxStackTracesCached = config.getInt(Constants.Config.MAX_STACK_TRACES_CACHED); + } catch (ClassCastException | ConversionException e) { + LEDSuite.logger.warn("Error while parsing MaxStackTracesCached! This needs to be a numeric value!" + LEDSuite.logger.getErrorMessage(e)); } // Setting the remaining values @@ -189,10 +191,9 @@ public void load(YAMLConfiguration config) { LEDSuite.logger.debug("Loaded config values to memory!"); } catch (NoSuchElementException e) { - LEDSuite.logger.error("Error while parsing config! Settings / values missing! You're probably using an old config file!"); - LEDSuite.logger.warn("Program halted to prevent any further errors!"); - LEDSuite.logger.warn("Please delete the old config file from your .config folder and restart the application!"); - LEDSuite.getInstance().exit(1); + LEDSuite.logger.fatal("Error while parsing config! Settings / values missing! You're probably using an old config file!"); + LEDSuite.logger.warn("You can reset the config file by starting the application with the -R CLI argument!"); + LEDSuite.getInstance().exit(3); } } @@ -234,18 +235,19 @@ public void copyImpl(Settings settings1, boolean log) { this.WindowDefHeight = settings.getWindowDefHeight(); this.LogLevel = settings.getLogLevel(); this.selectionDir = settings.getSelectionDir(); - this.DisplayStatusBar = settings.DisplayStatusBar; - this.NetworkingCommunicationClock = settings.NetworkingCommunicationClock; - this.CheckIPv4 = settings.CheckIPv4; - this.AutoPlayAfterUpload = settings.AutoPlayAfterUpload; - this.StatusRequestClockPassive = settings.StatusRequestClockPassive; - this.StatusRequestClockActive = settings.StatusRequestClockActive; - this.LogFileMaxFiles = settings.LogFileMaxFiles; - this.LogFileEnabled = settings.LogFileEnabled; - this.LogFileLogLevelAll = settings.LogFileLogLevelAll; - this.LogColors = settings.LogColors; - this.LogColorCodingEnabled = settings.LogColorCodingEnabled; + this.DisplayStatusBar = settings.isDisplayStatusBar(); + this.NetworkingCommunicationClock = settings.getNetworkingCommunicationClock(); + this.CheckIPv4 = settings.isCheckIPv4(); + this.AutoPlayAfterUpload = settings.isAutoPlayAfterUpload(); + this.StatusRequestClockPassive = settings.getStatusRequestClockPassive(); + this.StatusRequestClockActive = settings.getStatusRequestClockActive(); + this.LogFileMaxFiles = settings.getLogFileMaxFiles(); + this.LogFileEnabled = settings.isLogFileEnabled(); + this.LogFileLogLevelAll = settings.isLogFileLogLevelAll(); + this.LogColors = settings.getLogColors(); + this.LogColorCodingEnabled = settings.isLogColorCodingEnabled(); this.StackTraceDepth = settings.getStackTraceDepth(); + this.MaxStackTracesCached = settings.getMaxStackTracesCached(); if (log) LEDSuite.logger.debug("Successfully loaded settings from " + settings.getName() + "!"); if (log) LEDSuite.logger.debug(getName() + " now inherits all values from " + settings.getName()); @@ -278,8 +280,7 @@ public void save() { fH.load(Constants.File_System.config); comments = new TreeMap<>(CommentPreservation.extractComments(Constants.File_System.config)); } catch (ConfigurationException e) { - LEDSuite.logger.error("Error occurred while writing config values to config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); + LEDSuite.logger.warn("Error occurred while writing config values to config.yaml!"); return; } @@ -308,24 +309,16 @@ public void save() { conf.setProperty(Constants.Config.DISPLAY_STATUS_BAR, DisplayStatusBar); conf.setProperty(Constants.Config.AUTO_PLAY_AFTER_UPLOAD, AutoPlayAfterUpload); conf.setProperty(Constants.Config.COLOR_CODING_ENABLED, LogColorCodingEnabled); - - - + conf.setProperty(Constants.Config.MAX_STACK_TRACES_CACHED, MaxStackTracesCached); // Saving settings to disk fH.save(Constants.File_System.config); CommentPreservation.insertComments(Constants.File_System.config, comments); } catch (ConfigurationException e) { - LEDSuite.logger.error("Something went wrong while saving the config values for config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); - LEDSuite.logger.warn("Previously made changes to the config may be lost!"); - LEDSuite.logger.warn("If this message appears on every attempt to save config changes please open an issue on GitHub!"); + LEDSuite.logger.warn("Something went wrong while saving the config values for config.yaml!" + LEDSuite.logger.getErrorMessage(e)); return; } catch (IOException e) { - LEDSuite.logger.error("Something went wrong while saving the config comments for config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); - LEDSuite.logger.warn("Previously made changes to the config may be lost!"); - LEDSuite.logger.warn("If this message appears on every attempt to save config changes please open an issue on GitHub!"); + LEDSuite.logger.warn("Something went wrong while saving the config comments for config.yaml!" + LEDSuite.logger.getErrorMessage(e)); return; } @@ -449,6 +442,7 @@ public boolean equals(Object object) { StatusRequestClockActive == other.StatusRequestClockActive && LogColorCodingEnabled == other.LogColorCodingEnabled && StackTraceDepth == other.StackTraceDepth && + MaxStackTracesCached == other.MaxStackTracesCached && Objects.equals(selectionDir, other.selectionDir) && Objects.equals(NetworkingCommunicationClock, other.NetworkingCommunicationClock) && Objects.equals(LogColors, other.LogColors); @@ -469,7 +463,8 @@ public int hashCode() { NetworkingCommunicationClock, StatusRequestClockPassive, StatusRequestClockActive, LogFileEnabled, LogFileLogLevelAll, LogFileMaxFiles, LogColors, - LogColorCodingEnabled, StackTraceDepth + LogColorCodingEnabled, StackTraceDepth, + MaxStackTracesCached ); } } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/settings/ServerSettings.java b/src/main/java/com/toxicstoxm/LEDSuite/settings/ServerSettings.java index 15da738e..42d318ab 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/settings/ServerSettings.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/settings/ServerSettings.java @@ -112,22 +112,21 @@ public void copy(Settings settings1) { */ @Override public void load(YAMLConfiguration config) { + super.load(config); try { // Load IPv4 address this.IPv4 = config.getString(Constants.Server_Config.IPV4); int tempPort = config.getInt(Constants.Server_Config.PORT); // Validate the port number if (!Networking.Validation.isValidPORT(String.valueOf(tempPort))) { - LEDSuite.logger.error("Error while parsing Server-Port! Invalid Port!"); + LEDSuite.logger.warn("Error while parsing Server-Port! Invalid Port!"); LEDSuite.logger.warn("Port is outside the valid range of 0-65535!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); } else { this.Port = tempPort; } } catch (ConversionException | NullPointerException e) { - LEDSuite.logger.error("Error while parsing Server-IP and Server-Port! Not a valid number!"); + LEDSuite.logger.warn("Error while parsing Server-IP and Server-Port! Not a valid number!"); LEDSuite.logger.warn("Invalid port and / or IPv4 address! Please restart the application!"); - LEDSuite.logger.warn("There was an error while reading the config file, some settings may be broken!"); } // Load and convert LED brightness this.LED_Brightness = (float) config.getInt(Constants.Server_Config.BRIGHTNESS) / 100; @@ -158,8 +157,7 @@ public void save() { fH.load(Constants.File_System.server_config); comments = new TreeMap<>(CommentPreservation.extractComments(Constants.File_System.server_config)); } catch (ConfigurationException e) { - LEDSuite.logger.error("Error occurred while writing server-config values to server-config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); + LEDSuite.logger.warn("Error occurred while writing server-config values to server-config.yaml!" + LEDSuite.logger.getErrorMessage(e)); return; } @@ -171,16 +169,12 @@ public void save() { fH.save(Constants.File_System.server_config); CommentPreservation.insertComments(Constants.File_System.server_config, comments); } catch (ConfigurationException e) { - LEDSuite.logger.error("Something went wrong while saving the config values for server-config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); + LEDSuite.logger.warn("Something went wrong while saving the config values for server-config.yaml!" + LEDSuite.logger.getErrorMessage(e)); LEDSuite.logger.warn("Previously made changes to the server-config may be lost!"); - LEDSuite.logger.warn("If this message appears on every attempt to save config changes please open an issue on GitHub!"); return; } catch (IOException e) { - LEDSuite.logger.error("Something went wrong while saving the config comments for server-config.yaml!"); - LEDSuite.logger.warn("Please restart the application to prevent further errors!"); + LEDSuite.logger.warn("Something went wrong while saving the config comments for server-config.yaml!" + LEDSuite.logger.getErrorMessage(e)); LEDSuite.logger.warn("Previously made changes to the server-config may be lost!"); - LEDSuite.logger.warn("If this message appears on every attempt to save config changes please open an issue on GitHub!"); return; } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/settings/Settings.java b/src/main/java/com/toxicstoxm/LEDSuite/settings/Settings.java index 024376df..cd8fff7b 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/settings/Settings.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/settings/Settings.java @@ -50,7 +50,9 @@ public void saveDefaultConfig() throws IOException, NullPointerException {} * @param config The `YAMLConfiguration` object containing the configuration settings. * @since 1.0.0 */ - public void load(YAMLConfiguration config) {} + public void load(YAMLConfiguration config) { + LEDSuite.logger.verbose("[" + name + "] Loading settings for '" + type.name() + "' config."); + } /** * Saves the current settings. To be implemented by subclasses. diff --git a/src/main/java/com/toxicstoxm/LEDSuite/time/TickingSystem.java b/src/main/java/com/toxicstoxm/LEDSuite/time/TickingSystem.java index 04672383..06cd9e8c 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/time/TickingSystem.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/time/TickingSystem.java @@ -99,9 +99,8 @@ private void tickLoop() { try { Thread.sleep(TICK_DELAY_MS - elapsedTime); } catch (InterruptedException e) { - LEDSuite.logger.error("Ticking System was interrupted!"); - LEDSuite.logger.warn("Stopping program to prevent further errors!"); - LEDSuite.getInstance().exit(1); + LEDSuite.logger.fatal("Ticking System was interrupted! " + LEDSuite.logger.getErrorMessage(e)); + LEDSuite.getInstance().exit(4); } } } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/ui/AddFileDialog.java b/src/main/java/com/toxicstoxm/LEDSuite/ui/AddFileDialog.java index d2766227..b63cc704 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/ui/AddFileDialog.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/ui/AddFileDialog.java @@ -8,6 +8,7 @@ import com.toxicstoxm.LEDSuite.yaml_factory.YAMLMessage; import com.toxicstoxm.LEDSuite.yaml_factory.YAMLSerializer; import io.github.jwharm.javagi.base.GErrorException; +import lombok.NonNull; import org.apache.commons.configuration2.ex.ConfigurationException; import org.gnome.adw.*; import org.gnome.gio.File; @@ -69,12 +70,12 @@ public AddFileDialog() { // Create a preferences group for file selection var file = PreferencesGroup.builder() - .setTitle("File") + .setTitle(LEDSuite.i18n("add_file_dialog_title")) .build(); // Create an EntryRow for displaying the selected file path, initially empty var filenameRow = ActionRow.builder() - .setTitle("Selected File") + .setTitle(LEDSuite.i18n("filename_row_title")) .setSubtitle(filePath == null ? "N/A" : fileName) .setUseMarkup(false) .build(); @@ -92,7 +93,7 @@ public AddFileDialog() { // Create the file dialog for selecting files var fileSel = FileDialog.builder() - .setTitle("Pick file to upload") + .setTitle(LEDSuite.i18n("file_dialog_title")) .setModal(true) .setInitialFolder( File.newForPath(LEDSuite.settings.getSelectionDir()) @@ -141,8 +142,8 @@ public AddFileDialog() { var autoPlay = SwitchRow.builder() .setActive(toggled[0]) - .setTitle("Start animation after upload") - .setTooltipText("If true, the animation is automatically started after the upload finishes") + .setTitle(LEDSuite.i18n("add_file_dialog_auto_play_button_title")) + .setTooltipText(LEDSuite.i18n("add_file_dialog_auto_play_button_tooltip")) .build(); autoPlay.getActivatableWidget().onStateFlagsChanged(_ -> { @@ -157,17 +158,17 @@ public AddFileDialog() { file.add(autoPlay); statsRow = ExpanderRow.builder() - .setTitle("Upload Statistics") + .setTitle(LEDSuite.i18n("add_file_dialog_stats_row_title")) .setExpanded(false) .setActivatable(false) .build(); speed = ActionRow.builder() - .setTitle("Speed") + .setTitle(LEDSuite.i18n("add_file_dialog_speed_row_title")) .setSubtitle("N/A") .build(); eta = ActionRow.builder() - .setTitle("ETA") + .setTitle(LEDSuite.i18n("add_file_dialog_eta_row_title")) .setSubtitle("N/A") .build(); @@ -223,7 +224,7 @@ public AddFileDialog() { spinner = Spinner.builder().setSpinning(true).build(); var buttonBox = Box.builder().setOrientation(Orientation.HORIZONTAL).setSpacing(5).build(); - buttonBox.append(Label.builder().setLabel("Upload").build()); + buttonBox.append(Label.builder().setLabel(LEDSuite.i18n("add_file_dialog_upload_button_label")).build()); buttonBox.append(spinner); var clamp0 = Clamp.builder() .setMaximumSize(70) @@ -261,9 +262,9 @@ public AddFileDialog() { */ private void displayPlayRequestError(Exception e, String fileName) { LEDSuite.logger.error("Failed to send play request for uploaded file! File name : '" + fileName + "' File path: '" + filePath + "'"); - LEDSuite.mainWindow.toastOverlay.addToast( + LEDSuite.mainWindow.widgetCache.get(ToastOverlay.class, "toastOverlay").addToast( Toast.builder() - .setTitle("Error: Failed to autostart animation!") + .setTitle(LEDSuite.i18n("add_file_dialog_autostart_request_error")) .build() ); LEDSuite.logger.displayError(e); @@ -306,10 +307,10 @@ private void fileDialogProcessResult(File result, ActionRow filenameRow) { return; } } - LEDSuite.logger.error("Invalid file type selected! " + path); + LEDSuite.logger.warn("Invalid file type selected! " + path); LEDSuite.logger.warn("Only Image, Video and Shared Library files are allowed!"); - LEDSuite.mainWindow.toastOverlay.addToast( - Toast.builder().setTitle("Error: Invalid file type selected!").build() + LEDSuite.mainWindow.widgetCache.get(ToastOverlay.class, "toastOverlay").addToast( + Toast.builder().setTitle(LEDSuite.i18n("add_file_dialog_invalid_file_type_error")).build() ); } @@ -359,6 +360,9 @@ private interface FinishCallback { * @since 1.0.0 */ private void upload(FinishCallback callback) { + @NonNull var progressBar = LEDSuite.mainWindow.widgetCache.get(ProgressBar.class, "progressBar"); + @NonNull var mainView = LEDSuite.mainWindow.widgetCache.get(ToolbarView.class, "mainView"); + LEDSuite.logger.debug("Triggered animation upload!"); if (filePath == null || filePath.isEmpty() || !new java.io.File(filePath).exists()) { LEDSuite.logger.debug("File path is null or empty."); @@ -382,8 +386,8 @@ private void upload(FinishCallback callback) { public void processGui() { if (!cancelled[0] && progressTracker.isUpdated()) { cancelled[0] = true; - LEDSuite.mainWindow.progressBar.setFraction(0.0); - LEDSuite.mainWindow.rootView.setRevealBottomBars(true); + progressBar.setFraction(0.0); + mainView.setRevealBottomBars(true); statsRow.setExpanded(true); new LEDSuiteGuiRunnable() { @Override @@ -396,14 +400,14 @@ public void processGui() { if (!error) { if (!speed.getSubtitle().equals(speedNew)) speed.setSubtitle(speedNew); if (!eta.getSubtitle().equals(etaNew)) eta.setSubtitle(etaNew); - if (progressbar - LEDSuite.mainWindow.progressBar.getFraction() > 0.001) LEDSuite.mainWindow.progressBar.setFraction(progressTracker.getProgressPercentage()); + if (progressbar - progressBar.getFraction() > 0.001) progressBar.setFraction(progressTracker.getProgressPercentage()); } resetUI(1000, error, false, callback); cancel(); } if (!speed.getSubtitle().equals(speedNew)) speed.setSubtitle(speedNew); if (!eta.getSubtitle().equals(etaNew)) eta.setSubtitle(etaNew); - if (progressbar - LEDSuite.mainWindow.progressBar.getFraction() > 0.001) LEDSuite.mainWindow.progressBar.setFraction(progressTracker.getProgressPercentage()); + if (progressbar - progressBar.getFraction() > 0.001) progressBar.setFraction(progressTracker.getProgressPercentage()); } }.runTaskTimerAsynchronously(0, 100); cancel(); @@ -441,26 +445,28 @@ public void processGui() { * @since 1.0.0 */ private void resetUI(int delayInMillis, boolean error, boolean invalidPath, FinishCallback callback) { + @NonNull var mainView = LEDSuite.mainWindow.widgetCache.get(ToolbarView.class, "mainView"); + @NonNull var progressBar = LEDSuite.mainWindow.widgetCache.get(ProgressBar.class, "progressBar"); new LEDSuiteGuiRunnable() { @Override public void processGui() { - LEDSuite.mainWindow.rootView.setRevealBottomBars(false); + mainView.setRevealBottomBars(false); uploadButton.setCssClasses(new String[]{"suggested-action", "pill"}); uploading = false; spinner.setVisible(false); statsRow.setExpanded(false); speed.setSubtitle("N/A"); eta.setSubtitle("N/A"); - LEDSuite.mainWindow.progressBar.setFraction(0.0); + progressBar.setFraction(0.0); if (error) { - LEDSuite.mainWindow.toastOverlay.addToast( + LEDSuite.mainWindow.widgetCache.get(ToastOverlay.class, "toastOverlay").addToast( Toast.builder() - .setTitle("Error:" + - (invalidPath ? - " Invalid File for path: " + - (filePath == null || filePath.isEmpty() ? - "N/A" : filePath) : - " Failed to send file!") + .setTitle(LEDSuite.i18n("error") + ":" + ( + invalidPath ? + LEDSuite.i18n("add_file_dialog_invalid_file_error", "%PATH%", (filePath == null || filePath.isEmpty() ? "N/A" : filePath)) : + LEDSuite.i18n("add_file_dialog_failed_to_send_error") + ) + ) .build() ); diff --git a/src/main/java/com/toxicstoxm/LEDSuite/ui/SettingsDialog.java b/src/main/java/com/toxicstoxm/LEDSuite/ui/SettingsDialog.java index bdde1afe..a8e4c824 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/ui/SettingsDialog.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/ui/SettingsDialog.java @@ -5,6 +5,7 @@ import com.toxicstoxm.LEDSuite.communication.network.Networking; import com.toxicstoxm.LEDSuite.event_handling.Events; import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteGuiRunnable; +import lombok.NonNull; import org.gnome.adw.*; import org.gnome.gtk.Spinner; import org.gnome.gtk.Widget; @@ -34,7 +35,7 @@ public class SettingsDialog extends PreferencesDialog { */ public SettingsDialog() { // Configuring settings window appearance - setTitle("Settings"); + setTitle(LEDSuite.i18n("settings_dialog_title")); setSearchEnabled(true); // Setting the default values @@ -53,20 +54,19 @@ public SettingsDialog() { * @since 1.0.0 */ private PreferencesPage get_user_pref_page() { + @NonNull var statusBar = LEDSuite.mainWindow.widgetCache.get(Banner.class, "statusBar"); // Define new preference page var user_pref_page = new PreferencesPage(); - // Uncomment to set title with application version - // user_pref_page.setTitle(Constants.Application.VERSION); // Define a new preferences group for general settings var generalSettings = new PreferencesGroup(); - generalSettings.setTitle("General Settings"); + generalSettings.setTitle(LEDSuite.i18n("settings_dialog_generaL_settings_title")); // Create a switch row to toggle the status bar var statusBarToggle = SwitchRow.builder() - .setActive(LEDSuite.mainWindow.isBannerVisible()) - .setTitle("Status Bar") - .setTooltipText("Toggles the small status bar on the main window.") + .setActive(statusBar.getRevealed()) + .setTitle(LEDSuite.i18n("settings_dialog_status_bar_toggle_title")) + .setTooltipText(LEDSuite.i18n("settings_dialog_status_bar_toggle_tooltip")) .build(); // Handle state change of status bar toggle @@ -75,7 +75,7 @@ private PreferencesPage get_user_pref_page() { if (!temp[1] == active) { LEDSuite.logger.debug("StatusToggle: " + active); // Set the banner visibility based on the toggle - LEDSuite.mainWindow.setBannerVisible(active); + LEDSuite.mainWindow.setStatusBarVisible(active); temp[1] = active; } }); @@ -86,7 +86,7 @@ private PreferencesPage get_user_pref_page() { // Define preferences group for server settings serverSettings = new PreferencesGroup(); - serverSettings.setTitle("Cube Settings"); + serverSettings.setTitle(LEDSuite.i18n("settings_dialog_server_settings_backend_title")); // Create a spin row for LED brightness adjustment var brightnessRow = SpinRow.withRange(0, 100, 1); @@ -95,7 +95,7 @@ private PreferencesPage get_user_pref_page() { brightnessRow.setWrap(false); brightnessRow.setClimbRate(2); brightnessRow.setNumeric(true); - brightnessRow.setTitle("LED - Brightness"); + brightnessRow.setTitle(LEDSuite.i18n("settings_dialog_brightness_slider_title")); prev1 = brightnessRow.getValue(); // Handle brightness value change @@ -117,7 +117,7 @@ private PreferencesPage get_user_pref_page() { var spinner = new Spinner(); // Create an entry row for IPv4 address - var ipv4Row = EntryRow.builder().setTitle("IPv4").build(); + var ipv4Row = EntryRow.builder().setTitle(LEDSuite.i18n("settings_dialog_ipv4_title")).build(); ipv4Row.setShowApplyButton(true); ipv4Row.setText(LEDSuite.server_settings.getIPv4()); ipv4Row.setEnableUndo(true); @@ -144,7 +144,7 @@ public void processGui() { LEDSuite.sysBeep(); addToast( Toast.builder() - .setTitle("Server unreachable!") + .setTitle(LEDSuite.i18n("settings_dialog_ipv4_server_unreachable_error")) .setTimeout(10) .build() ); @@ -159,12 +159,12 @@ public void processGui() { Networking.Communication.NetworkHandler.hostChanged(); addToast( Toast.builder() - .setTitle("Connection failed! Reconnected to previous host: '" + prevIPv4.get() + "'") + .setTitle(LEDSuite.i18n("settings_dialog_ipv4_fallback_connect", "%PREVIOUS_HOST%", prevIPv4.get())) .setTimeout(10) .build() ); } catch (Networking.NetworkException ex) { - LEDSuite.logger.error("Fallback connection failed! Stopping network communication!"); + LEDSuite.logger.warn("Fallback connection failed! Stopping network communication!" + LEDSuite.logger.getErrorMessage(e)); Networking.Communication.NetworkHandler.cancel(); } } @@ -184,7 +184,7 @@ public void processGui() { var spinner1 = new Spinner(); // Create an entry row for port number - var port = EntryRow.builder().setTitle("Port").build(); + var port = EntryRow.builder().setTitle(LEDSuite.i18n("settings_dialog_port_title")).build(); port.setShowApplyButton(true); port.setText(String.valueOf(LEDSuite.server_settings.getPort())); port.setEnableUndo(true); @@ -214,7 +214,7 @@ public void processGui() { LEDSuite.sysBeep(); addToast( Toast.builder() - .setTitle("Invalid Port!") + .setTitle(LEDSuite.i18n("settings_dialog_port_invalid_port_error")) .setTimeout(10) .build() ); @@ -222,7 +222,7 @@ public void processGui() { LEDSuite.server_settings.setPort(Integer.parseInt(prevPort.get())); Networking.Communication.NetworkHandler.hostChanged(); } catch (NumberFormatException | Networking.NetworkException ex) { - LEDSuite.logger.error("Fallback connection failed! Stopping network communication!"); + LEDSuite.logger.warn("Fallback connection failed! Stopping network communication!" + LEDSuite.logger.getErrorMessage(e)); Networking.Communication.NetworkHandler.cancel(); } } finally { @@ -235,7 +235,7 @@ public void processGui() { }); serverSettings.add(port); - // Add server settings to the preferences page + // Add server settings to the preference page user_pref_page.add(serverSettings); return user_pref_page; } diff --git a/src/main/java/com/toxicstoxm/LEDSuite/ui/StatusDialog.java b/src/main/java/com/toxicstoxm/LEDSuite/ui/StatusDialog.java index 354434b7..f60d035d 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/ui/StatusDialog.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/ui/StatusDialog.java @@ -8,6 +8,7 @@ import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteGuiRunnable; import com.toxicstoxm.LEDSuite.task_scheduler.LEDSuiteTask; import com.toxicstoxm.LEDSuite.yaml_factory.wrappers.message_wrappers.StatusUpdate; +import lombok.NonNull; import org.gnome.adw.*; import org.gnome.gtk.Box; import org.gnome.gtk.Label; @@ -30,7 +31,7 @@ public class StatusDialog extends Dialog implements EventListener { private ActionRow lidState; // Displays lid state information private StatusPage statusPage; // Page to hold status-related UI elements private boolean statusInit = false; // Flag to track if status has been initialized - private boolean reboot = false; // Flag to indicate if a reboot is needed + private boolean reboot = false; // Flag to indicate if a reboot is necessary private long lastUpdate; // Timestamp of the last update /** @@ -41,27 +42,27 @@ public class StatusDialog extends Dialog implements EventListener { private void initialize() { // Initialize action rows for different status elements currentDraw = ActionRow.builder() - .setTitle("Current Draw") + .setTitle(LEDSuite.i18n("status_dialog_current_draw_title")) .setSubtitleSelectable(true) .setCssClasses(new String[]{"property"}) .build(); voltage = ActionRow.builder() - .setTitle("Voltage") + .setTitle(LEDSuite.i18n("status_dialog_voltage_title")) .setSubtitleSelectable(true) .setCssClasses(new String[]{"property"}) .build(); currentFile = ActionRow.builder() - .setTitle("Current File") + .setTitle(LEDSuite.i18n("status_dialog_current_file_title")) .setSubtitleSelectable(true) .setCssClasses(new String[]{"property"}) .build(); fileState = ActionRow.builder() - .setTitle("Current State") + .setTitle(LEDSuite.i18n("status_dialog_file_state_title")) .setSubtitleSelectable(true) .setCssClasses(new String[]{"property"}) .build(); lidState = ActionRow.builder() - .setTitle("Lid State") + .setTitle(LEDSuite.i18n("status_dialog_lid_state_title")) .setSubtitleSelectable(true) .setCssClasses(new String[]{"property"}) .build(); @@ -69,7 +70,7 @@ private void initialize() { // Initialize the status page statusPage = StatusPage.builder() .setIconName(Constants.Application.ICON) - .setTitle("Server Status") + .setTitle(LEDSuite.i18n("status_dialog_title")) .build(); } @@ -98,7 +99,7 @@ public StatusDialog() { // Create a header bar with a title widget var headerBar1 = HeaderBar.builder() .setShowTitle(false) - .setTitleWidget(Label.builder().setLabel("Server Status").build()) + .setTitleWidget(Label.builder().setLabel(LEDSuite.i18n("status_dialog_title")).build()) .build(); // Add the header bar to the toolbar view @@ -155,12 +156,12 @@ private void initStatus() { // Create and configure groups for power usage and file stats var powerUsage = PreferencesGroup.builder() .setCssClasses(new String[]{"background"}) - .setTitle("Power") + .setTitle(LEDSuite.i18n("status_dialog_power_section_title")) .build(); var fileStats = PreferencesGroup.builder() .setCssClasses(new String[]{"background"}) - .setTitle("Animation") + .setTitle(LEDSuite.i18n("status_dialog_animation_section_title")) .build(); // Add action rows to the groups @@ -168,7 +169,7 @@ private void initStatus() { fileStats.add(fileState); var general = PreferencesGroup.builder() - .setTitle("General") + .setTitle(LEDSuite.i18n("status_dialog_general_section")) .build(); general.add(lidState); @@ -209,12 +210,12 @@ private void configure(StatusUpdate statusUpdate) { // Add not connected status information statusList.append( - ActionRow.builder().setTitle("Not connected to Cube!") + ActionRow.builder().setTitle(LEDSuite.i18n("status_dialog_not_connected")) .setSubtitle(LEDSuite.server_settings.getIPv4() + ":" + LEDSuite.server_settings.getPort() + " is currently not responding!").build() ); statusList.append( - ActionRow.builder().setTitle("Possible causes") - .setSubtitle("Waiting for response, Connection failed due to invalid host / port, Connection refused by host").build() + ActionRow.builder().setTitle(LEDSuite.i18n("status_dialog_not_connected_causes_title")) + .setSubtitle(LEDSuite.i18n("status_dialog_not_connected_causes_subtitle")).build() ); statusPage.setChild(statusList); @@ -238,14 +239,14 @@ private void configure(StatusUpdate statusUpdate) { voltage.setSubtitle(statusUpdate.getVoltage() + "V"); if (statusUpdate.isFileLoaded()) { currentFile.setSubtitle(statusUpdate.getFileSelected()); - fileState.setSubtitle(statusUpdate.getFileState().name()); + fileState.setSubtitle(LEDSuite.i18n(statusUpdate.getFileState().getI18nKey())); } else { currentFile.setSubtitle("N/A"); - fileState.setSubtitle(statusUpdate.getFileState().name()); - fileState.setSubtitle("No animation selected!"); + fileState.setSubtitle(LEDSuite.i18n(statusUpdate.getFileState().getI18nKey())); + fileState.setSubtitle(LEDSuite.i18n("status_dialog_no_animation_loaded")); } - lidState.setSubtitle(statusUpdate.humanReadableLidState(statusUpdate.isLidState())); + lidState.setSubtitle(statusUpdate.isLidState() ? LEDSuite.i18n("lid_state_closed") : LEDSuite.i18n("lid_state_open")); } /** @@ -267,8 +268,9 @@ private void updateStatus(StatusUpdate statusUpdate) { * @since 1.0.0 */ private LEDSuiteTask updateLoop() { + @NonNull var statusBar = LEDSuite.mainWindow.widgetCache.get(Banner.class, "statusBar"); LEDSuite.eventManager.registerEvents(this); - if (!LEDSuite.mainWindow.isBannerVisible()) { + if (!statusBar.getRevealed()) { return new LEDSuiteGuiRunnable() { @Override public void processGui() { diff --git a/src/main/java/com/toxicstoxm/LEDSuite/ui/Window.java b/src/main/java/com/toxicstoxm/LEDSuite/ui/Window.java index d84afd83..d2d8fddf 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/ui/Window.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/ui/Window.java @@ -2,6 +2,7 @@ import com.toxicstoxm.LEDSuite.Constants; import com.toxicstoxm.LEDSuite.LEDSuite; +import com.toxicstoxm.LEDSuite.cache.Cache; import com.toxicstoxm.LEDSuite.communication.network.Networking; import com.toxicstoxm.LEDSuite.event_handling.EventHandler; import com.toxicstoxm.LEDSuite.event_handling.Events; @@ -14,6 +15,7 @@ import com.toxicstoxm.LEDSuite.yaml_factory.YAMLMessage; import com.toxicstoxm.LEDSuite.yaml_factory.YAMLSerializer; import com.toxicstoxm.LEDSuite.yaml_factory.wrappers.message_wrappers.StatusUpdate; +import lombok.NonNull; import org.apache.commons.configuration2.ex.ConfigurationException; import org.gnome.adw.AboutDialog; import org.gnome.adw.ApplicationWindow; @@ -33,198 +35,516 @@ // main application window public class Window extends ApplicationWindow implements EventListener { - // status banner and toast overlay used in the main window - // made public to enable global toggling - public Banner status = new Banner(""); - public ToastOverlay toastOverlay; - private ListBox animationsList = null; - private ListBox addFileList = null; - public ToolbarView rootView = null; - public ProgressBar progressBar = null; - public Button playPauseButton = null; - public Button stopButton = null; - private Revealer controlButtonsRevealer = null; - private Revealer StopRevealer = null; - private Revealer SidebarSpinner = null; - private HashMap availableAnimations; + private HashMap availableAnimations = new HashMap<>(); private Map.Entry currentAnimation = null; - // booleans to keep track of autoUpdate and statusBarEnabled settings - private boolean statusBarCurrentState = false; + public final Cache widgetCache = new Cache<>(); + private final Cache updateCache = new Cache<>(); // constructor for the main window public Window(org.gnome.adw.Application app) { + + // initializing an application window using the superclass constructor super(app); - // setting title and default size + + LEDSuite.logger.verbose("Assembling application window..."); + + // set window title, default window size, minimum window size and app icon this.setTitle(Constants.Application.NAME); - this.setDefaultSize(LEDSuite.argumentsSettings.getWindowDefWidth(), LEDSuite.argumentsSettings.getWindowDefHeight()); + this.setDefaultSize( + LEDSuite.argumentsSettings.getWindowDefWidth(), + LEDSuite.argumentsSettings.getWindowDefHeight() + ); this.setSizeRequest(360, 500); this.setIconName(Constants.Application.ICON); - // toast overlay used to display toasts (notification) to the user - toastOverlay = ToastOverlay.builder().build(); - - // the application header bar - var headerBar = new org.gnome.adw.HeaderBar(); - - // create search button and configure it - var sbutton = new ToggleButton(); - // setting the icon name to gnome icon name - sbutton.setIconName(Constants.GTK.Icons.Symbolic.SEARCH); - // executed when the button is toggled - sbutton.onToggled(() -> { - // display work in progress toast as the search function is not yet implemented - var wipToast = new Toast("Work in progress!"); - wipToast.setTimeout(1); - toastOverlay.addToast(wipToast); - }); + LEDSuite.logger.verbose("Loading content..."); - availableAnimations = new HashMap<>(); + // gets the GUI and adds it to the window + // checks for null pointer exceptions that indicate problems with GUI creation + // if any are found a shutdown is initiated + try { - // creating and configuring menu button - var mbutton = new MenuButton(); - mbutton.setAlwaysShowArrow(false); - // setting the icon name to gnome icon name - mbutton.setIconName(Constants.GTK.Icons.Symbolic.MENU); + this.setContent(getGUI()); + } catch (NullPointerException e) { + LEDSuite.logger.fatal("Failed to create rough content hierarchy! " + LEDSuite.logger.getErrorMessage(e)); + app.emitShutdown(); + } - var mainMenu = Menu.builder().build(); - var _status = MenuItem.builder().build(); - _status.setLabel("Status"); - _status.setDetailedAction(Constants.GTK.Actions._Actions._STATUS); - mainMenu.appendItem(_status); - var settings = MenuItem.builder().build(); - settings.setLabel("Settings"); - settings.setDetailedAction(Constants.GTK.Actions._Actions._SETTINGS); - mainMenu.appendItem(settings); - var generalMenu = Menu.builder().build(); - var shortcuts = MenuItem.builder().build(); - shortcuts.setLabel("Shortcuts"); - shortcuts.setDetailedAction(Constants.GTK.Actions._Actions._SHORTCUTS); - generalMenu.appendItem(shortcuts); - var about = MenuItem.builder().build(); - about.setLabel("About"); - about.setDetailedAction(Constants.GTK.Actions._Actions._ABOUT); - generalMenu.appendItem(about); - mainMenu.appendSection(null, generalMenu); + LEDSuite.logger.verbose("Loading actions..."); - // Creating actions used for keyboard shortcuts - var aboutRowAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.ABOUT).build(); - aboutRowAction.onActivate(_ -> triggerAboutRow()); - var settingsRowAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.SETTINGS).build(); - settingsRowAction.onActivate(_ -> triggerSettingsRow()); - var statusRowAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.STATUS).build(); - statusRowAction.onActivate(_ -> triggerStatusRow()); - var shortcutAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.SHORTCUTS).build(); - shortcutAction.onActivate(_ -> triggerShortcutRow()); + // adding the main and general action groups to the application window + this.insertActionGroup(Constants.GTK.Actions.Groups.MENU, getMainActionGroup()); + this.insertActionGroup(Constants.GTK.Actions.Groups.GENERAL, getGeneralActionGroup()); - // Add the actions to the window's action group - var actionGroup = new SimpleActionGroup(); - actionGroup.addAction(aboutRowAction); - actionGroup.addAction(settingsRowAction); - actionGroup.addAction(shortcutAction); - actionGroup.addAction(statusRowAction); - this.insertActionGroup(Constants.GTK.Actions.Groups.MENU, actionGroup); - - // Set up a shortcut controller - var shortcutController = ShortcutController.builder().setScope(ShortcutScope.MANAGED).build(); - // Define and add the shortcuts to the controller - var shortcutStatusRow = Shortcut.builder() - .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.STATUS)) - .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_STATUS)) + + LEDSuite.logger.verbose("Loading keyboard shortcuts..."); + + // Add the keyboard controller to the window + this.addController(getKeyboardShortcutController()); + this.addController(getGeneralShortcutController()); + + LEDSuite.logger.verbose("Loading breakpoints..."); + + this.addBreakpoint(getSideBarBreakpoint()); + + LEDSuite.logger.verbose("Successfully assembled application window!"); + } + // Creates the GUI structure and populates it with widgets + private ToolbarView getGUI() { + + LEDSuite.logger.verbose("Loading rough content hierarchy..."); + + // Creates rough content hierarchy (hierarchy is in reverse order) + createRoughContentHierarchy(); + + LEDSuite.logger.verbose("Loading cached hierarchy components..."); + // Gets the rough content hierarchy elements from cache + // Performs null checks to avoid unexpected behavior + @NonNull var mainView = widgetCache.get(ToolbarView.class, "mainView"); + @NonNull var windowOverlay = widgetCache.get(Overlay.class, "windowOverlay"); + @NonNull var sidebarContentView = widgetCache.get(ToolbarView.class, "sidebarContentView"); + @NonNull var contentView = widgetCache.get(ToolbarView.class, "contentView"); + + LEDSuite.logger.verbose("Loading bars..."); + // creates header bars, status bar and progress bar and caches them + createBars(); + + LEDSuite.logger.verbose("Loading cached bars..."); + // Gets the created bars from cache + // Performs null checks to avoid unexpected behavior + @NonNull var headerBar = widgetCache.get(HeaderBar.class, "headerBar"); + @NonNull var statusBar = widgetCache.get(Banner.class, "statusBar"); + @NonNull var progressBar = widgetCache.get(ProgressBar.class, "progressBar"); + @NonNull var sidebarHeaderbar = widgetCache.get(HeaderBar.class, "sidebarHeaderBar"); + + LEDSuite.logger.verbose("Inserting each bar into its parent container..."); + // Assigns each bar to its container + contentView.addTopBar(headerBar); + contentView.addTopBar(statusBar); + mainView.addBottomBar(progressBar); + sidebarContentView.addTopBar(sidebarHeaderbar); + + LEDSuite.logger.verbose("Configuring status bar..."); + // populates the status bar with some default values bases on user preference + setStatusBarVisible(LEDSuite.settings.isDisplayStatusBar()); + updateStatusBar(StatusUpdate.notConnected()); + // configuring status button + statusBar.onButtonClicked(this::triggerStatusRow); + statusBar.setButtonLabel(LEDSuite.i18n("status_button")); + + LEDSuite.logger.verbose("Loading overlays..."); + // gets the animation control buttons and add them to the window overlay + windowOverlay.addOverlay(getAnimationControlButtons()); + windowOverlay.addOverlay(getToastOverlay()); + + LEDSuite.logger.verbose("Loading sidebar..."); + populateSidebar(); + + LEDSuite.logger.verbose("Successfully Assembled GUI!"); + + // adding the main container to the window + return mainView; + } + + private ToastOverlay getToastOverlay() { + LEDSuite.logger.verbose("Loading toast overlay..."); + // creates a new toast overlay which is used to display toasts (notifications) inside the application window + // and add it to the window overlay + LEDSuite.logger.verbose("Assembling overlay..."); + var toastOverlay = ToastOverlay.builder() + .setValign(Align.END) + .setHalign(Align.CENTER) .build(); + LEDSuite.logger.verbose("Caching overlay..."); + widgetCache.put("toastOverlay", toastOverlay); + return toastOverlay; + } - var shortcutSettingsRow = Shortcut.builder() - .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SETTINGS)) - .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SETTINGS)) + private Breakpoint getSideBarBreakpoint() { + @NonNull var overlaySplitView = widgetCache.get(OverlaySplitView.class, "overlaySplitView"); + @NonNull var sideBarToggleButton = widgetCache.get(ToggleButton.class, "sideBarToggleButton"); + int min = 680; + + var sideBarBreakpoint = Breakpoint.builder() + .setCondition( + BreakpointCondition.parse("max-width: 600px") + ) .build(); + sideBarBreakpoint.onApply(() -> { + overlaySplitView.setCollapsed(true); + sideBarToggleButton.setVisible(true); + LEDSuite.logger.debug("Window with <= " + min + ". Collapsing sidebar"); + }); + sideBarBreakpoint.onUnapply(() -> { + overlaySplitView.setCollapsed(false); + sideBarToggleButton.setVisible(false); + }); + overlaySplitView.getSidebar().onStateFlagsChanged(_ -> { + if (sideBarToggleButton.getActive() && !overlaySplitView.getShowSidebar()) { + sideBarToggleButton.setActive(false); + } + }); + return sideBarBreakpoint; + } - var shortcutShortcutsRow = Shortcut.builder() - .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SHORTCUTS)) - .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SHORTCUTS)) + private ShortcutController getGeneralShortcutController() { + var sideBarShortcut = Shortcut.builder() + .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SIDEBAR)) + .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SIDEBAR)) .build(); - var shortcutAboutRow = Shortcut.builder() - .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.ABOUT)) - .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_ABOUT)) + var generalShortcutsController = ShortcutController.builder().setName("general").build(); + generalShortcutsController.addShortcut(sideBarShortcut); + return generalShortcutsController; + } + + private SimpleActionGroup getGeneralActionGroup() { + @NonNull var overlaySplitView = widgetCache.get(OverlaySplitView.class, "overlaySplitView"); + @NonNull var sideBarToggleButton = widgetCache.get(ToggleButton.class, "sideBarToggleButton"); + var sideBarButtonAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.SIDEBAR).build(); + sideBarButtonAction.onActivate(_ -> { + if (overlaySplitView.getCollapsed()) { + if (overlaySplitView.getShowSidebar()) { + overlaySplitView.setShowSidebar(false); + } else { + sideBarToggleButton.emitClicked(); + } + } + }); + var generalActionGroup = new SimpleActionGroup(); + generalActionGroup.addAction(sideBarButtonAction); + return generalActionGroup; + } + + private void populateSidebar() { + LEDSuite.logger.verbose("Loading cached parent views and containers..."); + @NonNull var sidebarContentBox = widgetCache.get(Box.class, "sidebarContentBox"); + @NonNull var mainContentRevealer = widgetCache.get(Revealer.class, "mainContentRevealer"); + @NonNull var contentBox = widgetCache.get(Box.class, "contentBox"); + @NonNull var sidebarAnimationSection = widgetCache.get(ListBox.class, "sidebarAnimationSection"); + @NonNull var controlButtonsRevealer = widgetCache.get(Revealer.class, "controlButtonsRevealer"); + + LEDSuite.logger.verbose("Loading sidebar sections..."); + var sidebarFileSection = ListBox.builder() + .setSelectionMode(SelectionMode.BROWSE) + .setCssClasses( + new String[]{"navigation-sidebar"} + ) + .build(); + Box addFile = Box.builder() + .setOrientation(Orientation.HORIZONTAL) + .setTooltipText(LEDSuite.i18n("add_file_row_tooltip")) + .setSpacing(10) + .build(); + addFile.append(Image.fromIconName(Constants.GTK.Icons.Symbolic.DOCUMENT_SEND)); + addFile.append( + Label.builder() + .setLabel(LEDSuite.i18n("add_file_row")) + .setEllipsize(EllipsizeMode.END) + .setXalign(0) + .build() + ); + sidebarFileSection.append( + ListBoxRow.builder() + .setSelectable(true) + .setChild( + addFile + ).build() + ); + + var Animations = Label.builder().setLabel(LEDSuite.i18n("animations_header")).build(); + + sidebarAnimationSection = ListBox.builder() + .setSelectionMode(SelectionMode.BROWSE) + .setCssClasses( + new String[]{"navigation-sidebar"} + ) .build(); - shortcutController.addShortcut(shortcutStatusRow); - shortcutController.addShortcut(shortcutSettingsRow); - shortcutController.addShortcut(shortcutShortcutsRow); - shortcutController.addShortcut(shortcutAboutRow); + LEDSuite.logger.verbose("Loading update spinner..."); + + var spinnerBox = Box.builder().setHalign(Align.CENTER).build(); + spinnerBox.append(Spinner.builder().setSpinning(true).build()); - // Add the controller to the window - this.addController(shortcutController); + var sidebarSpinnerRevealer = Revealer.builder() + .setChild(spinnerBox) + .setRevealChild(true) + .setTransitionType(RevealerTransitionType.CROSSFADE) + .build(); + LEDSuite.logger.verbose("Caching update spinner..."); + widgetCache.put("sidebarSpinnerRevealer", sidebarSpinnerRevealer); - mbutton.setMenuModel(mainMenu); + AtomicReference current = new AtomicReference<>(new ListBoxRow()); - mbutton.onActivate(() -> LEDSuite.logger.debug("Menu button clicked")); + TimeManager.initTimeTracker("sidebarClickDebounce", 200, System.currentTimeMillis() - 1000); - // adding the search button to the start of the header bar and the menu button to its end - //headerBar.packStart(sbutton); - headerBar.packEnd(mbutton); + LEDSuite.logger.verbose("Creating handlers..."); + @NonNull ListBox finalSidebarAnimationSection = sidebarAnimationSection; + sidebarFileSection.onRowActivated(row -> { + if (!TimeManager.call("sidebarClickDebounce")) { + sidebarFileSection.setSelectionMode(SelectionMode.NONE); + sidebarFileSection.setSelectionMode(SelectionMode.BROWSE); + return; + } + if (current.get() == row) return; + current.set(row); + controlButtonsRevealer.setRevealChild(false); + LEDSuite.logger.verbose("Clicked add file row!"); + mainContentRevealer.setRevealChild(false); + if (contentBox.getFirstChild() != null) contentBox.remove(contentBox.getFirstChild()); + contentBox.setValign(Align.START); + contentBox.append(new AddFileDialog()); + finalSidebarAnimationSection.setSelectionMode(SelectionMode.NONE); + finalSidebarAnimationSection.setSelectionMode(SelectionMode.BROWSE); + mainContentRevealer.setRevealChild(true); + }); - // creating the main container witch will hold the main window content - var mainContent = new Box(Orientation.VERTICAL, 0); - mainContent.setHomogeneous(false); - // creating north / center / south containers to correctly align window content - var TopBox = new Box(Orientation.VERTICAL, 0); - var CenterBox = Box.builder() + @NonNull ListBox finalSidebarAnimationSection1 = sidebarAnimationSection; + sidebarAnimationSection.onRowActivated(row -> { + if (!TimeManager.call("sidebarClickDebounce")) { + finalSidebarAnimationSection1.setSelectionMode(SelectionMode.NONE); + finalSidebarAnimationSection1.setSelectionMode(SelectionMode.BROWSE); + return; + } + if (current.get() == row) return; + controlButtonsRevealer.setRevealChild(true); + current.set(row); + if (!row.getSelectable()) return; + if (contentBox.getFirstChild() != null) { + mainContentRevealer.setRevealChild(false); + contentBox.remove(contentBox.getFirstChild()); + } + contentBox.setValign(Align.CENTER); + AtomicBoolean spinner = new AtomicBoolean(true); + new LEDSuiteRunnable() { + @Override + public void run() { + if (spinner.get()) { + GLib.idleAddOnce(() -> mainContentRevealer.setRevealChild(true)); + } + } + }.runTaskLaterAsynchronously(500); + String rowName = row.getName(); + if (!availableAnimations.containsKey(rowName)) + LEDSuite.logger.warn("Requesting menu for unknown animation menu! Name: '" + rowName + "'"); + else { + String icon = availableAnimations.get(rowName); + currentAnimation = new AbstractMap.SimpleEntry<>(rowName, icon); + } + try { + Networking.Communication.sendYAMLDefaultHost( + YAMLMessage.builder() + .setPacketType(YAMLMessage.PACKET_TYPE.request) + .setRequestType(YAMLMessage.REQUEST_TYPE.menu) + .setRequestFile(rowName) + .build(), + result -> { + if (result) { + LEDSuite.logger.debug("Requesting animation menu for '" + rowName + "' from server."); + getStatus(null); + } else { + LEDSuite.logger.warn("Failed to load menu for '" + rowName + "'!"); + } + }, + new LEDSuiteProcessor() { + @Override + public void run(YAMLMessage yaml) throws DefaultHandleException { + if (yaml.getPacketType().equals(YAMLMessage.PACKET_TYPE.reply) && yaml.getReplyType().equals(YAMLMessage.REPLY_TYPE.menu)) { + LEDSuite.logger.debug(yaml.getAnimationMenu().toString()); + String id = "[" + yaml.getNetworkID() + "] "; + LEDSuite.logger.debug(id + "Converting animation menu to displayable menu!"); + spinner.set(false); + mainContentRevealer.setRevealChild(false); + if (contentBox.getFirstChild() != null) { + mainContentRevealer.setRevealChild(false); + contentBox.remove(contentBox.getFirstChild()); + } + contentBox.setValign(Align.START); + contentBox.append(AnimationMenu.display(yaml.getAnimationMenu())); + LEDSuite.logger.debug(id + "Displaying converted menu!"); + mainContentRevealer.setRevealChild(true); + } else throw new DefaultHandleException("Unexpected response!"); + } + } + ); + } catch (ConfigurationException | YAMLSerializer.YAMLException e) { + LEDSuite.logger.warn("Failed to send / get menu request for: " + rowName); + finalSidebarAnimationSection1.remove(row); + LEDSuite.logger.displayError(e); + } + LEDSuite.logger.debug("AnimationSelected: " + rowName); + sidebarFileSection.setSelectionMode(SelectionMode.NONE); + sidebarFileSection.setSelectionMode(SelectionMode.BROWSE); + }); + LEDSuite.logger.verbose("Assembling sidebar..."); + sidebarContentBox.append(sidebarFileSection); + sidebarContentBox.append(Separator.builder().build()); + sidebarContentBox.append(Animations); + sidebarContentBox.append(sidebarAnimationSection); + sidebarContentBox.append(sidebarSpinnerRevealer); + + LEDSuite.logger.verbose("Caching sidebar..."); + widgetCache.put("sidebarFileSection", sidebarFileSection); + widgetCache.put("sidebarAnimationSection", sidebarAnimationSection); + } + + // creates the main header bar, the sidebar header bar, the status bar and the progress bar + // and caches them + private void createBars() { + LEDSuite.logger.verbose("Loading header bars..."); + var headerBar = HeaderBar.builder().build(); + // Inserts the main menu button at the end of the header bar + headerBar.packEnd(getMainMenuButton()); + headerBar.packStart(getSideBarToggleButton()); + widgetCache.put("headerBar", headerBar); + + LEDSuite.logger.verbose("Loading status bar..."); + // initializes the status bar with a default title and + widgetCache.put("statusBar", + Banner.builder() + .setTitle("N/A") + .build() + ); + + LEDSuite.logger.verbose("Loading progress bar..."); + // creates a progress bar and caches it + widgetCache.put("progressBar", + ProgressBar.builder() + .setFraction(0.0) + .build() + ); + + // creates a header bar for the sidebar and caches it + widgetCache.put("sidebarHeaderBar", + HeaderBar.builder() + .setTitleWidget( + Label.builder() + .setLabel(LEDSuite.i18n("file_management_header")) + .build() + ) + .setHexpand(true) + .setCssClasses(new String[]{"flat"}) + .build() + ); + LEDSuite.logger.verbose("Caching bars..."); + } + + private void createRoughContentHierarchy() { + + LEDSuite.logger.verbose("Loading containers..."); + // the content box holds all main content widgets, outside the sidebar + var contentBox = Box.builder() .setOrientation(Orientation.VERTICAL) .setSpacing(0) + // set vertical expanding to true for the center box, + // so it pushed the north box to the top of the window and the south box to the bottom + .setVexpand(true) .setValign(Align.CENTER) .build(); - // set vertical expanding to true for the center box, so it pushed the north box to the top of the window and the south box to the bottom - CenterBox.setVexpand(true); - // aligning the south box to the end (bottom) of the window to ensure it never aligns wrongly when resizing a window - // toggling status bar visibility depending on user preferences - setBannerVisible(LEDSuite.settings.isDisplayStatusBar()); + LEDSuite.logger.verbose("Loading revealers..."); + // main content box revealer manages visibility of the content box + var mainContentRevealer = Revealer.builder() + .setChild(contentBox) + .setRevealChild(true) + .setTransitionType(RevealerTransitionType.CROSSFADE) + .build(); + + LEDSuite.logger.verbose("Loading views..."); + // contains the header bar and content box + var contentView = ToolbarView.builder() + .setContent(mainContentRevealer) + .setBottomBarStyle(ToolbarStyle.FLAT) + .setTopBarStyle(ToolbarStyle.FLAT) + .build(); + + var sidebarContentBox = Box.builder() + .setOrientation(Orientation.VERTICAL) + .setSpacing(10) + .setValign(Align.START) + .setHexpand(true) + .build(); - updateStatus(StatusUpdate.notConnected()); + var sidebarContentView = ToolbarView.builder() + .setContent( + ScrolledWindow.builder() + .setChild(sidebarContentBox) + .build() + ) + .build(); - // adding the header bar container to the north box - TopBox.append(status); + // adds the sidebar as an overlay to the main view + var overlaySplitView = OverlaySplitView.builder() + .setContent(contentView) + .setSidebar(sidebarContentView) + .setEnableHideGesture(true) + .setEnableShowGesture(true) + .setSidebarWidthUnit(LengthUnit.PX) + .setSidebarWidthFraction(0.2) + .setShowSidebar(true) + .build(); - var TopRevealer = Revealer.builder() - .setChild(TopBox) - .setRevealChild(true) + LEDSuite.logger.verbose("Loading overlays..."); + // adds various overlays to the overlay split view + var windowOverlay = Overlay.builder() + .setChild(overlaySplitView) .build(); - TopRevealer.setTransitionType(RevealerTransitionType.SLIDE_DOWN); - var CenterRevealer = Revealer.builder() - .setChild(CenterBox) - .setRevealChild(true) + + // the main view is the top level container and contains all widgets + var mainView = ToolbarView.builder() + .setContent(windowOverlay) + .setRevealBottomBars(false) .build(); - CenterRevealer.setTransitionType(RevealerTransitionType.CROSSFADE); - // adding all alignment boxes to the main window container - mainContent.append(TopRevealer); - mainContent.append(CenterRevealer); + LEDSuite.logger.verbose("Caching loaded hierarchy components..."); + // adding the base hierarchy elements to the cache + widgetCache.put("mainView", mainView); + widgetCache.put("windowOverlay", windowOverlay); + widgetCache.put("overlaySplitView", overlaySplitView); + widgetCache.put("sidebarContentView", sidebarContentView); + widgetCache.put("sidebarContentBox", sidebarContentBox); + widgetCache.put("contentView", contentView); + widgetCache.put("mainContentRevealer", mainContentRevealer); + widgetCache.put("contentBox", contentBox); + } - playPauseButton = Button.builder() + private Clamp getAnimationControlButtons() { + LEDSuite.logger.verbose("Loading animation control button overlay..."); + LEDSuite.logger.verbose("Assembling buttons..."); + var playPauseButton = Button.builder() .setIconName(Constants.GTK.Icons.Symbolic.PLAY) .setName("play") .setCssClasses(new String[]{"osd", "circular"}) .build(); - stopButton = Button.builder() + + widgetCache.put("playPauseButton", playPauseButton); + + var stopButton = Button.builder() .setIconName(Constants.GTK.Icons.Symbolic.STOP) .setName("stop") .setCssClasses(new String[]{"osd", "circular"}) .build(); - StopRevealer = Revealer.builder() + var stopButtonRevealer = Revealer.builder() .setRevealChild(false) .setChild(stopButton) + .setTransitionType(RevealerTransitionType.CROSSFADE) .build(); - StopRevealer.setTransitionType(RevealerTransitionType.CROSSFADE); + + widgetCache.put("stopButtonRevealer", stopButtonRevealer); TimeManager.initTimeTracker("control_buttons", 500); AtomicBoolean allowPlayPause = new AtomicBoolean(true); String playState = "play"; String pauseState = "pause"; + + LEDSuite.logger.verbose("Creating handlers..."); + playPauseButton.onClicked(() -> { if (!TimeManager.call("control_buttons")) return; + LEDSuite.logger.verbose("Play pause button pressed!"); AtomicReference state = new AtomicReference<>(playPauseButton.getName()); if (allowPlayPause.get() && currentAnimation != null && availableAnimations.containsKey(currentAnimation.getKey())) { try { @@ -236,29 +556,30 @@ public Window(org.gnome.adw.Application app) { .build(), success -> { if (!success) { - idle(); + idle(playPauseButton, stopButtonRevealer); errorFeedback(playPauseButton, allowPlayPause); - LEDSuite.logger.error(com.toxicstoxm.LEDSuite.yaml_factory.AnimationMenu.capitalizeFirstLetter(state.get()) + " request for " + currentAnimation.getKey() + " failed!"); + LEDSuite.logger.warn(com.toxicstoxm.LEDSuite.yaml_factory.AnimationMenu.capitalizeFirstLetter(state.get()) + " request for " + currentAnimation.getKey() + " failed!"); } } ); } catch (ConfigurationException | YAMLSerializer.InvalidReplyTypeException | YAMLSerializer.InvalidPacketTypeException | YAMLSerializer.TODOException _) { - LEDSuite.logger.error(com.toxicstoxm.LEDSuite.yaml_factory.AnimationMenu.capitalizeFirstLetter(state.get()) + " request for " + currentAnimation.getKey() + " failed!"); + LEDSuite.logger.warn(com.toxicstoxm.LEDSuite.yaml_factory.AnimationMenu.capitalizeFirstLetter(state.get()) + " request for " + currentAnimation.getKey() + " failed!"); } } else { - idle(); + idle(playPauseButton, stopButtonRevealer); errorFeedback(playPauseButton, allowPlayPause); } boolean bool = state.get().equals(playState); playPauseButton.setName(bool ? pauseState : playState); playPauseButton.setIconName(bool ? Constants.GTK.Icons.Symbolic.PAUSE : Constants.GTK.Icons.Symbolic.PLAY); LEDSuite.logger.debug("Successfully send " + state + " request for " + currentAnimation.getKey() + "!"); - StopRevealer.setRevealChild(true); + stopButtonRevealer.setRevealChild(true); }); AtomicBoolean allowStop = new AtomicBoolean(true); stopButton.onClicked(() -> { if (!TimeManager.call("control_buttons")) return; + LEDSuite.logger.verbose("Stop button pressed!"); if (allowStop.get() && currentAnimation != null && availableAnimations.containsKey(currentAnimation.getKey())) { try { Networking.Communication.sendYAMLDefaultHost( @@ -270,360 +591,239 @@ public Window(org.gnome.adw.Application app) { success -> { if (!success) { errorFeedback(stopButton, allowStop); - LEDSuite.logger.error("Stop request for " + currentAnimation.getKey() + " failed!"); + LEDSuite.logger.warn("Stop request for " + currentAnimation.getKey() + " failed!"); } } ); } catch (ConfigurationException | YAMLSerializer.InvalidReplyTypeException | YAMLSerializer.InvalidPacketTypeException | YAMLSerializer.TODOException _) { - LEDSuite.logger.error("Stop request for " + currentAnimation.getKey() + " failed!"); + LEDSuite.logger.warn("Stop request for " + currentAnimation.getKey() + " failed!"); } } else errorFeedback(stopButton, allowStop); - idle(); + idle(playPauseButton, stopButtonRevealer); LEDSuite.logger.debug("Successfully send Stop request for " + currentAnimation.getKey() + "!"); }); + LEDSuite.logger.verbose("Assembling final overlay..."); + var controlButtons = Box.builder() .setOrientation(Orientation.HORIZONTAL) .setSpacing(10) .build(); - controlButtons.append(StopRevealer); + controlButtons.append(stopButtonRevealer); controlButtons.append(playPauseButton); - controlButtonsRevealer = Revealer.builder() + var controlButtonsRevealer = Revealer.builder() .setChild(controlButtons) .setRevealChild(false) .build(); controlButtonsRevealer.setTransitionType(RevealerTransitionType.CROSSFADE); + LEDSuite.logger.verbose("Caching assembled overlay..."); + widgetCache.put("controlButtonsRevealer", controlButtonsRevealer); - var controlButtonsWrapper = Clamp.builder() + return Clamp.builder() .setChild(controlButtonsRevealer) .setOrientation(Orientation.VERTICAL) .setMaximumSize(30) .setMarginEnd(30) .setMarginBottom(25) .setTighteningThreshold(30) + .setValign(Align.END) .setHalign(Align.END) .build(); + } - toastOverlay.setValign(Align.END); - toastOverlay.setHalign(Align.CENTER); - var mainView = ToolbarView.builder() - .setContent(mainContent) - .build(); - mainView.setBottomBarStyle(ToolbarStyle.FLAT); - - mainView.addTopBar(headerBar); - - var overlay = Overlay.builder().setChild(mainView).build(); - overlay.addOverlay(toastOverlay); - controlButtonsWrapper.setHalign(Align.END); - controlButtonsWrapper.setValign(Align.END); - overlay.addOverlay(controlButtonsWrapper); - - mainView.setTopBarStyle(ToolbarStyle.FLAT); - - var overlaySplitView = new OverlaySplitView(); - - overlaySplitView.setEnableHideGesture(true); - overlaySplitView.setEnableShowGesture(true); - - overlaySplitView.setContent(overlay); - overlaySplitView.setSidebarWidthUnit(LengthUnit.PX); - overlaySplitView.setSidebarWidthFraction(0.2); - overlaySplitView.setShowSidebar(true); - - var smallHeaderBar = HeaderBar.builder().build(); - smallHeaderBar.setTitleWidget(Label.builder().setLabel("File Management").build()); - smallHeaderBar.setHexpand(true); - - smallHeaderBar.setCssClasses(new String[]{"flat"}); - - var sidebarContentBox = Box.builder() - .setOrientation(Orientation.VERTICAL) - .setSpacing(10) - .setValign(Align.START) - .setHexpand(true) - .build(); - - addFileList = ListBox.builder() - .setSelectionMode(SelectionMode.BROWSE) - .setCssClasses( - new String[]{"navigation-sidebar"} - ) - .build(); - Box addFile = Box.builder() - .setOrientation(Orientation.HORIZONTAL) - .setTooltipText("Add file to LED-Cube (Upload)") - .setSpacing(10) - .build(); - addFile.append(Image.fromIconName(Constants.GTK.Icons.Symbolic.DOCUMENT_SEND)); - addFile.append( - Label.builder() - .setLabel("Add File") - .setEllipsize(EllipsizeMode.END) - .setXalign(0) - .build() - ); - addFileList.append( - ListBoxRow.builder() - .setSelectable(true) - .setChild( - addFile - ).build() - ); - - var Animations = Label.builder().setLabel("Animations").build(); - - animationsList = ListBox.builder() - .setSelectionMode(SelectionMode.BROWSE) - .setCssClasses( - new String[]{"navigation-sidebar"} - ) + private MenuButton getMainMenuButton() { + LEDSuite.logger.verbose("Loading main menu button..."); + // creating new menu button with hamburger menu icon + var mainMenuButton = MenuButton.builder() + .setLabel(LEDSuite.i18n("main_menu_button_label")) + .setTooltipText(LEDSuite.i18n("main_menu_button_tooltip")) + .setAlwaysShowArrow(false) + .setIconName(Constants.GTK.Icons.Symbolic.MENU) .build(); - var spinnerBox = Box.builder().setHalign(Align.CENTER).build(); - spinnerBox.append(Spinner.builder().setSpinning(true).build()); + LEDSuite.logger.verbose("Loading main menu..."); + // creating a new main menu dropdown, which will be displayed when pressing the main menu button + var mainMenu = Menu.builder().build(); - SidebarSpinner = Revealer.builder().setChild(spinnerBox).setRevealChild(true).build(); - SidebarSpinner.setTransitionType(RevealerTransitionType.CROSSFADE); + // creating all menu items that are later displayed inside the main menu + var mainMenuStatusRow = MenuItem.builder().build(); + mainMenuStatusRow.setLabel(LEDSuite.i18n("status_row")); + mainMenuStatusRow.setDetailedAction(Constants.GTK.Actions._Actions._STATUS); - AtomicReference current = new AtomicReference<>(new ListBoxRow()); + var mainMenuSettingsRow = MenuItem.builder().build(); + mainMenuSettingsRow.setLabel(LEDSuite.i18n("settings_row")); + mainMenuSettingsRow.setDetailedAction(Constants.GTK.Actions._Actions._SETTINGS); - TimeManager.initTimeTracker("sidebarClickDebounce", 200, System.currentTimeMillis() - 1000); - - addFileList.onRowActivated(_ -> { - if (!TimeManager.call("sidebarClickDebounce")) { - addFileList.setSelectionMode(SelectionMode.NONE); - addFileList.setSelectionMode(SelectionMode.BROWSE); - return; - } - controlButtonsRevealer.setRevealChild(false); - LEDSuite.logger.debug("Clicked add file row!"); - CenterRevealer.setRevealChild(false); - if (CenterBox.getFirstChild() != null) CenterBox.remove(CenterBox.getFirstChild()); - CenterBox.setValign(Align.START); - CenterBox.append(new com.toxicstoxm.LEDSuite.ui.AddFileDialog()); - animationsList.setSelectionMode(SelectionMode.NONE); - animationsList.setSelectionMode(SelectionMode.BROWSE); - CenterRevealer.setRevealChild(true); - }); + var shortcuts = MenuItem.builder().build(); + shortcuts.setLabel(LEDSuite.i18n("shortcuts_row")); + shortcuts.setDetailedAction(Constants.GTK.Actions._Actions._SHORTCUTS); - animationsList.onRowActivated(row -> { - controlButtonsRevealer.setRevealChild(true); - if (current.get() == row) return; - if (!TimeManager.call("sidebarClickDebounce")) { - animationsList.setSelectionMode(SelectionMode.NONE); - animationsList.unselectAll(); - animationsList.setSelectionMode(SelectionMode.BROWSE); - return; - } - current.set(row); - if (!row.getSelectable()) return; - if (CenterBox.getFirstChild() != null) { - CenterRevealer.setRevealChild(false); - CenterBox.remove(CenterBox.getFirstChild()); - } - CenterBox.setValign(Align.CENTER); - AtomicBoolean spinner = new AtomicBoolean(true); - new LEDSuiteRunnable() { - @Override - public void run() { - if (spinner.get()) { - GLib.idleAddOnce(() -> CenterRevealer.setRevealChild(true)); - } - } - }.runTaskLaterAsynchronously(500); - String rowName = row.getName(); - if (!availableAnimations.containsKey(rowName)) LEDSuite.logger.warn("Requesting menu for unknown animation menu! Name: '" + rowName + "'"); - else { - String icon = availableAnimations.get(rowName); - currentAnimation = new AbstractMap.SimpleEntry<>(rowName, icon); - } - try { - Networking.Communication.sendYAMLDefaultHost( - YAMLMessage.builder() - .setPacketType(YAMLMessage.PACKET_TYPE.request) - .setRequestType(YAMLMessage.REQUEST_TYPE.menu) - .setRequestFile(rowName) - .build(), - result -> { - if (result) { - LEDSuite.logger.debug("Requesting animation menu for '" + rowName + "' from server."); - getStatus(null); - } else { - LEDSuite.logger.error("Failed to load menu for '" + rowName + "'!"); - } - }, - new LEDSuiteProcessor() { - @Override - public void run(YAMLMessage yaml) throws DefaultHandleException { - if (yaml.getPacketType().equals(YAMLMessage.PACKET_TYPE.reply) && yaml.getReplyType().equals(YAMLMessage.REPLY_TYPE.menu)) { - LEDSuite.logger.debug(yaml.getAnimationMenu().toString()); - String id = "[" + yaml.getNetworkID() + "] "; - LEDSuite.logger.debug(id + "Converting animation menu to displayable menu!"); - spinner.set(false); - CenterRevealer.setRevealChild(false); - if (CenterBox.getFirstChild() != null) { - CenterRevealer.setRevealChild(false); - CenterBox.remove(CenterBox.getFirstChild()); - } - CenterBox.setValign(Align.START); - CenterBox.append(com.toxicstoxm.LEDSuite.ui.AnimationMenu.display(yaml.getAnimationMenu())); - LEDSuite.logger.debug(id + "Displaying converted menu!"); - CenterRevealer.setRevealChild(true); - } else throw new DefaultHandleException("Unexpected response!"); - } - } - ); - } catch (ConfigurationException | YAMLSerializer.YAMLException e) { - LEDSuite.logger.error("Failed to send / get menu request for: " + rowName); - animationsList.remove(row); - LEDSuite.logger.displayError(e); - } - LEDSuite.logger.debug("AnimationSelected: " + rowName); - addFileList.setSelectionMode(SelectionMode.NONE); - addFileList.setSelectionMode(SelectionMode.BROWSE); - }); - sidebarContentBox.append(addFileList); - sidebarContentBox.append(Separator.builder().build()); - sidebarContentBox.append(Animations); - sidebarContentBox.append(animationsList); - sidebarContentBox.append(SidebarSpinner); + var about = MenuItem.builder().build(); + about.setLabel(LEDSuite.i18n("about_row", "%APP_NAME%", Constants.Application.NAME)); + about.setDetailedAction(Constants.GTK.Actions._Actions._ABOUT); - var sidebarMainBox = new Box(Orientation.VERTICAL, 0); - sidebarMainBox.append(smallHeaderBar); - sidebarMainBox.append(sidebarContentBox); + LEDSuite.logger.verbose("Assembling main menu..."); - var scrolledView = ScrolledWindow.builder().setChild(sidebarMainBox).build(); + // adding the menu items to the main menu, in this specific order + mainMenu.appendItem(mainMenuStatusRow); + mainMenu.appendItem(mainMenuSettingsRow); + mainMenu.appendItem(shortcuts); + mainMenu.appendItem(about); - overlaySplitView.setSidebar(scrolledView); + // assigning the main menu to the main menu button + mainMenuButton.setMenuModel(mainMenu); + mainMenuButton.onActivate(() -> LEDSuite.logger.verbose("Main menu button clicked")); + return mainMenuButton; + } + private Button getSideBarToggleButton() { + LEDSuite.logger.verbose("Loading sidebar toggle button..."); + @NonNull var overlaySplitView = widgetCache.get(OverlaySplitView.class, "overlaySplitView"); var sideBarToggleButton = new ToggleButton(); sideBarToggleButton.setIconName(Constants.GTK.Icons.Symbolic.SIDEBAR_SHOW); - headerBar.packStart(sideBarToggleButton); + sideBarToggleButton.setVisible(overlaySplitView.getCollapsed()); sideBarToggleButton.onToggled(() -> { if (sideBarToggleButton.getActive() && !overlaySplitView.getShowSidebar()) { - LEDSuite.logger.debug("Sidebar show button pressed (toggle:true)"); + LEDSuite.logger.verbose("Sidebar toggle button was pressed. Showing sidebar"); overlaySplitView.setShowSidebar(true); // resetting the button to avoid checking continuously for sidebar hide signal sideBarToggleButton.setActive(false); } }); - var sideBarButtonAction = SimpleAction.builder().setName(Constants.GTK.Actions._Actions.SIDEBAR).build(); - sideBarButtonAction.onActivate(_ -> { - if (overlaySplitView.getCollapsed()) { - if (overlaySplitView.getShowSidebar()) { - overlaySplitView.setShowSidebar(false); - } else { - sideBarToggleButton.emitClicked(); - } - } - }); - var actionGroup0 = new SimpleActionGroup(); - actionGroup0.addAction(sideBarButtonAction); - this.insertActionGroup(Constants.GTK.Actions.Groups.GENERAL, actionGroup0); + LEDSuite.logger.verbose("Caching assembled button..."); + widgetCache.put("sideBarToggleButton", sideBarToggleButton); + return sideBarToggleButton; + } - var sideBarShortcut = Shortcut.builder() - .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SIDEBAR)) - .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SIDEBAR)) + private SimpleActionGroup getMainActionGroup() { + // Create a new action group to hold all keyboard shortcut related actions + var mainActionGroup = SimpleActionGroup.builder().build(); + + // Creating all actions required to handle keyboard shortcuts + var statusRowAction = SimpleAction.builder() + .setName(Constants.GTK.Actions._Actions.STATUS) .build(); - var generalShortcuts = ShortcutController.builder().setName("general").build(); - generalShortcuts.addShortcut(sideBarShortcut); - this.addController(generalShortcuts); + var settingsRowAction = SimpleAction.builder() + .setName(Constants.GTK.Actions._Actions.SETTINGS) + .build(); - int min = 680; + var shortcutAction = SimpleAction.builder() + .setName(Constants.GTK.Actions._Actions.SHORTCUTS) + .build(); - var sideBarBreakpoint = Breakpoint.builder() - .setCondition( - BreakpointCondition.parse("max-width: 600px") - ) + var aboutRowAction = SimpleAction.builder() + .setName(Constants.GTK.Actions._Actions.ABOUT) .build(); - sideBarBreakpoint.onApply(() -> { - overlaySplitView.setCollapsed(true); - sideBarToggleButton.setVisible(true); - LEDSuite.logger.debug("Window with <= " + min + ". Collapsing sidebar"); - }); - sideBarBreakpoint.onUnapply(() -> { - overlaySplitView.setCollapsed(false); - sideBarToggleButton.setVisible(false); - }); - this.addBreakpoint(sideBarBreakpoint); - overlaySplitView.getSidebar().onStateFlagsChanged(_ -> { - if (sideBarToggleButton.getActive() && !overlaySplitView.getShowSidebar()) { - sideBarToggleButton.setActive(false); - } - }); - status.onButtonClicked(this::triggerStatusRow); - status.setButtonLabel("LED Cube Status"); + // assigning the corresponding functions to the actions + statusRowAction.onActivate(_ -> triggerStatusRow()); + settingsRowAction.onActivate(_ -> triggerSettingsRow()); + shortcutAction.onActivate(_ -> triggerShortcutRow()); + aboutRowAction.onActivate(_ -> triggerAboutRow()); - progressBar = ProgressBar.builder().setFraction(0.0).build(); + // adding all the actions to the main action group + mainActionGroup.addAction(statusRowAction); + mainActionGroup.addAction(settingsRowAction); + mainActionGroup.addAction(shortcutAction); + mainActionGroup.addAction(aboutRowAction); + return mainActionGroup; + } - rootView = ToolbarView.builder() - .setContent(overlaySplitView) + private ShortcutController getKeyboardShortcutController() { + // Set up a new shortcut controller for handling keyboard shortcuts + var keyboardShortcutController = ShortcutController.builder() + .setScope(ShortcutScope.GLOBAL) .build(); - rootView.addBottomBar(progressBar); - rootView.setRevealBottomBars(false); - // adding the main container to the window - this.setContent(rootView); + // Creating all keyboard shortcuts + var statusRowKeyboardShortcut = Shortcut.builder() + .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.STATUS)) + .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_STATUS)) + .build(); + + var settingsRowKeyboardShortcut = Shortcut.builder() + .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SETTINGS)) + .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SETTINGS)) + .build(); + + var shortcutsRowKeyboardShortcut = Shortcut.builder() + .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.SHORTCUTS)) + .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_SHORTCUTS)) + .build(); + + var aboutRowKeyboardShortcut = Shortcut.builder() + .setTrigger(ShortcutTrigger.parseString(Constants.GTK.Shortcuts.ABOUT)) + .setAction(ShortcutAction.parseString(Constants.GTK.Actions.SHORTCUT_ABOUT)) + .build(); + + // Adding all the keyboard shortcuts to the keyboard shortcuts controller + keyboardShortcutController.addShortcut(statusRowKeyboardShortcut); + keyboardShortcutController.addShortcut(settingsRowKeyboardShortcut); + keyboardShortcutController.addShortcut(shortcutsRowKeyboardShortcut); + keyboardShortcutController.addShortcut(aboutRowKeyboardShortcut); + return keyboardShortcutController; } private void triggerStatusRow() { LEDSuite.logger.debug("User click: status row"); new com.toxicstoxm.LEDSuite.ui.StatusDialog().present(this); } + private void triggerSettingsRow() { LEDSuite.logger.debug("User click: settings row"); getSettingsDialog().present(this); } + private void triggerShortcutRow() { LEDSuite.logger.debug("User click: shortcut row"); var shortcuts = ShortcutsWindow.builder().build(); var shortcutSection = ShortcutsSection.builder().build(); - var generalGroup = ShortcutsGroup.builder().setTitle(Constants.GTK.Actions.Groups.GENERAL).build(); + var generalGroup = ShortcutsGroup.builder().setTitle(LEDSuite.i18n("shortcut_section_general")).build(); generalGroup.addShortcut( ShortcutsShortcut.builder() .setShortcutType(ShortcutType.ACCELERATOR) .setAccelerator(Constants.GTK.Shortcuts.SIDEBAR) - .setTitle("Toggle sidebar") + .setTitle(LEDSuite.i18n("sidebar_toggle_shortcut")) .build() ); - var menuGroup = ShortcutsGroup.builder().setTitle(Constants.GTK.Actions.Groups.MENU).build(); + var menuGroup = ShortcutsGroup.builder() + .setTitle(LEDSuite.i18n("shortcut_section_main_menu")) + .build(); menuGroup.addShortcut( ShortcutsShortcut.builder() .setShortcutType(ShortcutType.ACCELERATOR) .setAccelerator(Constants.GTK.Shortcuts.STATUS) - .setTitle("Open status dialog") + .setTitle(LEDSuite.i18n("status_dialog_shortcut")) .build() ); menuGroup.addShortcut( ShortcutsShortcut.builder() .setShortcutType(ShortcutType.ACCELERATOR) .setAccelerator(Constants.GTK.Shortcuts.SETTINGS) - .setTitle("Open settings dialog") + .setTitle(LEDSuite.i18n("settings_dialog_shortcut")) .build() ); menuGroup.addShortcut( ShortcutsShortcut.builder() .setShortcutType(ShortcutType.ACCELERATOR) .setAccelerator(Constants.GTK.Shortcuts.SHORTCUTS) - .setTitle("Open this dialog") + .setTitle(LEDSuite.i18n("shortcut_dialog_shortcut")) .build() ); menuGroup.addShortcut( ShortcutsShortcut.builder() .setShortcutType(ShortcutType.ACCELERATOR) .setAccelerator(Constants.GTK.Shortcuts.ABOUT) - .setTitle("Open about dialog") + .setTitle(LEDSuite.i18n("about_dialog_shortcut")) .build() ); @@ -635,6 +835,7 @@ private void triggerShortcutRow() { shortcuts.present(); } + private void triggerAboutRow() { LEDSuite.logger.debug("User click: about row"); getAboutDialog().present(this); @@ -656,27 +857,27 @@ public void run() { }.runTaskLaterAsynchronously(750); } - // about dialog - private org.gnome.adw.AboutDialog aDialog = null; - // method to either create a new about dialog or get an already existing one - // this ensures that only one about dialog is created to prevent the app unnecessarily using up system resources + // assembling about dialog private org.gnome.adw.AboutDialog getAboutDialog() { - // checking if an existing about dialog can be reused - if (aDialog == null) { - // if not, a new one is created - aDialog = AboutDialog.builder() - .setDevelopers(new String[]{"ToxicStoxm", "CraftBukkit GitHub Repo"}) - .setArtists(new String[]{"Hannes Campidell", "GNOME Foundation"}) - .setVersion(Constants.Application.VERSION) - .setLicenseType(License.GPL_3_0) - .setApplicationIcon(Constants.Application.ICON) - .setIssueUrl(Constants.Links.PROJECT_GITHUB + "issues") - .setWebsite(Constants.Links.PROJECT_GITHUB) - .setApplicationName(Constants.Application.NAME) - .build(); - } - return aDialog; + + var aboutDialog = AboutDialog.builder() + .setDevelopers(LEDSuite.i18n("developers", true)) + .setDesigners(LEDSuite.i18n("artists", true)) + .setTranslatorCredits(LEDSuite.i18n("translators")) + .setVersion(Constants.Application.VERSION) + .setLicenseType(License.GPL_3_0) + .setReleaseNotes(LEDSuite.i18n("release_notes")) + .setReleaseNotesVersion(Constants.Application.VERSION) + .setApplicationIcon(Constants.Application.ICON) + .setIssueUrl(Constants.Links.PROJECT_GITHUB + "issues") + .setWebsite(Constants.Links.PROJECT_GITHUB) + .setApplicationName(Constants.Application.NAME) + .build(); + aboutDialog.addAcknowledgementSection(LEDSuite.i18n("special_thanks_title"), LEDSuite.i18n("special_thanks", true)); + + return aboutDialog; } + public void getStatus(Networking.Communication.FinishCallback callback) { try { Networking.Communication.sendYAMLDefaultHost(new YAMLMessage() @@ -693,19 +894,22 @@ public void run(YAMLMessage yaml) { ); } catch (YAMLSerializer.TODOException | ConfigurationException | YAMLSerializer.InvalidReplyTypeException | YAMLSerializer.InvalidPacketTypeException e) { - LEDSuite.logger.error("Failed to send status request to server! Error message: " + e.getMessage()); + LEDSuite.logger.warn("Failed to send status request to server! " + LEDSuite.logger.getErrorMessage(e)); LEDSuite.logger.displayError(e); } } + // status bar updater private LEDSuiteTask statusBarUpdater; + // creates a new status bar updater - private void updateStatus() { + private void startAutomaticStatusRequestLoop() { + @NonNull var statusBar = widgetCache.get(Banner.class, "statusBar"); LEDSuite.logger.debug("Running new StatusBar update Task!"); statusBarUpdater = new LEDSuiteGuiRunnable() { @Override public void processGui() { - if (!status.getRevealed()) return; + if (!statusBar.getRevealed()) return; getStatus(success -> { if (!success) LEDSuite.eventManager.fireEvent(new Events.Status(StatusUpdate.notConnected())); }); @@ -714,36 +918,30 @@ public void processGui() { } // toggle status bar - public void setBannerVisible(boolean visible) { + public void setStatusBarVisible(boolean visible) { + @NonNull var statusBar = widgetCache.get(Banner.class, "statusBar"); LEDSuite.logger.debug("---------------------------------------------------------------"); - LEDSuite.logger.debug("Fulfilling StatusBarToggle: " + statusBarCurrentState + " >> " + visible); - status.setVisible(true); + LEDSuite.logger.debug("Fulfilling StatusBarToggle: " + statusBar.getRevealed() + " >> " + visible); + statusBar.setVisible(true); // if the status bar is currently turned off and should be activated - if (!statusBarCurrentState && visible) { - // the current status is set to true - statusBarCurrentState = true; - // and a new update status task is created and started - updateStatus(); + if (!statusBar.getRevealed() && visible) { + // A new update status task is created and started + startAutomaticStatusRequestLoop(); } else { // if the status bar is currently turned on and should be deactivated // if there is an updater task running if (statusBarUpdater != null) statusBarUpdater.cancel(); // trigger the tasks kill switch - // set the current status to false - statusBarCurrentState = false; } // toggle status bar visibility based on the provided value 'visible' - status.setRevealed(visible); + statusBar.setRevealed(visible); // update user settings to match new value if (LEDSuite.mainWindow != null) LEDSuite.settings.setDisplayStatusBar(visible); LEDSuite.logger.debug("---------------------------------------------------------------"); } - // check if the status bar is currently visible - public boolean isBannerVisible() { - return statusBarCurrentState; - } // settings dialog private com.toxicstoxm.LEDSuite.ui.SettingsDialog sD = null; + // method to either create a new settings dialog or get an already existing one // this ensures that only one settings dialog is created to prevent the app unnecessarily using up system resources private com.toxicstoxm.LEDSuite.ui.SettingsDialog getSettingsDialog() { @@ -759,37 +957,103 @@ public void resetSettingsDialog() { this.sD = null; } - public void updateStatus(StatusUpdate statusUpdate) { - status.setTitle(statusUpdate.minimal()); - if (statusUpdate.isNotConnected() && controlButtonsRevealer != null) controlButtonsRevealer.setRevealChild(false); + public void updateStatusBar(StatusUpdate statusUpdate) { + @NonNull var statusBar = widgetCache.get(Banner.class, "statusBar"); + @NonNull var controlButtonsRevealer = widgetCache.get(Revealer.class, "controlButtonsRevealer"); + statusBar.setTitle(statusUpdate.minimal()); + if (statusUpdate.isNotConnected() && controlButtonsRevealer != null) { + controlButtonsRevealer.setRevealChild(false); + } + } + + public ListBoxRow listBoxWrap(Widget widget) { + return ListBoxRow.builder() + .setSelectable(true) + .setName(widget.getName()) + .setChild( + widget + ).build(); + } + + public void playing(Button playPauseButton, Revealer stopButtonRevealer) { + stopButtonRevealer.setRevealChild(true); + playPauseButton.setName("pause"); + playPauseButton.setIconName("media-playback-pause-symbolic"); + } + + public void paused(Button playPauseButton, Revealer stopButtonRevealer) { + stopButtonRevealer.setRevealChild(true); + playPauseButton.setName("play"); + playPauseButton.setIconName("media-playback-start-symbolic"); + } + + public void idle(Button playPauseButton, Revealer stopButtonRevealer) { + stopButtonRevealer.setRevealChild(false); + playPauseButton.setName("play"); + playPauseButton.setIconName("media-playback-start-symbolic"); + } + + public void setControlButtons(StatusUpdate statusUpdate, Map.Entry name, Button playPauseButton, Revealer stopButtonRevealer) { + @NonNull var controlButtonsRevealer = widgetCache.get(Revealer.class, "controlButtonsRevealer"); + controlButtonsRevealer.setRevealChild(false); + if (statusUpdate.isNotConnected()) { + return; + } + if (statusUpdate.isFileLoaded()) { + if (!statusUpdate.getFileSelected().equals(name.getKey())) { + idle(playPauseButton, stopButtonRevealer); + controlButtonsRevealer.setRevealChild(true); + return; + } + switch (statusUpdate.getFileState()) { + case YAMLMessage.FILE_STATE.playing -> playing(playPauseButton, stopButtonRevealer); + case YAMLMessage.FILE_STATE.paused -> paused(playPauseButton, stopButtonRevealer); + default -> idle(playPauseButton, stopButtonRevealer); + } + } else idle(playPauseButton, stopButtonRevealer); + controlButtonsRevealer.setRevealChild(true); } @EventHandler public void onStatus(Events.Status e) { + @NonNull var sidebarAnimationSection = widgetCache.get(ListBox.class, "sidebarAnimationSection"); + @NonNull var sidebarSpinnerRevealer = widgetCache.get(Revealer.class, "sidebarSpinnerRevealer"); GLib.idleAddOnce(() -> { try { StatusUpdate statusUpdate = e.statusUpdate(); - updateStatus(statusUpdate); + updateStatusBar(statusUpdate); - ListBoxRow selectedRow = animationsList.getSelectedRow(); + ListBoxRow selectedRow = sidebarAnimationSection.getSelectedRow(); String name = ""; if (selectedRow != null) name = selectedRow.getName(); - if (!name.isBlank()) setControlButtons(statusUpdate, currentAnimation); - if (name.isBlank() || TimeManager.call("animations")) { - animationsList.unselectAll(); - animationsList.setSelectionMode(SelectionMode.NONE); - animationsList.removeAll(); - - SidebarSpinner.setRevealChild(true); - - List anims = new ArrayList<>(); + if (!name.isBlank()) { + var playPauseButton = widgetCache.get(Button.class, "playPauseButton"); + var stopButtonRevealer = widgetCache.get(Revealer.class, "stopButtonRevealer"); + if (playPauseButton != null && stopButtonRevealer != null) { + setControlButtons(statusUpdate, currentAnimation, playPauseButton, stopButtonRevealer); + } + } + availableAnimations = statusUpdate.getAvailableAnimations(); + if (availableAnimations != null) { + Integer previousChecksum = updateCache.get(Integer.class, "statusUpdateChecksum"); + Integer currentChecksum = statusUpdate.getAvailableAnimations().hashCode(); + boolean shouldUpdateAnimationList = !currentChecksum.equals(previousChecksum); + if (!shouldUpdateAnimationList) + LEDSuite.logger.verbose("Skipping update request for the available animations list, because previous checksum (" + previousChecksum + ") == current checksum (" + currentChecksum + ")!"); + if ((name.isBlank() || TimeManager.call("animations")) && shouldUpdateAnimationList) { + updateCache.put("statusUpdateChecksum", currentChecksum, true); + sidebarAnimationSection.unselectAll(); + sidebarAnimationSection.setSelectionMode(SelectionMode.NONE); + sidebarAnimationSection.removeAll(); + + sidebarSpinnerRevealer.setRevealChild(true); + + List anims = new ArrayList<>(); - availableAnimations = statusUpdate.getAvailableAnimations(); - if (availableAnimations != null) { for (Map.Entry entry : availableAnimations.entrySet()) { var availableAnimation = Box.builder() .setOrientation(Orientation.HORIZONTAL) - .setTooltipText("Open " + entry.getKey() + " settings menu") + .setTooltipText(LEDSuite.i18n("animations_tooltip", "%ANIMATION_NAME%", entry.getKey())) .setName(entry.getKey()) .setSpacing(10) .build(); @@ -805,13 +1069,14 @@ public void onStatus(Events.Status e) { if (entry.getKey().equals(name)) selectedRow = row; anims.add(row); } - SidebarSpinner.setRevealChild(false); + sidebarSpinnerRevealer.setRevealChild(false); for (ListBoxRow lbr : anims) { - animationsList.append(lbr); + sidebarAnimationSection.append(lbr); } + + sidebarAnimationSection.setSelectionMode(SelectionMode.BROWSE); + sidebarAnimationSection.selectRow(selectedRow); } - animationsList.setSelectionMode(SelectionMode.BROWSE); - animationsList.selectRow(selectedRow); } } catch (NumberFormatException ex) { LEDSuite.logger.warn("Status update failed!"); @@ -822,52 +1087,9 @@ public void onStatus(Events.Status e) { @EventHandler public void onStarted(Events.Started e) { - addFileList.emitRowSelected(addFileList.getRowAtIndex(0)); - addFileList.emitRowActivated(addFileList.getRowAtIndex(0)); - addFileList.emitSelectedRowsChanged(); + @NonNull var sidebarFileSection = widgetCache.get(ListBox.class, "sidebarFileSection"); + sidebarFileSection.emitRowSelected(sidebarFileSection.getRowAtIndex(0)); + sidebarFileSection.emitRowActivated(sidebarFileSection.getRowAtIndex(0)); + sidebarFileSection.emitSelectedRowsChanged(); } - - public ListBoxRow listBoxWrap(Widget widget) { - return ListBoxRow.builder() - .setSelectable(true) - .setName(widget.getName()) - .setChild( - widget - ).build(); - } - public void playing() { - StopRevealer.setRevealChild(true); - playPauseButton.setName("pause"); - playPauseButton.setIconName("media-playback-pause-symbolic"); - } - public void paused() { - StopRevealer.setRevealChild(true); - playPauseButton.setName("play"); - playPauseButton.setIconName("media-playback-start-symbolic"); - } - public void idle() { - StopRevealer.setRevealChild(false); - playPauseButton.setName("play"); - playPauseButton.setIconName("media-playback-start-symbolic"); - } - public void setControlButtons(StatusUpdate statusUpdate, Map.Entry name) { - controlButtonsRevealer.setRevealChild(false); - if (statusUpdate.isNotConnected()) { - return; - } - if (statusUpdate.isFileLoaded()) { - if (!statusUpdate.getFileSelected().equals(name.getKey())) { - idle(); - controlButtonsRevealer.setRevealChild(true); - return; - } - switch (statusUpdate.getFileState()) { - case YAMLMessage.FILE_STATE.playing -> playing(); - case YAMLMessage.FILE_STATE.paused -> paused(); - default -> idle(); - } - } else idle(); - controlButtonsRevealer.setRevealChild(true); - } - -} +} \ No newline at end of file diff --git a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/AnimationMenu.java b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/AnimationMenu.java index ed0a8dcd..92c199f9 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/AnimationMenu.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/AnimationMenu.java @@ -2,6 +2,7 @@ import com.toxicstoxm.LEDSuite.Constants; import com.toxicstoxm.LEDSuite.LEDSuite; +import com.toxicstoxm.LEDSuite.logging.Logger; import com.toxicstoxm.LEDSuite.yaml_factory.wrappers.menu_wrappers.Container; import lombok.Getter; import lombok.Setter; @@ -294,19 +295,17 @@ public LEDSuiteWidget deserialize(Configuration yaml, String id, String path) { try { result = type.deserialize(this, yaml, id); } catch (ConversionException | IllegalArgumentException | NoSuchElementException | NullPointerException e) { - LEDSuite.logger.error(id + "Failed to deserialize " + type + " from yaml! Error message: " + e.getMessage()); + LEDSuite.logger.warn(id + "Failed to deserialize " + type + " from yaml! Error message: " + LEDSuite.logger.getErrorMessage(e)); LEDSuite.logger.warn("Replacing entry with default missing value placeholder!"); LEDSuite.logger.debug("Possible causes: missing or malformed values"); - LEDSuite.logger.displayError(e); Configuration config = new YAMLConfiguration(); - config.setProperty(Constants.Network.YAML.MENU.WIDGET_CONTENT, "Failed to deserialize! Error message: " + e.getMessage()); + config.setProperty(Constants.Network.YAML.MENU.WIDGET_CONTENT, "Failed to deserialize! Error message: " + LEDSuite.logger.getErrorMessage(e)); try { result = result.deserialize(config, id, "missing-value-" + System.currentTimeMillis()); } catch (ConversionException | IllegalArgumentException | NoSuchElementException | NullPointerException ex) { - LEDSuite.logger.displayError(e); - LEDSuite.logger.fatal("FAILED TO DISPLAY MISSING VALUE PLACEHOLDER!"); - LEDSuite.getInstance().exit(1); + LEDSuite.logger.fatal("Failed to display missing value placeholder! " + LEDSuite.logger.getErrorMessage(e)); + LEDSuite.getInstance().exit(5); } } return result; diff --git a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLMessage.java b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLMessage.java index bb47953d..8149eb22 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLMessage.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLMessage.java @@ -158,9 +158,17 @@ public enum REPLY_TYPE { menu } + @Getter public enum FILE_STATE { - playing, - paused + playing("file_state_playing"), + paused("file_state_paused"); + + final String i18nKey; + + FILE_STATE(String i18nKey) { + this.i18nKey = i18nKey; + } + } public String getPacketTypeV() { diff --git a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLSerializer.java b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLSerializer.java index 7d65929e..79db5c2b 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLSerializer.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/YAMLSerializer.java @@ -52,6 +52,7 @@ protected static YAMLConfiguration serializeMenuReplyYAML(YAMLMessage yamlMessag LEDSuite.logger.fatal("Unsupported operation: Tried to serialize menu yaml request!"); LEDSuite.logger.warn("This wont be implemented since the front-end never sends menus back to the back-end!"); LEDSuite.logger.warn("If you want to implement it you can do so by contributing to the project on GitHub!"); + LEDSuite.getInstance().exit(6); } if (menuYaml == null) return null; if (yamlMessage.getPacketTypeV() != null) menuYaml.setProperty(Constants.Network.YAML.PACKET_TYPE, yamlMessage.getPacketTypeV()); diff --git a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/wrappers/message_wrappers/StatusUpdate.java b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/wrappers/message_wrappers/StatusUpdate.java index a1853381..a4fd9282 100644 --- a/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/wrappers/message_wrappers/StatusUpdate.java +++ b/src/main/java/com/toxicstoxm/LEDSuite/yaml_factory/wrappers/message_wrappers/StatusUpdate.java @@ -1,5 +1,6 @@ package com.toxicstoxm.LEDSuite.yaml_factory.wrappers.message_wrappers; +import com.toxicstoxm.LEDSuite.LEDSuite; import com.toxicstoxm.LEDSuite.yaml_factory.YAMLMessage; import lombok.Getter; @@ -110,14 +111,14 @@ public int hashCode() { public String minimal() { StringBuilder sb = new StringBuilder(); - sb.append("State: "); + sb.append(LEDSuite.i18n("status_minimal_state")).append(": "); if (this.isNotConnected()) { - sb.append("not connected"); + sb.append(LEDSuite.i18n("status_minimal_not_connected")); } else { if (isFileLoaded) { - sb.append(fileState.name()); - sb.append(" | Filename: ").append(fileSelected); - } else sb.append("idle"); + sb.append(LEDSuite.i18n(fileState.getI18nKey())); + sb.append(" | ").append(LEDSuite.i18n("status_minimal_filename")).append(": ").append(fileSelected); + } else sb.append(LEDSuite.i18n("status_minimal_idle")); } return sb.toString(); } diff --git a/src/main/resources/LEDSuite.properties b/src/main/resources/LEDSuite.properties index 11b4348c..b346e9c4 100644 --- a/src/main/resources/LEDSuite.properties +++ b/src/main/resources/LEDSuite.properties @@ -1 +1,23 @@ -greeting=hello \ No newline at end of file +developers= ToxicStoxm https://github.com/ToxicStoxm/ §\ + CraftBukkit GitHub Repo https://hub.spigotmc.org/stash/projects/spigot/repos/craftbukkit/browse + +artists= Hannes Campidell https://gitlab.com/hannescam §\ + ToxicStoxm https://github.com/ToxicStoxm/ §\ + GNOME Foundation https://gnome.org/ + +translators= ToxicStoxm https://github.com/ToxicStoxm/ \n\ + Hannes Campidell https://gitlab.com/hannescam/ + +special_thanks= Jan-Willem Harmannij https://github.com/jwharm §\ + GNOME Foundation https://gnome.org/ + +release_notes= \ +

This is our first stable release. Please report any bugs on the projects GitHub

\ +
    \ +
  • Added CLI arguments for all config options that are set on startup
  • \ +
  • Changed the startup process to support CLI arguments
  • \ +
  • Added new config options
  • \ +
  • Fixed a few bugs
  • \ +
  • Documentation (7%)
  • \ +
  • Code cleanup (12%)
  • \ +
\ No newline at end of file diff --git a/src/main/resources/LEDSuite_de_DE.properties b/src/main/resources/LEDSuite_de_DE.properties index a6c39d02..5dab81ca 100644 --- a/src/main/resources/LEDSuite_de_DE.properties +++ b/src/main/resources/LEDSuite_de_DE.properties @@ -1 +1,99 @@ -greeting=hallo \ No newline at end of file +# General +error= Fehler +file_state_playing= aktiv +file_state_paused= pausiert +lid_state_closed= geschlossen +lid_state_open= offen + +# Window + +# Header Bar + +# Main Manu Button +main_menu_button_label= Hauptmenü +main_menu_button_tooltip= Hauptmenü + +# Hamburger Menu +status_row= Status +settings_row= Einstellungen +shortcuts_row= Tastenkürzel +about_row= Über %APP_NAME% + +# Sidebar + +# Files +file_management_header= Dateiverwaltung +add_file_row= Datei hinzufüghen +add_file_row_tooltip= Datei auf Backend hochladen + +# Animations +animations_header= Animationen +animations_tooltip= Öffne %ANIMATION_NAME% Einstellungs Menü + +# Status Bar +status_button= Status +status_minimal_state= Zustand +status_minimal_not_connected= nicht verbunden +status_minimal_filename= Dateiname +status_minimal_idle= inaktiv + + +# Shortcut Dialog +# Shortcuts +shortcut_section_general= Allgemein +shortcut_section_main_menu= Hauptmenü +sidebar_toggle_shortcut= Seitenleiste umschalten +status_dialog_shortcut= Öffne Status Dialog +settings_dialog_shortcut= Öffne Einstellungs Dialog +shortcut_dialog_shortcut= Öffne Diesen +about_dialog_shortcut= Öffne Über Dialog + + +# Add File Dialog +add_file_dialog_title= Datei +filename_row_title= Ausgewählte Datei +file_dialog_title= Datei zum hochladen auswählen +add_file_dialog_auto_play_button_title= Animation nach dem hochladen starten +add_file_dialog_auto_play_button_tooltip= Wenn aktiviert, wird die Animation automatisch gestartet nachdem die Datei fertig Hochgeladen wurde +add_file_dialog_stats_row_title= Hochladestatistiken +add_file_dialog_speed_row_title= Geschwindigkeit +add_file_dialog_eta_row_title= Verbleibende Zeit +add_file_dialog_autostart_request_error= Fehler: Konnte die Animation nicht automatisch starten! +add_file_dialog_upload_button_label= Hochladen +add_file_dialog_invalid_file_type_error= Fehler: Ungültiger Dateityp ausgewählt! +add_file_dialog_invalid_file_error= Ungültige Datei für den Pfad: %PATH% +add_file_dialog_failed_to_send_error= Konnte die Datei nicht senden! + + +# Settings dialog +settings_dialog_title= Einstellungen +settings_dialog_generaL_settings_title= Allgemeine Einstellungen +settings_dialog_status_bar_toggle_title= Statusleiste +settings_dialog_status_bar_toggle_tooltip= Schalte die kleine Statusleiste auf dem Hauptfenster um. +settings_dialog_server_settings_backend_title= Backend Einstellungen +settings_dialog_brightness_slider_title= LED Helligkeit +settings_dialog_ipv4_title= IPv4 +settings_dialog_ipv4_server_unreachable_error= Server ist nicht erreichbar! +settings_dialog_ipv4_fallback_connect= Verbindung fehlgeschlagen! Wieder zum vorherigen Server verbunden: '%PREVIOUS_HOST%' +settings_dialog_port_title= Port +settings_dialog_port_invalid_port_error= Ungültiger Port! + + +# Status dialog +status_dialog_title= Server Status +status_dialog_current_draw_title= Stromverbrauch +status_dialog_voltage_title= Spannung +status_dialog_current_file_title= Ausgewählte Datei +status_dialog_file_state_title= Momentaner Zustand +status_dialog_lid_state_title= Abdeckung +status_dialog_power_section_title= Leistung +status_dialog_animation_section_title= Animation +status_dialog_general_section= Allgemein +status_dialog_not_connected= Nicht mit dem Server verbunden! +status_dialog_not_connected_causes_title= Mögliche Ursachen +status_dialog_not_connected_causes_subtitle= Warte auf Rückmeldung, Verbindung fehlgeschlagen wegen einen ungültigen Hostname / Port, Verbindung wurde vom Server abgelehnt +status_dialog_no_animation_loaded= Keine Animation ausgewählt! + + +# About dialog +special_thanks_title= Vielen Dank an \ No newline at end of file diff --git a/src/main/resources/LEDSuite_en_US.properties b/src/main/resources/LEDSuite_en_US.properties index 11b4348c..5e88219c 100644 --- a/src/main/resources/LEDSuite_en_US.properties +++ b/src/main/resources/LEDSuite_en_US.properties @@ -1 +1,99 @@ -greeting=hello \ No newline at end of file +# General +error= Error +file_state_playing= playing +file_state_paused= paused +lid_state_closed= closed +lid_state_open= open + +# Window + +# Header Bar + +# Main Manu Button +main_menu_button_label= Main Menu +main_menu_button_tooltip= Main Menu + +# Hamburger Menu +status_row= Server Status +settings_row= Preferences +shortcuts_row= Keyboard Shortcuts +about_row= About %APP_NAME% + +# Sidebar + +# Files +file_management_header= File Management +add_file_row= Add File +add_file_row_tooltip= Upload a file to backend + +# Animations +animations_header= Animations +animations_tooltip= Open %ANIMATION_NAME% settings menu + +# Status Bar +status_button= Status +status_minimal_state= State +status_minimal_not_connected= not connected +status_minimal_filename= Filename +status_minimal_idle= idle + + +# Shortcut dialog +# Shortcuts +shortcut_section_general= General +shortcut_section_main_menu= Main Menu +sidebar_toggle_shortcut= Toggle sidebar +status_dialog_shortcut= Open the status dialog +settings_dialog_shortcut= Open the settings dialog +shortcut_dialog_shortcut= Open this dialog +about_dialog_shortcut= Open the about dialog + + +# Add File dialog +add_file_dialog_title= File +filename_row_title= Selected File +file_dialog_title=Pick a file to upload +add_file_dialog_auto_play_button_title= Start animation after upload +add_file_dialog_auto_play_button_tooltip= If true, the animation is automatically started after the upload finishes +add_file_dialog_stats_row_title= Upload Statistics +add_file_dialog_speed_row_title= Speed +add_file_dialog_eta_row_title= ETA +add_file_dialog_autostart_request_error= Error: Failed to autostart animation! +add_file_dialog_upload_button_label= Upload +add_file_dialog_invalid_file_type_error= Error: Invalid file type selected! +add_file_dialog_invalid_file_error= Invalid File for the path: %PATH% +add_file_dialog_failed_to_send_error= Failed to send the file! + + +# Settings dialog +settings_dialog_title= Settings +settings_dialog_generaL_settings_title= General Settings +settings_dialog_status_bar_toggle_title= Status Bar +settings_dialog_status_bar_toggle_tooltip= Toggle the small status bar on the main window. +settings_dialog_server_settings_backend_title= Backend Settings +settings_dialog_brightness_slider_title= LED Brightness +settings_dialog_ipv4_title= IPv4 +settings_dialog_ipv4_server_unreachable_error= Server unreachable! +settings_dialog_ipv4_fallback_connect= Connection failed! Reconnected to the previous host: '%PREVIOUS_HOST%' +settings_dialog_port_title= Port +settings_dialog_port_invalid_port_error= Invalid Port! + + +# Status dialog +status_dialog_title= Server Status +status_dialog_current_draw_title= Current Draw +status_dialog_voltage_title= Voltage +status_dialog_current_file_title= Current File +status_dialog_file_state_title= Current State +status_dialog_lid_state_title= Lid State +status_dialog_power_section_title= Power +status_dialog_animation_section_title= Animation +status_dialog_general_section= General +status_dialog_not_connected= Not connected to Server! +status_dialog_not_connected_causes_title= Possible causes +status_dialog_not_connected_causes_subtitle= Waiting for response, Connection failed due to invalid host / port, Connection refused by host +status_dialog_no_animation_loaded= No animation selected! + + +# About dialog +special_thanks_title= Special thanks to \ No newline at end of file diff --git a/src/main/resources/LEDSuite_it_IT.properties b/src/main/resources/LEDSuite_it_IT.properties index 623dea39..5320f1c2 100644 --- a/src/main/resources/LEDSuite_it_IT.properties +++ b/src/main/resources/LEDSuite_it_IT.properties @@ -1 +1,99 @@ -greeting=ciao \ No newline at end of file +# General +error= Errore +file_state_playing= attivo +file_state_paused= in pausa +lid_state_closed= chiuso +lid_state_open= aperto + +# Window + +# Header Bar + +# Main Manu Button +main_menu_button_label= Menu principale +main_menu_button_tooltip= Menu principale + +# Hamburger Menu +status_row= Stato +settings_row= Impostazioni +shortcuts_row= Scorciatoie da tastiera +about_row= Tramite %APP_NAME% + +# Sidebar + +# Files +file_management_header= Gestione dei file +add_file_row= Aggiungi file +add_file_row_tooltip= Caricare file sul Backend + +# Animations +animations_header= Animazioni +animations_tooltip= Apri il menu di impostazione di %ANIMATION_NAME% + +# Status Bar +status_button= Stato +status_minimal_state= Condizione +status_minimal_not_connected= non collegato +status_minimal_filename= Nome del file +status_minimal_idle= inattivo + + +# Shortcut Dialog +# Shortcuts +shortcut_section_general= Generale +shortcut_section_main_menu= Menu principale +sidebar_toggle_shortcut= Alterna la barra laterale +status_dialog_shortcut= Apri il dialogo di stato +settings_dialog_shortcut= Apri il dialogo di impostazione +shortcut_dialog_shortcut= Apri quel dialogo +about_dialog_shortcut= Apri tramite dialogo + + +# Add File Dialog +add_file_dialog_title= File +filename_row_title= File selezionato +file_dialog_title= Selezionare file per caricare +add_file_dialog_auto_play_button_title= Iniziare l'animazione dopo aver caricato +add_file_dialog_auto_play_button_tooltip= Se attivo, l'animazione inizia automaticamente dopo che il caricamento finisce +add_file_dialog_stats_row_title= Statistiche del caricamento +add_file_dialog_speed_row_title= Velocità +add_file_dialog_eta_row_title= Tempo rimanente +add_file_dialog_autostart_request_error= Errore: L'animazione non poteva iniziare automaticamente! +add_file_dialog_upload_button_label= Caricare +add_file_dialog_invalid_file_type_error= Errore: Selezionato tipo del file invalido! +add_file_dialog_invalid_file_error= File invalido per il sentiero: %PATH% +add_file_dialog_failed_to_send_error= Il file non poteva essere inviato! + + +# Settings dialog +settings_dialog_title= Impostazioni +settings_dialog_generaL_settings_title= Impostazioni generali +settings_dialog_status_bar_toggle_title= Barra dello stato +settings_dialog_status_bar_toggle_tooltip= Alterna la barra di stato piccola della finestra principale. +settings_dialog_server_settings_backend_title= Impostazioni del Backend +settings_dialog_brightness_slider_title= Luminosità dei LED +settings_dialog_ipv4_title= IPv4 +settings_dialog_ipv4_server_unreachable_error= Server non è raggiungibile! +settings_dialog_ipv4_fallback_connect= Connessione fallita! Ricollegato al server precedente: '%PREVIOUS_HOST%' +settings_dialog_port_title= Port +settings_dialog_port_invalid_port_error= Port invalido! + + +# Status dialog +status_dialog_title= Stato del Server +status_dialog_current_draw_title= Consumo di energia +status_dialog_voltage_title= Tensione +status_dialog_current_file_title= File selezionato +status_dialog_file_state_title= Stato attuale +status_dialog_lid_state_title= Copertura +status_dialog_power_section_title= Potenza +status_dialog_animation_section_title= Animazioni +status_dialog_general_section= Generale +status_dialog_not_connected= Non collegato con il server! +status_dialog_not_connected_causes_title= Possibile cause +status_dialog_not_connected_causes_subtitle= Aspettando risposta, connessione fallita a cause di un Hostname / Port invalido, connessione rifiutata dal server +status_dialog_no_animation_loaded= Nessuna animazione scelta! + + +# About dialog +special_thanks_title= Grazie mille a \ No newline at end of file diff --git a/src/main/resources/config.yaml b/src/main/resources/config.yaml index 310bd383..23296904 100644 --- a/src/main/resources/config.yaml +++ b/src/main/resources/config.yaml @@ -1,5 +1,5 @@ # Changes made to this config file will be overwritten by the application on shutdown -# If you want to change anything, first shutdown the application, adjust the settings and start the application again +# If you want to change anything, first shutdown the application, adjust the settings and start the application again, # Any changes made to this config file can have an impact on performance or break the application # If you want to reset the values to default, delete this file or start the application with the -R argument Local-Settings: @@ -33,7 +33,8 @@ Local-Settings: # Trace color is used for color coding metadata (file name and line number) # DEFAULT: '0x009432' = Dark Green TRACE: '0x009432' - # Logger Settings + # Set how many stack traces are allowed to be cached, before the application starts overwriting the oldest one + Max-Stack-Traces-Cached: 100 # higher log levels include everything from lower log levels # Log - Levels: # 0 --> OFF: logger turned off @@ -52,7 +53,7 @@ Local-Settings: # If the application log should be written to a log file # Enabling, this can impact performance Enabled: false - # If set to true, all log messages will be written to the log file regardless of Log-Level above + # If set to true, all log messages will be written to the log file regardless of the Log-Level above # If set to false, only log messages with the above-specified Log-Level will be written to the log file Log-Level-All: true # Set how many log files are allowed to exist, before the application starts overwriting the oldest one diff --git a/src/main/resources/metadata/release-notes.html b/src/main/resources/metadata/release-notes.html deleted file mode 100644 index 000e28a6..00000000 --- a/src/main/resources/metadata/release-notes.html +++ /dev/null @@ -1,9 +0,0 @@ -

This is our fourth pre-release. Please report any bugs on the projects GitHub

-
    -
  • Added CLI arguments for all config options that are set on startup
  • -
  • Changed the startup process to support CLI arguments
  • -
  • Added new config options
  • -
  • Fixed a few bugs
  • -
  • Documentation (7%)
  • -
  • Code cleanup (12%)
  • -
\ No newline at end of file