From e0037e74aceda1f71dd51b69b9fe73d934824252 Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Sat, 28 Feb 2026 07:56:51 +0100 Subject: [PATCH 01/17] modal dialog --- .vscode/launch.json | 16 +- pom.xml | 94 +- .../java/backupmanager/BackupOperations.java | 4 +- src/main/java/backupmanager/Charts.java | 28 - .../Controllers/AppController.java | 2 +- .../Controllers/BackupEntryController.java | 4 +- .../Controllers/PreferenceController.java | 2 +- .../Dialogs/BackupEntryDialog.java | 2 +- .../Dialogs/PreferencesDialog.java | 2 +- .../Entities/ZippingContext.java | 4 +- .../backupmanager/Helpers/BackupHelper.java | 4 +- src/main/java/backupmanager/MainApp.java | 23 +- .../Services/RunningBackupService.java | 2 +- .../backupmanager/Table/TableDataManager.java | 2 +- .../backupmanager/Utils/DemoPreferences.java | 136 +++ .../java/backupmanager/Utils/FolderUtils.java | 2 +- .../java/backupmanager/Utils/SystemForm.java | 17 + .../java/backupmanager/Utils/UndoRedo.java | 94 ++ .../table/CheckBoxTableHeaderRenderer.java | 86 ++ .../Utils/table/CheckboxCellRenderer.java | 35 + .../Utils/table/ProgressBarRenderer.java | 26 + .../Utils/table/TableHeaderAlignment.java | 35 + .../backupmanager/Widgets/SideMenuPanel.java | 263 ----- .../java/backupmanager/component/About.java | 108 ++ .../component/AccentColorIcon.java | 34 + .../component/EmptyModalBorder.java | 64 ++ .../component/FormSearchButton.java | 37 + .../component/FormSearchPanel.java | 557 +++++++++ .../backupmanager/component/RefreshLine.java | 58 + .../component/ToolBarSelection.java | 28 + .../component/chart/BarChart.java | 62 + .../component/chart/CandlestickChart.java | 154 +++ .../component/chart/DefaultChartPanel.java | 81 ++ .../component/chart/PieChart.java | 52 + .../component/chart/SpiderChart.java | 96 ++ .../component/chart/TimeSeriesChart.java | 155 +++ .../renderer/ChartDeviationStepRenderer.java | 24 + .../renderer/ChartStackedXYBarRenderer.java | 15 + .../chart/renderer/ChartXYBarRenderer.java | 18 + .../chart/renderer/ChartXYCurveRenderer.java | 29 + .../renderer/ChartXYDifferenceRenderer.java | 21 + .../chart/renderer/ChartXYLineRenderer.java | 13 + .../chart/renderer/bar/ChartBarRenderer.java | 20 + .../other/ChartCandlestickRenderer.java | 34 + .../chart/themes/ChartDrawingSupplier.java | 88 ++ .../component/chart/themes/ColorThemes.java | 87 ++ .../chart/themes/DefaultChartTheme.java | 164 +++ .../chart/utils/CustomCrosshairToolTip.java | 32 + .../utils/DateCrosshairLabelGenerator.java | 26 + .../chart/utils/MultiXYTextAnnotation.java | 545 +++++++++ .../utils/ToolBarCategoryOrientation.java | 14 + .../utils/ToolBarTimeSeriesChartRenderer.java | 27 + .../component/dashboard/CardBox.java | 44 + .../component/dashboard/CardItem.java | 64 ++ .../backupmanager/forms/FormDashboard.java | 233 ++++ .../java/backupmanager/forms/FormSetting.java | 437 +++++++ .../java/backupmanager/forms/FormTable.java | 363 ++++++ .../backupmanager/frames/BackupManager.java | 27 + .../{GUI => frames}/BackupManagerGUI.form | 0 .../{GUI => frames}/BackupManagerGUI.java | 23 +- .../{GUI => frames}/BackupProgressGUI.form | 0 .../{GUI => frames}/BackupProgressGUI.java | 4 +- .../Controllers/BackupManagerController.java | 7 +- .../Controllers/BackupMenuController.java | 6 +- .../Controllers/BackupPopupController.java | 6 +- .../Controllers/BackupProgressController.java | 2 +- src/main/java/backupmanager/frames/Login.java | 92 ++ .../backupmanager/menu/MyDrawerBuilder.java | 193 ++++ .../backupmanager/menu/MyMenuValidation.java | 12 + .../java/backupmanager/sample/SampleData.java | 423 +++++++ .../sample/csv/CSVDataReader.java | 93 ++ .../backupmanager/sample/csv/Pageable.java | 40 + .../backupmanager/sample/csv/ResponseCSV.java | 10 + .../sample/csv/ResponsePageable.java | 15 + .../simple/SimpleInputForms.java | 116 ++ .../java/backupmanager/svg/SVGButton.java | 5 + .../backupmanager/svg/SVGIconUIColor.java | 42 + .../java/backupmanager/system/AllForms.java | 44 + src/main/java/backupmanager/system/Form.java | 36 + .../backupmanager/system/FormManager.java | 122 ++ .../java/backupmanager/system/FormSearch.java | 98 ++ .../java/backupmanager/system/MainForm.java | 130 +++ .../themes/ListCellTitledBorder.java | 74 ++ .../backupmanager/themes/PanelThemes.java | 209 ++++ .../java/backupmanager/themes/ThemesInfo.java | 25 + .../backupmanager/themes/ThemesManager.java | 57 + src/main/resources/data/customers-1000.csv | 1001 +++++++++++++++++ src/main/resources/drawer/icon/about.svg | 3 + src/main/resources/drawer/icon/chart.svg | 3 + src/main/resources/drawer/icon/dashboard.svg | 3 + src/main/resources/drawer/icon/donate.svg | 2 + src/main/resources/drawer/icon/forms.svg | 3 + src/main/resources/drawer/icon/help.svg | 5 + src/main/resources/drawer/icon/history.svg | 54 + src/main/resources/drawer/icon/info.svg | 4 + src/main/resources/drawer/icon/setting.svg | 3 + src/main/resources/drawer/logo.png | Bin 0 -> 9601 bytes src/main/resources/icons/add.svg | 3 + src/main/resources/icons/clear.svg | 3 + src/main/resources/icons/close.svg | 3 + src/main/resources/icons/color.svg | 3 + src/main/resources/icons/copy.svg | 3 + .../resources/icons/dashboard/customer.svg | 3 + .../resources/icons/dashboard/expense.svg | 3 + src/main/resources/icons/dashboard/income.svg | 3 + src/main/resources/icons/dashboard/profit.svg | 3 + src/main/resources/icons/delete.svg | 8 + src/main/resources/icons/donate.svg | 2 + src/main/resources/icons/edit.svg | 4 + src/main/resources/icons/eye.svg | 5 + src/main/resources/icons/favorite.svg | 3 + src/main/resources/icons/favorite_filled.svg | 3 + src/main/resources/icons/folder.svg | 12 + src/main/resources/icons/history.svg | 54 + src/main/resources/icons/info.svg | 4 + src/main/resources/icons/menu.svg | 3 + src/main/resources/icons/redo.svg | 3 + src/main/resources/icons/refresh.svg | 3 + src/main/resources/icons/search.svg | 3 + src/main/resources/icons/support.svg | 6 + src/main/resources/icons/timer.svg | 2 + src/main/resources/icons/undo.svg | 3 + .../resources/themes/FlatDarkLaf.properties | 1 + src/main/resources/themes/FlatLaf.properties | 48 + .../resources/themes/FlatLightLaf.properties | 1 + src/main/resources/themes/themes.json | 358 ++++++ 126 files changed, 7842 insertions(+), 370 deletions(-) delete mode 100644 src/main/java/backupmanager/Charts.java create mode 100644 src/main/java/backupmanager/Utils/DemoPreferences.java create mode 100644 src/main/java/backupmanager/Utils/SystemForm.java create mode 100644 src/main/java/backupmanager/Utils/UndoRedo.java create mode 100644 src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java create mode 100644 src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java create mode 100644 src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java create mode 100644 src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java delete mode 100644 src/main/java/backupmanager/Widgets/SideMenuPanel.java create mode 100644 src/main/java/backupmanager/component/About.java create mode 100644 src/main/java/backupmanager/component/AccentColorIcon.java create mode 100644 src/main/java/backupmanager/component/EmptyModalBorder.java create mode 100644 src/main/java/backupmanager/component/FormSearchButton.java create mode 100644 src/main/java/backupmanager/component/FormSearchPanel.java create mode 100644 src/main/java/backupmanager/component/RefreshLine.java create mode 100644 src/main/java/backupmanager/component/ToolBarSelection.java create mode 100644 src/main/java/backupmanager/component/chart/BarChart.java create mode 100644 src/main/java/backupmanager/component/chart/CandlestickChart.java create mode 100644 src/main/java/backupmanager/component/chart/DefaultChartPanel.java create mode 100644 src/main/java/backupmanager/component/chart/PieChart.java create mode 100644 src/main/java/backupmanager/component/chart/SpiderChart.java create mode 100644 src/main/java/backupmanager/component/chart/TimeSeriesChart.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java create mode 100644 src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java create mode 100644 src/main/java/backupmanager/component/chart/themes/ColorThemes.java create mode 100644 src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java create mode 100644 src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java create mode 100644 src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java create mode 100644 src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java create mode 100644 src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java create mode 100644 src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java create mode 100644 src/main/java/backupmanager/component/dashboard/CardBox.java create mode 100644 src/main/java/backupmanager/component/dashboard/CardItem.java create mode 100644 src/main/java/backupmanager/forms/FormDashboard.java create mode 100644 src/main/java/backupmanager/forms/FormSetting.java create mode 100644 src/main/java/backupmanager/forms/FormTable.java create mode 100644 src/main/java/backupmanager/frames/BackupManager.java rename src/main/java/backupmanager/{GUI => frames}/BackupManagerGUI.form (100%) rename src/main/java/backupmanager/{GUI => frames}/BackupManagerGUI.java (99%) rename src/main/java/backupmanager/{GUI => frames}/BackupProgressGUI.form (100%) rename src/main/java/backupmanager/{GUI => frames}/BackupProgressGUI.java (98%) rename src/main/java/backupmanager/{GUI => frames}/Controllers/BackupManagerController.java (97%) rename src/main/java/backupmanager/{GUI => frames}/Controllers/BackupMenuController.java (96%) rename src/main/java/backupmanager/{GUI => frames}/Controllers/BackupPopupController.java (98%) rename src/main/java/backupmanager/{GUI => frames}/Controllers/BackupProgressController.java (95%) create mode 100644 src/main/java/backupmanager/frames/Login.java create mode 100644 src/main/java/backupmanager/menu/MyDrawerBuilder.java create mode 100644 src/main/java/backupmanager/menu/MyMenuValidation.java create mode 100644 src/main/java/backupmanager/sample/SampleData.java create mode 100644 src/main/java/backupmanager/sample/csv/CSVDataReader.java create mode 100644 src/main/java/backupmanager/sample/csv/Pageable.java create mode 100644 src/main/java/backupmanager/sample/csv/ResponseCSV.java create mode 100644 src/main/java/backupmanager/sample/csv/ResponsePageable.java create mode 100644 src/main/java/backupmanager/simple/SimpleInputForms.java create mode 100644 src/main/java/backupmanager/svg/SVGIconUIColor.java create mode 100644 src/main/java/backupmanager/system/AllForms.java create mode 100644 src/main/java/backupmanager/system/Form.java create mode 100644 src/main/java/backupmanager/system/FormManager.java create mode 100644 src/main/java/backupmanager/system/FormSearch.java create mode 100644 src/main/java/backupmanager/system/MainForm.java create mode 100644 src/main/java/backupmanager/themes/ListCellTitledBorder.java create mode 100644 src/main/java/backupmanager/themes/PanelThemes.java create mode 100644 src/main/java/backupmanager/themes/ThemesInfo.java create mode 100644 src/main/java/backupmanager/themes/ThemesManager.java create mode 100644 src/main/resources/data/customers-1000.csv create mode 100644 src/main/resources/drawer/icon/about.svg create mode 100644 src/main/resources/drawer/icon/chart.svg create mode 100644 src/main/resources/drawer/icon/dashboard.svg create mode 100644 src/main/resources/drawer/icon/donate.svg create mode 100644 src/main/resources/drawer/icon/forms.svg create mode 100644 src/main/resources/drawer/icon/help.svg create mode 100644 src/main/resources/drawer/icon/history.svg create mode 100644 src/main/resources/drawer/icon/info.svg create mode 100644 src/main/resources/drawer/icon/setting.svg create mode 100644 src/main/resources/drawer/logo.png create mode 100644 src/main/resources/icons/add.svg create mode 100644 src/main/resources/icons/clear.svg create mode 100644 src/main/resources/icons/close.svg create mode 100644 src/main/resources/icons/color.svg create mode 100644 src/main/resources/icons/copy.svg create mode 100644 src/main/resources/icons/dashboard/customer.svg create mode 100644 src/main/resources/icons/dashboard/expense.svg create mode 100644 src/main/resources/icons/dashboard/income.svg create mode 100644 src/main/resources/icons/dashboard/profit.svg create mode 100644 src/main/resources/icons/delete.svg create mode 100644 src/main/resources/icons/donate.svg create mode 100644 src/main/resources/icons/edit.svg create mode 100644 src/main/resources/icons/eye.svg create mode 100644 src/main/resources/icons/favorite.svg create mode 100644 src/main/resources/icons/favorite_filled.svg create mode 100644 src/main/resources/icons/folder.svg create mode 100644 src/main/resources/icons/history.svg create mode 100644 src/main/resources/icons/info.svg create mode 100644 src/main/resources/icons/menu.svg create mode 100644 src/main/resources/icons/redo.svg create mode 100644 src/main/resources/icons/refresh.svg create mode 100644 src/main/resources/icons/search.svg create mode 100644 src/main/resources/icons/support.svg create mode 100644 src/main/resources/icons/timer.svg create mode 100644 src/main/resources/icons/undo.svg create mode 100644 src/main/resources/themes/FlatDarkLaf.properties create mode 100644 src/main/resources/themes/FlatLaf.properties create mode 100644 src/main/resources/themes/FlatLightLaf.properties create mode 100644 src/main/resources/themes/themes.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d83c636..2c9318ec 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,20 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "BackupManager", + "request": "launch", + "mainClass": "backupmanager.frames.BackupManager", + "projectName": "backupmanager" + }, + { + "type": "java", + "name": "MainApp", + "request": "launch", + "mainClass": "backupmanager.MainApp", + "projectName": "backupmanager" + }, { "type": "java", "name": "EncryptConfigFile", @@ -68,4 +82,4 @@ "args": "--background" } ] -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index dc2ec52c..4cf9d4ed 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,73 @@ + 4.0.0 - backupmanager - BackupManager - 1.0-SNAPSHOT - jar + + + io.github.dj-raven + swing-modal-dialog + 2.6.0 + + backupmanager + + 21 + 21 UTF-8 + + + + Central Portal Snapshots + central-portal-snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + + + + ${parent.groupId} + modal-dialog + ${version} + + + + io.github.dj-raven + swing-datetime-picker + 2.1.4-SNAPSHOT + + + io.github.dj-raven + swing-color-picker + 2.0.0 + + + io.github.dj-raven + swing-pack + 1.0.1-SNAPSHOT + + + org.jfree + jfreechart + 1.5.6 + + + + com.formdev + flatlaf-intellij-themes + 3.7 + + + com.formdev + flatlaf-fonts-roboto + 2.137 + + + com.fifesoft + rsyntaxtextarea + 3.4.0 + + com.google.code.gson @@ -47,22 +107,6 @@ test - - - com.formdev - flatlaf-intellij-themes - 3.5.2 - - - com.formdev - flatlaf - 3.4.1 - - - com.formdev - flatlaf-extras - 3.5.4 - org.netbeans.external AbsoluteLayout @@ -120,12 +164,6 @@ sqlite-jdbc 3.45.2.0 - - - org.jfree - jfreechart - 1.5.4 - @@ -161,7 +199,7 @@ - + diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 0d146c70..8049ea57 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -25,7 +25,6 @@ import backupmanager.Enums.ErrorType; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.BackupManagerGUI; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; import static backupmanager.Helpers.BackupHelper.formatter; @@ -33,8 +32,9 @@ import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; import backupmanager.Table.TableDataManager; -import backupmanager.Utils.FolderUtils; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.frames.BackupManagerGUI; +import backupmanager.utils.FolderUtils; public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); diff --git a/src/main/java/backupmanager/Charts.java b/src/main/java/backupmanager/Charts.java deleted file mode 100644 index 78470ba8..00000000 --- a/src/main/java/backupmanager/Charts.java +++ /dev/null @@ -1,28 +0,0 @@ -package backupmanager; - -import javax.swing.JPanel; - -import org.jfree.chart.ChartFactory; -import org.jfree.chart.ChartPanel; -import org.jfree.chart.JFreeChart; -import org.jfree.data.category.DefaultCategoryDataset; - -public class Charts { - public static void createChart(JPanel panelChart) { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - dataset.addValue(5, "Clicks", "2025-07-10"); - dataset.addValue(8, "Clicks", "2025-07-11"); - dataset.addValue(3, "Clicks", "2025-07-12"); - - JFreeChart chart = ChartFactory.createBarChart( - "Clicks per Giorno", "Data", "Clicks", dataset); - - ChartPanel chartPanel = new ChartPanel(chart); - chartPanel.setPreferredSize(new java.awt.Dimension(700, 500)); - - panelChart.setLayout(new java.awt.BorderLayout()); - panelChart.removeAll(); - panelChart.add(chartPanel, java.awt.BorderLayout.CENTER); - panelChart.validate(); - } -} diff --git a/src/main/java/backupmanager/Controllers/AppController.java b/src/main/java/backupmanager/Controllers/AppController.java index 5766bf73..62098e41 100644 --- a/src/main/java/backupmanager/Controllers/AppController.java +++ b/src/main/java/backupmanager/Controllers/AppController.java @@ -13,11 +13,11 @@ import backupmanager.Entities.Confingurations; import backupmanager.Entities.Subscription; import backupmanager.Enums.ConfigKey; -import backupmanager.GUI.BackupManagerGUI; import backupmanager.Helpers.SubscriptionNotifier; import backupmanager.Json.JSONConfigReader; import backupmanager.Services.BackgroundService; import backupmanager.database.Repositories.SubscriptionRepository; +import backupmanager.frames.BackupManagerGUI; public class AppController { private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); diff --git a/src/main/java/backupmanager/Controllers/BackupEntryController.java b/src/main/java/backupmanager/Controllers/BackupEntryController.java index 8b0f1f7f..57baf0a4 100644 --- a/src/main/java/backupmanager/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/Controllers/BackupEntryController.java @@ -16,11 +16,11 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; -import backupmanager.GUI.BackupManagerGUI; -import backupmanager.GUI.BackupProgressGUI; import backupmanager.Helpers.BackupHelper; import backupmanager.Table.BackupTable; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.frames.BackupManagerGUI; +import backupmanager.frames.BackupProgressGUI; public class BackupEntryController { private static final Logger logger = LoggerFactory.getLogger(BackupEntryController.class); diff --git a/src/main/java/backupmanager/Controllers/PreferenceController.java b/src/main/java/backupmanager/Controllers/PreferenceController.java index 5990480c..ccb00ce4 100644 --- a/src/main/java/backupmanager/Controllers/PreferenceController.java +++ b/src/main/java/backupmanager/Controllers/PreferenceController.java @@ -6,9 +6,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import backupmanager.GUI.BackupManagerGUI; import backupmanager.Managers.ExceptionManager; import backupmanager.Services.PreferenceService; +import backupmanager.frames.BackupManagerGUI; public record PreferenceController (PreferenceService service, BackupManagerGUI mainGui) { private static final Logger logger = LoggerFactory.getLogger(PreferenceController.class); diff --git a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java b/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java index 21f04ead..8d01b81f 100644 --- a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java +++ b/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java @@ -1,6 +1,6 @@ package backupmanager.Dialogs; -import static backupmanager.GUI.BackupManagerGUI.backupTable; +import static backupmanager.frames.BackupManagerGUI.backupTable; import java.time.LocalDateTime; diff --git a/src/main/java/backupmanager/Dialogs/PreferencesDialog.java b/src/main/java/backupmanager/Dialogs/PreferencesDialog.java index 8ad5e30e..4768782b 100644 --- a/src/main/java/backupmanager/Dialogs/PreferencesDialog.java +++ b/src/main/java/backupmanager/Dialogs/PreferencesDialog.java @@ -9,7 +9,7 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Managers.ThemeManager; import backupmanager.Services.PreferenceService; -import backupmanager.GUI.BackupManagerGUI; +import backupmanager.frames.BackupManagerGUI; public class PreferencesDialog extends javax.swing.JDialog { diff --git a/src/main/java/backupmanager/Entities/ZippingContext.java b/src/main/java/backupmanager/Entities/ZippingContext.java index f0ac5706..c56bb1f3 100644 --- a/src/main/java/backupmanager/Entities/ZippingContext.java +++ b/src/main/java/backupmanager/Entities/ZippingContext.java @@ -4,9 +4,9 @@ import javax.swing.JMenuItem; -import backupmanager.GUI.BackupProgressGUI; import backupmanager.Table.BackupTable; -import backupmanager.Utils.FolderUtils; +import backupmanager.frames.BackupProgressGUI; +import backupmanager.utils.FolderUtils; public record ZippingContext ( ConfigurationBackup backup, diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 3e5280e7..cb5a6709 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -17,12 +17,12 @@ import backupmanager.Enums.BackupStatus; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.BackupManagerGUI; -import backupmanager.GUI.BackupProgressGUI; import backupmanager.Table.BackupTable; import backupmanager.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.frames.BackupManagerGUI; +import backupmanager.frames.BackupProgressGUI; public class BackupHelper { diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index aa247b62..6246ff7b 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -1,20 +1,28 @@ package backupmanager; +import java.awt.Font; import java.io.IOException; import java.util.Arrays; +import javax.swing.UIManager; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; +import com.formdev.flatlaf.util.FontUtils; + import backupmanager.Controllers.AppController; import backupmanager.Entities.Confingurations; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.TranslationLoaderEnum; -import backupmanager.GUI.BackupManagerGUI; import backupmanager.Managers.ExceptionManager; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.ProductionDatabaseInitializer; +import backupmanager.frames.BackupManager; +import backupmanager.utils.DemoPreferences; public class MainApp { private static final String CONFIG = "src/main/resources/res/config/config.json"; @@ -80,9 +88,16 @@ private static void runBackgroundProcess() { } private static void runGui() { - javax.swing.SwingUtilities.invokeLater(() -> { - BackupManagerGUI gui = new BackupManagerGUI(); - gui.showWindow(); + java.awt.EventQueue.invokeLater(() -> { + + DemoPreferences.init(); + FlatRobotoFont.install(); + FlatLaf.registerCustomDefaultsSource(".themes"); + UIManager.put("defaultFont", FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13)); + DemoPreferences.setupLaf(); + + BackupManager frame = new BackupManager(); + frame.setVisible(true); }); } } diff --git a/src/main/java/backupmanager/Services/RunningBackupService.java b/src/main/java/backupmanager/Services/RunningBackupService.java index 96872fdd..e0d7ff30 100644 --- a/src/main/java/backupmanager/Services/RunningBackupService.java +++ b/src/main/java/backupmanager/Services/RunningBackupService.java @@ -9,9 +9,9 @@ import backupmanager.Enums.BackupStatus; import backupmanager.Helpers.BackupHelper; import backupmanager.Helpers.SqlHelper; -import backupmanager.Utils.FolderUtils; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.utils.FolderUtils; public class RunningBackupService { public static Optional getRunningBackupByName(String backupName) { diff --git a/src/main/java/backupmanager/Table/TableDataManager.java b/src/main/java/backupmanager/Table/TableDataManager.java index 235b6b89..e8f50502 100644 --- a/src/main/java/backupmanager/Table/TableDataManager.java +++ b/src/main/java/backupmanager/Table/TableDataManager.java @@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.GUI.BackupManagerGUI; +import backupmanager.frames.BackupManagerGUI; public class TableDataManager { diff --git a/src/main/java/backupmanager/Utils/DemoPreferences.java b/src/main/java/backupmanager/Utils/DemoPreferences.java new file mode 100644 index 00000000..2b4806c3 --- /dev/null +++ b/src/main/java/backupmanager/Utils/DemoPreferences.java @@ -0,0 +1,136 @@ +package backupmanager.utils; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.prefs.Preferences; + +import javax.swing.UIManager; + +import com.formdev.flatlaf.FlatDarculaLaf; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.IntelliJTheme; +import com.formdev.flatlaf.util.LoggingFacade; + +import backupmanager.themes.PanelThemes; + +public class DemoPreferences { + + public static final String PREFERENCES_ROOT_PATH = "/raven-flatlaf-demo"; + public static final String KEY_LAF = "laf"; + public static final String KEY_LAF_THEME = "lafTheme"; + public static final String KEY_ACCENT_COLOR = "accent"; + public static final String KEY_RECENT_SEARCH = "recentSearch"; + public static final String KEY_RECENT_SEARCH_FAVORITE = "recentSearchFavorite"; + + public static final String RESOURCE_PREFIX = "res:"; + + public static final String THEME_UI_KEY = "__RaVen.flatlaf.demo.theme"; + + public static Color accentColor; + + private static Preferences state; + + public static Preferences getState() { + return state; + } + + public static void init() { + state = Preferences.userRoot().node(PREFERENCES_ROOT_PATH); + } + + public static void setupLaf() { + // set look and feel + try { + String lafClassName = state.get(KEY_LAF, FlatDarculaLaf.class.getName()); + String rgbAccentColor = state.get(KEY_ACCENT_COLOR, null); + if (rgbAccentColor != null) { + accentColor = new Color(Integer.parseInt(rgbAccentColor), true); + FlatLaf.setSystemColorGetter(name -> name.equals("accent") ? accentColor : null); + } + + if (IntelliJTheme.ThemeLaf.class.getName().equals(lafClassName)) { + String theme = state.get(KEY_LAF_THEME, ""); + if (theme.startsWith(RESOURCE_PREFIX)) { + IntelliJTheme.setup(PanelThemes.class.getResourceAsStream(PanelThemes.THEMES_PACKAGE + theme.substring(RESOURCE_PREFIX.length()))); + } else { + FlatDarculaLaf.setup(); + } + if (!theme.isEmpty()) { + UIManager.getLookAndFeelDefaults().put(THEME_UI_KEY, theme); + } + } else { + UIManager.setLookAndFeel(lafClassName); + } + } catch (Exception e) { + LoggingFacade.INSTANCE.logSevere(null, e); + FlatDarculaLaf.setup(); + } + UIManager.addPropertyChangeListener(e -> { + if (e.getPropertyName().equals("lookAndFeel")) { + state.put(KEY_LAF, UIManager.getLookAndFeel().getClass().getName()); + } + }); + } + + public static String[] getRecentSearch(boolean favorite) { + String stringArr = state.get(favorite ? KEY_RECENT_SEARCH_FAVORITE : KEY_RECENT_SEARCH, null); + if (stringArr == null || stringArr.trim().isEmpty()) return null; + return stringArr.trim().split(","); + } + + public static void addRecentSearch(String value, boolean favorite) { + String[] oldRecent = getRecentSearch(false); + String[] oldFavorite = getRecentSearch(true); + if (favorite) { + if (oldRecent != null) { + // remove from recent search + List list = new ArrayList<>(Arrays.asList(oldRecent)); + list.remove(value); + state.put(KEY_RECENT_SEARCH, String.join(",", list)); + } + if (oldFavorite != null) { + List list = new ArrayList<>(Arrays.asList(oldFavorite)); + list.remove(value); + list.add(0, value); + state.put(KEY_RECENT_SEARCH_FAVORITE, String.join(",", list)); + } else { + state.put(KEY_RECENT_SEARCH_FAVORITE, value); + } + } else { + if (oldFavorite != null) { + List list = new ArrayList<>(Arrays.asList(oldFavorite)); + if (list.contains(value)) { + return; + } + } + if (oldRecent == null) { + state.put(KEY_RECENT_SEARCH, value); + } else { + List list = new ArrayList<>(Arrays.asList(oldRecent)); + list.remove(value); + list.add(0, value); + state.put(KEY_RECENT_SEARCH, String.join(",", list)); + } + } + } + + public static void removeRecentSearch(String value, boolean favorite) { + String[] oldRecent = getRecentSearch(favorite); + if (oldRecent != null) { + List list = new ArrayList<>(Arrays.asList(oldRecent)); + list.remove(value); + state.put(favorite ? KEY_RECENT_SEARCH_FAVORITE : KEY_RECENT_SEARCH, String.join(",", list)); + } + } + + public static void updateAccentColor(Color color) { + if (color != null) { + String rgb = color.getRGB() + ""; + state.put(KEY_ACCENT_COLOR, rgb); + } else { + state.remove(KEY_ACCENT_COLOR); + } + } +} diff --git a/src/main/java/backupmanager/Utils/FolderUtils.java b/src/main/java/backupmanager/Utils/FolderUtils.java index 75faab37..75beeaac 100644 --- a/src/main/java/backupmanager/Utils/FolderUtils.java +++ b/src/main/java/backupmanager/Utils/FolderUtils.java @@ -1,4 +1,4 @@ -package backupmanager.Utils; +package backupmanager.utils; import java.io.File; import java.io.IOException; diff --git a/src/main/java/backupmanager/Utils/SystemForm.java b/src/main/java/backupmanager/Utils/SystemForm.java new file mode 100644 index 00000000..873d77f7 --- /dev/null +++ b/src/main/java/backupmanager/Utils/SystemForm.java @@ -0,0 +1,17 @@ +package backupmanager.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SystemForm { + + String name() default ""; + + String description() default ""; + + String[] tags() default {}; +} diff --git a/src/main/java/backupmanager/Utils/UndoRedo.java b/src/main/java/backupmanager/Utils/UndoRedo.java new file mode 100644 index 00000000..d5942f2f --- /dev/null +++ b/src/main/java/backupmanager/Utils/UndoRedo.java @@ -0,0 +1,94 @@ +package backupmanager.utils; + +import java.util.Iterator; +import java.util.Stack; + +public class UndoRedo implements Iterable { + + private final Stack stack1; + private final Stack stack2; + + public UndoRedo() { + stack1 = new Stack<>(); + stack2 = new Stack<>(); + } + + public void add(E item) { + stack1.push(item); + stack2.clear(); + } + + public E undo() { + if (stack1.size() > 1) { + stack2.push(stack1.pop()); + return stack1.get(stack1.size() - 1); + } else { + return null; + } + } + + public E redo() { + if (!stack2.isEmpty()) { + E item = stack2.pop(); + stack1.push(item); + return item; + } else { + return null; + } + } + + public E getCurrent() { + if (stack1.isEmpty()) { + return null; + } else { + return stack1.get(stack1.size() - 1); + } + } + + public boolean isUndoAble() { + return stack1.size() > 1; + } + + public boolean isRedoAble() { + return !stack2.empty(); + } + + public void clear() { + stack1.clear(); + stack2.clear(); + } + + public void clearRedo() { + stack2.clear(); + } + + @Override + public Iterator iterator() { + return new MyIterator(); + } + + private class MyIterator implements Iterator { + + private int index = 0; + + @Override + public boolean hasNext() { + if (index < stack1.size()) { + return true; + } else if (index < stack1.size() + stack2.size()) { + return true; + } else { + return false; + } + } + + @Override + public E next() { + if (index < stack1.size()) { + return stack1.elementAt(index++); + } else { + return stack2.elementAt((index++) - stack1.size()); + } + } + } +} diff --git a/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java b/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java new file mode 100644 index 00000000..dd86b00d --- /dev/null +++ b/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java @@ -0,0 +1,86 @@ +package backupmanager.utils.table; + +import com.formdev.flatlaf.FlatClientProperties; + +import javax.swing.*; +import javax.swing.event.TableModelEvent; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class CheckBoxTableHeaderRenderer extends JCheckBox implements TableCellRenderer { + + private final JTable table; + private final int column; + private final TableCellRenderer oldCellRenderer; + + public CheckBoxTableHeaderRenderer(JTable table, int column) { + this.table = table; + this.column = column; + this.oldCellRenderer = table.getTableHeader().getDefaultRenderer(); + init(); + } + + private void init() { + putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + setHorizontalAlignment(SwingConstants.CENTER); + + table.getTableHeader().addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent me) { + if (SwingUtilities.isLeftMouseButton(me)) { + int col = table.columnAtPoint(me.getPoint()); + if (col == column) { + putClientProperty(FlatClientProperties.SELECTED_STATE, null); + setSelected(!isSelected()); + selectedTableRow(isSelected()); + } + } + } + }); + + table.getModel().addTableModelListener((tme) -> { + if (tme.getColumn() == column || tme.getType() == TableModelEvent.DELETE) { + checkRow(); + } + }); + } + + private void checkRow() { + boolean initValue = table.getRowCount() == 0 ? false : (boolean) table.getValueAt(0, column); + for (int i = 1; i < table.getRowCount(); i++) { + boolean v = (boolean) table.getValueAt(i, column); + if (initValue != v) { + putClientProperty(FlatClientProperties.SELECTED_STATE, FlatClientProperties.SELECTED_STATE_INDETERMINATE); + table.getTableHeader().repaint(); + return; + } + } + putClientProperty(FlatClientProperties.SELECTED_STATE, null); + setSelected(initValue); + table.getTableHeader().repaint(); + } + + private void selectedTableRow(boolean selected) { + for (int i = 0; i < table.getRowCount(); i++) { + table.setValueAt(selected, i, column); + } + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JComponent com = (JComponent) oldCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setBorder(com.getBorder()); + return this; + } + + @Override + protected void paintComponent(Graphics g) { + if (getBorder() != null) { + getBorder().paintBorder(this, g, 0, 0, getWidth(), getHeight()); + } + super.paintComponent(g); + } +} diff --git a/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java b/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java new file mode 100644 index 00000000..64c5540b --- /dev/null +++ b/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java @@ -0,0 +1,35 @@ +package backupmanager.utils.table; + +import java.awt.Color; +import java.awt.Component; +import javax.swing.JCheckBox; +import javax.swing.JTable; +import javax.swing.table.DefaultTableCellRenderer; + +public class CheckboxCellRenderer extends DefaultTableCellRenderer { + private final JCheckBox checkBox = new JCheckBox(); + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + if (value instanceof Boolean aBoolean) { + checkBox.setSelected(aBoolean); + checkBox.setHorizontalAlignment(CENTER); + + if (row % 2 == 0) { + checkBox.setBackground(new Color(223, 222, 243)); + } else { + checkBox.setBackground(Color.WHITE); + } + + if (isSelected) { + checkBox.setBackground(table.getSelectionBackground()); + checkBox.setForeground(table.getSelectionForeground()); + } else { + checkBox.setForeground(Color.BLACK); + } + + return checkBox; + } + return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } +} diff --git a/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java new file mode 100644 index 00000000..947f3090 --- /dev/null +++ b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java @@ -0,0 +1,26 @@ +package backupmanager.utils.table; + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.JProgressBar; +import javax.swing.JTable; +import javax.swing.table.DefaultTableCellRenderer; + +public class ProgressBarRenderer extends DefaultTableCellRenderer { + private final JProgressBar progressBar = new JProgressBar(0, 100); + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + // If the value is an Integer (assuming progress data), show the progress bar + if (value instanceof Integer integer) { + progressBar.setValue(integer); + progressBar.setString(integer + "%"); + + return progressBar; + } + + // Return the default (striped) component for non-progress values + return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + } +} diff --git a/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java b/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java new file mode 100644 index 00000000..c3325299 --- /dev/null +++ b/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java @@ -0,0 +1,35 @@ +package backupmanager.utils.table; + +import javax.swing.*; +import javax.swing.table.TableCellRenderer; +import java.awt.*; + +public class TableHeaderAlignment implements TableCellRenderer { + + private final TableCellRenderer headerDelegate; + private final TableCellRenderer cellDelegate; + + public TableHeaderAlignment(JTable table) { + this.headerDelegate = table.getTableHeader().getDefaultRenderer(); + this.cellDelegate = table.getDefaultRenderer(Object.class); + table.setDefaultRenderer(Object.class, new TableCellRenderer() { + @Override + public Component getTableCellRendererComponent(JTable jtable, Object o, boolean bln, boolean bln1, int row, int column) { + JLabel label = (JLabel) cellDelegate.getTableCellRendererComponent(jtable, o, bln, bln1, row, column); + label.setHorizontalAlignment(getAlignment(column)); + return label; + } + }); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + JLabel label = (JLabel) headerDelegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + label.setHorizontalAlignment(getAlignment(column)); + return label; + } + + protected int getAlignment(int column) { + return SwingConstants.CENTER; + } +} diff --git a/src/main/java/backupmanager/Widgets/SideMenuPanel.java b/src/main/java/backupmanager/Widgets/SideMenuPanel.java deleted file mode 100644 index c0261d48..00000000 --- a/src/main/java/backupmanager/Widgets/SideMenuPanel.java +++ /dev/null @@ -1,263 +0,0 @@ -package backupmanager.Widgets; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * @class SideMenuPanel - * @brief Provides a responsive animated side menu for Swing applications. - */ -public class SideMenuPanel { - - private int minWidth = 60; - private int maxWidth = 200; - private int speed = 2; - private int responsiveMinWidth = 600; - - private JPanel side; - private JComponent main; - private boolean mainAnimationEnabled = false; - private boolean isOpen = false; - private int currentWidth = 0; - - private final JFrame frame; - - /** - * @brief Constructs a SideMenuPanel attached to a given JFrame. - * @param frame The parent frame hosting the side and main panels. - */ - public SideMenuPanel(JFrame frame) { - this.frame = frame; - this.frame.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - currentWidth = 0; - } - }); - } - - /** @return Whether the side menu is currently open. */ - public boolean isOpen() { - return isOpen; - } - - /** - * @brief Manually sets the open state of the side menu. - * @param open True to set the menu as open; false to set as closed. - */ - public void setOpen(boolean open) { - this.isOpen = open; - } - - /** @return The minimum frame width required to enable main panel shifting. */ - public int getResponsiveMinWidth() { - return responsiveMinWidth; - } - - /** - * @brief Sets the minimum frame width that enables responsive main panel shifting. - * @param responsiveWidth Minimum width in pixels. - */ - public void setResponsiveMinWidth(int responsiveWidth) { - this.responsiveMinWidth = responsiveWidth; - } - - /** @return The animation speed in pixels per frame. */ - public int getSpeed() { - return speed; - } - - /** - * @brief Sets the animation speed. - * @param speed Speed in pixels per frame. If set to 0, uses maxWidth as speed. - */ - public void setSpeed(int speed) { - this.speed = (speed == 0) ? maxWidth : speed; - } - - /** @return The minimum width of the sidebar. */ - public int getMinWidth() { - return minWidth; - } - - /** - * @brief Sets the minimum sidebar width. - * @param minWidth Width in pixels. - */ - public void setMinWidth(int minWidth) { - this.minWidth = minWidth; - } - - /** @return The maximum width the sidebar can expand to. */ - public int getMaxWidth() { - return maxWidth; - } - - /** - * @brief Sets the maximum width of the sidebar. - * @param maxWidth Width in pixels. - */ - public void setMaxWidth(int maxWidth) { - this.maxWidth = maxWidth; - } - - /** @return The sidebar panel. */ - public JPanel getSide() { - return side; - } - - /** - * @brief Sets the sidebar panel. - * @param side A JPanel representing the sidebar. - */ - public void setSide(JPanel side) { - this.side = side; - } - - /** @return The main content panel. */ - public JComponent getMain() { - return main; - } - - /** - * @brief Sets the main content panel. Can be null for no responsiveness. - * @param main A JPanel or null. - */ - public void setMain(JComponent main) { - this.main = main; - } - - /** @return Whether the main panel moves with the sidebar. */ - public boolean isMainAnimationEnabled() { - return mainAnimationEnabled; - } - - /** - * @brief Enables or disables main panel animation on sidebar toggle. - * @param enabled True to enable animation; false otherwise. - */ - public void setMainAnimationEnabled(boolean enabled) { - this.mainAnimationEnabled = enabled; - } - - /** - * @brief Toggles the sidebar open or closed with animation. - * The main panel is shifted only if set and enabled. - */ - public void toggleMenu() { - if (side == null) { - System.err.println("Error: sidebar can't be null"); - return; - } - - if (currentWidth == maxWidth) { - animateMenu(false); // close - } else { - animateMenu(true); // open - } - } - - /** - * @brief Animates the sidebar either opening or closing. - * @param opening True to open; false to close. - */ - private void animateMenu(boolean opening) { - int direction = opening ? 1 : -1; - int target = opening ? maxWidth : 0; - int b = maxWidth % speed; - - new Thread(() -> { - try { - for (int i = currentWidth; opening ? i <= maxWidth : i >= 0; i += direction * speed) { - if (!opening && i <= b) i = 0; - if (opening && i >= maxWidth - b) i = maxWidth; - - updatePanelSize(i); - TimeUnit.NANOSECONDS.sleep(1); - } - currentWidth = target; - isOpen = opening; - } catch (InterruptedException e) { - Logger.getLogger(SideMenuPanel.class.getName()).log(Level.SEVERE, null, e); - } - }).start(); - } - - /** - * @brief Updates the width of the sidebar and, optionally, shifts the main panel. - * @param delta Width to add to minWidth for the sidebar. - */ - private void updatePanelSize(int delta) { - int width = minWidth + delta; - - side.setSize(new Dimension(width, side.getHeight())); - for (Component child : side.getComponents()) { - child.setSize(new Dimension(width, child.getHeight())); - } - - if (main != null && frame.getWidth() >= responsiveMinWidth && mainAnimationEnabled) { - main.setLocation(width, main.getY()); - } - } - - /** - * @brief Immediately closes the sidebar and resets layout. - */ - public void closeMenu() { - if (isOpen) { - updatePanelSize(0); - if (main != null) { - main.setLocation(minWidth, main.getY()); - updateLayout(minWidth); - } - isOpen = false; - currentWidth = 0; - } - } - - /** - * @brief Immediately opens the sidebar and updates layout. - */ - public void openMenu() { - if (!isOpen) { - updatePanelSize(maxWidth); - if (main != null) { - main.setLocation(minWidth + maxWidth, main.getY()); - updateLayout(minWidth + maxWidth); - } - currentWidth = maxWidth; - isOpen = true; - } - } - - /** - * @brief Updates the GroupLayout constraints of the main container - * to reflect the sidebar and main panel dimensions. - * @param size The width to apply to the sidebar in the layout. - */ - private void updateLayout(int size) { - if (main == null || main.getParent() == null) return; - - Container parent = main.getParent(); - GroupLayout layout = new GroupLayout(parent); - parent.setLayout(layout); - - layout.setHorizontalGroup( - layout.createSequentialGroup() - .addComponent(side, size, size, size) - .addGap(0) - .addComponent(main, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - - layout.setVerticalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addComponent(side, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(main, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - ); - } -} diff --git a/src/main/java/backupmanager/component/About.java b/src/main/java/backupmanager/component/About.java new file mode 100644 index 00000000..0a73e755 --- /dev/null +++ b/src/main/java/backupmanager/component/About.java @@ -0,0 +1,108 @@ +package backupmanager.component; + +import java.awt.Desktop; +import java.awt.Graphics; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTextPane; +import javax.swing.border.TitledBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.DefaultCaret; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.util.LoggingFacade; + +import backupmanager.Enums.ConfigKey; +import net.miginfocom.swing.MigLayout; + +public class About extends JPanel { + + public About() { + init(); + } + + private void init() { + setLayout(new MigLayout("fillx,wrap,insets 5 30 5 30,width 400", "[fill,330::]", "")); + + JTextPane title = createText("Modal Dialog Demo Project"); + title.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +5"); + + JTextPane description = createText(""); + description.setContentType("text/html"); + description.setText(getDescriptionText()); + description.addHyperlinkListener(e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + showUrl(e.getURL()); + } + }); + + add(title); + add(description); + add(createSystemInformation()); + } + + private JTextPane createText(String text) { + JTextPane textPane = new JTextPane(); + textPane.setBorder(BorderFactory.createEmptyBorder()); + textPane.setText(text); + textPane.setEditable(false); + textPane.setCaret(new DefaultCaret() { + @Override + public void paint(Graphics g) { + } + }); + return textPane; + } + + private String getDescriptionText() { + String text = "This is a demo project for the Modal Dialog library, " + + "built using FlatLaf Look and Feel and MigLayout library.
" + + "For source code, visit the GitHub Project."; + + return text; + } + + private String getSystemInformationText() { + String text = "Demo Version: %s
" + + "Java: %s
" + + "System: %s
"; + + return text; + } + + private JComponent createSystemInformation() { + JPanel panel = new JPanel(new MigLayout("wrap")); + panel.setBorder(new TitledBorder("System Information")); + JTextPane textPane = createText(""); + textPane.setContentType("text/html"); + String version = ConfigKey.VERSION.getValue(); + String java = System.getProperty("java.vendor") + " - v" + System.getProperty("java.version"); + String system = System.getProperty("os.name") + " " + System.getProperty("os.arch") + " - v" + System.getProperty("os.version"); + String text = String.format(getSystemInformationText(), + version, + java, + system); + textPane.setText(text); + panel.add(textPane); + return panel; + } + + private void showUrl(URL url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.BROWSE)) { + try { + desktop.browse(url.toURI()); + } catch (IOException | URISyntaxException e) { + LoggingFacade.INSTANCE.logSevere("Error browse url", e); + } + } + } + } +} diff --git a/src/main/java/backupmanager/component/AccentColorIcon.java b/src/main/java/backupmanager/component/AccentColorIcon.java new file mode 100644 index 00000000..bfad5926 --- /dev/null +++ b/src/main/java/backupmanager/component/AccentColorIcon.java @@ -0,0 +1,34 @@ +package backupmanager.component; + +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.icons.FlatAbstractIcon; +import com.formdev.flatlaf.util.ColorFunctions; + +import javax.swing.*; +import java.awt.*; + +public class AccentColorIcon extends FlatAbstractIcon { + + private final String colorKey; + + public AccentColorIcon(String colorKey) { + super(16, 16, null); + this.colorKey = colorKey; + } + + @Override + protected void paintIcon(Component c, Graphics2D g) { + Color color = UIManager.getColor(colorKey); + if (color == null) + color = Color.lightGray; + else if (!c.isEnabled()) { + color = FlatLaf.isLafDark() + ? ColorFunctions.shade(color, 0.5f) + : ColorFunctions.tint(color, 0.6f); + } + + g.setColor(color); + g.fillRoundRect(1, 1, width - 2, height - 2, 5, 5); + g.dispose(); + } +} diff --git a/src/main/java/backupmanager/component/EmptyModalBorder.java b/src/main/java/backupmanager/component/EmptyModalBorder.java new file mode 100644 index 00000000..816942b0 --- /dev/null +++ b/src/main/java/backupmanager/component/EmptyModalBorder.java @@ -0,0 +1,64 @@ +package backupmanager.component; + +import net.miginfocom.swing.MigLayout; +import raven.modal.component.Modal; +import raven.modal.component.ModalBorderAction; +import raven.modal.listener.ModalCallback; +import raven.modal.listener.ModalController; + +import java.awt.*; + +public class EmptyModalBorder extends Modal implements ModalBorderAction { + + protected final Component component; + public static final int OPENED = 20; + private final ModalCallback callback; + + public EmptyModalBorder(Component component) { + this(component, null); + } + + public EmptyModalBorder(Component component, ModalCallback callback) { + this.component = component; + this.callback = callback; + setLayout(new MigLayout("fill,insets 8 0 8 0", "[fill]", "[fill]")); + add(component); + } + + @Override + protected void modalOpened() { + if (callback != null) { + callback.action(createController(), OPENED); + } + } + + @Override + public void doAction(int action) { + if (callback == null) { + getController().closeModal(); + } else { + ModalController controller = createController(); + callback.action(controller, action); + if (!controller.getConsume()) { + getController().closeModal(); + } + } + } + + @Override + public Color getBackground() { + if (component == null) { + return super.getBackground(); + } + return component.getBackground(); + } + + private ModalController createController() { + return new ModalController(this) { + @Override + public void close() { + getController().closeModal(); + } + }; + } +} diff --git a/src/main/java/backupmanager/component/FormSearchButton.java b/src/main/java/backupmanager/component/FormSearchButton.java new file mode 100644 index 00000000..8431181f --- /dev/null +++ b/src/main/java/backupmanager/component/FormSearchButton.java @@ -0,0 +1,37 @@ +package backupmanager.component; + +import javax.swing.JButton; +import javax.swing.JLabel; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import net.miginfocom.swing.MigLayout; + +public class FormSearchButton extends JButton { + + public FormSearchButton() { + super("Quick Search...", new FlatSVGIcon("icons/search.svg", 0.4f)); + init(); + } + + private void init() { + setLayout(new MigLayout("insets 0,al trailing,filly", "", "[center]")); + setHorizontalAlignment(JButton.LEADING); + putClientProperty(FlatClientProperties.STYLE, "" + + "margin:5,7,5,10;" + + "arc:10;" + + "borderWidth:0;" + + "focusWidth:0;" + + "innerFocusWidth:0;" + + "[light]background:shade($Panel.background,10%);" + + "[dark]background:tint($Panel.background,10%);" + + "[light]foreground:tint($Button.foreground,40%);" + + "[dark]foreground:shade($Button.foreground,30%);"); + JLabel label = new JLabel("Ctrl F"); + label.putClientProperty(FlatClientProperties.STYLE, "" + + "[light]foreground:tint($Button.foreground,40%);" + + "[dark]foreground:shade($Button.foreground,30%);"); + add(label); + } +} diff --git a/src/main/java/backupmanager/component/FormSearchPanel.java b/src/main/java/backupmanager/component/FormSearchPanel.java new file mode 100644 index 00000000..38a38687 --- /dev/null +++ b/src/main/java/backupmanager/component/FormSearchPanel.java @@ -0,0 +1,557 @@ +package backupmanager.component; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.LookAndFeel; +import javax.swing.Scrollable; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.icons.FlatMenuArrowIcon; + +import backupmanager.menu.MyMenuValidation; +import backupmanager.svg.SVGIconUIColor; +import backupmanager.system.Form; +import backupmanager.system.FormSearch; +import backupmanager.utils.DemoPreferences; +import backupmanager.utils.SystemForm; +import net.miginfocom.swing.MigLayout; +import raven.modal.Drawer; +import raven.modal.ModalDialog; +import raven.modal.component.ModalContainer; + +public class FormSearchPanel extends JPanel { + + private LookAndFeel oldTheme = UIManager.getLookAndFeel(); + private final int SEARCH_MAX_LENGTH = 50; + private final Map> formsMap; + private final List listItems = new ArrayList<>(); + + public FormSearchPanel(Map> formsMap) { + this.formsMap = formsMap; + init(); + } + + private void init() { + setLayout(new MigLayout("fillx,insets 0,wrap", "[fill,500]")); + textSearch = new JTextField(); + panelResult = new PanelResult(); + textSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Search..."); + textSearch.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSVGIcon("icons/search.svg", 0.4f)); + textSearch.putClientProperty(FlatClientProperties.STYLE, "" + + "border:3,3,3,3;" + + "background:null;" + + "showClearButton:true;"); + add(textSearch, "gap 17 17 0 0"); + add(new JSeparator(), "height 2!"); + JScrollPane scrollPane = new JScrollPane(panelResult); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + + scrollPane.getVerticalScrollBar().putClientProperty(FlatClientProperties.STYLE, "" + + "trackArc:$ScrollBar.thumbArc;" + + "thumbInsets:0,3,0,3;" + + "trackInsets:0,3,0,3;" + + "width:12;"); + add(scrollPane); + installSearchField(); + } + + public final void formCheck() { + if (oldTheme != UIManager.getLookAndFeel()) { + oldTheme = UIManager.getLookAndFeel(); + SwingUtilities.updateComponentTreeUI(this); + } + } + + private void installSearchField() { + textSearch.setDocument(new PlainDocument() { + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { + if (getLength() + str.length() <= SEARCH_MAX_LENGTH) { + super.insertString(offs, str, a); + } + } + }); + textSearch.getDocument().addDocumentListener(new DocumentListener() { + private String text; + + @Override + public void insertUpdate(DocumentEvent e) { + search(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + search(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + search(); + } + + private void search() { + String st = textSearch.getText().trim().toLowerCase(); // Convert search term to lowercase + if (!st.equals(text)) { + text = st; + panelResult.removeAll(); + listItems.clear(); + if (st.isEmpty()) { + showRecentResult(); + } else { + for (Map.Entry> entry : formsMap.entrySet()) { + SystemForm s = entry.getKey(); + // Compare both name and description with lower cased search term + if (s.name().toLowerCase().contains(st) + || s.description().toLowerCase().contains(st) + || checkTags(s.tags(), st)) { + if (MyMenuValidation.validation(entry.getValue())) { + Item item = new Item(s, entry.getValue(), false, false); + checkComponentOrientation(item); + panelResult.add(item); + listItems.add(item); + } + } + } + if (!listItems.isEmpty()) { + setSelected(0); + } else { + panelResult.add(createNoResult(st)); + } + panelResult.repaint(); + updateLayout(); + } + } + } + + private boolean checkTags(String[] tags, String st) { + if (tags.length == 0) return false; + return Arrays.stream(tags).anyMatch(s -> s.contains(st)); + } + }); + textSearch.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_UP: + move(true); + break; + case KeyEvent.VK_DOWN: + move(false); + break; + case KeyEvent.VK_ENTER: + showForm(); + break; + } + } + }); + } + + private void updateLayout() { + Container container = SwingUtilities.getAncestorOfClass(ModalContainer.class, FormSearchPanel.this); + if (container != null) { + container.revalidate(); + } + } + + private void showForm() { + int index = getSelectedIndex(); + if (index != -1) { + listItems.get(index).showForm(); + } + } + + private void setSelected(int index) { + for (int i = 0; i < listItems.size(); i++) { + listItems.get(i).setSelected(index == i); + } + } + + private int getSelectedIndex() { + for (int i = 0; i < listItems.size(); i++) { + if (listItems.get(i).isSelected()) { + return i; + } + } + return -1; + } + + private void move(boolean up) { + if (listItems.isEmpty()) return; + int index = getSelectedIndex(); + int size = listItems.size(); + if (index == -1) { + if (up) { + index = listItems.size() - 1; + } else { + index = 0; + } + } else { + if (up) { + index = (index == 0) ? size - 1 : index - 1; + } else { + index = (index == size - 1) ? 0 : index + 1; + } + } + setSelected(index); + } + + private void showRecentResult() { + List recentSearch = getRecentSearch(false); + List favoriteSearch = getRecentSearch(true); + panelResult.removeAll(); + listItems.clear(); + if (recentSearch != null && !recentSearch.isEmpty()) { + panelResult.add(createLabel("Recent")); + for (Item item : recentSearch) { + checkComponentOrientation(item); + panelResult.add(item); + listItems.add(item); + } + } + + if (favoriteSearch != null && !favoriteSearch.isEmpty()) { + panelResult.add(createLabel("Favorite")); + for (Item item : favoriteSearch) { + checkComponentOrientation(item); + panelResult.add(item); + listItems.add(item); + } + } + if (listItems.isEmpty()) { + panelResult.add(new NoRecentResult()); + } else { + setSelected(0); + } + updateLayout(); + } + + private JLabel createLabel(String title) { + JLabel label = new JLabel(title); + label.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +1;" + + "border:5,15,5,15;"); + checkComponentOrientation(label); + return label; + } + + private List getRecentSearch(boolean favorite) { + String[] recentSearch = DemoPreferences.getRecentSearch(favorite); + if (recentSearch == null) { + return null; + } + List list = new ArrayList<>(); + for (String s : recentSearch) { + Class classForm = getClassForm(s); + if (MyMenuValidation.validation(classForm)) { + Item item = createRecentItem(s, favorite); + if (item != null) { + list.add(item); + } + } + } + return list; + } + + private Class getClassForm(String name) { + for (Map.Entry> entry : formsMap.entrySet()) { + if (entry.getKey().name().equals(name)) { + return entry.getValue(); + } + } + return null; + } + + private Item createRecentItem(String name, boolean favorite) { + for (Map.Entry> entry : formsMap.entrySet()) { + if (entry.getKey().name().equals(name)) { + return new Item(entry.getKey(), entry.getValue(), true, favorite); + } + } + return null; + } + + private Component createNoResult(String text) { + JPanel panel = new JPanel(new MigLayout("insets 15 5 15 5,al center,gapx 1")); + JLabel label = new JLabel("No result for \""); + JLabel labelEnd = new JLabel("\""); + label.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + labelEnd.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + JLabel labelText = new JLabel(text); + + panel.add(label); + panel.add(labelText); + panel.add(labelEnd); + return panel; + } + + public void clearSearch() { + if (!textSearch.getText().isEmpty()) { + textSearch.setText(""); + } else { + showRecentResult(); + } + } + + public void searchGrabFocus() { + textSearch.grabFocus(); + } + + private void checkComponentOrientation(Component com) { + if (getComponentOrientation().isLeftToRight() != com.getComponentOrientation().isLeftToRight()) { + com.applyComponentOrientation(getComponentOrientation()); + } + } + + private JTextField textSearch; + private JPanel panelResult; + + private static class NoRecentResult extends JPanel { + + public NoRecentResult() { + init(); + } + + private void init() { + setLayout(new MigLayout("insets 15 5 15 5,al center")); + JLabel label = new JLabel("No recent searches"); + label.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;" + + "font:bold;"); + add(label); + } + } + + private class Item extends JButton { + + private final SystemForm data; + private final Class form; + private final boolean isRecent; + private final boolean isFavorite; + private Component itemSource; + + public Item(SystemForm data, Class form, boolean isRecent, boolean isFavorite) { + this.data = data; + this.form = form; + this.isRecent = isRecent; + this.isFavorite = isFavorite; + init(); + } + + private void init() { + setFocusable(false); + setHorizontalAlignment(JButton.LEADING); + setLayout(new MigLayout("insets 3 3 3 0,filly,gapy 2", "[]push[]")); + putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;" + + "arc:10;" + + "borderWidth:0;" + + "focusWidth:0;" + + "innerFocusWidth:0;" + + "[light]selectedBackground:lighten($Button.selectedBackground,9%)"); + JLabel labelDescription = new JLabel(data.description()); + labelDescription.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + add(new JLabel(data.name()), "cell 0 0"); + add(labelDescription, "cell 0 1"); + if (!isRecent) { + add(new JLabel(new FlatMenuArrowIcon()), "cell 1 0,span 1 2"); + } else { + add(createRecentOption(), "cell 1 0,span 1 2"); + } + addActionListener(e -> { + if (itemSource == null) { + clearSelected(); + setSelected(true); + showForm(); + } else if (itemSource.getName().equals("remove")) { + removeRecent(); + } else if (itemSource.getName().equals("favorite")) { + addFavorite(); + } + }); + } + + private void clearSelected() { + for (Component com : getParent().getComponents()) { + if (com instanceof JButton) { + ((JButton) com).setSelected(false); + } + } + } + + protected void showForm() { + ModalDialog.closeModal(FormSearch.ID); + Drawer.setSelectedItemClass(form); + if (!isFavorite) { + DemoPreferences.addRecentSearch(data.name(), false); + } + } + + protected Component createRecentOption() { + JPanel panel = new JPanel(new MigLayout("insets n 0 n 0,fill,gapx 2", "", "[fill]")); + panel.setOpaque(false); + JButton cmdRemove = createButton("remove", "clear.svg", 0.35f, "Label.foreground", 0.9f); + if (!isFavorite) { + JButton cmdFavorite = createButton("favorite", "favorite.svg", 0.4f, "Component.accentColor", 0.9f); + panel.add(cmdFavorite); + } else { + JLabel label = new JLabel(new SVGIconUIColor("icons/favorite_filled.svg", 0.4f, "Component.accentColor", 0.8f)); + label.putClientProperty(FlatClientProperties.STYLE, "" + + "border:3,3,3,3;"); + panel.add(label); + } + panel.add(new JSeparator(JSeparator.VERTICAL), "gapy 5 5"); + panel.add(cmdRemove); + return panel; + } + + private JButton createButton(String name, String icon, float scale, String hoverKey, float alpha) { + SVGIconUIColor svgIcon = new SVGIconUIColor("icons/" + icon, scale, "Label.disabledForeground", alpha); + JButton button = new JButton(svgIcon); + button.setName(name); + button.setFocusable(false); + button.setContentAreaFilled(false); + button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + button.setModel(getModel()); + button.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:3,3,3,3;"); + + button.addMouseListener(new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + svgIcon.setColorKey(hoverKey); + itemSource = (Component) e.getSource(); + } + + @Override + public void mouseExited(MouseEvent e) { + svgIcon.setColorKey("Label.disabledForeground"); + itemSource = null; + } + }); + return button; + } + + protected void removeRecent() { + DemoPreferences.removeRecentSearch(data.name(), isFavorite); + panelResult.remove(this); + listItems.remove(this); + if (listItems.isEmpty()) { + panelResult.removeAll(); + panelResult.add(new NoRecentResult()); + } else { + if (getCount(isFavorite) == 0) { + if (isFavorite) { + panelResult.remove(panelResult.getComponentCount() - 1); + } else { + panelResult.remove(0); + } + } + } + updateLayout(); + } + + protected void addFavorite() { + DemoPreferences.addRecentSearch(data.name(), true); + int[] index = getFirstFavoriteIndex(); + panelResult.remove(this); + listItems.remove(this); + Item item = new Item(data, form, isRecent, true); + checkComponentOrientation(item); + if (index == null) { + panelResult.add(createLabel("Favorite")); + panelResult.add(item); + listItems.add(item); + } else { + panelResult.remove(this); + listItems.remove(this); + panelResult.add(item, index[1] - 1); + listItems.add(index[0] - 1, item); + } + if (getCount(false) == 0) { + panelResult.remove(0); + } + updateLayout(); + } + + private int getCount(boolean favorite) { + int count = 0; + for (Item item : listItems) { + if (item.isFavorite == favorite) { + count++; + } + } + return count; + } + + private int[] getFirstFavoriteIndex() { + for (int i = 0; i < listItems.size(); i++) { + if (listItems.get(i).isFavorite) { + return new int[]{i, panelResult.getComponentZOrder(listItems.get(i))}; + } + } + return null; + } + } + + private static class PanelResult extends JPanel implements Scrollable { + + public PanelResult() { + super(new MigLayout("insets 3 10 3 10,fillx,wrap", "[fill]")); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle rectangle, int i, int i1) { + return 50; + } + + @Override + public int getScrollableBlockIncrement(Rectangle rectangle, int i, int i1) { + return 50; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return false; + } + } +} diff --git a/src/main/java/backupmanager/component/RefreshLine.java b/src/main/java/backupmanager/component/RefreshLine.java new file mode 100644 index 00000000..3fc65cf1 --- /dev/null +++ b/src/main/java/backupmanager/component/RefreshLine.java @@ -0,0 +1,58 @@ +package backupmanager.component; + +import com.formdev.flatlaf.util.Animator; +import com.formdev.flatlaf.util.CubicBezierEasing; +import com.formdev.flatlaf.util.UIScale; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.RoundRectangle2D; + +public class RefreshLine extends JPanel { + + private Animator animator; + private float animate; + + public RefreshLine() { + init(); + } + + private void init() { + animator = new Animator(500, new Animator.TimingTarget() { + @Override + public void timingEvent(float v) { + animate = v; + RefreshLine.this.repaint(); + } + + @Override + public void end() { + animate = 0f; + repaint(); + } + }); + animator.setInterpolator(CubicBezierEasing.EASE_OUT); + } + + public void refresh() { + if (animator.isRunning()) { + animator.stop(); + } + animate = 0f; + animator.start(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + float pad = UIScale.scale(5f); + float width = getWidth() - (pad * 2); + float height = getHeight(); + g2.setColor(UIManager.getColor("Component.accentColor")); + g2.setComposite(AlphaComposite.SrcOver.derive(0.5f)); + g2.fill(new RoundRectangle2D.Float(pad, 0, width * animate, height, height, height)); + g2.dispose(); + } +} diff --git a/src/main/java/backupmanager/component/ToolBarSelection.java b/src/main/java/backupmanager/component/ToolBarSelection.java new file mode 100644 index 00000000..8f4de483 --- /dev/null +++ b/src/main/java/backupmanager/component/ToolBarSelection.java @@ -0,0 +1,28 @@ +package backupmanager.component; + +import com.formdev.flatlaf.FlatClientProperties; + +import javax.swing.*; +import java.util.function.Consumer; + +public class ToolBarSelection extends JToolBar { + + public ToolBarSelection(T[] data, Consumer callBack) { + putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + ButtonGroup group = new ButtonGroup(); + boolean selected = false; + for (T d : data) { + JToggleButton button = new JToggleButton(d.toString()); + button.addActionListener(e -> callBack.accept(d)); + group.add(button); + add(button); + if (!selected) { + button.setSelected(true); + selected = true; + } + button.putClientProperty(FlatClientProperties.STYLE, "" + + "toolbar.margin:2,5,2,5;"); + } + } +} diff --git a/src/main/java/backupmanager/component/chart/BarChart.java b/src/main/java/backupmanager/component/chart/BarChart.java new file mode 100644 index 00000000..ce706966 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/BarChart.java @@ -0,0 +1,62 @@ +package backupmanager.component.chart; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.CategoryAxis; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.CategoryPlot; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.data.category.CategoryDataset; +import backupmanager.component.chart.renderer.bar.ChartBarRenderer; +import backupmanager.component.chart.themes.ChartDrawingSupplier; + +public class BarChart extends DefaultChartPanel { + + private BarRenderer renderer; + + @Override + protected JFreeChart createChart() { + JFreeChart freeChart = ChartFactory.createBarChart(null, null, null, null); + return freeChart; + } + + @Override + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + CategoryPlot plot = chart.getCategoryPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + CategoryAxis domain = plot.getDomainAxis(); + + range.setAxisLineVisible(false); + domain.setAxisLineVisible(false); + + plot.setRenderer(getDefaultRender()); + } + + @Override + protected void styleChart(JFreeChart chart, ChartPanel panel) { + CategoryPlot plot = chart.getCategoryPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + CategoryAxis domain = plot.getDomainAxis(); + + range.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + + domain.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + + plot.setRangeGridlineStroke(ChartDrawingSupplier.getDefaultGridlineStroke()); + plot.setInsets(ChartDrawingSupplier.scaleRectangleInsets(Plot.DEFAULT_INSETS)); + } + + public BarRenderer getDefaultRender() { + if (renderer == null) { + renderer = new ChartBarRenderer(); + } + return renderer; + } + + public void setDataset(CategoryDataset dataset) { + freeChart.getCategoryPlot().setDataset(dataset); + } +} diff --git a/src/main/java/backupmanager/component/chart/CandlestickChart.java b/src/main/java/backupmanager/component/chart/CandlestickChart.java new file mode 100644 index 00000000..b22a7d47 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/CandlestickChart.java @@ -0,0 +1,154 @@ +package backupmanager.component.chart; + +import org.jfree.chart.*; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.DateAxis; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardCrosshairLabelGenerator; +import org.jfree.chart.panel.CrosshairOverlay; +import org.jfree.chart.plot.Crosshair; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.ui.RectangleAnchor; +import org.jfree.data.xy.OHLCDataset; +import backupmanager.component.chart.renderer.other.ChartCandlestickRenderer; +import backupmanager.component.chart.themes.ChartDrawingSupplier; +import backupmanager.component.chart.utils.CustomCrosshairToolTip; +import backupmanager.component.chart.utils.DateCrosshairLabelGenerator; + +import java.awt.geom.Rectangle2D; +import java.text.DateFormat; +import java.text.NumberFormat; + +public class CandlestickChart extends DefaultChartPanel { + + private CustomCrosshairToolTip xCrosshair; + private CustomCrosshairToolTip yCrosshair; + + @Override + protected JFreeChart createChart() { + JFreeChart freeChart = ChartFactory.createCandlestickChart(null, null, null, null, false); + return freeChart; + } + + @Override + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + XYPlot plot = chart.getXYPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + DateAxis domain = (DateAxis) plot.getDomainAxis(); + + range.setNumberFormatOverride(NumberFormat.getCurrencyInstance()); + range.setAxisLineVisible(false); + range.setTickMarksVisible(false); + range.setAutoRangeIncludesZero(false); + range.setLowerMargin(0.15); + + domain.setUpperMargin(0.1); + domain.setAxisLineVisible(false); + domain.setTickMarksVisible(false); + + plot.setDomainPannable(true); + plot.setRangeGridlinesVisible(false); + plot.setDomainGridlinesVisible(false); + plot.setRenderer(new ChartCandlestickRenderer()); + + panel.setMouseWheelEnabled(false); + panel.setRangeZoomable(false); + } + + @Override + protected void styleChart(JFreeChart chart, ChartPanel panel) { + XYPlot plot = chart.getXYPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + DateAxis domain = (DateAxis) plot.getDomainAxis(); + + range.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + domain.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + + plot.setDomainGridlineStroke(ChartDrawingSupplier.getDefaultGridlineStroke()); + plot.setInsets(ChartDrawingSupplier.scaleRectangleInsets(4, 8, 15, 8)); + if (xCrosshair != null) { + xCrosshair.installStyle(); + } + if (yCrosshair != null) { + yCrosshair.installStyle(); + } + } + + @Override + protected void createAnnotation(JFreeChart chart, ChartPanel panel) { + xCrosshair = new CustomCrosshairToolTip(); + yCrosshair = new CustomCrosshairToolTip(); + chartPanel.addOverlay(createCrosshair(chartPanel)); + } + + private CrosshairOverlay createCrosshair(ChartPanel panel) { + CrosshairOverlay crosshairOverlay = new CrosshairOverlay(); + xCrosshair.setLabelAnchor(RectangleAnchor.BOTTOM); + yCrosshair.setLabelAnchor(RectangleAnchor.RIGHT); + DateFormat dateFormat = DateFormat.getDateInstance(); + xCrosshair.setLabelGenerator(new DateCrosshairLabelGenerator("{0}", dateFormat)); + yCrosshair.setLabelGenerator(new StandardCrosshairLabelGenerator("{0}", NumberFormat.getCurrencyInstance())); + xCrosshair.setLabelVisible(true); + yCrosshair.setLabelVisible(true); + crosshairOverlay.addDomainCrosshair(xCrosshair); + crosshairOverlay.addRangeCrosshair(yCrosshair); + + panel.addChartMouseListener(new ChartMouseListener() { + + private void showCrosshair(Crosshair crosshair, boolean visible) { + if (crosshair.isVisible() != visible) { + crosshair.setVisible(visible); + } + } + + @Override + public void chartMouseClicked(ChartMouseEvent chartMouseEvent) { + } + + @Override + public void chartMouseMoved(ChartMouseEvent event) { + Rectangle2D dataArea = panel.getScreenDataArea(); + if (!dataArea.contains(event.getTrigger().getPoint())) { + showCrosshair(xCrosshair, false); + showCrosshair(yCrosshair, false); + return; + } + showCrosshair(xCrosshair, true); + showCrosshair(yCrosshair, true); + + JFreeChart chart = panel.getChart(); + XYPlot plot = (XYPlot) chart.getPlot(); + int seriesCount = plot.getSeriesCount(); + OHLCDataset dataset = (OHLCDataset) plot.getDataset(); + + double x = plot.getDomainAxis().java2DToValue(event.getTrigger().getX(), dataArea, plot.getDomainAxisEdge()); + double y = plot.getRangeAxis().java2DToValue(event.getTrigger().getY(), dataArea, plot.getRangeAxisEdge()); + + double minDistance = Double.MAX_VALUE; + double closestX = 0; + boolean found = false; + + for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) { + for (int itemIndex = 0; itemIndex < plot.getDataset().getItemCount(seriesIndex); itemIndex++) { + double dataX = dataset.getXValue(seriesIndex, itemIndex); + double distance = Math.abs(x - dataX); + if (distance < minDistance) { + minDistance = distance; + closestX = dataX; + found = true; + } + } + } + if (found) { + xCrosshair.setValue(closestX); + } + yCrosshair.setValue(y); + } + }); + return crosshairOverlay; + } + + public void setDataset(OHLCDataset dataset) { + freeChart.getXYPlot().setDataset(dataset); + } +} diff --git a/src/main/java/backupmanager/component/chart/DefaultChartPanel.java b/src/main/java/backupmanager/component/chart/DefaultChartPanel.java new file mode 100644 index 00000000..74613707 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/DefaultChartPanel.java @@ -0,0 +1,81 @@ +package backupmanager.component.chart; + +import com.formdev.flatlaf.FlatClientProperties; +import net.miginfocom.swing.MigLayout; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.ui.RectangleInsets; +import backupmanager.component.chart.themes.ChartDrawingSupplier; +import backupmanager.component.chart.themes.DefaultChartTheme; + +import javax.swing.*; +import java.awt.*; + +public abstract class DefaultChartPanel extends JPanel { + + public JFreeChart getFreeChart() { + return freeChart; + } + + public ChartPanel getChartPanel() { + return chartPanel; + } + + protected JFreeChart freeChart; + protected ChartPanel chartPanel; + + public DefaultChartPanel() { + init(); + } + + private void init() { + setLayout(new MigLayout("wrap,fillx,gap 0", "[fill]")); + putClientProperty(FlatClientProperties.STYLE_CLASS, "dashboardBackground"); + + freeChart = createChart(); + chartPanel = new ChartPanel(freeChart); + chartPanel.putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + createAnnotation(freeChart, chartPanel); + defaultStyleChart(freeChart, chartPanel); + applyStyledChart(freeChart, chartPanel); + + add(chartPanel); + } + + private void applyStyledChart(JFreeChart chart, ChartPanel panel) { + Color selectionColor = UIManager.getColor("List.selectionBackground"); + + // themes + DefaultChartTheme.applyTheme(chart); + // panel + chartPanel.setPopupMenu(null); + chartPanel.setZoomFillPaint(ChartDrawingSupplier.alpha(selectionColor, 0.2f)); + + // legend + if (chart.getLegend() != null) { + RectangleInsets insets = ChartDrawingSupplier.scaleRectangleInsets(new RectangleInsets(2, 2, 2, 2)); + chart.getLegend().setItemLabelPadding(insets); + } + styleChart(chart, panel); + } + + protected abstract JFreeChart createChart(); + + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + } + + protected void styleChart(JFreeChart chart, ChartPanel panel) { + } + + protected void createAnnotation(JFreeChart chart, ChartPanel panel) { + } + + @Override + public void updateUI() { + super.updateUI(); + if (freeChart != null && chartPanel != null) { + applyStyledChart(freeChart, chartPanel); + } + } +} diff --git a/src/main/java/backupmanager/component/chart/PieChart.java b/src/main/java/backupmanager/component/chart/PieChart.java new file mode 100644 index 00000000..0bb31ed6 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/PieChart.java @@ -0,0 +1,52 @@ +package backupmanager.component.chart; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.labels.StandardPieSectionLabelGenerator; +import org.jfree.chart.plot.PiePlot; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.data.general.PieDataset; +import backupmanager.component.chart.themes.ChartDrawingSupplier; + +import java.awt.*; + +public class PieChart extends DefaultChartPanel { + + public PieChart() { + } + + @Override + protected JFreeChart createChart() { + JFreeChart freeChart = ChartFactory.createPieChart(null, null, true, false, false); + return freeChart; + } + + @Override + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + PiePlot plot = (PiePlot) chart.getPlot(); + plot.setShadowPaint(null); + plot.setLabelShadowPaint(null); + plot.setLabelOutlinePaint(null); + plot.setLabelBackgroundPaint(null); + plot.setDefaultSectionOutlineStroke(new BasicStroke(0f)); + plot.setLabelGenerator(new StandardPieSectionLabelGenerator("{0} {2}")); + + // test + plot.setExplodePercent("Tablet", 0.15f); + } + + @Override + protected void styleChart(JFreeChart chart, ChartPanel panel) { + PiePlot plot = (PiePlot) chart.getPlot(); + plot.setLegendItemShape(ChartDrawingSupplier.getDefaultShape()); + plot.setLabelLinkStroke(new BasicStroke(UIScale.scale(1f))); + plot.setLabelPadding(ChartDrawingSupplier.scaleRectangleInsets(new RectangleInsets(2, 2, 2, 2))); + } + + public void setDataset(PieDataset dataset) { + PiePlot plot = (PiePlot) freeChart.getPlot(); + plot.setDataset(dataset); + } +} diff --git a/src/main/java/backupmanager/component/chart/SpiderChart.java b/src/main/java/backupmanager/component/chart/SpiderChart.java new file mode 100644 index 00000000..2c1c0a52 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/SpiderChart.java @@ -0,0 +1,96 @@ +package backupmanager.component.chart; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.ChartMouseEvent; +import org.jfree.chart.ChartMouseListener; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.entity.LegendItemEntity; +import org.jfree.chart.plot.SpiderWebPlot; +import org.jfree.data.category.CategoryDataset; +import backupmanager.component.chart.themes.ChartDrawingSupplier; +import backupmanager.component.chart.themes.DefaultChartTheme; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class SpiderChart extends DefaultChartPanel { + + public SpiderChart() { + } + + @Override + protected JFreeChart createChart() { + SpiderWebPlot plot = new SpiderWebPlot(); + JFreeChart freeChart = new JFreeChart(null, null, plot, true); + return freeChart; + } + + @Override + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + SpiderWebPlot plot = (SpiderWebPlot) chart.getPlot(); + + panel.addChartMouseListener(new ChartMouseListener() { + + @Override + public void chartMouseClicked(ChartMouseEvent evt) { + if (evt.getEntity() instanceof LegendItemEntity) { + LegendItemEntity entity = (LegendItemEntity) evt.getEntity(); + Comparable key = entity.getSeriesKey(); + setSectionKey(key); + } + } + + @Override + public void chartMouseMoved(ChartMouseEvent evt) { + } + + private void setSectionKey(Comparable key) { + List keys = plot.getDataset().getRowKeys(); + int index = 0; + for (Comparable k : keys) { + if (k == key) { + Paint paint = plot.getSeriesPaint(index); + if (paint instanceof Color) { + if (((Color) paint).getAlpha() == 255) { + plot.setSeriesPaint(index, ChartDrawingSupplier.alpha(paint, 0.3f)); + } else { + plot.setSeriesPaint(index, ChartDrawingSupplier.alpha(paint, 1f)); + } + } + return; + } + index++; + } + } + }); + } + + @Override + protected void styleChart(JFreeChart chart, ChartPanel panel) { + SpiderWebPlot plot = (SpiderWebPlot) chart.getPlot(); + + plot.setLegendItemShape(ChartDrawingSupplier.getDefaultShape()); + plot.setSeriesOutlineStroke(new BasicStroke(UIScale.scale(1f))); + plot.setAxisLineStroke(new BasicStroke(UIScale.scale(1f))); + + int index = 0; + for (Color color : DefaultChartTheme.getColors()) { + initSeriesStyle(plot, index++, color, index != 1); + } + } + + private void initSeriesStyle(SpiderWebPlot plot, int series, Color color, boolean alpha) { + Paint c = alpha ? ChartDrawingSupplier.alpha(color, 0.3f) : color; + plot.setLabelPaint(UIManager.getColor("Label.foreground")); + plot.setSeriesPaint(series, c); + plot.setSeriesOutlinePaint(series, c); + plot.setSeriesOutlineStroke(series, new BasicStroke(UIScale.scale(1f))); + } + + public void setDataset(CategoryDataset dataset) { + SpiderWebPlot plot = (SpiderWebPlot) freeChart.getPlot(); + plot.setDataset(dataset); + } +} diff --git a/src/main/java/backupmanager/component/chart/TimeSeriesChart.java b/src/main/java/backupmanager/component/chart/TimeSeriesChart.java new file mode 100644 index 00000000..a05698bf --- /dev/null +++ b/src/main/java/backupmanager/component/chart/TimeSeriesChart.java @@ -0,0 +1,155 @@ +package backupmanager.component.chart; + +import org.jfree.chart.*; +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.DateAxis; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.data.xy.TableXYDataset; +import org.jfree.data.xy.XYDataset; +import backupmanager.component.chart.renderer.ChartXYCurveRenderer; +import backupmanager.component.chart.themes.ChartDrawingSupplier; +import backupmanager.component.chart.themes.DefaultChartTheme; +import backupmanager.component.chart.utils.MultiXYTextAnnotation; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.Date; + +public class TimeSeriesChart extends DefaultChartPanel { + + private XYItemRenderer renderer; + + public TimeSeriesChart() { + super(); + } + + @Override + protected JFreeChart createChart() { + JFreeChart chart = ChartFactory.createTimeSeriesChart(null, null, null, null); + return chart; + } + + public void setRenderer(XYItemRenderer renderer) { + if (this.renderer != renderer) { + this.renderer = renderer; + XYPlot plot = (XYPlot) freeChart.getPlot(); + plot.setRenderer(renderer); + DefaultChartTheme.applyTheme(freeChart); + } + } + + @Override + protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + XYPlot plot = (XYPlot) chart.getPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + DateAxis domain = (DateAxis) plot.getDomainAxis(); + + range.setNumberFormatOverride(NumberFormat.getCurrencyInstance()); + range.setAxisLineVisible(false); + range.setTickMarksVisible(false); + range.setUpperMargin(0.2); + range.setLowerMargin(0.1); + + domain.setAxisLineVisible(false); + domain.setTickMarksVisible(false); + + plot.setDomainPannable(true); + plot.setRangeGridlinesVisible(false); + + plot.setRenderer(getDefaultRender()); + } + + @Override + protected void styleChart(JFreeChart chart, ChartPanel panel) { + XYPlot plot = (XYPlot) chart.getPlot(); + Color background = getBackground(); + Color foreground = getForeground(); + Font font = getFont(); + Color selectionColor = UIManager.getColor("List.selectionBackground"); + Color border = UIManager.getColor("Component.borderColor"); + + NumberAxis range = (NumberAxis) plot.getRangeAxis(); + DateAxis domain = (DateAxis) plot.getDomainAxis(); + + range.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + domain.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); + + plot.setDomainGridlineStroke(ChartDrawingSupplier.getDefaultGridlineStroke()); + plot.setInsets(ChartDrawingSupplier.scaleRectangleInsets(4, 8, 15, 8)); + + // annotation + MultiXYTextAnnotation annotation = (MultiXYTextAnnotation) plot.getAnnotations().get(0); + annotation.setBackgroundPaint(ChartDrawingSupplier.alpha(background, 0.7f)); + annotation.setDefaultPaint(foreground); + annotation.setFont(font); + annotation.setOutlinePaint(border); + annotation.setTitleLinePain(border); + annotation.setGridLinePaint(selectionColor); + } + + + @Override + protected void createAnnotation(JFreeChart chart, ChartPanel chartPanel) { + XYPlot plot = (XYPlot) chartPanel.getChart().getPlot(); + MultiXYTextAnnotation annotation = new MultiXYTextAnnotation(); + + DateFormat titleFormat = DateFormat.getDateInstance(); + NumberFormat valueFormat = NumberFormat.getCurrencyInstance(); + annotation.setTitleGenerator(xValue -> titleFormat.format(new Date((long) xValue))); + annotation.setNumberFormat(valueFormat); + + plot.addAnnotation(annotation); + chartPanel.addChartMouseListener(new ChartMouseListener() { + @Override + public void chartMouseClicked(ChartMouseEvent event) { + } + + @Override + public void chartMouseMoved(ChartMouseEvent event) { + Rectangle2D dataArea = chartPanel.getScreenDataArea(); + if (!dataArea.contains(event.getTrigger().getPoint())) { + annotation.setLabels(null); + return; + } + double x = plot.getDomainAxis().java2DToValue(event.getTrigger().getX(), dataArea, plot.getDomainAxisEdge()); + TableXYDataset dataset = (TableXYDataset) plot.getDataset(); + int seriesCount = plot.getSeriesCount(); + + double minDistance = Double.MAX_VALUE; + double closestX = 0; + boolean found = false; + + for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) { + for (int itemIndex = 0; itemIndex < plot.getDataset().getItemCount(seriesIndex); itemIndex++) { + double dataX = dataset.getXValue(seriesIndex, itemIndex); + double distance = Math.abs(x - dataX); + if (distance < minDistance) { + minDistance = distance; + closestX = dataX; + found = true; + } + } + } + if (found) { + annotation.autoCalculateX(closestX, dataset); + } + } + }); + } + + public XYItemRenderer getDefaultRender() { + if (renderer == null) { + renderer = new ChartXYCurveRenderer(); + } + return renderer; + } + + public void setDataset(XYDataset dataset) { + freeChart.getXYPlot().setDataset(dataset); + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java new file mode 100644 index 00000000..2222f7c7 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java @@ -0,0 +1,24 @@ +package backupmanager.component.chart.renderer; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.renderer.xy.DeviationStepRenderer; + +import java.awt.*; + +public class ChartDeviationStepRenderer extends DeviationStepRenderer { + + public ChartDeviationStepRenderer() { + initStyle(); + } + + private void initStyle() { + setAutoPopulateSeriesOutlinePaint(true); + setDefaultOutlineStroke(new BasicStroke(UIScale.scale(6f))); + setUseOutlinePaint(true); + } + + @Override + public String toString() { + return "Deviation Step"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java new file mode 100644 index 00000000..7df075d5 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java @@ -0,0 +1,15 @@ +package backupmanager.component.chart.renderer; + +import org.jfree.chart.renderer.xy.StackedXYBarRenderer; + +public class ChartStackedXYBarRenderer extends StackedXYBarRenderer { + + public ChartStackedXYBarRenderer() { + setMargin(0.3); + } + + @Override + public String toString() { + return "Stacked Bar"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java new file mode 100644 index 00000000..293f28cc --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java @@ -0,0 +1,18 @@ +package backupmanager.component.chart.renderer; + +import org.jfree.chart.renderer.xy.ClusteredXYBarRenderer; +import backupmanager.component.chart.themes.DefaultChartTheme; + +public class ChartXYBarRenderer extends ClusteredXYBarRenderer { + + public ChartXYBarRenderer() { + setBarPainter(DefaultChartTheme.getInstance().getXYBarPainter()); + setShadowVisible(DefaultChartTheme.getInstance().isShadowVisible()); + setMargin(0.3); + } + + @Override + public String toString() { + return "Bar"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java new file mode 100644 index 00000000..7b0251f8 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java @@ -0,0 +1,29 @@ +package backupmanager.component.chart.renderer; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.renderer.xy.XYSplineRenderer; + +public class ChartXYCurveRenderer extends XYSplineRenderer { + + private static final int precision = 10; + + public ChartXYCurveRenderer() { + this(UIScale.scale(precision)); + } + + public ChartXYCurveRenderer(int precision) { + super(precision); + initStyle(); + } + + private void initStyle() { + setAutoPopulateSeriesOutlinePaint(true); + setAutoPopulateSeriesOutlineStroke(true); + setUseOutlinePaint(true); + } + + @Override + public String toString() { + return "Curve"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java new file mode 100644 index 00000000..79078702 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java @@ -0,0 +1,21 @@ +package backupmanager.component.chart.renderer; + +import org.jfree.chart.renderer.xy.XYDifferenceRenderer; +import backupmanager.component.chart.themes.DefaultChartTheme; + +import java.awt.*; + +public class ChartXYDifferenceRenderer extends XYDifferenceRenderer { + + public ChartXYDifferenceRenderer() { + setPositivePaint(DefaultChartTheme.getColor(0)); + setNegativePaint(DefaultChartTheme.getColor(1)); + setAutoPopulateSeriesStroke(false); + setDefaultStroke(new BasicStroke(0f)); + } + + @Override + public String toString() { + return "Different"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java b/src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java new file mode 100644 index 00000000..d5dba0c5 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java @@ -0,0 +1,13 @@ +package backupmanager.component.chart.renderer; + +public class ChartXYLineRenderer extends ChartXYCurveRenderer { + + public ChartXYLineRenderer() { + super(1); + } + + @Override + public String toString() { + return "Line"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java b/src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java new file mode 100644 index 00000000..e9703049 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java @@ -0,0 +1,20 @@ +package backupmanager.component.chart.renderer.bar; + +import org.jfree.chart.renderer.category.BarRenderer; +import backupmanager.component.chart.themes.ChartDrawingSupplier; + +public class ChartBarRenderer extends BarRenderer { + + public ChartBarRenderer() { + initStyle(); + } + + private void initStyle() { + setDefaultLegendShape(ChartDrawingSupplier.getDefaultShape()); + } + + @Override + public String toString() { + return "Bar"; + } +} diff --git a/src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java b/src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java new file mode 100644 index 00000000..9b21904f --- /dev/null +++ b/src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java @@ -0,0 +1,34 @@ +package backupmanager.component.chart.renderer.other; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.renderer.xy.CandlestickRenderer; +import org.jfree.data.xy.OHLCDataset; + +import java.awt.*; + +public class ChartCandlestickRenderer extends CandlestickRenderer { + + public ChartCandlestickRenderer() { + initRedGreenColor(this); + setCandleWidth(UIScale.scale(8)); + setDefaultToolTipGenerator(null); + } + + @Override + public Paint getItemPaint(int row, int column) { + OHLCDataset highLowData = (OHLCDataset) getPlot().getDataset(); + double yOpen = highLowData.getOpenValue(row, column); + double yClose = highLowData.getCloseValue(row, column); + boolean isUpCandle = yClose > yOpen; + if (isUpCandle) { + return getUpPaint(); + } else { + return getDownPaint(); + } + } + + public static void initRedGreenColor(CandlestickRenderer renderer) { + renderer.setDownPaint(new Color(241, 89, 89)); + renderer.setUpPaint(new Color(37, 176, 127)); + } +} diff --git a/src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java b/src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java new file mode 100644 index 00000000..a2f2769e --- /dev/null +++ b/src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java @@ -0,0 +1,88 @@ +package backupmanager.component.chart.themes; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.plot.DefaultDrawingSupplier; +import org.jfree.chart.ui.RectangleInsets; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; + +public class ChartDrawingSupplier extends DefaultDrawingSupplier { + + protected ChartDrawingSupplier(ColorThemes colorThemes) { + this(colorThemes.getColors()); + } + + private ChartDrawingSupplier(Paint[] paintSequence) { + super(paintSequence, DEFAULT_FILL_PAINT_SEQUENCE, paintSequence, DEFAULT_STROKE_SEQUENCE, DEFAULT_OUTLINE_STROKE_SEQUENCE, DEFAULT_SHAPE_SEQUENCE); + } + + @Override + public Shape getNextShape() { + float size = UIScale.scale(3f); + return new Ellipse2D.Double(-size, -size, size * 2, size * 2); + } + + @Override + public Paint getNextOutlinePaint() { + return alpha(super.getNextOutlinePaint(), 0.2f); + } + + @Override + public Stroke getNextOutlineStroke() { + return scale(new BasicStroke(6f)); + } + + @Override + public Stroke getNextStroke() { + return scale(super.getNextStroke()); + } + + public static Shape getDefaultShape() { + float size = UIScale.scale(10f); + return new Ellipse2D.Double(0, 0, size, size); + } + + public static Stroke getDefaultGridlineStroke() { + float dash[] = {UIScale.scale(4f)}; + return new BasicStroke(UIScale.scale(1f), BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, UIScale.scale(5f), dash, 0f); + } + + public static Stroke scale(Stroke stroke) { + if (stroke instanceof BasicStroke) { + BasicStroke basicStroke = (BasicStroke) stroke; + float lineWidth = UIScale.scale(basicStroke.getLineWidth()); + if (lineWidth != basicStroke.getLineWidth()) { + return new BasicStroke(lineWidth, basicStroke.getEndCap(), basicStroke.getLineJoin(), basicStroke.getMiterLimit(), basicStroke.getDashArray(), basicStroke.getDashPhase()); + } + } + return stroke; + } + + public static Paint alpha(Paint paint, float alpha) { + if (paint instanceof Color) { + Color color = (Color) paint; + return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (255 * alpha)); + } + return paint; + } + + public static RectangleInsets scaleRectangleInsets(double top, double left, double bottom, double right) { + return new RectangleInsets(UIScale.scale((float) top), UIScale.scale((float) left), UIScale.scale((float) bottom), UIScale.scale((float) right)); + } + + public static RectangleInsets scaleRectangleInsets(RectangleInsets rec) { + return scaleRectangleInsets(rec.getTop(), rec.getLeft(), rec.getBottom(), rec.getRight()); + } + + public static Rectangle2D scaleRectangle(Rectangle2D rec) { + Rectangle r = rec.getBounds(); + return new Rectangle2D.Double(UIScale.scale((float) r.getX()), UIScale.scale((float) rec.getY()), UIScale.scale((float) rec.getWidth()), UIScale.scale((float) rec.getHeight())); + } + + public static Rectangle2D unscaleRectangle(Rectangle2D rec) { + Rectangle r = rec.getBounds(); + return new Rectangle2D.Double(UIScale.unscale((float) r.getX()), UIScale.unscale((float) rec.getY()), UIScale.unscale((float) rec.getWidth()), UIScale.unscale((float) rec.getHeight())); + } +} diff --git a/src/main/java/backupmanager/component/chart/themes/ColorThemes.java b/src/main/java/backupmanager/component/chart/themes/ColorThemes.java new file mode 100644 index 00000000..a4211976 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/themes/ColorThemes.java @@ -0,0 +1,87 @@ +package backupmanager.component.chart.themes; + +import java.awt.*; + +public enum ColorThemes { + DEFAULT( + Color.decode("#fd7f6f"), + Color.decode("#7eb0d5"), + Color.decode("#b2e061"), + Color.decode("#bd7ebe"), + Color.decode("#ffb55a"), + Color.decode("#ffee65"), + Color.decode("#beb9db"), + Color.decode("#fdcce5"), + Color.decode("#8bd3c7") + ), + RETRO_METRO( + Color.decode("#ea5545"), + Color.decode("#f46a9b"), + Color.decode("#ef9b20"), + Color.decode("#edbf33"), + Color.decode("#ede15b"), + Color.decode("#bdcf32"), + Color.decode("#87bc45"), + Color.decode("#27aeef"), + Color.decode("#b33dc6") + ), + BLUE_TO_YELLOW( + Color.decode("#115f9a"), + Color.decode("#1984c5"), + Color.decode("#22a7f0"), + Color.decode("#48b5c4"), + Color.decode("#76c68f"), + Color.decode("#a6d75b"), + Color.decode("#c9e52f"), + Color.decode("#d0ee11"), + Color.decode("#d0f400") + ), + SALMON_TO_AQUA( + Color.decode("#e27c7c"), + Color.decode("#a86464"), + Color.decode("#6d4b4b"), + Color.decode("#503f3f"), + Color.decode("#333333"), + Color.decode("#3c4e4b"), + Color.decode("#466964"), + Color.decode("#599e94"), + Color.decode("#6cd4c5") + ), + LIGHT( + Color.decode("#ffffff"), + Color.decode("#f0f0f0"), + Color.decode("#d9d9d9"), + Color.decode("#b3b3b3"), + Color.decode("#999999"), + Color.decode("#808080"), + Color.decode("#666666"), + Color.decode("#333333"), + Color.decode("#000000") + ), + DARK( + Color.decode("#444444"), + Color.decode("#555555"), + Color.decode("#666666"), + Color.decode("#777777"), + Color.decode("#888888"), + Color.decode("#999999"), + Color.decode("#aaaaaa"), + Color.decode("#bbbbbb"), + Color.decode("#cccccc") + ); + + private Color[] colors; + + ColorThemes(Color... colors) { + this.colors = colors; + } + + public Color[] getColors() { + return colors; + } + + @Override + public String toString() { + return super.toString().toLowerCase(); + } +} diff --git a/src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java b/src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java new file mode 100644 index 00000000..aed72261 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java @@ -0,0 +1,164 @@ +package backupmanager.component.chart.themes; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.StandardChartTheme; +import org.jfree.chart.plot.PiePlot; +import org.jfree.chart.plot.SpiderWebPlot; +import org.jfree.chart.renderer.AbstractRenderer; +import org.jfree.chart.renderer.category.BarRenderer; +import org.jfree.chart.renderer.category.StandardBarPainter; +import org.jfree.chart.renderer.xy.CandlestickRenderer; +import org.jfree.chart.renderer.xy.StandardXYBarPainter; +import org.jfree.chart.renderer.xy.XYDifferenceRenderer; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.chart.ui.RectangleEdge; + +import javax.swing.*; +import java.awt.*; +import java.awt.geom.RectangularShape; + +public class DefaultChartTheme extends StandardChartTheme { + + public static DefaultChartTheme getInstance() { + return instance; + } + + private static DefaultChartTheme instance = new DefaultChartTheme(); + public ColorThemes colorThemes = ColorThemes.DEFAULT; + + private DefaultChartTheme() { + super("Default Themes", false); + init(); + } + + private void init() { + Color background = new Color(0, 0, 0, 0); + Color foreground = UIManager.getColor("Label.foreground"); + Color border = UIManager.getColor("Component.borderColor"); + Font font = UIManager.getFont("Label.font"); + + setDrawingSupplier(new ChartDrawingSupplier(colorThemes)); + // chart + setChartBackgroundPaint(background); + + // plot + setPlotBackgroundPaint(background); + setPlotOutlinePaint(background); + + // renderer + setDomainGridlinePaint(border); + setRangeGridlinePaint(border); + + setBarPainter(new AlphaBarPainter()); + setXYBarPainter(new StandardXYBarPainter()); + + // text + setRegularFont(font); + setTitlePaint(foreground); + setSubtitlePaint(foreground); + setTickLabelPaint(foreground); + setItemLabelPaint(foreground); + setLabelLinkPaint(border); + + // legend + setLegendBackgroundPaint(background); + setLegendItemPaint(foreground); + + // other + } + + public static boolean setChartColors(ColorThemes colorThemes) { + if (instance.colorThemes != colorThemes) { + instance.colorThemes = colorThemes; + instance.setDrawingSupplier(new ChartDrawingSupplier(colorThemes)); + return true; + } + return false; + } + + public static Color getColor(int index) { + Color[] colors = instance.colorThemes.getColors(); + if (index > colors.length - 1) { + return colors[colors.length - 1]; + } + return colors[index]; + } + + public static Color[] getColors() { + return instance.colorThemes.getColors(); + } + + public static void applyTheme(JFreeChart chart) { + instance.init(); + instance.apply(chart); + } + + @Override + protected void applyToSpiderWebPlot(SpiderWebPlot plot) { + Color border = UIManager.getColor("Component.borderColor"); + plot.setLabelFont(getRegularFont()); + plot.setAxisLinePaint(border); + int index = 0; + for (Color color : instance.colorThemes.getColors()) { + boolean alpha = false; + Paint olePaint = plot.getSeriesPaint(index); + if (olePaint instanceof Color) { + alpha = ((Color) olePaint).getAlpha() < 255; + } + Paint c = alpha ? ChartDrawingSupplier.alpha(color, 0.3f) : color; + plot.setSeriesPaint(index, c); + plot.setSeriesOutlinePaint(index, c); + plot.setSeriesOutlineStroke(index, new BasicStroke(UIScale.scale(1f))); + index++; + } + } + + @Override + protected void applyToXYItemRenderer(XYItemRenderer renderer) { + super.applyToXYItemRenderer(renderer); + if (renderer != null) { + if (renderer instanceof XYDifferenceRenderer) { + XYDifferenceRenderer r = (XYDifferenceRenderer) renderer; + if (r.getAutoPopulateSeriesPaint()) { + r.setPositivePaint(getColor(0)); + r.setNegativePaint(getColor(1)); + } + } else if (renderer instanceof CandlestickRenderer) { + CandlestickRenderer r = (CandlestickRenderer) renderer; + if (r.getAutoPopulateSeriesPaint()) { + r.setDownPaint(getColor(0)); + r.setUpPaint(getColor(1)); + } + } + } + } + + @Override + protected void applyToAbstractRenderer(AbstractRenderer renderer) { + super.applyToAbstractRenderer(renderer); + + // apply null to series paint to get the new series paint + if (renderer.getAutoPopulateSeriesOutlinePaint()) { + int index = 0; + while (renderer.getSeriesOutlinePaint(index) != null) { + renderer.setSeriesOutlinePaint(index, null); + index++; + } + } + } + + @Override + protected void applyToPiePlot(PiePlot plot) { + plot.setLabelPaint(getItemLabelPaint()); + super.applyToPiePlot(plot); + } + + public class AlphaBarPainter extends StandardBarPainter { + @Override + public void paintBar(Graphics2D g2, BarRenderer renderer, int row, int column, RectangularShape bar, RectangleEdge base) { + g2.setComposite(AlphaComposite.SrcOver.derive(0.8f)); + super.paintBar(g2, renderer, row, column, bar, base); + } + } +} diff --git a/src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java b/src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java new file mode 100644 index 00000000..9fa1f794 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java @@ -0,0 +1,32 @@ +package backupmanager.component.chart.utils; + +import org.jfree.chart.plot.Crosshair; +import backupmanager.component.chart.themes.ChartDrawingSupplier; + +import javax.swing.*; +import java.awt.*; + +public class CustomCrosshairToolTip extends Crosshair { + + public CustomCrosshairToolTip() { + init(); + } + + private void init() { + installStyle(); + } + + public void installStyle() { + Color background = UIManager.getColor("Panel.background"); + Color foreground = UIManager.getColor("Label.foreground"); + Color border = UIManager.getColor("Component.borderColor"); + Font font = UIManager.getFont("Label.font"); + setLabelBackgroundPaint(ChartDrawingSupplier.alpha(background, 0.7f)); + setLabelPaint(foreground); + setLabelOutlinePaint(border); + setLabelFont(font); + setPaint(ChartDrawingSupplier.alpha(foreground, 0.5f)); + setStroke(ChartDrawingSupplier.getDefaultGridlineStroke()); + // setLabelPadding(ChartDrawingSupplier.scaleRectangleInsets(2, 5, 2, 5)); + } +} diff --git a/src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java b/src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java new file mode 100644 index 00000000..2e17d5f3 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java @@ -0,0 +1,26 @@ +package backupmanager.component.chart.utils; + +import org.jfree.chart.labels.CrosshairLabelGenerator; +import org.jfree.chart.plot.Crosshair; + +import java.io.Serializable; +import java.text.DateFormat; +import java.text.MessageFormat; + +public class DateCrosshairLabelGenerator implements CrosshairLabelGenerator, Serializable { + + private final String labelTemplate; + private final DateFormat format; + + public DateCrosshairLabelGenerator(String labelTemplate, DateFormat format) { + this.labelTemplate = labelTemplate; + this.format = format; + } + + @Override + public String generateLabel(Crosshair crosshair) { + Object[] v = new Object[]{this.format.format(crosshair.getValue())}; + String result = MessageFormat.format(this.labelTemplate, v); + return result; + } +} diff --git a/src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java b/src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java new file mode 100644 index 00000000..48f3130c --- /dev/null +++ b/src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java @@ -0,0 +1,545 @@ +package backupmanager.component.chart.utils; + +import com.formdev.flatlaf.util.UIScale; +import org.jfree.chart.annotations.AbstractXYAnnotation; +import org.jfree.chart.annotations.XYTextAnnotation; +import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.text.TextUtils; +import org.jfree.chart.ui.RectangleEdge; +import org.jfree.chart.ui.RectangleInsets; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.Args; +import org.jfree.data.general.DatasetUtils; +import org.jfree.data.xy.XYDataset; +import backupmanager.component.chart.themes.ChartDrawingSupplier; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.text.NumberFormat; + +public class MultiXYTextAnnotation extends AbstractXYAnnotation { + + + public Font getFont() { + return font; + } + + public void setFont(Font font) { + Args.nullNotPermitted(font, "font"); + this.font = font; + fireAnnotationChanged(); + } + + public Paint getPaint() { + return paint; + } + + public void setPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.paint = paint; + fireAnnotationChanged(); + } + + public Paint getBackgroundPaint() { + return backgroundPaint; + } + + public void setBackgroundPaint(Paint backgroundPaint) { + this.backgroundPaint = backgroundPaint; + fireAnnotationChanged(); + } + + public boolean isOutlineVisible() { + return outlineVisible; + } + + public void setOutlineVisible(boolean outlineVisible) { + this.outlineVisible = outlineVisible; + fireAnnotationChanged(); + } + + public Paint getOutlinePaint() { + return outlinePaint; + } + + public void setOutlinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.outlinePaint = paint; + fireAnnotationChanged(); + } + + public Paint getGridLinePaint() { + return gridLinePaint; + } + + public void setGridLinePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.gridLinePaint = paint; + fireAnnotationChanged(); + } + + public Paint getTitlePaint() { + return titlePaint; + } + + public void setTitlePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.titlePaint = paint; + fireAnnotationChanged(); + } + + public Paint getValuePaint() { + return valuePaint; + } + + public void setValuePaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.valuePaint = paint; + fireAnnotationChanged(); + } + + public Paint getTitleLinePain() { + return titleLinePain; + } + + public void setTitleLinePain(Paint paint) { + this.titleLinePain = paint; + fireAnnotationChanged(); + } + + public Stroke getOutlineStroke() { + return outlineStroke; + } + + public void setOutlineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.outlineStroke = stroke; + fireAnnotationChanged(); + } + + public Stroke getTitleLineStroke() { + return titleLineStroke; + } + + public void setTitleLineStroke(Stroke stroke) { + Args.nullNotPermitted(stroke, "stroke"); + this.titleLineStroke = stroke; + fireAnnotationChanged(); + } + + public double getGap() { + return gap; + } + + public void setGap(double gap) { + this.gap = gap; + fireAnnotationChanged(); + } + + public double getVerticalTextGap() { + return verticalTextGap; + } + + public void setVerticalTextGap(double verticalTextGap) { + this.verticalTextGap = verticalTextGap; + fireAnnotationChanged(); + } + + public RectangleInsets getPadding() { + return padding; + } + + public void setPadding(RectangleInsets padding) { + Args.nullNotPermitted(padding, "padding"); + this.padding = padding; + fireAnnotationChanged(); + } + + public RectangleInsets getSeriesPadding() { + return seriesPadding; + } + + public void setSeriesPadding(RectangleInsets padding) { + Args.nullNotPermitted(padding, "padding"); + this.seriesPadding = padding; + fireAnnotationChanged(); + } + + public RectangleInsets getTitlePadding() { + return titlePadding; + } + + public void setTitlePadding(RectangleInsets padding) { + Args.nullNotPermitted(padding, "padding"); + this.titlePadding = padding; + fireAnnotationChanged(); + } + + public RectangleInsets getTitleLinePadding() { + return titleLinePadding; + } + + public void setTitleLinePadding(RectangleInsets padding) { + Args.nullNotPermitted(padding, "padding"); + this.titleLinePadding = padding; + fireAnnotationChanged(); + } + + public double getRound() { + return round; + } + + public void setRound(double round) { + this.round = round; + fireAnnotationChanged(); + } + + public double getSeriesSize() { + return seriesSize; + } + + public void setSeriesSize(double seriesSize) { + this.seriesSize = seriesSize; + fireAnnotationChanged(); + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + fireAnnotationChanged(); + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + fireAnnotationChanged(); + } + + public Label[] getLabels() { + return labels; + } + + public void setLabels(Label[] labels) { + if (this.labels != labels) { + this.labels = labels; + fireAnnotationChanged(); + } + } + + public NumberFormat getNumberFormat() { + return numberFormat; + } + + public void setNumberFormat(NumberFormat numberFormat) { + Args.nullNotPermitted(numberFormat, "numberFormat"); + this.numberFormat = numberFormat; + fireAnnotationChanged(); + } + + public TitleGenerator getTitleGenerator() { + return titleGenerator; + } + + public void setTitleGenerator(TitleGenerator titleGenerator) { + this.titleGenerator = titleGenerator; + fireAnnotationChanged(); + } + + public void setDefaultPaint(Paint paint) { + Args.nullNotPermitted(paint, "paint"); + this.paint = paint; + this.titlePaint = paint; + this.valuePaint = paint; + fireAnnotationChanged(); + } + + private Font font; + private Paint paint; + private Paint backgroundPaint; + private boolean outlineVisible; + private Paint outlinePaint; + private Paint gridLinePaint; + private Paint titlePaint; + private Paint valuePaint; + private Paint titleLinePain; + private Stroke outlineStroke; + private Stroke titleLineStroke; + private double gap; + private double verticalTextGap; + private RectangleInsets padding; + private RectangleInsets seriesPadding; + private RectangleInsets titlePadding; + private RectangleInsets titleLinePadding; + private double round; + private double seriesSize; + private double x; + private double y; + private Label[] labels; + private NumberFormat numberFormat; + private TitleGenerator titleGenerator; + + public MultiXYTextAnnotation() { + this.paint = XYTextAnnotation.DEFAULT_PAINT; + this.font = XYTextAnnotation.DEFAULT_FONT; + this.backgroundPaint = new Color(255, 255, 255, 200); + this.outlinePaint = new Color(190, 190, 190); + this.gridLinePaint = new Color(25, 104, 148); + this.titlePaint = XYTextAnnotation.DEFAULT_PAINT; + this.valuePaint = XYTextAnnotation.DEFAULT_PAINT; + this.titleLinePain = new Color(190, 190, 190); + this.outlineVisible = true; + this.numberFormat = NumberFormat.getNumberInstance(); + this.padding = new RectangleInsets(10, 10, 10, 10); + this.seriesPadding = new RectangleInsets(3, 2, 3, 5); + this.titlePadding = new RectangleInsets(0, 0, 8, 0); + this.titleLinePadding = new RectangleInsets(0, 0, 8, 0); + this.outlineStroke = new BasicStroke(0.5f); + this.titleLineStroke = new BasicStroke(0.5f); + this.round = 10; + this.seriesSize = 10; + this.gap = 10; + this.verticalTextGap = 5; + } + + public void autoCalculateX(double x, XYDataset dataset) { + if (this.x != x || labels == null) { + this.x = x; + createValues(dataset); + } + } + + private void createValues(XYDataset dataset) { + int seriesCount = dataset.getSeriesCount(); + Label[] labels = new Label[seriesCount]; + double value = DatasetUtils.findYValue(dataset, 0, this.x); + double closestY = value; + labels[0] = new Label(dataset.getSeriesKey(0).toString(), getNumberFormat().format(value)); + for (int i = 1; i < seriesCount; i++) { + double y = DatasetUtils.findYValue(dataset, i, this.x); + String v = getNumberFormat().format(y); + String t = dataset.getSeriesKey(i).toString(); + labels[i] = new Label(t, v); + closestY = Math.max(closestY, y); + } + y = closestY; + this.labels = labels; + fireAnnotationChanged(); + } + + @Override + public void draw(Graphics2D g, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) { + Graphics2D g2 = (Graphics2D) g.create(); + if (labels != null) { + UIScale.scaleGraphics(g2); + PlotOrientation orientation = plot.getOrientation(); + RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(plot.getDomainAxisLocation(), orientation); + RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(plot.getRangeAxisLocation(), orientation); + + float anchorX = UIScale.unscale((float) domainAxis.valueToJava2D(this.getX(), dataArea, domainEdge)); + float anchorY = UIScale.unscale((float) rangeAxis.valueToJava2D(this.getY(), dataArea, rangeEdge)); + + if (orientation == PlotOrientation.HORIZONTAL) { + float tempAnchor = anchorX; + anchorX = anchorY; + anchorY = tempAnchor; + } + Font font = getFont(); + g2.setFont(font.deriveFont(UIScale.unscale((float) font.getSize()))); + + String title = getTitleGenerator() == null ? null : titleGenerator.getTitle(this.x); + Rectangle2D bgRec = getBackgroundRectangle(g2, anchorX, anchorY, title); + + // adjust annotation + int shadow = 5; + int space = 2; + Rectangle2D rec = ChartDrawingSupplier.scaleRectangleInsets(space, space, space + shadow, space + shadow).createInsetRectangle(dataArea); + + Rectangle2D bgRecScale = ChartDrawingSupplier.scaleRectangle(bgRec); + if (!rec.contains(bgRecScale)) { + double x = bgRecScale.getX(); + double y = bgRecScale.getY(); + double width = bgRecScale.getWidth(); + double height = bgRecScale.getHeight(); + if (x < rec.getX()) { + x = rec.getX(); + } else if (x + width > rec.getX() + rec.getWidth()) { + x = rec.getX() + rec.getWidth() - width; + } + if (y < rec.getY()) { + y = rec.getY(); + } else if (y + height > rec.getY() + rec.getHeight()) { + y = rec.getY() + rec.getHeight() - height; + } + bgRec = ChartDrawingSupplier.unscaleRectangle(new Rectangle2D.Double(x, y, width, height)); + } + Shape shape = round > 0 ? + new RoundRectangle2D.Double(bgRec.getX(), bgRec.getY(), bgRec.getWidth(), bgRec.getHeight(), round, round) : + bgRec; + + // draw domain grid line without scale ui + g.setStroke(plot.getDomainGridlineStroke()); + g.setPaint(getGridLinePaint()); + double gx = UIScale.scale(anchorX); + g.draw(new Line2D.Double(gx, dataArea.getY(), gx, dataArea.getY() + dataArea.getHeight())); + + if (getBackgroundPaint() != null) { + g2.setPaint(getBackgroundPaint()); + g2.fill(shape); + } + float x = (float) (bgRec.getX() + getSeriesSizeWidth() + getPadding().getLeft()); + float y = (float) (bgRec.getY() + getPadding().getTop()); + float x2 = (float) (bgRec.getX() + bgRec.getWidth() - getPadding().getRight()); + float seriesX = (float) (bgRec.getX() + getPadding().getLeft()); + + // draw title + if (title != null) { + y += getTitlePadding().getTop(); + float titleX = (float) (bgRec.getX() + getPadding().getLeft() + getTitlePadding().getLeft()); + y += drawTitle(g2, titleX, y, title); + y += getTitlePadding().getBottom(); + if (getTitleLinePain() != null) { + float titleLineWidth = getTitleLineStrokeWidth(); + g2.setStroke(getTitleLineStroke()); + g2.setPaint(getTitleLinePain()); + double lineWidth = bgRec.getWidth() - getTitleLinePadding().getLeft() - getTitleLinePadding().getRight(); + double lineX = bgRec.getX() + getTitleLinePadding().getLeft(); + y += getTitleLinePadding().getTop(); + g2.draw(new Line2D.Double(lineX, y + titleLineWidth / 2f, lineX + lineWidth, y + titleLineWidth / 2f)); + y += titleLineWidth + getTitleLinePadding().getBottom(); + } + } + + // draw label + for (int i = 0; i < labels.length; i++) { + Label label = labels[i]; + float size = (float) drawLabel(g2, x, y, x2, label); + drawSeries(g2, seriesX, y, size, plot.getRenderer().getSeriesPaint(i)); + y += size + getVerticalTextGap(); + } + if (isOutlineVisible()) { + g2.setStroke(getOutlineStroke()); + g2.setPaint(getOutlinePaint()); + g2.draw(shape); + } + String toolTip = getToolTipText(); + String url = getURL(); + if (toolTip != null || url != null) { + addEntity(info, shape, rendererIndex, toolTip, url); + } + } + g2.dispose(); + } + + protected double drawTitle(Graphics2D g2, float x, float y, String title) { + g2.setPaint(getTitlePaint()); + double textHeight = TextUtils.drawAlignedString(title, g2, x, y, TextAnchor.TOP_LEFT).getHeight(); + return textHeight; + } + + protected double drawLabel(Graphics2D g2, float x, float y, float x2, Label label) { + g2.setPaint(getPaint()); + double textHeight = TextUtils.drawAlignedString(label.getText(), g2, x, y, TextAnchor.TOP_LEFT).getHeight(); + g2.setPaint(getValuePaint()); + double valueHeight = TextUtils.drawAlignedString(label.getValue(), g2, x2, y, TextAnchor.TOP_RIGHT).getHeight(); + return Math.max(textHeight, valueHeight); + } + + protected void drawSeries(Graphics2D g2, float x, float y, float height, Paint paint) { + double size = Math.min(height - (getSeriesPadding().getTop() + getSeriesPadding().getBottom()), getSeriesSize()); + double lx = x + (getSeriesSize() - size) / 2f; + double ly = y + ((height - size) / 2); + g2.setPaint(paint); + g2.fill(new Ellipse2D.Double(lx, ly, size, size)); + } + + protected Rectangle2D getBackgroundRectangle(Graphics2D g2, float anchorX, float anchorY, String title) { + if (labels == null || labels.length == 0) return null; + + double textWidth = 0, valueWidth = 0, titleWidth = 0, minLineWidth = 0, totalHeight = 0; + FontMetrics fm = g2.getFontMetrics(); + for (int i = 0; i < labels.length; i++) { + Label label = labels[i]; + Rectangle2D textBounds = TextUtils.getTextBounds(label.getText(), g2, fm); + Rectangle2D valueBounds = TextUtils.getTextBounds(label.getValue(), g2, fm); + totalHeight += Math.max(textBounds.getHeight(), valueBounds.getHeight()); + textWidth = Math.max(textWidth, textBounds.getWidth()); + valueWidth = Math.max(valueWidth, valueBounds.getWidth()); + } + if (title != null) { + Rectangle2D titleBounds = TextUtils.getTextBounds(title, g2, fm); + titleWidth = titleBounds.getWidth() + getTitlePadding().getLeft() + getTitlePadding().getRight(); + totalHeight += titleBounds.getHeight(); + totalHeight += getTitlePadding().getTop() + getTitlePadding().getBottom(); + if (getTitleLinePain() != null) { + totalHeight += getTitleLineStrokeWidth() + getTitleLinePadding().getTop() + getTitleLinePadding().getBottom(); + minLineWidth = getTitleLinePadding().getLeft() + getTitleLinePadding().getRight(); + } + } + if (labels.length > 1) { + totalHeight += (getVerticalTextGap() * (labels.length - 1)); + } + totalHeight += getPadding().getTop() + getPadding().getBottom(); + + double totalLabelWidth = textWidth + valueWidth + getSeriesSizeWidth() + getGap(); + double totalWidth = Math.max(totalLabelWidth, Math.max(titleWidth, minLineWidth)) + getPadding().getLeft() + getPadding().getRight(); + double space = 10; + return new Rectangle2D.Double(anchorX - space, anchorY - totalHeight - space, totalWidth, totalHeight); + } + + private double getSeriesSizeWidth() { + return getSeriesSize() + getSeriesPadding().getLeft() + getSeriesPadding().getRight(); + } + + private float getTitleLineStrokeWidth() { + Stroke stroke = getTitleLineStroke(); + if (stroke instanceof BasicStroke) { + float lineSize = ((BasicStroke) stroke).getLineWidth(); + return lineSize; + } + return 0; + } + + public static class Label { + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Label(String text, String value) { + this.text = text; + this.value = value; + } + + private String text; + private String value; + } + + public interface TitleGenerator { + String getTitle(double xValue); + } +} diff --git a/src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java b/src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java new file mode 100644 index 00000000..2c34ceb1 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java @@ -0,0 +1,14 @@ +package backupmanager.component.chart.utils; + +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PlotOrientation; +import backupmanager.component.ToolBarSelection; + +public class ToolBarCategoryOrientation extends ToolBarSelection { + + public ToolBarCategoryOrientation(JFreeChart chart) { + super(new String[]{"Vertical", "Horizontal"}, orientation -> { + chart.getCategoryPlot().setOrientation(orientation == "Horizontal" ? PlotOrientation.HORIZONTAL : PlotOrientation.VERTICAL); + }); + } +} diff --git a/src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java b/src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java new file mode 100644 index 00000000..5488d8b8 --- /dev/null +++ b/src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java @@ -0,0 +1,27 @@ +package backupmanager.component.chart.utils; + +import org.jfree.chart.renderer.xy.XYItemRenderer; +import backupmanager.component.ToolBarSelection; +import backupmanager.component.chart.TimeSeriesChart; +import backupmanager.component.chart.renderer.*; + +public class ToolBarTimeSeriesChartRenderer extends ToolBarSelection { + + public ToolBarTimeSeriesChartRenderer(TimeSeriesChart chart) { + super(getRenderers(), renderer -> { + chart.setRenderer(renderer); + }); + } + + private static XYItemRenderer[] getRenderers() { + XYItemRenderer[] renderers = new XYItemRenderer[]{ + // new ChartXYCurveRenderer(), + // new ChartXYLineRenderer(), + new ChartXYBarRenderer(), + new ChartStackedXYBarRenderer(), + new ChartDeviationStepRenderer(), + new ChartXYDifferenceRenderer() + }; + return renderers; + } +} diff --git a/src/main/java/backupmanager/component/dashboard/CardBox.java b/src/main/java/backupmanager/component/dashboard/CardBox.java new file mode 100644 index 00000000..c6114871 --- /dev/null +++ b/src/main/java/backupmanager/component/dashboard/CardBox.java @@ -0,0 +1,44 @@ +package backupmanager.component.dashboard; + +import com.formdev.flatlaf.FlatClientProperties; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class CardBox extends JPanel { + + private final List cardItems = new ArrayList<>(); + + public CardBox() { + init(); + } + + private void init() { + setLayout(new MigLayout("", "[fill]", "[fill]")); + putClientProperty(FlatClientProperties.STYLE_CLASS, "dashboardBackground"); + } + + private void createSeparator() { + add(new JSeparator(JSeparator.VERTICAL), "width 3!"); + } + + public void addCardItem(Icon icon, String title) { + CardItem cardItem = new CardItem(icon, title); + cardItems.add(cardItem); + if (cardItems.size() > 1) { + createSeparator(); + } + add(cardItem, "width 100%"); + } + + public void setValueAt(int index, String value, String description, String tags, boolean up) { + cardItems.get(index).setValue(value, description, tags, up); + } + + public void setCardIconColor(int index, Color color) { + cardItems.get(index).setCardIconColor(color); + } +} diff --git a/src/main/java/backupmanager/component/dashboard/CardItem.java b/src/main/java/backupmanager/component/dashboard/CardItem.java new file mode 100644 index 00000000..d51b7e1b --- /dev/null +++ b/src/main/java/backupmanager/component/dashboard/CardItem.java @@ -0,0 +1,64 @@ +package backupmanager.component.dashboard; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; + +public class CardItem extends JPanel { + + public CardItem(Icon icon, String title) { + init(icon, title); + } + + private void init(Icon icon, String title) { + setLayout(new MigLayout("hidemode 3,wrap", "15 push[] 15 push")); + putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + + lbTitle = new JLabel(title, icon, JLabel.LEADING); + lbValue = new JLabel("0"); + lbDescription = new JLabel("description"); + lbTags = new JLabel("0%"); + + lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + lbValue.putClientProperty(FlatClientProperties.STYLE, "" + + "font:+8;"); + + lbDescription.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + + lbTags.putClientProperty(FlatClientProperties.STYLE_CLASS, "greenBadge small"); + + add(lbTitle); + add(lbValue); + add(lbDescription, "split 2"); + add(lbTags); + } + + public void setValue(String value, String description, String tags, boolean up) { + lbValue.setText(value); + lbDescription.setText(description); + lbTags.setText(tags); + lbTags.putClientProperty(FlatClientProperties.STYLE_CLASS, (up ? "greenBadge" : "redBadge") + " small"); + lbDescription.setVisible(description != null); + lbTags.setVisible(tags != null); + } + + + public void setCardIconColor(Color color) { + if (lbTitle.getIcon() != null && lbTitle.getIcon() instanceof FlatSVGIcon) { + FlatSVGIcon icon = (FlatSVGIcon) lbTitle.getIcon(); + icon.getColorFilter().setMapper(color1 -> color); + repaint(); + } + } + + private JLabel lbTitle; + private JLabel lbValue; + private JLabel lbDescription; + private JLabel lbTags; +} diff --git a/src/main/java/backupmanager/forms/FormDashboard.java b/src/main/java/backupmanager/forms/FormDashboard.java new file mode 100644 index 00000000..ee7ac5a5 --- /dev/null +++ b/src/main/java/backupmanager/forms/FormDashboard.java @@ -0,0 +1,233 @@ +package backupmanager.forms; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; + +import org.jfree.chart.renderer.xy.CandlestickRenderer; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.util.UIScale; + +import backupmanager.component.ToolBarSelection; +import backupmanager.component.chart.BarChart; +import backupmanager.component.chart.CandlestickChart; +import backupmanager.component.chart.PieChart; +import backupmanager.component.chart.SpiderChart; +import backupmanager.component.chart.TimeSeriesChart; +import backupmanager.component.chart.renderer.other.ChartCandlestickRenderer; +import backupmanager.component.chart.themes.ColorThemes; +import backupmanager.component.chart.themes.DefaultChartTheme; +import backupmanager.component.chart.utils.ToolBarCategoryOrientation; +import backupmanager.component.chart.utils.ToolBarTimeSeriesChartRenderer; +import backupmanager.component.dashboard.CardBox; +import backupmanager.sample.SampleData; +import backupmanager.system.Form; +import backupmanager.utils.SystemForm; +import net.miginfocom.swing.MigLayout; + +@SystemForm(name = "Dashboard", description = "dashboard form display some details") +public class FormDashboard extends Form { + + public FormDashboard() { + init(); + } + + private void init() { + setLayout(new MigLayout("wrap,fill", "[fill]", "[grow 0][fill]")); + createTitle(); + createPanelLayout(); + createCard(); + createChart(); + createOtherChart(); + } + + @Override + public void formInit() { + loadData(); + } + + @Override + public void formRefresh() { + loadData(); + } + + private void loadData() { + // load data card + cardBox.setValueAt(0, "1,205", "+305 new registered", "+25%", true); + cardBox.setValueAt(1, "$52,420.55", "less then previous month", "-5%", false); + cardBox.setValueAt(2, "$3,180.00", "more then previous month", "+12%", true); + cardBox.setValueAt(3, "$49,240.55", "more then previous month", "+7%", true); + + // load data chart + timeSeriesChart.setDataset(SampleData.getTimeSeriesDataset()); + candlestickChart.setDataset(SampleData.getOhlcDataset()); + barChart.setDataset(SampleData.getCategoryDataset()); + spiderChart.setDataset(SampleData.getCategoryDataset()); + pieChart.setDataset(SampleData.getPieDataset()); + } + + private void createTitle() { + JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); + JLabel title = new JLabel("Dashboard"); + + title.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +3"); + + ToolBarSelection toolBarSelection = new ToolBarSelection<>(ColorThemes.values(), colorThemes -> { + if (DefaultChartTheme.setChartColors(colorThemes)) { + DefaultChartTheme.applyTheme(timeSeriesChart.getFreeChart()); + DefaultChartTheme.applyTheme(candlestickChart.getFreeChart()); + DefaultChartTheme.applyTheme(barChart.getFreeChart()); + DefaultChartTheme.applyTheme(pieChart.getFreeChart()); + DefaultChartTheme.applyTheme(spiderChart.getFreeChart()); + cardBox.setCardIconColor(0, DefaultChartTheme.getColor(0)); + cardBox.setCardIconColor(1, DefaultChartTheme.getColor(1)); + cardBox.setCardIconColor(2, DefaultChartTheme.getColor(2)); + cardBox.setCardIconColor(3, DefaultChartTheme.getColor(3)); + } + }); + panel.add(title); + panel.add(toolBarSelection); + add(panel); + } + + private void createPanelLayout() { + panelLayout = new JPanel(new DashboardLayout()); + JScrollPane scrollPane = new JScrollPane(panelLayout); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.getVerticalScrollBar().setUnitIncrement(10); + scrollPane.getVerticalScrollBar().putClientProperty(FlatClientProperties.STYLE, "" + + "width:5;" + + "trackArc:$ScrollBar.thumbArc;" + + "trackInsets:0,0,0,0;" + + "thumbInsets:0,0,0,0;"); + add(scrollPane); + } + + private void createCard() { + JPanel panel = new JPanel(new MigLayout("fillx", "[fill]")); + cardBox = new CardBox(); + cardBox.addCardItem(createIcon("icons/dashboard/customer.svg", DefaultChartTheme.getColor(0)), "Total Customer"); + cardBox.addCardItem(createIcon("icons/dashboard/income.svg", DefaultChartTheme.getColor(1)), "Total Income"); + cardBox.addCardItem(createIcon("icons/dashboard/expense.svg", DefaultChartTheme.getColor(2)), "Total Expense"); + cardBox.addCardItem(createIcon("icons/dashboard/profit.svg", DefaultChartTheme.getColor(3)), "Last Profit"); + panel.add(cardBox); + panelLayout.add(panel); + } + + private void createChart() { + JPanel panel = new JPanel(new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); + timeSeriesChart = new TimeSeriesChart(); + candlestickChart = new CandlestickChart(); + barChart = new BarChart(); + timeSeriesChart.add(new ToolBarTimeSeriesChartRenderer(timeSeriesChart), "al trailing,grow 0", 0); + candlestickChart.add(new ToolBarSelection<>(new String[]{"default", "red_green"}, s -> { + CandlestickRenderer renderer = (CandlestickRenderer) candlestickChart.getFreeChart().getXYPlot().getRenderer(); + if (s == "default") { + renderer.setAutoPopulateSeriesPaint(true); + DefaultChartTheme.applyTheme(candlestickChart.getFreeChart()); + } else { + renderer.setAutoPopulateSeriesPaint(false); + ChartCandlestickRenderer.initRedGreenColor(renderer); + } + }), "al trailing,grow 0", 0); + barChart.add(new ToolBarCategoryOrientation(barChart.getFreeChart()), "al trailing,grow 0", 0); + panel.add(timeSeriesChart); + panel.add(candlestickChart); + panel.add(barChart); + panelLayout.add(panel); + } + + private void createOtherChart() { + JPanel panel = new JPanel(new MigLayout("fillx,gap 14", "[fill,300::]", "[300]")); + spiderChart = new SpiderChart(); + pieChart = new PieChart(); + panel.add(spiderChart); + panel.add(pieChart); + panelLayout.add(panel); + } + + private Icon createIcon(String icon, Color color) { + return new FlatSVGIcon(icon, 0.4f).setColorFilter(new FlatSVGIcon.ColorFilter(color1 -> color)); + } + + private JPanel panelLayout; + private CardBox cardBox; + + private TimeSeriesChart timeSeriesChart; + private CandlestickChart candlestickChart; + private BarChart barChart; + private SpiderChart spiderChart; + private PieChart pieChart; + + private class DashboardLayout implements LayoutManager { + + private int gap = 0; + + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + synchronized (parent.getTreeLock()) { + Insets insets = parent.getInsets(); + int width = (insets.left + insets.right); + int height = insets.top + insets.bottom; + int g = UIScale.scale(gap); + int count = parent.getComponentCount(); + for (int i = 0; i < count; i++) { + Component com = parent.getComponent(i); + Dimension size = com.getPreferredSize(); + height += size.height; + } + if (count > 1) { + height += (count - 1) * g; + } + return new Dimension(width, height); + } + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + synchronized (parent.getTreeLock()) { + return new Dimension(10, 10); + } + } + + @Override + public void layoutContainer(Container parent) { + synchronized (parent.getTreeLock()) { + Insets insets = parent.getInsets(); + int x = insets.left; + int y = insets.top; + int width = parent.getWidth() - (insets.left + insets.right); + int g = UIScale.scale(gap); + int count = parent.getComponentCount(); + for (int i = 0; i < count; i++) { + Component com = parent.getComponent(i); + Dimension size = com.getPreferredSize(); + com.setBounds(x, y, width, size.height); + y += size.height + g; + } + } + } + } +} diff --git a/src/main/java/backupmanager/forms/FormSetting.java b/src/main/java/backupmanager/forms/FormSetting.java new file mode 100644 index 00000000..e2a9388c --- /dev/null +++ b/src/main/java/backupmanager/forms/FormSetting.java @@ -0,0 +1,437 @@ +package backupmanager.forms; + +import java.awt.Color; +import java.awt.Component; +import java.awt.ComponentOrientation; +import java.awt.event.ActionEvent; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTabbedPane; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; +import javax.swing.LookAndFeel; +import javax.swing.UIManager; +import javax.swing.border.TitledBorder; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatDarculaLaf; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.themes.FlatMacDarkLaf; +import com.formdev.flatlaf.themes.FlatMacLightLaf; +import com.formdev.flatlaf.util.LoggingFacade; +import com.formdev.flatlaf.util.ScaledEmptyBorder; + +import backupmanager.component.AccentColorIcon; +import backupmanager.system.Form; +import backupmanager.system.FormManager; +import backupmanager.themes.PanelThemes; +import backupmanager.utils.DemoPreferences; +import backupmanager.utils.SystemForm; +import net.miginfocom.swing.MigLayout; +import raven.color.ColorPicker; +import raven.modal.Drawer; +import raven.modal.ModalDialog; +import raven.modal.component.SimpleModalBorder; +import raven.modal.drawer.DrawerBuilder; +import raven.modal.drawer.renderer.AbstractDrawerLineStyleRenderer; +import raven.modal.drawer.renderer.DrawerCurvedLineStyle; +import raven.modal.drawer.renderer.DrawerStraightDotLineStyle; +import raven.modal.drawer.simple.SimpleDrawerBuilder; +import raven.modal.option.LayoutOption; +import raven.modal.option.Location; +import raven.modal.option.Option; + +@SystemForm(name = "Setting", description = "application setting and configuration", tags = {"themes", "options"}) +public class FormSetting extends Form { + + public FormSetting() { + init(); + } + + private void init() { + setLayout(new MigLayout("fill", "[fill][fill,grow 0,250:250]", "[fill]")); + tabbedPane = new JTabbedPane(); + tabbedPane.putClientProperty(FlatClientProperties.STYLE, "" + + "tabType:card"); + + tabbedPane.addTab("Layout", createLayoutOption()); + tabbedPane.addTab("Style", createStyleOption()); + add(tabbedPane, "gapy 1 0"); + add(createThemes()); + } + + private JPanel createLayoutOption() { + JPanel panel = new JPanel(new MigLayout("wrap,fillx", "[fill]")); + panel.add(createWindowsLayout()); + panel.add(createDrawerLayout()); + panel.add(createModalDefaultOption()); + return panel; + } + + private Component createWindowsLayout() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(new TitledBorder("Windows layout")); + JCheckBox chRightToLeft = new JCheckBox("Right to Left", !getComponentOrientation().isLeftToRight()); + JCheckBox chFullWindow = new JCheckBox("Full Window Content", FlatClientProperties.clientPropertyBoolean(FormManager.getFrame().getRootPane(), FlatClientProperties.FULL_WINDOW_CONTENT, false)); + chRightToLeft.addActionListener(e -> { + if (chRightToLeft.isSelected()) { + FormManager.getFrame().applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); + } else { + FormManager.getFrame().applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + } + FormManager.getFrame().revalidate(); + }); + chFullWindow.addActionListener(e -> { + FormManager.getFrame().getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, chFullWindow.isSelected()); + }); + panel.add(chRightToLeft); + panel.add(chFullWindow); + return panel; + } + + private Component createDrawerLayout() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(new TitledBorder("Drawer layout")); + + JRadioButton jrLeft = new JRadioButton("Left"); + JRadioButton jrLeading = new JRadioButton("Leading"); + JRadioButton jrTrailing = new JRadioButton("Trailing"); + JRadioButton jrRight = new JRadioButton("Right"); + JRadioButton jrTop = new JRadioButton("Top"); + JRadioButton jrBottom = new JRadioButton("Bottom"); + + ButtonGroup group = new ButtonGroup(); + group.add(jrLeft); + group.add(jrLeading); + group.add(jrTrailing); + group.add(jrRight); + group.add(jrTop); + group.add(jrBottom); + + jrLeading.setSelected(true); + + jrLeft.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(drawerBuilder.getDrawerWidth(), 1f) + .setLocation(Location.LEFT, Location.TOP) + .setAnimateDistance(-0.7f, 0f); + getRootPane().revalidate(); + }); + jrLeading.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(drawerBuilder.getDrawerWidth(), 1f) + .setLocation(Location.LEADING, Location.TOP) + .setAnimateDistance(-0.7f, 0f); + getRootPane().revalidate(); + }); + jrTrailing.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(drawerBuilder.getDrawerWidth(), 1f) + .setLocation(Location.TRAILING, Location.TOP) + .setAnimateDistance(0.7f, 0f); + getRootPane().revalidate(); + }); + jrRight.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(drawerBuilder.getDrawerWidth(), 1f) + .setLocation(Location.RIGHT, Location.TOP) + .setAnimateDistance(0.7f, 0f); + getRootPane().revalidate(); + }); + jrTop.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(1f, drawerBuilder.getDrawerWidth()) + .setLocation(Location.LEADING, Location.TOP) + .setAnimateDistance(0f, -0.7f); + getRootPane().revalidate(); + }); + jrBottom.addActionListener(e -> { + DrawerBuilder drawerBuilder = Drawer.getDrawerBuilder(); + LayoutOption layoutOption = Drawer.getDrawerOption().getLayoutOption(); + layoutOption.setSize(1f, drawerBuilder.getDrawerWidth()) + .setLocation(Location.LEADING, Location.BOTTOM) + .setAnimateDistance(0f, 0.7f); + getRootPane().revalidate(); + }); + + panel.add(jrLeft); + panel.add(jrLeading); + panel.add(jrTrailing); + panel.add(jrRight); + panel.add(jrTop); + panel.add(jrBottom); + return panel; + } + + private Component createModalDefaultOption() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(new TitledBorder("Default modal option")); + JCheckBox chAnimation = new JCheckBox("Animation enable"); + JCheckBox chCloseOnPressedEscape = new JCheckBox("Close on pressed escape"); + chAnimation.setSelected(ModalDialog.getDefaultOption().isAnimationEnabled()); + chCloseOnPressedEscape.setSelected(ModalDialog.getDefaultOption().isCloseOnPressedEscape()); + + chAnimation.addActionListener(e -> ModalDialog.getDefaultOption().setAnimationEnabled(chAnimation.isSelected())); + chCloseOnPressedEscape.addActionListener(e -> ModalDialog.getDefaultOption().setCloseOnPressedEscape(chCloseOnPressedEscape.isSelected())); + + panel.add(chAnimation); + panel.add(chCloseOnPressedEscape); + + return panel; + } + + private JPanel createStyleOption() { + JPanel panel = new JPanel(new MigLayout("wrap,fillx", "[fill]")); + panel.add(createAccentColor()); + panel.add(createDrawerStyle()); + return panel; + } + + private static String[] accentColorKeys = { + "Demo.accent.default", "Demo.accent.blue", "Demo.accent.purple", "Demo.accent.red", + "Demo.accent.orange", "Demo.accent.yellow", "Demo.accent.green", + }; + private static String[] accentColorNames = { + "Default", "Blue", "Purple", "Red", "Orange", "Yellow", "Green", + }; + private final JToggleButton[] accentColorButtons = new JToggleButton[accentColorKeys.length]; + private JToggleButton accentColorCustomButton; + private JToggleButton oldSelected; + + private Component createAccentColor() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(new TitledBorder("Accent color")); + ButtonGroup group = new ButtonGroup(); + JToolBar toolBar = new JToolBar(); + toolBar.putClientProperty(FlatClientProperties.STYLE, "" + + "hoverButtonGroupBackground:null;"); + + boolean selected = false; + for (int i = 0; i < accentColorButtons.length; i++) { + accentColorButtons[i] = new JToggleButton(new AccentColorIcon(accentColorKeys[i])); + accentColorButtons[i].setToolTipText(accentColorNames[i]); + accentColorButtons[i].addActionListener(this::accentColorChanged); + toolBar.add(accentColorButtons[i]); + group.add(accentColorButtons[i]); + if (!selected) { + if (DemoPreferences.accentColor == null) { + if (i == 0) { + accentColorButtons[i].setSelected(true); + oldSelected = accentColorButtons[i]; + selected = true; + } + } else { + Color color = UIManager.getColor(accentColorKeys[i]); + if (DemoPreferences.accentColor.equals(color)) { + accentColorButtons[i].setSelected(true); + oldSelected = accentColorButtons[i]; + selected = true; + } + } + } + } + accentColorCustomButton = createCustomAccentColor(); + group.add(accentColorCustomButton); + toolBar.add(accentColorCustomButton); + if (!selected) { + accentColorCustomButton.setSelected(true); + } + + FlatLaf.setSystemColorGetter(name -> name.equals("accent") ? DemoPreferences.accentColor : null); + UIManager.addPropertyChangeListener(e -> { + if ("lookAndFeel".equals(e.getPropertyName())) { + updateAccentColorButtons(); + } + }); + updateAccentColorButtons(); + panel.add(toolBar); + return panel; + } + + private JToggleButton createCustomAccentColor() { + JToggleButton button = new JToggleButton(new FlatSVGIcon("icons/color.svg", 16, 16)); + button.addActionListener(e -> { + ColorPicker colorPicker = new ColorPicker(DemoPreferences.accentColor); + colorPicker.setBorder(new ScaledEmptyBorder(0, 20, 0, 20)); + Option option = ModalDialog.createOption(); + option.setAnimationEnabled(false); + ModalDialog.showModal(this, new SimpleModalBorder(colorPicker, "Select Color", SimpleModalBorder.YES_NO_OPTION, (controller, action) -> { + if (action == SimpleModalBorder.YES_OPTION) { + DemoPreferences.accentColor = colorPicker.getSelectedColor(); + oldSelected = null; + applyAccentColor(); + } else if (action == SimpleModalBorder.CLOSE_OPTION) { + if (oldSelected != null) { + oldSelected.setSelected(true); + } + } + }), option); + }); + return button; + } + + private Component createDrawerStyle() { + JPanel panel = new JPanel(new MigLayout("insets 0,filly", "[][][grow,fill]", "[fill]")); + JPanel lineStyle = new JPanel(new MigLayout("wrap", "[200]")); + JPanel lineStyleOption = new JPanel(new MigLayout("wrap", "[200]")); + JPanel lineColorOption = new JPanel(new MigLayout("wrap", "[200]")); + + lineStyle.setBorder(new TitledBorder("Drawer line style")); + lineStyleOption.setBorder(new TitledBorder("Line style option")); + lineColorOption.setBorder(new TitledBorder("Color option")); + + ButtonGroup groupStyle = new ButtonGroup(); + JRadioButton jrCurvedStyle = new JRadioButton("Curved line style"); + JRadioButton jrStraightDotStyle = new JRadioButton("Straight dot line style", true); + groupStyle.add(jrCurvedStyle); + groupStyle.add(jrStraightDotStyle); + + ButtonGroup groupStyleOption = new ButtonGroup(); + JRadioButton jrStyleOption1 = new JRadioButton("Rectangle"); + JRadioButton jrStyleOption2 = new JRadioButton("Ellipse", true); + groupStyleOption.add(jrStyleOption1); + groupStyleOption.add(jrStyleOption2); + + JCheckBox chPaintLineColor = new JCheckBox("Paint selected line color"); + + jrCurvedStyle.addActionListener(e -> { + if (jrCurvedStyle.isSelected()) { + jrStyleOption1.setText("Line"); + jrStyleOption2.setText("Curved"); + boolean round = jrStyleOption2.isSelected(); + boolean paintSelectedLine = chPaintLineColor.isSelected(); + setDrawerLineStyle(true, round, paintSelectedLine); + } + }); + jrStraightDotStyle.addActionListener(e -> { + if (jrStraightDotStyle.isSelected()) { + jrStyleOption1.setText("Rectangle"); + jrStyleOption2.setText("Ellipse"); + boolean round = jrStyleOption2.isSelected(); + boolean paintSelectedLine = chPaintLineColor.isSelected(); + setDrawerLineStyle(false, round, paintSelectedLine); + } + }); + + jrStyleOption1.addActionListener(e -> { + if (jrStyleOption1.isSelected()) { + boolean curved = jrCurvedStyle.isSelected(); + boolean paintSelectedLine = chPaintLineColor.isSelected(); + setDrawerLineStyle(curved, false, paintSelectedLine); + } + }); + + jrStyleOption2.addActionListener(e -> { + if (jrStyleOption2.isSelected()) { + boolean curved = jrCurvedStyle.isSelected(); + boolean paintSelectedLine = chPaintLineColor.isSelected(); + setDrawerLineStyle(curved, true, paintSelectedLine); + } + }); + + chPaintLineColor.addActionListener(e -> { + boolean curved = jrCurvedStyle.isSelected(); + boolean round = jrStyleOption2.isSelected(); + boolean paintSelectedLine = chPaintLineColor.isSelected(); + setDrawerLineStyle(curved, round, paintSelectedLine); + }); + + lineStyle.add(jrCurvedStyle); + lineStyle.add(jrStraightDotStyle); + + lineStyleOption.add(jrStyleOption1); + lineStyleOption.add(jrStyleOption2); + + lineColorOption.add(chPaintLineColor); + + panel.add(lineStyle); + panel.add(lineStyleOption); + panel.add(lineColorOption); + return panel; + } + + private void setDrawerLineStyle(boolean curved, boolean round, boolean color) { + AbstractDrawerLineStyleRenderer style; + if (curved) { + style = new DrawerCurvedLineStyle(round, color); + } else { + style = new DrawerStraightDotLineStyle(round, color); + } + ((SimpleDrawerBuilder) Drawer.getDrawerBuilder()).getSimpleMenuOption().getMenuStyle().setDrawerLineStyleRenderer(style); + ((SimpleDrawerBuilder) Drawer.getDrawerBuilder()).getDrawerMenu().repaint(); + } + + private void accentColorChanged(ActionEvent e) { + String accentColorKey = null; + for (int i = 0; i < accentColorButtons.length; i++) { + if (accentColorButtons[i].isSelected()) { + accentColorKey = accentColorKeys[i]; + oldSelected = accentColorButtons[i]; + break; + } + } + DemoPreferences.accentColor = (accentColorKey != null && accentColorKey != accentColorKeys[0]) + ? UIManager.getColor(accentColorKey) + : null; + applyAccentColor(); + } + + private void applyAccentColor() { + Class lafClass = UIManager.getLookAndFeel().getClass(); + try { + DemoPreferences.updateAccentColor(DemoPreferences.accentColor); + FlatLaf.setup(lafClass.getDeclaredConstructor().newInstance()); + FlatLaf.updateUI(); + } catch (Exception ex) { + LoggingFacade.INSTANCE.logSevere(null, ex); + } + } + + private void updateAccentColorButtons() { + Class lafClass = UIManager.getLookAndFeel().getClass(); + boolean isAccentColorSupported = + lafClass == FlatLightLaf.class || + lafClass == FlatDarkLaf.class || + lafClass == FlatIntelliJLaf.class || + lafClass == FlatDarculaLaf.class || + lafClass == FlatMacLightLaf.class || + lafClass == FlatMacDarkLaf.class; + for (int i = 0; i < accentColorButtons.length; i++) { + accentColorButtons[i].setEnabled(isAccentColorSupported); + } + if (accentColorCustomButton != null) { + accentColorCustomButton.setEnabled(isAccentColorSupported); + } + } + + private JPanel createThemes() { + JPanel panel = new JPanel(new MigLayout("wrap,fill,insets 0", "[fill]", "[grow 0,fill]0[fill]")); + final PanelThemes panelThemes = new PanelThemes(); + JPanel panelHeader = new JPanel(new MigLayout("fillx,insets 3", "[grow 0]push[]")); + panelHeader.add(new JLabel("Themes")); + JComboBox combo = new JComboBox(new Object[]{"All", "Light", "Dark"}); + combo.addActionListener(e -> { + panelThemes.updateThemesList(combo.getSelectedIndex()); + }); + panelHeader.add(combo); + panel.add(panelHeader); + panel.add(panelThemes); + return panel; + } + + private JTabbedPane tabbedPane; +} diff --git a/src/main/java/backupmanager/forms/FormTable.java b/src/main/java/backupmanager/forms/FormTable.java new file mode 100644 index 00000000..962487ec --- /dev/null +++ b/src/main/java/backupmanager/forms/FormTable.java @@ -0,0 +1,363 @@ +package backupmanager.forms; + +import java.awt.Component; +import java.io.IOException; +import java.text.DecimalFormat; + +import javax.swing.AbstractAction; +import javax.swing.BorderFactory; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableModel; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import backupmanager.sample.csv.CSVDataReader; +import backupmanager.sample.csv.ResponseCSV; +import backupmanager.simple.SimpleInputForms; +import backupmanager.svg.SVGButton; +import backupmanager.system.Form; +import backupmanager.utils.SystemForm; +import backupmanager.utils.table.TableHeaderAlignment; +import net.miginfocom.swing.MigLayout; +import raven.modal.ModalDialog; +import raven.modal.component.SimpleModalBorder; +import raven.modal.option.Location; +import raven.modal.option.Option; +import raven.swingpack.JPagination; + +@SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) +public class FormTable extends Form { + + public FormTable() { + init(); + } + + private void init() { + setLayout(new MigLayout( + "fill,wrap", + "[fill]", + "[][grow 100,fill][grow 0]" + )); + add(createInfo("Backup List", "A table is a user interface component that displays a collection of records in a structured, tabular format. It allows users to view, sort, and manage data or other resources.", 1)); + add(createBorder(createBasicTable()), "gapx 7 7, grow"); + add(createBorder(createDetails()), "gapx 7 7, hmin 150"); + } + + @Override + public void formInit() { + try { + data = CSVDataReader.load(getClass().getResourceAsStream("/data/customers-1000.csv")); + DefaultTableModel model = (DefaultTableModel) basicTable.getModel(); + model.setColumnIdentifiers(data.getColumns()); + basicTable.setModel(model); + + // table column size + basicTable.getColumnModel().getColumn(0).setMaxWidth(50); + + formRefresh(); + } catch (IOException e) { + System.err.println(e.getMessage()); + } + } + + @Override + public void formRefresh() { + showData(pagination.getSelectedPage()); + } + + private void showData(int page) { + if (data != null) { + ResponseCSV res = data.getData(page, limit); + lbTotalPage.setText(DecimalFormat.getInstance().format(res.getTotal())); + pagination.getModel().setPageRange(res.getPage(), res.getPageSize()); + + DefaultTableModel model = (DefaultTableModel) basicTable.getModel(); + model.setRowCount(0); + for (String[] row : res.getData()) { + model.addRow(row); + } + } + } + + private JPanel createInfo(String title, String description, int level) { + JPanel panel = new JPanel(new MigLayout("fillx,wrap", "[fill]")); + JLabel lbTitle = new JLabel(title); + JTextPane text = new JTextPane(); + text.setText(description); + text.setEditable(false); + text.setBorder(BorderFactory.createEmptyBorder()); + lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +" + (4 - level)); + panel.add(lbTitle); + panel.add(text, "width 500"); + return panel; + } + + private Component createBorder(Component component) { + JPanel panel = new JPanel(new MigLayout("fill,insets 7 0 7 0", "[fill]", "[fill]")); + panel.add(component); + return panel; + } + + private Component createBasicTable() { + JPanel panelTable = new JPanel(new MigLayout("fill,wrap,insets 10 0 10 0", + "[fill]", + "[][][grow,fill][pref!]")); + + // create table model + JTable table = new JTable(); + table.setModel(new DefaultTableModel() { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + }); + + table.putClientProperty(FlatClientProperties.STYLE, "rowHeight:34; showHorizontalLines:false;"); + + table.getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting()) { + int row = table.getSelectedRow(); + if (row != -1) { + StringBuilder details = new StringBuilder(); + for (int i = 0; i < table.getColumnCount(); i++) { + details.append(table.getColumnName(i)) + .append(": ") + .append(table.getValueAt(row, i)) + .append("\n"); + } + txtDetails.setText(details.toString()); + } + } + }); + + table.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseClicked(java.awt.event.MouseEvent e) { + if (e.getClickCount() == 2 && table.getSelectedRow() != -1) { + int row = table.getSelectedRow(); + //showRowDetail(table, row); + System.out.println("Double clicked row: " + row); + } + } + }); + + table.getInputMap(JComponent.WHEN_FOCUSED).put( + KeyStroke.getKeyStroke("DELETE"), + "deleteRow"); + + table.getActionMap().put("deleteRow", new AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent e) { + int row = table.getSelectedRow(); + if (row != -1) { + ((DefaultTableModel) table.getModel()).removeRow(row); + System.out.println("Deleted row: " + row); + } + } + }); + + // table scroll + JScrollPane scrollPane = new JScrollPane(table); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + + // alignment table header + table.getTableHeader().setDefaultRenderer(new TableHeaderAlignment(table) { + @Override + protected int getAlignment(int column) { + if (column == 0) { + return SwingConstants.CENTER; + } + return SwingConstants.LEADING; + } + }); + + // style + panelTable.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;" + + "background:$Table.background;"); + table.getTableHeader().putClientProperty(FlatClientProperties.STYLE, "" + + "height:30;" + + "hoverBackground:null;" + + "pressedBackground:null;" + + "separatorColor:$TableHeader.background;"); + table.putClientProperty(FlatClientProperties.STYLE, "" + + "rowHeight:30;" + + "showHorizontalLines:true;" + + "intercellSpacing:0,1;" + + "cellFocusColor:$TableHeader.hoverBackground;" + + "selectionBackground:$TableHeader.hoverBackground;" + + "selectionInactiveBackground:$TableHeader.hoverBackground;" + + "selectionForeground:$Table.foreground;"); + scrollPane.getVerticalScrollBar().putClientProperty(FlatClientProperties.STYLE, "" + + "trackArc:$ScrollBar.thumbArc;" + + "trackInsets:3,3,3,3;" + + "thumbInsets:3,3,3,3;" + + "background:$Table.background;"); + + // create title + panelTable.add(createHeaderAction()); + panelTable.add(scrollPane, "grow, push"); + + // create pagination + pagination = new JPagination(11, 1, 1); + pagination.setMinimumSize(pagination.getPreferredSize()); + pagination.addChangeListener(e -> { + showData(pagination.getSelectedPage()); + }); + JPanel panelPage = new JPanel(new MigLayout("insets 5 15 5 15", "[][]push[pref!]")); + lbTotalPage = new JLabel("0"); + pagination.putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + panelPage.putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + panelPage.add(new JLabel("Total:")); + panelPage.add(lbTotalPage); + panelPage.add(pagination); + + panelTable.add(panelPage); + + basicTable = table; + + + JPopupMenu popupMenu = new JPopupMenu(); + + JMenuItem itemEdit = new JMenuItem("Edit"); + JMenuItem itemDelete = new JMenuItem("Delete"); + JMenuItem itemDuplicate = new JMenuItem("Duplicate"); + JMenuItem itemRename = new JMenuItem("Rename"); + JMenuItem itemOpenTargetPath = new JMenuItem("Open target path"); + JMenuItem itemOpenDestinationPath = new JMenuItem("Open destination path"); + + JMenu itemBackup = new JMenu("Backup"); + JMenuItem itemRunSingleBackup = new JMenuItem("Run single backup"); + JCheckBoxMenuItem itemAutoBackup = new JCheckBoxMenuItem("Auto backup"); + + JMenu itemCopyText = new JMenu("Copy text"); + JMenuItem itemCopyBackupName = new JMenuItem("Copy backup name"); + JMenuItem itemCopyTargetPath = new JMenuItem("Copy target path"); + JMenuItem itemCopyDestinationPath = new JMenuItem("Copy destination path"); + + popupMenu.add(itemEdit); + popupMenu.add(itemDelete); + popupMenu.add(itemDuplicate); + popupMenu.add(itemRename); + popupMenu.addSeparator(); + popupMenu.add(itemOpenTargetPath); + popupMenu.add(itemOpenDestinationPath); + popupMenu.addSeparator(); + popupMenu.add(itemBackup); + itemBackup.add(itemRunSingleBackup); + itemBackup.add(itemAutoBackup); + popupMenu.addSeparator(); + popupMenu.add(itemCopyText); + itemCopyText.add(itemCopyBackupName); + itemCopyText.add(itemCopyTargetPath); + itemCopyText.add(itemCopyDestinationPath); + + table.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mousePressed(java.awt.event.MouseEvent e) { + if (e.isPopupTrigger()) { + showPopup(e); + } + } + + @Override + public void mouseReleased(java.awt.event.MouseEvent e) { + if (e.isPopupTrigger()) { + showPopup(e); + } + } + + private void showPopup(java.awt.event.MouseEvent e) { + int row = table.rowAtPoint(e.getPoint()); + if (row >= 0) { + table.setRowSelectionInterval(row, row); + } + popupMenu.show(e.getComponent(), e.getX(), e.getY()); + } + }); + + + return panelTable; + } + + private Component createDetails() { + JPanel detailsPanel = new JPanel( + new MigLayout("fill,insets 5 0 5 0", "[fill]", "[grow]") + ); + + txtDetails = new JTextArea(); + txtDetails.setEditable(false); + txtDetails.setLineWrap(true); + txtDetails.setWrapStyleWord(true); + + JScrollPane detailScroll = new JScrollPane(txtDetails); + detailScroll.putClientProperty(FlatClientProperties.STYLE, + "arc:10; border:1,1,1,1,$Component.borderColor"); + + detailsPanel.add(detailScroll, "grow"); + return detailsPanel; + } + + private Component createHeaderAction() { + JPanel panel = new JPanel(new MigLayout("insets 5 20 5 20", "[fill,300]push[][]")); + + JTextField txtSearch = new JTextField(); + txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Search..."); + txtSearch.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSVGIcon("icons/search.svg", 0.4f)); + SVGButton cmdCreate = new SVGButton("Create"); + SVGButton cmdEdit = new SVGButton("Edit"); + SVGButton cmdDelete = new SVGButton("Delete"); + + cmdCreate.setSvgImage("icons/add.svg", 16, 16); + cmdEdit.setSvgImage("icons/edit.svg", 16, 16); + cmdDelete.setSvgImage("icons/delete.svg", 16, 16); + + cmdCreate.putClientProperty(FlatClientProperties.STYLE, "background:$Component.accentColor;"); + cmdDelete.putClientProperty(FlatClientProperties.STYLE, "background:$Component.error.background;"); + + cmdCreate.addActionListener(e -> showModal()); + panel.add(txtSearch); + panel.add(cmdCreate); + panel.add(cmdEdit); + panel.add(cmdDelete); + + panel.putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + return panel; + } + + private void showModal() { + Option option = ModalDialog.createOption(); + option.getLayoutOption().setSize(-1, 1f) + .setLocation(Location.TRAILING, Location.TOP) + .setAnimateDistance(0.7f, 0); + ModalDialog.showModal(this, new SimpleModalBorder( + new SimpleInputForms(), "Create", SimpleModalBorder.YES_NO_OPTION, + (controller, action) -> { + }), option); + } + + private CSVDataReader data; + private final int limit = 50; + private JPagination pagination; + private JTable basicTable; + private JLabel lbTotalPage; + private JTextArea txtDetails; +} diff --git a/src/main/java/backupmanager/frames/BackupManager.java b/src/main/java/backupmanager/frames/BackupManager.java new file mode 100644 index 00000000..c6b760af --- /dev/null +++ b/src/main/java/backupmanager/frames/BackupManager.java @@ -0,0 +1,27 @@ +package backupmanager.frames; + + +import java.awt.Dimension; + +import javax.swing.JFrame; + +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.menu.MyDrawerBuilder; +import backupmanager.system.FormManager; +import raven.modal.Drawer; + +public class BackupManager extends JFrame{ + public BackupManager() { + init(); + } + + private void init() { + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); + Drawer.installDrawer(this, MyDrawerBuilder.getInstance()); + FormManager.install(this); + setSize(new Dimension(1366, 768)); + setLocationRelativeTo(null); + } +} diff --git a/src/main/java/backupmanager/GUI/BackupManagerGUI.form b/src/main/java/backupmanager/frames/BackupManagerGUI.form similarity index 100% rename from src/main/java/backupmanager/GUI/BackupManagerGUI.form rename to src/main/java/backupmanager/frames/BackupManagerGUI.form diff --git a/src/main/java/backupmanager/GUI/BackupManagerGUI.java b/src/main/java/backupmanager/frames/BackupManagerGUI.java similarity index 99% rename from src/main/java/backupmanager/GUI/BackupManagerGUI.java rename to src/main/java/backupmanager/frames/BackupManagerGUI.java index a2cdc721..01b6d3b1 100644 --- a/src/main/java/backupmanager/GUI/BackupManagerGUI.java +++ b/src/main/java/backupmanager/frames/BackupManagerGUI.java @@ -1,4 +1,4 @@ -package backupmanager.GUI; +package backupmanager.frames; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -11,7 +11,6 @@ import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; -import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; @@ -31,9 +30,6 @@ import backupmanager.Enums.TranslationLoaderEnum; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.Controllers.BackupManagerController; -import backupmanager.GUI.Controllers.BackupMenuController; -import backupmanager.GUI.Controllers.BackupPopupController; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; import static backupmanager.Helpers.BackupHelper.formatter; @@ -47,8 +43,10 @@ import backupmanager.Table.BackupTableModel; import backupmanager.Table.CheckboxCellRenderer; import backupmanager.Table.StripedRowRenderer; -import backupmanager.Widgets.SideMenuPanel; import backupmanager.database.Repositories.BackupConfigurationRepository; +import backupmanager.frames.Controllers.BackupManagerController; +import backupmanager.frames.Controllers.BackupMenuController; +import backupmanager.frames.Controllers.BackupPopupController; public final class BackupManagerGUI extends javax.swing.JFrame { private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); @@ -61,7 +59,6 @@ public final class BackupManagerGUI extends javax.swing.JFrame { public static BackupTableModel tableModel; public static BackupProgressGUI progressBar; private Integer selectedRow; - private final SideMenuPanel sp; public BackupManagerGUI() { ThemeManager.updateThemeFrame(this); @@ -70,7 +67,6 @@ public BackupManagerGUI() { backupManagerController = new BackupManagerController(new BackupService()); - sp = new SideMenuPanel(this); this.setIconImage(GuiController.getIcon(this.getClass())); @@ -92,17 +88,6 @@ public BackupManagerGUI() { // TODO: remove this interruptBackupPopupItem.setVisible(false); - - initSidebar(); - } - - private void initSidebar() { - sp.setMain(null); - sp.setMinWidth(55); - sp.setMaxWidth(150); - sp.setMainAnimationEnabled(true); - sp.setSpeed(4); - sp.setResponsiveMinWidth(1300); } public void showWindow() { diff --git a/src/main/java/backupmanager/GUI/BackupProgressGUI.form b/src/main/java/backupmanager/frames/BackupProgressGUI.form similarity index 100% rename from src/main/java/backupmanager/GUI/BackupProgressGUI.form rename to src/main/java/backupmanager/frames/BackupProgressGUI.form diff --git a/src/main/java/backupmanager/GUI/BackupProgressGUI.java b/src/main/java/backupmanager/frames/BackupProgressGUI.java similarity index 98% rename from src/main/java/backupmanager/GUI/BackupProgressGUI.java rename to src/main/java/backupmanager/frames/BackupProgressGUI.java index 06840476..0251bbbf 100644 --- a/src/main/java/backupmanager/GUI/BackupProgressGUI.java +++ b/src/main/java/backupmanager/frames/BackupProgressGUI.java @@ -1,9 +1,9 @@ -package backupmanager.GUI; +package backupmanager.frames; import backupmanager.Controllers.GuiController; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.Controllers.BackupProgressController; +import backupmanager.frames.Controllers.BackupProgressController; public class BackupProgressGUI extends javax.swing.JDialog { diff --git a/src/main/java/backupmanager/GUI/Controllers/BackupManagerController.java b/src/main/java/backupmanager/frames/Controllers/BackupManagerController.java similarity index 97% rename from src/main/java/backupmanager/GUI/Controllers/BackupManagerController.java rename to src/main/java/backupmanager/frames/Controllers/BackupManagerController.java index 412014b0..c0434681 100644 --- a/src/main/java/backupmanager/GUI/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/frames/Controllers/BackupManagerController.java @@ -1,4 +1,4 @@ -package backupmanager.GUI.Controllers; +package backupmanager.frames.Controllers; import java.awt.Dimension; import java.awt.Toolkit; @@ -21,14 +21,15 @@ import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.BackupManagerGUI; -import static backupmanager.GUI.BackupManagerGUI.backups; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; +import static backupmanager.frames.BackupManagerGUI.backups; + import backupmanager.Services.BackupService; import backupmanager.Table.BackupTable; import backupmanager.Table.TableDataManager; import backupmanager.database.Repositories.UserRepository; +import backupmanager.frames.BackupManagerGUI; public class BackupManagerController { diff --git a/src/main/java/backupmanager/GUI/Controllers/BackupMenuController.java b/src/main/java/backupmanager/frames/Controllers/BackupMenuController.java similarity index 96% rename from src/main/java/backupmanager/GUI/Controllers/BackupMenuController.java rename to src/main/java/backupmanager/frames/Controllers/BackupMenuController.java index 21f84861..cbdb25e1 100644 --- a/src/main/java/backupmanager/GUI/Controllers/BackupMenuController.java +++ b/src/main/java/backupmanager/frames/Controllers/BackupMenuController.java @@ -1,4 +1,4 @@ -package backupmanager.GUI.Controllers; +package backupmanager.frames.Controllers; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; @@ -14,11 +14,11 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.BackupManagerGUI; -import backupmanager.GUI.BackupProgressGUI; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.WebsiteManager; import backupmanager.Services.BackupObserver; +import backupmanager.frames.BackupManagerGUI; +import backupmanager.frames.BackupProgressGUI; public class BackupMenuController { diff --git a/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java b/src/main/java/backupmanager/frames/Controllers/BackupPopupController.java similarity index 98% rename from src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java rename to src/main/java/backupmanager/frames/Controllers/BackupPopupController.java index a42d9e6f..9ccc11d9 100644 --- a/src/main/java/backupmanager/GUI/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/frames/Controllers/BackupPopupController.java @@ -1,4 +1,4 @@ -package backupmanager.GUI.Controllers; +package backupmanager.frames.Controllers; import java.awt.Desktop; import java.awt.Toolkit; @@ -24,13 +24,13 @@ import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.GUI.BackupManagerGUI; -import backupmanager.GUI.BackupProgressGUI; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; import backupmanager.Table.BackupTable; import backupmanager.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; +import backupmanager.frames.BackupManagerGUI; +import backupmanager.frames.BackupProgressGUI; public class BackupPopupController { diff --git a/src/main/java/backupmanager/GUI/Controllers/BackupProgressController.java b/src/main/java/backupmanager/frames/Controllers/BackupProgressController.java similarity index 95% rename from src/main/java/backupmanager/GUI/Controllers/BackupProgressController.java rename to src/main/java/backupmanager/frames/Controllers/BackupProgressController.java index 70562398..20bd836f 100644 --- a/src/main/java/backupmanager/GUI/Controllers/BackupProgressController.java +++ b/src/main/java/backupmanager/frames/Controllers/BackupProgressController.java @@ -1,4 +1,4 @@ -package backupmanager.GUI.Controllers; +package backupmanager.frames.Controllers; import javax.swing.JOptionPane; diff --git a/src/main/java/backupmanager/frames/Login.java b/src/main/java/backupmanager/frames/Login.java new file mode 100644 index 00000000..7e5a9fb0 --- /dev/null +++ b/src/main/java/backupmanager/frames/Login.java @@ -0,0 +1,92 @@ +package backupmanager.frames; + +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.menu.MyDrawerBuilder; +import backupmanager.system.Form; +import backupmanager.system.FormManager; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; + +public class Login extends Form { + public Login() { + init(); + } + + private void init() { + setLayout(new MigLayout("al center center")); + createLogin(); + } + + private void createLogin() { + JPanel panelLogin = new JPanel(new MigLayout()); + + JPanel loginContent = new JPanel(new MigLayout("fillx,wrap,insets 35 35 25 35", "[fill,300]")); + + JLabel lbTitle = new JLabel("Login"); + JLabel lbDescription = new JLabel("Please enter your data to access the system"); + lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +12;"); + + loginContent.add(lbTitle); + loginContent.add(lbDescription); + + JTextField txtName = new JTextField(); + JTextField txtSurname = new JTextField(); + JTextField txtEmail = new JTextField(); + JButton cmdLogin = new JButton("Login") { + @Override + public boolean isDefaultButton() { + return true; + } + }; + + // style + txtName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your name"); + txtSurname.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your surname"); + txtEmail.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your email"); + + panelLogin.putClientProperty(FlatClientProperties.STYLE, "" + + "[light]border:5,5,5,5,shade($Panel.background,10%),,20;" + + "[dark]border:5,5,5,5,tint($Panel.background,5%),,20;" + + "[light]background:shade($Panel.background,3%);" + + "[dark]background:tint($Panel.background,2%);"); + + loginContent.putClientProperty(FlatClientProperties.STYLE, "" + + "background:null;"); + + txtName.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:4,10,4,10;" + + "arc:12;"); + txtSurname.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:4,10,4,10;" + + "arc:12;"); + txtEmail.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:4,10,4,10;" + + "arc:12;"); + + cmdLogin.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:4,10,4,10;" + + "arc:12;"); + + loginContent.add(new JLabel("Name"), "gapy 25"); + loginContent.add(txtName); + + loginContent.add(new JLabel("Surname"), "gapy 10"); + loginContent.add(txtSurname); + + loginContent.add(new JLabel("Email"), "gapy 10"); + loginContent.add(txtEmail); + loginContent.add(cmdLogin, "gapy 20"); + + panelLogin.add(loginContent); + add(panelLogin); + + // event + cmdLogin.addActionListener(e -> { + MyDrawerBuilder.getInstance().initHeader(); + FormManager.login(); + }); + } +} diff --git a/src/main/java/backupmanager/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/menu/MyDrawerBuilder.java new file mode 100644 index 00000000..12d271df --- /dev/null +++ b/src/main/java/backupmanager/menu/MyDrawerBuilder.java @@ -0,0 +1,193 @@ +package backupmanager.menu; + +import java.util.Arrays; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.UIManager; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import backupmanager.Enums.ConfigKey; +import backupmanager.forms.FormDashboard; +import backupmanager.forms.FormSetting; +import backupmanager.forms.FormTable; +import backupmanager.system.AllForms; +import backupmanager.system.Form; +import backupmanager.system.FormManager; +import raven.extras.AvatarIcon; +import raven.modal.drawer.DrawerPanel; +import raven.modal.drawer.item.Item; +import raven.modal.drawer.item.MenuItem; +import raven.modal.drawer.menu.MenuOption; +import raven.modal.drawer.menu.MenuStyle; +import raven.modal.drawer.renderer.DrawerStraightDotLineStyle; +import raven.modal.drawer.simple.SimpleDrawerBuilder; +import raven.modal.drawer.simple.footer.LightDarkButtonFooter; +import raven.modal.drawer.simple.footer.SimpleFooterData; +import raven.modal.drawer.simple.header.SimpleHeader; +import raven.modal.drawer.simple.header.SimpleHeaderData; +import raven.modal.option.Option; +import raven.modal.utils.FlatLafStyleUtils; + +public class MyDrawerBuilder extends SimpleDrawerBuilder { + + private static MyDrawerBuilder instance; + + public static MyDrawerBuilder getInstance() { + if (instance == null) { + instance = new MyDrawerBuilder(); + } + return instance; + } + + public void initHeader() { + // setup drawer header + SimpleHeader header = (SimpleHeader) getHeader(); + SimpleHeaderData data = header.getSimpleHeaderData(); + AvatarIcon icon = (AvatarIcon) data.getIcon(); + + icon.setIcon(new FlatSVGIcon("raven/modal/demo/drawer/logo.png", 100, 100)); + data.setTitle("Backup Manager"); + data.setDescription("assistenza@shardpc.it"); + header.setSimpleHeaderData(data); + + rebuildMenu(); + } + + private MyDrawerBuilder() { + super(createSimpleMenuOption()); + LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); + lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { + // event for light dark mode changed + }); + } + + @Override + public SimpleHeaderData getSimpleHeaderData() { + AvatarIcon icon = new AvatarIcon(new FlatSVGIcon("raven/modal/demo/drawer/logo.png", 100, 100), 50, 50, 3.5f); + icon.setType(AvatarIcon.Type.MASK_SQUIRCLE); + icon.setBorder(2, 2); + + changeAvatarIconBorderColor(icon); + + UIManager.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals("lookAndFeel")) { + changeAvatarIconBorderColor(icon); + } + }); + + return new SimpleHeaderData() + .setIcon(icon) + .setTitle("Ra Ven") + .setDescription("raven@gmail.com"); + } + + private void changeAvatarIconBorderColor(AvatarIcon icon) { + icon.setBorderColor(new AvatarIcon.BorderColor(UIManager.getColor("Component.accentColor"), 0.7f)); + } + + @Override + public SimpleFooterData getSimpleFooterData() { + return new SimpleFooterData() + .setTitle("Swing Modal Dialog") + .setDescription("Version " + ConfigKey.VERSION.getValue()); + } + + @Override + public Option createOption() { + Option option = super.createOption(); + option.setOpacity(0.3f); + return option; + } + + public static MenuOption createSimpleMenuOption() { + // create simple menu option + MenuOption simpleMenuOption = new MenuOption(); + + MenuItem[] items = new MenuItem[]{ + new Item.Label("MAIN"), + new Item("Backup List", "forms.svg", FormTable.class) + .subMenu("Create new backup") + .subMenu("Import backups from Csv") + .subMenu("Export backups to Csv"), + new Item("Dashboard", "dashboard.svg", FormDashboard.class), + new Item.Label("OTHER"), + new Item("Setting", "setting.svg", FormSetting.class), + new Item("History", "history.svg"), + new Item("Information", "info.svg"), + new Item("Support the Project", "donate.svg") + .subMenu("Paypal") + .subMenu("Buy me a coffee"), + new Item("Help", "help.svg") + .subMenu("Report a bug") + .subMenu("Support"), + new Item("About", "about.svg"), + }; + + simpleMenuOption.setMenuStyle(new MenuStyle() { + + @Override + public void styleMenuItem(JButton menu, int[] index, boolean isMainItem) { + boolean isTopLevel = index.length == 1; + if (isTopLevel) { + // adjust item menu at the top level because it's contain icon + menu.putClientProperty(FlatClientProperties.STYLE, "" + + "margin:-1,0,-1,0;"); + } + } + + @Override + public void styleMenu(JComponent component) { + component.putClientProperty(FlatClientProperties.STYLE, getDrawerBackgroundStyle()); + } + }); + + simpleMenuOption.getMenuStyle().setDrawerLineStyleRenderer(new DrawerStraightDotLineStyle()); + simpleMenuOption.setMenuValidation(new MyMenuValidation()); + + simpleMenuOption.addMenuEvent((action, index) -> { + System.out.println("Drawer menu selected " + Arrays.toString(index)); + Class itemClass = action.getItem().getItemClass(); + int i = index[0]; + if (i == 7) { + action.consume(); + FormManager.showAbout(); + return; + } + if (itemClass == null || !Form.class.isAssignableFrom(itemClass)) { + action.consume(); + return; + } + Class formClass = (Class) itemClass; + FormManager.showForm(AllForms.getForm(formClass)); + }); + + simpleMenuOption.setMenus(items) + .setBaseIconPath("drawer/icon/") + .setIconScale(0.45f); + + return simpleMenuOption; + } + + @Override + public int getOpenDrawerAt() { + return 1000; + } + + @Override + public boolean openDrawerAtScale() { + return false; + } + + @Override + public void build(DrawerPanel drawerPanel) { + drawerPanel.putClientProperty(FlatClientProperties.STYLE, getDrawerBackgroundStyle()); + FlatLafStyleUtils.appendStyle(drawerPanel, "border:0,0,0,1,$Separator.foreground;"); + } + + private static String getDrawerBackgroundStyle() { + return "background:$Menu.background;"; + } +} diff --git a/src/main/java/backupmanager/menu/MyMenuValidation.java b/src/main/java/backupmanager/menu/MyMenuValidation.java new file mode 100644 index 00000000..87ce1190 --- /dev/null +++ b/src/main/java/backupmanager/menu/MyMenuValidation.java @@ -0,0 +1,12 @@ +package backupmanager.menu; + +import raven.modal.Drawer; +import backupmanager.system.Form; +import raven.modal.drawer.menu.MenuValidation; + +public class MyMenuValidation extends MenuValidation { + public static boolean validation(Class itemClass) { + int[] index = Drawer.getMenuIndexClass(itemClass); + return index != null; + } +} diff --git a/src/main/java/backupmanager/sample/SampleData.java b/src/main/java/backupmanager/sample/SampleData.java new file mode 100644 index 00000000..561ba062 --- /dev/null +++ b/src/main/java/backupmanager/sample/SampleData.java @@ -0,0 +1,423 @@ +package backupmanager.sample; + +import java.util.Calendar; +import java.util.Date; + +import javax.swing.Icon; +import javax.swing.ImageIcon; + +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.general.DefaultPieDataset; +import org.jfree.data.general.PieDataset; +import org.jfree.data.time.Month; +import org.jfree.data.time.TimeTableXYDataset; +import org.jfree.data.xy.DefaultHighLowDataset; +import org.jfree.data.xy.OHLCDataset; +import org.jfree.data.xy.TableXYDataset; + +import raven.extras.AvatarIcon; + +public class SampleData { + public static TableXYDataset getTimeSeriesDataset() { + TimeTableXYDataset dataset = new TimeTableXYDataset(); + String seriesIncome = "Income"; + + dataset.add(new Month(2, 2001), 181.8, seriesIncome); + dataset.add(new Month(3, 2001), 167.3, seriesIncome); + dataset.add(new Month(4, 2001), 153.8, seriesIncome); + dataset.add(new Month(5, 2001), 167.6, seriesIncome); + dataset.add(new Month(6, 2001), 158.8, seriesIncome); + dataset.add(new Month(7, 2001), 148.3, seriesIncome); + dataset.add(new Month(8, 2001), 153.9, seriesIncome); + dataset.add(new Month(9, 2001), 142.7, seriesIncome); + dataset.add(new Month(10, 2001), 123.2, seriesIncome); + dataset.add(new Month(11, 2001), 131.8, seriesIncome); + dataset.add(new Month(12, 2001), 139.6, seriesIncome); + dataset.add(new Month(1, 2002), 142.9, seriesIncome); + dataset.add(new Month(2, 2002), 138.7, seriesIncome); + dataset.add(new Month(3, 2002), 137.3, seriesIncome); + dataset.add(new Month(4, 2002), 143.9, seriesIncome); + dataset.add(new Month(5, 2002), 139.8, seriesIncome); + dataset.add(new Month(6, 2002), 80.0, seriesIncome); + dataset.add(new Month(7, 2002), 50.8, seriesIncome); + + String seriesExpense = "Expense"; + dataset.add(new Month(2, 2001), 129.6, seriesExpense); + dataset.add(new Month(3, 2001), 123.2, seriesExpense); + dataset.add(new Month(4, 2001), 117.2, seriesExpense); + dataset.add(new Month(5, 2001), 124.1, seriesExpense); + dataset.add(new Month(6, 2001), 122.6, seriesExpense); + dataset.add(new Month(7, 2001), 119.2, seriesExpense); + dataset.add(new Month(8, 2001), 116.5, seriesExpense); + dataset.add(new Month(9, 2001), 112.7, seriesExpense); + dataset.add(new Month(10, 2001), 101.5, seriesExpense); + dataset.add(new Month(11, 2001), 106.1, seriesExpense); + dataset.add(new Month(12, 2001), 125.2, seriesExpense); + dataset.add(new Month(1, 2002), 111.7, seriesExpense); + dataset.add(new Month(2, 2002), 111.0, seriesExpense); + dataset.add(new Month(3, 2002), 109.6, seriesExpense); + dataset.add(new Month(4, 2002), 113.2, seriesExpense); + dataset.add(new Month(5, 2002), 111.6, seriesExpense); + dataset.add(new Month(6, 2002), 108.8, seriesExpense); + dataset.add(new Month(7, 2002), 101.6, seriesExpense); + + return dataset; + } + + public static CategoryDataset getCategoryDataset() { + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + // series key + String series1 = "Sales"; + String series2 = "Adjust"; + String series3 = "Return"; + String series4 = "Custom"; + + // product names + String product1 = "Laptop"; + String product2 = "Phone"; + String product3 = "Accessory"; + + // product 1 + dataset.addValue(200, product1, series1); + dataset.addValue(50, product1, series2); + dataset.addValue(80, product1, series3); + dataset.addValue(150, product1, series4); + + // product 2 + dataset.addValue(50, product2, series1); + dataset.addValue(180, product2, series2); + dataset.addValue(250, product2, series3); + dataset.addValue(230, product2, series4); + + // product 3 + dataset.addValue(180, product3, series1); + dataset.addValue(100, product3, series2); + dataset.addValue(250, product3, series3); + dataset.addValue(80, product3, series4); + + return dataset; + } + + public static PieDataset getPieDataset() { + DefaultPieDataset dataset = new DefaultPieDataset(); + + dataset.setValue("Laptop", 30); + dataset.setValue("Phone", 25); + dataset.setValue("Tablet", 18); + dataset.setValue("Watch", 12); + + return dataset; + } + + public static OHLCDataset getOhlcDataset() { + Date[] date = new Date[47]; + double[] high = new double[47]; + double[] low = new double[47]; + double[] open = new double[47]; + double[] close = new double[47]; + double[] volume = new double[47]; + int jan = 1; + int feb = 2; + date[0] = createOhlcData(2001, jan, 4, 12, 0); + high[0] = 47.0; + low[0] = 33.0; + open[0] = 35.0; + close[0] = 33.0; + volume[0] = 100.0; + date[1] = createOhlcData(2001, jan, 5, 12, 0); + high[1] = 47.0; + low[1] = 32.0; + open[1] = 41.0; + close[1] = 37.0; + volume[1] = 150.0; + date[2] = createOhlcData(2001, jan, 6, 12, 0); + high[2] = 49.0; + low[2] = 43.0; + open[2] = 46.0; + close[2] = 48.0; + volume[2] = 70.0; + date[3] = createOhlcData(2001, jan, 7, 12, 0); + high[3] = 51.0; + low[3] = 39.0; + open[3] = 40.0; + close[3] = 47.0; + volume[3] = 200.0; + date[4] = createOhlcData(2001, jan, 8, 12, 0); + high[4] = 60.0; + low[4] = 40.0; + open[4] = 46.0; + close[4] = 53.0; + volume[4] = 120.0; + date[5] = createOhlcData(2001, jan, 9, 12, 0); + high[5] = 62.0; + low[5] = 55.0; + open[5] = 57.0; + close[5] = 61.0; + volume[5] = 110.0; + date[6] = createOhlcData(2001, jan, 10, 12, 0); + high[6] = 65.0; + low[6] = 56.0; + open[6] = 62.0; + close[6] = 59.0; + volume[6] = 70.0; + date[7] = createOhlcData(2001, jan, 11, 12, 0); + high[7] = 55.0; + low[7] = 43.0; + open[7] = 45.0; + close[7] = 47.0; + volume[7] = 20.0; + date[8] = createOhlcData(2001, jan, 12, 12, 0); + high[8] = 54.0; + low[8] = 33.0; + open[8] = 40.0; + close[8] = 51.0; + volume[8] = 30.0; + date[9] = createOhlcData(2001, jan, 13, 12, 0); + high[9] = 47.0; + low[9] = 33.0; + open[9] = 35.0; + close[9] = 33.0; + volume[9] = 100.0; + date[10] = createOhlcData(2001, jan, 14, 12, 0); + high[10] = 54.0; + low[10] = 38.0; + open[10] = 43.0; + close[10] = 52.0; + volume[10] = 50.0; + date[11] = createOhlcData(2001, jan, 15, 12, 0); + high[11] = 48.0; + low[11] = 41.0; + open[11] = 44.0; + close[11] = 41.0; + volume[11] = 80.0; + date[12] = createOhlcData(2001, jan, 17, 12, 0); + high[12] = 60.0; + low[12] = 30.0; + open[12] = 34.0; + close[12] = 44.0; + volume[12] = 90.0; + date[13] = createOhlcData(2001, jan, 18, 12, 0); + high[13] = 58.0; + low[13] = 44.0; + open[13] = 54.0; + close[13] = 56.0; + volume[13] = 20.0; + date[14] = createOhlcData(2001, jan, 19, 12, 0); + high[14] = 54.0; + low[14] = 32.0; + open[14] = 42.0; + close[14] = 53.0; + volume[14] = 70.0; + date[15] = createOhlcData(2001, jan, 20, 12, 0); + high[15] = 53.0; + low[15] = 39.0; + open[15] = 50.0; + close[15] = 49.0; + volume[15] = 60.0; + date[16] = createOhlcData(2001, jan, 21, 12, 0); + high[16] = 47.0; + low[16] = 33.0; + open[16] = 41.0; + close[16] = 40.0; + volume[16] = 30.0; + date[17] = createOhlcData(2001, jan, 22, 12, 0); + high[17] = 55.0; + low[17] = 37.0; + open[17] = 43.0; + close[17] = 45.0; + volume[17] = 90.0; + date[18] = createOhlcData(2001, jan, 23, 12, 0); + high[18] = 54.0; + low[18] = 42.0; + open[18] = 50.0; + close[18] = 42.0; + volume[18] = 150.0; + date[19] = createOhlcData(2001, jan, 24, 12, 0); + high[19] = 48.0; + low[19] = 37.0; + open[19] = 37.0; + close[19] = 47.0; + volume[19] = 120.0; + date[20] = createOhlcData(2001, jan, 25, 12, 0); + high[20] = 58.0; + low[20] = 33.0; + open[20] = 39.0; + close[20] = 41.0; + volume[20] = 80.0; + date[21] = createOhlcData(2001, jan, 26, 12, 0); + high[21] = 47.0; + low[21] = 31.0; + open[21] = 36.0; + close[21] = 41.0; + volume[21] = 40.0; + date[22] = createOhlcData(2001, jan, 27, 12, 0); + high[22] = 58.0; + low[22] = 44.0; + open[22] = 49.0; + close[22] = 44.0; + volume[22] = 20.0; + date[23] = createOhlcData(2001, jan, 28, 12, 0); + high[23] = 46.0; + low[23] = 41.0; + open[23] = 43.0; + close[23] = 44.0; + volume[23] = 60.0; + date[24] = createOhlcData(2001, jan, 29, 12, 0); + high[24] = 56.0; + low[24] = 39.0; + open[24] = 39.0; + close[24] = 51.0; + volume[24] = 40.0; + date[25] = createOhlcData(2001, jan, 30, 12, 0); + high[25] = 56.0; + low[25] = 39.0; + open[25] = 47.0; + close[25] = 49.0; + volume[25] = 70.0; + date[26] = createOhlcData(2001, jan, 31, 12, 0); + high[26] = 53.0; + low[26] = 39.0; + open[26] = 52.0; + close[26] = 47.0; + volume[26] = 60.0; + date[27] = createOhlcData(2001, feb, 1, 12, 0); + high[27] = 51.0; + low[27] = 30.0; + open[27] = 45.0; + close[27] = 47.0; + volume[27] = 90.0; + date[28] = createOhlcData(2001, feb, 2, 12, 0); + high[28] = 47.0; + low[28] = 30.0; + open[28] = 34.0; + close[28] = 46.0; + volume[28] = 100.0; + date[29] = createOhlcData(2001, feb, 3, 12, 0); + high[29] = 57.0; + low[29] = 37.0; + open[29] = 44.0; + close[29] = 56.0; + volume[29] = 20.0; + date[30] = createOhlcData(2001, feb, 4, 12, 0); + high[30] = 49.0; + low[30] = 40.0; + open[30] = 47.0; + close[30] = 44.0; + volume[30] = 50.0; + date[31] = createOhlcData(2001, feb, 5, 12, 0); + high[31] = 46.0; + low[31] = 38.0; + open[31] = 43.0; + close[31] = 40.0; + volume[31] = 70.0; + date[32] = createOhlcData(2001, feb, 6, 12, 0); + high[32] = 55.0; + low[32] = 38.0; + open[32] = 39.0; + close[32] = 53.0; + volume[32] = 120.0; + date[33] = createOhlcData(2001, feb, 7, 12, 0); + high[33] = 50.0; + low[33] = 33.0; + open[33] = 37.0; + close[33] = 37.0; + volume[33] = 140.0; + date[34] = createOhlcData(2001, feb, 8, 12, 0); + high[34] = 59.0; + low[34] = 34.0; + open[34] = 57.0; + close[34] = 43.0; + volume[34] = 70.0; + date[35] = createOhlcData(2001, feb, 9, 12, 0); + high[35] = 48.0; + low[35] = 39.0; + open[35] = 46.0; + close[35] = 47.0; + volume[35] = 70.0; + date[36] = createOhlcData(2001, feb, 10, 12, 0); + high[36] = 55.0; + low[36] = 30.0; + open[36] = 37.0; + close[36] = 30.0; + volume[36] = 30.0; + date[37] = createOhlcData(2001, feb, 11, 12, 0); + high[37] = 60.0; + low[37] = 32.0; + open[37] = 56.0; + close[37] = 36.0; + volume[37] = 70.0; + date[38] = createOhlcData(2001, feb, 12, 12, 0); + high[38] = 56.0; + low[38] = 42.0; + open[38] = 53.0; + close[38] = 54.0; + volume[38] = 40.0; + date[39] = createOhlcData(2001, feb, 13, 12, 0); + high[39] = 49.0; + low[39] = 42.0; + open[39] = 45.0; + close[39] = 42.0; + volume[39] = 90.0; + date[40] = createOhlcData(2001, feb, 14, 12, 0); + high[40] = 55.0; + low[40] = 42.0; + open[40] = 47.0; + close[40] = 54.0; + volume[40] = 70.0; + date[41] = createOhlcData(2001, feb, 15, 12, 0); + high[41] = 49.0; + low[41] = 35.0; + open[41] = 38.0; + close[41] = 35.0; + volume[41] = 20.0; + date[42] = createOhlcData(2001, feb, 16, 12, 0); + high[42] = 47.0; + low[42] = 38.0; + open[42] = 43.0; + close[42] = 42.0; + volume[42] = 10.0; + date[43] = createOhlcData(2001, feb, 17, 12, 0); + high[43] = 53.0; + low[43] = 42.0; + open[43] = 47.0; + close[43] = 48.0; + volume[43] = 20.0; + date[44] = createOhlcData(2001, feb, 18, 12, 0); + high[44] = 47.0; + low[44] = 44.0; + open[44] = 46.0; + close[44] = 44.0; + volume[44] = 30.0; + date[45] = createOhlcData(2001, feb, 19, 12, 0); + high[45] = 46.0; + low[45] = 40.0; + open[45] = 43.0; + close[45] = 44.0; + volume[45] = 50.0; + date[46] = createOhlcData(2001, feb, 20, 12, 0); + high[46] = 48.0; + low[46] = 41.0; + open[46] = 46.0; + close[46] = 41.0; + volume[46] = 100.0; + return new DefaultHighLowDataset("Series 1", date, high, low, open, close, volume); + } + + private static Date createOhlcData(int y, int m, int d, int hour, int min) { + Calendar calendar = Calendar.getInstance(); + calendar.set(y, m - 1, d, hour, min); + return calendar.getTime(); + } + + private static Icon getProfileIcon(String name, boolean defaultIcon) { + if (defaultIcon) { + return new ImageIcon(SampleData.class.getResource("/images/" + name)); + } else { + AvatarIcon avatarIcon = new AvatarIcon(SampleData.class.getResource("/images/" + name), 45, 45, 3f); + avatarIcon.setType(AvatarIcon.Type.MASK_SQUIRCLE); + return avatarIcon; + } + } +} diff --git a/src/main/java/backupmanager/sample/csv/CSVDataReader.java b/src/main/java/backupmanager/sample/csv/CSVDataReader.java new file mode 100644 index 00000000..022930d8 --- /dev/null +++ b/src/main/java/backupmanager/sample/csv/CSVDataReader.java @@ -0,0 +1,93 @@ +package backupmanager.sample.csv; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class CSVDataReader { + + private final String[] columns; + private final List rows; + + private CSVDataReader(String[] columns, List rows) { + this.columns = columns; + this.rows = rows; + } + + public String[] getColumns() { + return columns; + } + + public ResponseCSV getData(int page, int limit) { + int total = rows.size(); + int pageSize = (int) Math.ceil((double) total / limit); + + if (page > pageSize) { + page = pageSize; + } + + int start = Math.min((page - 1) * limit, total); + int end = Math.min(start + limit, total); + return new ResponseCSV(total, page, pageSize, limit, new ArrayList<>(rows.subList(start, end))); + } + + public static CSVDataReader load(InputStream stream) throws IOException { + return loadImpl(new BufferedReader(new InputStreamReader(stream))); + } + + public static CSVDataReader load(File file) throws IOException { + return loadImpl(new BufferedReader(new FileReader(file))); + } + + private static CSVDataReader loadImpl(BufferedReader reader) throws IOException { + String[] columns = null; + List rows = new ArrayList<>(); + String line; + try (BufferedReader br = reader) { + while ((line = br.readLine()) != null) { + + // parse the line as a CSV row + String[] data = parseCSVLine(line); + if (columns == null) { + columns = data; + } else { + rows.add(data); + } + } + } + return new CSVDataReader(columns, rows); + } + + /** + * Code from ChatGPT + * To parse the csv row to array + */ + private static String[] parseCSVLine(String line) { + List result = new ArrayList<>(); + StringBuilder currentField = new StringBuilder(); + boolean inQuotes = false; + + for (int i = 0; i < line.length(); i++) { + char ch = line.charAt(i); + + if (ch == '"') { + // toggle the inQuotes flag unless it's an escaped quote + if (i + 1 < line.length() && line.charAt(i + 1) == '"') { + currentField.append(ch); // Add escaped quote + i++; // skip next quote + } else { + inQuotes = !inQuotes; + } + } else if (ch == ',' && !inQuotes) { + // end of field + result.add(currentField.toString()); + currentField.setLength(0); // reset the field + } else { + currentField.append(ch); // add character to the current field + } + } + // add the last field + result.add(currentField.toString()); + return result.toArray(new String[0]); + } +} diff --git a/src/main/java/backupmanager/sample/csv/Pageable.java b/src/main/java/backupmanager/sample/csv/Pageable.java new file mode 100644 index 00000000..68636222 --- /dev/null +++ b/src/main/java/backupmanager/sample/csv/Pageable.java @@ -0,0 +1,40 @@ +package backupmanager.sample.csv; + +public class Pageable { + + public int getTotal() { + return total; + } + + public int getPage() { + return page; + } + + public int getPageSize() { + return pageSize; + } + + public int getLimit() { + return limit; + } + + public Pageable(int total, int page, int pageSize, int limit) { + this.total = total; + this.page = page; + this.pageSize = pageSize; + this.limit = limit; + } + + private final int total; + private final int page; + private final int pageSize; + private final int limit; + + public boolean hasPrevious() { + return page > 1 && pageSize > 0; + } + + public boolean hasNext() { + return page < pageSize && pageSize > 0; + } +} diff --git a/src/main/java/backupmanager/sample/csv/ResponseCSV.java b/src/main/java/backupmanager/sample/csv/ResponseCSV.java new file mode 100644 index 00000000..d952e5ca --- /dev/null +++ b/src/main/java/backupmanager/sample/csv/ResponseCSV.java @@ -0,0 +1,10 @@ +package backupmanager.sample.csv; + +import java.util.List; + +public class ResponseCSV extends ResponsePageable> { + + public ResponseCSV(int total, int page, int pageSize, int limit, List data) { + super(total, page, pageSize, limit, data); + } +} diff --git a/src/main/java/backupmanager/sample/csv/ResponsePageable.java b/src/main/java/backupmanager/sample/csv/ResponsePageable.java new file mode 100644 index 00000000..a6fc0155 --- /dev/null +++ b/src/main/java/backupmanager/sample/csv/ResponsePageable.java @@ -0,0 +1,15 @@ +package backupmanager.sample.csv; + +public abstract class ResponsePageable

extends Pageable { + + public P getData() { + return data; + } + + public ResponsePageable(int total, int page, int pageSize, int limit, P data) { + super(total, page, pageSize, limit); + this.data = data; + } + + private final P data; +} diff --git a/src/main/java/backupmanager/simple/SimpleInputForms.java b/src/main/java/backupmanager/simple/SimpleInputForms.java new file mode 100644 index 00000000..f218f569 --- /dev/null +++ b/src/main/java/backupmanager/simple/SimpleInputForms.java @@ -0,0 +1,116 @@ +package backupmanager.simple; + +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSeparator; +import javax.swing.JSpinner; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SpinnerNumberModel; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import net.miginfocom.swing.MigLayout; +import raven.modal.component.ModalBorderAction; +import raven.modal.component.SimpleModalBorder; + +public class SimpleInputForms extends JPanel { + + public static int NEW_COUNTRY = 30; + + public SimpleInputForms() { + init(); + } + + private void init() { + setLayout(new MigLayout("fillx,wrap,insets 5 30 5 30,width 400", "[fill]", "")); + txtBackupName = new JTextField(); + txtTargetPath = new JTextField(); + txtDestinationPath = new JTextField(); + executeBackupBtn = new JButton("Execute Backup"); + automaticBackupBtn = new JButton("Automatic Backup (OFF)"); + targetPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); + destinationPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); + TimeIntervalBtn = new JButton(new FlatSVGIcon("icons/timer.svg", 25, 25)); + maxToKeeSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1)); + maxToKeeLabel = new JLabel("Max to keep"); + lastBackupLabel = new JLabel("Last backup: never"); + + JTextArea txtNotes = new JTextArea(); + txtNotes.setWrapStyleWord(true); + txtNotes.setLineWrap(true); + JScrollPane scroll = new JScrollPane(txtNotes); + + // style + txtBackupName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Backup name (unique)"); + txtTargetPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "target path e.g. C:\\Users\\Admin\\Documents"); + txtDestinationPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "destination folder e.g. D:\\Backups"); + + // add to panel + createTitle("Backup information"); + + add(new JLabel("Backup Name"), "gapy 5 0"); + add(txtBackupName); + add(new JLabel("Paths"), "gapy 5 0"); + add(txtTargetPath, "split 2"); + add(targetPathBtn, "w 30!, h 30!"); + add(txtDestinationPath, "split 2"); + add(destinationPathBtn, "w 30!, h 30!"); + + add(new JLabel("Notes"), "gapy 5 0"); + add(scroll, "height 120,grow,pushy"); + + createTitle("Advanced Information"); + + add(lastBackupLabel); + add(executeBackupBtn); + add(automaticBackupBtn, "split 2"); + add(TimeIntervalBtn, "w 30!, h 30!"); + + add(maxToKeeLabel, "gapy 5 0"); + add(maxToKeeSpinner, "width 100"); + + + txtNotes.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (e.isControlDown() && e.getKeyChar() == 10) { + ModalBorderAction modalBorderAction = ModalBorderAction.getModalBorderAction(SimpleInputForms.this); + if (modalBorderAction != null) { + modalBorderAction.doAction(SimpleModalBorder.YES_OPTION); + } + } + } + }); + } + + private void createTitle(String title) { + JLabel lb = new JLabel(title); + lb.putClientProperty(FlatClientProperties.STYLE, "" + + "font:+2"); + add(lb, "gapy 5 0"); + add(new JSeparator(), "height 2!,gapy 0 0"); + } + + public void formOpen() { + txtBackupName.grabFocus(); + } + + private JTextField txtBackupName; + private JTextField txtTargetPath; + private JTextField txtDestinationPath; + private JLabel lastBackupLabel; + private JButton executeBackupBtn; + private JButton automaticBackupBtn; + private JButton targetPathBtn; + private JButton destinationPathBtn; + private JButton TimeIntervalBtn; + private JSpinner maxToKeeSpinner; + private JLabel maxToKeeLabel; +} diff --git a/src/main/java/backupmanager/svg/SVGButton.java b/src/main/java/backupmanager/svg/SVGButton.java index b95b78d3..a684d422 100644 --- a/src/main/java/backupmanager/svg/SVGButton.java +++ b/src/main/java/backupmanager/svg/SVGButton.java @@ -12,6 +12,11 @@ public SVGButton() { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } + public SVGButton(String text) { + super(text); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + } + public void setSvgImage(String imagePath, int width, int height) { if (imagePath == null) return; diff --git a/src/main/java/backupmanager/svg/SVGIconUIColor.java b/src/main/java/backupmanager/svg/SVGIconUIColor.java new file mode 100644 index 00000000..464b6239 --- /dev/null +++ b/src/main/java/backupmanager/svg/SVGIconUIColor.java @@ -0,0 +1,42 @@ +package backupmanager.svg; + +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.util.ColorFunctions; + +import javax.swing.*; +import java.awt.*; + +public class SVGIconUIColor extends FlatSVGIcon { + + private String colorKey; + private float alpha; + + public SVGIconUIColor(String name, float scale, String colorKey, float alpha) { + super(name, scale); + this.colorKey = colorKey; + this.alpha = alpha; + setColorFilter(new ColorFilter(color -> { + Color uiColor = UIManager.getColor(getColorKey()); + if (uiColor != null) { + return getAlpha() == 1 ? uiColor : ColorFunctions.fade(uiColor, getAlpha()); + } + return color; + })); + } + + public String getColorKey() { + return colorKey; + } + + public void setColorKey(String colorKey) { + this.colorKey = colorKey; + } + + public float getAlpha() { + return alpha; + } + + public void setAlpha(float alpha) { + this.alpha = alpha; + } +} diff --git a/src/main/java/backupmanager/system/AllForms.java b/src/main/java/backupmanager/system/AllForms.java new file mode 100644 index 00000000..13be50de --- /dev/null +++ b/src/main/java/backupmanager/system/AllForms.java @@ -0,0 +1,44 @@ +package backupmanager.system; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.SwingUtilities; + +public class AllForms { + + private static AllForms instance; + + private final Map, Form> formsMap; + + private static AllForms getInstance() { + if (instance == null) { + instance = new AllForms(); + } + return instance; + } + + private AllForms() { + formsMap = new HashMap<>(); + } + + public static Form getForm(Class cls) { + if (getInstance().formsMap.containsKey(cls)) { + return getInstance().formsMap.get(cls); + } + try { + Form form = cls.getDeclaredConstructor().newInstance(); + getInstance().formsMap.put(cls, form); + formInit(form); + return form; + } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | + IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static void formInit(Form form) { + SwingUtilities.invokeLater(() -> form.formInit()); + } +} diff --git a/src/main/java/backupmanager/system/Form.java b/src/main/java/backupmanager/system/Form.java new file mode 100644 index 00000000..a465a22e --- /dev/null +++ b/src/main/java/backupmanager/system/Form.java @@ -0,0 +1,36 @@ +package backupmanager.system; + +import javax.swing.JPanel; +import javax.swing.LookAndFeel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +public class Form extends JPanel { + + private LookAndFeel oldTheme = UIManager.getLookAndFeel(); + + public Form() { + init(); + } + + private void init() { + } + + public void formInit() { + } + + public void formOpen() { + } + + public void formRefresh() { + } + + protected boolean formCheck() { + if (oldTheme != UIManager.getLookAndFeel()) { + oldTheme = UIManager.getLookAndFeel(); + SwingUtilities.updateComponentTreeUI(this); + return true; + } + return false; + } +} diff --git a/src/main/java/backupmanager/system/FormManager.java b/src/main/java/backupmanager/system/FormManager.java new file mode 100644 index 00000000..bcb14637 --- /dev/null +++ b/src/main/java/backupmanager/system/FormManager.java @@ -0,0 +1,122 @@ +package backupmanager.system; + +import javax.swing.JFrame; + +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.util.ColorFunctions; + +import backupmanager.component.About; +import backupmanager.forms.FormDashboard; +import backupmanager.frames.Login; +import backupmanager.utils.UndoRedo; +import raven.modal.Drawer; +import raven.modal.ModalDialog; +import raven.modal.component.SimpleModalBorder; + +public class FormManager { + + protected static final UndoRedo

FORMS = new UndoRedo<>(); + private static JFrame frame; + private static MainForm mainForm; + private static Login login; + + public static void install(JFrame f) { + frame = f; + install(); + logout(); + } + + private static void install() { + FormSearch.getInstance().installKeyMap(getMainForm()); + FlatSVGIcon.ColorFilter.getInstance().setMapperEx((component, color) -> { + if (color.getRGB() == -6908266) { + return FlatLaf.isLafDark() ? ColorFunctions.shade(component.getForeground(), 0.2f) + : ColorFunctions.tint(component.getForeground(), 0.4f); + } + return color; + }); + } + + public static void showForm(Form form) { + if (form != FORMS.getCurrent()) { + FORMS.add(form); + form.formCheck(); + form.formOpen(); + mainForm.setForm(form); + mainForm.refresh(); + } + } + + public static void undo() { + if (FORMS.isUndoAble()) { + Form form = FORMS.undo(); + form.formCheck(); + form.formOpen(); + mainForm.setForm(form); + Drawer.setSelectedItemClass(form.getClass()); + } + } + + public static void redo() { + if (FORMS.isRedoAble()) { + Form form = FORMS.redo(); + form.formCheck(); + form.formOpen(); + mainForm.setForm(form); + Drawer.setSelectedItemClass(form.getClass()); + } + } + + public static void refresh() { + if (FORMS.getCurrent() != null) { + FORMS.getCurrent().formRefresh(); + mainForm.refresh(); + } + } + + public static void login() { + Drawer.setVisible(true); + frame.getContentPane().removeAll(); + frame.getContentPane().add(getMainForm()); + + Drawer.setSelectedItemClass(FormDashboard.class); + frame.repaint(); + frame.revalidate(); + } + + public static void logout() { + Drawer.setVisible(false); + frame.getContentPane().removeAll(); + Form login = getLogin(); + login.formCheck(); + frame.getContentPane().add(login); + FORMS.clear(); + frame.repaint(); + frame.revalidate(); + } + + public static JFrame getFrame() { + return frame; + } + + private static MainForm getMainForm() { + if (mainForm == null) { + mainForm = new MainForm(); + } + return mainForm; + } + + private static Login getLogin() { + if (login == null) { + login = new Login(); + } + return login; + } + + public static void showAbout() { + ModalDialog.showModal(frame, new SimpleModalBorder(new About(), "About"), + ModalDialog.createOption().setAnimationEnabled(false) + ); + } +} diff --git a/src/main/java/backupmanager/system/FormSearch.java b/src/main/java/backupmanager/system/FormSearch.java new file mode 100644 index 00000000..07b09148 --- /dev/null +++ b/src/main/java/backupmanager/system/FormSearch.java @@ -0,0 +1,98 @@ +package backupmanager.system; + +import raven.modal.ModalDialog; +import backupmanager.component.EmptyModalBorder; +import backupmanager.component.FormSearchPanel; +import backupmanager.menu.MyDrawerBuilder; +import backupmanager.utils.SystemForm; +import raven.modal.drawer.item.Item; +import raven.modal.drawer.item.MenuItem; +import raven.modal.option.Location; +import raven.modal.option.Option; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FormSearch { + + private static FormSearch instance; + public static final String ID = "search"; + private final Map> formsMap; + private FormSearchPanel searchPanel; + + public static FormSearch getInstance() { + if (instance == null) { + instance = new FormSearch(); + } + return instance; + } + + private FormSearch() { + formsMap = new HashMap<>(); + for (Class cls : getClassForms()) { + if (cls.isAnnotationPresent(SystemForm.class)) { + SystemForm f = cls.getAnnotation(SystemForm.class); + formsMap.put(f, cls); + } + } + } + + private Class[] getClassForms() { + MenuItem[] menuItems = MyDrawerBuilder.getInstance().getSimpleMenuOption().getMenus(); + List> formClass = new ArrayList<>(); + getMenuClass(menuItems, formClass); + return formClass.toArray(new Class[0]); + } + + private void getMenuClass(MenuItem[] menuItems, List> formClass) { + for (MenuItem menu : menuItems) { + if (menu.isMenu()) { + Item item = (Item) menu; + if (item.getItemClass() != null) { + formClass.add(item.getItemClass()); + } + if (item.isSubmenuAble()) { + getMenuClass(item.getSubMenu().toArray(new Item[0]), formClass); + } + } + } + } + + public void installKeyMap(JComponent component) { + ActionListener key = e -> showSearch(); + component.registerKeyboardAction(key, KeyStroke.getKeyStroke(KeyEvent.VK_F, KeyEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + public void showSearch() { + if (ModalDialog.isIdExist(ID)) { + return; + } + Option option = ModalDialog.createOption(); + option.setAnimationEnabled(false); + option.getLayoutOption().setMargin(20, 10, 10, 10).setLocation(Location.CENTER, Location.TOP); + ModalDialog.showModal(FormManager.getFrame(), new EmptyModalBorder(getSearchPanel(), (controller, action) -> { + if (action == EmptyModalBorder.OPENED) { + searchPanel.searchGrabFocus(); + } + }), option, ID); + } + + private JPanel getSearchPanel() { + if (searchPanel == null) { + searchPanel = new FormSearchPanel(formsMap); + } + searchPanel.formCheck(); + searchPanel.clearSearch(); + ComponentOrientation orientation = FormManager.getFrame().getComponentOrientation(); + if (orientation.isLeftToRight() != searchPanel.getComponentOrientation().isLeftToRight()) { + searchPanel.applyComponentOrientation(orientation); + } + return searchPanel; + } +} diff --git a/src/main/java/backupmanager/system/MainForm.java b/src/main/java/backupmanager/system/MainForm.java new file mode 100644 index 00000000..580270d0 --- /dev/null +++ b/src/main/java/backupmanager/system/MainForm.java @@ -0,0 +1,130 @@ +package backupmanager.system; + +import java.awt.BorderLayout; +import java.awt.Component; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JToolBar; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import backupmanager.Enums.ConfigKey; +import backupmanager.component.FormSearchButton; +import backupmanager.component.RefreshLine; +import net.miginfocom.swing.MigLayout; +import raven.modal.Drawer; + +public class MainForm extends JPanel { + + public MainForm() { + init(); + } + + private void init() { + setLayout(new MigLayout("fillx,wrap,insets 0,gap 0", "[fill]", "[][][fill,grow][]")); + add(createHeader()); + add(createRefreshLine(), "height 3!"); + add(createMain()); + add(new JSeparator(), "height 2!"); + add(createFooter()); + } + + private JPanel createHeader() { + JPanel panel = new JPanel(new MigLayout("insets 3", "[]push[]push", "[fill]")); + JToolBar toolBar = new JToolBar(); + JButton buttonDrawer = new JButton(new FlatSVGIcon("icons/menu.svg", 0.5f)); + buttonUndo = new JButton(new FlatSVGIcon("icons/undo.svg", 0.5f)); + buttonRedo = new JButton(new FlatSVGIcon("icons/redo.svg", 0.5f)); + buttonRefresh = new JButton(new FlatSVGIcon("icons/refresh.svg", 0.5f)); + + // style + buttonDrawer.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;"); + buttonUndo.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;"); + buttonRedo.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;"); + buttonRefresh.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;"); + + buttonDrawer.addActionListener(e -> { + if (Drawer.isOpen()) { + Drawer.showDrawer(); + } else { + Drawer.toggleMenuOpenMode(); + } + }); + buttonUndo.addActionListener(e -> FormManager.undo()); + buttonRedo.addActionListener(e -> FormManager.redo()); + buttonRefresh.addActionListener(e -> FormManager.refresh()); + + toolBar.add(buttonDrawer); + toolBar.add(buttonUndo); + toolBar.add(buttonRedo); + toolBar.add(buttonRefresh); + panel.add(toolBar); + panel.add(createSearchBox(), "gapx n 135"); + return panel; + } + + private JPanel createFooter() { + JPanel panel = new JPanel(new MigLayout("insets 1 n 1 n,al trailing center,gapx 10,height 30!", "[]push[][]", "fill")); + panel.putClientProperty(FlatClientProperties.STYLE, "background:$Menu.background;"); + + // demo version + JLabel lbDemoVersion = new JLabel("Backup Manager: v" + ConfigKey.VERSION.getValue()); + lbDemoVersion.putClientProperty(FlatClientProperties.STYLE, "" + + "foreground:$Label.disabledForeground;"); + panel.add(lbDemoVersion); + + return panel; + } + + private JPanel createSearchBox() { + JPanel panel = new JPanel(new MigLayout("fill", "[fill,center,200:250:]", "[fill]")); + FormSearchButton button = new FormSearchButton(); + button.addActionListener(e -> FormSearch.getInstance().showSearch()); + panel.add(button); + return panel; + } + + private JPanel createRefreshLine() { + refreshLine = new RefreshLine(); + return refreshLine; + } + + private Component createMain() { + mainPanel = new JPanel(new BorderLayout()); + return mainPanel; + } + + public void setForm(Form form) { + mainPanel.removeAll(); + mainPanel.add(form); + mainPanel.repaint(); + mainPanel.revalidate(); + + // check button + buttonUndo.setEnabled(FormManager.FORMS.isUndoAble()); + buttonRedo.setEnabled(FormManager.FORMS.isRedoAble()); + // check component orientation and update + if (mainPanel.getComponentOrientation().isLeftToRight() != form.getComponentOrientation().isLeftToRight()) { + applyComponentOrientation(mainPanel.getComponentOrientation()); + } + } + + public void refresh() { + refreshLine.refresh(); + } + + private JPanel mainPanel; + private RefreshLine refreshLine; + + private JButton buttonUndo; + private JButton buttonRedo; + private JButton buttonRefresh; +} diff --git a/src/main/java/backupmanager/themes/ListCellTitledBorder.java b/src/main/java/backupmanager/themes/ListCellTitledBorder.java new file mode 100644 index 00000000..b1fd92d1 --- /dev/null +++ b/src/main/java/backupmanager/themes/ListCellTitledBorder.java @@ -0,0 +1,74 @@ +package backupmanager.themes; + +import java.awt.Component; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.geom.Rectangle2D; + +import javax.swing.JList; +import javax.swing.UIManager; +import javax.swing.border.Border; + +import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.UIScale; + +public class ListCellTitledBorder implements Border { + + private final JList list; + private final String title; + + ListCellTitledBorder(JList list, String title) { + this.list = list; + this.title = title; + } + + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + FontMetrics fm = c.getFontMetrics(list.getFont()); + int titleWidth = fm.stringWidth(title); + int titleHeight = fm.getHeight(); + + // fill background + g.setColor(list.getBackground()); + g.fillRect(x, y, width, titleHeight); + + int gap = UIScale.scale(4); + Graphics2D g2 = (Graphics2D) g.create(); + + try { + FlatUIUtils.setRenderingHints(g2); + g2.setColor(UIManager.getColor("Label.disabledForeground")); + + // paint separator line + int sepWidth = (width - titleWidth) / 2 - (gap * 2); + if (sepWidth > 0) { + int sy = y + Math.round(titleHeight / 2f); + float sepHeight = UIScale.scale(1f); + + g2.fill(new Rectangle2D.Float(x + gap, sy, sepWidth, sepHeight)); + g2.fill(new Rectangle2D.Float(x + width - gap - sepWidth, sy, sepWidth, sepHeight)); + } + + // draw title + int xt = x + ((width - titleWidth) / 2); + int yt = y + fm.getAscent(); + + FlatUIUtils.drawString(list, g2, title, xt, yt); + } finally { + g2.dispose(); + } + } + + @Override + public Insets getBorderInsets(Component c) { + int height = c.getFontMetrics(list.getFont()).getHeight(); + return new Insets(height, 0, 0, 0); + } + + @Override + public boolean isBorderOpaque() { + return true; + } +} diff --git a/src/main/java/backupmanager/themes/PanelThemes.java b/src/main/java/backupmanager/themes/PanelThemes.java new file mode 100644 index 00000000..df1e46a0 --- /dev/null +++ b/src/main/java/backupmanager/themes/PanelThemes.java @@ -0,0 +1,209 @@ +package backupmanager.themes; + +import com.formdev.flatlaf.*; +import com.formdev.flatlaf.extras.FlatAnimatedLafChange; +import com.formdev.flatlaf.util.LoggingFacade; +import net.miginfocom.swing.MigLayout; +import backupmanager.utils.DemoPreferences; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.event.ListSelectionEvent; +import java.awt.*; +import java.util.*; +import java.util.List; + +public class PanelThemes extends JPanel { + + public static final String THEMES_PACKAGE = "/com/formdev/flatlaf/intellijthemes/themes/"; + private final ThemesManager themesManager = new ThemesManager(); + private final Map categories = new HashMap<>(); + private final List themes = new ArrayList<>(); + + public PanelThemes() { + init(); + } + + private void init() { + setLayout(new MigLayout("fillx,insets 3", "[fill]", "[fill,grow]")); + themesList = new JList<>(); + themesList.setCellRenderer(new DefaultListCellRenderer() { + + private int index; + private boolean isSelected; + private int titleHeight; + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + this.index = index; + this.isSelected = isSelected; + this.titleHeight = 0; + + String title = categories.get(index); + String name = ((ThemesInfo) value).name; + int sep = name.indexOf('/'); + if (sep >= 0) { + name = name.substring(sep + 1).trim(); + } + + JComponent com = (JComponent) super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus); + com.setToolTipText(buildToolTip((ThemesInfo) value)); + if (title != null) { + Border titBorder = new ListCellTitledBorder(themesList, title); + com.setBorder(new CompoundBorder(titBorder, com.getBorder())); + this.titleHeight = titBorder.getBorderInsets(com).top; + } + return com; + } + + private String buildToolTip(ThemesInfo th) { + if (th.resourceName == null) { + return th.name; + } + return "Name :" + th.name + + "\nLicense: " + th.license + + "\nSource Code: " + th.sourceCodeUrl; + } + }); + themesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + themesList.addListSelectionListener(e -> themesListValueChanged(e)); + JScrollPane scrollPane = new JScrollPane(themesList); + add(scrollPane); + + themesManager.loadThemes(); + updateThemesList(0); + } + + public void updateThemesList(int option) { + boolean showLight = option != 2; + boolean showDark = option != 1; + + ThemesInfo oldSelected = themesList.getSelectedValue(); + + categories.clear(); + themes.clear(); + + // add core themes + categories.put(themes.size(), "Core Themes"); + for (ThemesInfo th : themesManager.coreThemes) { + boolean show = (showLight && !th.dark) || (showDark && th.dark); + if (show && !th.name.contains("/")) { + themes.add(th); + } + } + + // add uncategorized bundled themes + categories.put(themes.size(), "IntelliJ Themes"); + for (ThemesInfo th : themesManager.bundledThemes) { + boolean show = (showLight && !th.dark) || (showDark && th.dark); + if (show && !th.name.contains("/")) { + themes.add(th); + } + } + + // add categorized bundled themes + String lastCategory = null; + for (ThemesInfo th : themesManager.bundledThemes) { + boolean show = (showLight && !th.dark) || (showDark && th.dark); + int sep = th.name.indexOf('/'); + if (!show || sep < 0) { + continue; + } + String category = th.name.substring(0, sep).trim(); + if (!Objects.equals(lastCategory, category)) { + lastCategory = category; + categories.put(themes.size(), category); + } + themes.add(th); + } + + // add themes list model + themesList.setModel(new AbstractListModel() { + @Override + public int getSize() { + return themes.size(); + } + + @Override + public ThemesInfo getElementAt(int index) { + return themes.get(index); + } + }); + + // restore selection + if (oldSelected != null) { + themesList.setSelectedValue(oldSelected, true); + if (themesList.isSelectionEmpty()) { + themesList.setSelectedIndex(0); + } + } else { + selectedCurrentLookAndFeel(); + } + } + + private void selectedCurrentLookAndFeel() { + LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); + String theme = UIManager.getLookAndFeelDefaults().getString(DemoPreferences.THEME_UI_KEY); + String lafClassName = lookAndFeel.getClass().getName(); + for (int i = 0; i < themes.size(); i++) { + ThemesInfo ti = themes.get(i); + if (theme == null && ti.lafClassName != null && lafClassName.equals(ti.lafClassName)) { + themesList.setSelectedIndex(i); + break; + } + if (theme != null && ti.resourceName != null && theme.substring(DemoPreferences.RESOURCE_PREFIX.length()).equals(ti.resourceName)) { + themesList.setSelectedIndex(i); + break; + } + } + } + + private void themesListValueChanged(ListSelectionEvent e) { + ThemesInfo themesInfo = themesList.getSelectedValue(); + boolean bundledTheme = (themesInfo != null && themesInfo.resourceName != null); + if (e.getValueIsAdjusting()) { + return; + } + EventQueue.invokeLater(() -> setThemes(themesInfo)); + } + + private void setThemes(ThemesInfo themesInfo) { + if (themesInfo == null) { + return; + } + + // change look and feel + if (themesInfo.lafClassName != null) { + if (themesInfo.lafClassName.equals(UIManager.getLookAndFeel().getClass().getName())) { + return; + } + FlatAnimatedLafChange.showSnapshot(); + try { + UIManager.setLookAndFeel(themesInfo.lafClassName); + } catch (Exception e) { + LoggingFacade.INSTANCE.logSevere(null, e); + showInformationDialog("Failed to create '" + themesInfo.lafClassName + "'.", e); + } + } else { + String theme = UIManager.getLookAndFeelDefaults().getString(DemoPreferences.THEME_UI_KEY); + if (theme != null && themesInfo.resourceName.equals(theme.substring(DemoPreferences.RESOURCE_PREFIX.length()))) { + return; + } + FlatAnimatedLafChange.showSnapshot(); + IntelliJTheme.setup(getClass().getResourceAsStream(THEMES_PACKAGE + themesInfo.resourceName)); + DemoPreferences.getState().put(DemoPreferences.KEY_LAF_THEME, DemoPreferences.RESOURCE_PREFIX + themesInfo.resourceName); + } + FlatLaf.updateUI(); + FlatAnimatedLafChange.hideSnapshotWithAnimation(); + } + + private void showInformationDialog(String message, Exception e) { + JOptionPane.showMessageDialog(SwingUtilities.windowForComponent(this), + message + "\n\n" + e.getMessage(), + "Message", + JOptionPane.INFORMATION_MESSAGE); + } + + private JList themesList; +} diff --git a/src/main/java/backupmanager/themes/ThemesInfo.java b/src/main/java/backupmanager/themes/ThemesInfo.java new file mode 100644 index 00000000..1bda4d17 --- /dev/null +++ b/src/main/java/backupmanager/themes/ThemesInfo.java @@ -0,0 +1,25 @@ +package backupmanager.themes; + +public class ThemesInfo { + + final String name; + final String resourceName; + final boolean dark; + final String license; + final String licenseFile; + final String sourceCodeUrl; + final String sourceCodePath; + final String lafClassName; + + + public ThemesInfo(String name, String resourceName, boolean dark, String license, String licenseFile, String sourceCodeUrl, String sourceCodePath, String lafClassName) { + this.name = name; + this.resourceName = resourceName; + this.dark = dark; + this.license = license; + this.licenseFile = licenseFile; + this.sourceCodeUrl = sourceCodeUrl; + this.sourceCodePath = sourceCodePath; + this.lafClassName = lafClassName; + } +} diff --git a/src/main/java/backupmanager/themes/ThemesManager.java b/src/main/java/backupmanager/themes/ThemesManager.java new file mode 100644 index 00000000..e461e941 --- /dev/null +++ b/src/main/java/backupmanager/themes/ThemesManager.java @@ -0,0 +1,57 @@ +package backupmanager.themes; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.formdev.flatlaf.FlatDarculaLaf; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatIntelliJLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.json.Json; +import com.formdev.flatlaf.themes.FlatMacDarkLaf; +import com.formdev.flatlaf.themes.FlatMacLightLaf; +import com.formdev.flatlaf.util.LoggingFacade; + +public class ThemesManager { + final List bundledThemes = new ArrayList<>(); + final List coreThemes = new ArrayList<>(); + + void loadThemes() { + bundledThemes.clear(); + // create core themes + + coreThemes.add(new ThemesInfo("FlatLaf Light", null, false, null, null, null, null, FlatLightLaf.class.getName())); + coreThemes.add(new ThemesInfo("FlatLaf Dark", null, true, null, null, null, null, FlatDarkLaf.class.getName())); + coreThemes.add(new ThemesInfo("FlatLaf IntelliJ", null, false, null, null, null, null, FlatIntelliJLaf.class.getName())); + coreThemes.add(new ThemesInfo("FlatLaf Darcula", null, true, null, null, null, null, FlatDarculaLaf.class.getName())); + coreThemes.add(new ThemesInfo("FlatLaf macOS Light", null, false, null, null, null, null, FlatMacLightLaf.class.getName())); + coreThemes.add(new ThemesInfo("FlatLaf macOS Dark", null, true, null, null, null, null, FlatMacDarkLaf.class.getName())); + + // load themes.json + Map json; + try (Reader reader = new InputStreamReader(getClass().getResourceAsStream("themes.json"), StandardCharsets.UTF_8)) { + json = (Map) Json.parse(reader); + } catch (IOException e) { + LoggingFacade.INSTANCE.logSevere(null, e); + return; + } + + // add themes info + for (Map.Entry e : json.entrySet()) { + String resourceName = e.getKey(); + Map value = (Map) e.getValue(); + String name = value.get("name"); + boolean dark = Boolean.parseBoolean(value.get("dark")); + String license = value.get("license"); + String licenseFile = value.get("licenseFile"); + String sourceCodeUrl = value.get("sourceCodeUrl"); + String sourceCodePath = value.get("sourceCodePath"); + bundledThemes.add(new ThemesInfo(name, resourceName, dark, license, licenseFile, sourceCodeUrl, sourceCodePath, null)); + } + } +} diff --git a/src/main/resources/data/customers-1000.csv b/src/main/resources/data/customers-1000.csv new file mode 100644 index 00000000..2dda2b4e --- /dev/null +++ b/src/main/resources/data/customers-1000.csv @@ -0,0 +1,1001 @@ +#,First Name,Last Name,Company,City,Country,Email,Subscription Date,Website +1,Andrew,Goodman,Stewart-Flynn,Rowlandberg,Macao,marieyates@gomez-spencer.info,7/26/2021,http://www.shea.biz/ +2,Alvin,Lane,"Terry, Proctor and Lawrence",Bethside,Papua New Guinea,alexandra86@mccoy.com,6/24/2021,http://www.pena-cole.com/ +3,Jenna,Harding,Bailey Group,Moniquemouth,China,justincurtis@pierce.org,4/5/2020,http://www.booth-reese.biz/ +4,Fernando,Ford,Moss-Maxwell,Leeborough,Macao,adeleon@hubbard.org,11/29/2020,http://www.hebert.com/ +5,Kara,Woods,Mccarthy-Kelley,Port Jacksonland,Nepal,jesus90@roberson.info,4/22/2022,http://merritt.com/ +6,Marissa,Gamble,Cherry and Sons,Webertown,Sudan,katieallison@leonard.com,11/17/2021,http://www.kaufman.org/ +7,Julie,Cooley,"Yu, Norman and Sharp",West Sandra,Japan,priscilla88@stephens.info,3/26/2022,http://www.sexton-chang.com/ +8,Lauren,Villa,"French, Travis and Hensley",New Yolanda,Fiji,colehumphrey@austin-caldwell.com,8/14/2020,https://www.kerr.com/ +9,Emily,Bryant,"Moon, Strickland and Combs",East Normanchester,Seychelles,buckyvonne@church-lutz.com,12/30/2020,http://grimes.com/ +10,Marie,Estrada,May Inc,Welchton,United Arab Emirates,christie44@mckenzie.biz,9/3/2020,https://www.salinas.net/ +11,Nichole,Cannon,Rios and Sons,West Devon,Burundi,blandry@henson-harris.biz,4/26/2021,http://www.humphrey.org/ +12,Bernard,Ritter,Bradford and Sons,West Francisco,Palau,tammiepope@arroyo-baldwin.com,1/19/2022,http://sellers.biz/ +13,Darryl,Archer,Kerr-Cherry,Holtfurt,Uganda,woodalejandro@skinner-sloan.biz,4/18/2022,https://www.daniels.com/ +14,Ryan,Li,"Hooper, Cross and Holt",Batesville,Liechtenstein,lmassey@duke.com,3/6/2021,http://nunez.com/ +15,Vicki,Nunez,"Leonard, Galvan and Blackburn",Barbaraborough,Haiti,zgrant@sweeney.com,1/30/2022,https://reynolds.com/ +16,Sean,Townsend,Preston-Sosa,Velasquezberg,Iran,lkline@maxwell.info,5/30/2020,http://www.vargas.biz/ +17,Sophia,Mathis,Richard-Velasquez,Toddhaven,Switzerland,brockmason@faulkner-may.com,1/23/2020,http://www.vaughn.com/ +18,Helen,Potts,"Rangel, Livingston and Patel",Douglasland,Seychelles,carrollmia@donovan-keith.com,7/27/2021,http://www.kennedy-edwards.biz/ +19,Joann,Finley,Harvey PLC,Barrettshire,Montserrat,gabriela86@sampson.com,4/11/2022,http://www.harrell.com/ +20,Thomas,Walsh,Best-Thomas,Roblesport,Kiribati,timcoleman@frank-king.org,9/11/2020,http://www.kane.com/ +21,Cristina,Lam,Watts-Allison,West Jocelynfort,Korea,charlotte16@hood-zhang.org,1/4/2020,http://whitehead.net/ +22,Vicki,Heath,"Cherry, Schultz and Ruiz",Port Cameronbury,Bangladesh,alan46@benjamin.com,11/6/2020,https://www.bird.com/ +23,Glenn,Wang,Warner-Hodge,West Rachael,Gabon,anna80@mata.com,1/1/2022,http://brooks-kerr.com/ +24,Darius,Benitez,Giles LLC,Mejiashire,Jersey,garrettdurham@olsen.com,2/28/2022,https://washington.com/ +25,Xavier,Cruz,Wiley Ltd,Mindyborough,Latvia,andersongrant@pugh.com,2/7/2020,https://www.cohen.info/ +26,Douglas,Galloway,Duffy Inc,Eileenbury,Mongolia,caleb11@velazquez.com,10/24/2021,http://www.mcneil.net/ +27,Phyllis,Becker,Oneal and Sons,East Andre,Bouvet Island (Bouvetoya),darrylshort@bright-tucker.com,7/2/2020,https://www.farrell.com/ +28,Ebony,Murphy,Barry-Martinez,Atkinsfurt,Vanuatu,vpowers@moyer.com,2/17/2020,http://www.dorsey.com/ +29,Tyler,Stevenson,Burns and Sons,North Joannashire,Sri Lanka,mmayo@gilbert.com,3/2/2022,http://www.fry.org/ +30,Cesar,Bernard,Potter-Ho,Mccormickville,Iraq,damon31@grant-morrison.com,9/4/2021,https://bryan-walters.com/ +31,Darrell,Santos,Boone-Dawson,Lake Dwayne,Liechtenstein,hdaniels@mason.com,11/4/2021,http://hammond.com/ +32,Amanda,Santiago,Roberts-Heath,Benjaminchester,United Arab Emirates,emmarasmussen@roman-walter.biz,5/6/2021,http://gillespie.com/ +33,Marcus,Mcdonald,Hays-Howell,East Brad,Azerbaijan,cesar71@vang-wagner.com,6/25/2020,https://www.williams-mclaughlin.org/ +34,Lauren,Montes,Cohen-Copeland,Tonyville,Armenia,hodgebrenda@roach-winters.com,5/6/2021,https://www.garcia.com/ +35,Brent,Hinton,"Petersen, Blackburn and Meyers",Tommyland,Mexico,greg77@patel.biz,12/3/2021,http://aguirre.biz/ +36,Jill,Mayo,"Woodard, Haas and Mason",Port Carlside,Cuba,brandypowers@christensen.com,10/4/2020,http://briggs-johnston.com/ +37,Herbert,Byrd,Sheppard-Dougherty,Kimmouth,South Georgia and the South Sandwich Islands,gilloscar@webb.com,2/9/2020,https://www.rowland-lyons.com/ +38,Don,Krueger,"Cortez, Hester and Villegas",Melendezland,Guinea,trevor14@harvey.com,3/2/2020,http://www.combs.com/ +39,Cheryl,Gonzales,Walton-Drake,Pittmanmouth,Kenya,yvette30@haas-oneill.org,2/26/2020,http://www.tran-juarez.net/ +40,Rickey,Mays,"Escobar, Carrillo and Sloan",Hollandshire,United States of America,cmcdowell@riley-wolf.org,1/1/2020,http://www.nolan.com/ +41,Cassidy,Dillon,Coleman LLC,New Ebony,Liechtenstein,patrick43@stout.com,12/19/2021,https://www.meyer.com/ +42,Christina,Bautista,Lane Ltd,Lake Don,Turks and Caicos Islands,phenry@tate.biz,11/1/2020,https://warner.com/ +43,Alexandra,Castro,"Wall, Clay and Mcintosh",South Lynnton,Swaziland,swalters@harvey.biz,4/11/2021,http://www.vasquez-boyer.biz/ +44,Krystal,Mendoza,"Logan, Boyle and Villegas",West Henry,Panama,mjackson@david.com,9/9/2020,http://www.marks-ray.info/ +45,Ivan,Schroeder,"Peck, Nicholson and Knox",Port Grace,Saint Pierre and Miquelon,qgeorge@singh.com,8/16/2020,https://melton-alexander.biz/ +46,Stephanie,Bradshaw,Tanner LLC,East Paulaville,American Samoa,debbie56@baker-olsen.com,3/5/2022,http://jimenez.biz/ +47,Levi,Grimes,"Carpenter, Chang and Bass",Frederickfurt,Heard Island and McDonald Islands,robertmarks@willis.com,6/12/2021,https://cherry.com/ +48,Peter,Sosa,Hensley and Sons,Donaldton,Sao Tome and Principe,frederick02@gross.com,7/17/2021,http://www.chandler.info/ +49,Valerie,Haney,"Delgado, Rubio and Rose",Harryview,Cuba,yowens@erickson-charles.com,3/22/2020,https://www.fowler-alvarado.biz/ +50,Tom,Gardner,Werner-Bean,New Samuel,Barbados,darin81@callahan.com,1/4/2022,http://yang-everett.com/ +51,Randall,Galloway,Brady Inc,Brightburgh,Isle of Man,stuart07@reid.com,12/18/2020,https://hatfield-huff.biz/ +52,Perry,Whitaker,Odom LLC,North Jocelynberg,Western Sahara,jennynorton@randall.org,4/27/2021,http://www.hall.com/ +53,Gloria,Mosley,Calderon Ltd,East Reneefurt,Fiji,escobardeanna@sawyer-obrien.info,5/6/2022,https://www.huber.info/ +54,Cameron,Little,Howell Group,North Angel,Netherlands,stephen57@sellers.com,11/13/2020,https://collins.org/ +55,Glen,Gonzalez,Zamora-Ellis,Lake Isabelberg,Sudan,jschmidt@gardner-maldonado.com,7/21/2020,http://holt-mendez.info/ +56,Melvin,Day,"Alvarez, Gaines and Sweeney",Jackbury,Reunion,sheila46@ewing.org,12/22/2021,http://becker-warner.net/ +57,Kent,Salinas,"Shelton, Robinson and Smith",Nicolasfurt,Guatemala,dustindelgado@west-khan.com,2/21/2020,https://www.mora-tapia.info/ +58,Stacey,Martinez,"Rasmussen, Bauer and Lyons",Mauriceview,Uzbekistan,mikayla38@lawson-dougherty.com,3/22/2020,https://newman-townsend.com/ +59,Jennifer,Fleming,Schaefer-Chambers,Shepherdfort,Barbados,stoutalexandria@meza.com,11/24/2020,http://marsh.com/ +60,Teresa,Oconnell,"Mayo, Buchanan and Owen",Lake Douglas,Turkmenistan,hhahn@cantrell.net,12/4/2021,https://www.ellison-strickland.org/ +61,Bruce,Bass,"Day, Wiley and Mclaughlin",Juarezbury,China,gvang@woods.info,8/10/2020,http://www.goodwin.com/ +62,Sarah,Sweeney,Madden-Ho,Mejiamouth,Equatorial Guinea,lauren93@daniel-farley.com,7/17/2020,http://www.welch.com/ +63,Eddie,Salinas,Howard Group,North Frederick,Cook Islands,franklinweiss@porter.net,11/25/2020,https://www.joyce.com/ +64,Trevor,Rowland,"Ritter, Fox and English",Deanchester,Nigeria,simpsonraven@liu.com,3/4/2022,http://noble-beard.com/ +65,Marcus,Chang,"Maynard, Lambert and Blake",North Monica,Western Sahara,deborah61@wagner.com,5/20/2021,http://shaw.com/ +66,Sabrina,Roberts,Stewart-Diaz,Port Pennyton,Bhutan,brittneypotter@boyd-compton.com,1/10/2021,https://walter.com/ +67,Norman,French,Becker-Mata,South Emma,Marshall Islands,spearsfrank@mclean.com,3/27/2021,https://mccullough.info/ +68,Lonnie,Novak,Hayden Inc,East Malloryville,French Guiana,manuel48@raymond.net,3/22/2021,http://www.knight.com/ +69,Casey,Bauer,Houston-Woodard,New Brettfurt,Reunion,marilynbender@daugherty.net,4/4/2020,https://crawford.org/ +70,David,Goodman,"Macdonald, Byrd and Williams",Juliantown,Nigeria,ylutz@sawyer.com,7/15/2020,https://rhodes-ellison.org/ +71,Garrett,Rosario,Becker-Terrell,Marissaland,Philippines,christiegeorge@dominguez.com,5/8/2021,https://www.wright.com/ +72,Colin,Vaughan,"Mooney, Reed and Ingram",New Shelleyfort,United States Virgin Islands,wilsonyvonne@mcmillan.com,10/8/2020,https://choi.com/ +73,Maxwell,Griffin,Mcdowell-Adkins,North Kentland,Turkmenistan,priscillaharrell@glass.com,10/1/2020,http://ray.com/ +74,Diamond,Barnett,"Walker, Andersen and Reeves",New Patriciamouth,Trinidad and Tobago,gibbsemily@fisher.biz,1/4/2022,https://mcdowell-compton.biz/ +75,Kellie,Munoz,Flynn-Chapman,New Samantha,Greenland,lynnbooth@leach-lang.com,7/8/2021,http://www.mclaughlin.com/ +76,Sandy,Finley,Shah-Hanna,Glasshaven,Kiribati,hmora@brock.com,1/4/2021,https://www.estrada.biz/ +77,Katelyn,Petersen,Solis-Hardin,South Patricia,Saint Vincent and the Grenadines,santosebony@foley.com,7/4/2021,http://carr-holder.com/ +78,Neil,Murray,Washington-Ramirez,East Stephanie,Afghanistan,englishstefanie@braun.com,3/20/2022,http://ritter.com/ +79,Carlos,Wilcox,"Vega, Yoder and Ayala",East Katelynmouth,Slovenia,toddlove@rogers.info,10/11/2021,https://little.info/ +80,Adrienne,Lamb,"Henderson, Vega and Jensen",South Karl,Gambia,butlerroberta@mullen-pittman.net,5/3/2021,http://www.mann.com/ +81,Traci,Levy,"Simon, Flores and Carr",New Erica,Kiribati,camposherbert@lang.com,2/19/2022,http://www.burke-glover.com/ +82,Tammy,Harmon,Kidd-Stone,Chaneychester,Yemen,abigail05@mckee.info,9/1/2020,https://www.walsh-archer.com/ +83,Nicholas,Arias,Yoder-Bowen,Lake Gavinburgh,Holy See (Vatican City State),boonealex@cardenas.info,1/31/2020,http://www.wood.com/ +84,Sydney,Solis,"Wu, Strong and Flynn",Welchburgh,Suriname,ortegashane@li.com,2/22/2020,https://anderson-suarez.org/ +85,Jody,Beltran,Buchanan-Barton,Kristihaven,Heard Island and McDonald Islands,fritzandres@morales.biz,8/14/2020,https://www.martin.biz/ +86,Autumn,Choi,Bates LLC,Port Scott,Uganda,varmstrong@braun.org,12/7/2020,http://www.fritz-galloway.com/ +87,Chelsey,Boyer,"Goodman, Carrillo and Stein",Selenaville,Morocco,jonathon78@werner.com,4/17/2022,https://www.willis-todd.net/ +88,Trevor,Key,"Escobar, Adams and Huber",New Ryan,Palestinian Territory,bunderwood@owen.com,1/3/2022,http://webb-barnes.info/ +89,Bridget,Molina,Greene-Mays,East Brookebury,Chad,jcochran@burgess-costa.com,2/13/2021,https://grimes.net/ +90,Calvin,Rocha,Werner-Key,Wayneborough,Cameroon,riveraisabel@harmon.net,11/28/2020,https://salinas-peck.com/ +91,Austin,Matthews,"Sandoval, Parker and Mcdowell",Aimeeville,Macedonia,stanleymeagan@gilmore-newton.com,7/30/2021,https://hubbard.com/ +92,Molly,Murphy,Mercado PLC,Ericamouth,British Virgin Islands,kathyhuff@white-liu.com,12/1/2021,http://cooley.com/ +93,Jeremy,Haynes,"Cruz, Roach and Lynch",Breannaton,Central African Republic,lawsonmicheal@atkins.com,12/12/2020,http://www.gibbs.com/ +94,Don,Henry,"Giles, Kerr and Stafford",Marcusburgh,China,diamondhinton@mccormick.biz,10/18/2020,https://arnold.com/ +95,Dakota,Bowman,Gomez-Tapia,West Barbarafurt,China,branchmegan@dougherty.com,4/17/2022,https://deleon-avery.com/ +96,Manuel,Maynard,"Ellison, Berger and Osborn",Lake Calvin,Niger,xingram@le.com,2/28/2020,https://richards-jarvis.com/ +97,Howard,Simmons,Winters-Cohen,Mitchellmouth,Romania,ronnie03@bird-hood.com,8/21/2020,https://cordova.com/ +98,Jeffery,Wall,Santos-Barnett,Jonathonhaven,Cambodia,wanda50@webster.com,2/18/2022,http://www.dickerson.com/ +99,Colleen,Estes,"Garrett, Sharp and Kaufman",Lake Alisonside,Greece,tricia22@barrera.com,4/6/2020,http://www.richmond.com/ +100,Bianca,Henry,Harrell-Johns,Garrettstad,Albania,alan83@olson.com,4/13/2020,http://www.wilcox-burns.biz/ +101,Michelle,Good,"Randall, Harding and Powers",New Johnnychester,Korea,yeseniacallahan@hinton.com,12/21/2020,http://chavez-clay.com/ +102,Eileen,Skinner,Yang Ltd,New Kristophershire,Mexico,julian74@ritter-mccoy.info,3/27/2021,https://mays.com/ +103,Kyle,Richmond,Hart Group,New Arielfurt,Dominican Republic,adriennecarson@gallagher-mckinney.org,9/23/2020,http://mcclure.com/ +104,Omar,Davies,"Rich, Oneill and Daniels",Dicksonburgh,Guinea,hreid@jordan-pena.com,9/2/2021,http://www.best.biz/ +105,Chelsea,Giles,Curtis Inc,Evanfort,Kuwait,sharon31@stanley.info,11/26/2021,https://griffin-willis.com/ +106,Pam,Crane,Patton-English,East Taylorborough,Cameroon,luisreynolds@caldwell.com,10/27/2020,http://rich.info/ +107,Sandy,Kaufman,Gillespie-Kemp,East Taylorville,Antigua and Barbuda,carlaburton@tran-wu.info,6/11/2020,http://www.schaefer.org/ +108,Madison,Clark,"Atkinson, Benitez and Tapia",Cristianmouth,Nigeria,ikeith@richards.com,5/19/2022,http://www.mack.info/ +109,Leah,Coffey,Rasmussen-Frederick,Lake Darrell,Slovenia,felicia85@joseph.com,1/20/2020,http://terrell.net/ +110,Julie,Montgomery,"Brady, Le and Sherman",New Mathew,Northern Mariana Islands,jose39@huber.com,12/13/2021,http://whitehead.biz/ +111,Darrell,Small,Nicholson LLC,Lake Warrenmouth,Reunion,boyerjoy@gross-meadows.info,3/19/2022,http://www.sherman.com/ +112,Andrew,Bolton,Sutton-David,East Nathanmouth,United Arab Emirates,meghanharmon@travis.biz,4/27/2020,http://olson-collier.com/ +113,Jaime,Hayden,Higgins Ltd,Mullenmouth,Iran,mrowe@forbes-holmes.org,12/10/2021,http://randolph-stewart.info/ +114,Logan,Carney,Jensen-Crawford,Omarport,Gibraltar,rebecca89@marquez.net,10/31/2020,http://www.giles.com/ +115,Pedro,Franco,"Acevedo, Blanchard and Deleon",West Sharonville,Oman,hunterjenkins@cervantes.com,7/14/2021,https://bernard.com/ +116,Daryl,Meza,Roberts-Curry,West Jennifer,Hungary,conneraaron@bryant.net,1/12/2021,https://www.price-lyons.info/ +117,Haley,Levine,Bowers-Nichols,Jacobfort,Tuvalu,hamiltongreg@perkins.com,4/19/2020,https://www.salinas-roth.biz/ +118,Caitlyn,Vazquez,"Burnett, Carter and Shah",New Bruce,Maldives,ebush@jimenez.com,8/6/2020,https://www.blair.com/ +119,Keith,Combs,"Bryant, Blankenship and Orozco",North Pattystad,Vietnam,tommy24@wong-ray.com,10/21/2020,http://hernandez.com/ +120,Hayden,Cline,"Garrison, Kelley and Choi",Julianberg,Ireland,kristinebaldwin@holloway-sharp.com,6/15/2020,http://www.maynard.biz/ +121,Jeanette,Sanford,"Sutton, Doyle and Velez",Wadeborough,Dominica,daviesmatthew@turner.com,7/21/2021,http://www.santiago.com/ +122,Brandon,Richmond,Gould Ltd,Beardfort,Pakistan,fmcgee@foster.com,1/5/2020,http://barron-terry.com/ +123,Latasha,Miller,Romero Group,East Glenfort,Aruba,wcarter@ali.info,1/5/2020,http://www.atkinson.net/ +124,Shaun,Luna,"Sparks, Garcia and Maxwell",West Emma,Uganda,mshaw@cantu-le.net,12/18/2021,http://www.pennington.com/ +125,Allen,Mayer,Giles-Mooney,West Terrenceburgh,Comoros,danielsalinas@deleon.com,5/28/2021,https://garner.com/ +126,Yvonne,Jordan,"Oneal, Barker and Kaufman",South Caseyside,Timor-Leste,mercedes83@gill.org,10/23/2020,https://www.cohen-king.org/ +127,Joanne,Miranda,Perkins LLC,Aguilarchester,Niue,fernandoshaw@goodwin.org,9/5/2020,http://www.wolf.com/ +128,Jaclyn,Rice,Madden-Lewis,New Saraberg,Spain,tamara04@tate.biz,9/2/2021,https://diaz.com/ +129,Glen,Conway,"Bullock, West and Becker",Corystad,Nigeria,don46@freeman.net,11/24/2021,https://www.nichols.info/ +130,Stacey,Travis,Medina-Castro,Dudleyfurt,Ethiopia,ygarcia@andrade.com,1/16/2021,http://schaefer.com/ +131,Courtney,Hughes,Benitez LLC,Knoxfurt,Marshall Islands,gary98@carpenter-nelson.com,11/24/2020,https://www.knight.net/ +132,Raven,Nelson,"Suarez, Hull and Key",East Kristenfort,Luxembourg,fashley@burns-mckenzie.com,10/23/2020,https://www.rodgers.net/ +133,Kyle,Odonnell,Andrews-Harmon,Wallacemouth,Gabon,ywise@winters.net,2/18/2020,http://www.cook.biz/ +134,Sherry,Ponce,Petty Ltd,Holdenfurt,Isle of Man,vjacobson@perkins-dunlap.net,1/12/2022,https://dillon.info/ +135,Kirk,Villa,"Norris, Bailey and Campbell",Ericaside,Myanmar,parkroy@baxter.com,4/27/2020,https://www.barrett.com/ +136,Luke,Lucas,Snow-Avila,Pagebury,Belgium,masonshelley@freeman.org,9/26/2021,http://hanna.com/ +137,Lynn,Tran,Ware LLC,Latoyaside,Tunisia,marisa90@huynh.com,1/5/2021,http://whitaker.biz/ +138,Brian,Beasley,Chaney-Porter,North Daryl,Burkina Faso,stephensmike@bartlett-wade.com,10/15/2021,https://esparza.com/ +139,Christopher,Savage,Armstrong-Contreras,Port Isabellachester,Iran,debbieramos@davies-washington.biz,8/3/2021,http://www.vega.com/ +140,Dominique,Mckinney,"Sharp, Fleming and Gregory",Port Erin,Kazakhstan,felicia57@fletcher.com,2/28/2020,http://bradford.com/ +141,Dwayne,Crane,Mcdaniel and Sons,North Jessicaview,Montenegro,logan04@hines.biz,2/12/2022,http://www.harmon.biz/ +142,Autumn,Cuevas,Hahn Ltd,Mccoyfort,Burundi,jeffreyharding@johnson.com,2/5/2020,http://mcdowell-henry.com/ +143,Gregory,Collins,Fleming Inc,Port Grantton,Micronesia,tina43@hayes-johnson.com,10/2/2020,http://summers-chang.com/ +144,Isaac,Schmidt,Clements-Ayala,West Jasminfort,Ukraine,paulakane@singh.com,12/7/2021,https://ochoa-chapman.org/ +145,Bradley,Rangel,Castillo and Sons,Lake Bianca,Montserrat,underwoodangel@gallagher.info,12/5/2020,https://www.summers.org/ +146,Paige,Page,"Mullins, James and Herman",Kaufmanfurt,French Guiana,aodonnell@prince.com,4/2/2022,https://suarez-sims.org/ +147,Gwendolyn,Bradshaw,"Gay, Bush and Goodman",East Jonathan,Mali,eugene43@mccall.com,5/1/2021,http://ramirez.com/ +148,Belinda,Ferguson,"Lewis, Bowman and Craig",Moralesport,Lao People's Democratic Republic,billspears@harmon.org,1/2/2020,https://huff.com/ +149,Ivan,Hines,Aguilar Ltd,Lake Samantha,Qatar,tflowers@salinas.org,11/25/2021,https://frazier.com/ +150,Brett,Lin,"Mccoy, Larsen and Stevens",Nortonmouth,Saint Helena,stokespamela@koch.com,2/2/2021,http://serrano.com/ +151,Katherine,Williams,Kelly PLC,Juarezville,Armenia,jorozco@hinton-klein.org,12/7/2020,https://www.key-zamora.com/ +152,Andre,Burgess,Zhang-Stevenson,Mooremouth,Moldova,mayerlynn@haas-santos.org,5/12/2020,https://huynh.com/ +153,Laura,Decker,"Levy, Moyer and Fernandez",West Pamela,South Georgia and the South Sandwich Islands,vstuart@fowler-novak.com,8/3/2020,http://www.peterson-hughes.net/ +154,Tommy,Herman,Espinoza-Tyler,North Robert,United States Minor Outlying Islands,spencejennifer@bowman-pena.com,12/27/2020,http://harvey.com/ +155,Amber,Lyons,Ward-Mcintosh,South Kaitlyn,Zambia,betty81@shields.org,7/24/2020,https://ali.net/ +156,Shelia,Yang,"Coffey, Watson and Wilkins",Lake Edwintown,American Samoa,cynthia09@vang.com,12/10/2021,http://arellano.com/ +157,Russell,Martin,Duffy-Zuniga,East Megan,Angola,cody74@cochran-keith.com,5/24/2020,http://hurst.net/ +158,Yvette,Willis,"Hamilton, Solis and Salazar",South Tamarafort,Kuwait,neil44@barron.com,11/13/2020,https://navarro.com/ +159,Don,Ho,Stark-Glover,Riosport,Uganda,rwallace@shepherd-mcdowell.com,2/12/2022,https://www.ellison.com/ +160,Ellen,Torres,Cook PLC,New Chelsey,Kuwait,carla57@roberts.com,8/31/2021,http://padilla.net/ +161,Hayley,Morse,"Henry, Mcdonald and Austin",Lorrainefort,Iraq,fchung@montes.com,4/18/2020,http://walton.com/ +162,Ellen,Kerr,Duncan LLC,Port Jocelyn,Micronesia,vnoble@mooney.org,1/27/2020,https://www.vega.com/ +163,Martha,Cruz,Copeland-Freeman,New Kaylashire,Bolivia,collinsalejandro@arroyo.com,2/12/2020,http://rangel-blake.org/ +164,Douglas,Carson,Ferguson Ltd,Branchview,Israel,ethanle@gibson.com,4/26/2022,http://wong-strickland.com/ +165,Toni,Cline,Robles-Davies,Ortegaside,Ecuador,kyleramos@lee.com,3/18/2020,https://www.glover.com/ +166,Robyn,Berger,Suarez Group,South Karen,Lesotho,amatthews@owens.com,4/28/2020,http://www.brewer.com/ +167,Calvin,Roach,"Wilkins, Goodman and Cummings",North Melvin,Wallis and Futuna,kathleenbrewer@sweeney.com,1/4/2022,https://www.ward.com/ +168,Angel,Park,"Barry, Thomas and Oconnor",South Marciafurt,Morocco,masonadriana@price.com,3/29/2020,https://yang-roach.com/ +169,Bradley,Delacruz,Mathis-Rocha,Ortegaland,Cote d'Ivoire,khendrix@powers-levine.biz,5/26/2020,https://whitaker.com/ +170,Donald,Cross,"Donovan, Key and Leblanc",Bruceville,Saint Helena,srodriguez@hester.info,1/25/2020,http://www.grant-campos.net/ +171,Jeremiah,Guerrero,"Cole, Franco and Alvarez",New Erikville,French Polynesia,mosskevin@perkins.com,5/11/2022,http://montes.com/ +172,Perry,Trujillo,"Yoder, Watkins and Singh",Lake Joyton,Panama,geoffrey16@gentry.com,11/13/2021,https://www.webster.com/ +173,Brent,Beasley,Mendoza Group,South Kimberly,Antigua and Barbuda,bettymckinney@houston.com,4/14/2022,http://edwards-nguyen.net/ +174,Ariana,Velasquez,Stokes-Haney,South Codyfurt,Ireland,makayla22@carey-james.com,8/2/2020,https://www.orozco-santiago.com/ +175,Don,Vincent,Norton-Watkins,Sanfordmouth,Luxembourg,jarvislarry@lang.info,3/22/2020,https://www.mahoney.org/ +176,Bradley,Blair,"Mullins, Huber and Dillon",Franciscochester,Montenegro,masseyriley@blanchard.com,11/1/2021,https://welch.com/ +177,Henry,Conway,"Clarke, Fleming and Porter",Fordton,Saint Kitts and Nevis,qrusso@le.com,7/7/2020,https://www.hansen.info/ +178,Norman,Waters,Welch-Romero,West Stacey,Rwanda,meganwilkinson@bird-anderson.com,10/14/2020,http://www.schultz-zamora.com/ +179,Breanna,Miranda,Mejia Group,Colinberg,India,claytoncastillo@schroeder.org,4/28/2021,https://larson.com/ +180,Seth,Osborne,Rollins-Carson,East Kaylee,Holy See (Vatican City State),xcoleman@farrell-bernard.com,1/1/2022,http://www.bradford-rivas.com/ +181,Lydia,Ponce,Fitzgerald Inc,New Eduardo,Mongolia,lauramorris@anthony-bullock.com,5/24/2022,http://hahn.com/ +182,Sherri,Bradshaw,"Ramos, Suarez and Jacobs",Dariusview,Kuwait,mossangela@farmer.com,4/5/2020,https://www.may.com/ +183,Alejandra,Lowe,Benjamin Group,East Petermouth,Western Sahara,andre80@dodson.net,9/27/2020,http://wilcox-liu.com/ +184,Raymond,Bernard,Odom-Hull,Lake Karlaburgh,Burkina Faso,adam45@tanner.com,3/6/2020,http://www.collier.net/ +185,Patricia,Moss,Acevedo-Rasmussen,North Stacey,Belarus,pfleming@french.com,10/16/2021,https://www.underwood.com/ +186,Hector,Meyers,Hanna-Ortiz,New Mario,Tanzania,bowersjessica@arellano-hart.com,11/19/2021,https://massey.info/ +187,Pedro,Zhang,Conway-Stewart,Jacobmouth,Kazakhstan,barronsergio@valencia-proctor.com,7/11/2021,https://www.barajas.com/ +188,Marco,Donaldson,Wagner and Sons,West Aprilberg,Canada,holmesdwayne@sheppard.biz,7/22/2021,https://alvarado.info/ +189,Shannon,Yoder,Whitney Ltd,Jofort,Nepal,laura10@romero.com,7/17/2020,https://www.lang-ellison.org/ +190,Brian,Downs,Ramirez-Thompson,New Anitafurt,Kazakhstan,dcombs@mcneil.org,8/3/2020,http://www.rangel.com/ +191,Dillon,Cooley,Luna and Sons,Port Micheleville,Ireland,cody25@watkins.com,6/28/2020,http://bullock.com/ +192,Joyce,Chaney,"Stanton, Lane and Schmitt",North Francis,Luxembourg,joannarusso@nelson.com,1/26/2020,https://www.chapman-short.biz/ +193,Angelica,Schaefer,"Jackson, Gibbs and Parker",Lake Sydney,Sudan,mark43@stevenson-garcia.com,1/18/2021,https://whitehead-payne.com/ +194,Marcia,Horton,"Carter, Ford and Matthews",West Kendra,Kiribati,mcdowelljenna@beck-lewis.com,5/17/2022,http://arnold-morse.com/ +195,Sydney,Barr,Weiss LLC,East Courtneyview,Micronesia,carlosstewart@deleon-griffith.com,2/5/2022,https://hurley-blankenship.net/ +196,Jay,Hodge,Wolf Ltd,South Rebekah,Guernsey,fvillarreal@brewer-pena.com,2/6/2021,https://www.carlson.com/ +197,Angela,Jackson,"Reynolds, Patel and Rush",Reynoldsborough,Congo,arthurpetersen@bolton.info,2/6/2022,http://perry.net/ +198,Bethany,Barrera,"Swanson, Figueroa and Heath",Vickietown,South Georgia and the South Sandwich Islands,rhonda48@castro.info,5/29/2022,http://www.cortez.com/ +199,Cindy,Valenzuela,Rojas LLC,Maychester,Chile,maryforbes@oliver-mills.com,4/13/2020,http://www.holmes-wolfe.info/ +200,Christine,Camacho,Pace and Sons,South Daveville,Yemen,aguirrenatalie@randolph-moore.com,2/26/2021,https://www.nolan.com/ +201,Tyrone,Hendrix,"Small, Osborne and Rojas",East Wayne,Vanuatu,javierbarron@mcclure.com,2/15/2020,http://www.collins.com/ +202,Roy,Gould,Beasley Ltd,Jackberg,Montserrat,fmoore@vega.com,4/26/2020,http://www.livingston-stanton.net/ +203,Matthew,Mann,"Benton, Flowers and Snow",Aguirrebury,Papua New Guinea,summer05@harrison-bowen.info,1/6/2021,http://www.swanson.com/ +204,Taylor,Torres,"Gamble, Cooke and Lewis",Port Roberto,British Virgin Islands,ihuerta@lutz.info,12/29/2021,http://medina-williamson.com/ +205,Hannah,Waller,Glenn and Sons,Fritzbury,Turkmenistan,gouldruth@novak-dunlap.com,4/23/2022,http://www.cunningham.com/ +206,Randy,Cannon,Le PLC,South Nancy,French Southern Territories,pcampos@lloyd-leblanc.info,1/21/2022,https://www.henry.com/ +207,Adrienne,Hunter,Escobar-Cannon,Lake Glennside,Kazakhstan,lindsay75@levy-valentine.com,4/2/2022,http://www.wiggins-cuevas.net/ +208,Raven,Weaver,Bray PLC,Adamsfurt,Gibraltar,hutchinsonmartin@shelton-burnett.com,5/17/2021,http://duncan.com/ +209,Isaac,Murillo,Conrad Inc,Port Ryan,Romania,schmittstephen@elliott.biz,5/21/2020,https://www.burgess.biz/ +210,Kelly,Branch,Barton Group,Port Beckymouth,Montserrat,alexandranguyen@carr-gray.net,6/4/2021,https://www.orr-meyers.com/ +211,Chelsey,Larson,Walls Inc,New Yolanda,South Africa,katrinarasmussen@craig.com,5/9/2020,http://www.jimenez.com/ +212,Rick,Marquez,Woods-Warner,New Taylorburgh,Bahrain,josephflynn@rivers.com,9/8/2020,http://foley-porter.biz/ +213,Tricia,Mckee,Macdonald Ltd,Port Kimberly,Turkmenistan,gabriela43@wilson-stanton.biz,5/25/2020,http://www.pham-burton.com/ +214,Rebecca,Blake,Young-Sawyer,East Casey,Somalia,brownvalerie@wade.net,10/11/2021,https://chapman.com/ +215,Dan,Mcmillan,Wise-Mckay,South Jeremy,Uzbekistan,martin28@gibbs-whitney.biz,7/25/2020,http://shea-proctor.org/ +216,Sabrina,Hill,Park-Kaufman,Daveborough,Ireland,omiles@mcdowell.com,3/26/2020,http://koch.org/ +217,Victoria,Walter,"Kline, Cobb and Gregory",Francisside,Malaysia,melanie66@townsend.com,10/7/2020,https://fields.org/ +218,Jorge,Hendrix,"Delacruz, James and Calhoun",Jerrychester,South Africa,cbates@walker.com,6/17/2021,https://hartman-hayes.com/ +219,Jodi,Fox,"Walton, Davenport and Charles",Wallacestad,Eritrea,ashley25@lutz-arellano.biz,6/16/2020,https://estrada.org/ +220,Sara,Vargas,Maynard LLC,Patelhaven,Senegal,erikdalton@hines.org,6/17/2021,https://wilkins-riggs.com/ +221,Cynthia,Johns,Burns-Jimenez,South Shelley,Egypt,sellersmarvin@henry.com,11/20/2020,https://braun.com/ +222,Jordan,Hanna,"Nguyen, Ruiz and Finley",Santosport,Heard Island and McDonald Islands,davidchoi@kim.net,5/19/2021,https://case-hester.biz/ +223,Maurice,Ramsey,"Everett, Humphrey and Zhang",Lake Dillonfort,Antarctica (the territory South of 60 deg S),zturner@jimenez-wells.biz,7/27/2021,https://duke-church.biz/ +224,Catherine,Nicholson,Mckee PLC,North Tracey,Netherlands,bdelgado@henry.com,7/7/2020,https://www.peters-goodwin.com/ +225,Sean,Todd,Rice-Wilkins,New Reginahaven,Bangladesh,uwarner@meza-carrillo.com,3/13/2020,https://www.dougherty.com/ +226,Lacey,Bond,Dougherty-Day,Grantshire,Bangladesh,glenncook@king-garner.com,12/11/2020,https://flynn-frederick.biz/ +227,Katelyn,Wise,"Daugherty, Cooley and Joseph",New Alexa,Switzerland,ndougherty@bentley-lutz.com,12/26/2020,http://www.montes.org/ +228,Christine,Bowers,Davenport-Neal,Jacobmouth,Jamaica,jmorrow@campbell.info,10/3/2020,http://www.faulkner-nelson.com/ +229,Adam,Levy,Conrad Group,Bushview,Nepal,pcuevas@hancock.org,5/15/2020,http://osborn.com/ +230,Hayley,Ellison,Tyler Inc,South Howard,Lao People's Democratic Republic,ameza@cobb-poole.com,2/11/2020,http://cantrell.biz/ +231,Vernon,Warner,Monroe-Mccoy,Zoefurt,Japan,tzimmerman@moore.com,9/8/2021,http://holt.org/ +232,Fernando,Townsend,Wyatt-Henry,Lake Joshuastad,Turkey,kelly08@miller.net,4/12/2020,https://www.west.com/ +233,Walter,Parsons,Owen-Warren,East Alyssa,Mauritania,pboyer@lambert.com,12/24/2021,http://robinson.info/ +234,Brady,Hill,"Hull, Knight and Kerr",Montoyaberg,Saint Kitts and Nevis,yhaley@beasley-boyle.com,9/26/2021,http://pace-cowan.com/ +235,Loretta,Rice,Jimenez-Medina,Port Danburgh,Moldova,poncejackie@mooney-allison.com,11/16/2020,http://silva-shah.com/ +236,Hannah,Beck,"Gay, Ward and Villegas",Port Carlos,Mauritania,reyespaula@velazquez-gillespie.com,7/12/2021,http://ramsey.info/ +237,Jocelyn,Stephens,Macias-Burns,Kelleyview,Algeria,velazquezchloe@fitzpatrick-byrd.com,8/8/2021,http://www.koch-parks.com/ +238,Benjamin,Chan,"Huerta, Potts and Crosby",East Emma,Finland,kari54@short.com,9/21/2020,http://www.li-berry.com/ +239,Caroline,Clarke,Reed-Tucker,North Sydney,San Marino,amckenzie@leonard-newman.com,4/19/2020,https://alvarado.com/ +240,Cameron,Forbes,Cervantes-Hendrix,Rhodesside,Rwanda,marie76@molina.org,9/11/2021,http://carson.com/ +241,Wyatt,Mclean,"Flowers, Kline and Bass",Spencerchester,Dominican Republic,ckrueger@cervantes.com,1/19/2020,https://barnes.com/ +242,Kendra,Waters,Gates Inc,Warnerport,Nicaragua,tracey11@carney.com,1/2/2020,http://www.ayala.com/ +243,Tom,Bradley,Stone Ltd,Lonnieburgh,Trinidad and Tobago,terriblack@huffman-burnett.com,10/22/2020,https://gordon-chen.com/ +244,Adrian,Frazier,Franco Group,Seanport,Ecuador,ballfernando@miranda.com,4/13/2022,http://www.fleming.org/ +245,Beverly,Kirby,"Ruiz, Chase and Villa",Elijahchester,Bangladesh,valentinecarmen@michael.com,4/1/2022,https://www.mays-blevins.info/ +246,Priscilla,Stuart,Peck-Werner,East Max,Liechtenstein,jessehernandez@holder.org,4/16/2021,https://cantrell.net/ +247,Roberto,Hogan,Massey-Hoffman,East Javierfort,Aruba,christiangriffith@newman.com,5/27/2020,http://ferguson.net/ +248,Victor,Rogers,Walls-Randall,Gabriellafort,Kiribati,tannerbrandi@duarte.biz,4/11/2022,http://suarez.com/ +249,Alisha,Gallegos,Montoya-Mccarthy,Lake Christianton,Singapore,bernard01@hammond-delacruz.net,3/26/2022,https://www.hale.com/ +250,Stefanie,Fuller,Keith-Wyatt,Mcleanshire,Ukraine,bonnievilla@briggs.com,10/19/2021,https://www.nicholson-zavala.org/ +251,Jackson,Grimes,Chan-Mcknight,Gayfurt,Kuwait,fullercarly@wells.biz,9/26/2020,http://www.hunter.com/ +252,Miranda,Robles,Maynard-Ramos,Lake Kevin,Andorra,sgarza@thompson.com,10/16/2020,https://www.holder.com/ +253,Gilbert,Bowers,"Russell, Ashley and French",Wyattborough,Bahamas,sfields@sexton-archer.com,4/13/2022,http://nelson.com/ +254,Jon,Gay,"Frey, Howard and Burns",Lake Mike,Kenya,hmelendez@jenkins-ingram.org,2/23/2022,https://www.garza.com/ +255,Julia,Davila,Montoya Group,Port Caleb,Aruba,ericafrederick@beasley.org,2/19/2022,http://rush.com/ +256,Aaron,Potts,Berg-Cannon,Kathrynville,Bulgaria,kathryn22@patel-gross.com,5/21/2021,http://www.burns-kane.com/ +257,Rachael,Jimenez,"Burnett, Vang and Delgado",Alextown,Bermuda,romerotricia@chung.org,4/19/2022,http://yang.info/ +258,Lucas,Macdonald,Navarro-Patterson,West Baileystad,Benin,masseykathryn@sawyer.com,1/22/2020,https://www.morton-monroe.com/ +259,Kristopher,Sanders,Lamb-Oconnell,North Judyville,Comoros,eperry@yates.com,1/1/2021,https://www.warner.info/ +260,Alexandria,Hutchinson,Fry Ltd,East Jake,Azerbaijan,coffeymichele@hawkins-morrison.org,4/3/2020,http://www.collins.biz/ +261,Natasha,Schmitt,Russo PLC,New Tammy,Iceland,kelliewaters@fox.com,2/28/2020,http://alvarado.biz/ +262,Jose,Acosta,"Schmitt, Wyatt and Rice",East Gailhaven,Saint Lucia,brooke89@carson.biz,8/25/2020,http://www.wilson.com/ +263,Cassidy,Dominguez,Dixon-Winters,South Annetteville,Bouvet Island (Bouvetoya),omcfarland@lin-atkinson.biz,4/6/2022,http://lutz.biz/ +264,Debbie,Savage,Carey Inc,Port Franklin,Cape Verde,vsingleton@huynh.info,4/2/2021,http://fleming.com/ +265,Felicia,Burnett,Ortega and Sons,Lake Joyce,Saint Helena,stevensonbeverly@huerta.com,12/7/2020,http://case.info/ +266,Hayley,Gutierrez,Bridges-Keller,Alexandriamouth,Turkey,janice91@stanley.com,3/26/2020,http://www.ortiz-mcmillan.com/ +267,Melinda,Parrish,Carpenter PLC,East Wendy,Cook Islands,burchlee@atkins.info,5/5/2021,http://www.hendrix.net/ +268,Jeremy,Keith,"Petersen, Rivas and Mayo",Bethanystad,Morocco,udurham@singleton-lewis.com,10/3/2021,https://compton.com/ +269,James,Washington,"Fuentes, Park and Poole",East Darin,Tokelau,jacksondana@baird.com,5/15/2021,https://haley-stevens.biz/ +270,Karen,Leblanc,Farley Inc,Deanberg,Denmark,poneill@cameron.com,12/10/2021,https://lozano.org/ +271,Ivan,Malone,Coffey Ltd,South Soniabury,Moldova,seanhiggins@whitney.biz,5/17/2020,http://www.valentine.info/ +272,Jesus,Cox,Rush-Melton,Deniseburgh,Tokelau,aaronmorse@shepard.org,10/18/2021,https://www.morrison-randall.info/ +273,Michelle,Lowery,"Warren, Randall and Durham",Parksfurt,Australia,hthompson@fritz-sparks.com,10/3/2020,https://www.carter.info/ +274,Paul,Meyers,"Pham, Cabrera and Long",Port Mercedesberg,Panama,smckay@escobar.com,2/13/2022,http://robertson-gray.com/ +275,Eileen,Graves,Maxwell-Murillo,Antonioside,Saint Pierre and Miquelon,ddean@gray.com,4/25/2020,https://carlson-murillo.org/ +276,Christian,Jennings,"Lawrence, Mooney and Washington",West Kathryn,Iceland,brett70@juarez-christian.com,6/29/2021,https://www.turner.org/ +277,Don,Hendrix,"Blevins, David and Henderson",Wallaceville,Belgium,zcopeland@arias.com,2/17/2020,http://www.eaton-mitchell.com/ +278,Dominique,Summers,"Estes, Durham and Burgess",Rickyside,Maldives,traceyreynolds@wilson.com,5/21/2022,https://stuart-marsh.com/ +279,Wayne,Anderson,Manning-Pruitt,Haroldhaven,Samoa,sheila72@pollard.biz,5/22/2020,http://knox-owens.info/ +280,Chloe,Franklin,"Mclean, Robles and Orr",South Craig,Comoros,suarezbarbara@cross-baird.info,1/15/2022,http://www.christensen.com/ +281,Carolyn,Hendrix,Jimenez Inc,West Ricky,Anguilla,huntercathy@henry.com,6/21/2021,http://meyers.info/ +282,Isaiah,Buckley,Davies-Hardy,Youngfurt,China,haaszachary@holmes.com,1/19/2021,https://www.lynn.com/ +283,Gina,Townsend,Jacobs Inc,Tommyshire,Solomon Islands,claudia60@farrell-rivas.net,3/19/2020,http://www.daniels.com/ +284,Stefanie,Grant,"Rojas, Gamble and Jensen",New Derrick,Burkina Faso,franciscogomez@klein.com,1/11/2020,http://www.ibarra.com/ +285,Cole,Combs,Rocha-Dawson,Port Gregoryfurt,Guinea-Bissau,sdunn@newton.com,9/21/2020,http://www.curtis.com/ +286,Suzanne,Pope,Haley-Coleman,East Renee,Slovenia,melinda13@andrade-acosta.com,11/1/2020,https://www.carney-rangel.com/ +287,Gabriel,Green,Branch-Barry,Toddside,Central African Republic,qballard@fitzpatrick.com,7/5/2020,https://www.whitehead.net/ +288,Jeanne,Atkinson,"Swanson, Landry and Jackson",Alexfort,Guadeloupe,holderloretta@phillips-hays.com,4/18/2020,http://hughes.org/ +289,Lindsay,Ferguson,Randall-Franco,North Karihaven,Montserrat,marquezhaley@oliver-figueroa.info,12/22/2021,http://ramirez-harding.com/ +290,Ronald,Byrd,"Ramos, Hoover and Saunders",West Dan,Tonga,danielle48@pollard.com,7/11/2020,http://www.sims-barber.info/ +291,Marco,Holden,"Fleming, Parrish and Andersen",Port Beth,Palau,maureen60@chapman-duran.com,9/19/2020,https://hancock.org/ +292,Isabel,English,Leblanc PLC,Anthonyport,Monaco,epatel@ochoa-hoffman.com,7/9/2021,https://www.clark.org/ +293,Nichole,Green,Chambers LLC,New Thomasbury,Tokelau,jpacheco@lynn.org,7/11/2020,https://www.krause.info/ +294,Joann,Gonzales,"Bray, May and Riggs",North Tracieview,Eritrea,earlhawkins@fernandez.com,5/18/2020,http://www.peck-parker.biz/ +295,Francis,Miller,Harrell-Pacheco,Lake Yvetteberg,Dominica,kristidaugherty@burch.info,1/11/2022,https://www.owens-leblanc.com/ +296,Stuart,Valdez,"Dickson, Wilcox and Hatfield",East Glenn,Bhutan,michelleodom@mcconnell.com,3/1/2022,http://www.pollard.com/ +297,Tricia,Berg,Le-Banks,Port Gavin,Iceland,alimelissa@montes-poole.com,11/2/2020,http://www.fitzgerald.net/ +298,Roger,Bauer,Braun-Morton,South Herbertmouth,Kyrgyz Republic,joanne05@barron-perkins.org,5/1/2021,http://www.stevens-clarke.biz/ +299,Tanner,Hernandez,Mooney Inc,Adamston,Bouvet Island (Bouvetoya),sheri28@pollard-drake.org,2/23/2020,http://www.garcia-hernandez.com/ +300,Ellen,Cisneros,Larsen Ltd,Popeland,Belize,whughes@daniel.com,9/14/2020,https://www.carlson-ochoa.com/ +301,Latasha,Hancock,Massey-Myers,West Leroyview,Bangladesh,colleen53@taylor.com,5/16/2021,https://www.soto.com/ +302,Jenna,Lamb,Mosley LLC,Toddborough,Ecuador,frenchcaroline@chavez.com,11/27/2021,http://melendez.com/ +303,Francis,Ball,"Schaefer, Bowers and Li",Port Michaelafort,Equatorial Guinea,robinlove@mckinney.com,2/5/2021,https://www.henson-arellano.net/ +304,Jade,Archer,"Fischer, Vang and Skinner",Berrymouth,Romania,afitzpatrick@bartlett.org,9/2/2020,https://www.schneider.info/ +305,Zachary,Parker,Ewing and Sons,Port Glennborough,Burkina Faso,willie12@norris-pennington.com,12/9/2021,https://yoder.com/ +306,Karen,Kent,Dickerson Inc,Hofurt,Luxembourg,vfletcher@pineda.com,3/3/2021,https://www.willis-hendrix.biz/ +307,Joshua,Dalton,Cross PLC,Lake Troyville,Nepal,msellers@fisher.net,2/24/2021,http://stone.com/ +308,Kiara,Ashley,Watson-Delgado,Francohaven,Nigeria,norma73@martin.com,10/26/2021,https://www.humphrey-floyd.info/ +309,Doris,Greene,"Jacobs, Velazquez and Estrada",Lake Omar,Libyan Arab Jamahiriya,pjones@prince-blackburn.com,10/6/2021,https://freeman.org/ +310,Helen,Kaiser,Petty-Joseph,New Katrinaview,Heard Island and McDonald Islands,jmay@osborn.com,7/14/2021,http://www.gilmore-henson.com/ +311,Ariel,Francis,"Flores, Peters and Leon",Meyerfurt,Switzerland,gcarter@rollins.net,9/1/2020,http://mcdaniel.com/ +312,Heidi,Singleton,"Glenn, Peck and Ashley",East Alberthaven,France,tkelley@richardson.com,5/31/2021,https://day.biz/ +313,Guy,Keller,Callahan-Walters,Lake Morgan,San Marino,kleinmarilyn@frost-baird.biz,6/19/2020,https://www.fuentes.net/ +314,Meghan,Moses,Henry LLC,Bernardmouth,Holy See (Vatican City State),xcannon@leblanc-hays.com,2/23/2022,http://sloan.com/ +315,Jodi,Moran,Ashley-Johns,North Lacey,Solomon Islands,wsmall@jennings.com,5/17/2022,https://www.espinoza-gilmore.biz/ +316,Heather,York,Morrison Ltd,Alexville,Senegal,jesse32@ingram-paul.com,12/25/2020,https://hall.net/ +317,Gail,Salazar,Barnett Group,North Isaacport,Saudi Arabia,alvinbeck@petersen.net,3/10/2021,https://www.peters.com/ +318,Tammy,Cantu,Estes-Evans,North Chelseyland,Senegal,crystal44@holder-berger.com,1/22/2020,http://kennedy.org/ +319,Olivia,Mcgrath,Duran PLC,East Chelseyfurt,Vietnam,claytonday@drake.com,4/6/2021,https://www.mcdonald-ho.org/ +320,Michele,Case,Mathis-Young,South Roberta,Equatorial Guinea,isaachawkins@brock.net,4/5/2022,http://stout.com/ +321,Matthew,Brock,"Gregory, Harrison and Roman",Elizabethburgh,Bhutan,kendramyers@moses.biz,9/30/2020,http://wolf.org/ +322,Jenna,Henry,Clements-Roberts,Tommyshire,Jamaica,xzamora@rogers.net,12/20/2021,http://www.cantrell.com/ +323,Kristina,Hunter,Fry-Melendez,East Isaac,Ecuador,bonnie63@potter-combs.com,1/1/2022,http://jensen.com/ +324,Shirley,Bowman,Roman LLC,East Dianeport,Reunion,nicholasvaldez@wall.com,4/16/2022,http://www.miles-chapman.com/ +325,Allison,Walter,"Frost, Herring and Maxwell",Prestonchester,Sierra Leone,ypage@pittman.com,4/17/2020,http://mcgee.com/ +326,Frances,Beltran,"Hunt, Howard and Black",Lake Lindseyberg,Swaziland,manuelfoster@carr-nunez.com,4/28/2020,http://www.atkins.biz/ +327,Bailey,Waters,Fox-Frey,South Gary,Swaziland,jillian22@wyatt-olsen.net,4/13/2020,http://melton.info/ +328,Jeanne,Horn,Cisneros and Sons,Lake Billy,New Caledonia,murillobrittney@johnston-brady.com,9/9/2021,https://www.ho.net/ +329,Jermaine,Hodges,Fields-Frederick,Lake Marilynhaven,Cayman Islands,barry83@beltran-tyler.biz,5/4/2022,https://www.mcclain.biz/ +330,Jeremy,Clayton,"Contreras, Hester and Baird",North Renee,Lithuania,bethmerritt@maynard.com,7/5/2021,https://www.mcneil-valdez.com/ +331,Tracey,Hardy,Valentine-Murillo,Blanchardfurt,Libyan Arab Jamahiriya,lesterguy@figueroa-molina.com,10/9/2020,http://davenport-vincent.org/ +332,Leslie,Mcmahon,Adkins and Sons,East Dominique,Brazil,destiny13@cline.com,7/14/2020,http://www.mcgrath.com/ +333,Peter,Moore,Costa and Sons,West Debrahaven,Palau,traciliu@forbes.com,7/17/2020,https://nielsen.com/ +334,Ian,Krueger,Cobb and Sons,West Melvin,Puerto Rico,uvaughn@gardner.com,10/25/2021,http://www.montoya-monroe.org/ +335,Crystal,Choi,Mendoza-Franklin,South Monica,British Virgin Islands,usutton@harmon-davenport.info,5/29/2020,http://www.mcknight.net/ +336,Lydia,Peterson,"Downs, Bentley and Gallagher",Port Christiemouth,Singapore,obarr@padilla-moore.info,12/13/2021,http://parker.org/ +337,Douglas,Waters,"Lambert, Irwin and Jackson",Natalieton,Czech Republic,keithwilkins@adams.com,10/19/2020,http://zhang.com/ +338,Tonya,Bond,Gill-Herman,North Joshua,Luxembourg,dianemonroe@gibbs.com,1/15/2021,http://beasley.com/ +339,Caleb,Henson,Hahn Group,Andradeberg,Bangladesh,whitneymason@dunn.com,10/15/2021,https://www.donaldson.biz/ +340,Tristan,Lynch,Forbes-Collier,South Spencer,Belarus,cassie84@fletcher.com,5/28/2021,https://www.flowers.com/ +341,Tami,Mendoza,Calhoun Ltd,East Savannah,Cape Verde,lwolfe@chavez-morse.biz,8/24/2021,http://www.goodwin.com/ +342,Leslie,Howe,Johnston PLC,New Cody,Liechtenstein,susan82@prince.net,8/26/2021,https://buckley.net/ +343,Luke,Perez,Lozano LLC,Phillipchester,Thailand,crandall@levine.com,4/23/2020,http://hudson.biz/ +344,Gina,Rowe,Hebert Inc,Jocelynmouth,Central African Republic,carolinehunt@rowe-olson.com,1/18/2021,http://www.salinas-savage.com/ +345,Darius,Riggs,Best Ltd,North Philip,Moldova,smcintyre@jordan.com,11/14/2021,https://www.kerr.com/ +346,Hailey,Hart,"Lozano, Everett and Vargas",Chungbury,Venezuela,regina66@ochoa.com,3/26/2020,https://www.mccarty.com/ +347,Larry,Paul,Gilbert Inc,Lukeside,Cayman Islands,russell17@chavez-schwartz.com,3/3/2021,http://arnold.biz/ +348,Krystal,Woods,Stewart-Krueger,Crystalburgh,Taiwan,hudsonjoan@mora.net,6/10/2021,http://howe.com/ +349,Tabitha,Reilly,Kerr Inc,Taylorshire,San Marino,michealwhite@krause.com,3/16/2022,https://www.austin.info/ +350,Roberta,Quinn,Oneal Group,North Phillip,Egypt,ekoch@myers.com,10/26/2021,http://pearson.com/ +351,Reginald,Pitts,Perry Inc,Clarenceton,Samoa,robertsonnatasha@peterson.info,1/29/2020,http://norman-branch.com/ +352,Jesus,Rogers,Shields PLC,Jarviston,Afghanistan,frencheddie@contreras.org,2/1/2021,https://mccoy-harrington.info/ +353,Roberto,Roy,"Gonzales, Cochran and Madden",South Dustin,Lithuania,gnovak@haley.com,8/17/2021,http://www.mckee.com/ +354,Jillian,Mccullough,Melton and Sons,New Gerald,Martinique,lorettamoreno@kerr.net,7/28/2020,https://www.monroe.biz/ +355,Alan,Valenzuela,Avila-Melendez,Birdtown,United States Minor Outlying Islands,mccannmallory@parsons.com,4/28/2022,http://chan.info/ +356,Kristy,Dixon,Norris-Day,Ebonymouth,Holy See (Vatican City State),jacob13@mccann.com,5/3/2020,http://www.glass-mason.com/ +357,Henry,Holland,Eaton-Burnett,North Julian,Gabon,benitezgina@downs-holden.com,4/15/2022,http://gallegos.net/ +358,Diana,Cox,Lin-Fowler,Tanyastad,Macedonia,crystal25@spence.com,5/2/2022,https://www.sosa.com/ +359,Sara,Middleton,Maxwell-Kennedy,East Ray,Nicaragua,mcconnellglenn@gay-boyer.org,7/29/2021,https://www.warner.info/ +360,Jade,Jimenez,Mosley-Knox,Wolfeborough,Dominica,jaime61@wilson.com,11/23/2020,https://gonzales.com/ +361,Carmen,Mcclain,Curtis-Lyons,Harryfort,Nauru,tsummers@dixon-vega.com,10/22/2021,http://www.singh-nixon.com/ +362,Wanda,Sanchez,"Raymond, Contreras and Wall",Tuckerfurt,Montenegro,wnash@sims.net,12/20/2021,https://schultz.com/ +363,Valerie,Wilcox,Dennis PLC,Coreychester,Tuvalu,jeanette40@banks.com,4/24/2020,https://cummings-duran.com/ +364,Chloe,Hurst,Jacobs and Sons,Duncanview,Tajikistan,smitchell@harmon.com,10/29/2021,https://oconnor-shaffer.com/ +365,Virginia,Goodman,Salazar Ltd,Jameston,Morocco,dawn64@brock.com,5/21/2021,http://www.valenzuela-maldonado.info/ +366,Erin,Woods,Dawson Group,South Brandonville,Pakistan,bholloway@curtis-blevins.com,3/27/2021,https://www.parker-parsons.org/ +367,Cassidy,Bruce,Castaneda Group,East Eugene,Congo,dwaynespence@kramer.com,2/21/2022,http://benitez-lam.org/ +368,Sally,Hinton,Glover-Mccoy,Guyside,Montserrat,rose26@navarro.biz,3/8/2021,https://ashley-mcintyre.com/ +369,Jessica,Randolph,Cruz-Chandler,Holdenfort,Qatar,kayleehenderson@tapia.com,8/28/2021,http://www.vang.com/ +370,Kelly,Hogan,Estrada PLC,Jaimeberg,Saint Vincent and the Grenadines,billmata@hatfield.com,9/7/2021,http://hanna.net/ +371,Derek,Chung,"Cooke, Downs and Hines",North Adriana,Georgia,grahamevan@wright.com,11/24/2020,http://hooper.biz/ +372,Shirley,York,Nolan-Hardy,Rodriguezbury,Brunei Darussalam,flemingcindy@dominguez.net,4/3/2020,https://www.leon-donovan.net/ +373,Cody,Potts,"Acosta, Robbins and Nash",Lunaville,Panama,fischerselena@novak-fitzgerald.biz,4/19/2021,http://berg-hardin.com/ +374,Erin,Gill,Mosley-Whitney,South Rachelchester,Iran,dixonmelissa@brooks.com,7/12/2021,http://bentley.com/ +375,Nicole,Vargas,Powell LLC,Alvaradoton,Vietnam,hrobinson@estrada.com,12/12/2021,http://pace.com/ +376,Sharon,Mack,"Vaughn, Kelly and Meza",Odonnellberg,Saint Lucia,cristian24@hebert.net,11/10/2021,http://www.oconnor-riggs.com/ +377,Monica,Lopez,"Farrell, Mcclain and Kaiser",Latoyabury,Maldives,terrance86@orozco.net,1/25/2021,http://www.cervantes.com/ +378,April,Jones,"Daniel, Rowland and Heath",Fryeville,Cocos (Keeling) Islands,taylor78@roberson-cline.com,1/30/2020,http://www.lang.net/ +379,Johnny,Reeves,Holden-Camacho,Lynnborough,Chad,moyeralejandra@ball.com,8/12/2020,https://www.mueller.com/ +380,Brandi,Owens,Bray-Nelson,Lake Jacobstad,Dominican Republic,karadrake@harris.com,4/15/2020,http://perez.com/ +381,Max,Rasmussen,"Graves, Hardin and Cummings",West Tammieport,Liechtenstein,shawriley@rasmussen.com,3/6/2022,http://woodard-dawson.com/ +382,Christian,Moore,Cherry and Sons,South Anne,Gambia,moralesleslie@scott.com,6/14/2020,https://stevens-crane.com/ +383,Tyrone,Holloway,Peters-Perez,Pachecoburgh,Tonga,wdyer@mayo.net,4/9/2022,http://www.francis.biz/ +384,Victor,Ferguson,Lawrence and Sons,Turnerfort,Comoros,savannahglenn@ward.com,3/8/2021,https://bright.net/ +385,Logan,Carroll,Chaney and Sons,Melissaville,Swaziland,cummingsjo@shea.info,1/3/2022,https://barrett.com/ +386,Alexis,Hawkins,Beck PLC,West Nathaniel,Eritrea,lcox@hensley.biz,1/10/2020,https://ray-summers.com/ +387,Diane,Reid,Mendoza Inc,West Mikehaven,Sweden,ballmartha@salazar-escobar.com,6/18/2021,http://www.armstrong.net/ +388,Dylan,Sweeney,Mccarthy-Leblanc,Goodmanborough,Brunei Darussalam,allisonstanton@mcpherson.net,7/13/2021,http://moyer.org/ +389,Craig,Gonzales,Clayton-Sutton,Darinshire,Falkland Islands (Malvinas),chungneil@flowers.info,4/23/2020,https://www.mcclure-maxwell.com/ +390,Jill,Cardenas,Williamson-Franco,West Kerrifort,Brazil,teresa89@wallace.com,5/24/2020,https://www.harvey.com/ +391,Timothy,Chambers,Alexander-Farrell,Pammouth,Afghanistan,sean10@fletcher.com,6/4/2020,https://arias.org/ +392,Jaime,Terrell,Castaneda and Sons,Gilesburgh,Czech Republic,ssutton@schroeder.info,4/12/2022,https://www.rogers.com/ +393,Alejandro,Hancock,Cooley and Sons,Lake Trevorton,Ghana,orobbins@bradford.com,10/15/2020,https://ward.org/ +394,Gregg,Rodriguez,Roberts-Hernandez,Leonardberg,Gambia,bernard04@evans-cooley.net,5/16/2021,https://leon.com/ +395,Deanna,Pacheco,"Heath, Collins and Pierce",Georgeton,Oman,courtney52@farley.org,5/22/2020,http://boone-guzman.com/ +396,Mariah,Barton,Molina-Miranda,Hebertchester,French Southern Territories,rangelbrad@oconnor.org,10/12/2021,http://www.mitchell.com/ +397,Valerie,Branch,Atkins-Hodges,Port Jenna,Gabon,jennifershannon@lane.net,5/18/2020,https://pugh.com/ +398,Jonathan,Norman,Mays-Benjamin,Josephview,Trinidad and Tobago,chubbard@haney.com,1/23/2021,http://www.travis-vaughan.com/ +399,Samuel,Drake,"Myers, Krueger and Sampson",South Tonya,Nauru,xholden@walsh.com,3/18/2022,http://www.david-rollins.com/ +400,Riley,Aguirre,Hicks PLC,East Sally,Senegal,pattonsydney@graham.com,5/7/2022,https://tapia-erickson.com/ +401,Jermaine,Baker,"Ryan, Conner and Boyle",North Diamond,Nicaragua,atkinsonheather@sutton.com,11/29/2021,https://pennington-carney.com/ +402,Holly,Hartman,"Rivas, Payne and Arellano",Dianamouth,Namibia,tinakerr@aguilar.info,5/27/2020,http://www.stein.com/ +403,Haley,Frost,Benitez LLC,Watsonfort,Ethiopia,snyderfred@santiago.info,12/21/2020,https://marshall.org/ +404,Lindsay,Crane,Ramos-Howard,South Gabriela,Faroe Islands,xgordon@king-terry.com,5/26/2020,http://mccarthy.org/ +405,Adriana,Bartlett,Wheeler PLC,West Roystad,Svalbard & Jan Mayen Islands,makaylalove@foley-rodriguez.net,5/12/2020,http://www.duke.com/ +406,Bethany,Ruiz,"Chambers, Castillo and Graves",South Andreachester,Syrian Arab Republic,caleb30@mccann.com,6/2/2021,https://kelley.biz/ +407,Gabriel,Weaver,"Hutchinson, Hicks and Austin",Longton,Morocco,rellis@blankenship.com,7/8/2020,https://stafford.com/ +408,Jimmy,Heath,"Jenkins, Clarke and Faulkner",Coxburgh,Rwanda,allenwalter@escobar.biz,8/1/2020,https://hardin-elliott.com/ +409,Craig,Powers,"Trujillo, Hurst and Ortega",Grantmouth,Netherlands Antilles,donnasandoval@sellers.com,1/30/2021,http://ruiz.biz/ +410,Colin,Howard,Glass-Shepard,Alexberg,Australia,manningsuzanne@osborn.net,1/9/2021,http://www.page.com/ +411,Edgar,Zhang,Francis Ltd,New Reginafort,Yemen,rbradley@buchanan-rogers.com,8/17/2021,http://www.moore.com/ +412,Carolyn,Lucero,Rojas-Ross,Charlottestad,Bosnia and Herzegovina,ihawkins@tanner.com,5/6/2020,https://www.mcmillan.com/ +413,Tonya,Parsons,Meyer LLC,South Charleneport,Rwanda,singhfrank@castillo-horne.org,7/3/2020,http://www.boyd.com/ +414,Marc,Small,Trevino-Bennett,Bennettmouth,Colombia,michaelcandice@booker-hall.com,3/26/2021,http://dawson-tapia.com/ +415,Dakota,Elliott,"Marks, Mora and Bender",South Charleneport,Russian Federation,goodmanhayden@marquez-fritz.biz,12/2/2021,http://www.simpson.com/ +416,Hector,Barry,Hunt Group,Wumouth,Gabon,ofriedman@gonzalez.com,3/13/2020,http://bender-terrell.com/ +417,Morgan,Wells,Mcmillan-Ramos,East Yvetteside,Thailand,alec48@copeland.com,3/14/2020,http://www.fernandez.info/ +418,Maxwell,Glenn,"Everett, Holmes and Sheppard",Miketown,Niue,stefanie43@hooper.com,9/7/2021,https://roberson-white.com/ +419,Melody,Young,Peck-Rivers,Port Harold,Switzerland,brichard@cervantes.com,3/16/2021,https://pena-clements.info/ +420,Alejandro,Sutton,Byrd PLC,Lake Franklinland,Colombia,jillrichmond@pugh-roach.com,2/5/2022,https://riggs.com/ +421,Jermaine,Banks,Harvey LLC,Lake Martha,South Georgia and the South Sandwich Islands,wilkinsondaisy@chaney.com,3/14/2020,https://www.riddle-yates.com/ +422,Terry,Woodward,Hicks Inc,Schultzbury,Syrian Arab Republic,mcculloughkylie@ford.com,4/24/2022,https://www.osborn.com/ +423,Tanner,Cherry,Mclaughlin-Lucas,Noahview,Kyrgyz Republic,melvin55@marks.net,10/23/2020,http://www.cordova.biz/ +424,Troy,Elliott,Sosa LLC,South Robertbury,Equatorial Guinea,ezamora@burke.org,3/27/2022,https://www.sampson-torres.com/ +425,Barry,Rubio,Brock and Sons,Dalemouth,French Guiana,brad79@powell.org,4/28/2022,http://www.adams.com/ +426,Samantha,Lutz,Mcdonald Inc,Lake Hayden,Bahamas,collin50@wilkinson.net,6/28/2020,http://cummings.com/ +427,Wendy,Trevino,Wells and Sons,Port Louis,Saint Lucia,christyconway@reid.com,7/4/2020,http://www.allison.biz/ +428,Greg,Mcneil,Mcclure-Thomas,Alfredhaven,British Indian Ocean Territory (Chagos Archipelago),anitadorsey@mccarthy.biz,8/14/2021,https://larsen-parks.info/ +429,Rita,Hutchinson,Galvan Inc,Arroyoland,Liechtenstein,friedmandean@hodge.info,10/10/2021,https://www.friedman.com/ +430,Rose,Robertson,"Chambers, Bernard and Rivas",West Brandonfurt,Svalbard & Jan Mayen Islands,downsjackie@cole-orr.com,1/17/2020,https://edwards-morton.com/ +431,Paige,Aguirre,Black-Pruitt,Russellland,Benin,robertsmelanie@swanson-willis.net,9/12/2021,https://www.cain.com/ +432,Xavier,Terry,"Ray, Maxwell and Olson",North Ritachester,Reunion,shamilton@wheeler-jensen.biz,11/11/2021,https://www.hughes.org/ +433,Michaela,Thornton,Riggs-Rodgers,West Victoria,Norfolk Island,raysimon@smith.com,4/22/2022,http://maldonado.com/ +434,Kyle,Rojas,"Hoffman, Brandt and Gay",West Tyrone,Liberia,donaldsongina@underwood.com,6/16/2020,http://warner-jarvis.com/ +435,Devin,Baxter,"Kirby, Lang and Galvan",Lake Xaviertown,Jordan,ubennett@mckenzie-farley.info,2/14/2020,https://www.ritter-spears.com/ +436,Omar,Farmer,"Fuentes, Mcguire and Mendoza",Tatemouth,Lesotho,aowens@arnold.com,7/27/2020,https://www.robles-stuart.net/ +437,Desiree,Vang,"Patel, Donovan and Branch",Wigginsbury,Burkina Faso,leeeileen@peterson.com,3/6/2022,http://www.mckenzie.biz/ +438,Fernando,Wade,Chan-Good,Rosebury,South Georgia and the South Sandwich Islands,frederick82@pham.info,6/5/2020,http://www.schwartz.biz/ +439,Anne,Winters,Gay LLC,Lewisview,Monaco,ybanks@velazquez.biz,4/21/2021,https://paul.biz/ +440,Glenn,Harrell,Vincent and Sons,Lake Douglas,India,luis88@calhoun-gaines.com,7/5/2021,http://chan.org/ +441,Tracey,Boyd,Mcmahon-Hale,South Victorhaven,Niger,oconnellkristopher@roberts.com,10/11/2021,https://hansen-frank.info/ +442,Catherine,Becker,Ware and Sons,Christyville,Christmas Island,qlambert@obrien-gay.com,4/29/2022,https://moreno-brown.com/ +443,Sharon,Farrell,Collins-Kline,New Aliciaberg,Gabon,masseyvickie@prince.info,4/24/2021,http://olsen-patterson.org/ +444,Clinton,Schwartz,Horton Inc,Reedland,Greece,robin51@caldwell.net,7/12/2021,https://page.com/ +445,Sheri,Perez,"Velasquez, Haynes and Parks",East Krystalland,United States Virgin Islands,kyle22@robbins-trevino.net,9/8/2021,http://lara.com/ +446,Beth,White,Wiggins-Horne,East Hunterburgh,Israel,hendrixjoanne@hooper.info,6/25/2020,http://www.bruce.net/ +447,Cole,Benitez,"Daniel, Phillips and Garrett",Lake Amanda,Estonia,geoffrey76@gonzales.org,2/26/2022,https://www.roach.org/ +448,Caleb,Chapman,Mullen-Burgess,Port Stanleyside,Kiribati,lindseymcmahon@hayden-tucker.com,8/18/2021,https://mcintyre.com/ +449,Glen,Eaton,"White, Harding and Gilbert",Rosefort,Belize,jcantu@clayton.com,11/30/2020,http://www.carpenter.com/ +450,Cristina,Hanna,Mcconnell-Mccarty,West Arianafurt,Central African Republic,mathew61@woodward.com,3/2/2020,http://www.bernard.com/ +451,Kathy,Owens,Irwin Inc,Keithchester,Brunei Darussalam,ugordon@boyer.info,7/9/2020,http://www.lucas.com/ +452,Rachael,Keith,"Gentry, Hurley and Gomez",Shannonstad,Togo,paula85@douglas-mann.net,12/9/2021,https://cortez.info/ +453,Marcus,Thompson,Prince-Hodges,New Gwendolyn,Albania,gravesjanice@rice.org,8/27/2021,http://www.wall.org/ +454,Norman,Mclean,"Crosby, Ayers and Burch",South Stephen,Equatorial Guinea,nathaniel65@copeland-levy.org,4/3/2022,http://www.potter.com/ +455,Brandon,Cross,"Delacruz, Hodge and Snow",North Leon,Tuvalu,currysabrina@henry.biz,10/14/2021,https://rowe.com/ +456,Autumn,Soto,Ho-Long,Lake Deanna,Niger,robertasharp@vasquez.info,4/29/2022,https://www.andrews.com/ +457,Jaime,Cervantes,"Garrison, Mccall and Bowman",North Kristinside,Egypt,casey71@little.net,9/5/2020,https://www.walter-frederick.com/ +458,Reginald,Blankenship,"Holmes, Warner and Barrett",North Carly,Malawi,plawson@reyes.com,5/10/2022,https://www.small.com/ +459,Chelsey,Mcknight,Morse Group,Fordborough,Djibouti,aaroncollins@nunez.com,4/8/2022,https://rich.net/ +460,Clayton,Lindsey,Gilmore Ltd,North Sean,Greenland,katiestanton@villa.com,9/3/2021,https://www.henson-benitez.com/ +461,Kurt,Prince,Humphrey-Cain,Ronnieberg,United States of America,thompsonangie@may.com,5/5/2022,http://gibbs-friedman.com/ +462,Joy,Choi,Montgomery Group,West Jermaineton,Dominica,sheenarichards@harrison.com,2/25/2020,http://daniel-walton.info/ +463,Jeremiah,May,Larson LLC,Gabrielleland,Macao,fgoodman@livingston.org,7/29/2021,https://camacho.net/ +464,Candice,Huang,"Barrera, Conway and Holland",New Joanneberg,Guinea,adamsjim@luna.com,4/30/2021,http://porter.com/ +465,Alejandra,Tran,"Blackburn, Hampton and Conway",Karatown,French Guiana,kporter@kelley.com,12/26/2021,http://wagner-orr.com/ +466,Shaun,Richardson,"Cunningham, Grimes and Cameron",Rachelview,Armenia,craigkent@waller.com,1/20/2020,http://www.houston.info/ +467,Darryl,Russo,Copeland and Sons,Scottstad,Nicaragua,qbates@randall.com,10/26/2021,http://zavala.com/ +468,Joe,Cabrera,Wilkinson LLC,Kimberlystad,French Southern Territories,wesleymcfarland@dunlap.info,5/6/2020,https://rowe.info/ +469,Travis,Duran,"Vincent, Watson and Esparza",Port Russellmouth,Uganda,rshields@mack.com,7/23/2021,http://wolfe.net/ +470,Warren,House,"Frazier, Cain and Santos",Lake Devonmouth,Togo,brittneytravis@salinas-zhang.org,9/9/2021,https://wiggins.org/ +471,Katherine,Haas,"Villegas, Stanley and Calderon",South Tonya,Eritrea,iheath@woodward.biz,12/19/2020,http://www.schmitt.biz/ +472,Karen,Burton,Stein-Hess,South Andres,Cote d'Ivoire,gary29@craig.org,4/9/2021,https://warner.info/ +473,Logan,Riddle,"Leach, Gutierrez and Villarreal",Weberview,Jordan,valerie78@osborne.net,5/8/2022,http://www.christian.info/ +474,Kathryn,Hester,Lam and Sons,Port Paigefort,American Samoa,mccartycandace@mclean.com,1/22/2022,https://koch-willis.com/ +475,Christina,Villegas,"Mercado, Ewing and Villa",Vickimouth,Nigeria,isabella03@rojas.com,9/11/2021,https://mercado.com/ +476,Joel,Walters,Cuevas-Vaughn,Carlsonfort,Cayman Islands,icalhoun@scott.com,10/21/2020,http://www.cunningham.net/ +477,Frederick,Delacruz,"Beck, Lambert and Sweeney",Brianbury,Djibouti,kiaramacias@banks.com,2/5/2020,https://www.church.com/ +478,Warren,Cantrell,"Hood, Mora and Murphy",Whitneyburgh,Korea,darrell62@dickson-nielsen.biz,8/6/2021,https://www.carlson.info/ +479,Melvin,Bates,Fry-Roy,Karinabury,Vanuatu,rashley@robbins-boyer.org,1/27/2021,https://vasquez-whitaker.com/ +480,Meredith,Pollard,Gilmore Ltd,Anthonyshire,Sudan,sandy88@dougherty-gordon.biz,1/5/2022,http://www.miles.info/ +481,Dawn,Hines,"Kane, Roman and Osborne",Collinstown,Paraguay,nataliemedina@mitchell.net,10/17/2020,http://www.huber.org/ +482,Audrey,Goodman,Jenkins-Murillo,South Rogerhaven,Yemen,martinjeanette@petersen.com,10/1/2021,http://www.orr.org/ +483,Kurt,Waters,Molina and Sons,Port Shane,Papua New Guinea,sanderscheyenne@peters-ware.com,3/3/2020,https://www.weber-watson.info/ +484,Eugene,Harper,"Rowe, Aguirre and Holloway",East Josephfurt,Serbia,charlotte42@bowman-arellano.biz,6/14/2020,https://terrell-proctor.com/ +485,Caleb,Eaton,"Franco, Blankenship and Nolan",Rileyburgh,Sri Lanka,weverett@dixon.info,7/13/2020,https://www.bell.net/ +486,Kaitlin,Mejia,"James, Bowman and Bass",Lake Alechaven,Latvia,ross23@hooper.com,4/8/2022,http://www.hicks.info/ +487,Jennifer,Mercado,Espinoza LLC,South Vanessaton,Cayman Islands,deannamiller@boyle.com,5/12/2020,https://collins.net/ +488,David,Cardenas,Cortez-Flowers,Tanyabury,Bahamas,mpetty@clark-allison.info,1/23/2022,http://www.roth.com/ +489,Jenny,Reid,French-Norris,Karenmouth,Malaysia,dustinrhodes@fischer-hendricks.com,1/16/2021,http://www.burnett.com/ +490,Lonnie,Frye,Fox-Small,Carlsonfurt,Panama,ronnieprince@haley.com,9/3/2021,http://zamora-wong.net/ +491,Janice,Mayer,"Cobb, Chen and Fitzgerald",Port Sheenaborough,Jersey,careyspencer@ellison.com,6/19/2020,https://www.welch-houston.com/ +492,Debbie,Bates,"Fox, Lara and Short",South Toddchester,Sweden,sfrederick@fowler.com,5/30/2020,https://www.stevens.com/ +493,Jean,Christian,Sawyer Group,Hartburgh,Jordan,gregory88@kane-atkins.org,3/28/2021,https://frederick-dawson.com/ +494,Philip,Harding,Burgess-Stephenson,Jenniferland,Turkmenistan,barrerasamantha@manning.com,5/10/2020,http://rivera.net/ +495,Jillian,Oneal,Chambers Inc,Knoxton,Cape Verde,wilkinsontravis@booth-crawford.biz,9/28/2021,http://shepherd.com/ +496,Mindy,Christian,Fox PLC,Jesusfort,Liechtenstein,jessicagillespie@ibarra-cannon.org,8/4/2021,http://skinner-finley.com/ +497,Bobby,Mclean,Mosley Group,West Normanborough,Cote d'Ivoire,hodgekiara@conley-haynes.info,6/2/2021,https://nicholson.com/ +498,Amanda,Santos,Camacho-Lamb,Freemanberg,Antigua and Barbuda,slivingston@cherry-lara.info,5/29/2022,http://www.mcneil-gould.biz/ +499,Ralph,Buckley,"Tate, Wall and Trujillo",New Tara,Montenegro,hansenjoshua@pugh.com,5/15/2021,https://www.fuentes-vang.info/ +500,Brian,Montoya,Short and Sons,West Mirandaside,Gabon,jblankenship@harper.com,1/27/2021,http://boone.biz/ +501,Joann,Dyer,Salinas-Stephenson,Lake Chadside,Nigeria,llowe@mcmahon.biz,6/3/2021,http://www.fitzgerald-acosta.info/ +502,Rhonda,Cook,Pennington-Lee,Zamoraburgh,Lebanon,rollinscristian@harvey.com,5/14/2020,http://www.nielsen-davidson.com/ +503,Charlene,Huffman,"Henry, Weeks and Cantu",Stanleybury,Korea,belindawalls@wall-dalton.biz,4/1/2022,http://www.reynolds.org/ +504,Abigail,Shah,Ortiz LLC,Mercadoland,Sierra Leone,ecollins@wise.biz,3/11/2022,https://www.cantrell.biz/ +505,Frances,Bridges,Lane and Sons,Port Virginiaside,United Arab Emirates,nicholemccann@logan.com,9/7/2021,http://www.calhoun.net/ +506,Belinda,Kaiser,Hull Inc,Phillipsbury,Malawi,susan95@burgess.com,4/17/2022,http://www.shelton-maxwell.org/ +507,Michael,Griffin,"Finley, Molina and Ortega",West Sophiafort,Syrian Arab Republic,priscilla21@richmond.com,3/31/2020,http://www.grant.com/ +508,Brooke,Briggs,"Daugherty, Bond and Mcmillan",Mezaview,Sao Tome and Principe,moraleonard@grant-murphy.org,12/18/2021,http://lin-hardy.org/ +509,Desiree,Sloan,Mccann and Sons,North Kyle,Vanuatu,pittmanjay@pruitt.org,11/7/2020,https://dudley.info/ +510,Jared,Floyd,Waters-Novak,East Tylerton,Jersey,qenglish@huang.com,6/10/2020,https://www.schroeder-mora.com/ +511,Latasha,Spencer,Castro LLC,North Kimberlychester,Honduras,sean06@bridges-serrano.com,8/14/2021,https://www.ritter.com/ +512,Joy,Mack,Blackwell Group,North Stevestad,Latvia,calebsolomon@li-berg.com,3/20/2022,http://oconnor-hale.com/ +513,John,Rhodes,"Luna, Flowers and Beck",Port Sylviaton,Iceland,jamiemontgomery@cameron.org,1/7/2022,http://chandler.net/ +514,Claire,Schaefer,Lynch-Pittman,Stuartmouth,Algeria,hannah33@leach-frey.com,1/21/2021,http://www.dominguez-higgins.com/ +515,Judy,Nguyen,Graham LLC,Lindsayburgh,Uruguay,tmoses@mueller.com,5/21/2022,http://odom.com/ +516,Andrea,Dennis,Kramer Ltd,Colemanfurt,China,stephensonkevin@rojas-strong.com,12/16/2020,https://boyd-giles.info/ +517,Karla,Middleton,"Bradshaw, Mcbride and Gamble",Mayton,Mongolia,nathanielmoreno@cunningham.com,11/10/2021,https://www.frazier-logan.com/ +518,Sean,Coffey,Kirby-Coffey,Silvaberg,Anguilla,tbartlett@knox.com,9/23/2020,https://harrington.net/ +519,Nichole,George,Stout Group,West Charlotte,Greece,warddeborah@eaton.com,3/3/2020,http://avila.net/ +520,Justin,Dougherty,Gregory PLC,Greerbury,Netherlands,robersonangie@thornton.com,1/22/2020,http://www.garza-crane.com/ +521,Devon,Smith,Morgan PLC,Brandtville,Libyan Arab Jamahiriya,glenda72@hobbs-watts.com,8/23/2021,http://sutton.net/ +522,Becky,Scott,Coffey-Mcgee,Haroldport,Jersey,rblack@benson.com,2/13/2020,https://mclaughlin.com/ +523,Misty,Espinoza,"Baxter, Sims and Braun",Mataport,Luxembourg,zbarron@ross.com,6/27/2020,https://www.mosley-vazquez.com/ +524,Franklin,Rivers,Beasley-Lewis,East Joan,Reunion,miguel73@fisher-hatfield.org,3/7/2020,http://mitchell-ho.com/ +525,Leonard,Barber,Stevens Ltd,Lake Jasmineview,Rwanda,gainesbethany@washington-pearson.com,1/12/2020,http://www.hurst.net/ +526,Lonnie,Price,"Ibarra, Horton and Williams",North Kirk,Cayman Islands,hbradshaw@wang.biz,11/29/2021,http://www.sosa-baldwin.com/ +527,Clarence,Cantu,Dudley LLC,Robertstown,Gambia,billy41@malone.net,3/21/2021,http://daniel-campbell.info/ +528,Johnny,Foster,"Terry, Fletcher and Mclean",South Chelseyton,Puerto Rico,lancebenitez@nash.info,10/19/2021,https://walton.net/ +529,Richard,Vasquez,Glenn PLC,Stanleyhaven,Burundi,jonesjacob@downs.com,4/3/2020,http://www.kaufman-braun.net/ +530,Tabitha,Compton,Cantu-Hunt,West Tammietown,Mozambique,joneskrystal@blackburn.net,3/26/2020,https://www.villegas.com/ +531,Adrienne,Wong,Barrera PLC,Anthonyberg,Gibraltar,yjenkins@lowery.com,2/25/2022,http://browning-jennings.com/ +532,Jorge,Farrell,Delgado LLC,Dylanberg,Australia,alfredsnyder@tran-ellison.com,4/21/2022,https://mcknight-trevino.info/ +533,Kathy,Richards,Ellis Inc,Fieldsside,Turks and Caicos Islands,anitavillarreal@reilly.com,6/21/2020,http://www.anderson.net/ +534,Larry,Ellison,"Horne, Lloyd and Bolton",South Cristian,Sweden,chelsey46@calhoun.com,7/15/2021,https://taylor.com/ +535,Gabrielle,Chaney,"Acevedo, Randall and Harrell",Tanyachester,Guadeloupe,ulogan@gilmore.com,10/5/2021,https://www.washington.biz/ +536,Maxwell,Macias,"Marquez, Poole and Chang",New Edwin,Maldives,ray95@barnett.com,8/4/2021,https://www.mccoy.com/ +537,Joyce,Chan,Crawford-Nielsen,Isaiahfurt,Serbia,calhounkari@thompson.info,5/26/2021,https://www.solomon.com/ +538,Kari,Durham,Durham-Dean,Lake Julianberg,Greece,kathrynsims@wong-chapman.com,11/3/2021,https://www.trevino.com/ +539,Trevor,Mckee,Moss LLC,North Reginaton,Togo,nicholserika@cooke-le.net,7/4/2021,http://www.carter.com/ +540,Diana,Thomas,Ali-Stark,Rodriguezfurt,Greece,brendan87@hale.com,5/14/2021,http://www.ortega.com/ +541,Cynthia,Moss,Colon LLC,West Matthewside,South Georgia and the South Sandwich Islands,victoria21@pope-ortiz.com,4/16/2022,http://lambert.com/ +542,Billy,Garrison,"Travis, Nichols and Farmer",Andreburgh,Mayotte,kylie93@patterson.net,10/20/2021,https://www.townsend.com/ +543,Grace,Austin,"Petersen, Barrett and Leon",North Yvetteview,Sierra Leone,hayley87@washington.org,8/10/2021,http://www.tran.com/ +544,Kerri,Hamilton,Hart and Sons,North Karaside,Yemen,scurry@schaefer-tate.info,2/28/2022,https://www.lawson-maddox.com/ +545,Kaylee,Wright,"Randolph, Butler and Burgess",North Douglas,South Africa,barbara31@page.info,12/10/2020,https://www.pacheco-reilly.biz/ +546,Manuel,Delacruz,"Wilson, Kline and Bullock",Port Cassandrastad,Papua New Guinea,alexandria98@shields.com,12/22/2020,https://www.andrade.info/ +547,Helen,Baxter,Schneider and Sons,Garrisonberg,Bolivia,pacenatasha@whitaker.com,8/11/2021,http://www.boone.biz/ +548,Leon,Hendricks,Hood Inc,South Angelica,Chad,drew59@strong.info,7/31/2020,http://owen.com/ +549,Henry,Contreras,"Rice, Simpson and Russell",New Michellefort,Slovakia (Slovak Republic),gregorymcfarland@rodriguez.com,6/5/2020,https://www.proctor.biz/ +550,Gerald,Tanner,Fritz LLC,West Leslie,Western Sahara,isabel75@rubio.com,3/30/2021,https://lawrence.com/ +551,Ariel,Arnold,"Stokes, Floyd and Bradford",Michaelport,United Arab Emirates,sarroyo@mejia.com,11/14/2020,http://solis.org/ +552,Judy,Fisher,Riggs-Beltran,Juliestad,Fiji,dflynn@spears.com,4/18/2021,http://yoder-bender.info/ +553,Miguel,Wood,"Cooke, Mcgrath and Morse",East Douglasberg,France,hblankenship@ellison.biz,1/16/2021,http://wolfe.com/ +554,Lance,Gillespie,Collier-James,South Timothy,Lebanon,urojas@wolf.org,10/16/2021,http://shaffer-schmidt.com/ +555,Natalie,Wang,Holt Group,Mercadofurt,Macao,larsonray@hodges-bartlett.net,2/1/2020,https://www.ruiz-underwood.com/ +556,Charlotte,Byrd,Benson-Gardner,West Rita,Ireland,braunjillian@mcintyre-delgado.com,4/8/2021,https://www.gentry-woods.com/ +557,Chelsea,Wagner,"Hurley, Shepard and Harrell",Port Cristianview,Bosnia and Herzegovina,clayton96@knight.com,7/12/2020,http://saunders-warren.org/ +558,Larry,Rollins,"Michael, Cunningham and Ellison",Krausefurt,Djibouti,spearsmarissa@flowers.com,4/27/2022,https://www.rice.com/ +559,Paul,Leblanc,Holmes-Gates,South Heathershire,Rwanda,wolfedanielle@pitts.com,12/28/2020,http://www.caldwell.com/ +560,Caitlin,Vance,"Phillips, Frazier and Blair",New Julia,Korea,bonnieparks@ritter-flynn.com,6/26/2021,http://moreno.com/ +561,Adam,Shaffer,Diaz-Harrell,Garymouth,Tajikistan,isaiah33@ho-lane.com,4/16/2021,http://liu.com/ +562,Martin,Rocha,Hall-Daugherty,Oneillfurt,Niger,montgomerycharlotte@wong.com,11/1/2020,http://www.cook-wagner.com/ +563,Ronnie,Erickson,"Miller, Lucero and Mccann",South Miamouth,Papua New Guinea,tricia95@mcdonald.com,12/21/2021,http://www.valenzuela.com/ +564,Sheryl,Delgado,Manning Ltd,Tracyfurt,Liechtenstein,larry51@stark.com,8/5/2021,https://jarvis.biz/ +565,Kristen,Williamson,Doyle-Rodgers,East Kayleetown,French Southern Territories,watkinskaylee@pacheco.biz,4/27/2020,https://ho-medina.net/ +566,Cole,Warner,Grimes Inc,Port Kylieside,Bhutan,dorislucas@evans-hartman.info,3/6/2022,http://moses.net/ +567,Lee,Mercado,Pierce-Wilkinson,East Normaville,Panama,wilkersonnicholas@crosby.net,11/15/2020,https://strickland.com/ +568,Paula,Bates,Lin Group,Phillipsville,Taiwan,sethrhodes@silva.org,3/22/2020,http://cooke.com/ +569,Claire,Shaw,"Ayala, Krause and Hendrix",Anthonyville,Bulgaria,chaynes@rasmussen.com,10/19/2021,http://leach.com/ +570,Amy,Mclean,"Rasmussen, Pacheco and Mccann",Shortmouth,Cote d'Ivoire,hamptontammie@gonzales.com,11/27/2020,https://rivera-madden.biz/ +571,Joshua,Winters,Bolton Inc,South Terry,Luxembourg,brittanylandry@whitaker.org,7/15/2021,https://carlson.com/ +572,Brian,Pittman,"Friedman, Montes and Valenzuela",West Meredithshire,Faroe Islands,stevesuarez@winters.com,4/11/2021,https://www.brandt-romero.biz/ +573,Jeffrey,Waters,Montoya Inc,Jesseville,Bouvet Island (Bouvetoya),mezakristina@hunt.com,10/30/2020,http://hartman.com/ +574,Denise,Barber,Terry-Walls,East Katelyn,British Virgin Islands,qmyers@dominguez-doyle.net,9/14/2020,https://www.lutz-parks.info/ +575,Wayne,Sanders,Patel Group,Lucasmouth,Tokelau,eshea@simmons-calhoun.org,10/4/2020,http://keller-browning.org/ +576,Sandra,Sparks,"Allison, Fox and Norris",Carneyfurt,Saint Kitts and Nevis,mcgrathdalton@barr.com,10/17/2020,http://dominguez-bender.com/ +577,Jose,Singleton,Mcfarland-May,Haleyfurt,Azerbaijan,donhood@gilmore.com,2/7/2020,https://morse-vega.com/ +578,Joel,Shea,Richmond-Horne,South Alisha,Palau,gfarley@wheeler-ayala.com,5/29/2022,https://www.mercer.com/ +579,Sergio,Marquez,Arroyo-Braun,South Kelli,Chile,darrylbarker@hebert.com,2/9/2021,https://www.chang-short.com/ +580,Latoya,Decker,Cooke and Sons,East Franceshaven,Mauritania,audrey14@hopkins-serrano.biz,4/16/2022,http://www.koch.com/ +581,Chase,Guerrero,"Mccarty, Quinn and House",Armstrongtown,Gibraltar,randall08@palmer-wells.com,5/3/2022,https://www.andrews.biz/ +582,Jeff,Tate,Brennan Inc,East Robertatown,Nicaragua,carlsonguy@massey-brock.net,10/12/2021,http://herrera.org/ +583,Brandy,Valentine,"Kidd, Gibson and Ramos",Janetchester,Romania,dkirk@bartlett-gross.org,1/8/2021,https://www.mccullough.com/ +584,Jade,Vega,"Ruiz, Mcfarland and Terrell",Lake Jaclyn,Guinea,ihaynes@velazquez-robertson.org,1/4/2020,http://sanford.com/ +585,Melody,Nielsen,"Rush, Snyder and Bridges",North Chris,American Samoa,biancapratt@espinoza.org,2/4/2020,https://www.jordan-hooper.com/ +586,Brandon,Webb,Ferrell-Fitzgerald,Reyesstad,Peru,cheyennemurillo@campbell.com,12/12/2021,https://coffey-zimmerman.com/ +587,Jean,Mclaughlin,"Rivas, Frey and Figueroa",Thomasstad,Lao People's Democratic Republic,jeffrey93@russell.biz,10/30/2020,http://www.vang.org/ +588,Albert,Case,"Henson, Heath and Delgado",Lake Lonnieberg,Saint Martin,krystal85@aguirre.com,1/2/2022,http://www.wise.com/ +589,Jose,Duarte,Robertson Inc,Kristaland,Reunion,billykerr@martinez.org,11/7/2021,http://www.chavez-browning.com/ +590,Jennifer,Brady,Hampton-Riddle,Stanleyborough,Zimbabwe,darrell48@robinson-graves.com,3/9/2022,https://www.carter.net/ +591,Angel,Conner,"Banks, Pierce and Romero",Port Marieland,Cape Verde,fgeorge@daniels.com,5/19/2022,https://www.conner.com/ +592,Alyssa,Gamble,"Mcclure, Raymond and Mccoy",Olsontown,Zimbabwe,donaldsondamon@dickson.com,8/24/2020,https://www.mcbride-carpenter.com/ +593,Lawrence,Campos,Pittman LLC,Heathfurt,Uganda,santosrussell@hicks.com,2/9/2022,https://russo.com/ +594,Joyce,Michael,"Maddox, Wiley and Vincent",New Soniatown,Mongolia,tomwyatt@fischer-morgan.biz,9/6/2020,http://www.schmidt.com/ +595,Doris,Fox,"Hudson, Fritz and Mcdaniel",Huntville,Sudan,gerald81@norris.com,7/9/2020,https://www.clements.info/ +596,Johnny,Harris,Costa-Franklin,New Rickstad,Norway,ihutchinson@bass-nunez.info,1/22/2021,https://www.nichols-woodward.com/ +597,Leroy,Vargas,Acevedo Ltd,Reillyburgh,Ghana,helen10@mcgee.com,2/13/2021,http://wyatt.com/ +598,Diana,Green,Bishop PLC,South Andrewville,Kazakhstan,anarobles@barton.com,3/21/2020,https://pugh-hicks.info/ +599,Victor,Bowman,Nolan PLC,North Johnnymouth,Panama,nkey@buchanan.info,1/19/2021,http://www.wyatt.biz/ +600,Alexis,Perry,Tanner-Mullen,Darrylstad,Faroe Islands,singletonbriana@cameron.com,7/12/2021,http://holloway-kent.biz/ +601,Xavier,Mooney,"Boyer, Hatfield and Powers",South Antoniochester,Jersey,jessesavage@barr-mathews.com,10/5/2020,http://www.glenn-jennings.com/ +602,Lawrence,Gross,Miles-Hodge,Bradleymouth,Ukraine,ryan38@delgado-crane.com,2/10/2022,https://www.dickerson.org/ +603,Albert,Myers,Davies-Benitez,Port Moniquehaven,Honduras,wnielsen@reeves.com,5/25/2021,https://www.gentry-fry.org/ +604,Mikayla,Oneal,Cannon Ltd,Terrancetown,Tajikistan,rbryant@ritter.org,11/11/2020,http://evans.info/ +605,Frederick,Mcgrath,Dennis PLC,Port Jodyland,Falkland Islands (Malvinas),jonathon19@hicks.net,6/29/2020,https://www.bean.com/ +606,Kirk,Benitez,"Bates, Baird and Bryan",Sharonside,Bolivia,mconley@mercado.com,12/24/2021,http://baxter.biz/ +607,Alex,Mcdonald,Bradford PLC,Candacestad,Egypt,bailey40@le.com,2/29/2020,http://simmons.info/ +608,Trevor,Hayden,Jackson-Trujillo,Lake Angie,Bangladesh,kathleenvillanueva@bullock.com,12/8/2021,http://www.dalton-nguyen.info/ +609,Sherry,Lane,Randall and Sons,West Joel,Antigua and Barbuda,vmorrison@contreras.net,5/16/2020,https://steele.org/ +610,Mallory,Pratt,Landry-Carpenter,Payneport,Malta,cunninghamshelly@hines-curtis.org,3/14/2020,https://christensen-irwin.biz/ +611,Daniel,Flores,Barajas-Gordon,South Carrieshire,Guatemala,bvance@pollard.biz,7/12/2021,http://www.nguyen.com/ +612,Jesus,Kane,Conrad and Sons,Clarketon,Brunei Darussalam,moyertanya@santos-fletcher.com,4/28/2021,https://www.elliott.info/ +613,Gilbert,Hendricks,"Goodman, Hinton and Douglas",South Francisco,Malawi,castilloluis@kent-reese.biz,6/27/2021,http://www.hoover-foster.com/ +614,Kerry,Sanders,Larsen-Murray,New Grantshire,Montserrat,zcase@mccarthy-patton.com,4/29/2022,http://www.swanson.org/ +615,Alexandra,Davidson,"Morrison, Ballard and Alvarado",South Latoya,Botswana,deleonclinton@harrison-small.com,6/11/2020,http://www.rivera.com/ +616,Vincent,Carey,Murphy-Lopez,South Clayton,Jamaica,josephmeyer@cameron.com,9/12/2020,https://www.cobb.biz/ +617,Parker,Russo,Foley-Yoder,East Dorothy,Malawi,hancockbrianna@mccann.org,1/2/2020,https://perez-pollard.com/ +618,Ebony,Velasquez,Casey-Krause,Dennisfurt,Mexico,marvinschmidt@lopez.com,2/22/2021,http://haley-ho.com/ +619,Jesus,Holden,Carlson Ltd,Cathyton,Saint Barthelemy,rutharellano@stafford-gross.com,1/5/2022,https://ayers-lyons.net/ +620,Candace,Chen,Raymond-Romero,Stacystad,Aruba,candiceguzman@carlson-graham.com,6/15/2021,http://www.estrada-olson.biz/ +621,Juan,Saunders,Gillespie Inc,Sotoberg,Taiwan,choidean@hays.com,12/31/2020,https://www.coffey.com/ +622,Richard,Serrano,Beasley Group,North Raven,Kyrgyz Republic,mario93@roy.com,9/24/2021,http://www.bernard-greene.com/ +623,Rick,Barrera,"Wells, Gallagher and Robles",Jermainetown,Samoa,cmercado@reed.com,6/22/2021,http://strong.com/ +624,Meagan,Hoover,Chapman Group,Piercemouth,Tanzania,alvin00@zavala.com,2/18/2021,https://oconnell.com/ +625,Larry,Huff,Knapp-Hill,New Reneemouth,Samoa,kennethtownsend@dominguez.biz,2/20/2022,https://lowe-potter.com/ +626,Kelli,Gonzales,Miles Ltd,Lake Dominique,Slovenia,jgonzales@maddox.com,5/24/2020,http://www.ellison.com/ +627,Crystal,Howard,Stokes-Boyle,Guerreroshire,Ecuador,gwendolynterry@blanchard.com,4/2/2021,http://www.tapia.com/ +628,Frank,Walter,Barnett-Floyd,Yolandatown,Saint Pierre and Miquelon,leachnina@cantrell.com,3/26/2020,https://morrison.com/ +629,Rhonda,Donaldson,Graham-Blackwell,Autumnland,Christmas Island,ellisontanner@gray-lewis.org,1/31/2021,http://www.simpson-knapp.com/ +630,Lisa,Duran,Brennan-Spencer,East Sabrina,Senegal,hammondcristian@berry.com,2/11/2022,http://www.horne-arias.biz/ +631,Isabel,Cisneros,"Salas, Kelly and Johns",Blakechester,Guinea-Bissau,mccarthyangel@tate-lam.net,4/8/2021,http://ray.com/ +632,Bianca,Jennings,"Cooper, Romero and Mcneil",Ethanburgh,Cambodia,rodriguezralph@herring.biz,9/19/2020,https://ward.com/ +633,Leon,Lang,"Vazquez, Compton and Kane",North Tyler,Czech Republic,grahampaige@carpenter-olsen.com,1/10/2020,https://alvarez.com/ +634,Luis,Zamora,Knox Ltd,Summerstown,Saint Barthelemy,mcleanmichael@arroyo.com,9/25/2020,https://www.yu.info/ +635,Mitchell,Chang,"Mclean, Sheppard and Pearson",Mannport,Saint Pierre and Miquelon,colleenbarnett@hobbs-smith.com,8/6/2020,https://www.mosley.com/ +636,Edgar,Terry,Mosley-Mata,New Ann,Bulgaria,kphelps@trujillo.net,2/23/2020,https://rollins-ewing.net/ +637,Norman,Howard,Carpenter and Sons,Tammyberg,Sri Lanka,peggy04@mack-chambers.biz,7/5/2020,http://www.cisneros-taylor.biz/ +638,Fred,Alvarez,"Roberts, Solis and Carpenter",Port Katherineside,Greece,ccollier@baxter.com,3/26/2022,https://www.walter.com/ +639,Lauren,Nunez,Nichols-Key,Haysmouth,France,harrellkirsten@knox.com,7/20/2020,https://velez.com/ +640,Kristen,Terry,Howe-Mathis,South Rogerville,Niue,qhinton@ferguson.com,1/31/2022,https://www.berg.com/ +641,Olivia,Cross,Richard-Lutz,Rickburgh,Uganda,alec41@stuart.net,2/18/2020,https://zhang.org/ +642,Leonard,Pennington,"Elliott, Combs and Mcpherson",Tammieborough,Guam,victoriacooper@avery-mooney.com,2/13/2021,https://pugh.com/ +643,Nathan,Bryan,Kane Inc,Walshshire,Jordan,brucecarrillo@evans-powell.net,6/17/2021,http://www.richard.com/ +644,Theresa,Gallegos,Spence Ltd,Dorisport,Togo,ybrandt@hahn-mejia.com,4/10/2021,http://estes.com/ +645,Jamie,Hayes,"Harvey, Foley and Rush",Orrfurt,Latvia,max17@estrada.info,12/17/2020,http://durham-sullivan.com/ +646,Wayne,Herring,Brooks PLC,Johnburgh,United States Minor Outlying Islands,maxwellallison@gay-lynn.net,4/15/2021,https://vega.com/ +647,Allison,Franco,Willis PLC,South Cesarburgh,Japan,wileybill@archer-mckenzie.biz,10/20/2020,https://www.curtis.com/ +648,Ronald,White,"Ferguson, Knapp and Mathews",West Normaton,Turkmenistan,mmcclure@wiggins.com,12/14/2020,https://www.dennis-west.com/ +649,Deborah,Taylor,Pope and Sons,Port Roberta,Macedonia,catherine11@fleming-hull.com,11/2/2020,http://www.dodson.com/ +650,Shari,Bender,Lamb-Valenzuela,North Guy,Falkland Islands (Malvinas),mannashlee@kaufman-osborn.com,1/26/2020,http://gallagher.info/ +651,Brooke,Gibbs,"Burnett, Atkins and Norris",East Isabel,Mali,stefanie48@fowler.com,2/6/2021,http://www.hartman.com/ +652,Ashley,Melton,Oneill-Dickerson,West Stacy,Anguilla,dustinbray@edwards.com,6/20/2021,https://www.clark.org/ +653,Teresa,Hart,"Kane, Fry and Landry",Perkinsbury,Aruba,williamschmidt@watts-west.com,12/17/2021,http://www.weeks-hill.info/ +654,Bonnie,Erickson,Drake Inc,Lake Donbury,Rwanda,blakefitzpatrick@preston.com,1/31/2020,http://mcguire.com/ +655,Ruben,Trevino,Mcneil-Hancock,South Linda,Turkey,ksheppard@moody.com,2/25/2020,http://christian-welch.biz/ +656,Fernando,Perez,"Huang, Preston and Stevens",West Melody,New Caledonia,lancearellano@gentry.com,1/6/2021,http://warren.biz/ +657,Katelyn,Cabrera,"Brown, Valentine and Velez",New Miguel,New Caledonia,msantana@lester.com,6/9/2020,https://www.dickerson.com/ +658,Sylvia,Lin,Rivas-Alexander,Lake Juanport,Nauru,stevensmaureen@watts-tapia.biz,5/9/2021,http://www.mcgee-hood.net/ +659,Margaret,Snyder,Sharp-Oconnor,Cliffordburgh,Brazil,mirandaburnett@bradford-ross.com,10/27/2021,http://www.ware.com/ +660,Trevor,Schwartz,Mcdowell-Chang,Bradfordmouth,Isle of Man,graceherring@pratt.org,2/18/2021,https://www.crosby.net/ +661,Bethany,Colon,"Greene, Mcconnell and Frye",Flemington,Djibouti,mallory42@porter-cox.com,11/3/2020,http://cortez.com/ +662,Dustin,Vaughan,"Ramos, Bishop and Montgomery",Port Ebonyhaven,Eritrea,rebecca69@nolan-hines.com,3/3/2021,http://www.ho.com/ +663,Jim,Oconnell,Rodriguez and Sons,Roseport,New Caledonia,charlespriscilla@adkins.biz,1/16/2021,https://parsons.org/ +664,Kerry,Roberts,"Leon, Morgan and Huff",Lake Rachael,Solomon Islands,brettcrawford@griffin.com,1/17/2021,http://www.gates-torres.com/ +665,Allen,Mcgrath,Parker PLC,Dawsonshire,Saint Kitts and Nevis,lisa85@rivera-schroeder.com,5/18/2020,https://walton.com/ +666,Darlene,Ware,Stafford-Green,East Glenn,Grenada,xrosales@odonnell.info,9/12/2021,http://stein.com/ +667,Jane,Roman,Franco Inc,North Grace,Estonia,hollowaymarcia@boone.com,10/18/2020,https://www.cummings.com/ +668,Sheena,Burns,"Wade, Mills and Walters",South Tashatown,India,garrettwheeler@bullock-cervantes.info,1/28/2021,https://garza.com/ +669,Patricia,Cole,"Phelps, Hobbs and Pratt",North Elaine,Comoros,tiffany09@hunt.biz,12/22/2020,https://cardenas-huff.com/ +670,Shannon,White,Mitchell-Castaneda,Huffville,Korea,romanjoyce@ryan-macias.info,2/6/2020,https://www.norton.com/ +671,Mandy,Farley,Lozano-Wilkins,Powersstad,South Africa,duncanmadison@schmitt.com,2/8/2022,http://www.wheeler.net/ +672,Walter,Barber,"Howell, Burgess and Vega",North Suzanneberg,Nigeria,jimmy88@cruz-mcmillan.net,5/3/2021,https://www.jimenez.info/ +673,Summer,Fox,"Bates, Medina and Hudson",Kimberlyberg,Somalia,peckbenjamin@medina.com,1/20/2021,http://lin.com/ +674,Jeffery,Stuart,Tate-Gordon,Coxburgh,France,karl79@shannon.com,9/12/2020,https://www.townsend-bailey.com/ +675,Carla,Saunders,Allen-Brandt,North Darlenemouth,Zimbabwe,bmoreno@fisher.net,10/4/2021,https://spencer.com/ +676,Dustin,Herman,"Bean, Morse and Reed",Georgemouth,Venezuela,gkane@young.com,11/10/2020,http://griffin.com/ +677,Melody,Mckay,Paul Ltd,Claireberg,Czech Republic,bookerdennis@garrett-winters.com,4/29/2022,https://stafford.info/ +678,James,Ward,Marsh and Sons,Michaelashire,Anguilla,sara28@singleton.net,2/25/2021,http://www.ali.net/ +679,Jasmine,Berry,Garcia-Chaney,Port Rick,Djibouti,garzabianca@reed.com,7/22/2021,https://www.kennedy.com/ +680,Vanessa,Webb,"Flowers, Henry and Craig",Hatfieldbury,Sweden,arielbryant@kaufman-frazier.biz,11/13/2020,http://www.mason.com/ +681,Jermaine,Diaz,Hicks-Chandler,South Seth,Montserrat,shelby88@merritt.com,9/6/2020,https://mathis-solis.com/ +682,Mary,Holder,Lynch Group,Lake Jessicachester,Slovakia (Slovak Republic),alexandraforbes@conrad.info,11/18/2020,http://www.hebert.com/ +683,Johnathan,Mclaughlin,"Brewer, Mckinney and Taylor",South Theresafort,Senegal,stefanie30@hurley-wall.com,12/21/2021,https://www.vargas-hammond.com/ +684,Tracey,Massey,"Pitts, Klein and Gregory",North Theodore,Iran,meghan46@james-villanueva.com,10/16/2021,https://proctor.com/ +685,Tristan,Brooks,Pope and Sons,Ryanshire,Antarctica (the territory South of 60 deg S),maynardkatrina@stone.com,12/11/2021,http://www.guerrero-blake.com/ +686,Sara,Gilmore,Guerrero Inc,Lake Fred,Kuwait,wongwarren@riley.com,4/15/2022,http://estrada.com/ +687,Norma,Page,Carroll-May,Kruegerside,Cambodia,sabrinapeterson@webb.net,1/10/2022,http://www.hubbard-bennett.biz/ +688,Levi,Todd,Roberts and Sons,Lake Justin,Cook Islands,cuevasjade@gilbert.com,2/23/2021,https://bradshaw-singh.net/ +689,Anthony,Curtis,Orozco LLC,Tylermouth,Grenada,colleen70@johnston.com,8/31/2021,https://www.cuevas-robles.com/ +690,Kristine,Hawkins,Schwartz-Fernandez,Juanstad,Gambia,natashawarner@arias.com,2/13/2021,https://www.hickman.net/ +691,Ann,Levy,Spence Inc,New Dillonshire,Armenia,svance@mendoza.com,6/28/2021,http://www.frazier-roy.net/ +692,Laurie,Mccoy,"Mullins, Cohen and Atkins",Princechester,Uzbekistan,hughestyrone@medina-morrow.org,2/28/2020,https://lester.biz/ +693,Stephanie,Rivera,"Arroyo, Wagner and Christian",Cervantesmouth,Costa Rica,leonpedro@moss.org,12/8/2020,https://www.horne-larson.org/ +694,Ebony,Yang,Boyd Ltd,East Edwin,Turks and Caicos Islands,lbray@ferrell.com,3/16/2020,http://www.bennett.com/ +695,Norman,Morton,"Vaughn, Beasley and Holland",West Vanessahaven,Cape Verde,jermaine75@fowler-hancock.com,12/31/2021,https://www.hudson.com/ +696,Joann,Holder,"Cochran, Grant and Blake",Margaretfurt,Tuvalu,strongterrance@wolfe.com,1/25/2020,http://www.huffman-frederick.com/ +697,Catherine,Davis,Orr Group,Erikatown,Cape Verde,clinedennis@madden-cox.com,4/12/2020,https://www.dixon-wyatt.info/ +698,Grant,Mercado,Rich-Pugh,West Jessicastad,Papua New Guinea,pgarcia@floyd.com,12/18/2020,http://www.bryan-reyes.com/ +699,Jessica,Aguirre,"Barron, Stark and Hill",Kristinefurt,Guam,garrettmacias@booth.net,9/21/2021,http://cuevas.net/ +700,Trevor,Bowers,Richmond-Cardenas,Juliafort,Bangladesh,joycedarryl@dillon.info,9/25/2020,http://hayes.info/ +701,Autumn,Norris,Peterson-Boyd,New Alvin,Madagascar,mckenzie25@lang-donaldson.biz,6/30/2020,https://escobar.com/ +702,Nancy,Price,Burch-Barr,Darrenmouth,San Marino,mikaylagreen@coleman-cowan.net,5/30/2021,https://harvey.net/ +703,Kevin,Lozano,"Fletcher, Frazier and Baxter",Lake Francisfurt,Netherlands Antilles,jeffrey39@bautista.com,8/22/2020,https://www.mullins-summers.net/ +704,Dale,Hendricks,Levine-Larsen,Carrfurt,Turkmenistan,robertamays@waters-joseph.com,7/9/2020,https://morse.com/ +705,Alexis,Carroll,Foley Inc,New Kristi,Qatar,patricia79@fritz.net,8/1/2021,https://www.salas.com/ +706,Javier,Bowers,"Flores, Rodgers and Flores",New Samantha,Ethiopia,harmonclaire@reilly.com,3/11/2020,http://www.soto.com/ +707,Zoe,Guerra,Hogan-Butler,North Bethanyhaven,Qatar,cnewman@price-morrow.com,5/4/2021,https://berry.com/ +708,Tim,Henson,"Keith, Stephens and Wyatt",Maddenmouth,Armenia,matthew00@sheppard.com,11/5/2020,http://www.vazquez.com/ +709,Tim,Larson,"Hurley, Bentley and Acosta",Lake Lindaton,Papua New Guinea,toni70@santiago.com,10/5/2021,http://price-roberson.biz/ +710,Brian,Banks,Solomon-Cortez,Lyonsberg,Cambodia,fordholly@mason.biz,10/18/2021,https://www.swanson-oliver.com/ +711,Destiny,Cooke,Mueller-Kent,North Shaun,Saint Pierre and Miquelon,wandaruiz@hurley.com,11/9/2021,http://www.kramer.com/ +712,Austin,Bright,Aguilar PLC,Rebekahshire,Dominican Republic,ryanapril@francis-rowe.com,5/5/2022,https://brennan.com/ +713,Briana,Davis,Stone-Floyd,Castanedamouth,Kiribati,jermainefleming@vazquez-hughes.info,8/21/2020,http://harrell.com/ +714,Darius,Lara,Castillo-Lang,East Yolanda,Northern Mariana Islands,ualexander@braun.org,5/4/2020,http://moon.net/ +715,Toni,Rhodes,"Meyer, Lamb and Flynn",Deniseburgh,Tonga,ayalapamela@reilly.biz,9/3/2021,http://www.small-khan.com/ +716,Warren,Willis,Small-Wade,South Gracebury,Mongolia,qnunez@vincent-gregory.info,4/19/2022,http://ibarra-oneill.biz/ +717,Colleen,Cordova,Glover-Pearson,West Marilynchester,Slovakia (Slovak Republic),hardywesley@barber.biz,12/6/2021,http://rose.com/ +718,Marie,Norman,"Bush, Knapp and Gaines",Alexaside,Tunisia,ogoodman@small.com,11/8/2021,https://horn.com/ +719,Jasmine,Morrison,"Morrison, Graves and Odom",Skinnerland,Guadeloupe,mhayes@moss-martinez.com,4/9/2020,https://james.com/ +720,Catherine,Perkins,Meyers and Sons,West Gabriela,Honduras,ebony76@odom.org,5/11/2021,http://jordan.com/ +721,Nancy,Williams,Gordon-Hayes,Masseytown,Burkina Faso,mcmillankarla@jarvis.com,4/27/2021,http://morrow.com/ +722,Noah,Hebert,Gomez-Lester,Ortizside,Tanzania,abarrett@hodges-anthony.org,7/8/2020,http://www.powers.org/ +723,Latoya,Gillespie,Peters-Cruz,West Coreyview,Haiti,aprilbranch@knight.com,3/23/2022,https://cantrell.com/ +724,Chelsea,Oneal,"Haney, Curry and Griffith",Mcdanielhaven,Antarctica (the territory South of 60 deg S),prestonbarnett@bell-haynes.com,8/20/2020,https://www.turner.com/ +725,Katie,King,Schaefer-Bullock,East Rodneyland,Malta,potterdamon@mccall.com,8/20/2021,http://christian.com/ +726,Colin,Doyle,"Carroll, Walsh and Benjamin",Lesliefurt,Liechtenstein,allenbrady@robbins.biz,4/20/2020,http://www.jimenez.biz/ +727,Sonya,Schroeder,Curry Inc,Petersburgh,Nicaragua,kayla90@andersen-huber.com,8/7/2021,http://bowman.com/ +728,Kayla,Guzman,"Ali, Fleming and Madden",Kiddfort,Central African Republic,patricksteve@fleming.info,4/28/2022,https://lozano.com/ +729,Christie,Jenkins,Lloyd Group,East Angieside,Dominica,breese@oneill.com,7/22/2021,https://aguirre.biz/ +730,Joanne,Ferguson,"Reed, Navarro and Barber",Baileyside,Hungary,haley88@contreras-farmer.com,7/4/2020,http://madden.net/ +731,Yolanda,Robinson,Carey Ltd,West Tyronestad,Wallis and Futuna,daniel92@lynch.com,12/20/2021,https://mcbride.org/ +732,Katie,Craig,Garrison-Gordon,North Jamiemouth,Oman,ronaldwheeler@figueroa-hendrix.net,4/7/2020,https://www.tyler-harding.com/ +733,Dakota,Chavez,"Byrd, Hart and Pham",Brewerbury,Azerbaijan,chanpatty@mclaughlin.com,5/30/2021,http://mann.com/ +734,Johnny,Randolph,"Rivas, Maxwell and Farley",East Daniellestad,United States of America,martindoyle@villegas.net,5/25/2021,http://www.tapia.com/ +735,Collin,Alvarez,Ho LLC,Port Lawrence,Jordan,ritablake@shepard.biz,2/7/2022,http://richmond.com/ +736,Cassidy,Melton,"Guerra, Boyd and Palmer",Lorettaland,Guinea,qmooney@banks-huber.net,3/14/2020,https://www.rich.com/ +737,Adam,Singh,Johns Group,Lake Mckenzieville,Bolivia,chandlerluis@forbes-dickson.org,10/11/2021,https://www.hodge.org/ +738,Hayden,Pugh,Woodward-Guerra,New Marc,Israel,aprillewis@pham-tanner.com,10/25/2021,https://www.rosales.org/ +739,Kellie,Salas,"Cross, Kennedy and Bray",East Jeanne,Turkey,peter54@bentley-morrison.com,11/16/2020,http://www.fletcher.net/ +740,Caleb,Watkins,Nelson LLC,Levihaven,Luxembourg,isaiah61@vazquez.com,7/31/2021,https://www.fowler.com/ +741,Troy,Fritz,Mercado PLC,South Taylor,Cuba,rmartinez@huber-larsen.info,10/13/2021,http://riggs-estes.net/ +742,Phillip,Duffy,"Booker, Ritter and Daugherty",Lake Emily,Saint Lucia,christian65@mayo.com,12/31/2020,http://www.conner.net/ +743,Claire,Robinson,Lynch Inc,West Kevin,Tonga,sbeck@gray.com,4/16/2021,http://www.pace-garrett.biz/ +744,Jackson,Estrada,"Webster, Weeks and Vasquez",Chadborough,Argentina,larry07@long-banks.biz,10/12/2021,https://duarte.com/ +745,Tiffany,Peterson,"Chambers, Montoya and Gray",New Christianburgh,Colombia,alisonali@gibson-paul.com,7/9/2020,http://www.beasley-floyd.com/ +746,Jessica,Carlson,Black Group,Salazartown,Grenada,floydandrew@chang.org,11/13/2020,https://mccullough-brown.com/ +747,Alvin,Roy,Thornton Group,Brittneyton,Israel,gerald08@farrell-roy.com,1/5/2021,https://www.hurst.com/ +748,Harold,Paul,"Griffin, Stanton and Clements",Staffordhaven,Christmas Island,juarezphyllis@cunningham-hodge.org,1/24/2022,https://www.vargas.org/ +749,Rodney,Underwood,Griffin-Lam,Yvonnemouth,Iceland,tracihayden@clarke-little.biz,7/21/2020,https://olsen.info/ +750,Adrian,Herman,Terrell-Schultz,North Beverly,Iraq,gutierrezbob@paul-mccoy.com,1/29/2021,https://www.bruce-fields.net/ +751,Clifford,Phillips,Ramsey LLC,Lake Blake,Martinique,andersongrace@odom.org,3/1/2021,http://friedman.com/ +752,Regina,Mccoy,"Ponce, Malone and Waller",Burchchester,French Polynesia,rushshari@day-singh.com,10/13/2021,http://moran.com/ +753,Kaylee,Bowers,Horn LLC,Johnsonland,Pakistan,billymiranda@knight.biz,2/15/2022,https://lee.com/ +754,Reginald,Frederick,"Burke, Hopkins and Bradley",Douglasstad,France,robert91@maldonado-knox.net,1/20/2020,https://www.pruitt-anderson.biz/ +755,Shelby,Reese,Walton-Greene,New Margaret,Tunisia,charlene99@dixon.com,4/9/2021,https://www.krueger-hanna.com/ +756,Connor,Gentry,"Maldonado, Lowe and Espinoza",West Cristian,Trinidad and Tobago,igraham@flowers-stein.biz,2/11/2021,http://www.mora-hanna.com/ +757,Kevin,Fleming,Frye-Haney,New Shannonstad,Trinidad and Tobago,justin34@ruiz-wood.info,2/26/2022,http://www.hampton-chapman.com/ +758,Connie,Mercado,"Weeks, Perez and Andersen",New Vincentstad,China,gallagherfrank@fitzgerald.com,2/13/2022,http://www.petty.biz/ +759,Kaylee,Barber,Odom-Nolan,Solistown,Chile,nicholasmaldonado@west.com,4/21/2021,https://pitts.biz/ +760,Betty,Duncan,"Raymond, Frazier and Burton",Jacobsonchester,Denmark,joel57@singleton.org,6/7/2020,https://ford.com/ +761,Dwayne,Sweeney,Roth-Griffith,North Kenneth,Bolivia,rbryant@burnett.com,6/8/2021,https://www.lin.info/ +762,Kenneth,Anthony,"Hale, Payne and Saunders",Fletcherborough,Tanzania,piercefranklin@livingston-bass.biz,3/10/2020,https://swanson.biz/ +763,Peggy,Montoya,Oconnell PLC,Port Brandyside,Poland,goldengilbert@goodman.org,4/6/2021,https://petersen.com/ +764,Jonathan,Briggs,Atkins-Atkinson,Lake Aprilchester,Japan,dicksonshannon@levine.net,3/29/2020,https://www.ewing.com/ +765,Sean,Gray,Larsen-Payne,Berrymouth,China,drewmcgrath@bowen.com,5/21/2022,http://www.schroeder.org/ +766,Rebekah,Villegas,Casey Inc,Langberg,Venezuela,erik89@woodard.biz,4/26/2021,http://www.avery.com/ +767,Lisa,Small,Lane-Daniel,New Xavier,Micronesia,chungsharon@zavala-bond.info,8/8/2021,https://franco-wright.info/ +768,Paige,Pineda,"Cole, Huynh and Vang",Port Judith,Palau,hughescesar@pacheco.com,4/10/2022,https://pearson-wyatt.com/ +769,Howard,Glass,"Carter, Wiggins and Paul",New Kelliechester,Brazil,ykeith@lloyd.biz,2/26/2021,http://www.fox.org/ +770,Jose,Bond,Tucker Group,Lake Lydiatown,Mongolia,roberta05@bates.com,3/23/2021,http://hurst.biz/ +771,Janet,Bass,Hudson LLC,Port Lindsey,Christmas Island,yorkjermaine@noble-hayes.net,4/18/2020,https://www.huffman.com/ +772,Jasmin,Waters,Chandler-Holt,South Marisachester,Hungary,pclark@ortega.com,5/26/2022,https://costa-owens.com/ +773,Jennifer,Mcintosh,"Glover, Keith and Lozano",Simonside,Aruba,bianca43@nixon.com,3/18/2022,https://www.farley-powers.com/ +774,Gina,Benson,Lawson Group,Colechester,South Africa,geoffreywagner@willis-macias.com,7/17/2020,http://www.vargas.com/ +775,Vanessa,Gardner,Silva-Bauer,Meghanstad,Italy,haley36@maynard-richard.biz,12/22/2021,https://mclaughlin.com/ +776,Brenda,Clark,Coleman-Bishop,Noblemouth,Thailand,taylor22@chavez.net,1/29/2022,http://www.mayo-mosley.net/ +777,Chelsea,Randolph,Nixon Ltd,New Candice,Kyrgyz Republic,corey96@conner.com,5/22/2021,https://www.nelson.net/ +778,Tabitha,Logan,Hodges-Cummings,New Lance,Myanmar,melvinwilkerson@serrano-ochoa.com,6/15/2020,http://www.allen.org/ +779,Anne,Cabrera,Gay Inc,Toddtown,Australia,sabrina88@buchanan-richards.net,10/16/2020,http://www.kramer.com/ +780,Logan,Lamb,Chavez-Haas,North Erin,Korea,wsutton@vega.com,3/29/2020,http://case.com/ +781,Reginald,Mann,Pitts-Weeks,East Brandon,Netherlands,gabriela19@rivera.net,5/12/2020,http://weeks.org/ +782,Bob,Rosario,Spencer Ltd,Fowlerfort,Turkey,elijah10@zimmerman.com,7/4/2020,http://www.bush.com/ +783,Andrew,Weiss,Stewart Inc,New Pam,Nicaragua,monica69@cohen.org,2/16/2021,http://dalton-lucero.com/ +784,Rita,Livingston,Valentine Inc,North Whitneyborough,Guyana,earias@rangel.info,11/11/2020,http://browning-wiggins.biz/ +785,Briana,Peck,Salas LLC,Port Curtis,Chile,miguelwilkinson@shaffer-beasley.com,12/28/2020,http://curry.com/ +786,Brady,Mcdaniel,Knapp-Rodgers,Debraberg,Costa Rica,echapman@small.biz,5/17/2022,http://www.espinoza.com/ +787,Clifford,Rivers,Black-Lam,West Michelle,Niue,khoover@atkins.com,10/14/2020,https://ho.com/ +788,Kathryn,Burgess,Aguirre and Sons,South Andre,Uruguay,hayden76@estrada-michael.info,7/7/2021,https://fitzpatrick-mason.com/ +789,Rodney,Esparza,Mckay Group,East Ricky,Holy See (Vatican City State),bkirby@arellano.com,6/12/2021,https://www.grimes.net/ +790,Gabrielle,Vincent,Newton LLC,North Perryshire,El Salvador,isaacbyrd@chang-mcdonald.com,10/26/2020,http://lozano-macdonald.info/ +791,Yolanda,Schroeder,Johnston Ltd,Malikchester,Mauritius,andresgrimes@graves.org,12/7/2021,https://www.fritz.biz/ +792,Jonathon,West,Rice-Nichols,North Staceyport,Heard Island and McDonald Islands,maureen96@heath.info,8/15/2020,http://davidson-reyes.com/ +793,Lee,Harvey,Obrien Ltd,Port Leon,Ireland,tracie58@marsh.com,5/28/2021,https://howard.net/ +794,Yvette,Mccoy,"Reeves, Harding and Bowman",East Monica,Morocco,aatkinson@suarez.biz,3/14/2021,https://www.wolfe.com/ +795,Kayla,Fleming,Gregory Group,Clarketown,Saint Barthelemy,emma49@dunlap.biz,1/12/2021,https://www.joyce.biz/ +796,Vernon,Flowers,"Lawrence, Villegas and Sweeney",New Terryton,Greece,xkeller@cole.com,9/3/2021,http://collier.org/ +797,Bobby,Dodson,Fuller PLC,Mariamouth,Guyana,eugenemarsh@english.com,3/30/2022,http://www.meyers.com/ +798,Lindsey,Blevins,Fischer-Garrison,East Nathanielview,Reunion,lacey67@weeks.net,8/11/2021,http://www.hodges-rojas.biz/ +799,Jared,Howe,"Pacheco, Dennis and Velazquez",New Diane,Austria,larryhoffman@jarvis.info,1/11/2022,http://www.green-huffman.com/ +800,Mike,Mckay,Aguilar-Carney,Lake Lydiafurt,Samoa,sschroeder@proctor.com,2/16/2022,https://cantrell-conner.info/ +801,Leon,Summers,Owen Group,Holmeschester,Guernsey,shelia48@salazar-petty.com,8/26/2020,https://arnold.com/ +802,Darren,Gibbs,Baldwin-Best,Glennchester,Saint Barthelemy,maddoxdevin@burch.com,6/29/2021,http://www.ward.info/ +803,Chase,Lucas,"Mills, Esparza and Carpenter",Johnmouth,Saint Helena,cannontyler@townsend.com,2/15/2020,https://hobbs-hendricks.com/ +804,Kent,Cobb,Wolfe Inc,Schwartzchester,Belize,tracicabrera@park.com,6/17/2021,http://www.knight-ferguson.biz/ +805,Miranda,Lawson,Baldwin PLC,West Claudiahaven,Montenegro,audreymarks@howell.com,1/31/2022,https://norman-goodman.com/ +806,Kathy,Nguyen,Vaughan LLC,Kelleyborough,Cape Verde,breanna07@maddox.com,5/29/2021,http://www.sandoval.com/ +807,Jeremiah,Rubio,Chambers Group,Caseyborough,Russian Federation,wblake@oliver.com,4/25/2020,http://www.flowers.com/ +808,Gilbert,Barry,Parker-Nguyen,Gilmoreport,Central African Republic,sheenaboyle@stephens.net,8/22/2021,http://hinton.com/ +809,Tony,Shannon,Gutierrez PLC,Acevedomouth,Solomon Islands,bailey25@parks.net,6/23/2021,https://www.gallegos.org/ +810,Dana,Salas,Barton Ltd,Lake Tabitha,Liberia,prattdouglas@todd-wolf.info,2/27/2021,https://www.glass.biz/ +811,Benjamin,Galvan,Ewing-Everett,Masseymouth,Antarctica (the territory South of 60 deg S),masoncarson@meadows-deleon.com,10/13/2021,http://www.may.com/ +812,Teresa,Vargas,Sherman-Miranda,Velasquezstad,Finland,tim18@daugherty-raymond.com,10/2/2021,http://www.shea.com/ +813,Tricia,Leon,Lutz-Spears,Lake Tannerside,Antigua and Barbuda,brendantodd@curtis-gross.com,11/4/2021,http://sawyer-ellison.org/ +814,Carmen,Daugherty,Russo Ltd,Batesport,Saint Helena,riosraven@case.com,3/21/2020,http://www.sullivan-fleming.com/ +815,Tiffany,Rowe,Maynard-Mcbride,Smithview,Belgium,alice75@burke-peck.net,4/20/2022,http://www.mcbride.com/ +816,Wesley,Snow,Higgins LLC,East Melinda,Turkey,farleygene@white.biz,3/7/2021,https://www.blanchard.com/ +817,Raven,Mejia,Fisher-Lopez,Duncanside,Antarctica (the territory South of 60 deg S),princeadrienne@patrick-brewer.com,9/24/2020,http://www.michael.com/ +818,Elijah,Richardson,Fowler-Owen,Willietown,Cambodia,moonbeverly@leblanc.com,10/19/2020,https://www.ball-bradley.com/ +819,Colton,Sandoval,Villarreal-Lang,Zavalaview,Gabon,mosleyheidi@walter.com,2/18/2021,http://www.perkins.com/ +820,Krista,Mcclure,Simon and Sons,North Marissatown,Iceland,wyattwinters@cole.org,5/30/2021,https://zavala.com/ +821,Johnny,Rubio,"Porter, Johnston and Mullins",New Melodymouth,Thailand,skaufman@cline.com,1/6/2021,https://www.yu.com/ +822,Amy,Ballard,Rice Group,Briannaland,Canada,brockoscar@cole.org,3/1/2021,http://lane.com/ +823,Rachel,Watts,Holloway-Nolan,Aaronville,Gibraltar,kelliroy@sawyer-barker.com,5/12/2021,https://www.jordan-wolf.info/ +824,Judy,Mullins,Quinn Inc,Lake Shelby,American Samoa,hdonaldson@harmon.com,8/27/2021,https://www.mcneil.com/ +825,Priscilla,Huff,Saunders and Sons,New Brettmouth,Malta,gregoryrobyn@summers.com,9/13/2021,http://munoz.biz/ +826,Zoe,Solomon,Meyers-Odonnell,Bettyland,Wallis and Futuna,angelicaarias@harmon-cabrera.info,9/16/2021,https://www.bender-church.org/ +827,Gabriella,Ho,Sawyer PLC,Danielview,Chad,ericperry@simon.com,4/19/2022,http://knight.info/ +828,Jeff,Jensen,Haynes Group,Josephfort,Indonesia,alice96@payne.com,5/24/2021,https://mckay.com/ +829,Natalie,Ball,Garrett PLC,Port Laurie,Burundi,scottdrew@vazquez.com,4/11/2022,http://boyd-rios.com/ +830,Matthew,Fields,Sullivan-Archer,North Sabrina,Anguilla,eriksnyder@giles-bowers.com,4/11/2020,https://www.kennedy.com/ +831,Jack,Mendoza,"Cardenas, Bass and Callahan",Quinnfurt,Puerto Rico,kmccullough@bryant.com,5/28/2022,https://colon.net/ +832,Sheryl,Lyons,Henson-Trevino,South Roberta,Algeria,mcfarlandrobin@callahan-wilkins.com,4/7/2022,http://schmitt.com/ +833,Nina,Contreras,"Mcknight, Horne and Thornton",New Savannah,Yemen,willisemily@kidd.com,5/6/2020,http://mcclain.com/ +834,Samuel,Walker,"Gallegos, Paul and Williamson",Ginaport,Gabon,grace28@bullock-mcpherson.com,1/9/2021,https://nielsen.info/ +835,Charlotte,Spencer,Chambers Ltd,Port Tyronemouth,Antigua and Barbuda,ashleyrubio@holder.org,7/1/2020,http://farley.biz/ +836,Eduardo,Schultz,"Valdez, Pierce and Compton",Christianbury,Lao People's Democratic Republic,monique25@scott.com,4/2/2020,https://www.ball-oneill.net/ +837,Adrienne,Medina,"Benitez, Wilkinson and Mooney",Darrellstad,Nepal,edwardsjoyce@freeman-chang.info,3/28/2022,https://www.french-bennett.com/ +838,Darrell,French,"Macias, Dodson and Blackwell",Vegaview,Malaysia,mayerdaryl@krueger.com,2/19/2020,http://www.baxter-holloway.com/ +839,Rodney,Dunn,Poole LLC,Hardingshire,El Salvador,candacecalderon@green.com,12/24/2020,https://www.meadows.com/ +840,Bryan,Sampson,Clark-Murray,Stricklandborough,United States of America,allison44@bonilla-schmidt.info,2/6/2021,https://www.arnold.com/ +841,Gregory,Baxter,Levine Inc,Schultzfort,Senegal,mcochran@woods-norton.org,8/24/2020,http://www.hooper.com/ +842,Edgar,Norman,Gould-Heath,Lake Kayleeville,Yemen,orangel@bennett.com,5/4/2022,http://crawford.com/ +843,April,Garner,"Mckay, Moody and Rowland",Erikamouth,Hong Kong,maloneclayton@figueroa.org,3/3/2022,https://www.noble-warner.com/ +844,Mercedes,Bush,Davies-Johnson,North Katelyn,India,patricia64@ballard.com,2/4/2020,https://www.terrell-shaffer.info/ +845,Theresa,Randolph,Gilmore-Carr,Janicefort,British Virgin Islands,hcortez@greene.com,7/27/2021,https://www.house.biz/ +846,Tom,White,"Golden, Guzman and Webster",Lake Sean,Reunion,moniquemcneil@dudley.com,2/27/2021,https://www.salinas.biz/ +847,Tiffany,White,"Jacobs, Griffin and Fuller",Pambury,Qatar,dorisfritz@curry.org,9/7/2021,http://love-ross.info/ +848,Lacey,Clark,Wells-Riley,West Lydiamouth,Belgium,mcbridebrad@kirk.com,2/16/2020,http://mayo-cline.net/ +849,Gail,Thornton,Hines Group,Julieport,Cocos (Keeling) Islands,melinda57@pham.com,11/15/2021,http://carson.com/ +850,Leslie,Figueroa,Norris-Randall,Dillonmouth,Zimbabwe,geoffreysteele@rios-morrow.net,2/23/2020,http://ware.com/ +851,Katie,Mcintosh,Gibson and Sons,New Darrenfort,San Marino,stephenharper@wall.com,7/16/2021,http://curry.com/ +852,Kelly,Bradshaw,"Horn, Sherman and Barry",West Raven,Uganda,tgill@novak-patton.com,6/18/2021,http://leach-carlson.net/ +853,Blake,Avila,Bernard Inc,New Frances,Morocco,alexander77@mcdonald-daniel.info,6/22/2020,http://www.mosley.com/ +854,Jesse,Miranda,Johnson Group,Ellisland,Nigeria,richmondkeith@kelley-young.com,8/24/2020,http://acosta-grimes.com/ +855,Kristi,Thompson,"Lyons, Arroyo and Nash",New Lanceview,Antigua and Barbuda,ericalee@stanley-waller.net,9/15/2020,http://lawson-deleon.com/ +856,Isabel,Snow,"Cook, Lee and Maldonado",North Lynntown,Cyprus,ralphmcdonald@ware-bryan.biz,3/11/2020,https://mays-gentry.net/ +857,Donna,Sims,"Buckley, Bond and Parsons",North Davechester,Pakistan,mackenziemaldonado@colon.com,1/23/2021,https://fritz.com/ +858,Connor,Thomas,Wilcox-Edwards,Janiceview,Austria,darryllucero@herrera-melendez.com,11/13/2020,https://www.golden.com/ +859,Chloe,Hanson,Nguyen PLC,Daisytown,Svalbard & Jan Mayen Islands,mullinsrita@whitney-frey.com,11/14/2021,https://jimenez.info/ +860,Drew,Guzman,Chung Ltd,West Kirkbury,Guatemala,morrisonann@levy-gillespie.com,3/12/2021,https://bailey-lang.org/ +861,Clifford,Conway,French LLC,Brandtshire,Chad,kerrynewman@walters-herrera.com,5/12/2021,https://coleman-kline.com/ +862,Ashlee,Carney,"Fitzgerald, Rios and Stewart",Fullerbury,Estonia,vguerra@newman-higgins.com,2/4/2021,http://www.huffman-howard.com/ +863,Jackie,Mccullough,Davidson Inc,West Randallshire,Guatemala,spencergalloway@rivers.com,3/6/2022,https://www.zavala-stephenson.info/ +864,Crystal,Dougherty,Rodgers-Kelly,Alfredshire,Swaziland,stuart08@grant.com,1/26/2022,https://barnes.net/ +865,Caitlyn,Tapia,Farley LLC,Ewingchester,Anguilla,cartersally@moon.com,4/14/2021,https://morse-ellis.com/ +866,Brenda,Estrada,"Lee, Pearson and Parsons",South Gilbertmouth,Mali,hardinrebekah@burton.org,2/17/2020,http://marks.info/ +867,Tina,Andrews,Dickerson and Sons,Charlenebury,Chile,karlflynn@riley.com,2/3/2022,https://conway-lowery.com/ +868,Andre,Cunningham,Cervantes Inc,Lake Jesus,Liberia,juliannguyen@herrera.com,3/5/2020,https://www.leonard.com/ +869,Jesus,Abbott,"Pratt, Durham and Conley",Port Jermaine,Jamaica,pkent@gillespie.com,11/7/2020,http://www.conley.com/ +870,Kendra,Bishop,Vasquez PLC,Maysberg,Armenia,greg54@vang.com,7/13/2021,https://vargas.com/ +871,Tyrone,Ayers,Welch Inc,Bryantland,Barbados,pamelatorres@walsh.com,9/12/2020,http://ballard.com/ +872,Isaiah,Andersen,"Lang, Solis and Cunningham",Laurieport,Oman,vparrish@marsh-kane.com,5/2/2021,https://macdonald.com/ +873,Shelia,Rojas,"Conley, Moody and Maddox",West Ana,Marshall Islands,gonzalezkari@dominguez.com,5/11/2021,https://www.meyers.com/ +874,Donald,Arnold,"Dunn, Holt and Flowers",Bonnieport,Congo,isaiahescobar@wyatt-baxter.net,4/30/2021,http://merritt.com/ +875,Kaitlin,Fowler,"Merritt, Duarte and Marshall",Wilkinsonton,Georgia,nathan99@patel.net,6/26/2020,https://www.blair.com/ +876,Mallory,Browning,"Yang, Waller and Castillo",Lake Faithton,Equatorial Guinea,rfuller@hays.org,1/16/2020,http://www.day-stevenson.biz/ +877,Cathy,Compton,Lara Ltd,Johnstonfurt,Somalia,kathleen13@cobb-durham.biz,5/31/2021,http://www.cox.com/ +878,Larry,Blanchard,Macias-Cole,Trujilloside,Isle of Man,rhondachang@hooper.com,3/14/2022,http://www.oneal-stewart.net/ +879,Robert,Henderson,Cuevas Group,East Kristy,Mauritius,bauerfernando@lester-baldwin.info,4/12/2022,http://www.luna.info/ +880,Glen,Simon,Cruz and Sons,Lake Marcfurt,Zimbabwe,rbrown@blair.com,6/3/2021,http://www.tapia.com/ +881,Holly,Nelson,Valentine Ltd,Monicatown,Christmas Island,sarah29@mcdonald.biz,1/10/2021,https://ortega-vasquez.biz/ +882,Erin,Wall,Richards Ltd,Hancockburgh,Korea,finleyangie@carson-medina.com,12/25/2021,https://harrell.biz/ +883,Chloe,Durham,Colon LLC,Derrickland,Guyana,isabel97@small-pope.net,3/28/2022,https://www.reese.net/ +884,Jeanette,Schwartz,Mendoza-Garrison,Jeffport,Suriname,brittany40@ho-jenkins.com,1/12/2020,http://www.leach-sparks.com/ +885,Max,Clements,Phillips-Stanley,Riosville,Haiti,garyglenn@richard-kirby.info,2/10/2021,https://mcdonald.com/ +886,Julie,Coffey,Walls Inc,East Elizabethfort,San Marino,wbrady@hayes.com,3/9/2020,https://www.elliott.info/ +887,Regina,Gilmore,"Wright, Rogers and Chandler",Bellchester,Northern Mariana Islands,joannejohns@rich.biz,4/20/2020,http://www.paul-duarte.com/ +888,Lacey,Stark,Jefferson-Larson,East Emily,Switzerland,vcombs@curry-benjamin.com,9/9/2020,http://ball-patterson.com/ +889,Devin,Jefferson,Moyer-Martinez,Dickersonville,Argentina,tdaniels@cordova-baird.net,8/19/2020,http://www.oconnell-burgess.com/ +890,Shawna,Spencer,Gallegos-Eaton,Kelliville,Malawi,morgan50@phelps-owens.com,12/24/2021,http://tanner.com/ +891,Edgar,Meyers,"Ochoa, Durham and Fowler",Miguelhaven,French Southern Territories,robersonclifford@byrd-salas.com,2/25/2022,http://www.joyce-mcconnell.org/ +892,Levi,Larsen,Davenport-Roy,Costaville,Cayman Islands,brandihickman@lyons.com,12/2/2020,https://briggs.net/ +893,Victor,Simon,Washington-Horne,Maddoxland,United Arab Emirates,teresaholt@coffey.info,11/9/2021,https://frye.org/ +894,Lacey,Boone,Kim-Nash,Lake Craigchester,Belize,estuart@stein.com,11/18/2021,https://espinoza.com/ +895,Chelsea,Lester,Vaughan and Sons,Taylorport,Nepal,murrayeric@mcgrath.com,5/15/2021,http://www.williams.biz/ +896,Dominique,Benjamin,Brooks-Estrada,Jimmybury,Liechtenstein,jackson23@ashley.com,9/15/2021,http://www.ho.com/ +897,Cheyenne,Marshall,Molina-Love,Waltershire,Aruba,debbiedeleon@barber-flynn.info,11/7/2021,http://www.rowe-flores.com/ +898,Frances,Vance,"Montoya, Munoz and Riggs",North Evelynmouth,Ukraine,rlutz@dodson.org,10/31/2021,https://www.galvan.com/ +899,Melvin,Todd,Padilla LLC,West Kristin,Mauritania,brian89@casey.info,3/12/2022,http://www.morse.com/ +900,Gilbert,Larson,"Lambert, Olsen and Kent",Vanessabury,Gambia,alexander26@middleton.com,1/24/2022,https://abbott-wong.com/ +901,Tim,Hawkins,Frederick-Rollins,New Maryfort,Saudi Arabia,kenneth60@mcgrath.net,9/1/2020,https://www.pace-fernandez.com/ +902,Peter,Glass,Lynn and Sons,New Herbert,Bahrain,pamela16@tapia-wolf.biz,12/25/2021,http://gould.org/ +903,Dan,Shepard,Tucker-Hensley,East Shelleyfurt,Gabon,richardkatherine@fields.com,4/29/2022,http://rose.com/ +904,Crystal,Garcia,"Norman, Church and Cortez",Port Calebhaven,Armenia,ymejia@wolfe-vargas.com,6/7/2020,https://bonilla-blankenship.com/ +905,Helen,Diaz,"Haney, Wong and Hines",Sloanstad,Sri Lanka,ethornton@francis.com,12/20/2021,https://choi.info/ +906,Christine,Cervantes,"Greer, Buckley and Macdonald",Billytown,Timor-Leste,alexander60@bruce.net,1/10/2021,http://rocha-yates.com/ +907,Jeffery,Zhang,"Merritt, Adkins and Hendricks",Port Jodi,Angola,ujarvis@watts.com,5/14/2020,https://hunter.net/ +908,Mario,Nolan,Stuart-Livingston,North Dariusview,Vanuatu,pamelaneal@valencia.com,7/30/2020,https://shea.biz/ +909,Abigail,Cochran,"Valentine, Foster and Church",North Shannon,Switzerland,elijahcherry@mann.com,3/1/2020,http://larson.com/ +910,Darlene,Nelson,"Haynes, Rasmussen and Shelton",New Julia,Fiji,sbarton@shepard.com,9/13/2021,http://www.haley.org/ +911,Dwayne,Davidson,Crosby Inc,Campbellside,New Caledonia,johnsonterry@powers.net,11/14/2020,https://www.johnston.info/ +912,Gregory,Osborne,Davidson-Collins,Gabrielleside,Romania,maureenstanley@nolan.com,7/16/2020,http://www.clay-mckinney.net/ +913,Latoya,Clements,Russell LLC,Orrview,Congo,edowns@cantu-rodgers.com,5/16/2022,http://huber.org/ +914,Brooke,Savage,"Durham, Prince and Cantrell",Theresaland,French Polynesia,jonathan71@bush.com,4/2/2020,http://www.odom.com/ +915,Savannah,Grimes,Edwards Group,Shellyfurt,Guadeloupe,diana73@gilbert-schneider.biz,8/13/2020,https://www.braun.com/ +916,Donna,Webster,"Fernandez, Wong and Briggs",Yvetteville,Comoros,krausetom@buckley.org,3/10/2022,http://herrera.com/ +917,Jim,Ochoa,"Watts, Yates and Sutton",South Tom,Greece,carlosodom@schultz.org,3/23/2020,http://proctor-martin.com/ +918,Leslie,Spencer,"French, Estrada and Decker",West Ralph,China,huffcaitlyn@charles.com,11/30/2021,https://crawford.com/ +919,Ariana,Harmon,Steele-Osborne,North Collin,Lesotho,joshua45@dillon.org,1/18/2020,https://www.garcia.net/ +920,Paige,Wong,Lam-Cardenas,Laurabury,United Arab Emirates,hammondcaitlyn@aguirre.biz,3/22/2022,https://williams.net/ +921,Darryl,Burnett,Acevedo-Drake,Mckenzieton,Ecuador,emercado@boone-craig.com,12/30/2021,https://spears.net/ +922,Kristopher,Glass,Weeks Ltd,Hancockshire,Lithuania,aprilblevins@cummings-parrish.info,3/25/2020,http://le.com/ +923,Michael,Huff,"Carroll, Ballard and Zhang",West Bryan,Luxembourg,traci55@golden.com,8/12/2020,https://cervantes.com/ +924,Douglas,Wilkerson,Salazar-Kelley,New Marcus,Netherlands,epope@powell.com,5/24/2020,https://preston-willis.org/ +925,Tanya,Howard,"Pierce, Riddle and Black",South Alisha,El Salvador,kmelton@strong.biz,3/27/2022,http://www.martin.com/ +926,Christy,Franklin,Landry-Martin,Oliviatown,Angola,coltonarellano@boyle.com,4/15/2022,http://www.durham.biz/ +927,George,Dorsey,Castillo-Lester,East Alan,Switzerland,villanuevalogan@murillo.info,2/27/2021,https://www.stanton.biz/ +928,Colton,Nixon,Duncan PLC,Kanemouth,Ghana,nmccullough@vasquez.info,8/15/2021,https://morton-kim.com/ +929,Daniel,Cobb,Richardson-Woodward,Haynesmouth,Anguilla,barbaranorman@vazquez.com,1/28/2020,http://shields.biz/ +930,Cristian,Ball,"Evans, Mahoney and Campbell",Port Larryshire,Nauru,vwagner@blevins-alexander.biz,8/5/2021,https://www.velez.com/ +931,Francis,Goodman,Ortiz-Morgan,Lake Thomas,Afghanistan,marthayoung@ellis.net,2/8/2022,https://rowland-hendricks.com/ +932,Dylan,Boyer,"Little, Stanley and Mcbride",New Bradybury,Anguilla,randy44@raymond.com,1/30/2021,https://jennings.net/ +933,Olivia,Cooke,Norton and Sons,New Andrea,Cayman Islands,gbender@brown-baxter.com,7/17/2020,https://www.stevenson.biz/ +934,Dana,Cohen,Bridges-Moyer,Port Gavin,Andorra,herbertparker@wallace.biz,4/18/2022,http://www.mckay.com/ +935,Megan,Mills,"Hale, Elliott and Richard",Conradport,Montenegro,gavinhaas@harrison-barton.info,4/9/2021,http://kramer-henry.com/ +936,Daniel,Hurst,Cooper LLC,Livingstonview,Malaysia,saundersriley@aguilar.com,10/3/2021,https://www.riddle-goodwin.com/ +937,Danielle,Compton,Estrada Inc,Hamiltonton,Comoros,toddskinner@hart.net,5/10/2020,http://www.parsons.info/ +938,Mitchell,Mack,"Ferguson, Osborne and Lawrence",Lake Brooke,Colombia,shannonmcfarland@cisneros.com,3/20/2020,http://knapp-mcfarland.com/ +939,Jessica,Mcknight,"Walls, Fitzgerald and Hill",Jimmouth,New Zealand,jilldixon@frank.com,1/12/2022,https://oconnell.info/ +940,Tyrone,Marshall,"Moran, Walker and Riley",Nortonfort,Togo,carol33@ayala-chase.com,4/30/2021,http://www.johnston-mccullough.info/ +941,Greg,Allen,"Cervantes, Fuentes and Cunningham",North Sheilaland,Greenland,aaron08@davies-rush.com,5/5/2022,http://www.herring.com/ +942,Jill,Schultz,Cantrell-Zimmerman,East Johnathanbury,Serbia,tterry@clayton.net,10/12/2020,http://www.shea-brooks.com/ +943,Noah,Hall,Gould-Bird,Ninaton,Portugal,don16@crawford.net,4/19/2020,http://buchanan-lane.com/ +944,Wendy,Melendez,"Knox, Cervantes and Thomas",Jeffreybury,Macao,salinaseric@osborn.com,5/24/2021,http://www.rich.com/ +945,Guy,Rush,White Ltd,Collinsland,American Samoa,theodorewatson@lucero-ochoa.com,9/6/2020,https://soto-henry.com/ +946,Monica,Joyce,"Pham, Murphy and Watson",Howardville,Burundi,dboyle@donovan.info,3/15/2022,https://www.james.com/ +947,Lauren,Garcia,"Frye, Pacheco and Bowen",Kiarachester,Thailand,penaleon@mccoy.com,2/17/2021,http://www.aguilar.com/ +948,Louis,Norris,Yates-Edwards,Hayleybury,Saint Barthelemy,lindsay05@drake-sanford.com,7/18/2021,https://arias.net/ +949,Shelly,Rasmussen,Hooper Group,Lake Marvin,Netherlands Antilles,dorishaney@morse.com,7/9/2021,https://waller-humphrey.com/ +950,Dave,Espinoza,Sampson-Horne,Jonesshire,Saint Kitts and Nevis,josephpineda@villa.com,3/1/2020,http://osborne.com/ +951,Beverly,Mayo,Ayala Group,East Alejandraland,Holy See (Vatican City State),costabob@daniel.com,1/28/2020,https://james-pruitt.com/ +952,Larry,Key,Riley Group,Mcdonaldchester,Guinea,ygood@vaughn.com,1/4/2020,https://www.arroyo-schultz.com/ +953,Jennifer,Mcguire,Rangel Ltd,Cameronfurt,Burkina Faso,fnielsen@soto-villegas.com,1/10/2022,http://www.flowers.org/ +954,Olivia,Mills,"Chase, Ibarra and Gentry",South Christina,Ethiopia,bhouston@rosario.com,6/24/2020,http://www.horn-gates.com/ +955,Loretta,Simon,"Mcdowell, Lester and Michael",Palmerport,Pitcairn Islands,tduffy@haney.com,10/15/2021,https://www.crosby.net/ +956,Janet,Stevens,"Delgado, Nixon and Nielsen",Perkinshaven,Mozambique,cesar60@bean.com,11/6/2020,http://www.byrd-dougherty.net/ +957,Laurie,Hutchinson,Cardenas-Lee,New Frankborough,Togo,georgegabriella@burch-harrell.com,2/15/2020,https://yates.com/ +958,Shelly,Green,Rose-Mccarthy,New Seanmouth,Jordan,johnathan92@huff.com,4/28/2022,http://quinn.com/ +959,Curtis,Marsh,Hood and Sons,West Jeanette,Indonesia,alvinrivas@mosley.com,9/28/2021,https://www.chambers-vang.info/ +960,Molly,Prince,"Chandler, Marsh and Vaughn",East Mary,United States of America,ewatkins@ewing-brock.org,9/13/2021,https://www.holder.com/ +961,Jaclyn,Mcmillan,Rojas-Floyd,Port Brentside,Portugal,barry52@herring.biz,5/7/2020,http://holloway.net/ +962,Alejandra,Harding,Hobbs-Whitney,East Courtney,Northern Mariana Islands,uhaley@pena.com,4/28/2020,http://www.york-erickson.com/ +963,Angie,Salas,Downs and Sons,Lake Dwayne,Mauritania,kleingregg@melton.net,11/23/2021,https://www.montgomery.com/ +964,Krystal,English,Singleton-Soto,East Mark,Philippines,theodoreharper@meadows.com,4/9/2020,http://rios.com/ +965,Jenny,Chase,Carey LLC,Beverlymouth,Tanzania,nross@hodge.com,10/12/2021,http://www.simpson.com/ +966,Erika,Collier,"Boyd, Sweeney and Gilmore",North Dylan,Tajikistan,lanedorothy@cooper.info,10/12/2020,http://rubio-gaines.org/ +967,Anthony,Meza,Gamble Inc,New Sheriborough,Martinique,latoyahendricks@wheeler-mitchell.info,6/5/2021,http://nichols.org/ +968,Alice,Choi,Reese-Suarez,Hollyside,Sri Lanka,victor56@glover.com,5/13/2020,http://www.avery.com/ +969,Malik,Marks,Horne Ltd,Atkinsonberg,South Georgia and the South Sandwich Islands,carrillotabitha@leblanc.com,4/20/2020,http://www.hart.biz/ +970,Ruth,Jensen,"Villarreal, Waller and Kerr",Glenmouth,Montenegro,morsebeverly@farrell.biz,9/6/2020,https://www.mercer.com/ +971,Jack,Boone,Whitaker-Stewart,South Tammyfurt,Chad,ashley91@blevins.com,8/13/2020,http://www.roman.com/ +972,Charlene,Zamora,Henry-Bender,Marthaland,Heard Island and McDonald Islands,sherryburton@robinson.com,1/25/2020,https://www.barron-sparks.com/ +973,Jorge,Mcgrath,Patterson PLC,Estradahaven,Bolivia,hchambers@barrera.biz,11/5/2021,https://www.morales.com/ +974,Tricia,Harris,Mcguire Inc,Lake Terri,Sierra Leone,madeline80@cooper-middleton.com,8/5/2020,https://www.aguilar-tucker.com/ +975,Tyrone,Nolan,Myers Ltd,New Brett,Indonesia,meadowscarol@solis.biz,4/17/2020,https://morse-estes.com/ +976,Arthur,Pugh,"Hahn, Nichols and Cortez",Collinton,San Marino,bryce90@chambers.net,2/26/2020,https://www.stein.com/ +977,Kristie,Oconnor,Freeman Group,South Laurenberg,Northern Mariana Islands,kellie08@arias.net,4/2/2020,https://www.finley.com/ +978,Albert,Acosta,"Greene, Ruiz and Mora",North Jacquelineville,Fiji,caitlyn95@hardy.com,4/10/2021,http://dorsey-hamilton.com/ +979,Erin,Deleon,Weaver LLC,Charleneland,Holy See (Vatican City State),gabriel69@barr-orozco.org,11/9/2021,https://www.horn.biz/ +980,Jasmine,Robles,Kent-Sharp,East Normaview,Antigua and Barbuda,collinwerner@terrell.com,6/11/2020,http://hammond.com/ +981,Kendra,Zamora,Perez LLC,Bensonmouth,Lao People's Democratic Republic,pecksamantha@huber.com,8/25/2021,http://www.lamb.info/ +982,Dakota,Salas,Greene Group,Coleburgh,Cook Islands,murillomarcia@wyatt.info,7/13/2021,https://www.gregory-hawkins.info/ +983,Jimmy,Wang,Bentley and Sons,Doughertyfurt,Russian Federation,fbarrett@steele-blanchard.com,6/1/2020,https://garner-pierce.com/ +984,Lisa,Yates,Blair Group,Valeriehaven,United States Minor Outlying Islands,beverlypreston@cook-fuller.info,7/29/2020,https://huber.com/ +985,Collin,White,Silva-Walters,Chenton,Bangladesh,andrea14@tran-stevens.org,11/14/2021,https://nash.org/ +986,Brittney,Horn,Wall Inc,South Michael,Iraq,rthornton@hendrix.com,10/17/2021,http://www.giles-barron.net/ +987,Maureen,Hurst,Reilly-Monroe,Mosesberg,Mongolia,wolfecody@marshall.net,1/5/2020,http://blair.com/ +988,Cynthia,Greene,Choi Ltd,Hancockchester,Ghana,chad71@blankenship.org,12/2/2020,https://bridges.info/ +989,Maureen,Rivers,Patrick Inc,Maxwellborough,Guyana,duncaneric@salas-huff.org,5/16/2020,http://chung.org/ +990,Garrett,Villa,"Meadows, Lowe and Brennan",Calebbury,American Samoa,duncanpedro@higgins-maldonado.com,2/27/2020,https://briggs-villa.com/ +991,Bill,Richards,Grant Inc,South Shellyview,Liechtenstein,roger88@holden.com,3/1/2022,http://norton-buck.com/ +992,Kiara,Moon,Olsen-Morse,Hesterburgh,American Samoa,paynesteven@nguyen-richardson.com,12/5/2020,http://yang.org/ +993,Colin,Kaufman,Myers Group,Aaronfort,Isle of Man,robersonjanice@peterson-herring.com,10/4/2021,https://www.roberson-knapp.org/ +994,Evan,Marsh,"Orozco, Valenzuela and Warren",Lake Darrellshire,Philippines,julia00@mcclure.info,2/9/2020,https://turner-giles.biz/ +995,Kristie,Rice,Harper Ltd,East Leslie,Guinea,joycecombs@guerra-burns.com,12/6/2021,https://www.pena.com/ +996,Diana,Monroe,Bass-Wilson,Lake Jacksonmouth,Guatemala,cassidymercado@bonilla.com,1/9/2022,http://castro.net/ +997,Jerry,Morales,Pratt-King,South Dominiquemouth,Georgia,gavin13@logan-downs.com,8/21/2021,https://www.braun.info/ +998,Tracie,Floyd,"Holt, Wilson and Shields",East Chloeshire,Solomon Islands,lowehailey@oconnor.org,4/22/2020,http://www.zavala-rubio.com/ +999,Paul,Barnes,"Brown, Oliver and Haynes",South Shane,Finland,hodgeseddie@hardin-wells.com,3/12/2021,https://stone-randolph.info/ +1000,Dominic,Duran,Durham LLC,East Wendy,British Indian Ocean Territory (Chagos Archipelago),owarner@velasquez.info,12/9/2021,http://www.cox.com/ diff --git a/src/main/resources/drawer/icon/about.svg b/src/main/resources/drawer/icon/about.svg new file mode 100644 index 00000000..9c07e834 --- /dev/null +++ b/src/main/resources/drawer/icon/about.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/drawer/icon/chart.svg b/src/main/resources/drawer/icon/chart.svg new file mode 100644 index 00000000..f979f801 --- /dev/null +++ b/src/main/resources/drawer/icon/chart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/drawer/icon/dashboard.svg b/src/main/resources/drawer/icon/dashboard.svg new file mode 100644 index 00000000..3f8f7503 --- /dev/null +++ b/src/main/resources/drawer/icon/dashboard.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/drawer/icon/donate.svg b/src/main/resources/drawer/icon/donate.svg new file mode 100644 index 00000000..ef9b1f12 --- /dev/null +++ b/src/main/resources/drawer/icon/donate.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main/resources/drawer/icon/forms.svg b/src/main/resources/drawer/icon/forms.svg new file mode 100644 index 00000000..96bf5279 --- /dev/null +++ b/src/main/resources/drawer/icon/forms.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/drawer/icon/help.svg b/src/main/resources/drawer/icon/help.svg new file mode 100644 index 00000000..24d75977 --- /dev/null +++ b/src/main/resources/drawer/icon/help.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/resources/drawer/icon/history.svg b/src/main/resources/drawer/icon/history.svg new file mode 100644 index 00000000..0ff0f1d9 --- /dev/null +++ b/src/main/resources/drawer/icon/history.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/drawer/icon/info.svg b/src/main/resources/drawer/icon/info.svg new file mode 100644 index 00000000..724e1c6d --- /dev/null +++ b/src/main/resources/drawer/icon/info.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/drawer/icon/setting.svg b/src/main/resources/drawer/icon/setting.svg new file mode 100644 index 00000000..37d053fc --- /dev/null +++ b/src/main/resources/drawer/icon/setting.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/drawer/logo.png b/src/main/resources/drawer/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..830c0f66ea1a3e866dbceb35ca3f413554addc66 GIT binary patch literal 9601 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 + + \ No newline at end of file diff --git a/src/main/resources/icons/clear.svg b/src/main/resources/icons/clear.svg new file mode 100644 index 00000000..caeaa441 --- /dev/null +++ b/src/main/resources/icons/clear.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/close.svg b/src/main/resources/icons/close.svg new file mode 100644 index 00000000..caeaa441 --- /dev/null +++ b/src/main/resources/icons/close.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/color.svg b/src/main/resources/icons/color.svg new file mode 100644 index 00000000..56ddfd9c --- /dev/null +++ b/src/main/resources/icons/color.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/copy.svg b/src/main/resources/icons/copy.svg new file mode 100644 index 00000000..1f6917f7 --- /dev/null +++ b/src/main/resources/icons/copy.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/dashboard/customer.svg b/src/main/resources/icons/dashboard/customer.svg new file mode 100644 index 00000000..5cd7cbfb --- /dev/null +++ b/src/main/resources/icons/dashboard/customer.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/dashboard/expense.svg b/src/main/resources/icons/dashboard/expense.svg new file mode 100644 index 00000000..fddd2249 --- /dev/null +++ b/src/main/resources/icons/dashboard/expense.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/dashboard/income.svg b/src/main/resources/icons/dashboard/income.svg new file mode 100644 index 00000000..12d0c9a9 --- /dev/null +++ b/src/main/resources/icons/dashboard/income.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/dashboard/profit.svg b/src/main/resources/icons/dashboard/profit.svg new file mode 100644 index 00000000..10a8acd8 --- /dev/null +++ b/src/main/resources/icons/dashboard/profit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/delete.svg b/src/main/resources/icons/delete.svg new file mode 100644 index 00000000..f7e63e6a --- /dev/null +++ b/src/main/resources/icons/delete.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/icons/donate.svg b/src/main/resources/icons/donate.svg new file mode 100644 index 00000000..befb59c9 --- /dev/null +++ b/src/main/resources/icons/donate.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/icons/edit.svg b/src/main/resources/icons/edit.svg new file mode 100644 index 00000000..2fddad3a --- /dev/null +++ b/src/main/resources/icons/edit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/eye.svg b/src/main/resources/icons/eye.svg new file mode 100644 index 00000000..7f1214fc --- /dev/null +++ b/src/main/resources/icons/eye.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/icons/favorite.svg b/src/main/resources/icons/favorite.svg new file mode 100644 index 00000000..c640c7dd --- /dev/null +++ b/src/main/resources/icons/favorite.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/favorite_filled.svg b/src/main/resources/icons/favorite_filled.svg new file mode 100644 index 00000000..cd65f7a7 --- /dev/null +++ b/src/main/resources/icons/favorite_filled.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/folder.svg b/src/main/resources/icons/folder.svg new file mode 100644 index 00000000..ec1299ff --- /dev/null +++ b/src/main/resources/icons/folder.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/icons/history.svg b/src/main/resources/icons/history.svg new file mode 100644 index 00000000..c14c7995 --- /dev/null +++ b/src/main/resources/icons/history.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/icons/info.svg b/src/main/resources/icons/info.svg new file mode 100644 index 00000000..b3cfdf0e --- /dev/null +++ b/src/main/resources/icons/info.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/resources/icons/menu.svg b/src/main/resources/icons/menu.svg new file mode 100644 index 00000000..634f86bc --- /dev/null +++ b/src/main/resources/icons/menu.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/redo.svg b/src/main/resources/icons/redo.svg new file mode 100644 index 00000000..a45ace89 --- /dev/null +++ b/src/main/resources/icons/redo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/refresh.svg b/src/main/resources/icons/refresh.svg new file mode 100644 index 00000000..68d22ff3 --- /dev/null +++ b/src/main/resources/icons/refresh.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/search.svg b/src/main/resources/icons/search.svg new file mode 100644 index 00000000..2ddef201 --- /dev/null +++ b/src/main/resources/icons/search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/icons/support.svg b/src/main/resources/icons/support.svg new file mode 100644 index 00000000..5149f587 --- /dev/null +++ b/src/main/resources/icons/support.svg @@ -0,0 +1,6 @@ + + + support + + + \ No newline at end of file diff --git a/src/main/resources/icons/timer.svg b/src/main/resources/icons/timer.svg new file mode 100644 index 00000000..b9e4e5e0 --- /dev/null +++ b/src/main/resources/icons/timer.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/main/resources/icons/undo.svg b/src/main/resources/icons/undo.svg new file mode 100644 index 00000000..5c3f351b --- /dev/null +++ b/src/main/resources/icons/undo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/main/resources/themes/FlatDarkLaf.properties b/src/main/resources/themes/FlatDarkLaf.properties new file mode 100644 index 00000000..8814abfe --- /dev/null +++ b/src/main/resources/themes/FlatDarkLaf.properties @@ -0,0 +1 @@ +Menu.background=tint($Panel.background,3%) \ No newline at end of file diff --git a/src/main/resources/themes/FlatLaf.properties b/src/main/resources/themes/FlatLaf.properties new file mode 100644 index 00000000..3ec34510 --- /dev/null +++ b/src/main/resources/themes/FlatLaf.properties @@ -0,0 +1,48 @@ +PasswordField.revealIcon=backupmanager.icons.PasswordRevealIcon + +[style]ToggleButton.revealButton=\ + border:5,0,5,5;\ + toolbar.selectedBackground:null;\ + toolbar.hoverBackground:null;\ + toolbar.pressedBackground:null; + +[style]Label.redBadge=\ + arc:8;\ + border:2,5,2,5;\ + foreground:#f43f5e;\ + background:fade(#f43f5e,10%); + +[style]Label.greenBadge=\ + arc:8;\ + border:2,5,2,5;\ + foreground:#1f9669;\ + background:fade(#1f9669,10%); + +[style]Panel.dashboardBackground=\ + [dark]background:tint($Panel.background,3%);\ + [light]background:tint($Panel.background,25%);\ + border:3,3,3,3,$Component.borderColor,,15; + +# Pagination + +[style]Button.pageItem=\ + margin:5,5,5,5; + +[style]Button.pageItemSelected=\ + margin:5,5,5,5;\ + foreground:$Button.default.foreground;\ + background:$Button.default.background; + +[style]Button.pagePrevious=$[style]Button.pageItem + +[style]Button.pageNext=$[style]Button.pagePrevious + +# Accent colors from flatlaf demo + +Demo.accent.default = #2675BF +Demo.accent.blue = #007AFF +Demo.accent.purple = #BF5AF2 +Demo.accent.red = #FF3B30 +Demo.accent.orange = #FF9500 +Demo.accent.yellow = #FFCC00 +Demo.accent.green = #28CD41 diff --git a/src/main/resources/themes/FlatLightLaf.properties b/src/main/resources/themes/FlatLightLaf.properties new file mode 100644 index 00000000..3a6ca443 --- /dev/null +++ b/src/main/resources/themes/FlatLightLaf.properties @@ -0,0 +1 @@ +Menu.background=tint($Panel.background,20%) \ No newline at end of file diff --git a/src/main/resources/themes/themes.json b/src/main/resources/themes/themes.json new file mode 100644 index 00000000..80ec54e2 --- /dev/null +++ b/src/main/resources/themes/themes.json @@ -0,0 +1,358 @@ +{ + "arc-theme.theme.json": { + "name": "Arc", + "license": "MIT", + "licenseFile": "arc-themes.LICENSE.txt", + "sourceCodeUrl": "https://gitlab.com/zlamalp/arc-theme-idea", + "sourceCodePath": "blob/master/arc-theme-idea-light/resources/arc-theme.theme.json" + }, + "arc-theme-orange.theme.json": { + "name": "Arc - Orange", + "license": "MIT", + "licenseFile": "arc-themes.LICENSE.txt", + "sourceCodeUrl": "https://gitlab.com/zlamalp/arc-theme-idea", + "sourceCodePath": "blob/master/arc-theme-idea-light/resources/arc-theme-orange.theme.json" + }, + "arc_theme_dark.theme.json": { + "name": "Arc Dark", + "dark": true, + "license": "MIT", + "licenseFile": "arc-themes.LICENSE.txt", + "sourceCodeUrl": "https://gitlab.com/zlamalp/arc-theme-idea", + "sourceCodePath": "blob/master/arc-theme-idea-dark/resources/arc_theme_dark.theme.json" + }, + "arc_theme_dark_orange.theme.json": { + "name": "Arc Dark - Orange", + "dark": true, + "license": "MIT", + "licenseFile": "arc-themes.LICENSE.txt", + "sourceCodeUrl": "https://gitlab.com/zlamalp/arc-theme-idea", + "sourceCodePath": "blob/master/arc-theme-idea-dark/resources/arc_theme_dark_orange.theme.json" + }, + "Carbon.theme.json": { + "name": "Carbon", + "dark": true, + "license": "Apache License 2.0", + "licenseFile": "arc-themes.LICENSE.txt", + "sourceCodeUrl": "https://github.com/luisfer0793/theme-carbon", + "sourceCodePath": "blob/master/resources/matte_carbon_basics.theme.json" + }, + "Cobalt_2.theme.json": { + "name": "Cobalt 2", + "dark": true, + "license": "MIT", + "licenseFile": "Cobalt_2.LICENSE.txt", + "sourceCodeUrl": "https://github.com/ngehlert/cobalt2", + "sourceCodePath": "blob/master/Cobalt2-UI-Theme/resources/Cobalt_2.theme.json" + }, + "Cyan.theme.json": { + "name": "Cyan light", + "license": "MIT", + "licenseFile": "Cyan.LICENSE.txt", + "sourceCodeUrl": "https://github.com/OlyaB/CyanTheme", + "sourceCodePath": "blob/master/src/Cyan.theme.json" + }, + "DarkFlatTheme.theme.json": { + "name": "Dark Flat", + "dark": true, + "license": "MIT", + "licenseFile": "DarkFlatTheme.LICENSE.txt", + "sourceCodeUrl": "https://github.com/nerzhulart/DarkFlatTheme", + "sourceCodePath": "blob/master/src/DarkFlatTheme.theme.json" + }, + "DarkPurple.theme.json": { + "name": "Dark purple", + "dark": true, + "license": "MIT", + "licenseFile": "DarkPurple.LICENSE.txt", + "sourceCodeUrl": "https://github.com/OlyaB/DarkPurpleTheme", + "sourceCodePath": "blob/master/src/DarkPurple.theme.json" + }, + "Dracula.theme.json": { + "name": "Dracula", + "dark": true, + "license": "MIT", + "licenseFile": "Dracula.LICENSE.txt", + "sourceCodeUrl": "https://github.com/dracula/jetbrains", + "sourceCodePath": "blob/master/src/main/resources/themes/Dracula.theme.json" + }, + "Gradianto_dark_fuchsia.theme.json": { + "name": "Gradianto Dark Fuchsia", + "dark": true, + "license": "MIT", + "licenseFile": "Gradianto.LICENSE.txt", + "sourceCodeUrl": "https://github.com/thvardhan/Gradianto", + "sourceCodePath": "blob/master/src/main/resources/Gradianto_dark_fuchsia.theme.json" + }, + "Gradianto_deep_ocean.theme.json": { + "name": "Gradianto Deep Ocean", + "dark": true, + "license": "MIT", + "licenseFile": "Gradianto.LICENSE.txt", + "sourceCodeUrl": "https://github.com/thvardhan/Gradianto", + "sourceCodePath": "blob/master/src/main/resources/Gradianto_deep_ocean.theme.json" + }, + "Gradianto_midnight_blue.theme.json": { + "name": "Gradianto Midnight Blue", + "dark": true, + "license": "MIT", + "licenseFile": "Gradianto.LICENSE.txt", + "sourceCodeUrl": "https://github.com/thvardhan/Gradianto", + "sourceCodePath": "blob/master/src/main/resources/Gradianto_midnight_blue.theme.json" + }, + "Gradianto_Nature_Green.theme.json": { + "name": "Gradianto Nature Green", + "dark": true, + "license": "MIT", + "licenseFile": "Gradianto.LICENSE.txt", + "sourceCodeUrl": "https://github.com/thvardhan/Gradianto", + "sourceCodePath": "blob/master/src/main/resources/Gradianto_Nature_Green.theme.json" + }, + "Gray.theme.json": { + "name": "Gray", + "license": "MIT", + "licenseFile": "Gray.LICENSE.txt", + "sourceCodeUrl": "https://github.com/OlyaB/GreyTheme", + "sourceCodePath": "blob/master/src/Gray.theme.json" + }, + "gruvbox_dark_hard.theme.json": { + "name": "Gruvbox Dark Hard", + "dark": true, + "license": "MIT", + "licenseFile": "gruvbox_theme.LICENSE.txt", + "sourceCodeUrl": "https://github.com/Vincent-P/gruvbox-intellij-theme", + "sourceCodePath": "blob/master/src/main/resources/gruvbox_dark_hard.theme.json" + }, + "HiberbeeDark.theme.json": { + "name": "Hiberbee Dark", + "dark": true, + "license": "MIT", + "licenseFile": "Hiberbee.LICENSE.txt", + "sourceCodeUrl": "https://github.com/Hiberbee/themes", + "sourceCodePath": "blob/latest/src/intellij/src/main/resources/themes/HiberbeeDark.theme.json" + }, + "HighContrast.theme.json": { + "name": "High contrast", + "dark": true, + "license": "MIT", + "licenseFile": "HighContrast.LICENSE.txt", + "sourceCodeUrl": "https://github.com/OlyaB/HighContrastTheme", + "sourceCodePath": "blob/master/src/HighContrast.theme.json" + }, + "LightFlatTheme.theme.json": { + "name": "Light Flat", + "license": "MIT", + "licenseFile": "LightFlatTheme.LICENSE.txt", + "sourceCodeUrl": "https://github.com/nerzhulart/LightFlatTheme", + "sourceCodePath": "blob/master/src/LightFlatTheme.theme.json" + }, + "MaterialTheme.theme.json": { + "name": "Material Design Dark", + "dark": true, + "license": "MIT", + "licenseFile": "MaterialTheme.LICENSE.txt", + "sourceCodeUrl": "https://github.com/xinkunZ/NotReallyMDTheme", + "sourceCodePath": "blob/master/src/main/resources/MaterialTheme.theme.json" + }, + "Monocai.theme.json": { + "name": "Monocai", + "dark": true, + "license": "MIT", + "licenseFile": "Monocai.LICENSE.txt", + "sourceCodeUrl": "https://github.com/bmikaili/intellij-monocai-theme", + "sourceCodePath": "blob/master/resources/Monocai.theme.json" + }, + "Monokai_Pro.default.theme.json": { + "name": "Monokai Pro", + "dark": true, + "license": "MIT", + "licenseFile": "Monokai_Pro.LICENSE.txt", + "sourceCodeUrl": "https://github.com/subtheme-dev/monokai-pro" + }, + "nord.theme.json": { + "name": "Nord", + "dark": true, + "license": "MIT", + "licenseFile": "nord.LICENSE.txt", + "sourceCodeUrl": "https://github.com/arcticicestudio/nord-jetbrains", + "sourceCodePath": "blob/main/src/nord.theme.json" + }, + "one_dark.theme.json": { + "name": "One Dark", + "dark": true, + "license": "MIT", + "licenseFile": "one_dark.LICENSE.txt", + "sourceCodeUrl": "https://github.com/one-dark/jetbrains-one-dark-theme", + "sourceCodePath": "blob/master/buildSrc/templates/oneDark.template.theme.json" + }, + "SolarizedDark.theme.json": { + "name": "Solarized Dark", + "dark": true, + "license": "The Unlicense", + "licenseFile": "Solarized.LICENSE.txt", + "sourceCodeUrl": "https://github.com/4lex4/intellij-platform-solarized", + "sourceCodePath": "blob/master/resources/SolarizedDark.theme.json" + }, + "SolarizedLight.theme.json": { + "name": "Solarized Light", + "license": "The Unlicense", + "licenseFile": "Solarized.LICENSE.txt", + "sourceCodeUrl": "https://github.com/4lex4/intellij-platform-solarized", + "sourceCodePath": "blob/master/resources/SolarizedLight.theme.json" + }, + "Spacegray.theme.json": { + "name": "Spacegray", + "dark": true, + "license": "MIT", + "licenseFile": "Spacegray.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mturlo/intellij-spacegray", + "sourceCodePath": "blob/master/src/Spacegray.theme.json" + }, + "vuesion_theme.theme.json": { + "name": "Vuesion", + "dark": true, + "license": "MIT", + "licenseFile": "vuesion_theme.LICENSE.txt", + "sourceCodeUrl": "https://github.com/vuesion/intellij-theme", + "sourceCodePath": "blob/master/resources/META-INF/vuesion_theme.theme.json" + }, + "Xcode-Dark.theme.json": { + "name": "Xcode-Dark", + "dark": true, + "license": "MIT", + "licenseFile": "Xcode-Dark.LICENSE.txt", + "sourceCodeUrl": "https://github.com/antelle/intellij-xcode-dark-theme", + "sourceCodePath": "blob/master/resources/Xcode-Dark.theme.json" + }, + "material-theme-ui-lite/Arc Dark.theme.json": { + "name": "Material Theme UI Lite / Arc Dark", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Arc Dark.theme.json" + }, + "material-theme-ui-lite/Atom One Dark.theme.json": { + "name": "Material Theme UI Lite / Atom One Dark", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Atom One Dark.theme.json" + }, + "material-theme-ui-lite/Atom One Light.theme.json": { + "name": "Material Theme UI Lite / Atom One Light", + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Atom One Light.theme.json" + }, + "material-theme-ui-lite/Dracula.theme.json": { + "name": "Material Theme UI Lite / Dracula", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Dracula.theme.json" + }, + "material-theme-ui-lite/GitHub.theme.json": { + "name": "Material Theme UI Lite / GitHub", + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/GitHub.theme.json" + }, + "material-theme-ui-lite/GitHub Dark.theme.json": { + "name": "Material Theme UI Lite / GitHub Dark", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/GitHub Dark.theme.json" + }, + "material-theme-ui-lite/Light Owl.theme.json": { + "name": "Material Theme UI Lite / Light Owl", + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Light Owl.theme.json" + }, + "material-theme-ui-lite/Material Darker.theme.json": { + "name": "Material Theme UI Lite / Material Darker", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Material Darker.theme.json" + }, + "material-theme-ui-lite/Material Deep Ocean.theme.json": { + "name": "Material Theme UI Lite / Material Deep Ocean", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Material Deep Ocean.theme.json" + }, + "material-theme-ui-lite/Material Lighter.theme.json": { + "name": "Material Theme UI Lite / Material Lighter", + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Material Lighter.theme.json" + }, + "material-theme-ui-lite/Material Oceanic.theme.json": { + "name": "Material Theme UI Lite / Material Oceanic", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Material Oceanic.theme.json" + }, + "material-theme-ui-lite/Material Palenight.theme.json": { + "name": "Material Theme UI Lite / Material Palenight", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Material Palenight.theme.json" + }, + "material-theme-ui-lite/Monokai Pro.theme.json": { + "name": "Material Theme UI Lite / Monokai Pro", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Monokai Pro.theme.json" + }, + "material-theme-ui-lite/Moonlight.theme.json": { + "name": "Material Theme UI Lite / Moonlight", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Moonlight.theme.json" + }, + "material-theme-ui-lite/Night Owl.theme.json": { + "name": "Material Theme UI Lite / Night Owl", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Night Owl.theme.json" + }, + "material-theme-ui-lite/Solarized Dark.theme.json": { + "name": "Material Theme UI Lite / Solarized Dark", + "dark": true, + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Solarized Dark.theme.json" + }, + "material-theme-ui-lite/Solarized Light.theme.json": { + "name": "Material Theme UI Lite / Solarized Light", + "license": "MIT", + "licenseFile": "material-theme-ui-lite/Material Theme UI Lite.LICENSE.txt", + "sourceCodeUrl": "https://github.com/mallowigi/material-theme-ui-lite", + "sourceCodePath": "blob/master/src/main/resources/themes/regular/Solarized Light.theme.json" + } +} \ No newline at end of file From ae8424eb26726bfa7b6eb5225ad4602af16ea8a6 Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Sat, 28 Feb 2026 08:52:38 +0100 Subject: [PATCH 02/17] folder order --- .../java/backupmanager/BackupOperations.java | 4 +- .../Entities/ZippingContext.java | 4 +- .../backupmanager/Helpers/BackupHelper.java | 12 +-- .../Helpers/SubscriptionNotifier.java | 2 +- src/main/java/backupmanager/MainApp.java | 4 +- .../Services/BackgroundService.java | 2 +- .../Services/BackupObserver.java | 4 +- .../backupmanager/Utils/DemoPreferences.java | 2 +- .../{ => gui}/Controllers/AppController.java | 4 +- .../Controllers/BackupEntryController.java | 8 +- .../Controllers/EntryUserController.java | 2 +- .../{ => gui}/Controllers/GuiController.java | 2 +- .../Controllers/PreferenceController.java | 4 +- .../Controllers/TimePickerController.java | 2 +- .../{ => gui}/Controllers/TrayController.java | 2 +- .../{ => gui}/Dialogs/BackupEntryDialog.form | 8 +- .../{ => gui}/Dialogs/BackupEntryDialog.java | 22 ++--- .../{ => gui}/Dialogs/EntryUserDialog.form | 0 .../{ => gui}/Dialogs/EntryUserDialog.java | 4 +- .../{ => gui}/Dialogs/PreferencesDialog.form | 0 .../{ => gui}/Dialogs/PreferencesDialog.java | 8 +- .../{ => gui}/Dialogs/TimePicker.form | 0 .../{ => gui}/Dialogs/TimePicker.java | 6 +- .../{ => gui}/Table/BackupTable.java | 2 +- .../{ => gui}/Table/BackupTableModel.java | 2 +- .../{ => gui}/Table/CheckboxCellRenderer.java | 2 +- .../{ => gui}/Table/ProgressBarRenderer.java | 4 +- .../{ => gui}/Table/StripedRowRenderer.java | 2 +- .../{ => gui}/Table/TableDataManager.java | 4 +- .../{ => gui}/component/About.java | 2 +- .../{ => gui}/component/AccentColorIcon.java | 11 ++- .../{ => gui}/component/EmptyModalBorder.java | 7 +- .../{ => gui}/component/FormSearchButton.java | 2 +- .../{ => gui}/component/FormSearchPanel.java | 10 +- .../{ => gui}/component/RefreshLine.java | 15 ++- .../{ => gui}/component/ToolBarSelection.java | 11 ++- .../{ => gui}/component/chart/BarChart.java | 7 +- .../component/chart/CandlestickChart.java | 23 +++-- .../component/chart/DefaultChartPanel.java | 18 ++-- .../{ => gui}/component/chart/PieChart.java | 10 +- .../component/chart/SpiderChart.java | 19 ++-- .../component/chart/TimeSeriesChart.java | 31 +++--- .../renderer/ChartDeviationStepRenderer.java | 7 +- .../renderer/ChartStackedXYBarRenderer.java | 2 +- .../chart/renderer/ChartXYBarRenderer.java | 5 +- .../chart/renderer/ChartXYCurveRenderer.java | 5 +- .../renderer/ChartXYDifferenceRenderer.java | 7 +- .../chart/renderer/ChartXYLineRenderer.java | 2 +- .../chart/renderer/bar/ChartBarRenderer.java | 5 +- .../other/ChartCandlestickRenderer.java | 8 +- .../chart/themes/ChartDrawingSupplier.java | 16 ++- .../component/chart/themes/ColorThemes.java | 4 +- .../chart/themes/DefaultChartTheme.java | 17 +++- .../chart/utils/CustomCrosshairToolTip.java | 11 ++- .../utils/DateCrosshairLabelGenerator.java | 8 +- .../chart/utils/MultiXYTextAnnotation.java | 27 +++-- .../utils/ToolBarCategoryOrientation.java | 5 +- .../utils/ToolBarTimeSeriesChartRenderer.java | 12 ++- .../component/dashboard/CardBox.java | 16 +-- .../component/dashboard/CardItem.java | 12 ++- .../customwidgets/ModernTextField.java | 2 +- .../{ => gui}/forms/FormDashboard.java | 30 +++--- .../{ => gui}/forms/FormSetting.java | 10 +- .../{ => gui}/forms/FormTable.java | 12 +-- .../{ => gui}/frames/BackupManager.java | 6 +- .../{ => gui}/frames/BackupManagerGUI.form | 40 ++++---- .../{ => gui}/frames/BackupManagerGUI.java | 98 +++++++++---------- .../{ => gui}/frames/BackupProgressGUI.form | 0 .../{ => gui}/frames/BackupProgressGUI.java | 6 +- .../Controllers/BackupManagerController.java | 12 +-- .../Controllers/BackupMenuController.java | 8 +- .../Controllers/BackupPopupController.java | 10 +- .../Controllers/BackupProgressController.java | 2 +- .../backupmanager/{ => gui}/frames/Login.java | 8 +- .../{ => gui}/menu/MyDrawerBuilder.java | 33 ++++--- .../{ => gui}/menu/MyMenuValidation.java | 4 +- .../{ => gui}/sample/SampleData.java | 2 +- .../{ => gui}/sample/csv/CSVDataReader.java | 2 +- .../{ => gui}/sample/csv/Pageable.java | 2 +- .../{ => gui}/sample/csv/ResponseCSV.java | 2 +- .../sample/csv/ResponsePageable.java | 2 +- .../{ => gui}/simple/SimpleInputForms.java | 2 +- .../{ => gui}/svg/SVGButton.java | 2 +- .../{ => gui}/svg/SVGIconUIColor.java | 2 +- .../{ => gui}/svg/SVGManager.java | 2 +- .../backupmanager/{ => gui}/svg/SVGMenu.java | 2 +- .../{ => gui}/svg/SVGMenuItem.java | 2 +- .../{ => gui}/system/AllForms.java | 2 +- .../backupmanager/{ => gui}/system/Form.java | 2 +- .../{ => gui}/system/FormManager.java | 8 +- .../{ => gui}/system/FormSearch.java | 8 +- .../{ => gui}/system/MainForm.java | 6 +- .../themes/ListCellTitledBorder.java | 2 +- .../{ => gui}/themes/PanelThemes.java | 2 +- .../{ => gui}/themes/ThemesInfo.java | 2 +- .../{ => gui}/themes/ThemesManager.java | 2 +- src/main/resources/drawer/logo.svg | 39 ++++++++ 97 files changed, 478 insertions(+), 355 deletions(-) rename src/main/java/backupmanager/{ => gui}/Controllers/AppController.java (97%) rename src/main/java/backupmanager/{ => gui}/Controllers/BackupEntryController.java (97%) rename src/main/java/backupmanager/{ => gui}/Controllers/EntryUserController.java (96%) rename src/main/java/backupmanager/{ => gui}/Controllers/GuiController.java (87%) rename src/main/java/backupmanager/{ => gui}/Controllers/PreferenceController.java (91%) rename src/main/java/backupmanager/{ => gui}/Controllers/TimePickerController.java (98%) rename src/main/java/backupmanager/{ => gui}/Controllers/TrayController.java (98%) rename src/main/java/backupmanager/{ => gui}/Dialogs/BackupEntryDialog.form (98%) rename src/main/java/backupmanager/{ => gui}/Dialogs/BackupEntryDialog.java (97%) rename src/main/java/backupmanager/{ => gui}/Dialogs/EntryUserDialog.form (100%) rename src/main/java/backupmanager/{ => gui}/Dialogs/EntryUserDialog.java (98%) rename src/main/java/backupmanager/{ => gui}/Dialogs/PreferencesDialog.form (100%) rename src/main/java/backupmanager/{ => gui}/Dialogs/PreferencesDialog.java (97%) rename src/main/java/backupmanager/{ => gui}/Dialogs/TimePicker.form (100%) rename src/main/java/backupmanager/{ => gui}/Dialogs/TimePicker.java (98%) rename src/main/java/backupmanager/{ => gui}/Table/BackupTable.java (98%) rename src/main/java/backupmanager/{ => gui}/Table/BackupTableModel.java (93%) rename src/main/java/backupmanager/{ => gui}/Table/CheckboxCellRenderer.java (97%) rename src/main/java/backupmanager/{ => gui}/Table/ProgressBarRenderer.java (97%) rename src/main/java/backupmanager/{ => gui}/Table/StripedRowRenderer.java (96%) rename src/main/java/backupmanager/{ => gui}/Table/TableDataManager.java (98%) rename src/main/java/backupmanager/{ => gui}/component/About.java (98%) rename src/main/java/backupmanager/{ => gui}/component/AccentColorIcon.java (85%) rename src/main/java/backupmanager/{ => gui}/component/EmptyModalBorder.java (95%) rename src/main/java/backupmanager/{ => gui}/component/FormSearchButton.java (97%) rename src/main/java/backupmanager/{ => gui}/component/FormSearchPanel.java (98%) rename src/main/java/backupmanager/{ => gui}/component/RefreshLine.java (87%) rename src/main/java/backupmanager/{ => gui}/component/ToolBarSelection.java (86%) rename src/main/java/backupmanager/{ => gui}/component/chart/BarChart.java (91%) rename src/main/java/backupmanager/{ => gui}/component/chart/CandlestickChart.java (91%) rename src/main/java/backupmanager/{ => gui}/component/chart/DefaultChartPanel.java (89%) rename src/main/java/backupmanager/{ => gui}/component/chart/PieChart.java (91%) rename src/main/java/backupmanager/{ => gui}/component/chart/SpiderChart.java (91%) rename src/main/java/backupmanager/{ => gui}/component/chart/TimeSeriesChart.java (90%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartDeviationStepRenderer.java (86%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartStackedXYBarRenderer.java (84%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartXYBarRenderer.java (77%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartXYCurveRenderer.java (92%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartXYDifferenceRenderer.java (75%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/ChartXYLineRenderer.java (79%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/bar/ChartBarRenderer.java (73%) rename src/main/java/backupmanager/{ => gui}/component/chart/renderer/other/ChartCandlestickRenderer.java (90%) rename src/main/java/backupmanager/{ => gui}/component/chart/themes/ChartDrawingSupplier.java (94%) rename src/main/java/backupmanager/{ => gui}/component/chart/themes/ColorThemes.java (97%) rename src/main/java/backupmanager/{ => gui}/component/chart/themes/DefaultChartTheme.java (95%) rename src/main/java/backupmanager/{ => gui}/component/chart/utils/CustomCrosshairToolTip.java (82%) rename src/main/java/backupmanager/{ => gui}/component/chart/utils/DateCrosshairLabelGenerator.java (93%) rename src/main/java/backupmanager/{ => gui}/component/chart/utils/MultiXYTextAnnotation.java (98%) rename src/main/java/backupmanager/{ => gui}/component/chart/utils/ToolBarCategoryOrientation.java (81%) rename src/main/java/backupmanager/{ => gui}/component/chart/utils/ToolBarTimeSeriesChartRenderer.java (61%) rename src/main/java/backupmanager/{ => gui}/component/dashboard/CardBox.java (87%) rename src/main/java/backupmanager/{ => gui}/component/dashboard/CardItem.java (93%) rename src/main/java/backupmanager/{ => gui}/customwidgets/ModernTextField.java (99%) rename src/main/java/backupmanager/{ => gui}/forms/FormDashboard.java (90%) rename src/main/java/backupmanager/{ => gui}/forms/FormSetting.java (98%) rename src/main/java/backupmanager/{ => gui}/forms/FormTable.java (98%) rename src/main/java/backupmanager/{ => gui}/frames/BackupManager.java (82%) rename src/main/java/backupmanager/{ => gui}/frames/BackupManagerGUI.form (96%) rename src/main/java/backupmanager/{ => gui}/frames/BackupManagerGUI.java (95%) rename src/main/java/backupmanager/{ => gui}/frames/BackupProgressGUI.form (100%) rename src/main/java/backupmanager/{ => gui}/frames/BackupProgressGUI.java (98%) rename src/main/java/backupmanager/{ => gui}/frames/Controllers/BackupManagerController.java (95%) rename src/main/java/backupmanager/{ => gui}/frames/Controllers/BackupMenuController.java (94%) rename src/main/java/backupmanager/{ => gui}/frames/Controllers/BackupPopupController.java (97%) rename src/main/java/backupmanager/{ => gui}/frames/Controllers/BackupProgressController.java (94%) rename src/main/java/backupmanager/{ => gui}/frames/Login.java (94%) rename src/main/java/backupmanager/{ => gui}/menu/MyDrawerBuilder.java (88%) rename src/main/java/backupmanager/{ => gui}/menu/MyMenuValidation.java (81%) rename src/main/java/backupmanager/{ => gui}/sample/SampleData.java (99%) rename src/main/java/backupmanager/{ => gui}/sample/csv/CSVDataReader.java (98%) rename src/main/java/backupmanager/{ => gui}/sample/csv/Pageable.java (95%) rename src/main/java/backupmanager/{ => gui}/sample/csv/ResponseCSV.java (86%) rename src/main/java/backupmanager/{ => gui}/sample/csv/ResponsePageable.java (88%) rename src/main/java/backupmanager/{ => gui}/simple/SimpleInputForms.java (99%) rename src/main/java/backupmanager/{ => gui}/svg/SVGButton.java (95%) rename src/main/java/backupmanager/{ => gui}/svg/SVGIconUIColor.java (97%) rename src/main/java/backupmanager/{ => gui}/svg/SVGManager.java (97%) rename src/main/java/backupmanager/{ => gui}/svg/SVGMenu.java (92%) rename src/main/java/backupmanager/{ => gui}/svg/SVGMenuItem.java (92%) rename src/main/java/backupmanager/{ => gui}/system/AllForms.java (97%) rename src/main/java/backupmanager/{ => gui}/system/Form.java (95%) rename src/main/java/backupmanager/{ => gui}/system/FormManager.java (95%) rename src/main/java/backupmanager/{ => gui}/system/FormSearch.java (94%) rename src/main/java/backupmanager/{ => gui}/system/MainForm.java (96%) rename src/main/java/backupmanager/{ => gui}/themes/ListCellTitledBorder.java (98%) rename src/main/java/backupmanager/{ => gui}/themes/PanelThemes.java (99%) rename src/main/java/backupmanager/{ => gui}/themes/ThemesInfo.java (95%) rename src/main/java/backupmanager/{ => gui}/themes/ThemesManager.java (98%) create mode 100644 src/main/resources/drawer/logo.svg diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 8049ea57..0a1b3269 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -31,9 +31,9 @@ import backupmanager.Managers.ExceptionManager; import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; -import backupmanager.Table.TableDataManager; +import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.utils.FolderUtils; public class BackupOperations { diff --git a/src/main/java/backupmanager/Entities/ZippingContext.java b/src/main/java/backupmanager/Entities/ZippingContext.java index c56bb1f3..46fe5e52 100644 --- a/src/main/java/backupmanager/Entities/ZippingContext.java +++ b/src/main/java/backupmanager/Entities/ZippingContext.java @@ -4,8 +4,8 @@ import javax.swing.JMenuItem; -import backupmanager.Table.BackupTable; -import backupmanager.frames.BackupProgressGUI; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.frames.BackupProgressGUI; import backupmanager.utils.FolderUtils; public record ZippingContext ( diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index cb5a6709..5e35e39b 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -10,19 +10,19 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.Dialogs.BackupEntryDialog; -import backupmanager.Dialogs.TimePicker; +import backupmanager.gui.Dialogs.BackupEntryDialog; +import backupmanager.gui.Dialogs.TimePicker; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.BackupStatus; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.Table.BackupTable; -import backupmanager.Table.TableDataManager; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.frames.BackupManagerGUI; -import backupmanager.frames.BackupProgressGUI; +import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupProgressGUI; public class BackupHelper { diff --git a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java index 338f8f4d..e49465ed 100644 --- a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java +++ b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java @@ -4,7 +4,7 @@ import javax.swing.JOptionPane; -import backupmanager.Controllers.TrayController; +import backupmanager.gui.Controllers.TrayController; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 6246ff7b..7df82ead 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -13,7 +13,7 @@ import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.util.FontUtils; -import backupmanager.Controllers.AppController; +import backupmanager.gui.Controllers.AppController; import backupmanager.Entities.Confingurations; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.TranslationLoaderEnum; @@ -21,7 +21,7 @@ import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.ProductionDatabaseInitializer; -import backupmanager.frames.BackupManager; +import backupmanager.gui.frames.BackupManager; import backupmanager.utils.DemoPreferences; public class MainApp { diff --git a/src/main/java/backupmanager/Services/BackgroundService.java b/src/main/java/backupmanager/Services/BackgroundService.java index 94c8bdfe..99533f27 100644 --- a/src/main/java/backupmanager/Services/BackgroundService.java +++ b/src/main/java/backupmanager/Services/BackgroundService.java @@ -14,7 +14,7 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.Controllers.TrayController; +import backupmanager.gui.Controllers.TrayController; import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; diff --git a/src/main/java/backupmanager/Services/BackupObserver.java b/src/main/java/backupmanager/Services/BackupObserver.java index 72caea3d..061c4c6d 100644 --- a/src/main/java/backupmanager/Services/BackupObserver.java +++ b/src/main/java/backupmanager/Services/BackupObserver.java @@ -14,13 +14,13 @@ import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Table.TableDataManager; +import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; /* * I need a task that constantly checks if there are something running and i can't use a simple method calls instead because - * if a backup starts caused by the BackugroundService and we open the GUI, thre are 2 different instance of this program, + * if a backup starts caused by the BackugroundService and we open the GUI, thre are 2 different instance of this program, * so we need something like an observer that constantly checks if there are some backups in progress. */ public class BackupObserver { diff --git a/src/main/java/backupmanager/Utils/DemoPreferences.java b/src/main/java/backupmanager/Utils/DemoPreferences.java index 2b4806c3..eddb6f60 100644 --- a/src/main/java/backupmanager/Utils/DemoPreferences.java +++ b/src/main/java/backupmanager/Utils/DemoPreferences.java @@ -13,7 +13,7 @@ import com.formdev.flatlaf.IntelliJTheme; import com.formdev.flatlaf.util.LoggingFacade; -import backupmanager.themes.PanelThemes; +import backupmanager.gui.themes.PanelThemes; public class DemoPreferences { diff --git a/src/main/java/backupmanager/Controllers/AppController.java b/src/main/java/backupmanager/gui/Controllers/AppController.java similarity index 97% rename from src/main/java/backupmanager/Controllers/AppController.java rename to src/main/java/backupmanager/gui/Controllers/AppController.java index 62098e41..00a84b7f 100644 --- a/src/main/java/backupmanager/Controllers/AppController.java +++ b/src/main/java/backupmanager/gui/Controllers/AppController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import java.awt.Frame; import java.io.IOException; @@ -17,7 +17,7 @@ import backupmanager.Json.JSONConfigReader; import backupmanager.Services.BackgroundService; import backupmanager.database.Repositories.SubscriptionRepository; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; public class AppController { private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); diff --git a/src/main/java/backupmanager/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java similarity index 97% rename from src/main/java/backupmanager/Controllers/BackupEntryController.java rename to src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index 57baf0a4..8261891b 100644 --- a/src/main/java/backupmanager/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import java.time.LocalDateTime; @@ -17,10 +17,10 @@ import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; -import backupmanager.Table.BackupTable; +import backupmanager.gui.Table.BackupTable; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.frames.BackupManagerGUI; -import backupmanager.frames.BackupProgressGUI; +import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupProgressGUI; public class BackupEntryController { private static final Logger logger = LoggerFactory.getLogger(BackupEntryController.class); diff --git a/src/main/java/backupmanager/Controllers/EntryUserController.java b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java similarity index 96% rename from src/main/java/backupmanager/Controllers/EntryUserController.java rename to src/main/java/backupmanager/gui/Controllers/EntryUserController.java index 5e0ed2c5..52e60e90 100644 --- a/src/main/java/backupmanager/Controllers/EntryUserController.java +++ b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import javax.swing.JOptionPane; diff --git a/src/main/java/backupmanager/Controllers/GuiController.java b/src/main/java/backupmanager/gui/Controllers/GuiController.java similarity index 87% rename from src/main/java/backupmanager/Controllers/GuiController.java rename to src/main/java/backupmanager/gui/Controllers/GuiController.java index fe23c7f6..f9561b6a 100644 --- a/src/main/java/backupmanager/Controllers/GuiController.java +++ b/src/main/java/backupmanager/gui/Controllers/GuiController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import java.awt.Image; diff --git a/src/main/java/backupmanager/Controllers/PreferenceController.java b/src/main/java/backupmanager/gui/Controllers/PreferenceController.java similarity index 91% rename from src/main/java/backupmanager/Controllers/PreferenceController.java rename to src/main/java/backupmanager/gui/Controllers/PreferenceController.java index ccb00ce4..a95fa3cf 100644 --- a/src/main/java/backupmanager/Controllers/PreferenceController.java +++ b/src/main/java/backupmanager/gui/Controllers/PreferenceController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import java.io.IOException; import java.util.Arrays; @@ -8,7 +8,7 @@ import backupmanager.Managers.ExceptionManager; import backupmanager.Services.PreferenceService; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; public record PreferenceController (PreferenceService service, BackupManagerGUI mainGui) { private static final Logger logger = LoggerFactory.getLogger(PreferenceController.class); diff --git a/src/main/java/backupmanager/Controllers/TimePickerController.java b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java similarity index 98% rename from src/main/java/backupmanager/Controllers/TimePickerController.java rename to src/main/java/backupmanager/gui/Controllers/TimePickerController.java index 587c89cc..4866ba45 100644 --- a/src/main/java/backupmanager/Controllers/TimePickerController.java +++ b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import javax.swing.JOptionPane; diff --git a/src/main/java/backupmanager/Controllers/TrayController.java b/src/main/java/backupmanager/gui/Controllers/TrayController.java similarity index 98% rename from src/main/java/backupmanager/Controllers/TrayController.java rename to src/main/java/backupmanager/gui/Controllers/TrayController.java index 85a5a61e..fa74e947 100644 --- a/src/main/java/backupmanager/Controllers/TrayController.java +++ b/src/main/java/backupmanager/gui/Controllers/TrayController.java @@ -1,4 +1,4 @@ -package backupmanager.Controllers; +package backupmanager.gui.Controllers; import java.awt.AWTException; import java.awt.Image; diff --git a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.form b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form similarity index 98% rename from src/main/java/backupmanager/Dialogs/BackupEntryDialog.form rename to src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form index bfb184a5..5164cb8d 100644 --- a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.form +++ b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form @@ -143,7 +143,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -259,7 +259,7 @@ - + @@ -307,7 +307,7 @@ - + diff --git a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java similarity index 97% rename from src/main/java/backupmanager/Dialogs/BackupEntryDialog.java rename to src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java index 8d01b81f..e5d8028a 100644 --- a/src/main/java/backupmanager/Dialogs/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java @@ -1,6 +1,6 @@ -package backupmanager.Dialogs; +package backupmanager.gui.Dialogs; -import static backupmanager.frames.BackupManagerGUI.backupTable; +import static backupmanager.gui.frames.BackupManagerGUI.backupTable; import java.time.LocalDateTime; @@ -10,7 +10,7 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.BackupOperations; -import backupmanager.Controllers.BackupEntryController; +import backupmanager.gui.Controllers.BackupEntryController; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.ConfigKey; @@ -231,8 +231,8 @@ private void setTranslations() { private void initComponents() { startPathField = new javax.swing.JTextField(); - btnPathSearch1 = new backupmanager.svg.SVGButton(); - btnPathSearch2 = new backupmanager.svg.SVGButton(); + btnPathSearch1 = new backupmanager.gui.svg.SVGButton(); + btnPathSearch2 = new backupmanager.gui.svg.SVGButton(); destinationPathField = new javax.swing.JTextField(); jLabel2 = new javax.swing.JLabel(); jScrollPane2 = new javax.swing.JScrollPane(); @@ -240,12 +240,12 @@ private void initComponents() { lastBackupLabel = new javax.swing.JLabel(); singleBackup = new javax.swing.JButton(); toggleAutoBackup = new javax.swing.JToggleButton(); - btnTimePicker = new backupmanager.svg.SVGButton(); + btnTimePicker = new backupmanager.gui.svg.SVGButton(); maxBackupCountSpinner = new javax.swing.JSpinner(); jLabel4 = new javax.swing.JLabel(); closeButton = new javax.swing.JButton(); okButton = new javax.swing.JButton(); - backupNameField = new backupmanager.customwidgets.ModernTextField(); + backupNameField = new backupmanager.gui.customwidgets.ModernTextField(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setResizable(false); @@ -518,11 +518,11 @@ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRS // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton singleBackup; - private backupmanager.customwidgets.ModernTextField backupNameField; + private backupmanager.gui.customwidgets.ModernTextField backupNameField; private javax.swing.JTextArea backupNoteTextArea; - private backupmanager.svg.SVGButton btnPathSearch1; - private backupmanager.svg.SVGButton btnPathSearch2; - private backupmanager.svg.SVGButton btnTimePicker; + private backupmanager.gui.svg.SVGButton btnPathSearch1; + private backupmanager.gui.svg.SVGButton btnPathSearch2; + private backupmanager.gui.svg.SVGButton btnTimePicker; private javax.swing.JButton closeButton; private javax.swing.JTextField destinationPathField; private javax.swing.JLabel jLabel2; diff --git a/src/main/java/backupmanager/Dialogs/EntryUserDialog.form b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form similarity index 100% rename from src/main/java/backupmanager/Dialogs/EntryUserDialog.form rename to src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form diff --git a/src/main/java/backupmanager/Dialogs/EntryUserDialog.java b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java similarity index 98% rename from src/main/java/backupmanager/Dialogs/EntryUserDialog.java rename to src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java index c8e1d78e..62ddd890 100644 --- a/src/main/java/backupmanager/Dialogs/EntryUserDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java @@ -1,8 +1,8 @@ -package backupmanager.Dialogs; +package backupmanager.gui.Dialogs; import backupmanager.LimitDocument; -import backupmanager.Controllers.EntryUserController; +import backupmanager.gui.Controllers.EntryUserController; import backupmanager.Entities.User; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; diff --git a/src/main/java/backupmanager/Dialogs/PreferencesDialog.form b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form similarity index 100% rename from src/main/java/backupmanager/Dialogs/PreferencesDialog.form rename to src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form diff --git a/src/main/java/backupmanager/Dialogs/PreferencesDialog.java b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java similarity index 97% rename from src/main/java/backupmanager/Dialogs/PreferencesDialog.java rename to src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java index 4768782b..34a16e87 100644 --- a/src/main/java/backupmanager/Dialogs/PreferencesDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java @@ -1,7 +1,7 @@ -package backupmanager.Dialogs; +package backupmanager.gui.Dialogs; -import backupmanager.Controllers.GuiController; -import backupmanager.Controllers.PreferenceController; +import backupmanager.gui.Controllers.GuiController; +import backupmanager.gui.Controllers.PreferenceController; import backupmanager.Entities.Confingurations; import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.ThemesEnum; @@ -9,7 +9,7 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Managers.ThemeManager; import backupmanager.Services.PreferenceService; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; public class PreferencesDialog extends javax.swing.JDialog { diff --git a/src/main/java/backupmanager/Dialogs/TimePicker.form b/src/main/java/backupmanager/gui/Dialogs/TimePicker.form similarity index 100% rename from src/main/java/backupmanager/Dialogs/TimePicker.form rename to src/main/java/backupmanager/gui/Dialogs/TimePicker.form diff --git a/src/main/java/backupmanager/Dialogs/TimePicker.java b/src/main/java/backupmanager/gui/Dialogs/TimePicker.java similarity index 98% rename from src/main/java/backupmanager/Dialogs/TimePicker.java rename to src/main/java/backupmanager/gui/Dialogs/TimePicker.java index 7d13612a..c4781869 100644 --- a/src/main/java/backupmanager/Dialogs/TimePicker.java +++ b/src/main/java/backupmanager/gui/Dialogs/TimePicker.java @@ -1,11 +1,11 @@ -package backupmanager.Dialogs; +package backupmanager.gui.Dialogs; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.Controllers.GuiController; +import backupmanager.gui.Controllers.GuiController; import backupmanager.Entities.TimeInterval; -import backupmanager.Controllers.TimePickerController; +import backupmanager.gui.Controllers.TimePickerController; public class TimePicker extends javax.swing.JDialog { diff --git a/src/main/java/backupmanager/Table/BackupTable.java b/src/main/java/backupmanager/gui/Table/BackupTable.java similarity index 98% rename from src/main/java/backupmanager/Table/BackupTable.java rename to src/main/java/backupmanager/gui/Table/BackupTable.java index 535fa65e..d5ce86c4 100644 --- a/src/main/java/backupmanager/Table/BackupTable.java +++ b/src/main/java/backupmanager/gui/Table/BackupTable.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import java.awt.Point; diff --git a/src/main/java/backupmanager/Table/BackupTableModel.java b/src/main/java/backupmanager/gui/Table/BackupTableModel.java similarity index 93% rename from src/main/java/backupmanager/Table/BackupTableModel.java rename to src/main/java/backupmanager/gui/Table/BackupTableModel.java index 0e406580..ad055b9b 100644 --- a/src/main/java/backupmanager/Table/BackupTableModel.java +++ b/src/main/java/backupmanager/gui/Table/BackupTableModel.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import javax.swing.table.DefaultTableModel; diff --git a/src/main/java/backupmanager/Table/CheckboxCellRenderer.java b/src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java similarity index 97% rename from src/main/java/backupmanager/Table/CheckboxCellRenderer.java rename to src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java index 43f0dd73..282dcc9a 100644 --- a/src/main/java/backupmanager/Table/CheckboxCellRenderer.java +++ b/src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import java.awt.Color; import java.awt.Component; diff --git a/src/main/java/backupmanager/Table/ProgressBarRenderer.java b/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java similarity index 97% rename from src/main/java/backupmanager/Table/ProgressBarRenderer.java rename to src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java index d152a374..37e8b3d1 100644 --- a/src/main/java/backupmanager/Table/ProgressBarRenderer.java +++ b/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import java.awt.Color; import java.awt.Component; @@ -36,4 +36,4 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole // Return the default (striped) component for non-progress values return c; } -} \ No newline at end of file +} diff --git a/src/main/java/backupmanager/Table/StripedRowRenderer.java b/src/main/java/backupmanager/gui/Table/StripedRowRenderer.java similarity index 96% rename from src/main/java/backupmanager/Table/StripedRowRenderer.java rename to src/main/java/backupmanager/gui/Table/StripedRowRenderer.java index 1bfea4ea..7e4dc80f 100644 --- a/src/main/java/backupmanager/Table/StripedRowRenderer.java +++ b/src/main/java/backupmanager/gui/Table/StripedRowRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import java.awt.Color; import java.awt.Component; diff --git a/src/main/java/backupmanager/Table/TableDataManager.java b/src/main/java/backupmanager/gui/Table/TableDataManager.java similarity index 98% rename from src/main/java/backupmanager/Table/TableDataManager.java rename to src/main/java/backupmanager/gui/Table/TableDataManager.java index e8f50502..fbd768fa 100644 --- a/src/main/java/backupmanager/Table/TableDataManager.java +++ b/src/main/java/backupmanager/gui/Table/TableDataManager.java @@ -1,4 +1,4 @@ -package backupmanager.Table; +package backupmanager.gui.Table; import java.time.format.DateTimeFormatter; import java.util.List; @@ -10,7 +10,7 @@ import org.slf4j.LoggerFactory; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; public class TableDataManager { diff --git a/src/main/java/backupmanager/component/About.java b/src/main/java/backupmanager/gui/component/About.java similarity index 98% rename from src/main/java/backupmanager/component/About.java rename to src/main/java/backupmanager/gui/component/About.java index 0a73e755..e7ecc1d1 100644 --- a/src/main/java/backupmanager/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -1,4 +1,4 @@ -package backupmanager.component; +package backupmanager.gui.component; import java.awt.Desktop; import java.awt.Graphics; diff --git a/src/main/java/backupmanager/component/AccentColorIcon.java b/src/main/java/backupmanager/gui/component/AccentColorIcon.java similarity index 85% rename from src/main/java/backupmanager/component/AccentColorIcon.java rename to src/main/java/backupmanager/gui/component/AccentColorIcon.java index bfad5926..402e40f2 100644 --- a/src/main/java/backupmanager/component/AccentColorIcon.java +++ b/src/main/java/backupmanager/gui/component/AccentColorIcon.java @@ -1,12 +1,15 @@ -package backupmanager.component; +package backupmanager.gui.component; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics2D; + +import javax.swing.UIManager; import com.formdev.flatlaf.FlatLaf; import com.formdev.flatlaf.icons.FlatAbstractIcon; import com.formdev.flatlaf.util.ColorFunctions; -import javax.swing.*; -import java.awt.*; - public class AccentColorIcon extends FlatAbstractIcon { private final String colorKey; diff --git a/src/main/java/backupmanager/component/EmptyModalBorder.java b/src/main/java/backupmanager/gui/component/EmptyModalBorder.java similarity index 95% rename from src/main/java/backupmanager/component/EmptyModalBorder.java rename to src/main/java/backupmanager/gui/component/EmptyModalBorder.java index 816942b0..e31b7a12 100644 --- a/src/main/java/backupmanager/component/EmptyModalBorder.java +++ b/src/main/java/backupmanager/gui/component/EmptyModalBorder.java @@ -1,4 +1,7 @@ -package backupmanager.component; +package backupmanager.gui.component; + +import java.awt.Color; +import java.awt.Component; import net.miginfocom.swing.MigLayout; import raven.modal.component.Modal; @@ -6,8 +9,6 @@ import raven.modal.listener.ModalCallback; import raven.modal.listener.ModalController; -import java.awt.*; - public class EmptyModalBorder extends Modal implements ModalBorderAction { protected final Component component; diff --git a/src/main/java/backupmanager/component/FormSearchButton.java b/src/main/java/backupmanager/gui/component/FormSearchButton.java similarity index 97% rename from src/main/java/backupmanager/component/FormSearchButton.java rename to src/main/java/backupmanager/gui/component/FormSearchButton.java index 8431181f..937b261c 100644 --- a/src/main/java/backupmanager/component/FormSearchButton.java +++ b/src/main/java/backupmanager/gui/component/FormSearchButton.java @@ -1,4 +1,4 @@ -package backupmanager.component; +package backupmanager.gui.component; import javax.swing.JButton; import javax.swing.JLabel; diff --git a/src/main/java/backupmanager/component/FormSearchPanel.java b/src/main/java/backupmanager/gui/component/FormSearchPanel.java similarity index 98% rename from src/main/java/backupmanager/component/FormSearchPanel.java rename to src/main/java/backupmanager/gui/component/FormSearchPanel.java index 38a38687..c242e1ca 100644 --- a/src/main/java/backupmanager/component/FormSearchPanel.java +++ b/src/main/java/backupmanager/gui/component/FormSearchPanel.java @@ -1,4 +1,4 @@ -package backupmanager.component; +package backupmanager.gui.component; import java.awt.Component; import java.awt.Container; @@ -35,10 +35,10 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.icons.FlatMenuArrowIcon; -import backupmanager.menu.MyMenuValidation; -import backupmanager.svg.SVGIconUIColor; -import backupmanager.system.Form; -import backupmanager.system.FormSearch; +import backupmanager.gui.menu.MyMenuValidation; +import backupmanager.gui.svg.SVGIconUIColor; +import backupmanager.gui.system.Form; +import backupmanager.gui.system.FormSearch; import backupmanager.utils.DemoPreferences; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; diff --git a/src/main/java/backupmanager/component/RefreshLine.java b/src/main/java/backupmanager/gui/component/RefreshLine.java similarity index 87% rename from src/main/java/backupmanager/component/RefreshLine.java rename to src/main/java/backupmanager/gui/component/RefreshLine.java index 3fc65cf1..cf5ebe26 100644 --- a/src/main/java/backupmanager/component/RefreshLine.java +++ b/src/main/java/backupmanager/gui/component/RefreshLine.java @@ -1,13 +1,18 @@ -package backupmanager.component; +package backupmanager.gui.component; + +import java.awt.AlphaComposite; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.geom.RoundRectangle2D; + +import javax.swing.JPanel; +import javax.swing.UIManager; import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.CubicBezierEasing; import com.formdev.flatlaf.util.UIScale; -import javax.swing.*; -import java.awt.*; -import java.awt.geom.RoundRectangle2D; - public class RefreshLine extends JPanel { private Animator animator; diff --git a/src/main/java/backupmanager/component/ToolBarSelection.java b/src/main/java/backupmanager/gui/component/ToolBarSelection.java similarity index 86% rename from src/main/java/backupmanager/component/ToolBarSelection.java rename to src/main/java/backupmanager/gui/component/ToolBarSelection.java index 8f4de483..170c0fd4 100644 --- a/src/main/java/backupmanager/component/ToolBarSelection.java +++ b/src/main/java/backupmanager/gui/component/ToolBarSelection.java @@ -1,10 +1,13 @@ -package backupmanager.component; +package backupmanager.gui.component; -import com.formdev.flatlaf.FlatClientProperties; - -import javax.swing.*; import java.util.function.Consumer; +import javax.swing.ButtonGroup; +import javax.swing.JToggleButton; +import javax.swing.JToolBar; + +import com.formdev.flatlaf.FlatClientProperties; + public class ToolBarSelection extends JToolBar { public ToolBarSelection(T[] data, Consumer callBack) { diff --git a/src/main/java/backupmanager/component/chart/BarChart.java b/src/main/java/backupmanager/gui/component/chart/BarChart.java similarity index 91% rename from src/main/java/backupmanager/component/chart/BarChart.java rename to src/main/java/backupmanager/gui/component/chart/BarChart.java index ce706966..d21b7794 100644 --- a/src/main/java/backupmanager/component/chart/BarChart.java +++ b/src/main/java/backupmanager/gui/component/chart/BarChart.java @@ -1,4 +1,4 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; @@ -10,8 +10,9 @@ import org.jfree.chart.plot.Plot; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.data.category.CategoryDataset; -import backupmanager.component.chart.renderer.bar.ChartBarRenderer; -import backupmanager.component.chart.themes.ChartDrawingSupplier; + +import backupmanager.gui.component.chart.renderer.bar.ChartBarRenderer; +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; public class BarChart extends DefaultChartPanel { diff --git a/src/main/java/backupmanager/component/chart/CandlestickChart.java b/src/main/java/backupmanager/gui/component/chart/CandlestickChart.java similarity index 91% rename from src/main/java/backupmanager/component/chart/CandlestickChart.java rename to src/main/java/backupmanager/gui/component/chart/CandlestickChart.java index b22a7d47..9e5f63e7 100644 --- a/src/main/java/backupmanager/component/chart/CandlestickChart.java +++ b/src/main/java/backupmanager/gui/component/chart/CandlestickChart.java @@ -1,6 +1,14 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; -import org.jfree.chart.*; +import java.awt.geom.Rectangle2D; +import java.text.DateFormat; +import java.text.NumberFormat; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartMouseEvent; +import org.jfree.chart.ChartMouseListener; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.Axis; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; @@ -10,14 +18,11 @@ import org.jfree.chart.plot.XYPlot; import org.jfree.chart.ui.RectangleAnchor; import org.jfree.data.xy.OHLCDataset; -import backupmanager.component.chart.renderer.other.ChartCandlestickRenderer; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import backupmanager.component.chart.utils.CustomCrosshairToolTip; -import backupmanager.component.chart.utils.DateCrosshairLabelGenerator; -import java.awt.geom.Rectangle2D; -import java.text.DateFormat; -import java.text.NumberFormat; +import backupmanager.gui.component.chart.renderer.other.ChartCandlestickRenderer; +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; +import backupmanager.gui.component.chart.utils.CustomCrosshairToolTip; +import backupmanager.gui.component.chart.utils.DateCrosshairLabelGenerator; public class CandlestickChart extends DefaultChartPanel { diff --git a/src/main/java/backupmanager/component/chart/DefaultChartPanel.java b/src/main/java/backupmanager/gui/component/chart/DefaultChartPanel.java similarity index 89% rename from src/main/java/backupmanager/component/chart/DefaultChartPanel.java rename to src/main/java/backupmanager/gui/component/chart/DefaultChartPanel.java index 74613707..2e4c722a 100644 --- a/src/main/java/backupmanager/component/chart/DefaultChartPanel.java +++ b/src/main/java/backupmanager/gui/component/chart/DefaultChartPanel.java @@ -1,15 +1,19 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; + +import java.awt.Color; + +import javax.swing.JPanel; +import javax.swing.UIManager; -import com.formdev.flatlaf.FlatClientProperties; -import net.miginfocom.swing.MigLayout; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.ui.RectangleInsets; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import backupmanager.component.chart.themes.DefaultChartTheme; -import javax.swing.*; -import java.awt.*; +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; +import net.miginfocom.swing.MigLayout; public abstract class DefaultChartPanel extends JPanel { diff --git a/src/main/java/backupmanager/component/chart/PieChart.java b/src/main/java/backupmanager/gui/component/chart/PieChart.java similarity index 91% rename from src/main/java/backupmanager/component/chart/PieChart.java rename to src/main/java/backupmanager/gui/component/chart/PieChart.java index 0bb31ed6..674f8bde 100644 --- a/src/main/java/backupmanager/component/chart/PieChart.java +++ b/src/main/java/backupmanager/gui/component/chart/PieChart.java @@ -1,6 +1,7 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; + +import java.awt.BasicStroke; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; @@ -8,9 +9,10 @@ import org.jfree.chart.plot.PiePlot; import org.jfree.chart.ui.RectangleInsets; import org.jfree.data.general.PieDataset; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import java.awt.*; +import com.formdev.flatlaf.util.UIScale; + +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; public class PieChart extends DefaultChartPanel { diff --git a/src/main/java/backupmanager/component/chart/SpiderChart.java b/src/main/java/backupmanager/gui/component/chart/SpiderChart.java similarity index 91% rename from src/main/java/backupmanager/component/chart/SpiderChart.java rename to src/main/java/backupmanager/gui/component/chart/SpiderChart.java index 2c1c0a52..4fe8df5a 100644 --- a/src/main/java/backupmanager/component/chart/SpiderChart.java +++ b/src/main/java/backupmanager/gui/component/chart/SpiderChart.java @@ -1,6 +1,12 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Paint; +import java.util.List; + +import javax.swing.UIManager; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.ChartMouseEvent; import org.jfree.chart.ChartMouseListener; import org.jfree.chart.ChartPanel; @@ -8,12 +14,11 @@ import org.jfree.chart.entity.LegendItemEntity; import org.jfree.chart.plot.SpiderWebPlot; import org.jfree.data.category.CategoryDataset; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import backupmanager.component.chart.themes.DefaultChartTheme; -import javax.swing.*; -import java.awt.*; -import java.util.List; +import com.formdev.flatlaf.util.UIScale; + +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; public class SpiderChart extends DefaultChartPanel { diff --git a/src/main/java/backupmanager/component/chart/TimeSeriesChart.java b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java similarity index 90% rename from src/main/java/backupmanager/component/chart/TimeSeriesChart.java rename to src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java index a05698bf..fd9e82e9 100644 --- a/src/main/java/backupmanager/component/chart/TimeSeriesChart.java +++ b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java @@ -1,6 +1,19 @@ -package backupmanager.component.chart; +package backupmanager.gui.component.chart; -import org.jfree.chart.*; +import java.awt.Color; +import java.awt.Font; +import java.awt.geom.Rectangle2D; +import java.text.DateFormat; +import java.text.NumberFormat; +import java.util.Date; + +import javax.swing.UIManager; + +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartMouseEvent; +import org.jfree.chart.ChartMouseListener; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.Axis; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; @@ -8,17 +21,11 @@ import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.data.xy.TableXYDataset; import org.jfree.data.xy.XYDataset; -import backupmanager.component.chart.renderer.ChartXYCurveRenderer; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import backupmanager.component.chart.themes.DefaultChartTheme; -import backupmanager.component.chart.utils.MultiXYTextAnnotation; -import javax.swing.*; -import java.awt.*; -import java.awt.geom.Rectangle2D; -import java.text.DateFormat; -import java.text.NumberFormat; -import java.util.Date; +import backupmanager.gui.component.chart.renderer.ChartXYCurveRenderer; +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; +import backupmanager.gui.component.chart.utils.MultiXYTextAnnotation; public class TimeSeriesChart extends DefaultChartPanel { diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartDeviationStepRenderer.java similarity index 86% rename from src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartDeviationStepRenderer.java index 2222f7c7..6b0292e9 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartDeviationStepRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartDeviationStepRenderer.java @@ -1,9 +1,10 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; + +import java.awt.BasicStroke; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.renderer.xy.DeviationStepRenderer; -import java.awt.*; +import com.formdev.flatlaf.util.UIScale; public class ChartDeviationStepRenderer extends DeviationStepRenderer { diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartStackedXYBarRenderer.java similarity index 84% rename from src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartStackedXYBarRenderer.java index 7df075d5..41deb528 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartStackedXYBarRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartStackedXYBarRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; import org.jfree.chart.renderer.xy.StackedXYBarRenderer; diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYBarRenderer.java similarity index 77% rename from src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartXYBarRenderer.java index 293f28cc..4ab9ebc8 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartXYBarRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYBarRenderer.java @@ -1,7 +1,8 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; import org.jfree.chart.renderer.xy.ClusteredXYBarRenderer; -import backupmanager.component.chart.themes.DefaultChartTheme; + +import backupmanager.gui.component.chart.themes.DefaultChartTheme; public class ChartXYBarRenderer extends ClusteredXYBarRenderer { diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYCurveRenderer.java similarity index 92% rename from src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartXYCurveRenderer.java index 7b0251f8..da1f2125 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartXYCurveRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYCurveRenderer.java @@ -1,8 +1,9 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.renderer.xy.XYSplineRenderer; +import com.formdev.flatlaf.util.UIScale; + public class ChartXYCurveRenderer extends XYSplineRenderer { private static final int precision = 10; diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYDifferenceRenderer.java similarity index 75% rename from src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartXYDifferenceRenderer.java index 79078702..2833bdf3 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartXYDifferenceRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYDifferenceRenderer.java @@ -1,9 +1,10 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; + +import java.awt.BasicStroke; import org.jfree.chart.renderer.xy.XYDifferenceRenderer; -import backupmanager.component.chart.themes.DefaultChartTheme; -import java.awt.*; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; public class ChartXYDifferenceRenderer extends XYDifferenceRenderer { diff --git a/src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYLineRenderer.java similarity index 79% rename from src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/ChartXYLineRenderer.java index d5dba0c5..53784c82 100644 --- a/src/main/java/backupmanager/component/chart/renderer/ChartXYLineRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/ChartXYLineRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.component.chart.renderer; +package backupmanager.gui.component.chart.renderer; public class ChartXYLineRenderer extends ChartXYCurveRenderer { diff --git a/src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/bar/ChartBarRenderer.java similarity index 73% rename from src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/bar/ChartBarRenderer.java index e9703049..e267cf28 100644 --- a/src/main/java/backupmanager/component/chart/renderer/bar/ChartBarRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/bar/ChartBarRenderer.java @@ -1,7 +1,8 @@ -package backupmanager.component.chart.renderer.bar; +package backupmanager.gui.component.chart.renderer.bar; import org.jfree.chart.renderer.category.BarRenderer; -import backupmanager.component.chart.themes.ChartDrawingSupplier; + +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; public class ChartBarRenderer extends BarRenderer { diff --git a/src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java b/src/main/java/backupmanager/gui/component/chart/renderer/other/ChartCandlestickRenderer.java similarity index 90% rename from src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java rename to src/main/java/backupmanager/gui/component/chart/renderer/other/ChartCandlestickRenderer.java index 9b21904f..75aae941 100644 --- a/src/main/java/backupmanager/component/chart/renderer/other/ChartCandlestickRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/renderer/other/ChartCandlestickRenderer.java @@ -1,10 +1,12 @@ -package backupmanager.component.chart.renderer.other; +package backupmanager.gui.component.chart.renderer.other; + +import java.awt.Color; +import java.awt.Paint; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.renderer.xy.CandlestickRenderer; import org.jfree.data.xy.OHLCDataset; -import java.awt.*; +import com.formdev.flatlaf.util.UIScale; public class ChartCandlestickRenderer extends CandlestickRenderer { diff --git a/src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java b/src/main/java/backupmanager/gui/component/chart/themes/ChartDrawingSupplier.java similarity index 94% rename from src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java rename to src/main/java/backupmanager/gui/component/chart/themes/ChartDrawingSupplier.java index a2f2769e..9d119166 100644 --- a/src/main/java/backupmanager/component/chart/themes/ChartDrawingSupplier.java +++ b/src/main/java/backupmanager/gui/component/chart/themes/ChartDrawingSupplier.java @@ -1,12 +1,18 @@ -package backupmanager.component.chart.themes; +package backupmanager.gui.component.chart.themes; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Paint; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.plot.DefaultDrawingSupplier; import org.jfree.chart.ui.RectangleInsets; -import java.awt.*; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Rectangle2D; +import com.formdev.flatlaf.util.UIScale; public class ChartDrawingSupplier extends DefaultDrawingSupplier { diff --git a/src/main/java/backupmanager/component/chart/themes/ColorThemes.java b/src/main/java/backupmanager/gui/component/chart/themes/ColorThemes.java similarity index 97% rename from src/main/java/backupmanager/component/chart/themes/ColorThemes.java rename to src/main/java/backupmanager/gui/component/chart/themes/ColorThemes.java index a4211976..8f69c3ab 100644 --- a/src/main/java/backupmanager/component/chart/themes/ColorThemes.java +++ b/src/main/java/backupmanager/gui/component/chart/themes/ColorThemes.java @@ -1,6 +1,6 @@ -package backupmanager.component.chart.themes; +package backupmanager.gui.component.chart.themes; -import java.awt.*; +import java.awt.Color; public enum ColorThemes { DEFAULT( diff --git a/src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java b/src/main/java/backupmanager/gui/component/chart/themes/DefaultChartTheme.java similarity index 95% rename from src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java rename to src/main/java/backupmanager/gui/component/chart/themes/DefaultChartTheme.java index aed72261..2f23ca04 100644 --- a/src/main/java/backupmanager/component/chart/themes/DefaultChartTheme.java +++ b/src/main/java/backupmanager/gui/component/chart/themes/DefaultChartTheme.java @@ -1,6 +1,15 @@ -package backupmanager.component.chart.themes; +package backupmanager.gui.component.chart.themes; + +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.geom.RectangularShape; + +import javax.swing.UIManager; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.JFreeChart; import org.jfree.chart.StandardChartTheme; import org.jfree.chart.plot.PiePlot; @@ -14,9 +23,7 @@ import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.ui.RectangleEdge; -import javax.swing.*; -import java.awt.*; -import java.awt.geom.RectangularShape; +import com.formdev.flatlaf.util.UIScale; public class DefaultChartTheme extends StandardChartTheme { diff --git a/src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java b/src/main/java/backupmanager/gui/component/chart/utils/CustomCrosshairToolTip.java similarity index 82% rename from src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java rename to src/main/java/backupmanager/gui/component/chart/utils/CustomCrosshairToolTip.java index 9fa1f794..069133ca 100644 --- a/src/main/java/backupmanager/component/chart/utils/CustomCrosshairToolTip.java +++ b/src/main/java/backupmanager/gui/component/chart/utils/CustomCrosshairToolTip.java @@ -1,10 +1,13 @@ -package backupmanager.component.chart.utils; +package backupmanager.gui.component.chart.utils; + +import java.awt.Color; +import java.awt.Font; + +import javax.swing.UIManager; import org.jfree.chart.plot.Crosshair; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import javax.swing.*; -import java.awt.*; +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; public class CustomCrosshairToolTip extends Crosshair { diff --git a/src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java b/src/main/java/backupmanager/gui/component/chart/utils/DateCrosshairLabelGenerator.java similarity index 93% rename from src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java rename to src/main/java/backupmanager/gui/component/chart/utils/DateCrosshairLabelGenerator.java index 2e17d5f3..6662f5eb 100644 --- a/src/main/java/backupmanager/component/chart/utils/DateCrosshairLabelGenerator.java +++ b/src/main/java/backupmanager/gui/component/chart/utils/DateCrosshairLabelGenerator.java @@ -1,12 +1,12 @@ -package backupmanager.component.chart.utils; - -import org.jfree.chart.labels.CrosshairLabelGenerator; -import org.jfree.chart.plot.Crosshair; +package backupmanager.gui.component.chart.utils; import java.io.Serializable; import java.text.DateFormat; import java.text.MessageFormat; +import org.jfree.chart.labels.CrosshairLabelGenerator; +import org.jfree.chart.plot.Crosshair; + public class DateCrosshairLabelGenerator implements CrosshairLabelGenerator, Serializable { private final String labelTemplate; diff --git a/src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java b/src/main/java/backupmanager/gui/component/chart/utils/MultiXYTextAnnotation.java similarity index 98% rename from src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java rename to src/main/java/backupmanager/gui/component/chart/utils/MultiXYTextAnnotation.java index 48f3130c..74f6b7ba 100644 --- a/src/main/java/backupmanager/component/chart/utils/MultiXYTextAnnotation.java +++ b/src/main/java/backupmanager/gui/component/chart/utils/MultiXYTextAnnotation.java @@ -1,6 +1,19 @@ -package backupmanager.component.chart.utils; +package backupmanager.gui.component.chart.utils; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Line2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.text.NumberFormat; -import com.formdev.flatlaf.util.UIScale; import org.jfree.chart.annotations.AbstractXYAnnotation; import org.jfree.chart.annotations.XYTextAnnotation; import org.jfree.chart.axis.ValueAxis; @@ -15,14 +28,10 @@ import org.jfree.chart.util.Args; import org.jfree.data.general.DatasetUtils; import org.jfree.data.xy.XYDataset; -import backupmanager.component.chart.themes.ChartDrawingSupplier; -import java.awt.*; -import java.awt.geom.Ellipse2D; -import java.awt.geom.Line2D; -import java.awt.geom.Rectangle2D; -import java.awt.geom.RoundRectangle2D; -import java.text.NumberFormat; +import com.formdev.flatlaf.util.UIScale; + +import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; public class MultiXYTextAnnotation extends AbstractXYAnnotation { diff --git a/src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java b/src/main/java/backupmanager/gui/component/chart/utils/ToolBarCategoryOrientation.java similarity index 81% rename from src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java rename to src/main/java/backupmanager/gui/component/chart/utils/ToolBarCategoryOrientation.java index 2c34ceb1..d1911375 100644 --- a/src/main/java/backupmanager/component/chart/utils/ToolBarCategoryOrientation.java +++ b/src/main/java/backupmanager/gui/component/chart/utils/ToolBarCategoryOrientation.java @@ -1,8 +1,9 @@ -package backupmanager.component.chart.utils; +package backupmanager.gui.component.chart.utils; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.PlotOrientation; -import backupmanager.component.ToolBarSelection; + +import backupmanager.gui.component.ToolBarSelection; public class ToolBarCategoryOrientation extends ToolBarSelection { diff --git a/src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java b/src/main/java/backupmanager/gui/component/chart/utils/ToolBarTimeSeriesChartRenderer.java similarity index 61% rename from src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java rename to src/main/java/backupmanager/gui/component/chart/utils/ToolBarTimeSeriesChartRenderer.java index 5488d8b8..905e60c4 100644 --- a/src/main/java/backupmanager/component/chart/utils/ToolBarTimeSeriesChartRenderer.java +++ b/src/main/java/backupmanager/gui/component/chart/utils/ToolBarTimeSeriesChartRenderer.java @@ -1,9 +1,13 @@ -package backupmanager.component.chart.utils; +package backupmanager.gui.component.chart.utils; import org.jfree.chart.renderer.xy.XYItemRenderer; -import backupmanager.component.ToolBarSelection; -import backupmanager.component.chart.TimeSeriesChart; -import backupmanager.component.chart.renderer.*; + +import backupmanager.gui.component.ToolBarSelection; +import backupmanager.gui.component.chart.TimeSeriesChart; +import backupmanager.gui.component.chart.renderer.ChartDeviationStepRenderer; +import backupmanager.gui.component.chart.renderer.ChartStackedXYBarRenderer; +import backupmanager.gui.component.chart.renderer.ChartXYBarRenderer; +import backupmanager.gui.component.chart.renderer.ChartXYDifferenceRenderer; public class ToolBarTimeSeriesChartRenderer extends ToolBarSelection { diff --git a/src/main/java/backupmanager/component/dashboard/CardBox.java b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java similarity index 87% rename from src/main/java/backupmanager/component/dashboard/CardBox.java rename to src/main/java/backupmanager/gui/component/dashboard/CardBox.java index c6114871..336ef9f2 100644 --- a/src/main/java/backupmanager/component/dashboard/CardBox.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java @@ -1,13 +1,17 @@ -package backupmanager.component.dashboard; +package backupmanager.gui.component.dashboard; -import com.formdev.flatlaf.FlatClientProperties; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; +import java.awt.Color; import java.util.ArrayList; import java.util.List; +import javax.swing.Icon; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +import com.formdev.flatlaf.FlatClientProperties; + +import net.miginfocom.swing.MigLayout; + public class CardBox extends JPanel { private final List cardItems = new ArrayList<>(); diff --git a/src/main/java/backupmanager/component/dashboard/CardItem.java b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java similarity index 93% rename from src/main/java/backupmanager/component/dashboard/CardItem.java rename to src/main/java/backupmanager/gui/component/dashboard/CardItem.java index d51b7e1b..fd3c326c 100644 --- a/src/main/java/backupmanager/component/dashboard/CardItem.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java @@ -1,11 +1,15 @@ -package backupmanager.component.dashboard; +package backupmanager.gui.component.dashboard; + +import java.awt.Color; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.extras.FlatSVGIcon; -import net.miginfocom.swing.MigLayout; -import javax.swing.*; -import java.awt.*; +import net.miginfocom.swing.MigLayout; public class CardItem extends JPanel { diff --git a/src/main/java/backupmanager/customwidgets/ModernTextField.java b/src/main/java/backupmanager/gui/customwidgets/ModernTextField.java similarity index 99% rename from src/main/java/backupmanager/customwidgets/ModernTextField.java rename to src/main/java/backupmanager/gui/customwidgets/ModernTextField.java index 455a9ca7..7426a97f 100644 --- a/src/main/java/backupmanager/customwidgets/ModernTextField.java +++ b/src/main/java/backupmanager/gui/customwidgets/ModernTextField.java @@ -1,4 +1,4 @@ -package backupmanager.customwidgets; +package backupmanager.gui.customwidgets; import java.awt.Color; import java.awt.FontMetrics; diff --git a/src/main/java/backupmanager/forms/FormDashboard.java b/src/main/java/backupmanager/gui/forms/FormDashboard.java similarity index 90% rename from src/main/java/backupmanager/forms/FormDashboard.java rename to src/main/java/backupmanager/gui/forms/FormDashboard.java index ee7ac5a5..5d783c2f 100644 --- a/src/main/java/backupmanager/forms/FormDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormDashboard.java @@ -1,4 +1,4 @@ -package backupmanager.forms; +package backupmanager.gui.forms; import java.awt.Color; import java.awt.Component; @@ -20,20 +20,20 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.UIScale; -import backupmanager.component.ToolBarSelection; -import backupmanager.component.chart.BarChart; -import backupmanager.component.chart.CandlestickChart; -import backupmanager.component.chart.PieChart; -import backupmanager.component.chart.SpiderChart; -import backupmanager.component.chart.TimeSeriesChart; -import backupmanager.component.chart.renderer.other.ChartCandlestickRenderer; -import backupmanager.component.chart.themes.ColorThemes; -import backupmanager.component.chart.themes.DefaultChartTheme; -import backupmanager.component.chart.utils.ToolBarCategoryOrientation; -import backupmanager.component.chart.utils.ToolBarTimeSeriesChartRenderer; -import backupmanager.component.dashboard.CardBox; -import backupmanager.sample.SampleData; -import backupmanager.system.Form; +import backupmanager.gui.component.ToolBarSelection; +import backupmanager.gui.component.chart.BarChart; +import backupmanager.gui.component.chart.CandlestickChart; +import backupmanager.gui.component.chart.PieChart; +import backupmanager.gui.component.chart.SpiderChart; +import backupmanager.gui.component.chart.TimeSeriesChart; +import backupmanager.gui.component.chart.renderer.other.ChartCandlestickRenderer; +import backupmanager.gui.component.chart.themes.ColorThemes; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; +import backupmanager.gui.component.chart.utils.ToolBarCategoryOrientation; +import backupmanager.gui.component.chart.utils.ToolBarTimeSeriesChartRenderer; +import backupmanager.gui.component.dashboard.CardBox; +import backupmanager.gui.sample.SampleData; +import backupmanager.gui.system.Form; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; diff --git a/src/main/java/backupmanager/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java similarity index 98% rename from src/main/java/backupmanager/forms/FormSetting.java rename to src/main/java/backupmanager/gui/forms/FormSetting.java index e2a9388c..a1ac3b65 100644 --- a/src/main/java/backupmanager/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -1,4 +1,4 @@ -package backupmanager.forms; +package backupmanager.gui.forms; import java.awt.Color; import java.awt.Component; @@ -30,10 +30,10 @@ import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.ScaledEmptyBorder; -import backupmanager.component.AccentColorIcon; -import backupmanager.system.Form; -import backupmanager.system.FormManager; -import backupmanager.themes.PanelThemes; +import backupmanager.gui.component.AccentColorIcon; +import backupmanager.gui.system.Form; +import backupmanager.gui.system.FormManager; +import backupmanager.gui.themes.PanelThemes; import backupmanager.utils.DemoPreferences; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; diff --git a/src/main/java/backupmanager/forms/FormTable.java b/src/main/java/backupmanager/gui/forms/FormTable.java similarity index 98% rename from src/main/java/backupmanager/forms/FormTable.java rename to src/main/java/backupmanager/gui/forms/FormTable.java index 962487ec..ec65cbc2 100644 --- a/src/main/java/backupmanager/forms/FormTable.java +++ b/src/main/java/backupmanager/gui/forms/FormTable.java @@ -1,4 +1,4 @@ -package backupmanager.forms; +package backupmanager.gui.forms; import java.awt.Component; import java.io.IOException; @@ -25,11 +25,11 @@ import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.extras.FlatSVGIcon; -import backupmanager.sample.csv.CSVDataReader; -import backupmanager.sample.csv.ResponseCSV; -import backupmanager.simple.SimpleInputForms; -import backupmanager.svg.SVGButton; -import backupmanager.system.Form; +import backupmanager.gui.sample.csv.CSVDataReader; +import backupmanager.gui.sample.csv.ResponseCSV; +import backupmanager.gui.simple.SimpleInputForms; +import backupmanager.gui.svg.SVGButton; +import backupmanager.gui.system.Form; import backupmanager.utils.SystemForm; import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; diff --git a/src/main/java/backupmanager/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java similarity index 82% rename from src/main/java/backupmanager/frames/BackupManager.java rename to src/main/java/backupmanager/gui/frames/BackupManager.java index c6b760af..540332ee 100644 --- a/src/main/java/backupmanager/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -1,4 +1,4 @@ -package backupmanager.frames; +package backupmanager.gui.frames; import java.awt.Dimension; @@ -7,8 +7,8 @@ import com.formdev.flatlaf.FlatClientProperties; -import backupmanager.menu.MyDrawerBuilder; -import backupmanager.system.FormManager; +import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.system.FormManager; import raven.modal.Drawer; public class BackupManager extends JFrame{ diff --git a/src/main/java/backupmanager/frames/BackupManagerGUI.form b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.form similarity index 96% rename from src/main/java/backupmanager/frames/BackupManagerGUI.form rename to src/main/java/backupmanager/gui/frames/BackupManagerGUI.form index 45880cec..f9a90456 100644 --- a/src/main/java/backupmanager/frames/BackupManagerGUI.form +++ b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.form @@ -138,7 +138,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -170,19 +170,19 @@ - + - + - + @@ -193,7 +193,7 @@ - + @@ -208,7 +208,7 @@ - + @@ -216,7 +216,7 @@ - + @@ -234,7 +234,7 @@ - + @@ -242,7 +242,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -258,12 +258,12 @@ - + - + @@ -271,7 +271,7 @@ - + @@ -288,7 +288,7 @@ - + @@ -296,7 +296,7 @@ - + @@ -467,7 +467,7 @@ - + @@ -563,7 +563,7 @@ - + @@ -577,7 +577,7 @@ - + diff --git a/src/main/java/backupmanager/frames/BackupManagerGUI.java b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java similarity index 95% rename from src/main/java/backupmanager/frames/BackupManagerGUI.java rename to src/main/java/backupmanager/gui/frames/BackupManagerGUI.java index 01b6d3b1..fdaa70eb 100644 --- a/src/main/java/backupmanager/frames/BackupManagerGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java @@ -1,4 +1,4 @@ -package backupmanager.frames; +package backupmanager.gui.frames; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -22,7 +22,7 @@ import com.formdev.flatlaf.FlatClientProperties; -import backupmanager.Controllers.GuiController; +import backupmanager.gui.Controllers.GuiController; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.Confingurations; import backupmanager.Enums.ConfigKey; @@ -39,14 +39,14 @@ import backupmanager.Managers.ThemeManager; import backupmanager.Services.BackupObserver; import backupmanager.Services.BackupService; -import backupmanager.Table.BackupTable; -import backupmanager.Table.BackupTableModel; -import backupmanager.Table.CheckboxCellRenderer; -import backupmanager.Table.StripedRowRenderer; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.BackupTableModel; +import backupmanager.gui.Table.CheckboxCellRenderer; +import backupmanager.gui.Table.StripedRowRenderer; import backupmanager.database.Repositories.BackupConfigurationRepository; -import backupmanager.frames.Controllers.BackupManagerController; -import backupmanager.frames.Controllers.BackupMenuController; -import backupmanager.frames.Controllers.BackupPopupController; +import backupmanager.gui.frames.Controllers.BackupManagerController; +import backupmanager.gui.frames.Controllers.BackupMenuController; +import backupmanager.gui.frames.Controllers.BackupPopupController; public final class BackupManagerGUI extends javax.swing.JFrame { private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); @@ -225,12 +225,12 @@ private void initComponents() { panelBackupList = new javax.swing.JPanel(); detailsLabel = new javax.swing.JLabel(); researchField = new javax.swing.JTextField(); - exportAsPdfBtn = new backupmanager.svg.SVGButton(); + exportAsPdfBtn = new backupmanager.gui.svg.SVGButton(); jLabel1 = new javax.swing.JLabel(); jScrollPane1 = new javax.swing.JScrollPane(); table = new javax.swing.JTable(); - addBackupEntryButton = new backupmanager.svg.SVGButton(); - exportAsCsvBtn = new backupmanager.svg.SVGButton(); + addBackupEntryButton = new backupmanager.gui.svg.SVGButton(); + exportAsCsvBtn = new backupmanager.gui.svg.SVGButton(); ExportLabel = new javax.swing.JLabel(); panelDashboard = new javax.swing.JPanel(); chart2 = new javax.swing.JPanel(); @@ -248,28 +248,28 @@ private void initComponents() { totalSpaceNumber = new javax.swing.JLabel(); jMenuBar1 = new javax.swing.JMenuBar(); jMenu1 = new javax.swing.JMenu(); - MenuNew = new backupmanager.svg.SVGMenuItem(); - MenuSave = new backupmanager.svg.SVGMenuItem(); - MenuSaveWithName = new backupmanager.svg.SVGMenuItem(); + MenuNew = new backupmanager.gui.svg.SVGMenuItem(); + MenuSave = new backupmanager.gui.svg.SVGMenuItem(); + MenuSaveWithName = new backupmanager.gui.svg.SVGMenuItem(); jSeparator4 = new javax.swing.JPopupMenu.Separator(); - MenuImport = new backupmanager.svg.SVGMenuItem(); - MenuExport = new backupmanager.svg.SVGMenuItem(); + MenuImport = new backupmanager.gui.svg.SVGMenuItem(); + MenuExport = new backupmanager.gui.svg.SVGMenuItem(); jSeparator5 = new javax.swing.JPopupMenu.Separator(); - MenuClear = new backupmanager.svg.SVGMenuItem(); - MenuHistory = new backupmanager.svg.SVGMenuItem(); + MenuClear = new backupmanager.gui.svg.SVGMenuItem(); + MenuHistory = new backupmanager.gui.svg.SVGMenuItem(); jMenu2 = new javax.swing.JMenu(); - MenuPreferences = new backupmanager.svg.SVGMenuItem(); - MenuQuit = new backupmanager.svg.SVGMenuItem(); + MenuPreferences = new backupmanager.gui.svg.SVGMenuItem(); + MenuQuit = new backupmanager.gui.svg.SVGMenuItem(); jMenu3 = new javax.swing.JMenu(); - MenuWebsite = new backupmanager.svg.SVGMenuItem(); - MenuInfoPage = new backupmanager.svg.SVGMenuItem(); - MenuShare = new backupmanager.svg.SVGMenuItem(); - MenuDonate = new backupmanager.svg.SVGMenu(); - MenuPaypalDonate = new backupmanager.svg.SVGMenuItem(); - MenuBuyMeACoffeDonate = new backupmanager.svg.SVGMenuItem(); + MenuWebsite = new backupmanager.gui.svg.SVGMenuItem(); + MenuInfoPage = new backupmanager.gui.svg.SVGMenuItem(); + MenuShare = new backupmanager.gui.svg.SVGMenuItem(); + MenuDonate = new backupmanager.gui.svg.SVGMenu(); + MenuPaypalDonate = new backupmanager.gui.svg.SVGMenuItem(); + MenuBuyMeACoffeDonate = new backupmanager.gui.svg.SVGMenuItem(); jMenu5 = new javax.swing.JMenu(); - MenuBugReport = new backupmanager.svg.SVGMenuItem(); - MenuSupport = new backupmanager.svg.SVGMenuItem(); + MenuBugReport = new backupmanager.gui.svg.SVGMenuItem(); + MenuSupport = new backupmanager.gui.svg.SVGMenuItem(); EditPoputItem.setText("Edit"); EditPoputItem.addActionListener(new java.awt.event.ActionListener() { @@ -1133,35 +1133,35 @@ private void initializeMenuItems() { private javax.swing.JMenuItem DuplicatePopupItem; private javax.swing.JMenuItem EditPoputItem; private javax.swing.JLabel ExportLabel; - private backupmanager.svg.SVGMenuItem MenuBugReport; - private backupmanager.svg.SVGMenuItem MenuBuyMeACoffeDonate; - private backupmanager.svg.SVGMenuItem MenuClear; - private backupmanager.svg.SVGMenu MenuDonate; - private backupmanager.svg.SVGMenuItem MenuExport; - private backupmanager.svg.SVGMenuItem MenuHistory; - private backupmanager.svg.SVGMenuItem MenuImport; - private backupmanager.svg.SVGMenuItem MenuInfoPage; - private backupmanager.svg.SVGMenuItem MenuNew; - private backupmanager.svg.SVGMenuItem MenuPaypalDonate; - private backupmanager.svg.SVGMenuItem MenuPreferences; - private backupmanager.svg.SVGMenuItem MenuQuit; - private backupmanager.svg.SVGMenuItem MenuSave; - private backupmanager.svg.SVGMenuItem MenuSaveWithName; - private backupmanager.svg.SVGMenuItem MenuShare; - private backupmanager.svg.SVGMenuItem MenuSupport; - private backupmanager.svg.SVGMenuItem MenuWebsite; + private backupmanager.gui.svg.SVGMenuItem MenuBugReport; + private backupmanager.gui.svg.SVGMenuItem MenuBuyMeACoffeDonate; + private backupmanager.gui.svg.SVGMenuItem MenuClear; + private backupmanager.gui.svg.SVGMenu MenuDonate; + private backupmanager.gui.svg.SVGMenuItem MenuExport; + private backupmanager.gui.svg.SVGMenuItem MenuHistory; + private backupmanager.gui.svg.SVGMenuItem MenuImport; + private backupmanager.gui.svg.SVGMenuItem MenuInfoPage; + private backupmanager.gui.svg.SVGMenuItem MenuNew; + private backupmanager.gui.svg.SVGMenuItem MenuPaypalDonate; + private backupmanager.gui.svg.SVGMenuItem MenuPreferences; + private backupmanager.gui.svg.SVGMenuItem MenuQuit; + private backupmanager.gui.svg.SVGMenuItem MenuSave; + private backupmanager.gui.svg.SVGMenuItem MenuSaveWithName; + private backupmanager.gui.svg.SVGMenuItem MenuShare; + private backupmanager.gui.svg.SVGMenuItem MenuSupport; + private backupmanager.gui.svg.SVGMenuItem MenuWebsite; private javax.swing.JMenuItem OpenInitialDestinationItem; private javax.swing.JMenuItem OpenInitialFolderItem; private javax.swing.JMenuItem RunBackupPopupItem; private javax.swing.JPopupMenu TablePopup; - private backupmanager.svg.SVGButton addBackupEntryButton; + private backupmanager.gui.svg.SVGButton addBackupEntryButton; private javax.swing.JPanel chart1; private javax.swing.JLabel chart1TitleLabel; private javax.swing.JPanel chart2; private javax.swing.JLabel chart2TitleLabel; private javax.swing.JLabel detailsLabel; - private backupmanager.svg.SVGButton exportAsCsvBtn; - private backupmanager.svg.SVGButton exportAsPdfBtn; + private backupmanager.gui.svg.SVGButton exportAsCsvBtn; + private backupmanager.gui.svg.SVGButton exportAsPdfBtn; private javax.swing.JMenuItem interruptBackupPopupItem; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel3; diff --git a/src/main/java/backupmanager/frames/BackupProgressGUI.form b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.form similarity index 100% rename from src/main/java/backupmanager/frames/BackupProgressGUI.form rename to src/main/java/backupmanager/gui/frames/BackupProgressGUI.form diff --git a/src/main/java/backupmanager/frames/BackupProgressGUI.java b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java similarity index 98% rename from src/main/java/backupmanager/frames/BackupProgressGUI.java rename to src/main/java/backupmanager/gui/frames/BackupProgressGUI.java index 0251bbbf..45dea396 100644 --- a/src/main/java/backupmanager/frames/BackupProgressGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java @@ -1,9 +1,9 @@ -package backupmanager.frames; +package backupmanager.gui.frames; -import backupmanager.Controllers.GuiController; +import backupmanager.gui.Controllers.GuiController; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.frames.Controllers.BackupProgressController; +import backupmanager.gui.frames.Controllers.BackupProgressController; public class BackupProgressGUI extends javax.swing.JDialog { diff --git a/src/main/java/backupmanager/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java similarity index 95% rename from src/main/java/backupmanager/frames/Controllers/BackupManagerController.java rename to src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index c0434681..fdfe98c8 100644 --- a/src/main/java/backupmanager/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -1,4 +1,4 @@ -package backupmanager.frames.Controllers; +package backupmanager.gui.frames.Controllers; import java.awt.Dimension; import java.awt.Toolkit; @@ -12,7 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import backupmanager.Dialogs.EntryUserDialog; +import backupmanager.gui.Dialogs.EntryUserDialog; import backupmanager.Email.EmailSender; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.Confingurations; @@ -23,13 +23,13 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; -import static backupmanager.frames.BackupManagerGUI.backups; +import static backupmanager.gui.frames.BackupManagerGUI.backups; import backupmanager.Services.BackupService; -import backupmanager.Table.BackupTable; -import backupmanager.Table.TableDataManager; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.UserRepository; -import backupmanager.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManagerGUI; public class BackupManagerController { diff --git a/src/main/java/backupmanager/frames/Controllers/BackupMenuController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java similarity index 94% rename from src/main/java/backupmanager/frames/Controllers/BackupMenuController.java rename to src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java index cbdb25e1..d0af17ee 100644 --- a/src/main/java/backupmanager/frames/Controllers/BackupMenuController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java @@ -1,4 +1,4 @@ -package backupmanager.frames.Controllers; +package backupmanager.gui.frames.Controllers; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; @@ -10,15 +10,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import backupmanager.Dialogs.PreferencesDialog; +import backupmanager.gui.Dialogs.PreferencesDialog; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.WebsiteManager; import backupmanager.Services.BackupObserver; -import backupmanager.frames.BackupManagerGUI; -import backupmanager.frames.BackupProgressGUI; +import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupProgressGUI; public class BackupMenuController { diff --git a/src/main/java/backupmanager/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java similarity index 97% rename from src/main/java/backupmanager/frames/Controllers/BackupPopupController.java rename to src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index 9ccc11d9..e119bd94 100644 --- a/src/main/java/backupmanager/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -1,4 +1,4 @@ -package backupmanager.frames.Controllers; +package backupmanager.gui.frames.Controllers; import java.awt.Desktop; import java.awt.Toolkit; @@ -26,11 +26,11 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; -import backupmanager.Table.BackupTable; -import backupmanager.Table.TableDataManager; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; -import backupmanager.frames.BackupManagerGUI; -import backupmanager.frames.BackupProgressGUI; +import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupProgressGUI; public class BackupPopupController { diff --git a/src/main/java/backupmanager/frames/Controllers/BackupProgressController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java similarity index 94% rename from src/main/java/backupmanager/frames/Controllers/BackupProgressController.java rename to src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java index 20bd836f..371bb916 100644 --- a/src/main/java/backupmanager/frames/Controllers/BackupProgressController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java @@ -1,4 +1,4 @@ -package backupmanager.frames.Controllers; +package backupmanager.gui.frames.Controllers; import javax.swing.JOptionPane; diff --git a/src/main/java/backupmanager/frames/Login.java b/src/main/java/backupmanager/gui/frames/Login.java similarity index 94% rename from src/main/java/backupmanager/frames/Login.java rename to src/main/java/backupmanager/gui/frames/Login.java index 7e5a9fb0..1a68a811 100644 --- a/src/main/java/backupmanager/frames/Login.java +++ b/src/main/java/backupmanager/gui/frames/Login.java @@ -1,10 +1,10 @@ -package backupmanager.frames; +package backupmanager.gui.frames; import com.formdev.flatlaf.FlatClientProperties; -import backupmanager.menu.MyDrawerBuilder; -import backupmanager.system.Form; -import backupmanager.system.FormManager; +import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.system.Form; +import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; import javax.swing.*; diff --git a/src/main/java/backupmanager/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java similarity index 88% rename from src/main/java/backupmanager/menu/MyDrawerBuilder.java rename to src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index 12d271df..a6d11395 100644 --- a/src/main/java/backupmanager/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -1,4 +1,4 @@ -package backupmanager.menu; +package backupmanager.gui.menu; import java.util.Arrays; @@ -10,12 +10,12 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import backupmanager.Enums.ConfigKey; -import backupmanager.forms.FormDashboard; -import backupmanager.forms.FormSetting; -import backupmanager.forms.FormTable; -import backupmanager.system.AllForms; -import backupmanager.system.Form; -import backupmanager.system.FormManager; +import backupmanager.gui.forms.FormDashboard; +import backupmanager.gui.forms.FormSetting; +import backupmanager.gui.forms.FormTable; +import backupmanager.gui.system.AllForms; +import backupmanager.gui.system.Form; +import backupmanager.gui.system.FormManager; import raven.extras.AvatarIcon; import raven.modal.drawer.DrawerPanel; import raven.modal.drawer.item.Item; @@ -48,7 +48,7 @@ public void initHeader() { SimpleHeaderData data = header.getSimpleHeaderData(); AvatarIcon icon = (AvatarIcon) data.getIcon(); - icon.setIcon(new FlatSVGIcon("raven/modal/demo/drawer/logo.png", 100, 100)); + icon.setIcon(new FlatSVGIcon("drawer/logo.svg", 100, 100)); data.setTitle("Backup Manager"); data.setDescription("assistenza@shardpc.it"); header.setSimpleHeaderData(data); @@ -66,7 +66,7 @@ private MyDrawerBuilder() { @Override public SimpleHeaderData getSimpleHeaderData() { - AvatarIcon icon = new AvatarIcon(new FlatSVGIcon("raven/modal/demo/drawer/logo.png", 100, 100), 50, 50, 3.5f); + AvatarIcon icon = new AvatarIcon(new FlatSVGIcon("drawer/logo.svg", 100, 100), 50, 50, 3.5f); icon.setType(AvatarIcon.Type.MASK_SQUIRCLE); icon.setBorder(2, 2); @@ -131,10 +131,15 @@ public static MenuOption createSimpleMenuOption() { @Override public void styleMenuItem(JButton menu, int[] index, boolean isMainItem) { boolean isTopLevel = index.length == 1; + if (isTopLevel) { - // adjust item menu at the top level because it's contain icon - menu.putClientProperty(FlatClientProperties.STYLE, "" + - "margin:-1,0,-1,0;"); + + if (menu.getIcon() instanceof FlatSVGIcon svgIcon) { + FlatSVGIcon newIcon = new FlatSVGIcon(svgIcon.getName(), 20, 20); + menu.setIcon(newIcon); + } + + menu.putClientProperty(FlatClientProperties.STYLE, "margin:-1,0,-1,0;"); } } @@ -164,9 +169,7 @@ public void styleMenu(JComponent component) { FormManager.showForm(AllForms.getForm(formClass)); }); - simpleMenuOption.setMenus(items) - .setBaseIconPath("drawer/icon/") - .setIconScale(0.45f); + simpleMenuOption.setMenus(items).setBaseIconPath("drawer/icon/"); return simpleMenuOption; } diff --git a/src/main/java/backupmanager/menu/MyMenuValidation.java b/src/main/java/backupmanager/gui/menu/MyMenuValidation.java similarity index 81% rename from src/main/java/backupmanager/menu/MyMenuValidation.java rename to src/main/java/backupmanager/gui/menu/MyMenuValidation.java index 87ce1190..e257a31c 100644 --- a/src/main/java/backupmanager/menu/MyMenuValidation.java +++ b/src/main/java/backupmanager/gui/menu/MyMenuValidation.java @@ -1,7 +1,7 @@ -package backupmanager.menu; +package backupmanager.gui.menu; import raven.modal.Drawer; -import backupmanager.system.Form; +import backupmanager.gui.system.Form; import raven.modal.drawer.menu.MenuValidation; public class MyMenuValidation extends MenuValidation { diff --git a/src/main/java/backupmanager/sample/SampleData.java b/src/main/java/backupmanager/gui/sample/SampleData.java similarity index 99% rename from src/main/java/backupmanager/sample/SampleData.java rename to src/main/java/backupmanager/gui/sample/SampleData.java index 561ba062..2a0bef33 100644 --- a/src/main/java/backupmanager/sample/SampleData.java +++ b/src/main/java/backupmanager/gui/sample/SampleData.java @@ -1,4 +1,4 @@ -package backupmanager.sample; +package backupmanager.gui.sample; import java.util.Calendar; import java.util.Date; diff --git a/src/main/java/backupmanager/sample/csv/CSVDataReader.java b/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java similarity index 98% rename from src/main/java/backupmanager/sample/csv/CSVDataReader.java rename to src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java index 022930d8..b31e1851 100644 --- a/src/main/java/backupmanager/sample/csv/CSVDataReader.java +++ b/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java @@ -1,4 +1,4 @@ -package backupmanager.sample.csv; +package backupmanager.gui.sample.csv; import java.io.*; import java.util.ArrayList; diff --git a/src/main/java/backupmanager/sample/csv/Pageable.java b/src/main/java/backupmanager/gui/sample/csv/Pageable.java similarity index 95% rename from src/main/java/backupmanager/sample/csv/Pageable.java rename to src/main/java/backupmanager/gui/sample/csv/Pageable.java index 68636222..2c99605d 100644 --- a/src/main/java/backupmanager/sample/csv/Pageable.java +++ b/src/main/java/backupmanager/gui/sample/csv/Pageable.java @@ -1,4 +1,4 @@ -package backupmanager.sample.csv; +package backupmanager.gui.sample.csv; public class Pageable { diff --git a/src/main/java/backupmanager/sample/csv/ResponseCSV.java b/src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java similarity index 86% rename from src/main/java/backupmanager/sample/csv/ResponseCSV.java rename to src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java index d952e5ca..a3e04290 100644 --- a/src/main/java/backupmanager/sample/csv/ResponseCSV.java +++ b/src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java @@ -1,4 +1,4 @@ -package backupmanager.sample.csv; +package backupmanager.gui.sample.csv; import java.util.List; diff --git a/src/main/java/backupmanager/sample/csv/ResponsePageable.java b/src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java similarity index 88% rename from src/main/java/backupmanager/sample/csv/ResponsePageable.java rename to src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java index a6fc0155..3d513948 100644 --- a/src/main/java/backupmanager/sample/csv/ResponsePageable.java +++ b/src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java @@ -1,4 +1,4 @@ -package backupmanager.sample.csv; +package backupmanager.gui.sample.csv; public abstract class ResponsePageable

extends Pageable { diff --git a/src/main/java/backupmanager/simple/SimpleInputForms.java b/src/main/java/backupmanager/gui/simple/SimpleInputForms.java similarity index 99% rename from src/main/java/backupmanager/simple/SimpleInputForms.java rename to src/main/java/backupmanager/gui/simple/SimpleInputForms.java index f218f569..d853d570 100644 --- a/src/main/java/backupmanager/simple/SimpleInputForms.java +++ b/src/main/java/backupmanager/gui/simple/SimpleInputForms.java @@ -1,4 +1,4 @@ -package backupmanager.simple; +package backupmanager.gui.simple; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; diff --git a/src/main/java/backupmanager/svg/SVGButton.java b/src/main/java/backupmanager/gui/svg/SVGButton.java similarity index 95% rename from src/main/java/backupmanager/svg/SVGButton.java rename to src/main/java/backupmanager/gui/svg/SVGButton.java index a684d422..0d6523c2 100644 --- a/src/main/java/backupmanager/svg/SVGButton.java +++ b/src/main/java/backupmanager/gui/svg/SVGButton.java @@ -1,4 +1,4 @@ -package backupmanager.svg; +package backupmanager.gui.svg; import java.awt.Cursor; diff --git a/src/main/java/backupmanager/svg/SVGIconUIColor.java b/src/main/java/backupmanager/gui/svg/SVGIconUIColor.java similarity index 97% rename from src/main/java/backupmanager/svg/SVGIconUIColor.java rename to src/main/java/backupmanager/gui/svg/SVGIconUIColor.java index 464b6239..9832a831 100644 --- a/src/main/java/backupmanager/svg/SVGIconUIColor.java +++ b/src/main/java/backupmanager/gui/svg/SVGIconUIColor.java @@ -1,4 +1,4 @@ -package backupmanager.svg; +package backupmanager.gui.svg; import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.ColorFunctions; diff --git a/src/main/java/backupmanager/svg/SVGManager.java b/src/main/java/backupmanager/gui/svg/SVGManager.java similarity index 97% rename from src/main/java/backupmanager/svg/SVGManager.java rename to src/main/java/backupmanager/gui/svg/SVGManager.java index 275917f1..dce20c3e 100644 --- a/src/main/java/backupmanager/svg/SVGManager.java +++ b/src/main/java/backupmanager/gui/svg/SVGManager.java @@ -1,4 +1,4 @@ -package backupmanager.svg; +package backupmanager.gui.svg; import java.awt.Color; import java.awt.Component; diff --git a/src/main/java/backupmanager/svg/SVGMenu.java b/src/main/java/backupmanager/gui/svg/SVGMenu.java similarity index 92% rename from src/main/java/backupmanager/svg/SVGMenu.java rename to src/main/java/backupmanager/gui/svg/SVGMenu.java index ab928a68..4c5bd6b4 100644 --- a/src/main/java/backupmanager/svg/SVGMenu.java +++ b/src/main/java/backupmanager/gui/svg/SVGMenu.java @@ -1,4 +1,4 @@ -package backupmanager.svg; +package backupmanager.gui.svg; import com.formdev.flatlaf.extras.FlatSVGIcon; import javax.swing.JMenu; diff --git a/src/main/java/backupmanager/svg/SVGMenuItem.java b/src/main/java/backupmanager/gui/svg/SVGMenuItem.java similarity index 92% rename from src/main/java/backupmanager/svg/SVGMenuItem.java rename to src/main/java/backupmanager/gui/svg/SVGMenuItem.java index c49abfc1..785a2c94 100644 --- a/src/main/java/backupmanager/svg/SVGMenuItem.java +++ b/src/main/java/backupmanager/gui/svg/SVGMenuItem.java @@ -1,4 +1,4 @@ -package backupmanager.svg; +package backupmanager.gui.svg; import javax.swing.JMenuItem; diff --git a/src/main/java/backupmanager/system/AllForms.java b/src/main/java/backupmanager/gui/system/AllForms.java similarity index 97% rename from src/main/java/backupmanager/system/AllForms.java rename to src/main/java/backupmanager/gui/system/AllForms.java index 13be50de..3c2c432f 100644 --- a/src/main/java/backupmanager/system/AllForms.java +++ b/src/main/java/backupmanager/gui/system/AllForms.java @@ -1,4 +1,4 @@ -package backupmanager.system; +package backupmanager.gui.system; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; diff --git a/src/main/java/backupmanager/system/Form.java b/src/main/java/backupmanager/gui/system/Form.java similarity index 95% rename from src/main/java/backupmanager/system/Form.java rename to src/main/java/backupmanager/gui/system/Form.java index a465a22e..f8369ac7 100644 --- a/src/main/java/backupmanager/system/Form.java +++ b/src/main/java/backupmanager/gui/system/Form.java @@ -1,4 +1,4 @@ -package backupmanager.system; +package backupmanager.gui.system; import javax.swing.JPanel; import javax.swing.LookAndFeel; diff --git a/src/main/java/backupmanager/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java similarity index 95% rename from src/main/java/backupmanager/system/FormManager.java rename to src/main/java/backupmanager/gui/system/FormManager.java index bcb14637..edcfe499 100644 --- a/src/main/java/backupmanager/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -1,4 +1,4 @@ -package backupmanager.system; +package backupmanager.gui.system; import javax.swing.JFrame; @@ -6,9 +6,9 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.ColorFunctions; -import backupmanager.component.About; -import backupmanager.forms.FormDashboard; -import backupmanager.frames.Login; +import backupmanager.gui.forms.FormDashboard; +import backupmanager.gui.frames.Login; +import backupmanager.gui.component.About; import backupmanager.utils.UndoRedo; import raven.modal.Drawer; import raven.modal.ModalDialog; diff --git a/src/main/java/backupmanager/system/FormSearch.java b/src/main/java/backupmanager/gui/system/FormSearch.java similarity index 94% rename from src/main/java/backupmanager/system/FormSearch.java rename to src/main/java/backupmanager/gui/system/FormSearch.java index 07b09148..f7ad6171 100644 --- a/src/main/java/backupmanager/system/FormSearch.java +++ b/src/main/java/backupmanager/gui/system/FormSearch.java @@ -1,9 +1,9 @@ -package backupmanager.system; +package backupmanager.gui.system; import raven.modal.ModalDialog; -import backupmanager.component.EmptyModalBorder; -import backupmanager.component.FormSearchPanel; -import backupmanager.menu.MyDrawerBuilder; +import backupmanager.gui.component.EmptyModalBorder; +import backupmanager.gui.component.FormSearchPanel; +import backupmanager.gui.menu.MyDrawerBuilder; import backupmanager.utils.SystemForm; import raven.modal.drawer.item.Item; import raven.modal.drawer.item.MenuItem; diff --git a/src/main/java/backupmanager/system/MainForm.java b/src/main/java/backupmanager/gui/system/MainForm.java similarity index 96% rename from src/main/java/backupmanager/system/MainForm.java rename to src/main/java/backupmanager/gui/system/MainForm.java index 580270d0..2224f0c1 100644 --- a/src/main/java/backupmanager/system/MainForm.java +++ b/src/main/java/backupmanager/gui/system/MainForm.java @@ -1,4 +1,4 @@ -package backupmanager.system; +package backupmanager.gui.system; import java.awt.BorderLayout; import java.awt.Component; @@ -13,8 +13,8 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import backupmanager.Enums.ConfigKey; -import backupmanager.component.FormSearchButton; -import backupmanager.component.RefreshLine; +import backupmanager.gui.component.FormSearchButton; +import backupmanager.gui.component.RefreshLine; import net.miginfocom.swing.MigLayout; import raven.modal.Drawer; diff --git a/src/main/java/backupmanager/themes/ListCellTitledBorder.java b/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java similarity index 98% rename from src/main/java/backupmanager/themes/ListCellTitledBorder.java rename to src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java index b1fd92d1..be83f0a3 100644 --- a/src/main/java/backupmanager/themes/ListCellTitledBorder.java +++ b/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java @@ -1,4 +1,4 @@ -package backupmanager.themes; +package backupmanager.gui.themes; import java.awt.Component; import java.awt.FontMetrics; diff --git a/src/main/java/backupmanager/themes/PanelThemes.java b/src/main/java/backupmanager/gui/themes/PanelThemes.java similarity index 99% rename from src/main/java/backupmanager/themes/PanelThemes.java rename to src/main/java/backupmanager/gui/themes/PanelThemes.java index df1e46a0..0e8a6f7b 100644 --- a/src/main/java/backupmanager/themes/PanelThemes.java +++ b/src/main/java/backupmanager/gui/themes/PanelThemes.java @@ -1,4 +1,4 @@ -package backupmanager.themes; +package backupmanager.gui.themes; import com.formdev.flatlaf.*; import com.formdev.flatlaf.extras.FlatAnimatedLafChange; diff --git a/src/main/java/backupmanager/themes/ThemesInfo.java b/src/main/java/backupmanager/gui/themes/ThemesInfo.java similarity index 95% rename from src/main/java/backupmanager/themes/ThemesInfo.java rename to src/main/java/backupmanager/gui/themes/ThemesInfo.java index 1bda4d17..915a220c 100644 --- a/src/main/java/backupmanager/themes/ThemesInfo.java +++ b/src/main/java/backupmanager/gui/themes/ThemesInfo.java @@ -1,4 +1,4 @@ -package backupmanager.themes; +package backupmanager.gui.themes; public class ThemesInfo { diff --git a/src/main/java/backupmanager/themes/ThemesManager.java b/src/main/java/backupmanager/gui/themes/ThemesManager.java similarity index 98% rename from src/main/java/backupmanager/themes/ThemesManager.java rename to src/main/java/backupmanager/gui/themes/ThemesManager.java index e461e941..04e64050 100644 --- a/src/main/java/backupmanager/themes/ThemesManager.java +++ b/src/main/java/backupmanager/gui/themes/ThemesManager.java @@ -1,4 +1,4 @@ -package backupmanager.themes; +package backupmanager.gui.themes; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/resources/drawer/logo.svg b/src/main/resources/drawer/logo.svg new file mode 100644 index 00000000..71eb597f --- /dev/null +++ b/src/main/resources/drawer/logo.svg @@ -0,0 +1,39 @@ + + + + + + + + From 8772e9a4eba16d8583d3f11364623c7666a1fd7b Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Sat, 28 Feb 2026 09:23:11 +0100 Subject: [PATCH 03/17] login service check --- .../java/backupmanager/Entities/User.java | 4 - .../backupmanager/Services/LoginService.java | 59 +++++++++++++ .../Controllers/BackupManagerController.java | 14 +-- .../java/backupmanager/gui/frames/Login.java | 86 ++++++++++++------- .../backupmanager/gui/sample/SampleData.java | 1 + .../backupmanager/gui/system/FormManager.java | 2 +- .../backupmanager/gui/system/MainForm.java | 6 +- 7 files changed, 129 insertions(+), 43 deletions(-) create mode 100644 src/main/java/backupmanager/Services/LoginService.java diff --git a/src/main/java/backupmanager/Entities/User.java b/src/main/java/backupmanager/Entities/User.java index 51bc1cad..321d9adf 100644 --- a/src/main/java/backupmanager/Entities/User.java +++ b/src/main/java/backupmanager/Entities/User.java @@ -20,8 +20,4 @@ public String getUserCompleteName() { public String toString() { return name + " " + surname + ", " + email + ", " + language; } - - public static User getDefaultUser() { - return new User("Unregistered", "User", ""); - } } diff --git a/src/main/java/backupmanager/Services/LoginService.java b/src/main/java/backupmanager/Services/LoginService.java new file mode 100644 index 00000000..eabb82c2 --- /dev/null +++ b/src/main/java/backupmanager/Services/LoginService.java @@ -0,0 +1,59 @@ +package backupmanager.Services; + +import java.util.Locale; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import backupmanager.Email.EmailSender; +import backupmanager.Entities.Confingurations; +import backupmanager.Entities.User; +import backupmanager.Enums.LanguagesEnum; +import backupmanager.database.Repositories.UserRepository; + +public class LoginService { + + private static final Logger logger = LoggerFactory.getLogger(LoginService.class); + + public boolean isFirstAccess() { + logger.debug("Checking for first access"); + User user = UserRepository.getLastUser(); + + if (user == null) { + setLanguageBasedOnPcLanguage(); + return true; + } else { + logger.info("Current user: " + user.toString()); + return false; + } + } + + public void createNewUser(User user) { + if (user == null) throw new IllegalArgumentException("User cannot be null"); + + UserRepository.insertUser(user); + + sendRegistrationEmail(user); + } + + private void setLanguageBasedOnPcLanguage() { + Locale defaultLocale = Locale.getDefault(); + String language = defaultLocale.getLanguage(); + + logger.info("Setting default language to: " + language); + + switch (language) { + case "en" -> Confingurations.setLanguage(LanguagesEnum.ENG); + case "it" -> Confingurations.setLanguage(LanguagesEnum.ITA); + case "es" -> Confingurations.setLanguage(LanguagesEnum.ESP); + case "de" -> Confingurations.setLanguage(LanguagesEnum.DEU); + case "fr" -> Confingurations.setLanguage(LanguagesEnum.FRA); + default -> Confingurations.setLanguage(LanguagesEnum.ENG); + } + } + + private void sendRegistrationEmail(User user) { + EmailSender.sendUserCreationEmail(user); + EmailSender.sendConfirmEmailToUser(user); + } +} diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index fdfe98c8..7c562b57 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -12,7 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import backupmanager.gui.Dialogs.EntryUserDialog; import backupmanager.Email.EmailSender; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.Confingurations; @@ -23,23 +22,24 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; -import static backupmanager.gui.frames.BackupManagerGUI.backups; - import backupmanager.Services.BackupService; +import backupmanager.database.Repositories.UserRepository; +import backupmanager.gui.Dialogs.EntryUserDialog; import backupmanager.gui.Table.BackupTable; import backupmanager.gui.Table.TableDataManager; -import backupmanager.database.Repositories.UserRepository; import backupmanager.gui.frames.BackupManagerGUI; +import static backupmanager.gui.frames.BackupManagerGUI.backups; public class BackupManagerController { - private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); + private static final Logger logger = LoggerFactory.getLogger(BackupManagerController.class); private final BackupService backupService; public BackupManagerController(BackupService backupService) { this.backupService = backupService; } + @Deprecated public void checkForFirstAccess(BackupManagerGUI mainGui) { logger.debug("Checking for first access"); User user = UserRepository.getLastUser(); @@ -124,6 +124,7 @@ public void handleDeleteKeyPressOnTable(BackupTable backupTable) { BackupHelper.deleteBackup(row, backupTable, false); } + @Deprecated private void createNewUser(BackupManagerGUI mainGui) { User newUser = null; @@ -136,6 +137,7 @@ private void createNewUser(BackupManagerGUI mainGui) { sendRegistrationEmail(newUser); } + @Deprecated private void setLanguageBasedOnPcLanguage(BackupManagerGUI mainGui) { Locale defaultLocale = Locale.getDefault(); String language = defaultLocale.getLanguage(); @@ -154,12 +156,14 @@ private void setLanguageBasedOnPcLanguage(BackupManagerGUI mainGui) { mainGui.reloadPreferences(); } + @Deprecated private User openUserDialogAndObtainTheResult(BackupManagerGUI mainGui) { EntryUserDialog userDialog = new EntryUserDialog(mainGui, true); userDialog.setVisible(true); return userDialog.getUser(); } + @Deprecated private void sendRegistrationEmail(User user) { EmailSender.sendUserCreationEmail(user); EmailSender.sendConfirmEmailToUser(user); diff --git a/src/main/java/backupmanager/gui/frames/Login.java b/src/main/java/backupmanager/gui/frames/Login.java index 1a68a811..86a9db04 100644 --- a/src/main/java/backupmanager/gui/frames/Login.java +++ b/src/main/java/backupmanager/gui/frames/Login.java @@ -1,33 +1,49 @@ package backupmanager.gui.frames; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + import com.formdev.flatlaf.FlatClientProperties; +import backupmanager.Entities.User; +import backupmanager.Services.LoginService; import backupmanager.gui.menu.MyDrawerBuilder; import backupmanager.gui.system.Form; import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; -import javax.swing.*; - public class Login extends Form { + + private final LoginService loginService = new LoginService(); + public Login() { init(); } private void init() { setLayout(new MigLayout("al center center")); + + if (!loginService.isFirstAccess()) { + javax.swing.SwingUtilities.invokeLater(this::showMainForm); + return; + } + createLogin(); } private void createLogin() { - JPanel panelLogin = new JPanel(new MigLayout()); - JPanel loginContent = new JPanel(new MigLayout("fillx,wrap,insets 35 35 25 35", "[fill,300]")); + JPanel panelLogin = new JPanel(new MigLayout()); + JPanel loginContent = new JPanel( + new MigLayout("fillx,wrap,insets 35 35 25 35", "[fill,300]") + ); JLabel lbTitle = new JLabel("Login"); JLabel lbDescription = new JLabel("Please enter your data to access the system"); - lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold +12;"); + + lbTitle.putClientProperty(FlatClientProperties.STYLE, "font:bold +12;"); loginContent.add(lbTitle); loginContent.add(lbDescription); @@ -35,40 +51,30 @@ private void createLogin() { JTextField txtName = new JTextField(); JTextField txtSurname = new JTextField(); JTextField txtEmail = new JTextField(); - JButton cmdLogin = new JButton("Login") { - @Override - public boolean isDefaultButton() { - return true; - } - }; - // style + JButton cmdLogin = new JButton("Login"); + + // Placeholder txtName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your name"); txtSurname.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your surname"); txtEmail.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your email"); - panelLogin.putClientProperty(FlatClientProperties.STYLE, "" + + // Style + panelLogin.putClientProperty(FlatClientProperties.STYLE, "[light]border:5,5,5,5,shade($Panel.background,10%),,20;" + "[dark]border:5,5,5,5,tint($Panel.background,5%),,20;" + "[light]background:shade($Panel.background,3%);" + - "[dark]background:tint($Panel.background,2%);"); + "[dark]background:tint($Panel.background,2%);" + ); - loginContent.putClientProperty(FlatClientProperties.STYLE, "" + - "background:null;"); + loginContent.putClientProperty(FlatClientProperties.STYLE, "background:null;"); - txtName.putClientProperty(FlatClientProperties.STYLE, "" + - "margin:4,10,4,10;" + - "arc:12;"); - txtSurname.putClientProperty(FlatClientProperties.STYLE, "" + - "margin:4,10,4,10;" + - "arc:12;"); - txtEmail.putClientProperty(FlatClientProperties.STYLE, "" + - "margin:4,10,4,10;" + - "arc:12;"); + String fieldStyle = "margin:4,10,4,10;arc:12;"; - cmdLogin.putClientProperty(FlatClientProperties.STYLE, "" + - "margin:4,10,4,10;" + - "arc:12;"); + txtName.putClientProperty(FlatClientProperties.STYLE, fieldStyle); + txtSurname.putClientProperty(FlatClientProperties.STYLE, fieldStyle); + txtEmail.putClientProperty(FlatClientProperties.STYLE, fieldStyle); + cmdLogin.putClientProperty(FlatClientProperties.STYLE, fieldStyle); loginContent.add(new JLabel("Name"), "gapy 25"); loginContent.add(txtName); @@ -78,15 +84,31 @@ public boolean isDefaultButton() { loginContent.add(new JLabel("Email"), "gapy 10"); loginContent.add(txtEmail); + loginContent.add(cmdLogin, "gapy 20"); panelLogin.add(loginContent); add(panelLogin); - // event cmdLogin.addActionListener(e -> { - MyDrawerBuilder.getInstance().initHeader(); - FormManager.login(); + + String name = txtName.getText().trim(); + String surname = txtSurname.getText().trim(); + String email = txtEmail.getText().trim(); + + if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) { + return; + } + + User user = new User(name, surname, email); + loginService.createNewUser(user); + + javax.swing.SwingUtilities.invokeLater(this::showMainForm); }); } + + private void showMainForm() { + MyDrawerBuilder.getInstance().initHeader(); + FormManager.login(); + } } diff --git a/src/main/java/backupmanager/gui/sample/SampleData.java b/src/main/java/backupmanager/gui/sample/SampleData.java index 2a0bef33..cfb70e33 100644 --- a/src/main/java/backupmanager/gui/sample/SampleData.java +++ b/src/main/java/backupmanager/gui/sample/SampleData.java @@ -18,6 +18,7 @@ import raven.extras.AvatarIcon; +@Deprecated public class SampleData { public static TableXYDataset getTimeSeriesDataset() { TimeTableXYDataset dataset = new TimeTableXYDataset(); diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index edcfe499..c2779e59 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -6,9 +6,9 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.ColorFunctions; +import backupmanager.gui.component.About; import backupmanager.gui.forms.FormDashboard; import backupmanager.gui.frames.Login; -import backupmanager.gui.component.About; import backupmanager.utils.UndoRedo; import raven.modal.Drawer; import raven.modal.ModalDialog; diff --git a/src/main/java/backupmanager/gui/system/MainForm.java b/src/main/java/backupmanager/gui/system/MainForm.java index 2224f0c1..c2984d86 100644 --- a/src/main/java/backupmanager/gui/system/MainForm.java +++ b/src/main/java/backupmanager/gui/system/MainForm.java @@ -25,7 +25,11 @@ public MainForm() { } private void init() { - setLayout(new MigLayout("fillx,wrap,insets 0,gap 0", "[fill]", "[][][fill,grow][]")); + setLayout(new MigLayout( + "fillx,wrap,insets 0,gap 0", + "[fill]", + "[][3!][fill,grow][2!][]" + )); add(createHeader()); add(createRefreshLine(), "height 3!"); add(createMain()); From 09435274b3ebde7869e23d30939891eb3a78c58b Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Mon, 2 Mar 2026 21:13:08 +0100 Subject: [PATCH 04/17] updates --- .../Entities/BackupAnalyticsSnapshot.java | 26 ++ .../Entities/ConfigurationBackup.java | 40 +++ .../Entities/ZippingContext.java | 6 +- .../java/backupmanager/Enums/MenuItems.java | 18 +- .../Enums/SubscriptionStatus.java | 8 + .../Enums/TranslationLoaderEnum.java | 5 +- .../backupmanager/Helpers/BackupHelper.java | 67 ++-- .../Helpers/SubscriptionHelper.java | 51 +++ .../backupmanager/Json/JSONConfigReader.java | 6 + .../backupmanager/Managers/ExportManager.java | 4 +- .../Services/BackupAnalyticsService.java | 246 ++++++++++++++ .../backupmanager/Services/BackupService.java | 56 +++- .../backupmanager/Services/LoginService.java | 4 +- .../gui/Controllers/AppController.java | 39 +-- .../Controllers/BackupEntryController.java | 4 +- .../backupmanager/gui/component/About.java | 113 +++---- .../gui/component/Subscription.java | 110 ++++++ .../gui/component/chart/TimeSeriesChart.java | 3 +- .../gui/forms/FormBackupDashboard.java | 316 ++++++++++++++++++ .../gui/forms/FormDashboard.java | 233 ------------- .../backupmanager/gui/forms/FormHistory.java | 103 ++++++ .../backupmanager/gui/forms/FormSetting.java | 20 ++ .../backupmanager/gui/forms/FormTable.java | 285 ++++++++++++---- .../gui/frames/BackupManagerGUI.java | 2 +- .../Controllers/BackupManagerController.java | 23 ++ .../Controllers/BackupPopupController.java | 90 ++++- .../java/backupmanager/gui/frames/Login.java | 2 +- .../gui/menu/MyDrawerBuilder.java | 208 +++++++++--- .../csv/ConfigurationBackupDataTable.java | 25 ++ ...SimpleInputForms.java => BackupEntry.java} | 6 +- .../backupmanager/gui/system/FormManager.java | 11 +- .../gui/themes/ThemesManager.java | 2 +- src/main/resources/drawer/icon/github.svg | 19 ++ .../resources/drawer/icon/subscription.svg | 2 + .../resources/icons/dashboard/database.svg | 19 ++ .../resources/icons/dashboard/duration.svg | 2 + src/main/resources/icons/dashboard/rate.svg | 4 + src/main/resources/icons/dashboard/run.svg | 14 + src/main/resources/icons/dashboard/usage.svg | 2 + src/main/resources/logback.xml | 6 +- src/main/resources/res/config/config.json | 12 +- src/test/java/test/LoginServiceTest.java | 42 +++ .../BackupRequestRepositoryTest.java | 2 +- 43 files changed, 1756 insertions(+), 500 deletions(-) create mode 100644 src/main/java/backupmanager/Entities/BackupAnalyticsSnapshot.java create mode 100644 src/main/java/backupmanager/Enums/SubscriptionStatus.java create mode 100644 src/main/java/backupmanager/Helpers/SubscriptionHelper.java create mode 100644 src/main/java/backupmanager/Services/BackupAnalyticsService.java create mode 100644 src/main/java/backupmanager/gui/component/Subscription.java create mode 100644 src/main/java/backupmanager/gui/forms/FormBackupDashboard.java delete mode 100644 src/main/java/backupmanager/gui/forms/FormDashboard.java create mode 100644 src/main/java/backupmanager/gui/forms/FormHistory.java create mode 100644 src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java rename src/main/java/backupmanager/gui/simple/{SimpleInputForms.java => BackupEntry.java} (96%) create mode 100644 src/main/resources/drawer/icon/github.svg create mode 100644 src/main/resources/drawer/icon/subscription.svg create mode 100644 src/main/resources/icons/dashboard/database.svg create mode 100644 src/main/resources/icons/dashboard/duration.svg create mode 100644 src/main/resources/icons/dashboard/rate.svg create mode 100644 src/main/resources/icons/dashboard/run.svg create mode 100644 src/main/resources/icons/dashboard/usage.svg create mode 100644 src/test/java/test/LoginServiceTest.java diff --git a/src/main/java/backupmanager/Entities/BackupAnalyticsSnapshot.java b/src/main/java/backupmanager/Entities/BackupAnalyticsSnapshot.java new file mode 100644 index 00000000..b8e91b02 --- /dev/null +++ b/src/main/java/backupmanager/Entities/BackupAnalyticsSnapshot.java @@ -0,0 +1,26 @@ +package backupmanager.Entities; + +import java.time.LocalDate; +import java.util.Map; + +public record BackupAnalyticsSnapshot( + long totalRequests, + long successCount, + long failedCount, + double successRate, + double avgDurationMs, + double avgCompressionRate, + long totalDiskUsageBytes, + Map durationTrend +) { + public static BackupAnalyticsSnapshot emptyDataset() { + return new BackupAnalyticsSnapshot( + 0, 0, 0, + 0, + 0, + 0, + 0, + Map.of() + ); + } +} diff --git a/src/main/java/backupmanager/Entities/ConfigurationBackup.java b/src/main/java/backupmanager/Entities/ConfigurationBackup.java index fffdff2a..dd9d397c 100644 --- a/src/main/java/backupmanager/Entities/ConfigurationBackup.java +++ b/src/main/java/backupmanager/Entities/ConfigurationBackup.java @@ -107,6 +107,7 @@ public String toString() { ); } + @Deprecated public String toCsvString() { return String.format("%s,%s,%s,%s,%s,%s,%s,%d", name, @@ -120,6 +121,32 @@ public String toCsvString() { ); } + public String[] toCsvStrings() { + return new String[] { + name, + targetPath, + destinationPath, + lastBackupDate != null ? lastBackupDate.toString() : "", + Boolean.toString(automatic), + nextBackupDate != null ? nextBackupDate.toString() : "", + timeIntervalBackup != null ? timeIntervalBackup.toString() : "", + Integer.toString(maxToKeep) + }; + } + + public Object[] toTableRow() { + return new Object[] { + name, + targetPath, + destinationPath, + lastBackupDate, + automatic, + nextBackupDate, + timeIntervalBackup, + maxToKeep + }; + } + public static ConfigurationBackup getBackupByName(List backups, String name) { for (ConfigurationBackup backup : backups) { if (backup.getName().equals(name)) { @@ -137,6 +164,19 @@ public static String getCSVHeader() { return "BackupName,targetPath,DestinationPath,lastBackupDate,IsAutoBackup,NextDate,Interval (gg.HH:mm),MaxBackupsToKeep"; } + public static String[] getCSVHeaderArray() { + return new String[] { + "BackupName", + "targetPath", + "DestinationPath", + "lastBackupDate", + "IsAutoBackup", + "NextDate", + "Interval (gg.HH:mm)", + "MaxBackupsToKeep" + }; + } + public int getId() { return id; } diff --git a/src/main/java/backupmanager/Entities/ZippingContext.java b/src/main/java/backupmanager/Entities/ZippingContext.java index 46fe5e52..d9358e24 100644 --- a/src/main/java/backupmanager/Entities/ZippingContext.java +++ b/src/main/java/backupmanager/Entities/ZippingContext.java @@ -3,21 +3,21 @@ import java.awt.TrayIcon; import javax.swing.JMenuItem; +import javax.swing.JTable; -import backupmanager.gui.Table.BackupTable; import backupmanager.gui.frames.BackupProgressGUI; import backupmanager.utils.FolderUtils; public record ZippingContext ( ConfigurationBackup backup, TrayIcon trayIcon, - BackupTable backupTable, + JTable backupTable, BackupProgressGUI progressBar, JMenuItem interruptBackupPopupItem, JMenuItem deleteBackupPopupItem, long folderUnzippedSize ) { - public static ZippingContext create(ConfigurationBackup backup, TrayIcon trayIcon, BackupTable backupTable, BackupProgressGUI progressBar, JMenuItem interruptBackupPopupItem, JMenuItem deleteBackupPopupItem) { + public static ZippingContext create(ConfigurationBackup backup, TrayIcon trayIcon, JTable backupTable, BackupProgressGUI progressBar, JMenuItem interruptBackupPopupItem, JMenuItem deleteBackupPopupItem) { long folderSize = FolderUtils.calculateFileOrFolderSize(backup.getTargetPath()); return new ZippingContext(backup, trayIcon, backupTable, progressBar, interruptBackupPopupItem, deleteBackupPopupItem, folderSize); } diff --git a/src/main/java/backupmanager/Enums/MenuItems.java b/src/main/java/backupmanager/Enums/MenuItems.java index 5da1dd8b..e356d500 100644 --- a/src/main/java/backupmanager/Enums/MenuItems.java +++ b/src/main/java/backupmanager/Enums/MenuItems.java @@ -2,20 +2,26 @@ public enum MenuItems { BugReport, - Preferences, - Clear, + Preferences, //TODO: remove + Clear, //TODO: remove + Save, //TODO: remove + SaveWithName, //TODO: remove + Settings, Donate, PaypalDonate, BuymeacoffeeDonate, History, InfoPage, New, - Quit, - Save, + Quit, //TODO: remove Import, Export, - SaveWithName, Share, Support, - Website + ContactUs, + Website, + BackupList, + Dashboard, + About, + Subscription } diff --git a/src/main/java/backupmanager/Enums/SubscriptionStatus.java b/src/main/java/backupmanager/Enums/SubscriptionStatus.java new file mode 100644 index 00000000..bfcc8967 --- /dev/null +++ b/src/main/java/backupmanager/Enums/SubscriptionStatus.java @@ -0,0 +1,8 @@ +package backupmanager.Enums; + +public enum SubscriptionStatus { + EXPIRED, + ACTIVE, + EXPIRATION, + NONE // No subscription needed +} diff --git a/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java b/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java index 7eebce77..c8cc4924 100644 --- a/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java +++ b/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java @@ -274,7 +274,10 @@ public enum TranslationKey { SUBSCRIPTION_EXPIRING_TITLE("ExpiringTitle", "Backup Manager subscription expiring soon"), SUBSCRIPTION_EXPIRING_MESSAGE("ExpiringMessage", "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it."), SUBSCRIPTION_EXPIRED_TITLE("ExpiredTitle", "Backup Manager subscription expired"), - SUBSCRIPTION_EXPIRED_MESSAGE("ExpiredMessage", "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it."); + SUBSCRIPTION_EXPIRED_MESSAGE("ExpiredMessage", "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it."), + SUBSCRIPTION_ACTIVE("ActiveLabel", "Active"), + SUBSCRIPTION_EXPIRING("ExpiringLabel", "Expiring"), + SUBSCRIPTION_EXPIRED("ExpiredLabel", "Expired"); private final String keyName; private final String defaultValue; diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 5e35e39b..7898c561 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -10,17 +10,17 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.gui.Dialogs.BackupEntryDialog; -import backupmanager.gui.Dialogs.TimePicker; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.BackupStatus; import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.Dialogs.BackupEntryDialog; +import backupmanager.gui.Dialogs.TimePicker; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.TableDataManager; import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.frames.BackupProgressGUI; @@ -63,7 +63,41 @@ public static void deleteBackup(int selectedRow, BackupTable backupTable, boolea } String backupName = (String) backupTable.getValueAt(selectedRow, 0); - removeBackup(backupName); + deleteBackup(backupName); + } + + @Deprecated + public static void deleteBackup(int selectedRow, BackupTable backupTable) { + logger.info("Event --> deleting backup"); + + if (selectedRow != -1) { + int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.YES_OPTION) { + String backupName = (String) backupTable.getValueAt(selectedRow, 0); + BackupHelper.deleteBackup(backupName); + } + } + } + + public static void deleteBackup(String backupName) { + logger.info("Event --> deleting backup"); + ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backupName); + deleteBackup(backup); + } + + public static void deleteBackup(ConfigurationBackup backup) { + logger.info("Event --> deleting backup" + backup.getName()); + BackupConfigurationRepository.deleteBackup(backup.getId()); + updateBackupTable(); + } + + public static void deleteBackupWithConfirmition(ConfigurationBackup backup) { + logger.info("Event --> deleting backup request with confirmation for backup: " + backup.getName()); + + int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.YES_OPTION) { + BackupHelper.deleteBackup(backup); + } } public static void updateBackup(ConfigurationBackup updatedBackup) { @@ -125,34 +159,11 @@ public static LocalDateTime getNexDateBackup(TimeInterval timeInterval) { .plusMinutes(timeInterval.minutes()); } - public static void deleteBackup(int selectedRow, BackupTable backupTable) { - logger.info("Event --> deleting backup"); - - if (selectedRow != -1) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (response == JOptionPane.YES_OPTION) { - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - BackupHelper.removeBackup(backupName); - } - } - } - public static void forceBackupTermination(int requestId) { BackupRequestRepository.updateRequestStatusByRequestId(requestId, BackupStatus.TERMINATED); updateBackupTable(); } - public static void removeBackup(String backupName) { - ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backupName); - removeBackup(backup); - } - - private static void removeBackup(ConfigurationBackup backup) { - logger.info("Event --> removing backup" + backup.getName()); - BackupConfigurationRepository.deleteBackup(backup.getId()); - updateBackupTable(); - } - private static void updateBackupTable() { if (BackupManagerGUI.model != null) TableDataManager.updateTableWithNewBackupList(getBackupList(), formatter); diff --git a/src/main/java/backupmanager/Helpers/SubscriptionHelper.java b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java new file mode 100644 index 00000000..c12bf172 --- /dev/null +++ b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java @@ -0,0 +1,51 @@ +package backupmanager.Helpers; + +import java.time.LocalDate; + +import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Subscription; +import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.SubscriptionStatus; +import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; +import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Json.JSONConfigReader; +import backupmanager.database.Repositories.SubscriptionRepository; + +public class SubscriptionHelper { + private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + + public static SubscriptionStatus getSubscriptionStatus() { + if (!Confingurations.isSubscriptionNedded()) + return SubscriptionStatus.NONE; + + Subscription subscription = SubscriptionRepository.getAnySubscriptionValid(); + if (subscription == null) + return SubscriptionStatus.EXPIRED; + if (isSubscriptionExpiringSoon(subscription)) + return SubscriptionStatus.EXPIRATION; + + return SubscriptionStatus.ACTIVE; + } + + public static String getSubscriptionStatusTranslated(SubscriptionStatus status) { + String statusTranslation; + switch (status) { + case EXPIRED -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRED); + case ACTIVE -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_ACTIVE); + case EXPIRATION -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRING); + default -> statusTranslation = ""; + } + return statusTranslation; + } + + public static Subscription getLastValidSubscription() { + return SubscriptionRepository.getAnySubscriptionValid(); + } + + private static boolean isSubscriptionExpiringSoon(Subscription subscription) { + int days = configReader.getConfigValue("SubscriptionWarningDays", 7); + LocalDate now = LocalDate.now(); + LocalDate endMinusDays = subscription.endDate().minusDays(days); + return now.isAfter(endMinusDays); + } +} diff --git a/src/main/java/backupmanager/Json/JSONConfigReader.java b/src/main/java/backupmanager/Json/JSONConfigReader.java index a120cbcb..63c71ce1 100644 --- a/src/main/java/backupmanager/Json/JSONConfigReader.java +++ b/src/main/java/backupmanager/Json/JSONConfigReader.java @@ -11,6 +11,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import backupmanager.Enums.ConfigKey; + public class JSONConfigReader { private static final Logger logger = LoggerFactory.getLogger(JSONConfigReader.class); @@ -25,6 +27,10 @@ public JSONConfigReader(String filename, String directoryPath) { loadConfig(); // Load configuration at instantiation } + public JSONConfigReader() { + this(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + } + public boolean isMenuItemEnabled(String menuItem) { if (config == null) { logger.warn("Configuration not loaded. Cannot check menu items"); diff --git a/src/main/java/backupmanager/Managers/ExportManager.java b/src/main/java/backupmanager/Managers/ExportManager.java index 6b84bfd7..4aaf0a42 100644 --- a/src/main/java/backupmanager/Managers/ExportManager.java +++ b/src/main/java/backupmanager/Managers/ExportManager.java @@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.List; import javax.swing.JOptionPane; @@ -28,6 +29,7 @@ public class ExportManager { private static final Logger logger = LoggerFactory.getLogger(ExportManager.class); + @Deprecated public static void exportAsPDF(ArrayList backups, String headers) { logger.info("Exporting backups to PDF"); @@ -115,7 +117,7 @@ public static void exportAsPDF(ArrayList backups, String he } } - public static void exportAsCSV(ArrayList backups, String header) { + public static void exportAsCSV(List backups, String header) { logger.info("Exporting backups to CSV"); String path = BackupOperations.pathSearchWithFileChooser(false); diff --git a/src/main/java/backupmanager/Services/BackupAnalyticsService.java b/src/main/java/backupmanager/Services/BackupAnalyticsService.java new file mode 100644 index 00000000..f1b6d02c --- /dev/null +++ b/src/main/java/backupmanager/Services/BackupAnalyticsService.java @@ -0,0 +1,246 @@ +package backupmanager.Services; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.jfree.data.category.CategoryDataset; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.general.DefaultPieDataset; +import org.jfree.data.general.PieDataset; +import org.jfree.data.time.Day; +import org.jfree.data.time.TimeSeries; +import org.jfree.data.time.TimeSeriesCollection; +import org.jfree.data.xy.XYDataset; + +import backupmanager.Entities.BackupAnalyticsSnapshot; +import backupmanager.Entities.BackupRequest; +import backupmanager.Enums.BackupStatus; + +public class BackupAnalyticsService { + + public static BackupAnalyticsSnapshot buildSnapshot(List requests) { + + if (requests == null || requests.isEmpty()) { + return BackupAnalyticsSnapshot.emptyDataset(); + } + + long total = requests.size(); + + long successCount = requests.stream() + .filter(r -> r.status() == BackupStatus.FINISHED) + .count(); + + long failedCount = requests.stream() + .filter(r -> r.status() == BackupStatus.TERMINATED) + .count(); + + double successRate = total == 0 ? 0 : successCount * 100.0 / total; + + double avgDuration = requests.stream() + .filter(r -> r.durationMs() != null) + .mapToLong(BackupRequest::durationMs) + .average() + .orElse(0); + + double avgCompressionRate = computeCompressionRate(requests); + + long diskUsage = requests.stream() + .mapToLong(r -> + r.zippedTargetSize() != null ? + r.zippedTargetSize() : + 0) + .sum(); + + Map durationTrend = + requests.stream() + .filter(r -> r.durationMs() != null) + .collect(Collectors.groupingBy( + r -> r.startedDate().toLocalDate(), + Collectors.averagingDouble( + BackupRequest::durationMs + ))); + + return new BackupAnalyticsSnapshot(total, successCount, failedCount, successRate, avgDuration, avgCompressionRate, diskUsage, durationTrend); + } + + public static XYDataset buildRequestTrendDataset(List requests) { + + TimeSeries series = new TimeSeries("Backup Requests Trend"); + + if (requests == null || requests.isEmpty()) + return new TimeSeriesCollection(series); + + Map aggregation = requests.stream() + .collect(Collectors.groupingBy( + r -> r.startedDate().toLocalDate(), + Collectors.counting() + )); + + aggregation.forEach((date, count) -> + series.add( + new Day( + date.getDayOfMonth(), + date.getMonthValue(), + date.getYear() + ), + count + )); + + return new TimeSeriesCollection(series); + } + + public static CategoryDataset buildStatusCategoryDataset(List requests) { + + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + if (requests == null) + return dataset; + + Map map = + requests.stream().collect( + Collectors.groupingBy( + BackupRequest::status, + Collectors.counting() + )); + + map.forEach((status, count) -> + dataset.addValue( + count, + "Requests", + status.name() + )); + + return dataset; + } + + public static PieDataset buildStatusPieDataset(List requests) { + + DefaultPieDataset dataset = new DefaultPieDataset(); + + if (requests == null) + return dataset; + + Map map = + requests.stream() + .collect(Collectors.groupingBy( + BackupRequest::status, + Collectors.counting() + )); + + map.forEach((status, count) -> + dataset.setValue(status.name(), count)); + + return dataset; + } + + public static CategoryDataset buildBackupQualityDataset(List requests) { + + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); + + if (requests == null || requests.isEmpty()) + return dataset; + + long total = requests.size(); + + long success = requests.stream() + .filter(r -> r.status() == BackupStatus.FINISHED) + .count(); + + double successRate = total == 0 ? 0 : success * 100.0 / total; + + double avgDuration = requests.stream() + .filter(r -> r.durationMs() != null) + .mapToLong(BackupRequest::durationMs) + .average() + .orElse(0); + + double compressionEfficiency = requests.stream() + .filter(r -> r.unzippedTargetSize() > 0 + && r.zippedTargetSize() != null) + .mapToDouble(r -> + (double) r.zippedTargetSize() / + r.unzippedTargetSize()) + .average() + .orElse(0); + + dataset.addValue(successRate, "Quality", "Success Rate"); + dataset.addValue(avgDuration, "Quality", "Avg Duration"); + dataset.addValue(compressionEfficiency * 100, + "Quality", "Compression Ratio"); + + return dataset; + } + + public static String formatBytes(long bytes) { + + if (bytes < 1024) return bytes + " B"; + if (bytes < 1024 * 1024) return bytes / 1024 + " KB"; + if (bytes < 1024L * 1024 * 1024) return bytes / (1024 * 1024) + " MB"; + + return bytes / (1024L * 1024 * 1024) + " GB"; + } + + public static XYDataset buildDurationTrendDataset(Map trendMap) { + + TimeSeries series = new TimeSeries("Average Backup Duration"); + + trendMap.forEach((date, value) -> + series.add( + new Day( + date.getDayOfMonth(), + date.getMonthValue(), + date.getYear() + ), + value + )); + + return new TimeSeriesCollection(series); + } + + public static double computeCompressionRate(List requests) { + + long totalUnzipped = requests.stream() + .mapToLong(BackupRequest::unzippedTargetSize) + .sum(); + + long totalZipped = requests.stream() + .filter(r -> r.zippedTargetSize() != null) + .mapToLong(BackupRequest::zippedTargetSize) + .sum(); + + if (totalUnzipped == 0) + return 0; + + return (double) totalZipped / totalUnzipped; + } + + public static double computeHealthIndex(double successRate, double compressionRate, double stabilityRate) { + return Math.min(100,(successRate * 0.5 + compressionRate * 0.3 + stabilityRate * 0.2)); + } + + public static XYDataset buildStorageTrendDataset( + Map storageTrend) { + + TimeSeries series = new TimeSeries("Storage Growth"); + + storageTrend.forEach((date, value) -> { + + series.add( + new Day( + date.getDayOfMonth(), + date.getMonthValue(), + date.getYear() + ), + value / (1024.0 * 1024) // MB + ); + }); + + return new TimeSeriesCollection(series); + } + + public static double convertAvgDurationinMinutes(BackupAnalyticsSnapshot snapshot) { + return snapshot.avgDurationMs() / 60000.0; + } +} diff --git a/src/main/java/backupmanager/Services/BackupService.java b/src/main/java/backupmanager/Services/BackupService.java index 4a446caa..696b68c7 100644 --- a/src/main/java/backupmanager/Services/BackupService.java +++ b/src/main/java/backupmanager/Services/BackupService.java @@ -1,5 +1,6 @@ package backupmanager.Services; +import java.time.LocalDateTime; import java.util.List; import backupmanager.Entities.ConfigurationBackup; @@ -31,7 +32,7 @@ public String getBackupDetails(String name) { return buildDetails(backup); } - private String buildDetails(ConfigurationBackup backup) { + public String buildDetails(ConfigurationBackup backup) { String backupNameStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.BACKUP_NAME_DETAIL); String initialPathStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.INITIAL_PATH_DETAIL); String destinationPathStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.DESTINATION_PATH_DETAIL); @@ -44,19 +45,44 @@ private String buildDetails(ConfigurationBackup backup) { String notesStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.NOTES_DETAIL); String maxBackupsToKeepStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.MAX_BACKUPS_TO_KEEP_DETAIL); - return ( - "" + backupNameStr + ": " + backup.getName() + ", " + - "" + initialPathStr + ": " + backup.getTargetPath() + ", " + - "" + destinationPathStr + ": " + backup.getDestinationPath() + ", " + - "" + lastBackupStr + ": " + (backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "") + ", " + - "" + nextBackupStr + ": " + (backup.getNextBackupDate() != null ? backup.getNextBackupDate().format(formatter) : "_") + ", " + - "" + timeIntervalBackupStr + ": " + (backup.getTimeIntervalBackup() != null ? backup.getTimeIntervalBackup().toString() : "_") + ", " + - "" + creationDateStr + ": " + (backup.getCreationDate() != null ? backup.getCreationDate().format(formatter) : "_") + ", " + - "" + lastUpdateDateStr + ": " + (backup.getLastUpdateDate() != null ? backup.getLastUpdateDate().format(formatter) : "_") + ", " + - "" + backupCountStr + ": " + (backup.getCount()) + ", " + - "" + maxBackupsToKeepStr + ": " + (backup.getMaxToKeep()) + ", " + - "" + notesStr + ": " + (backup.getNotes()) + - "" - ); + return """ + +

+ + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + %s: %s. + +
+ + """.formatted( + backupNameStr, backup.getName(), + initialPathStr, backup.getTargetPath(), + destinationPathStr, backup.getDestinationPath(), + lastBackupStr, formatDate(backup.getLastBackupDate()), + nextBackupStr, formatDate(backup.getNextBackupDate()), + timeIntervalBackupStr, optionalString(backup.getTimeIntervalBackup()), + creationDateStr, formatDate(backup.getCreationDate()), + lastUpdateDateStr, formatDate(backup.getLastUpdateDate()), + backupCountStr, backup.getCount(), + maxBackupsToKeepStr, backup.getMaxToKeep(), + notesStr, backup.getNotes() + ); + } + + private String formatDate(LocalDateTime date) { + return date != null ? date.format(formatter) : "_"; } + + private String optionalString(Object value) { + return value != null ? value.toString() : "_"; + } + } diff --git a/src/main/java/backupmanager/Services/LoginService.java b/src/main/java/backupmanager/Services/LoginService.java index eabb82c2..39bb7538 100644 --- a/src/main/java/backupmanager/Services/LoginService.java +++ b/src/main/java/backupmanager/Services/LoginService.java @@ -30,9 +30,11 @@ public boolean isFirstAccess() { public void createNewUser(User user) { if (user == null) throw new IllegalArgumentException("User cannot be null"); - UserRepository.insertUser(user); + } + public void createUserAndSendEmail(User user) { + createNewUser(user); sendRegistrationEmail(user); } diff --git a/src/main/java/backupmanager/gui/Controllers/AppController.java b/src/main/java/backupmanager/gui/Controllers/AppController.java index 00a84b7f..34798aa2 100644 --- a/src/main/java/backupmanager/gui/Controllers/AppController.java +++ b/src/main/java/backupmanager/gui/Controllers/AppController.java @@ -2,7 +2,6 @@ import java.awt.Frame; import java.io.IOException; -import java.time.LocalDate; import javax.swing.JFrame; @@ -10,13 +9,12 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.Entities.Confingurations; -import backupmanager.Entities.Subscription; import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.SubscriptionStatus; +import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Helpers.SubscriptionNotifier; import backupmanager.Json.JSONConfigReader; import backupmanager.Services.BackgroundService; -import backupmanager.database.Repositories.SubscriptionRepository; import backupmanager.gui.frames.BackupManagerGUI; public class AppController { @@ -53,26 +51,23 @@ private AppController() throws IOException { } private boolean canBackgroundServiceStartsBasedOnSubscription() { - if (!Confingurations.isSubscriptionNedded()) return true; - - Subscription subscription = SubscriptionRepository.getAnySubscriptionValid(); - - if (subscription == null) { - logger.info("Subscription expired alert"); - SubscriptionNotifier.showExpiredAlert(trayController); - return false; - } - - int days = configReader.getConfigValue("SubscriptionWarningDays", 7); - LocalDate now = LocalDate.now(); - LocalDate endMinusDays = subscription.endDate().minusDays(days); + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + showSubscriptionNotificationIfNeeded(status); + return status != SubscriptionStatus.EXPIRED; + } - if (now.isAfter(endMinusDays)) { - logger.info("Subscription is expiring alert"); - SubscriptionNotifier.showExpiringWarning(trayController); + private void showSubscriptionNotificationIfNeeded(SubscriptionStatus status) { + switch (status) { + case SubscriptionStatus.EXPIRATION -> { + logger.info("Subscription is expiring alert"); + SubscriptionNotifier.showExpiringWarning(trayController); + } + case SubscriptionStatus.EXPIRED -> { + logger.info("Subscription expired alert"); + SubscriptionNotifier.showExpiredAlert(trayController); + } + case ACTIVE, NONE -> { } } - - return true; } private void openGui() { diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index 8261891b..d4e69f2d 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -17,8 +17,8 @@ import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; -import backupmanager.gui.Table.BackupTable; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.Table.BackupTable; import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.frames.BackupProgressGUI; @@ -83,7 +83,7 @@ public boolean canDisposeAfterOk(String name, String initialPath, String destina if (ConfigurationBackup.getBackupByName(currentBackup.getName()) != null) { int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.DUPLICATED_BACKUP_NAME_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { - BackupHelper.removeBackup(currentBackup.getName()); + BackupHelper.deleteBackup(currentBackup.getName()); } else { return false; } diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index e7ecc1d1..c01c68aa 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -1,11 +1,5 @@ package backupmanager.gui.component; -import java.awt.Desktop; -import java.awt.Graphics; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; - import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JPanel; @@ -15,9 +9,9 @@ import javax.swing.text.DefaultCaret; import com.formdev.flatlaf.FlatClientProperties; -import com.formdev.flatlaf.util.LoggingFacade; import backupmanager.Enums.ConfigKey; +import backupmanager.Managers.WebsiteManager; import net.miginfocom.swing.MigLayout; public class About extends JPanel { @@ -27,82 +21,81 @@ public About() { } private void init() { - setLayout(new MigLayout("fillx,wrap,insets 5 30 5 30,width 400", "[fill,330::]", "")); - JTextPane title = createText("Modal Dialog Demo Project"); - title.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold +5"); + setLayout(new MigLayout("fillx,wrap,insets 20,width 520")); + + JTextPane title = createText("Backup Manager"); + title.putClientProperty(FlatClientProperties.STYLE, "font:bold +6"); JTextPane description = createText(""); description.setContentType("text/html"); description.setText(getDescriptionText()); + description.addHyperlinkListener(e -> { - if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - showUrl(e.getURL()); - } + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) + WebsiteManager.openWebSite(e.getURL().toString()); }); add(title); - add(description); - add(createSystemInformation()); + add(description, "gapy 10"); + add(createSystemInformation(), "gapy 15"); } private JTextPane createText(String text) { - JTextPane textPane = new JTextPane(); - textPane.setBorder(BorderFactory.createEmptyBorder()); - textPane.setText(text); - textPane.setEditable(false); - textPane.setCaret(new DefaultCaret() { - @Override - public void paint(Graphics g) { - } + JTextPane pane = new JTextPane(); + pane.setBorder(BorderFactory.createEmptyBorder()); + pane.setText(text); + pane.setEditable(false); + pane.setOpaque(false); + + pane.setCaret(new DefaultCaret() { + public void paint(java.awt.Graphics g) {} }); - return textPane; + + return pane; } private String getDescriptionText() { - String text = "This is a demo project for the Modal Dialog library, " + - "built using FlatLaf Look and Feel and MigLayout library.
" + - "For source code, visit the GitHub Project."; + return """ + + Backup Manager is a simple and powerful application designed to automate + folder and subfolder backups. - return text; - } +

+ Users can schedule automatic backups or execute manual backups anytime. - private String getSystemInformationText() { - String text = "Demo Version: %s
" + - "Java: %s
" + - "System: %s
"; +

+ Backup history is stored securely, allowing full control over saved data. - return text; +

+ Visit project website for more information. + + """.formatted(ConfigKey.INFO_PAGE_LINK.getValue()); } private JComponent createSystemInformation() { - JPanel panel = new JPanel(new MigLayout("wrap")); + + JPanel panel = new JPanel(new MigLayout("wrap,insets 10")); panel.setBorder(new TitledBorder("System Information")); - JTextPane textPane = createText(""); - textPane.setContentType("text/html"); - String version = ConfigKey.VERSION.getValue(); - String java = System.getProperty("java.vendor") + " - v" + System.getProperty("java.version"); - String system = System.getProperty("os.name") + " " + System.getProperty("os.arch") + " - v" + System.getProperty("os.version"); - String text = String.format(getSystemInformationText(), - version, - java, - system); - textPane.setText(text); - panel.add(textPane); - return panel; - } - private void showUrl(URL url) { - if (Desktop.isDesktopSupported()) { - Desktop desktop = Desktop.getDesktop(); - if (desktop.isSupported(Desktop.Action.BROWSE)) { - try { - desktop.browse(url.toURI()); - } catch (IOException | URISyntaxException e) { - LoggingFacade.INSTANCE.logSevere("Error browse url", e); - } - } - } + JTextPane text = createText(""); + text.setContentType("text/html"); + + String info = """ + + Version: %s
+ Java: %s
+ OS: %s
+ + """.formatted( + ConfigKey.VERSION.getValue(), + System.getProperty("java.vendor") + " - v" + System.getProperty("java.version"), + System.getProperty("os.name") + " " + System.getProperty("os.arch") + ); + + text.setText(info); + panel.add(text); + + return panel; } } diff --git a/src/main/java/backupmanager/gui/component/Subscription.java b/src/main/java/backupmanager/gui/component/Subscription.java new file mode 100644 index 00000000..d2ba0519 --- /dev/null +++ b/src/main/java/backupmanager/gui/component/Subscription.java @@ -0,0 +1,110 @@ +package backupmanager.gui.component; + +import java.awt.Graphics; +import java.time.format.DateTimeFormatter; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JTextPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.DefaultCaret; + +import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.SubscriptionStatus; +import backupmanager.Helpers.SubscriptionHelper; +import backupmanager.Managers.WebsiteManager; +import net.miginfocom.swing.MigLayout; + +public class Subscription extends JPanel { + + private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + + public Subscription() { + init(); + } + + private void init() { + setLayout(new MigLayout("fillx, wrap, insets 25, width 520")); + setOpaque(false); + + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + String statusString = SubscriptionHelper.getSubscriptionStatusTranslated(status); + + backupmanager.Entities.Subscription subscription = SubscriptionHelper.getLastValidSubscription(); + String from = formatDate(subscription != null ? subscription.startDate() : null); + String to = formatDate(subscription != null ? subscription.endDate() : null); + + JTextPane description = createHtmlPane( + buildHtml(status, statusString, from, to) + ); + + add(description, "growx"); + } + + private JTextPane createHtmlPane(String html) { + JTextPane pane = new JTextPane(); + pane.setContentType("text/html"); + pane.setText(html); + pane.setEditable(false); + pane.setOpaque(false); + pane.setBorder(BorderFactory.createEmptyBorder()); + + pane.setCaret(new DefaultCaret() { + @Override public void paint(Graphics g) {} + }); + + pane.addHyperlinkListener(e -> { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + WebsiteManager.openWebSite(e.getURL().toString()); + } + }); + + return pane; + } + + private String formatDate(java.time.temporal.TemporalAccessor date) { + if (date == null) return "N/A"; + return DATE_FORMAT.format(date); + } + + private String buildHtml(SubscriptionStatus status, String statusText, String validFrom, String validTo) { + String statusColor = switch (status) { + case ACTIVE -> "#2E7D32"; + case EXPIRATION -> "#ED6C02"; + case EXPIRED -> "#D32F2F"; + case NONE -> "#757575"; + }; + + String subject = encodeURIComponent("Support - Backup Manager"); + + return """ + +
+ Subscription status: + %s +

+ + Valid from: %s
+ Valid to: %s
+ +
+ Contact us + to extend the subscription period. +
+ + """.formatted( + statusColor, + statusText, + validFrom, + validTo, + ConfigKey.EMAIL.getValue(), + subject + ); + } + + private String encodeURIComponent(String value) { + return java.net.URLEncoder + .encode(value, java.nio.charset.StandardCharsets.UTF_8) + .replace("+", "%20"); + } +} diff --git a/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java index fd9e82e9..6cb4ec29 100644 --- a/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java +++ b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java @@ -19,7 +19,6 @@ import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYItemRenderer; -import org.jfree.data.xy.TableXYDataset; import org.jfree.data.xy.XYDataset; import backupmanager.gui.component.chart.renderer.ChartXYCurveRenderer; @@ -124,7 +123,7 @@ public void chartMouseMoved(ChartMouseEvent event) { return; } double x = plot.getDomainAxis().java2DToValue(event.getTrigger().getX(), dataArea, plot.getDomainAxisEdge()); - TableXYDataset dataset = (TableXYDataset) plot.getDataset(); + XYDataset dataset = plot.getDataset(); int seriesCount = plot.getSeriesCount(); double minDistance = Double.MAX_VALUE; diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java new file mode 100644 index 00000000..ad139190 --- /dev/null +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -0,0 +1,316 @@ +package backupmanager.gui.forms; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; +import com.formdev.flatlaf.util.UIScale; + +import backupmanager.Entities.BackupAnalyticsSnapshot; +import backupmanager.Entities.BackupRequest; +import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Services.BackupAnalyticsService; +import backupmanager.database.Repositories.BackupConfigurationRepository; +import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.component.ToolBarSelection; +import backupmanager.gui.component.chart.BarChart; +import backupmanager.gui.component.chart.PieChart; +import backupmanager.gui.component.chart.SpiderChart; +import backupmanager.gui.component.chart.TimeSeriesChart; +import backupmanager.gui.component.chart.themes.ColorThemes; +import backupmanager.gui.component.chart.themes.DefaultChartTheme; +import backupmanager.gui.component.dashboard.CardBox; +import backupmanager.gui.system.Form; +import backupmanager.utils.SystemForm; +import net.miginfocom.swing.MigLayout; + +@SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") +public class FormBackupDashboard extends Form { + + public FormBackupDashboard() { + init(); + } + + private void init() { + setLayout(new MigLayout("wrap,fill", "[fill]", "[grow 0][fill]")); + createTitle(); + createPanelLayout(); + createCard(); + createDiskUsageChart(); + createExecutionsByMonthChart(); + createAvgDurationChart(); + } + + @Override + public void formInit() { + loadData(); + } + + @Override + public void formRefresh() { + loadData(); + } + + private void loadData() { + List configurations = BackupConfigurationRepository.getBackupList(); + List requests = BackupRequestRepository.getRequestBackups(); + BackupAnalyticsSnapshot snapshot = BackupAnalyticsService.buildSnapshot(requests); + + cardBox.setValueAt(0, + String.valueOf(configurations.size()), + "Total Backup Configurations", + "", + true); + + cardBox.setValueAt(1, + String.valueOf(snapshot.totalRequests()), + "Total Backup Executions", + snapshot.successRate() + "%", + true); + + cardBox.setValueAt(2, + BackupAnalyticsService.formatBytes(snapshot.totalDiskUsageBytes()), + "Disk Usage", + "", + true); + + cardBox.setValueAt(3, + String.format("%.2f min", BackupAnalyticsService.convertAvgDurationinMinutes(snapshot)), + "Avg Backup Duration", + "", + true); + + cardBox.setValueAt(4, + String.format("%.1f%%", snapshot.avgCompressionRate() * 100), + "Compression Rate", + "", + true); + + timeSeriesChart.setDataset(BackupAnalyticsService.buildDurationTrendDataset(snapshot.durationTrend())); + } + + private void createTitle() { + + JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); + + JLabel title = new JLabel("Backup Analytics Dashboard"); + title.putClientProperty(FlatClientProperties.STYLE, + "font:bold +3"); + + ToolBarSelection toolBarSelection = + new ToolBarSelection<>(ColorThemes.values(), colorThemes -> { + + if (DefaultChartTheme.setChartColors(colorThemes)) { + + DefaultChartTheme.applyTheme(timeSeriesChart.getFreeChart()); + DefaultChartTheme.applyTheme(barChart.getFreeChart()); + DefaultChartTheme.applyTheme(pieChart.getFreeChart()); + DefaultChartTheme.applyTheme(spiderChart.getFreeChart()); + + cardBox.setCardIconColor(0, DefaultChartTheme.getColor(0)); + cardBox.setCardIconColor(1, DefaultChartTheme.getColor(1)); + cardBox.setCardIconColor(2, DefaultChartTheme.getColor(2)); + cardBox.setCardIconColor(3, DefaultChartTheme.getColor(3)); + cardBox.setCardIconColor(4, DefaultChartTheme.getColor(4)); + } + }); + + panel.add(title); + panel.add(toolBarSelection); + add(panel); + } + + private void createPanelLayout() { + + panelLayout = new JPanel(new DashboardLayout()); + + JScrollPane scrollPane = new JScrollPane(panelLayout); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + + scrollPane.setHorizontalScrollBarPolicy( + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + scrollPane.getVerticalScrollBar().setUnitIncrement(10); + + scrollPane.getVerticalScrollBar().putClientProperty( + FlatClientProperties.STYLE, + "width:5;" + + "trackArc:$ScrollBar.thumbArc;" + + "trackInsets:0,0,0,0;" + + "thumbInsets:0,0,0,0;"); + + add(scrollPane); + } + + + private void createCard() { + + JPanel panel = new JPanel(new MigLayout("fillx", "[fill]")); + + cardBox = new CardBox(); + + cardBox.addCardItem( + createIcon("icons/dashboard/database.svg", DefaultChartTheme.getColor(0)), + "Total Backup Configurations"); + + cardBox.addCardItem( + createIcon("icons/dashboard/run.svg", DefaultChartTheme.getColor(1)), + "Total Backup Executions"); + + cardBox.addCardItem( + createIcon("icons/dashboard/usage.svg", DefaultChartTheme.getColor(2)), + "Disk Usage"); + + cardBox.addCardItem( + createIcon("icons/dashboard/duration.svg", DefaultChartTheme.getColor(3)), + "Avg Backup Duration"); + + cardBox.addCardItem( + createIcon("icons/dashboard/rate.svg", DefaultChartTheme.getColor(4)), + "Compression Rate"); + + panel.add(cardBox); + panelLayout.add(panel); + } + + private void createAvgDurationChart() { + + JPanel panel = new JPanel( + new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); + + timeSeriesChart = new TimeSeriesChart(); + barChart = new BarChart(); + + panel.add(timeSeriesChart); + panel.add(barChart); + + panelLayout.add(panel); + } + + private void createDiskUsageChart() { + + JPanel panel = new JPanel( + new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); + + spiderChart = new SpiderChart(); + pieChart = new PieChart(); + + panel.add(spiderChart); + panel.add(pieChart); + + panelLayout.add(panel); + } + + private void createExecutionsByMonthChart() { + + JPanel panel = new JPanel( + new MigLayout("fillx,gap 14", "[fill,300::]", "[300]")); + + timeSeriesChart = new TimeSeriesChart(); + barChart = new BarChart(); + + panel.add(timeSeriesChart); + panel.add(barChart); + + panelLayout.add(panel); + } + + + private Icon createIcon(String icon, Color color) { + return new FlatSVGIcon(icon, 20, 20).setColorFilter(new FlatSVGIcon.ColorFilter(color1 -> color)); + } + + private JPanel panelLayout; + private CardBox cardBox; + + private TimeSeriesChart timeSeriesChart; + private BarChart barChart; + private SpiderChart spiderChart; + private PieChart pieChart; + + private class DashboardLayout implements LayoutManager { + + private final int gap = 0; + + @Override + public void addLayoutComponent(String name, Component comp) {} + + @Override + public void removeLayoutComponent(Component comp) {} + + @Override + public Dimension preferredLayoutSize(Container parent) { + + synchronized (parent.getTreeLock()) { + + Insets insets = parent.getInsets(); + + int width = insets.left + insets.right; + int height = insets.top + insets.bottom; + + int g = UIScale.scale(gap); + + int count = parent.getComponentCount(); + + for (int i = 0; i < count; i++) { + Component com = parent.getComponent(i); + Dimension size = com.getPreferredSize(); + height += size.height; + } + + if (count > 1) { + height += (count - 1) * g; + } + + return new Dimension(width, height); + } + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return new Dimension(10, 10); + } + + @Override + public void layoutContainer(Container parent) { + + synchronized (parent.getTreeLock()) { + + Insets insets = parent.getInsets(); + + int x = insets.left; + int y = insets.top; + + int width = parent.getWidth() - + (insets.left + insets.right); + + int g = UIScale.scale(gap); + + int count = parent.getComponentCount(); + + for (int i = 0; i < count; i++) { + + Component com = parent.getComponent(i); + Dimension size = com.getPreferredSize(); + + com.setBounds(x, y, width, size.height); + + y += size.height + g; + } + } + } + } +} diff --git a/src/main/java/backupmanager/gui/forms/FormDashboard.java b/src/main/java/backupmanager/gui/forms/FormDashboard.java deleted file mode 100644 index 5d783c2f..00000000 --- a/src/main/java/backupmanager/gui/forms/FormDashboard.java +++ /dev/null @@ -1,233 +0,0 @@ -package backupmanager.gui.forms; - -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.Insets; -import java.awt.LayoutManager; - -import javax.swing.BorderFactory; -import javax.swing.Icon; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ScrollPaneConstants; - -import org.jfree.chart.renderer.xy.CandlestickRenderer; - -import com.formdev.flatlaf.FlatClientProperties; -import com.formdev.flatlaf.extras.FlatSVGIcon; -import com.formdev.flatlaf.util.UIScale; - -import backupmanager.gui.component.ToolBarSelection; -import backupmanager.gui.component.chart.BarChart; -import backupmanager.gui.component.chart.CandlestickChart; -import backupmanager.gui.component.chart.PieChart; -import backupmanager.gui.component.chart.SpiderChart; -import backupmanager.gui.component.chart.TimeSeriesChart; -import backupmanager.gui.component.chart.renderer.other.ChartCandlestickRenderer; -import backupmanager.gui.component.chart.themes.ColorThemes; -import backupmanager.gui.component.chart.themes.DefaultChartTheme; -import backupmanager.gui.component.chart.utils.ToolBarCategoryOrientation; -import backupmanager.gui.component.chart.utils.ToolBarTimeSeriesChartRenderer; -import backupmanager.gui.component.dashboard.CardBox; -import backupmanager.gui.sample.SampleData; -import backupmanager.gui.system.Form; -import backupmanager.utils.SystemForm; -import net.miginfocom.swing.MigLayout; - -@SystemForm(name = "Dashboard", description = "dashboard form display some details") -public class FormDashboard extends Form { - - public FormDashboard() { - init(); - } - - private void init() { - setLayout(new MigLayout("wrap,fill", "[fill]", "[grow 0][fill]")); - createTitle(); - createPanelLayout(); - createCard(); - createChart(); - createOtherChart(); - } - - @Override - public void formInit() { - loadData(); - } - - @Override - public void formRefresh() { - loadData(); - } - - private void loadData() { - // load data card - cardBox.setValueAt(0, "1,205", "+305 new registered", "+25%", true); - cardBox.setValueAt(1, "$52,420.55", "less then previous month", "-5%", false); - cardBox.setValueAt(2, "$3,180.00", "more then previous month", "+12%", true); - cardBox.setValueAt(3, "$49,240.55", "more then previous month", "+7%", true); - - // load data chart - timeSeriesChart.setDataset(SampleData.getTimeSeriesDataset()); - candlestickChart.setDataset(SampleData.getOhlcDataset()); - barChart.setDataset(SampleData.getCategoryDataset()); - spiderChart.setDataset(SampleData.getCategoryDataset()); - pieChart.setDataset(SampleData.getPieDataset()); - } - - private void createTitle() { - JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); - JLabel title = new JLabel("Dashboard"); - - title.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold +3"); - - ToolBarSelection toolBarSelection = new ToolBarSelection<>(ColorThemes.values(), colorThemes -> { - if (DefaultChartTheme.setChartColors(colorThemes)) { - DefaultChartTheme.applyTheme(timeSeriesChart.getFreeChart()); - DefaultChartTheme.applyTheme(candlestickChart.getFreeChart()); - DefaultChartTheme.applyTheme(barChart.getFreeChart()); - DefaultChartTheme.applyTheme(pieChart.getFreeChart()); - DefaultChartTheme.applyTheme(spiderChart.getFreeChart()); - cardBox.setCardIconColor(0, DefaultChartTheme.getColor(0)); - cardBox.setCardIconColor(1, DefaultChartTheme.getColor(1)); - cardBox.setCardIconColor(2, DefaultChartTheme.getColor(2)); - cardBox.setCardIconColor(3, DefaultChartTheme.getColor(3)); - } - }); - panel.add(title); - panel.add(toolBarSelection); - add(panel); - } - - private void createPanelLayout() { - panelLayout = new JPanel(new DashboardLayout()); - JScrollPane scrollPane = new JScrollPane(panelLayout); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - scrollPane.getVerticalScrollBar().setUnitIncrement(10); - scrollPane.getVerticalScrollBar().putClientProperty(FlatClientProperties.STYLE, "" + - "width:5;" + - "trackArc:$ScrollBar.thumbArc;" + - "trackInsets:0,0,0,0;" + - "thumbInsets:0,0,0,0;"); - add(scrollPane); - } - - private void createCard() { - JPanel panel = new JPanel(new MigLayout("fillx", "[fill]")); - cardBox = new CardBox(); - cardBox.addCardItem(createIcon("icons/dashboard/customer.svg", DefaultChartTheme.getColor(0)), "Total Customer"); - cardBox.addCardItem(createIcon("icons/dashboard/income.svg", DefaultChartTheme.getColor(1)), "Total Income"); - cardBox.addCardItem(createIcon("icons/dashboard/expense.svg", DefaultChartTheme.getColor(2)), "Total Expense"); - cardBox.addCardItem(createIcon("icons/dashboard/profit.svg", DefaultChartTheme.getColor(3)), "Last Profit"); - panel.add(cardBox); - panelLayout.add(panel); - } - - private void createChart() { - JPanel panel = new JPanel(new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); - timeSeriesChart = new TimeSeriesChart(); - candlestickChart = new CandlestickChart(); - barChart = new BarChart(); - timeSeriesChart.add(new ToolBarTimeSeriesChartRenderer(timeSeriesChart), "al trailing,grow 0", 0); - candlestickChart.add(new ToolBarSelection<>(new String[]{"default", "red_green"}, s -> { - CandlestickRenderer renderer = (CandlestickRenderer) candlestickChart.getFreeChart().getXYPlot().getRenderer(); - if (s == "default") { - renderer.setAutoPopulateSeriesPaint(true); - DefaultChartTheme.applyTheme(candlestickChart.getFreeChart()); - } else { - renderer.setAutoPopulateSeriesPaint(false); - ChartCandlestickRenderer.initRedGreenColor(renderer); - } - }), "al trailing,grow 0", 0); - barChart.add(new ToolBarCategoryOrientation(barChart.getFreeChart()), "al trailing,grow 0", 0); - panel.add(timeSeriesChart); - panel.add(candlestickChart); - panel.add(barChart); - panelLayout.add(panel); - } - - private void createOtherChart() { - JPanel panel = new JPanel(new MigLayout("fillx,gap 14", "[fill,300::]", "[300]")); - spiderChart = new SpiderChart(); - pieChart = new PieChart(); - panel.add(spiderChart); - panel.add(pieChart); - panelLayout.add(panel); - } - - private Icon createIcon(String icon, Color color) { - return new FlatSVGIcon(icon, 0.4f).setColorFilter(new FlatSVGIcon.ColorFilter(color1 -> color)); - } - - private JPanel panelLayout; - private CardBox cardBox; - - private TimeSeriesChart timeSeriesChart; - private CandlestickChart candlestickChart; - private BarChart barChart; - private SpiderChart spiderChart; - private PieChart pieChart; - - private class DashboardLayout implements LayoutManager { - - private int gap = 0; - - @Override - public void addLayoutComponent(String name, Component comp) { - } - - @Override - public void removeLayoutComponent(Component comp) { - } - - @Override - public Dimension preferredLayoutSize(Container parent) { - synchronized (parent.getTreeLock()) { - Insets insets = parent.getInsets(); - int width = (insets.left + insets.right); - int height = insets.top + insets.bottom; - int g = UIScale.scale(gap); - int count = parent.getComponentCount(); - for (int i = 0; i < count; i++) { - Component com = parent.getComponent(i); - Dimension size = com.getPreferredSize(); - height += size.height; - } - if (count > 1) { - height += (count - 1) * g; - } - return new Dimension(width, height); - } - } - - @Override - public Dimension minimumLayoutSize(Container parent) { - synchronized (parent.getTreeLock()) { - return new Dimension(10, 10); - } - } - - @Override - public void layoutContainer(Container parent) { - synchronized (parent.getTreeLock()) { - Insets insets = parent.getInsets(); - int x = insets.left; - int y = insets.top; - int width = parent.getWidth() - (insets.left + insets.right); - int g = UIScale.scale(gap); - int count = parent.getComponentCount(); - for (int i = 0; i < count; i++) { - Component com = parent.getComponent(i); - Dimension size = com.getPreferredSize(); - com.setBounds(x, y, width, size.height); - y += size.height + g; - } - } - } - } -} diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java new file mode 100644 index 00000000..daebbe58 --- /dev/null +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -0,0 +1,103 @@ +package backupmanager.gui.forms; + +import java.io.IOException; +import java.io.InputStream; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; + +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.Enums.ConfigKey; +import backupmanager.gui.system.Form; +import backupmanager.utils.SystemForm; +import net.miginfocom.swing.MigLayout; + + +@SystemForm(name = "History", description = "application history log") +public class FormHistory extends Form { + public FormHistory() { + init(); + } + + private void init() { + setLayout(new MigLayout("fill,wrap", "[fill]", "[][grow,fill]")); + createTitle(); + createInfo("Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."); + createLogPanel(); + loadLogs(); + } + + @Override + public void formInit() { + loadLogs(); + } + + @Override + public void formRefresh() { + loadLogs(); + } + + private void loadLogs() { + try { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("res/logs/" + ConfigKey.LOG_FILE_STRING.getValue())) { + if (is == null) { + logsPane.setText("Log file not found in resources"); + return; + } + + String content = new String(is.readAllBytes()); + + logsPane.setText(content); + logsPane.setCaretPosition(0); + } + + } catch (IOException ex) { + logsPane.setText("Failed to load logs:\n" + ex.getMessage()); + } + } + + private void createTitle() { + JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); + JLabel title = new JLabel("History"); + + title.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +3"); + + panel.add(title); + add(panel); + } + + private void createInfo(String description) { + JPanel panel = new JPanel(new MigLayout("fillx,wrap", "[fill]")); + JLabel lbTitle = new JLabel("Information"); + JTextPane text = new JTextPane(); + text.setText(description); + text.setEditable(false); + text.setBorder(BorderFactory.createEmptyBorder()); + lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold"); + panel.add(lbTitle); + panel.add(text, "width 500"); + add(panel); + } + + private void createLogPanel() { + logsPane = new JTextPane(); + logsPane.setEditable(false); + logsPane.setContentType("text/plain"); + + JScrollPane scroll = new JScrollPane(logsPane); + + scroll.putClientProperty(FlatClientProperties.STYLE, + "arc:10;" + + "border:1,1,1,1,$Component.borderColor"); + + add(scroll, "grow"); + } + + private JTextPane logsPane; +} diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index a1ac3b65..b62213ae 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -74,6 +74,7 @@ private JPanel createLayoutOption() { panel.add(createWindowsLayout()); panel.add(createDrawerLayout()); panel.add(createModalDefaultOption()); + panel.add(createLanguageOption()); return panel; } @@ -194,6 +195,25 @@ private Component createModalDefaultOption() { return panel; } + private Component createLanguageOption() { + JPanel panel = new JPanel(new MigLayout()); + panel.setBorder(new TitledBorder("Language")); + JComboBox languageCombo = new JComboBox(); + initComboItem(languageCombo); + + panel.add(languageCombo); + + return panel; + } + + private void initComboItem(JComboBox combo) { + combo.addItem("English"); + combo.addItem("Italiano"); + combo.addItem("Español"); + combo.addItem("Deutsch"); + combo.addItem("Français"); + } + private JPanel createStyleOption() { JPanel panel = new JPanel(new MigLayout("wrap,fillx", "[fill]")); panel.add(createAccentColor()); diff --git a/src/main/java/backupmanager/gui/forms/FormTable.java b/src/main/java/backupmanager/gui/forms/FormTable.java index ec65cbc2..ed6c645e 100644 --- a/src/main/java/backupmanager/gui/forms/FormTable.java +++ b/src/main/java/backupmanager/gui/forms/FormTable.java @@ -1,8 +1,9 @@ package backupmanager.gui.forms; import java.awt.Component; -import java.io.IOException; import java.text.DecimalFormat; +import java.time.LocalDateTime; +import java.util.List; import javax.swing.AbstractAction; import javax.swing.BorderFactory; @@ -15,33 +16,48 @@ import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; -import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingConstants; +import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.extras.FlatSVGIcon; -import backupmanager.gui.sample.csv.CSVDataReader; -import backupmanager.gui.sample.csv.ResponseCSV; -import backupmanager.gui.simple.SimpleInputForms; +import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Entities.TimeInterval; +import backupmanager.Helpers.BackupHelper; +import static backupmanager.Helpers.BackupHelper.formatter; +import backupmanager.Services.BackupService; +import backupmanager.gui.frames.Controllers.BackupManagerController; +import backupmanager.gui.frames.Controllers.BackupPopupController; +import backupmanager.gui.sample.csv.ConfigurationBackupDataTable; import backupmanager.gui.svg.SVGButton; import backupmanager.gui.system.Form; import backupmanager.utils.SystemForm; import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; -import raven.modal.ModalDialog; -import raven.modal.component.SimpleModalBorder; -import raven.modal.option.Location; -import raven.modal.option.Option; import raven.swingpack.JPagination; @SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) public class FormTable extends Form { + private static final Logger logger = LoggerFactory.getLogger(FormTable.class); + + private BackupManagerController managerController; + private final int COL_AUTOMATIC = 4; + private final int COL_NEXT_RUN = 5; + private final int COL_LAST_RUN = 3; + + private BackupService backupService; + private List backups; + private int autoColumnIndex; + public FormTable() { init(); } @@ -55,40 +71,39 @@ private void init() { add(createInfo("Backup List", "A table is a user interface component that displays a collection of records in a structured, tabular format. It allows users to view, sort, and manage data or other resources.", 1)); add(createBorder(createBasicTable()), "gapx 7 7, grow"); add(createBorder(createDetails()), "gapx 7 7, hmin 150"); + + backupService = new BackupService(); + managerController = new BackupManagerController(backupService); } @Override public void formInit() { - try { - data = CSVDataReader.load(getClass().getResourceAsStream("/data/customers-1000.csv")); - DefaultTableModel model = (DefaultTableModel) basicTable.getModel(); - model.setColumnIdentifiers(data.getColumns()); - basicTable.setModel(model); - - // table column size - basicTable.getColumnModel().getColumn(0).setMaxWidth(50); - - formRefresh(); - } catch (IOException e) { - System.err.println(e.getMessage()); - } + DefaultTableModel model = (DefaultTableModel) backupTable.getModel(); + model.setColumnIdentifiers(ConfigurationBackup.getCSVHeaderArray()); + + autoColumnIndex = COL_AUTOMATIC; + + backupTable.createDefaultColumnsFromModel(); + + formRefresh(); } @Override public void formRefresh() { + backups = backupService.getAllBackups(); showData(pagination.getSelectedPage()); } private void showData(int page) { - if (data != null) { - ResponseCSV res = data.getData(page, limit); + if (backups != null) { + ConfigurationBackupDataTable res = ConfigurationBackupDataTable.create(backups, page, limit); lbTotalPage.setText(DecimalFormat.getInstance().format(res.getTotal())); pagination.getModel().setPageRange(res.getPage(), res.getPageSize()); - DefaultTableModel model = (DefaultTableModel) basicTable.getModel(); + DefaultTableModel model = (DefaultTableModel) backupTable.getModel(); model.setRowCount(0); - for (String[] row : res.getData()) { - model.addRow(row); + for (ConfigurationBackup backup : res.getData()) { + model.addRow(backup.toTableRow()); } } } @@ -114,13 +129,38 @@ private Component createBorder(Component component) { } private Component createBasicTable() { - JPanel panelTable = new JPanel(new MigLayout("fill,wrap,insets 10 0 10 0", - "[fill]", - "[][][grow,fill][pref!]")); + JPanel panelTable = new JPanel(new MigLayout( + "fill,wrap,insets 10 0 10 0", + "[fill]", + "[][grow,fill][]" + )); // create table model JTable table = new JTable(); table.setModel(new DefaultTableModel() { + + @Override + public Class getColumnClass(int columnIndex) { + + if (columnIndex == COL_AUTOMATIC) { + return Boolean.class; + } + + if (columnIndex == COL_LAST_RUN || columnIndex == COL_NEXT_RUN) { + return LocalDateTime.class; + } + + if (columnIndex == 6) { + return TimeInterval.class; + } + + if (columnIndex == 7) { + return Integer.class; + } + + return String.class; + } + @Override public boolean isCellEditable(int row, int column) { return false; @@ -133,14 +173,8 @@ public boolean isCellEditable(int row, int column) { if (!e.getValueIsAdjusting()) { int row = table.getSelectedRow(); if (row != -1) { - StringBuilder details = new StringBuilder(); - for (int i = 0; i < table.getColumnCount(); i++) { - details.append(table.getColumnName(i)) - .append(": ") - .append(table.getValueAt(row, i)) - .append("\n"); - } - txtDetails.setText(details.toString()); + String details = backupService.buildDetails(backups.get(row)); + txtDetails.setText(details); } } }); @@ -150,8 +184,8 @@ public boolean isCellEditable(int row, int column) { public void mouseClicked(java.awt.event.MouseEvent e) { if (e.getClickCount() == 2 && table.getSelectedRow() != -1) { int row = table.getSelectedRow(); - //showRowDetail(table, row); - System.out.println("Double clicked row: " + row); + logger.debug("Double clicked row: " + row); + showCreateModal(); } } }); @@ -166,7 +200,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { int row = table.getSelectedRow(); if (row != -1) { ((DefaultTableModel) table.getModel()).removeRow(row); - System.out.println("Deleted row: " + row); + logger.debug("Deleted row: " + row); } } }); @@ -176,13 +210,12 @@ public void actionPerformed(java.awt.event.ActionEvent e) { scrollPane.setBorder(BorderFactory.createEmptyBorder()); // alignment table header - table.getTableHeader().setDefaultRenderer(new TableHeaderAlignment(table) { + table.getTableHeader().setDefaultRenderer( + new TableHeaderAlignment(table) { + @Override protected int getAlignment(int column) { - if (column == 0) { - return SwingConstants.CENTER; - } - return SwingConstants.LEADING; + return SwingConstants.LEFT; } }); @@ -211,7 +244,7 @@ protected int getAlignment(int column) { // create title panelTable.add(createHeaderAction()); - panelTable.add(scrollPane, "grow, push"); + panelTable.add(scrollPane, "grow, pushy"); // create pagination pagination = new JPagination(11, 1, 1); @@ -229,11 +262,67 @@ protected int getAlignment(int column) { panelPage.add(lbTotalPage); panelPage.add(pagination); - panelTable.add(panelPage); + panelTable.add(panelPage, "growx"); + + backupTable = table; + + setRenderer(); + + buildTablePopupMenu(); + + return panelTable; + } + + private void setRenderer() { + DefaultTableCellRenderer dateRenderer = new DefaultTableCellRenderer() { + @Override + protected void setValue(Object value) { + if (value instanceof LocalDateTime date) { + setText(date.format(formatter)); + } else { + super.setValue(value); + } + } + }; + + backupTable.setDefaultRenderer(LocalDateTime.class, dateRenderer); + + DefaultTableCellRenderer baseRenderer = new DefaultTableCellRenderer() { + + @Override + public void setHorizontalAlignment(int alignment) { + super.setHorizontalAlignment(SwingConstants.LEFT); + } + + @Override + protected void setValue(Object value) { + + if (value instanceof Boolean bool) { + setHorizontalAlignment(SwingConstants.CENTER); + setText(bool ? "✓" : ""); + return; + } + + if (value instanceof Integer || value instanceof Long) { + setHorizontalAlignment(SwingConstants.LEFT); + setText(String.valueOf(value)); + return; + } + + if (value instanceof LocalDateTime date) { + setHorizontalAlignment(SwingConstants.LEFT); + setText(date.format(formatter)); + return; + } - basicTable = table; + super.setValue(value); + } + }; + backupTable.setDefaultRenderer(Object.class, baseRenderer); + } + private void buildTablePopupMenu() { JPopupMenu popupMenu = new JPopupMenu(); JMenuItem itemEdit = new JMenuItem("Edit"); @@ -246,6 +335,7 @@ protected int getAlignment(int column) { JMenu itemBackup = new JMenu("Backup"); JMenuItem itemRunSingleBackup = new JMenuItem("Run single backup"); JCheckBoxMenuItem itemAutoBackup = new JCheckBoxMenuItem("Auto backup"); + JMenuItem itemInterruptBackup = new JMenuItem("Interrupt backup process"); JMenu itemCopyText = new JMenu("Copy text"); JMenuItem itemCopyBackupName = new JMenuItem("Copy backup name"); @@ -263,13 +353,29 @@ protected int getAlignment(int column) { popupMenu.add(itemBackup); itemBackup.add(itemRunSingleBackup); itemBackup.add(itemAutoBackup); + itemBackup.add(itemInterruptBackup); popupMenu.addSeparator(); popupMenu.add(itemCopyText); itemCopyText.add(itemCopyBackupName); itemCopyText.add(itemCopyTargetPath); itemCopyText.add(itemCopyDestinationPath); - table.addMouseListener(new java.awt.event.MouseAdapter() { + itemEdit.addActionListener(e -> handleAction("EDIT", itemInterruptBackup, itemRunSingleBackup)); + itemDelete.addActionListener(e -> handleAction("DELETE", itemInterruptBackup, itemRunSingleBackup)); + itemDuplicate.addActionListener(e -> handleAction("DUPLICATE", itemInterruptBackup, itemRunSingleBackup)); + itemRename.addActionListener(e -> handleAction("RENAME", itemInterruptBackup, itemRunSingleBackup)); + itemOpenTargetPath.addActionListener(e -> handleAction("OPEN_TARGET", itemInterruptBackup, itemRunSingleBackup)); + itemOpenDestinationPath.addActionListener(e -> handleAction("OPEN_DEST", itemInterruptBackup, itemRunSingleBackup)); + itemRunSingleBackup.addActionListener(e -> handleAction("RUN_SINGLE", itemInterruptBackup, itemRunSingleBackup)); + itemAutoBackup.addActionListener(e -> handleToggle()); + itemInterruptBackup.addActionListener(e -> handleAction("INTERRUPT_BACKUP", itemInterruptBackup, itemRunSingleBackup)); + itemCopyBackupName.addActionListener(e -> handleAction("COPY_NAME", itemInterruptBackup, itemRunSingleBackup)); + itemCopyTargetPath.addActionListener(e -> handleAction("COPY_TARGET", itemInterruptBackup, itemRunSingleBackup)); + itemCopyDestinationPath.addActionListener(e -> handleAction("COPY_DEST", itemInterruptBackup, itemRunSingleBackup)); + + itemInterruptBackup.setEnabled(false); // Disable interrupt option by default + + backupTable.addMouseListener(new java.awt.event.MouseAdapter() { @Override public void mousePressed(java.awt.event.MouseEvent e) { if (e.isPopupTrigger()) { @@ -285,16 +391,51 @@ public void mouseReleased(java.awt.event.MouseEvent e) { } private void showPopup(java.awt.event.MouseEvent e) { - int row = table.rowAtPoint(e.getPoint()); + int row = backupTable.rowAtPoint(e.getPoint()); if (row >= 0) { - table.setRowSelectionInterval(row, row); + backupTable.setRowSelectionInterval(row, row); } popupMenu.show(e.getComponent(), e.getX(), e.getY()); + ConfigurationBackup backup = getBackupFromTableRow(row); + itemAutoBackup.setSelected(backup.isAutomatic()); } }); + } + private void handleAction(String action, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { + int selectedRow = backupTable.getSelectedRow(); + if (selectedRow < 0) + return; + + ConfigurationBackup backup = getBackupFromTableRow(selectedRow); + switch (action) { + case "EDIT" -> BackupPopupController.popupItemEditBackupName(backup); + case "DELETE" -> BackupHelper.deleteBackup(backup); + case "DUPLICATE" -> BackupPopupController.popupItemDuplicateBackup(backup); + case "RENAME" -> BackupPopupController.popupItemRenameBackup(backups, backup); + case "OPEN_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); + case "OPEN_DEST" -> BackupPopupController.popupItemOpenDestinationPath(backup); + case "RUN_SINGLE" -> BackupPopupController.popupItemRunBackup(backup, backupTable, interruptBackupPopupItem, RunBackupPopupItem); + case "COPY_NAME" -> BackupPopupController.popupItemCopyBackupName(backup); + case "COPY_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); + case "COPY_DEST" -> BackupPopupController.popupItemCopyDestinationPath(backup); + } - return panelTable; + formRefresh(); + } + + private void handleToggle() { + int selectedRow = backupTable.getSelectedRow(); + if (selectedRow < 0) + return; + + ConfigurationBackup backup = getBackupFromTableRow(selectedRow); + BackupPopupController.popupItemAutoBackup(backup); + } + + private ConfigurationBackup getBackupFromTableRow(int row) { + row = backupTable.convertRowIndexToModel(row); + return backups.get(row); } private Component createDetails() { @@ -302,10 +443,9 @@ private Component createDetails() { new MigLayout("fill,insets 5 0 5 0", "[fill]", "[grow]") ); - txtDetails = new JTextArea(); + txtDetails = new JTextPane(); txtDetails.setEditable(false); - txtDetails.setLineWrap(true); - txtDetails.setWrapStyleWord(true); + txtDetails.setContentType("text/html"); JScrollPane detailScroll = new JScrollPane(txtDetails); detailScroll.putClientProperty(FlatClientProperties.STYLE, @@ -332,7 +472,9 @@ private Component createHeaderAction() { cmdCreate.putClientProperty(FlatClientProperties.STYLE, "background:$Component.accentColor;"); cmdDelete.putClientProperty(FlatClientProperties.STYLE, "background:$Component.error.background;"); - cmdCreate.addActionListener(e -> showModal()); + cmdCreate.addActionListener(e -> showCreateModal()); + cmdEdit.addActionListener(e -> showEditModal()); + cmdDelete.addActionListener(e -> showDeleteConfirmation()); panel.add(txtSearch); panel.add(cmdCreate); panel.add(cmdEdit); @@ -343,21 +485,26 @@ private Component createHeaderAction() { return panel; } - private void showModal() { - Option option = ModalDialog.createOption(); - option.getLayoutOption().setSize(-1, 1f) - .setLocation(Location.TRAILING, Location.TOP) - .setAnimateDistance(0.7f, 0); - ModalDialog.showModal(this, new SimpleModalBorder( - new SimpleInputForms(), "Create", SimpleModalBorder.YES_NO_OPTION, - (controller, action) -> { - }), option); + public void showCreateModal() { + managerController.showCreateModal(this); + } + + private void showEditModal() { + managerController.showCreateModal(this); + } + + private void showDeleteConfirmation() { + int selectedRow = backupTable.getSelectedRow(); + if (selectedRow < 0) + return; + + ConfigurationBackup backup = getBackupFromTableRow(selectedRow); + BackupHelper.deleteBackupWithConfirmition(backup); } - private CSVDataReader data; private final int limit = 50; private JPagination pagination; - private JTable basicTable; + private JTable backupTable; private JLabel lbTotalPage; - private JTextArea txtDetails; + private JTextPane txtDetails; } diff --git a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java index fdaa70eb..2a74bdd8 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java @@ -985,7 +985,7 @@ private void interruptBackupPopupItemActionPerformed(java.awt.event.ActionEvent }//GEN-LAST:event_interruptBackupPopupItemActionPerformed private void exportAsCsvBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportAsCsvBtnActionPerformed - ExportManager.exportAsCSV(new ArrayList<>(backups), ConfigurationBackup.getCSVHeader()); + ExportManager.exportAsCSV(backups, ConfigurationBackup.getCSVHeader()); }//GEN-LAST:event_exportAsCsvBtnActionPerformed private void exportAsPdfBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportAsPdfBtnActionPerformed diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index 7c562b57..4fe3d464 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -1,5 +1,6 @@ package backupmanager.gui.frames.Controllers; +import java.awt.Component; import java.awt.Dimension; import java.awt.Toolkit; import java.util.ArrayList; @@ -29,6 +30,11 @@ import backupmanager.gui.Table.TableDataManager; import backupmanager.gui.frames.BackupManagerGUI; import static backupmanager.gui.frames.BackupManagerGUI.backups; +import backupmanager.gui.simple.BackupEntry; +import raven.modal.ModalDialog; +import raven.modal.component.SimpleModalBorder; +import raven.modal.option.Location; +import raven.modal.option.Option; public class BackupManagerController { @@ -77,6 +83,23 @@ public void researchInTable(String research) { TableDataManager.updateTableWithNewBackupList(tempBackups, formatter); } + public void showCreateModal(Component parent) { + Option option = ModalDialog.createOption(); + option.getLayoutOption() + .setSize(-1, 1f) + .setLocation(Location.TRAILING, Location.TOP) + .setAnimateDistance(0.7f, 0); + + ModalDialog.showModal(parent, + new SimpleModalBorder( + new BackupEntry(), + "Create", + SimpleModalBorder.YES_NO_OPTION, + (controller, action) -> {} + ), + option); + } + public String getBackupDetails(String backupName) { return backupService.getBackupDetails(backupName); } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index e119bd94..10e065e4 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -14,6 +14,7 @@ import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenuItem; import javax.swing.JOptionPane; +import javax.swing.JTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,9 +27,9 @@ import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; +import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.gui.Table.BackupTable; import backupmanager.gui.Table.TableDataManager; -import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.frames.BackupProgressGUI; @@ -36,6 +37,7 @@ public class BackupPopupController { private static final Logger logger = LoggerFactory.getLogger(BackupPopupController.class); + @Deprecated public static void popupItemInterrupt(int selectedRow, BackupTable backupTable, List backups, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -45,6 +47,11 @@ public static void popupItemInterrupt(int selectedRow, BackupTable backupTable, } } + public static void popupItemInterrupt() { + + } + + @Deprecated public static void popupItemRenameBackup(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -52,6 +59,11 @@ public static void popupItemRenameBackup(int selectedRow, BackupTable backupTabl } } + public static void popupItemRenameBackup(List backups, ConfigurationBackup backup) { + renameBackup(backups, backup); + } + + @Deprecated public static void popupItemOpenDestinationPath(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -59,6 +71,11 @@ public static void popupItemOpenDestinationPath(int selectedRow, BackupTable bac } } + public static void popupItemOpenDestinationPath(ConfigurationBackup backup) { + openFolder(backup.getDestinationPath()); + } + + @Deprecated public static void popupItemOpenInitialPath(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -67,6 +84,11 @@ public static void popupItemOpenInitialPath(int selectedRow, BackupTable backupT } } + public static void popupItemOpenInitialPath(ConfigurationBackup backup) { + openFolder(backup.getTargetPath()); + } + + @Deprecated public static void popupItemAutoBackup(int selectedRow, BackupTable backupTable, List backups, JCheckBoxMenuItem autoBackupMenuItem) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -76,6 +98,11 @@ public static void popupItemAutoBackup(int selectedRow, BackupTable backupTable, } } + public static void popupItemAutoBackup(ConfigurationBackup backup) { + BackupHelper.toggleAutomaticBackup(backup); + } + + @Deprecated public static void popupItemCopyDestinationPath(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -85,6 +112,12 @@ public static void popupItemCopyDestinationPath(int selectedRow, BackupTable bac } } + public static void popupItemCopyDestinationPath(ConfigurationBackup backup) { + StringSelection selection = new StringSelection(backup.getDestinationPath()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); + } + + @Deprecated public static void popupItemCopyInitialPath(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -94,6 +127,12 @@ public static void popupItemCopyInitialPath(int selectedRow, BackupTable backupT } } + public static void popupItemCopyInitialPath(ConfigurationBackup backup) { + StringSelection selection = new StringSelection(backup.getTargetPath()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); + } + + @Deprecated public static void popupItemCopyBackupName(int selectedRow, BackupTable backupTable, List backups) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -103,6 +142,13 @@ public static void popupItemCopyBackupName(int selectedRow, BackupTable backupTa } } + public static void popupItemCopyBackupName(ConfigurationBackup backup) { + StringSelection selection = new StringSelection(backup.getName()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); + } + + + @Deprecated public static void popupItemRunBackup(int selectedRow, BackupTable backupTable, List backups, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { if (selectedRow != -1) { ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); @@ -114,6 +160,13 @@ public static void popupItemRunBackup(int selectedRow, BackupTable backupTable, } } + public static void popupItemRunBackup(ConfigurationBackup backup, JTable backupTable, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { + BackupManagerGUI.progressBar = new BackupProgressGUI(backup.getTargetPath(), backup.getDestinationPath()); + ZippingContext context = ZippingContext.create(backup, null, backupTable, BackupManagerGUI.progressBar, interruptBackupPopupItem, RunBackupPopupItem); + BackupOperations.singleBackup(context, BackupTriggerType.USER); + } + + @Deprecated public static void popupItemEditBackupName(int selectedRow, BackupTable backupTable, List backups, BackupManagerGUI main) { if (selectedRow != -1) { // get correct backup @@ -125,6 +178,11 @@ public static void popupItemEditBackupName(int selectedRow, BackupTable backupTa } } + public static void popupItemEditBackupName(ConfigurationBackup backup) { + // BackupHelper.openBackupById(backup.getId(), main); + } + + @Deprecated public static void popupItemDuplicateBackup(int selectedRow, BackupTable backupTable) { logger.info("Event --> duplicating backup"); @@ -156,15 +214,45 @@ public static void popupItemDuplicateBackup(int selectedRow, BackupTable backupT } } + public static void popupItemDuplicateBackup(ConfigurationBackup backup) { + logger.info("Event --> duplicating backup"); + + LocalDateTime dateNow = LocalDateTime.now(); + ConfigurationBackup newBackup = new ConfigurationBackup( + backup.getName() + "_copy", + backup.getTargetPath(), + backup.getDestinationPath(), + null, + backup.isAutomatic(), + backup.getNextBackupDate(), + backup.getTimeIntervalBackup(), + backup.getNotes(), + dateNow, + dateNow, + 0, + backup.getMaxToKeep() + ); + + BackupConfigurationRepository.insertBackup(newBackup); + + // List backups = BackupHelper.getBackupList(); + + // if (BackupManagerGUI.model != null) + // TableDataManager.updateTableWithNewBackupList(backups, BackupHelper.formatter); + } + + @Deprecated public static void popupItemDelete(int selectedRow, BackupTable backupTable) { BackupHelper.deleteBackup(selectedRow, backupTable); } + @Deprecated private static ConfigurationBackup getBackupByName(int selectedRow, BackupTable backupTable) { String backupName = (String) backupTable.getValueAt(selectedRow, 0); return BackupConfigurationRepository.getBackupByName(backupName); } + @Deprecated private static ConfigurationBackup getBackupByName(List backups, int selectedRow, BackupTable backupTable) { String backupName = (String) backupTable.getValueAt(selectedRow, 0); return ConfigurationBackup.getBackupByName(backups, backupName); diff --git a/src/main/java/backupmanager/gui/frames/Login.java b/src/main/java/backupmanager/gui/frames/Login.java index 86a9db04..d82ce814 100644 --- a/src/main/java/backupmanager/gui/frames/Login.java +++ b/src/main/java/backupmanager/gui/frames/Login.java @@ -101,7 +101,7 @@ private void createLogin() { } User user = new User(name, surname, email); - loginService.createNewUser(user); + loginService.createUserAndSendEmail(user); javax.swing.SwingUtilities.invokeLater(this::showMainForm); }); diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index a6d11395..5540819f 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -1,20 +1,34 @@ package backupmanager.gui.menu; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.UIManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.extras.FlatSVGIcon; +import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Entities.Confingurations; import backupmanager.Enums.ConfigKey; -import backupmanager.gui.forms.FormDashboard; +import backupmanager.Enums.MenuItems; +import backupmanager.Json.JSONConfigReader; +import backupmanager.Managers.ExportManager; +import backupmanager.Managers.WebsiteManager; +import backupmanager.database.Repositories.BackupConfigurationRepository; +import backupmanager.gui.forms.FormBackupDashboard; +import backupmanager.gui.forms.FormHistory; import backupmanager.gui.forms.FormSetting; import backupmanager.gui.forms.FormTable; import backupmanager.gui.system.AllForms; -import backupmanager.gui.system.Form; import backupmanager.gui.system.FormManager; import raven.extras.AvatarIcon; import raven.modal.drawer.DrawerPanel; @@ -32,7 +46,10 @@ import raven.modal.utils.FlatLafStyleUtils; public class MyDrawerBuilder extends SimpleDrawerBuilder { + private static final Logger logger = LoggerFactory.getLogger(MyDrawerBuilder.class); + private static final Map menuActionMap = new HashMap<>(); + private static final Map menuBindingMap = new HashMap<>(); private static MyDrawerBuilder instance; public static MyDrawerBuilder getInstance() { @@ -50,7 +67,7 @@ public void initHeader() { icon.setIcon(new FlatSVGIcon("drawer/logo.svg", 100, 100)); data.setTitle("Backup Manager"); - data.setDescription("assistenza@shardpc.it"); + data.setDescription(ConfigKey.EMAIL.getValue()); header.setSimpleHeaderData(data); rebuildMenu(); @@ -58,6 +75,7 @@ public void initHeader() { private MyDrawerBuilder() { super(createSimpleMenuOption()); + initMenuActions(); LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { // event for light dark mode changed @@ -80,8 +98,46 @@ public SimpleHeaderData getSimpleHeaderData() { return new SimpleHeaderData() .setIcon(icon) - .setTitle("Ra Ven") - .setDescription("raven@gmail.com"); + .setTitle("Backup Manager") + .setDescription(ConfigKey.EMAIL.getValue()); + } + + private static void initMenuActions() { + menuActionMap.put(MenuItems.BackupList, () -> + FormManager.showForm(AllForms.getForm(FormTable.class))); + + menuActionMap.put(MenuItems.Dashboard, () -> + FormManager.showForm(AllForms.getForm(FormBackupDashboard.class))); + + menuActionMap.put(MenuItems.Export, () -> + ExportManager.exportAsCSV(BackupConfigurationRepository.getBackupList(), ConfigurationBackup.getCSVHeader())); + + menuActionMap.put(MenuItems.Settings, () -> + FormManager.showForm(AllForms.getForm(FormSetting.class))); + + menuActionMap.put(MenuItems.History, () -> + FormManager.showForm(AllForms.getForm(FormHistory.class))); + + menuActionMap.put(MenuItems.InfoPage, () -> + WebsiteManager.openWebSite(ConfigKey.INFO_PAGE_LINK.getValue())); + + menuActionMap.put(MenuItems.BugReport, () -> + WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue())); + + menuActionMap.put(MenuItems.ContactUs, () -> + WebsiteManager.openWebSite(ConfigKey.EMAIL.getValue())); + + menuActionMap.put(MenuItems.PaypalDonate, () -> + WebsiteManager.openWebSite(ConfigKey.DONATE_PAYPAL_LINK.getValue())); + + menuActionMap.put(MenuItems.BuymeacoffeeDonate, () -> + WebsiteManager.openWebSite(ConfigKey.DONATE_BUYMEACOFFE_LINK.getValue())); + + menuActionMap.put(MenuItems.Subscription, () -> + FormManager.showSubscription()); + + menuActionMap.put(MenuItems.About, () -> + FormManager.showAbout()); } private void changeAvatarIconBorderColor(AvatarIcon icon) { @@ -103,28 +159,8 @@ public Option createOption() { } public static MenuOption createSimpleMenuOption() { - // create simple menu option MenuOption simpleMenuOption = new MenuOption(); - - MenuItem[] items = new MenuItem[]{ - new Item.Label("MAIN"), - new Item("Backup List", "forms.svg", FormTable.class) - .subMenu("Create new backup") - .subMenu("Import backups from Csv") - .subMenu("Export backups to Csv"), - new Item("Dashboard", "dashboard.svg", FormDashboard.class), - new Item.Label("OTHER"), - new Item("Setting", "setting.svg", FormSetting.class), - new Item("History", "history.svg"), - new Item("Information", "info.svg"), - new Item("Support the Project", "donate.svg") - .subMenu("Paypal") - .subMenu("Buy me a coffee"), - new Item("Help", "help.svg") - .subMenu("Report a bug") - .subMenu("Support"), - new Item("About", "about.svg"), - }; + MenuItem[] items = buildMenuItems().toArray(MenuItem[]::new); simpleMenuOption.setMenuStyle(new MenuStyle() { @@ -145,7 +181,8 @@ public void styleMenuItem(JButton menu, int[] index, boolean isMainItem) { @Override public void styleMenu(JComponent component) { - component.putClientProperty(FlatClientProperties.STYLE, getDrawerBackgroundStyle()); + component.putClientProperty(FlatClientProperties.STYLE, + getDrawerBackgroundStyle()); } }); @@ -153,20 +190,13 @@ public void styleMenu(JComponent component) { simpleMenuOption.setMenuValidation(new MyMenuValidation()); simpleMenuOption.addMenuEvent((action, index) -> { - System.out.println("Drawer menu selected " + Arrays.toString(index)); - Class itemClass = action.getItem().getItemClass(); - int i = index[0]; - if (i == 7) { - action.consume(); - FormManager.showAbout(); - return; - } - if (itemClass == null || !Form.class.isAssignableFrom(itemClass)) { + logger.debug("Drawer menu selected " + Arrays.toString(index)); + MenuItems menuItem = resolveMenuItem(action); + + if (menuItem != null && menuActionMap.containsKey(menuItem)) { + menuActionMap.get(menuItem).run(); action.consume(); - return; } - Class formClass = (Class) itemClass; - FormManager.showForm(AllForms.getForm(formClass)); }); simpleMenuOption.setMenus(items).setBaseIconPath("drawer/icon/"); @@ -174,6 +204,22 @@ public void styleMenu(JComponent component) { return simpleMenuOption; } + private static Item createMenuItem(String label, String icon, MenuItems menuEnum, Class formClass) { + Item item = new Item(label, icon, formClass); + menuBindingMap.put(label.replace(" ", "").toLowerCase(), menuEnum); + return item; + } + + private static MenuItems resolveMenuItem(raven.modal.drawer.menu.MenuAction action) { + String name = action.getItem().getName(); + + if (name == null) + return null; + + String key = name.replace(" ", "").toLowerCase(); + return menuBindingMap.get(key); + } + @Override public int getOpenDrawerAt() { return 1000; @@ -193,4 +239,88 @@ public void build(DrawerPanel drawerPanel) { private static String getDrawerBackgroundStyle() { return "background:$Menu.background;"; } + + private static List buildMenuItems() { + JSONConfigReader config = new JSONConfigReader(); + List itemList = new ArrayList<>(); + + itemList.add(new Item.Label("MAIN")); + + // Backup menu + Item backupItem = createMenuItem("Backup List", "forms.svg", MenuItems.BackupList, FormTable.class) + .subMenu("Create new backup"); + + if (config.isMenuItemEnabled(MenuItems.Import.name())) + backupItem.subMenu("Import backups from Csv"); + + if (config.isMenuItemEnabled(MenuItems.Export.name())) + backupItem.subMenu("Export backups to Csv"); + + itemList.add(backupItem); + + // Dashboard + itemList.add(createMenuItem("Dashboard", "dashboard.svg", MenuItems.Dashboard, FormBackupDashboard.class)); + + itemList.add(new Item.Label("OTHER")); + + // Settings + itemList.add(createMenuItem("Settings", "setting.svg", MenuItems.Settings, FormSetting.class)); + + // History (configurable) + if (config.isMenuItemEnabled(MenuItems.History.name())) { + itemList.add(createMenuItem("History", "history.svg", MenuItems.History, FormHistory.class)); + } + + // External link (no form class) + itemList.add(createMenuItem("Github page", "github.svg", MenuItems.InfoPage, null)); + + // Donate section + if (config.isMenuItemEnabled(MenuItems.Donate.name())) { + + Item donateItem = createMenuItem("Support the Project", "donate.svg", MenuItems.Donate, null); + + if (config.isMenuItemEnabled(MenuItems.PaypalDonate.name())) { + donateItem.subMenu("Paypal"); + bindSubMenu("Paypal", MenuItems.PaypalDonate); + } + + if (config.isMenuItemEnabled(MenuItems.BuymeacoffeeDonate.name())) { + donateItem.subMenu("Buy me a coffee"); + bindSubMenu("Buy me a coffee", MenuItems.BuymeacoffeeDonate); + } + + itemList.add(donateItem); + } + + // Support section + if (config.isMenuItemEnabled(MenuItems.Support.name())) { + + Item helpItem = createMenuItem("Help", "help.svg", MenuItems.Support, null); + + if (config.isMenuItemEnabled(MenuItems.BugReport.name())) { + helpItem.subMenu("Report a bug"); + bindSubMenu("Report a bug", MenuItems.BugReport); + } + + if (config.isMenuItemEnabled(MenuItems.ContactUs.name())) { + helpItem.subMenu("Contact us"); + bindSubMenu("Contact us", MenuItems.ContactUs); + } + + itemList.add(helpItem); + } + + if (Confingurations.isSubscriptionNedded()) { + itemList.add(createMenuItem("Subscription", "subscription.svg", MenuItems.Subscription, null)); + } + + // About (LAST ITEM) + itemList.add(createMenuItem("About", "about.svg", MenuItems.About, null)); + + return itemList; + } + + private static void bindSubMenu(String label, MenuItems menuEnum) { + menuBindingMap.put(label.replace(" ", "").toLowerCase(), menuEnum); + } } diff --git a/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java b/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java new file mode 100644 index 00000000..93bb94e2 --- /dev/null +++ b/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java @@ -0,0 +1,25 @@ +package backupmanager.gui.sample.csv; + +import java.util.ArrayList; +import java.util.List; + +import backupmanager.Entities.ConfigurationBackup; + +public class ConfigurationBackupDataTable extends ResponsePageable> { + public ConfigurationBackupDataTable(int total, int page, int pageSize, int limit, List data) { + super(total, page, pageSize, limit, data); + } + + public static ConfigurationBackupDataTable create(List rows, int page, int limit) { + int total = rows.size(); + int pageSize = (int) Math.ceil((double) total / limit); + + if (page > pageSize) { + page = pageSize; + } + + int start = Math.min((page - 1) * limit, total); + int end = Math.min(start + limit, total); + return new ConfigurationBackupDataTable(total, page, pageSize, limit, new ArrayList<>(rows.subList(start, end))); + } +} diff --git a/src/main/java/backupmanager/gui/simple/SimpleInputForms.java b/src/main/java/backupmanager/gui/simple/BackupEntry.java similarity index 96% rename from src/main/java/backupmanager/gui/simple/SimpleInputForms.java rename to src/main/java/backupmanager/gui/simple/BackupEntry.java index d853d570..7edb7ff7 100644 --- a/src/main/java/backupmanager/gui/simple/SimpleInputForms.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntry.java @@ -20,11 +20,11 @@ import raven.modal.component.ModalBorderAction; import raven.modal.component.SimpleModalBorder; -public class SimpleInputForms extends JPanel { +public class BackupEntry extends JPanel { public static int NEW_COUNTRY = 30; - public SimpleInputForms() { + public BackupEntry() { init(); } @@ -81,7 +81,7 @@ private void init() { @Override public void keyTyped(KeyEvent e) { if (e.isControlDown() && e.getKeyChar() == 10) { - ModalBorderAction modalBorderAction = ModalBorderAction.getModalBorderAction(SimpleInputForms.this); + ModalBorderAction modalBorderAction = ModalBorderAction.getModalBorderAction(BackupEntry.this); if (modalBorderAction != null) { modalBorderAction.doAction(SimpleModalBorder.YES_OPTION); } diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index c2779e59..75280818 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -7,7 +7,8 @@ import com.formdev.flatlaf.util.ColorFunctions; import backupmanager.gui.component.About; -import backupmanager.gui.forms.FormDashboard; +import backupmanager.gui.component.Subscription; +import backupmanager.gui.forms.FormTable; import backupmanager.gui.frames.Login; import backupmanager.utils.UndoRedo; import raven.modal.Drawer; @@ -80,7 +81,7 @@ public static void login() { frame.getContentPane().removeAll(); frame.getContentPane().add(getMainForm()); - Drawer.setSelectedItemClass(FormDashboard.class); + Drawer.setSelectedItemClass(FormTable.class); frame.repaint(); frame.revalidate(); } @@ -119,4 +120,10 @@ public static void showAbout() { ModalDialog.createOption().setAnimationEnabled(false) ); } + + public static void showSubscription() { + ModalDialog.showModal(frame, new SimpleModalBorder(new Subscription(), "Subscription"), + ModalDialog.createOption().setAnimationEnabled(false) + ); + } } diff --git a/src/main/java/backupmanager/gui/themes/ThemesManager.java b/src/main/java/backupmanager/gui/themes/ThemesManager.java index 04e64050..c9321c6f 100644 --- a/src/main/java/backupmanager/gui/themes/ThemesManager.java +++ b/src/main/java/backupmanager/gui/themes/ThemesManager.java @@ -34,7 +34,7 @@ void loadThemes() { // load themes.json Map json; - try (Reader reader = new InputStreamReader(getClass().getResourceAsStream("themes.json"), StandardCharsets.UTF_8)) { + try (Reader reader = new InputStreamReader(getClass().getResourceAsStream("/themes/themes.json"), StandardCharsets.UTF_8)) { json = (Map) Json.parse(reader); } catch (IOException e) { LoggingFacade.INSTANCE.logSevere(null, e); diff --git a/src/main/resources/drawer/icon/github.svg b/src/main/resources/drawer/icon/github.svg new file mode 100644 index 00000000..8c129995 --- /dev/null +++ b/src/main/resources/drawer/icon/github.svg @@ -0,0 +1,19 @@ + + + + + github [#142] + Created with Sketch. + + + + + + + + + + + + + diff --git a/src/main/resources/drawer/icon/subscription.svg b/src/main/resources/drawer/icon/subscription.svg new file mode 100644 index 00000000..2568017e --- /dev/null +++ b/src/main/resources/drawer/icon/subscription.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main/resources/icons/dashboard/database.svg b/src/main/resources/icons/dashboard/database.svg new file mode 100644 index 00000000..43459820 --- /dev/null +++ b/src/main/resources/icons/dashboard/database.svg @@ -0,0 +1,19 @@ + + + + + database_system [#1797] + Created with Sketch. + + + + + + + + + + + + + diff --git a/src/main/resources/icons/dashboard/duration.svg b/src/main/resources/icons/dashboard/duration.svg new file mode 100644 index 00000000..cae6d1ba --- /dev/null +++ b/src/main/resources/icons/dashboard/duration.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main/resources/icons/dashboard/rate.svg b/src/main/resources/icons/dashboard/rate.svg new file mode 100644 index 00000000..5edbb473 --- /dev/null +++ b/src/main/resources/icons/dashboard/rate.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/icons/dashboard/run.svg b/src/main/resources/icons/dashboard/run.svg new file mode 100644 index 00000000..88674750 --- /dev/null +++ b/src/main/resources/icons/dashboard/run.svg @@ -0,0 +1,14 @@ + + + + + Fill 1 + Created with Sketch. + + + + + + + + diff --git a/src/main/resources/icons/dashboard/usage.svg b/src/main/resources/icons/dashboard/usage.svg new file mode 100644 index 00000000..117888ec --- /dev/null +++ b/src/main/resources/icons/dashboard/usage.svg @@ -0,0 +1,2 @@ + + diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 7ea956ad..4cdf96a3 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -10,8 +10,8 @@ - - + + @@ -127,7 +127,7 @@ - + diff --git a/src/main/resources/res/config/config.json b/src/main/resources/res/config/config.json index 13fec9ac..815dd17a 100644 --- a/src/main/resources/res/config/config.json +++ b/src/main/resources/res/config/config.json @@ -19,7 +19,6 @@ "MenuItems": { "BugReport": true, "Preferences": true, - "Clear": false, "Donate": true, "PaypalDonate": true, "BuymeacoffeeDonate": true, @@ -27,13 +26,16 @@ "InfoPage": true, "New": true, "Quit": true, - "Save": false, "Import": false, - "Export": false, - "SaveWithName": false, + "Export": true, "Share": true, "Support": true, - "Website": true + "Website": true, + "ContactUs": true, + "BackupList": true, + "Dashboard": true, + "Settings": true, + "About": true }, "BackupService": { "value": 1, diff --git a/src/test/java/test/LoginServiceTest.java b/src/test/java/test/LoginServiceTest.java new file mode 100644 index 00000000..61566158 --- /dev/null +++ b/src/test/java/test/LoginServiceTest.java @@ -0,0 +1,42 @@ +package test; + +import java.io.IOException; + +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import backupmanager.Entities.User; +import backupmanager.Services.LoginService; +import backupmanager.database.Database; +import backupmanager.database.DatabasePaths; +import backupmanager.database.TestDatabaseInitializer; + +public class LoginServiceTest { + @BeforeEach + protected void setup() throws Exception { + Database.init(DatabasePaths.getTestDatabasePath()); + TestDatabaseInitializer.init(); + } + + @AfterEach + protected void clean() throws IOException { + TestDatabaseInitializer.deleteDatabase(); + } + + @Test + protected void isFirstAccess_shouldBeTrue_whenNoUserExists() { + LoginService loginService = new LoginService(); + boolean isFirstAccess = loginService.isFirstAccess(); + assertTrue(isFirstAccess); + } + + @Test + protected void createNewUser_shouldBeAbleToCreateUser() { + LoginService loginService = new LoginService(); + loginService.createNewUser(new User("TestName", "TestSurname", "test@gmail.com")); + boolean isFirstAccess = loginService.isFirstAccess(); + assertTrue(!isFirstAccess); + } +} diff --git a/src/test/java/test/repositories/BackupRequestRepositoryTest.java b/src/test/java/test/repositories/BackupRequestRepositoryTest.java index d6ae4ec6..5dfaa363 100644 --- a/src/test/java/test/repositories/BackupRequestRepositoryTest.java +++ b/src/test/java/test/repositories/BackupRequestRepositoryTest.java @@ -88,7 +88,7 @@ private void setupBackupRequestList() { BackupRequestRepository.insertBackupRequest(request); } - requests = BackupRequestRepository.getRequestBackups(); + } private void createBackupConfigurations() { From 7f50ebb4fe256e097c7c79d5b9021a385016b84a Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Fri, 6 Mar 2026 15:27:36 +0100 Subject: [PATCH 05/17] translations update --- .vscode/launch.json | 4 +- BackupManager_convert_to_exe_launch4j.xml | 6 +- BackupManager_installer_inno_setup.iss | 4 +- .../java/backupmanager/BackupOperations.java | 38 +- .../java/backupmanager/Email/EmailSender.java | 12 +- .../Entities/ConfigurationBackup.java | 149 ++---- ...nfingurations.java => Configurations.java} | 12 +- .../Enums/TranslationLoaderEnum.java | 343 -------------- .../backupmanager/Enums/Translations.java | 389 ++++++++++++++++ .../backupmanager/Helpers/BackupHelper.java | 22 +- .../java/backupmanager/Helpers/SqlHelper.java | 7 + .../Helpers/SubscriptionHelper.java | 19 +- .../Helpers/SubscriptionNotifier.java | 12 +- ...{JSONConfigReader.java => JsonConfig.java} | 32 +- src/main/java/backupmanager/MainApp.java | 8 +- .../Managers/ExceptionManager.java | 12 +- .../backupmanager/Managers/ExportManager.java | 24 +- .../backupmanager/Managers/ThemeManager.java | 6 +- .../Managers/WebsiteManager.java | 12 +- .../Services/BackgroundService.java | 7 +- .../backupmanager/Services/BackupService.java | 39 +- .../backupmanager/Services/LoginService.java | 14 +- .../Services/PreferenceService.java | 10 +- .../Utils/table/ProgressBarRenderer.java | 1 - .../backupmanager/database/DatabasePaths.java | 1 - .../Repositories/BackupRequestRepository.java | 2 - .../Repositories/SubscriptionRepository.java | 28 ++ .../gui/Controllers/AppController.java | 3 - .../Controllers/BackupEntryController.java | 17 +- .../gui/Controllers/EntryUserController.java | 8 +- .../gui/Controllers/TimePickerController.java | 26 +- .../gui/Controllers/TrayController.java | 9 +- .../gui/Dialogs/BackupEntryDialog.java | 61 ++- .../gui/Dialogs/EntryUserDialog.java | 12 +- .../gui/Dialogs/PreferencesDialog.java | 20 +- .../backupmanager/gui/Dialogs/TimePicker.java | 24 +- .../backupmanager/gui/component/About.java | 1 + .../gui/component/Subscription.java | 23 +- .../backupmanager/gui/forms/CustomForm.java | 61 +++ .../gui/forms/FormBackupDashboard.java | 26 +- .../{FormTable.java => FormBackupTable.java} | 183 +++++--- .../backupmanager/gui/forms/FormHistory.java | 75 +--- .../Login.java => forms/FormLogin.java} | 78 +++- .../backupmanager/gui/forms/FormSetting.java | 49 +- .../gui/frames/BackupManagerGUI.java | 112 +++-- .../gui/frames/BackupProgressGUI.java | 16 +- .../Controllers/BackupManagerController.java | 69 ++- .../Controllers/BackupMenuController.java | 8 +- .../Controllers/BackupPopupController.java | 12 +- .../Controllers/BackupProgressController.java | 6 +- .../gui/menu/MyDrawerBuilder.java | 58 +-- .../backupmanager/gui/sample/SampleData.java | 424 ------------------ .../gui/sample/csv/CSVDataReader.java | 2 +- .../backupmanager/gui/simple/BackupEntry.java | 116 ----- .../gui/simple/BackupEntryDialog.java | 338 ++++++++++++++ .../gui/simple/CustomDialog.java | 58 +++ .../gui/simple/TimePickerDialog.java | 104 +++++ .../backupmanager/gui/system/FormManager.java | 18 +- .../backupmanager/gui/system/FormSearch.java | 23 +- .../gui/themes/ListCellTitledBorder.java | 6 +- .../backupmanager/gui/themes/PanelThemes.java | 2 +- src/main/resources/drawer/logo.png | Bin 9601 -> 55372 bytes src/main/resources/drawer/logo.svg | 53 +-- src/main/resources/res/config/config.json | 2 +- src/main/resources/res/img/logo_old.ico | Bin 174328 -> 0 bytes src/main/resources/res/img/logo_old.png | Bin 6277 -> 0 bytes src/test/java/test/BackupManagerTest.java | 5 - src/test/java/test/BackupTest.java | 3 - src/test/java/test/SqlHelperTest.java | 3 +- .../java/test/SubscriptionHelperTest.java | 98 ++++ .../ConfigurationsRepositoryTest.java | 16 +- 71 files changed, 1852 insertions(+), 1589 deletions(-) rename src/main/java/backupmanager/Entities/{Confingurations.java => Configurations.java} (94%) delete mode 100644 src/main/java/backupmanager/Enums/TranslationLoaderEnum.java create mode 100644 src/main/java/backupmanager/Enums/Translations.java rename src/main/java/backupmanager/Json/{JSONConfigReader.java => JsonConfig.java} (76%) create mode 100644 src/main/java/backupmanager/gui/forms/CustomForm.java rename src/main/java/backupmanager/gui/forms/{FormTable.java => FormBackupTable.java} (75%) rename src/main/java/backupmanager/gui/{frames/Login.java => forms/FormLogin.java} (60%) delete mode 100644 src/main/java/backupmanager/gui/sample/SampleData.java delete mode 100644 src/main/java/backupmanager/gui/simple/BackupEntry.java create mode 100644 src/main/java/backupmanager/gui/simple/BackupEntryDialog.java create mode 100644 src/main/java/backupmanager/gui/simple/CustomDialog.java create mode 100644 src/main/java/backupmanager/gui/simple/TimePickerDialog.java delete mode 100644 src/main/resources/res/img/logo_old.ico delete mode 100644 src/main/resources/res/img/logo_old.png delete mode 100644 src/test/java/test/BackupManagerTest.java delete mode 100644 src/test/java/test/BackupTest.java create mode 100644 src/test/java/test/SubscriptionHelperTest.java diff --git a/.vscode/launch.json b/.vscode/launch.json index 2c9318ec..cd31bd86 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -61,9 +61,9 @@ }, { "type": "java", - "name": "TranslationLoaderEnum", + "name": "Translations", "request": "launch", - "mainClass": "backupmanager.Enums.TranslationLoaderEnum", + "mainClass": "backupmanager.Enums.Translations", "projectName": "BackupManager" }, { diff --git a/BackupManager_convert_to_exe_launch4j.xml b/BackupManager_convert_to_exe_launch4j.xml index c1068722..21610e81 100644 --- a/BackupManager_convert_to_exe_launch4j.xml +++ b/BackupManager_convert_to_exe_launch4j.xml @@ -25,11 +25,11 @@ - 2.2.1.0 + 3.0.0.0 2.0.RC1 Backup management and automation utility Copyright © 2024 Shard - 2.2.1.0 + 3.0.0.0 2.0.RC1 Backup Manager Shard @@ -38,4 +38,4 @@ ENGLISH_US - \ No newline at end of file + diff --git a/BackupManager_installer_inno_setup.iss b/BackupManager_installer_inno_setup.iss index 3c2e8b6f..c5d24a4b 100644 --- a/BackupManager_installer_inno_setup.iss +++ b/BackupManager_installer_inno_setup.iss @@ -4,14 +4,14 @@ [Setup] AppName=BackupManager -AppVersion=2.2.1 +AppVersion=3.0.0 AppPublisher=Shard AppPublisherURL=https://www.shardpc.it/ DefaultDirName={userdocs}\Shard\BackupManager DisableDirPage=yes DisableProgramGroupPage=no PrivilegesRequired=lowest -OutputBaseFilename=BackupManager_v2.2.1_Setup +OutputBaseFilename=BackupManager_v3.0.0_Setup SetupIconFile=src\main\resources\res\img\logo.ico SetupLogging=yes Compression=lzma diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 0a1b3269..5e161bfe 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -23,16 +23,16 @@ import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.ErrorType; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Managers.ExceptionManager; import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; -import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.Table.TableDataManager; import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.utils.FolderUtils; @@ -126,7 +126,7 @@ private static void updateAfterBackup(String path1, String path2, ZippingContext logger.info("Backup :\"" + context.backup().getName() + "\" updated after the backup"); if (context.trayIcon() != null) - context.trayIcon().displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + context.backup().getName() + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.SUCCESS_MESSAGE) + "\n" + TranslationCategory.GENERAL.getTranslation(TranslationKey.FROM) + ": " + path1 + "\n" + TranslationCategory.GENERAL.getTranslation(TranslationKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); + context.trayIcon().displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + context.backup().getName() + TCategory.TRAY_ICON.getTranslation(TKey.SUCCESS_MESSAGE) + "\n" + TCategory.GENERAL.getTranslation(TKey.FROM) + ": " + path1 + "\n" + TCategory.GENERAL.getTranslation(TKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); } catch (IllegalArgumentException ex) { logger.error("An error occurred: " + ex.getMessage(), ex); ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); @@ -134,7 +134,7 @@ private static void updateAfterBackup(String path1, String path2, ZippingContext } public static String pathSearchWithFileChooser(boolean allowFiles) { - logger.info("Event --> File chooser"); + logger.debug("File chooser, " + " files allowed: " + false); JFileChooser jfc = new JFileChooser(FileSystemView.getFileSystemView().getHomeDirectory()); @@ -330,51 +330,51 @@ public static void setError(ErrorType error, TrayIcon trayIcon, String backupNam case InputMissing -> { logger.warn("Input Missing!"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_INPUT_MISSING), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_INPUT_MISSING), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_INPUT_MISSING_GENERIC), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INPUT_MISSING_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case InputError -> { logger.warn("Input Error! One or both paths do not exist."); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_FILES_NOT_EXISTING), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_FILES_NOT_EXISTING), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_PATH_NOT_EXISTING), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_PATH_NOT_EXISTING), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case SamePaths -> { logger.warn("The initial path and destination path cannot be the same. Please choose different paths"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_SAME_PATHS), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_SAME_PATHS), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_SAME_PATHS_GENERIC), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_SAME_PATHS_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ErrorCountingFiles -> { logger.warn("Error during counting files in directory"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_COUNTING_FILES), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_COUNTING_FILES), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_COUNTING_FILES), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_COUNTING_FILES), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingGenericError -> { logger.warn("Error during zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_GENERIC), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_GENERIC), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingIOError -> { logger.warn("I/O error occurred while zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_IO), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_IO), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_IO), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_IO), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingSecurityError -> { logger.warn("Security exception while zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TranslationCategory.GENERAL.getTranslation(TranslationKey.APP_NAME), TranslationCategory.GENERAL.getTranslation(TranslationKey.BACKUP) + ": " + backupName + TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_SECURITY), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_ZIPPING_SECURITY), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } default -> throw new IllegalArgumentException("Error type not recognized: " + error); } diff --git a/src/main/java/backupmanager/Email/EmailSender.java b/src/main/java/backupmanager/Email/EmailSender.java index 95baf20f..3851ee5d 100644 --- a/src/main/java/backupmanager/Email/EmailSender.java +++ b/src/main/java/backupmanager/Email/EmailSender.java @@ -16,9 +16,9 @@ import backupmanager.Entities.User; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.EmailType; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.EmailRepository; import backupmanager.database.Repositories.UserRepository; import ch.qos.logback.classic.LoggerContext; @@ -29,7 +29,7 @@ */ public class EmailSender { - private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private static final JsonConfig configReader = JsonConfig.getInstance(); private static final Logger logger = LoggerFactory.getLogger(EmailSender.class); @@ -113,8 +113,8 @@ public static void sendUserCreationEmail(User user) { public static void sendConfirmEmailToUser(User user) { if (user == null) throw new IllegalArgumentException("User object cannot be null"); - String subject = TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.EMAIL_CONFIRMATION_SUBJECT); - String body = TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.EMAIL_CONFIRMATION_BODY); + String subject = TCategory.USER_DIALOG.getTranslation(TKey.EMAIL_CONFIRMATION_SUBJECT); + String body = TCategory.USER_DIALOG.getTranslation(TKey.EMAIL_CONFIRMATION_BODY); body = body.replace("[UserName]", user.getUserCompleteName()); body = body.replace("[SupportEmail]", ConfigKey.EMAIL.getValue()); diff --git a/src/main/java/backupmanager/Entities/ConfigurationBackup.java b/src/main/java/backupmanager/Entities/ConfigurationBackup.java index dd9d397c..1c2e6827 100644 --- a/src/main/java/backupmanager/Entities/ConfigurationBackup.java +++ b/src/main/java/backupmanager/Entities/ConfigurationBackup.java @@ -3,12 +3,12 @@ import java.time.LocalDateTime; import java.util.List; -import backupmanager.Enums.ConfigKey; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.BackupConfigurationRepository; public class ConfigurationBackup { - private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); private int id; private String name; @@ -37,7 +37,7 @@ public ConfigurationBackup() { creationDate = LocalDateTime.now(); lastUpdateDate = LocalDateTime.now(); count = 0; - maxToKeep = configReader.getConfigValue("MaxCountForSameBackup", 1); + maxToKeep = JsonConfig.getInstance().getConfigValue("MaxCountForSameBackup", 1); } public ConfigurationBackup(String name, String targetPath, String destinationPath, LocalDateTime lastBackupDate, Boolean automatic, LocalDateTime nextBackupDate, TimeInterval timeIntervalBackup, String notes, LocalDateTime creationDate, LocalDateTime lastUpdateDate, int count, int maxToKeep) { @@ -166,114 +166,41 @@ public static String getCSVHeader() { public static String[] getCSVHeaderArray() { return new String[] { - "BackupName", - "targetPath", - "DestinationPath", - "lastBackupDate", - "IsAutoBackup", - "NextDate", - "Interval (gg.HH:mm)", - "MaxBackupsToKeep" + Translations.get(TKey.BACKUP_NAME_COLUMN), + Translations.get(TKey.INITIAL_PATH_COLUMN), + Translations.get(TKey.DESTINATION_PATH_COLUMN), + Translations.get(TKey.LAST_BACKUP_COLUMN), + Translations.get(TKey.AUTOMATIC_BACKUP_COLUMN), + Translations.get(TKey.NEXT_BACKUP_DATE_COLUMN), + Translations.get(TKey.TIME_INTERVAL_COLUMN), + Translations.get(TKey.MAX_BACKUPS_COLUMN) }; } - public int getId() { - return id; - } - - public String getName() { - return name; - } - - public String getTargetPath() { - return targetPath; - } - - public String getDestinationPath() { - return destinationPath; - } - - public LocalDateTime getLastBackupDate() { - return lastBackupDate; - } - - public boolean isAutomatic() { - return automatic; - } - - public LocalDateTime getNextBackupDate() { - return nextBackupDate; - } - - public TimeInterval getTimeIntervalBackup() { - return timeIntervalBackup; - } - - public String getNotes() { - return notes; - } - - public LocalDateTime getCreationDate() { - return creationDate; - } - - public LocalDateTime getLastUpdateDate() { - return lastUpdateDate; - } - - public int getCount() { - return count; - } - - public int getMaxToKeep() { - return maxToKeep; - } - - public void setName(String name) { - this.name = name; - } - - public void setNotes(String notes) { - this.notes = notes; - } - - public void setAutomatic(boolean automatic) { - this.automatic = automatic; - } - - public void setCreationDate(LocalDateTime creationDate) { - this.creationDate = creationDate; - } - - public void setDestinationPath(String destinationPath) { - this.destinationPath = destinationPath; - } - - public void setLastBackupDate(LocalDateTime lastBackupDate) { - this.lastBackupDate = lastBackupDate; - } - - public void setLastUpdateDate(LocalDateTime lastUpdateDate) { - this.lastUpdateDate = lastUpdateDate; - } - - public void setNextBackupDate(LocalDateTime nextBackupDate) { - this.nextBackupDate = nextBackupDate; - } - - public void setTargetPath(String targetPath) { - this.targetPath = targetPath; - } - - public void setTimeIntervalBackup(TimeInterval timeIntervalBackup) { - this.timeIntervalBackup = timeIntervalBackup; - } - - public void setMaxToKeep(int maxToKeep) { - this.maxToKeep = maxToKeep; - } - - public void setCount(int count) { - this.count = count; - } + public int getId() { return id; } + public String getName() { return name; } + public String getTargetPath() { return targetPath; } + public String getDestinationPath() { return destinationPath; } + public LocalDateTime getLastBackupDate() { return lastBackupDate; } + public boolean isAutomatic() { return automatic; } + public LocalDateTime getNextBackupDate() { return nextBackupDate; } + public TimeInterval getTimeIntervalBackup() { return timeIntervalBackup; } + public String getNotes() { return notes; } + public LocalDateTime getCreationDate() { return creationDate; } + public LocalDateTime getLastUpdateDate() { return lastUpdateDate; } + public int getCount() { return count; } + public int getMaxToKeep() { return maxToKeep; } + + public void setName(String name) { this.name = name; } + public void setNotes(String notes) { this.notes = notes; } + public void setAutomatic(boolean automatic) { this.automatic = automatic; } + public void setCreationDate(LocalDateTime creationDate) { this.creationDate = creationDate; } + public void setDestinationPath(String destinationPath) { this.destinationPath = destinationPath; } + public void setLastBackupDate(LocalDateTime lastBackupDate) { this.lastBackupDate = lastBackupDate; } + public void setLastUpdateDate(LocalDateTime lastUpdateDate) { this.lastUpdateDate = lastUpdateDate; } + public void setNextBackupDate(LocalDateTime nextBackupDate) { this.nextBackupDate = nextBackupDate; } + public void setTargetPath(String targetPath) { this.targetPath = targetPath; } + public void setTimeIntervalBackup(TimeInterval timeIntervalBackup) { this.timeIntervalBackup = timeIntervalBackup; } + public void setMaxToKeep(int maxToKeep) { this.maxToKeep = maxToKeep; } + public void setCount(int count) { this.count = count; } } diff --git a/src/main/java/backupmanager/Entities/Confingurations.java b/src/main/java/backupmanager/Entities/Configurations.java similarity index 94% rename from src/main/java/backupmanager/Entities/Confingurations.java rename to src/main/java/backupmanager/Entities/Configurations.java index b2c6795c..756c37e7 100644 --- a/src/main/java/backupmanager/Entities/Confingurations.java +++ b/src/main/java/backupmanager/Entities/Configurations.java @@ -10,8 +10,8 @@ import backupmanager.Managers.ExceptionManager; import backupmanager.database.Repositories.ConfigurationRepository; -public class Confingurations { - private static final Logger logger = LoggerFactory.getLogger(Confingurations.class); +public class Configurations { + private static final Logger logger = LoggerFactory.getLogger(Configurations.class); private static LanguagesEnum language; private static ThemesEnum theme; private static boolean subscriptionNedded; // if true the subscription is needed to use the backuground service @@ -29,7 +29,7 @@ public static void updateAllConfigurations() { } public static void setLanguage(LanguagesEnum language) { - Confingurations.language = language; + Configurations.language = language; } public static void setLanguageByLanguageName(String selectedLanguage) { @@ -65,7 +65,7 @@ private static void setLanguageByFileName(String filename) { } public static void setTheme(ThemesEnum theme) { - Confingurations.theme = theme; + Configurations.theme = theme; } public static void setTheme(String selectedTheme) { try { @@ -83,6 +83,10 @@ public static void setTheme(String selectedTheme) { } } + public static void setSubscriptionNedded(boolean isNedded) { + subscriptionNedded = isNedded; + } + private static void setSubscriptionNedded(String subscriptionValue) { subscriptionValue = subscriptionValue.trim().toLowerCase(); subscriptionNedded = subscriptionValue.equals("true") || subscriptionValue.equals("1"); diff --git a/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java b/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java deleted file mode 100644 index c8cc4924..00000000 --- a/src/main/java/backupmanager/Enums/TranslationLoaderEnum.java +++ /dev/null @@ -1,343 +0,0 @@ -package backupmanager.Enums; - -import java.io.FileReader; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -public class TranslationLoaderEnum { - - private static final Logger logger = LoggerFactory.getLogger(TranslationLoaderEnum.class); - - public enum TranslationCategory { - GENERAL("General"), - MENU("Menu"), - TABBED_FRAMES("TabbedFrames"), - BACKUP_ENTRY("BackupEntry"), - BACKUP_LIST("BackupList"), - TIME_PICKER_DIALOG("TimePickerDialog"), - PREFERENCES_DIALOG("PreferencesDialog"), - USER_DIALOG("UserDialog"), - PROGRESS_BACKUP_FRAME("ProgressBackupFrame"), - TRAY_ICON("TrayIcon"), - DIALOGS("Dialogs"), - SUBSCRIPTION("Subscription"); - - private final String categoryName; - private final Map translations = new HashMap<>(); - - TranslationCategory(String categoryName) { - this.categoryName = categoryName; - } - - public void addTranslation(TranslationKey key, String value) { - translations.put(key, value); - } - - // Updated getTranslation method - public String getTranslation(TranslationKey key) { - return translations.getOrDefault(key, key.getDefaultValue()); - } - - public String getCategoryName() { - return categoryName; - } - } - - public enum TranslationKey { - // General - APP_NAME("AppName", "Backup Manager"), - VERSION("Version", "Version"), - BACKUP("Backup", "Backup"), - FROM("From", "From"), - TO("To", "To"), - CLOSE_BUTTON("CloseButton", "Close"), - OK_BUTTON("OkButton", "Ok"), - CANCEL_BUTTON("CancelButton", "Cancel"), - APPLY_BUTTON("ApplyButton", "Apply"), - SAVE_BUTTON("SaveButton", "Save"), - CREATE_BUTTON("CreateButton", "Create"), - - // Menu - FILE("File", "File"), - OPTIONS("Options", "Options"), - ABOUT("About", "About"), - HELP("Help", "Help"), - BUG_REPORT("BugReport", "Report a bug"), - CLEAR("Clear", "Clear"), - DONATE("Donate", "Donate"), - HISTORY("History", "History"), - INFO_PAGE("InfoPage", "Info"), - NEW("New", "New"), - QUIT("Quit", "Quit"), - SAVE("Save", "Save"), - PREFERENCES("Preferences", "Preferences"), - IMPORT("Import", "Import"), - EXPORT("Export", "Export"), - SAVE_WITH_NAME("SaveWithName", "Save with name"), - SHARE("Share", "Share"), - SUPPORT("Support", "Support"), - WEBSITE("Website", "Website"), - - // TabbedFrames - BACKUP_ENTRY("BackupEntry", "Backup Entry"), - BACKUP_LIST("BackupList", "Backup List"), - - // BackupEntry - PAGE_TITLE("PageTitle", "Backup Entry"), - CURRENT_FILE("CurrentFile", "Current file"), - NOTES("Notes", "Notes"), - LAST_BACKUP("LastBackup", "Last backup"), - SINGLE_BACKUP_BUTTON("SingleBackupButton", "Single Backup"), - AUTO_BACKUP_BUTTON("AutoBackupButton", "Auto Backup"), - AUTO_BACKUP_BUTTON_ON("AutoBackupButtonON", "Auto Backup (ON)"), - AUTO_BACKUP_BUTTON_OFF("AutoBackupButtonOFF", "Auto Backup (OFF)"), - INITIAL_PATH_PLACEHOLDER("InitialPathPlaceholder", "Initial path"), - DESTINATION_PATH_PLACEHOLDER("DestinationPathPlaceholder", "Destination path"), - BACKUP_NAME("BackupName", "Backup name"), - BACKUP_NAME_TOOLTIP("BackupNameTooltip", "(Required) Backup name"), - INITIAL_PATH_TOOLTIP("InitialPathTooltip", "(Required) Initial path"), - DESTINATION_PATH_TOOLTIP("DestinationPathTooltip", "(Required) Destination path"), - INITIAL_FILE_CHOOSER_TOOLTIP("InitialFileChooserTooltip", "Open file explorer"), - DESTINATION_FILE_CHOOSER_TOOLTIP("DestinationFileChooserTooltip", "Open file explorer"), - NOTES_TOOLTIP("NotesTooltip", "(Optional) Backup description"), - SINGLE_BACKUP_TOOLTIP("SingleBackupTooltip", "Perform the backup"), - AUTO_BACKUP_TOOLTIP("AutoBackupTooltip", "Enable/Disable automatic backup"), - TIME_PICKER_TOOLTIP("TimePickerTooltip", "Time picker"), - MAX_BACKUPS_TO_KEEP("MaxBackupsToKeep", "Max backups to keep"), - MAX_BACKUPS_TO_KEEP_TOOLTIP("MaxBackupsToKeepTooltip", "Maximum number of backups before removing the oldest."), - - // BackupList - BACKUP_NAME_COLUMN("BackupNameColumn", "Backup Name"), - INITIAL_PATH_COLUMN("InitialPathColumn", "Initial Path"), - DESTINATION_PATH_COLUMN("DestinationPathColumn", "Destination Path"), - LAST_BACKUP_COLUMN("LastBackupColumn", "Last Backup"), - AUTOMATIC_BACKUP_COLUMN("AutomaticBackupColumn", "Automatic Backup"), - NEXT_BACKUP_DATE_COLUMN("NextBackupDateColumn", "Next Backup Date"), - TIME_INTERVAL_COLUMN("TimeIntervalColumn", "Time Interval"), - BACKUP_NAME_DETAIL("BackupNameDetail", "BackupName"), - INITIAL_PATH_DETAIL("InitialPathDetail", "InitialPath"), - DESTINATION_PATH_DETAIL("DestinationPathDetail", "DestinationPath"), - LAST_BACKUP_DETAIL("LastBackupDetail", "LastBackup"), - NEXT_BACKUP_DATE_DETAIL("NextBackupDateDetail", "NextBackup"), - TIME_INTERVAL_DETAIL("TimeIntervalDetail", "TimeInterval"), - CREATION_DATE_DETAIL("CreationDateDetail", "CreationDate"), - LAST_UPDATE_DATE_DETAIL("LastUpdateDateDetail", "LastUpdateDate"), - BACKUP_COUNT_DETAIL("BackupCountDetail", "BackupCount"), - NOTES_DETAIL("NotesDetail", "Notes"), - MAX_BACKUPS_TO_KEEP_DETAIL("MaxBackupsToKeepDetail", "MaxBackupsToKeep"), - ADD_BACKUP_TOOLTIP("AddBackupTooltip", "Add new backup"), - EXPORT_AS("ExportAs", "Export as: "), - EXPORT_AS_PDF_TOOLTIP("ExportAsPdfTooltip", "Export as PDF"), - EXPORT_AS_CSV_TOOLTIP("ExportAsCsvTooltip", "Export as CSV"), - RESEARCH_BAR_TOOLTIP("ResearchBarTooltip", "Research bar"), - RESEARCH_BAR_PLACEHOLDER("ResearchBarPlaceholder", "Search..."), - EDIT_POPUP("EditPopup", "Edit"), - DELETE_POPUP("DeletePopup", "Delete"), - INTERRUPT_POPUP("InterruptPopup", "Interrupt"), - DUPLICATE_POPUP("DuplicatePopup", "Duplicate"), - RENAME_BACKUP_POPUP("RenameBackupPopup", "Rename backup"), - OPEN_INITIAL_FOLDER_POPUP("OpenInitialFolderPopup", "Open initial path"), - OPEN_DESTINATION_FOLDER_POPUP("OpenDestinationFolderPopup", "Open destination path"), - BACKUP_POPUP("BackupPopup", "Backup"), - SINGLE_BACKUP_POPUP("SingleBackupPopup", "Run single backup"), - AUTO_BACKUP_POPUP("AutoBackupPopup", "Auto backup"), - COPY_TEXT_POPUP("CopyTextPopup", "Copy text"), - COPY_BACKUP_NAME_POPUP("CopyBackupNamePopup", "Copy backup name"), - COPY_INITIAL_PATH_POPUP("CopyInitialPathPopup", "Copy initial path"), - COPY_DESTINATION_PATH_BACKUP("CopyDestinationPathPopup", "Copy destination path"), - - // TimePickerDialog - TIME_INTERVAL_TITLE("TimeIntervalTitle", "Time interval for auto backup"), - DESCRIPTION("Description", "Select how often to perform the automatic backup by choosing the frequency in days, hours, and minutes."), - DAYS("Days", "Days"), - HOURS("Hours", "Hours"), - MINUTES("Minutes", "Minutes"), - SPINNER_TOOLTIP("SpinnerTooltip", "Mouse wheel to adjust the value"), - - // PreferencesDialog - PREFERENCES_TITLE("PreferencesTitle", "Preferences"), - LANGUAGE("Language", "Language"), - THEME("Theme", "Theme"), - - // User dialog - USER_TITLE("UserTitle", "Insert your data"), - USER_NAME("Name", "Name"), - USER_SURNAME("Surname", "Surname"), - USER_EMAIL("Email", "Email"), - ERROR_MESSAGE_FOR_MISSING_DATA("ErrorMessageForMissingData", "Please fill in all the required fields."), - ERROR_MESSAGE_FOR_WRONG_EMAIL("ErrorMessageForWrongEmail", "The provided email address is invalid. Please provide a correct one."), - EMAIL_CONFIRMATION_SUBJECT("EmailConfirmationSubject", "Thank you for choosing Backup Manager!"), - EMAIL_CONFIRMATION_BODY("EmailConfirmationBody", "Hi [UserName],\n\nThank you for downloading and registering **Backup Manager**, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at **[SupportEmail]**.\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team"), - - // ProgressBackupFrame - PROGRESS_BACKUP_TITLE("ProgressBackupTitle", "Backup in progress"), - STATUS_COMPLETED("StatusCompleted", "Backup completed!"), - STATUS_LOADING("StatusLoading", "Loading..."), - - // TrayIcon - TRAY_TOOLTIP("TrayTooltip", "Backup Service"), - OPEN_ACTION("OpenAction", "Quick Access"), - EXIT_ACTION("ExitAction", "Exit"), - SUCCESS_MESSAGE("SuccessMessage", "\nThe backup was successfully completed:"), - ERROR_MESSAGE_INPUT_MISSING("ErrorMessageInputMissing", "\nError during automatic backup.\nInput Missing!"), - ERROR_MESSAGE_FILES_NOT_EXISTING("ErrorMessageFilesNotExisting", "\nError during automatic backup.\nOne or both paths do not exist!"), - ERROR_MESSAGE_SAME_PATHS("ErrorMessageSamePaths", "\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!"), - - // Dialogs - ERROR_GENERIC_TITLE("ErrorGenericTitle", "Error"), - WARNING_GENERIC_TITLE("WarningGenericTitle", "Warning"), - WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE("WarningBackupAlreadyInProgressMessage", "There is already a backup in progress. It is not possible to perform parallel backups"), - WARNING_SHORT_TIME_INTERVAL_MESSAGE("WarningShortTimeIntervalMessage", "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?"), - - ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING("ErrorMessageForFolderNotExisting", "The folder does not exist or is invalid"), - ERROR_MESSAGE_FOR_SAVING_FILE_WITH_PATHS_EMPTY("ErrorMessageForSavingFileWithPathsEmpty", "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty"), - BACKUP_SAVED_CORRECTLY_TITLE("BackupSavedCorrectlyTitle", "Backup saved"), - BACKUP_SAVED_CORRECTLY_MESSAGE("BackupSavedCorrectlyMessage", "saved successfully!"), - ERROR_SAVING_BACKUP_MESSAGE("ErrorSavingBackupMessage", "Error saving backup"), - BACKUP_NAME_INPUT("BackupNameInput", "Name of the backup"), - CONFIRMATION_REQUIRED_TITLE("ConfirmationRequiredTitle", "Confirmation required"), - DUPLICATED_BACKUP_NAME_MESSAGE("DuplicatedBackupNameMessage", "A backup with the same name already exists, do you want to overwrite it?"), - BACKUP_LIST_CORRECTLY_EXPORTED_TITLE("BackupListCorrectlyExportedTitle", "Menu Export"), - BACKUP_LIST_CORRECTLY_EXPORTED_MESSAGE("BackupListCorrectlyExportedMessage", "Backup list successfully exported to the Desktop!"), - BACKUP_LIST_CORRECTLY_IMPORTED_TITLE("BackupListCorrectlyImportedTitle", "Menu Import"), - BACKUP_LIST_CORRECTLY_IMPORTED_MESSAGE("BackupListCorrectlyImportedMessage", "Backup list successfully imported!"), - BACKUP_NAME_ALREADY_USED_MESSAGE("BackupNameAlreadyUsedMessage", "Backup name already used!"), - ERROR_MESSAGE_FOR_INCORRECT_INITIAL_PATH("ErrorMessageForIncorrectInitialPath", "Error during the backup operation: the initial path is incorrect!"), - EXCEPTION_MESSAGE_TITLE("ExceptionMessageTitle", "Error..."), - EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE("ExceptionMessageClipboardMessage", "Error text has been copied to the clipboard."), - EXCEPTION_MESSAGE_CLIPBOARD_BUTTON("ExceptionMessageClipboardButton", "Copy to clipboard"), - EXCEPTION_MESSAGE_REPORT_BUTTON("ExceptionMessageReportButton", "Report the Problem"), - EXCEPTION_MESSAGE_REPORT_MESSAGE("ExceptionMessageReportMessage", "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):"), - ERROR_MESSAGE_OPENING_WEBSITE("ErrorMessageOpeningWebsite", "Failed to open the web page. Please try again."), - CONFIRMATION_MESSAGE_FOR_CLEAR("ConfirmationMessageForClear", "Are you sure you want to clean the fields?"), - CONFIRMATION_MESSAGE_FOR_UNSAVED_CHANGES("ConfirmationMessageForUnsavedChanges", "There are unsaved changes, do you want to save them before moving to another backup?"), - ERROR_MESSAGE_OPEN_HISTORY_FILE("ErrorMessageOpenHistoryFile", "Error opening history file."), - CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP("ConfirmationMessageBeforeDeleteBackup", "Are you sure you want to delete this item? Please note, this action cannot be undone"), - SHARE_LINK_COPIED_MESSAGE("ShareLinkCopiedMessage", "Share link copied to clipboard!"), - CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP("ConfirmationMessageCancelAutoBackup", "Are you sure you want to cancel automatic backups for this entry?"), - CONFIRMATION_MESSAGE_CANCEL_SINGLE_BACKUP("ConfirmationMessageCancelSingleBackup", "Are you sure you want to cancel this backup?"), - CONFIRMATION_MESSAGE_BEFORE_EXIT("ConfirmationMessageBeforeExit", "Are you sure you want to exit?"), - ERROR_MESSAGE_UNEXPECTED("ErrorMessageUnexpected", "An unexpected error has occurred!"), - ERROR_MESSAGE_PATHS_CANNOT_BE_SAME("ErrorMessagePathsCannotBeSame", "The initial path and destination path cannot be the same!"), - ERROR_MESSAGE_PATHS_ARE_EMPTY("ErrorMessagePathsAreEmpty", "The initial path and destination path cannot be empty!"), - ERROR_MESSAGE_INVALID_PATH("ErrorMessageInvalidPath", "The selected path is invalid!"), - ERROR_MESSAGE_NOT_SUPPORTED_EMAIL("ErrorMessageNotSupportedEmail", "Your system does not support sending emails directly from this application."), - ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC("ErrorMessageNotSupportedEmailGeneric", "Your system does not support sending emails."), - ERROR_WRONG_TIME_INTERVAL("ErrorWrongTimeInterval", "The time interval is not correct"), - AUTO_BACKUP_ACTIVATED_MESSAGE("AutoBackupActivatedMessage", "Auto Backup has been activated"), - SETTED_EVERY_MESSAGE("SettedEveryMessage", "\nIs setted every"), - DAYS_MESSAGE("DaysMessage", " days"), - ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL("ErrorMessageUnableToSendEmail", "Unable to send email. Please try again later."), - INTERRUPT_BACKUP_PROCESS_MESSAGE("InterruptBackupProcessMessage", "Are you sure you want to stop this backup?"), - ERROR_MESSAGE_INPUT_MISSING_GENERIC("ErrorMessageInputMissingGeneric", "Input Missing!"), - ERROR_MESSAGE_SAVING_FILE("ErrorMessageForSavingFile", "Error saving file"), - ERROR_MESSAGE_PATH_NOT_EXISTING("ErrorMessageForPathNotExisting", "One or both paths do not exist!"), - ERROR_MESSAGE_SAME_PATHS_GENERIC("ErrorMessageForSamePaths", "The initial path and destination path cannot be the same. Please choose different paths!"), - ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_TITLE("ErrorMessageForWrongFileExtensionTitle", "Invalid File"), - ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_MESSAGE("ErrorMessageForWrongFileExtensionMessage", "Error: Please select a valid JSON file."), - ERROR_MESSAGE_COUNTING_FILES("ErrorMessageCountingFiles", "Error occurred while calculating files to back up."), - ERROR_MESSAGE_ZIPPING_GENERIC("ErrorMessageZippingGeneric", "Error occurred while zipping files."), - ERROR_MESSAGE_ZIPPING_IO("ErrorMessageZippingIO", "Error occurred while zipping files: I/O error."), - ERROR_MESSAGE_ZIPPING_SECURITY("ErrorMessageZippingSecurity", "Error occurred while zipping files: Security error."), - SUCCESS_GENERIC_TITLE( "SuccessGenericTitle", "Success"), - SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE("SuccessfullyExportedToCsvMessage", "Backups exported to CSV successfully!"), - SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE("SuccessfullyExportedToPdfMessage", "Backups exported to PDF successfully!"), - ERROR_MESSAGE_FOR_EXPORTING_TO_CSV("ErrorMessageForExportingToCsv", "Error exporting backups to CSV: "), - ERROR_MESSAGE_FOR_EXPORTING_TO_PDF("ErrorMessageForExportingToPdf", "Error exporting backups to PDF: "), - CSV_NAME_MESSAGE_INPUT("CsvNameMessageInput", "Enter the name of the CSV file."), - PDF_NAME_MESSAGE_INPUT("PdfNameMessageInput", "Enter the name of the PDF file."), - DUPLICATED_FILE_NAME_MESSAGE("DuplicatedFileNameMessage", "File already exists. Overwrite?"), - ERROR_MESSAGE_INVALID_FILENAME("ErrorMessageInvalidFilename", "Invalid file name. Use only alphanumeric characters, dashes, and underscores."), - CONFIRMATION_DELETION_TITLE("ConfirmationDeletionTitle", "Confirm Deletion"), - CONFIRMATION_DELETION_MESSAGE("ConfirmationDeletionMessage", "Are you sure you want to delete the selected rows?"), - - // InfoPage - INFO_PAGE_DESCRIPTION("InfoPageDescription", "Backup automatic system for files with the option to schedule and make backups regularly."), - INFO_PAGE_FEATURES_TITLE("InfoPageFeaturesTitle", "Features"), - INFO_PAGE_FEATURES_DESCRIPTION("InfoPageFeaturesDescription", "Backup files quickly and easily with automatic scheduling and periodic backups"), - INFO_PAGE_BUTTON_TITLE("InfoPageButtonTitle", "OK"), - INFO_PAGE_VERSION("InfoPageVersion", "Version: "), - INFO_PAGE_DEVELOPER("InfoPageDeveloper", "Developer: "), - INFO_PAGE_CREDITS("InfoPageCredits", "Credits"), - INFO_PAGE_LICENSE("InfoPageLicense", "License"), - - // Subscription - SUBSCRIPTION_EXPIRING_TITLE("ExpiringTitle", "Backup Manager subscription expiring soon"), - SUBSCRIPTION_EXPIRING_MESSAGE("ExpiringMessage", "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it."), - SUBSCRIPTION_EXPIRED_TITLE("ExpiredTitle", "Backup Manager subscription expired"), - SUBSCRIPTION_EXPIRED_MESSAGE("ExpiredMessage", "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it."), - SUBSCRIPTION_ACTIVE("ActiveLabel", "Active"), - SUBSCRIPTION_EXPIRING("ExpiringLabel", "Expiring"), - SUBSCRIPTION_EXPIRED("ExpiredLabel", "Expired"); - - private final String keyName; - private final String defaultValue; - - private static final Map lookup = new HashMap<>(); - - static { - for (TranslationKey key : TranslationKey.values()) { - lookup.put(key.keyName, key); - } - } - - // Constructor to assign both key and default value - private TranslationKey(String keyName, String defaultValue) { - this.keyName = keyName; - this.defaultValue = defaultValue; - } - - // Lookup by keyName (JSON key) - public static TranslationKey fromKeyName(String keyName) { - return lookup.get(keyName); - } - - public String getKeyName() { - return keyName; - } - - public String getDefaultValue() { - return defaultValue; - } - } - - public static void loadTranslations(String filePath) throws IOException { - Gson gson = new Gson(); - - try (FileReader reader = new FileReader(filePath, StandardCharsets.UTF_8)) { - JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); - - for (TranslationCategory category : TranslationCategory.values()) { - JsonObject categoryTranslations = jsonObject.getAsJsonObject(category.getCategoryName()); - - if (categoryTranslations != null) { - for (Map.Entry entry : categoryTranslations.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue().getAsString(); - - // Use fromKeyName to get the TranslationKey from the JSON key - TranslationKey translationKey = TranslationKey.fromKeyName(key); - if (translationKey != null) { - // If value is null or empty, fall back to the default value from the enum - String translationValue = (value != null && !value.isEmpty()) ? value : translationKey.getDefaultValue(); - category.addTranslation(translationKey, translationValue); - } else { - logger.warn("Unrecognized key in JSON: " + key + ", using default value"); - } - } - } - } - } catch (Exception ex) { - logger.error("An error occurred: " + ex.getMessage(), ex); - } - } -} diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java new file mode 100644 index 00000000..c03125c5 --- /dev/null +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -0,0 +1,389 @@ +package backupmanager.Enums; + +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import backupmanager.Entities.Configurations; + +public class Translations { + + private static final Logger logger = LoggerFactory.getLogger(Translations.class); + + public enum TCategory { + GENERAL("General"), + MENU("Menu"), + TABBED_FRAMES("TabbedFrames"), + BACKUP_ENTRY("BackupEntry"), + BACKUP_LIST("BackupList"), + TIME_PICKER_DIALOG("TimePickerDialog"), + PREFERENCES_DIALOG("PreferencesDialog"), + USER_DIALOG("UserDialog"), + PROGRESS_BACKUP_FRAME("ProgressBackupFrame"), + TRAY_ICON("TrayIcon"), + DIALOGS("Dialogs"), + SUBSCRIPTION("Subscription"), + HISTORY_LOGS("HistoryLogs"); // TODO: add to json + + private final String categoryName; + private final Map translations = new HashMap<>(); + + TCategory(String categoryName) { + this.categoryName = categoryName; + } + + public void addTranslation(TKey key, String value) { + translations.put(key, value); + } + + // Updated getTranslation method + public String getTranslation(TKey key) { + return translations.getOrDefault(key, key.getDefaultValue()); + } + + public String getCategoryName() { + return categoryName; + } + } + + public enum TKey { + // General + APP_NAME(TCategory.GENERAL, "AppName", "Backup Manager"), + VERSION(TCategory.GENERAL, "Version", "Version"), + BACKUP(TCategory.GENERAL, "Backup", "Backup"), + FROM(TCategory.GENERAL, "From", "From"), + TO(TCategory.GENERAL, "To", "To"), + CLOSE_BUTTON(TCategory.GENERAL, "CloseButton", "Close"), + OK_BUTTON(TCategory.GENERAL, "OkButton", "Ok"), + CANCEL_BUTTON(TCategory.GENERAL, "CancelButton", "Cancel"), + APPLY_BUTTON(TCategory.GENERAL, "ApplyButton", "Apply"), + SAVE_BUTTON(TCategory.GENERAL, "SaveButton", "Save"), + CREATE_BUTTON(TCategory.GENERAL, "CreateButton", "Create"), // TODO: add to json + EDIT_BUTTON(TCategory.GENERAL, "EditButton", "Edit"), // TODO: add to json + DELETE_BUTTON(TCategory.GENERAL, "DeleteButton", "Delete"), // TODO: add to json + + // Menu + SUBMENU_MAIN(TCategory.MENU, "SubmenuMain", "MAIN"), //TODO: add to json + SUBMENU_OTHER(TCategory.MENU, "SubmenuOther", "OTHER"), //TODO: add to json + FILE(TCategory.MENU, "File", "File"), + OPTIONS(TCategory.MENU, "Options", "Options"), + ABOUT(TCategory.MENU, "About", "About"), + HELP(TCategory.MENU, "Help", "Help"), + BUG_REPORT(TCategory.MENU, "BugReport", "Report a bug"), + CLEAR(TCategory.MENU, "Clear", "Clear"), + DONATE(TCategory.MENU, "Donate", "Support the project"), // TODO: to update + HISTORY(TCategory.MENU, "History", "History"), + INFO_PAGE(TCategory.MENU, "InfoPage", "Info"), + NEW(TCategory.MENU, "New", "New"), + QUIT(TCategory.MENU, "Quit", "Quit"), + SAVE(TCategory.MENU, "Save", "Save"), + PREFERENCES(TCategory.MENU, "Preferences", "Preferences"), + IMPORT(TCategory.MENU, "Import", "Import"), + EXPORT(TCategory.MENU, "Export", "Export"), + SAVE_WITH_NAME(TCategory.MENU, "SaveWithName", "Save with name"), + SHARE(TCategory.MENU, "Share", "Share"), + SUPPORT(TCategory.MENU, "Support", "Support"), + WEBSITE(TCategory.MENU, "Website", "Website"), + BACKUP_TABLE(TCategory.MENU, "Backups", "Backup List"), // TODO: add to json + CREATE_BACKUP(TCategory.MENU, "CreateBackup", "Create new backup"), // TODO: add to json + IMPORT_BACKUP(TCategory.MENU, "ImportBackup", "Import backups from Csv"), // TODO: add to json + EXPORT_BACKUP(TCategory.MENU, "ExportBackup", "Export backups to Csv"), // TODO: add to json + DASHBOARD(TCategory.MENU, "Dashboard", "Dashboard"), // TODO: add to json + GITHUB_PAGE(TCategory.MENU, "Github", "Github page"), // TODO: add to json + PAYPAL(TCategory.MENU, "Paypal", "Paypal"), // TODO: add to json + BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), // TODO: add to json + CONTACT_US(TCategory.MENU, "ContactUs", "ContactUs"), // TODO: add to json + SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), // TODO: add to json + + + // TabbedFrames + BACKUP_ENTRY(TCategory.TABBED_FRAMES, "BackupEntry", "Backup Entry"), + BACKUP_LIST(TCategory.TABBED_FRAMES, "BackupList", "Backup List"), + + // BackupEntry + PAGE_TITLE(TCategory.BACKUP_ENTRY, "PageTitle", "Backup Entry"), + PAGE_SUBTITLE_CREATE(TCategory.BACKUP_ENTRY, "PageSubtitleCreate", "Create Backup"), // TODO: add to json + PAGE_SUBTITLE_EDIT(TCategory.BACKUP_ENTRY, "PageSubtitleEdit", "Edit Backup"), // TODO: add to json + PAGE_SUBTITLE_INFO(TCategory.BACKUP_ENTRY, "PageSubtitleInfo", "Backup Information"), // TODO: add to json + PAGE_SUBTITLE_SETTINGS(TCategory.BACKUP_ENTRY, "PageSubtitleSettings", "Backups Settings"), // TODO: add to json + CURRENT_FILE(TCategory.BACKUP_ENTRY, "CurrentFile", "Current file"), + NOTES(TCategory.BACKUP_ENTRY, "Notes", "Notes"), + PATHS(TCategory.BACKUP_ENTRY, "Paths", "Paths"), // TODO: add to json + LAST_BACKUP(TCategory.BACKUP_ENTRY, "LastBackup", "Last backup"), + SINGLE_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "SingleBackupButton", "Single Backup"), + AUTO_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "AutoBackupButton", "Auto Backup"), + AUTO_BACKUP_BUTTON_ON(TCategory.BACKUP_ENTRY, "AutoBackupButtonON", "Auto Backup (ON)"), + AUTO_BACKUP_BUTTON_OFF(TCategory.BACKUP_ENTRY, "AutoBackupButtonOFF", "Auto Backup (OFF)"), + BACKUP_NAME_PLACEHOLDER(TCategory.BACKUP_ENTRY, "BackupNamePlaceholder", "Backup name (unique)"), // TODO: add to json + INITIAL_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "InitialPathPlaceholder", "Target path e.g. C:\\Users\\Admin\\Documents"), // TODO: to update + DESTINATION_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "DestinationPathPlaceholder", "Destination folder e.g. D:\\Backups"), // TODO: to update + BACKUP_NAME(TCategory.BACKUP_ENTRY, "BackupName", "Backup name"), + BACKUP_NAME_TOOLTIP(TCategory.BACKUP_ENTRY, "BackupNameTooltip", "(Required) Backup name"), + INITIAL_PATH_TOOLTIP(TCategory.BACKUP_ENTRY, "InitialPathTooltip", "(Required) Initial path"), + DESTINATION_PATH_TOOLTIP(TCategory.BACKUP_ENTRY, "DestinationPathTooltip", "(Required) Destination path"), + INITIAL_FILE_CHOOSER_TOOLTIP(TCategory.BACKUP_ENTRY, "InitialFileChooserTooltip", "Open file explorer"), + DESTINATION_FILE_CHOOSER_TOOLTIP(TCategory.BACKUP_ENTRY, "DestinationFileChooserTooltip", "Open file explorer"), + NOTES_TOOLTIP(TCategory.BACKUP_ENTRY, "NotesTooltip", "(Optional) Backup description"), + SINGLE_BACKUP_TOOLTIP(TCategory.BACKUP_ENTRY, "SingleBackupTooltip", "Perform the backup"), + AUTO_BACKUP_TOOLTIP(TCategory.BACKUP_ENTRY, "AutoBackupTooltip", "Enable/Disable automatic backup"), + TIME_PICKER_TOOLTIP(TCategory.BACKUP_ENTRY, "TimePickerTooltip", "Time picker"), + MAX_BACKUPS_TO_KEEP(TCategory.BACKUP_ENTRY, "MaxBackupsToKeep", "Max backups to keep"), + MAX_BACKUPS_TO_KEEP_TOOLTIP(TCategory.BACKUP_ENTRY, "MaxBackupsToKeepTooltip", "Maximum number of backups before removing the oldest."), + + // BackupList + BACKUP_LIST_TITLE(TCategory.BACKUP_LIST, "BackupListTitle", "Backup List"), // TODO: to update + BACKUP_LIST_DESCRIPTION(TCategory.BACKUP_LIST, "BackupListDescription", "Manage and monitor backup configurations, including creation, editing, scheduling, and execution."), // TODO: to update + BACKUP_NAME_COLUMN(TCategory.BACKUP_LIST, "BackupNameColumn", "Backup Name"), + INITIAL_PATH_COLUMN(TCategory.BACKUP_LIST, "InitialPathColumn", "Initial Path"), + DESTINATION_PATH_COLUMN(TCategory.BACKUP_LIST, "DestinationPathColumn", "Destination Path"), + LAST_BACKUP_COLUMN(TCategory.BACKUP_LIST, "LastBackupColumn", "Last Backup"), + AUTOMATIC_BACKUP_COLUMN(TCategory.BACKUP_LIST, "AutomaticBackupColumn", "Automatic Backup"), + NEXT_BACKUP_DATE_COLUMN(TCategory.BACKUP_LIST, "NextBackupDateColumn", "Next Backup Date"), + TIME_INTERVAL_COLUMN(TCategory.BACKUP_LIST, "TimeIntervalColumn", "Interval (gg.HH:mm)"), // TODO: to update + MAX_BACKUPS_COLUMN(TCategory.BACKUP_LIST, "MaxBackupsColumn", "Max Backups To Keep"), // TODO: add to json + BACKUP_NAME_DETAIL(TCategory.BACKUP_LIST, "BackupNameDetail", "BackupName"), + INITIAL_PATH_DETAIL(TCategory.BACKUP_LIST, "InitialPathDetail", "InitialPath"), + DESTINATION_PATH_DETAIL(TCategory.BACKUP_LIST, "DestinationPathDetail", "DestinationPath"), + LAST_BACKUP_DETAIL(TCategory.BACKUP_LIST, "LastBackupDetail", "LastBackup"), + NEXT_BACKUP_DATE_DETAIL(TCategory.BACKUP_LIST, "NextBackupDateDetail", "NextBackup"), + TIME_INTERVAL_DETAIL(TCategory.BACKUP_LIST, "TimeIntervalDetail", "TimeInterval"), + CREATION_DATE_DETAIL(TCategory.BACKUP_LIST, "CreationDateDetail", "CreationDate"), + LAST_UPDATE_DATE_DETAIL(TCategory.BACKUP_LIST, "LastUpdateDateDetail", "LastUpdateDate"), + BACKUP_COUNT_DETAIL(TCategory.BACKUP_LIST, "BackupCountDetail", "BackupCount"), + NOTES_DETAIL(TCategory.BACKUP_LIST, "NotesDetail", "Notes"), + MAX_BACKUPS_TO_KEEP_DETAIL(TCategory.BACKUP_LIST, "MaxBackupsToKeepDetail", "MaxBackupsToKeep"), + ADD_BACKUP_TOOLTIP(TCategory.BACKUP_LIST, "AddBackupTooltip", "Add new backup"), + EXPORT_AS(TCategory.BACKUP_LIST, "ExportAs", "Export as: "), + EXPORT_AS_PDF_TOOLTIP(TCategory.BACKUP_LIST, "ExportAsPdfTooltip", "Export as PDF"), + EXPORT_AS_CSV_TOOLTIP(TCategory.BACKUP_LIST, "ExportAsCsvTooltip", "Export as CSV"), + RESEARCH_BAR_TOOLTIP(TCategory.BACKUP_LIST, "ResearchBarTooltip", "Research bar"), + RESEARCH_BAR_PLACEHOLDER(TCategory.BACKUP_LIST, "ResearchBarPlaceholder", "Search..."), + EDIT_POPUP(TCategory.BACKUP_LIST, "EditPopup", "Edit"), + DELETE_POPUP(TCategory.BACKUP_LIST, "DeletePopup", "Delete"), + INTERRUPT_POPUP(TCategory.BACKUP_LIST, "InterruptPopup", "Interrupt"), + DUPLICATE_POPUP(TCategory.BACKUP_LIST, "DuplicatePopup", "Duplicate"), + RENAME_BACKUP_POPUP(TCategory.BACKUP_LIST, "RenameBackupPopup", "Rename backup"), + OPEN_INITIAL_FOLDER_POPUP(TCategory.BACKUP_LIST, "OpenInitialFolderPopup", "Open initial path"), + OPEN_DESTINATION_FOLDER_POPUP(TCategory.BACKUP_LIST, "OpenDestinationFolderPopup", "Open destination path"), + BACKUP_POPUP(TCategory.BACKUP_LIST, "BackupPopup", "Backup"), + SINGLE_BACKUP_POPUP(TCategory.BACKUP_LIST, "SingleBackupPopup", "Run single backup"), + AUTO_BACKUP_POPUP(TCategory.BACKUP_LIST, "AutoBackupPopup", "Auto backup"), + COPY_TEXT_POPUP(TCategory.BACKUP_LIST, "CopyTextPopup", "Copy text"), + COPY_BACKUP_NAME_POPUP(TCategory.BACKUP_LIST, "CopyBackupNamePopup", "Copy backup name"), + COPY_INITIAL_PATH_POPUP(TCategory.BACKUP_LIST, "CopyInitialPathPopup", "Copy initial path"), + COPY_DESTINATION_PATH_BACKUP(TCategory.BACKUP_LIST, "CopyDestinationPathPopup", "Copy destination path"), + + // TimePickerDialog + TIME_INTERVAL_TITLE(TCategory.TIME_PICKER_DIALOG, "TimeIntervalTitle", "Time interval for auto backup"), + DESCRIPTION(TCategory.TIME_PICKER_DIALOG, "Description", "Select how often to perform the automatic backup by choosing the frequency in days, hours, and minutes."), + DAYS(TCategory.TIME_PICKER_DIALOG, "Days", "Days"), + HOURS(TCategory.TIME_PICKER_DIALOG, "Hours", "Hours"), + MINUTES(TCategory.TIME_PICKER_DIALOG, "Minutes", "Minutes"), + SPINNER_TOOLTIP(TCategory.TIME_PICKER_DIALOG, "SpinnerTooltip", "Mouse wheel to adjust the value"), + + // PreferencesDialog + PREFERENCES_TITLE(TCategory.PREFERENCES_DIALOG, "PreferencesTitle", "Preferences"), + LANGUAGE(TCategory.PREFERENCES_DIALOG, "Language", "Language"), + THEME(TCategory.PREFERENCES_DIALOG, "Theme", "Theme"), + + // User dialog + USER_TITLE(TCategory.USER_DIALOG, "UserTitle", "Insert your data"), + USER_DESCRIPTION(TCategory.USER_DIALOG, "UserDescription", "Please enter your data to access the system"), // TODO: add to json + USER_NAME(TCategory.USER_DIALOG, "Name", "Name"), + USER_SURNAME(TCategory.USER_DIALOG, "Surname", "Surname"), + USER_EMAIL(TCategory.USER_DIALOG, "Email", "Email"), + USER_NAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserNamePlaceholder", "Enter your name"), // TODO: add to json + USER_SURNAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserSurnamePlaceholder", "Enter your surname"), // TODO: add to json + USER_EMAIL_PLACEHOLDER(TCategory.USER_DIALOG, "UserEmailPlaceholder", "Enter your email"), // TODO: add to json + ERROR_MESSAGE_FOR_MISSING_DATA(TCategory.USER_DIALOG, "ErrorMessageForMissingData", "Please fill in all the required fields."), + ERROR_MESSAGE_FOR_WRONG_EMAIL(TCategory.USER_DIALOG, "ErrorMessageForWrongEmail", "The provided email address is invalid. Please provide a correct one."), + EMAIL_CONFIRMATION_SUBJECT(TCategory.USER_DIALOG, "EmailConfirmationSubject", "Thank you for choosing Backup Manager!"), + EMAIL_CONFIRMATION_BODY(TCategory.USER_DIALOG, "EmailConfirmationBody", "Hi [UserName],\n\nThank you for downloading and registering **Backup Manager**, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at **[SupportEmail]**.\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team"), + + // ProgressBackupFrame + PROGRESS_BACKUP_TITLE(TCategory.PROGRESS_BACKUP_FRAME, "ProgressBackupTitle", "Backup in progress"), + STATUS_COMPLETED(TCategory.PROGRESS_BACKUP_FRAME, "StatusCompleted", "Backup completed!"), + STATUS_LOADING(TCategory.PROGRESS_BACKUP_FRAME, "StatusLoading", "Loading..."), + + // TrayIcon + TRAY_TOOLTIP(TCategory.TRAY_ICON, "TrayTooltip", "Backup Service"), + OPEN_ACTION(TCategory.TRAY_ICON, "OpenAction", "Quick Access"), + EXIT_ACTION(TCategory.TRAY_ICON, "ExitAction", "Exit"), + SUCCESS_MESSAGE(TCategory.TRAY_ICON, "SuccessMessage", "\nThe backup was successfully completed:"), + ERROR_MESSAGE_INPUT_MISSING(TCategory.TRAY_ICON, "ErrorMessageInputMissing", "\nError during automatic backup.\nInput Missing!"), + ERROR_MESSAGE_FILES_NOT_EXISTING(TCategory.TRAY_ICON, "ErrorMessageFilesNotExisting", "\nError during automatic backup.\nOne or both paths do not exist!"), + ERROR_MESSAGE_SAME_PATHS(TCategory.TRAY_ICON, "ErrorMessageSamePaths", "\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!"), + + // Dialogs + ERROR_GENERIC_TITLE(TCategory.DIALOGS, "ErrorGenericTitle", "Error"), + WARNING_GENERIC_TITLE(TCategory.DIALOGS, "WarningGenericTitle", "Warning"), + WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE(TCategory.DIALOGS, "WarningBackupAlreadyInProgressMessage", "There is already a backup in progress. It is not possible to perform parallel backups"), + WARNING_SHORT_TIME_INTERVAL_MESSAGE(TCategory.DIALOGS, "WarningShortTimeIntervalMessage", "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?"), + + ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING(TCategory.DIALOGS, "ErrorMessageForFolderNotExisting", "The folder does not exist or is invalid"), + ERROR_MESSAGE_FOR_SAVING_FILE_WITH_PATHS_EMPTY(TCategory.DIALOGS, "ErrorMessageForSavingFileWithPathsEmpty", "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty"), + BACKUP_SAVED_CORRECTLY_TITLE(TCategory.DIALOGS, "BackupSavedCorrectlyTitle", "Backup saved"), + BACKUP_SAVED_CORRECTLY_MESSAGE(TCategory.DIALOGS, "BackupSavedCorrectlyMessage", "saved successfully!"), + ERROR_SAVING_BACKUP_MESSAGE(TCategory.DIALOGS, "ErrorSavingBackupMessage", "Error saving backup"), + BACKUP_NAME_INPUT(TCategory.DIALOGS, "BackupNameInput", "Name of the backup"), + CONFIRMATION_REQUIRED_TITLE(TCategory.DIALOGS, "ConfirmationRequiredTitle", "Confirmation required"), + DUPLICATED_BACKUP_NAME_MESSAGE(TCategory.DIALOGS, "DuplicatedBackupNameMessage", "A backup with the same name already exists, do you want to overwrite it?"), + BACKUP_LIST_CORRECTLY_EXPORTED_TITLE(TCategory.DIALOGS, "BackupListCorrectlyExportedTitle", "Menu Export"), + BACKUP_LIST_CORRECTLY_EXPORTED_MESSAGE(TCategory.DIALOGS, "BackupListCorrectlyExportedMessage", "Backup list successfully exported to the Desktop!"), + BACKUP_LIST_CORRECTLY_IMPORTED_TITLE(TCategory.DIALOGS, "BackupListCorrectlyImportedTitle", "Menu Import"), + BACKUP_LIST_CORRECTLY_IMPORTED_MESSAGE(TCategory.DIALOGS, "BackupListCorrectlyImportedMessage", "Backup list successfully imported!"), + BACKUP_NAME_ALREADY_USED_MESSAGE(TCategory.DIALOGS, "BackupNameAlreadyUsedMessage", "Backup name already used!"), + ERROR_MESSAGE_FOR_INCORRECT_INITIAL_PATH(TCategory.DIALOGS, "ErrorMessageForIncorrectInitialPath", "Error during the backup operation: the initial path is incorrect!"), + EXCEPTION_MESSAGE_TITLE(TCategory.DIALOGS, "ExceptionMessageTitle", "Error..."), + EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE(TCategory.DIALOGS, "ExceptionMessageClipboardMessage", "Error text has been copied to the clipboard."), + EXCEPTION_MESSAGE_CLIPBOARD_BUTTON(TCategory.DIALOGS, "ExceptionMessageClipboardButton", "Copy to clipboard"), + EXCEPTION_MESSAGE_REPORT_BUTTON(TCategory.DIALOGS, "ExceptionMessageReportButton", "Report the Problem"), + EXCEPTION_MESSAGE_REPORT_MESSAGE(TCategory.DIALOGS, "ExceptionMessageReportMessage", "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):"), + ERROR_MESSAGE_OPENING_WEBSITE(TCategory.DIALOGS, "ErrorMessageOpeningWebsite", "Failed to open the web page. Please try again."), + CONFIRMATION_MESSAGE_FOR_CLEAR(TCategory.DIALOGS, "ConfirmationMessageForClear", "Are you sure you want to clean the fields?"), + CONFIRMATION_MESSAGE_FOR_UNSAVED_CHANGES(TCategory.DIALOGS, "ConfirmationMessageForUnsavedChanges", "There are unsaved changes, do you want to save them before moving to another backup?"), + ERROR_MESSAGE_OPEN_HISTORY_FILE(TCategory.DIALOGS, "ErrorMessageOpenHistoryFile", "Error opening history file."), + CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP(TCategory.DIALOGS, "ConfirmationMessageBeforeDeleteBackup", "Are you sure you want to delete this item? Please note, this action cannot be undone"), + SHARE_LINK_COPIED_MESSAGE(TCategory.DIALOGS, "ShareLinkCopiedMessage", "Share link copied to clipboard!"), + CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP(TCategory.DIALOGS, "ConfirmationMessageCancelAutoBackup", "Are you sure you want to cancel automatic backups for this entry?"), + CONFIRMATION_MESSAGE_CANCEL_SINGLE_BACKUP(TCategory.DIALOGS, "ConfirmationMessageCancelSingleBackup", "Are you sure you want to cancel this backup?"), + CONFIRMATION_MESSAGE_BEFORE_EXIT(TCategory.DIALOGS, "ConfirmationMessageBeforeExit", "Are you sure you want to exit?"), + ERROR_MESSAGE_UNEXPECTED(TCategory.DIALOGS, "ErrorMessageUnexpected", "An unexpected error has occurred!"), + ERROR_MESSAGE_PATHS_CANNOT_BE_SAME(TCategory.DIALOGS, "ErrorMessagePathsCannotBeSame", "The initial path and destination path cannot be the same!"), + ERROR_MESSAGE_PATHS_ARE_EMPTY(TCategory.DIALOGS, "ErrorMessagePathsAreEmpty", "The initial path and destination path cannot be empty!"), + ERROR_MESSAGE_INVALID_PATH(TCategory.DIALOGS, "ErrorMessageInvalidPath", "The selected path is invalid!"), + ERROR_MESSAGE_NOT_SUPPORTED_EMAIL(TCategory.DIALOGS, "ErrorMessageNotSupportedEmail", "Your system does not support sending emails directly from this application."), + ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC(TCategory.DIALOGS, "ErrorMessageNotSupportedEmailGeneric", "Your system does not support sending emails."), + ERROR_WRONG_TIME_INTERVAL(TCategory.DIALOGS, "ErrorWrongTimeInterval", "The time interval is not correct"), + AUTO_BACKUP_ACTIVATED_MESSAGE(TCategory.DIALOGS, "AutoBackupActivatedMessage", "Auto Backup has been activated"), + SETTED_EVERY_MESSAGE(TCategory.DIALOGS, "SettedEveryMessage", "\nIs setted every"), + DAYS_MESSAGE(TCategory.DIALOGS, "DaysMessage", " days"), + ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL(TCategory.DIALOGS, "ErrorMessageUnableToSendEmail", "Unable to send email. Please try again later."), + INTERRUPT_BACKUP_PROCESS_MESSAGE(TCategory.DIALOGS, "InterruptBackupProcessMessage", "Are you sure you want to stop this backup?"), + ERROR_MESSAGE_INPUT_MISSING_GENERIC(TCategory.DIALOGS, "ErrorMessageInputMissingGeneric", "Input Missing!"), + ERROR_MESSAGE_SAVING_FILE(TCategory.DIALOGS, "ErrorMessageForSavingFile", "Error saving file"), + ERROR_MESSAGE_PATH_NOT_EXISTING(TCategory.DIALOGS, "ErrorMessageForPathNotExisting", "One or both paths do not exist!"), + ERROR_MESSAGE_SAME_PATHS_GENERIC(TCategory.DIALOGS, "ErrorMessageForSamePaths", "The initial path and destination path cannot be the same. Please choose different paths!"), + ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_TITLE(TCategory.DIALOGS, "ErrorMessageForWrongFileExtensionTitle", "Invalid File"), + ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_MESSAGE(TCategory.DIALOGS, "ErrorMessageForWrongFileExtensionMessage", "Error: Please select a valid JSON file."), + ERROR_MESSAGE_COUNTING_FILES(TCategory.DIALOGS, "ErrorMessageCountingFiles", "Error occurred while calculating files to back up."), + ERROR_MESSAGE_ZIPPING_GENERIC(TCategory.DIALOGS, "ErrorMessageZippingGeneric", "Error occurred while zipping files."), + ERROR_MESSAGE_ZIPPING_IO(TCategory.DIALOGS, "ErrorMessageZippingIO", "Error occurred while zipping files: I/O error."), + ERROR_MESSAGE_ZIPPING_SECURITY(TCategory.DIALOGS, "ErrorMessageZippingSecurity", "Error occurred while zipping files: Security error."), + SUCCESS_GENERIC_TITLE(TCategory.DIALOGS, "SuccessGenericTitle", "Success"), + SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE(TCategory.DIALOGS, "SuccessfullyExportedToCsvMessage", "Backups exported to CSV successfully!"), + SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE(TCategory.DIALOGS, "SuccessfullyExportedToPdfMessage", "Backups exported to PDF successfully!"), + ERROR_MESSAGE_FOR_EXPORTING_TO_CSV(TCategory.DIALOGS, "ErrorMessageForExportingToCsv", "Error exporting backups to CSV: "), + ERROR_MESSAGE_FOR_EXPORTING_TO_PDF(TCategory.DIALOGS, "ErrorMessageForExportingToPdf", "Error exporting backups to PDF: "), + CSV_NAME_MESSAGE_INPUT(TCategory.DIALOGS, "CsvNameMessageInput", "Enter the name of the CSV file."), + PDF_NAME_MESSAGE_INPUT(TCategory.DIALOGS, "PdfNameMessageInput", "Enter the name of the PDF file."), + DUPLICATED_FILE_NAME_MESSAGE(TCategory.DIALOGS, "DuplicatedFileNameMessage", "File already exists. Overwrite?"), + ERROR_MESSAGE_INVALID_FILENAME(TCategory.DIALOGS, "ErrorMessageInvalidFilename", "Invalid file name. Use only alphanumeric characters, dashes, and underscores."), + CONFIRMATION_DELETION_TITLE(TCategory.DIALOGS, "ConfirmationDeletionTitle", "Confirm Deletion"), + CONFIRMATION_DELETION_MESSAGE(TCategory.DIALOGS, "ConfirmationDeletionMessage", "Are you sure you want to delete the selected rows?"), + + // Subscription + SUBSCRIPTION_EXPIRING_TITLE(TCategory.SUBSCRIPTION, "ExpiringTitle", "Backup Manager subscription expiring soon"), + SUBSCRIPTION_EXPIRING_MESSAGE(TCategory.SUBSCRIPTION, "ExpiringMessage", "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it."), + SUBSCRIPTION_EXPIRED_TITLE(TCategory.SUBSCRIPTION, "ExpiredTitle", "Backup Manager subscription expired"), + SUBSCRIPTION_EXPIRED_MESSAGE(TCategory.SUBSCRIPTION, "ExpiredMessage", "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it."), + SUBSCRIPTION_ACTIVE(TCategory.SUBSCRIPTION, "ActiveLabel", "Active"), // TODO: add to json + SUBSCRIPTION_EXPIRING(TCategory.SUBSCRIPTION, "ExpiringLabel", "Expiring"), // TODO: add to json + SUBSCRIPTION_EXPIRED(TCategory.SUBSCRIPTION, "ExpiredLabel", "Expired"), // TODO: add to json + SUBSCRIPTION_STATUS(TCategory.SUBSCRIPTION, "Status", "Subscription status"), // TODO: add to json + SUBSCRIPTION_VALID_FROM(TCategory.SUBSCRIPTION, "ValidFrom", "Valid from"), // TODO: add to json + SUBSCRIPTION_VALID_TO(TCategory.SUBSCRIPTION, "ValidTo", "Valid to"), // TODO: add to json + SUBSCRIPTION_CONTACT_US(TCategory.SUBSCRIPTION, "ContactUs", "Contact us"), // TODO: add to json + SUBSCRIPTION_TO_EXTEND(TCategory.SUBSCRIPTION, "ToExtend", "to extend the subscription period."), // TODO: add to json + + // HISTORY_LOGS + HISTORY_LOGS_TITLE(TCategory.HISTORY_LOGS, "HistoryLogsTitle", "History logs"), // TODO: add to json + HISTORY_LOGS_DESCRIPTION(TCategory.HISTORY_LOGS, "HistoryLogsDescription", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."); // TODO: add to json + + private final TCategory category; + private final String keyName; + private final String defaultValue; + + private static final Map lookup = new HashMap<>(); + + static { + for (TKey key : TKey.values()) { + lookup.put(key.keyName, key); + } + } + + // Constructor to assign both key and default value + private TKey(TCategory category, String keyName, String defaultValue) { + this.category = category; + this.keyName = keyName; + this.defaultValue = defaultValue; + } + + // Lookup by keyName (JSON key) + public static TKey fromKeyName(String keyName) { + return lookup.get(keyName); + } + + public TCategory getCategory() { return category; } + public String getKeyName() { return keyName; } + public String getDefaultValue() { return defaultValue; } + } + + public static String get(TKey key) { + TCategory category = key.getCategory(); + return category.getTranslation(key); + } + + public static void loadTranslations(String filePath) throws IOException { + Gson gson = new Gson(); + + try (FileReader reader = new FileReader(filePath, StandardCharsets.UTF_8)) { + JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); + + for (TCategory category : TCategory.values()) { + JsonObject categoryTranslations = jsonObject.getAsJsonObject(category.getCategoryName()); + + if (categoryTranslations == null) { + logger.warn("Missing category in {}: {}", Configurations.getLanguage().getFileName(), category.getCategoryName()); + continue; + } + + Set loadedKeys = new HashSet<>(); + for (Map.Entry entry : categoryTranslations.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().getAsString(); + + // Use fromKeyName to get the TKey from the JSON key + TKey translationKey = TKey.fromKeyName(key); + if (translationKey != null) { + // If value is null or empty, fall back to the default value from the enum + String translationValue = (value != null && !value.isEmpty()) ? value : translationKey.getDefaultValue(); + category.addTranslation(translationKey, translationValue); + loadedKeys.add(translationKey); + } else { + logger.warn("Unrecognized key in JSON: {}, using default value", key); + } + } + + for (TKey key : TKey.values()) { + if (key.getCategory() == category && !loadedKeys.contains(key)) { + logger.warn("Missing translation in {} -> category: {}, key: {}", Configurations.getLanguage().getFileName(), key.getCategory(), key.getKeyName()); + } + } + } + } catch (Exception ex) { + logger.error("An error occurred: {}", ex.getMessage(), ex); + } + } +} diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 7898c561..26626c3f 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -13,8 +13,8 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.BackupStatus; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.Dialogs.BackupEntryDialog; @@ -56,7 +56,7 @@ public static void deleteBackup(int selectedRow, BackupTable backupTable, boolea logger.info("Event --> deleting backup"); if (isConfermationRequired) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response != JOptionPane.YES_OPTION) { return; } @@ -71,7 +71,7 @@ public static void deleteBackup(int selectedRow, BackupTable backupTable) { logger.info("Event --> deleting backup"); if (selectedRow != -1) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { String backupName = (String) backupTable.getValueAt(selectedRow, 0); BackupHelper.deleteBackup(backupName); @@ -94,7 +94,7 @@ public static void deleteBackup(ConfigurationBackup backup) { public static void deleteBackupWithConfirmition(ConfigurationBackup backup) { logger.info("Event --> deleting backup request with confirmation for backup: " + backup.getName()); - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { BackupHelper.deleteBackup(backup); } @@ -126,11 +126,11 @@ public static TimeInterval openTimePicker(java.awt.Dialog parent, TimeInterval t } public static void showMessageActivationAutoBackup(TimeInterval timeInterval, String startPath, String destinationPath) { - String from = TranslationCategory.GENERAL.getTranslation(TranslationKey.FROM); - String to = TranslationCategory.GENERAL.getTranslation(TranslationKey.TO); - String activated = TranslationCategory.DIALOGS.getTranslation(TranslationKey.AUTO_BACKUP_ACTIVATED_MESSAGE); - String setted = TranslationCategory.DIALOGS.getTranslation(TranslationKey.SETTED_EVERY_MESSAGE); - String days = TranslationCategory.DIALOGS.getTranslation(TranslationKey.DAYS_MESSAGE); + String from = TCategory.GENERAL.getTranslation(TKey.FROM); + String to = TCategory.GENERAL.getTranslation(TKey.TO); + String activated = TCategory.DIALOGS.getTranslation(TKey.AUTO_BACKUP_ACTIVATED_MESSAGE); + String setted = TCategory.DIALOGS.getTranslation(TKey.SETTED_EVERY_MESSAGE); + String days = TCategory.DIALOGS.getTranslation(TKey.DAYS_MESSAGE); JOptionPane.showMessageDialog(null, activated + "\n\t" + from + ": " + startPath + "\n\t" + to + ": " @@ -173,7 +173,7 @@ public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup back logger.info("Event --> automatic backup"); if (backup.isAutomatic()) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response != JOptionPane.YES_OPTION) { return null; } diff --git a/src/main/java/backupmanager/Helpers/SqlHelper.java b/src/main/java/backupmanager/Helpers/SqlHelper.java index 0feb634d..2cce87d4 100644 --- a/src/main/java/backupmanager/Helpers/SqlHelper.java +++ b/src/main/java/backupmanager/Helpers/SqlHelper.java @@ -13,6 +13,13 @@ public static long toMilliseconds(LocalDateTime date) { return date != null ? date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : 0; } + public static long toMilliseconds(LocalDate date) { + return date == null ? 0L : + date.atStartOfDay(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli(); + } + public static long toMilliseconds(LocalDateTime date, LocalDateTime fallbackValue) { if (fallbackValue == null) throw new IllegalArgumentException("Cannot pass the fallback value as null"); return date != null ? date.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : fallbackValue.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); diff --git a/src/main/java/backupmanager/Helpers/SubscriptionHelper.java b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java index c12bf172..f0def76b 100644 --- a/src/main/java/backupmanager/Helpers/SubscriptionHelper.java +++ b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java @@ -2,20 +2,19 @@ import java.time.LocalDate; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Entities.Subscription; -import backupmanager.Enums.ConfigKey; import backupmanager.Enums.SubscriptionStatus; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.SubscriptionRepository; public class SubscriptionHelper { - private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private static final JsonConfig configReader = JsonConfig.getInstance(); public static SubscriptionStatus getSubscriptionStatus() { - if (!Confingurations.isSubscriptionNedded()) + if (!Configurations.isSubscriptionNedded()) return SubscriptionStatus.NONE; Subscription subscription = SubscriptionRepository.getAnySubscriptionValid(); @@ -30,9 +29,9 @@ public static SubscriptionStatus getSubscriptionStatus() { public static String getSubscriptionStatusTranslated(SubscriptionStatus status) { String statusTranslation; switch (status) { - case EXPIRED -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRED); - case ACTIVE -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_ACTIVE); - case EXPIRATION -> statusTranslation = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRING); + case EXPIRED -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED); + case ACTIVE -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_ACTIVE); + case EXPIRATION -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING); default -> statusTranslation = ""; } return statusTranslation; diff --git a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java index e49465ed..c72679cb 100644 --- a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java +++ b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java @@ -5,20 +5,20 @@ import javax.swing.JOptionPane; import backupmanager.gui.Controllers.TrayController; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class SubscriptionNotifier { public static void showExpiringWarning(TrayController trayController) { - String title = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRING_TITLE); - String message = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRING_MESSAGE); + String title = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING_TITLE); + String message = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING_MESSAGE); showMessage(trayController, title, message, TrayIcon.MessageType.WARNING); } public static void showExpiredAlert(TrayController trayController) { - String title = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRED_TITLE); - String message = TranslationCategory.SUBSCRIPTION.getTranslation(TranslationKey.SUBSCRIPTION_EXPIRED_MESSAGE); + String title = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED_TITLE); + String message = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED_MESSAGE); showMessage(trayController, title, message, TrayIcon.MessageType.ERROR); } diff --git a/src/main/java/backupmanager/Json/JSONConfigReader.java b/src/main/java/backupmanager/Json/JsonConfig.java similarity index 76% rename from src/main/java/backupmanager/Json/JSONConfigReader.java rename to src/main/java/backupmanager/Json/JsonConfig.java index 63c71ce1..492a933e 100644 --- a/src/main/java/backupmanager/Json/JSONConfigReader.java +++ b/src/main/java/backupmanager/Json/JsonConfig.java @@ -13,22 +13,38 @@ import backupmanager.Enums.ConfigKey; +// Singleton class +public class JsonConfig { + private static final Logger logger = LoggerFactory.getLogger(JsonConfig.class); -public class JSONConfigReader { - private static final Logger logger = LoggerFactory.getLogger(JSONConfigReader.class); + // The field must be declared volatile so that double check lock would work correctly. + private static volatile JsonConfig instance; private final String filename; private final String directoryPath; private JsonObject config; - public JSONConfigReader(String filename, String directoryPath) { - this.filename = filename; - this.directoryPath = directoryPath; - loadConfig(); // Load configuration at instantiation + // The approach taken here is called double-checked locking (DCL). It + // exists to prevent race condition between multiple threads that may + // attempt to get singleton instance at the same time, creating separate + // instances as a result. + public static JsonConfig getInstance() { + JsonConfig result = instance; + + if (result != null) + return result; + + synchronized (JsonConfig.class) { + if (instance == null) + instance = new JsonConfig(); + return instance; + } } - public JSONConfigReader() { - this(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private JsonConfig() { + filename = ConfigKey.CONFIG_FILE_STRING.getValue(); + directoryPath = ConfigKey.CONFIG_DIRECTORY_STRING.getValue(); + loadConfig(); // Load configuration at instantiation } public boolean isMenuItemEnabled(String menuItem) { diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 7df82ead..e42129de 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -14,9 +14,9 @@ import com.formdev.flatlaf.util.FontUtils; import backupmanager.gui.Controllers.AppController; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum; +import backupmanager.Enums.Translations; import backupmanager.Managers.ExceptionManager; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; @@ -60,8 +60,8 @@ private static void databaseInitialization() { private static void loadPreferredLanguage() { try { - Confingurations.loadAllConfigurations(); - TranslationLoaderEnum.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Confingurations.getLanguage().getFileName()); + Configurations.loadAllConfigurations(); + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); } catch (IOException ex) { logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); } diff --git a/src/main/java/backupmanager/Managers/ExceptionManager.java b/src/main/java/backupmanager/Managers/ExceptionManager.java index a6af2c8a..0c7e498a 100644 --- a/src/main/java/backupmanager/Managers/ExceptionManager.java +++ b/src/main/java/backupmanager/Managers/ExceptionManager.java @@ -13,14 +13,14 @@ import backupmanager.Email.EmailSender; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class ExceptionManager { private static final Logger logger = LoggerFactory.getLogger(ExceptionManager.class); public static void openExceptionMessage(String errorMessage, String stackTrace) { - Object[] options = {TranslationCategory.GENERAL.getTranslation(TranslationKey.CLOSE_BUTTON), TranslationCategory.DIALOGS.getTranslation(TranslationKey.EXCEPTION_MESSAGE_CLIPBOARD_BUTTON), TranslationCategory.DIALOGS.getTranslation(TranslationKey.EXCEPTION_MESSAGE_REPORT_BUTTON)}; + Object[] options = {TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON), TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_CLIPBOARD_BUTTON), TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_REPORT_BUTTON)}; if (errorMessage == null) { errorMessage = ""; @@ -30,7 +30,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) EmailSender.sendErrorEmail("Critical Error Report", stackTrace, errorMessage); - String stackTraceMessage = TranslationCategory.DIALOGS.getTranslation(TranslationKey.EXCEPTION_MESSAGE_REPORT_MESSAGE) + "\n" + stackTrace; + String stackTraceMessage = TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_REPORT_MESSAGE) + "\n" + stackTrace; int choice; @@ -59,7 +59,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) scrollPane.setPreferredSize(new Dimension(MAX_WIDTH, 300)); // Display the option dialog with the JScrollPane - String error = TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE); + String error = TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE); choice = JOptionPane.showOptionDialog( null, scrollPane, // The JScrollPane containing the error message @@ -75,7 +75,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) StringSelection selection = new StringSelection(stackTrace); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); logger.info("Error text has been copied to the clipboard"); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE)); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE)); } else if (choice == 2) { WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue()); } diff --git a/src/main/java/backupmanager/Managers/ExportManager.java b/src/main/java/backupmanager/Managers/ExportManager.java index 4aaf0a42..0e2254aa 100644 --- a/src/main/java/backupmanager/Managers/ExportManager.java +++ b/src/main/java/backupmanager/Managers/ExportManager.java @@ -22,8 +22,8 @@ import backupmanager.BackupOperations; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class ExportManager { @@ -40,7 +40,7 @@ public static void exportAsPDF(ArrayList backups, String he return; } - String filename = JOptionPane.showInputDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.PDF_NAME_MESSAGE_INPUT)); + String filename = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.PDF_NAME_MESSAGE_INPUT)); if (filename == null || filename.isEmpty()) { logger.info("Exporting backups to PDF cancelled"); return; @@ -48,7 +48,7 @@ public static void exportAsPDF(ArrayList backups, String he // Validate filename if (!filename.matches("[a-zA-Z0-9-_ ]+")) { - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_INVALID_FILENAME), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INVALID_FILENAME), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); logger.info("Exporting backups to PDF cancelled due to invalid file name"); return; } @@ -59,7 +59,7 @@ public static void exportAsPDF(ArrayList backups, String he // Check if the file exists File file = new File(fullPath); if (file.exists()) { - int overwrite = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.DUPLICATED_FILE_NAME_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); + int overwrite = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_FILE_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); if (overwrite != JOptionPane.YES_OPTION) { logger.info("Exporting backups to PDF cancelled by user (file exists)"); return; @@ -107,11 +107,11 @@ public static void exportAsPDF(ArrayList backups, String he document.close(); // Notify success - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); } catch (IOException ex) { logger.error("Error exporting backups to PDF: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_FOR_EXPORTING_TO_PDF) + ex.getMessage(), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_PDF) + ex.getMessage(), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } finally { logger.info("Exporting backups to PDF finished"); } @@ -127,7 +127,7 @@ public static void exportAsCSV(List backups, String header) return; } - String filename = JOptionPane.showInputDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CSV_NAME_MESSAGE_INPUT)); + String filename = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.CSV_NAME_MESSAGE_INPUT)); if (filename == null || filename.isEmpty()) { logger.info("Exporting backups to CSV cancelled"); return; @@ -135,7 +135,7 @@ public static void exportAsCSV(List backups, String header) // Validate filename if (!filename.matches("[a-zA-Z0-9-_ ]+")) { - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_INVALID_FILENAME), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INVALID_FILENAME), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); logger.info("Exporting backups to CSV cancelled due to invalid file name"); return; } @@ -146,7 +146,7 @@ public static void exportAsCSV(List backups, String header) // Check if the file exists File file = new File(fullPath); if (file.exists()) { - int overwrite = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.DUPLICATED_FILE_NAME_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); + int overwrite = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_FILE_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); if (overwrite != JOptionPane.YES_OPTION) { logger.info("Exporting backups to CSV cancelled by user (file exists)"); return; @@ -166,10 +166,10 @@ public static void exportAsCSV(List backups, String header) } } - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); } catch (IOException ex) { logger.error("Error exporting backups to CSV: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_FOR_EXPORTING_TO_CSV) + ex.getMessage(), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_CSV) + ex.getMessage(), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } finally { logger.info("Exporting backups to CSV finished"); } diff --git a/src/main/java/backupmanager/Managers/ThemeManager.java b/src/main/java/backupmanager/Managers/ThemeManager.java index b0ef7269..487ba426 100644 --- a/src/main/java/backupmanager/Managers/ThemeManager.java +++ b/src/main/java/backupmanager/Managers/ThemeManager.java @@ -24,7 +24,7 @@ import com.formdev.flatlaf.intellijthemes.FlatSolarizedDarkIJTheme; import com.formdev.flatlaf.intellijthemes.FlatSolarizedLightIJTheme; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; // https://www.formdev.com/flatlaf/#demo // https://www.formdev.com/flatlaf/themes/ @@ -63,7 +63,7 @@ private static void repaint(Object objectToRepaint) { private static void updateTheme() { try { - String selectedTheme = Confingurations.getTheme().getThemeName(); + String selectedTheme = Configurations.getTheme().getThemeName(); switch (selectedTheme.toLowerCase()) { case "light" -> UIManager.setLookAndFeel(new FlatIntelliJLaf()); @@ -83,4 +83,4 @@ private static void updateTheme() { ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); } } -} \ No newline at end of file +} diff --git a/src/main/java/backupmanager/Managers/WebsiteManager.java b/src/main/java/backupmanager/Managers/WebsiteManager.java index b00235d9..412dcbb3 100644 --- a/src/main/java/backupmanager/Managers/WebsiteManager.java +++ b/src/main/java/backupmanager/Managers/WebsiteManager.java @@ -11,8 +11,8 @@ import org.slf4j.LoggerFactory; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class WebsiteManager { private static final Logger logger = LoggerFactory.getLogger(WebsiteManager.class); @@ -27,7 +27,7 @@ public static void openWebSite(String reportUrl) { } } catch (IOException | URISyntaxException e) { logger.error("Failed to open the web page: " + e.getMessage(), e); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_OPENING_WEBSITE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_OPENING_WEBSITE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } @@ -44,15 +44,15 @@ public static void sendEmail() { desktop.mail(uri); } catch (IOException | URISyntaxException ex) { logger.error("Failed to send email: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { logger.warn("Mail action is unsupported in your system's desktop environment."); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { logger.warn("Desktop integration is unsupported on this system."); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } diff --git a/src/main/java/backupmanager/Services/BackgroundService.java b/src/main/java/backupmanager/Services/BackgroundService.java index 99533f27..566a3caf 100644 --- a/src/main/java/backupmanager/Services/BackgroundService.java +++ b/src/main/java/backupmanager/Services/BackgroundService.java @@ -14,15 +14,14 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.gui.Controllers.TrayController; import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; -import backupmanager.Enums.ConfigKey; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.Controllers.TrayController; public class BackgroundService { private static final Logger logger = LoggerFactory.getLogger(BackgroundService.class); @@ -30,7 +29,7 @@ public class BackgroundService { private ScheduledExecutorService scheduler; private TrayController trayIcon; - private final JSONConfigReader jsonConfig = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private final JsonConfig jsonConfig = JsonConfig.getInstance(); private final AtomicBoolean isBackingUp = new AtomicBoolean(false); public void start(TrayController trayIcon) throws IOException { diff --git a/src/main/java/backupmanager/Services/BackupService.java b/src/main/java/backupmanager/Services/BackupService.java index 696b68c7..3cc9dfb9 100644 --- a/src/main/java/backupmanager/Services/BackupService.java +++ b/src/main/java/backupmanager/Services/BackupService.java @@ -4,8 +4,8 @@ import java.util.List; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -18,9 +18,13 @@ public boolean isRunning(String name) { return RunningBackupService.getRunningBackupByName(name).isPresent(); } + public void deleteBackup(int id) { + BackupConfigurationRepository.deleteBackup(id); + } + public void deleteBackups(List names) { names.forEach(name -> { - ConfigurationBackup backup = BackupConfigurationRepository.getBackupByName(name); + ConfigurationBackup backup = getBackupByName(name); if (backup != null) { BackupConfigurationRepository.deleteBackup(backup.getId()); } @@ -28,22 +32,26 @@ public void deleteBackups(List names) { } public String getBackupDetails(String name) { - ConfigurationBackup backup = BackupConfigurationRepository.getBackupByName(name); + ConfigurationBackup backup = getBackupByName(name); return buildDetails(backup); } + public ConfigurationBackup getBackupByName(String name) { + return BackupConfigurationRepository.getBackupByName(name); + } + public String buildDetails(ConfigurationBackup backup) { - String backupNameStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.BACKUP_NAME_DETAIL); - String initialPathStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.INITIAL_PATH_DETAIL); - String destinationPathStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.DESTINATION_PATH_DETAIL); - String lastBackupStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.LAST_BACKUP_DETAIL); - String nextBackupStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.NEXT_BACKUP_DATE_DETAIL); - String timeIntervalBackupStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.TIME_INTERVAL_DETAIL); - String creationDateStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.CREATION_DATE_DETAIL); - String lastUpdateDateStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.LAST_UPDATE_DATE_DETAIL); - String backupCountStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.BACKUP_COUNT_DETAIL); - String notesStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.NOTES_DETAIL); - String maxBackupsToKeepStr = TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.MAX_BACKUPS_TO_KEEP_DETAIL); + String backupNameStr = TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_NAME_DETAIL); + String initialPathStr = TCategory.BACKUP_LIST.getTranslation(TKey.INITIAL_PATH_DETAIL); + String destinationPathStr = TCategory.BACKUP_LIST.getTranslation(TKey.DESTINATION_PATH_DETAIL); + String lastBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.LAST_BACKUP_DETAIL); + String nextBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.NEXT_BACKUP_DATE_DETAIL); + String timeIntervalBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.TIME_INTERVAL_DETAIL); + String creationDateStr = TCategory.BACKUP_LIST.getTranslation(TKey.CREATION_DATE_DETAIL); + String lastUpdateDateStr = TCategory.BACKUP_LIST.getTranslation(TKey.LAST_UPDATE_DATE_DETAIL); + String backupCountStr = TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_COUNT_DETAIL); + String notesStr = TCategory.BACKUP_LIST.getTranslation(TKey.NOTES_DETAIL); + String maxBackupsToKeepStr = TCategory.BACKUP_LIST.getTranslation(TKey.MAX_BACKUPS_TO_KEEP_DETAIL); return """ @@ -84,5 +92,4 @@ private String formatDate(LocalDateTime date) { private String optionalString(Object value) { return value != null ? value.toString() : "_"; } - } diff --git a/src/main/java/backupmanager/Services/LoginService.java b/src/main/java/backupmanager/Services/LoginService.java index 39bb7538..7300cb38 100644 --- a/src/main/java/backupmanager/Services/LoginService.java +++ b/src/main/java/backupmanager/Services/LoginService.java @@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory; import backupmanager.Email.EmailSender; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Entities.User; import backupmanager.Enums.LanguagesEnum; import backupmanager.database.Repositories.UserRepository; @@ -45,12 +45,12 @@ private void setLanguageBasedOnPcLanguage() { logger.info("Setting default language to: " + language); switch (language) { - case "en" -> Confingurations.setLanguage(LanguagesEnum.ENG); - case "it" -> Confingurations.setLanguage(LanguagesEnum.ITA); - case "es" -> Confingurations.setLanguage(LanguagesEnum.ESP); - case "de" -> Confingurations.setLanguage(LanguagesEnum.DEU); - case "fr" -> Confingurations.setLanguage(LanguagesEnum.FRA); - default -> Confingurations.setLanguage(LanguagesEnum.ENG); + case "en" -> Configurations.setLanguage(LanguagesEnum.ENG); + case "it" -> Configurations.setLanguage(LanguagesEnum.ITA); + case "es" -> Configurations.setLanguage(LanguagesEnum.ESP); + case "de" -> Configurations.setLanguage(LanguagesEnum.DEU); + case "fr" -> Configurations.setLanguage(LanguagesEnum.FRA); + default -> Configurations.setLanguage(LanguagesEnum.ENG); } } diff --git a/src/main/java/backupmanager/Services/PreferenceService.java b/src/main/java/backupmanager/Services/PreferenceService.java index de92149d..27008804 100644 --- a/src/main/java/backupmanager/Services/PreferenceService.java +++ b/src/main/java/backupmanager/Services/PreferenceService.java @@ -3,14 +3,14 @@ import java.io.IOException; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum; +import backupmanager.Enums.Translations; public class PreferenceService { public void updatePreferences(String language, String theme) throws IOException { - Confingurations.setLanguageByLanguageName(language); - Confingurations.setTheme(theme); - TranslationLoaderEnum.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Confingurations.getLanguage().getFileName()); + Configurations.setLanguageByLanguageName(language); + Configurations.setTheme(theme); + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); } } diff --git a/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java index 947f3090..d6f80a31 100644 --- a/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java +++ b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java @@ -1,6 +1,5 @@ package backupmanager.utils.table; -import java.awt.Color; import java.awt.Component; import javax.swing.JProgressBar; diff --git a/src/main/java/backupmanager/database/DatabasePaths.java b/src/main/java/backupmanager/database/DatabasePaths.java index d68405ca..738dec90 100644 --- a/src/main/java/backupmanager/database/DatabasePaths.java +++ b/src/main/java/backupmanager/database/DatabasePaths.java @@ -14,5 +14,4 @@ public static Path getProductionDatabasePath() { public static Path getTestDatabasePath() { return Paths.get("data", "BackupManager.db"); } - } diff --git a/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java b/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java index 451add3d..968d88c6 100644 --- a/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java +++ b/src/main/java/backupmanager/database/Repositories/BackupRequestRepository.java @@ -103,7 +103,6 @@ public static List getRunningBackups() { BackupTriggerType triggeredBy = BackupTriggerType.fromCode(triggeredByInt); backups.add(new BackupRequest(backupRequestId, backupConfigurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage)); - logger.debug("Loaded running backup: backupRequestId={} configurationId={}", backupRequestId, backupConfigurationId); } } @@ -163,7 +162,6 @@ public static List getRequestBackups() { BackupStatus status = BackupStatus.fromCode(statusInt); backups.add(new BackupRequest(backupRequestId, backupConfigurationId, startedDate, completionDate, status, progress, triggeredBy, durationMs, outputPath, unzippedTargetSize, zippedTargetSize, filesCount, errorMessage)); - logger.debug("Loaded running backup: backupRequestId={} configurationId={}", backupRequestId, backupConfigurationId); } } diff --git a/src/main/java/backupmanager/database/Repositories/SubscriptionRepository.java b/src/main/java/backupmanager/database/Repositories/SubscriptionRepository.java index 6dce13ed..2e583767 100644 --- a/src/main/java/backupmanager/database/Repositories/SubscriptionRepository.java +++ b/src/main/java/backupmanager/database/Repositories/SubscriptionRepository.java @@ -68,4 +68,32 @@ public static Subscription getAnySubscriptionValid() { return null; } + + // only for unit tests + public static void insertSubscription(Subscription sub) throws SQLException { + String sql = "INSERT INTO Subscriptions (InsertDate, StartDate, EndDate, CreationType) VALUES (?, ?, ?, ?)"; + try (Connection conn = Database.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.setLong(1, SqlHelper.toMilliseconds(sub.insertDate())); + stmt.setLong(2, SqlHelper.toMilliseconds(sub.startDate())); + stmt.setLong(3, SqlHelper.toMilliseconds(sub.endDate())); + stmt.setString(4, sub.creationType().name()); + stmt.executeUpdate(); + } catch (SQLException e) { + throw new SQLException("Subscription inserting error: " + e.getMessage()); + } + } + + // only for unit tests + public static void deleteSubscriptions() throws SQLException { + String sql = "DELETE FROM Subscriptions"; + try (Connection conn = Database.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + + stmt.executeUpdate(); + } catch (SQLException e) { + throw new SQLException("Subscription deletion error: " + e.getMessage()); + } + } } diff --git a/src/main/java/backupmanager/gui/Controllers/AppController.java b/src/main/java/backupmanager/gui/Controllers/AppController.java index 34798aa2..9b859235 100644 --- a/src/main/java/backupmanager/gui/Controllers/AppController.java +++ b/src/main/java/backupmanager/gui/Controllers/AppController.java @@ -9,16 +9,13 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; -import backupmanager.Enums.ConfigKey; import backupmanager.Enums.SubscriptionStatus; import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Helpers.SubscriptionNotifier; -import backupmanager.Json.JSONConfigReader; import backupmanager.Services.BackgroundService; import backupmanager.gui.frames.BackupManagerGUI; public class AppController { - private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); private static final Logger logger = LoggerFactory.getLogger(AppController.class); private BackupManagerGUI guiInstance; diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index d4e69f2d..866ae4fd 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import javax.swing.JOptionPane; +import javax.swing.JTextField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,8 +13,8 @@ import backupmanager.Entities.TimeInterval; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; @@ -59,6 +60,7 @@ public ConfigurationBackup getBackup(String name, String initialPath, String des } } + @Deprecated public TimeInterval handleTimePickerAction(javax.swing.JDialog dialog, String target, String destination) throws InvalidTimeInterval { TimeInterval time = BackupHelper.openTimePicker(dialog, currentBackup.getTimeIntervalBackup()); if (time == null) throw new InvalidTimeInterval(); @@ -81,7 +83,7 @@ public boolean canDisposeAfterOk(String name, String initialPath, String destina if (create) { if (ConfigurationBackup.getBackupByName(currentBackup.getName()) != null) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.DUPLICATED_BACKUP_NAME_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { BackupHelper.deleteBackup(currentBackup.getName()); } else { @@ -96,6 +98,13 @@ public boolean canDisposeAfterOk(String name, String initialPath, String destina return true; } + public void openFileChooser(JTextField filed, boolean allowFiles) { + String text = BackupOperations.pathSearchWithFileChooser(allowFiles); + if (text != null) { + filed.setText(text); + } + } + public boolean toggleAutomaticBackup(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { currentBackup = getBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); currentBackup.setAutomatic(!currentBackup.isAutomatic()); @@ -118,7 +127,7 @@ public boolean toggleAutomaticBackup(String name, String initialPath, String des public void handleSingleBackupRequest(BackupTable backupTable, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) throws BackupAlreadyRunningException { if (BackupRequestRepository.isAnyBackupRunning()) { - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); throw new BackupAlreadyRunningException(); } diff --git a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java index 52e60e90..4c8d0317 100644 --- a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java +++ b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java @@ -3,16 +3,16 @@ import javax.swing.JOptionPane; import backupmanager.Email.EmailValidator; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class EntryUserController { public boolean isInputOkAndShowErrorIfNecessary(javax.swing.JDialog dialog, String name, String surname, String email) { if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) { - JOptionPane.showMessageDialog(dialog, TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.ERROR_MESSAGE_FOR_MISSING_DATA), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, TCategory.USER_DIALOG.getTranslation(TKey.ERROR_MESSAGE_FOR_MISSING_DATA), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); return false; } else if (!EmailValidator.isValidEmail(email)) { - JOptionPane.showMessageDialog(dialog, TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.ERROR_MESSAGE_FOR_WRONG_EMAIL), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, TCategory.USER_DIALOG.getTranslation(TKey.ERROR_MESSAGE_FOR_WRONG_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); return false; } return true; diff --git a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java index 4866ba45..82a16af8 100644 --- a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java +++ b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java @@ -3,8 +3,9 @@ import javax.swing.JOptionPane; import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; +import backupmanager.gui.simple.TimePickerDialog; public class TimePickerController { @@ -16,6 +17,7 @@ public TimePickerController(TimeInterval timeInterval, boolean closeOk) { this.closeOk = closeOk; } + @Deprecated public void handleOkButton(javax.swing.JDialog dialog, int days, int hours, int minutes) { if (isLongTimeCorrect(days, hours, minutes)) { if (isShortTimeCorrect(days, hours) && !showWarningMessageForShortTimeAndGetIfItOkayResponse(dialog)) @@ -29,12 +31,26 @@ public void handleOkButton(javax.swing.JDialog dialog, int days, int hours, int showErrorMessageForLongTime(dialog); } + public TimeInterval getTimeIntervalIfPossible(TimePickerDialog dialog, int days, int hours, int minutes) { + if (isLongTimeCorrect(days, hours, minutes)) { + if (isShortTimeCorrect(days, hours) && !showWarningMessageForShortTimeAndGetIfItOkayResponse(null)) + return null; + + return new TimeInterval(days, hours, minutes); + } + else + showErrorMessageForLongTime(null); + return null; + } + + @Deprecated public TimeInterval getTimeInterval() { if (closeOk) return timeInterval; return null; } - public void setCloseOk(boolean closeOk) { this.closeOk = closeOk; } + @Deprecated + public void setCloseOk(boolean closeOk) { this.closeOk = closeOk; } private boolean isLongTimeCorrect(int days, int hours, int minutes) { return days >= 0 && hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && @@ -46,11 +62,11 @@ private boolean isShortTimeCorrect(int days, int hours) { } private void showErrorMessageForLongTime(javax.swing.JDialog dialog) { - JOptionPane.showMessageDialog(dialog, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_WRONG_TIME_INTERVAL), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.ERROR_WRONG_TIME_INTERVAL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } private boolean showWarningMessageForShortTimeAndGetIfItOkayResponse(javax.swing.JDialog dialog) { - int response = JOptionPane.showConfirmDialog(dialog, TranslationCategory.DIALOGS.getTranslation(TranslationKey.WARNING_SHORT_TIME_INTERVAL_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.WARNING_GENERIC_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.WARNING_SHORT_TIME_INTERVAL_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); return response == JOptionPane.YES_OPTION; } } diff --git a/src/main/java/backupmanager/gui/Controllers/TrayController.java b/src/main/java/backupmanager/gui/Controllers/TrayController.java index fa74e947..3cb6b827 100644 --- a/src/main/java/backupmanager/gui/Controllers/TrayController.java +++ b/src/main/java/backupmanager/gui/Controllers/TrayController.java @@ -15,8 +15,8 @@ import org.slf4j.LoggerFactory; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class TrayController { @@ -71,9 +71,8 @@ public void mouseClicked(MouseEvent e) { private PopupMenu setupAndGetPopupMenu() { PopupMenu popup = new PopupMenu(); - MenuItem openItem = new MenuItem(TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.OPEN_ACTION)); - MenuItem exitItem = new MenuItem(TranslationCategory.TRAY_ICON.getTranslation(TranslationKey.EXIT_ACTION)); - + MenuItem openItem = new MenuItem(TCategory.TRAY_ICON.getTranslation(TKey.OPEN_ACTION)); + MenuItem exitItem = new MenuItem(TCategory.TRAY_ICON.getTranslation(TKey.EXIT_ACTION)); popup.add(openItem); popup.addSeparator(); diff --git a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java index e5d8028a..5c338585 100644 --- a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java @@ -13,18 +13,17 @@ import backupmanager.gui.Controllers.BackupEntryController; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Json.JsonConfig; public class BackupEntryDialog extends javax.swing.JDialog { private static final Logger logger = LoggerFactory.getLogger(BackupEntryDialog.class); - private static final JSONConfigReader configReader = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + private static final JsonConfig configReader = JsonConfig.getInstance(); private final boolean create; private String backupOnText; @@ -40,7 +39,7 @@ public BackupEntryDialog(java.awt.Frame parent, boolean modal) { initializeDialog(); setAutoBackupOff(); this.create = true; - okButton.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CREATE_BUTTON)); + okButton.setText(TCategory.GENERAL.getTranslation(TKey.CREATE_BUTTON)); } public BackupEntryDialog(java.awt.Frame parent, boolean modal, ConfigurationBackup currentBackup) { @@ -54,7 +53,7 @@ public BackupEntryDialog(java.awt.Frame parent, boolean modal, ConfigurationBack backupNameField.setEditable(false); backupNameField.setFocusable(false); this.create = false; - okButton.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.SAVE_BUTTON)); + okButton.setText(TCategory.GENERAL.getTranslation(TKey.SAVE_BUTTON)); } private void setAutoBackupPreference(boolean option) { @@ -93,7 +92,7 @@ private void initializeDialog() { private void setLastBackupLabel(LocalDateTime date) { if (date != null) { String dateStr = date.format(BackupHelper.formatter); - dateStr = TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.LAST_BACKUP) + ": " + dateStr; + dateStr = TCategory.BACKUP_ENTRY.getTranslation(TKey.LAST_BACKUP) + ": " + dateStr; lastBackupLabel.setText(dateStr); } else lastBackupLabel.setText(""); @@ -147,7 +146,7 @@ private void setAutoBackupOff() { } private void disableTimePickerButton() { - btnTimePicker.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.TIME_PICKER_TOOLTIP)); + btnTimePicker.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.TIME_PICKER_TOOLTIP)); btnTimePicker.setEnabled(false); } @@ -197,28 +196,28 @@ private void setSvgImages() { } private void setTranslations() { - backupOnText = TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.AUTO_BACKUP_BUTTON_ON); - backupOffText = TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.AUTO_BACKUP_BUTTON_OFF); - btnPathSearch1.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.INITIAL_FILE_CHOOSER_TOOLTIP)); - btnPathSearch2.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.DESTINATION_FILE_CHOOSER_TOOLTIP)); - startPathField.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.INITIAL_PATH_TOOLTIP)); - destinationPathField.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.DESTINATION_PATH_TOOLTIP)); - backupNoteTextArea.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.NOTES_TOOLTIP)); - singleBackup.setText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.SINGLE_BACKUP_BUTTON)); - singleBackup.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.SINGLE_BACKUP_TOOLTIP)); - toggleAutoBackup.setText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.AUTO_BACKUP_BUTTON_OFF)); - toggleAutoBackup.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.AUTO_BACKUP_TOOLTIP)); - jLabel2.setText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.NOTES) + ":"); - lastBackupLabel.setText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.LAST_BACKUP) + ": "); - setTitle(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.PAGE_TITLE)); - startPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.INITIAL_PATH_PLACEHOLDER)); - destinationPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.DESTINATION_PATH_PLACEHOLDER)); - btnTimePicker.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.TIME_PICKER_TOOLTIP)); - maxBackupCountSpinner.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.MAX_BACKUPS_TO_KEEP_TOOLTIP) + "\n" + TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.SPINNER_TOOLTIP)); - jLabel4.setText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.MAX_BACKUPS_TO_KEEP)); - closeButton.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CLOSE_BUTTON)); - backupNameField.setToolTipText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.BACKUP_NAME_TOOLTIP)); - backupNameField.setHintText(TranslationCategory.BACKUP_ENTRY.getTranslation(TranslationKey.BACKUP_NAME)); + backupOnText = TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_ON); + backupOffText = TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_OFF); + btnPathSearch1.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_FILE_CHOOSER_TOOLTIP)); + btnPathSearch2.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_FILE_CHOOSER_TOOLTIP)); + startPathField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_PATH_TOOLTIP)); + destinationPathField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_PATH_TOOLTIP)); + backupNoteTextArea.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.NOTES_TOOLTIP)); + singleBackup.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.SINGLE_BACKUP_BUTTON)); + singleBackup.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.SINGLE_BACKUP_TOOLTIP)); + toggleAutoBackup.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_OFF)); + toggleAutoBackup.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_TOOLTIP)); + jLabel2.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.NOTES) + ":"); + lastBackupLabel.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.LAST_BACKUP) + ": "); + setTitle(TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_TITLE)); + startPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_PATH_PLACEHOLDER)); + destinationPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_PATH_PLACEHOLDER)); + btnTimePicker.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.TIME_PICKER_TOOLTIP)); + maxBackupCountSpinner.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.MAX_BACKUPS_TO_KEEP_TOOLTIP) + "\n" + TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); + jLabel4.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.MAX_BACKUPS_TO_KEEP)); + closeButton.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); + backupNameField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.BACKUP_NAME_TOOLTIP)); + backupNameField.setHintText(TCategory.BACKUP_ENTRY.getTranslation(TKey.BACKUP_NAME)); } /** diff --git a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java index 62ddd890..a3b0a45f 100644 --- a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java @@ -4,8 +4,8 @@ import backupmanager.LimitDocument; import backupmanager.gui.Controllers.EntryUserController; import backupmanager.Entities.User; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; public class EntryUserDialog extends javax.swing.JDialog { @@ -29,10 +29,10 @@ public EntryUserDialog(java.awt.Frame parent, boolean modal) { } private void setTranslactions() { - setTitle(TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.USER_TITLE)); - nameLabel.setText(TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.USER_NAME)); - surnameLabel.setText(TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.USER_SURNAME)); - emailLabel.setText(TranslationCategory.USER_DIALOG.getTranslation(TranslationKey.USER_EMAIL)); + setTitle(TCategory.USER_DIALOG.getTranslation(TKey.USER_TITLE)); + nameLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_NAME)); + surnameLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_SURNAME)); + emailLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_EMAIL)); } public User getUser() { diff --git a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java index 34a16e87..a227a95b 100644 --- a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java +++ b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java @@ -2,11 +2,11 @@ import backupmanager.gui.Controllers.GuiController; import backupmanager.gui.Controllers.PreferenceController; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.ThemesEnum; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Managers.ThemeManager; import backupmanager.Services.PreferenceService; import backupmanager.gui.frames.BackupManagerGUI; @@ -145,7 +145,7 @@ private void setLanguages() { languagesComboBox.addItem(LanguagesEnum.DEU.getLanguageName()); languagesComboBox.addItem(LanguagesEnum.FRA.getLanguageName()); - languagesComboBox.setSelectedItem(Confingurations.getLanguage().getLanguageName()); + languagesComboBox.setSelectedItem(Configurations.getLanguage().getLanguageName()); } private void setThemes() { @@ -160,15 +160,15 @@ private void setThemes() { themesComboBox.addItem(ThemesEnum.SOLARIZED_DARK.getThemeName()); themesComboBox.addItem(ThemesEnum.SOLARIZED_LIGHT.getThemeName()); - themesComboBox.setSelectedItem(Confingurations.getTheme().getThemeName()); + themesComboBox.setSelectedItem(Configurations.getTheme().getThemeName()); } private void setTranslations() { - setTitle(TranslationCategory.PREFERENCES_DIALOG.getTranslation(TranslationKey.PREFERENCES_TITLE)); - applyBtn.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.APPLY_BUTTON)); - closeBtn.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CLOSE_BUTTON)); - jLabel1.setText(TranslationCategory.PREFERENCES_DIALOG.getTranslation(TranslationKey.LANGUAGE)); - jLabel2.setText(TranslationCategory.PREFERENCES_DIALOG.getTranslation(TranslationKey.THEME)); + setTitle(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.PREFERENCES_TITLE)); + applyBtn.setText(TCategory.GENERAL.getTranslation(TKey.APPLY_BUTTON)); + closeBtn.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); + jLabel1.setText(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.LANGUAGE)); + jLabel2.setText(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.THEME)); } // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/src/main/java/backupmanager/gui/Dialogs/TimePicker.java b/src/main/java/backupmanager/gui/Dialogs/TimePicker.java index c4781869..8d4e14d1 100644 --- a/src/main/java/backupmanager/gui/Dialogs/TimePicker.java +++ b/src/main/java/backupmanager/gui/Dialogs/TimePicker.java @@ -1,7 +1,7 @@ package backupmanager.gui.Dialogs; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.gui.Controllers.GuiController; import backupmanager.Entities.TimeInterval; @@ -252,16 +252,16 @@ private void minutesSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GE }//GEN-LAST:event_minutesSpinnerStateChanged private void setTranslations() { - setTitle(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.TIME_INTERVAL_TITLE)); - jTextArea1.setText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.DESCRIPTION)); - daysSpinner.setToolTipText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.SPINNER_TOOLTIP)); - hoursSpinner.setToolTipText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.SPINNER_TOOLTIP)); - minutesSpinner.setToolTipText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.SPINNER_TOOLTIP)); - btnOk.setText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.OK_BUTTON)); - jButton2.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CANCEL_BUTTON)); - jLabel1.setText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.DAYS)); - jLabel2.setText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.HOURS)); - jLabel3.setText(TranslationCategory.TIME_PICKER_DIALOG.getTranslation(TranslationKey.MINUTES)); + setTitle(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.TIME_INTERVAL_TITLE)); + jTextArea1.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.DESCRIPTION)); + daysSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); + hoursSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); + minutesSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); + btnOk.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.OK_BUTTON)); + jButton2.setText(TCategory.GENERAL.getTranslation(TKey.CANCEL_BUTTON)); + jLabel1.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.DAYS)); + jLabel2.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.HOURS)); + jLabel3.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.MINUTES)); } // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index c01c68aa..1e07a010 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -49,6 +49,7 @@ private JTextPane createText(String text) { pane.setOpaque(false); pane.setCaret(new DefaultCaret() { + @Override public void paint(java.awt.Graphics g) {} }); diff --git a/src/main/java/backupmanager/gui/component/Subscription.java b/src/main/java/backupmanager/gui/component/Subscription.java index d2ba0519..00a49703 100644 --- a/src/main/java/backupmanager/gui/component/Subscription.java +++ b/src/main/java/backupmanager/gui/component/Subscription.java @@ -11,6 +11,8 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.SubscriptionStatus; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Managers.WebsiteManager; import net.miginfocom.swing.MigLayout; @@ -34,9 +36,7 @@ private void init() { String from = formatDate(subscription != null ? subscription.startDate() : null); String to = formatDate(subscription != null ? subscription.endDate() : null); - JTextPane description = createHtmlPane( - buildHtml(status, statusString, from, to) - ); + JTextPane description = createHtmlPane(buildHtml(status, statusString, from, to)); add(description, "growx"); } @@ -80,25 +80,30 @@ private String buildHtml(SubscriptionStatus status, String statusText, String va return """
- Subscription status: + %s: %s

- Valid from: %s
- Valid to: %s
+ %s: %s
+ %s: %s

- Contact us - to extend the subscription period. + %s + %s
""".formatted( + TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_STATUS), statusColor, statusText, + TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_VALID_FROM), validFrom, + TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_VALID_TO), validTo, ConfigKey.EMAIL.getValue(), - subject + subject, + TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_CONTACT_US), + TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_TO_EXTEND) ); } diff --git a/src/main/java/backupmanager/gui/forms/CustomForm.java b/src/main/java/backupmanager/gui/forms/CustomForm.java new file mode 100644 index 00000000..9576f363 --- /dev/null +++ b/src/main/java/backupmanager/gui/forms/CustomForm.java @@ -0,0 +1,61 @@ +package backupmanager.gui.forms; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextPane; + +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.gui.system.Form; +import net.miginfocom.swing.MigLayout; + +public abstract class CustomForm extends Form { + + private JLabel lbTitle; + private JTextPane text; + + protected CustomForm() {} + + protected void build() { + init(); + setTranslations(); + } + + protected abstract void init(); + protected abstract void setTranslations(); + + @Override + public void formInit() { + loadData(); + } + + @Override + public void formRefresh() { + loadData(); + } + + protected abstract void loadData(); + + protected JPanel createInfo(String title, String description, int level) { + JPanel panel = new JPanel(new MigLayout("fillx,wrap", "[fill]")); + lbTitle = new JLabel(title); + text = new JTextPane(); + text.setText(description); + text.setEditable(false); + text.setBorder(BorderFactory.createEmptyBorder()); + lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + + "font:bold +" + (4 - level)); + panel.add(lbTitle); + panel.add(text, "width 500"); + return panel; + } + + protected void editTitle(String title) { + lbTitle.setText(title); + } + + protected void editDescription(String description) { + text.setText(description); + } +} diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index ad139190..02b2d01e 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -33,18 +33,18 @@ import backupmanager.gui.component.chart.themes.ColorThemes; import backupmanager.gui.component.chart.themes.DefaultChartTheme; import backupmanager.gui.component.dashboard.CardBox; -import backupmanager.gui.system.Form; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; @SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") -public class FormBackupDashboard extends Form { +public class FormBackupDashboard extends CustomForm { public FormBackupDashboard() { - init(); + build(); } - private void init() { + @Override + protected void init() { setLayout(new MigLayout("wrap,fill", "[fill]", "[grow 0][fill]")); createTitle(); createPanelLayout(); @@ -55,16 +55,7 @@ private void init() { } @Override - public void formInit() { - loadData(); - } - - @Override - public void formRefresh() { - loadData(); - } - - private void loadData() { + protected void loadData() { List configurations = BackupConfigurationRepository.getBackupList(); List requests = BackupRequestRepository.getRequestBackups(); BackupAnalyticsSnapshot snapshot = BackupAnalyticsService.buildSnapshot(requests); @@ -102,7 +93,7 @@ private void loadData() { timeSeriesChart.setDataset(BackupAnalyticsService.buildDurationTrendDataset(snapshot.durationTrend())); } - private void createTitle() { + protected void createTitle() { JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); @@ -233,6 +224,11 @@ private Icon createIcon(String icon, Color color) { return new FlatSVGIcon(icon, 20, 20).setColorFilter(new FlatSVGIcon.ColorFilter(color1 -> color)); } + @Override + protected void setTranslations() { + + } + private JPanel panelLayout; private CardBox cardBox; diff --git a/src/main/java/backupmanager/gui/forms/FormTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java similarity index 75% rename from src/main/java/backupmanager/gui/forms/FormTable.java rename to src/main/java/backupmanager/gui/forms/FormBackupTable.java index ed6c645e..dab2b488 100644 --- a/src/main/java/backupmanager/gui/forms/FormTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -20,6 +20,8 @@ import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingConstants; +import javax.swing.event.DocumentListener; +import javax.swing.event.DocumentEvent; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; @@ -31,6 +33,8 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Services.BackupService; @@ -38,42 +42,41 @@ import backupmanager.gui.frames.Controllers.BackupPopupController; import backupmanager.gui.sample.csv.ConfigurationBackupDataTable; import backupmanager.gui.svg.SVGButton; -import backupmanager.gui.system.Form; import backupmanager.utils.SystemForm; import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; import raven.swingpack.JPagination; @SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) -public class FormTable extends Form { +public class FormBackupTable extends CustomForm { - private static final Logger logger = LoggerFactory.getLogger(FormTable.class); + private static final Logger logger = LoggerFactory.getLogger(FormBackupTable.class); - private BackupManagerController managerController; + private final BackupManagerController managerController; + private final int COL_LAST_RUN = 3; private final int COL_AUTOMATIC = 4; private final int COL_NEXT_RUN = 5; - private final int COL_LAST_RUN = 3; - private BackupService backupService; + private final BackupService backupService; private List backups; - private int autoColumnIndex; - public FormTable() { - init(); + public FormBackupTable() { + backupService = new BackupService(); + managerController = new BackupManagerController(backupService); + + build(); } - private void init() { + @Override + protected void init() { setLayout(new MigLayout( "fill,wrap", "[fill]", "[][grow 100,fill][grow 0]" )); - add(createInfo("Backup List", "A table is a user interface component that displays a collection of records in a structured, tabular format. It allows users to view, sort, and manage data or other resources.", 1)); + add(createInfo("Title", "Description", 1)); add(createBorder(createBasicTable()), "gapx 7 7, grow"); add(createBorder(createDetails()), "gapx 7 7, hmin 150"); - - backupService = new BackupService(); - managerController = new BackupManagerController(backupService); } @Override @@ -81,8 +84,6 @@ public void formInit() { DefaultTableModel model = (DefaultTableModel) backupTable.getModel(); model.setColumnIdentifiers(ConfigurationBackup.getCSVHeaderArray()); - autoColumnIndex = COL_AUTOMATIC; - backupTable.createDefaultColumnsFromModel(); formRefresh(); @@ -91,10 +92,15 @@ public void formInit() { @Override public void formRefresh() { backups = backupService.getAllBackups(); - showData(pagination.getSelectedPage()); + loadData(); } - private void showData(int page) { + @Override + protected void loadData() { + loadTable(pagination.getSelectedPage()); + } + + private void loadTable(int page) { if (backups != null) { ConfigurationBackupDataTable res = ConfigurationBackupDataTable.create(backups, page, limit); lbTotalPage.setText(DecimalFormat.getInstance().format(res.getTotal())); @@ -108,20 +114,6 @@ private void showData(int page) { } } - private JPanel createInfo(String title, String description, int level) { - JPanel panel = new JPanel(new MigLayout("fillx,wrap", "[fill]")); - JLabel lbTitle = new JLabel(title); - JTextPane text = new JTextPane(); - text.setText(description); - text.setEditable(false); - text.setBorder(BorderFactory.createEmptyBorder()); - lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold +" + (4 - level)); - panel.add(lbTitle); - panel.add(text, "width 500"); - return panel; - } - private Component createBorder(Component component) { JPanel panel = new JPanel(new MigLayout("fill,insets 7 0 7 0", "[fill]", "[fill]")); panel.add(component); @@ -185,7 +177,7 @@ public void mouseClicked(java.awt.event.MouseEvent e) { if (e.getClickCount() == 2 && table.getSelectedRow() != -1) { int row = table.getSelectedRow(); logger.debug("Double clicked row: " + row); - showCreateModal(); + showEditModal(backups.get(row)); } } }); @@ -199,8 +191,8 @@ public void mouseClicked(java.awt.event.MouseEvent e) { public void actionPerformed(java.awt.event.ActionEvent e) { int row = table.getSelectedRow(); if (row != -1) { - ((DefaultTableModel) table.getModel()).removeRow(row); logger.debug("Deleted row: " + row); + showDeleteConfirmation(); } } }); @@ -249,9 +241,7 @@ protected int getAlignment(int column) { // create pagination pagination = new JPagination(11, 1, 1); pagination.setMinimumSize(pagination.getPreferredSize()); - pagination.addChangeListener(e -> { - showData(pagination.getSelectedPage()); - }); + pagination.addChangeListener(e -> loadData()); JPanel panelPage = new JPanel(new MigLayout("insets 5 15 5 15", "[][]push[pref!]")); lbTotalPage = new JLabel("0"); pagination.putClientProperty(FlatClientProperties.STYLE, "" + @@ -325,22 +315,22 @@ protected void setValue(Object value) { private void buildTablePopupMenu() { JPopupMenu popupMenu = new JPopupMenu(); - JMenuItem itemEdit = new JMenuItem("Edit"); - JMenuItem itemDelete = new JMenuItem("Delete"); - JMenuItem itemDuplicate = new JMenuItem("Duplicate"); - JMenuItem itemRename = new JMenuItem("Rename"); - JMenuItem itemOpenTargetPath = new JMenuItem("Open target path"); - JMenuItem itemOpenDestinationPath = new JMenuItem("Open destination path"); + itemEdit = new JMenuItem("Edit"); + itemDelete = new JMenuItem("Delete"); + itemDuplicate = new JMenuItem("Duplicate"); + itemRename = new JMenuItem("Rename"); + itemOpenTargetPath = new JMenuItem("Open target path"); + itemOpenDestinationPath = new JMenuItem("Open destination path"); - JMenu itemBackup = new JMenu("Backup"); - JMenuItem itemRunSingleBackup = new JMenuItem("Run single backup"); - JCheckBoxMenuItem itemAutoBackup = new JCheckBoxMenuItem("Auto backup"); - JMenuItem itemInterruptBackup = new JMenuItem("Interrupt backup process"); + itemBackup = new JMenu("Backup"); + itemRunSingleBackup = new JMenuItem("Run single backup"); + itemAutoBackup = new JCheckBoxMenuItem("Auto backup"); + itemInterruptBackup = new JMenuItem("Interrupt backup process"); - JMenu itemCopyText = new JMenu("Copy text"); - JMenuItem itemCopyBackupName = new JMenuItem("Copy backup name"); - JMenuItem itemCopyTargetPath = new JMenuItem("Copy target path"); - JMenuItem itemCopyDestinationPath = new JMenuItem("Copy destination path"); + itemCopyText = new JMenu("Copy text"); + itemCopyBackupName = new JMenuItem("Copy backup name"); + itemCopyTargetPath = new JMenuItem("Copy target path"); + itemCopyDestinationPath = new JMenuItem("Copy destination path"); popupMenu.add(itemEdit); popupMenu.add(itemDelete); @@ -431,6 +421,8 @@ private void handleToggle() { ConfigurationBackup backup = getBackupFromTableRow(selectedRow); BackupPopupController.popupItemAutoBackup(backup); + + formRefresh(); } private ConfigurationBackup getBackupFromTableRow(int row) { @@ -458,12 +450,12 @@ private Component createDetails() { private Component createHeaderAction() { JPanel panel = new JPanel(new MigLayout("insets 5 20 5 20", "[fill,300]push[][]")); - JTextField txtSearch = new JTextField(); + txtSearch = new JTextField(); txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Search..."); txtSearch.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSVGIcon("icons/search.svg", 0.4f)); - SVGButton cmdCreate = new SVGButton("Create"); - SVGButton cmdEdit = new SVGButton("Edit"); - SVGButton cmdDelete = new SVGButton("Delete"); + cmdCreate = new SVGButton("Create"); + cmdEdit = new SVGButton("Edit"); + cmdDelete = new SVGButton("Delete"); cmdCreate.setSvgImage("icons/add.svg", 16, 16); cmdEdit.setSvgImage("icons/edit.svg", 16, 16); @@ -480,6 +472,25 @@ private Component createHeaderAction() { panel.add(cmdEdit); panel.add(cmdDelete); + txtSearch.getDocument().addDocumentListener(new DocumentListener() { + private void update() { + if (backups == null) + return; + + String research = txtSearch.getText(); + backups = backupService.getAllBackups(); + backups = managerController.researchInTableAndGet(backups, research); + loadData(); + } + + @Override + public void insertUpdate(DocumentEvent e) { update(); } + @Override + public void removeUpdate(DocumentEvent e) { update(); } + @Override + public void changedUpdate(DocumentEvent e) { update(); } + }); + panel.putClientProperty(FlatClientProperties.STYLE, "" + "background:null;"); return panel; @@ -487,10 +498,22 @@ private Component createHeaderAction() { public void showCreateModal() { managerController.showCreateModal(this); + loadData(); + } + + private void showEditModal(ConfigurationBackup backup) { + managerController.showEditModal(this, backup); + loadData(); } private void showEditModal() { - managerController.showCreateModal(this); + int selectedRow = backupTable.getSelectedRow(); + if (selectedRow < 0) + return; + + ConfigurationBackup backup = getBackupFromTableRow(selectedRow); + managerController.showEditModal(this, backup); + loadData(); } private void showDeleteConfirmation() { @@ -500,6 +523,35 @@ private void showDeleteConfirmation() { ConfigurationBackup backup = getBackupFromTableRow(selectedRow); BackupHelper.deleteBackupWithConfirmition(backup); + formRefresh(); + } + + @Override + protected void setTranslations() { + editTitle(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_TITLE)); + editDescription(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_DESCRIPTION)); + + txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_PLACEHOLDER)); + txtSearch.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_TOOLTIP)); + + cmdCreate.setText(TCategory.GENERAL.getTranslation(TKey.CREATE_BUTTON)); + cmdEdit.setText(TCategory.GENERAL.getTranslation(TKey.EDIT_BUTTON)); + cmdDelete.setText(TCategory.GENERAL.getTranslation(TKey.DELETE_BUTTON)); + + itemEdit.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EDIT_POPUP)); + itemDelete.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DELETE_POPUP)); + itemDuplicate.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DUPLICATE_POPUP)); + itemRename.setText(TCategory.BACKUP_LIST.getTranslation(TKey.RENAME_BACKUP_POPUP)); + itemOpenTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_INITIAL_FOLDER_POPUP)); + itemOpenDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_DESTINATION_FOLDER_POPUP)); + itemBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_POPUP)); + itemRunSingleBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.SINGLE_BACKUP_POPUP)); + itemAutoBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.AUTO_BACKUP_POPUP)); + itemInterruptBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.INTERRUPT_POPUP)); + itemCopyText.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_TEXT_POPUP)); + itemCopyBackupName.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_BACKUP_NAME_POPUP)); + itemCopyTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_INITIAL_PATH_POPUP)); + itemCopyDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); } private final int limit = 50; @@ -507,4 +559,23 @@ private void showDeleteConfirmation() { private JTable backupTable; private JLabel lbTotalPage; private JTextPane txtDetails; + private JTextField txtSearch; + private SVGButton cmdCreate; + private SVGButton cmdEdit; + private SVGButton cmdDelete; + + private JMenuItem itemEdit; + private JMenuItem itemDelete; + private JMenuItem itemDuplicate; + private JMenuItem itemRename; + private JMenuItem itemOpenTargetPath; + private JMenuItem itemOpenDestinationPath; + private JMenu itemBackup; + private JMenuItem itemRunSingleBackup; + private JCheckBoxMenuItem itemAutoBackup; + private JMenuItem itemInterruptBackup; + private JMenu itemCopyText; + private JMenuItem itemCopyBackupName; + private JMenuItem itemCopyTargetPath; + private JMenuItem itemCopyDestinationPath; } diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java index daebbe58..459d5652 100644 --- a/src/main/java/backupmanager/gui/forms/FormHistory.java +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -1,10 +1,9 @@ package backupmanager.gui.forms; +import java.awt.Component; import java.io.IOException; import java.io.InputStream; -import javax.swing.BorderFactory; -import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; @@ -12,36 +11,26 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Enums.ConfigKey; -import backupmanager.gui.system.Form; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; - @SystemForm(name = "History", description = "application history log") -public class FormHistory extends Form { +public class FormHistory extends CustomForm { public FormHistory() { - init(); - } - - private void init() { - setLayout(new MigLayout("fill,wrap", "[fill]", "[][grow,fill]")); - createTitle(); - createInfo("Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."); - createLogPanel(); - loadLogs(); + build(); } @Override - public void formInit() { - loadLogs(); + protected void init() { + setLayout(new MigLayout("fill,wrap", "[fill]", "[][grow,fill]")); + add(createInfo("Information", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time.", 1)); + add(createLogPanel()); } @Override - public void formRefresh() { - loadLogs(); - } - - private void loadLogs() { + protected void loadData() { try { try (InputStream is = getClass().getClassLoader().getResourceAsStream("res/logs/" + ConfigKey.LOG_FILE_STRING.getValue())) { if (is == null) { @@ -60,43 +49,27 @@ private void loadLogs() { } } - private void createTitle() { - JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); - JLabel title = new JLabel("History"); + private Component createLogPanel() { + JPanel panel = new JPanel( + new MigLayout("fill,insets 5 0 5 0", "[fill]", "[grow]") + ); - title.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold +3"); - - panel.add(title); - add(panel); - } - - private void createInfo(String description) { - JPanel panel = new JPanel(new MigLayout("fillx,wrap", "[fill]")); - JLabel lbTitle = new JLabel("Information"); - JTextPane text = new JTextPane(); - text.setText(description); - text.setEditable(false); - text.setBorder(BorderFactory.createEmptyBorder()); - lbTitle.putClientProperty(FlatClientProperties.STYLE, "" + - "font:bold"); - panel.add(lbTitle); - panel.add(text, "width 500"); - add(panel); - } - - private void createLogPanel() { logsPane = new JTextPane(); logsPane.setEditable(false); logsPane.setContentType("text/plain"); - JScrollPane scroll = new JScrollPane(logsPane); + JScrollPane detailScroll = new JScrollPane(logsPane); + detailScroll.putClientProperty(FlatClientProperties.STYLE, + "arc:10; border:1,1,1,1,$Component.borderColor"); - scroll.putClientProperty(FlatClientProperties.STYLE, - "arc:10;" + - "border:1,1,1,1,$Component.borderColor"); + panel.add(detailScroll, "grow"); + return panel; + } - add(scroll, "grow"); + @Override + protected void setTranslations() { + editTitle(Translations.get(TKey.HISTORY_LOGS_TITLE)); + editDescription(Translations.get(TKey.HISTORY_LOGS_DESCRIPTION)); } private JTextPane logsPane; diff --git a/src/main/java/backupmanager/gui/frames/Login.java b/src/main/java/backupmanager/gui/forms/FormLogin.java similarity index 60% rename from src/main/java/backupmanager/gui/frames/Login.java rename to src/main/java/backupmanager/gui/forms/FormLogin.java index d82ce814..f4ff37ca 100644 --- a/src/main/java/backupmanager/gui/frames/Login.java +++ b/src/main/java/backupmanager/gui/forms/FormLogin.java @@ -1,4 +1,4 @@ -package backupmanager.gui.frames; +package backupmanager.gui.forms; import javax.swing.JButton; import javax.swing.JLabel; @@ -8,21 +8,24 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Entities.User; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.LimitDocument; import backupmanager.Services.LoginService; import backupmanager.gui.menu.MyDrawerBuilder; -import backupmanager.gui.system.Form; import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; -public class Login extends Form { +public class FormLogin extends CustomForm { private final LoginService loginService = new LoginService(); - public Login() { - init(); + public FormLogin() { + build(); } - private void init() { + @Override + protected void init() { setLayout(new MigLayout("al center center")); if (!loginService.isFirstAccess()) { @@ -30,36 +33,35 @@ private void init() { return; } - createLogin(); + loadData(); } - private void createLogin() { + + @Override + protected void loadData() { JPanel panelLogin = new JPanel(new MigLayout()); JPanel loginContent = new JPanel( new MigLayout("fillx,wrap,insets 35 35 25 35", "[fill,300]") ); - JLabel lbTitle = new JLabel("Login"); - JLabel lbDescription = new JLabel("Please enter your data to access the system"); + lbTitle = new JLabel("Login"); + lbDescription = new JLabel("Please enter your data to access the system"); lbTitle.putClientProperty(FlatClientProperties.STYLE, "font:bold +12;"); loginContent.add(lbTitle); loginContent.add(lbDescription); - JTextField txtName = new JTextField(); - JTextField txtSurname = new JTextField(); - JTextField txtEmail = new JTextField(); + txtName = new JTextField(); + txtSurname = new JTextField(); + txtEmail = new JTextField(); + labelName = new JLabel("Name"); + labelSurname = new JLabel("Surname"); + labelEmail = new JLabel("Email"); JButton cmdLogin = new JButton("Login"); - // Placeholder - txtName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your name"); - txtSurname.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your surname"); - txtEmail.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Enter your email"); - - // Style panelLogin.putClientProperty(FlatClientProperties.STYLE, "[light]border:5,5,5,5,shade($Panel.background,10%),,20;" + "[dark]border:5,5,5,5,tint($Panel.background,5%),,20;" + @@ -76,13 +78,17 @@ private void createLogin() { txtEmail.putClientProperty(FlatClientProperties.STYLE, fieldStyle); cmdLogin.putClientProperty(FlatClientProperties.STYLE, fieldStyle); - loginContent.add(new JLabel("Name"), "gapy 25"); + txtName.setDocument(new LimitDocument(20)); + txtSurname.setDocument(new LimitDocument(20)); + txtEmail.setDocument(new LimitDocument(32)); + + loginContent.add(labelName, "gapy 25"); loginContent.add(txtName); - loginContent.add(new JLabel("Surname"), "gapy 10"); + loginContent.add(labelSurname, "gapy 10"); loginContent.add(txtSurname); - loginContent.add(new JLabel("Email"), "gapy 10"); + loginContent.add(labelEmail, "gapy 10"); loginContent.add(txtEmail); loginContent.add(cmdLogin, "gapy 20"); @@ -96,9 +102,8 @@ private void createLogin() { String surname = txtSurname.getText().trim(); String email = txtEmail.getText().trim(); - if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) { + if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) return; - } User user = new User(name, surname, email); loginService.createUserAndSendEmail(user); @@ -111,4 +116,29 @@ private void showMainForm() { MyDrawerBuilder.getInstance().initHeader(); FormManager.login(); } + + @Override + protected void setTranslations() { + if (lbTitle == null) { + return; + } + + lbTitle.setText(Translations.get(TKey.USER_TITLE)); + lbDescription.setText(Translations.get(TKey.USER_DESCRIPTION)); + txtName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.USER_NAME_PLACEHOLDER)); + txtSurname.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.USER_SURNAME_PLACEHOLDER)); + txtEmail.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.USER_EMAIL_PLACEHOLDER)); + labelName.setText(Translations.get(TKey.USER_NAME)); + labelSurname.setText(Translations.get(TKey.USER_SURNAME)); + labelEmail.setText(Translations.get(TKey.USER_EMAIL)); + } + + private JTextField txtName; + private JTextField txtSurname; + private JTextField txtEmail; + private JLabel labelName; + private JLabel labelSurname; + private JLabel labelEmail; + private JLabel lbTitle; + private JLabel lbDescription; } diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index b62213ae..f2dce63d 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -4,6 +4,7 @@ import java.awt.Component; import java.awt.ComponentOrientation; import java.awt.event.ActionEvent; +import java.lang.reflect.InvocationTargetException; import javax.swing.ButtonGroup; import javax.swing.JCheckBox; @@ -18,6 +19,9 @@ import javax.swing.UIManager; import javax.swing.border.TitledBorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.FlatDarculaLaf; import com.formdev.flatlaf.FlatDarkLaf; @@ -27,11 +31,10 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; -import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.ScaledEmptyBorder; import backupmanager.gui.component.AccentColorIcon; -import backupmanager.gui.system.Form; +import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.system.FormManager; import backupmanager.gui.themes.PanelThemes; import backupmanager.utils.DemoPreferences; @@ -51,13 +54,16 @@ import raven.modal.option.Option; @SystemForm(name = "Setting", description = "application setting and configuration", tags = {"themes", "options"}) -public class FormSetting extends Form { +public class FormSetting extends CustomForm { + + private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); public FormSetting() { - init(); + build(); } - private void init() { + @Override + protected void init() { setLayout(new MigLayout("fill", "[fill][fill,grow 0,250:250]", "[fill]")); tabbedPane = new JTabbedPane(); tabbedPane.putClientProperty(FlatClientProperties.STYLE, "" + @@ -69,6 +75,9 @@ private void init() { add(createThemes()); } + @Override + protected void loadData() {} + private JPanel createLayoutOption() { JPanel panel = new JPanel(new MigLayout("wrap,fillx", "[fill]")); panel.add(createWindowsLayout()); @@ -198,7 +207,7 @@ private Component createModalDefaultOption() { private Component createLanguageOption() { JPanel panel = new JPanel(new MigLayout()); panel.setBorder(new TitledBorder("Language")); - JComboBox languageCombo = new JComboBox(); + JComboBox languageCombo = new JComboBox<>(); initComboItem(languageCombo); panel.add(languageCombo); @@ -206,7 +215,7 @@ private Component createLanguageOption() { return panel; } - private void initComboItem(JComboBox combo) { + private void initComboItem(JComboBox combo) { combo.addItem("English"); combo.addItem("Italiano"); combo.addItem("Español"); @@ -221,11 +230,11 @@ private JPanel createStyleOption() { return panel; } - private static String[] accentColorKeys = { + private static final String[] accentColorKeys = { "Demo.accent.default", "Demo.accent.blue", "Demo.accent.purple", "Demo.accent.red", "Demo.accent.orange", "Demo.accent.yellow", "Demo.accent.green", }; - private static String[] accentColorNames = { + private static final String[] accentColorNames = { "Default", "Blue", "Purple", "Red", "Orange", "Yellow", "Green", }; private final JToggleButton[] accentColorButtons = new JToggleButton[accentColorKeys.length]; @@ -404,7 +413,7 @@ private void accentColorChanged(ActionEvent e) { break; } } - DemoPreferences.accentColor = (accentColorKey != null && accentColorKey != accentColorKeys[0]) + DemoPreferences.accentColor = (accentColorKey != null && !accentColorKey.equals(accentColorKeys[0])) ? UIManager.getColor(accentColorKey) : null; applyAccentColor(); @@ -412,13 +421,14 @@ private void accentColorChanged(ActionEvent e) { private void applyAccentColor() { Class lafClass = UIManager.getLookAndFeel().getClass(); + DemoPreferences.updateAccentColor(DemoPreferences.accentColor); try { - DemoPreferences.updateAccentColor(DemoPreferences.accentColor); FlatLaf.setup(lafClass.getDeclaredConstructor().newInstance()); - FlatLaf.updateUI(); - } catch (Exception ex) { - LoggingFacade.INSTANCE.logSevere(null, ex); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + logger.warn("Error while trying to apply the accent color: {}", e); } + FlatLaf.updateUI(); } private void updateAccentColorButtons() { @@ -430,8 +440,8 @@ private void updateAccentColorButtons() { lafClass == FlatDarculaLaf.class || lafClass == FlatMacLightLaf.class || lafClass == FlatMacDarkLaf.class; - for (int i = 0; i < accentColorButtons.length; i++) { - accentColorButtons[i].setEnabled(isAccentColorSupported); + for (JToggleButton btn : accentColorButtons) { + btn.setEnabled(isAccentColorSupported); } if (accentColorCustomButton != null) { accentColorCustomButton.setEnabled(isAccentColorSupported); @@ -443,7 +453,7 @@ private JPanel createThemes() { final PanelThemes panelThemes = new PanelThemes(); JPanel panelHeader = new JPanel(new MigLayout("fillx,insets 3", "[grow 0]push[]")); panelHeader.add(new JLabel("Themes")); - JComboBox combo = new JComboBox(new Object[]{"All", "Light", "Dark"}); + JComboBox combo = new JComboBox<>(new Object[]{"All", "Light", "Dark"}); combo.addActionListener(e -> { panelThemes.updateThemesList(combo.getSelectedIndex()); }); @@ -453,5 +463,10 @@ private JPanel createThemes() { return panel; } + @Override + protected void setTranslations() { + + } + private JTabbedPane tabbedPane; } diff --git a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java index 2a74bdd8..706b3506 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java @@ -24,16 +24,16 @@ import backupmanager.gui.Controllers.GuiController; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.MenuItems; -import backupmanager.Enums.TranslationLoaderEnum; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; import static backupmanager.Helpers.BackupHelper.formatter; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Json.JsonConfig; import backupmanager.Managers.ExceptionManager; import backupmanager.Managers.ExportManager; import backupmanager.Managers.ThemeManager; @@ -104,11 +104,11 @@ private void setScreenSize() { public void reloadPreferences() { logger.info("Reloading preferences"); - Confingurations.updateAllConfigurations(); + Configurations.updateAllConfigurations(); // load language try { - TranslationLoaderEnum.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Confingurations.getLanguage().getFileName()); + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); setTranslations(); } catch (IOException ex) { logger.error("An error occurred during reloading preferences operation: " + ex.getMessage(), ex); @@ -894,7 +894,6 @@ private void tableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event if (SwingUtilities.isRightMouseButton(evt)) { logger.debug("Right click on row: " + selectedRow); - AutoBackupMenuItem.setSelected(backupManagerController.isAutomaticBackup(backupName)); table.setRowSelectionInterval(selectedRow, selectedRow); // select clicked row TablePopup.show(evt.getComponent(), evt.getX(), evt.getY()); @@ -1002,7 +1001,6 @@ private void MenuBuyMeACoffeDonateActionPerformed(java.awt.event.ActionEvent evt private void researchFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_researchFieldKeyReleased String research = researchField.getText(); - backupManagerController.researchInTable(research); }//GEN-LAST:event_researchFieldKeyReleased private void setTranslations() { @@ -1011,65 +1009,65 @@ private void setTranslations() { displayBackupList(); // general - jLabel3.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.VERSION) + " " + ConfigKey.VERSION.getValue()); + jLabel3.setText(TCategory.GENERAL.getTranslation(TKey.VERSION) + " " + ConfigKey.VERSION.getValue()); // menu - jMenu1.setText(TranslationCategory.MENU.getTranslation(TranslationKey.FILE)); - jMenu2.setText(TranslationCategory.MENU.getTranslation(TranslationKey.OPTIONS)); - jMenu3.setText(TranslationCategory.MENU.getTranslation(TranslationKey.ABOUT)); - jMenu5.setText(TranslationCategory.MENU.getTranslation(TranslationKey.HELP)); + jMenu1.setText(TCategory.MENU.getTranslation(TKey.FILE)); + jMenu2.setText(TCategory.MENU.getTranslation(TKey.OPTIONS)); + jMenu3.setText(TCategory.MENU.getTranslation(TKey.ABOUT)); + jMenu5.setText(TCategory.MENU.getTranslation(TKey.HELP)); // menu items - MenuBugReport.setText(TranslationCategory.MENU.getTranslation(TranslationKey.BUG_REPORT)); - MenuClear.setText(TranslationCategory.MENU.getTranslation(TranslationKey.CLEAR)); - MenuDonate.setText(TranslationCategory.MENU.getTranslation(TranslationKey.DONATE)); - MenuHistory.setText(TranslationCategory.MENU.getTranslation(TranslationKey.HISTORY)); - MenuInfoPage.setText(TranslationCategory.MENU.getTranslation(TranslationKey.INFO_PAGE)); - MenuNew.setText(TranslationCategory.MENU.getTranslation(TranslationKey.NEW)); - MenuQuit.setText(TranslationCategory.MENU.getTranslation(TranslationKey.QUIT)); - MenuSave.setText(TranslationCategory.MENU.getTranslation(TranslationKey.SAVE)); - MenuSaveWithName.setText(TranslationCategory.MENU.getTranslation(TranslationKey.SAVE_WITH_NAME)); - MenuPreferences.setText(TranslationCategory.MENU.getTranslation(TranslationKey.PREFERENCES)); - MenuImport.setText(TranslationCategory.MENU.getTranslation(TranslationKey.IMPORT)); - MenuExport.setText(TranslationCategory.MENU.getTranslation(TranslationKey.EXPORT)); - MenuShare.setText(TranslationCategory.MENU.getTranslation(TranslationKey.SHARE)); - MenuSupport.setText(TranslationCategory.MENU.getTranslation(TranslationKey.SUPPORT)); - MenuWebsite.setText(TranslationCategory.MENU.getTranslation(TranslationKey.WEBSITE)); + MenuBugReport.setText(TCategory.MENU.getTranslation(TKey.BUG_REPORT)); + MenuClear.setText(TCategory.MENU.getTranslation(TKey.CLEAR)); + MenuDonate.setText(TCategory.MENU.getTranslation(TKey.DONATE)); + MenuHistory.setText(TCategory.MENU.getTranslation(TKey.HISTORY)); + MenuInfoPage.setText(TCategory.MENU.getTranslation(TKey.INFO_PAGE)); + MenuNew.setText(TCategory.MENU.getTranslation(TKey.NEW)); + MenuQuit.setText(TCategory.MENU.getTranslation(TKey.QUIT)); + MenuSave.setText(TCategory.MENU.getTranslation(TKey.SAVE)); + MenuSaveWithName.setText(TCategory.MENU.getTranslation(TKey.SAVE_WITH_NAME)); + MenuPreferences.setText(TCategory.MENU.getTranslation(TKey.PREFERENCES)); + MenuImport.setText(TCategory.MENU.getTranslation(TKey.IMPORT)); + MenuExport.setText(TCategory.MENU.getTranslation(TKey.EXPORT)); + MenuShare.setText(TCategory.MENU.getTranslation(TKey.SHARE)); + MenuSupport.setText(TCategory.MENU.getTranslation(TKey.SUPPORT)); + MenuWebsite.setText(TCategory.MENU.getTranslation(TKey.WEBSITE)); // backup list - ExportLabel.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.EXPORT_AS)); - addBackupEntryButton.setToolTipText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.ADD_BACKUP_TOOLTIP)); - exportAsPdfBtn.setToolTipText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.EXPORT_AS_PDF_TOOLTIP)); - exportAsCsvBtn.setToolTipText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.EXPORT_AS_CSV_TOOLTIP)); - researchField.setToolTipText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.RESEARCH_BAR_TOOLTIP)); - researchField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.RESEARCH_BAR_PLACEHOLDER)); + ExportLabel.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS)); + addBackupEntryButton.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.ADD_BACKUP_TOOLTIP)); + exportAsPdfBtn.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS_PDF_TOOLTIP)); + exportAsCsvBtn.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS_CSV_TOOLTIP)); + researchField.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_TOOLTIP)); + researchField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_PLACEHOLDER)); // popup - CopyBackupNamePopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.COPY_BACKUP_NAME_POPUP)); - CopyDestinationPathPopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.COPY_DESTINATION_PATH_BACKUP)); - RunBackupPopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.SINGLE_BACKUP_POPUP)); - CopyInitialPathPopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.COPY_INITIAL_PATH_POPUP)); - DeletePopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.DELETE_POPUP)); - interruptBackupPopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.INTERRUPT_POPUP)); - DuplicatePopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.DUPLICATE_POPUP)); - EditPoputItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.EDIT_POPUP)); - OpenInitialDestinationItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.OPEN_DESTINATION_FOLDER_POPUP)); - OpenInitialFolderItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.OPEN_INITIAL_FOLDER_POPUP)); - renamePopupItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.RENAME_BACKUP_POPUP)); - jMenu4.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.COPY_TEXT_POPUP)); - AutoBackupMenuItem.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.AUTO_BACKUP_POPUP)); - Backup.setText(TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.BACKUP_POPUP)); + CopyBackupNamePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_BACKUP_NAME_POPUP)); + CopyDestinationPathPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); + RunBackupPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.SINGLE_BACKUP_POPUP)); + CopyInitialPathPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_INITIAL_PATH_POPUP)); + DeletePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DELETE_POPUP)); + interruptBackupPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.INTERRUPT_POPUP)); + DuplicatePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DUPLICATE_POPUP)); + EditPoputItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EDIT_POPUP)); + OpenInitialDestinationItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_DESTINATION_FOLDER_POPUP)); + OpenInitialFolderItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_INITIAL_FOLDER_POPUP)); + renamePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.RENAME_BACKUP_POPUP)); + jMenu4.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_TEXT_POPUP)); + AutoBackupMenuItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.AUTO_BACKUP_POPUP)); + Backup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_POPUP)); } private String[] getColumnTranslations() { String[] columnNames = { - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.BACKUP_NAME_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.INITIAL_PATH_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.DESTINATION_PATH_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.LAST_BACKUP_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.AUTOMATIC_BACKUP_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.NEXT_BACKUP_DATE_COLUMN), - TranslationCategory.BACKUP_LIST.getTranslation(TranslationKey.TIME_INTERVAL_COLUMN) + TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_NAME_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.INITIAL_PATH_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.DESTINATION_PATH_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.LAST_BACKUP_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.AUTOMATIC_BACKUP_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.NEXT_BACKUP_DATE_COLUMN), + TCategory.BACKUP_LIST.getTranslation(TKey.TIME_INTERVAL_COLUMN) }; return columnNames; } @@ -1103,7 +1101,7 @@ private void setSvgImages() { } private void initializeMenuItems() { - JSONConfigReader config = new JSONConfigReader(ConfigKey.CONFIG_FILE_STRING.getValue(), ConfigKey.CONFIG_DIRECTORY_STRING.getValue()); + JsonConfig config = JsonConfig.getInstance(); MenuBugReport.setVisible(config.isMenuItemEnabled(MenuItems.BugReport.name())); MenuPreferences.setVisible(config.isMenuItemEnabled(MenuItems.Preferences.name())); MenuClear.setVisible(config.isMenuItemEnabled(MenuItems.Clear.name())); diff --git a/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java index 45dea396..cc7f473e 100644 --- a/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java @@ -1,8 +1,8 @@ package backupmanager.gui.frames; import backupmanager.gui.Controllers.GuiController; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.gui.frames.Controllers.BackupProgressController; @@ -34,10 +34,10 @@ public void updateProgressBar(int value, String fileProcessed, int filesCopiedSo fileZippedLabel.setText(fileProcessed); // edit the title with counts - setTitle(TranslationCategory.PROGRESS_BACKUP_FRAME.getTranslation(TranslationKey.PROGRESS_BACKUP_TITLE) + " - " + filesCopiedSoFar + "/" + totalFilesCount); + setTitle(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.PROGRESS_BACKUP_TITLE) + " - " + filesCopiedSoFar + "/" + totalFilesCount); if (value == 100) { - loadingMessageLabel.setText(TranslationCategory.PROGRESS_BACKUP_FRAME.getTranslation(TranslationKey.STATUS_COMPLETED)); + loadingMessageLabel.setText(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.STATUS_COMPLETED)); closeButton.setEnabled(true); CancelButton.setEnabled(false); fileZippedLabel.setText(""); @@ -155,10 +155,10 @@ private void CancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN- }//GEN-LAST:event_CancelButtonActionPerformed private void setTranslations() { - setTitle(TranslationCategory.PROGRESS_BACKUP_FRAME.getTranslation(TranslationKey.PROGRESS_BACKUP_TITLE)); - CancelButton.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CANCEL_BUTTON)); - closeButton.setText(TranslationCategory.GENERAL.getTranslation(TranslationKey.CLOSE_BUTTON)); - loadingMessageLabel.setText(TranslationCategory.PROGRESS_BACKUP_FRAME.getTranslation(TranslationKey.STATUS_LOADING)); + setTitle(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.PROGRESS_BACKUP_TITLE)); + CancelButton.setText(TCategory.GENERAL.getTranslation(TKey.CANCEL_BUTTON)); + closeButton.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); + loadingMessageLabel.setText(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.STATUS_LOADING)); } // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index 4fe3d464..c2b5b965 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -15,22 +15,20 @@ import backupmanager.Email.EmailSender; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Entities.User; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.LanguagesEnum; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; -import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Services.BackupService; import backupmanager.database.Repositories.UserRepository; import backupmanager.gui.Dialogs.EntryUserDialog; import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.TableDataManager; +import backupmanager.gui.forms.CustomForm; import backupmanager.gui.frames.BackupManagerGUI; -import static backupmanager.gui.frames.BackupManagerGUI.backups; -import backupmanager.gui.simple.BackupEntry; +import backupmanager.gui.simple.BackupEntryDialog; import raven.modal.ModalDialog; import raven.modal.component.SimpleModalBorder; import raven.modal.option.Location; @@ -65,7 +63,7 @@ public int[] getScreenSize() { return new int[]{width, height}; } - public void researchInTable(String research) { + public List researchInTableAndGet(List backups, String research) { List tempBackups = new ArrayList<>(); research = research.toLowerCase(); @@ -80,7 +78,7 @@ public void researchInTable(String research) { } } - TableDataManager.updateTableWithNewBackupList(tempBackups, formatter); + return tempBackups; } public void showCreateModal(Component parent) { @@ -92,14 +90,41 @@ public void showCreateModal(Component parent) { ModalDialog.showModal(parent, new SimpleModalBorder( - new BackupEntry(), - "Create", - SimpleModalBorder.YES_NO_OPTION, + new BackupEntryDialog(), + TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_SUBTITLE_CREATE), + SimpleModalBorder.OK_CANCEL_OPTION, (controller, action) -> {} ), option); } + public void showEditModal(CustomForm form, ConfigurationBackup backup) { + BackupEntryDialog dialog = new BackupEntryDialog(backup); + + Option option = ModalDialog.createOption(); + option.getLayoutOption() + .setSize(-1, 1f) + .setLocation(Location.TRAILING, Location.TOP) + .setAnimateDistance(0.7f, 0); + + ModalDialog.showModal( + form, + new SimpleModalBorder( + dialog, + TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_SUBTITLE_EDIT), + SimpleModalBorder.OK_CANCEL_OPTION, + (controller, action) -> { + if (action == SimpleModalBorder.OK_OPTION) { + ConfigurationBackup editedBackup = dialog.getResult(); + form.formRefresh(); + } + } + ), + option + ); + + } + public String getBackupDetails(String backupName) { return backupService.getBackupDetails(backupName); } @@ -108,11 +133,15 @@ public void deleteBackups(List names) { backupService.deleteBackups(names); } + public void deleteBackup(ConfigurationBackup backup) { + backupService.deleteBackup(backup.getId()); + } + public boolean isBackupRunning(String name) { return backupService.isRunning(name); } - public boolean isAutomaticBackup(String backupName) { + public boolean isAutomaticBackup(List backups, String backupName) { ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backups, backupName); return backup != null && backup.isAutomatic(); } @@ -139,7 +168,7 @@ public void handleDeleteKeyPressOnTable(BackupTable backupTable) { logger.debug("Delete key pressed on rows: " + Arrays.toString(selectedRows)); - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_DELETION_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_DELETION_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_DELETION_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_DELETION_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response != JOptionPane.YES_OPTION) return; @@ -168,12 +197,12 @@ private void setLanguageBasedOnPcLanguage(BackupManagerGUI mainGui) { logger.info("Setting default language to: " + language); switch (language) { - case "en" -> Confingurations.setLanguage(LanguagesEnum.ENG); - case "it" -> Confingurations.setLanguage(LanguagesEnum.ITA); - case "es" -> Confingurations.setLanguage(LanguagesEnum.ESP); - case "de" -> Confingurations.setLanguage(LanguagesEnum.DEU); - case "fr" -> Confingurations.setLanguage(LanguagesEnum.FRA); - default -> Confingurations.setLanguage(LanguagesEnum.ENG); + case "en" -> Configurations.setLanguage(LanguagesEnum.ENG); + case "it" -> Configurations.setLanguage(LanguagesEnum.ITA); + case "es" -> Configurations.setLanguage(LanguagesEnum.ESP); + case "de" -> Configurations.setLanguage(LanguagesEnum.DEU); + case "fr" -> Configurations.setLanguage(LanguagesEnum.FRA); + default -> Configurations.setLanguage(LanguagesEnum.ENG); } mainGui.reloadPreferences(); diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java index d0af17ee..b80fa2a8 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java @@ -12,8 +12,8 @@ import backupmanager.gui.Dialogs.PreferencesDialog; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.WebsiteManager; import backupmanager.Services.BackupObserver; @@ -59,7 +59,7 @@ public static void menuItemShare() { logger.info("Event --> share"); // pop-up message - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.SHARE_LINK_COPIED_MESSAGE)); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SHARE_LINK_COPIED_MESSAGE)); // copy link to the clipboard StringSelection stringSelectionObj = new StringSelection(ConfigKey.SHARE_LINK.getValue()); @@ -83,7 +83,7 @@ public static void menuItemHistory() { new ProcessBuilder("notepad.exe", ConfigKey.LOG_DIRECTORY_STRING.getValue() + ConfigKey.LOG_FILE_STRING.getValue()).start(); } catch (IOException e) { logger.error("Error opening history file: " + e.getMessage(), e); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_OPEN_HISTORY_FILE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_OPEN_HISTORY_FILE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index 10e065e4..222ce294 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -23,8 +23,8 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -271,7 +271,7 @@ private static void renameBackup(List backups, Configuratio private static String getBackupNameFromInputDialog(List backups, String oldName, boolean canOverwrite) { while (true) { - String backupName = JOptionPane.showInputDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.BACKUP_NAME_INPUT), oldName); + String backupName = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.BACKUP_NAME_INPUT), oldName); // If the user cancels the operation if (backupName == null || backupName.trim().isEmpty()) { @@ -284,7 +284,7 @@ private static String getBackupNameFromInputDialog(List bac if (existingBackup.isPresent()) { if (canOverwrite) { - int response = JOptionPane.showConfirmDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.DUPLICATED_BACKUP_NAME_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { backups.remove(existingBackup.get()); @@ -292,7 +292,7 @@ private static String getBackupNameFromInputDialog(List bac } } else { logger.warn("Backup name '{}' is already in use", backupName); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.BACKUP_NAME_ALREADY_USED_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.BACKUP_NAME_ALREADY_USED_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { return backupName; // Return valid name @@ -324,7 +324,7 @@ private static void openFolder(String path) { } } else { logger.warn("The folder does not exist or is invalid"); - JOptionPane.showMessageDialog(null, TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING), TranslationCategory.DIALOGS.getTranslation(TranslationKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java index 371bb916..b2fa952a 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java @@ -2,14 +2,14 @@ import javax.swing.JOptionPane; -import backupmanager.Enums.TranslationLoaderEnum.TranslationCategory; -import backupmanager.Enums.TranslationLoaderEnum.TranslationKey; +import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations.TKey; import backupmanager.Services.ZippingThread; public class BackupProgressController { public void handleCancelButtonRequest(javax.swing.JDialog dialog) { - int response = JOptionPane.showConfirmDialog(dialog, TranslationCategory.DIALOGS.getTranslation(TranslationKey.INTERRUPT_BACKUP_PROCESS_MESSAGE), TranslationCategory.DIALOGS.getTranslation(TranslationKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.INTERRUPT_BACKUP_PROCESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { cancelBackup(); dialog.dispose(); diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index 5540819f..edf256bd 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -17,17 +17,19 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.MenuItems; -import backupmanager.Json.JSONConfigReader; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.Managers.ExportManager; import backupmanager.Managers.WebsiteManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.gui.forms.FormBackupDashboard; +import backupmanager.gui.forms.FormBackupTable; import backupmanager.gui.forms.FormHistory; import backupmanager.gui.forms.FormSetting; -import backupmanager.gui.forms.FormTable; import backupmanager.gui.system.AllForms; import backupmanager.gui.system.FormManager; import raven.extras.AvatarIcon; @@ -104,7 +106,7 @@ public SimpleHeaderData getSimpleHeaderData() { private static void initMenuActions() { menuActionMap.put(MenuItems.BackupList, () -> - FormManager.showForm(AllForms.getForm(FormTable.class))); + FormManager.showForm(AllForms.getForm(FormBackupTable.class))); menuActionMap.put(MenuItems.Dashboard, () -> FormManager.showForm(AllForms.getForm(FormBackupDashboard.class))); @@ -241,52 +243,52 @@ private static String getDrawerBackgroundStyle() { } private static List buildMenuItems() { - JSONConfigReader config = new JSONConfigReader(); + JsonConfig config = JsonConfig.getInstance(); List itemList = new ArrayList<>(); - itemList.add(new Item.Label("MAIN")); + itemList.add(new Item.Label(Translations.get(TKey.SUBMENU_MAIN))); // Backup menu - Item backupItem = createMenuItem("Backup List", "forms.svg", MenuItems.BackupList, FormTable.class) - .subMenu("Create new backup"); + Item backupItem = createMenuItem(Translations.get(TKey.BACKUP_TABLE), "forms.svg", MenuItems.BackupList, FormBackupTable.class) + .subMenu(Translations.get(TKey.CREATE_BACKUP)); if (config.isMenuItemEnabled(MenuItems.Import.name())) - backupItem.subMenu("Import backups from Csv"); + backupItem.subMenu(Translations.get(TKey.IMPORT_BACKUP)); if (config.isMenuItemEnabled(MenuItems.Export.name())) - backupItem.subMenu("Export backups to Csv"); + backupItem.subMenu(Translations.get(TKey.EXPORT_BACKUP)); itemList.add(backupItem); // Dashboard - itemList.add(createMenuItem("Dashboard", "dashboard.svg", MenuItems.Dashboard, FormBackupDashboard.class)); + itemList.add(createMenuItem(Translations.get(TKey.DASHBOARD), "dashboard.svg", MenuItems.Dashboard, FormBackupDashboard.class)); - itemList.add(new Item.Label("OTHER")); + itemList.add(new Item.Label(Translations.get(TKey.SUBMENU_OTHER))); // Settings - itemList.add(createMenuItem("Settings", "setting.svg", MenuItems.Settings, FormSetting.class)); + itemList.add(createMenuItem(Translations.get(TKey.OPTIONS), "setting.svg", MenuItems.Settings, FormSetting.class)); // History (configurable) if (config.isMenuItemEnabled(MenuItems.History.name())) { - itemList.add(createMenuItem("History", "history.svg", MenuItems.History, FormHistory.class)); + itemList.add(createMenuItem(Translations.get(TKey.HISTORY), "history.svg", MenuItems.History, FormHistory.class)); } // External link (no form class) - itemList.add(createMenuItem("Github page", "github.svg", MenuItems.InfoPage, null)); + itemList.add(createMenuItem(Translations.get(TKey.GITHUB_PAGE), "github.svg", MenuItems.InfoPage, null)); // Donate section if (config.isMenuItemEnabled(MenuItems.Donate.name())) { - Item donateItem = createMenuItem("Support the Project", "donate.svg", MenuItems.Donate, null); + Item donateItem = createMenuItem(Translations.get(TKey.DONATE), "donate.svg", MenuItems.Donate, null); if (config.isMenuItemEnabled(MenuItems.PaypalDonate.name())) { - donateItem.subMenu("Paypal"); - bindSubMenu("Paypal", MenuItems.PaypalDonate); + donateItem.subMenu(Translations.get(TKey.PAYPAL)); + bindSubMenu(Translations.get(TKey.PAYPAL), MenuItems.PaypalDonate); } if (config.isMenuItemEnabled(MenuItems.BuymeacoffeeDonate.name())) { - donateItem.subMenu("Buy me a coffee"); - bindSubMenu("Buy me a coffee", MenuItems.BuymeacoffeeDonate); + donateItem.subMenu(Translations.get(TKey.BUYMEACOFFE)); + bindSubMenu(Translations.get(TKey.BUYMEACOFFE), MenuItems.BuymeacoffeeDonate); } itemList.add(donateItem); @@ -295,27 +297,27 @@ private static List buildMenuItems() { // Support section if (config.isMenuItemEnabled(MenuItems.Support.name())) { - Item helpItem = createMenuItem("Help", "help.svg", MenuItems.Support, null); + Item helpItem = createMenuItem(Translations.get(TKey.HELP), "help.svg", MenuItems.Support, null); if (config.isMenuItemEnabled(MenuItems.BugReport.name())) { - helpItem.subMenu("Report a bug"); - bindSubMenu("Report a bug", MenuItems.BugReport); + helpItem.subMenu(Translations.get(TKey.BUG_REPORT)); + bindSubMenu(Translations.get(TKey.BUG_REPORT), MenuItems.BugReport); } if (config.isMenuItemEnabled(MenuItems.ContactUs.name())) { - helpItem.subMenu("Contact us"); - bindSubMenu("Contact us", MenuItems.ContactUs); + helpItem.subMenu(Translations.get(TKey.CONTACT_US)); + bindSubMenu(Translations.get(TKey.CONTACT_US), MenuItems.ContactUs); } itemList.add(helpItem); } - if (Confingurations.isSubscriptionNedded()) { - itemList.add(createMenuItem("Subscription", "subscription.svg", MenuItems.Subscription, null)); + if (Configurations.isSubscriptionNedded()) { + itemList.add(createMenuItem(Translations.get(TKey.SUBSCRIPTION), "subscription.svg", MenuItems.Subscription, null)); } // About (LAST ITEM) - itemList.add(createMenuItem("About", "about.svg", MenuItems.About, null)); + itemList.add(createMenuItem(Translations.get(TKey.ABOUT), "about.svg", MenuItems.About, null)); return itemList; } diff --git a/src/main/java/backupmanager/gui/sample/SampleData.java b/src/main/java/backupmanager/gui/sample/SampleData.java deleted file mode 100644 index cfb70e33..00000000 --- a/src/main/java/backupmanager/gui/sample/SampleData.java +++ /dev/null @@ -1,424 +0,0 @@ -package backupmanager.gui.sample; - -import java.util.Calendar; -import java.util.Date; - -import javax.swing.Icon; -import javax.swing.ImageIcon; - -import org.jfree.data.category.CategoryDataset; -import org.jfree.data.category.DefaultCategoryDataset; -import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.general.PieDataset; -import org.jfree.data.time.Month; -import org.jfree.data.time.TimeTableXYDataset; -import org.jfree.data.xy.DefaultHighLowDataset; -import org.jfree.data.xy.OHLCDataset; -import org.jfree.data.xy.TableXYDataset; - -import raven.extras.AvatarIcon; - -@Deprecated -public class SampleData { - public static TableXYDataset getTimeSeriesDataset() { - TimeTableXYDataset dataset = new TimeTableXYDataset(); - String seriesIncome = "Income"; - - dataset.add(new Month(2, 2001), 181.8, seriesIncome); - dataset.add(new Month(3, 2001), 167.3, seriesIncome); - dataset.add(new Month(4, 2001), 153.8, seriesIncome); - dataset.add(new Month(5, 2001), 167.6, seriesIncome); - dataset.add(new Month(6, 2001), 158.8, seriesIncome); - dataset.add(new Month(7, 2001), 148.3, seriesIncome); - dataset.add(new Month(8, 2001), 153.9, seriesIncome); - dataset.add(new Month(9, 2001), 142.7, seriesIncome); - dataset.add(new Month(10, 2001), 123.2, seriesIncome); - dataset.add(new Month(11, 2001), 131.8, seriesIncome); - dataset.add(new Month(12, 2001), 139.6, seriesIncome); - dataset.add(new Month(1, 2002), 142.9, seriesIncome); - dataset.add(new Month(2, 2002), 138.7, seriesIncome); - dataset.add(new Month(3, 2002), 137.3, seriesIncome); - dataset.add(new Month(4, 2002), 143.9, seriesIncome); - dataset.add(new Month(5, 2002), 139.8, seriesIncome); - dataset.add(new Month(6, 2002), 80.0, seriesIncome); - dataset.add(new Month(7, 2002), 50.8, seriesIncome); - - String seriesExpense = "Expense"; - dataset.add(new Month(2, 2001), 129.6, seriesExpense); - dataset.add(new Month(3, 2001), 123.2, seriesExpense); - dataset.add(new Month(4, 2001), 117.2, seriesExpense); - dataset.add(new Month(5, 2001), 124.1, seriesExpense); - dataset.add(new Month(6, 2001), 122.6, seriesExpense); - dataset.add(new Month(7, 2001), 119.2, seriesExpense); - dataset.add(new Month(8, 2001), 116.5, seriesExpense); - dataset.add(new Month(9, 2001), 112.7, seriesExpense); - dataset.add(new Month(10, 2001), 101.5, seriesExpense); - dataset.add(new Month(11, 2001), 106.1, seriesExpense); - dataset.add(new Month(12, 2001), 125.2, seriesExpense); - dataset.add(new Month(1, 2002), 111.7, seriesExpense); - dataset.add(new Month(2, 2002), 111.0, seriesExpense); - dataset.add(new Month(3, 2002), 109.6, seriesExpense); - dataset.add(new Month(4, 2002), 113.2, seriesExpense); - dataset.add(new Month(5, 2002), 111.6, seriesExpense); - dataset.add(new Month(6, 2002), 108.8, seriesExpense); - dataset.add(new Month(7, 2002), 101.6, seriesExpense); - - return dataset; - } - - public static CategoryDataset getCategoryDataset() { - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - // series key - String series1 = "Sales"; - String series2 = "Adjust"; - String series3 = "Return"; - String series4 = "Custom"; - - // product names - String product1 = "Laptop"; - String product2 = "Phone"; - String product3 = "Accessory"; - - // product 1 - dataset.addValue(200, product1, series1); - dataset.addValue(50, product1, series2); - dataset.addValue(80, product1, series3); - dataset.addValue(150, product1, series4); - - // product 2 - dataset.addValue(50, product2, series1); - dataset.addValue(180, product2, series2); - dataset.addValue(250, product2, series3); - dataset.addValue(230, product2, series4); - - // product 3 - dataset.addValue(180, product3, series1); - dataset.addValue(100, product3, series2); - dataset.addValue(250, product3, series3); - dataset.addValue(80, product3, series4); - - return dataset; - } - - public static PieDataset getPieDataset() { - DefaultPieDataset dataset = new DefaultPieDataset(); - - dataset.setValue("Laptop", 30); - dataset.setValue("Phone", 25); - dataset.setValue("Tablet", 18); - dataset.setValue("Watch", 12); - - return dataset; - } - - public static OHLCDataset getOhlcDataset() { - Date[] date = new Date[47]; - double[] high = new double[47]; - double[] low = new double[47]; - double[] open = new double[47]; - double[] close = new double[47]; - double[] volume = new double[47]; - int jan = 1; - int feb = 2; - date[0] = createOhlcData(2001, jan, 4, 12, 0); - high[0] = 47.0; - low[0] = 33.0; - open[0] = 35.0; - close[0] = 33.0; - volume[0] = 100.0; - date[1] = createOhlcData(2001, jan, 5, 12, 0); - high[1] = 47.0; - low[1] = 32.0; - open[1] = 41.0; - close[1] = 37.0; - volume[1] = 150.0; - date[2] = createOhlcData(2001, jan, 6, 12, 0); - high[2] = 49.0; - low[2] = 43.0; - open[2] = 46.0; - close[2] = 48.0; - volume[2] = 70.0; - date[3] = createOhlcData(2001, jan, 7, 12, 0); - high[3] = 51.0; - low[3] = 39.0; - open[3] = 40.0; - close[3] = 47.0; - volume[3] = 200.0; - date[4] = createOhlcData(2001, jan, 8, 12, 0); - high[4] = 60.0; - low[4] = 40.0; - open[4] = 46.0; - close[4] = 53.0; - volume[4] = 120.0; - date[5] = createOhlcData(2001, jan, 9, 12, 0); - high[5] = 62.0; - low[5] = 55.0; - open[5] = 57.0; - close[5] = 61.0; - volume[5] = 110.0; - date[6] = createOhlcData(2001, jan, 10, 12, 0); - high[6] = 65.0; - low[6] = 56.0; - open[6] = 62.0; - close[6] = 59.0; - volume[6] = 70.0; - date[7] = createOhlcData(2001, jan, 11, 12, 0); - high[7] = 55.0; - low[7] = 43.0; - open[7] = 45.0; - close[7] = 47.0; - volume[7] = 20.0; - date[8] = createOhlcData(2001, jan, 12, 12, 0); - high[8] = 54.0; - low[8] = 33.0; - open[8] = 40.0; - close[8] = 51.0; - volume[8] = 30.0; - date[9] = createOhlcData(2001, jan, 13, 12, 0); - high[9] = 47.0; - low[9] = 33.0; - open[9] = 35.0; - close[9] = 33.0; - volume[9] = 100.0; - date[10] = createOhlcData(2001, jan, 14, 12, 0); - high[10] = 54.0; - low[10] = 38.0; - open[10] = 43.0; - close[10] = 52.0; - volume[10] = 50.0; - date[11] = createOhlcData(2001, jan, 15, 12, 0); - high[11] = 48.0; - low[11] = 41.0; - open[11] = 44.0; - close[11] = 41.0; - volume[11] = 80.0; - date[12] = createOhlcData(2001, jan, 17, 12, 0); - high[12] = 60.0; - low[12] = 30.0; - open[12] = 34.0; - close[12] = 44.0; - volume[12] = 90.0; - date[13] = createOhlcData(2001, jan, 18, 12, 0); - high[13] = 58.0; - low[13] = 44.0; - open[13] = 54.0; - close[13] = 56.0; - volume[13] = 20.0; - date[14] = createOhlcData(2001, jan, 19, 12, 0); - high[14] = 54.0; - low[14] = 32.0; - open[14] = 42.0; - close[14] = 53.0; - volume[14] = 70.0; - date[15] = createOhlcData(2001, jan, 20, 12, 0); - high[15] = 53.0; - low[15] = 39.0; - open[15] = 50.0; - close[15] = 49.0; - volume[15] = 60.0; - date[16] = createOhlcData(2001, jan, 21, 12, 0); - high[16] = 47.0; - low[16] = 33.0; - open[16] = 41.0; - close[16] = 40.0; - volume[16] = 30.0; - date[17] = createOhlcData(2001, jan, 22, 12, 0); - high[17] = 55.0; - low[17] = 37.0; - open[17] = 43.0; - close[17] = 45.0; - volume[17] = 90.0; - date[18] = createOhlcData(2001, jan, 23, 12, 0); - high[18] = 54.0; - low[18] = 42.0; - open[18] = 50.0; - close[18] = 42.0; - volume[18] = 150.0; - date[19] = createOhlcData(2001, jan, 24, 12, 0); - high[19] = 48.0; - low[19] = 37.0; - open[19] = 37.0; - close[19] = 47.0; - volume[19] = 120.0; - date[20] = createOhlcData(2001, jan, 25, 12, 0); - high[20] = 58.0; - low[20] = 33.0; - open[20] = 39.0; - close[20] = 41.0; - volume[20] = 80.0; - date[21] = createOhlcData(2001, jan, 26, 12, 0); - high[21] = 47.0; - low[21] = 31.0; - open[21] = 36.0; - close[21] = 41.0; - volume[21] = 40.0; - date[22] = createOhlcData(2001, jan, 27, 12, 0); - high[22] = 58.0; - low[22] = 44.0; - open[22] = 49.0; - close[22] = 44.0; - volume[22] = 20.0; - date[23] = createOhlcData(2001, jan, 28, 12, 0); - high[23] = 46.0; - low[23] = 41.0; - open[23] = 43.0; - close[23] = 44.0; - volume[23] = 60.0; - date[24] = createOhlcData(2001, jan, 29, 12, 0); - high[24] = 56.0; - low[24] = 39.0; - open[24] = 39.0; - close[24] = 51.0; - volume[24] = 40.0; - date[25] = createOhlcData(2001, jan, 30, 12, 0); - high[25] = 56.0; - low[25] = 39.0; - open[25] = 47.0; - close[25] = 49.0; - volume[25] = 70.0; - date[26] = createOhlcData(2001, jan, 31, 12, 0); - high[26] = 53.0; - low[26] = 39.0; - open[26] = 52.0; - close[26] = 47.0; - volume[26] = 60.0; - date[27] = createOhlcData(2001, feb, 1, 12, 0); - high[27] = 51.0; - low[27] = 30.0; - open[27] = 45.0; - close[27] = 47.0; - volume[27] = 90.0; - date[28] = createOhlcData(2001, feb, 2, 12, 0); - high[28] = 47.0; - low[28] = 30.0; - open[28] = 34.0; - close[28] = 46.0; - volume[28] = 100.0; - date[29] = createOhlcData(2001, feb, 3, 12, 0); - high[29] = 57.0; - low[29] = 37.0; - open[29] = 44.0; - close[29] = 56.0; - volume[29] = 20.0; - date[30] = createOhlcData(2001, feb, 4, 12, 0); - high[30] = 49.0; - low[30] = 40.0; - open[30] = 47.0; - close[30] = 44.0; - volume[30] = 50.0; - date[31] = createOhlcData(2001, feb, 5, 12, 0); - high[31] = 46.0; - low[31] = 38.0; - open[31] = 43.0; - close[31] = 40.0; - volume[31] = 70.0; - date[32] = createOhlcData(2001, feb, 6, 12, 0); - high[32] = 55.0; - low[32] = 38.0; - open[32] = 39.0; - close[32] = 53.0; - volume[32] = 120.0; - date[33] = createOhlcData(2001, feb, 7, 12, 0); - high[33] = 50.0; - low[33] = 33.0; - open[33] = 37.0; - close[33] = 37.0; - volume[33] = 140.0; - date[34] = createOhlcData(2001, feb, 8, 12, 0); - high[34] = 59.0; - low[34] = 34.0; - open[34] = 57.0; - close[34] = 43.0; - volume[34] = 70.0; - date[35] = createOhlcData(2001, feb, 9, 12, 0); - high[35] = 48.0; - low[35] = 39.0; - open[35] = 46.0; - close[35] = 47.0; - volume[35] = 70.0; - date[36] = createOhlcData(2001, feb, 10, 12, 0); - high[36] = 55.0; - low[36] = 30.0; - open[36] = 37.0; - close[36] = 30.0; - volume[36] = 30.0; - date[37] = createOhlcData(2001, feb, 11, 12, 0); - high[37] = 60.0; - low[37] = 32.0; - open[37] = 56.0; - close[37] = 36.0; - volume[37] = 70.0; - date[38] = createOhlcData(2001, feb, 12, 12, 0); - high[38] = 56.0; - low[38] = 42.0; - open[38] = 53.0; - close[38] = 54.0; - volume[38] = 40.0; - date[39] = createOhlcData(2001, feb, 13, 12, 0); - high[39] = 49.0; - low[39] = 42.0; - open[39] = 45.0; - close[39] = 42.0; - volume[39] = 90.0; - date[40] = createOhlcData(2001, feb, 14, 12, 0); - high[40] = 55.0; - low[40] = 42.0; - open[40] = 47.0; - close[40] = 54.0; - volume[40] = 70.0; - date[41] = createOhlcData(2001, feb, 15, 12, 0); - high[41] = 49.0; - low[41] = 35.0; - open[41] = 38.0; - close[41] = 35.0; - volume[41] = 20.0; - date[42] = createOhlcData(2001, feb, 16, 12, 0); - high[42] = 47.0; - low[42] = 38.0; - open[42] = 43.0; - close[42] = 42.0; - volume[42] = 10.0; - date[43] = createOhlcData(2001, feb, 17, 12, 0); - high[43] = 53.0; - low[43] = 42.0; - open[43] = 47.0; - close[43] = 48.0; - volume[43] = 20.0; - date[44] = createOhlcData(2001, feb, 18, 12, 0); - high[44] = 47.0; - low[44] = 44.0; - open[44] = 46.0; - close[44] = 44.0; - volume[44] = 30.0; - date[45] = createOhlcData(2001, feb, 19, 12, 0); - high[45] = 46.0; - low[45] = 40.0; - open[45] = 43.0; - close[45] = 44.0; - volume[45] = 50.0; - date[46] = createOhlcData(2001, feb, 20, 12, 0); - high[46] = 48.0; - low[46] = 41.0; - open[46] = 46.0; - close[46] = 41.0; - volume[46] = 100.0; - return new DefaultHighLowDataset("Series 1", date, high, low, open, close, volume); - } - - private static Date createOhlcData(int y, int m, int d, int hour, int min) { - Calendar calendar = Calendar.getInstance(); - calendar.set(y, m - 1, d, hour, min); - return calendar.getTime(); - } - - private static Icon getProfileIcon(String name, boolean defaultIcon) { - if (defaultIcon) { - return new ImageIcon(SampleData.class.getResource("/images/" + name)); - } else { - AvatarIcon avatarIcon = new AvatarIcon(SampleData.class.getResource("/images/" + name), 45, 45, 3f); - avatarIcon.setType(AvatarIcon.Type.MASK_SQUIRCLE); - return avatarIcon; - } - } -} diff --git a/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java b/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java index b31e1851..1aa69499 100644 --- a/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java +++ b/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java @@ -88,6 +88,6 @@ private static String[] parseCSVLine(String line) { } // add the last field result.add(currentField.toString()); - return result.toArray(new String[0]); + return result.toArray(String[]::new); } } diff --git a/src/main/java/backupmanager/gui/simple/BackupEntry.java b/src/main/java/backupmanager/gui/simple/BackupEntry.java deleted file mode 100644 index 7edb7ff7..00000000 --- a/src/main/java/backupmanager/gui/simple/BackupEntry.java +++ /dev/null @@ -1,116 +0,0 @@ -package backupmanager.gui.simple; - -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; - -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSeparator; -import javax.swing.JSpinner; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.SpinnerNumberModel; - -import com.formdev.flatlaf.FlatClientProperties; -import com.formdev.flatlaf.extras.FlatSVGIcon; - -import net.miginfocom.swing.MigLayout; -import raven.modal.component.ModalBorderAction; -import raven.modal.component.SimpleModalBorder; - -public class BackupEntry extends JPanel { - - public static int NEW_COUNTRY = 30; - - public BackupEntry() { - init(); - } - - private void init() { - setLayout(new MigLayout("fillx,wrap,insets 5 30 5 30,width 400", "[fill]", "")); - txtBackupName = new JTextField(); - txtTargetPath = new JTextField(); - txtDestinationPath = new JTextField(); - executeBackupBtn = new JButton("Execute Backup"); - automaticBackupBtn = new JButton("Automatic Backup (OFF)"); - targetPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); - destinationPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); - TimeIntervalBtn = new JButton(new FlatSVGIcon("icons/timer.svg", 25, 25)); - maxToKeeSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1)); - maxToKeeLabel = new JLabel("Max to keep"); - lastBackupLabel = new JLabel("Last backup: never"); - - JTextArea txtNotes = new JTextArea(); - txtNotes.setWrapStyleWord(true); - txtNotes.setLineWrap(true); - JScrollPane scroll = new JScrollPane(txtNotes); - - // style - txtBackupName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Backup name (unique)"); - txtTargetPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "target path e.g. C:\\Users\\Admin\\Documents"); - txtDestinationPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "destination folder e.g. D:\\Backups"); - - // add to panel - createTitle("Backup information"); - - add(new JLabel("Backup Name"), "gapy 5 0"); - add(txtBackupName); - add(new JLabel("Paths"), "gapy 5 0"); - add(txtTargetPath, "split 2"); - add(targetPathBtn, "w 30!, h 30!"); - add(txtDestinationPath, "split 2"); - add(destinationPathBtn, "w 30!, h 30!"); - - add(new JLabel("Notes"), "gapy 5 0"); - add(scroll, "height 120,grow,pushy"); - - createTitle("Advanced Information"); - - add(lastBackupLabel); - add(executeBackupBtn); - add(automaticBackupBtn, "split 2"); - add(TimeIntervalBtn, "w 30!, h 30!"); - - add(maxToKeeLabel, "gapy 5 0"); - add(maxToKeeSpinner, "width 100"); - - - txtNotes.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - if (e.isControlDown() && e.getKeyChar() == 10) { - ModalBorderAction modalBorderAction = ModalBorderAction.getModalBorderAction(BackupEntry.this); - if (modalBorderAction != null) { - modalBorderAction.doAction(SimpleModalBorder.YES_OPTION); - } - } - } - }); - } - - private void createTitle(String title) { - JLabel lb = new JLabel(title); - lb.putClientProperty(FlatClientProperties.STYLE, "" + - "font:+2"); - add(lb, "gapy 5 0"); - add(new JSeparator(), "height 2!,gapy 0 0"); - } - - public void formOpen() { - txtBackupName.grabFocus(); - } - - private JTextField txtBackupName; - private JTextField txtTargetPath; - private JTextField txtDestinationPath; - private JLabel lastBackupLabel; - private JButton executeBackupBtn; - private JButton automaticBackupBtn; - private JButton targetPathBtn; - private JButton destinationPathBtn; - private JButton TimeIntervalBtn; - private JSpinner maxToKeeSpinner; - private JLabel maxToKeeLabel; -} diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java new file mode 100644 index 00000000..1a1e8e04 --- /dev/null +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -0,0 +1,338 @@ +package backupmanager.gui.simple; + +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.time.LocalDateTime; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.JToggleButton; +import javax.swing.SpinnerNumberModel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Entities.TimeInterval; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Exceptions.BackupAlreadyRunningException; +import backupmanager.Helpers.BackupHelper; +import backupmanager.gui.Controllers.BackupEntryController; +import net.miginfocom.swing.MigLayout; +import raven.modal.ModalDialog; +import raven.modal.component.ModalBorderAction; +import raven.modal.component.SimpleModalBorder; +import raven.modal.option.Location; +import raven.modal.option.Option; + +public class BackupEntryDialog extends CustomDialog { + + private static final Logger logger = LoggerFactory.getLogger(BackupHelper.class); + private final BackupEntryController entryController; + + private final boolean create; + private String backupOnText; + private String backupOffText; + + public BackupEntryDialog() { + entryController = new BackupEntryController(null); + create = true; + + build(); + + setAutoBackupOff(); + } + + public BackupEntryDialog(ConfigurationBackup currentBackup) { + entryController = new BackupEntryController(currentBackup); + create = false; + + build(); + + updateCurrentFiedsByBackup(currentBackup); + txtBackupName.setText(currentBackup.getName()); + txtBackupName.setEditable(false); + txtBackupName.setFocusable(false); + } + + @Override + protected void init() { + setLayout(new MigLayout("fillx,wrap,insets 5 30 5 30,width 400", "[fill]", "")); + txtBackupName = new JTextField(); + txtTargetPath = new JTextField(); + txtDestinationPath = new JTextField(); + executeBackupBtn = new JButton("Execute Backup"); + automaticBackupBtn = new JToggleButton("Automatic Backup (OFF)"); + targetPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); + destinationPathBtn = new JButton(new FlatSVGIcon("icons/folder.svg", 25, 25)); + timeIntervalBtn = new JButton(new FlatSVGIcon("icons/timer.svg", 25, 25)); + maxToKeeSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 100, 1)); + maxToKeeLabel = new JLabel("Max to keep"); + lastBackupLabel = new JLabel("Last backup: never"); + txtNotes = new JLabel("Notes"); + textAreaNotes = new JTextArea(); + textAreaNotes.setWrapStyleWord(true); + textAreaNotes.setLineWrap(true); + txtPath = new JLabel("Paths"); + txtBackupNameLabel = new JLabel("Backup Name"); + JScrollPane scroll = new JScrollPane(textAreaNotes); + + createTitle(Translations.get(TKey.PAGE_SUBTITLE_INFO)); + + add(txtBackupNameLabel, "gapy 5 0"); + add(txtBackupName); + add(txtPath, "gapy 5 0"); + add(txtTargetPath, "split 2"); + add(targetPathBtn, "w 30!, h 30!"); + add(txtDestinationPath, "split 2"); + add(destinationPathBtn, "w 30!, h 30!"); + + add(txtNotes, "gapy 5 0"); + add(scroll, "height 120,grow,pushy"); + + createTitle(Translations.get(TKey.PAGE_SUBTITLE_SETTINGS)); + + add(lastBackupLabel); + add(executeBackupBtn); + add(automaticBackupBtn, "split 2"); + add(timeIntervalBtn, "w 30!, h 30!"); + + add(maxToKeeLabel, "gapy 5 0"); + add(maxToKeeSpinner, "width 100"); + + executeBackupBtn.addActionListener(e -> executeBackup()); + automaticBackupBtn.addActionListener(e -> toggleAutomaticBackup()); + targetPathBtn.addActionListener(e -> entryController.openFileChooser(txtTargetPath, true)); + destinationPathBtn.addActionListener(e -> entryController.openFileChooser(txtDestinationPath, false)); + timeIntervalBtn.addActionListener(e -> openTimeInterval()); + + styleSpinner(maxToKeeSpinner); + configureSpinner(maxToKeeSpinner, 1, 100); + + txtNotes.addKeyListener(new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (e.isControlDown() && e.getKeyChar() == 10) { + ModalBorderAction modalBorderAction = ModalBorderAction.getModalBorderAction(BackupEntryDialog.this); + if (modalBorderAction != null) { + modalBorderAction.doAction(SimpleModalBorder.YES_OPTION); + } + } + } + }); + } + + @Override + public ConfigurationBackup getResult() { + return entryController.getCurrentBackup(); + } + + private void openTimeInterval() { + // try { + // TimeInterval time = entryController.handleTimePickerAction(this, txtTargetPath.getText(), txtDestinationPath.getText()); + // timeIntervalBtn.setToolTipText(time.toString()); + // openBackupActivationMessage(time); + // } catch (InvalidTimeInterval e) { + // // no actions + // } + + TimePickerDialog timePicker = new TimePickerDialog(entryController.getCurrentBackup().getTimeIntervalBackup()); + + Option option = ModalDialog.createOption(); + option.getLayoutOption().setSize(-1, 1f) + .setLocation(Location.TRAILING, Location.TOP) + .setAnimateDistance(0.7f, 0); + ModalDialog.showModal(this, new SimpleModalBorder( + timePicker, + Translations.get(TKey.TIME_INTERVAL_TITLE), + SimpleModalBorder.YES_NO_OPTION, + (controller, action) -> { + if (action == SimpleModalBorder.YES_OPTION) { + + TimeInterval time = timePicker.getResult(); + + timeIntervalBtn.setToolTipText(time.toString()); + openBackupActivationMessage(time); + + controller.close(); + } + + if (action == SimpleModalBorder.NO_OPTION) { + controller.close(); + } + }), option); + } + + private void updateCurrentFiedsByBackup(ConfigurationBackup backup) { + setStartPathField(backup.getTargetPath()); + setDestinationPathField(backup.getDestinationPath()); + setLastBackupLabel(backup.getLastUpdateDate()); + setAutoBackupPreference(backup.isAutomatic()); + setCurrentBackupNotes(backup.getNotes()); + setCurrentBackupMaxBackupsToKeep(backup.getMaxToKeep()); + + if (backup.getTimeIntervalBackup() != null) { + setAutoBackupOn(backup); + } else { + setAutoBackupOff(); + } + } + + private void executeBackup() { + try { + entryController.handleSingleBackupRequest( + null, + txtBackupName.getText(), + txtTargetPath.getText(), + txtDestinationPath.getText(), + textAreaNotes.getText(), + automaticBackupBtn.isSelected(), + (int) maxToKeeSpinner.getValue() + ); + } catch (BackupAlreadyRunningException e) { + // no handle + } + } + + private void toggleAutomaticBackup() { + if (entryController.toggleAutomaticBackup(txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), txtNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue())) { + setAutoBackupOn(entryController.getCurrentBackup()); + automaticBackupBtn.setSelected(true); + timeIntervalBtn.setToolTipText(entryController.getCurrentBackup().getTimeIntervalBackup().toString()); + timeIntervalBtn.setEnabled(true); + } else { + setAutoBackupOff(); + automaticBackupBtn.setSelected(false); + } + } + + private void setAutoBackupOn(ConfigurationBackup backup) { + automaticBackupBtn.setSelected(true); + automaticBackupBtn.setText(backupOnText); + + if (backup != null) + enableTimePickerButton(backup); + else + disableTimePickerButton(); + } + + private void setAutoBackupOff() { + automaticBackupBtn.setSelected(false); + automaticBackupBtn.setText(backupOffText); + disableTimePickerButton(); + } + + private void disableTimePickerButton() { + timeIntervalBtn.setToolTipText(Translations.get(TKey.TIME_PICKER_TOOLTIP)); + timeIntervalBtn.setEnabled(false); + } + + private void enableTimePickerButton(ConfigurationBackup backup) { + if (backup.getTimeIntervalBackup() != null) { + timeIntervalBtn.setToolTipText(backup.getTimeIntervalBackup().toString()); + timeIntervalBtn.setEnabled(true); + } else { + timeIntervalBtn.setEnabled(true); + } + } + + private boolean canDispose() { + return entryController.canDisposeAfterOk(txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), textAreaNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue(), create); + } + + private void disableAutoBackup(ConfigurationBackup backup) { + logger.info("Event --> auto backup disabled"); + + backup.setTimeIntervalBackup(null); + backup.setNextBackupDate(null); + backup.setAutomatic(false); + backup.setLastUpdateDate(LocalDateTime.now()); + } + + private void openBackupActivationMessage(TimeInterval newtimeInterval) { + entryController.handleOpenBackupActivationMessage(newtimeInterval, txtTargetPath.getText(), txtDestinationPath.getText()); + } + + private void setAutoBackupPreference(boolean option) { + ConfigurationBackup currentBackup = entryController.getCurrentBackup(); + currentBackup.setAutomatic(option); + + if (option) { + setAutoBackupOn(currentBackup); + } else { + disableAutoBackup(currentBackup); + } + } + + private void setLastBackupLabel(LocalDateTime date) { + if (date != null) { + String dateStr = date.format(BackupHelper.formatter); + dateStr = Translations.get(TKey.LAST_BACKUP) + ": " + dateStr; + lastBackupLabel.setText(dateStr); + } + else lastBackupLabel.setText(""); + } + + private void setStartPathField(String text) { + txtTargetPath.setText(text); + } + private void setDestinationPathField(String text) { + txtDestinationPath.setText(text); + } + private void setCurrentBackupNotes(String notes) { + textAreaNotes.setText(notes); + } + private void setCurrentBackupMaxBackupsToKeep(int maxBackupsCount) { + maxToKeeSpinner.setValue(maxBackupsCount); + } + + @Override + protected void setTranslations() { + backupOnText = Translations.get(TKey.AUTO_BACKUP_BUTTON_ON); + backupOffText = Translations.get(TKey.AUTO_BACKUP_BUTTON_OFF); + targetPathBtn.setToolTipText(Translations.get(TKey.INITIAL_FILE_CHOOSER_TOOLTIP)); + destinationPathBtn.setToolTipText(Translations.get(TKey.DESTINATION_FILE_CHOOSER_TOOLTIP)); + txtTargetPath.setToolTipText(Translations.get(TKey.INITIAL_PATH_TOOLTIP)); + txtDestinationPath.setToolTipText(Translations.get(TKey.DESTINATION_PATH_TOOLTIP)); + textAreaNotes.setToolTipText(Translations.get(TKey.NOTES_TOOLTIP)); + executeBackupBtn.setText(Translations.get(TKey.SINGLE_BACKUP_BUTTON)); + executeBackupBtn.setToolTipText(Translations.get(TKey.SINGLE_BACKUP_TOOLTIP)); + automaticBackupBtn.setText(Translations.get(TKey.AUTO_BACKUP_BUTTON_OFF)); + automaticBackupBtn.setToolTipText(Translations.get(TKey.AUTO_BACKUP_TOOLTIP)); + txtNotes.setText(Translations.get(TKey.NOTES) + ":"); + lastBackupLabel.setText(Translations.get(TKey.LAST_BACKUP) + ": "); + txtTargetPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.INITIAL_PATH_PLACEHOLDER)); + txtDestinationPath.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.DESTINATION_PATH_PLACEHOLDER)); + timeIntervalBtn.setToolTipText(Translations.get(TKey.TIME_PICKER_TOOLTIP)); + maxToKeeSpinner.setToolTipText(Translations.get(TKey.MAX_BACKUPS_TO_KEEP_TOOLTIP) + "\n" + Translations.get(TKey.SPINNER_TOOLTIP)); + maxToKeeLabel.setText(Translations.get(TKey.MAX_BACKUPS_TO_KEEP)); + txtBackupName.setToolTipText(Translations.get(TKey.BACKUP_NAME_TOOLTIP)); + txtBackupName.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.BACKUP_NAME_PLACEHOLDER)); + txtPath.setText(Translations.get(TKey.PATHS)); + txtBackupNameLabel.setText(Translations.get(TKey.BACKUP_NAME)); + } + + private JTextField txtBackupName; + private JTextField txtTargetPath; + private JTextField txtDestinationPath; + private JLabel txtBackupNameLabel; + private JLabel txtPath; + private JLabel txtNotes; + private JTextArea textAreaNotes; + private JLabel lastBackupLabel; + private JButton executeBackupBtn; + private JToggleButton automaticBackupBtn; + private JButton targetPathBtn; + private JButton destinationPathBtn; + private JButton timeIntervalBtn; + private JSpinner maxToKeeSpinner; + private JLabel maxToKeeLabel; +} diff --git a/src/main/java/backupmanager/gui/simple/CustomDialog.java b/src/main/java/backupmanager/gui/simple/CustomDialog.java new file mode 100644 index 00000000..1bf2f61c --- /dev/null +++ b/src/main/java/backupmanager/gui/simple/CustomDialog.java @@ -0,0 +1,58 @@ +package backupmanager.gui.simple; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JSpinner; + +import com.formdev.flatlaf.FlatClientProperties; + +public abstract class CustomDialog extends JPanel { + + public CustomDialog() { } + + protected final void build() { + init(); + setTranslations(); + } + + protected abstract void init(); + protected abstract void setTranslations(); + protected abstract T getResult(); + + protected void createTitle(String title) { + JLabel lb = new JLabel(title); + lb.putClientProperty(FlatClientProperties.STYLE, "font:+2"); + add(lb, "gapy 5 0"); + add(new JSeparator(), "height 2!,gapy 0 0"); + } + + protected void styleSpinner(JSpinner spinner) { + spinner.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:10;" + + "minimumWidth:90"); + } + + protected void configureSpinner(JSpinner spinner, int min, int max) { + if (spinner.getEditor() instanceof JSpinner.NumberEditor editor) { + editor.getTextField().setEditable(false); + } + + spinner.addMouseWheelListener(evt -> { + int rotation = evt.getWheelRotation(); + int current = (Integer) spinner.getValue(); + spinner.setValue(current + (rotation < 0 ? 1 : -1)); + validateSpinner(spinner, min, max); + }); + } + + private void validateSpinner(JSpinner spinner, int min, int max) { + Integer value = (Integer) spinner.getValue(); + + if (value == null || value < min) { + spinner.setValue(min); + } else if (value > max) { + spinner.setValue(max); + } + } +} diff --git a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java new file mode 100644 index 00000000..966f5263 --- /dev/null +++ b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java @@ -0,0 +1,104 @@ +package backupmanager.gui.simple; + +import javax.swing.JLabel; +import javax.swing.JSpinner; +import javax.swing.JTextArea; +import javax.swing.SpinnerNumberModel; + +import com.formdev.flatlaf.FlatClientProperties; + +import backupmanager.Entities.TimeInterval; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.gui.Controllers.TimePickerController; +import net.miginfocom.swing.MigLayout; + +public class TimePickerDialog extends CustomDialog { + + private final TimePickerController timePickerController; + + public TimePickerDialog(TimeInterval timeInterval) { + build(); + + timePickerController = new TimePickerController(timeInterval, false); + + if (timeInterval != null) { + daysSpinner.setValue(timeInterval.days()); + hoursSpinner.setValue(timeInterval.hours()); + minutesSpinner.setValue(timeInterval.minutes()); + } + } + + @Override + protected void init() { + + setLayout(new MigLayout( + "fillx,wrap 2,insets 20 35 20 35,width 420", + "[right][grow,fill]", + "" + )); + + description = new JTextArea(); + description.setEditable(false); + description.setOpaque(false); + description.setWrapStyleWord(true); + description.setLineWrap(true); + description.setFocusable(false); + description.putClientProperty(FlatClientProperties.STYLE, "foreground:$Label.disabledForeground"); + + add(description, "span,growx,gapy 0 15"); + + daysLabel = new JLabel(); + hoursLabel = new JLabel(); + minutesLabel = new JLabel(); + + daysSpinner = new JSpinner(new SpinnerNumberModel(30, 0, 365, 1)); + hoursSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 23, 1)); + minutesSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 59, 1)); + + styleSpinner(daysSpinner); + styleSpinner(hoursSpinner); + styleSpinner(minutesSpinner); + + configureSpinner(daysSpinner, 0, 365); + configureSpinner(hoursSpinner, 0, 23); + configureSpinner(minutesSpinner, 0, 59); + + add(daysLabel, "gapy 5 0"); + add(daysSpinner, "width 90!"); + + add(hoursLabel, "gapy 5 0"); + add(hoursSpinner, "width 90!"); + + add(minutesLabel, "gapy 5 0"); + add(minutesSpinner, "width 90!"); + } + + @Override + public TimeInterval getResult() { + int days = (Integer) daysSpinner.getValue(); + int hours = (Integer) hoursSpinner.getValue(); + int minutes = (Integer) minutesSpinner.getValue(); + + return timePickerController.getTimeIntervalIfPossible(this, days, hours, minutes); + } + + @Override + protected void setTranslations() { + description.setText(Translations.get(TKey.DESCRIPTION)); + daysSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); + hoursSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); + minutesSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); + daysLabel.setText(Translations.get(TKey.DAYS)); + hoursLabel.setText(Translations.get(TKey.HOURS)); + minutesLabel.setText(Translations.get(TKey.MINUTES)); + } + + private JTextArea description; + private JLabel daysLabel; + private JLabel hoursLabel; + private JLabel minutesLabel; + private JSpinner daysSpinner; + private JSpinner hoursSpinner; + private JSpinner minutesSpinner; +} diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index 75280818..e273171a 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -6,10 +6,12 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.ColorFunctions; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import backupmanager.gui.component.About; import backupmanager.gui.component.Subscription; -import backupmanager.gui.forms.FormTable; -import backupmanager.gui.frames.Login; +import backupmanager.gui.forms.FormBackupTable; +import backupmanager.gui.forms.FormLogin; import backupmanager.utils.UndoRedo; import raven.modal.Drawer; import raven.modal.ModalDialog; @@ -20,7 +22,7 @@ public class FormManager { protected static final UndoRedo FORMS = new UndoRedo<>(); private static JFrame frame; private static MainForm mainForm; - private static Login login; + private static FormLogin login; public static void install(JFrame f) { frame = f; @@ -81,7 +83,7 @@ public static void login() { frame.getContentPane().removeAll(); frame.getContentPane().add(getMainForm()); - Drawer.setSelectedItemClass(FormTable.class); + Drawer.setSelectedItemClass(FormBackupTable.class); frame.repaint(); frame.revalidate(); } @@ -108,21 +110,21 @@ private static MainForm getMainForm() { return mainForm; } - private static Login getLogin() { + private static FormLogin getLogin() { if (login == null) { - login = new Login(); + login = new FormLogin(); } return login; } public static void showAbout() { - ModalDialog.showModal(frame, new SimpleModalBorder(new About(), "About"), + ModalDialog.showModal(frame, new SimpleModalBorder(new About(), Translations.get(TKey.FILE)), ModalDialog.createOption().setAnimationEnabled(false) ); } public static void showSubscription() { - ModalDialog.showModal(frame, new SimpleModalBorder(new Subscription(), "Subscription"), + ModalDialog.showModal(frame, new SimpleModalBorder(new Subscription(), Translations.get(TKey.SUBSCRIPTION)), ModalDialog.createOption().setAnimationEnabled(false) ); } diff --git a/src/main/java/backupmanager/gui/system/FormSearch.java b/src/main/java/backupmanager/gui/system/FormSearch.java index f7ad6171..8671b3af 100644 --- a/src/main/java/backupmanager/gui/system/FormSearch.java +++ b/src/main/java/backupmanager/gui/system/FormSearch.java @@ -1,24 +1,27 @@ package backupmanager.gui.system; -import raven.modal.ModalDialog; +import java.awt.ComponentOrientation; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.KeyStroke; + import backupmanager.gui.component.EmptyModalBorder; import backupmanager.gui.component.FormSearchPanel; import backupmanager.gui.menu.MyDrawerBuilder; import backupmanager.utils.SystemForm; +import raven.modal.ModalDialog; import raven.modal.drawer.item.Item; import raven.modal.drawer.item.MenuItem; import raven.modal.option.Location; import raven.modal.option.Option; -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionListener; -import java.awt.event.KeyEvent; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - public class FormSearch { private static FormSearch instance; diff --git a/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java b/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java index be83f0a3..0b2d6515 100644 --- a/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java +++ b/src/main/java/backupmanager/gui/themes/ListCellTitledBorder.java @@ -14,12 +14,12 @@ import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.UIScale; -public class ListCellTitledBorder implements Border { +public class ListCellTitledBorder implements Border { - private final JList list; + private final JList list; private final String title; - ListCellTitledBorder(JList list, String title) { + ListCellTitledBorder(JList list, String title) { this.list = list; this.title = title; } diff --git a/src/main/java/backupmanager/gui/themes/PanelThemes.java b/src/main/java/backupmanager/gui/themes/PanelThemes.java index 0e8a6f7b..32efc18f 100644 --- a/src/main/java/backupmanager/gui/themes/PanelThemes.java +++ b/src/main/java/backupmanager/gui/themes/PanelThemes.java @@ -50,7 +50,7 @@ public Component getListCellRendererComponent(JList list, Object value, int i JComponent com = (JComponent) super.getListCellRendererComponent(list, name, index, isSelected, cellHasFocus); com.setToolTipText(buildToolTip((ThemesInfo) value)); if (title != null) { - Border titBorder = new ListCellTitledBorder(themesList, title); + Border titBorder = new ListCellTitledBorder<>(themesList, title); com.setBorder(new CompoundBorder(titBorder, com.getBorder())); this.titleHeight = titBorder.getBorderInsets(com).top; } diff --git a/src/main/resources/drawer/logo.png b/src/main/resources/drawer/logo.png index 830c0f66ea1a3e866dbceb35ca3f413554addc66..864b897b881a5be7dfd80b5e2ce27b7e9b0d72b1 100644 GIT binary patch literal 55372 zcmW(+1yEbx(@k)9cZcHc#l1kGxE3#kBEg|ZaDuxOw-ycV4uJ*;_A6Q_ZpBLRqF?^s zoq5U3o5_8fyZ7ulyLTT%PgfluhXw}#0N`tCsJsIJfQ`tj4hsW$r`P;q0{MmQsbS^~ z0Qd_2?*jf3@tOhvmYy|L6b%CkPM$ut@{Rj4#D&NR&vaep9|ScxX~SgSv0-_lXo8tE zL|^G=sw7QiThv!qx(o2Nt+ulX1gDmm;eO1ScX6* zJphi;b-8)s zk}v;vpM2r_mYpO#iLyL>HZ(nZ#pDFl_h41-oqrxkK6W+wV9lp@S^6ya2WT~!{7gi) z=rh0D!HQ3V-K714M|X^8A^!p0Du=B5Nr2md?|ku#wMI#qFgL!ocHtP8CmcYYh>kAxRm0~sJh`eEEACx}-LTSXdvC64OT?RBB zdHi|mMq756`%VKLv8=+z*FdBAF6gg{cuQcKKCzWCZ3f5z7GQd+y%eE);*=NwbgMq{ z0^o{=ip1|Q^awWo)6$TXlRU>TyaN2>-XV!cTM#IZV&PVHm;?x*b`ov^!K>4M+yMS1 z@R>(yCH4bCXO5OsI7t(_2HMv~+Ks-zx$KgT z>mKsMp&I?WmgF?rB0JS-dlc2g7isiOZtxOnfEaiQ<%*0foDVFBsh3bWFLXm^=H_vg z0q5E3wDDjWyw4^h6F;+v#=r`xiaC`6yp>#tL)&2{`}hlZCk1$qe$M{nPd|v0`S9N- zHfc*l6MM_fa$1+BnaWUa6w93fpa|{U7~p|P)W)E zq;k5!V{(dAIeC1RVijj_1MM6SJcDv4npxS7vNxPK3LoD18cAnH$BE+F(uin?A~7gH zJ$C|Z;BQ(1`X~lJlII`Ubm}MS-qhUFJ6@|eVPpH^Rj;Z;?^ne)?=3UeSZw*#fSYoI z@6pab1LT04-heZu(YY^nAHFBUlTpLPh;trDh%!63rvUwogZ{uP7r+}PoMoovLUm)S z`ljVFhY)tQM9K$;=(e-3DdKpt&&6;N=YS<7SFhF*U&cmI==WDe=J#FptZn`l*&YHx&|l7T}z-uXAc zqc9k`k|>#pVN_}OGAq?EeYQq^iY2si_P$mV+@HvI%JcCgn(h>=mrWpU3TvZRP{1;=pfjxx!3OzT%ZA<0DbTgVfce7 zD0C!wXtA!6RSl3>xA!tR>g^Z{X(cmzhiiWd?V0`P5Ls)4D#r#`8oYA_yl{cT z&^L=w?qa;JxYBe)AUgEn=rHuVioLNeev~_XKp1g}2QY7XR5`9Tt5s?EXS8JG@QR9D zY^-MgZF#}!{Gxnpyza(&s*z-C6%C_mn?Ae-@~@eyYJjH1&U}gMJ4*vV))Tunr!LwWB&|>g#PQjKWI&)D0m~H-tUCIjkOj zsWZHFS!%0n$zSPpj9!ajoa}XpZ@h`0CwIZs(bnI>SXlJ9)@wG{C8k!E*c_ltTta>H z20RlR#t=$~8=E<(Sqkx~h?Ao&GR=FAyHX5303MeBAv|%y{zfLNUJ#?PW|G+6`^W_;uOWU*e~=?WczGW;pgz3s6kzCuTjp;(6@) zaD=p}BYE0CyOcLJk49i5+8Gtb|8$hZ@zPQUmWwq_8 ztAg)n-6BUuO#9P_X|?9ayUhmqOWV@(mSP}8d9JXfU-4+0r#6qGgD-f4@=;n$YZ`Wg z4i@&LZ)}w7_#@KC251Lg2uf}PjB^h7>9RVz(-*nw_mcD3N-lL=tm^HRSr@(B)=oAV z)}3RBsz;N)k#hfxaQw{C2C1JUV6dXqn6nVou7ppg+d5E~J2nY_XxDY%Y!kbo0K9wv zFnUO8+6>n>L0B6p0()J3`4q>uUwDY1T`HCL>@xXA{6l9jG3k%`$0v87Lry^y>BXv~ z9MPXG@#(6W1>N1(1b_wRodX-nCG2YVsjdf?>n2ieU9A9$luffTB5dg){@3u)vC5 zHK#0`Cfu}@YaW)C21AU^p1}Z|%@-N)3$;tI@Dh!POo1s$eid;jZ5_V|Nt4+P7kGeh zb^|j_*?KoM=lOEKbbqAT#Y#2Q_X1~$a3b!geG5t?BJBSl@tevIy5BE-+tF^dOWplP z$y6!r78Om*$KpgbX6+5m3@9b6{Mt++o*?d^CQnfaB=a|3C? z1Guxdi&inykqNIiYY?x|j>h+ip_G}Y_r$tuEsP>DmSH}ijt>SrpyKMk9jXh~Lq6a9 zx4<-|`svP+6y77k%}cKB{_ibrIzF~97CenU)+Tk7{LoTI8#%N3e*Demt0rtKGYvXZ zMnR39Z_?vgC*)04YiJ4Bkw?7)0m3M`SNm2nWJ>m7qoW+dD6Y%`KVS$zFXQ0b$ioNn z!0OROm-seGqK}GSuI+XgW=Xv2>q?iuQ>@P0!>`pTd(i2u<~(3u4(y9s^ccBgv%;SZ zXr;o2pH+#|RexOK-z;1$wTJ8oMLz7D{^-lzGzT77AAa7cV>EYd`fN3Dmi;3cTD<<0 zb`~Ad#JU>wwq}&&lJ_C#m#Ld;jVTMTj1h4D10aXyxYoHY@Mkl_E{R?`TGExd8BI;3R{hlAR?^kK zU_OrdRF{u(ah|+YWb!m~*0XhIr3qDYAL=st_Kvg7Pbop3)z@wLgIfqy492`e5X2N2~)Pt22L69 z-fvhNM}kLpaBF&A%0}EJWlG>7LaPDp>Jge&Dnp_9R>{--%E@k)9iV9Y7=qi#0*Hd{ zp}UDcUyS>x!tPeE+>pCK6xZ<@+vjY+BR=?9I*tNS z6G}vKJyGkTzr4{|E11hTp$TaXrX%{8q++V$u%K;djbhU93~f^J7wu-Gm6*j$PFwI~ z+4Z!z^-}h7WoyT(PEv5z)olEu;?$#VB7Cgju->7}@X;v0i+S3G79(yN{I>&G6sx7| zNEVN$>VO9(kn#Rr+DRk>>(xcMTh+@pDd@!tt=!2Qt6Ka!&Qn!^P+v1Qi(aMk$LnHj z8)Y6}Ca?MSd+b%;ozWoy-Y@Ww+_l%t)Iq?X<oDIX?^iWi-EJ`!m;3G+)GVYrW%_io27>BO;BneHpuc@2ic{1x47h zQ9_qgKHV^@Xz;O#cBJFN@ygT996)5hS6dDh9NK)>IOpa=VD&NfY{nt--hd}Zzze?N zFTD94ae5j^Qq~~Ne@b1uzBuu{&)!5+%d|(Igte;_4{dzWRT=*KZm)0uHCxrTa!-^4 zVQBJSyiq8#?eEcvJ>pa&Uo8|j@(@+MrWbCzip^dn63El2D$}WP!GrDtN}?^>2Z5@O z8$(_OFNWDPtA99LD!XO|*TQz(E0P^3?xBxwlI@WThQ)VVYCdoIXSBZQYj>;h!8G8i zcqBd7?C-4Rr%z~$g2_3PyPSYWK0I~-|8?#1FCJ~(=D(tUD>ofpGKPvOA#`xA=#=b7*BJN%~&d@AaE4Yj^ z`{W+zTsibQ$96H#U|q}U%(tVHiZ-qPZM5x%Q)fXzbwJ^7dCZ3b8jTYV#owfe_*gAF zd*()FApwPI{1*(h=3a!b;pOS>f{)+t5k0DRz|RS5d7TN3HFPo=+4YS)|HYe`F1$*8 z!G!VE#0Mzl*lFOM%dh_oLy6LUiFeuf{9JoOzcTRL;5YNS3>RA7odU9JS<@c~GwS*$ zN}pr@OL~=1*n5!>!W+_{EVyg&OAPYQ*DVV6OadOjHu`{>)%<$7Q{!Y1yN>;JW?oTv z`oxQ`p-=szc+ z^ST)RB$eARPWR--)uG0unf6)N(fINCy5K^lfLBX^M@qdApI!A_JfPCxB(f_ay;-@ed@Vy}-1n?`SGBGHe17I>ly{OwLF!UoUdo@ghKb6h){nLRBpU~ z+THON5p*ABag*L6Vp-YpD!x#fXS8|892%G=W4ya zevk5q11`oVI144heAXLVAmnA^|3UTwPzWPb`7q^DIG(T>769p$F5>}p3G}h^r?dj* z+5af2{x&yX=+_1%wiXZ~LItOT&RTrs*HY`0%7h=<*o^r0P@c9qF1*;i3<&z|*!WX( zY8ATT`Tu-KD}?EWVr%bTR)W{G{poddflZ(`cZGnuiP{HKYP}QreEo4m$PIa3*9=*S z^GkEde>y7SXrVi!0`AO03ohYL#&gjyWXSXf_G50EY%Tp2w4nE)ctB-sWPVi!*rW$M z6EbywGQO<5fb`x_mXnwyw4vXD>T6^l!SmjRmG`XFTISP2+R@8erb5la1G|Mf91^KH zlki>XxXDcpZLLv>N7)OnuJBLc!WP4Rp{f}c8lR4=qbv3;K5mIzVe7bw7a$Bro!fE8 z6Ws!xvyzk?&dPAU7sCA%?U)uceyiAd%E-v0>cUUAfZs94`76kRRy=O7{~p69j1W1letZ*p@v3AiuaNr%?5UyiOa2{C zS=KDeq8V*owRG}=cnm_8vOMsuQf=ZD2;BX-$@-u8Tl6f;Bt%TAiT&#k%W+pa7yK7Cs zO^8TDZv0zwU|`-a&|UCpZ}mLdpAz{1^t`pGmm!Al@soqrJzj62&Iwa;k?#=p-)^xg z4uRTC-?dtzzTWPKWLWlb8-aN8(v+PPS`y~ig#$24peosx2|yN!%5HjoeX-PBWpktQ zIp3$BR$&5zFXZ5t*M<|-rsyE1Ggt_FBXjvH+$vlBXEFtOGtEBuM>CTw!zctvU=ldy z_)N>P{%^gFj6lC_9p8r>0uZr5UvJRDF{=-dB14-=?^aPpm&NH5)^vp(=}mC*;)l ztU@hCE4qBR;*>UzZ2w!nw*}=IbmvL(RaCiwJ5~^OKrp6Z=ty7-j;aLiIB%MX#0e{? z8oN(W%`$B{TV3~AeT%Llyfx~*kP^6b+c?is*%-=`V^bllyUbV7{|%>XP0<<}p;0;dyI$li$6h| zI6(t{!pfueXX4EhGS5sL<3_FN9-M4t?54cUfG7Wu5o3W6hp%)M@0!A=BG4N zVYGvn0PBdXk;N5{VucpUfslx{0MRw-|6WvpeQ>Q8b07 z^fJ5R$;s0{*B|f#M^$MxcEw1#O|7oc7wpQI zp-N1N9%5bC`SpC?RS(-nup%T3_eB={@Tnh z*a))Hm9v$?yuW@JA{5>d{P*X1+s`fWm8s&UUaUc+67yjC8)dFzx>U+J;%9ME@$zkx z$j|KZBb{$2+lHZ~$6Lb$*Z=&jjMK7;Zg}}^i}6x4qma@Tcdva2exbbf5+08Tn~idh)z#DQbb$R<%-SMi_Wd)#bO3hxG72CJ5at`vW~NDgpF*sD5N+t($*;r%V1w+F^Gwvqhr zPWa>rt~HfeCA}J4u)*c{jt6s9swn_2z+>{W@78_5ZB$=3kySS6CGq;20sL%gl1-k)zg9vPw0Xxz3FtICM_(bhkg>YPkcItaqYgPVXQ+U~=wcoAqm-$2_yCkz3_ z|L_1YNW5NLF0R7%>BB=(T+UPn{VBh2yw#3q*!a#(V9)c@sZ`x}8^6I}CxSH!E=ICB z3M=#|h*VSJHHa1A10X{QBkH;9IXeB>$nuY*^%N>o(m*S~`I4f~Tpb0c`)$y0XHJVQ zOG8ZA`6(zcMvaLd4OY1>alAhF+0SAsi)(~V4`9h8z{r$S%fukbJsh=zwha_!004yl z5t((xv&cj%hPL02VFPjpQw0};$Ug(WKzM0bahH^n;3q`}pAcoZkl)*a3>eG4S4S~t zJ|W%&h!=G53~DC?Rge8_H2Uz1CUkWcHV{fuB2QR`ATe+b>Ea8tdbL>Bu%l(&Z*)X1 zQRf;s=JwWk8wXt|rp^Vbq@QLW+8;fuQ)Oq7x27`C86673?6Nf3&61|#1n3!=L$u{o ztf62%rFf}a*_@)!O+oL&j|$zr_?oSk#pEzLAe6y`Sn;xSvE3ZTWQ(JUQs|Qi?$9|g zz!ET+-*k!{YPGT1N0RI6OMSrtc#CoshBCKT<0Y2m!Z{vnx-v!(k~w#-r2nbw#}~JF z@4zJBYa0hgkkm{>RdlBhyRu)gc4DvPkT!@kFpyEnJv@~c8pYA6(vCAkIK}#KqaMfA|BdRB18}0f(w6bNs@VrQB z|5De$DTOWqd585ur(+&9>$9^I+CZ`3*GY_qHoglnY5vk9wA;d~F)(yigy3X;+#7HJ zX5Ti!eWXRrh>!=TD~~N27Yk``QldobVL73uh|%?I9^7&%APH{c7jxz^D(+gB_A42S zOPIE$(azVmGr40T5Ks${!{+2sKkwl=i5^rso=jcI2i|)q`ut~@DZXTjxc^Te^Nsob zt!Y~n4b+vbcJgCj_pxzPxHPC*4F0|&ytCO0A1UC1_Fg-9k!qq8H=0xpDd9SJp+=FU z?+<3;GYel&o3r2^lcB|{mYf-*C5kx>e04}#*i54uoy@Nj9Ed*W%4GRVZq+V8{@q=L z?#v!vvyN~xWkiS4C?EXRL1UM^4{Dy~8@c@1r4E{_F%mo&t}tR2yOzt{H8aA61GG~! zmQ&z2@ySlqBhF84^d}sWIjsa6U9DK5m7Xe65syo__*PJzmTv(OHK67wy0k49Y zE*hIr=QZj|t4hzYN`BuL22~nmIs0#&YF6gmw*dOFHkoDFsW(r9(Gp$4yR>({CI~k* zNC2-kS}oqk6sWcJCyNBG44)b2f8e&lHH2d#ijnfdCE(uAWz56 z?LlfmLGaZpcvivEy6N=Ngzv&K21`!E1dFt(yB8yZkJYb;oN$)0H!*L>*I}>bY$Pky zGdGo}H`*G6*&-tV-W9KVltWl5nc*~1u-}k^6Oo{Yduo9Y{Nc(N?7?O0OXvoMycj405orx>m z*u5M$F6(cLVGj{1;fbS6%b8?aUuj6t(O9M=ViWF)Ss8kBNG<{7*H25z@t9kT4yCq{ z#q;|qiZSKC1siigAw*VWSv*$sYZ_uKY}*#fjB`!=hzALQheMPvd3RJTjWgSE?Uou0_bV0tOtcH>`A@N;Nx_p=>ZWcwFIRs`C12#W6zsT^4H~t=S|6Un z)8?cjC-NvaSsOW-mYCyl2Xfi+H6S|RKCcauvS*!>^0wo|_MeVZ3oH!fCqsI-N7NEw zk9qD$8=fNpMaiE8g}ZBrMp|%n2L7O)t8{uv9M`s;$CV^3kn2;&7@Jg#!uNNgp4vw8 zm#?*xr40Cyq&NB+Yh8O{IO-Lu@;QY{Q)Hc`dLEx`Y9Ua==mTt*aHnh`J}V-v^78fX zj}odR!1Yf1sIBU%OWzMAH^`YN_**9wyz;#&_PHXR+j#L`TL$wpw` z&usn9Q7*Rm5fa4SJY)4jTU_LB{AzDEw>rtLhwrQDeyar{aLP-#>W*1uZD<#yz=Qxv z)Qu#`X0b(Nc!HF>Mgs>efI&SyO8E0Wx_+4}F?_?gF(4Wr$3X=;`K zy=%T(DWsg^VXTle877X1O4T|PYPm#->#+G1f97vjaTi)nxXV`UMj9YGHY%Dd3K1(U zwPpaWlo`${tE}CBFxb_tLo-gZ2w~KouSFe0u_{7FH zoN1TC$)wf#XThN5EKbe^~KR?zKgO446WiX=I&_ut62dygYDn^cfag<(B(Wb3po?fB6m8_l14TQz~va zcct$mb9_JSR`6?U=%vDUYud-ohPcUDQBuCVX;QX4F@?ye)y-7@4mI>YanrLzfoax} zTUo}P&8;!5+5`IdErp!K#Yxs$<&+6OvkA;BoymFLjwr4 z5$^f5pG&uBI#CyC654lD@E~FfzuH6NJxI5(D%FBptX~6|gZUPFCY(K~FF4{3aKlH)2 zJC5redcIp*%8Taq41tW;%QD(ptpu?GN88>>BWhu+K+xOKp4~CI_ZCXSe`$agyi%sO?-cqGR6!Stz3ekq@%+@raSEa6JvK zH4RuNnBOEVJE`MajZcay{Dkw|Faw$batZ~It`D~c@m!y3X)JRRv0YrqIr%}A;W|_e z`n)-1g(em?j0PJ`Ep<9hVJdu0YIs{%!`OfZTnZnSG$;$f?eqb_8dYyY_He9i7NQ-gGsaJmwnJ+idocA8=*p_NOP5=*M}Z-y3UpMgjF8 z3j-Q2nB&%1&!Uh)Z&|GITMZ3KF06Gm4^P~_Asztbkp&)%UI%BC2GZsiz@TRL18lYU zXKwPV3DDGY@z4R%HafWW(QDBvfh zXB*$a%Pj3});~1~ZESPTER9c+F`npKD28?$wvRiu^Hfv-SqvL%i8slT1o6Gv>tcfj zG%mm0IZS35*A$A_F0ipP6LPpg%n$E$>xExqyg^Ag1))-TYns=fM>69im~GQbG)~&vVU06v6xEd9;$fCPb+^^+QGjk7F%2)$ zm-$%=RNP4{?qAJw)J`Bj1iq!(!y*&>O>9J(&O1P%n~(w*(c zExS1zfgQ7BrJG0=4`sBK3Q#}<1bqRQODc=tP^-kHe1PS97*$|<+#w&$fYp#FQX-uVIpyLR(Z=Aw886&WsRsh|63Rmq4C zbME1R&hg=+&Ln_o#mXmVsWkYNiapu|%1v4?1KT7Ivu!-F38#2!E`M!Ct{H=OfY^*K zx%4H<6ku#*X{v9@2nhlLg|Ndw?Wtc3X}!JK$ny?ldyR~=g`MJu4-)0-mD*9}0_Gbk zPT#&2xy5>ui|Enq@YAfIB%E|~|M*toko>pJTnJsF=NcU{;Mt}-p^_dB#y5IGS8IU9s9cqSCRLv|WgiA^*j8 zgk3=Ee=`Uvtdab(8nV1mcJoq-rS(9vY3*+I#_9#I98xZUBM2eT{110Agi({HKl{+pIGE8jtdv+bg zz0}?7;jaHn8RIY?0_c(5kQq42c1qB4HM@m|Rj$Qda(1Ca1-lIE7Zm!=9kyUxgjID+ z>fq0J#kUHjxS)>QR9gB#;Fs~qkfFp0I3oXJz%Ki45K99x&R}iRXJuL->nlP_%BTTs zPMQ-sQqd*GMTR7+-*!l{H!W=Hv-OuO<7Q>7T&&8UUb88RZ&0C_sVHSP+mkhUA*d=AI$^}8WoW`bV^7I?gPMN7g%t+Vtvbxh7K=oua_O;#W> zr4K=mwa@%?MIP=em$ldoDzRVV;r{7aS1UM)u?!vV78^yq@^@E&C!uzwM@2>bRx{>D z<~0~it@!U7nDJUCj`7RNk~I@zPrqP#aX~>6G~>}!&`or20oI>Y=z6GK z!MQ9oEDa<`yBxfpnE2V4nYZFfOym@d*CRioSrF$|rvwM~wYA1Tq`z`8?3Yb5qqt~f zQGpf~2|g`W)eMpGDKzb5vc`Q5yd7t+kFzX7v}zKMGHG1Juli*!A`OdD6AaqxyRVuq z%!GV>#2Otrf&;#In4JlQ8t}t@#vh(2;rWB8sP?5r_k-59%O)!0YGnCyFq^mKg7zX& zJ7bVDlw)m^JZ&eRvKsRVvT^hZ`(^+8E0_c05MvO(0EuvU6t76`G80-E#evhsyZzh$ zq7h0EZoBVT2kr64D=S;?aR=>LY1jWNN=^0TVp^a3z2_&2-8G?-94F0Ui_d!0_?dq7 zUrM|rr-~F?q&&Abj~a7h-nqt)*!E6Jx(Kh{$@Qw5DwGB!>AhbAf}~7L9_`Jp(P$ki?S29z13+Q%5sj>CVZIJ zmttnNC4}#OX@)WRYFqJ;+LsZ0CHUL8<-6&?92DU_Vl3$Vj`^LSfFKHwE}C=?LB6mX zOobNEIWIN+PJIu#8hxw+CaL7N3wU`{J&@Qg1||sxCu4E8{j;X}>EZx2RI;V0`$T8D zy5l$micwI?Sin!_@*U;cE`nHCA3_ueBY9%WK?;s#6OF5hhi<}Z(hs-7zIcDBbozMa zcCr|{*;kN0LCm^@(~DQ1j`~vFXGV6KvC)=wtgmh%n`q@< zY7ncF!~^1bPc>xoiG5~~PR+($>xP*9N=I^2MsrIAbE)*lMr}IWCd|HJfMx6mDs)fGCW4oP9{y;rneQY6sUXZKeR7a8a>ObrvCYZbLpVk2*m) zI7;KZFRLIG(1>?bh{A=5F$vDq$NzfzSxcPn9=wL(VU4Ns*3F;}wUPo;pTH9ox0LT` z3f~j%EKp1co2StjJ;fO`oFP&FT-Hw_rOfYv)S zE(3gJ^8hj~@$g`Vd3pSGGmPuE)nj^H*bdU9x?T7V(muTYWFSXxFdf+6SfNi90Py&Q z<{?n)o8SwNZ2-tj#?N*lZvhF7=R>&+9yBGJnfTKvZ}tA8GevO!hvM_ZzLjpYr*$>9 zk<>-fCBC93hz%DCuG13d@l%ZHOGNi$`s!+s>Xmy;luSt7{|Z_!^y4J~=&%>$Mb990 z_<{c(uvhZ@F|KP;K^zmbEe6Ob8RY~Japj{on=NctuFc(3Nr)C2>g|?x>MQ@YB`J`qj;;9I`ETI%;IWHSixm)epm3 z`_o$}jEVz>b6NZ6$YoU=VrC;`;5IBR>`&z0+C@-{N9)uXG3yJ5PmmY7v5qo-rCGt_ zk_R^3kvVT6cODAt(TM37XlHzxKKm!`{@VL}y0YO--=#3i^04~>Y)fL?#_54Sref?_ zV9FZJX(lp{c_Mbx3zy4(&_aTFsVLI&DV?P=syfDtT~6Uu_TW5f-cs2@mUDeWH_zNJ zDUOI18X{7V3Vt0wzsi#%Yq7>9kC#?E+#7G?c>Ju|f4&$*pT4#jNI)8>!&((a19wU* z48on2udL&n00%3Qrc_yuF_=kFn7npOvH!R*W*5<7jcJHBwlLtJF#$Cs&SsY|GjkxX zh7`qDHlfW}fhC_f6rKfl?|7}Ixe+xC9&3(8ak5-*IDVUAW9aF7(zU} zW7ZkN&S+qfd8@cN(qv*&B9$+qqJdJexDs_j)g5*gpf0F#7Q6JGQ~`&c2R0zJ@PT0V zBrxuWhcTm7@-tBo1&*b(tXi%X(i-r| zgCdjF!mFIp|GcB1D*he>`Z_ot#73@XVi`@#5vn5N(l?7B{JN�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 literal 9601 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 - - - - - - + + + + + + + + + + + + + + + diff --git a/src/main/resources/res/config/config.json b/src/main/resources/res/config/config.json index 815dd17a..e4f650f5 100644 --- a/src/main/resources/res/config/config.json +++ b/src/main/resources/res/config/config.json @@ -12,7 +12,7 @@ "EMAIL": "assistenza@shardpc.it", "SHARD_WEBSITE": "https://www.shardpc.it/", "LOGO_IMG": "/res/img/logo.png", - "VERSION": "2.2.1", + "VERSION": "3.0.0", "GUI_WIDTH": "982", "GUI_HEIGHT": "715", diff --git a/src/main/resources/res/img/logo_old.ico b/src/main/resources/res/img/logo_old.ico deleted file mode 100644 index ab96f69c06a355d1c498f4301b27e9d9e717993a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174328 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 diff --git a/src/main/resources/res/img/logo_old.png b/src/main/resources/res/img/logo_old.png deleted file mode 100644 index 6e543187f0a121f441404575331955100a74c14b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6277 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 diff --git a/src/test/java/test/BackupManagerTest.java b/src/test/java/test/BackupManagerTest.java deleted file mode 100644 index 46748894..00000000 --- a/src/test/java/test/BackupManagerTest.java +++ /dev/null @@ -1,5 +0,0 @@ -package test; - -public class BackupManagerTest { - -} diff --git a/src/test/java/test/BackupTest.java b/src/test/java/test/BackupTest.java deleted file mode 100644 index 62dcb5a9..00000000 --- a/src/test/java/test/BackupTest.java +++ /dev/null @@ -1,3 +0,0 @@ -package test; - -public class BackupTest {} diff --git a/src/test/java/test/SqlHelperTest.java b/src/test/java/test/SqlHelperTest.java index 48681d0b..92e415d5 100644 --- a/src/test/java/test/SqlHelperTest.java +++ b/src/test/java/test/SqlHelperTest.java @@ -25,7 +25,8 @@ void toMilliseconds_shouldBeEquals_forValidLocalDate() { @Test void toMilliseconds_shouldReturnTrue_forNullLocalDate() { - long mills = SqlHelper.toMilliseconds(null); + LocalDateTime date = null; + long mills = SqlHelper.toMilliseconds(date); assertTrue(mills == 0); } diff --git a/src/test/java/test/SubscriptionHelperTest.java b/src/test/java/test/SubscriptionHelperTest.java new file mode 100644 index 00000000..c89dd1c8 --- /dev/null +++ b/src/test/java/test/SubscriptionHelperTest.java @@ -0,0 +1,98 @@ +package test; + +import java.io.IOException; +import java.sql.SQLException; +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import backupmanager.Entities.Configurations; +import backupmanager.Entities.Subscription; +import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.SubscriptionCreationType; +import backupmanager.Enums.SubscriptionStatus; +import backupmanager.Helpers.SubscriptionHelper; +import backupmanager.database.Database; +import backupmanager.database.DatabasePaths; +import backupmanager.database.Repositories.SubscriptionRepository; +import backupmanager.database.TestDatabaseInitializer; + +class SubscriptionHelperTest { + + private static final String CONFIG = "src/main/resources/res/config/config.json"; + + @BeforeEach + protected void setup() throws Exception { + Database.init(DatabasePaths.getTestDatabasePath()); + TestDatabaseInitializer.init(); + ConfigKey.loadFromJson(CONFIG); + } + + @AfterEach + protected void clean() throws IOException { + TestDatabaseInitializer.deleteDatabase(); + } + + @Test + protected void getSubscriptionStatus_shouldBeEquals_forExpiredSubscripion() throws SQLException { + createSubscriptionByStatus(SubscriptionStatus.EXPIRED); + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + assertEquals(SubscriptionStatus.EXPIRED, status); + } + + @Test + protected void getSubscriptionStatus_shouldBeEquals_forActiveSubscripion() throws SQLException { + createSubscriptionByStatus(SubscriptionStatus.ACTIVE); + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + assertEquals(SubscriptionStatus.ACTIVE, status); + } + + @Test + protected void getSubscriptionStatus_shouldBeEquals_forExpiringSubscripion() throws SQLException { + createSubscriptionByStatus(SubscriptionStatus.EXPIRATION); + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + assertEquals(SubscriptionStatus.EXPIRATION, status); + } + + @Test + protected void getSubscriptionStatus_shouldBeEquals_forNoneSubscripion() throws SQLException { + createSubscriptionByStatus(SubscriptionStatus.NONE); + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + assertEquals(SubscriptionStatus.NONE, status); + } + + @Test + protected void getSubscriptionStatusTranslated_shouldBeTrue_forSubscriptionValidStatus() { + String translationActive = SubscriptionHelper.getSubscriptionStatusTranslated(SubscriptionStatus.ACTIVE); + String translationExpiration= SubscriptionHelper.getSubscriptionStatusTranslated(SubscriptionStatus.EXPIRATION); + String translationExpired = SubscriptionHelper.getSubscriptionStatusTranslated(SubscriptionStatus.EXPIRED); + assertTrue(!translationActive.isBlank() && !translationExpiration.isEmpty() && !translationExpired.isEmpty()); + } + + @Test + protected void getSubscriptionStatusTranslated_shouldBeTrue_forNoNeddedSubscriptionStatus() { + String translation = SubscriptionHelper.getSubscriptionStatusTranslated(SubscriptionStatus.NONE); + assertTrue(translation.isEmpty()); + } + + private void createSubscriptionByStatus(SubscriptionStatus status) throws SQLException { + LocalDate start = LocalDate.now().plusDays(-1); + LocalDate end = LocalDate.now(); + Configurations.setSubscriptionNedded(true); + switch (status) { + case ACTIVE -> end = end.plusDays(30); + case EXPIRATION -> end = end.plusDays(5); + case EXPIRED -> end = end.plusDays(-1); + case NONE -> Configurations.setSubscriptionNedded(false); + } + + Subscription sub = new Subscription(0, LocalDateTime.now(), start, end, SubscriptionCreationType.MANUAL); + SubscriptionRepository.deleteSubscriptions(); + SubscriptionRepository.insertSubscription(sub); + } +} diff --git a/src/test/java/test/repositories/ConfigurationsRepositoryTest.java b/src/test/java/test/repositories/ConfigurationsRepositoryTest.java index e5db56b4..a8f14a27 100644 --- a/src/test/java/test/repositories/ConfigurationsRepositoryTest.java +++ b/src/test/java/test/repositories/ConfigurationsRepositoryTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import backupmanager.Entities.Confingurations; +import backupmanager.Entities.Configurations; import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.ThemesEnum; import backupmanager.database.Database; @@ -20,7 +20,7 @@ protected void setup() throws Exception { Database.init(DatabasePaths.getTestDatabasePath()); TestDatabaseInitializer.init(); - Confingurations.loadAllConfigurations(); + Configurations.loadAllConfigurations(); buildAndReloadConfigurations(); } @@ -32,12 +32,12 @@ protected void clean() throws IOException { @Test protected void equals_shouldReturnTrue_forSameLanguage() throws IOException { - assertEquals(LanguagesEnum.DEU, Confingurations.getLanguage()); + assertEquals(LanguagesEnum.DEU, Configurations.getLanguage()); } @Test protected void equals_shouldReturnTrue_forSameTheme() throws IOException { - assertEquals(ThemesEnum.CARBON, Confingurations.getTheme()); + assertEquals(ThemesEnum.CARBON, Configurations.getTheme()); } private void buildAndReloadConfigurations() throws IOException { @@ -46,12 +46,12 @@ private void buildAndReloadConfigurations() throws IOException { } private void buildValidConfigurationsObject() { - Confingurations.setLanguage(LanguagesEnum.DEU); - Confingurations.setTheme(ThemesEnum.CARBON.getThemeName()); + Configurations.setLanguage(LanguagesEnum.DEU); + Configurations.setTheme(ThemesEnum.CARBON.getThemeName()); } private void realodConfigurations() { - Confingurations.updateAllConfigurations(); - Confingurations.loadAllConfigurations(); + Configurations.updateAllConfigurations(); + Configurations.loadAllConfigurations(); } } From 7612e65acd2de504468adf4fadee04e0c7fb4156 Mon Sep 17 00:00:00 2001 From: DennisTurco Date: Sat, 7 Mar 2026 14:39:25 +0100 Subject: [PATCH 06/17] ui migration fix and code refactoring --- .vscode/launch.json | 66 +- .../java/backupmanager/BackupOperations.java | 88 +- .../Entities/BackupExecutionContext.java | 13 + .../Entities/BackupUIContext.java | 16 + .../Entities/ZippingContext.java | 24 +- .../backupmanager/Helpers/BackupHelper.java | 45 +- src/main/java/backupmanager/MainApp.java | 4 +- .../Managers/RunningBackupManager.java | 38 + .../Services/BackgroundService.java | 9 +- .../Services/BackupObserver.java | 13 +- .../Services/RunningBackupService.java | 1 + .../backupmanager/Services/ZippingThread.java | 2 +- .../java/backupmanager/ZipFileVisitor.java | 2 +- .../gui/Controllers/AppController.java | 22 +- .../Controllers/BackupEntryController.java | 34 +- .../gui/Controllers/PreferenceController.java | 27 - .../gui/Dialogs/BackupEntryDialog.form | 329 ----- .../gui/Dialogs/BackupEntryDialog.java | 536 -------- .../gui/Dialogs/EntryUserDialog.form | 122 -- .../gui/Dialogs/EntryUserDialog.java | 183 --- .../gui/Dialogs/PreferencesDialog.form | 124 -- .../gui/Dialogs/PreferencesDialog.java | 182 --- .../backupmanager/gui/Dialogs/TimePicker.form | 187 --- .../backupmanager/gui/Dialogs/TimePicker.java | 279 ---- .../gui/Table/BackupTableDataService.java | 105 ++ .../gui/Table/CheckboxCellRenderer.java | 35 - .../gui/Table/ProgressBarRenderer.java | 14 +- .../gui/Table/StripedRowRenderer.java | 34 - .../gui/Table/TableDataManager.java | 106 -- .../gui/forms/FormBackupTable.java | 72 +- .../backupmanager/gui/forms/FormSetting.java | 3 +- .../gui/frames/BackupManager.java | 12 +- .../gui/frames/BackupManagerGUI.form | 899 ------------- .../gui/frames/BackupManagerGUI.java | 1195 ----------------- .../Controllers/BackupManagerController.java | 132 +- .../Controllers/BackupMenuController.java | 95 -- .../Controllers/BackupPopupController.java | 178 +-- .../gui/simple/BackupEntryDialog.java | 10 +- 38 files changed, 355 insertions(+), 4881 deletions(-) create mode 100644 src/main/java/backupmanager/Entities/BackupExecutionContext.java create mode 100644 src/main/java/backupmanager/Entities/BackupUIContext.java create mode 100644 src/main/java/backupmanager/Managers/RunningBackupManager.java delete mode 100644 src/main/java/backupmanager/gui/Controllers/PreferenceController.java delete mode 100644 src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form delete mode 100644 src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java delete mode 100644 src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form delete mode 100644 src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java delete mode 100644 src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form delete mode 100644 src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java delete mode 100644 src/main/java/backupmanager/gui/Dialogs/TimePicker.form delete mode 100644 src/main/java/backupmanager/gui/Dialogs/TimePicker.java create mode 100644 src/main/java/backupmanager/gui/Table/BackupTableDataService.java delete mode 100644 src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java delete mode 100644 src/main/java/backupmanager/gui/Table/StripedRowRenderer.java delete mode 100644 src/main/java/backupmanager/gui/Table/TableDataManager.java delete mode 100644 src/main/java/backupmanager/gui/frames/BackupManagerGUI.form delete mode 100644 src/main/java/backupmanager/gui/frames/BackupManagerGUI.java delete mode 100644 src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java diff --git a/.vscode/launch.json b/.vscode/launch.json index cd31bd86..a8ade45c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,79 +6,17 @@ "configurations": [ { "type": "java", - "name": "BackupManager", - "request": "launch", - "mainClass": "backupmanager.frames.BackupManager", - "projectName": "backupmanager" - }, - { - "type": "java", - "name": "MainApp", + "name": "Backup Manager", "request": "launch", "mainClass": "backupmanager.MainApp", "projectName": "backupmanager" }, - { - "type": "java", - "name": "EncryptConfigFile", - "request": "launch", - "mainClass": "backupmanager.Email.EncryptConfigFile", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "ConfigReader", - "request": "launch", - "mainClass": "backupmanager.Email.ConfigReader", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "SidebarTest", - "request": "launch", - "mainClass": "test.SidebarTest", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "DecryptPassword", - "request": "launch", - "mainClass": "backupmanager.Email.DecryptPassword", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "EncryptPassword", - "request": "launch", - "mainClass": "backupmanager.Email.EncryptPassword", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "Current File", - "request": "launch", - "mainClass": "${file}" - }, - { - "type": "java", - "name": "Translations", - "request": "launch", - "mainClass": "backupmanager.Enums.Translations", - "projectName": "BackupManager" - }, - { - "type": "java", - "name": "MainApp", - "request": "launch", - "mainClass": "backupmanager.MainApp", - "projectName": "BackupManager" - }, { "type": "java", "name": "Background service", "request": "launch", "mainClass": "backupmanager.MainApp", - "projectName": "BackupManager", + "projectName": "backupmanager", "args": "--background" } ] diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 5e161bfe..5aa1c015 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -27,31 +27,41 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; -import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Managers.ExceptionManager; import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.gui.Table.TableDataManager; -import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.utils.FolderUtils; public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); - public static void singleBackup(ZippingContext context, BackupTriggerType triggeredBy) { - if (context.backup() == null) throw new IllegalArgumentException("Backup cannot be null!"); + + public static void requestSingleBackup(ZippingContext context, BackupTriggerType triggeredBy) { + switch (triggeredBy) { + case USER -> { + if (!BackupRequestRepository.isAnyBackupRunning()) + singleBackup(context, triggeredBy); + else + JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); + } + case SCHEDULER -> singleBackup(context, triggeredBy); + } + } + + private static void singleBackup(ZippingContext context, BackupTriggerType triggeredBy) { + if (context.execution().backup() == null) throw new IllegalArgumentException("Backup cannot be null!"); logger.info("Event --> manual backup started"); try { - String path1 = context.backup().getTargetPath(); - String path2 = context.backup().getDestinationPath(); + String path1 = context.execution().backup().getTargetPath(); + String path2 = context.execution().backup().getDestinationPath(); - if(!checkInputCorrect(context.backup().getName(), path1, path2, context.trayIcon())) + if(!checkInputCorrect(context.execution().backup().getName(), path1, path2, context.ui().trayIcon())) return; - if (context.progressBar() != null) - context.progressBar().setVisible(true); + if (context.ui().progressBar() != null) + context.ui().progressBar().setVisible(true); LocalDateTime dateNow = LocalDateTime.now(); String date = dateNow.format(dateForfolderNameFormatter); @@ -82,7 +92,7 @@ public static void executeBackup(ZippingContext context, BackupTriggerType trigg private static void createBackupRequest(ZippingContext context, BackupTriggerType triggeredBy, File sourceFile, File outputFile, int totalFilesCount) { long targetSize = FolderUtils.calculateFileOrFolderSize(sourceFile.getAbsolutePath()); - BackupRequestRepository.insertBackupRequest(BackupRequest.createNewBackupRequest(context.backup().getId(), triggeredBy, outputFile.getAbsolutePath(), targetSize, totalFilesCount)); + BackupRequestRepository.insertBackupRequest(BackupRequest.createNewBackupRequest(context.execution().backup().getId(), triggeredBy, outputFile.getAbsolutePath(), targetSize, totalFilesCount)); } public static String removeExtension(String fileName) { @@ -93,7 +103,7 @@ public static String removeExtension(String fileName) { } private static void updateAfterBackup(String path1, String path2, ZippingContext context) { - if (context.backup() == null) throw new IllegalArgumentException("Backup cannot be null!"); + if (context.execution().backup() == null) throw new IllegalArgumentException("Backup cannot be null!"); if (path1 == null) throw new IllegalArgumentException("Initial path cannot be null!"); if (path2 == null) throw new IllegalArgumentException("Destination path cannot be null!"); @@ -102,31 +112,31 @@ private static void updateAfterBackup(String path1, String path2, ZippingContext reEnableButtonsAndTable(context); // next day backup update - if (context.backup().isAutomatic() == true) { - TimeInterval time = context.backup().getTimeIntervalBackup(); + if (context.execution().backup().isAutomatic()) { + TimeInterval time = context.execution().backup().getTimeIntervalBackup(); LocalDateTime nextDateBackup = BackupHelper.getNexDateBackup(time); - context.backup().setNextBackupDate(nextDateBackup); + context.execution().backup().setNextBackupDate(nextDateBackup); logger.info("Next date backup setted to: " + nextDateBackup); } - context.backup().setLastBackupDate(LocalDateTime.now()); - context.backup().setCount(context.backup().getCount()+1); + context.execution().backup().setLastBackupDate(LocalDateTime.now()); + context.execution().backup().setCount(context.execution().backup().getCount()+1); try { List backups = BackupHelper.getBackupList(); for (ConfigurationBackup b : backups) { - if (b.getName().equals(context.backup().getName())) { - b.updateBackup(context.backup()); + if (b.getName().equals(context.execution().backup().getName())) { + b.updateBackup(context.execution().backup()); break; } } - BackupHelper.updateBackup(context.backup()); + BackupHelper.updateBackup(context.execution().backup()); - logger.info("Backup :\"" + context.backup().getName() + "\" updated after the backup"); + logger.info("Backup :\"" + context.execution().backup().getName() + "\" updated after the backup"); - if (context.trayIcon() != null) - context.trayIcon().displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + context.backup().getName() + TCategory.TRAY_ICON.getTranslation(TKey.SUCCESS_MESSAGE) + "\n" + TCategory.GENERAL.getTranslation(TKey.FROM) + ": " + path1 + "\n" + TCategory.GENERAL.getTranslation(TKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); + if (context.ui().trayIcon() != null) + context.ui().trayIcon().displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + context.execution().backup().getName() + TCategory.TRAY_ICON.getTranslation(TKey.SUCCESS_MESSAGE) + "\n" + TCategory.GENERAL.getTranslation(TKey.FROM) + ": " + path1 + "\n" + TCategory.GENERAL.getTranslation(TKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); } catch (IllegalArgumentException ex) { logger.error("An error occurred: " + ex.getMessage(), ex); ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); @@ -185,31 +195,31 @@ public static void interruptBackupProcess(ZippingContext context) { if (ZippingThread.isInterrupted()) reEnableButtonsAndTable(context); - if (context.progressBar() != null) - context.progressBar().dispose(); + if (context.ui().progressBar() != null) + context.ui().progressBar().dispose(); } public static void reEnableButtonsAndTable(ZippingContext context) { - if (context.interruptBackupPopupItem() != null) context.interruptBackupPopupItem().setEnabled(false); - if (context.deleteBackupPopupItem() != null) context.deleteBackupPopupItem().setEnabled(true); + if (context.ui().interruptBackupPopupItem() != null) context.ui().interruptBackupPopupItem().setEnabled(false); + if (context.ui().deleteBackupPopupItem() != null) context.ui().deleteBackupPopupItem().setEnabled(true); - RunningBackupService.updateBackupStatusAfterCompletitionByBackupConfigurationId(context.backup().getId()); + RunningBackupService.updateBackupStatusAfterCompletitionByBackupConfigurationId(context.execution().backup().getId()); - if (BackupManagerGUI.backupTable != null) - TableDataManager.removeProgressInTheTableAndRestoreAsDefault(context.backup(), formatter); + if (context.ui().backupTableService() != null) + context.ui().backupTableService().removeProgress(context.execution().backup()); } public static void updateProgressPercentage(int value, String path1, String path2, ZippingContext context, String fileProcessed, int filesCopiedSoFar, int totalFilesCount) { if (value == 0 || value == 25 || value == 50 || value == 75 || value == 100) logger.info("Zipping progress: " + value + "%"); - if (context.progressBar() != null) - context.progressBar().updateProgressBar(value, fileProcessed, filesCopiedSoFar, totalFilesCount); + if (context.ui().progressBar() != null) + context.ui().progressBar().updateProgressBar(value, fileProcessed, filesCopiedSoFar, totalFilesCount); - if (BackupManagerGUI.backupTable != null) - TableDataManager.updateProgressBarPercentage(context.backup(), value, formatter); + if (context.ui().backupTableService() != null) + context.ui().backupTableService().updateProgress(context.execution().backup(), value); - BackupRequest request = BackupRequestRepository.getLastBackupInProgressByConfigurationId(context.backup().getId()); + BackupRequest request = BackupRequestRepository.getLastBackupInProgressByConfigurationId(context.execution().backup().getId()); if (request != null) { if (value < 100) @@ -218,7 +228,7 @@ else if (value == 100) { RunningBackupService.updateBackupZippedFolderSizeById(request.backupRequestId(), path2); updateAfterBackup(path1, path2, context); - deleteOldBackupsIfNecessary(context.backup().getMaxToKeep(), path2); + deleteOldBackupsIfNecessary(context.execution().backup().getMaxToKeep(), path2); } } } @@ -280,10 +290,8 @@ public static void deletePotentiallyIncompletedBackupsFromLastExecution() { List requests = BackupRequestRepository.getRunningBackups(); if (requests != null) { for (BackupRequest request : requests) { - boolean deleted = deletePartialBackup(request.outputPath()); - if (deleted) { - BackupHelper.forceBackupTermination(request.backupRequestId()); - } + deletePartialBackup(request.outputPath()); + BackupHelper.forceBackupTermination(request.backupRequestId()); } } } diff --git a/src/main/java/backupmanager/Entities/BackupExecutionContext.java b/src/main/java/backupmanager/Entities/BackupExecutionContext.java new file mode 100644 index 00000000..9d1322a7 --- /dev/null +++ b/src/main/java/backupmanager/Entities/BackupExecutionContext.java @@ -0,0 +1,13 @@ +package backupmanager.Entities; + +import backupmanager.utils.FolderUtils; + +public record BackupExecutionContext ( + ConfigurationBackup backup, + long folderUnzippedSize +) { + public static BackupExecutionContext create(ConfigurationBackup backup) { + long folderSize = FolderUtils.calculateFileOrFolderSize(backup.getTargetPath()); + return new BackupExecutionContext(backup, folderSize); + } +} diff --git a/src/main/java/backupmanager/Entities/BackupUIContext.java b/src/main/java/backupmanager/Entities/BackupUIContext.java new file mode 100644 index 00000000..d78a0b64 --- /dev/null +++ b/src/main/java/backupmanager/Entities/BackupUIContext.java @@ -0,0 +1,16 @@ +package backupmanager.Entities; + +import java.awt.TrayIcon; + +import javax.swing.JMenuItem; + +import backupmanager.gui.Table.BackupTableDataService; +import backupmanager.gui.frames.BackupProgressGUI; + +public record BackupUIContext ( + TrayIcon trayIcon, + BackupTableDataService backupTableService, + BackupProgressGUI progressBar, + JMenuItem interruptBackupPopupItem, + JMenuItem deleteBackupPopupItem +) { } diff --git a/src/main/java/backupmanager/Entities/ZippingContext.java b/src/main/java/backupmanager/Entities/ZippingContext.java index d9358e24..6962c6ba 100644 --- a/src/main/java/backupmanager/Entities/ZippingContext.java +++ b/src/main/java/backupmanager/Entities/ZippingContext.java @@ -1,24 +1,6 @@ package backupmanager.Entities; -import java.awt.TrayIcon; - -import javax.swing.JMenuItem; -import javax.swing.JTable; - -import backupmanager.gui.frames.BackupProgressGUI; -import backupmanager.utils.FolderUtils; - public record ZippingContext ( - ConfigurationBackup backup, - TrayIcon trayIcon, - JTable backupTable, - BackupProgressGUI progressBar, - JMenuItem interruptBackupPopupItem, - JMenuItem deleteBackupPopupItem, - long folderUnzippedSize -) { - public static ZippingContext create(ConfigurationBackup backup, TrayIcon trayIcon, JTable backupTable, BackupProgressGUI progressBar, JMenuItem interruptBackupPopupItem, JMenuItem deleteBackupPopupItem) { - long folderSize = FolderUtils.calculateFileOrFolderSize(backup.getTargetPath()); - return new ZippingContext(backup, trayIcon, backupTable, progressBar, interruptBackupPopupItem, deleteBackupPopupItem, folderSize); - } -} + BackupExecutionContext execution, + BackupUIContext ui +) { } diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 26626c3f..05788071 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -17,12 +17,11 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.gui.Dialogs.BackupEntryDialog; -import backupmanager.gui.Dialogs.TimePicker; import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.TableDataManager; -import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; +import backupmanager.gui.simple.BackupEntryDialog; +import backupmanager.gui.simple.TimePickerDialog; public class BackupHelper { @@ -30,20 +29,15 @@ public class BackupHelper { public static final DateTimeFormatter dateForfolderNameFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH-mm-ss"); public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); - public static void openBackupById(int id, java.awt.Frame frame) { + public static void openBackupById(BackupTableDataService backupTable, int id) { logger.info("Event --> opening backup"); ConfigurationBackup backup = BackupConfigurationRepository.getBackupById(id); - - BackupEntryDialog dialog = new BackupEntryDialog(frame, false, backup); - dialog.setVisible(true); + openBackupEntryDialog(backupTable, backup); } - public static void newBackup(BackupProgressGUI progressBar, java.awt.Frame frame) { + public static void newBackup(BackupProgressGUI progressBar) { logger.info("Event --> new backup"); - - BackupEntryDialog dialog = new BackupEntryDialog(frame, false); - dialog.setVisible(true); } public static void newBackup(ConfigurationBackup backup) { @@ -115,14 +109,14 @@ public static void updateBackup(ConfigurationBackup updatedBackup) { public static List getBackupList() { List backups = BackupConfigurationRepository.getBackupList(); - BackupManagerGUI.backups = backups; // i have to keep update also the backup list in the main panel + // BackupManagerGUI.backups = backups; // i have to keep update also the backup list in the main panel return backups; } - public static TimeInterval openTimePicker(java.awt.Dialog parent, TimeInterval time) { - TimePicker picker = new TimePicker(parent, time, true); + public static TimeInterval openTimePicker(TimeInterval time) { + TimePickerDialog picker = new TimePickerDialog(time); picker.setVisible(true); - return picker.getTimeInterval(); + return picker.getResult(); } public static void showMessageActivationAutoBackup(TimeInterval timeInterval, String startPath, String destinationPath) { @@ -138,17 +132,8 @@ public static void showMessageActivationAutoBackup(TimeInterval timeInterval, St "AutoBackup", 1); } - public static void openBackupByName(String backupName, java.awt.Frame frame) { - logger.info("Event --> opening backup"); - - ConfigurationBackup backup = BackupConfigurationRepository.getBackupByName(backupName); - - BackupEntryDialog dialog = new BackupEntryDialog(frame, false, backup); - dialog.setVisible(true); - } - - public static void openBackupEntryDialog(java.awt.Frame frame) { - BackupEntryDialog dialog = new BackupEntryDialog(frame, false); + public static void openBackupEntryDialog(BackupTableDataService backupTable, ConfigurationBackup backup) { + BackupEntryDialog dialog = new BackupEntryDialog(backupTable, backup); dialog.setVisible(true); } @@ -165,8 +150,8 @@ public static void forceBackupTermination(int requestId) { } private static void updateBackupTable() { - if (BackupManagerGUI.model != null) - TableDataManager.updateTableWithNewBackupList(getBackupList(), formatter); + // if (BackupManagerGUI.model != null) + // TableDataManager.updateTableWithNewBackupList(getBackupList()); } public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup backup) { @@ -198,7 +183,7 @@ public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup back if (backup.getName() == null || backup.getName().isEmpty()) return null; // message - TimeInterval timeInterval = openTimePicker(null, null); + TimeInterval timeInterval = openTimePicker(null); if (timeInterval == null) return null; //set date for next backup diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index e42129de..e3fabe88 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -13,7 +13,6 @@ import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.util.FontUtils; -import backupmanager.gui.Controllers.AppController; import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; @@ -21,6 +20,7 @@ import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.ProductionDatabaseInitializer; +import backupmanager.gui.Controllers.AppController; import backupmanager.gui.frames.BackupManager; import backupmanager.utils.DemoPreferences; @@ -96,7 +96,7 @@ private static void runGui() { UIManager.put("defaultFont", FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13)); DemoPreferences.setupLaf(); - BackupManager frame = new BackupManager(); + BackupManager frame = BackupManager.getInstance(); frame.setVisible(true); }); } diff --git a/src/main/java/backupmanager/Managers/RunningBackupManager.java b/src/main/java/backupmanager/Managers/RunningBackupManager.java new file mode 100644 index 00000000..6c10c164 --- /dev/null +++ b/src/main/java/backupmanager/Managers/RunningBackupManager.java @@ -0,0 +1,38 @@ +package backupmanager.Managers; + +public final class RunningBackupManager { + + private static volatile RunningBackupManager instance; + private volatile boolean running = false; + + private RunningBackupManager() {} + + public static RunningBackupManager getInstance() { + RunningBackupManager result = instance; + + if (result != null) + return result; + + synchronized (RunningBackupManager.class) { + if (instance == null) + instance = new RunningBackupManager(); + return instance; + } + } + + public synchronized boolean startBackup() { + if (running) { + return false; + } + running = true; + return true; + } + + public synchronized void finishBackup() { + running = false; + } + + public boolean isRunning() { + return running; + } +} diff --git a/src/main/java/backupmanager/Services/BackgroundService.java b/src/main/java/backupmanager/Services/BackgroundService.java index 566a3caf..a5f3c1cd 100644 --- a/src/main/java/backupmanager/Services/BackgroundService.java +++ b/src/main/java/backupmanager/Services/BackgroundService.java @@ -14,7 +14,9 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; +import backupmanager.Entities.BackupExecutionContext; import backupmanager.Entities.BackupRequest; +import backupmanager.Entities.BackupUIContext; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; @@ -115,8 +117,11 @@ private void executeBackups(List backups) { javax.swing.SwingUtilities.invokeLater(() -> { try { for (ConfigurationBackup backup : backups) { - ZippingContext context = ZippingContext.create(backup, trayIcon.getTrayIcon(), null, null, null, null); - BackupOperations.singleBackup(context, BackupTriggerType.SCHEDULER); + ZippingContext context = new ZippingContext( + BackupExecutionContext.create(backup), + new BackupUIContext(trayIcon.getTrayIcon(), null, null, null, null) + ); + BackupOperations.requestSingleBackup(context, BackupTriggerType.SCHEDULER); } } finally { logger.info("All backups completed. Resetting isBackingUp flag."); diff --git a/src/main/java/backupmanager/Services/BackupObserver.java b/src/main/java/backupmanager/Services/BackupObserver.java index 061c4c6d..6eb6891e 100644 --- a/src/main/java/backupmanager/Services/BackupObserver.java +++ b/src/main/java/backupmanager/Services/BackupObserver.java @@ -1,6 +1,5 @@ package backupmanager.Services; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; @@ -14,9 +13,9 @@ import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.gui.Table.TableDataManager; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.Table.BackupTableDataService; /* * I need a task that constantly checks if there are something running and i can't use a simple method calls instead because @@ -27,12 +26,12 @@ public class BackupObserver { private static final Logger logger = LoggerFactory.getLogger(BackupObserver.class); private final ScheduledExecutorService scheduler; - private final DateTimeFormatter formatter; + private final BackupTableDataService tableService; private final long millisecondsToWait; - public BackupObserver(DateTimeFormatter formatter, int millisecondsToWait) { + public BackupObserver(BackupTableDataService tableService, int millisecondsToWait) { + this.tableService = tableService; this.millisecondsToWait = millisecondsToWait; - this.formatter = formatter; this.scheduler = Executors.newSingleThreadScheduledExecutor(); // create single thread } @@ -57,9 +56,9 @@ public void start() { BackupRequest updatedRequest = BackupRequestRepository.getBackupRequestById(request.backupRequestId()); if (updatedRequest != null && updatedRequest.progress() < 99) { - TableDataManager.updateProgressBarPercentage(config, updatedRequest.progress(), formatter); + tableService.updateProgress(config, updatedRequest.progress()); } else { - TableDataManager.removeProgressInTheTableAndRestoreAsDefault(config, formatter); + tableService.removeProgress(config); } }); } diff --git a/src/main/java/backupmanager/Services/RunningBackupService.java b/src/main/java/backupmanager/Services/RunningBackupService.java index e0d7ff30..518eed79 100644 --- a/src/main/java/backupmanager/Services/RunningBackupService.java +++ b/src/main/java/backupmanager/Services/RunningBackupService.java @@ -14,6 +14,7 @@ import backupmanager.utils.FolderUtils; public class RunningBackupService { + public static Optional getRunningBackupByName(String backupName) { ConfigurationBackup config = BackupConfigurationRepository.getBackupByName(backupName); if (config == null) return Optional.empty(); diff --git a/src/main/java/backupmanager/Services/ZippingThread.java b/src/main/java/backupmanager/Services/ZippingThread.java index 743d800f..7472586c 100644 --- a/src/main/java/backupmanager/Services/ZippingThread.java +++ b/src/main/java/backupmanager/Services/ZippingThread.java @@ -66,7 +66,7 @@ public static void zipDirectory(File sourceFile, File outputFile, ZippingContext private static void handleError(String message, ErrorType errorType, ZippingContext context) { logger.error(message); - BackupOperations.setError(errorType, context.trayIcon(), null); + BackupOperations.setError(errorType, context.ui().trayIcon(), null); BackupOperations.reEnableButtonsAndTable(context); } diff --git a/src/main/java/backupmanager/ZipFileVisitor.java b/src/main/java/backupmanager/ZipFileVisitor.java index 031d7025..a0a47fdf 100644 --- a/src/main/java/backupmanager/ZipFileVisitor.java +++ b/src/main/java/backupmanager/ZipFileVisitor.java @@ -85,7 +85,7 @@ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOExce private boolean isZippingThreadInterrupted() { if (Thread.currentThread().isInterrupted()) { - RunningBackupService.updateBackupStatusAfterForceTerminationByBackupConfigurationId(context.backup().getId()); + RunningBackupService.updateBackupStatusAfterForceTerminationByBackupConfigurationId(context.execution().backup().getId()); logger.info("Zipping process manually interrupted"); return true; } diff --git a/src/main/java/backupmanager/gui/Controllers/AppController.java b/src/main/java/backupmanager/gui/Controllers/AppController.java index 9b859235..57897046 100644 --- a/src/main/java/backupmanager/gui/Controllers/AppController.java +++ b/src/main/java/backupmanager/gui/Controllers/AppController.java @@ -3,8 +3,6 @@ import java.awt.Frame; import java.io.IOException; -import javax.swing.JFrame; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,13 +11,11 @@ import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Helpers.SubscriptionNotifier; import backupmanager.Services.BackgroundService; -import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.frames.BackupManager; public class AppController { private static final Logger logger = LoggerFactory.getLogger(AppController.class); - private BackupManagerGUI guiInstance; - private final BackgroundService backgroundService; private final TrayController trayController; @@ -69,18 +65,14 @@ private void showSubscriptionNotificationIfNeeded(SubscriptionStatus status) { private void openGui() { logger.info("Opening main GUI"); + BackupManager frame = BackupManager.getInstance(); - if (guiInstance == null) { - guiInstance = new BackupManagerGUI(); - guiInstance.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - } - - guiInstance.setVisible(true); - guiInstance.toFront(); - guiInstance.requestFocus(); + frame.setVisible(true); + frame.toFront(); + frame.requestFocus(); - if (guiInstance.getState() == Frame.ICONIFIED) { - guiInstance.setState(Frame.NORMAL); + if (frame.getState() == Frame.ICONIFIED) { + frame.setState(Frame.NORMAL); } } diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index 866ae4fd..9a4793f2 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -1,5 +1,6 @@ package backupmanager.gui.Controllers; +import java.io.File; import java.time.LocalDateTime; import javax.swing.JOptionPane; @@ -9,6 +10,8 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; +import backupmanager.Entities.BackupExecutionContext; +import backupmanager.Entities.BackupUIContext; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Entities.ZippingContext; @@ -19,8 +22,7 @@ import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; public class BackupEntryController { @@ -60,9 +62,8 @@ public ConfigurationBackup getBackup(String name, String initialPath, String des } } - @Deprecated - public TimeInterval handleTimePickerAction(javax.swing.JDialog dialog, String target, String destination) throws InvalidTimeInterval { - TimeInterval time = BackupHelper.openTimePicker(dialog, currentBackup.getTimeIntervalBackup()); + public TimeInterval handleTimePickerAction(String target, String destination) throws InvalidTimeInterval { + TimeInterval time = BackupHelper.openTimePicker(currentBackup.getTimeIntervalBackup()); if (time == null) throw new InvalidTimeInterval(); LocalDateTime nextDateBackup = BackupHelper.getNexDateBackup(time); @@ -125,7 +126,7 @@ public boolean toggleAutomaticBackup(String name, String initialPath, String des return false; } - public void handleSingleBackupRequest(BackupTable backupTable, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) throws BackupAlreadyRunningException { + public void handleSingleBackupRequest(BackupTableDataService backupTable, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) throws BackupAlreadyRunningException { if (BackupRequestRepository.isAnyBackupRunning()) { JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); throw new BackupAlreadyRunningException(); @@ -139,7 +140,7 @@ public void handleSingleBackupRequest(BackupTable backupTable, String name, Stri singleBackup(initialPath, destinationPath, backupTable); } - private void singleBackup(String target, String destination, BackupTable backupTable) { + private void singleBackup(String target, String destination, BackupTableDataService backupTable) { logger.info("Event --> single backup"); String path1 = target; @@ -147,22 +148,14 @@ private void singleBackup(String target, String destination, BackupTable backupT currentBackup.setTargetPath(path2); - String temp = "\\"; - if (!BackupOperations.checkInputCorrect(currentBackup.getName(), path1, path2, null)) return; LocalDateTime dateNow = LocalDateTime.now(); - String name1; // folder name/initial file String date = dateNow.format(BackupHelper.dateForfolderNameFormatter); //------------------------------SET ALL THE STRINGS------------------------------ - name1 = path1.substring(path1.length()-1, path1.length()-1); - - for(int i=path1.length()-1; i>=0; i--) { - if(path1.charAt(i) != temp.charAt(0)) name1 = path1.charAt(i) + name1; - else break; - } + String name1 = new File(path1).getName(); name1 = BackupOperations.removeExtension(name1); path2 = path2 + "\\" + name1 + " (Backup " + date + ")"; @@ -170,10 +163,13 @@ private void singleBackup(String target, String destination, BackupTable backupT //------------------------------COPY THE FILE OR DIRECTORY------------------------------ logger.info("date backup: " + date); - BackupManagerGUI.progressBar = new BackupProgressGUI(path1, path2); - BackupManagerGUI.progressBar.setVisible(true); + BackupProgressGUI progressBar = new BackupProgressGUI(path1, path2); + progressBar.setVisible(true); - ZippingContext context = ZippingContext.create(currentBackup, null, backupTable, BackupManagerGUI.progressBar, null, null); + ZippingContext context = new ZippingContext( + BackupExecutionContext.create(currentBackup), + new BackupUIContext(null, backupTable, progressBar, null, null) + ); BackupOperations.executeBackup(context, BackupTriggerType.USER, path1, path2); diff --git a/src/main/java/backupmanager/gui/Controllers/PreferenceController.java b/src/main/java/backupmanager/gui/Controllers/PreferenceController.java deleted file mode 100644 index a95fa3cf..00000000 --- a/src/main/java/backupmanager/gui/Controllers/PreferenceController.java +++ /dev/null @@ -1,27 +0,0 @@ -package backupmanager.gui.Controllers; - -import java.io.IOException; -import java.util.Arrays; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import backupmanager.Managers.ExceptionManager; -import backupmanager.Services.PreferenceService; -import backupmanager.gui.frames.BackupManagerGUI; - -public record PreferenceController (PreferenceService service, BackupManagerGUI mainGui) { - private static final Logger logger = LoggerFactory.getLogger(PreferenceController.class); - - public void applyPreferences(String language, String theme) { - logger.info("Updating preferences -> Language: {}; Theme: ()", language, theme); - - try { - service.updatePreferences(language, theme); - mainGui.reloadPreferences(); - } catch (IOException ex) { - logger.error("An error occurred during applying preferences: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - } -} diff --git a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form deleted file mode 100644 index 5164cb8d..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.form +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java b/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java deleted file mode 100644 index 5c338585..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/BackupEntryDialog.java +++ /dev/null @@ -1,536 +0,0 @@ -package backupmanager.gui.Dialogs; - -import static backupmanager.gui.frames.BackupManagerGUI.backupTable; - -import java.time.LocalDateTime; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.formdev.flatlaf.FlatClientProperties; - -import backupmanager.BackupOperations; -import backupmanager.gui.Controllers.BackupEntryController; -import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; -import backupmanager.Exceptions.BackupAlreadyRunningException; -import backupmanager.Exceptions.InvalidTimeInterval; -import backupmanager.Helpers.BackupHelper; -import backupmanager.Json.JsonConfig; - -public class BackupEntryDialog extends javax.swing.JDialog { - - private static final Logger logger = LoggerFactory.getLogger(BackupEntryDialog.class); - private static final JsonConfig configReader = JsonConfig.getInstance(); - - private final boolean create; - private String backupOnText; - private String backupOffText; - - private final BackupEntryController entryController; - - public BackupEntryDialog(java.awt.Frame parent, boolean modal) { - super(parent, modal); - - entryController = new BackupEntryController(null); - - initializeDialog(); - setAutoBackupOff(); - this.create = true; - okButton.setText(TCategory.GENERAL.getTranslation(TKey.CREATE_BUTTON)); - } - - public BackupEntryDialog(java.awt.Frame parent, boolean modal, ConfigurationBackup currentBackup) { - super(parent, modal); - - entryController = new BackupEntryController(currentBackup); - - initializeDialog(); - updateCurrentFiedsByBackup(currentBackup); - backupNameField.setText(currentBackup.getName()); - backupNameField.setEditable(false); - backupNameField.setFocusable(false); - this.create = false; - okButton.setText(TCategory.GENERAL.getTranslation(TKey.SAVE_BUTTON)); - } - - private void setAutoBackupPreference(boolean option) { - ConfigurationBackup currentBackup = entryController.getCurrentBackup(); - currentBackup.setAutomatic(option); - - if (option) { - setAutoBackupOn(currentBackup); - } else { - disableAutoBackup(currentBackup); - } - } - - private void setStartPathField(String text) { - startPathField.setText(text); - } - private void setDestinationPathField(String text) { - destinationPathField.setText(text); - } - private void setCurrentBackupNotes(String notes) { - backupNoteTextArea.setText(notes); - } - private void setCurrentBackupMaxBackupsToKeep(int maxBackupsCount) { - maxBackupCountSpinner.setValue(maxBackupsCount); - } - - private void initializeDialog() { - initComponents(); - - setCurrentBackupMaxBackupsToKeep(configReader.getConfigValue("MaxCountForSameBackup", 1)); - - setSvgImages(); - setTranslations(); - } - - private void setLastBackupLabel(LocalDateTime date) { - if (date != null) { - String dateStr = date.format(BackupHelper.formatter); - dateStr = TCategory.BACKUP_ENTRY.getTranslation(TKey.LAST_BACKUP) + ": " + dateStr; - lastBackupLabel.setText(dateStr); - } - else lastBackupLabel.setText(""); - } - - private void openBackupActivationMessage(TimeInterval newtimeInterval) { - entryController.handleOpenBackupActivationMessage(newtimeInterval, startPathField.getText(), destinationPathField.getText()); - } - - private void updateCurrentFiedsByBackup(ConfigurationBackup backup) { - setStartPathField(backup.getTargetPath()); - setDestinationPathField(backup.getDestinationPath()); - setLastBackupLabel(backup.getLastUpdateDate()); - setAutoBackupPreference(backup.isAutomatic()); - setCurrentBackupNotes(backup.getNotes()); - setCurrentBackupMaxBackupsToKeep(backup.getMaxToKeep()); - - if (backup.getTimeIntervalBackup() != null) { - setAutoBackupOn(backup); - } else { - setAutoBackupOff(); - } - } - - private void toggleAutomaticBackup() { - if (entryController.toggleAutomaticBackup(backupNameField.getText(), startPathField.getText(), destinationPathField.getText(), backupNoteTextArea.getText(), toggleAutoBackup.isSelected(), (int) maxBackupCountSpinner.getValue())) { - setAutoBackupOn(entryController.getCurrentBackup()); - toggleAutoBackup.setSelected(true); - btnTimePicker.setToolTipText(entryController.getCurrentBackup().getTimeIntervalBackup().toString()); - btnTimePicker.setEnabled(true); - } else { - setAutoBackupOff(); - toggleAutoBackup.setSelected(false); - } - } - - private void setAutoBackupOn(ConfigurationBackup backup) { - toggleAutoBackup.setSelected(true); - toggleAutoBackup.setText(backupOnText); - - if (backup != null) - enableTimePickerButton(backup); - else - disableTimePickerButton(); - } - - private void setAutoBackupOff() { - toggleAutoBackup.setSelected(false); - toggleAutoBackup.setText(backupOffText); - disableTimePickerButton(); - } - - private void disableTimePickerButton() { - btnTimePicker.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.TIME_PICKER_TOOLTIP)); - btnTimePicker.setEnabled(false); - } - - private void enableTimePickerButton(ConfigurationBackup backup) { - if (backup.getTimeIntervalBackup() != null) { - btnTimePicker.setToolTipText(backup.getTimeIntervalBackup().toString()); - btnTimePicker.setEnabled(true); - } else { - btnTimePicker.setEnabled(true); - } - } - - private void disableAutoBackup(ConfigurationBackup backup) { - logger.info("Event --> auto backup disabled"); - - backup.setTimeIntervalBackup(null); - backup.setNextBackupDate(null); - backup.setAutomatic(false); - backup.setLastUpdateDate(LocalDateTime.now()); - } - - private void maxBackupCountSpinnerChange() { - Integer backupCount = (Integer) maxBackupCountSpinner.getValue(); - - if (backupCount == null || backupCount < 1) { - maxBackupCountSpinner.setValue(1); - } else if (backupCount > 10) { - maxBackupCountSpinner.setValue(10); - } - } - - private void mouseWeel(java.awt.event.MouseWheelEvent evt) { - javax.swing.JSpinner spinner = (javax.swing.JSpinner) evt.getSource(); - int rotation = evt.getWheelRotation(); - - if (rotation < 0) { - spinner.setValue((Integer) spinner.getValue() + 1); - } else { - spinner.setValue((Integer) spinner.getValue() - 1); - } - } - - private void setSvgImages() { - btnPathSearch1.setSvgImage("res/img/folder.svg", 30, 30); - btnPathSearch2.setSvgImage("res/img/folder.svg", 30, 30); - btnTimePicker.setSvgImage("res/img/timer.svg", 30, 30); - } - - private void setTranslations() { - backupOnText = TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_ON); - backupOffText = TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_OFF); - btnPathSearch1.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_FILE_CHOOSER_TOOLTIP)); - btnPathSearch2.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_FILE_CHOOSER_TOOLTIP)); - startPathField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_PATH_TOOLTIP)); - destinationPathField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_PATH_TOOLTIP)); - backupNoteTextArea.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.NOTES_TOOLTIP)); - singleBackup.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.SINGLE_BACKUP_BUTTON)); - singleBackup.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.SINGLE_BACKUP_TOOLTIP)); - toggleAutoBackup.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_BUTTON_OFF)); - toggleAutoBackup.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.AUTO_BACKUP_TOOLTIP)); - jLabel2.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.NOTES) + ":"); - lastBackupLabel.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.LAST_BACKUP) + ": "); - setTitle(TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_TITLE)); - startPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_ENTRY.getTranslation(TKey.INITIAL_PATH_PLACEHOLDER)); - destinationPathField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_ENTRY.getTranslation(TKey.DESTINATION_PATH_PLACEHOLDER)); - btnTimePicker.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.TIME_PICKER_TOOLTIP)); - maxBackupCountSpinner.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.MAX_BACKUPS_TO_KEEP_TOOLTIP) + "\n" + TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); - jLabel4.setText(TCategory.BACKUP_ENTRY.getTranslation(TKey.MAX_BACKUPS_TO_KEEP)); - closeButton.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); - backupNameField.setToolTipText(TCategory.BACKUP_ENTRY.getTranslation(TKey.BACKUP_NAME_TOOLTIP)); - backupNameField.setHintText(TCategory.BACKUP_ENTRY.getTranslation(TKey.BACKUP_NAME)); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - startPathField = new javax.swing.JTextField(); - btnPathSearch1 = new backupmanager.gui.svg.SVGButton(); - btnPathSearch2 = new backupmanager.gui.svg.SVGButton(); - destinationPathField = new javax.swing.JTextField(); - jLabel2 = new javax.swing.JLabel(); - jScrollPane2 = new javax.swing.JScrollPane(); - backupNoteTextArea = new javax.swing.JTextArea(); - lastBackupLabel = new javax.swing.JLabel(); - singleBackup = new javax.swing.JButton(); - toggleAutoBackup = new javax.swing.JToggleButton(); - btnTimePicker = new backupmanager.gui.svg.SVGButton(); - maxBackupCountSpinner = new javax.swing.JSpinner(); - jLabel4 = new javax.swing.JLabel(); - closeButton = new javax.swing.JButton(); - okButton = new javax.swing.JButton(); - backupNameField = new backupmanager.gui.customwidgets.ModernTextField(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setResizable(false); - - startPathField.setToolTipText("(Required) Initial path"); - startPathField.setActionCommand("null"); - startPathField.setAlignmentX(0.0F); - startPathField.setAlignmentY(0.0F); - startPathField.setAutoscrolls(false); - startPathField.setMaximumSize(new java.awt.Dimension(465, 26)); - startPathField.setMinimumSize(new java.awt.Dimension(465, 26)); - startPathField.setPreferredSize(new java.awt.Dimension(465, 26)); - - btnPathSearch1.setToolTipText(""); - btnPathSearch1.setMaximumSize(new java.awt.Dimension(35, 35)); - btnPathSearch1.setMinimumSize(new java.awt.Dimension(35, 35)); - btnPathSearch1.setPreferredSize(new java.awt.Dimension(35, 35)); - btnPathSearch1.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnPathSearch1ActionPerformed(evt); - } - }); - - btnPathSearch2.setToolTipText("Open file explorer"); - btnPathSearch2.setMaximumSize(new java.awt.Dimension(35, 35)); - btnPathSearch2.setMinimumSize(new java.awt.Dimension(35, 35)); - btnPathSearch2.setPreferredSize(new java.awt.Dimension(35, 35)); - btnPathSearch2.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnPathSearch2ActionPerformed(evt); - } - }); - - destinationPathField.setToolTipText("(Required) Destination path"); - destinationPathField.setActionCommand(""); - destinationPathField.setAlignmentX(0.0F); - destinationPathField.setAlignmentY(0.0F); - destinationPathField.setMaximumSize(new java.awt.Dimension(465, 26)); - destinationPathField.setMinimumSize(new java.awt.Dimension(465, 26)); - destinationPathField.setPreferredSize(new java.awt.Dimension(465, 26)); - - jLabel2.setText("notes:"); - - backupNoteTextArea.setColumns(20); - backupNoteTextArea.setRows(5); - backupNoteTextArea.setToolTipText("(Optional) Backup description"); - backupNoteTextArea.setMaximumSize(new java.awt.Dimension(232, 84)); - backupNoteTextArea.setMinimumSize(new java.awt.Dimension(232, 84)); - jScrollPane2.setViewportView(backupNoteTextArea); - - lastBackupLabel.setText("last backup: "); - - singleBackup.setBackground(new java.awt.Color(51, 153, 255)); - singleBackup.setForeground(new java.awt.Color(255, 255, 255)); - singleBackup.setText("Single Backup"); - singleBackup.setToolTipText("Perform the backup"); - singleBackup.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - singleBackup.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - SingleBackupActionPerformed(evt); - } - }); - - toggleAutoBackup.setText("Auto Backup"); - toggleAutoBackup.setToolTipText("Enable/Disable automatic backup"); - toggleAutoBackup.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - toggleAutoBackup.setPreferredSize(new java.awt.Dimension(108, 27)); - toggleAutoBackup.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - toggleAutoBackupActionPerformed(evt); - } - }); - - btnTimePicker.setToolTipText("Time picker"); - btnTimePicker.setMaximumSize(new java.awt.Dimension(36, 36)); - btnTimePicker.setMinimumSize(new java.awt.Dimension(36, 36)); - btnTimePicker.setPreferredSize(new java.awt.Dimension(36, 36)); - btnTimePicker.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnTimePickerActionPerformed(evt); - } - }); - - maxBackupCountSpinner.setToolTipText("Maximum number of backups before removing the oldest."); - maxBackupCountSpinner.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(javax.swing.event.ChangeEvent evt) { - maxBackupCountSpinnerStateChanged(evt); - } - }); - maxBackupCountSpinner.addMouseWheelListener(new java.awt.event.MouseWheelListener() { - public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { - maxBackupCountSpinnerMouseWheelMoved(evt); - } - }); - - jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); - jLabel4.setText("Keep only last"); - - closeButton.setText("Close"); - closeButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - closeButtonActionPerformed(evt); - } - }); - - okButton.setText("Ok"); - okButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - okButtonActionPerformed(evt); - } - }); - - backupNameField.setToolTipText("(Required) Backup name"); - backupNameField.setActionCommand("null"); - backupNameField.setAlignmentX(0.0F); - backupNameField.setAlignmentY(0.0F); - backupNameField.setAutoscrolls(false); - backupNameField.setMaximumSize(new java.awt.Dimension(465, 26)); - backupNameField.setMinimumSize(new java.awt.Dimension(465, 26)); - backupNameField.setPreferredSize(new java.awt.Dimension(465, 26)); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap(35, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addComponent(okButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(closeButton) - .addContainerGap()) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 469, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addComponent(destinationPathField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnPathSearch2, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 462, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lastBackupLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 461, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addGap(6, 6, 6) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(131, 131, 131) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(singleBackup, javax.swing.GroupLayout.PREFERRED_SIZE, 188, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGroup(layout.createSequentialGroup() - .addComponent(toggleAutoBackup, javax.swing.GroupLayout.PREFERRED_SIZE, 188, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnTimePicker, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel4, javax.swing.GroupLayout.PREFERRED_SIZE, 244, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(maxBackupCountSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(backupNameField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(startPathField, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(btnPathSearch1, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(15, 15, 15)))) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(16, 16, 16) - .addComponent(backupNameField, javax.swing.GroupLayout.DEFAULT_SIZE, 53, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(startPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(btnPathSearch1, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(destinationPathField, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(btnPathSearch2, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jLabel2) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 190, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(lastBackupLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() - .addComponent(singleBackup, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(toggleAutoBackup, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(btnTimePicker, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(maxBackupCountSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 31, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel4)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(closeButton) - .addComponent(okButton)) - .addContainerGap()) - ); - - pack(); - setLocationRelativeTo(null); - }// //GEN-END:initComponents - - private void btnPathSearch1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPathSearch1ActionPerformed - logger.debug("File chooser: " + startPathField.getName() + ", files allowed: " + true); - String text = BackupOperations.pathSearchWithFileChooser(true); - if (text != null) { - startPathField.setText(text); - } - }//GEN-LAST:event_btnPathSearch1ActionPerformed - - private void btnPathSearch2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnPathSearch2ActionPerformed - logger.debug("File chooser: " + destinationPathField.getName() + ", files allowed: " + false); - String text = BackupOperations.pathSearchWithFileChooser(false); - if (text != null) { - destinationPathField.setText(text); - } - }//GEN-LAST:event_btnPathSearch2ActionPerformed - - private void SingleBackupActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_SingleBackupActionPerformed - try { - entryController.handleSingleBackupRequest( - backupTable, - backupNameField.getText(), - startPathField.getText(), - destinationPathField.getText(), - backupNoteTextArea.getText(), - toggleAutoBackup.isSelected(), - (int) maxBackupCountSpinner.getValue() - ); - } catch (BackupAlreadyRunningException e) { - // no handle - } - }//GEN-LAST:event_SingleBackupActionPerformed - - private void toggleAutoBackupActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_toggleAutoBackupActionPerformed - logger.info("Event --> Changing auto backup preference"); - toggleAutomaticBackup(); - }//GEN-LAST:event_toggleAutoBackupActionPerformed - - private void btnTimePickerActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTimePickerActionPerformed - try { - TimeInterval time = entryController.handleTimePickerAction(this, startPathField.getText(), destinationPathField.getText()); - btnTimePicker.setToolTipText(time.toString()); - openBackupActivationMessage(time); - } catch (InvalidTimeInterval e) { - // no actions - } - }//GEN-LAST:event_btnTimePickerActionPerformed - - private void maxBackupCountSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_maxBackupCountSpinnerStateChanged - maxBackupCountSpinnerChange(); - }//GEN-LAST:event_maxBackupCountSpinnerStateChanged - - private void maxBackupCountSpinnerMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_maxBackupCountSpinnerMouseWheelMoved - mouseWeel(evt); - }//GEN-LAST:event_maxBackupCountSpinnerMouseWheelMoved - - private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed - this.dispose(); - }//GEN-LAST:event_closeButtonActionPerformed - - private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - if (entryController.canDisposeAfterOk(backupNameField.getText(), startPathField.getText(), destinationPathField.getText(), backupNoteTextArea.getText(), toggleAutoBackup.isSelected(), (int) maxBackupCountSpinner.getValue(), create)) - this.dispose(); - }//GEN-LAST:event_okButtonActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton singleBackup; - private backupmanager.gui.customwidgets.ModernTextField backupNameField; - private javax.swing.JTextArea backupNoteTextArea; - private backupmanager.gui.svg.SVGButton btnPathSearch1; - private backupmanager.gui.svg.SVGButton btnPathSearch2; - private backupmanager.gui.svg.SVGButton btnTimePicker; - private javax.swing.JButton closeButton; - private javax.swing.JTextField destinationPathField; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel4; - private javax.swing.JScrollPane jScrollPane2; - private javax.swing.JLabel lastBackupLabel; - private javax.swing.JSpinner maxBackupCountSpinner; - private javax.swing.JButton okButton; - private javax.swing.JTextField startPathField; - private javax.swing.JToggleButton toggleAutoBackup; - // End of variables declaration//GEN-END:variables -} diff --git a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form deleted file mode 100644 index 528982c2..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.form +++ /dev/null @@ -1,122 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java b/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java deleted file mode 100644 index a3b0a45f..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/EntryUserDialog.java +++ /dev/null @@ -1,183 +0,0 @@ -package backupmanager.gui.Dialogs; - - -import backupmanager.LimitDocument; -import backupmanager.gui.Controllers.EntryUserController; -import backupmanager.Entities.User; -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; - -public class EntryUserDialog extends javax.swing.JDialog { - - private User user; - private final EntryUserController userController; - - public EntryUserDialog(java.awt.Frame parent, boolean modal) { - super(parent, modal); - - userController = new EntryUserController(); - - initComponents(); - - nameTextField.setDocument(new LimitDocument(20)); - surnameTextField.setDocument(new LimitDocument(20)); - emailTextField.setDocument(new LimitDocument(32)); - - user = null; - - setTranslactions(); - } - - private void setTranslactions() { - setTitle(TCategory.USER_DIALOG.getTranslation(TKey.USER_TITLE)); - nameLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_NAME)); - surnameLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_SURNAME)); - emailLabel.setText(TCategory.USER_DIALOG.getTranslation(TKey.USER_EMAIL)); - } - - public User getUser() { - return user; - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - okBtn = new javax.swing.JButton(); - nameLabel = new javax.swing.JLabel(); - nameTextField = new javax.swing.JTextField(); - surnameLabel = new javax.swing.JLabel(); - surnameTextField = new javax.swing.JTextField(); - emailLabel = new javax.swing.JLabel(); - emailTextField = new javax.swing.JTextField(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Insert your data"); - setAlwaysOnTop(true); - setResizable(false); - - okBtn.setText("Ok"); - okBtn.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - okBtnActionPerformed(evt); - } - }); - - nameLabel.setText("Name"); - - nameTextField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - nameTextFieldKeyReleased(evt); - } - }); - - surnameLabel.setText("Surname"); - - surnameTextField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - surnameTextFieldKeyReleased(evt); - } - }); - - emailLabel.setText("Email"); - - emailTextField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - emailTextFieldKeyReleased(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(10, 10, 10) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(emailLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(surnameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 81, Short.MAX_VALUE) - .addComponent(nameLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(0, 128, Short.MAX_VALUE) - .addComponent(okBtn)) - .addComponent(surnameTextField, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(emailTextField) - .addComponent(nameTextField)) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nameLabel) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(surnameLabel) - .addComponent(surnameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 39, Short.MAX_VALUE) - .addComponent(okBtn) - .addContainerGap()) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(emailLabel) - .addComponent(emailTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - ); - - pack(); - setLocationRelativeTo(null); - }// //GEN-END:initComponents - - private void okBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okBtnActionPerformed - String name = nameTextField.getText(); - String surname = surnameTextField.getText(); - String email = emailTextField.getText(); - - if (!userController.isInputOkAndShowErrorIfNecessary(this, name, surname, email)) - return; - - user = new User(name, surname, email); - - this.dispose(); - }//GEN-LAST:event_okBtnActionPerformed - - private void emailTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_emailTextFieldKeyReleased - if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) { - okBtnActionPerformed(null); - } - }//GEN-LAST:event_emailTextFieldKeyReleased - - private void nameTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_nameTextFieldKeyReleased - if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) { - surnameTextField.requestFocus(); - } - }//GEN-LAST:event_nameTextFieldKeyReleased - - private void surnameTextFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_surnameTextFieldKeyReleased - if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) { - emailTextField.requestFocus(); - } - }//GEN-LAST:event_surnameTextFieldKeyReleased - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JLabel emailLabel; - private javax.swing.JTextField emailTextField; - private javax.swing.JLabel nameLabel; - private javax.swing.JTextField nameTextField; - private javax.swing.JButton okBtn; - private javax.swing.JLabel surnameLabel; - private javax.swing.JTextField surnameTextField; - // End of variables declaration//GEN-END:variables -} diff --git a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form deleted file mode 100644 index ca18cce3..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.form +++ /dev/null @@ -1,124 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java b/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java deleted file mode 100644 index a227a95b..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/PreferencesDialog.java +++ /dev/null @@ -1,182 +0,0 @@ -package backupmanager.gui.Dialogs; - -import backupmanager.gui.Controllers.GuiController; -import backupmanager.gui.Controllers.PreferenceController; -import backupmanager.Entities.Configurations; -import backupmanager.Enums.LanguagesEnum; -import backupmanager.Enums.ThemesEnum; -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; -import backupmanager.Managers.ThemeManager; -import backupmanager.Services.PreferenceService; -import backupmanager.gui.frames.BackupManagerGUI; - - -public class PreferencesDialog extends javax.swing.JDialog { - - private final PreferenceController preferenceController; - - public PreferencesDialog(java.awt.Frame parent, boolean modal, BackupManagerGUI mainGui) { - super(parent, modal); - - preferenceController = new PreferenceController(new PreferenceService(), mainGui); - - initComponents(); - - this.setIconImage(GuiController.getIcon(this.getClass())); - - ThemeManager.updateThemeDialog(this); - - setLanguages(); - setThemes(); - setTranslations(); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - languagesComboBox = new javax.swing.JComboBox<>(); - jLabel1 = new javax.swing.JLabel(); - jLabel2 = new javax.swing.JLabel(); - themesComboBox = new javax.swing.JComboBox<>(); - applyBtn = new javax.swing.JButton(); - closeBtn = new javax.swing.JButton(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Preferences"); - setAlwaysOnTop(true); - setResizable(false); - - languagesComboBox.setToolTipText(""); - - jLabel1.setText("Language"); - - jLabel2.setText("Theme"); - - themesComboBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - themesComboBoxActionPerformed(evt); - } - }); - - applyBtn.setText("Apply"); - applyBtn.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - applyBtnActionPerformed(evt); - } - }); - - closeBtn.setText("Close"); - closeBtn.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - closeBtnActionPerformed(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() - .addContainerGap(223, Short.MAX_VALUE) - .addComponent(applyBtn) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(closeBtn)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGap(22, 22, 22) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, 345, Short.MAX_VALUE) - .addComponent(themesComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(languagesComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addContainerGap(13, Short.MAX_VALUE)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(20, 20, 20) - .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(languagesComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(jLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(themesComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 109, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(applyBtn) - .addComponent(closeBtn)) - .addGap(14, 14, 14)) - ); - - pack(); - setLocationRelativeTo(null); - }// //GEN-END:initComponents - - private void themesComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_themesComboBoxActionPerformed - - }//GEN-LAST:event_themesComboBoxActionPerformed - - private void applyBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyBtnActionPerformed - preferenceController.applyPreferences( - (String) languagesComboBox.getSelectedItem(), - (String) themesComboBox.getSelectedItem() - ); - setTranslations(); - ThemeManager.updateThemeDialog(this); - }//GEN-LAST:event_applyBtnActionPerformed - - private void closeBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeBtnActionPerformed - this.dispose(); - }//GEN-LAST:event_closeBtnActionPerformed - - private void setLanguages() { - languagesComboBox.addItem(LanguagesEnum.ENG.getLanguageName()); - languagesComboBox.addItem(LanguagesEnum.ITA.getLanguageName()); - languagesComboBox.addItem(LanguagesEnum.ESP.getLanguageName()); - languagesComboBox.addItem(LanguagesEnum.DEU.getLanguageName()); - languagesComboBox.addItem(LanguagesEnum.FRA.getLanguageName()); - - languagesComboBox.setSelectedItem(Configurations.getLanguage().getLanguageName()); - } - - private void setThemes() { - themesComboBox.addItem(ThemesEnum.INTELLIJ.getThemeName()); - themesComboBox.addItem(ThemesEnum.DRACULA.getThemeName()); - themesComboBox.addItem(ThemesEnum.CARBON.getThemeName()); - themesComboBox.addItem(ThemesEnum.ARC_ORAGE.getThemeName()); - themesComboBox.addItem(ThemesEnum.ARC_DARK_ORANGE.getThemeName()); - themesComboBox.addItem(ThemesEnum.CYAN_LIGHT.getThemeName()); - themesComboBox.addItem(ThemesEnum.NORD.getThemeName()); - themesComboBox.addItem(ThemesEnum.HIGH_CONTRAST.getThemeName()); - themesComboBox.addItem(ThemesEnum.SOLARIZED_DARK.getThemeName()); - themesComboBox.addItem(ThemesEnum.SOLARIZED_LIGHT.getThemeName()); - - themesComboBox.setSelectedItem(Configurations.getTheme().getThemeName()); - } - - private void setTranslations() { - setTitle(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.PREFERENCES_TITLE)); - applyBtn.setText(TCategory.GENERAL.getTranslation(TKey.APPLY_BUTTON)); - closeBtn.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); - jLabel1.setText(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.LANGUAGE)); - jLabel2.setText(TCategory.PREFERENCES_DIALOG.getTranslation(TKey.THEME)); - } - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton applyBtn; - private javax.swing.JButton closeBtn; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JComboBox languagesComboBox; - private javax.swing.JComboBox themesComboBox; - // End of variables declaration//GEN-END:variables -} diff --git a/src/main/java/backupmanager/gui/Dialogs/TimePicker.form b/src/main/java/backupmanager/gui/Dialogs/TimePicker.form deleted file mode 100644 index 7ee92e1f..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/TimePicker.form +++ /dev/null @@ -1,187 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/main/java/backupmanager/gui/Dialogs/TimePicker.java b/src/main/java/backupmanager/gui/Dialogs/TimePicker.java deleted file mode 100644 index 8d4e14d1..00000000 --- a/src/main/java/backupmanager/gui/Dialogs/TimePicker.java +++ /dev/null @@ -1,279 +0,0 @@ -package backupmanager.gui.Dialogs; - -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; -import backupmanager.gui.Controllers.GuiController; -import backupmanager.Entities.TimeInterval; - -import backupmanager.gui.Controllers.TimePickerController; - -public class TimePicker extends javax.swing.JDialog { - - private final TimePickerController timePickerController; - - public TimePicker(java.awt.Dialog parent, TimeInterval timeInterval, boolean modal) { - super(parent, modal); - - timePickerController = new TimePickerController(timeInterval, false); - - initComponents(); - - if (timeInterval != null) { - daysSpinner.setValue(timeInterval.days()); - hoursSpinner.setValue(timeInterval.hours()); - minutesSpinner.setValue(timeInterval.minutes()); - } - - this.setIconImage(GuiController.getIcon(this.getClass())); - - setTranslations(); - } - - public TimeInterval getTimeInterval() { - return timePickerController.getTimeInterval(); - } - - private void daysIntervalSpinnerChange() { - Integer days = (Integer) daysSpinner.getValue(); - - if (days == null || days < 0) { - daysSpinner.setValue(0); - } - } - - private void hoursIntervalSpinnerChange() { - Integer hours = (Integer) hoursSpinner.getValue(); - - if (hours == null || hours < 0) { - hoursSpinner.setValue(0); - } else if (hours > 23) { - hoursSpinner.setValue(23); - } - } - - private void minutesIntervalSpinnerChange() { - Integer minutes = (Integer) minutesSpinner.getValue(); - - if (minutes == null || minutes < 0) { - minutesSpinner.setValue(0); - } else if (minutes > 59) { - minutesSpinner.setValue(59); - } - } - - private void mouseWeel(java.awt.event.MouseWheelEvent evt) { - javax.swing.JSpinner spinner = (javax.swing.JSpinner) evt.getSource(); - int rotation = evt.getWheelRotation(); - - if (rotation < 0) { - spinner.setValue((Integer) spinner.getValue() + 1); - } else { - spinner.setValue((Integer) spinner.getValue() - 1); - } - } - - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - jScrollPane1 = new javax.swing.JScrollPane(); - jTextArea1 = new javax.swing.JTextArea(); - jLabel1 = new javax.swing.JLabel(); - daysSpinner = new javax.swing.JSpinner(); - hoursSpinner = new javax.swing.JSpinner(); - jLabel2 = new javax.swing.JLabel(); - jLabel3 = new javax.swing.JLabel(); - minutesSpinner = new javax.swing.JSpinner(); - btnOk = new javax.swing.JButton(); - jButton2 = new javax.swing.JButton(); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Time interval for auto backup"); - setMaximumSize(new java.awt.Dimension(325, 290)); - setMinimumSize(new java.awt.Dimension(325, 290)); - setPreferredSize(new java.awt.Dimension(325, 290)); - setResizable(false); - - jTextArea1.setEditable(false); - jTextArea1.setColumns(20); - jTextArea1.setRows(2); - jTextArea1.setText("Select how often to perform the automatic backup by \nchoosing the frequency in days, hours, and minutes."); - jTextArea1.setAutoscrolls(false); - jTextArea1.setBorder(null); - jTextArea1.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - jTextArea1.setFocusable(false); - jTextArea1.setRequestFocusEnabled(false); - jScrollPane1.setViewportView(jTextArea1); - - jLabel1.setText("Days"); - - daysSpinner.setToolTipText("Mouse wheel to adjust the value"); - daysSpinner.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(javax.swing.event.ChangeEvent evt) { - daysSpinnerStateChanged(evt); - } - }); - daysSpinner.addMouseWheelListener(new java.awt.event.MouseWheelListener() { - public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { - daysSpinnerMouseWheelMoved(evt); - } - }); - - hoursSpinner.setToolTipText("Mouse wheel to adjust the value"); - hoursSpinner.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(javax.swing.event.ChangeEvent evt) { - hoursSpinnerStateChanged(evt); - } - }); - hoursSpinner.addMouseWheelListener(new java.awt.event.MouseWheelListener() { - public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { - hoursSpinnerMouseWheelMoved(evt); - } - }); - - jLabel2.setText("Hours"); - - jLabel3.setText("Minutes"); - - minutesSpinner.setToolTipText("Mouse wheel to adjust the value"); - minutesSpinner.addChangeListener(new javax.swing.event.ChangeListener() { - public void stateChanged(javax.swing.event.ChangeEvent evt) { - minutesSpinnerStateChanged(evt); - } - }); - minutesSpinner.addMouseWheelListener(new java.awt.event.MouseWheelListener() { - public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) { - minutesSpinnerMouseWheelMoved(evt); - } - }); - - btnOk.setText("Ok"); - btnOk.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - btnOkActionPerformed(evt); - } - }); - - jButton2.setText("Cancel"); - jButton2.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButton2ActionPerformed(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); - getContentPane().setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 313, Short.MAX_VALUE) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 92, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(18, 18, 18) - .addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGap(18, 18, 18) - .addComponent(hoursSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(minutesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addComponent(btnOk)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jButton2))) - .addContainerGap()) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(daysSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel1)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(hoursSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel2)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(minutesSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 35, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jLabel3)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 87, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(btnOk) - .addComponent(jButton2)) - .addContainerGap()) - ); - - pack(); - setLocationRelativeTo(null); - }// //GEN-END:initComponents - - private void daysSpinnerMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_daysSpinnerMouseWheelMoved - mouseWeel(evt); - }//GEN-LAST:event_daysSpinnerMouseWheelMoved - - private void hoursSpinnerMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_hoursSpinnerMouseWheelMoved - mouseWeel(evt); - }//GEN-LAST:event_hoursSpinnerMouseWheelMoved - - private void minutesSpinnerMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {//GEN-FIRST:event_minutesSpinnerMouseWheelMoved - mouseWeel(evt); - }//GEN-LAST:event_minutesSpinnerMouseWheelMoved - - private void btnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOkActionPerformed - timePickerController.handleOkButton(this, (Integer) daysSpinner.getValue(), (Integer) hoursSpinner.getValue(), (Integer) minutesSpinner.getValue()); - }//GEN-LAST:event_btnOkActionPerformed - - private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed - timePickerController.setCloseOk(false); - this.dispose(); - }//GEN-LAST:event_jButton2ActionPerformed - - private void daysSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_daysSpinnerStateChanged - daysIntervalSpinnerChange(); - }//GEN-LAST:event_daysSpinnerStateChanged - - private void hoursSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_hoursSpinnerStateChanged - hoursIntervalSpinnerChange(); - }//GEN-LAST:event_hoursSpinnerStateChanged - - private void minutesSpinnerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_minutesSpinnerStateChanged - minutesIntervalSpinnerChange(); - }//GEN-LAST:event_minutesSpinnerStateChanged - - private void setTranslations() { - setTitle(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.TIME_INTERVAL_TITLE)); - jTextArea1.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.DESCRIPTION)); - daysSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); - hoursSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); - minutesSpinner.setToolTipText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.SPINNER_TOOLTIP)); - btnOk.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.OK_BUTTON)); - jButton2.setText(TCategory.GENERAL.getTranslation(TKey.CANCEL_BUTTON)); - jLabel1.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.DAYS)); - jLabel2.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.HOURS)); - jLabel3.setText(TCategory.TIME_PICKER_DIALOG.getTranslation(TKey.MINUTES)); - } - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton btnOk; - private javax.swing.JSpinner daysSpinner; - private javax.swing.JSpinner hoursSpinner; - private javax.swing.JButton jButton2; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel2; - private javax.swing.JLabel jLabel3; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JTextArea jTextArea1; - private javax.swing.JSpinner minutesSpinner; - // End of variables declaration//GEN-END:variables -} diff --git a/src/main/java/backupmanager/gui/Table/BackupTableDataService.java b/src/main/java/backupmanager/gui/Table/BackupTableDataService.java new file mode 100644 index 00000000..9d6053f3 --- /dev/null +++ b/src/main/java/backupmanager/gui/Table/BackupTableDataService.java @@ -0,0 +1,105 @@ +package backupmanager.gui.Table; + +import java.time.format.DateTimeFormatter; + +import javax.swing.JTable; +import javax.swing.SwingUtilities; +import javax.swing.table.TableCellRenderer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import backupmanager.Entities.ConfigurationBackup; + +public class BackupTableDataService { + + private final Logger logger = LoggerFactory.getLogger(BackupTableDataService.class); + private final JTable table; + private final DateTimeFormatter formatter; + private final ProgressBarRenderer progressRenderer; + private final TableCellRenderer defaultRenderer; + + private static final int COLUMN_PROGRESS = 3; + + public BackupTableDataService(JTable table, DateTimeFormatter formatter) { + if (table == null) throw new IllegalArgumentException("Table cannot be null"); + if (formatter == null) throw new IllegalArgumentException("Formatter cannot be null"); + this.table = table; + this.formatter = formatter; + this.progressRenderer = new ProgressBarRenderer(); + this.defaultRenderer = table.getColumnModel().getColumn(COLUMN_PROGRESS).getCellRenderer(); + } + + public void removeProgress(ConfigurationBackup backup) { + if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); + + int row = findBackupRowIndex(backup); + + // remove the progress bar renderer + table.getColumnModel().getColumn(COLUMN_PROGRESS).setCellRenderer(defaultRenderer); + + // Set last backup value in the table + table.getModel().setValueAt( + backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", + row, + COLUMN_PROGRESS + ); + + table.repaint(); // Repaints the whole table + } + + public void updateProgress(ConfigurationBackup backup, int value) { + if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); + if (value < 0 || value > 100) throw new IllegalArgumentException("Value must be between 0 and 100"); + + SwingUtilities.invokeLater(() -> { + // Locate the row index of the backup in the table + int rowIndex = findBackupRowIndex(backup); + if (rowIndex == -1) return; + + table.getColumnModel().getColumn(COLUMN_PROGRESS).setCellRenderer(progressRenderer); + + // Restore the original renderer after completion + if (value == 100) { + logger.debug("Restore the original renderer after completion"); + removeProgress(backup); + } else { + // Update the value of the progress in the table + table.getModel().setValueAt(value, rowIndex, COLUMN_PROGRESS); + } + + table.repaint(); + }); + } + + // public void updateTableWithNewBackupList(List updatedBackups) { + // logger.debug("updating backup list"); + + // SwingUtilities.invokeLater(() -> { + // BackupManagerGUI.model.setRowCount(0); + + // for (ConfigurationBackup backup : updatedBackups) { + // BackupManagerGUI.model.addRow(new Object[]{ + // backup.getName(), + // backup.getTargetPath(), + // backup.getDestinationPath(), + // backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", + // backup.isAutomatic(), + // backup.getNextBackupDate() != null ? backup.getNextBackupDate().format(formatter) : "", + // backup.getTimeIntervalBackup() != null ? backup.getTimeIntervalBackup().toString() : "" + // }); + // } + // }); + // } + + private int findBackupRowIndex(ConfigurationBackup backup) { + if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); + + for (int i = 0; i < table.getRowCount(); i++) { + if (table.getValueAt(i, 0).equals(backup.getName())) { // first column holds unique backup names + return i; + } + } + return -1; + } +} diff --git a/src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java b/src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java deleted file mode 100644 index 282dcc9a..00000000 --- a/src/main/java/backupmanager/gui/Table/CheckboxCellRenderer.java +++ /dev/null @@ -1,35 +0,0 @@ -package backupmanager.gui.Table; - -import java.awt.Color; -import java.awt.Component; -import javax.swing.JCheckBox; -import javax.swing.JTable; -import javax.swing.table.DefaultTableCellRenderer; - -public class CheckboxCellRenderer extends DefaultTableCellRenderer { - private final JCheckBox checkBox = new JCheckBox(); - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (value instanceof Boolean aBoolean) { - checkBox.setSelected(aBoolean); - checkBox.setHorizontalAlignment(CENTER); - - if (row % 2 == 0) { - checkBox.setBackground(new Color(223, 222, 243)); - } else { - checkBox.setBackground(Color.WHITE); - } - - if (isSelected) { - checkBox.setBackground(table.getSelectionBackground()); - checkBox.setForeground(table.getSelectionForeground()); - } else { - checkBox.setForeground(Color.BLACK); - } - - return checkBox; - } - return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - } -} diff --git a/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java b/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java index 37e8b3d1..745cbab4 100644 --- a/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java +++ b/src/main/java/backupmanager/gui/Table/ProgressBarRenderer.java @@ -1,6 +1,5 @@ package backupmanager.gui.Table; -import java.awt.Color; import java.awt.Component; import javax.swing.JProgressBar; @@ -8,28 +7,17 @@ import javax.swing.table.DefaultTableCellRenderer; public class ProgressBarRenderer extends DefaultTableCellRenderer { - private final StripedRowRenderer stripedRowRenderer = new StripedRowRenderer(); private final JProgressBar progressBar = new JProgressBar(0, 100); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - // Delegate the striped row coloring logic to the StripedRowRenderer - Component c = stripedRowRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); // If the value is an Integer (assuming progress data), show the progress bar if (value instanceof Integer integer) { progressBar.setValue(integer); progressBar.setString(integer + "%"); progressBar.setStringPainted(true); - - // Set the progress bar background color based on the row (even/odd striped rows) - if (row % 2 == 0) { - progressBar.setBackground(new Color(223, 222, 243)); // Even row color for progress bar - } else { - progressBar.setBackground(Color.WHITE); // Odd row color for progress bar - } - - // Return the progress bar component instead of the default cell component return progressBar; } diff --git a/src/main/java/backupmanager/gui/Table/StripedRowRenderer.java b/src/main/java/backupmanager/gui/Table/StripedRowRenderer.java deleted file mode 100644 index 7e4dc80f..00000000 --- a/src/main/java/backupmanager/gui/Table/StripedRowRenderer.java +++ /dev/null @@ -1,34 +0,0 @@ -package backupmanager.gui.Table; - -import java.awt.Color; -import java.awt.Component; - -import javax.swing.JTable; -import javax.swing.table.DefaultTableCellRenderer; - -public class StripedRowRenderer extends DefaultTableCellRenderer { - private final Color evenRowColor = new Color(223, 222, 243); - private final Color oddRowColor = Color.WHITE; - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - - // Apply striped row colors - if (row % 2 == 0) { - c.setBackground(evenRowColor); - } else { - c.setBackground(oddRowColor); - } - - // Handle selection - if (isSelected) { - c.setBackground(table.getSelectionBackground()); - c.setForeground(table.getSelectionForeground()); - } else { - c.setForeground(Color.BLACK); - } - - return c; - } -} diff --git a/src/main/java/backupmanager/gui/Table/TableDataManager.java b/src/main/java/backupmanager/gui/Table/TableDataManager.java deleted file mode 100644 index fbd768fa..00000000 --- a/src/main/java/backupmanager/gui/Table/TableDataManager.java +++ /dev/null @@ -1,106 +0,0 @@ -package backupmanager.gui.Table; - -import java.time.format.DateTimeFormatter; -import java.util.List; - -import javax.swing.SwingUtilities; -import javax.swing.table.TableColumnModel; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import backupmanager.Entities.ConfigurationBackup; -import backupmanager.gui.frames.BackupManagerGUI; - -public class TableDataManager { - - private static final Logger logger = LoggerFactory.getLogger(TableDataManager.class); - - public static void removeProgressInTheTableAndRestoreAsDefault(ConfigurationBackup backup, DateTimeFormatter formatter) { - if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); - if (formatter == null) throw new IllegalArgumentException("Formatter cannot be null"); - - if (BackupManagerGUI.backupTable == null) { - return; - } - - // remove the progress bar renderer - BackupManagerGUI.backupTable.getColumnModel().getColumn(3).setCellRenderer(new StripedRowRenderer()); - - // Set last backup value in the table - BackupManagerGUI.backupTable.getModel().setValueAt( - backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", - TableDataManager.findBackupRowIndex(backup, BackupManagerGUI.backupTable), 3); - - BackupManagerGUI.backupTable.repaint(); // Repaints the whole table - BackupManagerGUI.backupTable.revalidate(); // Revalidates the table layout - } - - public static void updateProgressBarPercentage(ConfigurationBackup backup, int value, DateTimeFormatter formatter) { - if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); - if (value < 0 || value > 100) throw new IllegalArgumentException("Value must be between 0 and 100"); - if (formatter == null) throw new IllegalArgumentException("Formatter cannot be null"); - - if (BackupManagerGUI.backupTable == null) { - return; - } - - SwingUtilities.invokeLater(() -> { - // Locate the row index of the backup in the table - int rowIndex = TableDataManager.findBackupRowIndex(backup, BackupManagerGUI.backupTable); - if (rowIndex != -1) { - TableColumnModel columnModel = BackupManagerGUI.backupTable.getColumnModel(); - int targetColumnIndex = 3; - - columnModel.getColumn(targetColumnIndex).setCellRenderer(new ProgressBarRenderer()); - - // Restore the original renderer after completion - if (value == 100) { - logger.debug("Restore the original renderer after completion"); - BackupManagerGUI.backupTable.getModel().setValueAt( - backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", - rowIndex, - targetColumnIndex - ); - } else { - // Update the value of the progress in the table - BackupManagerGUI.backupTable.getModel().setValueAt(value, rowIndex, targetColumnIndex); - } - - BackupManagerGUI.backupTable.repaint(); - } - }); - } - - public static void updateTableWithNewBackupList(List updatedBackups, DateTimeFormatter formatter) { - logger.debug("updating backup list"); - - SwingUtilities.invokeLater(() -> { - BackupManagerGUI.model.setRowCount(0); - - for (ConfigurationBackup backup : updatedBackups) { - BackupManagerGUI.model.addRow(new Object[]{ - backup.getName(), - backup.getTargetPath(), - backup.getDestinationPath(), - backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", - backup.isAutomatic(), - backup.getNextBackupDate() != null ? backup.getNextBackupDate().format(formatter) : "", - backup.getTimeIntervalBackup() != null ? backup.getTimeIntervalBackup().toString() : "" - }); - } - }); - } - - private static int findBackupRowIndex(ConfigurationBackup backup, BackupTable table) { - if (backup == null) throw new IllegalArgumentException("Backup cannot be null"); - if (table == null) throw new IllegalArgumentException("Table cannot be null"); - - for (int i = 0; i < table.getRowCount(); i++) { - if (table.getValueAt(i, 0).equals(backup.getName())) { // first column holds unique backup names - return i; - } - } - return -1; - } -} diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index dab2b488..40f903ca 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -15,13 +15,12 @@ import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; -import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingConstants; -import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; @@ -38,6 +37,8 @@ import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Services.BackupService; +import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.Controllers.BackupManagerController; import backupmanager.gui.frames.Controllers.BackupPopupController; import backupmanager.gui.sample.csv.ConfigurationBackupDataTable; @@ -52,18 +53,15 @@ public class FormBackupTable extends CustomForm { private static final Logger logger = LoggerFactory.getLogger(FormBackupTable.class); - private final BackupManagerController managerController; + private BackupManagerController managerController; + private BackupTableDataService tableService; private final int COL_LAST_RUN = 3; private final int COL_AUTOMATIC = 4; private final int COL_NEXT_RUN = 5; - private final BackupService backupService; private List backups; public FormBackupTable() { - backupService = new BackupService(); - managerController = new BackupManagerController(backupService); - build(); } @@ -86,12 +84,15 @@ public void formInit() { backupTable.createDefaultColumnsFromModel(); + tableService = new BackupTableDataService(backupTable, formatter); + managerController = new BackupManagerController(new BackupService(), tableService); + formRefresh(); } @Override public void formRefresh() { - backups = backupService.getAllBackups(); + backups = managerController.getAllBackups(); loadData(); } @@ -128,36 +129,27 @@ private Component createBasicTable() { )); // create table model - JTable table = new JTable(); - table.setModel(new DefaultTableModel() { - - @Override - public Class getColumnClass(int columnIndex) { - - if (columnIndex == COL_AUTOMATIC) { - return Boolean.class; - } - - if (columnIndex == COL_LAST_RUN || columnIndex == COL_NEXT_RUN) { - return LocalDateTime.class; + BackupTable table = new BackupTable( + new DefaultTableModel() { + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == COL_AUTOMATIC) + return Boolean.class; + if (columnIndex == COL_LAST_RUN || columnIndex == COL_NEXT_RUN) + return LocalDateTime.class; + if (columnIndex == 6) + return TimeInterval.class; + if (columnIndex == 7) + return Integer.class; + return String.class; } - if (columnIndex == 6) { - return TimeInterval.class; + @Override + public boolean isCellEditable(int row, int column) { + return false; } - - if (columnIndex == 7) { - return Integer.class; - } - - return String.class; - } - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - }); + }); table.putClientProperty(FlatClientProperties.STYLE, "rowHeight:34; showHorizontalLines:false;"); @@ -165,7 +157,7 @@ public boolean isCellEditable(int row, int column) { if (!e.getValueIsAdjusting()) { int row = table.getSelectedRow(); if (row != -1) { - String details = backupService.buildDetails(backups.get(row)); + String details = managerController.buildDetails(backups.get(row)); txtDetails.setText(details); } } @@ -399,13 +391,13 @@ private void handleAction(String action, JMenuItem interruptBackupPopupItem, JMe ConfigurationBackup backup = getBackupFromTableRow(selectedRow); switch (action) { - case "EDIT" -> BackupPopupController.popupItemEditBackupName(backup); + case "EDIT" -> BackupPopupController.popupItemEditBackupName(tableService, backup); case "DELETE" -> BackupHelper.deleteBackup(backup); case "DUPLICATE" -> BackupPopupController.popupItemDuplicateBackup(backup); case "RENAME" -> BackupPopupController.popupItemRenameBackup(backups, backup); case "OPEN_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); case "OPEN_DEST" -> BackupPopupController.popupItemOpenDestinationPath(backup); - case "RUN_SINGLE" -> BackupPopupController.popupItemRunBackup(backup, backupTable, interruptBackupPopupItem, RunBackupPopupItem); + case "RUN_SINGLE" -> BackupPopupController.popupItemRunBackup(backup, tableService, interruptBackupPopupItem, RunBackupPopupItem); case "COPY_NAME" -> BackupPopupController.popupItemCopyBackupName(backup); case "COPY_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); case "COPY_DEST" -> BackupPopupController.popupItemCopyDestinationPath(backup); @@ -478,7 +470,7 @@ private void update() { return; String research = txtSearch.getText(); - backups = backupService.getAllBackups(); + backups = managerController.getAllBackups(); backups = managerController.researchInTableAndGet(backups, research); loadData(); } @@ -556,7 +548,7 @@ protected void setTranslations() { private final int limit = 50; private JPagination pagination; - private JTable backupTable; + private BackupTable backupTable; private JLabel lbTotalPage; private JTextPane txtDetails; private JTextField txtSearch; diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index f2dce63d..72634bc0 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -34,7 +34,6 @@ import com.formdev.flatlaf.util.ScaledEmptyBorder; import backupmanager.gui.component.AccentColorIcon; -import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.system.FormManager; import backupmanager.gui.themes.PanelThemes; import backupmanager.utils.DemoPreferences; @@ -56,7 +55,7 @@ @SystemForm(name = "Setting", description = "application setting and configuration", tags = {"themes", "options"}) public class FormSetting extends CustomForm { - private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); + private static final Logger logger = LoggerFactory.getLogger(FormSetting.class); public FormSetting() { build(); diff --git a/src/main/java/backupmanager/gui/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java index 540332ee..c4269375 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -12,10 +12,20 @@ import raven.modal.Drawer; public class BackupManager extends JFrame{ - public BackupManager() { + + private static BackupManager instance; + + private BackupManager() { init(); } + public static synchronized BackupManager getInstance() { + if (instance == null) { + instance = new BackupManager(); + } + return instance; + } + private void init() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); diff --git a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.form b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.form deleted file mode 100644 index f9a90456..00000000 --- a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.form +++ /dev/null @@ -1,899 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - <Column maxWidth="-1" minWidth="-1" prefWidth="-1" resizable="true"> - <Title/> - <Editor/> - <Renderer/> - </Column> - </TableColumnModel> - </Property> - <Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor"> - <Color id="Cursore predefinito"/> - </Property> - <Property name="rowHeight" type="int" value="50"/> - <Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor"> - <TableHeader reorderingAllowed="true" resizingAllowed="true"/> - </Property> - </Properties> - <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="tableMouseClicked"/> - </Events> - </Component> - </SubComponents> - </Container> - <Component class="backupmanager.gui.svg.SVGButton" name="addBackupEntryButton"> - <Properties> - <Property name="toolTipText" type="java.lang.String" value="Add new backup"/> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32, 32]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32, 32]"/> - </Property> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="addBackupEntryButtonActionPerformed"/> - </Events> - </Component> - <Component class="backupmanager.gui.svg.SVGButton" name="exportAsCsvBtn"> - <Properties> - <Property name="toolTipText" type="java.lang.String" value="Export as .csv"/> - <Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32, 32]"/> - </Property> - <Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[32, 32]"/> - </Property> - </Properties> - <Events> - <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="exportAsCsvBtnActionPerformed"/> - </Events> - </Component> - <Component class="javax.swing.JLabel" name="ExportLabel"> - <Properties> - <Property name="horizontalAlignment" type="int" value="4"/> - <Property name="text" type="java.lang.String" value="Export As:"/> - </Properties> - </Component> - </SubComponents> - </Container> - <Container class="javax.swing.JPanel" name="panelDashboard"> - <Constraints> - <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription"> - <CardConstraints cardName="Dashboard"/> - </Constraint> - </Constraints> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="1" attributes="0"> - <EmptySpace min="-2" pref="52" max="-2" attributes="0"/> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" attributes="0"> - <Component id="totalBackupsPanel" min="-2" max="-2" attributes="0"/> - <EmptySpace type="separate" max="-2" attributes="0"/> - <Component id="totalBackupConfigurationsPanel" min="-2" max="-2" attributes="0"/> - </Group> - <Component id="totalSpaceUsedPanel" min="-2" max="-2" attributes="0"/> - </Group> - <EmptySpace pref="228" max="32767" attributes="0"/> - <Group type="103" groupAlignment="1" max="-2" attributes="0"> - <Component id="chart2" alignment="1" pref="300" max="32767" attributes="0"/> - <Component id="chart1" alignment="1" pref="300" max="32767" attributes="0"/> - <Component id="chart2TitleLabel" alignment="1" pref="300" max="32767" attributes="0"/> - <Component id="chart1TitleLabel" alignment="1" max="32767" attributes="0"/> - </Group> - <EmptySpace min="-2" pref="16" max="-2" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="1" attributes="0"> - <EmptySpace min="-2" pref="18" max="-2" attributes="0"/> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" attributes="0"> - <Component id="chart1TitleLabel" min="-2" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> - <Component id="chart1" min="-2" max="-2" attributes="0"/> - </Group> - <Component id="totalBackupConfigurationsPanel" min="-2" max="-2" attributes="0"/> - </Group> - <EmptySpace type="unrelated" max="-2" attributes="0"/> - <Component id="chart2TitleLabel" min="-2" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> - <Component id="chart2" min="-2" max="-2" attributes="0"/> - </Group> - <Group type="102" alignment="0" attributes="0"> - <Component id="totalBackupsPanel" min="-2" max="-2" attributes="0"/> - <EmptySpace min="-2" pref="20" max="-2" attributes="0"/> - <Component id="totalSpaceUsedPanel" min="-2" max="-2" attributes="0"/> - </Group> - </Group> - <EmptySpace pref="34" max="32767" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - </Layout> - <SubComponents> - <Container class="javax.swing.JPanel" name="chart2"> - <Properties> - <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> - <Border info="org.netbeans.modules.form.compat2.border.SoftBevelBorderInfo"> - <BevelBorder/> - </Border> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[230, 180]"/> - </Property> - </Properties> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="0" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="174" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - </Layout> - </Container> - <Container class="javax.swing.JPanel" name="chart1"> - <Properties> - <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> - <Border info="org.netbeans.modules.form.compat2.border.SoftBevelBorderInfo"> - <BevelBorder/> - </Border> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[230, 180]"/> - </Property> - </Properties> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="0" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <EmptySpace min="0" pref="174" max="32767" attributes="0"/> - </Group> - </DimensionLayout> - </Layout> - </Container> - <Component class="javax.swing.JLabel" name="chart1TitleLabel"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="0"/> - </Property> - <Property name="horizontalAlignment" type="int" value="0"/> - <Property name="text" type="java.lang.String" value="Title Chart 1"/> - </Properties> - </Component> - <Component class="javax.swing.JLabel" name="chart2TitleLabel"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="0"/> - </Property> - <Property name="horizontalAlignment" type="int" value="0"/> - <Property name="text" type="java.lang.String" value="Title Chart 2"/> - </Properties> - </Component> - <Container class="javax.swing.JPanel" name="totalBackupsPanel"> - <Properties> - <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> - <Border info="org.netbeans.modules.form.compat2.border.MatteColorBorderInfo"> - <MatteColorBorder/> - </Border> - </Property> - </Properties> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <EmptySpace min="-2" pref="12" max="-2" attributes="0"/> - <Group type="103" groupAlignment="0" attributes="0"> - <Component id="totalBackupsNumber" max="32767" attributes="0"/> - <Component id="totalBackupsTitleLabel" pref="194" max="32767" attributes="0"/> - </Group> - <EmptySpace max="-2" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <Component id="totalBackupsTitleLabel" min="-2" pref="40" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> - <Component id="totalBackupsNumber" min="-2" pref="25" max="-2" attributes="0"/> - <EmptySpace min="0" pref="31" max="32767" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - </Layout> - <SubComponents> - <Component class="javax.swing.JLabel" name="totalBackupsTitleLabel"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="0"/> - </Property> - <Property name="text" type="java.lang.String" value="Total Backups"/> - </Properties> - </Component> - <Component class="javax.swing.JLabel" name="totalBackupsNumber"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="1"/> - </Property> - <Property name="text" type="java.lang.String" value="10"/> - </Properties> - </Component> - </SubComponents> - </Container> - <Container class="javax.swing.JPanel" name="totalBackupConfigurationsPanel"> - <Properties> - <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> - <Border info="org.netbeans.modules.form.compat2.border.MatteColorBorderInfo"> - <MatteColorBorder/> - </Border> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[214, 104]"/> - </Property> - </Properties> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <EmptySpace min="-2" pref="12" max="-2" attributes="0"/> - <Group type="103" groupAlignment="0" attributes="0"> - <Component id="totalBackupConfigurationsNumber" alignment="0" max="32767" attributes="0"/> - <Component id="totalBackupConfigurationsTitleLabel" alignment="0" pref="194" max="32767" attributes="0"/> - </Group> - <EmptySpace max="-2" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <Component id="totalBackupConfigurationsTitleLabel" min="-2" pref="40" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> - <Component id="totalBackupConfigurationsNumber" min="-2" pref="25" max="-2" attributes="0"/> - <EmptySpace min="0" pref="31" max="32767" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - </Layout> - <SubComponents> - <Component class="javax.swing.JLabel" name="totalBackupConfigurationsTitleLabel"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="0"/> - </Property> - <Property name="text" type="java.lang.String" value="Total Backups"/> - </Properties> - </Component> - <Component class="javax.swing.JLabel" name="totalBackupConfigurationsNumber"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="1"/> - </Property> - <Property name="text" type="java.lang.String" value="10"/> - </Properties> - </Component> - </SubComponents> - </Container> - <Container class="javax.swing.JPanel" name="totalSpaceUsedPanel"> - <Properties> - <Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor"> - <Border info="org.netbeans.modules.form.compat2.border.MatteColorBorderInfo"> - <MatteColorBorder/> - </Border> - </Property> - <Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor"> - <Dimension value="[214, 104]"/> - </Property> - </Properties> - - <Layout> - <DimensionLayout dim="0"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <EmptySpace min="-2" pref="12" max="-2" attributes="0"/> - <Group type="103" groupAlignment="0" attributes="0"> - <Component id="totalSpaceNumber" alignment="0" max="32767" attributes="0"/> - <Component id="totalSpaceTitleLabel" alignment="0" pref="194" max="32767" attributes="0"/> - </Group> - <EmptySpace max="-2" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - <DimensionLayout dim="1"> - <Group type="103" groupAlignment="0" attributes="0"> - <Group type="102" alignment="0" attributes="0"> - <Component id="totalSpaceTitleLabel" min="-2" pref="40" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> - <Component id="totalSpaceNumber" min="-2" pref="25" max="-2" attributes="0"/> - <EmptySpace min="0" pref="31" max="32767" attributes="0"/> - </Group> - </Group> - </DimensionLayout> - </Layout> - <SubComponents> - <Component class="javax.swing.JLabel" name="totalSpaceTitleLabel"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="0"/> - </Property> - <Property name="text" type="java.lang.String" value="Total Space Used"/> - </Properties> - </Component> - <Component class="javax.swing.JLabel" name="totalSpaceNumber"> - <Properties> - <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Segoe UI" size="18" style="1"/> - </Property> - <Property name="text" type="java.lang.String" value="10"/> - </Properties> - </Component> - </SubComponents> - </Container> - </SubComponents> - </Container> - </SubComponents> - </Container> - </SubComponents> -</Form> diff --git a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java b/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java deleted file mode 100644 index 706b3506..00000000 --- a/src/main/java/backupmanager/gui/frames/BackupManagerGUI.java +++ /dev/null @@ -1,1195 +0,0 @@ -package backupmanager.gui.frames; - -import java.awt.event.ActionEvent; -import java.awt.event.KeyEvent; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.swing.AbstractAction; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.JComponent; -import javax.swing.JScrollPane; -import javax.swing.KeyStroke; -import javax.swing.SwingUtilities; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableColumnModel; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.formdev.flatlaf.FlatClientProperties; - -import backupmanager.gui.Controllers.GuiController; -import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.Configurations; -import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.MenuItems; -import backupmanager.Enums.Translations; -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; -import backupmanager.Helpers.BackupHelper; -import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; -import static backupmanager.Helpers.BackupHelper.formatter; -import backupmanager.Json.JsonConfig; -import backupmanager.Managers.ExceptionManager; -import backupmanager.Managers.ExportManager; -import backupmanager.Managers.ThemeManager; -import backupmanager.Services.BackupObserver; -import backupmanager.Services.BackupService; -import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.BackupTableModel; -import backupmanager.gui.Table.CheckboxCellRenderer; -import backupmanager.gui.Table.StripedRowRenderer; -import backupmanager.database.Repositories.BackupConfigurationRepository; -import backupmanager.gui.frames.Controllers.BackupManagerController; -import backupmanager.gui.frames.Controllers.BackupMenuController; -import backupmanager.gui.frames.Controllers.BackupPopupController; - -public final class BackupManagerGUI extends javax.swing.JFrame { - private static final Logger logger = LoggerFactory.getLogger(BackupManagerGUI.class); - - private final BackupManagerController backupManagerController; - private final BackupObserver observer; - public static List<ConfigurationBackup> backups; - public static DefaultTableModel model; - public static BackupTable backupTable; - public static BackupTableModel tableModel; - public static BackupProgressGUI progressBar; - private Integer selectedRow; - - public BackupManagerGUI() { - ThemeManager.updateThemeFrame(this); - - initComponents(); - - backupManagerController = new BackupManagerController(new BackupService()); - - - this.setIconImage(GuiController.getIcon(this.getClass())); - - initializeMenuItems(); - setScreenSize(); - researchField.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new com.formdev.flatlaf.extras.FlatSVGIcon("res/img/search.svg", 16, 16)); - setTranslations(); - - // first initialize the table, then start observer thread - initializeTable(); - observer = new BackupObserver(dateForfolderNameFormatter, 1000); - observer.start(); - - interruptBackupPopupItem.setEnabled(false); - - setSvgImages(); - backupManagerController.checkForFirstAccess(this); - jSeparator5.setVisible(false); - - // TODO: remove this - interruptBackupPopupItem.setVisible(false); - } - - public void showWindow() { - setVisible(true); - toFront(); - requestFocus(); - } - - private void setScreenSize() { - int[] screenSize = backupManagerController.getScreenSize(); - this.setSize(screenSize[0], screenSize[1]); - } - - public void reloadPreferences() { - logger.info("Reloading preferences"); - - Configurations.updateAllConfigurations(); - - // load language - try { - Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); - setTranslations(); - } catch (IOException ex) { - logger.error("An error occurred during reloading preferences operation: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - - // load theme - ThemeManager.updateThemeFrame(this); - ThemeManager.refreshPopup(TablePopup); - setSvgImages(); - } - - private void displayBackupList() { - BackupTableModel tempModel = new BackupTableModel(getColumnTranslations(), 0); - BackupManagerGUI main = this; - - // Populate the model with backup data - for (ConfigurationBackup backup : backups) { - tempModel.addRow(new Object[]{ - backup.getName(), - backup.getTargetPath(), - backup.getDestinationPath(), - backup.getLastBackupDate() != null ? backup.getLastBackupDate().format(formatter) : "", - backup.isAutomatic(), - backup.getNextBackupDate() != null ? backup.getNextBackupDate().format(formatter) : "", - backup.getTimeIntervalBackup() != null ? backup.getTimeIntervalBackup().toString() : "" - }); - } - - backupTable = new BackupTable(tempModel); - - // Add key bindings using InputMap and ActionMap - InputMap inputMap = backupTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - ActionMap actionMap = backupTable.getActionMap(); - - // Handle Enter key - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enterKey"); - actionMap.put("enterKey", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - backupManagerController.handleEnterKeyPressOnTable(main, backupTable); - } - }); - - // Handle Delete key - inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteKey"); - actionMap.put("deleteKey", new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - backupManagerController.handleDeleteKeyPressOnTable(backupTable); - } - }); - - // Apply renderers for each column - TableColumnModel columnModel = backupTable.getColumnModel(); - - for (int i = 0; i < columnModel.getColumnCount(); i++) { - if (i == 4) { - columnModel.getColumn(i).setCellRenderer(new CheckboxCellRenderer()); - columnModel.getColumn(i).setCellEditor(backupTable.getDefaultEditor(Boolean.class)); - } else { - columnModel.getColumn(i).setCellRenderer(new StripedRowRenderer()); - } - } - - // Add the existing mouse listener to the new table - backupTable.addMouseListener(new java.awt.event.MouseAdapter() { - @Override - public void mouseClicked(java.awt.event.MouseEvent evt) { - tableMouseClicked(evt); // Reuse the existing method - } - }); - - // Update the global model reference - BackupManagerGUI.model = tempModel; - - // Replace the existing table in the GUI - JScrollPane scrollPane = (JScrollPane) table.getParent().getParent(); - table = backupTable; // Update the reference to the new table - scrollPane.setViewportView(table); // Replace the table in the scroll pane - } - - /** - * This method is called from within the constructor to initialize the form. - * - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents - private void initComponents() { - - TablePopup = new javax.swing.JPopupMenu(); - EditPoputItem = new javax.swing.JMenuItem(); - DeletePopupItem = new javax.swing.JMenuItem(); - interruptBackupPopupItem = new javax.swing.JMenuItem(); - DuplicatePopupItem = new javax.swing.JMenuItem(); - renamePopupItem = new javax.swing.JMenuItem(); - jSeparator1 = new javax.swing.JPopupMenu.Separator(); - OpenInitialFolderItem = new javax.swing.JMenuItem(); - OpenInitialDestinationItem = new javax.swing.JMenuItem(); - jSeparator3 = new javax.swing.JPopupMenu.Separator(); - Backup = new javax.swing.JMenu(); - RunBackupPopupItem = new javax.swing.JMenuItem(); - AutoBackupMenuItem = new javax.swing.JCheckBoxMenuItem(); - jSeparator2 = new javax.swing.JPopupMenu.Separator(); - jMenu4 = new javax.swing.JMenu(); - CopyBackupNamePopupItem = new javax.swing.JMenuItem(); - CopyInitialPathPopupItem = new javax.swing.JMenuItem(); - CopyDestinationPathPopupItem = new javax.swing.JMenuItem(); - panelVersion = new javax.swing.JPanel(); - jLabel3 = new javax.swing.JLabel(); - layeredCardPanel = new javax.swing.JLayeredPane(); - panelBackupList = new javax.swing.JPanel(); - detailsLabel = new javax.swing.JLabel(); - researchField = new javax.swing.JTextField(); - exportAsPdfBtn = new backupmanager.gui.svg.SVGButton(); - jLabel1 = new javax.swing.JLabel(); - jScrollPane1 = new javax.swing.JScrollPane(); - table = new javax.swing.JTable(); - addBackupEntryButton = new backupmanager.gui.svg.SVGButton(); - exportAsCsvBtn = new backupmanager.gui.svg.SVGButton(); - ExportLabel = new javax.swing.JLabel(); - panelDashboard = new javax.swing.JPanel(); - chart2 = new javax.swing.JPanel(); - chart1 = new javax.swing.JPanel(); - chart1TitleLabel = new javax.swing.JLabel(); - chart2TitleLabel = new javax.swing.JLabel(); - totalBackupsPanel = new javax.swing.JPanel(); - totalBackupsTitleLabel = new javax.swing.JLabel(); - totalBackupsNumber = new javax.swing.JLabel(); - totalBackupConfigurationsPanel = new javax.swing.JPanel(); - totalBackupConfigurationsTitleLabel = new javax.swing.JLabel(); - totalBackupConfigurationsNumber = new javax.swing.JLabel(); - totalSpaceUsedPanel = new javax.swing.JPanel(); - totalSpaceTitleLabel = new javax.swing.JLabel(); - totalSpaceNumber = new javax.swing.JLabel(); - jMenuBar1 = new javax.swing.JMenuBar(); - jMenu1 = new javax.swing.JMenu(); - MenuNew = new backupmanager.gui.svg.SVGMenuItem(); - MenuSave = new backupmanager.gui.svg.SVGMenuItem(); - MenuSaveWithName = new backupmanager.gui.svg.SVGMenuItem(); - jSeparator4 = new javax.swing.JPopupMenu.Separator(); - MenuImport = new backupmanager.gui.svg.SVGMenuItem(); - MenuExport = new backupmanager.gui.svg.SVGMenuItem(); - jSeparator5 = new javax.swing.JPopupMenu.Separator(); - MenuClear = new backupmanager.gui.svg.SVGMenuItem(); - MenuHistory = new backupmanager.gui.svg.SVGMenuItem(); - jMenu2 = new javax.swing.JMenu(); - MenuPreferences = new backupmanager.gui.svg.SVGMenuItem(); - MenuQuit = new backupmanager.gui.svg.SVGMenuItem(); - jMenu3 = new javax.swing.JMenu(); - MenuWebsite = new backupmanager.gui.svg.SVGMenuItem(); - MenuInfoPage = new backupmanager.gui.svg.SVGMenuItem(); - MenuShare = new backupmanager.gui.svg.SVGMenuItem(); - MenuDonate = new backupmanager.gui.svg.SVGMenu(); - MenuPaypalDonate = new backupmanager.gui.svg.SVGMenuItem(); - MenuBuyMeACoffeDonate = new backupmanager.gui.svg.SVGMenuItem(); - jMenu5 = new javax.swing.JMenu(); - MenuBugReport = new backupmanager.gui.svg.SVGMenuItem(); - MenuSupport = new backupmanager.gui.svg.SVGMenuItem(); - - EditPoputItem.setText("Edit"); - EditPoputItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - EditPoputItemActionPerformed(evt); - } - }); - TablePopup.add(EditPoputItem); - - DeletePopupItem.setText("Delete"); - DeletePopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - DeletePopupItemActionPerformed(evt); - } - }); - TablePopup.add(DeletePopupItem); - - interruptBackupPopupItem.setText("Interrupt"); - interruptBackupPopupItem.setToolTipText(""); - interruptBackupPopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - interruptBackupPopupItemActionPerformed(evt); - } - }); - TablePopup.add(interruptBackupPopupItem); - - DuplicatePopupItem.setText("Duplicate"); - DuplicatePopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - DuplicatePopupItemActionPerformed(evt); - } - }); - TablePopup.add(DuplicatePopupItem); - - renamePopupItem.setText("Rename backup"); - renamePopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - renamePopupItemActionPerformed(evt); - } - }); - TablePopup.add(renamePopupItem); - TablePopup.add(jSeparator1); - - OpenInitialFolderItem.setText("Open initial folder"); - OpenInitialFolderItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - OpenInitialFolderItemActionPerformed(evt); - } - }); - TablePopup.add(OpenInitialFolderItem); - - OpenInitialDestinationItem.setText("Open destination folder"); - OpenInitialDestinationItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - OpenInitialDestinationItemActionPerformed(evt); - } - }); - TablePopup.add(OpenInitialDestinationItem); - TablePopup.add(jSeparator3); - - Backup.setText("Backup"); - - RunBackupPopupItem.setText("Run single backup"); - RunBackupPopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - RunBackupPopupItemActionPerformed(evt); - } - }); - Backup.add(RunBackupPopupItem); - - AutoBackupMenuItem.setSelected(true); - AutoBackupMenuItem.setText("Auto Backup"); - AutoBackupMenuItem.setToolTipText(""); - AutoBackupMenuItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - AutoBackupMenuItemActionPerformed(evt); - } - }); - Backup.add(AutoBackupMenuItem); - - TablePopup.add(Backup); - TablePopup.add(jSeparator2); - - jMenu4.setText("Copy text"); - - CopyBackupNamePopupItem.setText("Copy backup name"); - CopyBackupNamePopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - CopyBackupNamePopupItemActionPerformed(evt); - } - }); - jMenu4.add(CopyBackupNamePopupItem); - - CopyInitialPathPopupItem.setText("Copy initial path"); - CopyInitialPathPopupItem.setToolTipText(""); - CopyInitialPathPopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - CopyInitialPathPopupItemActionPerformed(evt); - } - }); - jMenu4.add(CopyInitialPathPopupItem); - - CopyDestinationPathPopupItem.setText("Copy destination path"); - CopyDestinationPathPopupItem.setToolTipText(""); - CopyDestinationPathPopupItem.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - CopyDestinationPathPopupItemActionPerformed(evt); - } - }); - jMenu4.add(CopyDestinationPathPopupItem); - - TablePopup.add(jMenu4); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setTitle("Backup Manager"); - setMinimumSize(new java.awt.Dimension(750, 450)); - - panelVersion.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5)); - panelVersion.setPreferredSize(new java.awt.Dimension(100, 30)); - - jLabel3.setText("Version 2.0.2"); - jLabel3.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - - javax.swing.GroupLayout panelVersionLayout = new javax.swing.GroupLayout(panelVersion); - panelVersion.setLayout(panelVersionLayout); - panelVersionLayout.setHorizontalGroup( - panelVersionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelVersionLayout.createSequentialGroup() - .addContainerGap() - .addComponent(jLabel3, javax.swing.GroupLayout.PREFERRED_SIZE, 997, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - ); - panelVersionLayout.setVerticalGroup( - panelVersionLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelVersionLayout.createSequentialGroup() - .addComponent(jLabel3) - .addGap(0, 4, Short.MAX_VALUE)) - ); - - getContentPane().add(panelVersion, java.awt.BorderLayout.PAGE_END); - - layeredCardPanel.setLayout(new java.awt.CardLayout()); - - panelBackupList.setBorder(javax.swing.BorderFactory.createEmptyBorder(10, 10, 10, 10)); - panelBackupList.setVerifyInputWhenFocusTarget(false); - - detailsLabel.setVerticalAlignment(javax.swing.SwingConstants.TOP); - - researchField.setToolTipText("Research bar"); - researchField.addKeyListener(new java.awt.event.KeyAdapter() { - public void keyReleased(java.awt.event.KeyEvent evt) { - researchFieldKeyReleased(evt); - } - }); - - exportAsPdfBtn.setToolTipText("Export as .pdf"); - exportAsPdfBtn.setMaximumSize(new java.awt.Dimension(32, 32)); - exportAsPdfBtn.setMinimumSize(new java.awt.Dimension(32, 32)); - exportAsPdfBtn.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - exportAsPdfBtnActionPerformed(evt); - } - }); - - jLabel1.setFont(new java.awt.Font("Segoe UI", 0, 20)); // NOI18N - jLabel1.setText("|"); - jLabel1.setAlignmentY(0.0F); - - table.setModel(new javax.swing.table.DefaultTableModel( - new Object [][] { - - }, - new String [] { - "Backup name", "Initial path", "Destination path", "Last backup", "Auto backup", "Next date backup", "Days interval backup" - } - ) { - Class[] types = new Class [] { - java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.Boolean.class, java.lang.String.class, java.lang.Integer.class - }; - boolean[] canEdit = new boolean [] { - false, false, false, false, false, false, false - }; - - public Class getColumnClass(int columnIndex) { - return types [columnIndex]; - } - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return canEdit [columnIndex]; - } - }); - table.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); - table.setRowHeight(50); - table.addMouseListener(new java.awt.event.MouseAdapter() { - public void mouseClicked(java.awt.event.MouseEvent evt) { - tableMouseClicked(evt); - } - }); - jScrollPane1.setViewportView(table); - - addBackupEntryButton.setToolTipText("Add new backup"); - addBackupEntryButton.setMaximumSize(new java.awt.Dimension(32, 32)); - addBackupEntryButton.setMinimumSize(new java.awt.Dimension(32, 32)); - addBackupEntryButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - addBackupEntryButtonActionPerformed(evt); - } - }); - - exportAsCsvBtn.setToolTipText("Export as .csv"); - exportAsCsvBtn.setMaximumSize(new java.awt.Dimension(32, 32)); - exportAsCsvBtn.setMinimumSize(new java.awt.Dimension(32, 32)); - exportAsCsvBtn.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - exportAsCsvBtnActionPerformed(evt); - } - }); - - ExportLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); - ExportLabel.setText("Export As:"); - - javax.swing.GroupLayout panelBackupListLayout = new javax.swing.GroupLayout(panelBackupList); - panelBackupList.setLayout(panelBackupListLayout); - panelBackupListLayout.setHorizontalGroup( - panelBackupListLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(detailsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(jScrollPane1) - .addGroup(panelBackupListLayout.createSequentialGroup() - .addComponent(addBackupEntryButton, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, 9, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(researchField, javax.swing.GroupLayout.PREFERRED_SIZE, 321, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(ExportLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 554, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(exportAsCsvBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(exportAsPdfBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(12, 12, 12)) - ); - panelBackupListLayout.setVerticalGroup( - panelBackupListLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelBackupListLayout.createSequentialGroup() - .addGroup(panelBackupListLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(ExportLabel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(panelBackupListLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelBackupListLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(researchField, javax.swing.GroupLayout.PREFERRED_SIZE, 34, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(addBackupEntryButton, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(exportAsCsvBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(exportAsPdfBtn, javax.swing.GroupLayout.PREFERRED_SIZE, 32, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(10, 10, 10) - .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 293, Short.MAX_VALUE) - .addGap(12, 12, 12) - .addComponent(detailsLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 96, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 0, 0)) - ); - - researchField.getAccessibleContext().setAccessibleName(""); - - layeredCardPanel.add(panelBackupList, "BackupList"); - - chart2.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED)); - chart2.setPreferredSize(new java.awt.Dimension(230, 180)); - - javax.swing.GroupLayout chart2Layout = new javax.swing.GroupLayout(chart2); - chart2.setLayout(chart2Layout); - chart2Layout.setHorizontalGroup( - chart2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - chart2Layout.setVerticalGroup( - chart2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 174, Short.MAX_VALUE) - ); - - chart1.setBorder(new javax.swing.border.SoftBevelBorder(javax.swing.border.BevelBorder.RAISED)); - chart1.setPreferredSize(new java.awt.Dimension(230, 180)); - - javax.swing.GroupLayout chart1Layout = new javax.swing.GroupLayout(chart1); - chart1.setLayout(chart1Layout); - chart1Layout.setHorizontalGroup( - chart1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 0, Short.MAX_VALUE) - ); - chart1Layout.setVerticalGroup( - chart1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGap(0, 174, Short.MAX_VALUE) - ); - - chart1TitleLabel.setFont(new java.awt.Font("Segoe UI", 0, 18)); // NOI18N - chart1TitleLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - chart1TitleLabel.setText("Title Chart 1"); - - chart2TitleLabel.setFont(new java.awt.Font("Segoe UI", 0, 18)); // NOI18N - chart2TitleLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); - chart2TitleLabel.setText("Title Chart 2"); - - totalBackupsPanel.setBorder(new javax.swing.border.MatteBorder(null)); - - totalBackupsTitleLabel.setFont(new java.awt.Font("Segoe UI", 0, 18)); // NOI18N - totalBackupsTitleLabel.setText("Total Backups"); - - totalBackupsNumber.setFont(new java.awt.Font("Segoe UI", 1, 18)); // NOI18N - totalBackupsNumber.setText("10"); - - javax.swing.GroupLayout totalBackupsPanelLayout = new javax.swing.GroupLayout(totalBackupsPanel); - totalBackupsPanel.setLayout(totalBackupsPanelLayout); - totalBackupsPanelLayout.setHorizontalGroup( - totalBackupsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalBackupsPanelLayout.createSequentialGroup() - .addGap(12, 12, 12) - .addGroup(totalBackupsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(totalBackupsNumber, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(totalBackupsTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE)) - .addContainerGap()) - ); - totalBackupsPanelLayout.setVerticalGroup( - totalBackupsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalBackupsPanelLayout.createSequentialGroup() - .addComponent(totalBackupsTitleLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(totalBackupsNumber, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 31, Short.MAX_VALUE)) - ); - - totalBackupConfigurationsPanel.setBorder(new javax.swing.border.MatteBorder(null)); - totalBackupConfigurationsPanel.setPreferredSize(new java.awt.Dimension(214, 104)); - - totalBackupConfigurationsTitleLabel.setFont(new java.awt.Font("Segoe UI", 0, 18)); // NOI18N - totalBackupConfigurationsTitleLabel.setText("Total Backups"); - - totalBackupConfigurationsNumber.setFont(new java.awt.Font("Segoe UI", 1, 18)); // NOI18N - totalBackupConfigurationsNumber.setText("10"); - - javax.swing.GroupLayout totalBackupConfigurationsPanelLayout = new javax.swing.GroupLayout(totalBackupConfigurationsPanel); - totalBackupConfigurationsPanel.setLayout(totalBackupConfigurationsPanelLayout); - totalBackupConfigurationsPanelLayout.setHorizontalGroup( - totalBackupConfigurationsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalBackupConfigurationsPanelLayout.createSequentialGroup() - .addGap(12, 12, 12) - .addGroup(totalBackupConfigurationsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(totalBackupConfigurationsNumber, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(totalBackupConfigurationsTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE)) - .addContainerGap()) - ); - totalBackupConfigurationsPanelLayout.setVerticalGroup( - totalBackupConfigurationsPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalBackupConfigurationsPanelLayout.createSequentialGroup() - .addComponent(totalBackupConfigurationsTitleLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(totalBackupConfigurationsNumber, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 31, Short.MAX_VALUE)) - ); - - totalSpaceUsedPanel.setBorder(new javax.swing.border.MatteBorder(null)); - totalSpaceUsedPanel.setPreferredSize(new java.awt.Dimension(214, 104)); - - totalSpaceTitleLabel.setFont(new java.awt.Font("Segoe UI", 0, 18)); // NOI18N - totalSpaceTitleLabel.setText("Total Space Used"); - - totalSpaceNumber.setFont(new java.awt.Font("Segoe UI", 1, 18)); // NOI18N - totalSpaceNumber.setText("10"); - - javax.swing.GroupLayout totalSpaceUsedPanelLayout = new javax.swing.GroupLayout(totalSpaceUsedPanel); - totalSpaceUsedPanel.setLayout(totalSpaceUsedPanelLayout); - totalSpaceUsedPanelLayout.setHorizontalGroup( - totalSpaceUsedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalSpaceUsedPanelLayout.createSequentialGroup() - .addGap(12, 12, 12) - .addGroup(totalSpaceUsedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(totalSpaceNumber, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(totalSpaceTitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 194, Short.MAX_VALUE)) - .addContainerGap()) - ); - totalSpaceUsedPanelLayout.setVerticalGroup( - totalSpaceUsedPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(totalSpaceUsedPanelLayout.createSequentialGroup() - .addComponent(totalSpaceTitleLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(totalSpaceNumber, javax.swing.GroupLayout.PREFERRED_SIZE, 25, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(0, 31, Short.MAX_VALUE)) - ); - - javax.swing.GroupLayout panelDashboardLayout = new javax.swing.GroupLayout(panelDashboard); - panelDashboard.setLayout(panelDashboardLayout); - panelDashboardLayout.setHorizontalGroup( - panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelDashboardLayout.createSequentialGroup() - .addGap(52, 52, 52) - .addGroup(panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelDashboardLayout.createSequentialGroup() - .addComponent(totalBackupsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(totalBackupConfigurationsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(totalSpaceUsedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 228, Short.MAX_VALUE) - .addGroup(panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(chart2, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) - .addComponent(chart1, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) - .addComponent(chart2TitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE) - .addComponent(chart1TitleLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(16, 16, 16)) - ); - panelDashboardLayout.setVerticalGroup( - panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, panelDashboardLayout.createSequentialGroup() - .addGap(18, 18, 18) - .addGroup(panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelDashboardLayout.createSequentialGroup() - .addGroup(panelDashboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(panelDashboardLayout.createSequentialGroup() - .addComponent(chart1TitleLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(chart1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(totalBackupConfigurationsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(chart2TitleLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(chart2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(panelDashboardLayout.createSequentialGroup() - .addComponent(totalBackupsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(20, 20, 20) - .addComponent(totalSpaceUsedPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addContainerGap(34, Short.MAX_VALUE)) - ); - - layeredCardPanel.add(panelDashboard, "Dashboard"); - - getContentPane().add(layeredCardPanel, java.awt.BorderLayout.CENTER); - - jMenu1.setText("File"); - - MenuNew.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_N, java.awt.event.InputEvent.CTRL_DOWN_MASK)); - MenuNew.setText("New"); - MenuNew.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuNewActionPerformed(evt); - } - }); - jMenu1.add(MenuNew); - - MenuSave.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_DOWN_MASK)); - MenuSave.setText("Save"); - MenuSave.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuSaveActionPerformed(evt); - } - }); - jMenu1.add(MenuSave); - - MenuSaveWithName.setText("Save with name"); - MenuSaveWithName.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuSaveWithNameActionPerformed(evt); - } - }); - jMenu1.add(MenuSaveWithName); - jMenu1.add(jSeparator4); - - MenuImport.setText("Import backup list"); - jMenu1.add(MenuImport); - - MenuExport.setText("Export backup list"); - jMenu1.add(MenuExport); - jMenu1.add(jSeparator5); - - MenuClear.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_C, java.awt.event.InputEvent.CTRL_DOWN_MASK)); - MenuClear.setText("Clear"); - MenuClear.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuClearActionPerformed(evt); - } - }); - jMenu1.add(MenuClear); - - MenuHistory.setText("History"); - MenuHistory.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuHistoryActionPerformed(evt); - } - }); - jMenu1.add(MenuHistory); - - jMenuBar1.add(jMenu1); - - jMenu2.setText("Options"); - - MenuPreferences.setText("Preferences"); - MenuPreferences.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuPreferencesActionPerformed(evt); - } - }); - jMenu2.add(MenuPreferences); - - MenuQuit.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_F4, java.awt.event.InputEvent.ALT_DOWN_MASK)); - MenuQuit.setText("Quit"); - MenuQuit.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuQuitActionPerformed(evt); - } - }); - jMenu2.add(MenuQuit); - - jMenuBar1.add(jMenu2); - - jMenu3.setText("About"); - - MenuWebsite.setText("Website"); - MenuWebsite.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuWebsiteActionPerformed(evt); - } - }); - jMenu3.add(MenuWebsite); - - MenuInfoPage.setText("Info"); - MenuInfoPage.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuInfoPageActionPerformed(evt); - } - }); - jMenu3.add(MenuInfoPage); - - MenuShare.setText("Share"); - MenuShare.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuShareActionPerformed(evt); - } - }); - jMenu3.add(MenuShare); - - MenuDonate.setText("Donate"); - - MenuPaypalDonate.setText("Paypal"); - MenuPaypalDonate.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuPaypalDonateActionPerformed(evt); - } - }); - MenuDonate.add(MenuPaypalDonate); - - MenuBuyMeACoffeDonate.setText("Buy me a coffe"); - MenuBuyMeACoffeDonate.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuBuyMeACoffeDonateActionPerformed(evt); - } - }); - MenuDonate.add(MenuBuyMeACoffeDonate); - - jMenu3.add(MenuDonate); - - jMenuBar1.add(jMenu3); - - jMenu5.setText("Help"); - - MenuBugReport.setText("Report a bug"); - MenuBugReport.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuBugReportActionPerformed(evt); - } - }); - jMenu5.add(MenuBugReport); - - MenuSupport.setText("Support"); - MenuSupport.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - MenuSupportActionPerformed(evt); - } - }); - jMenu5.add(MenuSupport); - - jMenuBar1.add(jMenu5); - - setJMenuBar(jMenuBar1); - - pack(); - setLocationRelativeTo(null); - }// </editor-fold>//GEN-END:initComponents - - private void MenuQuitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuQuitActionPerformed - BackupMenuController.menuItemQuit(observer, this); - }//GEN-LAST:event_MenuQuitActionPerformed - - private void MenuHistoryActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuHistoryActionPerformed - BackupMenuController.menuItemHistory(); - }//GEN-LAST:event_MenuHistoryActionPerformed - - private void MenuClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuClearActionPerformed - }//GEN-LAST:event_MenuClearActionPerformed - - private void MenuSaveWithNameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuSaveWithNameActionPerformed - }//GEN-LAST:event_MenuSaveWithNameActionPerformed - - private void MenuSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuSaveActionPerformed - }//GEN-LAST:event_MenuSaveActionPerformed - - private void MenuNewActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuNewActionPerformed - BackupMenuController.menuItemNew(progressBar, this); - }//GEN-LAST:event_MenuNewActionPerformed - - private void EditPoputItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_EditPoputItemActionPerformed - BackupPopupController.popupItemEditBackupName(selectedRow, backupTable, backups, this); - }//GEN-LAST:event_EditPoputItemActionPerformed - - private void DeletePopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_DeletePopupItemActionPerformed - BackupPopupController.popupItemDelete(selectedRow, backupTable); - }//GEN-LAST:event_DeletePopupItemActionPerformed - - private void tableMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_tableMouseClicked - selectedRow = table.rowAtPoint(evt.getPoint()); // get index of the row - - if (selectedRow == -1) { // if clicked outside valid rows - table.clearSelection(); // deselect any selected row - detailsLabel.setText(""); // clear the label - } else { - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - logger.debug("Selected backup: " + backupName); - - if (SwingUtilities.isRightMouseButton(evt)) { - logger.debug("Right click on row: " + selectedRow); - table.setRowSelectionInterval(selectedRow, selectedRow); // select clicked row - TablePopup.show(evt.getComponent(), evt.getX(), evt.getY()); - - boolean isRunning = backupManagerController.isBackupRunning(backupName); - - DeletePopupItem.setEnabled(!isRunning); - interruptBackupPopupItem.setEnabled(isRunning); - } - - // Handling left mouse button double-click - else if (SwingUtilities.isLeftMouseButton(evt) && evt.getClickCount() == 2) { - backupManagerController.openBackup(backupName, this); - } - - else if (SwingUtilities.isLeftMouseButton(evt)) { - String details = backupManagerController.getBackupDetails(backupName); - detailsLabel.setText(details); - } - } - }//GEN-LAST:event_tableMouseClicked - - private void DuplicatePopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_DuplicatePopupItemActionPerformed - BackupPopupController.popupItemDuplicateBackup(selectedRow, backupTable); - }//GEN-LAST:event_DuplicatePopupItemActionPerformed - - private void RunBackupPopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_RunBackupPopupItemActionPerformed - BackupPopupController.popupItemRunBackup(selectedRow, backupTable, backups, interruptBackupPopupItem, RunBackupPopupItem); - }//GEN-LAST:event_RunBackupPopupItemActionPerformed - - private void CopyBackupNamePopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CopyBackupNamePopupItemActionPerformed - BackupPopupController.popupItemCopyBackupName(selectedRow, backupTable, backups); - }//GEN-LAST:event_CopyBackupNamePopupItemActionPerformed - - private void CopyInitialPathPopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CopyInitialPathPopupItemActionPerformed - BackupPopupController.popupItemCopyInitialPath(selectedRow, backupTable, backups); - }//GEN-LAST:event_CopyInitialPathPopupItemActionPerformed - - private void CopyDestinationPathPopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_CopyDestinationPathPopupItemActionPerformed - BackupPopupController.popupItemCopyDestinationPath(selectedRow, backupTable, backups); - }//GEN-LAST:event_CopyDestinationPathPopupItemActionPerformed - - private void AutoBackupMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_AutoBackupMenuItemActionPerformed - BackupPopupController.popupItemAutoBackup(selectedRow, backupTable, backups, AutoBackupMenuItem); - }//GEN-LAST:event_AutoBackupMenuItemActionPerformed - - private void OpenInitialFolderItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_OpenInitialFolderItemActionPerformed - BackupPopupController.popupItemOpenInitialPath(selectedRow, backupTable, backups); - }//GEN-LAST:event_OpenInitialFolderItemActionPerformed - - private void OpenInitialDestinationItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_OpenInitialDestinationItemActionPerformed - BackupPopupController.popupItemOpenDestinationPath(selectedRow, backupTable, backups); - }//GEN-LAST:event_OpenInitialDestinationItemActionPerformed - - private void renamePopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_renamePopupItemActionPerformed - BackupPopupController.popupItemRenameBackup(selectedRow, backupTable, backups); - }//GEN-LAST:event_renamePopupItemActionPerformed - - private void MenuPaypalDonateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuPaypalDonateActionPerformed - BackupMenuController.menuItemDonateViaPaypal(); - }//GEN-LAST:event_MenuPaypalDonateActionPerformed - - private void MenuBugReportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuBugReportActionPerformed - BackupMenuController.menuItemBugReport(); - }//GEN-LAST:event_MenuBugReportActionPerformed - - private void MenuShareActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuShareActionPerformed - BackupMenuController.menuItemShare(); - }//GEN-LAST:event_MenuShareActionPerformed - - private void MenuWebsiteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuWebsiteActionPerformed - BackupMenuController.menuItemWebsite(); - }//GEN-LAST:event_MenuWebsiteActionPerformed - - private void MenuSupportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuSupportActionPerformed - BackupMenuController.menuItemSupport(); - }//GEN-LAST:event_MenuSupportActionPerformed - - private void MenuInfoPageActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuInfoPageActionPerformed - BackupMenuController.menuItemInfoPage(); - }//GEN-LAST:event_MenuInfoPageActionPerformed - - private void MenuPreferencesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuPreferencesActionPerformed - BackupMenuController.menuItemOpenPreferences(this); - }//GEN-LAST:event_MenuPreferencesActionPerformed - - private void interruptBackupPopupItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_interruptBackupPopupItemActionPerformed - BackupPopupController.popupItemInterrupt(selectedRow, backupTable, backups, interruptBackupPopupItem, RunBackupPopupItem); - }//GEN-LAST:event_interruptBackupPopupItemActionPerformed - - private void exportAsCsvBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportAsCsvBtnActionPerformed - ExportManager.exportAsCSV(backups, ConfigurationBackup.getCSVHeader()); - }//GEN-LAST:event_exportAsCsvBtnActionPerformed - - private void exportAsPdfBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportAsPdfBtnActionPerformed - ExportManager.exportAsPDF(new ArrayList<>(backups), ConfigurationBackup.getCSVHeader()); - }//GEN-LAST:event_exportAsPdfBtnActionPerformed - - private void addBackupEntryButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addBackupEntryButtonActionPerformed - BackupHelper.newBackup(progressBar, this); - }//GEN-LAST:event_addBackupEntryButtonActionPerformed - - private void MenuBuyMeACoffeDonateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_MenuBuyMeACoffeDonateActionPerformed - BackupMenuController.menuItemDonateViaBuymeacoffe(); - }//GEN-LAST:event_MenuBuyMeACoffeDonateActionPerformed - - private void researchFieldKeyReleased(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_researchFieldKeyReleased - String research = researchField.getText(); - }//GEN-LAST:event_researchFieldKeyReleased - - private void setTranslations() { - // update table translations - if (backups != null) - displayBackupList(); - - // general - jLabel3.setText(TCategory.GENERAL.getTranslation(TKey.VERSION) + " " + ConfigKey.VERSION.getValue()); - - // menu - jMenu1.setText(TCategory.MENU.getTranslation(TKey.FILE)); - jMenu2.setText(TCategory.MENU.getTranslation(TKey.OPTIONS)); - jMenu3.setText(TCategory.MENU.getTranslation(TKey.ABOUT)); - jMenu5.setText(TCategory.MENU.getTranslation(TKey.HELP)); - - // menu items - MenuBugReport.setText(TCategory.MENU.getTranslation(TKey.BUG_REPORT)); - MenuClear.setText(TCategory.MENU.getTranslation(TKey.CLEAR)); - MenuDonate.setText(TCategory.MENU.getTranslation(TKey.DONATE)); - MenuHistory.setText(TCategory.MENU.getTranslation(TKey.HISTORY)); - MenuInfoPage.setText(TCategory.MENU.getTranslation(TKey.INFO_PAGE)); - MenuNew.setText(TCategory.MENU.getTranslation(TKey.NEW)); - MenuQuit.setText(TCategory.MENU.getTranslation(TKey.QUIT)); - MenuSave.setText(TCategory.MENU.getTranslation(TKey.SAVE)); - MenuSaveWithName.setText(TCategory.MENU.getTranslation(TKey.SAVE_WITH_NAME)); - MenuPreferences.setText(TCategory.MENU.getTranslation(TKey.PREFERENCES)); - MenuImport.setText(TCategory.MENU.getTranslation(TKey.IMPORT)); - MenuExport.setText(TCategory.MENU.getTranslation(TKey.EXPORT)); - MenuShare.setText(TCategory.MENU.getTranslation(TKey.SHARE)); - MenuSupport.setText(TCategory.MENU.getTranslation(TKey.SUPPORT)); - MenuWebsite.setText(TCategory.MENU.getTranslation(TKey.WEBSITE)); - - // backup list - ExportLabel.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS)); - addBackupEntryButton.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.ADD_BACKUP_TOOLTIP)); - exportAsPdfBtn.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS_PDF_TOOLTIP)); - exportAsCsvBtn.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.EXPORT_AS_CSV_TOOLTIP)); - researchField.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_TOOLTIP)); - researchField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_PLACEHOLDER)); - - // popup - CopyBackupNamePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_BACKUP_NAME_POPUP)); - CopyDestinationPathPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); - RunBackupPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.SINGLE_BACKUP_POPUP)); - CopyInitialPathPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_INITIAL_PATH_POPUP)); - DeletePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DELETE_POPUP)); - interruptBackupPopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.INTERRUPT_POPUP)); - DuplicatePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DUPLICATE_POPUP)); - EditPoputItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EDIT_POPUP)); - OpenInitialDestinationItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_DESTINATION_FOLDER_POPUP)); - OpenInitialFolderItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_INITIAL_FOLDER_POPUP)); - renamePopupItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.RENAME_BACKUP_POPUP)); - jMenu4.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_TEXT_POPUP)); - AutoBackupMenuItem.setText(TCategory.BACKUP_LIST.getTranslation(TKey.AUTO_BACKUP_POPUP)); - Backup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_POPUP)); - } - - private String[] getColumnTranslations() { - String[] columnNames = { - TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_NAME_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.INITIAL_PATH_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.DESTINATION_PATH_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.LAST_BACKUP_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.AUTOMATIC_BACKUP_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.NEXT_BACKUP_DATE_COLUMN), - TCategory.BACKUP_LIST.getTranslation(TKey.TIME_INTERVAL_COLUMN) - }; - return columnNames; - } - - private void initializeTable() { - backups = BackupConfigurationRepository.getBackupList(); - displayBackupList(); - } - - private void setSvgImages() { - exportAsCsvBtn.setSvgImage("res/img/csv.svg", 30, 30); - exportAsPdfBtn.setSvgImage("res/img/pdf.svg", 30, 30); - addBackupEntryButton.setSvgImage("res/img/add.svg", 30, 30); - MenuSave.setSvgImage("res/img/save.svg", 16, 16); - MenuSaveWithName.setSvgImage("res/img/save_as.svg", 16, 16); - MenuImport.setSvgImage("res/img/import.svg", 16, 16); - MenuExport.setSvgImage("res/img/export.svg", 16, 16); - MenuNew.setSvgImage("res/img/new_file.svg", 16, 16); - MenuBugReport.setSvgImage("res/img/bug.svg", 16, 16); - MenuClear.setSvgImage("res/img/clear.svg", 16, 16); - MenuHistory.setSvgImage("res/img/history.svg", 16, 16); - MenuDonate.setSvgImage("res/img/donate.svg", 16, 16); - MenuPaypalDonate.setSvgImage("res/img/paypal.svg", 16, 16); - MenuBuyMeACoffeDonate.setSvgImage("res/img/buymeacoffee.svg", 16, 16); - MenuPreferences.setSvgImage("res/img/settings.svg", 16, 16); - MenuShare.setSvgImage("res/img/share.svg", 16, 16); - MenuSupport.setSvgImage("res/img/support.svg", 16, 16); - MenuWebsite.setSvgImage("res/img/website.svg", 16, 16); - MenuQuit.setSvgImage("res/img/quit.svg", 16, 16); - MenuInfoPage.setSvgImage("res/img/info.svg", 16, 16); - } - - private void initializeMenuItems() { - JsonConfig config = JsonConfig.getInstance(); - MenuBugReport.setVisible(config.isMenuItemEnabled(MenuItems.BugReport.name())); - MenuPreferences.setVisible(config.isMenuItemEnabled(MenuItems.Preferences.name())); - MenuClear.setVisible(config.isMenuItemEnabled(MenuItems.Clear.name())); - MenuDonate.setVisible(config.isMenuItemEnabled(MenuItems.Donate.name())); - MenuPaypalDonate.setVisible(config.isMenuItemEnabled(MenuItems.PaypalDonate.name())); - MenuBuyMeACoffeDonate.setVisible(config.isMenuItemEnabled(MenuItems.BuymeacoffeeDonate.name())); - MenuHistory.setVisible(config.isMenuItemEnabled(MenuItems.History.name())); - MenuInfoPage.setVisible(config.isMenuItemEnabled(MenuItems.InfoPage.name())); - MenuNew.setVisible(config.isMenuItemEnabled(MenuItems.New.name())); - MenuQuit.setVisible(config.isMenuItemEnabled(MenuItems.Quit.name())); - MenuSave.setVisible(config.isMenuItemEnabled(MenuItems.Save.name())); - MenuSaveWithName.setVisible(config.isMenuItemEnabled(MenuItems.SaveWithName.name())); - MenuShare.setVisible(config.isMenuItemEnabled(MenuItems.Share.name())); - MenuSupport.setVisible(config.isMenuItemEnabled(MenuItems.Support.name())); - MenuWebsite.setVisible(config.isMenuItemEnabled(MenuItems.Website.name())); - MenuImport.setVisible(config.isMenuItemEnabled(MenuItems.Import.name())); - MenuExport.setVisible(config.isMenuItemEnabled(MenuItems.Export.name())); - } - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JCheckBoxMenuItem AutoBackupMenuItem; - private javax.swing.JMenu Backup; - private javax.swing.JMenuItem CopyBackupNamePopupItem; - private javax.swing.JMenuItem CopyDestinationPathPopupItem; - private javax.swing.JMenuItem CopyInitialPathPopupItem; - private javax.swing.JMenuItem DeletePopupItem; - private javax.swing.JMenuItem DuplicatePopupItem; - private javax.swing.JMenuItem EditPoputItem; - private javax.swing.JLabel ExportLabel; - private backupmanager.gui.svg.SVGMenuItem MenuBugReport; - private backupmanager.gui.svg.SVGMenuItem MenuBuyMeACoffeDonate; - private backupmanager.gui.svg.SVGMenuItem MenuClear; - private backupmanager.gui.svg.SVGMenu MenuDonate; - private backupmanager.gui.svg.SVGMenuItem MenuExport; - private backupmanager.gui.svg.SVGMenuItem MenuHistory; - private backupmanager.gui.svg.SVGMenuItem MenuImport; - private backupmanager.gui.svg.SVGMenuItem MenuInfoPage; - private backupmanager.gui.svg.SVGMenuItem MenuNew; - private backupmanager.gui.svg.SVGMenuItem MenuPaypalDonate; - private backupmanager.gui.svg.SVGMenuItem MenuPreferences; - private backupmanager.gui.svg.SVGMenuItem MenuQuit; - private backupmanager.gui.svg.SVGMenuItem MenuSave; - private backupmanager.gui.svg.SVGMenuItem MenuSaveWithName; - private backupmanager.gui.svg.SVGMenuItem MenuShare; - private backupmanager.gui.svg.SVGMenuItem MenuSupport; - private backupmanager.gui.svg.SVGMenuItem MenuWebsite; - private javax.swing.JMenuItem OpenInitialDestinationItem; - private javax.swing.JMenuItem OpenInitialFolderItem; - private javax.swing.JMenuItem RunBackupPopupItem; - private javax.swing.JPopupMenu TablePopup; - private backupmanager.gui.svg.SVGButton addBackupEntryButton; - private javax.swing.JPanel chart1; - private javax.swing.JLabel chart1TitleLabel; - private javax.swing.JPanel chart2; - private javax.swing.JLabel chart2TitleLabel; - private javax.swing.JLabel detailsLabel; - private backupmanager.gui.svg.SVGButton exportAsCsvBtn; - private backupmanager.gui.svg.SVGButton exportAsPdfBtn; - private javax.swing.JMenuItem interruptBackupPopupItem; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel3; - private javax.swing.JMenu jMenu1; - private javax.swing.JMenu jMenu2; - private javax.swing.JMenu jMenu3; - private javax.swing.JMenu jMenu4; - private javax.swing.JMenu jMenu5; - private javax.swing.JMenuBar jMenuBar1; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JPopupMenu.Separator jSeparator1; - private javax.swing.JPopupMenu.Separator jSeparator2; - private javax.swing.JPopupMenu.Separator jSeparator3; - private javax.swing.JPopupMenu.Separator jSeparator4; - private javax.swing.JPopupMenu.Separator jSeparator5; - private javax.swing.JLayeredPane layeredCardPanel; - private javax.swing.JPanel panelBackupList; - private javax.swing.JPanel panelDashboard; - private javax.swing.JPanel panelVersion; - private javax.swing.JMenuItem renamePopupItem; - private javax.swing.JTextField researchField; - private javax.swing.JTable table; - private javax.swing.JLabel totalBackupConfigurationsNumber; - private javax.swing.JPanel totalBackupConfigurationsPanel; - private javax.swing.JLabel totalBackupConfigurationsTitleLabel; - private javax.swing.JLabel totalBackupsNumber; - private javax.swing.JPanel totalBackupsPanel; - private javax.swing.JLabel totalBackupsTitleLabel; - private javax.swing.JLabel totalSpaceNumber; - private javax.swing.JLabel totalSpaceTitleLabel; - private javax.swing.JPanel totalSpaceUsedPanel; - // End of variables declaration//GEN-END:variables -} diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index c2b5b965..f1d5ab8d 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -4,30 +4,18 @@ import java.awt.Dimension; import java.awt.Toolkit; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Locale; - -import javax.swing.JOptionPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import backupmanager.Email.EmailSender; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Entities.Configurations; -import backupmanager.Entities.User; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.Translations.TCategory; import backupmanager.Enums.Translations.TKey; -import backupmanager.Helpers.BackupHelper; import backupmanager.Services.BackupService; -import backupmanager.database.Repositories.UserRepository; -import backupmanager.gui.Dialogs.EntryUserDialog; -import backupmanager.gui.Table.BackupTable; +import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.forms.CustomForm; -import backupmanager.gui.frames.BackupManagerGUI; import backupmanager.gui.simple.BackupEntryDialog; import raven.modal.ModalDialog; import raven.modal.component.SimpleModalBorder; @@ -38,22 +26,11 @@ public class BackupManagerController { private static final Logger logger = LoggerFactory.getLogger(BackupManagerController.class); private final BackupService backupService; + private final BackupTableDataService backupTable; - public BackupManagerController(BackupService backupService) { + public BackupManagerController(BackupService backupService, BackupTableDataService backupTable) { this.backupService = backupService; - } - - @Deprecated - public void checkForFirstAccess(BackupManagerGUI mainGui) { - logger.debug("Checking for first access"); - User user = UserRepository.getLastUser(); - - if (user == null) { - setLanguageBasedOnPcLanguage(mainGui); - createNewUser(mainGui); - } else { - logger.info("Current user: " + user.toString()); - } + this.backupTable = backupTable; } public int[] getScreenSize() { @@ -90,7 +67,7 @@ public void showCreateModal(Component parent) { ModalDialog.showModal(parent, new SimpleModalBorder( - new BackupEntryDialog(), + new BackupEntryDialog(backupTable), TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_SUBTITLE_CREATE), SimpleModalBorder.OK_CANCEL_OPTION, (controller, action) -> {} @@ -99,7 +76,7 @@ public void showCreateModal(Component parent) { } public void showEditModal(CustomForm form, ConfigurationBackup backup) { - BackupEntryDialog dialog = new BackupEntryDialog(backup); + BackupEntryDialog dialog = new BackupEntryDialog(backupTable, backup); Option option = ModalDialog.createOption(); option.getLayoutOption() @@ -122,102 +99,13 @@ public void showEditModal(CustomForm form, ConfigurationBackup backup) { ), option ); - - } - - public String getBackupDetails(String backupName) { - return backupService.getBackupDetails(backupName); - } - - public void deleteBackups(List<String> names) { - backupService.deleteBackups(names); - } - - public void deleteBackup(ConfigurationBackup backup) { - backupService.deleteBackup(backup.getId()); - } - - public boolean isBackupRunning(String name) { - return backupService.isRunning(name); - } - - public boolean isAutomaticBackup(List<ConfigurationBackup> backups, String backupName) { - ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backups, backupName); - return backup != null && backup.isAutomatic(); - } - - public void openBackup(String name, java.awt.Frame frame) { - logger.info("Double-click on row: " + name); - BackupHelper.openBackupByName(name, frame); - } - - public void handleEnterKeyPressOnTable(java.awt.Frame frame, BackupTable backupTable) { - int selectedRow = backupTable.getSelectedRow(); - if (selectedRow == -1) return; - - logger.debug("Enter key pressed on row: " + selectedRow); - BackupHelper.openBackupByName((String) backupTable.getValueAt(selectedRow, 0), frame); - - BackupHelper.openBackupEntryDialog(frame); - } - - public void handleDeleteKeyPressOnTable(BackupTable backupTable) { - int[] selectedRows = backupTable.getSelectedRows(); - if (selectedRows.length == 0) - return; - - logger.debug("Delete key pressed on rows: " + Arrays.toString(selectedRows)); - - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_DELETION_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_DELETION_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (response != JOptionPane.YES_OPTION) - return; - - for (int row : selectedRows) - BackupHelper.deleteBackup(row, backupTable, false); - } - - @Deprecated - private void createNewUser(BackupManagerGUI mainGui) { - User newUser = null; - - while (newUser == null) { - newUser = openUserDialogAndObtainTheResult(mainGui); - } - - UserRepository.insertUser(newUser); - - sendRegistrationEmail(newUser); - } - - @Deprecated - private void setLanguageBasedOnPcLanguage(BackupManagerGUI mainGui) { - Locale defaultLocale = Locale.getDefault(); - String language = defaultLocale.getLanguage(); - - logger.info("Setting default language to: " + language); - - switch (language) { - case "en" -> Configurations.setLanguage(LanguagesEnum.ENG); - case "it" -> Configurations.setLanguage(LanguagesEnum.ITA); - case "es" -> Configurations.setLanguage(LanguagesEnum.ESP); - case "de" -> Configurations.setLanguage(LanguagesEnum.DEU); - case "fr" -> Configurations.setLanguage(LanguagesEnum.FRA); - default -> Configurations.setLanguage(LanguagesEnum.ENG); - } - - mainGui.reloadPreferences(); } - @Deprecated - private User openUserDialogAndObtainTheResult(BackupManagerGUI mainGui) { - EntryUserDialog userDialog = new EntryUserDialog(mainGui, true); - userDialog.setVisible(true); - return userDialog.getUser(); + public List<ConfigurationBackup> getAllBackups() { + return backupService.getAllBackups(); } - @Deprecated - private void sendRegistrationEmail(User user) { - EmailSender.sendUserCreationEmail(user); - EmailSender.sendConfirmEmailToUser(user); + public String buildDetails(ConfigurationBackup backup) { + return backupService.buildDetails(backup); } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java deleted file mode 100644 index b80fa2a8..00000000 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupMenuController.java +++ /dev/null @@ -1,95 +0,0 @@ -package backupmanager.gui.frames.Controllers; - -import java.awt.Toolkit; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.StringSelection; -import java.io.IOException; - -import javax.swing.JOptionPane; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import backupmanager.gui.Dialogs.PreferencesDialog; -import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations.TCategory; -import backupmanager.Enums.Translations.TKey; -import backupmanager.Helpers.BackupHelper; -import backupmanager.Managers.WebsiteManager; -import backupmanager.Services.BackupObserver; -import backupmanager.gui.frames.BackupManagerGUI; -import backupmanager.gui.frames.BackupProgressGUI; - -public class BackupMenuController { - - private static final Logger logger = LoggerFactory.getLogger(BackupMenuController.class); - - public static void menuItemDonateViaBuymeacoffe() { - logger.info("Event --> buymeacoffe donation"); - WebsiteManager.openWebSite(ConfigKey.DONATE_BUYMEACOFFE_LINK.getValue()); - } - - public static void menuItemDonateViaPaypal() { - logger.info("Event --> paypal donation"); - WebsiteManager.openWebSite(ConfigKey.DONATE_PAYPAL_LINK.getValue()); - } - - public static void menuItemOpenPreferences(BackupManagerGUI main) { - logger.info("Event --> opening preferences dialog"); - PreferencesDialog prefs = new PreferencesDialog(main, true, main); - prefs.setVisible(true); - } - - public static void menuItemInfoPage() { - logger.info("Event --> shard website"); - WebsiteManager.openWebSite(ConfigKey.INFO_PAGE_LINK.getValue()); - } - - public static void menuItemSupport() { - logger.info("Event --> support"); - WebsiteManager.sendEmail(); - } - - public static void menuItemWebsite() { - logger.info("Event --> shard website"); - WebsiteManager.openWebSite(ConfigKey.SHARD_WEBSITE.getValue()); - } - - public static void menuItemShare() { - logger.info("Event --> share"); - - // pop-up message - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SHARE_LINK_COPIED_MESSAGE)); - - // copy link to the clipboard - StringSelection stringSelectionObj = new StringSelection(ConfigKey.SHARE_LINK.getValue()); - Clipboard clipboardObj = Toolkit.getDefaultToolkit().getSystemClipboard(); - clipboardObj.setContents(stringSelectionObj, null); - } - - public static void menuItemBugReport() { - logger.info("Event --> bug report"); - WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue()); - } - - public static void menuItemNew(BackupProgressGUI progressBar, BackupManagerGUI main) { - BackupHelper.newBackup(progressBar, main); - } - - public static void menuItemHistory() { - logger.info("Event --> history"); - try { - logger.debug("Opening log file with path: " + ConfigKey.LOG_DIRECTORY_STRING.getValue() + ConfigKey.LOG_FILE_STRING.getValue()); - new ProcessBuilder("notepad.exe", ConfigKey.LOG_DIRECTORY_STRING.getValue() + ConfigKey.LOG_FILE_STRING.getValue()).start(); - } catch (IOException e) { - logger.error("Error opening history file: " + e.getMessage(), e); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_OPEN_HISTORY_FILE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); - } - } - - public static void menuItemQuit(BackupObserver observer, BackupManagerGUI main) { - logger.info("Event --> exit"); - observer.stop(); - System.exit(0); - } -} diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index 222ce294..f40072d8 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -6,20 +6,19 @@ import java.io.File; import java.io.IOException; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; -import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -import javax.swing.JTable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; +import backupmanager.Entities.BackupExecutionContext; +import backupmanager.Entities.BackupUIContext; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; @@ -28,88 +27,42 @@ import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; import backupmanager.database.Repositories.BackupConfigurationRepository; -import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.TableDataManager; -import backupmanager.gui.frames.BackupManagerGUI; +import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; public class BackupPopupController { private static final Logger logger = LoggerFactory.getLogger(BackupPopupController.class); - @Deprecated - public static void popupItemInterrupt(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - ZippingContext context = ZippingContext.create(backup, null, backupTable, BackupManagerGUI.progressBar, interruptBackupPopupItem, RunBackupPopupItem); - BackupOperations.interruptBackupProcess(context); - } - } - public static void popupItemInterrupt() { } - @Deprecated - public static void popupItemRenameBackup(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - renameBackup(backups, backup); - } - } - public static void popupItemRenameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { renameBackup(backups, backup); } - @Deprecated - public static void popupItemOpenDestinationPath(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - openFolder(backup.getDestinationPath()); - } - } - public static void popupItemOpenDestinationPath(ConfigurationBackup backup) { openFolder(backup.getDestinationPath()); } - @Deprecated - public static void popupItemOpenInitialPath(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - openFolder(backup.getTargetPath()); - } - } - public static void popupItemOpenInitialPath(ConfigurationBackup backup) { openFolder(backup.getTargetPath()); } - @Deprecated - public static void popupItemAutoBackup(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups, JCheckBoxMenuItem autoBackupMenuItem) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - autoBackupMenuItem.setSelected(!backup.isAutomatic()); - BackupHelper.toggleAutomaticBackup(backup); - } - } - public static void popupItemAutoBackup(ConfigurationBackup backup) { BackupHelper.toggleAutomaticBackup(backup); } - @Deprecated - public static void popupItemCopyDestinationPath(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); + public static void popupItemRunBackup(ConfigurationBackup backup, BackupTableDataService backupTable, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { + BackupProgressGUI progressBar = new BackupProgressGUI(backup.getTargetPath(), backup.getDestinationPath()); - StringSelection selection = new StringSelection(backup.getDestinationPath()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); - } + ZippingContext context = new ZippingContext( + BackupExecutionContext.create(backup), + new BackupUIContext(null, backupTable, progressBar, interruptBackupPopupItem, RunBackupPopupItem) + ); + + BackupOperations.requestSingleBackup(context, BackupTriggerType.USER); } public static void popupItemCopyDestinationPath(ConfigurationBackup backup) { @@ -117,101 +70,18 @@ public static void popupItemCopyDestinationPath(ConfigurationBackup backup) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } - @Deprecated - public static void popupItemCopyInitialPath(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - StringSelection selection = new StringSelection(backup.getTargetPath()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); - } - } - public static void popupItemCopyInitialPath(ConfigurationBackup backup) { StringSelection selection = new StringSelection(backup.getTargetPath()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } - @Deprecated - public static void popupItemCopyBackupName(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - StringSelection selection = new StringSelection(backup.getName()); - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); - } - } - public static void popupItemCopyBackupName(ConfigurationBackup backup) { StringSelection selection = new StringSelection(backup.getName()); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } - - @Deprecated - public static void popupItemRunBackup(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(backups, selectedRow, backupTable); - - BackupManagerGUI.progressBar = new BackupProgressGUI(backup.getTargetPath(), backup.getDestinationPath()); - - ZippingContext context = ZippingContext.create(backup, null, backupTable, BackupManagerGUI.progressBar, interruptBackupPopupItem, RunBackupPopupItem); - BackupOperations.singleBackup(context, BackupTriggerType.USER); - } - } - - public static void popupItemRunBackup(ConfigurationBackup backup, JTable backupTable, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { - BackupManagerGUI.progressBar = new BackupProgressGUI(backup.getTargetPath(), backup.getDestinationPath()); - ZippingContext context = ZippingContext.create(backup, null, backupTable, BackupManagerGUI.progressBar, interruptBackupPopupItem, RunBackupPopupItem); - BackupOperations.singleBackup(context, BackupTriggerType.USER); - } - - @Deprecated - public static void popupItemEditBackupName(int selectedRow, BackupTable backupTable, List<ConfigurationBackup> backups, BackupManagerGUI main) { - if (selectedRow != -1) { - // get correct backup - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - ConfigurationBackup backup = ConfigurationBackup.getBackupByName(new ArrayList<>(backups), backupName); - - logger.info("Edit row : " + selectedRow); - BackupHelper.openBackupById(backup.getId(), main); - } - } - - public static void popupItemEditBackupName(ConfigurationBackup backup) { - // BackupHelper.openBackupById(backup.getId(), main); - } - - @Deprecated - public static void popupItemDuplicateBackup(int selectedRow, BackupTable backupTable) { - logger.info("Event --> duplicating backup"); - - if (selectedRow != -1) { - ConfigurationBackup backup = getBackupByName(selectedRow, backupTable); - - LocalDateTime dateNow = LocalDateTime.now(); - ConfigurationBackup newBackup = new ConfigurationBackup( - backup.getName() + "_copy", - backup.getTargetPath(), - backup.getDestinationPath(), - null, - backup.isAutomatic(), - backup.getNextBackupDate(), - backup.getTimeIntervalBackup(), - backup.getNotes(), - dateNow, - dateNow, - 0, - backup.getMaxToKeep() - ); - - BackupConfigurationRepository.insertBackup(newBackup); - - List<ConfigurationBackup> backups = BackupHelper.getBackupList(); - - if (BackupManagerGUI.model != null) - TableDataManager.updateTableWithNewBackupList(backups, BackupHelper.formatter); - } + public static void popupItemEditBackupName(BackupTableDataService backupTable, ConfigurationBackup backup) { + BackupHelper.openBackupById(backupTable, backup.getId()); } public static void popupItemDuplicateBackup(ConfigurationBackup backup) { @@ -234,28 +104,6 @@ public static void popupItemDuplicateBackup(ConfigurationBackup backup) { ); BackupConfigurationRepository.insertBackup(newBackup); - - // List<ConfigurationBackup> backups = BackupHelper.getBackupList(); - - // if (BackupManagerGUI.model != null) - // TableDataManager.updateTableWithNewBackupList(backups, BackupHelper.formatter); - } - - @Deprecated - public static void popupItemDelete(int selectedRow, BackupTable backupTable) { - BackupHelper.deleteBackup(selectedRow, backupTable); - } - - @Deprecated - private static ConfigurationBackup getBackupByName(int selectedRow, BackupTable backupTable) { - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - return BackupConfigurationRepository.getBackupByName(backupName); - } - - @Deprecated - private static ConfigurationBackup getBackupByName(List<ConfigurationBackup> backups, int selectedRow, BackupTable backupTable) { - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - return ConfigurationBackup.getBackupByName(backups, backupName); } private static void renameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index 1a1e8e04..067db20e 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -26,6 +26,7 @@ import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Helpers.BackupHelper; import backupmanager.gui.Controllers.BackupEntryController; +import backupmanager.gui.Table.BackupTableDataService; import net.miginfocom.swing.MigLayout; import raven.modal.ModalDialog; import raven.modal.component.ModalBorderAction; @@ -37,13 +38,15 @@ public class BackupEntryDialog extends CustomDialog<ConfigurationBackup> { private static final Logger logger = LoggerFactory.getLogger(BackupHelper.class); private final BackupEntryController entryController; + private final BackupTableDataService backupTable; private final boolean create; private String backupOnText; private String backupOffText; - public BackupEntryDialog() { + public BackupEntryDialog(BackupTableDataService backupTable) { entryController = new BackupEntryController(null); + this.backupTable = backupTable; create = true; build(); @@ -51,8 +54,9 @@ public BackupEntryDialog() { setAutoBackupOff(); } - public BackupEntryDialog(ConfigurationBackup currentBackup) { + public BackupEntryDialog(BackupTableDataService backupTable, ConfigurationBackup currentBackup) { entryController = new BackupEntryController(currentBackup); + this.backupTable = backupTable; create = false; build(); @@ -189,7 +193,7 @@ private void updateCurrentFiedsByBackup(ConfigurationBackup backup) { private void executeBackup() { try { entryController.handleSingleBackupRequest( - null, + backupTable, txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), From a7d301fd66bdfbc67a43883a1d15f04a2d44be04 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Sun, 5 Apr 2026 18:22:06 +0200 Subject: [PATCH 07/17] some translations --- .../java/backupmanager/BackupOperations.java | 2 +- .../Entities/ConfigurationBackup.java | 15 - .../backupmanager/Enums/Translations.java | 25 +- .../backupmanager/Helpers/BackupHelper.java | 28 +- src/main/java/backupmanager/MainApp.java | 6 +- .../backupmanager/Managers/ExportManager.java | 96 -- .../Services/BackupAnalyticsService.java | 66 +- .../backupmanager/Services/BackupService.java | 4 + ...moPreferences.java => AppPreferences.java} | 8 +- .../gui/Controllers/SettingsController.java | 7 + .../gui/Controllers/TimePickerController.java | 31 +- .../backupmanager/gui/component/About.java | 27 +- .../gui/component/FormSearchButton.java | 4 +- .../gui/component/FormSearchPanel.java | 10 +- .../gui/component/chart/BarChart.java | 31 +- .../gui/component/chart/TimeSeriesChart.java | 5 +- .../gui/forms/FormBackupDashboard.java | 103 +- .../gui/forms/FormBackupTable.java | 49 +- .../backupmanager/gui/forms/FormSetting.java | 31 +- .../Controllers/BackupManagerController.java | 5 +- .../Controllers/BackupPopupController.java | 29 +- .../gui/menu/MyDrawerBuilder.java | 3 +- .../gui/simple/BackupEntryDialog.java | 6 + .../gui/simple/TimePickerDialog.java | 2 +- .../java/backupmanager/gui/system/Form.java | 14 +- .../backupmanager/gui/system/FormManager.java | 2 +- .../backupmanager/gui/system/MainForm.java | 6 +- .../backupmanager/gui/themes/PanelThemes.java | 12 +- src/main/resources/data/customers-1000.csv | 1001 ----------------- src/main/resources/db/002_seed.sql | 2 - src/main/resources/themes/FlatLaf.properties | 18 +- 31 files changed, 255 insertions(+), 1393 deletions(-) rename src/main/java/backupmanager/Utils/{DemoPreferences.java => AppPreferences.java} (97%) create mode 100644 src/main/java/backupmanager/gui/Controllers/SettingsController.java delete mode 100644 src/main/resources/data/customers-1000.csv diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 5aa1c015..cc08836d 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -331,7 +331,7 @@ private static boolean deletePartialBackup(String filePath) { } return false; -} + } public static void setError(ErrorType error, TrayIcon trayIcon, String backupName) { switch (error) { diff --git a/src/main/java/backupmanager/Entities/ConfigurationBackup.java b/src/main/java/backupmanager/Entities/ConfigurationBackup.java index 1c2e6827..9371c04a 100644 --- a/src/main/java/backupmanager/Entities/ConfigurationBackup.java +++ b/src/main/java/backupmanager/Entities/ConfigurationBackup.java @@ -92,7 +92,6 @@ public final void updateBackup(ConfigurationBackup backupUpdated) { this.maxToKeep = backupUpdated.getMaxToKeep(); } - @Override public String toString() { return String.format("[Id: %d, Name: %s, targetPath: %s, DestinationPath: %s, lastBackupDate: %s, IsAutoBackup: %s, NextDate: %s, Interval: %s, MaxBackupsToKeep: %d]", id, @@ -107,7 +106,6 @@ public String toString() { ); } - @Deprecated public String toCsvString() { return String.format("%s,%s,%s,%s,%s,%s,%s,%d", name, @@ -121,19 +119,6 @@ public String toCsvString() { ); } - public String[] toCsvStrings() { - return new String[] { - name, - targetPath, - destinationPath, - lastBackupDate != null ? lastBackupDate.toString() : "", - Boolean.toString(automatic), - nextBackupDate != null ? nextBackupDate.toString() : "", - timeIntervalBackup != null ? timeIntervalBackup.toString() : "", - Integer.toString(maxToKeep) - }; - } - public Object[] toTableRow() { return new Object[] { name, diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index c03125c5..e694244a 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -33,8 +33,10 @@ public enum TCategory { PROGRESS_BACKUP_FRAME("ProgressBackupFrame"), TRAY_ICON("TrayIcon"), DIALOGS("Dialogs"), - SUBSCRIPTION("Subscription"), - HISTORY_LOGS("HistoryLogs"); // TODO: add to json + SUBSCRIPTION("Subscription"), // TODO: add to json + HISTORY_LOGS("HistoryLogs"), // TODO: add to json + ABOUT("About"), // TODO: add to json + DASHBOARD("Dashboard"); // TODO: add to json private final String categoryName; private final Map<TKey, String> translations = new HashMap<>(); @@ -72,6 +74,7 @@ public enum TKey { CREATE_BUTTON(TCategory.GENERAL, "CreateButton", "Create"), // TODO: add to json EDIT_BUTTON(TCategory.GENERAL, "EditButton", "Edit"), // TODO: add to json DELETE_BUTTON(TCategory.GENERAL, "DeleteButton", "Delete"), // TODO: add to json + QUICK_SEARCH(TCategory.GENERAL, "QuickSearch", "Quick Search..."), // TODO: add to json // Menu SUBMENU_MAIN(TCategory.MENU, "SubmenuMain", "MAIN"), //TODO: add to json @@ -103,7 +106,7 @@ public enum TKey { GITHUB_PAGE(TCategory.MENU, "Github", "Github page"), // TODO: add to json PAYPAL(TCategory.MENU, "Paypal", "Paypal"), // TODO: add to json BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), // TODO: add to json - CONTACT_US(TCategory.MENU, "ContactUs", "ContactUs"), // TODO: add to json + CONTACT_US(TCategory.MENU, "ContactUs", "Contact us"), // TODO: add to json SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), // TODO: add to json @@ -307,9 +310,23 @@ public enum TKey { SUBSCRIPTION_CONTACT_US(TCategory.SUBSCRIPTION, "ContactUs", "Contact us"), // TODO: add to json SUBSCRIPTION_TO_EXTEND(TCategory.SUBSCRIPTION, "ToExtend", "to extend the subscription period."), // TODO: add to json + // DASHBOARD + DASHBOARD_TITLE(TCategory.DASHBOARD, "DashboardTitle", "Backup Analytics Dashboard"), // TODO: add to json + DASHBOARD_CARD_TOTAL_CONFIGURATIONS(TCategory.DASHBOARD, "DashboardCardTotalConfigurations", "Total Backup Configurations"), // TODO: add to json + DASHBOARD_CARD_TOTAL_EXECUTIONS(TCategory.DASHBOARD, "DashboardCardTotalExecutions", "Total Backup Executions"), // TODO: add to json + DASHBOARD_CARD_SUCCESS_RATE(TCategory.DASHBOARD, "DashboardCardSuccessRate", "Success rate"), // TODO: add to json + DASHBOARD_CARD_AVG_DURATION(TCategory.DASHBOARD, "DashboardCardAvgDuration", "Avg Backup Duration"), // TODO: add to json + DASHBOARD_CARD_COMPRESSION_RATE(TCategory.DASHBOARD, "DashboardCardCompressionRate", "Compression Rate"), // TODO: add to json + DASHBOARD_CHART_EXECUTIONS(TCategory.DASHBOARD, "DashboardChartExecutions", "Backup Executions"), // TODO: add to json + DASHBOARD_CHART_AVG_DURATION(TCategory.DASHBOARD, "DashboardChartAvgDuration", "Average Backup Duration (min)"), // TODO: add to json + // HISTORY_LOGS HISTORY_LOGS_TITLE(TCategory.HISTORY_LOGS, "HistoryLogsTitle", "History logs"), // TODO: add to json - HISTORY_LOGS_DESCRIPTION(TCategory.HISTORY_LOGS, "HistoryLogsDescription", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."); // TODO: add to json + HISTORY_LOGS_DESCRIPTION(TCategory.HISTORY_LOGS, "HistoryLogsDescription", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."), // TODO: add to json + + // ABOUT + ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), // TODO: add to json + ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><br>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</html>"); // TODO: add to json private final TCategory category; private final String keyName; diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 05788071..9f5ea965 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -18,9 +18,7 @@ import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; -import backupmanager.gui.simple.BackupEntryDialog; import backupmanager.gui.simple.TimePickerDialog; public class BackupHelper { @@ -29,18 +27,12 @@ public class BackupHelper { public static final DateTimeFormatter dateForfolderNameFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH-mm-ss"); public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); - public static void openBackupById(BackupTableDataService backupTable, int id) { - logger.info("Event --> opening backup"); - - ConfigurationBackup backup = BackupConfigurationRepository.getBackupById(id); - openBackupEntryDialog(backupTable, backup); - } - public static void newBackup(BackupProgressGUI progressBar) { logger.info("Event --> new backup"); } public static void newBackup(ConfigurationBackup backup) { + logger.info("Event --> new backup"); BackupConfigurationRepository.insertBackup(backup); updateBackupTable(); @@ -60,19 +52,6 @@ public static void deleteBackup(int selectedRow, BackupTable backupTable, boolea deleteBackup(backupName); } - @Deprecated - public static void deleteBackup(int selectedRow, BackupTable backupTable) { - logger.info("Event --> deleting backup"); - - if (selectedRow != -1) { - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (response == JOptionPane.YES_OPTION) { - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - BackupHelper.deleteBackup(backupName); - } - } - } - public static void deleteBackup(String backupName) { logger.info("Event --> deleting backup"); ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backupName); @@ -132,11 +111,6 @@ public static void showMessageActivationAutoBackup(TimeInterval timeInterval, St "AutoBackup", 1); } - public static void openBackupEntryDialog(BackupTableDataService backupTable, ConfigurationBackup backup) { - BackupEntryDialog dialog = new BackupEntryDialog(backupTable, backup); - dialog.setVisible(true); - } - public static LocalDateTime getNexDateBackup(TimeInterval timeInterval) { return LocalDateTime.now() .plusDays(timeInterval.days()) diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index e3fabe88..24f21ebe 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -22,7 +22,7 @@ import backupmanager.database.ProductionDatabaseInitializer; import backupmanager.gui.Controllers.AppController; import backupmanager.gui.frames.BackupManager; -import backupmanager.utils.DemoPreferences; +import backupmanager.utils.AppPreferences; public class MainApp { private static final String CONFIG = "src/main/resources/res/config/config.json"; @@ -90,11 +90,11 @@ private static void runBackgroundProcess() { private static void runGui() { java.awt.EventQueue.invokeLater(() -> { - DemoPreferences.init(); + AppPreferences.init(); FlatRobotoFont.install(); FlatLaf.registerCustomDefaultsSource(".themes"); UIManager.put("defaultFont", FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13)); - DemoPreferences.setupLaf(); + AppPreferences.setupLaf(); BackupManager frame = BackupManager.getInstance(); frame.setVisible(true); diff --git a/src/main/java/backupmanager/Managers/ExportManager.java b/src/main/java/backupmanager/Managers/ExportManager.java index 0e2254aa..9f0a47f1 100644 --- a/src/main/java/backupmanager/Managers/ExportManager.java +++ b/src/main/java/backupmanager/Managers/ExportManager.java @@ -5,7 +5,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; @@ -13,13 +12,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfWriter; -import com.itextpdf.layout.Document; -import com.itextpdf.layout.element.Cell; -import com.itextpdf.layout.element.Paragraph; -import com.itextpdf.layout.element.Table; - import backupmanager.BackupOperations; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Enums.Translations.TCategory; @@ -29,94 +21,6 @@ public class ExportManager { private static final Logger logger = LoggerFactory.getLogger(ExportManager.class); - @Deprecated - public static void exportAsPDF(ArrayList<ConfigurationBackup> backups, String headers) { - logger.info("Exporting backups to PDF"); - - String path = BackupOperations.pathSearchWithFileChooser(false); - - if (path == null) { - logger.info("Exporting backups to PDF cancelled"); - return; - } - - String filename = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.PDF_NAME_MESSAGE_INPUT)); - if (filename == null || filename.isEmpty()) { - logger.info("Exporting backups to PDF cancelled"); - return; - } - - // Validate filename - if (!filename.matches("[a-zA-Z0-9-_ ]+")) { - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INVALID_FILENAME), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); - logger.info("Exporting backups to PDF cancelled due to invalid file name"); - return; - } - - // Build full path - String fullPath = Paths.get(path, filename + ".pdf").toString(); - - // Check if the file exists - File file = new File(fullPath); - if (file.exists()) { - int overwrite = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_FILE_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); - if (overwrite != JOptionPane.YES_OPTION) { - logger.info("Exporting backups to PDF cancelled by user (file exists)"); - return; - } - } - - try { - // Initialize PDF writer - PdfWriter writer = new PdfWriter(fullPath); - PdfDocument pdfDoc = new PdfDocument(writer); - Document document = new Document(pdfDoc); - - // insert pdf title - document.setFontSize(12f).setBold(); - - // Create table - String[] headerArray = headers.split(","); // Assuming headers are comma-separated - Table table = new Table(headerArray.length); - - // Add header cells - for (String header : headerArray) { - table.addCell(new Cell().add(new Paragraph(header.trim())).setFontSize(8f)); // Wrap the header in a Paragraph - } - - // Add backup data - if (backups != null && !backups.isEmpty()) { - for (ConfigurationBackup backup : backups) { - String[] data = backup.toCsvString().split(","); // Assuming backup data is comma-separated - for (String value : data) { - // new line every 25 characters - for (int i = 0; i < value.length(); i++) { - if (i % 25 == 0) { - value = value.substring(0, i) + "\n" + value.substring(i); - } - } - table.addCell(new Cell().add(new Paragraph(value.trim())).setFontSize(5f)); // Wrap the value in a Paragraph - } - } - } - - // Add table to document - document.add(table); - - // Close document - document.close(); - - // Notify success - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); - - } catch (IOException ex) { - logger.error("Error exporting backups to PDF: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_PDF) + ex.getMessage(), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); - } finally { - logger.info("Exporting backups to PDF finished"); - } - } - public static void exportAsCSV(List<ConfigurationBackup> backups, String header) { logger.info("Exporting backups to CSV"); diff --git a/src/main/java/backupmanager/Services/BackupAnalyticsService.java b/src/main/java/backupmanager/Services/BackupAnalyticsService.java index f1b6d02c..c2c50457 100644 --- a/src/main/java/backupmanager/Services/BackupAnalyticsService.java +++ b/src/main/java/backupmanager/Services/BackupAnalyticsService.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; @@ -59,36 +60,41 @@ public static BackupAnalyticsSnapshot buildSnapshot(List<BackupRequest> requests .collect(Collectors.groupingBy( r -> r.startedDate().toLocalDate(), Collectors.averagingDouble( - BackupRequest::durationMs + r -> r.durationMs() / 60000.0 ))); return new BackupAnalyticsSnapshot(total, successCount, failedCount, successRate, avgDuration, avgCompressionRate, diskUsage, durationTrend); } - public static XYDataset buildRequestTrendDataset(List<BackupRequest> requests) { + public static CategoryDataset buildRequestsPerMonthDataset(List<BackupRequest> requests, String title) { - TimeSeries series = new TimeSeries("Backup Requests Trend"); + DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - if (requests == null || requests.isEmpty()) - return new TimeSeriesCollection(series); + LocalDate now = LocalDate.now(); + LocalDate oneYearAgo = now.minusMonths(11).withDayOfMonth(1); - Map<LocalDate, Long> aggregation = requests.stream() + Map<String, Long> map = requests.stream() + .filter(r -> !r.startedDate().toLocalDate().isBefore(oneYearAgo)) .collect(Collectors.groupingBy( - r -> r.startedDate().toLocalDate(), + r -> { + var d = r.startedDate(); + return d.getYear() + "-" + String.format("%02d", d.getMonthValue()); + }, Collectors.counting() )); - aggregation.forEach((date, count) -> - series.add( - new Day( - date.getDayOfMonth(), - date.getMonthValue(), - date.getYear() - ), - count - )); + List<String> last12Months = IntStream.rangeClosed(0, 11) + .mapToObj(i -> { + LocalDate month = oneYearAgo.plusMonths(i); + return month.getYear() + "-" + String.format("%02d", month.getMonthValue()); + }) + .toList(); - return new TimeSeriesCollection(series); + for (String month : last12Months) { + dataset.addValue(map.getOrDefault(month, 0L), title, month); + } + + return dataset; } public static CategoryDataset buildStatusCategoryDataset(List<BackupRequest> requests) { @@ -182,19 +188,25 @@ public static String formatBytes(long bytes) { return bytes / (1024L * 1024 * 1024) + " GB"; } - public static XYDataset buildDurationTrendDataset(Map<LocalDate, Double> trendMap) { + public static XYDataset buildDurationTrendDataset(Map<LocalDate, Double> trendMap, String title) { + + TimeSeries series = new TimeSeries(title); - TimeSeries series = new TimeSeries("Average Backup Duration"); + LocalDate today = LocalDate.now(); + LocalDate thirtyDaysAgo = today.minusDays(29); + + trendMap.entrySet().stream() + .filter(entry -> !entry.getKey().isBefore(thirtyDaysAgo)) + .sorted(Map.Entry.comparingByKey()) + .forEach(entry -> { + LocalDate date = entry.getKey(); + Double value = entry.getValue(); - trendMap.forEach((date, value) -> series.add( - new Day( - date.getDayOfMonth(), - date.getMonthValue(), - date.getYear() - ), - value - )); + new Day(date.getDayOfMonth(), date.getMonthValue(), date.getYear()), + value + ); + }); return new TimeSeriesCollection(series); } diff --git a/src/main/java/backupmanager/Services/BackupService.java b/src/main/java/backupmanager/Services/BackupService.java index 3cc9dfb9..c3a48b7f 100644 --- a/src/main/java/backupmanager/Services/BackupService.java +++ b/src/main/java/backupmanager/Services/BackupService.java @@ -36,6 +36,10 @@ public String getBackupDetails(String name) { return buildDetails(backup); } + public void updateBackup(ConfigurationBackup backup) { + BackupConfigurationRepository.updateBackup(backup); + } + public ConfigurationBackup getBackupByName(String name) { return BackupConfigurationRepository.getBackupByName(name); } diff --git a/src/main/java/backupmanager/Utils/DemoPreferences.java b/src/main/java/backupmanager/Utils/AppPreferences.java similarity index 97% rename from src/main/java/backupmanager/Utils/DemoPreferences.java rename to src/main/java/backupmanager/Utils/AppPreferences.java index eddb6f60..0241189f 100644 --- a/src/main/java/backupmanager/Utils/DemoPreferences.java +++ b/src/main/java/backupmanager/Utils/AppPreferences.java @@ -15,21 +15,17 @@ import backupmanager.gui.themes.PanelThemes; -public class DemoPreferences { +public class AppPreferences { - public static final String PREFERENCES_ROOT_PATH = "/raven-flatlaf-demo"; + public static final String PREFERENCES_ROOT_PATH = "/BackupManager"; public static final String KEY_LAF = "laf"; public static final String KEY_LAF_THEME = "lafTheme"; public static final String KEY_ACCENT_COLOR = "accent"; public static final String KEY_RECENT_SEARCH = "recentSearch"; public static final String KEY_RECENT_SEARCH_FAVORITE = "recentSearchFavorite"; - public static final String RESOURCE_PREFIX = "res:"; - public static final String THEME_UI_KEY = "__RaVen.flatlaf.demo.theme"; - public static Color accentColor; - private static Preferences state; public static Preferences getState() { diff --git a/src/main/java/backupmanager/gui/Controllers/SettingsController.java b/src/main/java/backupmanager/gui/Controllers/SettingsController.java new file mode 100644 index 00000000..90d44a46 --- /dev/null +++ b/src/main/java/backupmanager/gui/Controllers/SettingsController.java @@ -0,0 +1,7 @@ +package backupmanager.gui.Controllers; + +public class SettingsController { + public void onLanguageChanged(String language) { + + } +} diff --git a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java index 82a16af8..234dbd84 100644 --- a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java +++ b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java @@ -9,27 +9,7 @@ public class TimePickerController { - private TimeInterval timeInterval; - private boolean closeOk; - - public TimePickerController(TimeInterval timeInterval, boolean closeOk) { - this.timeInterval = timeInterval; - this.closeOk = closeOk; - } - - @Deprecated - public void handleOkButton(javax.swing.JDialog dialog, int days, int hours, int minutes) { - if (isLongTimeCorrect(days, hours, minutes)) { - if (isShortTimeCorrect(days, hours) && !showWarningMessageForShortTimeAndGetIfItOkayResponse(dialog)) - return; - - timeInterval = new TimeInterval(days, hours, minutes); - closeOk = true; - dialog.dispose(); - } - else - showErrorMessageForLongTime(dialog); - } + public TimePickerController() { } public TimeInterval getTimeIntervalIfPossible(TimePickerDialog dialog, int days, int hours, int minutes) { if (isLongTimeCorrect(days, hours, minutes)) { @@ -43,15 +23,6 @@ public TimeInterval getTimeIntervalIfPossible(TimePickerDialog dialog, int days, return null; } - @Deprecated - public TimeInterval getTimeInterval() { - if (closeOk) return timeInterval; - return null; - } - - @Deprecated - public void setCloseOk(boolean closeOk) { this.closeOk = closeOk; } - private boolean isLongTimeCorrect(int days, int hours, int minutes) { return days >= 0 && hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 && (days != 0 || hours != 0 || minutes != 0); diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index 1e07a010..1d198065 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -11,6 +11,8 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import backupmanager.Managers.WebsiteManager; import net.miginfocom.swing.MigLayout; @@ -24,7 +26,7 @@ private void init() { setLayout(new MigLayout("fillx,wrap,insets 20,width 520")); - JTextPane title = createText("Backup Manager"); + JTextPane title = createText(Translations.get(TKey.APP_NAME)); title.putClientProperty(FlatClientProperties.STYLE, "font:bold +6"); JTextPane description = createText(""); @@ -57,38 +59,27 @@ public void paint(java.awt.Graphics g) {} } private String getDescriptionText() { - return """ - <html> - <b>Backup Manager</b> is a simple and powerful application designed to automate - folder and subfolder backups. - - <br><br> - Users can schedule automatic backups or execute manual backups anytime. - - <br><br> - Backup history is stored securely, allowing full control over saved data. - - <br><br> - Visit <a href="%s">project website</a> for more information. - </html> - """.formatted(ConfigKey.INFO_PAGE_LINK.getValue()); + String message = Translations.get(TKey.ABOUT_MESSAGE_BODY); + message = message.replace("[PROJECT_WEBSITE]", ConfigKey.INFO_PAGE_LINK.getValue()) ; + return message; } private JComponent createSystemInformation() { JPanel panel = new JPanel(new MigLayout("wrap,insets 10")); - panel.setBorder(new TitledBorder("System Information")); + panel.setBorder(new TitledBorder(Translations.get(TKey.ABOUT_SYSTEM_INFORMATION))); JTextPane text = createText(""); text.setContentType("text/html"); String info = """ <html> - Version: %s<br> + %s: %s<br> Java: %s<br> OS: %s<br> </html> """.formatted( + Translations.get(TKey.VERSION), ConfigKey.VERSION.getValue(), System.getProperty("java.vendor") + " - v" + System.getProperty("java.version"), System.getProperty("os.name") + " " + System.getProperty("os.arch") diff --git a/src/main/java/backupmanager/gui/component/FormSearchButton.java b/src/main/java/backupmanager/gui/component/FormSearchButton.java index 937b261c..c8d5c2f0 100644 --- a/src/main/java/backupmanager/gui/component/FormSearchButton.java +++ b/src/main/java/backupmanager/gui/component/FormSearchButton.java @@ -6,12 +6,14 @@ import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.extras.FlatSVGIcon; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import net.miginfocom.swing.MigLayout; public class FormSearchButton extends JButton { public FormSearchButton() { - super("Quick Search...", new FlatSVGIcon("icons/search.svg", 0.4f)); + super(Translations.get(TKey.QUICK_SEARCH), new FlatSVGIcon("icons/search.svg", 0.4f)); init(); } diff --git a/src/main/java/backupmanager/gui/component/FormSearchPanel.java b/src/main/java/backupmanager/gui/component/FormSearchPanel.java index c242e1ca..8577fed9 100644 --- a/src/main/java/backupmanager/gui/component/FormSearchPanel.java +++ b/src/main/java/backupmanager/gui/component/FormSearchPanel.java @@ -39,7 +39,7 @@ import backupmanager.gui.svg.SVGIconUIColor; import backupmanager.gui.system.Form; import backupmanager.gui.system.FormSearch; -import backupmanager.utils.DemoPreferences; +import backupmanager.utils.AppPreferences; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; import raven.modal.Drawer; @@ -262,7 +262,7 @@ private JLabel createLabel(String title) { } private List<Item> getRecentSearch(boolean favorite) { - String[] recentSearch = DemoPreferences.getRecentSearch(favorite); + String[] recentSearch = AppPreferences.getRecentSearch(favorite); if (recentSearch == null) { return null; } @@ -412,7 +412,7 @@ protected void showForm() { ModalDialog.closeModal(FormSearch.ID); Drawer.setSelectedItemClass(form); if (!isFavorite) { - DemoPreferences.addRecentSearch(data.name(), false); + AppPreferences.addRecentSearch(data.name(), false); } } @@ -462,7 +462,7 @@ public void mouseExited(MouseEvent e) { } protected void removeRecent() { - DemoPreferences.removeRecentSearch(data.name(), isFavorite); + AppPreferences.removeRecentSearch(data.name(), isFavorite); panelResult.remove(this); listItems.remove(this); if (listItems.isEmpty()) { @@ -481,7 +481,7 @@ protected void removeRecent() { } protected void addFavorite() { - DemoPreferences.addRecentSearch(data.name(), true); + AppPreferences.addRecentSearch(data.name(), true); int[] index = getFirstFavoriteIndex(); panelResult.remove(this); listItems.remove(this); diff --git a/src/main/java/backupmanager/gui/component/chart/BarChart.java b/src/main/java/backupmanager/gui/component/chart/BarChart.java index d21b7794..071330a2 100644 --- a/src/main/java/backupmanager/gui/component/chart/BarChart.java +++ b/src/main/java/backupmanager/gui/component/chart/BarChart.java @@ -6,10 +6,12 @@ import org.jfree.chart.axis.Axis; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.labels.StandardCategoryToolTipGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.renderer.category.BarRenderer; import org.jfree.data.category.CategoryDataset; +import org.jfree.data.category.DefaultCategoryDataset; import backupmanager.gui.component.chart.renderer.bar.ChartBarRenderer; import backupmanager.gui.component.chart.themes.ChartDrawingSupplier; @@ -20,40 +22,57 @@ public class BarChart extends DefaultChartPanel { @Override protected JFreeChart createChart() { - JFreeChart freeChart = ChartFactory.createBarChart(null, null, null, null); - return freeChart; + return ChartFactory.createBarChart(null, null, null, new DefaultCategoryDataset()); } @Override protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { + CategoryPlot plot = chart.getCategoryPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); CategoryAxis domain = plot.getDomainAxis(); range.setAxisLineVisible(false); domain.setAxisLineVisible(false); - plot.setRenderer(getDefaultRender()); + range.setStandardTickUnits(NumberAxis.createIntegerTickUnits()); + range.setAutoRangeIncludesZero(true); + + // renderer + plot.setRenderer(getDefaultRenderer()); } @Override protected void styleChart(JFreeChart chart, ChartPanel panel) { + CategoryPlot plot = chart.getCategoryPlot(); + NumberAxis range = (NumberAxis) plot.getRangeAxis(); CategoryAxis domain = plot.getDomainAxis(); range.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); - domain.setTickLabelInsets(ChartDrawingSupplier.scaleRectangleInsets(Axis.DEFAULT_TICK_LABEL_INSETS)); - plot.setRangeGridlineStroke(ChartDrawingSupplier.getDefaultGridlineStroke()); plot.setInsets(ChartDrawingSupplier.scaleRectangleInsets(Plot.DEFAULT_INSETS)); } - public BarRenderer getDefaultRender() { + private BarRenderer getDefaultRenderer() { + if (renderer == null) { + renderer = new ChartBarRenderer(); + + // tooltip hover + renderer.setDefaultToolTipGenerator(new StandardCategoryToolTipGenerator()); + + // outline + renderer.setDrawBarOutline(false); + + // spacing + renderer.setItemMargin(0.1); } + return renderer; } diff --git a/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java index 6cb4ec29..5702913d 100644 --- a/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java +++ b/src/main/java/backupmanager/gui/component/chart/TimeSeriesChart.java @@ -4,6 +4,7 @@ import java.awt.Font; import java.awt.geom.Rectangle2D; import java.text.DateFormat; +import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Date; @@ -55,7 +56,7 @@ protected void defaultStyleChart(JFreeChart chart, ChartPanel panel) { NumberAxis range = (NumberAxis) plot.getRangeAxis(); DateAxis domain = (DateAxis) plot.getDomainAxis(); - range.setNumberFormatOverride(NumberFormat.getCurrencyInstance()); + range.setNumberFormatOverride(new DecimalFormat("#0.00")); range.setAxisLineVisible(false); range.setTickMarksVisible(false); range.setUpperMargin(0.2); @@ -105,7 +106,7 @@ protected void createAnnotation(JFreeChart chart, ChartPanel chartPanel) { MultiXYTextAnnotation annotation = new MultiXYTextAnnotation(); DateFormat titleFormat = DateFormat.getDateInstance(); - NumberFormat valueFormat = NumberFormat.getCurrencyInstance(); + NumberFormat valueFormat = new DecimalFormat("#0.00"); annotation.setTitleGenerator(xValue -> titleFormat.format(new Date((long) xValue))); annotation.setNumberFormat(valueFormat); diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index 02b2d01e..bf372557 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -22,6 +22,8 @@ import backupmanager.Entities.BackupAnalyticsSnapshot; import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import backupmanager.Services.BackupAnalyticsService; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; @@ -39,6 +41,12 @@ @SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") public class FormBackupDashboard extends CustomForm { + private static final int CARD_TOTAL_CONFIG = 0; + private static final int CARD_SUCCESS_RATE = 1; + private static final int CARD_DURATION = 2; + private static final int CARD_COMPRESSION = 3; + private static final int CARD_DISK_USAGE = 4; + public FormBackupDashboard() { build(); } @@ -49,7 +57,7 @@ protected void init() { createTitle(); createPanelLayout(); createCard(); - createDiskUsageChart(); + // createDiskUsageChart(); createExecutionsByMonthChart(); createAvgDurationChart(); } @@ -60,44 +68,39 @@ protected void loadData() { List<BackupRequest> requests = BackupRequestRepository.getRequestBackups(); BackupAnalyticsSnapshot snapshot = BackupAnalyticsService.buildSnapshot(requests); - cardBox.setValueAt(0, + cardBox.setValueAt(CARD_TOTAL_CONFIG, String.valueOf(configurations.size()), - "Total Backup Configurations", + "", "", true); - cardBox.setValueAt(1, + cardBox.setValueAt(CARD_SUCCESS_RATE, String.valueOf(snapshot.totalRequests()), - "Total Backup Executions", - snapshot.successRate() + "%", - true); - - cardBox.setValueAt(2, - BackupAnalyticsService.formatBytes(snapshot.totalDiskUsageBytes()), - "Disk Usage", - "", + Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE), + String.format("%.2f%%", snapshot.successRate()), true); - cardBox.setValueAt(3, + cardBox.setValueAt(CARD_DURATION, String.format("%.2f min", BackupAnalyticsService.convertAvgDurationinMinutes(snapshot)), - "Avg Backup Duration", + "", "", true); - cardBox.setValueAt(4, + cardBox.setValueAt(CARD_COMPRESSION, String.format("%.1f%%", snapshot.avgCompressionRate() * 100), - "Compression Rate", + "", "", true); - timeSeriesChart.setDataset(BackupAnalyticsService.buildDurationTrendDataset(snapshot.durationTrend())); + durationChart.setDataset(BackupAnalyticsService.buildDurationTrendDataset(snapshot.durationTrend(), Translations.get(TKey.DASHBOARD_CHART_AVG_DURATION))); + executionsChart.setDataset(BackupAnalyticsService.buildRequestsPerMonthDataset(requests, Translations.get(TKey.DASHBOARD_CHART_EXECUTIONS))); } protected void createTitle() { JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); - JLabel title = new JLabel("Backup Analytics Dashboard"); + JLabel title = new JLabel(Translations.get(TKey.DASHBOARD_TITLE)); title.putClientProperty(FlatClientProperties.STYLE, "font:bold +3"); @@ -106,16 +109,13 @@ protected void createTitle() { if (DefaultChartTheme.setChartColors(colorThemes)) { - DefaultChartTheme.applyTheme(timeSeriesChart.getFreeChart()); - DefaultChartTheme.applyTheme(barChart.getFreeChart()); - DefaultChartTheme.applyTheme(pieChart.getFreeChart()); - DefaultChartTheme.applyTheme(spiderChart.getFreeChart()); + DefaultChartTheme.applyTheme(durationChart.getFreeChart()); + DefaultChartTheme.applyTheme(executionsChart.getFreeChart()); cardBox.setCardIconColor(0, DefaultChartTheme.getColor(0)); cardBox.setCardIconColor(1, DefaultChartTheme.getColor(1)); cardBox.setCardIconColor(2, DefaultChartTheme.getColor(2)); cardBox.setCardIconColor(3, DefaultChartTheme.getColor(3)); - cardBox.setCardIconColor(4, DefaultChartTheme.getColor(4)); } }); @@ -154,47 +154,39 @@ private void createCard() { cardBox = new CardBox(); cardBox.addCardItem( - createIcon("icons/dashboard/database.svg", DefaultChartTheme.getColor(0)), - "Total Backup Configurations"); + createIcon("icons/dashboard/database.svg", DefaultChartTheme.getColor(CARD_TOTAL_CONFIG)), + Translations.get(TKey.DASHBOARD_CARD_TOTAL_CONFIGURATIONS)); cardBox.addCardItem( - createIcon("icons/dashboard/run.svg", DefaultChartTheme.getColor(1)), - "Total Backup Executions"); + createIcon("icons/dashboard/run.svg", DefaultChartTheme.getColor(CARD_SUCCESS_RATE)), + Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); cardBox.addCardItem( - createIcon("icons/dashboard/usage.svg", DefaultChartTheme.getColor(2)), - "Disk Usage"); + createIcon("icons/dashboard/duration.svg", DefaultChartTheme.getColor(CARD_DURATION)), + Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); cardBox.addCardItem( - createIcon("icons/dashboard/duration.svg", DefaultChartTheme.getColor(3)), - "Avg Backup Duration"); - - cardBox.addCardItem( - createIcon("icons/dashboard/rate.svg", DefaultChartTheme.getColor(4)), - "Compression Rate"); + createIcon("icons/dashboard/rate.svg", DefaultChartTheme.getColor(CARD_COMPRESSION)), + Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); panel.add(cardBox); panelLayout.add(panel); } - private void createAvgDurationChart() { - - JPanel panel = new JPanel( - new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); - - timeSeriesChart = new TimeSeriesChart(); - barChart = new BarChart(); - - panel.add(timeSeriesChart); - panel.add(barChart); + private JPanel createChartPanel(int height) { + return new JPanel(new MigLayout("gap 14,wrap,fillx", "[fill]", "[" + height + "]")); + } + private void createAvgDurationChart() { + JPanel panel = createChartPanel(350); + durationChart = new TimeSeriesChart(); + panel.add(durationChart); panelLayout.add(panel); } private void createDiskUsageChart() { - JPanel panel = new JPanel( - new MigLayout("gap 14,wrap,fillx", "[fill]", "[350]")); + JPanel panel = createChartPanel(350); spiderChart = new SpiderChart(); pieChart = new PieChart(); @@ -206,16 +198,9 @@ private void createDiskUsageChart() { } private void createExecutionsByMonthChart() { - - JPanel panel = new JPanel( - new MigLayout("fillx,gap 14", "[fill,300::]", "[300]")); - - timeSeriesChart = new TimeSeriesChart(); - barChart = new BarChart(); - - panel.add(timeSeriesChart); - panel.add(barChart); - + JPanel panel = createChartPanel(350); + executionsChart = new BarChart(); + panel.add(executionsChart); panelLayout.add(panel); } @@ -232,8 +217,8 @@ protected void setTranslations() { private JPanel panelLayout; private CardBox cardBox; - private TimeSeriesChart timeSeriesChart; - private BarChart barChart; + private TimeSeriesChart durationChart; + private BarChart executionsChart; private SpiderChart spiderChart; private PieChart pieChart; diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index 40f903ca..dd4cd8db 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -1,7 +1,6 @@ package backupmanager.gui.forms; import java.awt.Component; -import java.text.DecimalFormat; import java.time.LocalDateTime; import java.util.List; @@ -9,7 +8,6 @@ import javax.swing.BorderFactory; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; -import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; @@ -41,12 +39,10 @@ import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.Controllers.BackupManagerController; import backupmanager.gui.frames.Controllers.BackupPopupController; -import backupmanager.gui.sample.csv.ConfigurationBackupDataTable; import backupmanager.gui.svg.SVGButton; import backupmanager.utils.SystemForm; import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; -import raven.swingpack.JPagination; @SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) public class FormBackupTable extends CustomForm { @@ -98,20 +94,14 @@ public void formRefresh() { @Override protected void loadData() { - loadTable(pagination.getSelectedPage()); - } + if (backups == null) + return; - private void loadTable(int page) { - if (backups != null) { - ConfigurationBackupDataTable res = ConfigurationBackupDataTable.create(backups, page, limit); - lbTotalPage.setText(DecimalFormat.getInstance().format(res.getTotal())); - pagination.getModel().setPageRange(res.getPage(), res.getPageSize()); + DefaultTableModel model = (DefaultTableModel) backupTable.getModel(); + model.setRowCount(0); - DefaultTableModel model = (DefaultTableModel) backupTable.getModel(); - model.setRowCount(0); - for (ConfigurationBackup backup : res.getData()) { - model.addRow(backup.toTableRow()); - } + for (ConfigurationBackup backup : backups) { + model.addRow(backup.toTableRow()); } } @@ -123,9 +113,9 @@ private Component createBorder(Component component) { private Component createBasicTable() { JPanel panelTable = new JPanel(new MigLayout( - "fill,wrap,insets 10 0 10 0", + "fill,wrap,insets 10 10 10 10", "[fill]", - "[][grow,fill][]" + "[][grow,fill]" )); // create table model @@ -230,22 +220,6 @@ protected int getAlignment(int column) { panelTable.add(createHeaderAction()); panelTable.add(scrollPane, "grow, pushy"); - // create pagination - pagination = new JPagination(11, 1, 1); - pagination.setMinimumSize(pagination.getPreferredSize()); - pagination.addChangeListener(e -> loadData()); - JPanel panelPage = new JPanel(new MigLayout("insets 5 15 5 15", "[][]push[pref!]")); - lbTotalPage = new JLabel("0"); - pagination.putClientProperty(FlatClientProperties.STYLE, "" + - "background:null;"); - panelPage.putClientProperty(FlatClientProperties.STYLE, "" + - "background:null;"); - panelPage.add(new JLabel("Total:")); - panelPage.add(lbTotalPage); - panelPage.add(pagination); - - panelTable.add(panelPage, "growx"); - backupTable = table; setRenderer(); @@ -391,11 +365,11 @@ private void handleAction(String action, JMenuItem interruptBackupPopupItem, JMe ConfigurationBackup backup = getBackupFromTableRow(selectedRow); switch (action) { - case "EDIT" -> BackupPopupController.popupItemEditBackupName(tableService, backup); + case "EDIT" -> showEditModal(backup); case "DELETE" -> BackupHelper.deleteBackup(backup); case "DUPLICATE" -> BackupPopupController.popupItemDuplicateBackup(backup); case "RENAME" -> BackupPopupController.popupItemRenameBackup(backups, backup); - case "OPEN_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); + case "OPEN_TARGET" -> BackupPopupController.popupItemOpenInitialPath(backup); case "OPEN_DEST" -> BackupPopupController.popupItemOpenDestinationPath(backup); case "RUN_SINGLE" -> BackupPopupController.popupItemRunBackup(backup, tableService, interruptBackupPopupItem, RunBackupPopupItem); case "COPY_NAME" -> BackupPopupController.popupItemCopyBackupName(backup); @@ -546,10 +520,7 @@ protected void setTranslations() { itemCopyDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); } - private final int limit = 50; - private JPagination pagination; private BackupTable backupTable; - private JLabel lbTotalPage; private JTextPane txtDetails; private JTextField txtSearch; private SVGButton cmdCreate; diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index 72634bc0..9b117839 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -36,7 +36,7 @@ import backupmanager.gui.component.AccentColorIcon; import backupmanager.gui.system.FormManager; import backupmanager.gui.themes.PanelThemes; -import backupmanager.utils.DemoPreferences; +import backupmanager.utils.AppPreferences; import backupmanager.utils.SystemForm; import net.miginfocom.swing.MigLayout; import raven.color.ColorPicker; @@ -206,9 +206,17 @@ private Component createModalDefaultOption() { private Component createLanguageOption() { JPanel panel = new JPanel(new MigLayout()); panel.setBorder(new TitledBorder("Language")); - JComboBox<Object> languageCombo = new JComboBox<>(); + languageCombo = new JComboBox<>(); initComboItem(languageCombo); + languageCombo.addActionListener(e -> { + Object selected = languageCombo.getSelectedItem(); + + logger.info("Language setted to: {}", selected.toString()); + + // onLanguageChanged(selected); + }); + panel.add(languageCombo); return panel; @@ -230,8 +238,8 @@ private JPanel createStyleOption() { } private static final String[] accentColorKeys = { - "Demo.accent.default", "Demo.accent.blue", "Demo.accent.purple", "Demo.accent.red", - "Demo.accent.orange", "Demo.accent.yellow", "Demo.accent.green", + "App.accent.default", "App.accent.blue", "App.accent.purple", "App.accent.red", + "App.accent.orange", "App.accent.yellow", "App.accent.green", }; private static final String[] accentColorNames = { "Default", "Blue", "Purple", "Red", "Orange", "Yellow", "Green", @@ -256,7 +264,7 @@ private Component createAccentColor() { toolBar.add(accentColorButtons[i]); group.add(accentColorButtons[i]); if (!selected) { - if (DemoPreferences.accentColor == null) { + if (AppPreferences.accentColor == null) { if (i == 0) { accentColorButtons[i].setSelected(true); oldSelected = accentColorButtons[i]; @@ -264,7 +272,7 @@ private Component createAccentColor() { } } else { Color color = UIManager.getColor(accentColorKeys[i]); - if (DemoPreferences.accentColor.equals(color)) { + if (AppPreferences.accentColor.equals(color)) { accentColorButtons[i].setSelected(true); oldSelected = accentColorButtons[i]; selected = true; @@ -279,7 +287,7 @@ private Component createAccentColor() { accentColorCustomButton.setSelected(true); } - FlatLaf.setSystemColorGetter(name -> name.equals("accent") ? DemoPreferences.accentColor : null); + FlatLaf.setSystemColorGetter(name -> name.equals("accent") ? AppPreferences.accentColor : null); UIManager.addPropertyChangeListener(e -> { if ("lookAndFeel".equals(e.getPropertyName())) { updateAccentColorButtons(); @@ -293,13 +301,13 @@ private Component createAccentColor() { private JToggleButton createCustomAccentColor() { JToggleButton button = new JToggleButton(new FlatSVGIcon("icons/color.svg", 16, 16)); button.addActionListener(e -> { - ColorPicker colorPicker = new ColorPicker(DemoPreferences.accentColor); + ColorPicker colorPicker = new ColorPicker(AppPreferences.accentColor); colorPicker.setBorder(new ScaledEmptyBorder(0, 20, 0, 20)); Option option = ModalDialog.createOption(); option.setAnimationEnabled(false); ModalDialog.showModal(this, new SimpleModalBorder(colorPicker, "Select Color", SimpleModalBorder.YES_NO_OPTION, (controller, action) -> { if (action == SimpleModalBorder.YES_OPTION) { - DemoPreferences.accentColor = colorPicker.getSelectedColor(); + AppPreferences.accentColor = colorPicker.getSelectedColor(); oldSelected = null; applyAccentColor(); } else if (action == SimpleModalBorder.CLOSE_OPTION) { @@ -412,7 +420,7 @@ private void accentColorChanged(ActionEvent e) { break; } } - DemoPreferences.accentColor = (accentColorKey != null && !accentColorKey.equals(accentColorKeys[0])) + AppPreferences.accentColor = (accentColorKey != null && !accentColorKey.equals(accentColorKeys[0])) ? UIManager.getColor(accentColorKey) : null; applyAccentColor(); @@ -420,7 +428,7 @@ private void accentColorChanged(ActionEvent e) { private void applyAccentColor() { Class<? extends LookAndFeel> lafClass = UIManager.getLookAndFeel().getClass(); - DemoPreferences.updateAccentColor(DemoPreferences.accentColor); + AppPreferences.updateAccentColor(AppPreferences.accentColor); try { FlatLaf.setup(lafClass.getDeclaredConstructor().newInstance()); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException @@ -468,4 +476,5 @@ protected void setTranslations() { } private JTabbedPane tabbedPane; + private JComboBox<Object> languageCombo; } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index f1d5ab8d..6dffa90e 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -6,9 +6,6 @@ import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import backupmanager.Entities.ConfigurationBackup; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations.TCategory; @@ -24,7 +21,6 @@ public class BackupManagerController { - private static final Logger logger = LoggerFactory.getLogger(BackupManagerController.class); private final BackupService backupService; private final BackupTableDataService backupTable; @@ -93,6 +89,7 @@ public void showEditModal(CustomForm form, ConfigurationBackup backup) { (controller, action) -> { if (action == SimpleModalBorder.OK_OPTION) { ConfigurationBackup editedBackup = dialog.getResult(); + backupService.updateBackup(editedBackup); form.formRefresh(); } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index f40072d8..58571c83 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -9,6 +9,8 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.JMenuItem; import javax.swing.JOptionPane; @@ -80,16 +82,14 @@ public static void popupItemCopyBackupName(ConfigurationBackup backup) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); } - public static void popupItemEditBackupName(BackupTableDataService backupTable, ConfigurationBackup backup) { - BackupHelper.openBackupById(backupTable, backup.getId()); - } - public static void popupItemDuplicateBackup(ConfigurationBackup backup) { logger.info("Event --> duplicating backup"); + int value = getIncrementalBackupNameValue(backup.getName()); + LocalDateTime dateNow = LocalDateTime.now(); ConfigurationBackup newBackup = new ConfigurationBackup( - backup.getName() + "_copy", + backup.getName() + "(" + value + ")", backup.getTargetPath(), backup.getDestinationPath(), null, @@ -106,6 +106,25 @@ public static void popupItemDuplicateBackup(ConfigurationBackup backup) { BackupConfigurationRepository.insertBackup(newBackup); } + private static int getIncrementalBackupNameValue(String backupName) { + var backups = BackupConfigurationRepository.getBackupList(); + int max = 0; + + Pattern pattern = Pattern.compile( + Pattern.quote(backupName) + "\\((\\d+)\\)$" + ); + + for (var backup : backups) { + Matcher matcher = pattern.matcher(backup.getName()); + if (matcher.find()) { + int value = Integer.parseInt(matcher.group(1)); + max = Math.max(max, value); + } + } + + return max + 1; + } + private static void renameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { logger.info("Event --> backup renaming"); diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index edf256bd..4d8b806d 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -249,8 +249,7 @@ private static List<MenuItem> buildMenuItems() { itemList.add(new Item.Label(Translations.get(TKey.SUBMENU_MAIN))); // Backup menu - Item backupItem = createMenuItem(Translations.get(TKey.BACKUP_TABLE), "forms.svg", MenuItems.BackupList, FormBackupTable.class) - .subMenu(Translations.get(TKey.CREATE_BACKUP)); + Item backupItem = createMenuItem(Translations.get(TKey.BACKUP_TABLE), "forms.svg", MenuItems.BackupList, FormBackupTable.class); if (config.isMenuItemEnabled(MenuItems.Import.name())) backupItem.subMenu(Translations.get(TKey.IMPORT_BACKUP)); diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index 067db20e..e138cc41 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -118,6 +118,12 @@ protected void init() { destinationPathBtn.addActionListener(e -> entryController.openFileChooser(txtDestinationPath, false)); timeIntervalBtn.addActionListener(e -> openTimeInterval()); + executeBackupBtn.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + automaticBackupBtn.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + targetPathBtn.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + destinationPathBtn.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + timeIntervalBtn.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + styleSpinner(maxToKeeSpinner); configureSpinner(maxToKeeSpinner, 1, 100); diff --git a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java index 966f5263..09c9ffe2 100644 --- a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java +++ b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java @@ -20,7 +20,7 @@ public class TimePickerDialog extends CustomDialog<TimeInterval> { public TimePickerDialog(TimeInterval timeInterval) { build(); - timePickerController = new TimePickerController(timeInterval, false); + timePickerController = new TimePickerController(); if (timeInterval != null) { daysSpinner.setValue(timeInterval.days()); diff --git a/src/main/java/backupmanager/gui/system/Form.java b/src/main/java/backupmanager/gui/system/Form.java index f8369ac7..a5e3f871 100644 --- a/src/main/java/backupmanager/gui/system/Form.java +++ b/src/main/java/backupmanager/gui/system/Form.java @@ -5,7 +5,7 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; -public class Form extends JPanel { +public abstract class Form extends JPanel { private LookAndFeel oldTheme = UIManager.getLookAndFeel(); @@ -13,17 +13,13 @@ public Form() { init(); } - private void init() { - } + private void init() { } - public void formInit() { - } + public abstract void formInit(); - public void formOpen() { - } + public void formOpen() { } - public void formRefresh() { - } + public abstract void formRefresh(); protected boolean formCheck() { if (oldTheme != UIManager.getLookAndFeel()) { diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index e273171a..8ce612cf 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -118,7 +118,7 @@ private static FormLogin getLogin() { } public static void showAbout() { - ModalDialog.showModal(frame, new SimpleModalBorder(new About(), Translations.get(TKey.FILE)), + ModalDialog.showModal(frame, new SimpleModalBorder(new About(), Translations.get(TKey.ABOUT)), ModalDialog.createOption().setAnimationEnabled(false) ); } diff --git a/src/main/java/backupmanager/gui/system/MainForm.java b/src/main/java/backupmanager/gui/system/MainForm.java index c2984d86..bfcde352 100644 --- a/src/main/java/backupmanager/gui/system/MainForm.java +++ b/src/main/java/backupmanager/gui/system/MainForm.java @@ -80,10 +80,10 @@ private JPanel createFooter() { panel.putClientProperty(FlatClientProperties.STYLE, "background:$Menu.background;"); // demo version - JLabel lbDemoVersion = new JLabel("Backup Manager: v" + ConfigKey.VERSION.getValue()); - lbDemoVersion.putClientProperty(FlatClientProperties.STYLE, "" + + JLabel version = new JLabel("Backup Manager: v" + ConfigKey.VERSION.getValue()); + version.putClientProperty(FlatClientProperties.STYLE, "" + "foreground:$Label.disabledForeground;"); - panel.add(lbDemoVersion); + panel.add(version); return panel; } diff --git a/src/main/java/backupmanager/gui/themes/PanelThemes.java b/src/main/java/backupmanager/gui/themes/PanelThemes.java index 32efc18f..e0e462c1 100644 --- a/src/main/java/backupmanager/gui/themes/PanelThemes.java +++ b/src/main/java/backupmanager/gui/themes/PanelThemes.java @@ -4,7 +4,7 @@ import com.formdev.flatlaf.extras.FlatAnimatedLafChange; import com.formdev.flatlaf.util.LoggingFacade; import net.miginfocom.swing.MigLayout; -import backupmanager.utils.DemoPreferences; +import backupmanager.utils.AppPreferences; import javax.swing.*; import javax.swing.border.Border; @@ -144,7 +144,7 @@ public ThemesInfo getElementAt(int index) { private void selectedCurrentLookAndFeel() { LookAndFeel lookAndFeel = UIManager.getLookAndFeel(); - String theme = UIManager.getLookAndFeelDefaults().getString(DemoPreferences.THEME_UI_KEY); + String theme = UIManager.getLookAndFeelDefaults().getString(AppPreferences.THEME_UI_KEY); String lafClassName = lookAndFeel.getClass().getName(); for (int i = 0; i < themes.size(); i++) { ThemesInfo ti = themes.get(i); @@ -152,7 +152,7 @@ private void selectedCurrentLookAndFeel() { themesList.setSelectedIndex(i); break; } - if (theme != null && ti.resourceName != null && theme.substring(DemoPreferences.RESOURCE_PREFIX.length()).equals(ti.resourceName)) { + if (theme != null && ti.resourceName != null && theme.substring(AppPreferences.RESOURCE_PREFIX.length()).equals(ti.resourceName)) { themesList.setSelectedIndex(i); break; } @@ -186,13 +186,13 @@ private void setThemes(ThemesInfo themesInfo) { showInformationDialog("Failed to create '" + themesInfo.lafClassName + "'.", e); } } else { - String theme = UIManager.getLookAndFeelDefaults().getString(DemoPreferences.THEME_UI_KEY); - if (theme != null && themesInfo.resourceName.equals(theme.substring(DemoPreferences.RESOURCE_PREFIX.length()))) { + String theme = UIManager.getLookAndFeelDefaults().getString(AppPreferences.THEME_UI_KEY); + if (theme != null && themesInfo.resourceName.equals(theme.substring(AppPreferences.RESOURCE_PREFIX.length()))) { return; } FlatAnimatedLafChange.showSnapshot(); IntelliJTheme.setup(getClass().getResourceAsStream(THEMES_PACKAGE + themesInfo.resourceName)); - DemoPreferences.getState().put(DemoPreferences.KEY_LAF_THEME, DemoPreferences.RESOURCE_PREFIX + themesInfo.resourceName); + AppPreferences.getState().put(AppPreferences.KEY_LAF_THEME, AppPreferences.RESOURCE_PREFIX + themesInfo.resourceName); } FlatLaf.updateUI(); FlatAnimatedLafChange.hideSnapshotWithAnimation(); diff --git a/src/main/resources/data/customers-1000.csv b/src/main/resources/data/customers-1000.csv deleted file mode 100644 index 2dda2b4e..00000000 --- a/src/main/resources/data/customers-1000.csv +++ /dev/null @@ -1,1001 +0,0 @@ -#,First Name,Last Name,Company,City,Country,Email,Subscription Date,Website -1,Andrew,Goodman,Stewart-Flynn,Rowlandberg,Macao,marieyates@gomez-spencer.info,7/26/2021,http://www.shea.biz/ -2,Alvin,Lane,"Terry, Proctor and Lawrence",Bethside,Papua New Guinea,alexandra86@mccoy.com,6/24/2021,http://www.pena-cole.com/ -3,Jenna,Harding,Bailey Group,Moniquemouth,China,justincurtis@pierce.org,4/5/2020,http://www.booth-reese.biz/ -4,Fernando,Ford,Moss-Maxwell,Leeborough,Macao,adeleon@hubbard.org,11/29/2020,http://www.hebert.com/ -5,Kara,Woods,Mccarthy-Kelley,Port Jacksonland,Nepal,jesus90@roberson.info,4/22/2022,http://merritt.com/ -6,Marissa,Gamble,Cherry and Sons,Webertown,Sudan,katieallison@leonard.com,11/17/2021,http://www.kaufman.org/ -7,Julie,Cooley,"Yu, Norman and Sharp",West Sandra,Japan,priscilla88@stephens.info,3/26/2022,http://www.sexton-chang.com/ -8,Lauren,Villa,"French, Travis and Hensley",New Yolanda,Fiji,colehumphrey@austin-caldwell.com,8/14/2020,https://www.kerr.com/ -9,Emily,Bryant,"Moon, Strickland and Combs",East Normanchester,Seychelles,buckyvonne@church-lutz.com,12/30/2020,http://grimes.com/ -10,Marie,Estrada,May Inc,Welchton,United Arab Emirates,christie44@mckenzie.biz,9/3/2020,https://www.salinas.net/ -11,Nichole,Cannon,Rios and Sons,West Devon,Burundi,blandry@henson-harris.biz,4/26/2021,http://www.humphrey.org/ -12,Bernard,Ritter,Bradford and Sons,West Francisco,Palau,tammiepope@arroyo-baldwin.com,1/19/2022,http://sellers.biz/ -13,Darryl,Archer,Kerr-Cherry,Holtfurt,Uganda,woodalejandro@skinner-sloan.biz,4/18/2022,https://www.daniels.com/ -14,Ryan,Li,"Hooper, Cross and Holt",Batesville,Liechtenstein,lmassey@duke.com,3/6/2021,http://nunez.com/ -15,Vicki,Nunez,"Leonard, Galvan and Blackburn",Barbaraborough,Haiti,zgrant@sweeney.com,1/30/2022,https://reynolds.com/ -16,Sean,Townsend,Preston-Sosa,Velasquezberg,Iran,lkline@maxwell.info,5/30/2020,http://www.vargas.biz/ -17,Sophia,Mathis,Richard-Velasquez,Toddhaven,Switzerland,brockmason@faulkner-may.com,1/23/2020,http://www.vaughn.com/ -18,Helen,Potts,"Rangel, Livingston and Patel",Douglasland,Seychelles,carrollmia@donovan-keith.com,7/27/2021,http://www.kennedy-edwards.biz/ -19,Joann,Finley,Harvey PLC,Barrettshire,Montserrat,gabriela86@sampson.com,4/11/2022,http://www.harrell.com/ -20,Thomas,Walsh,Best-Thomas,Roblesport,Kiribati,timcoleman@frank-king.org,9/11/2020,http://www.kane.com/ -21,Cristina,Lam,Watts-Allison,West Jocelynfort,Korea,charlotte16@hood-zhang.org,1/4/2020,http://whitehead.net/ -22,Vicki,Heath,"Cherry, Schultz and Ruiz",Port Cameronbury,Bangladesh,alan46@benjamin.com,11/6/2020,https://www.bird.com/ -23,Glenn,Wang,Warner-Hodge,West Rachael,Gabon,anna80@mata.com,1/1/2022,http://brooks-kerr.com/ -24,Darius,Benitez,Giles LLC,Mejiashire,Jersey,garrettdurham@olsen.com,2/28/2022,https://washington.com/ -25,Xavier,Cruz,Wiley Ltd,Mindyborough,Latvia,andersongrant@pugh.com,2/7/2020,https://www.cohen.info/ -26,Douglas,Galloway,Duffy Inc,Eileenbury,Mongolia,caleb11@velazquez.com,10/24/2021,http://www.mcneil.net/ -27,Phyllis,Becker,Oneal and Sons,East Andre,Bouvet Island (Bouvetoya),darrylshort@bright-tucker.com,7/2/2020,https://www.farrell.com/ -28,Ebony,Murphy,Barry-Martinez,Atkinsfurt,Vanuatu,vpowers@moyer.com,2/17/2020,http://www.dorsey.com/ -29,Tyler,Stevenson,Burns and Sons,North Joannashire,Sri Lanka,mmayo@gilbert.com,3/2/2022,http://www.fry.org/ -30,Cesar,Bernard,Potter-Ho,Mccormickville,Iraq,damon31@grant-morrison.com,9/4/2021,https://bryan-walters.com/ -31,Darrell,Santos,Boone-Dawson,Lake Dwayne,Liechtenstein,hdaniels@mason.com,11/4/2021,http://hammond.com/ -32,Amanda,Santiago,Roberts-Heath,Benjaminchester,United Arab Emirates,emmarasmussen@roman-walter.biz,5/6/2021,http://gillespie.com/ -33,Marcus,Mcdonald,Hays-Howell,East Brad,Azerbaijan,cesar71@vang-wagner.com,6/25/2020,https://www.williams-mclaughlin.org/ -34,Lauren,Montes,Cohen-Copeland,Tonyville,Armenia,hodgebrenda@roach-winters.com,5/6/2021,https://www.garcia.com/ -35,Brent,Hinton,"Petersen, Blackburn and Meyers",Tommyland,Mexico,greg77@patel.biz,12/3/2021,http://aguirre.biz/ -36,Jill,Mayo,"Woodard, Haas and Mason",Port Carlside,Cuba,brandypowers@christensen.com,10/4/2020,http://briggs-johnston.com/ -37,Herbert,Byrd,Sheppard-Dougherty,Kimmouth,South Georgia and the South Sandwich Islands,gilloscar@webb.com,2/9/2020,https://www.rowland-lyons.com/ -38,Don,Krueger,"Cortez, Hester and Villegas",Melendezland,Guinea,trevor14@harvey.com,3/2/2020,http://www.combs.com/ -39,Cheryl,Gonzales,Walton-Drake,Pittmanmouth,Kenya,yvette30@haas-oneill.org,2/26/2020,http://www.tran-juarez.net/ -40,Rickey,Mays,"Escobar, Carrillo and Sloan",Hollandshire,United States of America,cmcdowell@riley-wolf.org,1/1/2020,http://www.nolan.com/ -41,Cassidy,Dillon,Coleman LLC,New Ebony,Liechtenstein,patrick43@stout.com,12/19/2021,https://www.meyer.com/ -42,Christina,Bautista,Lane Ltd,Lake Don,Turks and Caicos Islands,phenry@tate.biz,11/1/2020,https://warner.com/ -43,Alexandra,Castro,"Wall, Clay and Mcintosh",South Lynnton,Swaziland,swalters@harvey.biz,4/11/2021,http://www.vasquez-boyer.biz/ -44,Krystal,Mendoza,"Logan, Boyle and Villegas",West Henry,Panama,mjackson@david.com,9/9/2020,http://www.marks-ray.info/ -45,Ivan,Schroeder,"Peck, Nicholson and Knox",Port Grace,Saint Pierre and Miquelon,qgeorge@singh.com,8/16/2020,https://melton-alexander.biz/ -46,Stephanie,Bradshaw,Tanner LLC,East Paulaville,American Samoa,debbie56@baker-olsen.com,3/5/2022,http://jimenez.biz/ -47,Levi,Grimes,"Carpenter, Chang and Bass",Frederickfurt,Heard Island and McDonald Islands,robertmarks@willis.com,6/12/2021,https://cherry.com/ -48,Peter,Sosa,Hensley and Sons,Donaldton,Sao Tome and Principe,frederick02@gross.com,7/17/2021,http://www.chandler.info/ -49,Valerie,Haney,"Delgado, Rubio and Rose",Harryview,Cuba,yowens@erickson-charles.com,3/22/2020,https://www.fowler-alvarado.biz/ -50,Tom,Gardner,Werner-Bean,New Samuel,Barbados,darin81@callahan.com,1/4/2022,http://yang-everett.com/ -51,Randall,Galloway,Brady Inc,Brightburgh,Isle of Man,stuart07@reid.com,12/18/2020,https://hatfield-huff.biz/ -52,Perry,Whitaker,Odom LLC,North Jocelynberg,Western Sahara,jennynorton@randall.org,4/27/2021,http://www.hall.com/ -53,Gloria,Mosley,Calderon Ltd,East Reneefurt,Fiji,escobardeanna@sawyer-obrien.info,5/6/2022,https://www.huber.info/ -54,Cameron,Little,Howell Group,North Angel,Netherlands,stephen57@sellers.com,11/13/2020,https://collins.org/ -55,Glen,Gonzalez,Zamora-Ellis,Lake Isabelberg,Sudan,jschmidt@gardner-maldonado.com,7/21/2020,http://holt-mendez.info/ -56,Melvin,Day,"Alvarez, Gaines and Sweeney",Jackbury,Reunion,sheila46@ewing.org,12/22/2021,http://becker-warner.net/ -57,Kent,Salinas,"Shelton, Robinson and Smith",Nicolasfurt,Guatemala,dustindelgado@west-khan.com,2/21/2020,https://www.mora-tapia.info/ -58,Stacey,Martinez,"Rasmussen, Bauer and Lyons",Mauriceview,Uzbekistan,mikayla38@lawson-dougherty.com,3/22/2020,https://newman-townsend.com/ -59,Jennifer,Fleming,Schaefer-Chambers,Shepherdfort,Barbados,stoutalexandria@meza.com,11/24/2020,http://marsh.com/ -60,Teresa,Oconnell,"Mayo, Buchanan and Owen",Lake Douglas,Turkmenistan,hhahn@cantrell.net,12/4/2021,https://www.ellison-strickland.org/ -61,Bruce,Bass,"Day, Wiley and Mclaughlin",Juarezbury,China,gvang@woods.info,8/10/2020,http://www.goodwin.com/ -62,Sarah,Sweeney,Madden-Ho,Mejiamouth,Equatorial Guinea,lauren93@daniel-farley.com,7/17/2020,http://www.welch.com/ -63,Eddie,Salinas,Howard Group,North Frederick,Cook Islands,franklinweiss@porter.net,11/25/2020,https://www.joyce.com/ -64,Trevor,Rowland,"Ritter, Fox and English",Deanchester,Nigeria,simpsonraven@liu.com,3/4/2022,http://noble-beard.com/ -65,Marcus,Chang,"Maynard, Lambert and Blake",North Monica,Western Sahara,deborah61@wagner.com,5/20/2021,http://shaw.com/ -66,Sabrina,Roberts,Stewart-Diaz,Port Pennyton,Bhutan,brittneypotter@boyd-compton.com,1/10/2021,https://walter.com/ -67,Norman,French,Becker-Mata,South Emma,Marshall Islands,spearsfrank@mclean.com,3/27/2021,https://mccullough.info/ -68,Lonnie,Novak,Hayden Inc,East Malloryville,French Guiana,manuel48@raymond.net,3/22/2021,http://www.knight.com/ -69,Casey,Bauer,Houston-Woodard,New Brettfurt,Reunion,marilynbender@daugherty.net,4/4/2020,https://crawford.org/ -70,David,Goodman,"Macdonald, Byrd and Williams",Juliantown,Nigeria,ylutz@sawyer.com,7/15/2020,https://rhodes-ellison.org/ -71,Garrett,Rosario,Becker-Terrell,Marissaland,Philippines,christiegeorge@dominguez.com,5/8/2021,https://www.wright.com/ -72,Colin,Vaughan,"Mooney, Reed and Ingram",New Shelleyfort,United States Virgin Islands,wilsonyvonne@mcmillan.com,10/8/2020,https://choi.com/ -73,Maxwell,Griffin,Mcdowell-Adkins,North Kentland,Turkmenistan,priscillaharrell@glass.com,10/1/2020,http://ray.com/ -74,Diamond,Barnett,"Walker, Andersen and Reeves",New Patriciamouth,Trinidad and Tobago,gibbsemily@fisher.biz,1/4/2022,https://mcdowell-compton.biz/ -75,Kellie,Munoz,Flynn-Chapman,New Samantha,Greenland,lynnbooth@leach-lang.com,7/8/2021,http://www.mclaughlin.com/ -76,Sandy,Finley,Shah-Hanna,Glasshaven,Kiribati,hmora@brock.com,1/4/2021,https://www.estrada.biz/ -77,Katelyn,Petersen,Solis-Hardin,South Patricia,Saint Vincent and the Grenadines,santosebony@foley.com,7/4/2021,http://carr-holder.com/ -78,Neil,Murray,Washington-Ramirez,East Stephanie,Afghanistan,englishstefanie@braun.com,3/20/2022,http://ritter.com/ -79,Carlos,Wilcox,"Vega, Yoder and Ayala",East Katelynmouth,Slovenia,toddlove@rogers.info,10/11/2021,https://little.info/ -80,Adrienne,Lamb,"Henderson, Vega and Jensen",South Karl,Gambia,butlerroberta@mullen-pittman.net,5/3/2021,http://www.mann.com/ -81,Traci,Levy,"Simon, Flores and Carr",New Erica,Kiribati,camposherbert@lang.com,2/19/2022,http://www.burke-glover.com/ -82,Tammy,Harmon,Kidd-Stone,Chaneychester,Yemen,abigail05@mckee.info,9/1/2020,https://www.walsh-archer.com/ -83,Nicholas,Arias,Yoder-Bowen,Lake Gavinburgh,Holy See (Vatican City State),boonealex@cardenas.info,1/31/2020,http://www.wood.com/ -84,Sydney,Solis,"Wu, Strong and Flynn",Welchburgh,Suriname,ortegashane@li.com,2/22/2020,https://anderson-suarez.org/ -85,Jody,Beltran,Buchanan-Barton,Kristihaven,Heard Island and McDonald Islands,fritzandres@morales.biz,8/14/2020,https://www.martin.biz/ -86,Autumn,Choi,Bates LLC,Port Scott,Uganda,varmstrong@braun.org,12/7/2020,http://www.fritz-galloway.com/ -87,Chelsey,Boyer,"Goodman, Carrillo and Stein",Selenaville,Morocco,jonathon78@werner.com,4/17/2022,https://www.willis-todd.net/ -88,Trevor,Key,"Escobar, Adams and Huber",New Ryan,Palestinian Territory,bunderwood@owen.com,1/3/2022,http://webb-barnes.info/ -89,Bridget,Molina,Greene-Mays,East Brookebury,Chad,jcochran@burgess-costa.com,2/13/2021,https://grimes.net/ -90,Calvin,Rocha,Werner-Key,Wayneborough,Cameroon,riveraisabel@harmon.net,11/28/2020,https://salinas-peck.com/ -91,Austin,Matthews,"Sandoval, Parker and Mcdowell",Aimeeville,Macedonia,stanleymeagan@gilmore-newton.com,7/30/2021,https://hubbard.com/ -92,Molly,Murphy,Mercado PLC,Ericamouth,British Virgin Islands,kathyhuff@white-liu.com,12/1/2021,http://cooley.com/ -93,Jeremy,Haynes,"Cruz, Roach and Lynch",Breannaton,Central African Republic,lawsonmicheal@atkins.com,12/12/2020,http://www.gibbs.com/ -94,Don,Henry,"Giles, Kerr and Stafford",Marcusburgh,China,diamondhinton@mccormick.biz,10/18/2020,https://arnold.com/ -95,Dakota,Bowman,Gomez-Tapia,West Barbarafurt,China,branchmegan@dougherty.com,4/17/2022,https://deleon-avery.com/ -96,Manuel,Maynard,"Ellison, Berger and Osborn",Lake Calvin,Niger,xingram@le.com,2/28/2020,https://richards-jarvis.com/ -97,Howard,Simmons,Winters-Cohen,Mitchellmouth,Romania,ronnie03@bird-hood.com,8/21/2020,https://cordova.com/ -98,Jeffery,Wall,Santos-Barnett,Jonathonhaven,Cambodia,wanda50@webster.com,2/18/2022,http://www.dickerson.com/ -99,Colleen,Estes,"Garrett, Sharp and Kaufman",Lake Alisonside,Greece,tricia22@barrera.com,4/6/2020,http://www.richmond.com/ -100,Bianca,Henry,Harrell-Johns,Garrettstad,Albania,alan83@olson.com,4/13/2020,http://www.wilcox-burns.biz/ -101,Michelle,Good,"Randall, Harding and Powers",New Johnnychester,Korea,yeseniacallahan@hinton.com,12/21/2020,http://chavez-clay.com/ -102,Eileen,Skinner,Yang Ltd,New Kristophershire,Mexico,julian74@ritter-mccoy.info,3/27/2021,https://mays.com/ -103,Kyle,Richmond,Hart Group,New Arielfurt,Dominican Republic,adriennecarson@gallagher-mckinney.org,9/23/2020,http://mcclure.com/ -104,Omar,Davies,"Rich, Oneill and Daniels",Dicksonburgh,Guinea,hreid@jordan-pena.com,9/2/2021,http://www.best.biz/ -105,Chelsea,Giles,Curtis Inc,Evanfort,Kuwait,sharon31@stanley.info,11/26/2021,https://griffin-willis.com/ -106,Pam,Crane,Patton-English,East Taylorborough,Cameroon,luisreynolds@caldwell.com,10/27/2020,http://rich.info/ -107,Sandy,Kaufman,Gillespie-Kemp,East Taylorville,Antigua and Barbuda,carlaburton@tran-wu.info,6/11/2020,http://www.schaefer.org/ -108,Madison,Clark,"Atkinson, Benitez and Tapia",Cristianmouth,Nigeria,ikeith@richards.com,5/19/2022,http://www.mack.info/ -109,Leah,Coffey,Rasmussen-Frederick,Lake Darrell,Slovenia,felicia85@joseph.com,1/20/2020,http://terrell.net/ -110,Julie,Montgomery,"Brady, Le and Sherman",New Mathew,Northern Mariana Islands,jose39@huber.com,12/13/2021,http://whitehead.biz/ -111,Darrell,Small,Nicholson LLC,Lake Warrenmouth,Reunion,boyerjoy@gross-meadows.info,3/19/2022,http://www.sherman.com/ -112,Andrew,Bolton,Sutton-David,East Nathanmouth,United Arab Emirates,meghanharmon@travis.biz,4/27/2020,http://olson-collier.com/ -113,Jaime,Hayden,Higgins Ltd,Mullenmouth,Iran,mrowe@forbes-holmes.org,12/10/2021,http://randolph-stewart.info/ -114,Logan,Carney,Jensen-Crawford,Omarport,Gibraltar,rebecca89@marquez.net,10/31/2020,http://www.giles.com/ -115,Pedro,Franco,"Acevedo, Blanchard and Deleon",West Sharonville,Oman,hunterjenkins@cervantes.com,7/14/2021,https://bernard.com/ -116,Daryl,Meza,Roberts-Curry,West Jennifer,Hungary,conneraaron@bryant.net,1/12/2021,https://www.price-lyons.info/ -117,Haley,Levine,Bowers-Nichols,Jacobfort,Tuvalu,hamiltongreg@perkins.com,4/19/2020,https://www.salinas-roth.biz/ -118,Caitlyn,Vazquez,"Burnett, Carter and Shah",New Bruce,Maldives,ebush@jimenez.com,8/6/2020,https://www.blair.com/ -119,Keith,Combs,"Bryant, Blankenship and Orozco",North Pattystad,Vietnam,tommy24@wong-ray.com,10/21/2020,http://hernandez.com/ -120,Hayden,Cline,"Garrison, Kelley and Choi",Julianberg,Ireland,kristinebaldwin@holloway-sharp.com,6/15/2020,http://www.maynard.biz/ -121,Jeanette,Sanford,"Sutton, Doyle and Velez",Wadeborough,Dominica,daviesmatthew@turner.com,7/21/2021,http://www.santiago.com/ -122,Brandon,Richmond,Gould Ltd,Beardfort,Pakistan,fmcgee@foster.com,1/5/2020,http://barron-terry.com/ -123,Latasha,Miller,Romero Group,East Glenfort,Aruba,wcarter@ali.info,1/5/2020,http://www.atkinson.net/ -124,Shaun,Luna,"Sparks, Garcia and Maxwell",West Emma,Uganda,mshaw@cantu-le.net,12/18/2021,http://www.pennington.com/ -125,Allen,Mayer,Giles-Mooney,West Terrenceburgh,Comoros,danielsalinas@deleon.com,5/28/2021,https://garner.com/ -126,Yvonne,Jordan,"Oneal, Barker and Kaufman",South Caseyside,Timor-Leste,mercedes83@gill.org,10/23/2020,https://www.cohen-king.org/ -127,Joanne,Miranda,Perkins LLC,Aguilarchester,Niue,fernandoshaw@goodwin.org,9/5/2020,http://www.wolf.com/ -128,Jaclyn,Rice,Madden-Lewis,New Saraberg,Spain,tamara04@tate.biz,9/2/2021,https://diaz.com/ -129,Glen,Conway,"Bullock, West and Becker",Corystad,Nigeria,don46@freeman.net,11/24/2021,https://www.nichols.info/ -130,Stacey,Travis,Medina-Castro,Dudleyfurt,Ethiopia,ygarcia@andrade.com,1/16/2021,http://schaefer.com/ -131,Courtney,Hughes,Benitez LLC,Knoxfurt,Marshall Islands,gary98@carpenter-nelson.com,11/24/2020,https://www.knight.net/ -132,Raven,Nelson,"Suarez, Hull and Key",East Kristenfort,Luxembourg,fashley@burns-mckenzie.com,10/23/2020,https://www.rodgers.net/ -133,Kyle,Odonnell,Andrews-Harmon,Wallacemouth,Gabon,ywise@winters.net,2/18/2020,http://www.cook.biz/ -134,Sherry,Ponce,Petty Ltd,Holdenfurt,Isle of Man,vjacobson@perkins-dunlap.net,1/12/2022,https://dillon.info/ -135,Kirk,Villa,"Norris, Bailey and Campbell",Ericaside,Myanmar,parkroy@baxter.com,4/27/2020,https://www.barrett.com/ -136,Luke,Lucas,Snow-Avila,Pagebury,Belgium,masonshelley@freeman.org,9/26/2021,http://hanna.com/ -137,Lynn,Tran,Ware LLC,Latoyaside,Tunisia,marisa90@huynh.com,1/5/2021,http://whitaker.biz/ -138,Brian,Beasley,Chaney-Porter,North Daryl,Burkina Faso,stephensmike@bartlett-wade.com,10/15/2021,https://esparza.com/ -139,Christopher,Savage,Armstrong-Contreras,Port Isabellachester,Iran,debbieramos@davies-washington.biz,8/3/2021,http://www.vega.com/ -140,Dominique,Mckinney,"Sharp, Fleming and Gregory",Port Erin,Kazakhstan,felicia57@fletcher.com,2/28/2020,http://bradford.com/ -141,Dwayne,Crane,Mcdaniel and Sons,North Jessicaview,Montenegro,logan04@hines.biz,2/12/2022,http://www.harmon.biz/ -142,Autumn,Cuevas,Hahn Ltd,Mccoyfort,Burundi,jeffreyharding@johnson.com,2/5/2020,http://mcdowell-henry.com/ -143,Gregory,Collins,Fleming Inc,Port Grantton,Micronesia,tina43@hayes-johnson.com,10/2/2020,http://summers-chang.com/ -144,Isaac,Schmidt,Clements-Ayala,West Jasminfort,Ukraine,paulakane@singh.com,12/7/2021,https://ochoa-chapman.org/ -145,Bradley,Rangel,Castillo and Sons,Lake Bianca,Montserrat,underwoodangel@gallagher.info,12/5/2020,https://www.summers.org/ -146,Paige,Page,"Mullins, James and Herman",Kaufmanfurt,French Guiana,aodonnell@prince.com,4/2/2022,https://suarez-sims.org/ -147,Gwendolyn,Bradshaw,"Gay, Bush and Goodman",East Jonathan,Mali,eugene43@mccall.com,5/1/2021,http://ramirez.com/ -148,Belinda,Ferguson,"Lewis, Bowman and Craig",Moralesport,Lao People's Democratic Republic,billspears@harmon.org,1/2/2020,https://huff.com/ -149,Ivan,Hines,Aguilar Ltd,Lake Samantha,Qatar,tflowers@salinas.org,11/25/2021,https://frazier.com/ -150,Brett,Lin,"Mccoy, Larsen and Stevens",Nortonmouth,Saint Helena,stokespamela@koch.com,2/2/2021,http://serrano.com/ -151,Katherine,Williams,Kelly PLC,Juarezville,Armenia,jorozco@hinton-klein.org,12/7/2020,https://www.key-zamora.com/ -152,Andre,Burgess,Zhang-Stevenson,Mooremouth,Moldova,mayerlynn@haas-santos.org,5/12/2020,https://huynh.com/ -153,Laura,Decker,"Levy, Moyer and Fernandez",West Pamela,South Georgia and the South Sandwich Islands,vstuart@fowler-novak.com,8/3/2020,http://www.peterson-hughes.net/ -154,Tommy,Herman,Espinoza-Tyler,North Robert,United States Minor Outlying Islands,spencejennifer@bowman-pena.com,12/27/2020,http://harvey.com/ -155,Amber,Lyons,Ward-Mcintosh,South Kaitlyn,Zambia,betty81@shields.org,7/24/2020,https://ali.net/ -156,Shelia,Yang,"Coffey, Watson and Wilkins",Lake Edwintown,American Samoa,cynthia09@vang.com,12/10/2021,http://arellano.com/ -157,Russell,Martin,Duffy-Zuniga,East Megan,Angola,cody74@cochran-keith.com,5/24/2020,http://hurst.net/ -158,Yvette,Willis,"Hamilton, Solis and Salazar",South Tamarafort,Kuwait,neil44@barron.com,11/13/2020,https://navarro.com/ -159,Don,Ho,Stark-Glover,Riosport,Uganda,rwallace@shepherd-mcdowell.com,2/12/2022,https://www.ellison.com/ -160,Ellen,Torres,Cook PLC,New Chelsey,Kuwait,carla57@roberts.com,8/31/2021,http://padilla.net/ -161,Hayley,Morse,"Henry, Mcdonald and Austin",Lorrainefort,Iraq,fchung@montes.com,4/18/2020,http://walton.com/ -162,Ellen,Kerr,Duncan LLC,Port Jocelyn,Micronesia,vnoble@mooney.org,1/27/2020,https://www.vega.com/ -163,Martha,Cruz,Copeland-Freeman,New Kaylashire,Bolivia,collinsalejandro@arroyo.com,2/12/2020,http://rangel-blake.org/ -164,Douglas,Carson,Ferguson Ltd,Branchview,Israel,ethanle@gibson.com,4/26/2022,http://wong-strickland.com/ -165,Toni,Cline,Robles-Davies,Ortegaside,Ecuador,kyleramos@lee.com,3/18/2020,https://www.glover.com/ -166,Robyn,Berger,Suarez Group,South Karen,Lesotho,amatthews@owens.com,4/28/2020,http://www.brewer.com/ -167,Calvin,Roach,"Wilkins, Goodman and Cummings",North Melvin,Wallis and Futuna,kathleenbrewer@sweeney.com,1/4/2022,https://www.ward.com/ -168,Angel,Park,"Barry, Thomas and Oconnor",South Marciafurt,Morocco,masonadriana@price.com,3/29/2020,https://yang-roach.com/ -169,Bradley,Delacruz,Mathis-Rocha,Ortegaland,Cote d'Ivoire,khendrix@powers-levine.biz,5/26/2020,https://whitaker.com/ -170,Donald,Cross,"Donovan, Key and Leblanc",Bruceville,Saint Helena,srodriguez@hester.info,1/25/2020,http://www.grant-campos.net/ -171,Jeremiah,Guerrero,"Cole, Franco and Alvarez",New Erikville,French Polynesia,mosskevin@perkins.com,5/11/2022,http://montes.com/ -172,Perry,Trujillo,"Yoder, Watkins and Singh",Lake Joyton,Panama,geoffrey16@gentry.com,11/13/2021,https://www.webster.com/ -173,Brent,Beasley,Mendoza Group,South Kimberly,Antigua and Barbuda,bettymckinney@houston.com,4/14/2022,http://edwards-nguyen.net/ -174,Ariana,Velasquez,Stokes-Haney,South Codyfurt,Ireland,makayla22@carey-james.com,8/2/2020,https://www.orozco-santiago.com/ -175,Don,Vincent,Norton-Watkins,Sanfordmouth,Luxembourg,jarvislarry@lang.info,3/22/2020,https://www.mahoney.org/ -176,Bradley,Blair,"Mullins, Huber and Dillon",Franciscochester,Montenegro,masseyriley@blanchard.com,11/1/2021,https://welch.com/ -177,Henry,Conway,"Clarke, Fleming and Porter",Fordton,Saint Kitts and Nevis,qrusso@le.com,7/7/2020,https://www.hansen.info/ -178,Norman,Waters,Welch-Romero,West Stacey,Rwanda,meganwilkinson@bird-anderson.com,10/14/2020,http://www.schultz-zamora.com/ -179,Breanna,Miranda,Mejia Group,Colinberg,India,claytoncastillo@schroeder.org,4/28/2021,https://larson.com/ -180,Seth,Osborne,Rollins-Carson,East Kaylee,Holy See (Vatican City State),xcoleman@farrell-bernard.com,1/1/2022,http://www.bradford-rivas.com/ -181,Lydia,Ponce,Fitzgerald Inc,New Eduardo,Mongolia,lauramorris@anthony-bullock.com,5/24/2022,http://hahn.com/ -182,Sherri,Bradshaw,"Ramos, Suarez and Jacobs",Dariusview,Kuwait,mossangela@farmer.com,4/5/2020,https://www.may.com/ -183,Alejandra,Lowe,Benjamin Group,East Petermouth,Western Sahara,andre80@dodson.net,9/27/2020,http://wilcox-liu.com/ -184,Raymond,Bernard,Odom-Hull,Lake Karlaburgh,Burkina Faso,adam45@tanner.com,3/6/2020,http://www.collier.net/ -185,Patricia,Moss,Acevedo-Rasmussen,North Stacey,Belarus,pfleming@french.com,10/16/2021,https://www.underwood.com/ -186,Hector,Meyers,Hanna-Ortiz,New Mario,Tanzania,bowersjessica@arellano-hart.com,11/19/2021,https://massey.info/ -187,Pedro,Zhang,Conway-Stewart,Jacobmouth,Kazakhstan,barronsergio@valencia-proctor.com,7/11/2021,https://www.barajas.com/ -188,Marco,Donaldson,Wagner and Sons,West Aprilberg,Canada,holmesdwayne@sheppard.biz,7/22/2021,https://alvarado.info/ -189,Shannon,Yoder,Whitney Ltd,Jofort,Nepal,laura10@romero.com,7/17/2020,https://www.lang-ellison.org/ -190,Brian,Downs,Ramirez-Thompson,New Anitafurt,Kazakhstan,dcombs@mcneil.org,8/3/2020,http://www.rangel.com/ -191,Dillon,Cooley,Luna and Sons,Port Micheleville,Ireland,cody25@watkins.com,6/28/2020,http://bullock.com/ -192,Joyce,Chaney,"Stanton, Lane and Schmitt",North Francis,Luxembourg,joannarusso@nelson.com,1/26/2020,https://www.chapman-short.biz/ -193,Angelica,Schaefer,"Jackson, Gibbs and Parker",Lake Sydney,Sudan,mark43@stevenson-garcia.com,1/18/2021,https://whitehead-payne.com/ -194,Marcia,Horton,"Carter, Ford and Matthews",West Kendra,Kiribati,mcdowelljenna@beck-lewis.com,5/17/2022,http://arnold-morse.com/ -195,Sydney,Barr,Weiss LLC,East Courtneyview,Micronesia,carlosstewart@deleon-griffith.com,2/5/2022,https://hurley-blankenship.net/ -196,Jay,Hodge,Wolf Ltd,South Rebekah,Guernsey,fvillarreal@brewer-pena.com,2/6/2021,https://www.carlson.com/ -197,Angela,Jackson,"Reynolds, Patel and Rush",Reynoldsborough,Congo,arthurpetersen@bolton.info,2/6/2022,http://perry.net/ -198,Bethany,Barrera,"Swanson, Figueroa and Heath",Vickietown,South Georgia and the South Sandwich Islands,rhonda48@castro.info,5/29/2022,http://www.cortez.com/ -199,Cindy,Valenzuela,Rojas LLC,Maychester,Chile,maryforbes@oliver-mills.com,4/13/2020,http://www.holmes-wolfe.info/ -200,Christine,Camacho,Pace and Sons,South Daveville,Yemen,aguirrenatalie@randolph-moore.com,2/26/2021,https://www.nolan.com/ -201,Tyrone,Hendrix,"Small, Osborne and Rojas",East Wayne,Vanuatu,javierbarron@mcclure.com,2/15/2020,http://www.collins.com/ -202,Roy,Gould,Beasley Ltd,Jackberg,Montserrat,fmoore@vega.com,4/26/2020,http://www.livingston-stanton.net/ -203,Matthew,Mann,"Benton, Flowers and Snow",Aguirrebury,Papua New Guinea,summer05@harrison-bowen.info,1/6/2021,http://www.swanson.com/ -204,Taylor,Torres,"Gamble, Cooke and Lewis",Port Roberto,British Virgin Islands,ihuerta@lutz.info,12/29/2021,http://medina-williamson.com/ -205,Hannah,Waller,Glenn and Sons,Fritzbury,Turkmenistan,gouldruth@novak-dunlap.com,4/23/2022,http://www.cunningham.com/ -206,Randy,Cannon,Le PLC,South Nancy,French Southern Territories,pcampos@lloyd-leblanc.info,1/21/2022,https://www.henry.com/ -207,Adrienne,Hunter,Escobar-Cannon,Lake Glennside,Kazakhstan,lindsay75@levy-valentine.com,4/2/2022,http://www.wiggins-cuevas.net/ -208,Raven,Weaver,Bray PLC,Adamsfurt,Gibraltar,hutchinsonmartin@shelton-burnett.com,5/17/2021,http://duncan.com/ -209,Isaac,Murillo,Conrad Inc,Port Ryan,Romania,schmittstephen@elliott.biz,5/21/2020,https://www.burgess.biz/ -210,Kelly,Branch,Barton Group,Port Beckymouth,Montserrat,alexandranguyen@carr-gray.net,6/4/2021,https://www.orr-meyers.com/ -211,Chelsey,Larson,Walls Inc,New Yolanda,South Africa,katrinarasmussen@craig.com,5/9/2020,http://www.jimenez.com/ -212,Rick,Marquez,Woods-Warner,New Taylorburgh,Bahrain,josephflynn@rivers.com,9/8/2020,http://foley-porter.biz/ -213,Tricia,Mckee,Macdonald Ltd,Port Kimberly,Turkmenistan,gabriela43@wilson-stanton.biz,5/25/2020,http://www.pham-burton.com/ -214,Rebecca,Blake,Young-Sawyer,East Casey,Somalia,brownvalerie@wade.net,10/11/2021,https://chapman.com/ -215,Dan,Mcmillan,Wise-Mckay,South Jeremy,Uzbekistan,martin28@gibbs-whitney.biz,7/25/2020,http://shea-proctor.org/ -216,Sabrina,Hill,Park-Kaufman,Daveborough,Ireland,omiles@mcdowell.com,3/26/2020,http://koch.org/ -217,Victoria,Walter,"Kline, Cobb and Gregory",Francisside,Malaysia,melanie66@townsend.com,10/7/2020,https://fields.org/ -218,Jorge,Hendrix,"Delacruz, James and Calhoun",Jerrychester,South Africa,cbates@walker.com,6/17/2021,https://hartman-hayes.com/ -219,Jodi,Fox,"Walton, Davenport and Charles",Wallacestad,Eritrea,ashley25@lutz-arellano.biz,6/16/2020,https://estrada.org/ -220,Sara,Vargas,Maynard LLC,Patelhaven,Senegal,erikdalton@hines.org,6/17/2021,https://wilkins-riggs.com/ -221,Cynthia,Johns,Burns-Jimenez,South Shelley,Egypt,sellersmarvin@henry.com,11/20/2020,https://braun.com/ -222,Jordan,Hanna,"Nguyen, Ruiz and Finley",Santosport,Heard Island and McDonald Islands,davidchoi@kim.net,5/19/2021,https://case-hester.biz/ -223,Maurice,Ramsey,"Everett, Humphrey and Zhang",Lake Dillonfort,Antarctica (the territory South of 60 deg S),zturner@jimenez-wells.biz,7/27/2021,https://duke-church.biz/ -224,Catherine,Nicholson,Mckee PLC,North Tracey,Netherlands,bdelgado@henry.com,7/7/2020,https://www.peters-goodwin.com/ -225,Sean,Todd,Rice-Wilkins,New Reginahaven,Bangladesh,uwarner@meza-carrillo.com,3/13/2020,https://www.dougherty.com/ -226,Lacey,Bond,Dougherty-Day,Grantshire,Bangladesh,glenncook@king-garner.com,12/11/2020,https://flynn-frederick.biz/ -227,Katelyn,Wise,"Daugherty, Cooley and Joseph",New Alexa,Switzerland,ndougherty@bentley-lutz.com,12/26/2020,http://www.montes.org/ -228,Christine,Bowers,Davenport-Neal,Jacobmouth,Jamaica,jmorrow@campbell.info,10/3/2020,http://www.faulkner-nelson.com/ -229,Adam,Levy,Conrad Group,Bushview,Nepal,pcuevas@hancock.org,5/15/2020,http://osborn.com/ -230,Hayley,Ellison,Tyler Inc,South Howard,Lao People's Democratic Republic,ameza@cobb-poole.com,2/11/2020,http://cantrell.biz/ -231,Vernon,Warner,Monroe-Mccoy,Zoefurt,Japan,tzimmerman@moore.com,9/8/2021,http://holt.org/ -232,Fernando,Townsend,Wyatt-Henry,Lake Joshuastad,Turkey,kelly08@miller.net,4/12/2020,https://www.west.com/ -233,Walter,Parsons,Owen-Warren,East Alyssa,Mauritania,pboyer@lambert.com,12/24/2021,http://robinson.info/ -234,Brady,Hill,"Hull, Knight and Kerr",Montoyaberg,Saint Kitts and Nevis,yhaley@beasley-boyle.com,9/26/2021,http://pace-cowan.com/ -235,Loretta,Rice,Jimenez-Medina,Port Danburgh,Moldova,poncejackie@mooney-allison.com,11/16/2020,http://silva-shah.com/ -236,Hannah,Beck,"Gay, Ward and Villegas",Port Carlos,Mauritania,reyespaula@velazquez-gillespie.com,7/12/2021,http://ramsey.info/ -237,Jocelyn,Stephens,Macias-Burns,Kelleyview,Algeria,velazquezchloe@fitzpatrick-byrd.com,8/8/2021,http://www.koch-parks.com/ -238,Benjamin,Chan,"Huerta, Potts and Crosby",East Emma,Finland,kari54@short.com,9/21/2020,http://www.li-berry.com/ -239,Caroline,Clarke,Reed-Tucker,North Sydney,San Marino,amckenzie@leonard-newman.com,4/19/2020,https://alvarado.com/ -240,Cameron,Forbes,Cervantes-Hendrix,Rhodesside,Rwanda,marie76@molina.org,9/11/2021,http://carson.com/ -241,Wyatt,Mclean,"Flowers, Kline and Bass",Spencerchester,Dominican Republic,ckrueger@cervantes.com,1/19/2020,https://barnes.com/ -242,Kendra,Waters,Gates Inc,Warnerport,Nicaragua,tracey11@carney.com,1/2/2020,http://www.ayala.com/ -243,Tom,Bradley,Stone Ltd,Lonnieburgh,Trinidad and Tobago,terriblack@huffman-burnett.com,10/22/2020,https://gordon-chen.com/ -244,Adrian,Frazier,Franco Group,Seanport,Ecuador,ballfernando@miranda.com,4/13/2022,http://www.fleming.org/ -245,Beverly,Kirby,"Ruiz, Chase and Villa",Elijahchester,Bangladesh,valentinecarmen@michael.com,4/1/2022,https://www.mays-blevins.info/ -246,Priscilla,Stuart,Peck-Werner,East Max,Liechtenstein,jessehernandez@holder.org,4/16/2021,https://cantrell.net/ -247,Roberto,Hogan,Massey-Hoffman,East Javierfort,Aruba,christiangriffith@newman.com,5/27/2020,http://ferguson.net/ -248,Victor,Rogers,Walls-Randall,Gabriellafort,Kiribati,tannerbrandi@duarte.biz,4/11/2022,http://suarez.com/ -249,Alisha,Gallegos,Montoya-Mccarthy,Lake Christianton,Singapore,bernard01@hammond-delacruz.net,3/26/2022,https://www.hale.com/ -250,Stefanie,Fuller,Keith-Wyatt,Mcleanshire,Ukraine,bonnievilla@briggs.com,10/19/2021,https://www.nicholson-zavala.org/ -251,Jackson,Grimes,Chan-Mcknight,Gayfurt,Kuwait,fullercarly@wells.biz,9/26/2020,http://www.hunter.com/ -252,Miranda,Robles,Maynard-Ramos,Lake Kevin,Andorra,sgarza@thompson.com,10/16/2020,https://www.holder.com/ -253,Gilbert,Bowers,"Russell, Ashley and French",Wyattborough,Bahamas,sfields@sexton-archer.com,4/13/2022,http://nelson.com/ -254,Jon,Gay,"Frey, Howard and Burns",Lake Mike,Kenya,hmelendez@jenkins-ingram.org,2/23/2022,https://www.garza.com/ -255,Julia,Davila,Montoya Group,Port Caleb,Aruba,ericafrederick@beasley.org,2/19/2022,http://rush.com/ -256,Aaron,Potts,Berg-Cannon,Kathrynville,Bulgaria,kathryn22@patel-gross.com,5/21/2021,http://www.burns-kane.com/ -257,Rachael,Jimenez,"Burnett, Vang and Delgado",Alextown,Bermuda,romerotricia@chung.org,4/19/2022,http://yang.info/ -258,Lucas,Macdonald,Navarro-Patterson,West Baileystad,Benin,masseykathryn@sawyer.com,1/22/2020,https://www.morton-monroe.com/ -259,Kristopher,Sanders,Lamb-Oconnell,North Judyville,Comoros,eperry@yates.com,1/1/2021,https://www.warner.info/ -260,Alexandria,Hutchinson,Fry Ltd,East Jake,Azerbaijan,coffeymichele@hawkins-morrison.org,4/3/2020,http://www.collins.biz/ -261,Natasha,Schmitt,Russo PLC,New Tammy,Iceland,kelliewaters@fox.com,2/28/2020,http://alvarado.biz/ -262,Jose,Acosta,"Schmitt, Wyatt and Rice",East Gailhaven,Saint Lucia,brooke89@carson.biz,8/25/2020,http://www.wilson.com/ -263,Cassidy,Dominguez,Dixon-Winters,South Annetteville,Bouvet Island (Bouvetoya),omcfarland@lin-atkinson.biz,4/6/2022,http://lutz.biz/ -264,Debbie,Savage,Carey Inc,Port Franklin,Cape Verde,vsingleton@huynh.info,4/2/2021,http://fleming.com/ -265,Felicia,Burnett,Ortega and Sons,Lake Joyce,Saint Helena,stevensonbeverly@huerta.com,12/7/2020,http://case.info/ -266,Hayley,Gutierrez,Bridges-Keller,Alexandriamouth,Turkey,janice91@stanley.com,3/26/2020,http://www.ortiz-mcmillan.com/ -267,Melinda,Parrish,Carpenter PLC,East Wendy,Cook Islands,burchlee@atkins.info,5/5/2021,http://www.hendrix.net/ -268,Jeremy,Keith,"Petersen, Rivas and Mayo",Bethanystad,Morocco,udurham@singleton-lewis.com,10/3/2021,https://compton.com/ -269,James,Washington,"Fuentes, Park and Poole",East Darin,Tokelau,jacksondana@baird.com,5/15/2021,https://haley-stevens.biz/ -270,Karen,Leblanc,Farley Inc,Deanberg,Denmark,poneill@cameron.com,12/10/2021,https://lozano.org/ -271,Ivan,Malone,Coffey Ltd,South Soniabury,Moldova,seanhiggins@whitney.biz,5/17/2020,http://www.valentine.info/ -272,Jesus,Cox,Rush-Melton,Deniseburgh,Tokelau,aaronmorse@shepard.org,10/18/2021,https://www.morrison-randall.info/ -273,Michelle,Lowery,"Warren, Randall and Durham",Parksfurt,Australia,hthompson@fritz-sparks.com,10/3/2020,https://www.carter.info/ -274,Paul,Meyers,"Pham, Cabrera and Long",Port Mercedesberg,Panama,smckay@escobar.com,2/13/2022,http://robertson-gray.com/ -275,Eileen,Graves,Maxwell-Murillo,Antonioside,Saint Pierre and Miquelon,ddean@gray.com,4/25/2020,https://carlson-murillo.org/ -276,Christian,Jennings,"Lawrence, Mooney and Washington",West Kathryn,Iceland,brett70@juarez-christian.com,6/29/2021,https://www.turner.org/ -277,Don,Hendrix,"Blevins, David and Henderson",Wallaceville,Belgium,zcopeland@arias.com,2/17/2020,http://www.eaton-mitchell.com/ -278,Dominique,Summers,"Estes, Durham and Burgess",Rickyside,Maldives,traceyreynolds@wilson.com,5/21/2022,https://stuart-marsh.com/ -279,Wayne,Anderson,Manning-Pruitt,Haroldhaven,Samoa,sheila72@pollard.biz,5/22/2020,http://knox-owens.info/ -280,Chloe,Franklin,"Mclean, Robles and Orr",South Craig,Comoros,suarezbarbara@cross-baird.info,1/15/2022,http://www.christensen.com/ -281,Carolyn,Hendrix,Jimenez Inc,West Ricky,Anguilla,huntercathy@henry.com,6/21/2021,http://meyers.info/ -282,Isaiah,Buckley,Davies-Hardy,Youngfurt,China,haaszachary@holmes.com,1/19/2021,https://www.lynn.com/ -283,Gina,Townsend,Jacobs Inc,Tommyshire,Solomon Islands,claudia60@farrell-rivas.net,3/19/2020,http://www.daniels.com/ -284,Stefanie,Grant,"Rojas, Gamble and Jensen",New Derrick,Burkina Faso,franciscogomez@klein.com,1/11/2020,http://www.ibarra.com/ -285,Cole,Combs,Rocha-Dawson,Port Gregoryfurt,Guinea-Bissau,sdunn@newton.com,9/21/2020,http://www.curtis.com/ -286,Suzanne,Pope,Haley-Coleman,East Renee,Slovenia,melinda13@andrade-acosta.com,11/1/2020,https://www.carney-rangel.com/ -287,Gabriel,Green,Branch-Barry,Toddside,Central African Republic,qballard@fitzpatrick.com,7/5/2020,https://www.whitehead.net/ -288,Jeanne,Atkinson,"Swanson, Landry and Jackson",Alexfort,Guadeloupe,holderloretta@phillips-hays.com,4/18/2020,http://hughes.org/ -289,Lindsay,Ferguson,Randall-Franco,North Karihaven,Montserrat,marquezhaley@oliver-figueroa.info,12/22/2021,http://ramirez-harding.com/ -290,Ronald,Byrd,"Ramos, Hoover and Saunders",West Dan,Tonga,danielle48@pollard.com,7/11/2020,http://www.sims-barber.info/ -291,Marco,Holden,"Fleming, Parrish and Andersen",Port Beth,Palau,maureen60@chapman-duran.com,9/19/2020,https://hancock.org/ -292,Isabel,English,Leblanc PLC,Anthonyport,Monaco,epatel@ochoa-hoffman.com,7/9/2021,https://www.clark.org/ -293,Nichole,Green,Chambers LLC,New Thomasbury,Tokelau,jpacheco@lynn.org,7/11/2020,https://www.krause.info/ -294,Joann,Gonzales,"Bray, May and Riggs",North Tracieview,Eritrea,earlhawkins@fernandez.com,5/18/2020,http://www.peck-parker.biz/ -295,Francis,Miller,Harrell-Pacheco,Lake Yvetteberg,Dominica,kristidaugherty@burch.info,1/11/2022,https://www.owens-leblanc.com/ -296,Stuart,Valdez,"Dickson, Wilcox and Hatfield",East Glenn,Bhutan,michelleodom@mcconnell.com,3/1/2022,http://www.pollard.com/ -297,Tricia,Berg,Le-Banks,Port Gavin,Iceland,alimelissa@montes-poole.com,11/2/2020,http://www.fitzgerald.net/ -298,Roger,Bauer,Braun-Morton,South Herbertmouth,Kyrgyz Republic,joanne05@barron-perkins.org,5/1/2021,http://www.stevens-clarke.biz/ -299,Tanner,Hernandez,Mooney Inc,Adamston,Bouvet Island (Bouvetoya),sheri28@pollard-drake.org,2/23/2020,http://www.garcia-hernandez.com/ -300,Ellen,Cisneros,Larsen Ltd,Popeland,Belize,whughes@daniel.com,9/14/2020,https://www.carlson-ochoa.com/ -301,Latasha,Hancock,Massey-Myers,West Leroyview,Bangladesh,colleen53@taylor.com,5/16/2021,https://www.soto.com/ -302,Jenna,Lamb,Mosley LLC,Toddborough,Ecuador,frenchcaroline@chavez.com,11/27/2021,http://melendez.com/ -303,Francis,Ball,"Schaefer, Bowers and Li",Port Michaelafort,Equatorial Guinea,robinlove@mckinney.com,2/5/2021,https://www.henson-arellano.net/ -304,Jade,Archer,"Fischer, Vang and Skinner",Berrymouth,Romania,afitzpatrick@bartlett.org,9/2/2020,https://www.schneider.info/ -305,Zachary,Parker,Ewing and Sons,Port Glennborough,Burkina Faso,willie12@norris-pennington.com,12/9/2021,https://yoder.com/ -306,Karen,Kent,Dickerson Inc,Hofurt,Luxembourg,vfletcher@pineda.com,3/3/2021,https://www.willis-hendrix.biz/ -307,Joshua,Dalton,Cross PLC,Lake Troyville,Nepal,msellers@fisher.net,2/24/2021,http://stone.com/ -308,Kiara,Ashley,Watson-Delgado,Francohaven,Nigeria,norma73@martin.com,10/26/2021,https://www.humphrey-floyd.info/ -309,Doris,Greene,"Jacobs, Velazquez and Estrada",Lake Omar,Libyan Arab Jamahiriya,pjones@prince-blackburn.com,10/6/2021,https://freeman.org/ -310,Helen,Kaiser,Petty-Joseph,New Katrinaview,Heard Island and McDonald Islands,jmay@osborn.com,7/14/2021,http://www.gilmore-henson.com/ -311,Ariel,Francis,"Flores, Peters and Leon",Meyerfurt,Switzerland,gcarter@rollins.net,9/1/2020,http://mcdaniel.com/ -312,Heidi,Singleton,"Glenn, Peck and Ashley",East Alberthaven,France,tkelley@richardson.com,5/31/2021,https://day.biz/ -313,Guy,Keller,Callahan-Walters,Lake Morgan,San Marino,kleinmarilyn@frost-baird.biz,6/19/2020,https://www.fuentes.net/ -314,Meghan,Moses,Henry LLC,Bernardmouth,Holy See (Vatican City State),xcannon@leblanc-hays.com,2/23/2022,http://sloan.com/ -315,Jodi,Moran,Ashley-Johns,North Lacey,Solomon Islands,wsmall@jennings.com,5/17/2022,https://www.espinoza-gilmore.biz/ -316,Heather,York,Morrison Ltd,Alexville,Senegal,jesse32@ingram-paul.com,12/25/2020,https://hall.net/ -317,Gail,Salazar,Barnett Group,North Isaacport,Saudi Arabia,alvinbeck@petersen.net,3/10/2021,https://www.peters.com/ -318,Tammy,Cantu,Estes-Evans,North Chelseyland,Senegal,crystal44@holder-berger.com,1/22/2020,http://kennedy.org/ -319,Olivia,Mcgrath,Duran PLC,East Chelseyfurt,Vietnam,claytonday@drake.com,4/6/2021,https://www.mcdonald-ho.org/ -320,Michele,Case,Mathis-Young,South Roberta,Equatorial Guinea,isaachawkins@brock.net,4/5/2022,http://stout.com/ -321,Matthew,Brock,"Gregory, Harrison and Roman",Elizabethburgh,Bhutan,kendramyers@moses.biz,9/30/2020,http://wolf.org/ -322,Jenna,Henry,Clements-Roberts,Tommyshire,Jamaica,xzamora@rogers.net,12/20/2021,http://www.cantrell.com/ -323,Kristina,Hunter,Fry-Melendez,East Isaac,Ecuador,bonnie63@potter-combs.com,1/1/2022,http://jensen.com/ -324,Shirley,Bowman,Roman LLC,East Dianeport,Reunion,nicholasvaldez@wall.com,4/16/2022,http://www.miles-chapman.com/ -325,Allison,Walter,"Frost, Herring and Maxwell",Prestonchester,Sierra Leone,ypage@pittman.com,4/17/2020,http://mcgee.com/ -326,Frances,Beltran,"Hunt, Howard and Black",Lake Lindseyberg,Swaziland,manuelfoster@carr-nunez.com,4/28/2020,http://www.atkins.biz/ -327,Bailey,Waters,Fox-Frey,South Gary,Swaziland,jillian22@wyatt-olsen.net,4/13/2020,http://melton.info/ -328,Jeanne,Horn,Cisneros and Sons,Lake Billy,New Caledonia,murillobrittney@johnston-brady.com,9/9/2021,https://www.ho.net/ -329,Jermaine,Hodges,Fields-Frederick,Lake Marilynhaven,Cayman Islands,barry83@beltran-tyler.biz,5/4/2022,https://www.mcclain.biz/ -330,Jeremy,Clayton,"Contreras, Hester and Baird",North Renee,Lithuania,bethmerritt@maynard.com,7/5/2021,https://www.mcneil-valdez.com/ -331,Tracey,Hardy,Valentine-Murillo,Blanchardfurt,Libyan Arab Jamahiriya,lesterguy@figueroa-molina.com,10/9/2020,http://davenport-vincent.org/ -332,Leslie,Mcmahon,Adkins and Sons,East Dominique,Brazil,destiny13@cline.com,7/14/2020,http://www.mcgrath.com/ -333,Peter,Moore,Costa and Sons,West Debrahaven,Palau,traciliu@forbes.com,7/17/2020,https://nielsen.com/ -334,Ian,Krueger,Cobb and Sons,West Melvin,Puerto Rico,uvaughn@gardner.com,10/25/2021,http://www.montoya-monroe.org/ -335,Crystal,Choi,Mendoza-Franklin,South Monica,British Virgin Islands,usutton@harmon-davenport.info,5/29/2020,http://www.mcknight.net/ -336,Lydia,Peterson,"Downs, Bentley and Gallagher",Port Christiemouth,Singapore,obarr@padilla-moore.info,12/13/2021,http://parker.org/ -337,Douglas,Waters,"Lambert, Irwin and Jackson",Natalieton,Czech Republic,keithwilkins@adams.com,10/19/2020,http://zhang.com/ -338,Tonya,Bond,Gill-Herman,North Joshua,Luxembourg,dianemonroe@gibbs.com,1/15/2021,http://beasley.com/ -339,Caleb,Henson,Hahn Group,Andradeberg,Bangladesh,whitneymason@dunn.com,10/15/2021,https://www.donaldson.biz/ -340,Tristan,Lynch,Forbes-Collier,South Spencer,Belarus,cassie84@fletcher.com,5/28/2021,https://www.flowers.com/ -341,Tami,Mendoza,Calhoun Ltd,East Savannah,Cape Verde,lwolfe@chavez-morse.biz,8/24/2021,http://www.goodwin.com/ -342,Leslie,Howe,Johnston PLC,New Cody,Liechtenstein,susan82@prince.net,8/26/2021,https://buckley.net/ -343,Luke,Perez,Lozano LLC,Phillipchester,Thailand,crandall@levine.com,4/23/2020,http://hudson.biz/ -344,Gina,Rowe,Hebert Inc,Jocelynmouth,Central African Republic,carolinehunt@rowe-olson.com,1/18/2021,http://www.salinas-savage.com/ -345,Darius,Riggs,Best Ltd,North Philip,Moldova,smcintyre@jordan.com,11/14/2021,https://www.kerr.com/ -346,Hailey,Hart,"Lozano, Everett and Vargas",Chungbury,Venezuela,regina66@ochoa.com,3/26/2020,https://www.mccarty.com/ -347,Larry,Paul,Gilbert Inc,Lukeside,Cayman Islands,russell17@chavez-schwartz.com,3/3/2021,http://arnold.biz/ -348,Krystal,Woods,Stewart-Krueger,Crystalburgh,Taiwan,hudsonjoan@mora.net,6/10/2021,http://howe.com/ -349,Tabitha,Reilly,Kerr Inc,Taylorshire,San Marino,michealwhite@krause.com,3/16/2022,https://www.austin.info/ -350,Roberta,Quinn,Oneal Group,North Phillip,Egypt,ekoch@myers.com,10/26/2021,http://pearson.com/ -351,Reginald,Pitts,Perry Inc,Clarenceton,Samoa,robertsonnatasha@peterson.info,1/29/2020,http://norman-branch.com/ -352,Jesus,Rogers,Shields PLC,Jarviston,Afghanistan,frencheddie@contreras.org,2/1/2021,https://mccoy-harrington.info/ -353,Roberto,Roy,"Gonzales, Cochran and Madden",South Dustin,Lithuania,gnovak@haley.com,8/17/2021,http://www.mckee.com/ -354,Jillian,Mccullough,Melton and Sons,New Gerald,Martinique,lorettamoreno@kerr.net,7/28/2020,https://www.monroe.biz/ -355,Alan,Valenzuela,Avila-Melendez,Birdtown,United States Minor Outlying Islands,mccannmallory@parsons.com,4/28/2022,http://chan.info/ -356,Kristy,Dixon,Norris-Day,Ebonymouth,Holy See (Vatican City State),jacob13@mccann.com,5/3/2020,http://www.glass-mason.com/ -357,Henry,Holland,Eaton-Burnett,North Julian,Gabon,benitezgina@downs-holden.com,4/15/2022,http://gallegos.net/ -358,Diana,Cox,Lin-Fowler,Tanyastad,Macedonia,crystal25@spence.com,5/2/2022,https://www.sosa.com/ -359,Sara,Middleton,Maxwell-Kennedy,East Ray,Nicaragua,mcconnellglenn@gay-boyer.org,7/29/2021,https://www.warner.info/ -360,Jade,Jimenez,Mosley-Knox,Wolfeborough,Dominica,jaime61@wilson.com,11/23/2020,https://gonzales.com/ -361,Carmen,Mcclain,Curtis-Lyons,Harryfort,Nauru,tsummers@dixon-vega.com,10/22/2021,http://www.singh-nixon.com/ -362,Wanda,Sanchez,"Raymond, Contreras and Wall",Tuckerfurt,Montenegro,wnash@sims.net,12/20/2021,https://schultz.com/ -363,Valerie,Wilcox,Dennis PLC,Coreychester,Tuvalu,jeanette40@banks.com,4/24/2020,https://cummings-duran.com/ -364,Chloe,Hurst,Jacobs and Sons,Duncanview,Tajikistan,smitchell@harmon.com,10/29/2021,https://oconnor-shaffer.com/ -365,Virginia,Goodman,Salazar Ltd,Jameston,Morocco,dawn64@brock.com,5/21/2021,http://www.valenzuela-maldonado.info/ -366,Erin,Woods,Dawson Group,South Brandonville,Pakistan,bholloway@curtis-blevins.com,3/27/2021,https://www.parker-parsons.org/ -367,Cassidy,Bruce,Castaneda Group,East Eugene,Congo,dwaynespence@kramer.com,2/21/2022,http://benitez-lam.org/ -368,Sally,Hinton,Glover-Mccoy,Guyside,Montserrat,rose26@navarro.biz,3/8/2021,https://ashley-mcintyre.com/ -369,Jessica,Randolph,Cruz-Chandler,Holdenfort,Qatar,kayleehenderson@tapia.com,8/28/2021,http://www.vang.com/ -370,Kelly,Hogan,Estrada PLC,Jaimeberg,Saint Vincent and the Grenadines,billmata@hatfield.com,9/7/2021,http://hanna.net/ -371,Derek,Chung,"Cooke, Downs and Hines",North Adriana,Georgia,grahamevan@wright.com,11/24/2020,http://hooper.biz/ -372,Shirley,York,Nolan-Hardy,Rodriguezbury,Brunei Darussalam,flemingcindy@dominguez.net,4/3/2020,https://www.leon-donovan.net/ -373,Cody,Potts,"Acosta, Robbins and Nash",Lunaville,Panama,fischerselena@novak-fitzgerald.biz,4/19/2021,http://berg-hardin.com/ -374,Erin,Gill,Mosley-Whitney,South Rachelchester,Iran,dixonmelissa@brooks.com,7/12/2021,http://bentley.com/ -375,Nicole,Vargas,Powell LLC,Alvaradoton,Vietnam,hrobinson@estrada.com,12/12/2021,http://pace.com/ -376,Sharon,Mack,"Vaughn, Kelly and Meza",Odonnellberg,Saint Lucia,cristian24@hebert.net,11/10/2021,http://www.oconnor-riggs.com/ -377,Monica,Lopez,"Farrell, Mcclain and Kaiser",Latoyabury,Maldives,terrance86@orozco.net,1/25/2021,http://www.cervantes.com/ -378,April,Jones,"Daniel, Rowland and Heath",Fryeville,Cocos (Keeling) Islands,taylor78@roberson-cline.com,1/30/2020,http://www.lang.net/ -379,Johnny,Reeves,Holden-Camacho,Lynnborough,Chad,moyeralejandra@ball.com,8/12/2020,https://www.mueller.com/ -380,Brandi,Owens,Bray-Nelson,Lake Jacobstad,Dominican Republic,karadrake@harris.com,4/15/2020,http://perez.com/ -381,Max,Rasmussen,"Graves, Hardin and Cummings",West Tammieport,Liechtenstein,shawriley@rasmussen.com,3/6/2022,http://woodard-dawson.com/ -382,Christian,Moore,Cherry and Sons,South Anne,Gambia,moralesleslie@scott.com,6/14/2020,https://stevens-crane.com/ -383,Tyrone,Holloway,Peters-Perez,Pachecoburgh,Tonga,wdyer@mayo.net,4/9/2022,http://www.francis.biz/ -384,Victor,Ferguson,Lawrence and Sons,Turnerfort,Comoros,savannahglenn@ward.com,3/8/2021,https://bright.net/ -385,Logan,Carroll,Chaney and Sons,Melissaville,Swaziland,cummingsjo@shea.info,1/3/2022,https://barrett.com/ -386,Alexis,Hawkins,Beck PLC,West Nathaniel,Eritrea,lcox@hensley.biz,1/10/2020,https://ray-summers.com/ -387,Diane,Reid,Mendoza Inc,West Mikehaven,Sweden,ballmartha@salazar-escobar.com,6/18/2021,http://www.armstrong.net/ -388,Dylan,Sweeney,Mccarthy-Leblanc,Goodmanborough,Brunei Darussalam,allisonstanton@mcpherson.net,7/13/2021,http://moyer.org/ -389,Craig,Gonzales,Clayton-Sutton,Darinshire,Falkland Islands (Malvinas),chungneil@flowers.info,4/23/2020,https://www.mcclure-maxwell.com/ -390,Jill,Cardenas,Williamson-Franco,West Kerrifort,Brazil,teresa89@wallace.com,5/24/2020,https://www.harvey.com/ -391,Timothy,Chambers,Alexander-Farrell,Pammouth,Afghanistan,sean10@fletcher.com,6/4/2020,https://arias.org/ -392,Jaime,Terrell,Castaneda and Sons,Gilesburgh,Czech Republic,ssutton@schroeder.info,4/12/2022,https://www.rogers.com/ -393,Alejandro,Hancock,Cooley and Sons,Lake Trevorton,Ghana,orobbins@bradford.com,10/15/2020,https://ward.org/ -394,Gregg,Rodriguez,Roberts-Hernandez,Leonardberg,Gambia,bernard04@evans-cooley.net,5/16/2021,https://leon.com/ -395,Deanna,Pacheco,"Heath, Collins and Pierce",Georgeton,Oman,courtney52@farley.org,5/22/2020,http://boone-guzman.com/ -396,Mariah,Barton,Molina-Miranda,Hebertchester,French Southern Territories,rangelbrad@oconnor.org,10/12/2021,http://www.mitchell.com/ -397,Valerie,Branch,Atkins-Hodges,Port Jenna,Gabon,jennifershannon@lane.net,5/18/2020,https://pugh.com/ -398,Jonathan,Norman,Mays-Benjamin,Josephview,Trinidad and Tobago,chubbard@haney.com,1/23/2021,http://www.travis-vaughan.com/ -399,Samuel,Drake,"Myers, Krueger and Sampson",South Tonya,Nauru,xholden@walsh.com,3/18/2022,http://www.david-rollins.com/ -400,Riley,Aguirre,Hicks PLC,East Sally,Senegal,pattonsydney@graham.com,5/7/2022,https://tapia-erickson.com/ -401,Jermaine,Baker,"Ryan, Conner and Boyle",North Diamond,Nicaragua,atkinsonheather@sutton.com,11/29/2021,https://pennington-carney.com/ -402,Holly,Hartman,"Rivas, Payne and Arellano",Dianamouth,Namibia,tinakerr@aguilar.info,5/27/2020,http://www.stein.com/ -403,Haley,Frost,Benitez LLC,Watsonfort,Ethiopia,snyderfred@santiago.info,12/21/2020,https://marshall.org/ -404,Lindsay,Crane,Ramos-Howard,South Gabriela,Faroe Islands,xgordon@king-terry.com,5/26/2020,http://mccarthy.org/ -405,Adriana,Bartlett,Wheeler PLC,West Roystad,Svalbard & Jan Mayen Islands,makaylalove@foley-rodriguez.net,5/12/2020,http://www.duke.com/ -406,Bethany,Ruiz,"Chambers, Castillo and Graves",South Andreachester,Syrian Arab Republic,caleb30@mccann.com,6/2/2021,https://kelley.biz/ -407,Gabriel,Weaver,"Hutchinson, Hicks and Austin",Longton,Morocco,rellis@blankenship.com,7/8/2020,https://stafford.com/ -408,Jimmy,Heath,"Jenkins, Clarke and Faulkner",Coxburgh,Rwanda,allenwalter@escobar.biz,8/1/2020,https://hardin-elliott.com/ -409,Craig,Powers,"Trujillo, Hurst and Ortega",Grantmouth,Netherlands Antilles,donnasandoval@sellers.com,1/30/2021,http://ruiz.biz/ -410,Colin,Howard,Glass-Shepard,Alexberg,Australia,manningsuzanne@osborn.net,1/9/2021,http://www.page.com/ -411,Edgar,Zhang,Francis Ltd,New Reginafort,Yemen,rbradley@buchanan-rogers.com,8/17/2021,http://www.moore.com/ -412,Carolyn,Lucero,Rojas-Ross,Charlottestad,Bosnia and Herzegovina,ihawkins@tanner.com,5/6/2020,https://www.mcmillan.com/ -413,Tonya,Parsons,Meyer LLC,South Charleneport,Rwanda,singhfrank@castillo-horne.org,7/3/2020,http://www.boyd.com/ -414,Marc,Small,Trevino-Bennett,Bennettmouth,Colombia,michaelcandice@booker-hall.com,3/26/2021,http://dawson-tapia.com/ -415,Dakota,Elliott,"Marks, Mora and Bender",South Charleneport,Russian Federation,goodmanhayden@marquez-fritz.biz,12/2/2021,http://www.simpson.com/ -416,Hector,Barry,Hunt Group,Wumouth,Gabon,ofriedman@gonzalez.com,3/13/2020,http://bender-terrell.com/ -417,Morgan,Wells,Mcmillan-Ramos,East Yvetteside,Thailand,alec48@copeland.com,3/14/2020,http://www.fernandez.info/ -418,Maxwell,Glenn,"Everett, Holmes and Sheppard",Miketown,Niue,stefanie43@hooper.com,9/7/2021,https://roberson-white.com/ -419,Melody,Young,Peck-Rivers,Port Harold,Switzerland,brichard@cervantes.com,3/16/2021,https://pena-clements.info/ -420,Alejandro,Sutton,Byrd PLC,Lake Franklinland,Colombia,jillrichmond@pugh-roach.com,2/5/2022,https://riggs.com/ -421,Jermaine,Banks,Harvey LLC,Lake Martha,South Georgia and the South Sandwich Islands,wilkinsondaisy@chaney.com,3/14/2020,https://www.riddle-yates.com/ -422,Terry,Woodward,Hicks Inc,Schultzbury,Syrian Arab Republic,mcculloughkylie@ford.com,4/24/2022,https://www.osborn.com/ -423,Tanner,Cherry,Mclaughlin-Lucas,Noahview,Kyrgyz Republic,melvin55@marks.net,10/23/2020,http://www.cordova.biz/ -424,Troy,Elliott,Sosa LLC,South Robertbury,Equatorial Guinea,ezamora@burke.org,3/27/2022,https://www.sampson-torres.com/ -425,Barry,Rubio,Brock and Sons,Dalemouth,French Guiana,brad79@powell.org,4/28/2022,http://www.adams.com/ -426,Samantha,Lutz,Mcdonald Inc,Lake Hayden,Bahamas,collin50@wilkinson.net,6/28/2020,http://cummings.com/ -427,Wendy,Trevino,Wells and Sons,Port Louis,Saint Lucia,christyconway@reid.com,7/4/2020,http://www.allison.biz/ -428,Greg,Mcneil,Mcclure-Thomas,Alfredhaven,British Indian Ocean Territory (Chagos Archipelago),anitadorsey@mccarthy.biz,8/14/2021,https://larsen-parks.info/ -429,Rita,Hutchinson,Galvan Inc,Arroyoland,Liechtenstein,friedmandean@hodge.info,10/10/2021,https://www.friedman.com/ -430,Rose,Robertson,"Chambers, Bernard and Rivas",West Brandonfurt,Svalbard & Jan Mayen Islands,downsjackie@cole-orr.com,1/17/2020,https://edwards-morton.com/ -431,Paige,Aguirre,Black-Pruitt,Russellland,Benin,robertsmelanie@swanson-willis.net,9/12/2021,https://www.cain.com/ -432,Xavier,Terry,"Ray, Maxwell and Olson",North Ritachester,Reunion,shamilton@wheeler-jensen.biz,11/11/2021,https://www.hughes.org/ -433,Michaela,Thornton,Riggs-Rodgers,West Victoria,Norfolk Island,raysimon@smith.com,4/22/2022,http://maldonado.com/ -434,Kyle,Rojas,"Hoffman, Brandt and Gay",West Tyrone,Liberia,donaldsongina@underwood.com,6/16/2020,http://warner-jarvis.com/ -435,Devin,Baxter,"Kirby, Lang and Galvan",Lake Xaviertown,Jordan,ubennett@mckenzie-farley.info,2/14/2020,https://www.ritter-spears.com/ -436,Omar,Farmer,"Fuentes, Mcguire and Mendoza",Tatemouth,Lesotho,aowens@arnold.com,7/27/2020,https://www.robles-stuart.net/ -437,Desiree,Vang,"Patel, Donovan and Branch",Wigginsbury,Burkina Faso,leeeileen@peterson.com,3/6/2022,http://www.mckenzie.biz/ -438,Fernando,Wade,Chan-Good,Rosebury,South Georgia and the South Sandwich Islands,frederick82@pham.info,6/5/2020,http://www.schwartz.biz/ -439,Anne,Winters,Gay LLC,Lewisview,Monaco,ybanks@velazquez.biz,4/21/2021,https://paul.biz/ -440,Glenn,Harrell,Vincent and Sons,Lake Douglas,India,luis88@calhoun-gaines.com,7/5/2021,http://chan.org/ -441,Tracey,Boyd,Mcmahon-Hale,South Victorhaven,Niger,oconnellkristopher@roberts.com,10/11/2021,https://hansen-frank.info/ -442,Catherine,Becker,Ware and Sons,Christyville,Christmas Island,qlambert@obrien-gay.com,4/29/2022,https://moreno-brown.com/ -443,Sharon,Farrell,Collins-Kline,New Aliciaberg,Gabon,masseyvickie@prince.info,4/24/2021,http://olsen-patterson.org/ -444,Clinton,Schwartz,Horton Inc,Reedland,Greece,robin51@caldwell.net,7/12/2021,https://page.com/ -445,Sheri,Perez,"Velasquez, Haynes and Parks",East Krystalland,United States Virgin Islands,kyle22@robbins-trevino.net,9/8/2021,http://lara.com/ -446,Beth,White,Wiggins-Horne,East Hunterburgh,Israel,hendrixjoanne@hooper.info,6/25/2020,http://www.bruce.net/ -447,Cole,Benitez,"Daniel, Phillips and Garrett",Lake Amanda,Estonia,geoffrey76@gonzales.org,2/26/2022,https://www.roach.org/ -448,Caleb,Chapman,Mullen-Burgess,Port Stanleyside,Kiribati,lindseymcmahon@hayden-tucker.com,8/18/2021,https://mcintyre.com/ -449,Glen,Eaton,"White, Harding and Gilbert",Rosefort,Belize,jcantu@clayton.com,11/30/2020,http://www.carpenter.com/ -450,Cristina,Hanna,Mcconnell-Mccarty,West Arianafurt,Central African Republic,mathew61@woodward.com,3/2/2020,http://www.bernard.com/ -451,Kathy,Owens,Irwin Inc,Keithchester,Brunei Darussalam,ugordon@boyer.info,7/9/2020,http://www.lucas.com/ -452,Rachael,Keith,"Gentry, Hurley and Gomez",Shannonstad,Togo,paula85@douglas-mann.net,12/9/2021,https://cortez.info/ -453,Marcus,Thompson,Prince-Hodges,New Gwendolyn,Albania,gravesjanice@rice.org,8/27/2021,http://www.wall.org/ -454,Norman,Mclean,"Crosby, Ayers and Burch",South Stephen,Equatorial Guinea,nathaniel65@copeland-levy.org,4/3/2022,http://www.potter.com/ -455,Brandon,Cross,"Delacruz, Hodge and Snow",North Leon,Tuvalu,currysabrina@henry.biz,10/14/2021,https://rowe.com/ -456,Autumn,Soto,Ho-Long,Lake Deanna,Niger,robertasharp@vasquez.info,4/29/2022,https://www.andrews.com/ -457,Jaime,Cervantes,"Garrison, Mccall and Bowman",North Kristinside,Egypt,casey71@little.net,9/5/2020,https://www.walter-frederick.com/ -458,Reginald,Blankenship,"Holmes, Warner and Barrett",North Carly,Malawi,plawson@reyes.com,5/10/2022,https://www.small.com/ -459,Chelsey,Mcknight,Morse Group,Fordborough,Djibouti,aaroncollins@nunez.com,4/8/2022,https://rich.net/ -460,Clayton,Lindsey,Gilmore Ltd,North Sean,Greenland,katiestanton@villa.com,9/3/2021,https://www.henson-benitez.com/ -461,Kurt,Prince,Humphrey-Cain,Ronnieberg,United States of America,thompsonangie@may.com,5/5/2022,http://gibbs-friedman.com/ -462,Joy,Choi,Montgomery Group,West Jermaineton,Dominica,sheenarichards@harrison.com,2/25/2020,http://daniel-walton.info/ -463,Jeremiah,May,Larson LLC,Gabrielleland,Macao,fgoodman@livingston.org,7/29/2021,https://camacho.net/ -464,Candice,Huang,"Barrera, Conway and Holland",New Joanneberg,Guinea,adamsjim@luna.com,4/30/2021,http://porter.com/ -465,Alejandra,Tran,"Blackburn, Hampton and Conway",Karatown,French Guiana,kporter@kelley.com,12/26/2021,http://wagner-orr.com/ -466,Shaun,Richardson,"Cunningham, Grimes and Cameron",Rachelview,Armenia,craigkent@waller.com,1/20/2020,http://www.houston.info/ -467,Darryl,Russo,Copeland and Sons,Scottstad,Nicaragua,qbates@randall.com,10/26/2021,http://zavala.com/ -468,Joe,Cabrera,Wilkinson LLC,Kimberlystad,French Southern Territories,wesleymcfarland@dunlap.info,5/6/2020,https://rowe.info/ -469,Travis,Duran,"Vincent, Watson and Esparza",Port Russellmouth,Uganda,rshields@mack.com,7/23/2021,http://wolfe.net/ -470,Warren,House,"Frazier, Cain and Santos",Lake Devonmouth,Togo,brittneytravis@salinas-zhang.org,9/9/2021,https://wiggins.org/ -471,Katherine,Haas,"Villegas, Stanley and Calderon",South Tonya,Eritrea,iheath@woodward.biz,12/19/2020,http://www.schmitt.biz/ -472,Karen,Burton,Stein-Hess,South Andres,Cote d'Ivoire,gary29@craig.org,4/9/2021,https://warner.info/ -473,Logan,Riddle,"Leach, Gutierrez and Villarreal",Weberview,Jordan,valerie78@osborne.net,5/8/2022,http://www.christian.info/ -474,Kathryn,Hester,Lam and Sons,Port Paigefort,American Samoa,mccartycandace@mclean.com,1/22/2022,https://koch-willis.com/ -475,Christina,Villegas,"Mercado, Ewing and Villa",Vickimouth,Nigeria,isabella03@rojas.com,9/11/2021,https://mercado.com/ -476,Joel,Walters,Cuevas-Vaughn,Carlsonfort,Cayman Islands,icalhoun@scott.com,10/21/2020,http://www.cunningham.net/ -477,Frederick,Delacruz,"Beck, Lambert and Sweeney",Brianbury,Djibouti,kiaramacias@banks.com,2/5/2020,https://www.church.com/ -478,Warren,Cantrell,"Hood, Mora and Murphy",Whitneyburgh,Korea,darrell62@dickson-nielsen.biz,8/6/2021,https://www.carlson.info/ -479,Melvin,Bates,Fry-Roy,Karinabury,Vanuatu,rashley@robbins-boyer.org,1/27/2021,https://vasquez-whitaker.com/ -480,Meredith,Pollard,Gilmore Ltd,Anthonyshire,Sudan,sandy88@dougherty-gordon.biz,1/5/2022,http://www.miles.info/ -481,Dawn,Hines,"Kane, Roman and Osborne",Collinstown,Paraguay,nataliemedina@mitchell.net,10/17/2020,http://www.huber.org/ -482,Audrey,Goodman,Jenkins-Murillo,South Rogerhaven,Yemen,martinjeanette@petersen.com,10/1/2021,http://www.orr.org/ -483,Kurt,Waters,Molina and Sons,Port Shane,Papua New Guinea,sanderscheyenne@peters-ware.com,3/3/2020,https://www.weber-watson.info/ -484,Eugene,Harper,"Rowe, Aguirre and Holloway",East Josephfurt,Serbia,charlotte42@bowman-arellano.biz,6/14/2020,https://terrell-proctor.com/ -485,Caleb,Eaton,"Franco, Blankenship and Nolan",Rileyburgh,Sri Lanka,weverett@dixon.info,7/13/2020,https://www.bell.net/ -486,Kaitlin,Mejia,"James, Bowman and Bass",Lake Alechaven,Latvia,ross23@hooper.com,4/8/2022,http://www.hicks.info/ -487,Jennifer,Mercado,Espinoza LLC,South Vanessaton,Cayman Islands,deannamiller@boyle.com,5/12/2020,https://collins.net/ -488,David,Cardenas,Cortez-Flowers,Tanyabury,Bahamas,mpetty@clark-allison.info,1/23/2022,http://www.roth.com/ -489,Jenny,Reid,French-Norris,Karenmouth,Malaysia,dustinrhodes@fischer-hendricks.com,1/16/2021,http://www.burnett.com/ -490,Lonnie,Frye,Fox-Small,Carlsonfurt,Panama,ronnieprince@haley.com,9/3/2021,http://zamora-wong.net/ -491,Janice,Mayer,"Cobb, Chen and Fitzgerald",Port Sheenaborough,Jersey,careyspencer@ellison.com,6/19/2020,https://www.welch-houston.com/ -492,Debbie,Bates,"Fox, Lara and Short",South Toddchester,Sweden,sfrederick@fowler.com,5/30/2020,https://www.stevens.com/ -493,Jean,Christian,Sawyer Group,Hartburgh,Jordan,gregory88@kane-atkins.org,3/28/2021,https://frederick-dawson.com/ -494,Philip,Harding,Burgess-Stephenson,Jenniferland,Turkmenistan,barrerasamantha@manning.com,5/10/2020,http://rivera.net/ -495,Jillian,Oneal,Chambers Inc,Knoxton,Cape Verde,wilkinsontravis@booth-crawford.biz,9/28/2021,http://shepherd.com/ -496,Mindy,Christian,Fox PLC,Jesusfort,Liechtenstein,jessicagillespie@ibarra-cannon.org,8/4/2021,http://skinner-finley.com/ -497,Bobby,Mclean,Mosley Group,West Normanborough,Cote d'Ivoire,hodgekiara@conley-haynes.info,6/2/2021,https://nicholson.com/ -498,Amanda,Santos,Camacho-Lamb,Freemanberg,Antigua and Barbuda,slivingston@cherry-lara.info,5/29/2022,http://www.mcneil-gould.biz/ -499,Ralph,Buckley,"Tate, Wall and Trujillo",New Tara,Montenegro,hansenjoshua@pugh.com,5/15/2021,https://www.fuentes-vang.info/ -500,Brian,Montoya,Short and Sons,West Mirandaside,Gabon,jblankenship@harper.com,1/27/2021,http://boone.biz/ -501,Joann,Dyer,Salinas-Stephenson,Lake Chadside,Nigeria,llowe@mcmahon.biz,6/3/2021,http://www.fitzgerald-acosta.info/ -502,Rhonda,Cook,Pennington-Lee,Zamoraburgh,Lebanon,rollinscristian@harvey.com,5/14/2020,http://www.nielsen-davidson.com/ -503,Charlene,Huffman,"Henry, Weeks and Cantu",Stanleybury,Korea,belindawalls@wall-dalton.biz,4/1/2022,http://www.reynolds.org/ -504,Abigail,Shah,Ortiz LLC,Mercadoland,Sierra Leone,ecollins@wise.biz,3/11/2022,https://www.cantrell.biz/ -505,Frances,Bridges,Lane and Sons,Port Virginiaside,United Arab Emirates,nicholemccann@logan.com,9/7/2021,http://www.calhoun.net/ -506,Belinda,Kaiser,Hull Inc,Phillipsbury,Malawi,susan95@burgess.com,4/17/2022,http://www.shelton-maxwell.org/ -507,Michael,Griffin,"Finley, Molina and Ortega",West Sophiafort,Syrian Arab Republic,priscilla21@richmond.com,3/31/2020,http://www.grant.com/ -508,Brooke,Briggs,"Daugherty, Bond and Mcmillan",Mezaview,Sao Tome and Principe,moraleonard@grant-murphy.org,12/18/2021,http://lin-hardy.org/ -509,Desiree,Sloan,Mccann and Sons,North Kyle,Vanuatu,pittmanjay@pruitt.org,11/7/2020,https://dudley.info/ -510,Jared,Floyd,Waters-Novak,East Tylerton,Jersey,qenglish@huang.com,6/10/2020,https://www.schroeder-mora.com/ -511,Latasha,Spencer,Castro LLC,North Kimberlychester,Honduras,sean06@bridges-serrano.com,8/14/2021,https://www.ritter.com/ -512,Joy,Mack,Blackwell Group,North Stevestad,Latvia,calebsolomon@li-berg.com,3/20/2022,http://oconnor-hale.com/ -513,John,Rhodes,"Luna, Flowers and Beck",Port Sylviaton,Iceland,jamiemontgomery@cameron.org,1/7/2022,http://chandler.net/ -514,Claire,Schaefer,Lynch-Pittman,Stuartmouth,Algeria,hannah33@leach-frey.com,1/21/2021,http://www.dominguez-higgins.com/ -515,Judy,Nguyen,Graham LLC,Lindsayburgh,Uruguay,tmoses@mueller.com,5/21/2022,http://odom.com/ -516,Andrea,Dennis,Kramer Ltd,Colemanfurt,China,stephensonkevin@rojas-strong.com,12/16/2020,https://boyd-giles.info/ -517,Karla,Middleton,"Bradshaw, Mcbride and Gamble",Mayton,Mongolia,nathanielmoreno@cunningham.com,11/10/2021,https://www.frazier-logan.com/ -518,Sean,Coffey,Kirby-Coffey,Silvaberg,Anguilla,tbartlett@knox.com,9/23/2020,https://harrington.net/ -519,Nichole,George,Stout Group,West Charlotte,Greece,warddeborah@eaton.com,3/3/2020,http://avila.net/ -520,Justin,Dougherty,Gregory PLC,Greerbury,Netherlands,robersonangie@thornton.com,1/22/2020,http://www.garza-crane.com/ -521,Devon,Smith,Morgan PLC,Brandtville,Libyan Arab Jamahiriya,glenda72@hobbs-watts.com,8/23/2021,http://sutton.net/ -522,Becky,Scott,Coffey-Mcgee,Haroldport,Jersey,rblack@benson.com,2/13/2020,https://mclaughlin.com/ -523,Misty,Espinoza,"Baxter, Sims and Braun",Mataport,Luxembourg,zbarron@ross.com,6/27/2020,https://www.mosley-vazquez.com/ -524,Franklin,Rivers,Beasley-Lewis,East Joan,Reunion,miguel73@fisher-hatfield.org,3/7/2020,http://mitchell-ho.com/ -525,Leonard,Barber,Stevens Ltd,Lake Jasmineview,Rwanda,gainesbethany@washington-pearson.com,1/12/2020,http://www.hurst.net/ -526,Lonnie,Price,"Ibarra, Horton and Williams",North Kirk,Cayman Islands,hbradshaw@wang.biz,11/29/2021,http://www.sosa-baldwin.com/ -527,Clarence,Cantu,Dudley LLC,Robertstown,Gambia,billy41@malone.net,3/21/2021,http://daniel-campbell.info/ -528,Johnny,Foster,"Terry, Fletcher and Mclean",South Chelseyton,Puerto Rico,lancebenitez@nash.info,10/19/2021,https://walton.net/ -529,Richard,Vasquez,Glenn PLC,Stanleyhaven,Burundi,jonesjacob@downs.com,4/3/2020,http://www.kaufman-braun.net/ -530,Tabitha,Compton,Cantu-Hunt,West Tammietown,Mozambique,joneskrystal@blackburn.net,3/26/2020,https://www.villegas.com/ -531,Adrienne,Wong,Barrera PLC,Anthonyberg,Gibraltar,yjenkins@lowery.com,2/25/2022,http://browning-jennings.com/ -532,Jorge,Farrell,Delgado LLC,Dylanberg,Australia,alfredsnyder@tran-ellison.com,4/21/2022,https://mcknight-trevino.info/ -533,Kathy,Richards,Ellis Inc,Fieldsside,Turks and Caicos Islands,anitavillarreal@reilly.com,6/21/2020,http://www.anderson.net/ -534,Larry,Ellison,"Horne, Lloyd and Bolton",South Cristian,Sweden,chelsey46@calhoun.com,7/15/2021,https://taylor.com/ -535,Gabrielle,Chaney,"Acevedo, Randall and Harrell",Tanyachester,Guadeloupe,ulogan@gilmore.com,10/5/2021,https://www.washington.biz/ -536,Maxwell,Macias,"Marquez, Poole and Chang",New Edwin,Maldives,ray95@barnett.com,8/4/2021,https://www.mccoy.com/ -537,Joyce,Chan,Crawford-Nielsen,Isaiahfurt,Serbia,calhounkari@thompson.info,5/26/2021,https://www.solomon.com/ -538,Kari,Durham,Durham-Dean,Lake Julianberg,Greece,kathrynsims@wong-chapman.com,11/3/2021,https://www.trevino.com/ -539,Trevor,Mckee,Moss LLC,North Reginaton,Togo,nicholserika@cooke-le.net,7/4/2021,http://www.carter.com/ -540,Diana,Thomas,Ali-Stark,Rodriguezfurt,Greece,brendan87@hale.com,5/14/2021,http://www.ortega.com/ -541,Cynthia,Moss,Colon LLC,West Matthewside,South Georgia and the South Sandwich Islands,victoria21@pope-ortiz.com,4/16/2022,http://lambert.com/ -542,Billy,Garrison,"Travis, Nichols and Farmer",Andreburgh,Mayotte,kylie93@patterson.net,10/20/2021,https://www.townsend.com/ -543,Grace,Austin,"Petersen, Barrett and Leon",North Yvetteview,Sierra Leone,hayley87@washington.org,8/10/2021,http://www.tran.com/ -544,Kerri,Hamilton,Hart and Sons,North Karaside,Yemen,scurry@schaefer-tate.info,2/28/2022,https://www.lawson-maddox.com/ -545,Kaylee,Wright,"Randolph, Butler and Burgess",North Douglas,South Africa,barbara31@page.info,12/10/2020,https://www.pacheco-reilly.biz/ -546,Manuel,Delacruz,"Wilson, Kline and Bullock",Port Cassandrastad,Papua New Guinea,alexandria98@shields.com,12/22/2020,https://www.andrade.info/ -547,Helen,Baxter,Schneider and Sons,Garrisonberg,Bolivia,pacenatasha@whitaker.com,8/11/2021,http://www.boone.biz/ -548,Leon,Hendricks,Hood Inc,South Angelica,Chad,drew59@strong.info,7/31/2020,http://owen.com/ -549,Henry,Contreras,"Rice, Simpson and Russell",New Michellefort,Slovakia (Slovak Republic),gregorymcfarland@rodriguez.com,6/5/2020,https://www.proctor.biz/ -550,Gerald,Tanner,Fritz LLC,West Leslie,Western Sahara,isabel75@rubio.com,3/30/2021,https://lawrence.com/ -551,Ariel,Arnold,"Stokes, Floyd and Bradford",Michaelport,United Arab Emirates,sarroyo@mejia.com,11/14/2020,http://solis.org/ -552,Judy,Fisher,Riggs-Beltran,Juliestad,Fiji,dflynn@spears.com,4/18/2021,http://yoder-bender.info/ -553,Miguel,Wood,"Cooke, Mcgrath and Morse",East Douglasberg,France,hblankenship@ellison.biz,1/16/2021,http://wolfe.com/ -554,Lance,Gillespie,Collier-James,South Timothy,Lebanon,urojas@wolf.org,10/16/2021,http://shaffer-schmidt.com/ -555,Natalie,Wang,Holt Group,Mercadofurt,Macao,larsonray@hodges-bartlett.net,2/1/2020,https://www.ruiz-underwood.com/ -556,Charlotte,Byrd,Benson-Gardner,West Rita,Ireland,braunjillian@mcintyre-delgado.com,4/8/2021,https://www.gentry-woods.com/ -557,Chelsea,Wagner,"Hurley, Shepard and Harrell",Port Cristianview,Bosnia and Herzegovina,clayton96@knight.com,7/12/2020,http://saunders-warren.org/ -558,Larry,Rollins,"Michael, Cunningham and Ellison",Krausefurt,Djibouti,spearsmarissa@flowers.com,4/27/2022,https://www.rice.com/ -559,Paul,Leblanc,Holmes-Gates,South Heathershire,Rwanda,wolfedanielle@pitts.com,12/28/2020,http://www.caldwell.com/ -560,Caitlin,Vance,"Phillips, Frazier and Blair",New Julia,Korea,bonnieparks@ritter-flynn.com,6/26/2021,http://moreno.com/ -561,Adam,Shaffer,Diaz-Harrell,Garymouth,Tajikistan,isaiah33@ho-lane.com,4/16/2021,http://liu.com/ -562,Martin,Rocha,Hall-Daugherty,Oneillfurt,Niger,montgomerycharlotte@wong.com,11/1/2020,http://www.cook-wagner.com/ -563,Ronnie,Erickson,"Miller, Lucero and Mccann",South Miamouth,Papua New Guinea,tricia95@mcdonald.com,12/21/2021,http://www.valenzuela.com/ -564,Sheryl,Delgado,Manning Ltd,Tracyfurt,Liechtenstein,larry51@stark.com,8/5/2021,https://jarvis.biz/ -565,Kristen,Williamson,Doyle-Rodgers,East Kayleetown,French Southern Territories,watkinskaylee@pacheco.biz,4/27/2020,https://ho-medina.net/ -566,Cole,Warner,Grimes Inc,Port Kylieside,Bhutan,dorislucas@evans-hartman.info,3/6/2022,http://moses.net/ -567,Lee,Mercado,Pierce-Wilkinson,East Normaville,Panama,wilkersonnicholas@crosby.net,11/15/2020,https://strickland.com/ -568,Paula,Bates,Lin Group,Phillipsville,Taiwan,sethrhodes@silva.org,3/22/2020,http://cooke.com/ -569,Claire,Shaw,"Ayala, Krause and Hendrix",Anthonyville,Bulgaria,chaynes@rasmussen.com,10/19/2021,http://leach.com/ -570,Amy,Mclean,"Rasmussen, Pacheco and Mccann",Shortmouth,Cote d'Ivoire,hamptontammie@gonzales.com,11/27/2020,https://rivera-madden.biz/ -571,Joshua,Winters,Bolton Inc,South Terry,Luxembourg,brittanylandry@whitaker.org,7/15/2021,https://carlson.com/ -572,Brian,Pittman,"Friedman, Montes and Valenzuela",West Meredithshire,Faroe Islands,stevesuarez@winters.com,4/11/2021,https://www.brandt-romero.biz/ -573,Jeffrey,Waters,Montoya Inc,Jesseville,Bouvet Island (Bouvetoya),mezakristina@hunt.com,10/30/2020,http://hartman.com/ -574,Denise,Barber,Terry-Walls,East Katelyn,British Virgin Islands,qmyers@dominguez-doyle.net,9/14/2020,https://www.lutz-parks.info/ -575,Wayne,Sanders,Patel Group,Lucasmouth,Tokelau,eshea@simmons-calhoun.org,10/4/2020,http://keller-browning.org/ -576,Sandra,Sparks,"Allison, Fox and Norris",Carneyfurt,Saint Kitts and Nevis,mcgrathdalton@barr.com,10/17/2020,http://dominguez-bender.com/ -577,Jose,Singleton,Mcfarland-May,Haleyfurt,Azerbaijan,donhood@gilmore.com,2/7/2020,https://morse-vega.com/ -578,Joel,Shea,Richmond-Horne,South Alisha,Palau,gfarley@wheeler-ayala.com,5/29/2022,https://www.mercer.com/ -579,Sergio,Marquez,Arroyo-Braun,South Kelli,Chile,darrylbarker@hebert.com,2/9/2021,https://www.chang-short.com/ -580,Latoya,Decker,Cooke and Sons,East Franceshaven,Mauritania,audrey14@hopkins-serrano.biz,4/16/2022,http://www.koch.com/ -581,Chase,Guerrero,"Mccarty, Quinn and House",Armstrongtown,Gibraltar,randall08@palmer-wells.com,5/3/2022,https://www.andrews.biz/ -582,Jeff,Tate,Brennan Inc,East Robertatown,Nicaragua,carlsonguy@massey-brock.net,10/12/2021,http://herrera.org/ -583,Brandy,Valentine,"Kidd, Gibson and Ramos",Janetchester,Romania,dkirk@bartlett-gross.org,1/8/2021,https://www.mccullough.com/ -584,Jade,Vega,"Ruiz, Mcfarland and Terrell",Lake Jaclyn,Guinea,ihaynes@velazquez-robertson.org,1/4/2020,http://sanford.com/ -585,Melody,Nielsen,"Rush, Snyder and Bridges",North Chris,American Samoa,biancapratt@espinoza.org,2/4/2020,https://www.jordan-hooper.com/ -586,Brandon,Webb,Ferrell-Fitzgerald,Reyesstad,Peru,cheyennemurillo@campbell.com,12/12/2021,https://coffey-zimmerman.com/ -587,Jean,Mclaughlin,"Rivas, Frey and Figueroa",Thomasstad,Lao People's Democratic Republic,jeffrey93@russell.biz,10/30/2020,http://www.vang.org/ -588,Albert,Case,"Henson, Heath and Delgado",Lake Lonnieberg,Saint Martin,krystal85@aguirre.com,1/2/2022,http://www.wise.com/ -589,Jose,Duarte,Robertson Inc,Kristaland,Reunion,billykerr@martinez.org,11/7/2021,http://www.chavez-browning.com/ -590,Jennifer,Brady,Hampton-Riddle,Stanleyborough,Zimbabwe,darrell48@robinson-graves.com,3/9/2022,https://www.carter.net/ -591,Angel,Conner,"Banks, Pierce and Romero",Port Marieland,Cape Verde,fgeorge@daniels.com,5/19/2022,https://www.conner.com/ -592,Alyssa,Gamble,"Mcclure, Raymond and Mccoy",Olsontown,Zimbabwe,donaldsondamon@dickson.com,8/24/2020,https://www.mcbride-carpenter.com/ -593,Lawrence,Campos,Pittman LLC,Heathfurt,Uganda,santosrussell@hicks.com,2/9/2022,https://russo.com/ -594,Joyce,Michael,"Maddox, Wiley and Vincent",New Soniatown,Mongolia,tomwyatt@fischer-morgan.biz,9/6/2020,http://www.schmidt.com/ -595,Doris,Fox,"Hudson, Fritz and Mcdaniel",Huntville,Sudan,gerald81@norris.com,7/9/2020,https://www.clements.info/ -596,Johnny,Harris,Costa-Franklin,New Rickstad,Norway,ihutchinson@bass-nunez.info,1/22/2021,https://www.nichols-woodward.com/ -597,Leroy,Vargas,Acevedo Ltd,Reillyburgh,Ghana,helen10@mcgee.com,2/13/2021,http://wyatt.com/ -598,Diana,Green,Bishop PLC,South Andrewville,Kazakhstan,anarobles@barton.com,3/21/2020,https://pugh-hicks.info/ -599,Victor,Bowman,Nolan PLC,North Johnnymouth,Panama,nkey@buchanan.info,1/19/2021,http://www.wyatt.biz/ -600,Alexis,Perry,Tanner-Mullen,Darrylstad,Faroe Islands,singletonbriana@cameron.com,7/12/2021,http://holloway-kent.biz/ -601,Xavier,Mooney,"Boyer, Hatfield and Powers",South Antoniochester,Jersey,jessesavage@barr-mathews.com,10/5/2020,http://www.glenn-jennings.com/ -602,Lawrence,Gross,Miles-Hodge,Bradleymouth,Ukraine,ryan38@delgado-crane.com,2/10/2022,https://www.dickerson.org/ -603,Albert,Myers,Davies-Benitez,Port Moniquehaven,Honduras,wnielsen@reeves.com,5/25/2021,https://www.gentry-fry.org/ -604,Mikayla,Oneal,Cannon Ltd,Terrancetown,Tajikistan,rbryant@ritter.org,11/11/2020,http://evans.info/ -605,Frederick,Mcgrath,Dennis PLC,Port Jodyland,Falkland Islands (Malvinas),jonathon19@hicks.net,6/29/2020,https://www.bean.com/ -606,Kirk,Benitez,"Bates, Baird and Bryan",Sharonside,Bolivia,mconley@mercado.com,12/24/2021,http://baxter.biz/ -607,Alex,Mcdonald,Bradford PLC,Candacestad,Egypt,bailey40@le.com,2/29/2020,http://simmons.info/ -608,Trevor,Hayden,Jackson-Trujillo,Lake Angie,Bangladesh,kathleenvillanueva@bullock.com,12/8/2021,http://www.dalton-nguyen.info/ -609,Sherry,Lane,Randall and Sons,West Joel,Antigua and Barbuda,vmorrison@contreras.net,5/16/2020,https://steele.org/ -610,Mallory,Pratt,Landry-Carpenter,Payneport,Malta,cunninghamshelly@hines-curtis.org,3/14/2020,https://christensen-irwin.biz/ -611,Daniel,Flores,Barajas-Gordon,South Carrieshire,Guatemala,bvance@pollard.biz,7/12/2021,http://www.nguyen.com/ -612,Jesus,Kane,Conrad and Sons,Clarketon,Brunei Darussalam,moyertanya@santos-fletcher.com,4/28/2021,https://www.elliott.info/ -613,Gilbert,Hendricks,"Goodman, Hinton and Douglas",South Francisco,Malawi,castilloluis@kent-reese.biz,6/27/2021,http://www.hoover-foster.com/ -614,Kerry,Sanders,Larsen-Murray,New Grantshire,Montserrat,zcase@mccarthy-patton.com,4/29/2022,http://www.swanson.org/ -615,Alexandra,Davidson,"Morrison, Ballard and Alvarado",South Latoya,Botswana,deleonclinton@harrison-small.com,6/11/2020,http://www.rivera.com/ -616,Vincent,Carey,Murphy-Lopez,South Clayton,Jamaica,josephmeyer@cameron.com,9/12/2020,https://www.cobb.biz/ -617,Parker,Russo,Foley-Yoder,East Dorothy,Malawi,hancockbrianna@mccann.org,1/2/2020,https://perez-pollard.com/ -618,Ebony,Velasquez,Casey-Krause,Dennisfurt,Mexico,marvinschmidt@lopez.com,2/22/2021,http://haley-ho.com/ -619,Jesus,Holden,Carlson Ltd,Cathyton,Saint Barthelemy,rutharellano@stafford-gross.com,1/5/2022,https://ayers-lyons.net/ -620,Candace,Chen,Raymond-Romero,Stacystad,Aruba,candiceguzman@carlson-graham.com,6/15/2021,http://www.estrada-olson.biz/ -621,Juan,Saunders,Gillespie Inc,Sotoberg,Taiwan,choidean@hays.com,12/31/2020,https://www.coffey.com/ -622,Richard,Serrano,Beasley Group,North Raven,Kyrgyz Republic,mario93@roy.com,9/24/2021,http://www.bernard-greene.com/ -623,Rick,Barrera,"Wells, Gallagher and Robles",Jermainetown,Samoa,cmercado@reed.com,6/22/2021,http://strong.com/ -624,Meagan,Hoover,Chapman Group,Piercemouth,Tanzania,alvin00@zavala.com,2/18/2021,https://oconnell.com/ -625,Larry,Huff,Knapp-Hill,New Reneemouth,Samoa,kennethtownsend@dominguez.biz,2/20/2022,https://lowe-potter.com/ -626,Kelli,Gonzales,Miles Ltd,Lake Dominique,Slovenia,jgonzales@maddox.com,5/24/2020,http://www.ellison.com/ -627,Crystal,Howard,Stokes-Boyle,Guerreroshire,Ecuador,gwendolynterry@blanchard.com,4/2/2021,http://www.tapia.com/ -628,Frank,Walter,Barnett-Floyd,Yolandatown,Saint Pierre and Miquelon,leachnina@cantrell.com,3/26/2020,https://morrison.com/ -629,Rhonda,Donaldson,Graham-Blackwell,Autumnland,Christmas Island,ellisontanner@gray-lewis.org,1/31/2021,http://www.simpson-knapp.com/ -630,Lisa,Duran,Brennan-Spencer,East Sabrina,Senegal,hammondcristian@berry.com,2/11/2022,http://www.horne-arias.biz/ -631,Isabel,Cisneros,"Salas, Kelly and Johns",Blakechester,Guinea-Bissau,mccarthyangel@tate-lam.net,4/8/2021,http://ray.com/ -632,Bianca,Jennings,"Cooper, Romero and Mcneil",Ethanburgh,Cambodia,rodriguezralph@herring.biz,9/19/2020,https://ward.com/ -633,Leon,Lang,"Vazquez, Compton and Kane",North Tyler,Czech Republic,grahampaige@carpenter-olsen.com,1/10/2020,https://alvarez.com/ -634,Luis,Zamora,Knox Ltd,Summerstown,Saint Barthelemy,mcleanmichael@arroyo.com,9/25/2020,https://www.yu.info/ -635,Mitchell,Chang,"Mclean, Sheppard and Pearson",Mannport,Saint Pierre and Miquelon,colleenbarnett@hobbs-smith.com,8/6/2020,https://www.mosley.com/ -636,Edgar,Terry,Mosley-Mata,New Ann,Bulgaria,kphelps@trujillo.net,2/23/2020,https://rollins-ewing.net/ -637,Norman,Howard,Carpenter and Sons,Tammyberg,Sri Lanka,peggy04@mack-chambers.biz,7/5/2020,http://www.cisneros-taylor.biz/ -638,Fred,Alvarez,"Roberts, Solis and Carpenter",Port Katherineside,Greece,ccollier@baxter.com,3/26/2022,https://www.walter.com/ -639,Lauren,Nunez,Nichols-Key,Haysmouth,France,harrellkirsten@knox.com,7/20/2020,https://velez.com/ -640,Kristen,Terry,Howe-Mathis,South Rogerville,Niue,qhinton@ferguson.com,1/31/2022,https://www.berg.com/ -641,Olivia,Cross,Richard-Lutz,Rickburgh,Uganda,alec41@stuart.net,2/18/2020,https://zhang.org/ -642,Leonard,Pennington,"Elliott, Combs and Mcpherson",Tammieborough,Guam,victoriacooper@avery-mooney.com,2/13/2021,https://pugh.com/ -643,Nathan,Bryan,Kane Inc,Walshshire,Jordan,brucecarrillo@evans-powell.net,6/17/2021,http://www.richard.com/ -644,Theresa,Gallegos,Spence Ltd,Dorisport,Togo,ybrandt@hahn-mejia.com,4/10/2021,http://estes.com/ -645,Jamie,Hayes,"Harvey, Foley and Rush",Orrfurt,Latvia,max17@estrada.info,12/17/2020,http://durham-sullivan.com/ -646,Wayne,Herring,Brooks PLC,Johnburgh,United States Minor Outlying Islands,maxwellallison@gay-lynn.net,4/15/2021,https://vega.com/ -647,Allison,Franco,Willis PLC,South Cesarburgh,Japan,wileybill@archer-mckenzie.biz,10/20/2020,https://www.curtis.com/ -648,Ronald,White,"Ferguson, Knapp and Mathews",West Normaton,Turkmenistan,mmcclure@wiggins.com,12/14/2020,https://www.dennis-west.com/ -649,Deborah,Taylor,Pope and Sons,Port Roberta,Macedonia,catherine11@fleming-hull.com,11/2/2020,http://www.dodson.com/ -650,Shari,Bender,Lamb-Valenzuela,North Guy,Falkland Islands (Malvinas),mannashlee@kaufman-osborn.com,1/26/2020,http://gallagher.info/ -651,Brooke,Gibbs,"Burnett, Atkins and Norris",East Isabel,Mali,stefanie48@fowler.com,2/6/2021,http://www.hartman.com/ -652,Ashley,Melton,Oneill-Dickerson,West Stacy,Anguilla,dustinbray@edwards.com,6/20/2021,https://www.clark.org/ -653,Teresa,Hart,"Kane, Fry and Landry",Perkinsbury,Aruba,williamschmidt@watts-west.com,12/17/2021,http://www.weeks-hill.info/ -654,Bonnie,Erickson,Drake Inc,Lake Donbury,Rwanda,blakefitzpatrick@preston.com,1/31/2020,http://mcguire.com/ -655,Ruben,Trevino,Mcneil-Hancock,South Linda,Turkey,ksheppard@moody.com,2/25/2020,http://christian-welch.biz/ -656,Fernando,Perez,"Huang, Preston and Stevens",West Melody,New Caledonia,lancearellano@gentry.com,1/6/2021,http://warren.biz/ -657,Katelyn,Cabrera,"Brown, Valentine and Velez",New Miguel,New Caledonia,msantana@lester.com,6/9/2020,https://www.dickerson.com/ -658,Sylvia,Lin,Rivas-Alexander,Lake Juanport,Nauru,stevensmaureen@watts-tapia.biz,5/9/2021,http://www.mcgee-hood.net/ -659,Margaret,Snyder,Sharp-Oconnor,Cliffordburgh,Brazil,mirandaburnett@bradford-ross.com,10/27/2021,http://www.ware.com/ -660,Trevor,Schwartz,Mcdowell-Chang,Bradfordmouth,Isle of Man,graceherring@pratt.org,2/18/2021,https://www.crosby.net/ -661,Bethany,Colon,"Greene, Mcconnell and Frye",Flemington,Djibouti,mallory42@porter-cox.com,11/3/2020,http://cortez.com/ -662,Dustin,Vaughan,"Ramos, Bishop and Montgomery",Port Ebonyhaven,Eritrea,rebecca69@nolan-hines.com,3/3/2021,http://www.ho.com/ -663,Jim,Oconnell,Rodriguez and Sons,Roseport,New Caledonia,charlespriscilla@adkins.biz,1/16/2021,https://parsons.org/ -664,Kerry,Roberts,"Leon, Morgan and Huff",Lake Rachael,Solomon Islands,brettcrawford@griffin.com,1/17/2021,http://www.gates-torres.com/ -665,Allen,Mcgrath,Parker PLC,Dawsonshire,Saint Kitts and Nevis,lisa85@rivera-schroeder.com,5/18/2020,https://walton.com/ -666,Darlene,Ware,Stafford-Green,East Glenn,Grenada,xrosales@odonnell.info,9/12/2021,http://stein.com/ -667,Jane,Roman,Franco Inc,North Grace,Estonia,hollowaymarcia@boone.com,10/18/2020,https://www.cummings.com/ -668,Sheena,Burns,"Wade, Mills and Walters",South Tashatown,India,garrettwheeler@bullock-cervantes.info,1/28/2021,https://garza.com/ -669,Patricia,Cole,"Phelps, Hobbs and Pratt",North Elaine,Comoros,tiffany09@hunt.biz,12/22/2020,https://cardenas-huff.com/ -670,Shannon,White,Mitchell-Castaneda,Huffville,Korea,romanjoyce@ryan-macias.info,2/6/2020,https://www.norton.com/ -671,Mandy,Farley,Lozano-Wilkins,Powersstad,South Africa,duncanmadison@schmitt.com,2/8/2022,http://www.wheeler.net/ -672,Walter,Barber,"Howell, Burgess and Vega",North Suzanneberg,Nigeria,jimmy88@cruz-mcmillan.net,5/3/2021,https://www.jimenez.info/ -673,Summer,Fox,"Bates, Medina and Hudson",Kimberlyberg,Somalia,peckbenjamin@medina.com,1/20/2021,http://lin.com/ -674,Jeffery,Stuart,Tate-Gordon,Coxburgh,France,karl79@shannon.com,9/12/2020,https://www.townsend-bailey.com/ -675,Carla,Saunders,Allen-Brandt,North Darlenemouth,Zimbabwe,bmoreno@fisher.net,10/4/2021,https://spencer.com/ -676,Dustin,Herman,"Bean, Morse and Reed",Georgemouth,Venezuela,gkane@young.com,11/10/2020,http://griffin.com/ -677,Melody,Mckay,Paul Ltd,Claireberg,Czech Republic,bookerdennis@garrett-winters.com,4/29/2022,https://stafford.info/ -678,James,Ward,Marsh and Sons,Michaelashire,Anguilla,sara28@singleton.net,2/25/2021,http://www.ali.net/ -679,Jasmine,Berry,Garcia-Chaney,Port Rick,Djibouti,garzabianca@reed.com,7/22/2021,https://www.kennedy.com/ -680,Vanessa,Webb,"Flowers, Henry and Craig",Hatfieldbury,Sweden,arielbryant@kaufman-frazier.biz,11/13/2020,http://www.mason.com/ -681,Jermaine,Diaz,Hicks-Chandler,South Seth,Montserrat,shelby88@merritt.com,9/6/2020,https://mathis-solis.com/ -682,Mary,Holder,Lynch Group,Lake Jessicachester,Slovakia (Slovak Republic),alexandraforbes@conrad.info,11/18/2020,http://www.hebert.com/ -683,Johnathan,Mclaughlin,"Brewer, Mckinney and Taylor",South Theresafort,Senegal,stefanie30@hurley-wall.com,12/21/2021,https://www.vargas-hammond.com/ -684,Tracey,Massey,"Pitts, Klein and Gregory",North Theodore,Iran,meghan46@james-villanueva.com,10/16/2021,https://proctor.com/ -685,Tristan,Brooks,Pope and Sons,Ryanshire,Antarctica (the territory South of 60 deg S),maynardkatrina@stone.com,12/11/2021,http://www.guerrero-blake.com/ -686,Sara,Gilmore,Guerrero Inc,Lake Fred,Kuwait,wongwarren@riley.com,4/15/2022,http://estrada.com/ -687,Norma,Page,Carroll-May,Kruegerside,Cambodia,sabrinapeterson@webb.net,1/10/2022,http://www.hubbard-bennett.biz/ -688,Levi,Todd,Roberts and Sons,Lake Justin,Cook Islands,cuevasjade@gilbert.com,2/23/2021,https://bradshaw-singh.net/ -689,Anthony,Curtis,Orozco LLC,Tylermouth,Grenada,colleen70@johnston.com,8/31/2021,https://www.cuevas-robles.com/ -690,Kristine,Hawkins,Schwartz-Fernandez,Juanstad,Gambia,natashawarner@arias.com,2/13/2021,https://www.hickman.net/ -691,Ann,Levy,Spence Inc,New Dillonshire,Armenia,svance@mendoza.com,6/28/2021,http://www.frazier-roy.net/ -692,Laurie,Mccoy,"Mullins, Cohen and Atkins",Princechester,Uzbekistan,hughestyrone@medina-morrow.org,2/28/2020,https://lester.biz/ -693,Stephanie,Rivera,"Arroyo, Wagner and Christian",Cervantesmouth,Costa Rica,leonpedro@moss.org,12/8/2020,https://www.horne-larson.org/ -694,Ebony,Yang,Boyd Ltd,East Edwin,Turks and Caicos Islands,lbray@ferrell.com,3/16/2020,http://www.bennett.com/ -695,Norman,Morton,"Vaughn, Beasley and Holland",West Vanessahaven,Cape Verde,jermaine75@fowler-hancock.com,12/31/2021,https://www.hudson.com/ -696,Joann,Holder,"Cochran, Grant and Blake",Margaretfurt,Tuvalu,strongterrance@wolfe.com,1/25/2020,http://www.huffman-frederick.com/ -697,Catherine,Davis,Orr Group,Erikatown,Cape Verde,clinedennis@madden-cox.com,4/12/2020,https://www.dixon-wyatt.info/ -698,Grant,Mercado,Rich-Pugh,West Jessicastad,Papua New Guinea,pgarcia@floyd.com,12/18/2020,http://www.bryan-reyes.com/ -699,Jessica,Aguirre,"Barron, Stark and Hill",Kristinefurt,Guam,garrettmacias@booth.net,9/21/2021,http://cuevas.net/ -700,Trevor,Bowers,Richmond-Cardenas,Juliafort,Bangladesh,joycedarryl@dillon.info,9/25/2020,http://hayes.info/ -701,Autumn,Norris,Peterson-Boyd,New Alvin,Madagascar,mckenzie25@lang-donaldson.biz,6/30/2020,https://escobar.com/ -702,Nancy,Price,Burch-Barr,Darrenmouth,San Marino,mikaylagreen@coleman-cowan.net,5/30/2021,https://harvey.net/ -703,Kevin,Lozano,"Fletcher, Frazier and Baxter",Lake Francisfurt,Netherlands Antilles,jeffrey39@bautista.com,8/22/2020,https://www.mullins-summers.net/ -704,Dale,Hendricks,Levine-Larsen,Carrfurt,Turkmenistan,robertamays@waters-joseph.com,7/9/2020,https://morse.com/ -705,Alexis,Carroll,Foley Inc,New Kristi,Qatar,patricia79@fritz.net,8/1/2021,https://www.salas.com/ -706,Javier,Bowers,"Flores, Rodgers and Flores",New Samantha,Ethiopia,harmonclaire@reilly.com,3/11/2020,http://www.soto.com/ -707,Zoe,Guerra,Hogan-Butler,North Bethanyhaven,Qatar,cnewman@price-morrow.com,5/4/2021,https://berry.com/ -708,Tim,Henson,"Keith, Stephens and Wyatt",Maddenmouth,Armenia,matthew00@sheppard.com,11/5/2020,http://www.vazquez.com/ -709,Tim,Larson,"Hurley, Bentley and Acosta",Lake Lindaton,Papua New Guinea,toni70@santiago.com,10/5/2021,http://price-roberson.biz/ -710,Brian,Banks,Solomon-Cortez,Lyonsberg,Cambodia,fordholly@mason.biz,10/18/2021,https://www.swanson-oliver.com/ -711,Destiny,Cooke,Mueller-Kent,North Shaun,Saint Pierre and Miquelon,wandaruiz@hurley.com,11/9/2021,http://www.kramer.com/ -712,Austin,Bright,Aguilar PLC,Rebekahshire,Dominican Republic,ryanapril@francis-rowe.com,5/5/2022,https://brennan.com/ -713,Briana,Davis,Stone-Floyd,Castanedamouth,Kiribati,jermainefleming@vazquez-hughes.info,8/21/2020,http://harrell.com/ -714,Darius,Lara,Castillo-Lang,East Yolanda,Northern Mariana Islands,ualexander@braun.org,5/4/2020,http://moon.net/ -715,Toni,Rhodes,"Meyer, Lamb and Flynn",Deniseburgh,Tonga,ayalapamela@reilly.biz,9/3/2021,http://www.small-khan.com/ -716,Warren,Willis,Small-Wade,South Gracebury,Mongolia,qnunez@vincent-gregory.info,4/19/2022,http://ibarra-oneill.biz/ -717,Colleen,Cordova,Glover-Pearson,West Marilynchester,Slovakia (Slovak Republic),hardywesley@barber.biz,12/6/2021,http://rose.com/ -718,Marie,Norman,"Bush, Knapp and Gaines",Alexaside,Tunisia,ogoodman@small.com,11/8/2021,https://horn.com/ -719,Jasmine,Morrison,"Morrison, Graves and Odom",Skinnerland,Guadeloupe,mhayes@moss-martinez.com,4/9/2020,https://james.com/ -720,Catherine,Perkins,Meyers and Sons,West Gabriela,Honduras,ebony76@odom.org,5/11/2021,http://jordan.com/ -721,Nancy,Williams,Gordon-Hayes,Masseytown,Burkina Faso,mcmillankarla@jarvis.com,4/27/2021,http://morrow.com/ -722,Noah,Hebert,Gomez-Lester,Ortizside,Tanzania,abarrett@hodges-anthony.org,7/8/2020,http://www.powers.org/ -723,Latoya,Gillespie,Peters-Cruz,West Coreyview,Haiti,aprilbranch@knight.com,3/23/2022,https://cantrell.com/ -724,Chelsea,Oneal,"Haney, Curry and Griffith",Mcdanielhaven,Antarctica (the territory South of 60 deg S),prestonbarnett@bell-haynes.com,8/20/2020,https://www.turner.com/ -725,Katie,King,Schaefer-Bullock,East Rodneyland,Malta,potterdamon@mccall.com,8/20/2021,http://christian.com/ -726,Colin,Doyle,"Carroll, Walsh and Benjamin",Lesliefurt,Liechtenstein,allenbrady@robbins.biz,4/20/2020,http://www.jimenez.biz/ -727,Sonya,Schroeder,Curry Inc,Petersburgh,Nicaragua,kayla90@andersen-huber.com,8/7/2021,http://bowman.com/ -728,Kayla,Guzman,"Ali, Fleming and Madden",Kiddfort,Central African Republic,patricksteve@fleming.info,4/28/2022,https://lozano.com/ -729,Christie,Jenkins,Lloyd Group,East Angieside,Dominica,breese@oneill.com,7/22/2021,https://aguirre.biz/ -730,Joanne,Ferguson,"Reed, Navarro and Barber",Baileyside,Hungary,haley88@contreras-farmer.com,7/4/2020,http://madden.net/ -731,Yolanda,Robinson,Carey Ltd,West Tyronestad,Wallis and Futuna,daniel92@lynch.com,12/20/2021,https://mcbride.org/ -732,Katie,Craig,Garrison-Gordon,North Jamiemouth,Oman,ronaldwheeler@figueroa-hendrix.net,4/7/2020,https://www.tyler-harding.com/ -733,Dakota,Chavez,"Byrd, Hart and Pham",Brewerbury,Azerbaijan,chanpatty@mclaughlin.com,5/30/2021,http://mann.com/ -734,Johnny,Randolph,"Rivas, Maxwell and Farley",East Daniellestad,United States of America,martindoyle@villegas.net,5/25/2021,http://www.tapia.com/ -735,Collin,Alvarez,Ho LLC,Port Lawrence,Jordan,ritablake@shepard.biz,2/7/2022,http://richmond.com/ -736,Cassidy,Melton,"Guerra, Boyd and Palmer",Lorettaland,Guinea,qmooney@banks-huber.net,3/14/2020,https://www.rich.com/ -737,Adam,Singh,Johns Group,Lake Mckenzieville,Bolivia,chandlerluis@forbes-dickson.org,10/11/2021,https://www.hodge.org/ -738,Hayden,Pugh,Woodward-Guerra,New Marc,Israel,aprillewis@pham-tanner.com,10/25/2021,https://www.rosales.org/ -739,Kellie,Salas,"Cross, Kennedy and Bray",East Jeanne,Turkey,peter54@bentley-morrison.com,11/16/2020,http://www.fletcher.net/ -740,Caleb,Watkins,Nelson LLC,Levihaven,Luxembourg,isaiah61@vazquez.com,7/31/2021,https://www.fowler.com/ -741,Troy,Fritz,Mercado PLC,South Taylor,Cuba,rmartinez@huber-larsen.info,10/13/2021,http://riggs-estes.net/ -742,Phillip,Duffy,"Booker, Ritter and Daugherty",Lake Emily,Saint Lucia,christian65@mayo.com,12/31/2020,http://www.conner.net/ -743,Claire,Robinson,Lynch Inc,West Kevin,Tonga,sbeck@gray.com,4/16/2021,http://www.pace-garrett.biz/ -744,Jackson,Estrada,"Webster, Weeks and Vasquez",Chadborough,Argentina,larry07@long-banks.biz,10/12/2021,https://duarte.com/ -745,Tiffany,Peterson,"Chambers, Montoya and Gray",New Christianburgh,Colombia,alisonali@gibson-paul.com,7/9/2020,http://www.beasley-floyd.com/ -746,Jessica,Carlson,Black Group,Salazartown,Grenada,floydandrew@chang.org,11/13/2020,https://mccullough-brown.com/ -747,Alvin,Roy,Thornton Group,Brittneyton,Israel,gerald08@farrell-roy.com,1/5/2021,https://www.hurst.com/ -748,Harold,Paul,"Griffin, Stanton and Clements",Staffordhaven,Christmas Island,juarezphyllis@cunningham-hodge.org,1/24/2022,https://www.vargas.org/ -749,Rodney,Underwood,Griffin-Lam,Yvonnemouth,Iceland,tracihayden@clarke-little.biz,7/21/2020,https://olsen.info/ -750,Adrian,Herman,Terrell-Schultz,North Beverly,Iraq,gutierrezbob@paul-mccoy.com,1/29/2021,https://www.bruce-fields.net/ -751,Clifford,Phillips,Ramsey LLC,Lake Blake,Martinique,andersongrace@odom.org,3/1/2021,http://friedman.com/ -752,Regina,Mccoy,"Ponce, Malone and Waller",Burchchester,French Polynesia,rushshari@day-singh.com,10/13/2021,http://moran.com/ -753,Kaylee,Bowers,Horn LLC,Johnsonland,Pakistan,billymiranda@knight.biz,2/15/2022,https://lee.com/ -754,Reginald,Frederick,"Burke, Hopkins and Bradley",Douglasstad,France,robert91@maldonado-knox.net,1/20/2020,https://www.pruitt-anderson.biz/ -755,Shelby,Reese,Walton-Greene,New Margaret,Tunisia,charlene99@dixon.com,4/9/2021,https://www.krueger-hanna.com/ -756,Connor,Gentry,"Maldonado, Lowe and Espinoza",West Cristian,Trinidad and Tobago,igraham@flowers-stein.biz,2/11/2021,http://www.mora-hanna.com/ -757,Kevin,Fleming,Frye-Haney,New Shannonstad,Trinidad and Tobago,justin34@ruiz-wood.info,2/26/2022,http://www.hampton-chapman.com/ -758,Connie,Mercado,"Weeks, Perez and Andersen",New Vincentstad,China,gallagherfrank@fitzgerald.com,2/13/2022,http://www.petty.biz/ -759,Kaylee,Barber,Odom-Nolan,Solistown,Chile,nicholasmaldonado@west.com,4/21/2021,https://pitts.biz/ -760,Betty,Duncan,"Raymond, Frazier and Burton",Jacobsonchester,Denmark,joel57@singleton.org,6/7/2020,https://ford.com/ -761,Dwayne,Sweeney,Roth-Griffith,North Kenneth,Bolivia,rbryant@burnett.com,6/8/2021,https://www.lin.info/ -762,Kenneth,Anthony,"Hale, Payne and Saunders",Fletcherborough,Tanzania,piercefranklin@livingston-bass.biz,3/10/2020,https://swanson.biz/ -763,Peggy,Montoya,Oconnell PLC,Port Brandyside,Poland,goldengilbert@goodman.org,4/6/2021,https://petersen.com/ -764,Jonathan,Briggs,Atkins-Atkinson,Lake Aprilchester,Japan,dicksonshannon@levine.net,3/29/2020,https://www.ewing.com/ -765,Sean,Gray,Larsen-Payne,Berrymouth,China,drewmcgrath@bowen.com,5/21/2022,http://www.schroeder.org/ -766,Rebekah,Villegas,Casey Inc,Langberg,Venezuela,erik89@woodard.biz,4/26/2021,http://www.avery.com/ -767,Lisa,Small,Lane-Daniel,New Xavier,Micronesia,chungsharon@zavala-bond.info,8/8/2021,https://franco-wright.info/ -768,Paige,Pineda,"Cole, Huynh and Vang",Port Judith,Palau,hughescesar@pacheco.com,4/10/2022,https://pearson-wyatt.com/ -769,Howard,Glass,"Carter, Wiggins and Paul",New Kelliechester,Brazil,ykeith@lloyd.biz,2/26/2021,http://www.fox.org/ -770,Jose,Bond,Tucker Group,Lake Lydiatown,Mongolia,roberta05@bates.com,3/23/2021,http://hurst.biz/ -771,Janet,Bass,Hudson LLC,Port Lindsey,Christmas Island,yorkjermaine@noble-hayes.net,4/18/2020,https://www.huffman.com/ -772,Jasmin,Waters,Chandler-Holt,South Marisachester,Hungary,pclark@ortega.com,5/26/2022,https://costa-owens.com/ -773,Jennifer,Mcintosh,"Glover, Keith and Lozano",Simonside,Aruba,bianca43@nixon.com,3/18/2022,https://www.farley-powers.com/ -774,Gina,Benson,Lawson Group,Colechester,South Africa,geoffreywagner@willis-macias.com,7/17/2020,http://www.vargas.com/ -775,Vanessa,Gardner,Silva-Bauer,Meghanstad,Italy,haley36@maynard-richard.biz,12/22/2021,https://mclaughlin.com/ -776,Brenda,Clark,Coleman-Bishop,Noblemouth,Thailand,taylor22@chavez.net,1/29/2022,http://www.mayo-mosley.net/ -777,Chelsea,Randolph,Nixon Ltd,New Candice,Kyrgyz Republic,corey96@conner.com,5/22/2021,https://www.nelson.net/ -778,Tabitha,Logan,Hodges-Cummings,New Lance,Myanmar,melvinwilkerson@serrano-ochoa.com,6/15/2020,http://www.allen.org/ -779,Anne,Cabrera,Gay Inc,Toddtown,Australia,sabrina88@buchanan-richards.net,10/16/2020,http://www.kramer.com/ -780,Logan,Lamb,Chavez-Haas,North Erin,Korea,wsutton@vega.com,3/29/2020,http://case.com/ -781,Reginald,Mann,Pitts-Weeks,East Brandon,Netherlands,gabriela19@rivera.net,5/12/2020,http://weeks.org/ -782,Bob,Rosario,Spencer Ltd,Fowlerfort,Turkey,elijah10@zimmerman.com,7/4/2020,http://www.bush.com/ -783,Andrew,Weiss,Stewart Inc,New Pam,Nicaragua,monica69@cohen.org,2/16/2021,http://dalton-lucero.com/ -784,Rita,Livingston,Valentine Inc,North Whitneyborough,Guyana,earias@rangel.info,11/11/2020,http://browning-wiggins.biz/ -785,Briana,Peck,Salas LLC,Port Curtis,Chile,miguelwilkinson@shaffer-beasley.com,12/28/2020,http://curry.com/ -786,Brady,Mcdaniel,Knapp-Rodgers,Debraberg,Costa Rica,echapman@small.biz,5/17/2022,http://www.espinoza.com/ -787,Clifford,Rivers,Black-Lam,West Michelle,Niue,khoover@atkins.com,10/14/2020,https://ho.com/ -788,Kathryn,Burgess,Aguirre and Sons,South Andre,Uruguay,hayden76@estrada-michael.info,7/7/2021,https://fitzpatrick-mason.com/ -789,Rodney,Esparza,Mckay Group,East Ricky,Holy See (Vatican City State),bkirby@arellano.com,6/12/2021,https://www.grimes.net/ -790,Gabrielle,Vincent,Newton LLC,North Perryshire,El Salvador,isaacbyrd@chang-mcdonald.com,10/26/2020,http://lozano-macdonald.info/ -791,Yolanda,Schroeder,Johnston Ltd,Malikchester,Mauritius,andresgrimes@graves.org,12/7/2021,https://www.fritz.biz/ -792,Jonathon,West,Rice-Nichols,North Staceyport,Heard Island and McDonald Islands,maureen96@heath.info,8/15/2020,http://davidson-reyes.com/ -793,Lee,Harvey,Obrien Ltd,Port Leon,Ireland,tracie58@marsh.com,5/28/2021,https://howard.net/ -794,Yvette,Mccoy,"Reeves, Harding and Bowman",East Monica,Morocco,aatkinson@suarez.biz,3/14/2021,https://www.wolfe.com/ -795,Kayla,Fleming,Gregory Group,Clarketown,Saint Barthelemy,emma49@dunlap.biz,1/12/2021,https://www.joyce.biz/ -796,Vernon,Flowers,"Lawrence, Villegas and Sweeney",New Terryton,Greece,xkeller@cole.com,9/3/2021,http://collier.org/ -797,Bobby,Dodson,Fuller PLC,Mariamouth,Guyana,eugenemarsh@english.com,3/30/2022,http://www.meyers.com/ -798,Lindsey,Blevins,Fischer-Garrison,East Nathanielview,Reunion,lacey67@weeks.net,8/11/2021,http://www.hodges-rojas.biz/ -799,Jared,Howe,"Pacheco, Dennis and Velazquez",New Diane,Austria,larryhoffman@jarvis.info,1/11/2022,http://www.green-huffman.com/ -800,Mike,Mckay,Aguilar-Carney,Lake Lydiafurt,Samoa,sschroeder@proctor.com,2/16/2022,https://cantrell-conner.info/ -801,Leon,Summers,Owen Group,Holmeschester,Guernsey,shelia48@salazar-petty.com,8/26/2020,https://arnold.com/ -802,Darren,Gibbs,Baldwin-Best,Glennchester,Saint Barthelemy,maddoxdevin@burch.com,6/29/2021,http://www.ward.info/ -803,Chase,Lucas,"Mills, Esparza and Carpenter",Johnmouth,Saint Helena,cannontyler@townsend.com,2/15/2020,https://hobbs-hendricks.com/ -804,Kent,Cobb,Wolfe Inc,Schwartzchester,Belize,tracicabrera@park.com,6/17/2021,http://www.knight-ferguson.biz/ -805,Miranda,Lawson,Baldwin PLC,West Claudiahaven,Montenegro,audreymarks@howell.com,1/31/2022,https://norman-goodman.com/ -806,Kathy,Nguyen,Vaughan LLC,Kelleyborough,Cape Verde,breanna07@maddox.com,5/29/2021,http://www.sandoval.com/ -807,Jeremiah,Rubio,Chambers Group,Caseyborough,Russian Federation,wblake@oliver.com,4/25/2020,http://www.flowers.com/ -808,Gilbert,Barry,Parker-Nguyen,Gilmoreport,Central African Republic,sheenaboyle@stephens.net,8/22/2021,http://hinton.com/ -809,Tony,Shannon,Gutierrez PLC,Acevedomouth,Solomon Islands,bailey25@parks.net,6/23/2021,https://www.gallegos.org/ -810,Dana,Salas,Barton Ltd,Lake Tabitha,Liberia,prattdouglas@todd-wolf.info,2/27/2021,https://www.glass.biz/ -811,Benjamin,Galvan,Ewing-Everett,Masseymouth,Antarctica (the territory South of 60 deg S),masoncarson@meadows-deleon.com,10/13/2021,http://www.may.com/ -812,Teresa,Vargas,Sherman-Miranda,Velasquezstad,Finland,tim18@daugherty-raymond.com,10/2/2021,http://www.shea.com/ -813,Tricia,Leon,Lutz-Spears,Lake Tannerside,Antigua and Barbuda,brendantodd@curtis-gross.com,11/4/2021,http://sawyer-ellison.org/ -814,Carmen,Daugherty,Russo Ltd,Batesport,Saint Helena,riosraven@case.com,3/21/2020,http://www.sullivan-fleming.com/ -815,Tiffany,Rowe,Maynard-Mcbride,Smithview,Belgium,alice75@burke-peck.net,4/20/2022,http://www.mcbride.com/ -816,Wesley,Snow,Higgins LLC,East Melinda,Turkey,farleygene@white.biz,3/7/2021,https://www.blanchard.com/ -817,Raven,Mejia,Fisher-Lopez,Duncanside,Antarctica (the territory South of 60 deg S),princeadrienne@patrick-brewer.com,9/24/2020,http://www.michael.com/ -818,Elijah,Richardson,Fowler-Owen,Willietown,Cambodia,moonbeverly@leblanc.com,10/19/2020,https://www.ball-bradley.com/ -819,Colton,Sandoval,Villarreal-Lang,Zavalaview,Gabon,mosleyheidi@walter.com,2/18/2021,http://www.perkins.com/ -820,Krista,Mcclure,Simon and Sons,North Marissatown,Iceland,wyattwinters@cole.org,5/30/2021,https://zavala.com/ -821,Johnny,Rubio,"Porter, Johnston and Mullins",New Melodymouth,Thailand,skaufman@cline.com,1/6/2021,https://www.yu.com/ -822,Amy,Ballard,Rice Group,Briannaland,Canada,brockoscar@cole.org,3/1/2021,http://lane.com/ -823,Rachel,Watts,Holloway-Nolan,Aaronville,Gibraltar,kelliroy@sawyer-barker.com,5/12/2021,https://www.jordan-wolf.info/ -824,Judy,Mullins,Quinn Inc,Lake Shelby,American Samoa,hdonaldson@harmon.com,8/27/2021,https://www.mcneil.com/ -825,Priscilla,Huff,Saunders and Sons,New Brettmouth,Malta,gregoryrobyn@summers.com,9/13/2021,http://munoz.biz/ -826,Zoe,Solomon,Meyers-Odonnell,Bettyland,Wallis and Futuna,angelicaarias@harmon-cabrera.info,9/16/2021,https://www.bender-church.org/ -827,Gabriella,Ho,Sawyer PLC,Danielview,Chad,ericperry@simon.com,4/19/2022,http://knight.info/ -828,Jeff,Jensen,Haynes Group,Josephfort,Indonesia,alice96@payne.com,5/24/2021,https://mckay.com/ -829,Natalie,Ball,Garrett PLC,Port Laurie,Burundi,scottdrew@vazquez.com,4/11/2022,http://boyd-rios.com/ -830,Matthew,Fields,Sullivan-Archer,North Sabrina,Anguilla,eriksnyder@giles-bowers.com,4/11/2020,https://www.kennedy.com/ -831,Jack,Mendoza,"Cardenas, Bass and Callahan",Quinnfurt,Puerto Rico,kmccullough@bryant.com,5/28/2022,https://colon.net/ -832,Sheryl,Lyons,Henson-Trevino,South Roberta,Algeria,mcfarlandrobin@callahan-wilkins.com,4/7/2022,http://schmitt.com/ -833,Nina,Contreras,"Mcknight, Horne and Thornton",New Savannah,Yemen,willisemily@kidd.com,5/6/2020,http://mcclain.com/ -834,Samuel,Walker,"Gallegos, Paul and Williamson",Ginaport,Gabon,grace28@bullock-mcpherson.com,1/9/2021,https://nielsen.info/ -835,Charlotte,Spencer,Chambers Ltd,Port Tyronemouth,Antigua and Barbuda,ashleyrubio@holder.org,7/1/2020,http://farley.biz/ -836,Eduardo,Schultz,"Valdez, Pierce and Compton",Christianbury,Lao People's Democratic Republic,monique25@scott.com,4/2/2020,https://www.ball-oneill.net/ -837,Adrienne,Medina,"Benitez, Wilkinson and Mooney",Darrellstad,Nepal,edwardsjoyce@freeman-chang.info,3/28/2022,https://www.french-bennett.com/ -838,Darrell,French,"Macias, Dodson and Blackwell",Vegaview,Malaysia,mayerdaryl@krueger.com,2/19/2020,http://www.baxter-holloway.com/ -839,Rodney,Dunn,Poole LLC,Hardingshire,El Salvador,candacecalderon@green.com,12/24/2020,https://www.meadows.com/ -840,Bryan,Sampson,Clark-Murray,Stricklandborough,United States of America,allison44@bonilla-schmidt.info,2/6/2021,https://www.arnold.com/ -841,Gregory,Baxter,Levine Inc,Schultzfort,Senegal,mcochran@woods-norton.org,8/24/2020,http://www.hooper.com/ -842,Edgar,Norman,Gould-Heath,Lake Kayleeville,Yemen,orangel@bennett.com,5/4/2022,http://crawford.com/ -843,April,Garner,"Mckay, Moody and Rowland",Erikamouth,Hong Kong,maloneclayton@figueroa.org,3/3/2022,https://www.noble-warner.com/ -844,Mercedes,Bush,Davies-Johnson,North Katelyn,India,patricia64@ballard.com,2/4/2020,https://www.terrell-shaffer.info/ -845,Theresa,Randolph,Gilmore-Carr,Janicefort,British Virgin Islands,hcortez@greene.com,7/27/2021,https://www.house.biz/ -846,Tom,White,"Golden, Guzman and Webster",Lake Sean,Reunion,moniquemcneil@dudley.com,2/27/2021,https://www.salinas.biz/ -847,Tiffany,White,"Jacobs, Griffin and Fuller",Pambury,Qatar,dorisfritz@curry.org,9/7/2021,http://love-ross.info/ -848,Lacey,Clark,Wells-Riley,West Lydiamouth,Belgium,mcbridebrad@kirk.com,2/16/2020,http://mayo-cline.net/ -849,Gail,Thornton,Hines Group,Julieport,Cocos (Keeling) Islands,melinda57@pham.com,11/15/2021,http://carson.com/ -850,Leslie,Figueroa,Norris-Randall,Dillonmouth,Zimbabwe,geoffreysteele@rios-morrow.net,2/23/2020,http://ware.com/ -851,Katie,Mcintosh,Gibson and Sons,New Darrenfort,San Marino,stephenharper@wall.com,7/16/2021,http://curry.com/ -852,Kelly,Bradshaw,"Horn, Sherman and Barry",West Raven,Uganda,tgill@novak-patton.com,6/18/2021,http://leach-carlson.net/ -853,Blake,Avila,Bernard Inc,New Frances,Morocco,alexander77@mcdonald-daniel.info,6/22/2020,http://www.mosley.com/ -854,Jesse,Miranda,Johnson Group,Ellisland,Nigeria,richmondkeith@kelley-young.com,8/24/2020,http://acosta-grimes.com/ -855,Kristi,Thompson,"Lyons, Arroyo and Nash",New Lanceview,Antigua and Barbuda,ericalee@stanley-waller.net,9/15/2020,http://lawson-deleon.com/ -856,Isabel,Snow,"Cook, Lee and Maldonado",North Lynntown,Cyprus,ralphmcdonald@ware-bryan.biz,3/11/2020,https://mays-gentry.net/ -857,Donna,Sims,"Buckley, Bond and Parsons",North Davechester,Pakistan,mackenziemaldonado@colon.com,1/23/2021,https://fritz.com/ -858,Connor,Thomas,Wilcox-Edwards,Janiceview,Austria,darryllucero@herrera-melendez.com,11/13/2020,https://www.golden.com/ -859,Chloe,Hanson,Nguyen PLC,Daisytown,Svalbard & Jan Mayen Islands,mullinsrita@whitney-frey.com,11/14/2021,https://jimenez.info/ -860,Drew,Guzman,Chung Ltd,West Kirkbury,Guatemala,morrisonann@levy-gillespie.com,3/12/2021,https://bailey-lang.org/ -861,Clifford,Conway,French LLC,Brandtshire,Chad,kerrynewman@walters-herrera.com,5/12/2021,https://coleman-kline.com/ -862,Ashlee,Carney,"Fitzgerald, Rios and Stewart",Fullerbury,Estonia,vguerra@newman-higgins.com,2/4/2021,http://www.huffman-howard.com/ -863,Jackie,Mccullough,Davidson Inc,West Randallshire,Guatemala,spencergalloway@rivers.com,3/6/2022,https://www.zavala-stephenson.info/ -864,Crystal,Dougherty,Rodgers-Kelly,Alfredshire,Swaziland,stuart08@grant.com,1/26/2022,https://barnes.net/ -865,Caitlyn,Tapia,Farley LLC,Ewingchester,Anguilla,cartersally@moon.com,4/14/2021,https://morse-ellis.com/ -866,Brenda,Estrada,"Lee, Pearson and Parsons",South Gilbertmouth,Mali,hardinrebekah@burton.org,2/17/2020,http://marks.info/ -867,Tina,Andrews,Dickerson and Sons,Charlenebury,Chile,karlflynn@riley.com,2/3/2022,https://conway-lowery.com/ -868,Andre,Cunningham,Cervantes Inc,Lake Jesus,Liberia,juliannguyen@herrera.com,3/5/2020,https://www.leonard.com/ -869,Jesus,Abbott,"Pratt, Durham and Conley",Port Jermaine,Jamaica,pkent@gillespie.com,11/7/2020,http://www.conley.com/ -870,Kendra,Bishop,Vasquez PLC,Maysberg,Armenia,greg54@vang.com,7/13/2021,https://vargas.com/ -871,Tyrone,Ayers,Welch Inc,Bryantland,Barbados,pamelatorres@walsh.com,9/12/2020,http://ballard.com/ -872,Isaiah,Andersen,"Lang, Solis and Cunningham",Laurieport,Oman,vparrish@marsh-kane.com,5/2/2021,https://macdonald.com/ -873,Shelia,Rojas,"Conley, Moody and Maddox",West Ana,Marshall Islands,gonzalezkari@dominguez.com,5/11/2021,https://www.meyers.com/ -874,Donald,Arnold,"Dunn, Holt and Flowers",Bonnieport,Congo,isaiahescobar@wyatt-baxter.net,4/30/2021,http://merritt.com/ -875,Kaitlin,Fowler,"Merritt, Duarte and Marshall",Wilkinsonton,Georgia,nathan99@patel.net,6/26/2020,https://www.blair.com/ -876,Mallory,Browning,"Yang, Waller and Castillo",Lake Faithton,Equatorial Guinea,rfuller@hays.org,1/16/2020,http://www.day-stevenson.biz/ -877,Cathy,Compton,Lara Ltd,Johnstonfurt,Somalia,kathleen13@cobb-durham.biz,5/31/2021,http://www.cox.com/ -878,Larry,Blanchard,Macias-Cole,Trujilloside,Isle of Man,rhondachang@hooper.com,3/14/2022,http://www.oneal-stewart.net/ -879,Robert,Henderson,Cuevas Group,East Kristy,Mauritius,bauerfernando@lester-baldwin.info,4/12/2022,http://www.luna.info/ -880,Glen,Simon,Cruz and Sons,Lake Marcfurt,Zimbabwe,rbrown@blair.com,6/3/2021,http://www.tapia.com/ -881,Holly,Nelson,Valentine Ltd,Monicatown,Christmas Island,sarah29@mcdonald.biz,1/10/2021,https://ortega-vasquez.biz/ -882,Erin,Wall,Richards Ltd,Hancockburgh,Korea,finleyangie@carson-medina.com,12/25/2021,https://harrell.biz/ -883,Chloe,Durham,Colon LLC,Derrickland,Guyana,isabel97@small-pope.net,3/28/2022,https://www.reese.net/ -884,Jeanette,Schwartz,Mendoza-Garrison,Jeffport,Suriname,brittany40@ho-jenkins.com,1/12/2020,http://www.leach-sparks.com/ -885,Max,Clements,Phillips-Stanley,Riosville,Haiti,garyglenn@richard-kirby.info,2/10/2021,https://mcdonald.com/ -886,Julie,Coffey,Walls Inc,East Elizabethfort,San Marino,wbrady@hayes.com,3/9/2020,https://www.elliott.info/ -887,Regina,Gilmore,"Wright, Rogers and Chandler",Bellchester,Northern Mariana Islands,joannejohns@rich.biz,4/20/2020,http://www.paul-duarte.com/ -888,Lacey,Stark,Jefferson-Larson,East Emily,Switzerland,vcombs@curry-benjamin.com,9/9/2020,http://ball-patterson.com/ -889,Devin,Jefferson,Moyer-Martinez,Dickersonville,Argentina,tdaniels@cordova-baird.net,8/19/2020,http://www.oconnell-burgess.com/ -890,Shawna,Spencer,Gallegos-Eaton,Kelliville,Malawi,morgan50@phelps-owens.com,12/24/2021,http://tanner.com/ -891,Edgar,Meyers,"Ochoa, Durham and Fowler",Miguelhaven,French Southern Territories,robersonclifford@byrd-salas.com,2/25/2022,http://www.joyce-mcconnell.org/ -892,Levi,Larsen,Davenport-Roy,Costaville,Cayman Islands,brandihickman@lyons.com,12/2/2020,https://briggs.net/ -893,Victor,Simon,Washington-Horne,Maddoxland,United Arab Emirates,teresaholt@coffey.info,11/9/2021,https://frye.org/ -894,Lacey,Boone,Kim-Nash,Lake Craigchester,Belize,estuart@stein.com,11/18/2021,https://espinoza.com/ -895,Chelsea,Lester,Vaughan and Sons,Taylorport,Nepal,murrayeric@mcgrath.com,5/15/2021,http://www.williams.biz/ -896,Dominique,Benjamin,Brooks-Estrada,Jimmybury,Liechtenstein,jackson23@ashley.com,9/15/2021,http://www.ho.com/ -897,Cheyenne,Marshall,Molina-Love,Waltershire,Aruba,debbiedeleon@barber-flynn.info,11/7/2021,http://www.rowe-flores.com/ -898,Frances,Vance,"Montoya, Munoz and Riggs",North Evelynmouth,Ukraine,rlutz@dodson.org,10/31/2021,https://www.galvan.com/ -899,Melvin,Todd,Padilla LLC,West Kristin,Mauritania,brian89@casey.info,3/12/2022,http://www.morse.com/ -900,Gilbert,Larson,"Lambert, Olsen and Kent",Vanessabury,Gambia,alexander26@middleton.com,1/24/2022,https://abbott-wong.com/ -901,Tim,Hawkins,Frederick-Rollins,New Maryfort,Saudi Arabia,kenneth60@mcgrath.net,9/1/2020,https://www.pace-fernandez.com/ -902,Peter,Glass,Lynn and Sons,New Herbert,Bahrain,pamela16@tapia-wolf.biz,12/25/2021,http://gould.org/ -903,Dan,Shepard,Tucker-Hensley,East Shelleyfurt,Gabon,richardkatherine@fields.com,4/29/2022,http://rose.com/ -904,Crystal,Garcia,"Norman, Church and Cortez",Port Calebhaven,Armenia,ymejia@wolfe-vargas.com,6/7/2020,https://bonilla-blankenship.com/ -905,Helen,Diaz,"Haney, Wong and Hines",Sloanstad,Sri Lanka,ethornton@francis.com,12/20/2021,https://choi.info/ -906,Christine,Cervantes,"Greer, Buckley and Macdonald",Billytown,Timor-Leste,alexander60@bruce.net,1/10/2021,http://rocha-yates.com/ -907,Jeffery,Zhang,"Merritt, Adkins and Hendricks",Port Jodi,Angola,ujarvis@watts.com,5/14/2020,https://hunter.net/ -908,Mario,Nolan,Stuart-Livingston,North Dariusview,Vanuatu,pamelaneal@valencia.com,7/30/2020,https://shea.biz/ -909,Abigail,Cochran,"Valentine, Foster and Church",North Shannon,Switzerland,elijahcherry@mann.com,3/1/2020,http://larson.com/ -910,Darlene,Nelson,"Haynes, Rasmussen and Shelton",New Julia,Fiji,sbarton@shepard.com,9/13/2021,http://www.haley.org/ -911,Dwayne,Davidson,Crosby Inc,Campbellside,New Caledonia,johnsonterry@powers.net,11/14/2020,https://www.johnston.info/ -912,Gregory,Osborne,Davidson-Collins,Gabrielleside,Romania,maureenstanley@nolan.com,7/16/2020,http://www.clay-mckinney.net/ -913,Latoya,Clements,Russell LLC,Orrview,Congo,edowns@cantu-rodgers.com,5/16/2022,http://huber.org/ -914,Brooke,Savage,"Durham, Prince and Cantrell",Theresaland,French Polynesia,jonathan71@bush.com,4/2/2020,http://www.odom.com/ -915,Savannah,Grimes,Edwards Group,Shellyfurt,Guadeloupe,diana73@gilbert-schneider.biz,8/13/2020,https://www.braun.com/ -916,Donna,Webster,"Fernandez, Wong and Briggs",Yvetteville,Comoros,krausetom@buckley.org,3/10/2022,http://herrera.com/ -917,Jim,Ochoa,"Watts, Yates and Sutton",South Tom,Greece,carlosodom@schultz.org,3/23/2020,http://proctor-martin.com/ -918,Leslie,Spencer,"French, Estrada and Decker",West Ralph,China,huffcaitlyn@charles.com,11/30/2021,https://crawford.com/ -919,Ariana,Harmon,Steele-Osborne,North Collin,Lesotho,joshua45@dillon.org,1/18/2020,https://www.garcia.net/ -920,Paige,Wong,Lam-Cardenas,Laurabury,United Arab Emirates,hammondcaitlyn@aguirre.biz,3/22/2022,https://williams.net/ -921,Darryl,Burnett,Acevedo-Drake,Mckenzieton,Ecuador,emercado@boone-craig.com,12/30/2021,https://spears.net/ -922,Kristopher,Glass,Weeks Ltd,Hancockshire,Lithuania,aprilblevins@cummings-parrish.info,3/25/2020,http://le.com/ -923,Michael,Huff,"Carroll, Ballard and Zhang",West Bryan,Luxembourg,traci55@golden.com,8/12/2020,https://cervantes.com/ -924,Douglas,Wilkerson,Salazar-Kelley,New Marcus,Netherlands,epope@powell.com,5/24/2020,https://preston-willis.org/ -925,Tanya,Howard,"Pierce, Riddle and Black",South Alisha,El Salvador,kmelton@strong.biz,3/27/2022,http://www.martin.com/ -926,Christy,Franklin,Landry-Martin,Oliviatown,Angola,coltonarellano@boyle.com,4/15/2022,http://www.durham.biz/ -927,George,Dorsey,Castillo-Lester,East Alan,Switzerland,villanuevalogan@murillo.info,2/27/2021,https://www.stanton.biz/ -928,Colton,Nixon,Duncan PLC,Kanemouth,Ghana,nmccullough@vasquez.info,8/15/2021,https://morton-kim.com/ -929,Daniel,Cobb,Richardson-Woodward,Haynesmouth,Anguilla,barbaranorman@vazquez.com,1/28/2020,http://shields.biz/ -930,Cristian,Ball,"Evans, Mahoney and Campbell",Port Larryshire,Nauru,vwagner@blevins-alexander.biz,8/5/2021,https://www.velez.com/ -931,Francis,Goodman,Ortiz-Morgan,Lake Thomas,Afghanistan,marthayoung@ellis.net,2/8/2022,https://rowland-hendricks.com/ -932,Dylan,Boyer,"Little, Stanley and Mcbride",New Bradybury,Anguilla,randy44@raymond.com,1/30/2021,https://jennings.net/ -933,Olivia,Cooke,Norton and Sons,New Andrea,Cayman Islands,gbender@brown-baxter.com,7/17/2020,https://www.stevenson.biz/ -934,Dana,Cohen,Bridges-Moyer,Port Gavin,Andorra,herbertparker@wallace.biz,4/18/2022,http://www.mckay.com/ -935,Megan,Mills,"Hale, Elliott and Richard",Conradport,Montenegro,gavinhaas@harrison-barton.info,4/9/2021,http://kramer-henry.com/ -936,Daniel,Hurst,Cooper LLC,Livingstonview,Malaysia,saundersriley@aguilar.com,10/3/2021,https://www.riddle-goodwin.com/ -937,Danielle,Compton,Estrada Inc,Hamiltonton,Comoros,toddskinner@hart.net,5/10/2020,http://www.parsons.info/ -938,Mitchell,Mack,"Ferguson, Osborne and Lawrence",Lake Brooke,Colombia,shannonmcfarland@cisneros.com,3/20/2020,http://knapp-mcfarland.com/ -939,Jessica,Mcknight,"Walls, Fitzgerald and Hill",Jimmouth,New Zealand,jilldixon@frank.com,1/12/2022,https://oconnell.info/ -940,Tyrone,Marshall,"Moran, Walker and Riley",Nortonfort,Togo,carol33@ayala-chase.com,4/30/2021,http://www.johnston-mccullough.info/ -941,Greg,Allen,"Cervantes, Fuentes and Cunningham",North Sheilaland,Greenland,aaron08@davies-rush.com,5/5/2022,http://www.herring.com/ -942,Jill,Schultz,Cantrell-Zimmerman,East Johnathanbury,Serbia,tterry@clayton.net,10/12/2020,http://www.shea-brooks.com/ -943,Noah,Hall,Gould-Bird,Ninaton,Portugal,don16@crawford.net,4/19/2020,http://buchanan-lane.com/ -944,Wendy,Melendez,"Knox, Cervantes and Thomas",Jeffreybury,Macao,salinaseric@osborn.com,5/24/2021,http://www.rich.com/ -945,Guy,Rush,White Ltd,Collinsland,American Samoa,theodorewatson@lucero-ochoa.com,9/6/2020,https://soto-henry.com/ -946,Monica,Joyce,"Pham, Murphy and Watson",Howardville,Burundi,dboyle@donovan.info,3/15/2022,https://www.james.com/ -947,Lauren,Garcia,"Frye, Pacheco and Bowen",Kiarachester,Thailand,penaleon@mccoy.com,2/17/2021,http://www.aguilar.com/ -948,Louis,Norris,Yates-Edwards,Hayleybury,Saint Barthelemy,lindsay05@drake-sanford.com,7/18/2021,https://arias.net/ -949,Shelly,Rasmussen,Hooper Group,Lake Marvin,Netherlands Antilles,dorishaney@morse.com,7/9/2021,https://waller-humphrey.com/ -950,Dave,Espinoza,Sampson-Horne,Jonesshire,Saint Kitts and Nevis,josephpineda@villa.com,3/1/2020,http://osborne.com/ -951,Beverly,Mayo,Ayala Group,East Alejandraland,Holy See (Vatican City State),costabob@daniel.com,1/28/2020,https://james-pruitt.com/ -952,Larry,Key,Riley Group,Mcdonaldchester,Guinea,ygood@vaughn.com,1/4/2020,https://www.arroyo-schultz.com/ -953,Jennifer,Mcguire,Rangel Ltd,Cameronfurt,Burkina Faso,fnielsen@soto-villegas.com,1/10/2022,http://www.flowers.org/ -954,Olivia,Mills,"Chase, Ibarra and Gentry",South Christina,Ethiopia,bhouston@rosario.com,6/24/2020,http://www.horn-gates.com/ -955,Loretta,Simon,"Mcdowell, Lester and Michael",Palmerport,Pitcairn Islands,tduffy@haney.com,10/15/2021,https://www.crosby.net/ -956,Janet,Stevens,"Delgado, Nixon and Nielsen",Perkinshaven,Mozambique,cesar60@bean.com,11/6/2020,http://www.byrd-dougherty.net/ -957,Laurie,Hutchinson,Cardenas-Lee,New Frankborough,Togo,georgegabriella@burch-harrell.com,2/15/2020,https://yates.com/ -958,Shelly,Green,Rose-Mccarthy,New Seanmouth,Jordan,johnathan92@huff.com,4/28/2022,http://quinn.com/ -959,Curtis,Marsh,Hood and Sons,West Jeanette,Indonesia,alvinrivas@mosley.com,9/28/2021,https://www.chambers-vang.info/ -960,Molly,Prince,"Chandler, Marsh and Vaughn",East Mary,United States of America,ewatkins@ewing-brock.org,9/13/2021,https://www.holder.com/ -961,Jaclyn,Mcmillan,Rojas-Floyd,Port Brentside,Portugal,barry52@herring.biz,5/7/2020,http://holloway.net/ -962,Alejandra,Harding,Hobbs-Whitney,East Courtney,Northern Mariana Islands,uhaley@pena.com,4/28/2020,http://www.york-erickson.com/ -963,Angie,Salas,Downs and Sons,Lake Dwayne,Mauritania,kleingregg@melton.net,11/23/2021,https://www.montgomery.com/ -964,Krystal,English,Singleton-Soto,East Mark,Philippines,theodoreharper@meadows.com,4/9/2020,http://rios.com/ -965,Jenny,Chase,Carey LLC,Beverlymouth,Tanzania,nross@hodge.com,10/12/2021,http://www.simpson.com/ -966,Erika,Collier,"Boyd, Sweeney and Gilmore",North Dylan,Tajikistan,lanedorothy@cooper.info,10/12/2020,http://rubio-gaines.org/ -967,Anthony,Meza,Gamble Inc,New Sheriborough,Martinique,latoyahendricks@wheeler-mitchell.info,6/5/2021,http://nichols.org/ -968,Alice,Choi,Reese-Suarez,Hollyside,Sri Lanka,victor56@glover.com,5/13/2020,http://www.avery.com/ -969,Malik,Marks,Horne Ltd,Atkinsonberg,South Georgia and the South Sandwich Islands,carrillotabitha@leblanc.com,4/20/2020,http://www.hart.biz/ -970,Ruth,Jensen,"Villarreal, Waller and Kerr",Glenmouth,Montenegro,morsebeverly@farrell.biz,9/6/2020,https://www.mercer.com/ -971,Jack,Boone,Whitaker-Stewart,South Tammyfurt,Chad,ashley91@blevins.com,8/13/2020,http://www.roman.com/ -972,Charlene,Zamora,Henry-Bender,Marthaland,Heard Island and McDonald Islands,sherryburton@robinson.com,1/25/2020,https://www.barron-sparks.com/ -973,Jorge,Mcgrath,Patterson PLC,Estradahaven,Bolivia,hchambers@barrera.biz,11/5/2021,https://www.morales.com/ -974,Tricia,Harris,Mcguire Inc,Lake Terri,Sierra Leone,madeline80@cooper-middleton.com,8/5/2020,https://www.aguilar-tucker.com/ -975,Tyrone,Nolan,Myers Ltd,New Brett,Indonesia,meadowscarol@solis.biz,4/17/2020,https://morse-estes.com/ -976,Arthur,Pugh,"Hahn, Nichols and Cortez",Collinton,San Marino,bryce90@chambers.net,2/26/2020,https://www.stein.com/ -977,Kristie,Oconnor,Freeman Group,South Laurenberg,Northern Mariana Islands,kellie08@arias.net,4/2/2020,https://www.finley.com/ -978,Albert,Acosta,"Greene, Ruiz and Mora",North Jacquelineville,Fiji,caitlyn95@hardy.com,4/10/2021,http://dorsey-hamilton.com/ -979,Erin,Deleon,Weaver LLC,Charleneland,Holy See (Vatican City State),gabriel69@barr-orozco.org,11/9/2021,https://www.horn.biz/ -980,Jasmine,Robles,Kent-Sharp,East Normaview,Antigua and Barbuda,collinwerner@terrell.com,6/11/2020,http://hammond.com/ -981,Kendra,Zamora,Perez LLC,Bensonmouth,Lao People's Democratic Republic,pecksamantha@huber.com,8/25/2021,http://www.lamb.info/ -982,Dakota,Salas,Greene Group,Coleburgh,Cook Islands,murillomarcia@wyatt.info,7/13/2021,https://www.gregory-hawkins.info/ -983,Jimmy,Wang,Bentley and Sons,Doughertyfurt,Russian Federation,fbarrett@steele-blanchard.com,6/1/2020,https://garner-pierce.com/ -984,Lisa,Yates,Blair Group,Valeriehaven,United States Minor Outlying Islands,beverlypreston@cook-fuller.info,7/29/2020,https://huber.com/ -985,Collin,White,Silva-Walters,Chenton,Bangladesh,andrea14@tran-stevens.org,11/14/2021,https://nash.org/ -986,Brittney,Horn,Wall Inc,South Michael,Iraq,rthornton@hendrix.com,10/17/2021,http://www.giles-barron.net/ -987,Maureen,Hurst,Reilly-Monroe,Mosesberg,Mongolia,wolfecody@marshall.net,1/5/2020,http://blair.com/ -988,Cynthia,Greene,Choi Ltd,Hancockchester,Ghana,chad71@blankenship.org,12/2/2020,https://bridges.info/ -989,Maureen,Rivers,Patrick Inc,Maxwellborough,Guyana,duncaneric@salas-huff.org,5/16/2020,http://chung.org/ -990,Garrett,Villa,"Meadows, Lowe and Brennan",Calebbury,American Samoa,duncanpedro@higgins-maldonado.com,2/27/2020,https://briggs-villa.com/ -991,Bill,Richards,Grant Inc,South Shellyview,Liechtenstein,roger88@holden.com,3/1/2022,http://norton-buck.com/ -992,Kiara,Moon,Olsen-Morse,Hesterburgh,American Samoa,paynesteven@nguyen-richardson.com,12/5/2020,http://yang.org/ -993,Colin,Kaufman,Myers Group,Aaronfort,Isle of Man,robersonjanice@peterson-herring.com,10/4/2021,https://www.roberson-knapp.org/ -994,Evan,Marsh,"Orozco, Valenzuela and Warren",Lake Darrellshire,Philippines,julia00@mcclure.info,2/9/2020,https://turner-giles.biz/ -995,Kristie,Rice,Harper Ltd,East Leslie,Guinea,joycecombs@guerra-burns.com,12/6/2021,https://www.pena.com/ -996,Diana,Monroe,Bass-Wilson,Lake Jacksonmouth,Guatemala,cassidymercado@bonilla.com,1/9/2022,http://castro.net/ -997,Jerry,Morales,Pratt-King,South Dominiquemouth,Georgia,gavin13@logan-downs.com,8/21/2021,https://www.braun.info/ -998,Tracie,Floyd,"Holt, Wilson and Shields",East Chloeshire,Solomon Islands,lowehailey@oconnor.org,4/22/2020,http://www.zavala-rubio.com/ -999,Paul,Barnes,"Brown, Oliver and Haynes",South Shane,Finland,hodgeseddie@hardin-wells.com,3/12/2021,https://stone-randolph.info/ -1000,Dominic,Duran,Durham LLC,East Wendy,British Indian Ocean Territory (Chagos Archipelago),owarner@velasquez.info,12/9/2021,http://www.cox.com/ diff --git a/src/main/resources/db/002_seed.sql b/src/main/resources/db/002_seed.sql index 6833c3e3..8541b030 100644 --- a/src/main/resources/db/002_seed.sql +++ b/src/main/resources/db/002_seed.sql @@ -1,6 +1,4 @@ INSERT OR IGNORE INTO Configurations (Code, Value) VALUES -('Language', 'eng.json'), -('Theme', 'light'), ('SubscriptionNedded', 'False'); INSERT OR IGNORE INTO SchemaVersion VALUES (1); diff --git a/src/main/resources/themes/FlatLaf.properties b/src/main/resources/themes/FlatLaf.properties index 3ec34510..53680d46 100644 --- a/src/main/resources/themes/FlatLaf.properties +++ b/src/main/resources/themes/FlatLaf.properties @@ -37,12 +37,12 @@ PasswordField.revealIcon=backupmanager.icons.PasswordRevealIcon [style]Button.pageNext=$[style]Button.pagePrevious -# Accent colors from flatlaf demo - -Demo.accent.default = #2675BF -Demo.accent.blue = #007AFF -Demo.accent.purple = #BF5AF2 -Demo.accent.red = #FF3B30 -Demo.accent.orange = #FF9500 -Demo.accent.yellow = #FFCC00 -Demo.accent.green = #28CD41 +# Accent colors from flatlaf app + +App.accent.default = #2675BF +App.accent.blue = #007AFF +App.accent.purple = #BF5AF2 +App.accent.red = #FF3B30 +App.accent.orange = #FF9500 +App.accent.yellow = #FFCC00 +App.accent.green = #28CD41 From 45c848719b10a4979763b60f5899c7e78dea8311 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Sun, 5 Apr 2026 21:58:58 +0200 Subject: [PATCH 08/17] translations preference --- .../Entities/ConfigurationBackup.java | 18 +- .../Entities/Configurations.java | 78 --------- .../backupmanager/Entities/TimeInterval.java | 15 +- .../java/backupmanager/Entities/User.java | 4 - .../backupmanager/Enums/LanguagesEnum.java | 6 +- .../java/backupmanager/Enums/MenuItems.java | 11 +- .../java/backupmanager/Enums/ThemesEnum.java | 24 --- .../backupmanager/Enums/Translations.java | 48 ++++-- src/main/java/backupmanager/MainApp.java | 6 +- .../Managers/LanguageManager.java | 58 +++++++ .../backupmanager/Managers/ThemeManager.java | 86 ---------- .../Services/BackupAnalyticsService.java | 117 ------------- .../backupmanager/Services/LoginService.java | 16 +- .../Services/PreferenceService.java | 16 -- .../backupmanager/Utils/AppPreferences.java | 10 ++ .../gui/Controllers/SettingsController.java | 7 - .../gui/customwidgets/ModernTextField.java | 162 ------------------ .../backupmanager/gui/forms/FormSetting.java | 130 ++++++++++---- .../gui/sample/csv/CSVDataReader.java | 93 ---------- .../csv/ConfigurationBackupDataTable.java | 25 --- .../gui/sample/csv/Pageable.java | 40 ----- .../gui/sample/csv/ResponseCSV.java | 10 -- .../gui/sample/csv/ResponsePageable.java | 15 -- src/main/resources/res/languages/deu.json | 7 +- src/main/resources/res/languages/eng.json | 5 - src/main/resources/res/languages/esp.json | 5 - src/main/resources/res/languages/fra.json | 5 - src/main/resources/res/languages/ita.json | 5 - .../ConfigurationsRepositoryTest.java | 57 ------ 29 files changed, 227 insertions(+), 852 deletions(-) delete mode 100644 src/main/java/backupmanager/Enums/ThemesEnum.java create mode 100644 src/main/java/backupmanager/Managers/LanguageManager.java delete mode 100644 src/main/java/backupmanager/Managers/ThemeManager.java delete mode 100644 src/main/java/backupmanager/Services/PreferenceService.java delete mode 100644 src/main/java/backupmanager/gui/Controllers/SettingsController.java delete mode 100644 src/main/java/backupmanager/gui/customwidgets/ModernTextField.java delete mode 100644 src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java delete mode 100644 src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java delete mode 100644 src/main/java/backupmanager/gui/sample/csv/Pageable.java delete mode 100644 src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java delete mode 100644 src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java delete mode 100644 src/test/java/test/repositories/ConfigurationsRepositoryTest.java diff --git a/src/main/java/backupmanager/Entities/ConfigurationBackup.java b/src/main/java/backupmanager/Entities/ConfigurationBackup.java index 9371c04a..33e1c383 100644 --- a/src/main/java/backupmanager/Entities/ConfigurationBackup.java +++ b/src/main/java/backupmanager/Entities/ConfigurationBackup.java @@ -5,7 +5,6 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; -import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.BackupConfigurationRepository; public class ConfigurationBackup { @@ -24,22 +23,6 @@ public class ConfigurationBackup { private int count; private int maxToKeep; - public ConfigurationBackup() { - id = 0; - name = ""; - targetPath = ""; - destinationPath = ""; - lastBackupDate = null; - automatic = false; - nextBackupDate = null; - timeIntervalBackup = null; - notes = ""; - creationDate = LocalDateTime.now(); - lastUpdateDate = LocalDateTime.now(); - count = 0; - maxToKeep = JsonConfig.getInstance().getConfigValue("MaxCountForSameBackup", 1); - } - public ConfigurationBackup(String name, String targetPath, String destinationPath, LocalDateTime lastBackupDate, Boolean automatic, LocalDateTime nextBackupDate, TimeInterval timeIntervalBackup, String notes, LocalDateTime creationDate, LocalDateTime lastUpdateDate, int count, int maxToKeep) { this.name = name; this.targetPath = targetPath; @@ -92,6 +75,7 @@ public final void updateBackup(ConfigurationBackup backupUpdated) { this.maxToKeep = backupUpdated.getMaxToKeep(); } + @Override public String toString() { return String.format("[Id: %d, Name: %s, targetPath: %s, DestinationPath: %s, lastBackupDate: %s, IsAutoBackup: %s, NextDate: %s, Interval: %s, MaxBackupsToKeep: %d]", id, diff --git a/src/main/java/backupmanager/Entities/Configurations.java b/src/main/java/backupmanager/Entities/Configurations.java index 756c37e7..448b09e5 100644 --- a/src/main/java/backupmanager/Entities/Configurations.java +++ b/src/main/java/backupmanager/Entities/Configurations.java @@ -1,86 +1,16 @@ package backupmanager.Entities; -import java.util.Arrays; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import backupmanager.Enums.LanguagesEnum; -import backupmanager.Enums.ThemesEnum; -import backupmanager.Managers.ExceptionManager; import backupmanager.database.Repositories.ConfigurationRepository; public class Configurations { - private static final Logger logger = LoggerFactory.getLogger(Configurations.class); - private static LanguagesEnum language; - private static ThemesEnum theme; private static boolean subscriptionNedded; // if true the subscription is needed to use the backuground service public static void loadAllConfigurations() { - setLanguageByFileName(ConfigurationRepository.getConfigurationValueByCode("Language")); - setTheme(ConfigurationRepository.getConfigurationValueByCode("Theme")); setSubscriptionNedded(ConfigurationRepository.getConfigurationValueByCode("SubscriptionNedded")); } // i don't want to update the subscription value from the code. for now the only method is doing manually public static void updateAllConfigurations() { - ConfigurationRepository.updateConfigurationValueByCode("Language", language.getFileName()); - ConfigurationRepository.updateConfigurationValueByCode("Theme", theme.getThemeName()); - } - - public static void setLanguage(LanguagesEnum language) { - Configurations.language = language; - } - - public static void setLanguageByLanguageName(String selectedLanguage) { - try { - for (LanguagesEnum lang : LanguagesEnum.values()) { - if (lang.getLanguageName().equalsIgnoreCase(selectedLanguage)) { - language = lang; - logger.info("Language setted to: " + language.getLanguageName()); - return; - } - } - logger.warn("Invalid language name: " + selectedLanguage); - } catch (Exception ex) { - logger.error("An error occurred during setting language operation: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - } - - private static void setLanguageByFileName(String filename) { - try { - for (LanguagesEnum lang : LanguagesEnum.values()) { - if (lang.getFileName().equalsIgnoreCase(filename)) { - language = lang; - logger.info("Language setted to: " + language.getLanguageName()); - return; - } - } - logger.warn("Invalid file name: " + filename); - } catch (Exception ex) { - logger.error("An error occurred during setting language operation: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - } - - public static void setTheme(ThemesEnum theme) { - Configurations.theme = theme; - } - public static void setTheme(String selectedTheme) { - try { - for (ThemesEnum t : ThemesEnum.values()) { - if (t.getThemeName().equalsIgnoreCase(selectedTheme)) { - theme = t; - logger.info("Theme set to: " + theme.getThemeName()); - return; - } - } - logger.warn("Invalid theme name: " + selectedTheme); - } catch (Exception ex) { - logger.error("An error occurred during setting theme operation: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } } public static void setSubscriptionNedded(boolean isNedded) { @@ -92,14 +22,6 @@ private static void setSubscriptionNedded(String subscriptionValue) { subscriptionNedded = subscriptionValue.equals("true") || subscriptionValue.equals("1"); } - public static LanguagesEnum getLanguage() { - return language; - } - - public static ThemesEnum getTheme() { - return theme; - } - public static boolean isSubscriptionNedded() { return subscriptionNedded; } diff --git a/src/main/java/backupmanager/Entities/TimeInterval.java b/src/main/java/backupmanager/Entities/TimeInterval.java index 52c5a467..b91905e2 100644 --- a/src/main/java/backupmanager/Entities/TimeInterval.java +++ b/src/main/java/backupmanager/Entities/TimeInterval.java @@ -18,11 +18,11 @@ public String toString() { } public static TimeInterval getTimeIntervalFromString(String time) { - if (time != null && !time.matches("\\d+\\.\\d{1,2}:\\d{1,2}")) { + if (time != null && !time.matches("\\d+\\.\\d{1,2}:\\d{1,2}")) throw new IllegalArgumentException("Invalid time format. Expected format: days.hours:minutes (e.g., 1.12:30)"); - } - if (time == null) return null; + if (time == null) + return null; String[] dayAndTime = time.split("\\."); int parsedDays = Integer.parseInt(dayAndTime[0]); @@ -31,15 +31,12 @@ public static TimeInterval getTimeIntervalFromString(String time) { int parsedHours = Integer.parseInt(hourAndMinute[0]); int parsedMinutes = Integer.parseInt(hourAndMinute[1]); - if (parsedDays < 0) { + if (parsedDays < 0) throw new IllegalArgumentException("Days cannot be negative"); - } - if (parsedHours < 0 || parsedHours > 23) { + if (parsedHours < 0 || parsedHours > 23) throw new IllegalArgumentException("Hours must be between 0 and 23"); - } - if (parsedMinutes < 0 || parsedMinutes > 59) { + if (parsedMinutes < 0 || parsedMinutes > 59) throw new IllegalArgumentException("Minutes must be between 0 and 59"); - } return new TimeInterval(parsedDays, parsedHours, parsedMinutes); } diff --git a/src/main/java/backupmanager/Entities/User.java b/src/main/java/backupmanager/Entities/User.java index 321d9adf..d0c8875b 100644 --- a/src/main/java/backupmanager/Entities/User.java +++ b/src/main/java/backupmanager/Entities/User.java @@ -8,10 +8,6 @@ public User(String name, String surname, String email) { this(0, name, surname, email, Locale.getDefault().getDisplayName()); } - public User(int id, String name, String surname, String email) { - this(id, name, surname, email, Locale.getDefault().getDisplayName()); - } - public String getUserCompleteName() { return name + " " + surname; } diff --git a/src/main/java/backupmanager/Enums/LanguagesEnum.java b/src/main/java/backupmanager/Enums/LanguagesEnum.java index ac0ba615..92aa7b69 100644 --- a/src/main/java/backupmanager/Enums/LanguagesEnum.java +++ b/src/main/java/backupmanager/Enums/LanguagesEnum.java @@ -17,8 +17,12 @@ public String getLanguageName() { return languageName; } + public static LanguagesEnum getDefault() { + return ENG; + } + private LanguagesEnum(String languageName, String fileName) { this.languageName = languageName; this.fileName = fileName; } -} \ No newline at end of file +} diff --git a/src/main/java/backupmanager/Enums/MenuItems.java b/src/main/java/backupmanager/Enums/MenuItems.java index e356d500..c4698d8f 100644 --- a/src/main/java/backupmanager/Enums/MenuItems.java +++ b/src/main/java/backupmanager/Enums/MenuItems.java @@ -2,24 +2,19 @@ public enum MenuItems { BugReport, - Preferences, //TODO: remove - Clear, //TODO: remove - Save, //TODO: remove - SaveWithName, //TODO: remove Settings, Donate, PaypalDonate, BuymeacoffeeDonate, History, InfoPage, - New, - Quit, //TODO: remove + New, //TODO: Used?? Import, Export, - Share, + Share, //TODO: Used?? Support, ContactUs, - Website, + Website, //TODO: Used?? BackupList, Dashboard, About, diff --git a/src/main/java/backupmanager/Enums/ThemesEnum.java b/src/main/java/backupmanager/Enums/ThemesEnum.java deleted file mode 100644 index 774ab6b8..00000000 --- a/src/main/java/backupmanager/Enums/ThemesEnum.java +++ /dev/null @@ -1,24 +0,0 @@ -package backupmanager.Enums; - -public enum ThemesEnum { - INTELLIJ("Light"), - DRACULA("Dark"), - CARBON("Carbon"), - ARC_ORAGE("Arc - Orange"), - ARC_DARK_ORANGE("Arc Dark - Orange"), - CYAN_LIGHT("Cyan light"), - NORD("Nord"), - HIGH_CONTRAST("High contrast"), - SOLARIZED_DARK("Solarized dark"), - SOLARIZED_LIGHT("Solarized light"); - - private final String themeName; - - public String getThemeName() { - return themeName; - } - - private ThemesEnum(String themeName) { - this.themeName = themeName; - } -} \ No newline at end of file diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index e694244a..fa7c596a 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -15,7 +15,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import backupmanager.Entities.Configurations; +import backupmanager.Managers.LanguageManager; public class Translations { @@ -28,7 +28,6 @@ public enum TCategory { BACKUP_ENTRY("BackupEntry"), BACKUP_LIST("BackupList"), TIME_PICKER_DIALOG("TimePickerDialog"), - PREFERENCES_DIALOG("PreferencesDialog"), USER_DIALOG("UserDialog"), PROGRESS_BACKUP_FRAME("ProgressBackupFrame"), TRAY_ICON("TrayIcon"), @@ -36,7 +35,8 @@ public enum TCategory { SUBSCRIPTION("Subscription"), // TODO: add to json HISTORY_LOGS("HistoryLogs"), // TODO: add to json ABOUT("About"), // TODO: add to json - DASHBOARD("Dashboard"); // TODO: add to json + DASHBOARD("Dashboard"), // TODO: add to json + SETTINGS("Settings"); // TODO: add to json private final String categoryName; private final Map<TKey, String> translations = new HashMap<>(); @@ -195,11 +195,6 @@ public enum TKey { MINUTES(TCategory.TIME_PICKER_DIALOG, "Minutes", "Minutes"), SPINNER_TOOLTIP(TCategory.TIME_PICKER_DIALOG, "SpinnerTooltip", "Mouse wheel to adjust the value"), - // PreferencesDialog - PREFERENCES_TITLE(TCategory.PREFERENCES_DIALOG, "PreferencesTitle", "Preferences"), - LANGUAGE(TCategory.PREFERENCES_DIALOG, "Language", "Language"), - THEME(TCategory.PREFERENCES_DIALOG, "Theme", "Theme"), - // User dialog USER_TITLE(TCategory.USER_DIALOG, "UserTitle", "Insert your data"), USER_DESCRIPTION(TCategory.USER_DIALOG, "UserDescription", "Please enter your data to access the system"), // TODO: add to json @@ -326,7 +321,38 @@ public enum TKey { // ABOUT ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), // TODO: add to json - ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><br>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</html>"); // TODO: add to json + ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><br>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</html>"), // TODO: add to json + + // SETTINGS + SETTINGS_LAYOUT_TAB(TCategory.SETTINGS, "SettingsLayoutTab", "Layout"), // TODO: add to json + SETTINGS_STYLE_TAB(TCategory.SETTINGS, "SettingsStyleTab", "Style"), // TODO: add to json + SETTINGS_WINDOWS_LAYOUT(TCategory.SETTINGS, "SettingsWindowsLayout", "Windows Layout"), // TODO: add to json + SETTINGS_WINDOWS_RIGHT(TCategory.SETTINGS, "SettingsWindowsRight", "Right to Left"), // TODO: add to json + SETTINGS_WINDOWS_FULL(TCategory.SETTINGS, "SettingsWindowsFull", "Full Window Content"), // TODO: add to json + SETTINGS_DRAWER_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLayout", "Drawer layout"), // TODO: add to json + SETTINGS_DRAWER_LEFT(TCategory.SETTINGS, "SettingsDrawerLeft", "Left"), // TODO: add to json + SETTINGS_DRAWER_LEADING(TCategory.SETTINGS, "SettingsDrawerLeading", "Leading"), // TODO: add to json + SETTINGS_DRAWER_TRAILING(TCategory.SETTINGS, "SettingsDrawerTrailing", "Trailing"), // TODO: add to json + SETTINGS_DRAWER_RIGHT(TCategory.SETTINGS, "SettingsDrawerRight", "Right"), // TODO: add to json + SETTINGS_DRAWER_TOP(TCategory.SETTINGS, "SettingsDrawerTop", "Top"), // TODO: add to json + SETTINGS_DRAWER_BOTTOM(TCategory.SETTINGS, "SettingsDrawerBottom", "Bottom"), // TODO: add to json + SETTINGS_MODAL_OPTION(TCategory.SETTINGS, "SettingsModalOption", "Default modal option"), // TODO: add to json + SETTINGS_MODAL_ANIMATION(TCategory.SETTINGS, "SettingsModalAnimation", "Animation enable"), // TODO: add to json + SETTINGS_MODAL_CLOSE(TCategory.SETTINGS, "SettingsModalClose", "Close on pressed escape"), // TODO: add to json + SETTINGS_LANGUAGES_LAYOUT(TCategory.SETTINGS, "SettingsLanguagesLayout", "Language"), // TODO: add to json + SETTINGS_ACCENT_LAYOUT(TCategory.SETTINGS, "SettingsAccentLayout", "Accent color"), // TODO: add to json + SETTINGS_COLOR_PICKER_LAYOUT(TCategory.SETTINGS, "SettingsColorPickerLayout", "Color Picker"), // TODO: add to json + SETTINGS_DRAWER_LINE_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLineLayout", "Drawer line style"), // TODO: add to json + SETTINGS_DRAWER_LINE_CURVED(TCategory.SETTINGS, "SettingsDrawerLineCurved", "Curved line style"), // TODO: add to json + SETTINGS_DRAWER_DOT_LINE(TCategory.SETTINGS, "SettingsDrawerDotLine", "Straight dot line style"), // TODO: add to json + SETTINGS_LINE_STYLE_LAYOUT(TCategory.SETTINGS, "SettingsLineStyleLayout", "Line style option"), // TODO: add to json + SETTINGS_LINE_STYLE_RETTANGLE(TCategory.SETTINGS, "SettingsLineStyleRettangle", "Rettangle"), // TODO: add to json + SETTINGS_LINE_STYLE_ELLIPSE(TCategory.SETTINGS, "SettingsLineStyleEllipse", "Ellipse"), // TODO: add to json + SETTINGS_LINE_STYLE_LINE(TCategory.SETTINGS, "SettingsLineStyleLine", "Line"), // TODO: add to json + SETTINGS_LINE_STYLE_CURVED(TCategory.SETTINGS, "SettingsLineStyleCurved", "Curved"), // TODO: add to json + SETTINGS_COLOR_OPTION_LAYOUT(TCategory.SETTINGS, "SettingsColorOptionLayout", "Color option"), // TODO: add to json + SETTINGS_COLOR_OPTION_PAINTED(TCategory.SETTINGS, "SettingsColorOptionPainted", "Paint selected line color"); // TODO: add to json + private final TCategory category; private final String keyName; @@ -372,7 +398,7 @@ public static void loadTranslations(String filePath) throws IOException { JsonObject categoryTranslations = jsonObject.getAsJsonObject(category.getCategoryName()); if (categoryTranslations == null) { - logger.warn("Missing category in {}: {}", Configurations.getLanguage().getFileName(), category.getCategoryName()); + logger.warn("Missing category in {}: {}", LanguageManager.getLanguage().getFileName(), category.getCategoryName()); continue; } @@ -395,7 +421,7 @@ public static void loadTranslations(String filePath) throws IOException { for (TKey key : TKey.values()) { if (key.getCategory() == category && !loadedKeys.contains(key)) { - logger.warn("Missing translation in {} -> category: {}, key: {}", Configurations.getLanguage().getFileName(), key.getCategory(), key.getKeyName()); + logger.warn("Missing translation in {} -> category: {}, key: {}", LanguageManager.getLanguage().getFileName(), key.getCategory(), key.getKeyName()); } } } diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 24f21ebe..4170c7c0 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -17,6 +17,7 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; import backupmanager.Managers.ExceptionManager; +import backupmanager.Managers.LanguageManager; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.ProductionDatabaseInitializer; @@ -33,6 +34,7 @@ public static void main(String[] args) { databaseInitialization(); + AppPreferences.init(); loadPreferredLanguage(); boolean isBackgroundMode = isBackgroundMode(args); @@ -61,7 +63,7 @@ private static void databaseInitialization() { private static void loadPreferredLanguage() { try { Configurations.loadAllConfigurations(); - Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + LanguageManager.getLanguage().getFileName()); } catch (IOException ex) { logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); } @@ -89,8 +91,6 @@ private static void runBackgroundProcess() { private static void runGui() { java.awt.EventQueue.invokeLater(() -> { - - AppPreferences.init(); FlatRobotoFont.install(); FlatLaf.registerCustomDefaultsSource(".themes"); UIManager.put("defaultFont", FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13)); diff --git a/src/main/java/backupmanager/Managers/LanguageManager.java b/src/main/java/backupmanager/Managers/LanguageManager.java new file mode 100644 index 00000000..ffbcc29d --- /dev/null +++ b/src/main/java/backupmanager/Managers/LanguageManager.java @@ -0,0 +1,58 @@ +package backupmanager.Managers; + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import backupmanager.Enums.LanguagesEnum; +import backupmanager.utils.AppPreferences; + +public class LanguageManager { + private static final Logger logger = LoggerFactory.getLogger(LanguageManager.class); + + public static void setLanguage(LanguagesEnum language) { + String fileName = language.getFileName(); + AppPreferences.setLanguage(fileName); + logger.info("Language setted to: {}", language.getLanguageName()); + } + + public static void setLanguage(String language) { + var lang = getLanguageByLanguageName(language); + setLanguage(lang); + } + + + public static LanguagesEnum getLanguage() { + return getLanguageInPreferences(); + } + + private static LanguagesEnum getLanguageInPreferences() { + String langPref = AppPreferences.getLanguage(); + String filename = !langPref.isEmpty() ? langPref : LanguagesEnum.getDefault().getFileName(); + + try { + return getLanguageByFileName(filename); + } catch (Exception ex) { + logger.error("An error occurred during fetching language: {}", ex.getMessage(), ex); + ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); + } + return LanguagesEnum.getDefault(); + } + + private static LanguagesEnum getLanguageByFileName(String filename) { + for (LanguagesEnum lang : LanguagesEnum.values()) { + if (lang.getFileName().equalsIgnoreCase(filename)) + return lang; + } + throw new IllegalArgumentException("Impossible fetch language with filename: " + filename); + } + + private static LanguagesEnum getLanguageByLanguageName(String language) { + for (LanguagesEnum lang : LanguagesEnum.values()) { + if (lang.getLanguageName().equalsIgnoreCase(language)) + return lang; + } + throw new IllegalArgumentException("Impossible fetch language with name: " + language); + } +} diff --git a/src/main/java/backupmanager/Managers/ThemeManager.java b/src/main/java/backupmanager/Managers/ThemeManager.java deleted file mode 100644 index 487ba426..00000000 --- a/src/main/java/backupmanager/Managers/ThemeManager.java +++ /dev/null @@ -1,86 +0,0 @@ -package backupmanager.Managers; - -import java.awt.Component; -import java.awt.Dialog; -import java.awt.Frame; -import java.util.Arrays; - -import javax.swing.JPopupMenu; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.UnsupportedLookAndFeelException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.formdev.flatlaf.FlatDarculaLaf; -import com.formdev.flatlaf.FlatIntelliJLaf; -import com.formdev.flatlaf.intellijthemes.FlatArcDarkOrangeIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatArcOrangeIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatCarbonIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatCyanLightIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatHighContrastIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatNordIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatSolarizedDarkIJTheme; -import com.formdev.flatlaf.intellijthemes.FlatSolarizedLightIJTheme; - -import backupmanager.Entities.Configurations; - -// https://www.formdev.com/flatlaf/#demo -// https://www.formdev.com/flatlaf/themes/ -// https://github.com/JFormDesigner/FlatLaf/tree/main/flatlaf-intellij-themes - -public class ThemeManager { - private static final Logger logger = LoggerFactory.getLogger(ThemeManager.class); - - public static void updateThemeFrame(Frame frame) { - updateTheme(); - repaint(frame); - } - - public static void refreshPopup(JPopupMenu popup) { - repaint(popup); - } - - public static void updateThemeDialog(Dialog dialog) { - updateTheme(); - repaint(dialog); - } - - private static void repaint(Object objectToRepaint) { - if (objectToRepaint == null) - throw new NullPointerException("objectToRepaint cannot be null"); - - if (objectToRepaint instanceof Dialog || objectToRepaint instanceof JPopupMenu || objectToRepaint instanceof Frame) { - // Update all components and revalidate and repaint - SwingUtilities.updateComponentTreeUI((Component) objectToRepaint); - ((Component) objectToRepaint).revalidate(); - ((Component) objectToRepaint).repaint(); - } else { - throw new IllegalArgumentException("Unsupported object type for repainting: " + objectToRepaint.getClass().getName()); - } - } - - private static void updateTheme() { - try { - String selectedTheme = Configurations.getTheme().getThemeName(); - - switch (selectedTheme.toLowerCase()) { - case "light" -> UIManager.setLookAndFeel(new FlatIntelliJLaf()); - case "dark" -> UIManager.setLookAndFeel(new FlatDarculaLaf()); - case "carbon" -> UIManager.setLookAndFeel(new FlatCarbonIJTheme()); - case "arc - orange" -> UIManager.setLookAndFeel(new FlatArcOrangeIJTheme()); - case "arc dark - orange" -> UIManager.setLookAndFeel(new FlatArcDarkOrangeIJTheme()); - case "cyan light" -> UIManager.setLookAndFeel(new FlatCyanLightIJTheme()); - case "nord" -> UIManager.setLookAndFeel(new FlatNordIJTheme()); - case "high contrast" -> UIManager.setLookAndFeel(new FlatHighContrastIJTheme()); - case "solarized dark" -> UIManager.setLookAndFeel(new FlatSolarizedDarkIJTheme()); - case "solarized light" -> UIManager.setLookAndFeel(new FlatSolarizedLightIJTheme()); - default -> UIManager.setLookAndFeel(new FlatIntelliJLaf()); // If no match, apply the default theme - } - } catch (UnsupportedLookAndFeelException ex) { - logger.error("Error setting LookAndFeel: " + ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - } -} diff --git a/src/main/java/backupmanager/Services/BackupAnalyticsService.java b/src/main/java/backupmanager/Services/BackupAnalyticsService.java index c2c50457..1a18576e 100644 --- a/src/main/java/backupmanager/Services/BackupAnalyticsService.java +++ b/src/main/java/backupmanager/Services/BackupAnalyticsService.java @@ -8,8 +8,6 @@ import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; -import org.jfree.data.general.DefaultPieDataset; -import org.jfree.data.general.PieDataset; import org.jfree.data.time.Day; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; @@ -97,97 +95,6 @@ public static CategoryDataset buildRequestsPerMonthDataset(List<BackupRequest> r return dataset; } - public static CategoryDataset buildStatusCategoryDataset(List<BackupRequest> requests) { - - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - if (requests == null) - return dataset; - - Map<BackupStatus, Long> map = - requests.stream().collect( - Collectors.groupingBy( - BackupRequest::status, - Collectors.counting() - )); - - map.forEach((status, count) -> - dataset.addValue( - count, - "Requests", - status.name() - )); - - return dataset; - } - - public static PieDataset buildStatusPieDataset(List<BackupRequest> requests) { - - DefaultPieDataset dataset = new DefaultPieDataset(); - - if (requests == null) - return dataset; - - Map<BackupStatus, Long> map = - requests.stream() - .collect(Collectors.groupingBy( - BackupRequest::status, - Collectors.counting() - )); - - map.forEach((status, count) -> - dataset.setValue(status.name(), count)); - - return dataset; - } - - public static CategoryDataset buildBackupQualityDataset(List<BackupRequest> requests) { - - DefaultCategoryDataset dataset = new DefaultCategoryDataset(); - - if (requests == null || requests.isEmpty()) - return dataset; - - long total = requests.size(); - - long success = requests.stream() - .filter(r -> r.status() == BackupStatus.FINISHED) - .count(); - - double successRate = total == 0 ? 0 : success * 100.0 / total; - - double avgDuration = requests.stream() - .filter(r -> r.durationMs() != null) - .mapToLong(BackupRequest::durationMs) - .average() - .orElse(0); - - double compressionEfficiency = requests.stream() - .filter(r -> r.unzippedTargetSize() > 0 - && r.zippedTargetSize() != null) - .mapToDouble(r -> - (double) r.zippedTargetSize() / - r.unzippedTargetSize()) - .average() - .orElse(0); - - dataset.addValue(successRate, "Quality", "Success Rate"); - dataset.addValue(avgDuration, "Quality", "Avg Duration"); - dataset.addValue(compressionEfficiency * 100, - "Quality", "Compression Ratio"); - - return dataset; - } - - public static String formatBytes(long bytes) { - - if (bytes < 1024) return bytes + " B"; - if (bytes < 1024 * 1024) return bytes / 1024 + " KB"; - if (bytes < 1024L * 1024 * 1024) return bytes / (1024 * 1024) + " MB"; - - return bytes / (1024L * 1024 * 1024) + " GB"; - } - public static XYDataset buildDurationTrendDataset(Map<LocalDate, Double> trendMap, String title) { TimeSeries series = new TimeSeries(title); @@ -228,30 +135,6 @@ public static double computeCompressionRate(List<BackupRequest> requests) { return (double) totalZipped / totalUnzipped; } - public static double computeHealthIndex(double successRate, double compressionRate, double stabilityRate) { - return Math.min(100,(successRate * 0.5 + compressionRate * 0.3 + stabilityRate * 0.2)); - } - - public static XYDataset buildStorageTrendDataset( - Map<LocalDate, Long> storageTrend) { - - TimeSeries series = new TimeSeries("Storage Growth"); - - storageTrend.forEach((date, value) -> { - - series.add( - new Day( - date.getDayOfMonth(), - date.getMonthValue(), - date.getYear() - ), - value / (1024.0 * 1024) // MB - ); - }); - - return new TimeSeriesCollection(series); - } - public static double convertAvgDurationinMinutes(BackupAnalyticsSnapshot snapshot) { return snapshot.avgDurationMs() / 60000.0; } diff --git a/src/main/java/backupmanager/Services/LoginService.java b/src/main/java/backupmanager/Services/LoginService.java index 7300cb38..4d1aa909 100644 --- a/src/main/java/backupmanager/Services/LoginService.java +++ b/src/main/java/backupmanager/Services/LoginService.java @@ -6,9 +6,9 @@ import org.slf4j.LoggerFactory; import backupmanager.Email.EmailSender; -import backupmanager.Entities.Configurations; import backupmanager.Entities.User; import backupmanager.Enums.LanguagesEnum; +import backupmanager.Managers.LanguageManager; import backupmanager.database.Repositories.UserRepository; public class LoginService { @@ -44,14 +44,16 @@ private void setLanguageBasedOnPcLanguage() { logger.info("Setting default language to: " + language); + LanguagesEnum languageValue; switch (language) { - case "en" -> Configurations.setLanguage(LanguagesEnum.ENG); - case "it" -> Configurations.setLanguage(LanguagesEnum.ITA); - case "es" -> Configurations.setLanguage(LanguagesEnum.ESP); - case "de" -> Configurations.setLanguage(LanguagesEnum.DEU); - case "fr" -> Configurations.setLanguage(LanguagesEnum.FRA); - default -> Configurations.setLanguage(LanguagesEnum.ENG); + case "en" -> languageValue = LanguagesEnum.ENG; + case "it" -> languageValue = LanguagesEnum.ITA; + case "es" -> languageValue = LanguagesEnum.ESP; + case "de" -> languageValue = LanguagesEnum.DEU; + case "fr" -> languageValue = LanguagesEnum.FRA; + default -> languageValue = LanguagesEnum.getDefault(); } + LanguageManager.setLanguage(languageValue); } private void sendRegistrationEmail(User user) { diff --git a/src/main/java/backupmanager/Services/PreferenceService.java b/src/main/java/backupmanager/Services/PreferenceService.java deleted file mode 100644 index 27008804..00000000 --- a/src/main/java/backupmanager/Services/PreferenceService.java +++ /dev/null @@ -1,16 +0,0 @@ -package backupmanager.Services; - - -import java.io.IOException; - -import backupmanager.Entities.Configurations; -import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations; - -public class PreferenceService { - public void updatePreferences(String language, String theme) throws IOException { - Configurations.setLanguageByLanguageName(language); - Configurations.setTheme(theme); - Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + Configurations.getLanguage().getFileName()); - } -} diff --git a/src/main/java/backupmanager/Utils/AppPreferences.java b/src/main/java/backupmanager/Utils/AppPreferences.java index 0241189f..2d975f90 100644 --- a/src/main/java/backupmanager/Utils/AppPreferences.java +++ b/src/main/java/backupmanager/Utils/AppPreferences.java @@ -13,6 +13,7 @@ import com.formdev.flatlaf.IntelliJTheme; import com.formdev.flatlaf.util.LoggingFacade; +import backupmanager.Enums.LanguagesEnum; import backupmanager.gui.themes.PanelThemes; public class AppPreferences { @@ -25,6 +26,7 @@ public class AppPreferences { public static final String KEY_RECENT_SEARCH_FAVORITE = "recentSearchFavorite"; public static final String RESOURCE_PREFIX = "res:"; public static final String THEME_UI_KEY = "__RaVen.flatlaf.demo.theme"; + public static final String KEY_LANGUAGE = "language"; public static Color accentColor; private static Preferences state; @@ -129,4 +131,12 @@ public static void updateAccentColor(Color color) { state.remove(KEY_ACCENT_COLOR); } } + + public static String getLanguage() { + return state.get(KEY_LANGUAGE, LanguagesEnum.getDefault().getFileName()); + } + + public static void setLanguage(String lang) { + state.put(KEY_LANGUAGE, lang); + } } diff --git a/src/main/java/backupmanager/gui/Controllers/SettingsController.java b/src/main/java/backupmanager/gui/Controllers/SettingsController.java deleted file mode 100644 index 90d44a46..00000000 --- a/src/main/java/backupmanager/gui/Controllers/SettingsController.java +++ /dev/null @@ -1,7 +0,0 @@ -package backupmanager.gui.Controllers; - -public class SettingsController { - public void onLanguageChanged(String language) { - - } -} diff --git a/src/main/java/backupmanager/gui/customwidgets/ModernTextField.java b/src/main/java/backupmanager/gui/customwidgets/ModernTextField.java deleted file mode 100644 index 7426a97f..00000000 --- a/src/main/java/backupmanager/gui/customwidgets/ModernTextField.java +++ /dev/null @@ -1,162 +0,0 @@ -package backupmanager.gui.customwidgets; - -import java.awt.Color; -import java.awt.FontMetrics; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.RenderingHints; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.geom.Rectangle2D; - -import javax.swing.JTextField; -import javax.swing.border.EmptyBorder; - -import org.jdesktop.animation.timing.Animator; -import org.jdesktop.animation.timing.TimingTarget; -import org.jdesktop.animation.timing.TimingTargetAdapter; - -public class ModernTextField extends JTextField { - - private String labelText = "Label"; - private String hintText = "Label"; - private final Color lineColor = new Color(3, 155, 216); - private final Animator animator; - private boolean animateHinText = true; - private float location; - private boolean show; - private boolean mouseOver = false; - - public ModernTextField() { - setBorder(new EmptyBorder(20, 3, 10, 3)); - setSelectionColor(new Color(76, 204, 255)); - - addMouseListener(new MouseAdapter() { - @Override - public void mouseEntered(MouseEvent me) { - mouseOver = true; - repaint(); - } - - @Override - public void mouseExited(MouseEvent me) { - mouseOver = false; - repaint(); - } - }); - - addFocusListener(new FocusAdapter() { - @Override - public void focusGained(FocusEvent fe) { - showing(false); - } - - @Override - public void focusLost(FocusEvent fe) { - showing(true); - } - }); - - TimingTarget target = new TimingTargetAdapter() { - @Override - public void begin() { - animateHinText = getText().isEmpty(); - } - - @Override - public void timingEvent(float fraction) { - location = fraction; - repaint(); - } - }; - - animator = new Animator(300, target); - animator.setResolution(0); - animator.setAcceleration(0.5f); - animator.setDeceleration(0.5f); - } - - private void showing(boolean action) { - if (animator.isRunning()) { - animator.stop(); - } else { - location = 1; - } - animator.setStartFraction(1f - location); - show = action; - location = 1f - location; - animator.start(); - } - - @Override - public void paint(Graphics grphcs) { - super.paint(grphcs); - Graphics2D g2 = (Graphics2D) grphcs; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); - - int width = getWidth(); - int height = getHeight(); - - // Line color adjustment based on mouse hover state - g2.setColor(mouseOver ? lineColor : new Color(150, 150, 150)); - g2.fillRect(2, height - 1, width - 4, 1); - - // Draw the hint text and line style - createHintText(g2); - createLineStyle(g2); - - g2.dispose(); - } - - private void createHintText(Graphics2D g2) { - Insets in = getInsets(); - g2.setColor(new Color(150, 150, 150)); - FontMetrics ft = g2.getFontMetrics(); - Rectangle2D r2 = ft.getStringBounds(labelText, g2); - - double height = getHeight() - in.top - in.bottom; - double textY = (height - r2.getHeight()) / 2; - double size = animateHinText ? (show ? 18 * (1 - location) : 18 * location) : 18; - - g2.drawString(hintText, in.right, (int) (in.top + textY + ft.getAscent() - size)); - } - - private void createLineStyle(Graphics2D g2) { - if (isFocusOwner()) { - double width = getWidth() - 4; - int height = getHeight(); - g2.setColor(lineColor); - - double size = show ? width * (1 - location) : width * location; - double x = (width - size) / 2; - g2.fillRect((int) (x + 2), height - 2, (int) size, 2); - } - } - - @Override - public void setText(String string) { - boolean wasEmpty = getText().isEmpty(); - super.setText(string); - if (wasEmpty != string.isEmpty()) { - showing(string.isEmpty()); - } - } - - public void setHintText(String string) { - this.hintText = string; - repaint(); - } - - public String getLabelText() { - return labelText; - } - - public void setLabelText(String labelText) { - this.labelText = labelText; - repaint(); - } -} diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index 9b117839..bda89ce9 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -33,6 +33,9 @@ import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.util.ScaledEmptyBorder; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; +import backupmanager.Managers.LanguageManager; import backupmanager.gui.component.AccentColorIcon; import backupmanager.gui.system.FormManager; import backupmanager.gui.themes.PanelThemes; @@ -68,8 +71,8 @@ protected void init() { tabbedPane.putClientProperty(FlatClientProperties.STYLE, "" + "tabType:card"); - tabbedPane.addTab("Layout", createLayoutOption()); - tabbedPane.addTab("Style", createStyleOption()); + tabbedPane.addTab(Translations.get(TKey.SETTINGS_LAYOUT_TAB), createLayoutOption()); + tabbedPane.addTab(Translations.get(TKey.SETTINGS_LAYOUT_TAB), createStyleOption()); add(tabbedPane, "gapy 1 0"); add(createThemes()); } @@ -88,9 +91,10 @@ private JPanel createLayoutOption() { private Component createWindowsLayout() { JPanel panel = new JPanel(new MigLayout()); - panel.setBorder(new TitledBorder("Windows layout")); - JCheckBox chRightToLeft = new JCheckBox("Right to Left", !getComponentOrientation().isLeftToRight()); - JCheckBox chFullWindow = new JCheckBox("Full Window Content", FlatClientProperties.clientPropertyBoolean(FormManager.getFrame().getRootPane(), FlatClientProperties.FULL_WINDOW_CONTENT, false)); + windowsLayout = new TitledBorder("Windows Layout"); + panel.setBorder(windowsLayout); + chRightToLeft = new JCheckBox("Right to Left", !getComponentOrientation().isLeftToRight()); + chFullWindow = new JCheckBox("Full Window Content", FlatClientProperties.clientPropertyBoolean(FormManager.getFrame().getRootPane(), FlatClientProperties.FULL_WINDOW_CONTENT, false)); chRightToLeft.addActionListener(e -> { if (chRightToLeft.isSelected()) { FormManager.getFrame().applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); @@ -109,14 +113,15 @@ private Component createWindowsLayout() { private Component createDrawerLayout() { JPanel panel = new JPanel(new MigLayout()); - panel.setBorder(new TitledBorder("Drawer layout")); + drawerLayout = new TitledBorder("Drawer layout"); + panel.setBorder(drawerLayout); - JRadioButton jrLeft = new JRadioButton("Left"); - JRadioButton jrLeading = new JRadioButton("Leading"); - JRadioButton jrTrailing = new JRadioButton("Trailing"); - JRadioButton jrRight = new JRadioButton("Right"); - JRadioButton jrTop = new JRadioButton("Top"); - JRadioButton jrBottom = new JRadioButton("Bottom"); + jrLeft = new JRadioButton("Left"); + jrLeading = new JRadioButton("Leading"); + jrTrailing = new JRadioButton("Trailing"); + jrRight = new JRadioButton("Right"); + jrTop = new JRadioButton("Top"); + jrBottom = new JRadioButton("Bottom"); ButtonGroup group = new ButtonGroup(); group.add(jrLeft); @@ -188,9 +193,10 @@ private Component createDrawerLayout() { private Component createModalDefaultOption() { JPanel panel = new JPanel(new MigLayout()); - panel.setBorder(new TitledBorder("Default modal option")); - JCheckBox chAnimation = new JCheckBox("Animation enable"); - JCheckBox chCloseOnPressedEscape = new JCheckBox("Close on pressed escape"); + modalOption = new TitledBorder("Default modal option"); + panel.setBorder(new TitledBorder(modalOption)); + chAnimation = new JCheckBox("Animation enable"); + chCloseOnPressedEscape = new JCheckBox("Close on pressed escape"); chAnimation.setSelected(ModalDialog.getDefaultOption().isAnimationEnabled()); chCloseOnPressedEscape.setSelected(ModalDialog.getDefaultOption().isCloseOnPressedEscape()); @@ -205,16 +211,15 @@ private Component createModalDefaultOption() { private Component createLanguageOption() { JPanel panel = new JPanel(new MigLayout()); - panel.setBorder(new TitledBorder("Language")); + languageTitleBorder = new TitledBorder("Language"); + panel.setBorder(languageTitleBorder); languageCombo = new JComboBox<>(); initComboItem(languageCombo); languageCombo.addActionListener(e -> { Object selected = languageCombo.getSelectedItem(); - - logger.info("Language setted to: {}", selected.toString()); - - // onLanguageChanged(selected); + String languageName = selected.toString(); + LanguageManager.setLanguage(languageName); }); panel.add(languageCombo); @@ -250,7 +255,8 @@ private JPanel createStyleOption() { private Component createAccentColor() { JPanel panel = new JPanel(new MigLayout()); - panel.setBorder(new TitledBorder("Accent color")); + accentLayout = new TitledBorder("Accent color"); + panel.setBorder(accentLayout); ButtonGroup group = new ButtonGroup(); JToolBar toolBar = new JToolBar(); toolBar.putClientProperty(FlatClientProperties.STYLE, "" + @@ -305,7 +311,7 @@ private JToggleButton createCustomAccentColor() { colorPicker.setBorder(new ScaledEmptyBorder(0, 20, 0, 20)); Option option = ModalDialog.createOption(); option.setAnimationEnabled(false); - ModalDialog.showModal(this, new SimpleModalBorder(colorPicker, "Select Color", SimpleModalBorder.YES_NO_OPTION, (controller, action) -> { + colorPickerLayout = new SimpleModalBorder(colorPicker, Translations.get(TKey.SETTINGS_COLOR_PICKER_LAYOUT), SimpleModalBorder.YES_NO_OPTION, (controller, action) -> { if (action == SimpleModalBorder.YES_OPTION) { AppPreferences.accentColor = colorPicker.getSelectedColor(); oldSelected = null; @@ -315,7 +321,8 @@ private JToggleButton createCustomAccentColor() { oldSelected.setSelected(true); } } - }), option); + }); + ModalDialog.showModal(this, colorPickerLayout, option); }); return button; } @@ -326,28 +333,32 @@ private Component createDrawerStyle() { JPanel lineStyleOption = new JPanel(new MigLayout("wrap", "[200]")); JPanel lineColorOption = new JPanel(new MigLayout("wrap", "[200]")); - lineStyle.setBorder(new TitledBorder("Drawer line style")); - lineStyleOption.setBorder(new TitledBorder("Line style option")); - lineColorOption.setBorder(new TitledBorder("Color option")); + drawerLineLayout = new TitledBorder("Drawer line style"); + lineStyleOptionLayout = new TitledBorder("Line style option"); + colorOptionLayout = new TitledBorder("Color option"); + + lineStyle.setBorder(drawerLineLayout); + lineStyleOption.setBorder(lineStyleOptionLayout); + lineColorOption.setBorder(colorOptionLayout); ButtonGroup groupStyle = new ButtonGroup(); - JRadioButton jrCurvedStyle = new JRadioButton("Curved line style"); - JRadioButton jrStraightDotStyle = new JRadioButton("Straight dot line style", true); + jrCurvedStyle = new JRadioButton("Curved line style"); + jrStraightDotStyle = new JRadioButton("Straight dot line style", true); groupStyle.add(jrCurvedStyle); groupStyle.add(jrStraightDotStyle); ButtonGroup groupStyleOption = new ButtonGroup(); - JRadioButton jrStyleOption1 = new JRadioButton("Rectangle"); - JRadioButton jrStyleOption2 = new JRadioButton("Ellipse", true); + jrStyleOption1 = new JRadioButton("Rectangle"); + jrStyleOption2 = new JRadioButton("Ellipse", true); groupStyleOption.add(jrStyleOption1); groupStyleOption.add(jrStyleOption2); - JCheckBox chPaintLineColor = new JCheckBox("Paint selected line color"); + chPaintLineColor = new JCheckBox("Paint selected line color"); jrCurvedStyle.addActionListener(e -> { if (jrCurvedStyle.isSelected()) { - jrStyleOption1.setText("Line"); - jrStyleOption2.setText("Curved"); + jrStyleOption1.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_LINE)); + jrStyleOption2.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_CURVED)); boolean round = jrStyleOption2.isSelected(); boolean paintSelectedLine = chPaintLineColor.isSelected(); setDrawerLineStyle(true, round, paintSelectedLine); @@ -355,8 +366,8 @@ private Component createDrawerStyle() { }); jrStraightDotStyle.addActionListener(e -> { if (jrStraightDotStyle.isSelected()) { - jrStyleOption1.setText("Rectangle"); - jrStyleOption2.setText("Ellipse"); + jrStyleOption1.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_RETTANGLE)); + jrStyleOption2.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_ELLIPSE)); boolean round = jrStyleOption2.isSelected(); boolean paintSelectedLine = chPaintLineColor.isSelected(); setDrawerLineStyle(false, round, paintSelectedLine); @@ -472,9 +483,56 @@ private JPanel createThemes() { @Override protected void setTranslations() { - + windowsLayout.setTitle(Translations.get(TKey.SETTINGS_WINDOWS_LAYOUT)); + chRightToLeft.setText(Translations.get(TKey.SETTINGS_WINDOWS_RIGHT)); + chFullWindow.setText(Translations.get(TKey.SETTINGS_WINDOWS_FULL)); + drawerLayout.setTitle(Translations.get(TKey.SETTINGS_DRAWER_LAYOUT)); + jrLeft.setText(Translations.get(TKey.SETTINGS_DRAWER_LEFT)); + jrLeading.setText(Translations.get(TKey.SETTINGS_DRAWER_LEADING)); + jrTrailing.setText(Translations.get(TKey.SETTINGS_DRAWER_TRAILING)); + jrRight.setText(Translations.get(TKey.SETTINGS_DRAWER_RIGHT)); + jrTop.setText(Translations.get(TKey.SETTINGS_DRAWER_TOP)); + jrBottom.setText(Translations.get(TKey.SETTINGS_DRAWER_BOTTOM)); + modalOption.setTitle(Translations.get(TKey.SETTINGS_MODAL_OPTION)); + chAnimation.setText(Translations.get(TKey.SETTINGS_MODAL_ANIMATION)); + chCloseOnPressedEscape.setText(Translations.get(TKey.SETTINGS_MODAL_CLOSE)); + languageTitleBorder.setTitle(Translations.get(TKey.SETTINGS_LANGUAGES_LAYOUT)); + accentLayout.setTitle(Translations.get(TKey.SETTINGS_ACCENT_LAYOUT)); + // colorPickerLayout.setTitle(Translations.get(TKey.SETTINGS_COLOR_PICKER_LAYOUT)); // the method is not actually avaiable + drawerLineLayout.setTitle(Translations.get(TKey.SETTINGS_DRAWER_LINE_LAYOUT)); + lineStyleOptionLayout.setTitle(Translations.get(TKey.SETTINGS_LINE_STYLE_LAYOUT)); + colorOptionLayout.setTitle(Translations.get(TKey.SETTINGS_COLOR_OPTION_LAYOUT)); + jrCurvedStyle.setText(Translations.get(TKey.SETTINGS_DRAWER_LINE_CURVED)); + jrStraightDotStyle.setText(Translations.get(TKey.SETTINGS_DRAWER_DOT_LINE)); + jrStyleOption1.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_RETTANGLE)); + jrStyleOption2.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_ELLIPSE)); + chPaintLineColor.setText(Translations.get(TKey.SETTINGS_COLOR_OPTION_PAINTED)); } private JTabbedPane tabbedPane; private JComboBox<Object> languageCombo; + private TitledBorder windowsLayout; + private JCheckBox chRightToLeft; + private JCheckBox chFullWindow; + private TitledBorder drawerLayout; + private JRadioButton jrLeft; + private JRadioButton jrLeading; + private JRadioButton jrTrailing; + private JRadioButton jrRight; + private JRadioButton jrTop; + private JRadioButton jrBottom; + private TitledBorder modalOption; + private JCheckBox chAnimation; + private JCheckBox chCloseOnPressedEscape; + private TitledBorder languageTitleBorder; + private TitledBorder accentLayout; + private SimpleModalBorder colorPickerLayout; + private TitledBorder drawerLineLayout; + private TitledBorder lineStyleOptionLayout; + private TitledBorder colorOptionLayout; + private JRadioButton jrCurvedStyle; + private JRadioButton jrStraightDotStyle; + private JRadioButton jrStyleOption1; + private JRadioButton jrStyleOption2; + private JCheckBox chPaintLineColor; } diff --git a/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java b/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java deleted file mode 100644 index 1aa69499..00000000 --- a/src/main/java/backupmanager/gui/sample/csv/CSVDataReader.java +++ /dev/null @@ -1,93 +0,0 @@ -package backupmanager.gui.sample.csv; - -import java.io.*; -import java.util.ArrayList; -import java.util.List; - -public class CSVDataReader { - - private final String[] columns; - private final List<String[]> rows; - - private CSVDataReader(String[] columns, List<String[]> rows) { - this.columns = columns; - this.rows = rows; - } - - public String[] getColumns() { - return columns; - } - - public ResponseCSV getData(int page, int limit) { - int total = rows.size(); - int pageSize = (int) Math.ceil((double) total / limit); - - if (page > pageSize) { - page = pageSize; - } - - int start = Math.min((page - 1) * limit, total); - int end = Math.min(start + limit, total); - return new ResponseCSV(total, page, pageSize, limit, new ArrayList<>(rows.subList(start, end))); - } - - public static CSVDataReader load(InputStream stream) throws IOException { - return loadImpl(new BufferedReader(new InputStreamReader(stream))); - } - - public static CSVDataReader load(File file) throws IOException { - return loadImpl(new BufferedReader(new FileReader(file))); - } - - private static CSVDataReader loadImpl(BufferedReader reader) throws IOException { - String[] columns = null; - List<String[]> rows = new ArrayList<>(); - String line; - try (BufferedReader br = reader) { - while ((line = br.readLine()) != null) { - - // parse the line as a CSV row - String[] data = parseCSVLine(line); - if (columns == null) { - columns = data; - } else { - rows.add(data); - } - } - } - return new CSVDataReader(columns, rows); - } - - /** - * Code from ChatGPT - * To parse the csv row to array - */ - private static String[] parseCSVLine(String line) { - List<String> result = new ArrayList<>(); - StringBuilder currentField = new StringBuilder(); - boolean inQuotes = false; - - for (int i = 0; i < line.length(); i++) { - char ch = line.charAt(i); - - if (ch == '"') { - // toggle the inQuotes flag unless it's an escaped quote - if (i + 1 < line.length() && line.charAt(i + 1) == '"') { - currentField.append(ch); // Add escaped quote - i++; // skip next quote - } else { - inQuotes = !inQuotes; - } - } else if (ch == ',' && !inQuotes) { - // end of field - result.add(currentField.toString()); - currentField.setLength(0); // reset the field - } else { - currentField.append(ch); // add character to the current field - } - } - // add the last field - result.add(currentField.toString()); - return result.toArray(String[]::new); - } -} diff --git a/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java b/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java deleted file mode 100644 index 93bb94e2..00000000 --- a/src/main/java/backupmanager/gui/sample/csv/ConfigurationBackupDataTable.java +++ /dev/null @@ -1,25 +0,0 @@ -package backupmanager.gui.sample.csv; - -import java.util.ArrayList; -import java.util.List; - -import backupmanager.Entities.ConfigurationBackup; - -public class ConfigurationBackupDataTable extends ResponsePageable<List<ConfigurationBackup>> { - public ConfigurationBackupDataTable(int total, int page, int pageSize, int limit, List<ConfigurationBackup> data) { - super(total, page, pageSize, limit, data); - } - - public static ConfigurationBackupDataTable create(List<ConfigurationBackup> rows, int page, int limit) { - int total = rows.size(); - int pageSize = (int) Math.ceil((double) total / limit); - - if (page > pageSize) { - page = pageSize; - } - - int start = Math.min((page - 1) * limit, total); - int end = Math.min(start + limit, total); - return new ConfigurationBackupDataTable(total, page, pageSize, limit, new ArrayList<>(rows.subList(start, end))); - } -} diff --git a/src/main/java/backupmanager/gui/sample/csv/Pageable.java b/src/main/java/backupmanager/gui/sample/csv/Pageable.java deleted file mode 100644 index 2c99605d..00000000 --- a/src/main/java/backupmanager/gui/sample/csv/Pageable.java +++ /dev/null @@ -1,40 +0,0 @@ -package backupmanager.gui.sample.csv; - -public class Pageable { - - public int getTotal() { - return total; - } - - public int getPage() { - return page; - } - - public int getPageSize() { - return pageSize; - } - - public int getLimit() { - return limit; - } - - public Pageable(int total, int page, int pageSize, int limit) { - this.total = total; - this.page = page; - this.pageSize = pageSize; - this.limit = limit; - } - - private final int total; - private final int page; - private final int pageSize; - private final int limit; - - public boolean hasPrevious() { - return page > 1 && pageSize > 0; - } - - public boolean hasNext() { - return page < pageSize && pageSize > 0; - } -} diff --git a/src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java b/src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java deleted file mode 100644 index a3e04290..00000000 --- a/src/main/java/backupmanager/gui/sample/csv/ResponseCSV.java +++ /dev/null @@ -1,10 +0,0 @@ -package backupmanager.gui.sample.csv; - -import java.util.List; - -public class ResponseCSV extends ResponsePageable<List<String[]>> { - - public ResponseCSV(int total, int page, int pageSize, int limit, List<String[]> data) { - super(total, page, pageSize, limit, data); - } -} diff --git a/src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java b/src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java deleted file mode 100644 index 3d513948..00000000 --- a/src/main/java/backupmanager/gui/sample/csv/ResponsePageable.java +++ /dev/null @@ -1,15 +0,0 @@ -package backupmanager.gui.sample.csv; - -public abstract class ResponsePageable<P> extends Pageable { - - public P getData() { - return data; - } - - public ResponsePageable(int total, int page, int pageSize, int limit, P data) { - super(total, page, pageSize, limit); - this.data = data; - } - - private final P data; -} diff --git a/src/main/resources/res/languages/deu.json b/src/main/resources/res/languages/deu.json index 5c8ca20b..94a8103e 100644 --- a/src/main/resources/res/languages/deu.json +++ b/src/main/resources/res/languages/deu.json @@ -109,11 +109,6 @@ "Minutes": "Minuten", "SpinnerTooltip": "Mausrad zum Anpassen des Wertes" }, - "PreferencesDialog": { - "PreferencesTitle": "Einstellungen", - "Language": "Sprache", - "Theme": "Thema" - }, "UserDialog": { "UserTitle": "Geben Sie Ihre Daten ein", "Name": "Vorname", @@ -205,4 +200,4 @@ "ExpiredTitle": "Backup Manager Abonnement abgelaufen", "ExpiredMessage": "Ihr Backup Manager Abonnement ist abgelaufen.\nAutomatische Backups werden nicht mehr ausgeführt.\nBitte kontaktieren Sie den Support, um es zu reaktivieren." } -} \ No newline at end of file +} diff --git a/src/main/resources/res/languages/eng.json b/src/main/resources/res/languages/eng.json index 5b5e441b..84db8699 100644 --- a/src/main/resources/res/languages/eng.json +++ b/src/main/resources/res/languages/eng.json @@ -109,11 +109,6 @@ "Minutes": "Minutes", "SpinnerTooltip": "Mouse wheel to adjust the value" }, - "PreferencesDialog": { - "PreferencesTitle": "Preferences", - "Language": "Language", - "Theme": "Theme" - }, "UserDialog": { "UserTitle": "Insert your data", "Name": "Name", diff --git a/src/main/resources/res/languages/esp.json b/src/main/resources/res/languages/esp.json index b5ba7f0f..758b9f8c 100644 --- a/src/main/resources/res/languages/esp.json +++ b/src/main/resources/res/languages/esp.json @@ -109,11 +109,6 @@ "Minutes": "Minutos", "SpinnerTooltip": "Rueda del ratón para ajustar el valor" }, - "PreferencesDialog": { - "PreferencesTitle": "Preferencias", - "Language": "Idioma", - "Theme": "Tema" - }, "UserDialog": { "UserTitle": "Inserta tus datos", "Name": "Nombre", diff --git a/src/main/resources/res/languages/fra.json b/src/main/resources/res/languages/fra.json index 69b3545f..4810891c 100644 --- a/src/main/resources/res/languages/fra.json +++ b/src/main/resources/res/languages/fra.json @@ -109,11 +109,6 @@ "Minutes": "Minutes", "SpinnerTooltip": "Roulette de la souris pour ajuster la valeur" }, - "PreferencesDialog": { - "PreferencesTitle": "Préférences", - "Language": "Langue", - "Theme": "Thème" - }, "UserDialog": { "UserTitle": "Entrez vos informations", "Name": "Prénom", diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 430389b0..311089de 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -109,11 +109,6 @@ "Minutes": "Minuti", "SpinnerTooltip": "Usa la rotellina per regolare il valore" }, - "PreferencesDialog": { - "PreferencesTitle": "Preferenze", - "Language": "Lingua", - "Theme": "Tema" - }, "UserDialog": { "UserTitle": "Inserisci i tuoi dati", "Name": "Nome", diff --git a/src/test/java/test/repositories/ConfigurationsRepositoryTest.java b/src/test/java/test/repositories/ConfigurationsRepositoryTest.java deleted file mode 100644 index a8f14a27..00000000 --- a/src/test/java/test/repositories/ConfigurationsRepositoryTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package test.repositories; - -import java.io.IOException; - -import org.junit.jupiter.api.AfterEach; -import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import backupmanager.Entities.Configurations; -import backupmanager.Enums.LanguagesEnum; -import backupmanager.Enums.ThemesEnum; -import backupmanager.database.Database; -import backupmanager.database.DatabasePaths; -import backupmanager.database.TestDatabaseInitializer; - -public class ConfigurationsRepositoryTest { - @BeforeEach - protected void setup() throws Exception { - Database.init(DatabasePaths.getTestDatabasePath()); - TestDatabaseInitializer.init(); - - Configurations.loadAllConfigurations(); - - buildAndReloadConfigurations(); - } - - @AfterEach - protected void clean() throws IOException { - TestDatabaseInitializer.deleteDatabase(); - } - - @Test - protected void equals_shouldReturnTrue_forSameLanguage() throws IOException { - assertEquals(LanguagesEnum.DEU, Configurations.getLanguage()); - } - - @Test - protected void equals_shouldReturnTrue_forSameTheme() throws IOException { - assertEquals(ThemesEnum.CARBON, Configurations.getTheme()); - } - - private void buildAndReloadConfigurations() throws IOException { - buildValidConfigurationsObject(); - realodConfigurations(); - } - - private void buildValidConfigurationsObject() { - Configurations.setLanguage(LanguagesEnum.DEU); - Configurations.setTheme(ThemesEnum.CARBON.getThemeName()); - } - - private void realodConfigurations() { - Configurations.updateAllConfigurations(); - Configurations.loadAllConfigurations(); - } -} From c4148f8357e1e1d8156470f27878a33d8fb4a162 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Thu, 23 Apr 2026 16:03:27 +0200 Subject: [PATCH 09/17] translations --- .../java/backupmanager/Enums/MenuItems.java | 2 +- .../backupmanager/Enums/Translations.java | 2 +- src/main/java/backupmanager/MainApp.java | 13 +---- .../Managers/LanguageManager.java | 56 ++++++++++++------ .../backupmanager/gui/component/About.java | 11 +++- .../gui/component/dashboard/CardBox.java | 8 +++ .../gui/component/dashboard/CardItem.java | 8 +++ .../backupmanager/gui/forms/CustomForm.java | 9 ++- .../gui/forms/FormBackupDashboard.java | 40 +++++-------- .../gui/forms/FormBackupTable.java | 52 ++++++++--------- .../backupmanager/gui/forms/FormHistory.java | 2 +- .../backupmanager/gui/forms/FormLogin.java | 6 +- .../backupmanager/gui/forms/FormSetting.java | 3 +- .../gui/frames/BackupManager.java | 5 +- .../backupmanager/gui/menu/DrawerManager.java | 58 +++++++++++++++++++ .../gui/menu/MyDrawerBuilder.java | 29 +++++----- .../gui/simple/BackupEntryDialog.java | 2 +- .../gui/simple/TimePickerDialog.java | 2 +- .../backupmanager/gui/system/FormSearch.java | 4 +- .../interfaces/ITranslatable.java | 5 ++ 20 files changed, 204 insertions(+), 113 deletions(-) create mode 100644 src/main/java/backupmanager/gui/menu/DrawerManager.java create mode 100644 src/main/java/backupmanager/interfaces/ITranslatable.java diff --git a/src/main/java/backupmanager/Enums/MenuItems.java b/src/main/java/backupmanager/Enums/MenuItems.java index c4698d8f..60c97d2d 100644 --- a/src/main/java/backupmanager/Enums/MenuItems.java +++ b/src/main/java/backupmanager/Enums/MenuItems.java @@ -14,7 +14,7 @@ public enum MenuItems { Share, //TODO: Used?? Support, ContactUs, - Website, //TODO: Used?? + Website, BackupList, Dashboard, About, diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index fa7c596a..4161507b 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -321,7 +321,7 @@ public enum TKey { // ABOUT ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), // TODO: add to json - ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><br>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</html>"), // TODO: add to json + ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><p>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</p></html>"), // TODO: add to json // SETTINGS SETTINGS_LAYOUT_TAB(TCategory.SETTINGS, "SettingsLayoutTab", "Layout"), // TODO: add to json diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 4170c7c0..27b95a74 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -13,9 +13,7 @@ import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.util.FontUtils; -import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations; import backupmanager.Managers.ExceptionManager; import backupmanager.Managers.LanguageManager; import backupmanager.database.Database; @@ -35,7 +33,7 @@ public static void main(String[] args) { databaseInitialization(); AppPreferences.init(); - loadPreferredLanguage(); + LanguageManager.loadPreferredLanguage(); boolean isBackgroundMode = isBackgroundMode(args); @@ -60,15 +58,6 @@ private static void databaseInitialization() { } } - private static void loadPreferredLanguage() { - try { - Configurations.loadAllConfigurations(); - Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + LanguageManager.getLanguage().getFileName()); - } catch (IOException ex) { - logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); - } - } - private static boolean isBackgroundMode(String[] args) { boolean isBackgroundMode = args.length > 0 && args[0].equalsIgnoreCase("--background"); diff --git a/src/main/java/backupmanager/Managers/LanguageManager.java b/src/main/java/backupmanager/Managers/LanguageManager.java index ffbcc29d..8ea1dc9c 100644 --- a/src/main/java/backupmanager/Managers/LanguageManager.java +++ b/src/main/java/backupmanager/Managers/LanguageManager.java @@ -1,43 +1,65 @@ package backupmanager.Managers; -import java.util.Arrays; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.SwingUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import backupmanager.Enums.ConfigKey; import backupmanager.Enums.LanguagesEnum; +import backupmanager.Enums.Translations; +import backupmanager.interfaces.ITranslatable; import backupmanager.utils.AppPreferences; +// Observer class -> every time the language is changed, it notify all the components registered public class LanguageManager { private static final Logger logger = LoggerFactory.getLogger(LanguageManager.class); - public static void setLanguage(LanguagesEnum language) { - String fileName = language.getFileName(); - AppPreferences.setLanguage(fileName); - logger.info("Language setted to: {}", language.getLanguageName()); - } + private static final List<WeakReference<ITranslatable>> listeners = new ArrayList<>(); public static void setLanguage(String language) { var lang = getLanguageByLanguageName(language); setLanguage(lang); } + public static void setLanguage(LanguagesEnum language) { + AppPreferences.setLanguage(language.getFileName()); + logger.info("Language setted to: {}", language.getLanguageName()); + loadPreferredLanguage(); + notifyLanguageChanged(); + } + + public static void loadPreferredLanguage() { + try { + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + LanguageManager.getLanguage().getFileName()); + } catch (IOException ex) { + logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); + } + } public static LanguagesEnum getLanguage() { - return getLanguageInPreferences(); + return getLanguageByFileName(AppPreferences.getLanguage()); } - private static LanguagesEnum getLanguageInPreferences() { - String langPref = AppPreferences.getLanguage(); - String filename = !langPref.isEmpty() ? langPref : LanguagesEnum.getDefault().getFileName(); + public static void register(ITranslatable t) { + listeners.add(new WeakReference<>(t)); + } - try { - return getLanguageByFileName(filename); - } catch (Exception ex) { - logger.error("An error occurred during fetching language: {}", ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - return LanguagesEnum.getDefault(); + public static void notifyLanguageChanged() { + SwingUtilities.invokeLater(() -> { + listeners.removeIf(ref -> ref.get() == null); + for (WeakReference<ITranslatable> ref : listeners) { + ITranslatable t = ref.get(); + if (t != null) { + t.setTranslations(); + } + } + }); } private static LanguagesEnum getLanguageByFileName(String filename) { diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index 1d198065..2db9270a 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -11,8 +11,10 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.MenuItems; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.Managers.WebsiteManager; import net.miginfocom.swing.MigLayout; @@ -60,7 +62,14 @@ public void paint(java.awt.Graphics g) {} private String getDescriptionText() { String message = Translations.get(TKey.ABOUT_MESSAGE_BODY); - message = message.replace("[PROJECT_WEBSITE]", ConfigKey.INFO_PAGE_LINK.getValue()) ; + message = message.replace("[PROJECT_WEBSITE]", ConfigKey.INFO_PAGE_LINK.getValue()); + + // removing all the info inside the <p> tag + JsonConfig config = JsonConfig.getInstance(); + if (!config.isMenuItemEnabled(MenuItems.Website.name())) { + message = message.replaceAll("<p>.*?</p>", ""); + } + return message; } diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java index 336ef9f2..752db2b3 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java @@ -45,4 +45,12 @@ public void setValueAt(int index, String value, String description, String tags, public void setCardIconColor(int index, Color color) { cardItems.get(index).setCardIconColor(color); } + + public void setTitleTextAt(int index, String text) { + cardItems.get(index).setTitleText(text); + } + + public void setDescriptionTextAt(int index, String text) { + cardItems.get(index).setDescriptionText(text); + } } diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java index fd3c326c..4b5c150e 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java @@ -61,6 +61,14 @@ public void setCardIconColor(Color color) { } } + public void setTitleText(String text) { + lbTitle.setText(text); + } + + public void setDescriptionText(String text) { + lbDescription.setText(text); + } + private JLabel lbTitle; private JLabel lbValue; private JLabel lbDescription; diff --git a/src/main/java/backupmanager/gui/forms/CustomForm.java b/src/main/java/backupmanager/gui/forms/CustomForm.java index 9576f363..95d2bd93 100644 --- a/src/main/java/backupmanager/gui/forms/CustomForm.java +++ b/src/main/java/backupmanager/gui/forms/CustomForm.java @@ -7,10 +7,12 @@ import com.formdev.flatlaf.FlatClientProperties; +import backupmanager.Managers.LanguageManager; import backupmanager.gui.system.Form; +import backupmanager.interfaces.ITranslatable; import net.miginfocom.swing.MigLayout; -public abstract class CustomForm extends Form { +public abstract class CustomForm extends Form implements ITranslatable { private JLabel lbTitle; private JTextPane text; @@ -19,11 +21,14 @@ protected CustomForm() {} protected void build() { init(); + LanguageManager.register(this); setTranslations(); } protected abstract void init(); - protected abstract void setTranslations(); + + @Override + public abstract void setTranslations(); @Override public void formInit() { diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index bf372557..f797f469 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -29,8 +29,6 @@ import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.component.ToolBarSelection; import backupmanager.gui.component.chart.BarChart; -import backupmanager.gui.component.chart.PieChart; -import backupmanager.gui.component.chart.SpiderChart; import backupmanager.gui.component.chart.TimeSeriesChart; import backupmanager.gui.component.chart.themes.ColorThemes; import backupmanager.gui.component.chart.themes.DefaultChartTheme; @@ -76,7 +74,7 @@ protected void loadData() { cardBox.setValueAt(CARD_SUCCESS_RATE, String.valueOf(snapshot.totalRequests()), - Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE), + "Success rate", String.format("%.2f%%", snapshot.successRate()), true); @@ -100,7 +98,7 @@ protected void createTitle() { JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); - JLabel title = new JLabel(Translations.get(TKey.DASHBOARD_TITLE)); + title = new JLabel("Backup Analytics Dashboard"); title.putClientProperty(FlatClientProperties.STYLE, "font:bold +3"); @@ -155,19 +153,19 @@ private void createCard() { cardBox.addCardItem( createIcon("icons/dashboard/database.svg", DefaultChartTheme.getColor(CARD_TOTAL_CONFIG)), - Translations.get(TKey.DASHBOARD_CARD_TOTAL_CONFIGURATIONS)); + "Total Backup Configurations"); cardBox.addCardItem( createIcon("icons/dashboard/run.svg", DefaultChartTheme.getColor(CARD_SUCCESS_RATE)), - Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); + "Total Backup Executions"); cardBox.addCardItem( createIcon("icons/dashboard/duration.svg", DefaultChartTheme.getColor(CARD_DURATION)), - Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); + "Avg Backup Duration"); cardBox.addCardItem( createIcon("icons/dashboard/rate.svg", DefaultChartTheme.getColor(CARD_COMPRESSION)), - Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); + "Compression Rate"); panel.add(cardBox); panelLayout.add(panel); @@ -184,19 +182,6 @@ private void createAvgDurationChart() { panelLayout.add(panel); } - private void createDiskUsageChart() { - - JPanel panel = createChartPanel(350); - - spiderChart = new SpiderChart(); - pieChart = new PieChart(); - - panel.add(spiderChart); - panel.add(pieChart); - - panelLayout.add(panel); - } - private void createExecutionsByMonthChart() { JPanel panel = createChartPanel(350); executionsChart = new BarChart(); @@ -210,17 +195,22 @@ private Icon createIcon(String icon, Color color) { } @Override - protected void setTranslations() { - + public void setTranslations() { + title.setText(Translations.get(TKey.DASHBOARD_TITLE)); + cardBox.setTitleTextAt(CARD_TOTAL_CONFIG, Translations.get(TKey.DASHBOARD_CARD_TOTAL_CONFIGURATIONS)); + cardBox.setTitleTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); + cardBox.setTitleTextAt(CARD_DURATION, Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); + cardBox.setTitleTextAt(CARD_COMPRESSION, Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); + cardBox.setDescriptionTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE)); } + private JLabel title; + private JPanel panelLayout; private CardBox cardBox; private TimeSeriesChart durationChart; private BarChart executionsChart; - private SpiderChart spiderChart; - private PieChart pieChart; private class DashboardLayout implements LayoutManager { diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index dd4cd8db..7a11348f 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -30,7 +30,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; @@ -493,31 +493,31 @@ private void showDeleteConfirmation() { } @Override - protected void setTranslations() { - editTitle(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_TITLE)); - editDescription(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_DESCRIPTION)); - - txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_PLACEHOLDER)); - txtSearch.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_TOOLTIP)); - - cmdCreate.setText(TCategory.GENERAL.getTranslation(TKey.CREATE_BUTTON)); - cmdEdit.setText(TCategory.GENERAL.getTranslation(TKey.EDIT_BUTTON)); - cmdDelete.setText(TCategory.GENERAL.getTranslation(TKey.DELETE_BUTTON)); - - itemEdit.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EDIT_POPUP)); - itemDelete.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DELETE_POPUP)); - itemDuplicate.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DUPLICATE_POPUP)); - itemRename.setText(TCategory.BACKUP_LIST.getTranslation(TKey.RENAME_BACKUP_POPUP)); - itemOpenTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_INITIAL_FOLDER_POPUP)); - itemOpenDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_DESTINATION_FOLDER_POPUP)); - itemBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_POPUP)); - itemRunSingleBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.SINGLE_BACKUP_POPUP)); - itemAutoBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.AUTO_BACKUP_POPUP)); - itemInterruptBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.INTERRUPT_POPUP)); - itemCopyText.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_TEXT_POPUP)); - itemCopyBackupName.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_BACKUP_NAME_POPUP)); - itemCopyTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_INITIAL_PATH_POPUP)); - itemCopyDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); + public void setTranslations() { + editTitle(Translations.get(TKey.BACKUP_LIST_TITLE)); + editDescription(Translations.get(TKey.BACKUP_LIST_DESCRIPTION)); + + txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.RESEARCH_BAR_PLACEHOLDER)); + txtSearch.setToolTipText(Translations.get(TKey.RESEARCH_BAR_TOOLTIP)); + + cmdCreate.setText(Translations.get(TKey.CREATE_BUTTON)); + cmdEdit.setText(Translations.get(TKey.EDIT_BUTTON)); + cmdDelete.setText(Translations.get(TKey.DELETE_BUTTON)); + + itemEdit.setText(Translations.get(TKey.EDIT_POPUP)); + itemDelete.setText(Translations.get(TKey.DELETE_POPUP)); + itemDuplicate.setText(Translations.get(TKey.DUPLICATE_POPUP)); + itemRename.setText(Translations.get(TKey.RENAME_BACKUP_POPUP)); + itemOpenTargetPath.setText(Translations.get(TKey.OPEN_INITIAL_FOLDER_POPUP)); + itemOpenDestinationPath.setText(Translations.get(TKey.OPEN_DESTINATION_FOLDER_POPUP)); + itemBackup.setText(Translations.get(TKey.BACKUP_POPUP)); + itemRunSingleBackup.setText(Translations.get(TKey.SINGLE_BACKUP_POPUP)); + itemAutoBackup.setText(Translations.get(TKey.AUTO_BACKUP_POPUP)); + itemInterruptBackup.setText(Translations.get(TKey.INTERRUPT_POPUP)); + itemCopyText.setText(Translations.get(TKey.COPY_TEXT_POPUP)); + itemCopyBackupName.setText(Translations.get(TKey.COPY_BACKUP_NAME_POPUP)); + itemCopyTargetPath.setText(Translations.get(TKey.COPY_INITIAL_PATH_POPUP)); + itemCopyDestinationPath.setText(Translations.get(TKey.COPY_DESTINATION_PATH_BACKUP)); } private BackupTable backupTable; diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java index 459d5652..a00915eb 100644 --- a/src/main/java/backupmanager/gui/forms/FormHistory.java +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -67,7 +67,7 @@ private Component createLogPanel() { } @Override - protected void setTranslations() { + public void setTranslations() { editTitle(Translations.get(TKey.HISTORY_LOGS_TITLE)); editDescription(Translations.get(TKey.HISTORY_LOGS_DESCRIPTION)); } diff --git a/src/main/java/backupmanager/gui/forms/FormLogin.java b/src/main/java/backupmanager/gui/forms/FormLogin.java index f4ff37ca..71d56935 100644 --- a/src/main/java/backupmanager/gui/forms/FormLogin.java +++ b/src/main/java/backupmanager/gui/forms/FormLogin.java @@ -12,7 +12,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.LimitDocument; import backupmanager.Services.LoginService; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; @@ -113,12 +113,12 @@ protected void loadData() { } private void showMainForm() { - MyDrawerBuilder.getInstance().initHeader(); + DrawerManager.getInstance().getDrawer(); FormManager.login(); } @Override - protected void setTranslations() { + public void setTranslations() { if (lbTitle == null) { return; } diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index bda89ce9..390deeb9 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -222,6 +222,7 @@ private Component createLanguageOption() { LanguageManager.setLanguage(languageName); }); + languageCombo.setSelectedItem(LanguageManager.getLanguage().getLanguageName()); panel.add(languageCombo); return panel; @@ -482,7 +483,7 @@ private JPanel createThemes() { } @Override - protected void setTranslations() { + public void setTranslations() { windowsLayout.setTitle(Translations.get(TKey.SETTINGS_WINDOWS_LAYOUT)); chRightToLeft.setText(Translations.get(TKey.SETTINGS_WINDOWS_RIGHT)); chFullWindow.setText(Translations.get(TKey.SETTINGS_WINDOWS_FULL)); diff --git a/src/main/java/backupmanager/gui/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java index c4269375..231ee802 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -7,9 +7,8 @@ import com.formdev.flatlaf.FlatClientProperties; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; -import raven.modal.Drawer; public class BackupManager extends JFrame{ @@ -29,7 +28,7 @@ public static synchronized BackupManager getInstance() { private void init() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); - Drawer.installDrawer(this, MyDrawerBuilder.getInstance()); + DrawerManager.getInstance().install(this); FormManager.install(this); setSize(new Dimension(1366, 768)); setLocationRelativeTo(null); diff --git a/src/main/java/backupmanager/gui/menu/DrawerManager.java b/src/main/java/backupmanager/gui/menu/DrawerManager.java new file mode 100644 index 00000000..b43878f6 --- /dev/null +++ b/src/main/java/backupmanager/gui/menu/DrawerManager.java @@ -0,0 +1,58 @@ +package backupmanager.gui.menu; + +import javax.swing.JFrame; + +import backupmanager.Managers.LanguageManager; +import backupmanager.interfaces.ITranslatable; +import raven.modal.Drawer; +import raven.modal.drawer.DrawerPanel; +import raven.modal.drawer.item.MenuItem; + +public class DrawerManager implements ITranslatable { + + private DrawerPanel drawerPanel; + private MenuItem[] menuItems; + + private static DrawerManager instance; + + public static DrawerManager getInstance() { + if (instance == null) { + instance = new DrawerManager(); + } + return instance; + } + + public DrawerPanel getDrawer() { + return drawerPanel; + } + + public MenuItem[] getMenuItems() { + return menuItems; + } + + public void install(JFrame frame) { + MyDrawerBuilder builder = new MyDrawerBuilder(); + + drawerPanel = builder.createDrawer(); + menuItems = builder.getSimpleMenuOption().getMenus(); + + Drawer.installDrawer(frame, builder); + } + + private DrawerManager() { + LanguageManager.register(this); + rebuildDrawer(); + } + + private void rebuildDrawer() { + MyDrawerBuilder builder = new MyDrawerBuilder(); + drawerPanel = builder.createDrawer(); + } + + // it's not the best rebuild the menu every time the language is changed but right now there is no a method to update + // the MenuItem title textin the raven library + @Override + public void setTranslations() { + rebuildDrawer(); + } +} diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index 4d8b806d..05d33624 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -52,13 +52,14 @@ public class MyDrawerBuilder extends SimpleDrawerBuilder { private static final Map<MenuItems, Runnable> menuActionMap = new HashMap<>(); private static final Map<String, MenuItems> menuBindingMap = new HashMap<>(); - private static MyDrawerBuilder instance; - public static MyDrawerBuilder getInstance() { - if (instance == null) { - instance = new MyDrawerBuilder(); - } - return instance; + public MyDrawerBuilder() { + super(createSimpleMenuOption()); + initMenuActions(); + LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); + lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { + // event for light dark mode changed + }); } public void initHeader() { @@ -75,15 +76,6 @@ public void initHeader() { rebuildMenu(); } - private MyDrawerBuilder() { - super(createSimpleMenuOption()); - initMenuActions(); - LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); - lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { - // event for light dark mode changed - }); - } - @Override public SimpleHeaderData getSimpleHeaderData() { AvatarIcon icon = new AvatarIcon(new FlatSVGIcon("drawer/logo.svg", 100, 100), 50, 50, 3.5f); @@ -127,7 +119,7 @@ private static void initMenuActions() { WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue())); menuActionMap.put(MenuItems.ContactUs, () -> - WebsiteManager.openWebSite(ConfigKey.EMAIL.getValue())); + WebsiteManager.sendEmail()); menuActionMap.put(MenuItems.PaypalDonate, () -> WebsiteManager.openWebSite(ConfigKey.DONATE_PAYPAL_LINK.getValue())); @@ -242,6 +234,11 @@ private static String getDrawerBackgroundStyle() { return "background:$Menu.background;"; } + public DrawerPanel createDrawer() { + Option option = createOption(); + return new DrawerPanel(this, option); + } + private static List<MenuItem> buildMenuItems() { JsonConfig config = JsonConfig.getInstance(); List<MenuItem> itemList = new ArrayList<>(); diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index e138cc41..fe563503 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -305,7 +305,7 @@ private void setCurrentBackupMaxBackupsToKeep(int maxBackupsCount) { } @Override - protected void setTranslations() { + public void setTranslations() { backupOnText = Translations.get(TKey.AUTO_BACKUP_BUTTON_ON); backupOffText = Translations.get(TKey.AUTO_BACKUP_BUTTON_OFF); targetPathBtn.setToolTipText(Translations.get(TKey.INITIAL_FILE_CHOOSER_TOOLTIP)); diff --git a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java index 09c9ffe2..86a184cb 100644 --- a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java +++ b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java @@ -84,7 +84,7 @@ public TimeInterval getResult() { } @Override - protected void setTranslations() { + public void setTranslations() { description.setText(Translations.get(TKey.DESCRIPTION)); daysSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); hoursSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); diff --git a/src/main/java/backupmanager/gui/system/FormSearch.java b/src/main/java/backupmanager/gui/system/FormSearch.java index 8671b3af..27a7b378 100644 --- a/src/main/java/backupmanager/gui/system/FormSearch.java +++ b/src/main/java/backupmanager/gui/system/FormSearch.java @@ -14,7 +14,7 @@ import backupmanager.gui.component.EmptyModalBorder; import backupmanager.gui.component.FormSearchPanel; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.utils.SystemForm; import raven.modal.ModalDialog; import raven.modal.drawer.item.Item; @@ -47,7 +47,7 @@ private FormSearch() { } private Class<? extends Form>[] getClassForms() { - MenuItem[] menuItems = MyDrawerBuilder.getInstance().getSimpleMenuOption().getMenus(); + MenuItem[] menuItems = DrawerManager.getInstance().getMenuItems(); List<Class<?>> formClass = new ArrayList<>(); getMenuClass(menuItems, formClass); return formClass.toArray(new Class[0]); diff --git a/src/main/java/backupmanager/interfaces/ITranslatable.java b/src/main/java/backupmanager/interfaces/ITranslatable.java new file mode 100644 index 00000000..1fb6f21b --- /dev/null +++ b/src/main/java/backupmanager/interfaces/ITranslatable.java @@ -0,0 +1,5 @@ +package backupmanager.interfaces; + +public interface ITranslatable { + public void setTranslations(); +} From 039a58c6b62864e540b66348f69bf84d5689c3ed Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Thu, 23 Apr 2026 19:30:04 +0200 Subject: [PATCH 10/17] translations update --- .../java/backupmanager/Enums/MenuItems.java | 2 +- .../backupmanager/Enums/Translations.java | 11 +- src/main/java/backupmanager/MainApp.java | 13 +- .../Managers/LanguageManager.java | 56 ++- .../backupmanager/gui/component/About.java | 11 +- .../gui/component/dashboard/CardBox.java | 8 + .../gui/component/dashboard/CardItem.java | 8 + .../backupmanager/gui/forms/CustomForm.java | 9 +- .../gui/forms/FormBackupDashboard.java | 40 +- .../gui/forms/FormBackupTable.java | 52 +-- .../backupmanager/gui/forms/FormHistory.java | 2 +- .../backupmanager/gui/forms/FormLogin.java | 6 +- .../backupmanager/gui/forms/FormSetting.java | 3 +- .../gui/frames/BackupManager.java | 5 +- .../backupmanager/gui/menu/DrawerManager.java | 58 +++ .../gui/menu/MyDrawerBuilder.java | 29 +- .../gui/simple/BackupEntryDialog.java | 2 +- .../gui/simple/TimePickerDialog.java | 2 +- .../backupmanager/gui/system/FormSearch.java | 4 +- .../interfaces/ITranslatable.java | 5 + src/main/resources/res/languages/deu.json | 349 +++++++++--------- src/main/resources/res/languages/eng.json | 349 +++++++++--------- src/main/resources/res/languages/esp.json | 349 +++++++++--------- src/main/resources/res/languages/fra.json | 349 +++++++++--------- src/main/resources/res/languages/ita.json | 349 +++++++++--------- 25 files changed, 1113 insertions(+), 958 deletions(-) create mode 100644 src/main/java/backupmanager/gui/menu/DrawerManager.java create mode 100644 src/main/java/backupmanager/interfaces/ITranslatable.java diff --git a/src/main/java/backupmanager/Enums/MenuItems.java b/src/main/java/backupmanager/Enums/MenuItems.java index c4698d8f..60c97d2d 100644 --- a/src/main/java/backupmanager/Enums/MenuItems.java +++ b/src/main/java/backupmanager/Enums/MenuItems.java @@ -14,7 +14,7 @@ public enum MenuItems { Share, //TODO: Used?? Support, ContactUs, - Website, //TODO: Used?? + Website, BackupList, Dashboard, About, diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index fa7c596a..28414b5c 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -57,6 +57,10 @@ public String getTranslation(TKey key) { public String getCategoryName() { return categoryName; } + + public void clearTranslations() { + translations.clear(); + } } public enum TKey { @@ -321,7 +325,7 @@ public enum TKey { // ABOUT ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), // TODO: add to json - ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><br>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</html>"), // TODO: add to json + ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><p>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</p></html>"), // TODO: add to json // SETTINGS SETTINGS_LAYOUT_TAB(TCategory.SETTINGS, "SettingsLayoutTab", "Layout"), // TODO: add to json @@ -391,6 +395,11 @@ public static String get(TKey key) { public static void loadTranslations(String filePath) throws IOException { Gson gson = new Gson(); + // Clear previous translations to avoid stale values when switching languages + for (TCategory c : TCategory.values()) { + c.clearTranslations(); + } + try (FileReader reader = new FileReader(filePath, StandardCharsets.UTF_8)) { JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 4170c7c0..27b95a74 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -13,9 +13,7 @@ import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.util.FontUtils; -import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations; import backupmanager.Managers.ExceptionManager; import backupmanager.Managers.LanguageManager; import backupmanager.database.Database; @@ -35,7 +33,7 @@ public static void main(String[] args) { databaseInitialization(); AppPreferences.init(); - loadPreferredLanguage(); + LanguageManager.loadPreferredLanguage(); boolean isBackgroundMode = isBackgroundMode(args); @@ -60,15 +58,6 @@ private static void databaseInitialization() { } } - private static void loadPreferredLanguage() { - try { - Configurations.loadAllConfigurations(); - Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + LanguageManager.getLanguage().getFileName()); - } catch (IOException ex) { - logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); - } - } - private static boolean isBackgroundMode(String[] args) { boolean isBackgroundMode = args.length > 0 && args[0].equalsIgnoreCase("--background"); diff --git a/src/main/java/backupmanager/Managers/LanguageManager.java b/src/main/java/backupmanager/Managers/LanguageManager.java index ffbcc29d..8ea1dc9c 100644 --- a/src/main/java/backupmanager/Managers/LanguageManager.java +++ b/src/main/java/backupmanager/Managers/LanguageManager.java @@ -1,43 +1,65 @@ package backupmanager.Managers; -import java.util.Arrays; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.SwingUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import backupmanager.Enums.ConfigKey; import backupmanager.Enums.LanguagesEnum; +import backupmanager.Enums.Translations; +import backupmanager.interfaces.ITranslatable; import backupmanager.utils.AppPreferences; +// Observer class -> every time the language is changed, it notify all the components registered public class LanguageManager { private static final Logger logger = LoggerFactory.getLogger(LanguageManager.class); - public static void setLanguage(LanguagesEnum language) { - String fileName = language.getFileName(); - AppPreferences.setLanguage(fileName); - logger.info("Language setted to: {}", language.getLanguageName()); - } + private static final List<WeakReference<ITranslatable>> listeners = new ArrayList<>(); public static void setLanguage(String language) { var lang = getLanguageByLanguageName(language); setLanguage(lang); } + public static void setLanguage(LanguagesEnum language) { + AppPreferences.setLanguage(language.getFileName()); + logger.info("Language setted to: {}", language.getLanguageName()); + loadPreferredLanguage(); + notifyLanguageChanged(); + } + + public static void loadPreferredLanguage() { + try { + Translations.loadTranslations(ConfigKey.LANGUAGES_DIRECTORY_STRING.getValue() + LanguageManager.getLanguage().getFileName()); + } catch (IOException ex) { + logger.error("An error occurred during loading preferences: {}", ex.getMessage(), ex); + } + } public static LanguagesEnum getLanguage() { - return getLanguageInPreferences(); + return getLanguageByFileName(AppPreferences.getLanguage()); } - private static LanguagesEnum getLanguageInPreferences() { - String langPref = AppPreferences.getLanguage(); - String filename = !langPref.isEmpty() ? langPref : LanguagesEnum.getDefault().getFileName(); + public static void register(ITranslatable t) { + listeners.add(new WeakReference<>(t)); + } - try { - return getLanguageByFileName(filename); - } catch (Exception ex) { - logger.error("An error occurred during fetching language: {}", ex.getMessage(), ex); - ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); - } - return LanguagesEnum.getDefault(); + public static void notifyLanguageChanged() { + SwingUtilities.invokeLater(() -> { + listeners.removeIf(ref -> ref.get() == null); + for (WeakReference<ITranslatable> ref : listeners) { + ITranslatable t = ref.get(); + if (t != null) { + t.setTranslations(); + } + } + }); } private static LanguagesEnum getLanguageByFileName(String filename) { diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index 1d198065..2db9270a 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -11,8 +11,10 @@ import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Enums.ConfigKey; +import backupmanager.Enums.MenuItems; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Json.JsonConfig; import backupmanager.Managers.WebsiteManager; import net.miginfocom.swing.MigLayout; @@ -60,7 +62,14 @@ public void paint(java.awt.Graphics g) {} private String getDescriptionText() { String message = Translations.get(TKey.ABOUT_MESSAGE_BODY); - message = message.replace("[PROJECT_WEBSITE]", ConfigKey.INFO_PAGE_LINK.getValue()) ; + message = message.replace("[PROJECT_WEBSITE]", ConfigKey.INFO_PAGE_LINK.getValue()); + + // removing all the info inside the <p> tag + JsonConfig config = JsonConfig.getInstance(); + if (!config.isMenuItemEnabled(MenuItems.Website.name())) { + message = message.replaceAll("<p>.*?</p>", ""); + } + return message; } diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java index 336ef9f2..752db2b3 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java @@ -45,4 +45,12 @@ public void setValueAt(int index, String value, String description, String tags, public void setCardIconColor(int index, Color color) { cardItems.get(index).setCardIconColor(color); } + + public void setTitleTextAt(int index, String text) { + cardItems.get(index).setTitleText(text); + } + + public void setDescriptionTextAt(int index, String text) { + cardItems.get(index).setDescriptionText(text); + } } diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java index fd3c326c..4b5c150e 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java @@ -61,6 +61,14 @@ public void setCardIconColor(Color color) { } } + public void setTitleText(String text) { + lbTitle.setText(text); + } + + public void setDescriptionText(String text) { + lbDescription.setText(text); + } + private JLabel lbTitle; private JLabel lbValue; private JLabel lbDescription; diff --git a/src/main/java/backupmanager/gui/forms/CustomForm.java b/src/main/java/backupmanager/gui/forms/CustomForm.java index 9576f363..95d2bd93 100644 --- a/src/main/java/backupmanager/gui/forms/CustomForm.java +++ b/src/main/java/backupmanager/gui/forms/CustomForm.java @@ -7,10 +7,12 @@ import com.formdev.flatlaf.FlatClientProperties; +import backupmanager.Managers.LanguageManager; import backupmanager.gui.system.Form; +import backupmanager.interfaces.ITranslatable; import net.miginfocom.swing.MigLayout; -public abstract class CustomForm extends Form { +public abstract class CustomForm extends Form implements ITranslatable { private JLabel lbTitle; private JTextPane text; @@ -19,11 +21,14 @@ protected CustomForm() {} protected void build() { init(); + LanguageManager.register(this); setTranslations(); } protected abstract void init(); - protected abstract void setTranslations(); + + @Override + public abstract void setTranslations(); @Override public void formInit() { diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index bf372557..f797f469 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -29,8 +29,6 @@ import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.component.ToolBarSelection; import backupmanager.gui.component.chart.BarChart; -import backupmanager.gui.component.chart.PieChart; -import backupmanager.gui.component.chart.SpiderChart; import backupmanager.gui.component.chart.TimeSeriesChart; import backupmanager.gui.component.chart.themes.ColorThemes; import backupmanager.gui.component.chart.themes.DefaultChartTheme; @@ -76,7 +74,7 @@ protected void loadData() { cardBox.setValueAt(CARD_SUCCESS_RATE, String.valueOf(snapshot.totalRequests()), - Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE), + "Success rate", String.format("%.2f%%", snapshot.successRate()), true); @@ -100,7 +98,7 @@ protected void createTitle() { JPanel panel = new JPanel(new MigLayout("fillx", "[]push[][]")); - JLabel title = new JLabel(Translations.get(TKey.DASHBOARD_TITLE)); + title = new JLabel("Backup Analytics Dashboard"); title.putClientProperty(FlatClientProperties.STYLE, "font:bold +3"); @@ -155,19 +153,19 @@ private void createCard() { cardBox.addCardItem( createIcon("icons/dashboard/database.svg", DefaultChartTheme.getColor(CARD_TOTAL_CONFIG)), - Translations.get(TKey.DASHBOARD_CARD_TOTAL_CONFIGURATIONS)); + "Total Backup Configurations"); cardBox.addCardItem( createIcon("icons/dashboard/run.svg", DefaultChartTheme.getColor(CARD_SUCCESS_RATE)), - Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); + "Total Backup Executions"); cardBox.addCardItem( createIcon("icons/dashboard/duration.svg", DefaultChartTheme.getColor(CARD_DURATION)), - Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); + "Avg Backup Duration"); cardBox.addCardItem( createIcon("icons/dashboard/rate.svg", DefaultChartTheme.getColor(CARD_COMPRESSION)), - Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); + "Compression Rate"); panel.add(cardBox); panelLayout.add(panel); @@ -184,19 +182,6 @@ private void createAvgDurationChart() { panelLayout.add(panel); } - private void createDiskUsageChart() { - - JPanel panel = createChartPanel(350); - - spiderChart = new SpiderChart(); - pieChart = new PieChart(); - - panel.add(spiderChart); - panel.add(pieChart); - - panelLayout.add(panel); - } - private void createExecutionsByMonthChart() { JPanel panel = createChartPanel(350); executionsChart = new BarChart(); @@ -210,17 +195,22 @@ private Icon createIcon(String icon, Color color) { } @Override - protected void setTranslations() { - + public void setTranslations() { + title.setText(Translations.get(TKey.DASHBOARD_TITLE)); + cardBox.setTitleTextAt(CARD_TOTAL_CONFIG, Translations.get(TKey.DASHBOARD_CARD_TOTAL_CONFIGURATIONS)); + cardBox.setTitleTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); + cardBox.setTitleTextAt(CARD_DURATION, Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); + cardBox.setTitleTextAt(CARD_COMPRESSION, Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); + cardBox.setDescriptionTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE)); } + private JLabel title; + private JPanel panelLayout; private CardBox cardBox; private TimeSeriesChart durationChart; private BarChart executionsChart; - private SpiderChart spiderChart; - private PieChart pieChart; private class DashboardLayout implements LayoutManager { diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index dd4cd8db..7a11348f 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -30,7 +30,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; @@ -493,31 +493,31 @@ private void showDeleteConfirmation() { } @Override - protected void setTranslations() { - editTitle(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_TITLE)); - editDescription(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_LIST_DESCRIPTION)); - - txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_PLACEHOLDER)); - txtSearch.setToolTipText(TCategory.BACKUP_LIST.getTranslation(TKey.RESEARCH_BAR_TOOLTIP)); - - cmdCreate.setText(TCategory.GENERAL.getTranslation(TKey.CREATE_BUTTON)); - cmdEdit.setText(TCategory.GENERAL.getTranslation(TKey.EDIT_BUTTON)); - cmdDelete.setText(TCategory.GENERAL.getTranslation(TKey.DELETE_BUTTON)); - - itemEdit.setText(TCategory.BACKUP_LIST.getTranslation(TKey.EDIT_POPUP)); - itemDelete.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DELETE_POPUP)); - itemDuplicate.setText(TCategory.BACKUP_LIST.getTranslation(TKey.DUPLICATE_POPUP)); - itemRename.setText(TCategory.BACKUP_LIST.getTranslation(TKey.RENAME_BACKUP_POPUP)); - itemOpenTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_INITIAL_FOLDER_POPUP)); - itemOpenDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.OPEN_DESTINATION_FOLDER_POPUP)); - itemBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_POPUP)); - itemRunSingleBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.SINGLE_BACKUP_POPUP)); - itemAutoBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.AUTO_BACKUP_POPUP)); - itemInterruptBackup.setText(TCategory.BACKUP_LIST.getTranslation(TKey.INTERRUPT_POPUP)); - itemCopyText.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_TEXT_POPUP)); - itemCopyBackupName.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_BACKUP_NAME_POPUP)); - itemCopyTargetPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_INITIAL_PATH_POPUP)); - itemCopyDestinationPath.setText(TCategory.BACKUP_LIST.getTranslation(TKey.COPY_DESTINATION_PATH_BACKUP)); + public void setTranslations() { + editTitle(Translations.get(TKey.BACKUP_LIST_TITLE)); + editDescription(Translations.get(TKey.BACKUP_LIST_DESCRIPTION)); + + txtSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.RESEARCH_BAR_PLACEHOLDER)); + txtSearch.setToolTipText(Translations.get(TKey.RESEARCH_BAR_TOOLTIP)); + + cmdCreate.setText(Translations.get(TKey.CREATE_BUTTON)); + cmdEdit.setText(Translations.get(TKey.EDIT_BUTTON)); + cmdDelete.setText(Translations.get(TKey.DELETE_BUTTON)); + + itemEdit.setText(Translations.get(TKey.EDIT_POPUP)); + itemDelete.setText(Translations.get(TKey.DELETE_POPUP)); + itemDuplicate.setText(Translations.get(TKey.DUPLICATE_POPUP)); + itemRename.setText(Translations.get(TKey.RENAME_BACKUP_POPUP)); + itemOpenTargetPath.setText(Translations.get(TKey.OPEN_INITIAL_FOLDER_POPUP)); + itemOpenDestinationPath.setText(Translations.get(TKey.OPEN_DESTINATION_FOLDER_POPUP)); + itemBackup.setText(Translations.get(TKey.BACKUP_POPUP)); + itemRunSingleBackup.setText(Translations.get(TKey.SINGLE_BACKUP_POPUP)); + itemAutoBackup.setText(Translations.get(TKey.AUTO_BACKUP_POPUP)); + itemInterruptBackup.setText(Translations.get(TKey.INTERRUPT_POPUP)); + itemCopyText.setText(Translations.get(TKey.COPY_TEXT_POPUP)); + itemCopyBackupName.setText(Translations.get(TKey.COPY_BACKUP_NAME_POPUP)); + itemCopyTargetPath.setText(Translations.get(TKey.COPY_INITIAL_PATH_POPUP)); + itemCopyDestinationPath.setText(Translations.get(TKey.COPY_DESTINATION_PATH_BACKUP)); } private BackupTable backupTable; diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java index 459d5652..a00915eb 100644 --- a/src/main/java/backupmanager/gui/forms/FormHistory.java +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -67,7 +67,7 @@ private Component createLogPanel() { } @Override - protected void setTranslations() { + public void setTranslations() { editTitle(Translations.get(TKey.HISTORY_LOGS_TITLE)); editDescription(Translations.get(TKey.HISTORY_LOGS_DESCRIPTION)); } diff --git a/src/main/java/backupmanager/gui/forms/FormLogin.java b/src/main/java/backupmanager/gui/forms/FormLogin.java index f4ff37ca..71d56935 100644 --- a/src/main/java/backupmanager/gui/forms/FormLogin.java +++ b/src/main/java/backupmanager/gui/forms/FormLogin.java @@ -12,7 +12,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.LimitDocument; import backupmanager.Services.LoginService; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; @@ -113,12 +113,12 @@ protected void loadData() { } private void showMainForm() { - MyDrawerBuilder.getInstance().initHeader(); + DrawerManager.getInstance().getDrawer(); FormManager.login(); } @Override - protected void setTranslations() { + public void setTranslations() { if (lbTitle == null) { return; } diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index bda89ce9..390deeb9 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -222,6 +222,7 @@ private Component createLanguageOption() { LanguageManager.setLanguage(languageName); }); + languageCombo.setSelectedItem(LanguageManager.getLanguage().getLanguageName()); panel.add(languageCombo); return panel; @@ -482,7 +483,7 @@ private JPanel createThemes() { } @Override - protected void setTranslations() { + public void setTranslations() { windowsLayout.setTitle(Translations.get(TKey.SETTINGS_WINDOWS_LAYOUT)); chRightToLeft.setText(Translations.get(TKey.SETTINGS_WINDOWS_RIGHT)); chFullWindow.setText(Translations.get(TKey.SETTINGS_WINDOWS_FULL)); diff --git a/src/main/java/backupmanager/gui/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java index c4269375..231ee802 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -7,9 +7,8 @@ import com.formdev.flatlaf.FlatClientProperties; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; -import raven.modal.Drawer; public class BackupManager extends JFrame{ @@ -29,7 +28,7 @@ public static synchronized BackupManager getInstance() { private void init() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); - Drawer.installDrawer(this, MyDrawerBuilder.getInstance()); + DrawerManager.getInstance().install(this); FormManager.install(this); setSize(new Dimension(1366, 768)); setLocationRelativeTo(null); diff --git a/src/main/java/backupmanager/gui/menu/DrawerManager.java b/src/main/java/backupmanager/gui/menu/DrawerManager.java new file mode 100644 index 00000000..b43878f6 --- /dev/null +++ b/src/main/java/backupmanager/gui/menu/DrawerManager.java @@ -0,0 +1,58 @@ +package backupmanager.gui.menu; + +import javax.swing.JFrame; + +import backupmanager.Managers.LanguageManager; +import backupmanager.interfaces.ITranslatable; +import raven.modal.Drawer; +import raven.modal.drawer.DrawerPanel; +import raven.modal.drawer.item.MenuItem; + +public class DrawerManager implements ITranslatable { + + private DrawerPanel drawerPanel; + private MenuItem[] menuItems; + + private static DrawerManager instance; + + public static DrawerManager getInstance() { + if (instance == null) { + instance = new DrawerManager(); + } + return instance; + } + + public DrawerPanel getDrawer() { + return drawerPanel; + } + + public MenuItem[] getMenuItems() { + return menuItems; + } + + public void install(JFrame frame) { + MyDrawerBuilder builder = new MyDrawerBuilder(); + + drawerPanel = builder.createDrawer(); + menuItems = builder.getSimpleMenuOption().getMenus(); + + Drawer.installDrawer(frame, builder); + } + + private DrawerManager() { + LanguageManager.register(this); + rebuildDrawer(); + } + + private void rebuildDrawer() { + MyDrawerBuilder builder = new MyDrawerBuilder(); + drawerPanel = builder.createDrawer(); + } + + // it's not the best rebuild the menu every time the language is changed but right now there is no a method to update + // the MenuItem title textin the raven library + @Override + public void setTranslations() { + rebuildDrawer(); + } +} diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index 4d8b806d..05d33624 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -52,13 +52,14 @@ public class MyDrawerBuilder extends SimpleDrawerBuilder { private static final Map<MenuItems, Runnable> menuActionMap = new HashMap<>(); private static final Map<String, MenuItems> menuBindingMap = new HashMap<>(); - private static MyDrawerBuilder instance; - public static MyDrawerBuilder getInstance() { - if (instance == null) { - instance = new MyDrawerBuilder(); - } - return instance; + public MyDrawerBuilder() { + super(createSimpleMenuOption()); + initMenuActions(); + LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); + lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { + // event for light dark mode changed + }); } public void initHeader() { @@ -75,15 +76,6 @@ public void initHeader() { rebuildMenu(); } - private MyDrawerBuilder() { - super(createSimpleMenuOption()); - initMenuActions(); - LightDarkButtonFooter lightDarkButtonFooter = (LightDarkButtonFooter) getFooter(); - lightDarkButtonFooter.addModeChangeListener(isDarkMode -> { - // event for light dark mode changed - }); - } - @Override public SimpleHeaderData getSimpleHeaderData() { AvatarIcon icon = new AvatarIcon(new FlatSVGIcon("drawer/logo.svg", 100, 100), 50, 50, 3.5f); @@ -127,7 +119,7 @@ private static void initMenuActions() { WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue())); menuActionMap.put(MenuItems.ContactUs, () -> - WebsiteManager.openWebSite(ConfigKey.EMAIL.getValue())); + WebsiteManager.sendEmail()); menuActionMap.put(MenuItems.PaypalDonate, () -> WebsiteManager.openWebSite(ConfigKey.DONATE_PAYPAL_LINK.getValue())); @@ -242,6 +234,11 @@ private static String getDrawerBackgroundStyle() { return "background:$Menu.background;"; } + public DrawerPanel createDrawer() { + Option option = createOption(); + return new DrawerPanel(this, option); + } + private static List<MenuItem> buildMenuItems() { JsonConfig config = JsonConfig.getInstance(); List<MenuItem> itemList = new ArrayList<>(); diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index e138cc41..fe563503 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -305,7 +305,7 @@ private void setCurrentBackupMaxBackupsToKeep(int maxBackupsCount) { } @Override - protected void setTranslations() { + public void setTranslations() { backupOnText = Translations.get(TKey.AUTO_BACKUP_BUTTON_ON); backupOffText = Translations.get(TKey.AUTO_BACKUP_BUTTON_OFF); targetPathBtn.setToolTipText(Translations.get(TKey.INITIAL_FILE_CHOOSER_TOOLTIP)); diff --git a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java index 09c9ffe2..86a184cb 100644 --- a/src/main/java/backupmanager/gui/simple/TimePickerDialog.java +++ b/src/main/java/backupmanager/gui/simple/TimePickerDialog.java @@ -84,7 +84,7 @@ public TimeInterval getResult() { } @Override - protected void setTranslations() { + public void setTranslations() { description.setText(Translations.get(TKey.DESCRIPTION)); daysSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); hoursSpinner.setToolTipText(Translations.get(TKey.SPINNER_TOOLTIP)); diff --git a/src/main/java/backupmanager/gui/system/FormSearch.java b/src/main/java/backupmanager/gui/system/FormSearch.java index 8671b3af..27a7b378 100644 --- a/src/main/java/backupmanager/gui/system/FormSearch.java +++ b/src/main/java/backupmanager/gui/system/FormSearch.java @@ -14,7 +14,7 @@ import backupmanager.gui.component.EmptyModalBorder; import backupmanager.gui.component.FormSearchPanel; -import backupmanager.gui.menu.MyDrawerBuilder; +import backupmanager.gui.menu.DrawerManager; import backupmanager.utils.SystemForm; import raven.modal.ModalDialog; import raven.modal.drawer.item.Item; @@ -47,7 +47,7 @@ private FormSearch() { } private Class<? extends Form>[] getClassForms() { - MenuItem[] menuItems = MyDrawerBuilder.getInstance().getSimpleMenuOption().getMenus(); + MenuItem[] menuItems = DrawerManager.getInstance().getMenuItems(); List<Class<?>> formClass = new ArrayList<>(); getMenuClass(menuItems, formClass); return formClass.toArray(new Class[0]); diff --git a/src/main/java/backupmanager/interfaces/ITranslatable.java b/src/main/java/backupmanager/interfaces/ITranslatable.java new file mode 100644 index 00000000..1fb6f21b --- /dev/null +++ b/src/main/java/backupmanager/interfaces/ITranslatable.java @@ -0,0 +1,5 @@ +package backupmanager.interfaces; + +public interface ITranslatable { + public void setTranslations(); +} diff --git a/src/main/resources/res/languages/deu.json b/src/main/resources/res/languages/deu.json index 94a8103e..babe1ce7 100644 --- a/src/main/resources/res/languages/deu.json +++ b/src/main/resources/res/languages/deu.json @@ -1,203 +1,214 @@ { + "TrayIcon": { + "SuccessMessage": "\nDas Backup wurde erfolgreich abgeschlossen:", + "ExitAction": "Beenden", + "ErrorMessageFilesNotExisting": "\nFehler beim automatischen Backup.\nEin oder beide Pfade existieren nicht!", + "TrayTooltip": "Backup-Dienst", + "OpenAction": "Schnellzugriff", + "ErrorMessageInputMissing": "\nFehler beim automatischen Backup.\nEingabe fehlt!", + "ErrorMessageSamePaths": "\nFehler beim automatischen Backup.\nDer Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!" + }, + "If value is null or empty, fall back to the default value from the enum": {}, + "Dialogs": { + "WarningGenericTitle": "Warnung", + "SuccessfullyExportedToCsvMessage": "Backups erfolgreich als CSV exportiert!", + "ErrorMessageForPathNotExisting": "Ein oder beide Pfade existieren nicht!", + "ConfirmationDeletionMessage": "Sind Sie sicher, dass Sie die ausgewählten Zeilen löschen möchten?", + "ExceptionMessageReportMessage": "Bitte melden Sie diesen Fehler, entweder mit einem Screenshot oder indem Sie den folgenden Fehlertext kopieren (es ist hilfreich, eine Beschreibung der durchgeführten Aktionen vor dem Fehler anzugeben):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Fehler beim Exportieren der Backups in CSV: ", + "ErrorGenericTitle": "Fehler", + "ErrorMessageForExportingToPdf": "Fehler beim Exportieren der Backups in PDF: ", + "BackupListCorrectlyImportedTitle": "Menü Importieren", + "ErrorWrongTimeInterval": "Das Zeitintervall ist nicht korrekt", + "ErrorMessageOpenHistoryFile": "Fehler beim Öffnen der Verlaufsdatei.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Der Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Freigabelink wurde in die Zwischenablage kopiert!", + "ErrorMessageForSavingFileWithPathsEmpty": "Die Datei konnte nicht gespeichert werden. Sowohl der Anfangs- als auch der Zielpfad müssen angegeben werden und dürfen nicht leer sein", + "BackupListCorrectlyExportedMessage": "Backup-Liste erfolgreich auf den Desktop exportiert!", + "BackupSavedCorrectlyMessage": "erfolgreich gespeichert!", + "SettedEveryMessage": "\nWird eingestellt auf alle", + "WarningBackupAlreadyInProgressMessage": "Es läuft bereits eine Sicherung. Es ist nicht möglich, parallele Sicherungen durchzuführen.", + "WarningShortTimeIntervalMessage": "Das ausgewählte Zeitintervall ist sehr kurz. Für eine optimale Leistung empfehlen wir, es auf mindestens eine Stunde einzustellen. Möchten Sie dennoch fortfahren?", + "ConfirmationMessageCancelAutoBackup": "Sind Sie sicher, dass Sie automatische Backups für diesen Eintrag deaktivieren möchten?", + "ErrorMessageCountingFiles": "Fehler beim Zählen der zu sichernden Dateien.", + "ErrorMessageForFolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Fehler beim Backup-Vorgang: Der Anfangspfad ist falsch!", + "ErrorMessageInputMissingGeneric": "Eingabe fehlt!", + "BackupNameInput": "Name des Backups", + "CsvNameMessageInput": "Geben Sie den Namen der CSV-Datei ein.", + "ConfirmationDeletionTitle": "Löschen bestätigen", + "ErrorMessageForWrongFileExtensionMessage": "Fehler: Bitte wählen Sie eine gültige JSON-Datei aus.", + "ErrorMessageZippingGeneric": "Fehler beim Komprimieren der Dateien.", + "ErrorMessageNotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht.", + "ErrorMessageZippingIO": "Fehler beim Komprimieren der Dateien: E/A-Fehler.", + "SuccessfullyExportedToPdfMessage": "Backups erfolgreich als PDF exportiert!", + "DuplicatedFileNameMessage": "Datei existiert bereits. Überschreiben?", + "AutoBackupActivatedMessage": "Auto-Backup wurde aktiviert", + "DaysMessage": " Tage", + "ExceptionMessageReportButton": "Problem melden", + "ErrorMessageInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche.", + "ExceptionMessageClipboardMessage": "Fehlertext wurde in die Zwischenablage kopiert.", + "SuccessGenericTitle": "Erfolg", + "ConfirmationMessageForClear": "Sind Sie sicher, dass Sie die Felder bereinigen möchten?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Backup-Liste erfolgreich importiert!", + "DuplicatedBackupNameMessage": "Ein Backup mit demselben Namen existiert bereits. Möchten Sie es überschreiben?", + "ExceptionMessageClipboardButton": "In Zwischenablage kopieren", + "ErrorMessageZippingSecurity": "Fehler beim Komprimieren der Dateien: Sicherheitsfehler.", + "InterruptBackupProcessMessage": "Sind Sie sicher, dass Sie dieses Backup abbrechen möchten?", + "BackupListCorrectlyExportedTitle": "Menü Exportieren", + "ConfirmationMessageForUnsavedChanges": "Es gibt nicht gespeicherte Änderungen. Möchten Sie sie speichern, bevor Sie zu einem anderen Backup wechseln?", + "ExceptionMessageTitle": "Fehler...", + "PdfNameMessageInput": "Geben Sie den Namen der PDF-Datei ein.", + "ErrorMessageNotSupportedEmail": "Ihr System unterstützt das Senden von E-Mails direkt aus dieser Anwendung nicht.", + "ErrorMessageForSavingFile": "Fehler beim Speichern der Datei", + "ErrorMessageUnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.", + "ConfirmationMessageBeforeDeleteBackup": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "ErrorMessageOpeningWebsite": "Die Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut.", + "ErrorSavingBackupMessage": "Fehler beim Speichern des Backups", + "ConfirmationRequiredTitle": "Bestätigung erforderlich", + "BackupNameAlreadyUsedMessage": "Backup-Name bereits verwendet!", + "ErrorMessageForWrongFileExtensionTitle": "Ungültige Datei", + "BackupSavedCorrectlyTitle": "Backup gespeichert" + }, + "User dialog": {}, + "TimePickerDialog": { + "TimeIntervalTitle": "Zeitintervall für Auto-Backup", + "Days": "Tage", + "Hours": "Stunden", + "SpinnerTooltip": "Mausrad zum Anpassen des Wertes", + "Description": "Wählen Sie, wie oft das automatische Backup durchgeführt werden soll, \nindem Sie die Frequenz in Tagen, Stunden und Minuten festlegen.", + "Minutes": "Minuten" + }, + "HISTORY_LOGS": {}, + "Constructor to assign both key and default value": {}, + "Subscription": { + "ExpiredTitle": "Backup Manager Abonnement abgelaufen", + "ExpiringMessage": "Ihr Backup Manager Abonnement läuft bald ab.\nAutomatische Backups werden bis zum Ablaufdatum weiterhin ausgeführt.\nBitte kontaktieren Sie den Support, um es zu verlängern.", + "ExpiringTitle": "Backup Manager Abonnement läuft bald ab", + "ExpiredMessage": "Ihr Backup Manager Abonnement ist abgelaufen.\nAutomatische Backups werden nicht mehr ausgeführt.\nBitte kontaktieren Sie den Support, um es zu reaktivieren." + }, + "ProgressBackupFrame": { + "StatusLoading": "Lädt...", + "ProgressBackupTitle": "Backup läuft", + "StatusCompleted": "Backup abgeschlossen!" + }, + "Lookup by keyName (JSON key)": {}, + "DASHBOARD": {}, + "ABOUT": {}, "General": { "AppName": "Backup Manager", + "CancelButton": "Abbrechen", "Backup": "Backup", "Version": "Version", - "From": "Von", - "To": "Nach", - "OkButton": "Ok", - "CancelButton": "Abbrechen", - "CloseButton": "Schließen", "ApplyButton": "Anwenden", + "OkButton": "Ok", + "From": "Von", "SaveButton": "Speichern", - "CreateButton": "Erstellen" + "CloseButton": "Schließen", + "To": "Nach" }, "Menu": { - "File": "Datei", - "Options": "Optionen", + "Import": "Backup-Liste importieren", + "Save": "Speichern", "About": "Über", - "Help": "Hilfe", + "File": "Datei", + "Support": "Support", "BugReport": "Fehler melden", - "Clear": "Löschen", - "Donate": "Spenden", - "History": "Verlauf", - "InfoPage": "Info", - "New": "Neu", "Quit": "Beenden", - "Save": "Speichern", - "SaveWithName": "Speichern unter", + "Options": "Optionen", + "New": "Neu", + "Clear": "Löschen", + "Share": "Teilen", "Preferences": "Einstellungen", - "Import": "Backup-Liste importieren", + "Website": "Webseite", + "SaveWithName": "Speichern unter", "Export": "Backup-Liste exportieren", - "Share": "Teilen", - "Support": "Support", - "Website": "Webseite" - }, - "TabbedFrames": { - "BackupEntry": "Backup-Eintrag", - "BackupList": "Backup-Liste" - }, - "BackupEntry": { - "PageTitle": "Backup-Eintrag", - "CurrentFile": "Aktuelle Datei", - "Notes": "Notizen", - "LastBackup": "Letztes Backup", - "SingleBackupButton": "Einzel-Backup", - "AutoBackupButton": "Auto-Backup", - "AutoBackupButtonON": "Auto-Backup (AN)", - "AutoBackupButtonOFF": "Auto-Backup (AUS)", - "InitialPathPlaceholder": "Anfangspfad", - "DestinationPathPlaceholder": "Zielpfad", - "BackupName": "Sicherungsname", - "BackupNameTooltip": "(Erforderlich) Sicherungsname", - "InitialPathTooltip": "(Erforderlich) Anfangspfad", - "DestinationPathTooltip": "(Erforderlich) Zielpfad", - "InitialFileChooserTooltip": "Dateiexplorer öffnen", - "DestinationFileChooserTooltip": "Dateiexplorer öffnen", - "NotesTooltip": "(Optional) Backup-Beschreibung", - "SingleBackupTooltip": "Backup durchführen", - "AutoBackupTooltip": "Automatisches Backup aktivieren/deaktivieren", - "TimePickerTooltip": "Zeitwähler", - "MaxBackupsToKeep": "Maximale Anzahl an Sicherungen beibehalten", - "MaxBackupsToKeepTooltip": "Maximale Anzahl an Sicherungen, bevor die ältesten entfernt werden." + "Help": "Hilfe", + "InfoPage": "Info", + "History": "Verlauf" }, + "Use fromKeyName to get the TKey from the JSON key": {}, + "Clear previous translations to avoid stale values when switching languages": {}, "BackupList": { - "BackupNameColumn": "Backup-Name", - "InitialPathColumn": "Anfangspfad", - "DestinationPathColumn": "Zielpfad", - "LastBackupColumn": "Letztes Backup", - "AutomaticBackupColumn": "Automatisches Backup", - "NextBackupDateColumn": "Nächstes Backup-Datum", - "TimeIntervalColumn": "Zeitintervall", + "NotesDetail": "Notizen", "BackupNameDetail": "Backup-Name", + "ExportAsPdfTooltip": "Exportieren als PDF", + "EditPopup": "Bearbeiten", + "ResearchBarTooltip": "Suchleiste", "InitialPathDetail": "Anfangspfad", - "DestinationPathDetail": "Zielpfad", - "LastBackupDetail": "LetztesBackup", + "ExportAs": "Exportieren als: ", + "RenameBackupPopup": "Backup umbenennen", "NextBackupDateDetail": "NächstesBackup", - "TimeIntervalDetail": "Zeitintervall", - "CreationDateDetail": "Erstellungsdatum", - "LastUpdateDateDetail": "LetzteAktualisierung", + "AutoBackupPopup": "Auto-Backup", + "BackupNameColumn": "Backup-Name", + "BackupPopup": "Backup", "BackupCountDetail": "Backup-Anzahl", - "NotesDetail": "Notizen", - "MaxBackupsToKeepDetail": "MaximaleSicherungenBehalten", - "AddBackupTooltip": "Neues Backup hinzufügen", - "ExportAs": "Exportieren als: ", - "ExportAsPdfTooltip": "Exportieren als PDF", "ExportAsCsvTooltip": "Exportieren als CSV", - "ResearchBarTooltip": "Suchleiste", - "ResearchBarPlaceholder": "Suchen...", - "EditPopup": "Bearbeiten", + "InitialPathColumn": "Anfangspfad", + "MaxBackupsToKeepDetail": "MaximaleSicherungenBehalten", "DeletePopup": "Löschen", - "InterruptPopup": "Unterbrechen", - "DuplicatePopup": "Duplizieren", - "RenameBackupPopup": "Backup umbenennen", - "OpenInitialFolderPopup": "Anfangspfad öffnen", "OpenDestinationFolderPopup": "Zielpfad öffnen", - "BackupPopup": "Backup", + "LastBackupColumn": "Letztes Backup", "SingleBackupPopup": "Einzel-Backup ausführen", - "AutoBackupPopup": "Auto-Backup", + "AddBackupTooltip": "Neues Backup hinzufügen", "CopyTextPopup": "Text kopieren", + "DestinationPathDetail": "Zielpfad", + "CopyDestinationPathPopup": "Zielpfad kopieren", + "LastBackupDetail": "LetztesBackup", + "OpenInitialFolderPopup": "Anfangspfad öffnen", + "ResearchBarPlaceholder": "Suchen...", + "InterruptPopup": "Unterbrechen", + "DuplicatePopup": "Duplizieren", + "NextBackupDateColumn": "Nächstes Backup-Datum", + "DestinationPathColumn": "Zielpfad", + "AutomaticBackupColumn": "Automatisches Backup", + "LastUpdateDateDetail": "LetzteAktualisierung", + "TimeIntervalDetail": "Zeitintervall", "CopyBackupNamePopup": "Backup-Name kopieren", - "CopyInitialPathPopup": "Anfangspfad kopieren", - "CopyDestinationPathPopup": "Zielpfad kopieren" + "CreationDateDetail": "Erstellungsdatum", + "CopyInitialPathPopup": "Anfangspfad kopieren" }, - "TimePickerDialog": { - "TimeIntervalTitle": "Zeitintervall für Auto-Backup", - "Description": "Wählen Sie, wie oft das automatische Backup durchgeführt werden soll, \nindem Sie die Frequenz in Tagen, Stunden und Minuten festlegen.", - "Days": "Tage", - "Hours": "Stunden", - "Minutes": "Minuten", - "SpinnerTooltip": "Mausrad zum Anpassen des Wertes" + "TabbedFrames": { + "BackupEntry": "Backup-Eintrag", + "BackupList": "Backup-Liste" }, "UserDialog": { - "UserTitle": "Geben Sie Ihre Daten ein", - "Name": "Vorname", - "Surname": "Nachname", - "Email": "E-Mail", "ErrorMessageForMissingData": "Bitte füllen Sie alle erforderlichen Felder aus.", - "ErrorMessageForWrongEmail": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte Adresse an.", + "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team", "EmailConfirmationSubject": "Vielen Dank, dass Sie sich für Backup Manager entschieden haben!", - "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team" - }, - "ProgressBackupFrame": { - "ProgressBackupTitle": "Backup läuft", - "StatusCompleted": "Backup abgeschlossen!", - "StatusLoading": "Lädt..." - }, - "TrayIcon": { - "TrayTooltip": "Backup-Dienst", - "OpenAction": "Schnellzugriff", - "ExitAction": "Beenden", - "SuccessMessage": "\nDas Backup wurde erfolgreich abgeschlossen:", - "ErrorMessageInputMissing": "\nFehler beim automatischen Backup.\nEingabe fehlt!", - "ErrorMessageFilesNotExisting": "\nFehler beim automatischen Backup.\nEin oder beide Pfade existieren nicht!", - "ErrorMessageSamePaths": "\nFehler beim automatischen Backup.\nDer Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!" - }, - "Dialogs": { - "ErrorGenericTitle": "Fehler", - "WarningGenericTitle": "Warnung", - "WarningBackupAlreadyInProgressMessage": "Es läuft bereits eine Sicherung. Es ist nicht möglich, parallele Sicherungen durchzuführen.", - "WarningShortTimeIntervalMessage": "Das ausgewählte Zeitintervall ist sehr kurz. Für eine optimale Leistung empfehlen wir, es auf mindestens eine Stunde einzustellen. Möchten Sie dennoch fortfahren?", - "ErrorMessageForFolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", - "ErrorMessageForSavingFileWithPathsEmpty": "Die Datei konnte nicht gespeichert werden. Sowohl der Anfangs- als auch der Zielpfad müssen angegeben werden und dürfen nicht leer sein", - "BackupSavedCorrectlyTitle": "Backup gespeichert", - "BackupSavedCorrectlyMessage": "erfolgreich gespeichert!", - "ErrorSavingBackupMessage": "Fehler beim Speichern des Backups", - "BackupNameInput": "Name des Backups", - "ConfirmationRequiredTitle": "Bestätigung erforderlich", - "DuplicatedBackupNameMessage": "Ein Backup mit demselben Namen existiert bereits. Möchten Sie es überschreiben?", - "BackupNameAlreadyUsedMessage": "Backup-Name bereits verwendet!", - "ErrorMessageForIncorrectInitialPath": "Fehler beim Backup-Vorgang: Der Anfangspfad ist falsch!", - "ExceptionMessageTitle": "Fehler...", - "ExceptionMessageClipboardMessage": "Fehlertext wurde in die Zwischenablage kopiert.", - "ExceptionMessageClipboardButton": "In Zwischenablage kopieren", - "ExceptionMessageReportButton": "Problem melden", - "ExceptionMessageReportMessage": "Bitte melden Sie diesen Fehler, entweder mit einem Screenshot oder indem Sie den folgenden Fehlertext kopieren (es ist hilfreich, eine Beschreibung der durchgeführten Aktionen vor dem Fehler anzugeben):", - "ErrorMessageOpeningWebsite": "Die Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut.", - "ConfirmationMessageForClear": "Sind Sie sicher, dass Sie die Felder bereinigen möchten?", - "ConfirmationMessageForUnsavedChanges": "Es gibt nicht gespeicherte Änderungen. Möchten Sie sie speichern, bevor Sie zu einem anderen Backup wechseln?", - "ErrorMessageOpenHistoryFile": "Fehler beim Öffnen der Verlaufsdatei.", - "ConfirmationMessageBeforeDeleteBackup": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "ShareLinkCopiedMessage": "Freigabelink wurde in die Zwischenablage kopiert!", - "ConfirmationMessageCancelAutoBackup": "Sind Sie sicher, dass Sie automatische Backups für diesen Eintrag deaktivieren möchten?", - "ErrorMessageUnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.", - "ErrorMessageNotSupportedEmail": "Ihr System unterstützt das Senden von E-Mails direkt aus dieser Anwendung nicht.", - "ErrorMessageNotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht.", - "ErrorWrongTimeInterval": "Das Zeitintervall ist nicht korrekt", - "AutoBackupActivatedMessage": "Auto-Backup wurde aktiviert", - "SettedEveryMessage": "\nWird eingestellt auf alle", - "DaysMessage": " Tage", - "InterruptBackupProcessMessage": "Sind Sie sicher, dass Sie dieses Backup abbrechen möchten?", - "ErrorMessageInputMissingGeneric": "Eingabe fehlt!", - "ErrorMessageForSavingFile": "Fehler beim Speichern der Datei", - "ErrorMessageForPathNotExisting": "Ein oder beide Pfade existieren nicht!", - "ErrorMessageForSamePaths": "Der Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!", - "BackupListCorrectlyExportedTitle": "Menü Exportieren", - "BackupListCorrectlyExportedMessage": "Backup-Liste erfolgreich auf den Desktop exportiert!", - "BackupListCorrectlyImportedTitle": "Menü Importieren", - "BackupListCorrectlyImportedMessage": "Backup-Liste erfolgreich importiert!", - "ErrorMessageForWrongFileExtensionTitle": "Ungültige Datei", - "ErrorMessageForWrongFileExtensionMessage": "Fehler: Bitte wählen Sie eine gültige JSON-Datei aus.", - "ErrorMessageCountingFiles": "Fehler beim Zählen der zu sichernden Dateien.", - "ErrorMessageZippingGeneric": "Fehler beim Komprimieren der Dateien.", - "ErrorMessageZippingIO": "Fehler beim Komprimieren der Dateien: E/A-Fehler.", - "ErrorMessageZippingSecurity": "Fehler beim Komprimieren der Dateien: Sicherheitsfehler.", - "SuccessGenericTitle": "Erfolg", - "SuccessfullyExportedToCsvMessage": "Backups erfolgreich als CSV exportiert!", - "SuccessfullyExportedToPdfMessage": "Backups erfolgreich als PDF exportiert!", - "ErrorMessageForExportingToCsv": "Fehler beim Exportieren der Backups in CSV: ", - "ErrorMessageForExportingToPdf": "Fehler beim Exportieren der Backups in PDF: ", - "CsvNameMessageInput": "Geben Sie den Namen der CSV-Datei ein.", - "PdfNameMessageInput": "Geben Sie den Namen der PDF-Datei ein.", - "DuplicatedFileNameMessage": "Datei existiert bereits. Überschreiben?", - "ErrorMessageInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche.", - "ConfirmationDeletionTitle": "Löschen bestätigen", - "ConfirmationDeletionMessage": "Sind Sie sicher, dass Sie die ausgewählten Zeilen löschen möchten?" + "Surname": "Nachname", + "ErrorMessageForWrongEmail": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte Adresse an.", + "Name": "Vorname", + "Email": "E-Mail", + "UserTitle": "Geben Sie Ihre Daten ein" }, - "Subscription": { - "ExpiringTitle": "Backup Manager Abonnement läuft bald ab", - "ExpiringMessage": "Ihr Backup Manager Abonnement läuft bald ab.\nAutomatische Backups werden bis zum Ablaufdatum weiterhin ausgeführt.\nBitte kontaktieren Sie den Support, um es zu verlängern.", - "ExpiredTitle": "Backup Manager Abonnement abgelaufen", - "ExpiredMessage": "Ihr Backup Manager Abonnement ist abgelaufen.\nAutomatische Backups werden nicht mehr ausgeführt.\nBitte kontaktieren Sie den Support, um es zu reaktivieren." + "SETTINGS": {}, + "BackupEntry": { + "TimePickerTooltip": "Zeitwähler", + "MaxBackupsToKeepTooltip": "Maximale Anzahl an Sicherungen, bevor die ältesten entfernt werden.", + "BackupName": "Sicherungsname", + "AutoBackupButton": "Auto-Backup", + "InitialPathTooltip": "(Erforderlich) Anfangspfad", + "CurrentFile": "Aktuelle Datei", + "InitialFileChooserTooltip": "Dateiexplorer öffnen", + "LastBackup": "Letztes Backup", + "SingleBackupButton": "Einzel-Backup", + "AutoBackupButtonON": "Auto-Backup (AN)", + "AutoBackupButtonOFF": "Auto-Backup (AUS)", + "DestinationPathTooltip": "(Erforderlich) Zielpfad", + "MaxBackupsToKeep": "Maximale Anzahl an Sicherungen beibehalten", + "SingleBackupTooltip": "Backup durchführen", + "AutoBackupTooltip": "Automatisches Backup aktivieren/deaktivieren", + "NotesTooltip": "(Optional) Backup-Beschreibung", + "DestinationFileChooserTooltip": "Dateiexplorer öffnen", + "BackupNameTooltip": "(Erforderlich) Sicherungsname", + "PageTitle": "Backup-Eintrag", + "Notes": "Notizen" } } diff --git a/src/main/resources/res/languages/eng.json b/src/main/resources/res/languages/eng.json index 84db8699..92761d2d 100644 --- a/src/main/resources/res/languages/eng.json +++ b/src/main/resources/res/languages/eng.json @@ -1,203 +1,214 @@ { + "TrayIcon": { + "SuccessMessage": "\nThe backup was successfully completed:", + "ExitAction": "Exit", + "ErrorMessageFilesNotExisting": "\nError during automatic backup.\nOne or both paths do not exist!", + "TrayTooltip": "Backup Service", + "OpenAction": "Quick Access", + "ErrorMessageInputMissing": "\nError during automatic backup.\nInput Missing!", + "ErrorMessageSamePaths": "\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!" + }, + "If value is null or empty, fall back to the default value from the enum": {}, + "Dialogs": { + "WarningGenericTitle": "Warning", + "SuccessfullyExportedToCsvMessage": "Backups exported to CSV successfully!", + "ErrorMessageForPathNotExisting": "One or both paths do not exist!", + "ConfirmationDeletionMessage": "Are you sure you want to delete the selected rows?", + "ExceptionMessageReportMessage": "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Error exporting backups to CSV: ", + "ErrorGenericTitle": "Error", + "ErrorMessageForExportingToPdf": "Error exporting backups to PDF: ", + "BackupListCorrectlyImportedTitle": "Menu Import", + "ErrorWrongTimeInterval": "The time interval is not correct", + "ErrorMessageOpenHistoryFile": "Error opening history file.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "The initial path and destination path cannot be the same. Please choose different paths!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Share link copied to clipboard!", + "ErrorMessageForSavingFileWithPathsEmpty": "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty", + "BackupListCorrectlyExportedMessage": "Backup list successfully exported to the Desktop!", + "BackupSavedCorrectlyMessage": "saved successfully!", + "SettedEveryMessage": "\nIs setted every", + "WarningBackupAlreadyInProgressMessage": "There is already a backup in progress. It is not possible to perform parallel backups", + "WarningShortTimeIntervalMessage": "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?", + "ConfirmationMessageCancelAutoBackup": "Are you sure you want to cancel automatic backups for this entry?", + "ErrorMessageCountingFiles": "Error occurred while calculating files to back up.", + "ErrorMessageForFolderNotExisting": "The folder does not exist or is invalid", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Error during the backup operation: the initial path is incorrect!", + "ErrorMessageInputMissingGeneric": "Input Missing!", + "BackupNameInput": "Name of the backup", + "CsvNameMessageInput": "Enter the name of the CSV file.", + "ConfirmationDeletionTitle": "Confirm Deletion", + "ErrorMessageForWrongFileExtensionMessage": "Error: Please select a valid JSON file.", + "ErrorMessageZippingGeneric": "Error occurred while zipping files.", + "ErrorMessageNotSupportedEmailGeneric": "Your system does not support sending emails.", + "ErrorMessageZippingIO": "Error occurred while zipping files: I/O error.", + "SuccessfullyExportedToPdfMessage": "Backups exported to PDF successfully!", + "DuplicatedFileNameMessage": "File already exists. Overwrite?", + "AutoBackupActivatedMessage": "Auto Backup has been activated", + "DaysMessage": " days", + "ExceptionMessageReportButton": "Report the Problem", + "ErrorMessageInvalidFilename": "Invalid file name. Use only alphanumeric characters, dashes, and underscores.", + "ExceptionMessageClipboardMessage": "Error text has been copied to the clipboard.", + "SuccessGenericTitle": "Success", + "ConfirmationMessageForClear": "Are you sure you want to clean the fields?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Backup list successfully imported!", + "DuplicatedBackupNameMessage": "A backup with the same name already exists, do you want to overwrite it?", + "ExceptionMessageClipboardButton": "Copy to clipboard", + "ErrorMessageZippingSecurity": "Error occurred while zipping files: Security error.", + "InterruptBackupProcessMessage": "Are you sure you want to stop this backup?", + "BackupListCorrectlyExportedTitle": "Menu Export", + "ConfirmationMessageForUnsavedChanges": "There are unsaved changes, do you want to save them before moving to another backup?", + "ExceptionMessageTitle": "Error...", + "PdfNameMessageInput": "Enter the name of the PDF file.", + "ErrorMessageNotSupportedEmail": "Your system does not support sending emails directly from this application.", + "ErrorMessageForSavingFile": "Error saving file", + "ErrorMessageUnableToSendEmail": "Unable to send email. Please try again later.", + "ConfirmationMessageBeforeDeleteBackup": "Are you sure you want to delete this item? Please note, this action cannot be undone", + "ErrorMessageOpeningWebsite": "Failed to open the web page. Please try again.", + "ErrorSavingBackupMessage": "Error saving backup", + "ConfirmationRequiredTitle": "Confirmation required", + "BackupNameAlreadyUsedMessage": "Backup name already used!", + "ErrorMessageForWrongFileExtensionTitle": "Invalid File", + "BackupSavedCorrectlyTitle": "Backup saved" + }, + "User dialog": {}, + "TimePickerDialog": { + "TimeIntervalTitle": "Time interval for auto backup", + "Days": "Days", + "Hours": "Hours", + "SpinnerTooltip": "Mouse wheel to adjust the value", + "Description": "Select how often to perform the automatic backup by \nchoosing the frequency in days, hours, and minutes.", + "Minutes": "Minutes" + }, + "HISTORY_LOGS": {}, + "Constructor to assign both key and default value": {}, + "Subscription": { + "ExpiredTitle": "Backup Manager subscription expired", + "ExpiringMessage": "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it.", + "ExpiringTitle": "Backup Manager subscription expiring soon", + "ExpiredMessage": "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it." + }, + "ProgressBackupFrame": { + "StatusLoading": "Loading...", + "ProgressBackupTitle": "Backup in progress", + "StatusCompleted": "Backup completed!" + }, + "Lookup by keyName (JSON key)": {}, + "DASHBOARD": {}, + "ABOUT": {}, "General": { "AppName": "Backup Manager", + "CancelButton": "Cancel", "Backup": "Backup", "Version": "Version", - "From": "From", - "To": "A", + "ApplyButton": "Apply", "OkButton": "Ok", - "CancelButton": "Cancel", - "CloseButton":"Close", - "ApplyButton":"Apply", + "From": "From", "SaveButton": "Save", - "CreateButton": "Create" + "CloseButton": "Close", + "To": "A" }, "Menu": { - "File": "File", - "Options": "Options", + "Import": "Import backup list", + "Save": "Save", "About": "About", - "Help": "Help", + "File": "File", + "Support": "Support", "BugReport": "Report a bug", - "Clear": "Clear", - "Donate": "Donate", - "History": "History", - "InfoPage": "Info", - "New": "New", "Quit": "Quit", - "Save": "Save", - "SaveWithName": "Save with name", + "Options": "Options", + "New": "New", + "Clear": "Clear", + "Share": "Share", "Preferences": "Preferences", - "Import": "Import backup list", + "Website": "Website", + "SaveWithName": "Save with name", "Export": "Export backup list", - "Share": "Share", - "Support": "Support", - "Website": "Website" - }, - "TabbedFrames": { - "BackupEntry": "BackupEntry", - "BackupList": "BackupList" - }, - "BackupEntry": { - "PageTitle": "Backup Entry", - "CurrentFile": "Current file", - "Notes": "Notes", - "LastBackup": "Last backup", - "SingleBackupButton": "Single Backup", - "AutoBackupButton": "Auto Backup", - "AutoBackupButtonON": "Auto Backup (ON)", - "AutoBackupButtonOFF": "Auto Backup (OFF)", - "InitialPathPlaceholder": "Initial path", - "DestinationPathPlaceholder": "Destination path", - "BackupName": "Backup name", - "BackupNameTooltip": "(Required) Backup name", - "InitialPathTooltip": "(Required) Initial path", - "DestinationPathTooltip": "(Required) Destination path", - "InitialFileChooserTooltip": "Open file explorer", - "DestinationFileChooserTooltip": "Open file explorer", - "NotesTooltip": "(Optional) Backup description", - "SingleBackupTooltip": "Perform the backup", - "AutoBackupTooltip": "Enable/Disable automatic backup", - "TimePickerTooltip": "Time picker", - "MaxBackupsToKeep": "Max backups to keep", - "MaxBackupsToKeepTooltip": "Maximum number of backups before removing the oldest." + "Help": "Help", + "InfoPage": "Info", + "History": "History" }, + "Use fromKeyName to get the TKey from the JSON key": {}, + "Clear previous translations to avoid stale values when switching languages": {}, "BackupList": { - "BackupNameColumn": "Backup Name", - "InitialPathColumn": "Initial Path", - "DestinationPathColumn": "Destination Path", - "LastBackupColumn": "Last Backup", - "AutomaticBackupColumn": "Automatic Backup", - "NextBackupDateColumn": "Next Backup Date", - "TimeIntervalColumn": "Time Interval", + "NotesDetail": "Notes", "BackupNameDetail": "BackupName", + "ExportAsPdfTooltip": "Export as PDF", + "EditPopup": "Edit", + "ResearchBarTooltip": "Research bar", "InitialPathDetail": "InitialPath", - "DestinationPathDetail": "DestinationPath", - "LastBackupDetail": "LastBackup", + "ExportAs": "Export as: ", + "RenameBackupPopup": "Rename backup", "NextBackupDateDetail": "NextBackup", - "TimeIntervalDetail": "TimeInterval", - "CreationDateDetail": "CreationDate", - "LastUpdateDateDetail": "LastUpdateDate", + "AutoBackupPopup": "Auto backup", + "BackupNameColumn": "Backup Name", + "BackupPopup": "Backup", "BackupCountDetail": "BackupCount", - "NotesDetail": "Notes", - "MaxBackupsToKeepDetail": "MaxBackupsToKeep", - "AddBackupTooltip": "Add new backup", - "ExportAs": "Export as: ", - "ExportAsPdfTooltip": "Export as PDF", "ExportAsCsvTooltip": "Export as CSV", - "ResearchBarTooltip": "Research bar", - "ResearchBarPlaceholder": "Search...", - "EditPopup": "Edit", + "InitialPathColumn": "Initial Path", + "MaxBackupsToKeepDetail": "MaxBackupsToKeep", "DeletePopup": "Delete", - "InterruptPopup": "Interrupt", - "DuplicatePopup": "Duplicate", - "RenameBackupPopup": "Rename backup", - "OpenInitialFolderPopup": "Open initial path", "OpenDestinationFolderPopup": "Open destination path", - "BackupPopup": "Backup", + "LastBackupColumn": "Last Backup", "SingleBackupPopup": "Run single backup", - "AutoBackupPopup": "Auto backup", + "AddBackupTooltip": "Add new backup", "CopyTextPopup": "Copy text", + "DestinationPathDetail": "DestinationPath", + "CopyDestinationPathPopup": "Copy destination path", + "LastBackupDetail": "LastBackup", + "OpenInitialFolderPopup": "Open initial path", + "ResearchBarPlaceholder": "Search...", + "InterruptPopup": "Interrupt", + "DuplicatePopup": "Duplicate", + "NextBackupDateColumn": "Next Backup Date", + "DestinationPathColumn": "Destination Path", + "AutomaticBackupColumn": "Automatic Backup", + "LastUpdateDateDetail": "LastUpdateDate", + "TimeIntervalDetail": "TimeInterval", "CopyBackupNamePopup": "Copy backup name", - "CopyInitialPathPopup": "Copy initial path", - "CopyDestinationPathPopup": "Copy destination path" + "CreationDateDetail": "CreationDate", + "CopyInitialPathPopup": "Copy initial path" }, - "TimePickerDialog": { - "TimeIntervalTitle": "Time interval for auto backup", - "Description": "Select how often to perform the automatic backup by \nchoosing the frequency in days, hours, and minutes.", - "Days": "Days", - "Hours": "Hours", - "Minutes": "Minutes", - "SpinnerTooltip": "Mouse wheel to adjust the value" + "TabbedFrames": { + "BackupEntry": "BackupEntry", + "BackupList": "BackupList" }, "UserDialog": { - "UserTitle": "Insert your data", - "Name": "Name", - "Surname": "Surname", - "Email": "Email", "ErrorMessageForMissingData": "Please fill in all the required fields.", - "ErrorMessageForWrongEmail": "The provided email address is invalid. Please provide a correct one.", + "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team", "EmailConfirmationSubject": "Thank you for choosing Backup Manager!", - "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team" - }, - "ProgressBackupFrame": { - "ProgressBackupTitle":"Backup in progress", - "StatusCompleted":"Backup completed!", - "StatusLoading":"Loading..." - }, - "TrayIcon": { - "TrayTooltip":"Backup Service", - "OpenAction": "Quick Access", - "ExitAction": "Exit", - "SuccessMessage":"\nThe backup was successfully completed:", - "ErrorMessageInputMissing":"\nError during automatic backup.\nInput Missing!", - "ErrorMessageFilesNotExisting":"\nError during automatic backup.\nOne or both paths do not exist!", - "ErrorMessageSamePaths":"\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!" - }, - "Dialogs": { - "ErrorGenericTitle":"Error", - "WarningGenericTitle": "Warning", - "WarningBackupAlreadyInProgressMessage": "There is already a backup in progress. It is not possible to perform parallel backups", - "WarningShortTimeIntervalMessage": "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?", - "ErrorMessageForFolderNotExisting":"The folder does not exist or is invalid", - "ErrorMessageForSavingFileWithPathsEmpty":"Unable to save the file. Both the initial and destination paths must be specified and cannot be empty", - "BackupSavedCorrectlyTitle":"Backup saved", - "BackupSavedCorrectlyMessage":"saved successfully!", - "ErrorSavingBackupMessage":"Error saving backup", - "BackupNameInput":"Name of the backup", - "ConfirmationRequiredTitle":"Confirmation required", - "DuplicatedBackupNameMessage":"A backup with the same name already exists, do you want to overwrite it?", - "BackupNameAlreadyUsedMessage":"Backup name already used!", - "ErrorMessageForIncorrectInitialPath":"Error during the backup operation: the initial path is incorrect!", - "ExceptionMessageTitle":"Error...", - "ExceptionMessageClipboardMessage":"Error text has been copied to the clipboard.", - "ExceptionMessageClipboardButton":"Copy to clipboard", - "ExceptionMessageReportButton":"Report the Problem", - "ExceptionMessageReportMessage":"Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):", - "ErrorMessageOpeningWebsite":"Failed to open the web page. Please try again.", - "ConfirmationMessageForClear":"Are you sure you want to clean the fields?", - "ConfirmationMessageForUnsavedChanges":"There are unsaved changes, do you want to save them before moving to another backup?", - "ErrorMessageOpenHistoryFile":"Error opening history file.", - "ConfirmationMessageBeforeDeleteBackup":"Are you sure you want to delete this item? Please note, this action cannot be undone", - "ShareLinkCopiedMessage":"Share link copied to clipboard!", - "ConfirmationMessageCancelAutoBackup":"Are you sure you want to cancel automatic backups for this entry?", - "ErrorMessageUnableToSendEmail":"Unable to send email. Please try again later.", - "ErrorMessageNotSupportedEmail":"Your system does not support sending emails directly from this application.", - "ErrorMessageNotSupportedEmailGeneric":"Your system does not support sending emails.", - "ErrorWrongTimeInterval":"The time interval is not correct", - "AutoBackupActivatedMessage":"Auto Backup has been activated", - "SettedEveryMessage":"\nIs setted every", - "DaysMessage":" days", - "InterruptBackupProcessMessage":"Are you sure you want to stop this backup?", - "ErrorMessageInputMissingGeneric": "Input Missing!", - "ErrorMessageForSavingFile": "Error saving file", - "ErrorMessageForPathNotExisting": "One or both paths do not exist!", - "BackupListCorrectlyExportedTitle": "Menu Export", - "BackupListCorrectlyExportedMessage": "Backup list successfully exported to the Desktop!", - "BackupListCorrectlyImportedTitle": "Menu Import", - "BackupListCorrectlyImportedMessage": "Backup list successfully imported!", - "ErrorMessageForSamePaths": "The initial path and destination path cannot be the same. Please choose different paths!", - "ErrorMessageForWrongFileExtensionTitle": "Invalid File", - "ErrorMessageForWrongFileExtensionMessage": "Error: Please select a valid JSON file.", - "ErrorMessageCountingFiles": "Error occurred while calculating files to back up.", - "ErrorMessageZippingGeneric": "Error occurred while zipping files.", - "ErrorMessageZippingIO": "Error occurred while zipping files: I/O error.", - "ErrorMessageZippingSecurity": "Error occurred while zipping files: Security error.", - "SuccessGenericTitle":"Success", - "SuccessfullyExportedToCsvMessage":"Backups exported to CSV successfully!", - "SuccessfullyExportedToPdfMessage":"Backups exported to PDF successfully!", - "ErrorMessageForExportingToCsv":"Error exporting backups to CSV: ", - "ErrorMessageForExportingToPdf":"Error exporting backups to PDF: ", - "CsvNameMessageInput":"Enter the name of the CSV file.", - "PdfNameMessageInput":"Enter the name of the PDF file.", - "DuplicatedFileNameMessage":"File already exists. Overwrite?", - "ErrorMessageInvalidFilename":"Invalid file name. Use only alphanumeric characters, dashes, and underscores.", - "ConfirmationDeletionTitle":"Confirm Deletion", - "ConfirmationDeletionMessage":"Are you sure you want to delete the selected rows?" + "Surname": "Surname", + "ErrorMessageForWrongEmail": "The provided email address is invalid. Please provide a correct one.", + "Name": "Name", + "Email": "Email", + "UserTitle": "Insert your data" }, - "Subscription": { - "ExpiringTitle": "Backup Manager subscription expiring soon", - "ExpiringMessage": "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it.", - "ExpiredTitle": "Backup Manager subscription expired", - "ExpiredMessage": "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it." + "SETTINGS": {}, + "BackupEntry": { + "TimePickerTooltip": "Time picker", + "MaxBackupsToKeepTooltip": "Maximum number of backups before removing the oldest.", + "BackupName": "Backup name", + "AutoBackupButton": "Auto Backup", + "InitialPathTooltip": "(Required) Initial path", + "CurrentFile": "Current file", + "InitialFileChooserTooltip": "Open file explorer", + "LastBackup": "Last backup", + "SingleBackupButton": "Single Backup", + "AutoBackupButtonON": "Auto Backup (ON)", + "AutoBackupButtonOFF": "Auto Backup (OFF)", + "DestinationPathTooltip": "(Required) Destination path", + "MaxBackupsToKeep": "Max backups to keep", + "SingleBackupTooltip": "Perform the backup", + "AutoBackupTooltip": "Enable/Disable automatic backup", + "NotesTooltip": "(Optional) Backup description", + "DestinationFileChooserTooltip": "Open file explorer", + "BackupNameTooltip": "(Required) Backup name", + "PageTitle": "Backup Entry", + "Notes": "Notes" } } diff --git a/src/main/resources/res/languages/esp.json b/src/main/resources/res/languages/esp.json index 758b9f8c..72a4b91e 100644 --- a/src/main/resources/res/languages/esp.json +++ b/src/main/resources/res/languages/esp.json @@ -1,203 +1,214 @@ { + "TrayIcon": { + "SuccessMessage": "\nLa copia de seguridad se completó con éxito:", + "ExitAction": "Salir", + "ErrorMessageFilesNotExisting": "\nError en la copia automática.\n¡Una o ambas rutas no existen!", + "TrayTooltip": "Servicio de Copias de Seguridad", + "OpenAction": "Acceso Rápido", + "ErrorMessageInputMissing": "\nError en la copia automática.\n¡Faltan datos de entrada!", + "ErrorMessageSamePaths": "\nError en la copia automática.\nLa ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!" + }, + "If value is null or empty, fall back to the default value from the enum": {}, + "Dialogs": { + "WarningGenericTitle": "Advertencia", + "SuccessfullyExportedToCsvMessage": "¡Copias de seguridad exportadas a CSV con éxito!", + "ErrorMessageForPathNotExisting": "¡Una o ambas rutas no existen!", + "ConfirmationDeletionMessage": "¿Está seguro de que desea eliminar las filas seleccionadas?", + "ExceptionMessageReportMessage": "Por favor, informe de este error, ya sea con una captura de pantalla o copiando el siguiente texto del error (se agradece proporcionar una descripción de las acciones realizadas antes del error):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Error al exportar copias de seguridad a CSV: ", + "ErrorGenericTitle": "Error", + "ErrorMessageForExportingToPdf": "Error al exportar copias de seguridad a PDF: ", + "BackupListCorrectlyImportedTitle": "Menú Importar", + "ErrorWrongTimeInterval": "El intervalo de tiempo no es correcto", + "ErrorMessageOpenHistoryFile": "Error al abrir el archivo de historial.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "La ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "¡Enlace de compartir copiado al portapapeles!", + "ErrorMessageForSavingFileWithPathsEmpty": "No se puede guardar el archivo. Tanto la ruta inicial como la de destino deben especificarse y no pueden estar vacías", + "BackupListCorrectlyExportedMessage": "¡Lista de copias de seguridad exportada correctamente al escritorio!", + "BackupSavedCorrectlyMessage": "guardada con éxito.", + "SettedEveryMessage": "\nSe establece cada", + "WarningBackupAlreadyInProgressMessage": "Ya hay una copia de seguridad en progreso. No es posible realizar copias de seguridad en paralelo.", + "WarningShortTimeIntervalMessage": "El intervalo de tiempo seleccionado es muy corto. Para un funcionamiento óptimo, recomendamos configurarlo en al menos una hora. ¿Quieres continuar de todos modos?", + "ConfirmationMessageCancelAutoBackup": "¿Está seguro de que desea cancelar las copias automáticas para esta entrada?", + "ErrorMessageCountingFiles": "Error al calcular los archivos para la copia de seguridad.", + "ErrorMessageForFolderNotExisting": "La carpeta no existe o no es válida", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Error en la operación de copia: ¡la ruta inicial es incorrecta!", + "ErrorMessageInputMissingGeneric": "¡Faltan datos de entrada!", + "BackupNameInput": "Nombre de la copia", + "CsvNameMessageInput": "Introduce el nombre del archivo CSV.", + "ConfirmationDeletionTitle": "Confirmar Eliminación", + "ErrorMessageForWrongFileExtensionMessage": "Error: Seleccione un archivo JSON válido.", + "ErrorMessageZippingGeneric": "Error al comprimir los archivos.", + "ErrorMessageNotSupportedEmailGeneric": "Su sistema no admite el envío de correos electrónicos.", + "ErrorMessageZippingIO": "Error al comprimir los archivos: error de E/S.", + "SuccessfullyExportedToPdfMessage": "¡Copias de seguridad exportadas a PDF con éxito!", + "DuplicatedFileNameMessage": "El archivo ya existe. ¿Sobrescribir?", + "AutoBackupActivatedMessage": "La copia automática se ha activado", + "DaysMessage": " días", + "ExceptionMessageReportButton": "Informar del problema", + "ErrorMessageInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos.", + "ExceptionMessageClipboardMessage": "El texto del error se ha copiado al portapapeles.", + "SuccessGenericTitle": "Éxito", + "ConfirmationMessageForClear": "¿Está seguro de que desea limpiar los campos?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "¡Lista de copias de seguridad importada correctamente!", + "DuplicatedBackupNameMessage": "Ya existe una copia con el mismo nombre. ¿Desea sobrescribirla?", + "ExceptionMessageClipboardButton": "Copiar al portapapeles", + "ErrorMessageZippingSecurity": "Error al comprimir los archivos: Error de seguridad.", + "InterruptBackupProcessMessage": "¿Está seguro de que desea detener esta copia?", + "BackupListCorrectlyExportedTitle": "Menú Exportar", + "ConfirmationMessageForUnsavedChanges": "Hay cambios no guardados. ¿Desea guardarlos antes de pasar a otra copia de seguridad?", + "ExceptionMessageTitle": "Error...", + "PdfNameMessageInput": "Introduce el nombre del archivo PDF.", + "ErrorMessageNotSupportedEmail": "Su sistema no admite el envío de correos electrónicos directamente desde esta aplicación.", + "ErrorMessageForSavingFile": "Error al guardar el archivo", + "ErrorMessageUnableToSendEmail": "No se pudo enviar el correo electrónico. Por favor, intente de nuevo más tarde.", + "ConfirmationMessageBeforeDeleteBackup": "¿Está seguro de que desea eliminar este elemento? Tenga en cuenta que esta acción no se puede deshacer.", + "ErrorMessageOpeningWebsite": "No se pudo abrir la página web. Por favor, intente de nuevo.", + "ErrorSavingBackupMessage": "Error al guardar la copia de seguridad", + "ConfirmationRequiredTitle": "Confirmación requerida", + "BackupNameAlreadyUsedMessage": "¡Nombre de copia ya en uso!", + "ErrorMessageForWrongFileExtensionTitle": "Archivo no válido", + "BackupSavedCorrectlyTitle": "Copia Guardada" + }, + "User dialog": {}, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervalo de tiempo para copias automáticas", + "Days": "Días", + "Hours": "Horas", + "SpinnerTooltip": "Rueda del ratón para ajustar el valor", + "Description": "Seleccione la frecuencia para realizar la copia automática \neligiendo los días, horas y minutos.", + "Minutes": "Minutos" + }, + "HISTORY_LOGS": {}, + "Constructor to assign both key and default value": {}, + "Subscription": { + "ExpiredTitle": "La suscripción de Backup Manager ha expirado", + "ExpiringMessage": "Tu suscripción a Backup Manager está a punto de expirar.\nLas copias de seguridad automáticas seguirán funcionando hasta la fecha de vencimiento.\nContacta con el soporte para renovarla.", + "ExpiringTitle": "La suscripción de Backup Manager está por vencer", + "ExpiredMessage": "Tu suscripción a Backup Manager ha expirado.\nLas copias de seguridad automáticas ya no se ejecutarán.\nContacta con el soporte para reactivarla." + }, + "ProgressBackupFrame": { + "StatusLoading": "Cargando...", + "ProgressBackupTitle": "Copia de Seguridad en Progreso", + "StatusCompleted": "¡Copia completada!" + }, + "Lookup by keyName (JSON key)": {}, + "DASHBOARD": {}, + "ABOUT": {}, "General": { "AppName": "Backup Manager", + "CancelButton": "Cancelar", "Backup": "Copia de Seguridad", "Version": "Versión", - "From": "Desde", - "To": "A", - "OkButton": "Aceptar", - "CancelButton": "Cancelar", - "CloseButton": "Cerrar", "ApplyButton": "Aplicar", + "OkButton": "Aceptar", + "From": "Desde", "SaveButton": "Guardar", - "CreateButton": "Crear" + "CloseButton": "Cerrar", + "To": "A" }, "Menu": { - "File": "Archivo", - "Options": "Opciones", + "Import": "Importar lista de copias de seguridad", + "Save": "Guardar", "About": "Acerca de", - "Help": "Ayuda", + "File": "Archivo", + "Support": "Soporte", "BugReport": "Reportar un error", - "Clear": "Limpiar", - "Donate": "Donar", - "History": "Historial", - "InfoPage": "Información", - "New": "Nuevo", "Quit": "Salir", - "Save": "Guardar", - "SaveWithName": "Guardar con nombre", + "Options": "Opciones", + "New": "Nuevo", + "Clear": "Limpiar", + "Share": "Compartir", "Preferences": "Preferencias", - "Import": "Importar lista de copias de seguridad", + "Website": "Sitio web", + "SaveWithName": "Guardar con nombre", "Export": "Exportar lista de copias de seguridad", - "Share": "Compartir", - "Support": "Soporte", - "Website": "Sitio web" - }, - "TabbedFrames": { - "BackupEntry": "Entrada de Copia de Seguridad", - "BackupList": "Lista de Copias de Seguridad" - }, - "BackupEntry": { - "PageTitle": "Entrada de Copia de Seguridad", - "CurrentFile": "Archivo actual", - "Notes": "Notas", - "LastBackup": "Última copia de seguridad", - "SingleBackupButton": "Copia de Seguridad Única", - "AutoBackupButton": "Copia de Seguridad Automática", - "AutoBackupButtonON": "Copia de Seguridad Automática (ACTIVADA)", - "AutoBackupButtonOFF": "Copia de Seguridad Automática (DESACTIVADA)", - "InitialPathPlaceholder": "Ruta inicial", - "DestinationPathPlaceholder": "Ruta de destino", - "BackupName": "Nombre de la copia de seguridad", - "BackupNameTooltip": "(Requerido) Nombre de la copia de seguridad", - "InitialPathTooltip": "(Requerido) Ruta inicial", - "DestinationPathTooltip": "(Requerido) Ruta de destino", - "InitialFileChooserTooltip": "Abrir explorador de archivos", - "DestinationFileChooserTooltip": "Abrir explorador de archivos", - "NotesTooltip": "(Opcional) Descripción de la copia de seguridad", - "SingleBackupTooltip": "Realizar la copia de seguridad", - "AutoBackupTooltip": "Activar/Desactivar copia de seguridad automática", - "TimePickerTooltip": "Selector de tiempo", - "MaxBackupsToKeep": "Máximo de copias de seguridad a mantener", - "MaxBackupsToKeepTooltip": "Número máximo de copias de seguridad antes de eliminar las más antiguas." + "Help": "Ayuda", + "InfoPage": "Información", + "History": "Historial" }, + "Use fromKeyName to get the TKey from the JSON key": {}, + "Clear previous translations to avoid stale values when switching languages": {}, "BackupList": { - "BackupNameColumn": "Nombre de la Copia de Seguridad", - "InitialPathColumn": "Ruta Inicial", - "DestinationPathColumn": "Ruta de Destino", - "LastBackupColumn": "Última Copia de Seguridad", - "AutomaticBackupColumn": "Copia Automática", - "NextBackupDateColumn": "Próxima Fecha de Copia", - "TimeIntervalColumn": "Intervalo de Tiempo", + "NotesDetail": "Notas", "BackupNameDetail": "NombreCopia", + "ExportAsPdfTooltip": "Exportar como PDF", + "EditPopup": "Editar", + "ResearchBarTooltip": "Barra de búsqueda", "InitialPathDetail": "RutaInicial", - "DestinationPathDetail": "RutaDestino", - "LastBackupDetail": "ÚltimaCopia", + "ExportAs": "Exportar como: ", + "RenameBackupPopup": "Renombrar copia de seguridad", "NextBackupDateDetail": "PróximaFecha", - "TimeIntervalDetail": "IntervaloTiempo", - "CreationDateDetail": "FechaCreación", - "LastUpdateDateDetail": "ÚltimaActualización", + "AutoBackupPopup": "Copia automática", + "BackupNameColumn": "Nombre de la Copia de Seguridad", + "BackupPopup": "Copia de seguridad", "BackupCountDetail": "NúmeroCopias", - "NotesDetail": "Notas", - "MaxBackupsToKeepDetail": "MaximoCopiasDeSeguridadMantener", - "AddBackupTooltip": "Agregar nueva copia de seguridad", - "ExportAs": "Exportar como: ", - "ExportAsPdfTooltip": "Exportar como PDF", "ExportAsCsvTooltip": "Exportar como CSV", - "ResearchBarTooltip": "Barra de búsqueda", - "ResearchBarPlaceholder": "Buscar...", - "EditPopup": "Editar", + "InitialPathColumn": "Ruta Inicial", + "MaxBackupsToKeepDetail": "MaximoCopiasDeSeguridadMantener", "DeletePopup": "Eliminar", - "InterruptPopup": "Interrumpir", - "DuplicatePopup": "Duplicar", - "RenameBackupPopup": "Renombrar copia de seguridad", - "OpenInitialFolderPopup": "Abrir ruta inicial", "OpenDestinationFolderPopup": "Abrir ruta de destino", - "BackupPopup": "Copia de seguridad", + "LastBackupColumn": "Última Copia de Seguridad", "SingleBackupPopup": "Ejecutar copia única", - "AutoBackupPopup": "Copia automática", + "AddBackupTooltip": "Agregar nueva copia de seguridad", "CopyTextPopup": "Copiar texto", + "DestinationPathDetail": "RutaDestino", + "CopyDestinationPathPopup": "Copiar ruta de destino", + "LastBackupDetail": "ÚltimaCopia", + "OpenInitialFolderPopup": "Abrir ruta inicial", + "ResearchBarPlaceholder": "Buscar...", + "InterruptPopup": "Interrumpir", + "DuplicatePopup": "Duplicar", + "NextBackupDateColumn": "Próxima Fecha de Copia", + "DestinationPathColumn": "Ruta de Destino", + "AutomaticBackupColumn": "Copia Automática", + "LastUpdateDateDetail": "ÚltimaActualización", + "TimeIntervalDetail": "IntervaloTiempo", "CopyBackupNamePopup": "Copiar nombre de copia", - "CopyInitialPathPopup": "Copiar ruta inicial", - "CopyDestinationPathPopup": "Copiar ruta de destino" + "CreationDateDetail": "FechaCreación", + "CopyInitialPathPopup": "Copiar ruta inicial" }, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervalo de tiempo para copias automáticas", - "Description": "Seleccione la frecuencia para realizar la copia automática \neligiendo los días, horas y minutos.", - "Days": "Días", - "Hours": "Horas", - "Minutes": "Minutos", - "SpinnerTooltip": "Rueda del ratón para ajustar el valor" + "TabbedFrames": { + "BackupEntry": "Entrada de Copia de Seguridad", + "BackupList": "Lista de Copias de Seguridad" }, "UserDialog": { - "UserTitle": "Inserta tus datos", - "Name": "Nombre", - "Surname": "Apellido", - "Email": "Correo electrónico", "ErrorMessageForMissingData": "Por favor, completa todos los campos requeridos.", - "ErrorMessageForWrongEmail": "La dirección de correo electrónico proporcionada no es válida. Por favor, proporciona una correcta.", + "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager", "EmailConfirmationSubject": "¡Gracias por elegir Backup Manager!", - "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager" - }, - "ProgressBackupFrame": { - "ProgressBackupTitle": "Copia de Seguridad en Progreso", - "StatusCompleted": "¡Copia completada!", - "StatusLoading": "Cargando..." - }, - "TrayIcon": { - "TrayTooltip": "Servicio de Copias de Seguridad", - "OpenAction": "Acceso Rápido", - "ExitAction": "Salir", - "SuccessMessage": "\nLa copia de seguridad se completó con éxito:", - "ErrorMessageInputMissing": "\nError en la copia automática.\n¡Faltan datos de entrada!", - "ErrorMessageFilesNotExisting": "\nError en la copia automática.\n¡Una o ambas rutas no existen!", - "ErrorMessageSamePaths": "\nError en la copia automática.\nLa ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!" - }, - "Dialogs": { - "ErrorGenericTitle": "Error", - "WarningGenericTitle": "Advertencia", - "WarningBackupAlreadyInProgressMessage": "Ya hay una copia de seguridad en progreso. No es posible realizar copias de seguridad en paralelo.", - "WarningShortTimeIntervalMessage": "El intervalo de tiempo seleccionado es muy corto. Para un funcionamiento óptimo, recomendamos configurarlo en al menos una hora. ¿Quieres continuar de todos modos?", - "ErrorMessageForFolderNotExisting": "La carpeta no existe o no es válida", - "ErrorMessageForSavingFileWithPathsEmpty": "No se puede guardar el archivo. Tanto la ruta inicial como la de destino deben especificarse y no pueden estar vacías", - "BackupSavedCorrectlyTitle": "Copia Guardada", - "BackupSavedCorrectlyMessage": "guardada con éxito.", - "ErrorSavingBackupMessage": "Error al guardar la copia de seguridad", - "BackupNameInput": "Nombre de la copia", - "ConfirmationRequiredTitle": "Confirmación requerida", - "DuplicatedBackupNameMessage": "Ya existe una copia con el mismo nombre. ¿Desea sobrescribirla?", - "BackupNameAlreadyUsedMessage": "¡Nombre de copia ya en uso!", - "ErrorMessageForIncorrectInitialPath": "Error en la operación de copia: ¡la ruta inicial es incorrecta!", - "ExceptionMessageTitle": "Error...", - "ExceptionMessageClipboardMessage": "El texto del error se ha copiado al portapapeles.", - "ExceptionMessageClipboardButton": "Copiar al portapapeles", - "ExceptionMessageReportButton": "Informar del problema", - "ExceptionMessageReportMessage": "Por favor, informe de este error, ya sea con una captura de pantalla o copiando el siguiente texto del error (se agradece proporcionar una descripción de las acciones realizadas antes del error):", - "ErrorMessageOpeningWebsite": "No se pudo abrir la página web. Por favor, intente de nuevo.", - "ConfirmationMessageForClear": "¿Está seguro de que desea limpiar los campos?", - "ConfirmationMessageForUnsavedChanges": "Hay cambios no guardados. ¿Desea guardarlos antes de pasar a otra copia de seguridad?", - "ErrorMessageOpenHistoryFile": "Error al abrir el archivo de historial.", - "ConfirmationMessageBeforeDeleteBackup": "¿Está seguro de que desea eliminar este elemento? Tenga en cuenta que esta acción no se puede deshacer.", - "ShareLinkCopiedMessage": "¡Enlace de compartir copiado al portapapeles!", - "ConfirmationMessageCancelAutoBackup": "¿Está seguro de que desea cancelar las copias automáticas para esta entrada?", - "ErrorMessageUnableToSendEmail": "No se pudo enviar el correo electrónico. Por favor, intente de nuevo más tarde.", - "ErrorMessageNotSupportedEmail": "Su sistema no admite el envío de correos electrónicos directamente desde esta aplicación.", - "ErrorMessageNotSupportedEmailGeneric": "Su sistema no admite el envío de correos electrónicos.", - "ErrorWrongTimeInterval": "El intervalo de tiempo no es correcto", - "AutoBackupActivatedMessage": "La copia automática se ha activado", - "SettedEveryMessage": "\nSe establece cada", - "DaysMessage": " días", - "InterruptBackupProcessMessage": "¿Está seguro de que desea detener esta copia?", - "ErrorMessageInputMissingGeneric": "¡Faltan datos de entrada!", - "ErrorMessageForSavingFile": "Error al guardar el archivo", - "ErrorMessageForPathNotExisting": "¡Una o ambas rutas no existen!", - "ErrorMessageForSamePaths": "La ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!", - "BackupListCorrectlyExportedTitle": "Menú Exportar", - "BackupListCorrectlyExportedMessage": "¡Lista de copias de seguridad exportada correctamente al escritorio!", - "BackupListCorrectlyImportedTitle": "Menú Importar", - "BackupListCorrectlyImportedMessage": "¡Lista de copias de seguridad importada correctamente!", - "ErrorMessageForWrongFileExtensionTitle": "Archivo no válido", - "ErrorMessageForWrongFileExtensionMessage": "Error: Seleccione un archivo JSON válido.", - "ErrorMessageCountingFiles": "Error al calcular los archivos para la copia de seguridad.", - "ErrorMessageZippingGeneric": "Error al comprimir los archivos.", - "ErrorMessageZippingIO": "Error al comprimir los archivos: error de E/S.", - "ErrorMessageZippingSecurity": "Error al comprimir los archivos: Error de seguridad.", - "SuccessGenericTitle": "Éxito", - "SuccessfullyExportedToCsvMessage": "¡Copias de seguridad exportadas a CSV con éxito!", - "SuccessfullyExportedToPdfMessage": "¡Copias de seguridad exportadas a PDF con éxito!", - "ErrorMessageForExportingToCsv": "Error al exportar copias de seguridad a CSV: ", - "ErrorMessageForExportingToPdf": "Error al exportar copias de seguridad a PDF: ", - "CsvNameMessageInput": "Introduce el nombre del archivo CSV.", - "PdfNameMessageInput": "Introduce el nombre del archivo PDF.", - "DuplicatedFileNameMessage": "El archivo ya existe. ¿Sobrescribir?", - "ErrorMessageInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos.", - "ConfirmationDeletionTitle": "Confirmar Eliminación", - "ConfirmationDeletionMessage": "¿Está seguro de que desea eliminar las filas seleccionadas?" + "Surname": "Apellido", + "ErrorMessageForWrongEmail": "La dirección de correo electrónico proporcionada no es válida. Por favor, proporciona una correcta.", + "Name": "Nombre", + "Email": "Correo electrónico", + "UserTitle": "Inserta tus datos" }, - "Subscription": { - "ExpiringTitle": "La suscripción de Backup Manager está por vencer", - "ExpiringMessage": "Tu suscripción a Backup Manager está a punto de expirar.\nLas copias de seguridad automáticas seguirán funcionando hasta la fecha de vencimiento.\nContacta con el soporte para renovarla.", - "ExpiredTitle": "La suscripción de Backup Manager ha expirado", - "ExpiredMessage": "Tu suscripción a Backup Manager ha expirado.\nLas copias de seguridad automáticas ya no se ejecutarán.\nContacta con el soporte para reactivarla." + "SETTINGS": {}, + "BackupEntry": { + "TimePickerTooltip": "Selector de tiempo", + "MaxBackupsToKeepTooltip": "Número máximo de copias de seguridad antes de eliminar las más antiguas.", + "BackupName": "Nombre de la copia de seguridad", + "AutoBackupButton": "Copia de Seguridad Automática", + "InitialPathTooltip": "(Requerido) Ruta inicial", + "CurrentFile": "Archivo actual", + "InitialFileChooserTooltip": "Abrir explorador de archivos", + "LastBackup": "Última copia de seguridad", + "SingleBackupButton": "Copia de Seguridad Única", + "AutoBackupButtonON": "Copia de Seguridad Automática (ACTIVADA)", + "AutoBackupButtonOFF": "Copia de Seguridad Automática (DESACTIVADA)", + "DestinationPathTooltip": "(Requerido) Ruta de destino", + "MaxBackupsToKeep": "Máximo de copias de seguridad a mantener", + "SingleBackupTooltip": "Realizar la copia de seguridad", + "AutoBackupTooltip": "Activar/Desactivar copia de seguridad automática", + "NotesTooltip": "(Opcional) Descripción de la copia de seguridad", + "DestinationFileChooserTooltip": "Abrir explorador de archivos", + "BackupNameTooltip": "(Requerido) Nombre de la copia de seguridad", + "PageTitle": "Entrada de Copia de Seguridad", + "Notes": "Notas" } } diff --git a/src/main/resources/res/languages/fra.json b/src/main/resources/res/languages/fra.json index 4810891c..5b1d6979 100644 --- a/src/main/resources/res/languages/fra.json +++ b/src/main/resources/res/languages/fra.json @@ -1,203 +1,214 @@ { + "TrayIcon": { + "SuccessMessage": "\nLa sauvegarde a été effectuée avec succès :", + "ExitAction": "Quitter", + "ErrorMessageFilesNotExisting": "\nErreur lors de la sauvegarde automatique.\nUn ou les deux chemins n'existent pas !", + "TrayTooltip": "Service de Sauvegardes", + "OpenAction": "Accès Rapide", + "ErrorMessageInputMissing": "\nErreur lors de la sauvegarde automatique.\nEntrée manquante !", + "ErrorMessageSamePaths": "\nErreur lors de la sauvegarde automatique.\nLe chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !" + }, + "If value is null or empty, fall back to the default value from the enum": {}, + "Dialogs": { + "WarningGenericTitle": "Avertissement", + "SuccessfullyExportedToCsvMessage": "Sauvegardes exportées en CSV avec succès !", + "ErrorMessageForPathNotExisting": "Un ou les deux chemins n'existent pas !", + "ConfirmationDeletionMessage": "Êtes-vous sûr de vouloir supprimer les lignes sélectionnées ?", + "ExceptionMessageReportMessage": "Veuillez signaler cette erreur, soit avec une capture d'écran, soit en copiant le texte d'erreur suivant (il est recommandé de fournir une description des actions effectuées avant l'erreur) :", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Erreur lors de l'exportation des sauvegardes en CSV : ", + "ErrorGenericTitle": "Erreur", + "ErrorMessageForExportingToPdf": "Erreur lors de l'exportation des sauvegardes en PDF : ", + "BackupListCorrectlyImportedTitle": "Menu Importer", + "ErrorWrongTimeInterval": "L'intervalle de temps est incorrect", + "ErrorMessageOpenHistoryFile": "Erreur lors de l'ouverture du fichier d'historique.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Le chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Lien de partage copié dans le presse-papiers !", + "ErrorMessageForSavingFileWithPathsEmpty": "Impossible d'enregistrer le fichier. Les chemins initial et de destination doivent être spécifiés et ne peuvent pas être vides", + "BackupListCorrectlyExportedMessage": "Liste de sauvegarde exportée avec succès sur le bureau !", + "BackupSavedCorrectlyMessage": "enregistrée avec succès.", + "SettedEveryMessage": "\nEst défini tous les", + "WarningBackupAlreadyInProgressMessage": "Une sauvegarde est déjà en cours. Il n'est pas possible d'effectuer des sauvegardes en parallèle.", + "WarningShortTimeIntervalMessage": "L'intervalle de temps sélectionné est très court. Pour un fonctionnement optimal, nous recommandons de le régler à au moins une heure. Voulez-vous quand même continuer ?", + "ConfirmationMessageCancelAutoBackup": "Êtes-vous sûr de vouloir annuler les sauvegardes automatiques pour cette entrée ?", + "ErrorMessageCountingFiles": "Erreur lors du calcul des fichiers à sauvegarder.", + "ErrorMessageForFolderNotExisting": "Le dossier n'existe pas ou est invalide", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Erreur lors de l'opération de sauvegarde : le chemin initial est incorrect !", + "ErrorMessageInputMissingGeneric": "Entrée manquante !", + "BackupNameInput": "Nom de la sauvegarde", + "CsvNameMessageInput": "Entrez le nom du fichier CSV.", + "ConfirmationDeletionTitle": "Confirmer la suppression", + "ErrorMessageForWrongFileExtensionMessage": "Erreur : Veuillez sélectionner un fichier JSON valide.", + "ErrorMessageZippingGeneric": "Erreur lors de la compression des fichiers.", + "ErrorMessageNotSupportedEmailGeneric": "Votre système ne prend pas en charge l'envoi d'e-mails.", + "ErrorMessageZippingIO": "Erreur lors de la compression des fichiers : erreur d'E/S.", + "SuccessfullyExportedToPdfMessage": "Sauvegardes exportées en PDF avec succès !", + "DuplicatedFileNameMessage": "Le fichier existe déjà. Écraser?", + "AutoBackupActivatedMessage": "La sauvegarde automatique a été activée", + "DaysMessage": " jours", + "ExceptionMessageReportButton": "Signaler le problème", + "ErrorMessageInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores.", + "ExceptionMessageClipboardMessage": "Le texte de l'erreur a été copié dans le presse-papiers.", + "SuccessGenericTitle": "Succès", + "ConfirmationMessageForClear": "Êtes-vous sûr de vouloir effacer les champs ?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Liste de sauvegarde importée avec succès !", + "DuplicatedBackupNameMessage": "Une sauvegarde avec le même nom existe déjà. Voulez-vous l'écraser ?", + "ExceptionMessageClipboardButton": "Copier dans le presse-papiers", + "ErrorMessageZippingSecurity": "Erreur lors de la compression des fichiers : Erreur de sécurité.", + "InterruptBackupProcessMessage": "Êtes-vous sûr de vouloir arrêter cette sauvegarde ?", + "BackupListCorrectlyExportedTitle": "Menu Exporter", + "ConfirmationMessageForUnsavedChanges": "Des modifications non enregistrées existent. Voulez-vous les enregistrer avant de passer à une autre sauvegarde ?", + "ExceptionMessageTitle": "Erreur...", + "PdfNameMessageInput": "Entrez le nom du fichier PDF.", + "ErrorMessageNotSupportedEmail": "Votre système ne prend pas en charge l'envoi d'e-mails directement depuis cette application.", + "ErrorMessageForSavingFile": "Erreur lors de l'enregistrement du fichier", + "ErrorMessageUnableToSendEmail": "Impossible d'envoyer l'e-mail. Veuillez réessayer plus tard.", + "ConfirmationMessageBeforeDeleteBackup": "Êtes-vous sûr de vouloir supprimer cet élément ? Cette action est irréversible.", + "ErrorMessageOpeningWebsite": "Échec de l'ouverture de la page web. Veuillez réessayer.", + "ErrorSavingBackupMessage": "Erreur lors de l'enregistrement de la sauvegarde", + "ConfirmationRequiredTitle": "Confirmation requise", + "BackupNameAlreadyUsedMessage": "Nom de sauvegarde déjà utilisé !", + "ErrorMessageForWrongFileExtensionTitle": "Fichier invalide", + "BackupSavedCorrectlyTitle": "Sauvegarde Enregistrée" + }, + "User dialog": {}, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervalle de temps pour les sauvegardes automatiques", + "Days": "Jours", + "Hours": "Heures", + "SpinnerTooltip": "Roulette de la souris pour ajuster la valeur", + "Description": "Sélectionnez la fréquence des sauvegardes automatiques en \nchoisissant le nombre de jours, d'heures et de minutes.", + "Minutes": "Minutes" + }, + "HISTORY_LOGS": {}, + "Constructor to assign both key and default value": {}, + "Subscription": { + "ExpiredTitle": "L'abonnement Backup Manager a expiré", + "ExpiringMessage": "Votre abonnement Backup Manager est sur le point d'expirer.\nLes sauvegardes automatiques continueront de fonctionner jusqu'à la date d'expiration.\nVeuillez contacter le support pour le renouveler.", + "ExpiringTitle": "L'abonnement Backup Manager expire bientôt", + "ExpiredMessage": "Votre abonnement Backup Manager a expiré.\nLes sauvegardes automatiques ne seront plus exécutées.\nVeuillez contacter le support pour le réactiver." + }, + "ProgressBackupFrame": { + "StatusLoading": "Chargement...", + "ProgressBackupTitle": "Sauvegarde en cours", + "StatusCompleted": "Sauvegarde terminée !" + }, + "Lookup by keyName (JSON key)": {}, + "DASHBOARD": {}, + "ABOUT": {}, "General": { "AppName": "Backup Manager", + "CancelButton": "Annuler", "Backup": "Sauvegarde", "Version": "Version", - "From": "De", - "To": "À", - "OkButton": "OK", - "CancelButton": "Annuler", - "CloseButton": "Fermer", "ApplyButton": "Appliquer", + "OkButton": "OK", + "From": "De", "SaveButton": "Enregistrer", - "CreateButton": "Créer" + "CloseButton": "Fermer", + "To": "À" }, "Menu": { - "File": "Fichier", - "Options": "Options", + "Import": "Importer la liste de sauvegarde", + "Save": "Enregistrer", "About": "À propos", - "Help": "Aide", + "File": "Fichier", + "Support": "Assistance", "BugReport": "Signaler un bug", - "Clear": "Effacer", - "Donate": "Faire un don", - "History": "Historique", - "InfoPage": "Info", - "New": "Nouveau", "Quit": "Quitter", - "Save": "Enregistrer", - "SaveWithName": "Enregistrer sous", + "Options": "Options", + "New": "Nouveau", + "Clear": "Effacer", + "Share": "Partager", "Preferences": "Préférences", - "Import": "Importer la liste de sauvegarde", + "Website": "Site web", + "SaveWithName": "Enregistrer sous", "Export": "Exporter la liste de sauvegarde", - "Share": "Partager", - "Support": "Assistance", - "Website": "Site web" - }, - "TabbedFrames": { - "BackupEntry": "Entrée de Sauvegarde", - "BackupList": "Liste de Sauvegardes" - }, - "BackupEntry": { - "PageTitle": "Entrée de Sauvegarde", - "CurrentFile": "Fichier actuel", - "Notes": "Notes", - "LastBackup": "Dernière sauvegarde", - "SingleBackupButton": "Sauvegarde Unique", - "AutoBackupButton": "Sauvegarde Automatique", - "AutoBackupButtonON": "Sauvegarde Automatique (ACTIVÉE)", - "AutoBackupButtonOFF": "Sauvegarde Automatique (DÉSACTIVÉE)", - "InitialPathPlaceholder": "Chemin initial", - "DestinationPathPlaceholder": "Chemin de destination", - "BackupName": "Nom de la sauvegarde", - "BackupNameTooltip": "(Requis) Nom de la sauvegarde", - "InitialPathTooltip": "(Requis) Chemin initial", - "DestinationPathTooltip": "(Requis) Chemin de destination", - "InitialFileChooserTooltip": "Ouvrir l'explorateur de fichiers", - "DestinationFileChooserTooltip": "Ouvrir l'explorateur de fichiers", - "NotesTooltip": "(Optionnel) Description de la sauvegarde", - "SingleBackupTooltip": "Effectuer la sauvegarde", - "AutoBackupTooltip": "Activer/Désactiver la sauvegarde automatique", - "TimePickerTooltip": "Sélecteur de temps", - "MaxBackupsToKeep": "Nombre maximum de sauvegardes à conserver", - "MaxBackupsToKeepTooltip": "Nombre maximum de sauvegardes avant de supprimer les plus anciennes." + "Help": "Aide", + "InfoPage": "Info", + "History": "Historique" }, + "Use fromKeyName to get the TKey from the JSON key": {}, + "Clear previous translations to avoid stale values when switching languages": {}, "BackupList": { - "BackupNameColumn": "Nom de la Sauvegarde", - "InitialPathColumn": "Chemin Initial", - "DestinationPathColumn": "Chemin de Destination", - "LastBackupColumn": "Dernière Sauvegarde", - "AutomaticBackupColumn": "Sauvegarde Automatique", - "NextBackupDateColumn": "Prochaine Date de Sauvegarde", - "TimeIntervalColumn": "Intervalle de Temps", + "NotesDetail": "Notes", "BackupNameDetail": "NomSauvegarde", + "ExportAsPdfTooltip": "Exporter en tant que PDF", + "EditPopup": "Modifier", + "ResearchBarTooltip": "Barre de recherche", "InitialPathDetail": "CheminInitial", - "DestinationPathDetail": "CheminDestination", - "LastBackupDetail": "DernièreSauvegarde", + "ExportAs": "Exporter en tant que : ", + "RenameBackupPopup": "Renommer la sauvegarde", "NextBackupDateDetail": "ProchaineSauvegarde", - "TimeIntervalDetail": "IntervalleTemps", - "CreationDateDetail": "DateCréation", - "LastUpdateDateDetail": "DernièreMiseJour", + "AutoBackupPopup": "Sauvegarde automatique", + "BackupNameColumn": "Nom de la Sauvegarde", + "BackupPopup": "Sauvegarde", "BackupCountDetail": "NombreSauvegardes", - "NotesDetail": "Notes", - "MaxBackupsToKeepDetail": "NombreMaxSauvegardesConserver", - "AddBackupTooltip": "Ajouter une nouvelle sauvegarde", - "ExportAs": "Exporter en tant que : ", - "ExportAsPdfTooltip": "Exporter en tant que PDF", "ExportAsCsvTooltip": "Exporter en tant que CSV", - "ResearchBarTooltip": "Barre de recherche", - "ResearchBarPlaceholder": "Rechercher...", - "EditPopup": "Modifier", + "InitialPathColumn": "Chemin Initial", + "MaxBackupsToKeepDetail": "NombreMaxSauvegardesConserver", "DeletePopup": "Supprimer", - "InterruptPopup": "Interrompre", - "DuplicatePopup": "Dupliquer", - "RenameBackupPopup": "Renommer la sauvegarde", - "OpenInitialFolderPopup": "Ouvrir le chemin initial", "OpenDestinationFolderPopup": "Ouvrir le chemin de destination", - "BackupPopup": "Sauvegarde", + "LastBackupColumn": "Dernière Sauvegarde", "SingleBackupPopup": "Effectuer une sauvegarde unique", - "AutoBackupPopup": "Sauvegarde automatique", + "AddBackupTooltip": "Ajouter une nouvelle sauvegarde", "CopyTextPopup": "Copier le texte", + "DestinationPathDetail": "CheminDestination", + "CopyDestinationPathPopup": "Copier le chemin de destination", + "LastBackupDetail": "DernièreSauvegarde", + "OpenInitialFolderPopup": "Ouvrir le chemin initial", + "ResearchBarPlaceholder": "Rechercher...", + "InterruptPopup": "Interrompre", + "DuplicatePopup": "Dupliquer", + "NextBackupDateColumn": "Prochaine Date de Sauvegarde", + "DestinationPathColumn": "Chemin de Destination", + "AutomaticBackupColumn": "Sauvegarde Automatique", + "LastUpdateDateDetail": "DernièreMiseJour", + "TimeIntervalDetail": "IntervalleTemps", "CopyBackupNamePopup": "Copier le nom de la sauvegarde", - "CopyInitialPathPopup": "Copier le chemin initial", - "CopyDestinationPathPopup": "Copier le chemin de destination" + "CreationDateDetail": "DateCréation", + "CopyInitialPathPopup": "Copier le chemin initial" }, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervalle de temps pour les sauvegardes automatiques", - "Description": "Sélectionnez la fréquence des sauvegardes automatiques en \nchoisissant le nombre de jours, d'heures et de minutes.", - "Days": "Jours", - "Hours": "Heures", - "Minutes": "Minutes", - "SpinnerTooltip": "Roulette de la souris pour ajuster la valeur" + "TabbedFrames": { + "BackupEntry": "Entrée de Sauvegarde", + "BackupList": "Liste de Sauvegardes" }, "UserDialog": { - "UserTitle": "Entrez vos informations", - "Name": "Prénom", - "Surname": "Nom de famille", - "Email": "E-Mail", "ErrorMessageForMissingData": "Veuillez remplir tous les champs requis.", - "ErrorMessageForWrongEmail": "L'adresse e-mail fournie n'est pas valide. Veuillez en fournir une correcte.", + "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager", "EmailConfirmationSubject": "Merci d'avoir choisi Backup Manager !", - "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager" - }, - "ProgressBackupFrame": { - "ProgressBackupTitle": "Sauvegarde en cours", - "StatusCompleted": "Sauvegarde terminée !", - "StatusLoading": "Chargement..." - }, - "TrayIcon": { - "TrayTooltip": "Service de Sauvegardes", - "OpenAction": "Accès Rapide", - "ExitAction": "Quitter", - "SuccessMessage": "\nLa sauvegarde a été effectuée avec succès :", - "ErrorMessageInputMissing": "\nErreur lors de la sauvegarde automatique.\nEntrée manquante !", - "ErrorMessageFilesNotExisting": "\nErreur lors de la sauvegarde automatique.\nUn ou les deux chemins n'existent pas !", - "ErrorMessageSamePaths": "\nErreur lors de la sauvegarde automatique.\nLe chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !" - }, - "Dialogs": { - "ErrorGenericTitle": "Erreur", - "WarningGenericTitle": "Avertissement", - "WarningBackupAlreadyInProgressMessage": "Une sauvegarde est déjà en cours. Il n'est pas possible d'effectuer des sauvegardes en parallèle.", - "WarningShortTimeIntervalMessage": "L'intervalle de temps sélectionné est très court. Pour un fonctionnement optimal, nous recommandons de le régler à au moins une heure. Voulez-vous quand même continuer ?", - "ErrorMessageForFolderNotExisting": "Le dossier n'existe pas ou est invalide", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossible d'enregistrer le fichier. Les chemins initial et de destination doivent être spécifiés et ne peuvent pas être vides", - "BackupSavedCorrectlyTitle": "Sauvegarde Enregistrée", - "BackupSavedCorrectlyMessage": "enregistrée avec succès.", - "ErrorSavingBackupMessage": "Erreur lors de l'enregistrement de la sauvegarde", - "BackupNameInput": "Nom de la sauvegarde", - "ConfirmationRequiredTitle": "Confirmation requise", - "DuplicatedBackupNameMessage": "Une sauvegarde avec le même nom existe déjà. Voulez-vous l'écraser ?", - "BackupNameAlreadyUsedMessage": "Nom de sauvegarde déjà utilisé !", - "ErrorMessageForIncorrectInitialPath": "Erreur lors de l'opération de sauvegarde : le chemin initial est incorrect !", - "ExceptionMessageTitle": "Erreur...", - "ExceptionMessageClipboardMessage": "Le texte de l'erreur a été copié dans le presse-papiers.", - "ExceptionMessageClipboardButton": "Copier dans le presse-papiers", - "ExceptionMessageReportButton": "Signaler le problème", - "ExceptionMessageReportMessage": "Veuillez signaler cette erreur, soit avec une capture d'écran, soit en copiant le texte d'erreur suivant (il est recommandé de fournir une description des actions effectuées avant l'erreur) :", - "ErrorMessageOpeningWebsite": "Échec de l'ouverture de la page web. Veuillez réessayer.", - "ConfirmationMessageForClear": "Êtes-vous sûr de vouloir effacer les champs ?", - "ConfirmationMessageForUnsavedChanges": "Des modifications non enregistrées existent. Voulez-vous les enregistrer avant de passer à une autre sauvegarde ?", - "ErrorMessageOpenHistoryFile": "Erreur lors de l'ouverture du fichier d'historique.", - "ConfirmationMessageBeforeDeleteBackup": "Êtes-vous sûr de vouloir supprimer cet élément ? Cette action est irréversible.", - "ShareLinkCopiedMessage": "Lien de partage copié dans le presse-papiers !", - "ConfirmationMessageCancelAutoBackup": "Êtes-vous sûr de vouloir annuler les sauvegardes automatiques pour cette entrée ?", - "ErrorMessageUnableToSendEmail": "Impossible d'envoyer l'e-mail. Veuillez réessayer plus tard.", - "ErrorMessageNotSupportedEmail": "Votre système ne prend pas en charge l'envoi d'e-mails directement depuis cette application.", - "ErrorMessageNotSupportedEmailGeneric": "Votre système ne prend pas en charge l'envoi d'e-mails.", - "ErrorWrongTimeInterval": "L'intervalle de temps est incorrect", - "AutoBackupActivatedMessage": "La sauvegarde automatique a été activée", - "SettedEveryMessage": "\nEst défini tous les", - "DaysMessage": " jours", - "InterruptBackupProcessMessage": "Êtes-vous sûr de vouloir arrêter cette sauvegarde ?", - "ErrorMessageInputMissingGeneric": "Entrée manquante !", - "ErrorMessageForSavingFile": "Erreur lors de l'enregistrement du fichier", - "ErrorMessageForPathNotExisting": "Un ou les deux chemins n'existent pas !", - "ErrorMessageForSamePaths": "Le chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !", - "BackupListCorrectlyExportedTitle": "Menu Exporter", - "BackupListCorrectlyExportedMessage": "Liste de sauvegarde exportée avec succès sur le bureau !", - "BackupListCorrectlyImportedTitle": "Menu Importer", - "BackupListCorrectlyImportedMessage": "Liste de sauvegarde importée avec succès !", - "ErrorMessageForWrongFileExtensionTitle": "Fichier invalide", - "ErrorMessageForWrongFileExtensionMessage": "Erreur : Veuillez sélectionner un fichier JSON valide.", - "ErrorMessageCountingFiles": "Erreur lors du calcul des fichiers à sauvegarder.", - "ErrorMessageZippingGeneric": "Erreur lors de la compression des fichiers.", - "ErrorMessageZippingIO": "Erreur lors de la compression des fichiers : erreur d'E/S.", - "ErrorMessageZippingSecurity": "Erreur lors de la compression des fichiers : Erreur de sécurité.", - "SuccessGenericTitle": "Succès", - "SuccessfullyExportedToCsvMessage": "Sauvegardes exportées en CSV avec succès !", - "SuccessfullyExportedToPdfMessage": "Sauvegardes exportées en PDF avec succès !", - "ErrorMessageForExportingToCsv": "Erreur lors de l'exportation des sauvegardes en CSV : ", - "ErrorMessageForExportingToPdf": "Erreur lors de l'exportation des sauvegardes en PDF : ", - "CsvNameMessageInput": "Entrez le nom du fichier CSV.", - "PdfNameMessageInput": "Entrez le nom du fichier PDF.", - "DuplicatedFileNameMessage": "Le fichier existe déjà. Écraser?", - "ErrorMessageInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores.", - "ConfirmationDeletionTitle": "Confirmer la suppression", - "ConfirmationDeletionMessage": "Êtes-vous sûr de vouloir supprimer les lignes sélectionnées ?" + "Surname": "Nom de famille", + "ErrorMessageForWrongEmail": "L'adresse e-mail fournie n'est pas valide. Veuillez en fournir une correcte.", + "Name": "Prénom", + "Email": "E-Mail", + "UserTitle": "Entrez vos informations" }, - "Subscription": { - "ExpiringTitle": "L'abonnement Backup Manager expire bientôt", - "ExpiringMessage": "Votre abonnement Backup Manager est sur le point d'expirer.\nLes sauvegardes automatiques continueront de fonctionner jusqu'à la date d'expiration.\nVeuillez contacter le support pour le renouveler.", - "ExpiredTitle": "L'abonnement Backup Manager a expiré", - "ExpiredMessage": "Votre abonnement Backup Manager a expiré.\nLes sauvegardes automatiques ne seront plus exécutées.\nVeuillez contacter le support pour le réactiver." + "SETTINGS": {}, + "BackupEntry": { + "TimePickerTooltip": "Sélecteur de temps", + "MaxBackupsToKeepTooltip": "Nombre maximum de sauvegardes avant de supprimer les plus anciennes.", + "BackupName": "Nom de la sauvegarde", + "AutoBackupButton": "Sauvegarde Automatique", + "InitialPathTooltip": "(Requis) Chemin initial", + "CurrentFile": "Fichier actuel", + "InitialFileChooserTooltip": "Ouvrir l'explorateur de fichiers", + "LastBackup": "Dernière sauvegarde", + "SingleBackupButton": "Sauvegarde Unique", + "AutoBackupButtonON": "Sauvegarde Automatique (ACTIVÉE)", + "AutoBackupButtonOFF": "Sauvegarde Automatique (DÉSACTIVÉE)", + "DestinationPathTooltip": "(Requis) Chemin de destination", + "MaxBackupsToKeep": "Nombre maximum de sauvegardes à conserver", + "SingleBackupTooltip": "Effectuer la sauvegarde", + "AutoBackupTooltip": "Activer/Désactiver la sauvegarde automatique", + "NotesTooltip": "(Optionnel) Description de la sauvegarde", + "DestinationFileChooserTooltip": "Ouvrir l'explorateur de fichiers", + "BackupNameTooltip": "(Requis) Nom de la sauvegarde", + "PageTitle": "Entrée de Sauvegarde", + "Notes": "Notes" } } diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 311089de..1a5b291a 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -1,203 +1,214 @@ { + "TrayIcon": { + "SuccessMessage": "\nIl backup è stato completato con successo:", + "ExitAction": "Esci", + "ErrorMessageFilesNotExisting": "\nErrore durante il backup automatico.\nUno o entrambi i percorsi non esistono!", + "TrayTooltip": "Servizio di Backup", + "OpenAction": "Accesso Rapido", + "ErrorMessageInputMissing": "\nErrore durante il backup automatico.\nPercorso mancante!", + "ErrorMessageSamePaths": "\nErrore durante il backup automatico.\nIl percorso iniziale e il percorso di destinazione non possono essere uguali. Scegli percorsi diversi!" + }, + "If value is null or empty, fall back to the default value from the enum": {}, + "Dialogs": { + "WarningGenericTitle": "Avviso", + "SuccessfullyExportedToCsvMessage": "Backup esportati in CSV con successo!", + "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", + "ConfirmationDeletionMessage": "Sei sicuro di voler eliminare le righe selezionate?", + "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Errore durante l'esportazione dei backup in CSV: ", + "ErrorGenericTitle": "Errore", + "ErrorMessageForExportingToPdf": "Errore durante l'esportazione dei backup in PDF: ", + "BackupListCorrectlyImportedTitle": "Menu Importa", + "ErrorWrongTimeInterval": "L'intervallo di tempo non è corretto", + "ErrorMessageOpenHistoryFile": "Errore durante l'apertura del file storico.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Link di condivisione copiato negli appunti!", + "ErrorMessageForSavingFileWithPathsEmpty": "Impossibile salvare il file. Entrambi i percorsi iniziale e di destinazione devono essere specificati e non possono essere vuoti", + "BackupListCorrectlyExportedMessage": "Lista di backup esportata correttamente sul desktop!", + "BackupSavedCorrectlyMessage": "salvato con successo!", + "SettedEveryMessage": "\nImpostato ogni", + "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", + "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?", + "ConfirmationMessageCancelAutoBackup": "Sei sicuro di voler annullare il backup automatico?", + "ErrorMessageCountingFiles": "Errore durante il calcolo dei file da eseguire il backup.", + "ErrorMessageForFolderNotExisting": "La cartella non esiste o non è valida", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Errore durante il backup: il percorso iniziale è errato!", + "ErrorMessageInputMissingGeneric": "Input Mancanti!", + "BackupNameInput": "Nome del backup", + "CsvNameMessageInput": "Inserisci il nome del file CSV.", + "ConfirmationDeletionTitle": "Conferma Eliminazione", + "ErrorMessageForWrongFileExtensionMessage": "Errore: Selezionare un file JSON valido.", + "ErrorMessageZippingGeneric": "Errore durante la compressione dei file.", + "ErrorMessageNotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email.", + "ErrorMessageZippingIO": "Errore durante la compressione dei file: errore di I/O.", + "SuccessfullyExportedToPdfMessage": "Backup esportati in PDF con successo!", + "DuplicatedFileNameMessage": "Il file esiste già. Sovrascrivere?", + "AutoBackupActivatedMessage": "Backup Automatico attivato", + "DaysMessage": " giorni", + "ExceptionMessageReportButton": "Riporta il problema", + "ErrorMessageInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore.", + "ExceptionMessageClipboardMessage": "Il testo dell'errore è stato copiato negli appunti.", + "SuccessGenericTitle": "Successo", + "ConfirmationMessageForClear": "Sei sicuro di voler pulire i campi?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Lista di backup importata correttamente!", + "DuplicatedBackupNameMessage": "Esiste già un backup con lo stesso nome, vuoi sovrascriverlo?", + "ExceptionMessageClipboardButton": "Copia negli appunti", + "ErrorMessageZippingSecurity": "Errore durante la compressione dei file: Errore di sicurezza.", + "InterruptBackupProcessMessage": "Sei sicuro di voler interrompere questo backup?", + "BackupListCorrectlyExportedTitle": "Menu Esporta", + "ConfirmationMessageForUnsavedChanges": "Ci sono modifiche non salvate, vuoi salvarle prima di passare a un altro backup?", + "ExceptionMessageTitle": "Errore...", + "PdfNameMessageInput": "Inserisci il nome del file PDF.", + "ErrorMessageNotSupportedEmail": "Il tuo sistema non supporta l'invio di email direttamente da questa applicazione.", + "ErrorMessageForSavingFile": "Errore nel salvataggio del file", + "ErrorMessageUnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi.", + "ConfirmationMessageBeforeDeleteBackup": "Sei sicuro di voler eliminare questo elemento? Nota: questa azione non può essere annullata.", + "ErrorMessageOpeningWebsite": "Impossibile aprire la pagina web. Riprova.", + "ErrorSavingBackupMessage": "Errore durante il salvataggio del backup", + "ConfirmationRequiredTitle": "Conferma richiesta", + "BackupNameAlreadyUsedMessage": "Nome del backup già utilizzato!", + "ErrorMessageForWrongFileExtensionTitle": "File non valido", + "BackupSavedCorrectlyTitle": "Backup salvato" + }, + "User dialog": {}, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervallo di tempo per backup automatico", + "Days": "Giorni", + "Hours": "Ore", + "SpinnerTooltip": "Usa la rotellina per regolare il valore", + "Description": "Seleziona la frequenza del backup automatico \nscegliendo la frequenza in giorni, ore e minuti.", + "Minutes": "Minuti" + }, + "HISTORY_LOGS": {}, + "Constructor to assign both key and default value": {}, + "Subscription": { + "ExpiredTitle": "Abbonamento Backup Manager scaduto", + "ExpiringMessage": "Il tuo abbonamento a Backup Manager sta per scadere.\nI backup automatici continueranno a funzionare fino alla data di scadenza.\nContatta l'assistenza per rinnovarlo.", + "ExpiringTitle": "Abbonamento Backup Manager in scadenza", + "ExpiredMessage": "Il tuo abbonamento a Backup Manager è scaduto.\nI backup automatici non verranno più eseguiti.\nContatta l'assistenza per riattivarlo." + }, + "ProgressBackupFrame": { + "StatusLoading": "Caricamento...", + "ProgressBackupTitle": "Backup in corso", + "StatusCompleted": "Backup completato!" + }, + "Lookup by keyName (JSON key)": {}, + "DASHBOARD": {}, + "ABOUT": {}, "General": { "AppName": "Backup Manager", + "CancelButton": "Annulla", "Backup": "Backup", "Version": "Versione", - "From": "Da", - "To": "A", - "CloseButton": "Chiudi", + "ApplyButton": "Applica", "OkButton": "Ok", - "CancelButton": "Annulla", - "ApplyButton":"Applica", + "From": "Da", "SaveButton": "Salva", - "CreateButton": "Crea" + "CloseButton": "Chiudi", + "To": "A" }, "Menu": { - "File": "File", - "Options": "Opzioni", + "Import": "Importa lista di backup", + "Save": "Salva", "About": "Informazioni", - "Help": "Aiuto", + "File": "File", + "Support": "Supporto", "BugReport": "Segnala un problema", - "Clear": "Pulisci", - "Donate": "Donazione", - "History": "Storico", - "InfoPage": "Info", - "New": "Nuovo", "Quit": "Esci", - "Save": "Salva", - "SaveWithName": "Salva con nome", + "Options": "Opzioni", + "New": "Nuovo", + "Clear": "Pulisci", + "Share": "Condividi", "Preferences": "Preferenze", - "Import": "Importa lista di backup", + "Website": "Sito web", + "SaveWithName": "Salva con nome", "Export": "Esporta lista di backup", - "Share": "Condividi", - "Support": "Supporto", - "Website": "Sito web" - }, - "TabbedFrames": { - "BackupEntry": "VoceBackup", - "BackupList": "ListaBackup" - }, - "BackupEntry": { - "PageTitle": "Voce di Backup", - "CurrentFile": "File corrente", - "Notes": "Note", - "LastBackup": "Ultimo backup", - "SingleBackupButton": "Backup Singolo", - "AutoBackupButton": "Backup Automatico", - "AutoBackupButtonON": "Backup Automatico (ON)", - "AutoBackupButtonOFF": "Backup Automatico (OFF)", - "InitialPathPlaceholder": "Percorso iniziale", - "DestinationPathPlaceholder": "Percorso di destinazione", - "BackupName": "Nome del backup", - "BackupNameTooltip": "(Obbligatorio) Nome del backup", - "InitialPathTooltip": "(Obbligatorio) Percorso iniziale", - "DestinationPathTooltip": "(Obbligatorio) Percorso di destinazione", - "InitialFileChooserTooltip": "Apri esplora file", - "DestinationFileChooserTooltip": "Apri esplora file", - "NotesTooltip": "(Opzionale) Descrizione del backup", - "SingleBackupTooltip": "Esegui il backup", - "AutoBackupTooltip": "Attiva/Disattiva backup automatico", - "TimePickerTooltip": "Selettore orario", - "MaxBackupsToKeep": "Massimo numero di backup da mantenere", - "MaxBackupsToKeepTooltip": "Numero massimo di backup da conservare prima di eliminare i più vecchi." + "Help": "Aiuto", + "InfoPage": "Info", + "History": "Storico" }, + "Use fromKeyName to get the TKey from the JSON key": {}, + "Clear previous translations to avoid stale values when switching languages": {}, "BackupList": { - "BackupNameColumn": "Nome del Backup", - "InitialPathColumn": "Percorso Iniziale", - "DestinationPathColumn": "Percorso di Destinazione", - "LastBackupColumn": "Ultimo Backup", - "AutomaticBackupColumn": "Backup Automatico", - "NextBackupDateColumn": "Data del Prossimo Backup", - "TimeIntervalColumn": "Intervallo di Tempo", + "NotesDetail": "Note", "BackupNameDetail": "NomeBackup", + "ExportAsPdfTooltip": "Esporta come PDF", + "EditPopup": "Modifica", + "ResearchBarTooltip": "Barra di ricerca", "InitialPathDetail": "PercorsoIniziale", - "DestinationPathDetail": "PercorsoDestinazione", - "LastBackupDetail": "UltimoBackup", + "ExportAs": "Esporta come: ", + "RenameBackupPopup": "Rinomina backup", "NextBackupDateDetail": "ProssimoBackup", - "TimeIntervalDetail": "IntervalloDiTempo", - "CreationDateDetail": "DataCreazione", - "LastUpdateDateDetail": "DataUltimoAggiornamento", + "AutoBackupPopup": "Backup automatico", + "BackupNameColumn": "Nome del Backup", + "BackupPopup": "Backup", "BackupCountDetail": "ConteggioBackup", - "MaxBackupsToKeepDetail": "MassimoNumeroBackupDaMantenere", - "NotesDetail": "Note", - "AddBackupTooltip": "Aggiungi nuovo backup", - "ExportAs": "Esporta come: ", - "ExportAsPdfTooltip": "Esporta come PDF", "ExportAsCsvTooltip": "Esporta come CSV", - "ResearchBarTooltip": "Barra di ricerca", - "ResearchBarPlaceholder": "Cerca...", - "EditPopup": "Modifica", + "InitialPathColumn": "Percorso Iniziale", + "MaxBackupsToKeepDetail": "MassimoNumeroBackupDaMantenere", "DeletePopup": "Elimina", - "InterruptPopup": "Interrompi", - "DuplicatePopup": "Duplica", - "RenameBackupPopup": "Rinomina backup", - "OpenInitialFolderPopup": "Apri percorso iniziale", "OpenDestinationFolderPopup": "Apri percorso di destinazione", - "BackupPopup": "Backup", + "LastBackupColumn": "Ultimo Backup", "SingleBackupPopup": "Esegui backup singolo", - "AutoBackupPopup": "Backup automatico", + "AddBackupTooltip": "Aggiungi nuovo backup", "CopyTextPopup": "Copia testo", + "DestinationPathDetail": "PercorsoDestinazione", + "CopyDestinationPathPopup": "Copia percorso di destinazione", + "LastBackupDetail": "UltimoBackup", + "OpenInitialFolderPopup": "Apri percorso iniziale", + "ResearchBarPlaceholder": "Cerca...", + "InterruptPopup": "Interrompi", + "DuplicatePopup": "Duplica", + "NextBackupDateColumn": "Data del Prossimo Backup", + "DestinationPathColumn": "Percorso di Destinazione", + "AutomaticBackupColumn": "Backup Automatico", + "LastUpdateDateDetail": "DataUltimoAggiornamento", + "TimeIntervalDetail": "IntervalloDiTempo", "CopyBackupNamePopup": "Copia nome backup", - "CopyInitialPathPopup": "Copia percorso iniziale", - "CopyDestinationPathPopup": "Copia percorso di destinazione" + "CreationDateDetail": "DataCreazione", + "CopyInitialPathPopup": "Copia percorso iniziale" }, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervallo di tempo per backup automatico", - "Description": "Seleziona la frequenza del backup automatico \nscegliendo la frequenza in giorni, ore e minuti.", - "Days": "Giorni", - "Hours": "Ore", - "Minutes": "Minuti", - "SpinnerTooltip": "Usa la rotellina per regolare il valore" + "TabbedFrames": { + "BackupEntry": "VoceBackup", + "BackupList": "ListaBackup" }, "UserDialog": { - "UserTitle": "Inserisci i tuoi dati", - "Name": "Nome", - "Surname": "Cognome", - "Email": "Email", "ErrorMessageForMissingData": "Per favore, compila tutti i campi richiesti.", - "ErrorMessageForWrongEmail": "L'indirizzo email fornito non è valido. Inseriscine uno corretto.", + "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager", "EmailConfirmationSubject": "Grazie per aver scelto Backup Manager!", - "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager" - }, - "ProgressBackupFrame": { - "ProgressBackupTitle": "Backup in corso", - "StatusCompleted": "Backup completato!", - "StatusLoading": "Caricamento..." - }, - "TrayIcon": { - "TrayTooltip": "Servizio di Backup", - "OpenAction": "Accesso Rapido", - "ExitAction": "Esci", - "SuccessMessage": "\nIl backup è stato completato con successo:", - "ErrorMessageInputMissing": "\nErrore durante il backup automatico.\nPercorso mancante!", - "ErrorMessageFilesNotExisting": "\nErrore durante il backup automatico.\nUno o entrambi i percorsi non esistono!", - "ErrorMessageSamePaths": "\nErrore durante il backup automatico.\nIl percorso iniziale e il percorso di destinazione non possono essere uguali. Scegli percorsi diversi!" - }, - "Dialogs": { - "ErrorGenericTitle": "Errore", - "WarningGenericTitle": "Avviso", - "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", - "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?", - "ErrorMessageForFolderNotExisting": "La cartella non esiste o non è valida", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossibile salvare il file. Entrambi i percorsi iniziale e di destinazione devono essere specificati e non possono essere vuoti", - "BackupSavedCorrectlyTitle": "Backup salvato", - "BackupSavedCorrectlyMessage": "salvato con successo!", - "ErrorSavingBackupMessage": "Errore durante il salvataggio del backup", - "BackupNameInput": "Nome del backup", - "ConfirmationRequiredTitle": "Conferma richiesta", - "DuplicatedBackupNameMessage": "Esiste già un backup con lo stesso nome, vuoi sovrascriverlo?", - "BackupNameAlreadyUsedMessage": "Nome del backup già utilizzato!", - "ErrorMessageForIncorrectInitialPath": "Errore durante il backup: il percorso iniziale è errato!", - "ExceptionMessageTitle": "Errore...", - "ExceptionMessageClipboardMessage": "Il testo dell'errore è stato copiato negli appunti.", - "ExceptionMessageClipboardButton":"Copia negli appunti", - "ExceptionMessageReportButton":"Riporta il problema", - "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", - "ErrorMessageOpeningWebsite": "Impossibile aprire la pagina web. Riprova.", - "ConfirmationMessageForClear": "Sei sicuro di voler pulire i campi?", - "ConfirmationMessageForUnsavedChanges": "Ci sono modifiche non salvate, vuoi salvarle prima di passare a un altro backup?", - "ErrorMessageOpenHistoryFile": "Errore durante l'apertura del file storico.", - "ConfirmationMessageBeforeDeleteBackup": "Sei sicuro di voler eliminare questo elemento? Nota: questa azione non può essere annullata.", - "ShareLinkCopiedMessage": "Link di condivisione copiato negli appunti!", - "ConfirmationMessageCancelAutoBackup": "Sei sicuro di voler annullare il backup automatico?", - "ErrorMessageUnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi.", - "ErrorMessageNotSupportedEmail": "Il tuo sistema non supporta l'invio di email direttamente da questa applicazione.", - "ErrorMessageNotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email.", - "ErrorWrongTimeInterval":"L'intervallo di tempo non è corretto", - "AutoBackupActivatedMessage": "Backup Automatico attivato", - "SettedEveryMessage": "\nImpostato ogni", - "DaysMessage": " giorni", - "InterruptBackupProcessMessage": "Sei sicuro di voler interrompere questo backup?", - "ErrorMessageInputMissingGeneric": "Input Mancanti!", - "ErrorMessageForSavingFile": "Errore nel salvataggio del file", - "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", - "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!", - "BackupListCorrectlyExportedTitle": "Menu Esporta", - "BackupListCorrectlyExportedMessage": "Lista di backup esportata correttamente sul desktop!", - "BackupListCorrectlyImportedTitle": "Menu Importa", - "BackupListCorrectlyImportedMessage": "Lista di backup importata correttamente!", - "ErrorMessageForWrongFileExtensionTitle": "File non valido", - "ErrorMessageForWrongFileExtensionMessage": "Errore: Selezionare un file JSON valido.", - "ErrorMessageCountingFiles": "Errore durante il calcolo dei file da eseguire il backup.", - "ErrorMessageZippingGeneric": "Errore durante la compressione dei file.", - "ErrorMessageZippingIO": "Errore durante la compressione dei file: errore di I/O.", - "ErrorMessageZippingSecurity": "Errore durante la compressione dei file: Errore di sicurezza.", - "SuccessGenericTitle": "Successo", - "SuccessfullyExportedToCsvMessage": "Backup esportati in CSV con successo!", - "SuccessfullyExportedToPdfMessage": "Backup esportati in PDF con successo!", - "ErrorMessageForExportingToCsv": "Errore durante l'esportazione dei backup in CSV: ", - "ErrorMessageForExportingToPdf": "Errore durante l'esportazione dei backup in PDF: ", - "CsvNameMessageInput": "Inserisci il nome del file CSV.", - "PdfNameMessageInput": "Inserisci il nome del file PDF.", - "DuplicatedFileNameMessage": "Il file esiste già. Sovrascrivere?", - "ErrorMessageInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore.", - "ConfirmationDeletionTitle": "Conferma Eliminazione", - "ConfirmationDeletionMessage": "Sei sicuro di voler eliminare le righe selezionate?" + "Surname": "Cognome", + "ErrorMessageForWrongEmail": "L'indirizzo email fornito non è valido. Inseriscine uno corretto.", + "Name": "Nome", + "Email": "Email", + "UserTitle": "Inserisci i tuoi dati" }, - "Subscription": { - "ExpiringTitle": "Abbonamento Backup Manager in scadenza", - "ExpiringMessage": "Il tuo abbonamento a Backup Manager sta per scadere.\nI backup automatici continueranno a funzionare fino alla data di scadenza.\nContatta l'assistenza per rinnovarlo.", - "ExpiredTitle": "Abbonamento Backup Manager scaduto", - "ExpiredMessage": "Il tuo abbonamento a Backup Manager è scaduto.\nI backup automatici non verranno più eseguiti.\nContatta l'assistenza per riattivarlo." + "SETTINGS": {}, + "BackupEntry": { + "TimePickerTooltip": "Selettore orario", + "MaxBackupsToKeepTooltip": "Numero massimo di backup da conservare prima di eliminare i più vecchi.", + "BackupName": "Nome del backup", + "AutoBackupButton": "Backup Automatico", + "InitialPathTooltip": "(Obbligatorio) Percorso iniziale", + "CurrentFile": "File corrente", + "InitialFileChooserTooltip": "Apri esplora file", + "LastBackup": "Ultimo backup", + "SingleBackupButton": "Backup Singolo", + "AutoBackupButtonON": "Backup Automatico (ON)", + "AutoBackupButtonOFF": "Backup Automatico (OFF)", + "DestinationPathTooltip": "(Obbligatorio) Percorso di destinazione", + "MaxBackupsToKeep": "Massimo numero di backup da mantenere", + "SingleBackupTooltip": "Esegui il backup", + "AutoBackupTooltip": "Attiva/Disattiva backup automatico", + "NotesTooltip": "(Opzionale) Descrizione del backup", + "DestinationFileChooserTooltip": "Apri esplora file", + "BackupNameTooltip": "(Obbligatorio) Nome del backup", + "PageTitle": "Voce di Backup", + "Notes": "Note" } } From 00481c8e1d92828f0d9b3c91bcfe2efab5f61abc Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Fri, 24 Apr 2026 11:10:08 +0200 Subject: [PATCH 11/17] translations --- .../backupmanager/Enums/Translations.java | 181 ++++--- .../gui/component/FormSearchPanel.java | 18 +- .../backupmanager/gui/forms/FormSetting.java | 2 +- src/main/resources/res/languages/deu.json | 510 ++++++++++-------- src/main/resources/res/languages/eng.json | 510 ++++++++++-------- src/main/resources/res/languages/esp.json | 510 ++++++++++-------- src/main/resources/res/languages/fra.json | 510 ++++++++++-------- src/main/resources/res/languages/ita.json | 510 ++++++++++-------- 8 files changed, 1596 insertions(+), 1155 deletions(-) diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index 28414b5c..638e0bc4 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -32,11 +32,12 @@ public enum TCategory { PROGRESS_BACKUP_FRAME("ProgressBackupFrame"), TRAY_ICON("TrayIcon"), DIALOGS("Dialogs"), - SUBSCRIPTION("Subscription"), // TODO: add to json - HISTORY_LOGS("HistoryLogs"), // TODO: add to json - ABOUT("About"), // TODO: add to json - DASHBOARD("Dashboard"), // TODO: add to json - SETTINGS("Settings"); // TODO: add to json + SUBSCRIPTION("Subscription"), + HISTORY_LOGS("HistoryLogs"), + ABOUT("About"), + DASHBOARD("Dashboard"), + SETTINGS("Settings"), + SEARCH_BAR("SearchBar"); private final String categoryName; private final Map<TKey, String> translations = new HashMap<>(); @@ -75,21 +76,21 @@ public enum TKey { CANCEL_BUTTON(TCategory.GENERAL, "CancelButton", "Cancel"), APPLY_BUTTON(TCategory.GENERAL, "ApplyButton", "Apply"), SAVE_BUTTON(TCategory.GENERAL, "SaveButton", "Save"), - CREATE_BUTTON(TCategory.GENERAL, "CreateButton", "Create"), // TODO: add to json - EDIT_BUTTON(TCategory.GENERAL, "EditButton", "Edit"), // TODO: add to json - DELETE_BUTTON(TCategory.GENERAL, "DeleteButton", "Delete"), // TODO: add to json - QUICK_SEARCH(TCategory.GENERAL, "QuickSearch", "Quick Search..."), // TODO: add to json + CREATE_BUTTON(TCategory.GENERAL, "CreateButton", "Create"), + EDIT_BUTTON(TCategory.GENERAL, "EditButton", "Edit"), + DELETE_BUTTON(TCategory.GENERAL, "DeleteButton", "Delete"), + QUICK_SEARCH(TCategory.GENERAL, "QuickSearch", "Quick Search..."), // Menu - SUBMENU_MAIN(TCategory.MENU, "SubmenuMain", "MAIN"), //TODO: add to json - SUBMENU_OTHER(TCategory.MENU, "SubmenuOther", "OTHER"), //TODO: add to json + SUBMENU_MAIN(TCategory.MENU, "SubmenuMain", "MAIN"), + SUBMENU_OTHER(TCategory.MENU, "SubmenuOther", "OTHER"), FILE(TCategory.MENU, "File", "File"), OPTIONS(TCategory.MENU, "Options", "Options"), ABOUT(TCategory.MENU, "About", "About"), HELP(TCategory.MENU, "Help", "Help"), BUG_REPORT(TCategory.MENU, "BugReport", "Report a bug"), CLEAR(TCategory.MENU, "Clear", "Clear"), - DONATE(TCategory.MENU, "Donate", "Support the project"), // TODO: to update + DONATE(TCategory.MENU, "Donate", "Support the project"), HISTORY(TCategory.MENU, "History", "History"), INFO_PAGE(TCategory.MENU, "InfoPage", "Info"), NEW(TCategory.MENU, "New", "New"), @@ -102,16 +103,16 @@ public enum TKey { SHARE(TCategory.MENU, "Share", "Share"), SUPPORT(TCategory.MENU, "Support", "Support"), WEBSITE(TCategory.MENU, "Website", "Website"), - BACKUP_TABLE(TCategory.MENU, "Backups", "Backup List"), // TODO: add to json - CREATE_BACKUP(TCategory.MENU, "CreateBackup", "Create new backup"), // TODO: add to json - IMPORT_BACKUP(TCategory.MENU, "ImportBackup", "Import backups from Csv"), // TODO: add to json - EXPORT_BACKUP(TCategory.MENU, "ExportBackup", "Export backups to Csv"), // TODO: add to json - DASHBOARD(TCategory.MENU, "Dashboard", "Dashboard"), // TODO: add to json - GITHUB_PAGE(TCategory.MENU, "Github", "Github page"), // TODO: add to json - PAYPAL(TCategory.MENU, "Paypal", "Paypal"), // TODO: add to json - BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), // TODO: add to json - CONTACT_US(TCategory.MENU, "ContactUs", "Contact us"), // TODO: add to json - SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), // TODO: add to json + BACKUP_TABLE(TCategory.MENU, "Backups", "Backup List"), + CREATE_BACKUP(TCategory.MENU, "CreateBackup", "Create new backup"), + IMPORT_BACKUP(TCategory.MENU, "ImportBackup", "Import backups from Csv"), + EXPORT_BACKUP(TCategory.MENU, "ExportBackup", "Export backups to Csv"), + DASHBOARD(TCategory.MENU, "Dashboard", "Dashboard"), + GITHUB_PAGE(TCategory.MENU, "Github", "Github page"), + PAYPAL(TCategory.MENU, "Paypal", "Paypal"), + BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), + CONTACT_US(TCategory.MENU, "ContactUs", "Contact us"), + SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), // TabbedFrames @@ -120,21 +121,21 @@ public enum TKey { // BackupEntry PAGE_TITLE(TCategory.BACKUP_ENTRY, "PageTitle", "Backup Entry"), - PAGE_SUBTITLE_CREATE(TCategory.BACKUP_ENTRY, "PageSubtitleCreate", "Create Backup"), // TODO: add to json - PAGE_SUBTITLE_EDIT(TCategory.BACKUP_ENTRY, "PageSubtitleEdit", "Edit Backup"), // TODO: add to json - PAGE_SUBTITLE_INFO(TCategory.BACKUP_ENTRY, "PageSubtitleInfo", "Backup Information"), // TODO: add to json - PAGE_SUBTITLE_SETTINGS(TCategory.BACKUP_ENTRY, "PageSubtitleSettings", "Backups Settings"), // TODO: add to json + PAGE_SUBTITLE_CREATE(TCategory.BACKUP_ENTRY, "PageSubtitleCreate", "Create Backup"), + PAGE_SUBTITLE_EDIT(TCategory.BACKUP_ENTRY, "PageSubtitleEdit", "Edit Backup"), + PAGE_SUBTITLE_INFO(TCategory.BACKUP_ENTRY, "PageSubtitleInfo", "Backup Information"), + PAGE_SUBTITLE_SETTINGS(TCategory.BACKUP_ENTRY, "PageSubtitleSettings", "Backups Settings"), CURRENT_FILE(TCategory.BACKUP_ENTRY, "CurrentFile", "Current file"), NOTES(TCategory.BACKUP_ENTRY, "Notes", "Notes"), - PATHS(TCategory.BACKUP_ENTRY, "Paths", "Paths"), // TODO: add to json + PATHS(TCategory.BACKUP_ENTRY, "Paths", "Paths"), LAST_BACKUP(TCategory.BACKUP_ENTRY, "LastBackup", "Last backup"), SINGLE_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "SingleBackupButton", "Single Backup"), AUTO_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "AutoBackupButton", "Auto Backup"), AUTO_BACKUP_BUTTON_ON(TCategory.BACKUP_ENTRY, "AutoBackupButtonON", "Auto Backup (ON)"), AUTO_BACKUP_BUTTON_OFF(TCategory.BACKUP_ENTRY, "AutoBackupButtonOFF", "Auto Backup (OFF)"), - BACKUP_NAME_PLACEHOLDER(TCategory.BACKUP_ENTRY, "BackupNamePlaceholder", "Backup name (unique)"), // TODO: add to json - INITIAL_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "InitialPathPlaceholder", "Target path e.g. C:\\Users\\Admin\\Documents"), // TODO: to update - DESTINATION_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "DestinationPathPlaceholder", "Destination folder e.g. D:\\Backups"), // TODO: to update + BACKUP_NAME_PLACEHOLDER(TCategory.BACKUP_ENTRY, "BackupNamePlaceholder", "Backup name (unique)"), + INITIAL_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "InitialPathPlaceholder", "Target path e.g. C:\\Users\\Admin\\Documents"), + DESTINATION_PATH_PLACEHOLDER(TCategory.BACKUP_ENTRY, "DestinationPathPlaceholder", "Destination folder e.g. D:\\Backups"), BACKUP_NAME(TCategory.BACKUP_ENTRY, "BackupName", "Backup name"), BACKUP_NAME_TOOLTIP(TCategory.BACKUP_ENTRY, "BackupNameTooltip", "(Required) Backup name"), INITIAL_PATH_TOOLTIP(TCategory.BACKUP_ENTRY, "InitialPathTooltip", "(Required) Initial path"), @@ -149,16 +150,16 @@ public enum TKey { MAX_BACKUPS_TO_KEEP_TOOLTIP(TCategory.BACKUP_ENTRY, "MaxBackupsToKeepTooltip", "Maximum number of backups before removing the oldest."), // BackupList - BACKUP_LIST_TITLE(TCategory.BACKUP_LIST, "BackupListTitle", "Backup List"), // TODO: to update - BACKUP_LIST_DESCRIPTION(TCategory.BACKUP_LIST, "BackupListDescription", "Manage and monitor backup configurations, including creation, editing, scheduling, and execution."), // TODO: to update + BACKUP_LIST_TITLE(TCategory.BACKUP_LIST, "BackupListTitle", "Backup List"), + BACKUP_LIST_DESCRIPTION(TCategory.BACKUP_LIST, "BackupListDescription", "Manage and monitor backup configurations, including creation, editing, scheduling, and execution."), BACKUP_NAME_COLUMN(TCategory.BACKUP_LIST, "BackupNameColumn", "Backup Name"), INITIAL_PATH_COLUMN(TCategory.BACKUP_LIST, "InitialPathColumn", "Initial Path"), DESTINATION_PATH_COLUMN(TCategory.BACKUP_LIST, "DestinationPathColumn", "Destination Path"), LAST_BACKUP_COLUMN(TCategory.BACKUP_LIST, "LastBackupColumn", "Last Backup"), AUTOMATIC_BACKUP_COLUMN(TCategory.BACKUP_LIST, "AutomaticBackupColumn", "Automatic Backup"), NEXT_BACKUP_DATE_COLUMN(TCategory.BACKUP_LIST, "NextBackupDateColumn", "Next Backup Date"), - TIME_INTERVAL_COLUMN(TCategory.BACKUP_LIST, "TimeIntervalColumn", "Interval (gg.HH:mm)"), // TODO: to update - MAX_BACKUPS_COLUMN(TCategory.BACKUP_LIST, "MaxBackupsColumn", "Max Backups To Keep"), // TODO: add to json + TIME_INTERVAL_COLUMN(TCategory.BACKUP_LIST, "TimeIntervalColumn", "Interval (gg.HH:mm)"), + MAX_BACKUPS_COLUMN(TCategory.BACKUP_LIST, "MaxBackupsColumn", "Max Backups To Keep"), BACKUP_NAME_DETAIL(TCategory.BACKUP_LIST, "BackupNameDetail", "BackupName"), INITIAL_PATH_DETAIL(TCategory.BACKUP_LIST, "InitialPathDetail", "InitialPath"), DESTINATION_PATH_DETAIL(TCategory.BACKUP_LIST, "DestinationPathDetail", "DestinationPath"), @@ -201,13 +202,13 @@ public enum TKey { // User dialog USER_TITLE(TCategory.USER_DIALOG, "UserTitle", "Insert your data"), - USER_DESCRIPTION(TCategory.USER_DIALOG, "UserDescription", "Please enter your data to access the system"), // TODO: add to json + USER_DESCRIPTION(TCategory.USER_DIALOG, "UserDescription", "Please enter your data to access the system"), USER_NAME(TCategory.USER_DIALOG, "Name", "Name"), USER_SURNAME(TCategory.USER_DIALOG, "Surname", "Surname"), USER_EMAIL(TCategory.USER_DIALOG, "Email", "Email"), - USER_NAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserNamePlaceholder", "Enter your name"), // TODO: add to json - USER_SURNAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserSurnamePlaceholder", "Enter your surname"), // TODO: add to json - USER_EMAIL_PLACEHOLDER(TCategory.USER_DIALOG, "UserEmailPlaceholder", "Enter your email"), // TODO: add to json + USER_NAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserNamePlaceholder", "Enter your name"), + USER_SURNAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserSurnamePlaceholder", "Enter your surname"), + USER_EMAIL_PLACEHOLDER(TCategory.USER_DIALOG, "UserEmailPlaceholder", "Enter your email"), ERROR_MESSAGE_FOR_MISSING_DATA(TCategory.USER_DIALOG, "ErrorMessageForMissingData", "Please fill in all the required fields."), ERROR_MESSAGE_FOR_WRONG_EMAIL(TCategory.USER_DIALOG, "ErrorMessageForWrongEmail", "The provided email address is invalid. Please provide a correct one."), EMAIL_CONFIRMATION_SUBJECT(TCategory.USER_DIALOG, "EmailConfirmationSubject", "Thank you for choosing Backup Manager!"), @@ -300,62 +301,70 @@ public enum TKey { SUBSCRIPTION_EXPIRING_MESSAGE(TCategory.SUBSCRIPTION, "ExpiringMessage", "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it."), SUBSCRIPTION_EXPIRED_TITLE(TCategory.SUBSCRIPTION, "ExpiredTitle", "Backup Manager subscription expired"), SUBSCRIPTION_EXPIRED_MESSAGE(TCategory.SUBSCRIPTION, "ExpiredMessage", "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it."), - SUBSCRIPTION_ACTIVE(TCategory.SUBSCRIPTION, "ActiveLabel", "Active"), // TODO: add to json - SUBSCRIPTION_EXPIRING(TCategory.SUBSCRIPTION, "ExpiringLabel", "Expiring"), // TODO: add to json - SUBSCRIPTION_EXPIRED(TCategory.SUBSCRIPTION, "ExpiredLabel", "Expired"), // TODO: add to json - SUBSCRIPTION_STATUS(TCategory.SUBSCRIPTION, "Status", "Subscription status"), // TODO: add to json - SUBSCRIPTION_VALID_FROM(TCategory.SUBSCRIPTION, "ValidFrom", "Valid from"), // TODO: add to json - SUBSCRIPTION_VALID_TO(TCategory.SUBSCRIPTION, "ValidTo", "Valid to"), // TODO: add to json - SUBSCRIPTION_CONTACT_US(TCategory.SUBSCRIPTION, "ContactUs", "Contact us"), // TODO: add to json - SUBSCRIPTION_TO_EXTEND(TCategory.SUBSCRIPTION, "ToExtend", "to extend the subscription period."), // TODO: add to json + SUBSCRIPTION_ACTIVE(TCategory.SUBSCRIPTION, "ActiveLabel", "Active"), + SUBSCRIPTION_EXPIRING(TCategory.SUBSCRIPTION, "ExpiringLabel", "Expiring"), + SUBSCRIPTION_EXPIRED(TCategory.SUBSCRIPTION, "ExpiredLabel", "Expired"), + SUBSCRIPTION_STATUS(TCategory.SUBSCRIPTION, "Status", "Subscription status"), + SUBSCRIPTION_VALID_FROM(TCategory.SUBSCRIPTION, "ValidFrom", "Valid from"), + SUBSCRIPTION_VALID_TO(TCategory.SUBSCRIPTION, "ValidTo", "Valid to"), + SUBSCRIPTION_CONTACT_US(TCategory.SUBSCRIPTION, "ContactUs", "Contact us"), + SUBSCRIPTION_TO_EXTEND(TCategory.SUBSCRIPTION, "ToExtend", "to extend the subscription period."), // DASHBOARD - DASHBOARD_TITLE(TCategory.DASHBOARD, "DashboardTitle", "Backup Analytics Dashboard"), // TODO: add to json - DASHBOARD_CARD_TOTAL_CONFIGURATIONS(TCategory.DASHBOARD, "DashboardCardTotalConfigurations", "Total Backup Configurations"), // TODO: add to json - DASHBOARD_CARD_TOTAL_EXECUTIONS(TCategory.DASHBOARD, "DashboardCardTotalExecutions", "Total Backup Executions"), // TODO: add to json - DASHBOARD_CARD_SUCCESS_RATE(TCategory.DASHBOARD, "DashboardCardSuccessRate", "Success rate"), // TODO: add to json - DASHBOARD_CARD_AVG_DURATION(TCategory.DASHBOARD, "DashboardCardAvgDuration", "Avg Backup Duration"), // TODO: add to json - DASHBOARD_CARD_COMPRESSION_RATE(TCategory.DASHBOARD, "DashboardCardCompressionRate", "Compression Rate"), // TODO: add to json - DASHBOARD_CHART_EXECUTIONS(TCategory.DASHBOARD, "DashboardChartExecutions", "Backup Executions"), // TODO: add to json - DASHBOARD_CHART_AVG_DURATION(TCategory.DASHBOARD, "DashboardChartAvgDuration", "Average Backup Duration (min)"), // TODO: add to json + DASHBOARD_TITLE(TCategory.DASHBOARD, "DashboardTitle", "Backup Analytics Dashboard"), + DASHBOARD_CARD_TOTAL_CONFIGURATIONS(TCategory.DASHBOARD, "DashboardCardTotalConfigurations", "Total Backup Configurations"), + DASHBOARD_CARD_TOTAL_EXECUTIONS(TCategory.DASHBOARD, "DashboardCardTotalExecutions", "Total Backup Executions"), + DASHBOARD_CARD_SUCCESS_RATE(TCategory.DASHBOARD, "DashboardCardSuccessRate", "Success rate"), + DASHBOARD_CARD_AVG_DURATION(TCategory.DASHBOARD, "DashboardCardAvgDuration", "Avg Backup Duration"), + DASHBOARD_CARD_COMPRESSION_RATE(TCategory.DASHBOARD, "DashboardCardCompressionRate", "Compression Rate"), + DASHBOARD_CHART_EXECUTIONS(TCategory.DASHBOARD, "DashboardChartExecutions", "Backup Executions"), + DASHBOARD_CHART_AVG_DURATION(TCategory.DASHBOARD, "DashboardChartAvgDuration", "Average Backup Duration (min)"), // HISTORY_LOGS - HISTORY_LOGS_TITLE(TCategory.HISTORY_LOGS, "HistoryLogsTitle", "History logs"), // TODO: add to json - HISTORY_LOGS_DESCRIPTION(TCategory.HISTORY_LOGS, "HistoryLogsDescription", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."), // TODO: add to json + HISTORY_LOGS_TITLE(TCategory.HISTORY_LOGS, "HistoryLogsTitle", "History logs"), + HISTORY_LOGS_DESCRIPTION(TCategory.HISTORY_LOGS, "HistoryLogsDescription", "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time."), // ABOUT - ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), // TODO: add to json - ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><p>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</p></html>"), // TODO: add to json + ABOUT_SYSTEM_INFORMATION(TCategory.ABOUT, "AboutSystemInformation", "System Information"), + ABOUT_MESSAGE_BODY(TCategory.ABOUT, "AboutMessageBody", "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><p>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</p></html>"), // SETTINGS - SETTINGS_LAYOUT_TAB(TCategory.SETTINGS, "SettingsLayoutTab", "Layout"), // TODO: add to json - SETTINGS_STYLE_TAB(TCategory.SETTINGS, "SettingsStyleTab", "Style"), // TODO: add to json - SETTINGS_WINDOWS_LAYOUT(TCategory.SETTINGS, "SettingsWindowsLayout", "Windows Layout"), // TODO: add to json - SETTINGS_WINDOWS_RIGHT(TCategory.SETTINGS, "SettingsWindowsRight", "Right to Left"), // TODO: add to json - SETTINGS_WINDOWS_FULL(TCategory.SETTINGS, "SettingsWindowsFull", "Full Window Content"), // TODO: add to json - SETTINGS_DRAWER_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLayout", "Drawer layout"), // TODO: add to json - SETTINGS_DRAWER_LEFT(TCategory.SETTINGS, "SettingsDrawerLeft", "Left"), // TODO: add to json - SETTINGS_DRAWER_LEADING(TCategory.SETTINGS, "SettingsDrawerLeading", "Leading"), // TODO: add to json - SETTINGS_DRAWER_TRAILING(TCategory.SETTINGS, "SettingsDrawerTrailing", "Trailing"), // TODO: add to json - SETTINGS_DRAWER_RIGHT(TCategory.SETTINGS, "SettingsDrawerRight", "Right"), // TODO: add to json - SETTINGS_DRAWER_TOP(TCategory.SETTINGS, "SettingsDrawerTop", "Top"), // TODO: add to json - SETTINGS_DRAWER_BOTTOM(TCategory.SETTINGS, "SettingsDrawerBottom", "Bottom"), // TODO: add to json - SETTINGS_MODAL_OPTION(TCategory.SETTINGS, "SettingsModalOption", "Default modal option"), // TODO: add to json - SETTINGS_MODAL_ANIMATION(TCategory.SETTINGS, "SettingsModalAnimation", "Animation enable"), // TODO: add to json - SETTINGS_MODAL_CLOSE(TCategory.SETTINGS, "SettingsModalClose", "Close on pressed escape"), // TODO: add to json - SETTINGS_LANGUAGES_LAYOUT(TCategory.SETTINGS, "SettingsLanguagesLayout", "Language"), // TODO: add to json - SETTINGS_ACCENT_LAYOUT(TCategory.SETTINGS, "SettingsAccentLayout", "Accent color"), // TODO: add to json - SETTINGS_COLOR_PICKER_LAYOUT(TCategory.SETTINGS, "SettingsColorPickerLayout", "Color Picker"), // TODO: add to json - SETTINGS_DRAWER_LINE_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLineLayout", "Drawer line style"), // TODO: add to json - SETTINGS_DRAWER_LINE_CURVED(TCategory.SETTINGS, "SettingsDrawerLineCurved", "Curved line style"), // TODO: add to json - SETTINGS_DRAWER_DOT_LINE(TCategory.SETTINGS, "SettingsDrawerDotLine", "Straight dot line style"), // TODO: add to json - SETTINGS_LINE_STYLE_LAYOUT(TCategory.SETTINGS, "SettingsLineStyleLayout", "Line style option"), // TODO: add to json - SETTINGS_LINE_STYLE_RETTANGLE(TCategory.SETTINGS, "SettingsLineStyleRettangle", "Rettangle"), // TODO: add to json - SETTINGS_LINE_STYLE_ELLIPSE(TCategory.SETTINGS, "SettingsLineStyleEllipse", "Ellipse"), // TODO: add to json - SETTINGS_LINE_STYLE_LINE(TCategory.SETTINGS, "SettingsLineStyleLine", "Line"), // TODO: add to json - SETTINGS_LINE_STYLE_CURVED(TCategory.SETTINGS, "SettingsLineStyleCurved", "Curved"), // TODO: add to json - SETTINGS_COLOR_OPTION_LAYOUT(TCategory.SETTINGS, "SettingsColorOptionLayout", "Color option"), // TODO: add to json - SETTINGS_COLOR_OPTION_PAINTED(TCategory.SETTINGS, "SettingsColorOptionPainted", "Paint selected line color"); // TODO: add to json + SETTINGS_LAYOUT_TAB(TCategory.SETTINGS, "SettingsLayoutTab", "Layout"), + SETTINGS_STYLE_TAB(TCategory.SETTINGS, "SettingsStyleTab", "Style"), + SETTINGS_WINDOWS_LAYOUT(TCategory.SETTINGS, "SettingsWindowsLayout", "Windows Layout"), + SETTINGS_WINDOWS_RIGHT(TCategory.SETTINGS, "SettingsWindowsRight", "Right to Left"), + SETTINGS_WINDOWS_FULL(TCategory.SETTINGS, "SettingsWindowsFull", "Full Window Content"), + SETTINGS_DRAWER_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLayout", "Drawer layout"), + SETTINGS_DRAWER_LEFT(TCategory.SETTINGS, "SettingsDrawerLeft", "Left"), + SETTINGS_DRAWER_LEADING(TCategory.SETTINGS, "SettingsDrawerLeading", "Leading"), + SETTINGS_DRAWER_TRAILING(TCategory.SETTINGS, "SettingsDrawerTrailing", "Trailing"), + SETTINGS_DRAWER_RIGHT(TCategory.SETTINGS, "SettingsDrawerRight", "Right"), + SETTINGS_DRAWER_TOP(TCategory.SETTINGS, "SettingsDrawerTop", "Top"), + SETTINGS_DRAWER_BOTTOM(TCategory.SETTINGS, "SettingsDrawerBottom", "Bottom"), + SETTINGS_MODAL_OPTION(TCategory.SETTINGS, "SettingsModalOption", "Default modal option"), + SETTINGS_MODAL_ANIMATION(TCategory.SETTINGS, "SettingsModalAnimation", "Animation enable"), + SETTINGS_MODAL_CLOSE(TCategory.SETTINGS, "SettingsModalClose", "Close on pressed escape"), + SETTINGS_LANGUAGES_LAYOUT(TCategory.SETTINGS, "SettingsLanguagesLayout", "Language"), + SETTINGS_ACCENT_LAYOUT(TCategory.SETTINGS, "SettingsAccentLayout", "Accent color"), + SETTINGS_COLOR_PICKER_LAYOUT(TCategory.SETTINGS, "SettingsColorPickerLayout", "Color Picker"), + SETTINGS_DRAWER_LINE_LAYOUT(TCategory.SETTINGS, "SettingsDrawerLineLayout", "Drawer line style"), + SETTINGS_DRAWER_LINE_CURVED(TCategory.SETTINGS, "SettingsDrawerLineCurved", "Curved line style"), + SETTINGS_DRAWER_DOT_LINE(TCategory.SETTINGS, "SettingsDrawerDotLine", "Straight dot line style"), + SETTINGS_LINE_STYLE_LAYOUT(TCategory.SETTINGS, "SettingsLineStyleLayout", "Line style option"), + SETTINGS_LINE_STYLE_RETTANGLE(TCategory.SETTINGS, "SettingsLineStyleRettangle", "Rettangle"), + SETTINGS_LINE_STYLE_ELLIPSE(TCategory.SETTINGS, "SettingsLineStyleEllipse", "Ellipse"), + SETTINGS_LINE_STYLE_LINE(TCategory.SETTINGS, "SettingsLineStyleLine", "Line"), + SETTINGS_LINE_STYLE_CURVED(TCategory.SETTINGS, "SettingsLineStyleCurved", "Curved"), + SETTINGS_COLOR_OPTION_LAYOUT(TCategory.SETTINGS, "SettingsColorOptionLayout", "Color option"), + SETTINGS_COLOR_OPTION_PAINTED(TCategory.SETTINGS, "SettingsColorOptionPainted", "Paint selected line color"), + + // SEARCH BAR + SEARCH_TITLE(TCategory.SEARCH_BAR, "SearcTitle", "Search..."), + SEARCH_NO_RECENT(TCategory.SEARCH_BAR, "SearchNoRecent", "No recent searches"), + SEARCH_NO_RESULT(TCategory.SEARCH_BAR, "SearchNoResult", "No result for"), + SEARCH_FAVORITE(TCategory.SEARCH_BAR, "SearchFavorite", "Favorite"), + SEARCH_RECENT(TCategory.SEARCH_BAR, "SearchRecent", "Recents"), + ; private final TCategory category; diff --git a/src/main/java/backupmanager/gui/component/FormSearchPanel.java b/src/main/java/backupmanager/gui/component/FormSearchPanel.java index 8577fed9..2f77051a 100644 --- a/src/main/java/backupmanager/gui/component/FormSearchPanel.java +++ b/src/main/java/backupmanager/gui/component/FormSearchPanel.java @@ -35,6 +35,8 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.icons.FlatMenuArrowIcon; +import backupmanager.Enums.Translations; +import backupmanager.Enums.Translations.TKey; import backupmanager.gui.menu.MyMenuValidation; import backupmanager.gui.svg.SVGIconUIColor; import backupmanager.gui.system.Form; @@ -62,7 +64,7 @@ private void init() { setLayout(new MigLayout("fillx,insets 0,wrap", "[fill,500]")); textSearch = new JTextField(); panelResult = new PanelResult(); - textSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Search..."); + textSearch.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, Translations.get(TKey.SEARCH_TITLE)); textSearch.putClientProperty(FlatClientProperties.TEXT_FIELD_LEADING_ICON, new FlatSVGIcon("icons/search.svg", 0.4f)); textSearch.putClientProperty(FlatClientProperties.STYLE, "" + "border:3,3,3,3;" + @@ -228,7 +230,7 @@ private void showRecentResult() { panelResult.removeAll(); listItems.clear(); if (recentSearch != null && !recentSearch.isEmpty()) { - panelResult.add(createLabel("Recent")); + panelResult.add(createLabel(Translations.get(TKey.SEARCH_RECENT))); for (Item item : recentSearch) { checkComponentOrientation(item); panelResult.add(item); @@ -237,7 +239,7 @@ private void showRecentResult() { } if (favoriteSearch != null && !favoriteSearch.isEmpty()) { - panelResult.add(createLabel("Favorite")); + panelResult.add(createLabel(Translations.get(TKey.SEARCH_FAVORITE))); for (Item item : favoriteSearch) { checkComponentOrientation(item); panelResult.add(item); @@ -299,7 +301,7 @@ private Item createRecentItem(String name, boolean favorite) { private Component createNoResult(String text) { JPanel panel = new JPanel(new MigLayout("insets 15 5 15 5,al center,gapx 1")); - JLabel label = new JLabel("No result for \""); + JLabel label = new JLabel(Translations.get(TKey.SEARCH_NO_RECENT) + " \""); JLabel labelEnd = new JLabel("\""); label.putClientProperty(FlatClientProperties.STYLE, "" + "foreground:$Label.disabledForeground;"); @@ -342,7 +344,7 @@ public NoRecentResult() { private void init() { setLayout(new MigLayout("insets 15 5 15 5,al center")); - JLabel label = new JLabel("No recent searches"); + JLabel label = new JLabel(Translations.get(TKey.SEARCH_NO_RECENT)); label.putClientProperty(FlatClientProperties.STYLE, "" + "foreground:$Label.disabledForeground;" + "font:bold;"); @@ -402,8 +404,8 @@ private void init() { private void clearSelected() { for (Component com : getParent().getComponents()) { - if (com instanceof JButton) { - ((JButton) com).setSelected(false); + if (com instanceof JButton jButton) { + jButton.setSelected(false); } } } @@ -488,7 +490,7 @@ protected void addFavorite() { Item item = new Item(data, form, isRecent, true); checkComponentOrientation(item); if (index == null) { - panelResult.add(createLabel("Favorite")); + panelResult.add(createLabel(Translations.get(TKey.SEARCH_FAVORITE))); panelResult.add(item); listItems.add(item); } else { diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index 390deeb9..63653025 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -72,7 +72,7 @@ protected void init() { "tabType:card"); tabbedPane.addTab(Translations.get(TKey.SETTINGS_LAYOUT_TAB), createLayoutOption()); - tabbedPane.addTab(Translations.get(TKey.SETTINGS_LAYOUT_TAB), createStyleOption()); + tabbedPane.addTab(Translations.get(TKey.SETTINGS_STYLE_TAB), createStyleOption()); add(tabbedPane, "gapy 1 0"); add(createThemes()); } diff --git a/src/main/resources/res/languages/deu.json b/src/main/resources/res/languages/deu.json index babe1ce7..5c5fca08 100644 --- a/src/main/resources/res/languages/deu.json +++ b/src/main/resources/res/languages/deu.json @@ -1,214 +1,300 @@ { - "TrayIcon": { - "SuccessMessage": "\nDas Backup wurde erfolgreich abgeschlossen:", - "ExitAction": "Beenden", - "ErrorMessageFilesNotExisting": "\nFehler beim automatischen Backup.\nEin oder beide Pfade existieren nicht!", - "TrayTooltip": "Backup-Dienst", - "OpenAction": "Schnellzugriff", - "ErrorMessageInputMissing": "\nFehler beim automatischen Backup.\nEingabe fehlt!", - "ErrorMessageSamePaths": "\nFehler beim automatischen Backup.\nDer Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!" - }, - "If value is null or empty, fall back to the default value from the enum": {}, - "Dialogs": { - "WarningGenericTitle": "Warnung", - "SuccessfullyExportedToCsvMessage": "Backups erfolgreich als CSV exportiert!", - "ErrorMessageForPathNotExisting": "Ein oder beide Pfade existieren nicht!", - "ConfirmationDeletionMessage": "Sind Sie sicher, dass Sie die ausgewählten Zeilen löschen möchten?", - "ExceptionMessageReportMessage": "Bitte melden Sie diesen Fehler, entweder mit einem Screenshot oder indem Sie den folgenden Fehlertext kopieren (es ist hilfreich, eine Beschreibung der durchgeführten Aktionen vor dem Fehler anzugeben):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Fehler beim Exportieren der Backups in CSV: ", - "ErrorGenericTitle": "Fehler", - "ErrorMessageForExportingToPdf": "Fehler beim Exportieren der Backups in PDF: ", - "BackupListCorrectlyImportedTitle": "Menü Importieren", - "ErrorWrongTimeInterval": "Das Zeitintervall ist nicht korrekt", - "ErrorMessageOpenHistoryFile": "Fehler beim Öffnen der Verlaufsdatei.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "Der Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Freigabelink wurde in die Zwischenablage kopiert!", - "ErrorMessageForSavingFileWithPathsEmpty": "Die Datei konnte nicht gespeichert werden. Sowohl der Anfangs- als auch der Zielpfad müssen angegeben werden und dürfen nicht leer sein", - "BackupListCorrectlyExportedMessage": "Backup-Liste erfolgreich auf den Desktop exportiert!", - "BackupSavedCorrectlyMessage": "erfolgreich gespeichert!", - "SettedEveryMessage": "\nWird eingestellt auf alle", - "WarningBackupAlreadyInProgressMessage": "Es läuft bereits eine Sicherung. Es ist nicht möglich, parallele Sicherungen durchzuführen.", - "WarningShortTimeIntervalMessage": "Das ausgewählte Zeitintervall ist sehr kurz. Für eine optimale Leistung empfehlen wir, es auf mindestens eine Stunde einzustellen. Möchten Sie dennoch fortfahren?", - "ConfirmationMessageCancelAutoBackup": "Sind Sie sicher, dass Sie automatische Backups für diesen Eintrag deaktivieren möchten?", - "ErrorMessageCountingFiles": "Fehler beim Zählen der zu sichernden Dateien.", - "ErrorMessageForFolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Fehler beim Backup-Vorgang: Der Anfangspfad ist falsch!", - "ErrorMessageInputMissingGeneric": "Eingabe fehlt!", - "BackupNameInput": "Name des Backups", - "CsvNameMessageInput": "Geben Sie den Namen der CSV-Datei ein.", - "ConfirmationDeletionTitle": "Löschen bestätigen", - "ErrorMessageForWrongFileExtensionMessage": "Fehler: Bitte wählen Sie eine gültige JSON-Datei aus.", - "ErrorMessageZippingGeneric": "Fehler beim Komprimieren der Dateien.", - "ErrorMessageNotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht.", - "ErrorMessageZippingIO": "Fehler beim Komprimieren der Dateien: E/A-Fehler.", - "SuccessfullyExportedToPdfMessage": "Backups erfolgreich als PDF exportiert!", - "DuplicatedFileNameMessage": "Datei existiert bereits. Überschreiben?", - "AutoBackupActivatedMessage": "Auto-Backup wurde aktiviert", - "DaysMessage": " Tage", - "ExceptionMessageReportButton": "Problem melden", - "ErrorMessageInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche.", - "ExceptionMessageClipboardMessage": "Fehlertext wurde in die Zwischenablage kopiert.", - "SuccessGenericTitle": "Erfolg", - "ConfirmationMessageForClear": "Sind Sie sicher, dass Sie die Felder bereinigen möchten?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Backup-Liste erfolgreich importiert!", - "DuplicatedBackupNameMessage": "Ein Backup mit demselben Namen existiert bereits. Möchten Sie es überschreiben?", - "ExceptionMessageClipboardButton": "In Zwischenablage kopieren", - "ErrorMessageZippingSecurity": "Fehler beim Komprimieren der Dateien: Sicherheitsfehler.", - "InterruptBackupProcessMessage": "Sind Sie sicher, dass Sie dieses Backup abbrechen möchten?", - "BackupListCorrectlyExportedTitle": "Menü Exportieren", - "ConfirmationMessageForUnsavedChanges": "Es gibt nicht gespeicherte Änderungen. Möchten Sie sie speichern, bevor Sie zu einem anderen Backup wechseln?", - "ExceptionMessageTitle": "Fehler...", - "PdfNameMessageInput": "Geben Sie den Namen der PDF-Datei ein.", - "ErrorMessageNotSupportedEmail": "Ihr System unterstützt das Senden von E-Mails direkt aus dieser Anwendung nicht.", - "ErrorMessageForSavingFile": "Fehler beim Speichern der Datei", - "ErrorMessageUnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.", - "ConfirmationMessageBeforeDeleteBackup": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "ErrorMessageOpeningWebsite": "Die Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut.", - "ErrorSavingBackupMessage": "Fehler beim Speichern des Backups", - "ConfirmationRequiredTitle": "Bestätigung erforderlich", - "BackupNameAlreadyUsedMessage": "Backup-Name bereits verwendet!", - "ErrorMessageForWrongFileExtensionTitle": "Ungültige Datei", - "BackupSavedCorrectlyTitle": "Backup gespeichert" - }, - "User dialog": {}, - "TimePickerDialog": { - "TimeIntervalTitle": "Zeitintervall für Auto-Backup", - "Days": "Tage", - "Hours": "Stunden", - "SpinnerTooltip": "Mausrad zum Anpassen des Wertes", - "Description": "Wählen Sie, wie oft das automatische Backup durchgeführt werden soll, \nindem Sie die Frequenz in Tagen, Stunden und Minuten festlegen.", - "Minutes": "Minuten" - }, - "HISTORY_LOGS": {}, - "Constructor to assign both key and default value": {}, - "Subscription": { - "ExpiredTitle": "Backup Manager Abonnement abgelaufen", - "ExpiringMessage": "Ihr Backup Manager Abonnement läuft bald ab.\nAutomatische Backups werden bis zum Ablaufdatum weiterhin ausgeführt.\nBitte kontaktieren Sie den Support, um es zu verlängern.", - "ExpiringTitle": "Backup Manager Abonnement läuft bald ab", - "ExpiredMessage": "Ihr Backup Manager Abonnement ist abgelaufen.\nAutomatische Backups werden nicht mehr ausgeführt.\nBitte kontaktieren Sie den Support, um es zu reaktivieren." - }, - "ProgressBackupFrame": { - "StatusLoading": "Lädt...", - "ProgressBackupTitle": "Backup läuft", - "StatusCompleted": "Backup abgeschlossen!" - }, - "Lookup by keyName (JSON key)": {}, - "DASHBOARD": {}, - "ABOUT": {}, - "General": { - "AppName": "Backup Manager", - "CancelButton": "Abbrechen", - "Backup": "Backup", - "Version": "Version", - "ApplyButton": "Anwenden", - "OkButton": "Ok", - "From": "Von", - "SaveButton": "Speichern", - "CloseButton": "Schließen", - "To": "Nach" - }, - "Menu": { - "Import": "Backup-Liste importieren", - "Save": "Speichern", - "About": "Über", - "File": "Datei", - "Support": "Support", - "BugReport": "Fehler melden", - "Quit": "Beenden", - "Options": "Optionen", - "New": "Neu", - "Clear": "Löschen", - "Share": "Teilen", - "Preferences": "Einstellungen", - "Website": "Webseite", - "SaveWithName": "Speichern unter", - "Export": "Backup-Liste exportieren", - "Help": "Hilfe", - "InfoPage": "Info", - "History": "Verlauf" - }, - "Use fromKeyName to get the TKey from the JSON key": {}, - "Clear previous translations to avoid stale values when switching languages": {}, - "BackupList": { - "NotesDetail": "Notizen", - "BackupNameDetail": "Backup-Name", - "ExportAsPdfTooltip": "Exportieren als PDF", - "EditPopup": "Bearbeiten", - "ResearchBarTooltip": "Suchleiste", - "InitialPathDetail": "Anfangspfad", - "ExportAs": "Exportieren als: ", - "RenameBackupPopup": "Backup umbenennen", - "NextBackupDateDetail": "NächstesBackup", - "AutoBackupPopup": "Auto-Backup", - "BackupNameColumn": "Backup-Name", - "BackupPopup": "Backup", - "BackupCountDetail": "Backup-Anzahl", - "ExportAsCsvTooltip": "Exportieren als CSV", - "InitialPathColumn": "Anfangspfad", - "MaxBackupsToKeepDetail": "MaximaleSicherungenBehalten", - "DeletePopup": "Löschen", - "OpenDestinationFolderPopup": "Zielpfad öffnen", - "LastBackupColumn": "Letztes Backup", - "SingleBackupPopup": "Einzel-Backup ausführen", - "AddBackupTooltip": "Neues Backup hinzufügen", - "CopyTextPopup": "Text kopieren", - "DestinationPathDetail": "Zielpfad", - "CopyDestinationPathPopup": "Zielpfad kopieren", - "LastBackupDetail": "LetztesBackup", - "OpenInitialFolderPopup": "Anfangspfad öffnen", - "ResearchBarPlaceholder": "Suchen...", - "InterruptPopup": "Unterbrechen", - "DuplicatePopup": "Duplizieren", - "NextBackupDateColumn": "Nächstes Backup-Datum", - "DestinationPathColumn": "Zielpfad", - "AutomaticBackupColumn": "Automatisches Backup", - "LastUpdateDateDetail": "LetzteAktualisierung", - "TimeIntervalDetail": "Zeitintervall", - "CopyBackupNamePopup": "Backup-Name kopieren", - "CreationDateDetail": "Erstellungsdatum", - "CopyInitialPathPopup": "Anfangspfad kopieren" - }, - "TabbedFrames": { - "BackupEntry": "Backup-Eintrag", - "BackupList": "Backup-Liste" - }, - "UserDialog": { - "ErrorMessageForMissingData": "Bitte füllen Sie alle erforderlichen Felder aus.", - "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team", - "EmailConfirmationSubject": "Vielen Dank, dass Sie sich für Backup Manager entschieden haben!", - "Surname": "Nachname", - "ErrorMessageForWrongEmail": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte Adresse an.", - "Name": "Vorname", - "Email": "E-Mail", - "UserTitle": "Geben Sie Ihre Daten ein" - }, - "SETTINGS": {}, - "BackupEntry": { - "TimePickerTooltip": "Zeitwähler", - "MaxBackupsToKeepTooltip": "Maximale Anzahl an Sicherungen, bevor die ältesten entfernt werden.", - "BackupName": "Sicherungsname", - "AutoBackupButton": "Auto-Backup", - "InitialPathTooltip": "(Erforderlich) Anfangspfad", - "CurrentFile": "Aktuelle Datei", - "InitialFileChooserTooltip": "Dateiexplorer öffnen", - "LastBackup": "Letztes Backup", - "SingleBackupButton": "Einzel-Backup", - "AutoBackupButtonON": "Auto-Backup (AN)", - "AutoBackupButtonOFF": "Auto-Backup (AUS)", - "DestinationPathTooltip": "(Erforderlich) Zielpfad", - "MaxBackupsToKeep": "Maximale Anzahl an Sicherungen beibehalten", - "SingleBackupTooltip": "Backup durchführen", - "AutoBackupTooltip": "Automatisches Backup aktivieren/deaktivieren", - "NotesTooltip": "(Optional) Backup-Beschreibung", - "DestinationFileChooserTooltip": "Dateiexplorer öffnen", - "BackupNameTooltip": "(Erforderlich) Sicherungsname", - "PageTitle": "Backup-Eintrag", - "Notes": "Notizen" - } + "General": { + "AppName": "Backup Manager", + "CancelButton": "Abbrechen", + "Backup": "Backup", + "Version": "Version", + "ApplyButton": "Anwenden", + "OkButton": "Ok", + "From": "Von", + "SaveButton": "Speichern", + "CloseButton": "Schließen", + "CreateButton": "Erstellen", + "EditButton": "Bearbeiten", + "DeleteButton": "Löschen", + "QuickSearch": "Schnellsuche", + "To": "Nach" + }, + "TrayIcon": { + "SuccessMessage": "\nDas Backup wurde erfolgreich abgeschlossen:", + "ExitAction": "Beenden", + "ErrorMessageFilesNotExisting": "\nFehler beim automatischen Backup.\nEin oder beide Pfade existieren nicht!", + "TrayTooltip": "Backup-Dienst", + "OpenAction": "Schnellzugriff", + "ErrorMessageInputMissing": "\nFehler beim automatischen Backup.\nEingabe fehlt!", + "ErrorMessageSamePaths": "\nFehler beim automatischen Backup.\nDer Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!" + }, + "Dialogs": { + "WarningGenericTitle": "Warnung", + "SuccessfullyExportedToCsvMessage": "Backups erfolgreich als CSV exportiert!", + "ErrorMessageForPathNotExisting": "Ein oder beide Pfade existieren nicht!", + "ConfirmationDeletionMessage": "Sind Sie sicher, dass Sie die ausgewählten Zeilen löschen möchten?", + "ExceptionMessageReportMessage": "Bitte melden Sie diesen Fehler, entweder mit einem Screenshot oder indem Sie den folgenden Fehlertext kopieren (es ist hilfreich, eine Beschreibung der durchgeführten Aktionen vor dem Fehler anzugeben):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Fehler beim Exportieren der Backups in CSV: ", + "ErrorGenericTitle": "Fehler", + "ErrorMessageForExportingToPdf": "Fehler beim Exportieren der Backups in PDF: ", + "BackupListCorrectlyImportedTitle": "Menü Importieren", + "ErrorWrongTimeInterval": "Das Zeitintervall ist nicht korrekt", + "ErrorMessageOpenHistoryFile": "Fehler beim Öffnen der Verlaufsdatei.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Der Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Freigabelink wurde in die Zwischenablage kopiert!", + "ErrorMessageForSavingFileWithPathsEmpty": "Die Datei konnte nicht gespeichert werden. Sowohl der Anfangs- als auch der Zielpfad müssen angegeben werden und dürfen nicht leer sein", + "BackupListCorrectlyExportedMessage": "Backup-Liste erfolgreich auf den Desktop exportiert!", + "BackupSavedCorrectlyMessage": "erfolgreich gespeichert!", + "SettedEveryMessage": "\nWird eingestellt auf alle", + "WarningBackupAlreadyInProgressMessage": "Es läuft bereits eine Sicherung. Es ist nicht möglich, parallele Sicherungen durchzuführen.", + "WarningShortTimeIntervalMessage": "Das ausgewählte Zeitintervall ist sehr kurz. Für eine optimale Leistung empfehlen wir, es auf mindestens eine Stunde einzustellen. Möchten Sie dennoch fortfahren?", + "ConfirmationMessageCancelAutoBackup": "Sind Sie sicher, dass Sie automatische Backups für diesen Eintrag deaktivieren möchten?", + "ErrorMessageCountingFiles": "Fehler beim Zählen der zu sichernden Dateien.", + "ErrorMessageForFolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Fehler beim Backup-Vorgang: Der Anfangspfad ist falsch!", + "ErrorMessageInputMissingGeneric": "Eingabe fehlt!", + "BackupNameInput": "Name des Backups", + "CsvNameMessageInput": "Geben Sie den Namen der CSV-Datei ein.", + "ConfirmationDeletionTitle": "Löschen bestätigen", + "ErrorMessageForWrongFileExtensionMessage": "Fehler: Bitte wählen Sie eine gültige JSON-Datei aus.", + "ErrorMessageZippingGeneric": "Fehler beim Komprimieren der Dateien.", + "ErrorMessageNotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht.", + "ErrorMessageZippingIO": "Fehler beim Komprimieren der Dateien: E/A-Fehler.", + "SuccessfullyExportedToPdfMessage": "Backups erfolgreich als PDF exportiert!", + "DuplicatedFileNameMessage": "Datei existiert bereits. Überschreiben?", + "AutoBackupActivatedMessage": "Auto-Backup wurde aktiviert", + "DaysMessage": " Tage", + "ExceptionMessageReportButton": "Problem melden", + "ErrorMessageInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche.", + "ExceptionMessageClipboardMessage": "Fehlertext wurde in die Zwischenablage kopiert.", + "SuccessGenericTitle": "Erfolg", + "ConfirmationMessageForClear": "Sind Sie sicher, dass Sie die Felder bereinigen möchten?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Backup-Liste erfolgreich importiert!", + "DuplicatedBackupNameMessage": "Ein Backup mit demselben Namen existiert bereits. Möchten Sie es überschreiben?", + "ExceptionMessageClipboardButton": "In Zwischenablage kopieren", + "ErrorMessageZippingSecurity": "Fehler beim Komprimieren der Dateien: Sicherheitsfehler.", + "InterruptBackupProcessMessage": "Sind Sie sicher, dass Sie dieses Backup abbrechen möchten?", + "BackupListCorrectlyExportedTitle": "Menü Exportieren", + "ConfirmationMessageForUnsavedChanges": "Es gibt nicht gespeicherte Änderungen. Möchten Sie sie speichern, bevor Sie zu einem anderen Backup wechseln?", + "ExceptionMessageTitle": "Fehler...", + "PdfNameMessageInput": "Geben Sie den Namen der PDF-Datei ein.", + "ErrorMessageNotSupportedEmail": "Ihr System unterstützt das Senden von E-Mails direkt aus dieser Anwendung nicht.", + "ErrorMessageForSavingFile": "Fehler beim Speichern der Datei", + "ErrorMessageUnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.", + "ConfirmationMessageBeforeDeleteBackup": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "ErrorMessageOpeningWebsite": "Die Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut.", + "ErrorSavingBackupMessage": "Fehler beim Speichern des Backups", + "ConfirmationRequiredTitle": "Bestätigung erforderlich", + "BackupNameAlreadyUsedMessage": "Backup-Name bereits verwendet!", + "ErrorMessageForWrongFileExtensionTitle": "Ungültige Datei", + "BackupSavedCorrectlyTitle": "Backup gespeichert" + }, + "TimePickerDialog": { + "TimeIntervalTitle": "Zeitintervall für Auto-Backup", + "Days": "Tage", + "Hours": "Stunden", + "SpinnerTooltip": "Mausrad zum Anpassen des Wertes", + "Description": "Wählen Sie, wie oft das automatische Backup durchgeführt werden soll, \nindem Sie die Frequenz in Tagen, Stunden und Minuten festlegen.", + "Minutes": "Minuten" + }, + "Subscription": { + "ExpiredTitle": "Backup Manager Abonnement abgelaufen", + "ExpiringMessage": "Ihr Backup Manager Abonnement läuft bald ab.\nAutomatische Backups werden bis zum Ablaufdatum weiterhin ausgeführt.\nBitte kontaktieren Sie den Support, um es zu verlängern.", + "ExpiringTitle": "Backup Manager Abonnement läuft bald ab", + "ExpiredMessage": "Ihr Backup Manager Abonnement ist abgelaufen.\nAutomatische Backups werden nicht mehr ausgeführt.\nBitte kontaktieren Sie den Support, um es zu reaktivieren.", + "ActiveLabel": "Aktiv", + "ExpiringLabel": "Läuft bald ab", + "ExpiredLabel": "Abgelaufen", + "Status": "Abonnementstatus", + "ValidFrom": "Gültig ab", + "ValidTo": "Gültig bis", + "ContactUs": "Kontaktieren Sie uns", + "ToExtend": "um den Abonnementzeitraum zu verlängern." + }, + "ProgressBackupFrame": { + "StatusLoading": "Lädt...", + "ProgressBackupTitle": "Backup läuft", + "StatusCompleted": "Backup abgeschlossen!" + }, + "Menu": { + "Import": "Backup-Liste importieren", + "Save": "Speichern", + "About": "Über", + "File": "Datei", + "Support": "Support", + "BugReport": "Fehler melden", + "Quit": "Beenden", + "Options": "Optionen", + "New": "Neu", + "Clear": "Löschen", + "Share": "Teilen", + "Preferences": "Einstellungen", + "Website": "Webseite", + "SaveWithName": "Speichern unter", + "Export": "Backup-Liste exportieren", + "Help": "Hilfe", + "InfoPage": "Info", + "History": "Verlauf", + "SubmenuMain": "HAUPT", + "SubmenuOther": "ANDERE", + "Donate": "Projekt unterstützen", + "Backups": "Backup-Liste", + "CreateBackup": "Neues Backup erstellen", + "ImportBackup": "Backups aus CSV importieren", + "ExportBackup": "Backups in CSV exportieren", + "Dashboard": "Dashboard", + "Github": "GitHub-Seite", + "Paypal": "PayPal", + "BuyMeACoffe": "Spendiere mir einen Kaffee", + "ContactUs": "Kontaktieren Sie uns", + "Subscription": "Abonnement" + }, + "BackupList": { + "NotesDetail": "Notizen", + "BackupNameDetail": "Backup-Name", + "ExportAsPdfTooltip": "Exportieren als PDF", + "EditPopup": "Bearbeiten", + "ResearchBarTooltip": "Suchleiste", + "InitialPathDetail": "Anfangspfad", + "ExportAs": "Exportieren als: ", + "RenameBackupPopup": "Backup umbenennen", + "NextBackupDateDetail": "NächstesBackup", + "AutoBackupPopup": "Auto-Backup", + "BackupNameColumn": "Backup-Name", + "BackupPopup": "Backup", + "BackupCountDetail": "Backup-Anzahl", + "ExportAsCsvTooltip": "Exportieren als CSV", + "InitialPathColumn": "Anfangspfad", + "MaxBackupsToKeepDetail": "MaximaleSicherungenBehalten", + "DeletePopup": "Löschen", + "OpenDestinationFolderPopup": "Zielpfad öffnen", + "LastBackupColumn": "Letztes Backup", + "SingleBackupPopup": "Einzel-Backup ausführen", + "AddBackupTooltip": "Neues Backup hinzufügen", + "CopyTextPopup": "Text kopieren", + "DestinationPathDetail": "Zielpfad", + "CopyDestinationPathPopup": "Zielpfad kopieren", + "LastBackupDetail": "LetztesBackup", + "OpenInitialFolderPopup": "Anfangspfad öffnen", + "ResearchBarPlaceholder": "Suchen...", + "InterruptPopup": "Unterbrechen", + "DuplicatePopup": "Duplizieren", + "NextBackupDateColumn": "Nächstes Backup-Datum", + "DestinationPathColumn": "Zielpfad", + "AutomaticBackupColumn": "Automatisches Backup", + "LastUpdateDateDetail": "LetzteAktualisierung", + "TimeIntervalDetail": "Zeitintervall", + "CopyBackupNamePopup": "Backup-Name kopieren", + "CreationDateDetail": "Erstellungsdatum", + "CopyInitialPathPopup": "Anfangspfad kopieren", + "BackupListTitle": "Backup-Liste", + "BackupListDescription": "Verwalten und überwachen Sie Backup-Konfigurationen, einschließlich Erstellung, Bearbeitung, Planung und Ausführung.", + "TimeIntervalColumn": "Intervall (tt.HH:mm)", + "MaxBackupsColumn": "Maximale Anzahl aufzubewahrender Backups" + }, + "TabbedFrames": { + "BackupEntry": "Backup-Eintrag", + "BackupList": "Backup-Liste" + }, + "UserDialog": { + "ErrorMessageForMissingData": "Bitte füllen Sie alle erforderlichen Felder aus.", + "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team", + "EmailConfirmationSubject": "Vielen Dank, dass Sie sich für Backup Manager entschieden haben!", + "Surname": "Nachname", + "ErrorMessageForWrongEmail": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte Adresse an.", + "Name": "Vorname", + "Email": "E-Mail", + "UserTitle": "Geben Sie Ihre Daten ein", + "UserDescription": "Bitte geben Sie Ihre Daten ein, um auf das System zuzugreifen", + "UserNamePlaceholder": "Geben Sie Ihren Namen ein", + "UserSurnamePlaceholder": "Geben Sie Ihren Nachnamen ein", + "UserEmailPlaceholder": "Geben Sie Ihre E-Mail-Adresse ein" + }, + "BackupEntry": { + "TimePickerTooltip": "Zeitwähler", + "MaxBackupsToKeepTooltip": "Maximale Anzahl an Sicherungen, bevor die ältesten entfernt werden.", + "BackupName": "Sicherungsname", + "AutoBackupButton": "Auto-Backup", + "InitialPathTooltip": "(Erforderlich) Anfangspfad", + "CurrentFile": "Aktuelle Datei", + "InitialFileChooserTooltip": "Dateiexplorer öffnen", + "LastBackup": "Letztes Backup", + "SingleBackupButton": "Einzel-Backup", + "AutoBackupButtonON": "Auto-Backup (AN)", + "AutoBackupButtonOFF": "Auto-Backup (AUS)", + "DestinationPathTooltip": "(Erforderlich) Zielpfad", + "MaxBackupsToKeep": "Maximale Anzahl an Sicherungen beibehalten", + "SingleBackupTooltip": "Backup durchführen", + "AutoBackupTooltip": "Automatisches Backup aktivieren/deaktivieren", + "NotesTooltip": "(Optional) Backup-Beschreibung", + "DestinationFileChooserTooltip": "Dateiexplorer öffnen", + "BackupNameTooltip": "(Erforderlich) Sicherungsname", + "PageTitle": "Backup-Eintrag", + "Notes": "Notizen", + "PageSubtitleCreate": "Backup erstellen", + "PageSubtitleEdit": "Backup bearbeiten", + "PageSubtitleInfo": "Backup-Informationen", + "PageSubtitleSettings": "Backup-Einstellungen", + "Paths": "Pfade", + "BackupNamePlaceholder": "Backup-Name (eindeutig)", + "InitialPathPlaceholder": "Quellpfad z. B. C:\\Users\\Admin\\Documents", + "DestinationPathPlaceholder": "Zielordner z. B. D:\\Backups" + }, + "HistoryLogs": { + "HistoryLogsTitle": "Verlaufsprotokolle", + "HistoryLogsDescription": "Hier finden Sie die Anwendungsprotokolle, die bei der Fehlerbehebung helfen und das Verhalten der Anwendung im Laufe der Zeit verständlich machen." + }, + "About": { + "AboutSystemInformation": "Systeminformationen", + "AboutMessageBody": "<html><b>Backup Manager</b> ist eine einfache und leistungsstarke Anwendung zur Automatisierung von Sicherungen von Ordnern und Unterordnern.<br><br> Benutzer können automatische Backups planen oder jederzeit manuelle Backups ausführen.<br><br> Die Backup-Historie wird sicher gespeichert und ermöglicht die vollständige Kontrolle über gespeicherte Daten.<br><p>Besuchen Sie die <a href=[PROJECT_WEBSITE]>Projektwebsite</a> für weitere Informationen.</p></html>" + }, + "Dashboard": { + "DashboardTitle": "Backup-Analyse-Dashboard", + "DashboardCardTotalConfigurations": "Gesamte Backup-Konfigurationen", + "DashboardCardTotalExecutions": "Gesamte Backup-Ausführungen", + "DashboardCardSuccessRate": "Erfolgsrate", + "DashboardCardAvgDuration": "Durchschnittliche Backup-Dauer", + "DashboardCardCompressionRate": "Komprimierungsrate", + "DashboardChartExecutions": "Backup-Ausführungen", + "DashboardChartAvgDuration": "Durchschnittliche Backup-Dauer (Min.)" + }, + "Settings": { + "SettingsLayoutTab": "Layout", + "SettingsStyleTab": "Stil", + "SettingsWindowsLayout": "Fensterlayout", + "SettingsWindowsRight": "Von rechts nach links", + "SettingsWindowsFull": "Vollständiger Fensterinhalt", + "SettingsDrawerLayout": "Panel-Layout", + "SettingsDrawerLeft": "Links", + "SettingsDrawerLeading": "Anfang", + "SettingsDrawerTrailing": "Ende", + "SettingsDrawerRight": "Rechts", + "SettingsDrawerTop": "Oben", + "SettingsDrawerBottom": "Unten", + "SettingsModalOption": "Standard-Modaloption", + "SettingsModalAnimation": "Animation aktivieren", + "SettingsModalClose": "Mit ESC schließen", + "SettingsLanguagesLayout": "Sprache", + "SettingsAccentLayout": "Akzentfarbe", + "SettingsColorPickerLayout": "Farbauswahl", + "SettingsDrawerLineLayout": "Panel-Linienstil", + "SettingsDrawerLineCurved": "Gebogene Linie", + "SettingsDrawerDotLine": "Gepunktete gerade Linie", + "SettingsLineStyleLayout": "Linienstiloption", + "SettingsLineStyleRettangle": "Rechteck", + "SettingsLineStyleEllipse": "Ellipse", + "SettingsLineStyleLine": "Linie", + "SettingsLineStyleCurved": "Gebogen", + "SettingsColorOptionLayout": "Farboption", + "SettingsColorOptionPainted": "Ausgewählte Linie einfärben" + }, + "SearchBar": { + "SearcTitle": "Suchen...", + "SearchNoRecent": "Keine letzten Suchen", + "SearchNoResult": "Kein Ergebnis für", + "SearchFavorite": "Favorit", + "SearchRecent": "Letzte" + } } diff --git a/src/main/resources/res/languages/eng.json b/src/main/resources/res/languages/eng.json index 92761d2d..b6026ebb 100644 --- a/src/main/resources/res/languages/eng.json +++ b/src/main/resources/res/languages/eng.json @@ -1,214 +1,300 @@ { - "TrayIcon": { - "SuccessMessage": "\nThe backup was successfully completed:", - "ExitAction": "Exit", - "ErrorMessageFilesNotExisting": "\nError during automatic backup.\nOne or both paths do not exist!", - "TrayTooltip": "Backup Service", - "OpenAction": "Quick Access", - "ErrorMessageInputMissing": "\nError during automatic backup.\nInput Missing!", - "ErrorMessageSamePaths": "\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!" - }, - "If value is null or empty, fall back to the default value from the enum": {}, - "Dialogs": { - "WarningGenericTitle": "Warning", - "SuccessfullyExportedToCsvMessage": "Backups exported to CSV successfully!", - "ErrorMessageForPathNotExisting": "One or both paths do not exist!", - "ConfirmationDeletionMessage": "Are you sure you want to delete the selected rows?", - "ExceptionMessageReportMessage": "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Error exporting backups to CSV: ", - "ErrorGenericTitle": "Error", - "ErrorMessageForExportingToPdf": "Error exporting backups to PDF: ", - "BackupListCorrectlyImportedTitle": "Menu Import", - "ErrorWrongTimeInterval": "The time interval is not correct", - "ErrorMessageOpenHistoryFile": "Error opening history file.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "The initial path and destination path cannot be the same. Please choose different paths!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Share link copied to clipboard!", - "ErrorMessageForSavingFileWithPathsEmpty": "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty", - "BackupListCorrectlyExportedMessage": "Backup list successfully exported to the Desktop!", - "BackupSavedCorrectlyMessage": "saved successfully!", - "SettedEveryMessage": "\nIs setted every", - "WarningBackupAlreadyInProgressMessage": "There is already a backup in progress. It is not possible to perform parallel backups", - "WarningShortTimeIntervalMessage": "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?", - "ConfirmationMessageCancelAutoBackup": "Are you sure you want to cancel automatic backups for this entry?", - "ErrorMessageCountingFiles": "Error occurred while calculating files to back up.", - "ErrorMessageForFolderNotExisting": "The folder does not exist or is invalid", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Error during the backup operation: the initial path is incorrect!", - "ErrorMessageInputMissingGeneric": "Input Missing!", - "BackupNameInput": "Name of the backup", - "CsvNameMessageInput": "Enter the name of the CSV file.", - "ConfirmationDeletionTitle": "Confirm Deletion", - "ErrorMessageForWrongFileExtensionMessage": "Error: Please select a valid JSON file.", - "ErrorMessageZippingGeneric": "Error occurred while zipping files.", - "ErrorMessageNotSupportedEmailGeneric": "Your system does not support sending emails.", - "ErrorMessageZippingIO": "Error occurred while zipping files: I/O error.", - "SuccessfullyExportedToPdfMessage": "Backups exported to PDF successfully!", - "DuplicatedFileNameMessage": "File already exists. Overwrite?", - "AutoBackupActivatedMessage": "Auto Backup has been activated", - "DaysMessage": " days", - "ExceptionMessageReportButton": "Report the Problem", - "ErrorMessageInvalidFilename": "Invalid file name. Use only alphanumeric characters, dashes, and underscores.", - "ExceptionMessageClipboardMessage": "Error text has been copied to the clipboard.", - "SuccessGenericTitle": "Success", - "ConfirmationMessageForClear": "Are you sure you want to clean the fields?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Backup list successfully imported!", - "DuplicatedBackupNameMessage": "A backup with the same name already exists, do you want to overwrite it?", - "ExceptionMessageClipboardButton": "Copy to clipboard", - "ErrorMessageZippingSecurity": "Error occurred while zipping files: Security error.", - "InterruptBackupProcessMessage": "Are you sure you want to stop this backup?", - "BackupListCorrectlyExportedTitle": "Menu Export", - "ConfirmationMessageForUnsavedChanges": "There are unsaved changes, do you want to save them before moving to another backup?", - "ExceptionMessageTitle": "Error...", - "PdfNameMessageInput": "Enter the name of the PDF file.", - "ErrorMessageNotSupportedEmail": "Your system does not support sending emails directly from this application.", - "ErrorMessageForSavingFile": "Error saving file", - "ErrorMessageUnableToSendEmail": "Unable to send email. Please try again later.", - "ConfirmationMessageBeforeDeleteBackup": "Are you sure you want to delete this item? Please note, this action cannot be undone", - "ErrorMessageOpeningWebsite": "Failed to open the web page. Please try again.", - "ErrorSavingBackupMessage": "Error saving backup", - "ConfirmationRequiredTitle": "Confirmation required", - "BackupNameAlreadyUsedMessage": "Backup name already used!", - "ErrorMessageForWrongFileExtensionTitle": "Invalid File", - "BackupSavedCorrectlyTitle": "Backup saved" - }, - "User dialog": {}, - "TimePickerDialog": { - "TimeIntervalTitle": "Time interval for auto backup", - "Days": "Days", - "Hours": "Hours", - "SpinnerTooltip": "Mouse wheel to adjust the value", - "Description": "Select how often to perform the automatic backup by \nchoosing the frequency in days, hours, and minutes.", - "Minutes": "Minutes" - }, - "HISTORY_LOGS": {}, - "Constructor to assign both key and default value": {}, - "Subscription": { - "ExpiredTitle": "Backup Manager subscription expired", - "ExpiringMessage": "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it.", - "ExpiringTitle": "Backup Manager subscription expiring soon", - "ExpiredMessage": "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it." - }, - "ProgressBackupFrame": { - "StatusLoading": "Loading...", - "ProgressBackupTitle": "Backup in progress", - "StatusCompleted": "Backup completed!" - }, - "Lookup by keyName (JSON key)": {}, - "DASHBOARD": {}, - "ABOUT": {}, - "General": { - "AppName": "Backup Manager", - "CancelButton": "Cancel", - "Backup": "Backup", - "Version": "Version", - "ApplyButton": "Apply", - "OkButton": "Ok", - "From": "From", - "SaveButton": "Save", - "CloseButton": "Close", - "To": "A" - }, - "Menu": { - "Import": "Import backup list", - "Save": "Save", - "About": "About", - "File": "File", - "Support": "Support", - "BugReport": "Report a bug", - "Quit": "Quit", - "Options": "Options", - "New": "New", - "Clear": "Clear", - "Share": "Share", - "Preferences": "Preferences", - "Website": "Website", - "SaveWithName": "Save with name", - "Export": "Export backup list", - "Help": "Help", - "InfoPage": "Info", - "History": "History" - }, - "Use fromKeyName to get the TKey from the JSON key": {}, - "Clear previous translations to avoid stale values when switching languages": {}, - "BackupList": { - "NotesDetail": "Notes", - "BackupNameDetail": "BackupName", - "ExportAsPdfTooltip": "Export as PDF", - "EditPopup": "Edit", - "ResearchBarTooltip": "Research bar", - "InitialPathDetail": "InitialPath", - "ExportAs": "Export as: ", - "RenameBackupPopup": "Rename backup", - "NextBackupDateDetail": "NextBackup", - "AutoBackupPopup": "Auto backup", - "BackupNameColumn": "Backup Name", - "BackupPopup": "Backup", - "BackupCountDetail": "BackupCount", - "ExportAsCsvTooltip": "Export as CSV", - "InitialPathColumn": "Initial Path", - "MaxBackupsToKeepDetail": "MaxBackupsToKeep", - "DeletePopup": "Delete", - "OpenDestinationFolderPopup": "Open destination path", - "LastBackupColumn": "Last Backup", - "SingleBackupPopup": "Run single backup", - "AddBackupTooltip": "Add new backup", - "CopyTextPopup": "Copy text", - "DestinationPathDetail": "DestinationPath", - "CopyDestinationPathPopup": "Copy destination path", - "LastBackupDetail": "LastBackup", - "OpenInitialFolderPopup": "Open initial path", - "ResearchBarPlaceholder": "Search...", - "InterruptPopup": "Interrupt", - "DuplicatePopup": "Duplicate", - "NextBackupDateColumn": "Next Backup Date", - "DestinationPathColumn": "Destination Path", - "AutomaticBackupColumn": "Automatic Backup", - "LastUpdateDateDetail": "LastUpdateDate", - "TimeIntervalDetail": "TimeInterval", - "CopyBackupNamePopup": "Copy backup name", - "CreationDateDetail": "CreationDate", - "CopyInitialPathPopup": "Copy initial path" - }, - "TabbedFrames": { - "BackupEntry": "BackupEntry", - "BackupList": "BackupList" - }, - "UserDialog": { - "ErrorMessageForMissingData": "Please fill in all the required fields.", - "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team", - "EmailConfirmationSubject": "Thank you for choosing Backup Manager!", - "Surname": "Surname", - "ErrorMessageForWrongEmail": "The provided email address is invalid. Please provide a correct one.", - "Name": "Name", - "Email": "Email", - "UserTitle": "Insert your data" - }, - "SETTINGS": {}, - "BackupEntry": { - "TimePickerTooltip": "Time picker", - "MaxBackupsToKeepTooltip": "Maximum number of backups before removing the oldest.", - "BackupName": "Backup name", - "AutoBackupButton": "Auto Backup", - "InitialPathTooltip": "(Required) Initial path", - "CurrentFile": "Current file", - "InitialFileChooserTooltip": "Open file explorer", - "LastBackup": "Last backup", - "SingleBackupButton": "Single Backup", - "AutoBackupButtonON": "Auto Backup (ON)", - "AutoBackupButtonOFF": "Auto Backup (OFF)", - "DestinationPathTooltip": "(Required) Destination path", - "MaxBackupsToKeep": "Max backups to keep", - "SingleBackupTooltip": "Perform the backup", - "AutoBackupTooltip": "Enable/Disable automatic backup", - "NotesTooltip": "(Optional) Backup description", - "DestinationFileChooserTooltip": "Open file explorer", - "BackupNameTooltip": "(Required) Backup name", - "PageTitle": "Backup Entry", - "Notes": "Notes" - } + "General": { + "AppName": "Backup Manager", + "CancelButton": "Cancel", + "Backup": "Backup", + "Version": "Version", + "ApplyButton": "Apply", + "OkButton": "Ok", + "From": "From", + "SaveButton": "Save", + "CloseButton": "Close", + "CreateButton": "Create", + "EditButton": "Edit", + "DeleteButton": "Delete", + "QuickSearch": "Quick search", + "To": "A" + }, + "TrayIcon": { + "SuccessMessage": "\nThe backup was successfully completed:", + "ExitAction": "Exit", + "ErrorMessageFilesNotExisting": "\nError during automatic backup.\nOne or both paths do not exist!", + "TrayTooltip": "Backup Service", + "OpenAction": "Quick Access", + "ErrorMessageInputMissing": "\nError during automatic backup.\nInput Missing!", + "ErrorMessageSamePaths": "\nError during automatic backup.\nThe initial path and destination path cannot be the same. Please choose different paths!" + }, + "Dialogs": { + "WarningGenericTitle": "Warning", + "SuccessfullyExportedToCsvMessage": "Backups exported to CSV successfully!", + "ErrorMessageForPathNotExisting": "One or both paths do not exist!", + "ConfirmationDeletionMessage": "Are you sure you want to delete the selected rows?", + "ExceptionMessageReportMessage": "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Error exporting backups to CSV: ", + "ErrorGenericTitle": "Error", + "ErrorMessageForExportingToPdf": "Error exporting backups to PDF: ", + "BackupListCorrectlyImportedTitle": "Menu Import", + "ErrorWrongTimeInterval": "The time interval is not correct", + "ErrorMessageOpenHistoryFile": "Error opening history file.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "The initial path and destination path cannot be the same. Please choose different paths!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Share link copied to clipboard!", + "ErrorMessageForSavingFileWithPathsEmpty": "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty", + "BackupListCorrectlyExportedMessage": "Backup list successfully exported to the Desktop!", + "BackupSavedCorrectlyMessage": "saved successfully!", + "SettedEveryMessage": "\nIs setted every", + "WarningBackupAlreadyInProgressMessage": "There is already a backup in progress. It is not possible to perform parallel backups", + "WarningShortTimeIntervalMessage": "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?", + "ConfirmationMessageCancelAutoBackup": "Are you sure you want to cancel automatic backups for this entry?", + "ErrorMessageCountingFiles": "Error occurred while calculating files to back up.", + "ErrorMessageForFolderNotExisting": "The folder does not exist or is invalid", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Error during the backup operation: the initial path is incorrect!", + "ErrorMessageInputMissingGeneric": "Input Missing!", + "BackupNameInput": "Name of the backup", + "CsvNameMessageInput": "Enter the name of the CSV file.", + "ConfirmationDeletionTitle": "Confirm Deletion", + "ErrorMessageForWrongFileExtensionMessage": "Error: Please select a valid JSON file.", + "ErrorMessageZippingGeneric": "Error occurred while zipping files.", + "ErrorMessageNotSupportedEmailGeneric": "Your system does not support sending emails.", + "ErrorMessageZippingIO": "Error occurred while zipping files: I/O error.", + "SuccessfullyExportedToPdfMessage": "Backups exported to PDF successfully!", + "DuplicatedFileNameMessage": "File already exists. Overwrite?", + "AutoBackupActivatedMessage": "Auto Backup has been activated", + "DaysMessage": " days", + "ExceptionMessageReportButton": "Report the Problem", + "ErrorMessageInvalidFilename": "Invalid file name. Use only alphanumeric characters, dashes, and underscores.", + "ExceptionMessageClipboardMessage": "Error text has been copied to the clipboard.", + "SuccessGenericTitle": "Success", + "ConfirmationMessageForClear": "Are you sure you want to clean the fields?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Backup list successfully imported!", + "DuplicatedBackupNameMessage": "A backup with the same name already exists, do you want to overwrite it?", + "ExceptionMessageClipboardButton": "Copy to clipboard", + "ErrorMessageZippingSecurity": "Error occurred while zipping files: Security error.", + "InterruptBackupProcessMessage": "Are you sure you want to stop this backup?", + "BackupListCorrectlyExportedTitle": "Menu Export", + "ConfirmationMessageForUnsavedChanges": "There are unsaved changes, do you want to save them before moving to another backup?", + "ExceptionMessageTitle": "Error...", + "PdfNameMessageInput": "Enter the name of the PDF file.", + "ErrorMessageNotSupportedEmail": "Your system does not support sending emails directly from this application.", + "ErrorMessageForSavingFile": "Error saving file", + "ErrorMessageUnableToSendEmail": "Unable to send email. Please try again later.", + "ConfirmationMessageBeforeDeleteBackup": "Are you sure you want to delete this item? Please note, this action cannot be undone", + "ErrorMessageOpeningWebsite": "Failed to open the web page. Please try again.", + "ErrorSavingBackupMessage": "Error saving backup", + "ConfirmationRequiredTitle": "Confirmation required", + "BackupNameAlreadyUsedMessage": "Backup name already used!", + "ErrorMessageForWrongFileExtensionTitle": "Invalid File", + "BackupSavedCorrectlyTitle": "Backup saved" + }, + "TimePickerDialog": { + "TimeIntervalTitle": "Time interval for auto backup", + "Days": "Days", + "Hours": "Hours", + "SpinnerTooltip": "Mouse wheel to adjust the value", + "Description": "Select how often to perform the automatic backup by \nchoosing the frequency in days, hours, and minutes.", + "Minutes": "Minutes" + }, + "Subscription": { + "ExpiredTitle": "Backup Manager subscription expired", + "ExpiringMessage": "Your Backup Manager subscription is about to expire.\nAutomatic backups will continue to run until the expiration date.\nPlease contact support to renew it.", + "ExpiringTitle": "Backup Manager subscription expiring soon", + "ExpiredMessage": "Your Backup Manager subscription has expired.\nAutomatic backups will no longer run.\nPlease contact support to reactivate it.", + "ActiveLabel": "Active", + "ExpiringLabel": "Expiring", + "ExpiredLabel": "Expired", + "Status": "Subscription status", + "ValidFrom": "Valid from", + "ValidTo": "Valid to", + "ContactUs": "Contact us", + "ToExtend": "to extend the subscription period." + }, + "ProgressBackupFrame": { + "StatusLoading": "Loading...", + "ProgressBackupTitle": "Backup in progress", + "StatusCompleted": "Backup completed!" + }, + "Menu": { + "Import": "Import backup list", + "Save": "Save", + "About": "About", + "File": "File", + "Support": "Support", + "BugReport": "Report a bug", + "Quit": "Quit", + "Options": "Options", + "New": "New", + "Clear": "Clear", + "Share": "Share", + "Preferences": "Preferences", + "Website": "Website", + "SaveWithName": "Save with name", + "Export": "Export backup list", + "Help": "Help", + "InfoPage": "Info", + "History": "History", + "SubmenuMain": "MAIN", + "SubmenuOther": "OTHER", + "Donate": "Support the project", + "Backups": "Backup List", + "CreateBackup": "Create new backup", + "ImportBackup": "Import backups from Csv", + "ExportBackup": "Export backups to Csv", + "Dashboard": "Dashboard", + "Github": "Github page", + "Paypal": "Paypal", + "BuyMeACoffe": "Buy me a coffe", + "ContactUs": "Contact us", + "Subscription": "Subscription" + }, + "BackupList": { + "NotesDetail": "Notes", + "BackupNameDetail": "BackupName", + "ExportAsPdfTooltip": "Export as PDF", + "EditPopup": "Edit", + "ResearchBarTooltip": "Research bar", + "InitialPathDetail": "InitialPath", + "ExportAs": "Export as: ", + "RenameBackupPopup": "Rename backup", + "NextBackupDateDetail": "NextBackup", + "AutoBackupPopup": "Auto backup", + "BackupNameColumn": "Backup Name", + "BackupPopup": "Backup", + "BackupCountDetail": "BackupCount", + "ExportAsCsvTooltip": "Export as CSV", + "InitialPathColumn": "Initial Path", + "MaxBackupsToKeepDetail": "MaxBackupsToKeep", + "DeletePopup": "Delete", + "OpenDestinationFolderPopup": "Open destination path", + "LastBackupColumn": "Last Backup", + "SingleBackupPopup": "Run single backup", + "AddBackupTooltip": "Add new backup", + "CopyTextPopup": "Copy text", + "DestinationPathDetail": "DestinationPath", + "CopyDestinationPathPopup": "Copy destination path", + "LastBackupDetail": "LastBackup", + "OpenInitialFolderPopup": "Open initial path", + "ResearchBarPlaceholder": "Search...", + "InterruptPopup": "Interrupt", + "DuplicatePopup": "Duplicate", + "NextBackupDateColumn": "Next Backup Date", + "DestinationPathColumn": "Destination Path", + "AutomaticBackupColumn": "Automatic Backup", + "LastUpdateDateDetail": "LastUpdateDate", + "TimeIntervalDetail": "TimeInterval", + "CopyBackupNamePopup": "Copy backup name", + "CreationDateDetail": "CreationDate", + "CopyInitialPathPopup": "Copy initial path", + "BackupListTitle": "Backup List", + "BackupListDescription": "Manage and monitor backup configurations, including creation, editing, scheduling, and execution.", + "TimeIntervalColumn": "Interval (gg.HH:mm)", + "MaxBackupsColumn": "Max Backups To Keep" + }, + "TabbedFrames": { + "BackupEntry": "BackupEntry", + "BackupList": "BackupList" + }, + "UserDialog": { + "ErrorMessageForMissingData": "Please fill in all the required fields.", + "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team", + "EmailConfirmationSubject": "Thank you for choosing Backup Manager!", + "Surname": "Surname", + "ErrorMessageForWrongEmail": "The provided email address is invalid. Please provide a correct one.", + "Name": "Name", + "Email": "Email", + "UserTitle": "Insert your data", + "UserDescription": "Please enter your data to access the system", + "UserNamePlaceholder": "Enter your name", + "UserSurnamePlaceholder": "Enter your surname", + "UserEmailPlaceholder": "Enter your email" + }, + "BackupEntry": { + "TimePickerTooltip": "Time picker", + "MaxBackupsToKeepTooltip": "Maximum number of backups before removing the oldest.", + "BackupName": "Backup name", + "AutoBackupButton": "Auto Backup", + "InitialPathTooltip": "(Required) Initial path", + "CurrentFile": "Current file", + "InitialFileChooserTooltip": "Open file explorer", + "LastBackup": "Last backup", + "SingleBackupButton": "Single Backup", + "AutoBackupButtonON": "Auto Backup (ON)", + "AutoBackupButtonOFF": "Auto Backup (OFF)", + "DestinationPathTooltip": "(Required) Destination path", + "MaxBackupsToKeep": "Max backups to keep", + "SingleBackupTooltip": "Perform the backup", + "AutoBackupTooltip": "Enable/Disable automatic backup", + "NotesTooltip": "(Optional) Backup description", + "DestinationFileChooserTooltip": "Open file explorer", + "BackupNameTooltip": "(Required) Backup name", + "PageTitle": "Backup Entry", + "Notes": "Notes", + "PageSubtitleCreate": "Create Backup", + "PageSubtitleEdit": "Edit Backup", + "PageSubtitleInfo": "Backup Information", + "PageSubtitleSettings": "Backups Settings", + "Paths": "Paths", + "BackupNamePlaceholder": "Backup name (unique)", + "InitialPathPlaceholder": "Target path e.g. C:\\Users\\Admin\\Documents", + "DestinationPathPlaceholder": "Destination folder e.g. D:\\Backups" + }, + "HistoryLogs": { + "HistoryLogsTitle": "History logs", + "HistoryLogsDescription": "Here you can find the application logs, useful for troubleshooting and understanding the application's behavior over time." + }, + "About": { + "AboutSystemInformation": "System Information", + "AboutMessageBody": "<html><b>Backup Manager</b> is a simple and powerful application designed to automate folder and subfolder backups.<br><br> Users can schedule automatic backups or execute manual backups anytime.<br><br> Backup history is stored securely, allowing full control over saved data.<br><p>Visit <a href=[PROJECT_WEBSITE]>project website</a> for more information.</p></html>" + }, + "Dashboard": { + "DashboardTitle": "Backup Analytics Dashboard", + "DashboardCardTotalConfigurations": "Total Backup Configurations", + "DashboardCardTotalExecutions": "Total Backup Executions", + "DashboardCardSuccessRate": "Success rate", + "DashboardCardAvgDuration": "Avg Backup Duration", + "DashboardCardCompressionRate": "Compression Rate", + "DashboardChartExecutions": "Backup Executions", + "DashboardChartAvgDuration": "Average Backup Duration (min)" + }, + "Settings": { + "SettingsLayoutTab": "Layout", + "SettingsStyleTab": "Style", + "SettingsWindowsLayout": "Windows Layout", + "SettingsWindowsRight": "Right to Left", + "SettingsWindowsFull": "Full Window Content", + "SettingsDrawerLayout": "Drawer layout", + "SettingsDrawerLeft": "Left", + "SettingsDrawerLeading": "Leading", + "SettingsDrawerTrailing": "Trailing", + "SettingsDrawerRight": "Right", + "SettingsDrawerTop": "Top", + "SettingsDrawerBottom": "Bottom", + "SettingsModalOption": "Default modal option", + "SettingsModalAnimation": "Animation enable", + "SettingsModalClose": "Close on pressed escape", + "SettingsLanguagesLayout": "Language", + "SettingsAccentLayout": "Accent color", + "SettingsColorPickerLayout": "Color Picker", + "SettingsDrawerLineLayout": "Drawer line style", + "SettingsDrawerLineCurved": "Curved line style", + "SettingsDrawerDotLine": "Straight dot line style", + "SettingsLineStyleLayout": "Line style option", + "SettingsLineStyleRettangle": "Rettangle", + "SettingsLineStyleEllipse": "Ellipse", + "SettingsLineStyleLine": "Line", + "SettingsLineStyleCurved": "Curved", + "SettingsColorOptionLayout": "Color option", + "SettingsColorOptionPainted": "Paint selected line color" + }, + "SearchBar": { + "SearcTitle": "Search...", + "SearchNoRecent": "No recent searches", + "SearchNoResult": "No result for", + "SearchFavorite": "Favorite", + "SearchRecent": "Recents" + } } diff --git a/src/main/resources/res/languages/esp.json b/src/main/resources/res/languages/esp.json index 72a4b91e..d36dc476 100644 --- a/src/main/resources/res/languages/esp.json +++ b/src/main/resources/res/languages/esp.json @@ -1,214 +1,300 @@ { - "TrayIcon": { - "SuccessMessage": "\nLa copia de seguridad se completó con éxito:", - "ExitAction": "Salir", - "ErrorMessageFilesNotExisting": "\nError en la copia automática.\n¡Una o ambas rutas no existen!", - "TrayTooltip": "Servicio de Copias de Seguridad", - "OpenAction": "Acceso Rápido", - "ErrorMessageInputMissing": "\nError en la copia automática.\n¡Faltan datos de entrada!", - "ErrorMessageSamePaths": "\nError en la copia automática.\nLa ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!" - }, - "If value is null or empty, fall back to the default value from the enum": {}, - "Dialogs": { - "WarningGenericTitle": "Advertencia", - "SuccessfullyExportedToCsvMessage": "¡Copias de seguridad exportadas a CSV con éxito!", - "ErrorMessageForPathNotExisting": "¡Una o ambas rutas no existen!", - "ConfirmationDeletionMessage": "¿Está seguro de que desea eliminar las filas seleccionadas?", - "ExceptionMessageReportMessage": "Por favor, informe de este error, ya sea con una captura de pantalla o copiando el siguiente texto del error (se agradece proporcionar una descripción de las acciones realizadas antes del error):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Error al exportar copias de seguridad a CSV: ", - "ErrorGenericTitle": "Error", - "ErrorMessageForExportingToPdf": "Error al exportar copias de seguridad a PDF: ", - "BackupListCorrectlyImportedTitle": "Menú Importar", - "ErrorWrongTimeInterval": "El intervalo de tiempo no es correcto", - "ErrorMessageOpenHistoryFile": "Error al abrir el archivo de historial.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "La ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "¡Enlace de compartir copiado al portapapeles!", - "ErrorMessageForSavingFileWithPathsEmpty": "No se puede guardar el archivo. Tanto la ruta inicial como la de destino deben especificarse y no pueden estar vacías", - "BackupListCorrectlyExportedMessage": "¡Lista de copias de seguridad exportada correctamente al escritorio!", - "BackupSavedCorrectlyMessage": "guardada con éxito.", - "SettedEveryMessage": "\nSe establece cada", - "WarningBackupAlreadyInProgressMessage": "Ya hay una copia de seguridad en progreso. No es posible realizar copias de seguridad en paralelo.", - "WarningShortTimeIntervalMessage": "El intervalo de tiempo seleccionado es muy corto. Para un funcionamiento óptimo, recomendamos configurarlo en al menos una hora. ¿Quieres continuar de todos modos?", - "ConfirmationMessageCancelAutoBackup": "¿Está seguro de que desea cancelar las copias automáticas para esta entrada?", - "ErrorMessageCountingFiles": "Error al calcular los archivos para la copia de seguridad.", - "ErrorMessageForFolderNotExisting": "La carpeta no existe o no es válida", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Error en la operación de copia: ¡la ruta inicial es incorrecta!", - "ErrorMessageInputMissingGeneric": "¡Faltan datos de entrada!", - "BackupNameInput": "Nombre de la copia", - "CsvNameMessageInput": "Introduce el nombre del archivo CSV.", - "ConfirmationDeletionTitle": "Confirmar Eliminación", - "ErrorMessageForWrongFileExtensionMessage": "Error: Seleccione un archivo JSON válido.", - "ErrorMessageZippingGeneric": "Error al comprimir los archivos.", - "ErrorMessageNotSupportedEmailGeneric": "Su sistema no admite el envío de correos electrónicos.", - "ErrorMessageZippingIO": "Error al comprimir los archivos: error de E/S.", - "SuccessfullyExportedToPdfMessage": "¡Copias de seguridad exportadas a PDF con éxito!", - "DuplicatedFileNameMessage": "El archivo ya existe. ¿Sobrescribir?", - "AutoBackupActivatedMessage": "La copia automática se ha activado", - "DaysMessage": " días", - "ExceptionMessageReportButton": "Informar del problema", - "ErrorMessageInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos.", - "ExceptionMessageClipboardMessage": "El texto del error se ha copiado al portapapeles.", - "SuccessGenericTitle": "Éxito", - "ConfirmationMessageForClear": "¿Está seguro de que desea limpiar los campos?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "¡Lista de copias de seguridad importada correctamente!", - "DuplicatedBackupNameMessage": "Ya existe una copia con el mismo nombre. ¿Desea sobrescribirla?", - "ExceptionMessageClipboardButton": "Copiar al portapapeles", - "ErrorMessageZippingSecurity": "Error al comprimir los archivos: Error de seguridad.", - "InterruptBackupProcessMessage": "¿Está seguro de que desea detener esta copia?", - "BackupListCorrectlyExportedTitle": "Menú Exportar", - "ConfirmationMessageForUnsavedChanges": "Hay cambios no guardados. ¿Desea guardarlos antes de pasar a otra copia de seguridad?", - "ExceptionMessageTitle": "Error...", - "PdfNameMessageInput": "Introduce el nombre del archivo PDF.", - "ErrorMessageNotSupportedEmail": "Su sistema no admite el envío de correos electrónicos directamente desde esta aplicación.", - "ErrorMessageForSavingFile": "Error al guardar el archivo", - "ErrorMessageUnableToSendEmail": "No se pudo enviar el correo electrónico. Por favor, intente de nuevo más tarde.", - "ConfirmationMessageBeforeDeleteBackup": "¿Está seguro de que desea eliminar este elemento? Tenga en cuenta que esta acción no se puede deshacer.", - "ErrorMessageOpeningWebsite": "No se pudo abrir la página web. Por favor, intente de nuevo.", - "ErrorSavingBackupMessage": "Error al guardar la copia de seguridad", - "ConfirmationRequiredTitle": "Confirmación requerida", - "BackupNameAlreadyUsedMessage": "¡Nombre de copia ya en uso!", - "ErrorMessageForWrongFileExtensionTitle": "Archivo no válido", - "BackupSavedCorrectlyTitle": "Copia Guardada" - }, - "User dialog": {}, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervalo de tiempo para copias automáticas", - "Days": "Días", - "Hours": "Horas", - "SpinnerTooltip": "Rueda del ratón para ajustar el valor", - "Description": "Seleccione la frecuencia para realizar la copia automática \neligiendo los días, horas y minutos.", - "Minutes": "Minutos" - }, - "HISTORY_LOGS": {}, - "Constructor to assign both key and default value": {}, - "Subscription": { - "ExpiredTitle": "La suscripción de Backup Manager ha expirado", - "ExpiringMessage": "Tu suscripción a Backup Manager está a punto de expirar.\nLas copias de seguridad automáticas seguirán funcionando hasta la fecha de vencimiento.\nContacta con el soporte para renovarla.", - "ExpiringTitle": "La suscripción de Backup Manager está por vencer", - "ExpiredMessage": "Tu suscripción a Backup Manager ha expirado.\nLas copias de seguridad automáticas ya no se ejecutarán.\nContacta con el soporte para reactivarla." - }, - "ProgressBackupFrame": { - "StatusLoading": "Cargando...", - "ProgressBackupTitle": "Copia de Seguridad en Progreso", - "StatusCompleted": "¡Copia completada!" - }, - "Lookup by keyName (JSON key)": {}, - "DASHBOARD": {}, - "ABOUT": {}, - "General": { - "AppName": "Backup Manager", - "CancelButton": "Cancelar", - "Backup": "Copia de Seguridad", - "Version": "Versión", - "ApplyButton": "Aplicar", - "OkButton": "Aceptar", - "From": "Desde", - "SaveButton": "Guardar", - "CloseButton": "Cerrar", - "To": "A" - }, - "Menu": { - "Import": "Importar lista de copias de seguridad", - "Save": "Guardar", - "About": "Acerca de", - "File": "Archivo", - "Support": "Soporte", - "BugReport": "Reportar un error", - "Quit": "Salir", - "Options": "Opciones", - "New": "Nuevo", - "Clear": "Limpiar", - "Share": "Compartir", - "Preferences": "Preferencias", - "Website": "Sitio web", - "SaveWithName": "Guardar con nombre", - "Export": "Exportar lista de copias de seguridad", - "Help": "Ayuda", - "InfoPage": "Información", - "History": "Historial" - }, - "Use fromKeyName to get the TKey from the JSON key": {}, - "Clear previous translations to avoid stale values when switching languages": {}, - "BackupList": { - "NotesDetail": "Notas", - "BackupNameDetail": "NombreCopia", - "ExportAsPdfTooltip": "Exportar como PDF", - "EditPopup": "Editar", - "ResearchBarTooltip": "Barra de búsqueda", - "InitialPathDetail": "RutaInicial", - "ExportAs": "Exportar como: ", - "RenameBackupPopup": "Renombrar copia de seguridad", - "NextBackupDateDetail": "PróximaFecha", - "AutoBackupPopup": "Copia automática", - "BackupNameColumn": "Nombre de la Copia de Seguridad", - "BackupPopup": "Copia de seguridad", - "BackupCountDetail": "NúmeroCopias", - "ExportAsCsvTooltip": "Exportar como CSV", - "InitialPathColumn": "Ruta Inicial", - "MaxBackupsToKeepDetail": "MaximoCopiasDeSeguridadMantener", - "DeletePopup": "Eliminar", - "OpenDestinationFolderPopup": "Abrir ruta de destino", - "LastBackupColumn": "Última Copia de Seguridad", - "SingleBackupPopup": "Ejecutar copia única", - "AddBackupTooltip": "Agregar nueva copia de seguridad", - "CopyTextPopup": "Copiar texto", - "DestinationPathDetail": "RutaDestino", - "CopyDestinationPathPopup": "Copiar ruta de destino", - "LastBackupDetail": "ÚltimaCopia", - "OpenInitialFolderPopup": "Abrir ruta inicial", - "ResearchBarPlaceholder": "Buscar...", - "InterruptPopup": "Interrumpir", - "DuplicatePopup": "Duplicar", - "NextBackupDateColumn": "Próxima Fecha de Copia", - "DestinationPathColumn": "Ruta de Destino", - "AutomaticBackupColumn": "Copia Automática", - "LastUpdateDateDetail": "ÚltimaActualización", - "TimeIntervalDetail": "IntervaloTiempo", - "CopyBackupNamePopup": "Copiar nombre de copia", - "CreationDateDetail": "FechaCreación", - "CopyInitialPathPopup": "Copiar ruta inicial" - }, - "TabbedFrames": { - "BackupEntry": "Entrada de Copia de Seguridad", - "BackupList": "Lista de Copias de Seguridad" - }, - "UserDialog": { - "ErrorMessageForMissingData": "Por favor, completa todos los campos requeridos.", - "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager", - "EmailConfirmationSubject": "¡Gracias por elegir Backup Manager!", - "Surname": "Apellido", - "ErrorMessageForWrongEmail": "La dirección de correo electrónico proporcionada no es válida. Por favor, proporciona una correcta.", - "Name": "Nombre", - "Email": "Correo electrónico", - "UserTitle": "Inserta tus datos" - }, - "SETTINGS": {}, - "BackupEntry": { - "TimePickerTooltip": "Selector de tiempo", - "MaxBackupsToKeepTooltip": "Número máximo de copias de seguridad antes de eliminar las más antiguas.", - "BackupName": "Nombre de la copia de seguridad", - "AutoBackupButton": "Copia de Seguridad Automática", - "InitialPathTooltip": "(Requerido) Ruta inicial", - "CurrentFile": "Archivo actual", - "InitialFileChooserTooltip": "Abrir explorador de archivos", - "LastBackup": "Última copia de seguridad", - "SingleBackupButton": "Copia de Seguridad Única", - "AutoBackupButtonON": "Copia de Seguridad Automática (ACTIVADA)", - "AutoBackupButtonOFF": "Copia de Seguridad Automática (DESACTIVADA)", - "DestinationPathTooltip": "(Requerido) Ruta de destino", - "MaxBackupsToKeep": "Máximo de copias de seguridad a mantener", - "SingleBackupTooltip": "Realizar la copia de seguridad", - "AutoBackupTooltip": "Activar/Desactivar copia de seguridad automática", - "NotesTooltip": "(Opcional) Descripción de la copia de seguridad", - "DestinationFileChooserTooltip": "Abrir explorador de archivos", - "BackupNameTooltip": "(Requerido) Nombre de la copia de seguridad", - "PageTitle": "Entrada de Copia de Seguridad", - "Notes": "Notas" - } + "General": { + "AppName": "Backup Manager", + "CancelButton": "Cancelar", + "Backup": "Copia de Seguridad", + "Version": "Versión", + "ApplyButton": "Aplicar", + "OkButton": "Aceptar", + "From": "Desde", + "SaveButton": "Guardar", + "CloseButton": "Cerrar", + "CreateButton": "Crear", + "EditButton": "Editar", + "DeleteButton": "Eliminar", + "QuickSearch": "Búsqueda rápida", + "To": "A" + }, + "TrayIcon": { + "SuccessMessage": "\nLa copia de seguridad se completó con éxito:", + "ExitAction": "Salir", + "ErrorMessageFilesNotExisting": "\nError en la copia automática.\n¡Una o ambas rutas no existen!", + "TrayTooltip": "Servicio de Copias de Seguridad", + "OpenAction": "Acceso Rápido", + "ErrorMessageInputMissing": "\nError en la copia automática.\n¡Faltan datos de entrada!", + "ErrorMessageSamePaths": "\nError en la copia automática.\nLa ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!" + }, + "Dialogs": { + "WarningGenericTitle": "Advertencia", + "SuccessfullyExportedToCsvMessage": "¡Copias de seguridad exportadas a CSV con éxito!", + "ErrorMessageForPathNotExisting": "¡Una o ambas rutas no existen!", + "ConfirmationDeletionMessage": "¿Está seguro de que desea eliminar las filas seleccionadas?", + "ExceptionMessageReportMessage": "Por favor, informe de este error, ya sea con una captura de pantalla o copiando el siguiente texto del error (se agradece proporcionar una descripción de las acciones realizadas antes del error):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Error al exportar copias de seguridad a CSV: ", + "ErrorGenericTitle": "Error", + "ErrorMessageForExportingToPdf": "Error al exportar copias de seguridad a PDF: ", + "BackupListCorrectlyImportedTitle": "Menú Importar", + "ErrorWrongTimeInterval": "El intervalo de tiempo no es correcto", + "ErrorMessageOpenHistoryFile": "Error al abrir el archivo de historial.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "La ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "¡Enlace de compartir copiado al portapapeles!", + "ErrorMessageForSavingFileWithPathsEmpty": "No se puede guardar el archivo. Tanto la ruta inicial como la de destino deben especificarse y no pueden estar vacías", + "BackupListCorrectlyExportedMessage": "¡Lista de copias de seguridad exportada correctamente al escritorio!", + "BackupSavedCorrectlyMessage": "guardada con éxito.", + "SettedEveryMessage": "\nSe establece cada", + "WarningBackupAlreadyInProgressMessage": "Ya hay una copia de seguridad en progreso. No es posible realizar copias de seguridad en paralelo.", + "WarningShortTimeIntervalMessage": "El intervalo de tiempo seleccionado es muy corto. Para un funcionamiento óptimo, recomendamos configurarlo en al menos una hora. ¿Quieres continuar de todos modos?", + "ConfirmationMessageCancelAutoBackup": "¿Está seguro de que desea cancelar las copias automáticas para esta entrada?", + "ErrorMessageCountingFiles": "Error al calcular los archivos para la copia de seguridad.", + "ErrorMessageForFolderNotExisting": "La carpeta no existe o no es válida", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Error en la operación de copia: ¡la ruta inicial es incorrecta!", + "ErrorMessageInputMissingGeneric": "¡Faltan datos de entrada!", + "BackupNameInput": "Nombre de la copia", + "CsvNameMessageInput": "Introduce el nombre del archivo CSV.", + "ConfirmationDeletionTitle": "Confirmar Eliminación", + "ErrorMessageForWrongFileExtensionMessage": "Error: Seleccione un archivo JSON válido.", + "ErrorMessageZippingGeneric": "Error al comprimir los archivos.", + "ErrorMessageNotSupportedEmailGeneric": "Su sistema no admite el envío de correos electrónicos.", + "ErrorMessageZippingIO": "Error al comprimir los archivos: error de E/S.", + "SuccessfullyExportedToPdfMessage": "¡Copias de seguridad exportadas a PDF con éxito!", + "DuplicatedFileNameMessage": "El archivo ya existe. ¿Sobrescribir?", + "AutoBackupActivatedMessage": "La copia automática se ha activado", + "DaysMessage": " días", + "ExceptionMessageReportButton": "Informar del problema", + "ErrorMessageInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos.", + "ExceptionMessageClipboardMessage": "El texto del error se ha copiado al portapapeles.", + "SuccessGenericTitle": "Éxito", + "ConfirmationMessageForClear": "¿Está seguro de que desea limpiar los campos?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "¡Lista de copias de seguridad importada correctamente!", + "DuplicatedBackupNameMessage": "Ya existe una copia con el mismo nombre. ¿Desea sobrescribirla?", + "ExceptionMessageClipboardButton": "Copiar al portapapeles", + "ErrorMessageZippingSecurity": "Error al comprimir los archivos: Error de seguridad.", + "InterruptBackupProcessMessage": "¿Está seguro de que desea detener esta copia?", + "BackupListCorrectlyExportedTitle": "Menú Exportar", + "ConfirmationMessageForUnsavedChanges": "Hay cambios no guardados. ¿Desea guardarlos antes de pasar a otra copia de seguridad?", + "ExceptionMessageTitle": "Error...", + "PdfNameMessageInput": "Introduce el nombre del archivo PDF.", + "ErrorMessageNotSupportedEmail": "Su sistema no admite el envío de correos electrónicos directamente desde esta aplicación.", + "ErrorMessageForSavingFile": "Error al guardar el archivo", + "ErrorMessageUnableToSendEmail": "No se pudo enviar el correo electrónico. Por favor, intente de nuevo más tarde.", + "ConfirmationMessageBeforeDeleteBackup": "¿Está seguro de que desea eliminar este elemento? Tenga en cuenta que esta acción no se puede deshacer.", + "ErrorMessageOpeningWebsite": "No se pudo abrir la página web. Por favor, intente de nuevo.", + "ErrorSavingBackupMessage": "Error al guardar la copia de seguridad", + "ConfirmationRequiredTitle": "Confirmación requerida", + "BackupNameAlreadyUsedMessage": "¡Nombre de copia ya en uso!", + "ErrorMessageForWrongFileExtensionTitle": "Archivo no válido", + "BackupSavedCorrectlyTitle": "Copia Guardada" + }, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervalo de tiempo para copias automáticas", + "Days": "Días", + "Hours": "Horas", + "SpinnerTooltip": "Rueda del ratón para ajustar el valor", + "Description": "Seleccione la frecuencia para realizar la copia automática \neligiendo los días, horas y minutos.", + "Minutes": "Minutos" + }, + "Subscription": { + "ExpiredTitle": "La suscripción de Backup Manager ha expirado", + "ExpiringMessage": "Tu suscripción a Backup Manager está a punto de expirar.\nLas copias de seguridad automáticas seguirán funcionando hasta la fecha de vencimiento.\nContacta con el soporte para renovarla.", + "ExpiringTitle": "La suscripción de Backup Manager está por vencer", + "ExpiredMessage": "Tu suscripción a Backup Manager ha expirado.\nLas copias de seguridad automáticas ya no se ejecutarán.\nContacta con el soporte para reactivarla.", + "ActiveLabel": "Activo", + "ExpiringLabel": "Próximo a expirar", + "ExpiredLabel": "Expirado", + "Status": "Estado de la suscripción", + "ValidFrom": "Válido desde", + "ValidTo": "Válido hasta", + "ContactUs": "Contáctanos", + "ToExtend": "para extender el período de suscripción." + }, + "ProgressBackupFrame": { + "StatusLoading": "Cargando...", + "ProgressBackupTitle": "Copia de Seguridad en Progreso", + "StatusCompleted": "¡Copia completada!" + }, + "Menu": { + "Import": "Importar lista de copias de seguridad", + "Save": "Guardar", + "About": "Acerca de", + "File": "Archivo", + "Support": "Soporte", + "BugReport": "Reportar un error", + "Quit": "Salir", + "Options": "Opciones", + "New": "Nuevo", + "Clear": "Limpiar", + "Share": "Compartir", + "Preferences": "Preferencias", + "Website": "Sitio web", + "SaveWithName": "Guardar con nombre", + "Export": "Exportar lista de copias de seguridad", + "Help": "Ayuda", + "InfoPage": "Información", + "History": "Historial", + "SubmenuMain": "PRINCIPAL", + "SubmenuOther": "OTROS", + "Donate": "Apoya el proyecto", + "Backups": "Lista de copias de seguridad", + "CreateBackup": "Crear nueva copia de seguridad", + "ImportBackup": "Importar copias de seguridad desde CSV", + "ExportBackup": "Exportar copias de seguridad a CSV", + "Dashboard": "Panel", + "Github": "Página de GitHub", + "Paypal": "PayPal", + "BuyMeACoffe": "Invítame a un café", + "ContactUs": "Contáctanos", + "Subscription": "Suscripción" + }, + "BackupList": { + "NotesDetail": "Notas", + "BackupNameDetail": "NombreCopia", + "ExportAsPdfTooltip": "Exportar como PDF", + "EditPopup": "Editar", + "ResearchBarTooltip": "Barra de búsqueda", + "InitialPathDetail": "RutaInicial", + "ExportAs": "Exportar como: ", + "RenameBackupPopup": "Renombrar copia de seguridad", + "NextBackupDateDetail": "PróximaFecha", + "AutoBackupPopup": "Copia automática", + "BackupNameColumn": "Nombre de la Copia de Seguridad", + "BackupPopup": "Copia de seguridad", + "BackupCountDetail": "NúmeroCopias", + "ExportAsCsvTooltip": "Exportar como CSV", + "InitialPathColumn": "Ruta Inicial", + "MaxBackupsToKeepDetail": "MaximoCopiasDeSeguridadMantener", + "DeletePopup": "Eliminar", + "OpenDestinationFolderPopup": "Abrir ruta de destino", + "LastBackupColumn": "Última Copia de Seguridad", + "SingleBackupPopup": "Ejecutar copia única", + "AddBackupTooltip": "Agregar nueva copia de seguridad", + "CopyTextPopup": "Copiar texto", + "DestinationPathDetail": "RutaDestino", + "CopyDestinationPathPopup": "Copiar ruta de destino", + "LastBackupDetail": "ÚltimaCopia", + "OpenInitialFolderPopup": "Abrir ruta inicial", + "ResearchBarPlaceholder": "Buscar...", + "InterruptPopup": "Interrumpir", + "DuplicatePopup": "Duplicar", + "NextBackupDateColumn": "Próxima Fecha de Copia", + "DestinationPathColumn": "Ruta de Destino", + "AutomaticBackupColumn": "Copia Automática", + "LastUpdateDateDetail": "ÚltimaActualización", + "TimeIntervalDetail": "IntervaloTiempo", + "CopyBackupNamePopup": "Copiar nombre de copia", + "CreationDateDetail": "FechaCreación", + "CopyInitialPathPopup": "Copiar ruta inicial", + "BackupListTitle": "Lista de copias de seguridad", + "BackupListDescription": "Gestiona y supervisa las configuraciones de copia de seguridad, incluyendo creación, edición, programación y ejecución.", + "TimeIntervalColumn": "Intervalo (dd.HH:mm)", + "MaxBackupsColumn": "Número máximo de copias a conservar" + }, + "TabbedFrames": { + "BackupEntry": "Entrada de Copia de Seguridad", + "BackupList": "Lista de Copias de Seguridad" + }, + "UserDialog": { + "ErrorMessageForMissingData": "Por favor, completa todos los campos requeridos.", + "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager", + "EmailConfirmationSubject": "¡Gracias por elegir Backup Manager!", + "Surname": "Apellido", + "ErrorMessageForWrongEmail": "La dirección de correo electrónico proporcionada no es válida. Por favor, proporciona una correcta.", + "Name": "Nombre", + "Email": "Correo electrónico", + "UserTitle": "Inserta tus datos", + "UserDescription": "Por favor introduce tus datos para acceder al sistema", + "UserNamePlaceholder": "Introduce tu nombre", + "UserSurnamePlaceholder": "Introduce tus apellidos", + "UserEmailPlaceholder": "Introduce tu correo electrónico" + }, + "BackupEntry": { + "TimePickerTooltip": "Selector de tiempo", + "MaxBackupsToKeepTooltip": "Número máximo de copias de seguridad antes de eliminar las más antiguas.", + "BackupName": "Nombre de la copia de seguridad", + "AutoBackupButton": "Copia de Seguridad Automática", + "InitialPathTooltip": "(Requerido) Ruta inicial", + "CurrentFile": "Archivo actual", + "InitialFileChooserTooltip": "Abrir explorador de archivos", + "LastBackup": "Última copia de seguridad", + "SingleBackupButton": "Copia de Seguridad Única", + "AutoBackupButtonON": "Copia de Seguridad Automática (ACTIVADA)", + "AutoBackupButtonOFF": "Copia de Seguridad Automática (DESACTIVADA)", + "DestinationPathTooltip": "(Requerido) Ruta de destino", + "MaxBackupsToKeep": "Máximo de copias de seguridad a mantener", + "SingleBackupTooltip": "Realizar la copia de seguridad", + "AutoBackupTooltip": "Activar/Desactivar copia de seguridad automática", + "NotesTooltip": "(Opcional) Descripción de la copia de seguridad", + "DestinationFileChooserTooltip": "Abrir explorador de archivos", + "BackupNameTooltip": "(Requerido) Nombre de la copia de seguridad", + "PageTitle": "Entrada de Copia de Seguridad", + "Notes": "Notas", + "PageSubtitleCreate": "Crear copia de seguridad", + "PageSubtitleEdit": "Editar copia de seguridad", + "PageSubtitleInfo": "Información de la copia de seguridad", + "PageSubtitleSettings": "Configuración de copias de seguridad", + "Paths": "Rutas", + "BackupNamePlaceholder": "Nombre de la copia de seguridad (único)", + "InitialPathPlaceholder": "Ruta de origen p. ej. C:\\Users\\Admin\\Documents", + "DestinationPathPlaceholder": "Carpeta de destino p. ej. D:\\Backups" + }, + "HistoryLogs": { + "HistoryLogsTitle": "Registros del historial", + "HistoryLogsDescription": "Aquí puedes encontrar los registros de la aplicación, útiles para la resolución de problemas y para comprender el comportamiento de la aplicación a lo largo del tiempo." + }, + "About": { + "AboutSystemInformation": "Información del sistema", + "AboutMessageBody": "<html><b>Backup Manager</b> es una aplicación simple y potente diseñada para automatizar las copias de seguridad de carpetas y subcarpetas.<br><br> Los usuarios pueden programar copias automáticas o ejecutar copias manuales en cualquier momento.<br><br> El historial de copias se almacena de forma segura, permitiendo un control total sobre los datos guardados.<br><p>Visita el <a href=[PROJECT_WEBSITE]>sitio web del proyecto</a> para más información.</p></html>" + }, + "Dashboard": { + "DashboardTitle": "Panel analítico de backups", + "DashboardCardTotalConfigurations": "Configuraciones de backup totales", + "DashboardCardTotalExecutions": "Ejecuciones de backup totales", + "DashboardCardSuccessRate": "Tasa de éxito", + "DashboardCardAvgDuration": "Duración media del backup", + "DashboardCardCompressionRate": "Tasa de compresión", + "DashboardChartExecutions": "Ejecuciones de backup", + "DashboardChartAvgDuration": "Duración media del backup (min)" + }, + "Settings": { + "SettingsLayoutTab": "Diseño", + "SettingsStyleTab": "Estilo", + "SettingsWindowsLayout": "Diseño de ventana", + "SettingsWindowsRight": "De derecha a izquierda", + "SettingsWindowsFull": "Contenido completo de ventana", + "SettingsDrawerLayout": "Diseño del panel", + "SettingsDrawerLeft": "Izquierda", + "SettingsDrawerLeading": "Inicio", + "SettingsDrawerTrailing": "Final", + "SettingsDrawerRight": "Derecha", + "SettingsDrawerTop": "Arriba", + "SettingsDrawerBottom": "Abajo", + "SettingsModalOption": "Opción modal predeterminada", + "SettingsModalAnimation": "Activar animación", + "SettingsModalClose": "Cerrar al presionar ESC", + "SettingsLanguagesLayout": "Idioma", + "SettingsAccentLayout": "Color de acento", + "SettingsColorPickerLayout": "Selector de color", + "SettingsDrawerLineLayout": "Estilo de línea del panel", + "SettingsDrawerLineCurved": "Línea curva", + "SettingsDrawerDotLine": "Línea punteada recta", + "SettingsLineStyleLayout": "Opción estilo de línea", + "SettingsLineStyleRettangle": "Rectángulo", + "SettingsLineStyleEllipse": "Elipse", + "SettingsLineStyleLine": "Línea", + "SettingsLineStyleCurved": "Curva", + "SettingsColorOptionLayout": "Opción de color", + "SettingsColorOptionPainted": "Colorear la línea seleccionada" + }, + "SearchBar": { + "SearcTitle": "Buscar...", + "SearchNoRecent": "No hay búsquedas recientes", + "SearchNoResult": "Sin resultados para", + "SearchFavorite": "Favorito", + "SearchRecent": "Recientes" + } } diff --git a/src/main/resources/res/languages/fra.json b/src/main/resources/res/languages/fra.json index 5b1d6979..1b1cc832 100644 --- a/src/main/resources/res/languages/fra.json +++ b/src/main/resources/res/languages/fra.json @@ -1,214 +1,300 @@ { - "TrayIcon": { - "SuccessMessage": "\nLa sauvegarde a été effectuée avec succès :", - "ExitAction": "Quitter", - "ErrorMessageFilesNotExisting": "\nErreur lors de la sauvegarde automatique.\nUn ou les deux chemins n'existent pas !", - "TrayTooltip": "Service de Sauvegardes", - "OpenAction": "Accès Rapide", - "ErrorMessageInputMissing": "\nErreur lors de la sauvegarde automatique.\nEntrée manquante !", - "ErrorMessageSamePaths": "\nErreur lors de la sauvegarde automatique.\nLe chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !" - }, - "If value is null or empty, fall back to the default value from the enum": {}, - "Dialogs": { - "WarningGenericTitle": "Avertissement", - "SuccessfullyExportedToCsvMessage": "Sauvegardes exportées en CSV avec succès !", - "ErrorMessageForPathNotExisting": "Un ou les deux chemins n'existent pas !", - "ConfirmationDeletionMessage": "Êtes-vous sûr de vouloir supprimer les lignes sélectionnées ?", - "ExceptionMessageReportMessage": "Veuillez signaler cette erreur, soit avec une capture d'écran, soit en copiant le texte d'erreur suivant (il est recommandé de fournir une description des actions effectuées avant l'erreur) :", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Erreur lors de l'exportation des sauvegardes en CSV : ", - "ErrorGenericTitle": "Erreur", - "ErrorMessageForExportingToPdf": "Erreur lors de l'exportation des sauvegardes en PDF : ", - "BackupListCorrectlyImportedTitle": "Menu Importer", - "ErrorWrongTimeInterval": "L'intervalle de temps est incorrect", - "ErrorMessageOpenHistoryFile": "Erreur lors de l'ouverture du fichier d'historique.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "Le chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Lien de partage copié dans le presse-papiers !", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossible d'enregistrer le fichier. Les chemins initial et de destination doivent être spécifiés et ne peuvent pas être vides", - "BackupListCorrectlyExportedMessage": "Liste de sauvegarde exportée avec succès sur le bureau !", - "BackupSavedCorrectlyMessage": "enregistrée avec succès.", - "SettedEveryMessage": "\nEst défini tous les", - "WarningBackupAlreadyInProgressMessage": "Une sauvegarde est déjà en cours. Il n'est pas possible d'effectuer des sauvegardes en parallèle.", - "WarningShortTimeIntervalMessage": "L'intervalle de temps sélectionné est très court. Pour un fonctionnement optimal, nous recommandons de le régler à au moins une heure. Voulez-vous quand même continuer ?", - "ConfirmationMessageCancelAutoBackup": "Êtes-vous sûr de vouloir annuler les sauvegardes automatiques pour cette entrée ?", - "ErrorMessageCountingFiles": "Erreur lors du calcul des fichiers à sauvegarder.", - "ErrorMessageForFolderNotExisting": "Le dossier n'existe pas ou est invalide", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Erreur lors de l'opération de sauvegarde : le chemin initial est incorrect !", - "ErrorMessageInputMissingGeneric": "Entrée manquante !", - "BackupNameInput": "Nom de la sauvegarde", - "CsvNameMessageInput": "Entrez le nom du fichier CSV.", - "ConfirmationDeletionTitle": "Confirmer la suppression", - "ErrorMessageForWrongFileExtensionMessage": "Erreur : Veuillez sélectionner un fichier JSON valide.", - "ErrorMessageZippingGeneric": "Erreur lors de la compression des fichiers.", - "ErrorMessageNotSupportedEmailGeneric": "Votre système ne prend pas en charge l'envoi d'e-mails.", - "ErrorMessageZippingIO": "Erreur lors de la compression des fichiers : erreur d'E/S.", - "SuccessfullyExportedToPdfMessage": "Sauvegardes exportées en PDF avec succès !", - "DuplicatedFileNameMessage": "Le fichier existe déjà. Écraser?", - "AutoBackupActivatedMessage": "La sauvegarde automatique a été activée", - "DaysMessage": " jours", - "ExceptionMessageReportButton": "Signaler le problème", - "ErrorMessageInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores.", - "ExceptionMessageClipboardMessage": "Le texte de l'erreur a été copié dans le presse-papiers.", - "SuccessGenericTitle": "Succès", - "ConfirmationMessageForClear": "Êtes-vous sûr de vouloir effacer les champs ?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Liste de sauvegarde importée avec succès !", - "DuplicatedBackupNameMessage": "Une sauvegarde avec le même nom existe déjà. Voulez-vous l'écraser ?", - "ExceptionMessageClipboardButton": "Copier dans le presse-papiers", - "ErrorMessageZippingSecurity": "Erreur lors de la compression des fichiers : Erreur de sécurité.", - "InterruptBackupProcessMessage": "Êtes-vous sûr de vouloir arrêter cette sauvegarde ?", - "BackupListCorrectlyExportedTitle": "Menu Exporter", - "ConfirmationMessageForUnsavedChanges": "Des modifications non enregistrées existent. Voulez-vous les enregistrer avant de passer à une autre sauvegarde ?", - "ExceptionMessageTitle": "Erreur...", - "PdfNameMessageInput": "Entrez le nom du fichier PDF.", - "ErrorMessageNotSupportedEmail": "Votre système ne prend pas en charge l'envoi d'e-mails directement depuis cette application.", - "ErrorMessageForSavingFile": "Erreur lors de l'enregistrement du fichier", - "ErrorMessageUnableToSendEmail": "Impossible d'envoyer l'e-mail. Veuillez réessayer plus tard.", - "ConfirmationMessageBeforeDeleteBackup": "Êtes-vous sûr de vouloir supprimer cet élément ? Cette action est irréversible.", - "ErrorMessageOpeningWebsite": "Échec de l'ouverture de la page web. Veuillez réessayer.", - "ErrorSavingBackupMessage": "Erreur lors de l'enregistrement de la sauvegarde", - "ConfirmationRequiredTitle": "Confirmation requise", - "BackupNameAlreadyUsedMessage": "Nom de sauvegarde déjà utilisé !", - "ErrorMessageForWrongFileExtensionTitle": "Fichier invalide", - "BackupSavedCorrectlyTitle": "Sauvegarde Enregistrée" - }, - "User dialog": {}, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervalle de temps pour les sauvegardes automatiques", - "Days": "Jours", - "Hours": "Heures", - "SpinnerTooltip": "Roulette de la souris pour ajuster la valeur", - "Description": "Sélectionnez la fréquence des sauvegardes automatiques en \nchoisissant le nombre de jours, d'heures et de minutes.", - "Minutes": "Minutes" - }, - "HISTORY_LOGS": {}, - "Constructor to assign both key and default value": {}, - "Subscription": { - "ExpiredTitle": "L'abonnement Backup Manager a expiré", - "ExpiringMessage": "Votre abonnement Backup Manager est sur le point d'expirer.\nLes sauvegardes automatiques continueront de fonctionner jusqu'à la date d'expiration.\nVeuillez contacter le support pour le renouveler.", - "ExpiringTitle": "L'abonnement Backup Manager expire bientôt", - "ExpiredMessage": "Votre abonnement Backup Manager a expiré.\nLes sauvegardes automatiques ne seront plus exécutées.\nVeuillez contacter le support pour le réactiver." - }, - "ProgressBackupFrame": { - "StatusLoading": "Chargement...", - "ProgressBackupTitle": "Sauvegarde en cours", - "StatusCompleted": "Sauvegarde terminée !" - }, - "Lookup by keyName (JSON key)": {}, - "DASHBOARD": {}, - "ABOUT": {}, - "General": { - "AppName": "Backup Manager", - "CancelButton": "Annuler", - "Backup": "Sauvegarde", - "Version": "Version", - "ApplyButton": "Appliquer", - "OkButton": "OK", - "From": "De", - "SaveButton": "Enregistrer", - "CloseButton": "Fermer", - "To": "À" - }, - "Menu": { - "Import": "Importer la liste de sauvegarde", - "Save": "Enregistrer", - "About": "À propos", - "File": "Fichier", - "Support": "Assistance", - "BugReport": "Signaler un bug", - "Quit": "Quitter", - "Options": "Options", - "New": "Nouveau", - "Clear": "Effacer", - "Share": "Partager", - "Preferences": "Préférences", - "Website": "Site web", - "SaveWithName": "Enregistrer sous", - "Export": "Exporter la liste de sauvegarde", - "Help": "Aide", - "InfoPage": "Info", - "History": "Historique" - }, - "Use fromKeyName to get the TKey from the JSON key": {}, - "Clear previous translations to avoid stale values when switching languages": {}, - "BackupList": { - "NotesDetail": "Notes", - "BackupNameDetail": "NomSauvegarde", - "ExportAsPdfTooltip": "Exporter en tant que PDF", - "EditPopup": "Modifier", - "ResearchBarTooltip": "Barre de recherche", - "InitialPathDetail": "CheminInitial", - "ExportAs": "Exporter en tant que : ", - "RenameBackupPopup": "Renommer la sauvegarde", - "NextBackupDateDetail": "ProchaineSauvegarde", - "AutoBackupPopup": "Sauvegarde automatique", - "BackupNameColumn": "Nom de la Sauvegarde", - "BackupPopup": "Sauvegarde", - "BackupCountDetail": "NombreSauvegardes", - "ExportAsCsvTooltip": "Exporter en tant que CSV", - "InitialPathColumn": "Chemin Initial", - "MaxBackupsToKeepDetail": "NombreMaxSauvegardesConserver", - "DeletePopup": "Supprimer", - "OpenDestinationFolderPopup": "Ouvrir le chemin de destination", - "LastBackupColumn": "Dernière Sauvegarde", - "SingleBackupPopup": "Effectuer une sauvegarde unique", - "AddBackupTooltip": "Ajouter une nouvelle sauvegarde", - "CopyTextPopup": "Copier le texte", - "DestinationPathDetail": "CheminDestination", - "CopyDestinationPathPopup": "Copier le chemin de destination", - "LastBackupDetail": "DernièreSauvegarde", - "OpenInitialFolderPopup": "Ouvrir le chemin initial", - "ResearchBarPlaceholder": "Rechercher...", - "InterruptPopup": "Interrompre", - "DuplicatePopup": "Dupliquer", - "NextBackupDateColumn": "Prochaine Date de Sauvegarde", - "DestinationPathColumn": "Chemin de Destination", - "AutomaticBackupColumn": "Sauvegarde Automatique", - "LastUpdateDateDetail": "DernièreMiseJour", - "TimeIntervalDetail": "IntervalleTemps", - "CopyBackupNamePopup": "Copier le nom de la sauvegarde", - "CreationDateDetail": "DateCréation", - "CopyInitialPathPopup": "Copier le chemin initial" - }, - "TabbedFrames": { - "BackupEntry": "Entrée de Sauvegarde", - "BackupList": "Liste de Sauvegardes" - }, - "UserDialog": { - "ErrorMessageForMissingData": "Veuillez remplir tous les champs requis.", - "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager", - "EmailConfirmationSubject": "Merci d'avoir choisi Backup Manager !", - "Surname": "Nom de famille", - "ErrorMessageForWrongEmail": "L'adresse e-mail fournie n'est pas valide. Veuillez en fournir une correcte.", - "Name": "Prénom", - "Email": "E-Mail", - "UserTitle": "Entrez vos informations" - }, - "SETTINGS": {}, - "BackupEntry": { - "TimePickerTooltip": "Sélecteur de temps", - "MaxBackupsToKeepTooltip": "Nombre maximum de sauvegardes avant de supprimer les plus anciennes.", - "BackupName": "Nom de la sauvegarde", - "AutoBackupButton": "Sauvegarde Automatique", - "InitialPathTooltip": "(Requis) Chemin initial", - "CurrentFile": "Fichier actuel", - "InitialFileChooserTooltip": "Ouvrir l'explorateur de fichiers", - "LastBackup": "Dernière sauvegarde", - "SingleBackupButton": "Sauvegarde Unique", - "AutoBackupButtonON": "Sauvegarde Automatique (ACTIVÉE)", - "AutoBackupButtonOFF": "Sauvegarde Automatique (DÉSACTIVÉE)", - "DestinationPathTooltip": "(Requis) Chemin de destination", - "MaxBackupsToKeep": "Nombre maximum de sauvegardes à conserver", - "SingleBackupTooltip": "Effectuer la sauvegarde", - "AutoBackupTooltip": "Activer/Désactiver la sauvegarde automatique", - "NotesTooltip": "(Optionnel) Description de la sauvegarde", - "DestinationFileChooserTooltip": "Ouvrir l'explorateur de fichiers", - "BackupNameTooltip": "(Requis) Nom de la sauvegarde", - "PageTitle": "Entrée de Sauvegarde", - "Notes": "Notes" - } + "General": { + "AppName": "Backup Manager", + "CancelButton": "Annuler", + "Backup": "Sauvegarde", + "Version": "Version", + "ApplyButton": "Appliquer", + "OkButton": "OK", + "From": "De", + "SaveButton": "Enregistrer", + "CloseButton": "Fermer", + "CreateButton": "Créer", + "EditButton": "Modifier", + "DeleteButton": "Supprimer", + "QuickSearch": "Recherche rapide", + "To": "À" + }, + "TrayIcon": { + "SuccessMessage": "\nLa sauvegarde a été effectuée avec succès :", + "ExitAction": "Quitter", + "ErrorMessageFilesNotExisting": "\nErreur lors de la sauvegarde automatique.\nUn ou les deux chemins n'existent pas !", + "TrayTooltip": "Service de Sauvegardes", + "OpenAction": "Accès Rapide", + "ErrorMessageInputMissing": "\nErreur lors de la sauvegarde automatique.\nEntrée manquante !", + "ErrorMessageSamePaths": "\nErreur lors de la sauvegarde automatique.\nLe chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !" + }, + "Dialogs": { + "WarningGenericTitle": "Avertissement", + "SuccessfullyExportedToCsvMessage": "Sauvegardes exportées en CSV avec succès !", + "ErrorMessageForPathNotExisting": "Un ou les deux chemins n'existent pas !", + "ConfirmationDeletionMessage": "Êtes-vous sûr de vouloir supprimer les lignes sélectionnées ?", + "ExceptionMessageReportMessage": "Veuillez signaler cette erreur, soit avec une capture d'écran, soit en copiant le texte d'erreur suivant (il est recommandé de fournir une description des actions effectuées avant l'erreur) :", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Erreur lors de l'exportation des sauvegardes en CSV : ", + "ErrorGenericTitle": "Erreur", + "ErrorMessageForExportingToPdf": "Erreur lors de l'exportation des sauvegardes en PDF : ", + "BackupListCorrectlyImportedTitle": "Menu Importer", + "ErrorWrongTimeInterval": "L'intervalle de temps est incorrect", + "ErrorMessageOpenHistoryFile": "Erreur lors de l'ouverture du fichier d'historique.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Le chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Lien de partage copié dans le presse-papiers !", + "ErrorMessageForSavingFileWithPathsEmpty": "Impossible d'enregistrer le fichier. Les chemins initial et de destination doivent être spécifiés et ne peuvent pas être vides", + "BackupListCorrectlyExportedMessage": "Liste de sauvegarde exportée avec succès sur le bureau !", + "BackupSavedCorrectlyMessage": "enregistrée avec succès.", + "SettedEveryMessage": "\nEst défini tous les", + "WarningBackupAlreadyInProgressMessage": "Une sauvegarde est déjà en cours. Il n'est pas possible d'effectuer des sauvegardes en parallèle.", + "WarningShortTimeIntervalMessage": "L'intervalle de temps sélectionné est très court. Pour un fonctionnement optimal, nous recommandons de le régler à au moins une heure. Voulez-vous quand même continuer ?", + "ConfirmationMessageCancelAutoBackup": "Êtes-vous sûr de vouloir annuler les sauvegardes automatiques pour cette entrée ?", + "ErrorMessageCountingFiles": "Erreur lors du calcul des fichiers à sauvegarder.", + "ErrorMessageForFolderNotExisting": "Le dossier n'existe pas ou est invalide", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Erreur lors de l'opération de sauvegarde : le chemin initial est incorrect !", + "ErrorMessageInputMissingGeneric": "Entrée manquante !", + "BackupNameInput": "Nom de la sauvegarde", + "CsvNameMessageInput": "Entrez le nom du fichier CSV.", + "ConfirmationDeletionTitle": "Confirmer la suppression", + "ErrorMessageForWrongFileExtensionMessage": "Erreur : Veuillez sélectionner un fichier JSON valide.", + "ErrorMessageZippingGeneric": "Erreur lors de la compression des fichiers.", + "ErrorMessageNotSupportedEmailGeneric": "Votre système ne prend pas en charge l'envoi d'e-mails.", + "ErrorMessageZippingIO": "Erreur lors de la compression des fichiers : erreur d'E/S.", + "SuccessfullyExportedToPdfMessage": "Sauvegardes exportées en PDF avec succès !", + "DuplicatedFileNameMessage": "Le fichier existe déjà. Écraser?", + "AutoBackupActivatedMessage": "La sauvegarde automatique a été activée", + "DaysMessage": " jours", + "ExceptionMessageReportButton": "Signaler le problème", + "ErrorMessageInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores.", + "ExceptionMessageClipboardMessage": "Le texte de l'erreur a été copié dans le presse-papiers.", + "SuccessGenericTitle": "Succès", + "ConfirmationMessageForClear": "Êtes-vous sûr de vouloir effacer les champs ?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Liste de sauvegarde importée avec succès !", + "DuplicatedBackupNameMessage": "Une sauvegarde avec le même nom existe déjà. Voulez-vous l'écraser ?", + "ExceptionMessageClipboardButton": "Copier dans le presse-papiers", + "ErrorMessageZippingSecurity": "Erreur lors de la compression des fichiers : Erreur de sécurité.", + "InterruptBackupProcessMessage": "Êtes-vous sûr de vouloir arrêter cette sauvegarde ?", + "BackupListCorrectlyExportedTitle": "Menu Exporter", + "ConfirmationMessageForUnsavedChanges": "Des modifications non enregistrées existent. Voulez-vous les enregistrer avant de passer à une autre sauvegarde ?", + "ExceptionMessageTitle": "Erreur...", + "PdfNameMessageInput": "Entrez le nom du fichier PDF.", + "ErrorMessageNotSupportedEmail": "Votre système ne prend pas en charge l'envoi d'e-mails directement depuis cette application.", + "ErrorMessageForSavingFile": "Erreur lors de l'enregistrement du fichier", + "ErrorMessageUnableToSendEmail": "Impossible d'envoyer l'e-mail. Veuillez réessayer plus tard.", + "ConfirmationMessageBeforeDeleteBackup": "Êtes-vous sûr de vouloir supprimer cet élément ? Cette action est irréversible.", + "ErrorMessageOpeningWebsite": "Échec de l'ouverture de la page web. Veuillez réessayer.", + "ErrorSavingBackupMessage": "Erreur lors de l'enregistrement de la sauvegarde", + "ConfirmationRequiredTitle": "Confirmation requise", + "BackupNameAlreadyUsedMessage": "Nom de sauvegarde déjà utilisé !", + "ErrorMessageForWrongFileExtensionTitle": "Fichier invalide", + "BackupSavedCorrectlyTitle": "Sauvegarde Enregistrée" + }, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervalle de temps pour les sauvegardes automatiques", + "Days": "Jours", + "Hours": "Heures", + "SpinnerTooltip": "Roulette de la souris pour ajuster la valeur", + "Description": "Sélectionnez la fréquence des sauvegardes automatiques en \nchoisissant le nombre de jours, d'heures et de minutes.", + "Minutes": "Minutes" + }, + "Subscription": { + "ExpiredTitle": "L'abonnement Backup Manager a expiré", + "ExpiringMessage": "Votre abonnement Backup Manager est sur le point d'expirer.\nLes sauvegardes automatiques continueront de fonctionner jusqu'à la date d'expiration.\nVeuillez contacter le support pour le renouveler.", + "ExpiringTitle": "L'abonnement Backup Manager expire bientôt", + "ExpiredMessage": "Votre abonnement Backup Manager a expiré.\nLes sauvegardes automatiques ne seront plus exécutées.\nVeuillez contacter le support pour le réactiver.", + "ActiveLabel": "Actif", + "ExpiringLabel": "Bientôt expiré", + "ExpiredLabel": "Expiré", + "Status": "Statut de l'abonnement", + "ValidFrom": "Valide à partir du", + "ValidTo": "Valide jusqu'au", + "ContactUs": "Nous contacter", + "ToExtend": "pour prolonger la période d'abonnement." + }, + "ProgressBackupFrame": { + "StatusLoading": "Chargement...", + "ProgressBackupTitle": "Sauvegarde en cours", + "StatusCompleted": "Sauvegarde terminée !" + }, + "Menu": { + "Import": "Importer la liste de sauvegarde", + "Save": "Enregistrer", + "About": "À propos", + "File": "Fichier", + "Support": "Assistance", + "BugReport": "Signaler un bug", + "Quit": "Quitter", + "Options": "Options", + "New": "Nouveau", + "Clear": "Effacer", + "Share": "Partager", + "Preferences": "Préférences", + "Website": "Site web", + "SaveWithName": "Enregistrer sous", + "Export": "Exporter la liste de sauvegarde", + "Help": "Aide", + "InfoPage": "Info", + "History": "Historique", + "SubmenuMain": "PRINCIPAL", + "SubmenuOther": "AUTRE", + "Donate": "Soutenir le projet", + "Backups": "Liste des sauvegardes", + "CreateBackup": "Créer une nouvelle sauvegarde", + "ImportBackup": "Importer des sauvegardes depuis CSV", + "ExportBackup": "Exporter les sauvegardes vers CSV", + "Dashboard": "Tableau de bord", + "Github": "Page GitHub", + "Paypal": "PayPal", + "BuyMeACoffe": "Offrez-moi un café", + "ContactUs": "Nous contacter", + "Subscription": "Abonnement" + }, + "BackupList": { + "NotesDetail": "Notes", + "BackupNameDetail": "NomSauvegarde", + "ExportAsPdfTooltip": "Exporter en tant que PDF", + "EditPopup": "Modifier", + "ResearchBarTooltip": "Barre de recherche", + "InitialPathDetail": "CheminInitial", + "ExportAs": "Exporter en tant que : ", + "RenameBackupPopup": "Renommer la sauvegarde", + "NextBackupDateDetail": "ProchaineSauvegarde", + "AutoBackupPopup": "Sauvegarde automatique", + "BackupNameColumn": "Nom de la Sauvegarde", + "BackupPopup": "Sauvegarde", + "BackupCountDetail": "NombreSauvegardes", + "ExportAsCsvTooltip": "Exporter en tant que CSV", + "InitialPathColumn": "Chemin Initial", + "MaxBackupsToKeepDetail": "NombreMaxSauvegardesConserver", + "DeletePopup": "Supprimer", + "OpenDestinationFolderPopup": "Ouvrir le chemin de destination", + "LastBackupColumn": "Dernière Sauvegarde", + "SingleBackupPopup": "Effectuer une sauvegarde unique", + "AddBackupTooltip": "Ajouter une nouvelle sauvegarde", + "CopyTextPopup": "Copier le texte", + "DestinationPathDetail": "CheminDestination", + "CopyDestinationPathPopup": "Copier le chemin de destination", + "LastBackupDetail": "DernièreSauvegarde", + "OpenInitialFolderPopup": "Ouvrir le chemin initial", + "ResearchBarPlaceholder": "Rechercher...", + "InterruptPopup": "Interrompre", + "DuplicatePopup": "Dupliquer", + "NextBackupDateColumn": "Prochaine Date de Sauvegarde", + "DestinationPathColumn": "Chemin de Destination", + "AutomaticBackupColumn": "Sauvegarde Automatique", + "LastUpdateDateDetail": "DernièreMiseJour", + "TimeIntervalDetail": "IntervalleTemps", + "CopyBackupNamePopup": "Copier le nom de la sauvegarde", + "CreationDateDetail": "DateCréation", + "CopyInitialPathPopup": "Copier le chemin initial", + "BackupListTitle": "Liste des sauvegardes", + "BackupListDescription": "Gérez et surveillez les configurations de sauvegarde, y compris la création, la modification, la planification et l’exécution.", + "TimeIntervalColumn": "Intervalle (jj.HH:mm)", + "MaxBackupsColumn": "Nombre maximal de sauvegardes à conserver" + }, + "TabbedFrames": { + "BackupEntry": "Entrée de Sauvegarde", + "BackupList": "Liste de Sauvegardes" + }, + "UserDialog": { + "ErrorMessageForMissingData": "Veuillez remplir tous les champs requis.", + "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager", + "EmailConfirmationSubject": "Merci d'avoir choisi Backup Manager !", + "Surname": "Nom de famille", + "ErrorMessageForWrongEmail": "L'adresse e-mail fournie n'est pas valide. Veuillez en fournir une correcte.", + "Name": "Prénom", + "Email": "E-Mail", + "UserTitle": "Entrez vos informations", + "UserDescription": "Veuillez saisir vos informations pour accéder au système", + "UserNamePlaceholder": "Entrez votre prénom", + "UserSurnamePlaceholder": "Entrez votre nom de famille", + "UserEmailPlaceholder": "Entrez votre adresse e-mail" + }, + "BackupEntry": { + "TimePickerTooltip": "Sélecteur de temps", + "MaxBackupsToKeepTooltip": "Nombre maximum de sauvegardes avant de supprimer les plus anciennes.", + "BackupName": "Nom de la sauvegarde", + "AutoBackupButton": "Sauvegarde Automatique", + "InitialPathTooltip": "(Requis) Chemin initial", + "CurrentFile": "Fichier actuel", + "InitialFileChooserTooltip": "Ouvrir l'explorateur de fichiers", + "LastBackup": "Dernière sauvegarde", + "SingleBackupButton": "Sauvegarde Unique", + "AutoBackupButtonON": "Sauvegarde Automatique (ACTIVÉE)", + "AutoBackupButtonOFF": "Sauvegarde Automatique (DÉSACTIVÉE)", + "DestinationPathTooltip": "(Requis) Chemin de destination", + "MaxBackupsToKeep": "Nombre maximum de sauvegardes à conserver", + "SingleBackupTooltip": "Effectuer la sauvegarde", + "AutoBackupTooltip": "Activer/Désactiver la sauvegarde automatique", + "NotesTooltip": "(Optionnel) Description de la sauvegarde", + "DestinationFileChooserTooltip": "Ouvrir l'explorateur de fichiers", + "BackupNameTooltip": "(Requis) Nom de la sauvegarde", + "PageTitle": "Entrée de Sauvegarde", + "Notes": "Notes", + "PageSubtitleCreate": "Créer une sauvegarde", + "PageSubtitleEdit": "Modifier la sauvegarde", + "PageSubtitleInfo": "Informations sur la sauvegarde", + "PageSubtitleSettings": "Paramètres des sauvegardes", + "Paths": "Chemins", + "BackupNamePlaceholder": "Nom de la sauvegarde (unique)", + "InitialPathPlaceholder": "Chemin source ex. C:\\Users\\Admin\\Documents", + "DestinationPathPlaceholder": "Dossier de destination ex. D:\\Backups" + }, + "HistoryLogs": { + "HistoryLogsTitle": "Journaux d'historique", + "HistoryLogsDescription": "Vous trouverez ici les journaux de l'application, utiles pour le dépannage et pour comprendre le comportement de l'application au fil du temps." + }, + "About": { + "AboutSystemInformation": "Informations système", + "AboutMessageBody": "<html><b>Backup Manager</b> est une application simple et puissante conçue pour automatiser les sauvegardes de dossiers et sous-dossiers.<br><br> Les utilisateurs peuvent planifier des sauvegardes automatiques ou exécuter des sauvegardes manuelles à tout moment.<br><br> L’historique des sauvegardes est stocké en toute sécurité, offrant un contrôle total sur les données enregistrées.<br><p>Visitez le <a href=[PROJECT_WEBSITE]>site du projet</a> pour plus d’informations.</p></html>" + }, + "Dashboard": { + "DashboardTitle": "Tableau de bord analytique des sauvegardes", + "DashboardCardTotalConfigurations": "Configurations de sauvegarde totales", + "DashboardCardTotalExecutions": "Exécutions de sauvegarde totales", + "DashboardCardSuccessRate": "Taux de réussite", + "DashboardCardAvgDuration": "Durée moyenne des sauvegardes", + "DashboardCardCompressionRate": "Taux de compression", + "DashboardChartExecutions": "Exécutions des sauvegardes", + "DashboardChartAvgDuration": "Durée moyenne des sauvegardes (min)" + }, + "Settings": { + "SettingsLayoutTab": "Disposition", + "SettingsStyleTab": "Style", + "SettingsWindowsLayout": "Disposition des fenêtres", + "SettingsWindowsRight": "De droite à gauche", + "SettingsWindowsFull": "Contenu fenêtre complet", + "SettingsDrawerLayout": "Disposition du panneau", + "SettingsDrawerLeft": "Gauche", + "SettingsDrawerLeading": "Début", + "SettingsDrawerTrailing": "Fin", + "SettingsDrawerRight": "Droite", + "SettingsDrawerTop": "Haut", + "SettingsDrawerBottom": "Bas", + "SettingsModalOption": "Option modale par défaut", + "SettingsModalAnimation": "Activer l’animation", + "SettingsModalClose": "Fermer avec la touche Échap", + "SettingsLanguagesLayout": "Langue", + "SettingsAccentLayout": "Couleur d’accent", + "SettingsColorPickerLayout": "Sélecteur de couleur", + "SettingsDrawerLineLayout": "Style de ligne du panneau", + "SettingsDrawerLineCurved": "Ligne courbe", + "SettingsDrawerDotLine": "Ligne pointillée droite", + "SettingsLineStyleLayout": "Option style de ligne", + "SettingsLineStyleRettangle": "Rectangle", + "SettingsLineStyleEllipse": "Ellipse", + "SettingsLineStyleLine": "Ligne", + "SettingsLineStyleCurved": "Courbe", + "SettingsColorOptionLayout": "Option de couleur", + "SettingsColorOptionPainted": "Colorer la ligne sélectionnée" + }, + "SearchBar": { + "SearcTitle": "Rechercher...", + "SearchNoRecent": "Aucune recherche récente", + "SearchNoResult": "Aucun résultat pour", + "SearchFavorite": "Favori", + "SearchRecent": "Récents" + } } diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 1a5b291a..1859c716 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -1,214 +1,300 @@ { - "TrayIcon": { - "SuccessMessage": "\nIl backup è stato completato con successo:", - "ExitAction": "Esci", - "ErrorMessageFilesNotExisting": "\nErrore durante il backup automatico.\nUno o entrambi i percorsi non esistono!", - "TrayTooltip": "Servizio di Backup", - "OpenAction": "Accesso Rapido", - "ErrorMessageInputMissing": "\nErrore durante il backup automatico.\nPercorso mancante!", - "ErrorMessageSamePaths": "\nErrore durante il backup automatico.\nIl percorso iniziale e il percorso di destinazione non possono essere uguali. Scegli percorsi diversi!" - }, - "If value is null or empty, fall back to the default value from the enum": {}, - "Dialogs": { - "WarningGenericTitle": "Avviso", - "SuccessfullyExportedToCsvMessage": "Backup esportati in CSV con successo!", - "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", - "ConfirmationDeletionMessage": "Sei sicuro di voler eliminare le righe selezionate?", - "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Errore durante l'esportazione dei backup in CSV: ", - "ErrorGenericTitle": "Errore", - "ErrorMessageForExportingToPdf": "Errore durante l'esportazione dei backup in PDF: ", - "BackupListCorrectlyImportedTitle": "Menu Importa", - "ErrorWrongTimeInterval": "L'intervallo di tempo non è corretto", - "ErrorMessageOpenHistoryFile": "Errore durante l'apertura del file storico.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Link di condivisione copiato negli appunti!", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossibile salvare il file. Entrambi i percorsi iniziale e di destinazione devono essere specificati e non possono essere vuoti", - "BackupListCorrectlyExportedMessage": "Lista di backup esportata correttamente sul desktop!", - "BackupSavedCorrectlyMessage": "salvato con successo!", - "SettedEveryMessage": "\nImpostato ogni", - "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", - "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?", - "ConfirmationMessageCancelAutoBackup": "Sei sicuro di voler annullare il backup automatico?", - "ErrorMessageCountingFiles": "Errore durante il calcolo dei file da eseguire il backup.", - "ErrorMessageForFolderNotExisting": "La cartella non esiste o non è valida", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Errore durante il backup: il percorso iniziale è errato!", - "ErrorMessageInputMissingGeneric": "Input Mancanti!", - "BackupNameInput": "Nome del backup", - "CsvNameMessageInput": "Inserisci il nome del file CSV.", - "ConfirmationDeletionTitle": "Conferma Eliminazione", - "ErrorMessageForWrongFileExtensionMessage": "Errore: Selezionare un file JSON valido.", - "ErrorMessageZippingGeneric": "Errore durante la compressione dei file.", - "ErrorMessageNotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email.", - "ErrorMessageZippingIO": "Errore durante la compressione dei file: errore di I/O.", - "SuccessfullyExportedToPdfMessage": "Backup esportati in PDF con successo!", - "DuplicatedFileNameMessage": "Il file esiste già. Sovrascrivere?", - "AutoBackupActivatedMessage": "Backup Automatico attivato", - "DaysMessage": " giorni", - "ExceptionMessageReportButton": "Riporta il problema", - "ErrorMessageInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore.", - "ExceptionMessageClipboardMessage": "Il testo dell'errore è stato copiato negli appunti.", - "SuccessGenericTitle": "Successo", - "ConfirmationMessageForClear": "Sei sicuro di voler pulire i campi?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Lista di backup importata correttamente!", - "DuplicatedBackupNameMessage": "Esiste già un backup con lo stesso nome, vuoi sovrascriverlo?", - "ExceptionMessageClipboardButton": "Copia negli appunti", - "ErrorMessageZippingSecurity": "Errore durante la compressione dei file: Errore di sicurezza.", - "InterruptBackupProcessMessage": "Sei sicuro di voler interrompere questo backup?", - "BackupListCorrectlyExportedTitle": "Menu Esporta", - "ConfirmationMessageForUnsavedChanges": "Ci sono modifiche non salvate, vuoi salvarle prima di passare a un altro backup?", - "ExceptionMessageTitle": "Errore...", - "PdfNameMessageInput": "Inserisci il nome del file PDF.", - "ErrorMessageNotSupportedEmail": "Il tuo sistema non supporta l'invio di email direttamente da questa applicazione.", - "ErrorMessageForSavingFile": "Errore nel salvataggio del file", - "ErrorMessageUnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi.", - "ConfirmationMessageBeforeDeleteBackup": "Sei sicuro di voler eliminare questo elemento? Nota: questa azione non può essere annullata.", - "ErrorMessageOpeningWebsite": "Impossibile aprire la pagina web. Riprova.", - "ErrorSavingBackupMessage": "Errore durante il salvataggio del backup", - "ConfirmationRequiredTitle": "Conferma richiesta", - "BackupNameAlreadyUsedMessage": "Nome del backup già utilizzato!", - "ErrorMessageForWrongFileExtensionTitle": "File non valido", - "BackupSavedCorrectlyTitle": "Backup salvato" - }, - "User dialog": {}, - "TimePickerDialog": { - "TimeIntervalTitle": "Intervallo di tempo per backup automatico", - "Days": "Giorni", - "Hours": "Ore", - "SpinnerTooltip": "Usa la rotellina per regolare il valore", - "Description": "Seleziona la frequenza del backup automatico \nscegliendo la frequenza in giorni, ore e minuti.", - "Minutes": "Minuti" - }, - "HISTORY_LOGS": {}, - "Constructor to assign both key and default value": {}, - "Subscription": { - "ExpiredTitle": "Abbonamento Backup Manager scaduto", - "ExpiringMessage": "Il tuo abbonamento a Backup Manager sta per scadere.\nI backup automatici continueranno a funzionare fino alla data di scadenza.\nContatta l'assistenza per rinnovarlo.", - "ExpiringTitle": "Abbonamento Backup Manager in scadenza", - "ExpiredMessage": "Il tuo abbonamento a Backup Manager è scaduto.\nI backup automatici non verranno più eseguiti.\nContatta l'assistenza per riattivarlo." - }, - "ProgressBackupFrame": { - "StatusLoading": "Caricamento...", - "ProgressBackupTitle": "Backup in corso", - "StatusCompleted": "Backup completato!" - }, - "Lookup by keyName (JSON key)": {}, - "DASHBOARD": {}, - "ABOUT": {}, - "General": { - "AppName": "Backup Manager", - "CancelButton": "Annulla", - "Backup": "Backup", - "Version": "Versione", - "ApplyButton": "Applica", - "OkButton": "Ok", - "From": "Da", - "SaveButton": "Salva", - "CloseButton": "Chiudi", - "To": "A" - }, - "Menu": { - "Import": "Importa lista di backup", - "Save": "Salva", - "About": "Informazioni", - "File": "File", - "Support": "Supporto", - "BugReport": "Segnala un problema", - "Quit": "Esci", - "Options": "Opzioni", - "New": "Nuovo", - "Clear": "Pulisci", - "Share": "Condividi", - "Preferences": "Preferenze", - "Website": "Sito web", - "SaveWithName": "Salva con nome", - "Export": "Esporta lista di backup", - "Help": "Aiuto", - "InfoPage": "Info", - "History": "Storico" - }, - "Use fromKeyName to get the TKey from the JSON key": {}, - "Clear previous translations to avoid stale values when switching languages": {}, - "BackupList": { - "NotesDetail": "Note", - "BackupNameDetail": "NomeBackup", - "ExportAsPdfTooltip": "Esporta come PDF", - "EditPopup": "Modifica", - "ResearchBarTooltip": "Barra di ricerca", - "InitialPathDetail": "PercorsoIniziale", - "ExportAs": "Esporta come: ", - "RenameBackupPopup": "Rinomina backup", - "NextBackupDateDetail": "ProssimoBackup", - "AutoBackupPopup": "Backup automatico", - "BackupNameColumn": "Nome del Backup", - "BackupPopup": "Backup", - "BackupCountDetail": "ConteggioBackup", - "ExportAsCsvTooltip": "Esporta come CSV", - "InitialPathColumn": "Percorso Iniziale", - "MaxBackupsToKeepDetail": "MassimoNumeroBackupDaMantenere", - "DeletePopup": "Elimina", - "OpenDestinationFolderPopup": "Apri percorso di destinazione", - "LastBackupColumn": "Ultimo Backup", - "SingleBackupPopup": "Esegui backup singolo", - "AddBackupTooltip": "Aggiungi nuovo backup", - "CopyTextPopup": "Copia testo", - "DestinationPathDetail": "PercorsoDestinazione", - "CopyDestinationPathPopup": "Copia percorso di destinazione", - "LastBackupDetail": "UltimoBackup", - "OpenInitialFolderPopup": "Apri percorso iniziale", - "ResearchBarPlaceholder": "Cerca...", - "InterruptPopup": "Interrompi", - "DuplicatePopup": "Duplica", - "NextBackupDateColumn": "Data del Prossimo Backup", - "DestinationPathColumn": "Percorso di Destinazione", - "AutomaticBackupColumn": "Backup Automatico", - "LastUpdateDateDetail": "DataUltimoAggiornamento", - "TimeIntervalDetail": "IntervalloDiTempo", - "CopyBackupNamePopup": "Copia nome backup", - "CreationDateDetail": "DataCreazione", - "CopyInitialPathPopup": "Copia percorso iniziale" - }, - "TabbedFrames": { - "BackupEntry": "VoceBackup", - "BackupList": "ListaBackup" - }, - "UserDialog": { - "ErrorMessageForMissingData": "Per favore, compila tutti i campi richiesti.", - "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager", - "EmailConfirmationSubject": "Grazie per aver scelto Backup Manager!", - "Surname": "Cognome", - "ErrorMessageForWrongEmail": "L'indirizzo email fornito non è valido. Inseriscine uno corretto.", - "Name": "Nome", - "Email": "Email", - "UserTitle": "Inserisci i tuoi dati" - }, - "SETTINGS": {}, - "BackupEntry": { - "TimePickerTooltip": "Selettore orario", - "MaxBackupsToKeepTooltip": "Numero massimo di backup da conservare prima di eliminare i più vecchi.", - "BackupName": "Nome del backup", - "AutoBackupButton": "Backup Automatico", - "InitialPathTooltip": "(Obbligatorio) Percorso iniziale", - "CurrentFile": "File corrente", - "InitialFileChooserTooltip": "Apri esplora file", - "LastBackup": "Ultimo backup", - "SingleBackupButton": "Backup Singolo", - "AutoBackupButtonON": "Backup Automatico (ON)", - "AutoBackupButtonOFF": "Backup Automatico (OFF)", - "DestinationPathTooltip": "(Obbligatorio) Percorso di destinazione", - "MaxBackupsToKeep": "Massimo numero di backup da mantenere", - "SingleBackupTooltip": "Esegui il backup", - "AutoBackupTooltip": "Attiva/Disattiva backup automatico", - "NotesTooltip": "(Opzionale) Descrizione del backup", - "DestinationFileChooserTooltip": "Apri esplora file", - "BackupNameTooltip": "(Obbligatorio) Nome del backup", - "PageTitle": "Voce di Backup", - "Notes": "Note" - } + "General": { + "AppName": "Backup Manager", + "CancelButton": "Annulla", + "Backup": "Backup", + "Version": "Versione", + "ApplyButton": "Applica", + "OkButton": "Ok", + "From": "Da", + "SaveButton": "Salva", + "CloseButton": "Chiudi", + "CreateButton": "Crea", + "EditButton": "Modifica", + "DeleteButton": "Elimina", + "QuickSearch": "Ricerca rapida", + "To": "A" + }, + "TrayIcon": { + "SuccessMessage": "\nIl backup è stato completato con successo:", + "ExitAction": "Esci", + "ErrorMessageFilesNotExisting": "\nErrore durante il backup automatico.\nUno o entrambi i percorsi non esistono!", + "TrayTooltip": "Servizio di Backup", + "OpenAction": "Accesso Rapido", + "ErrorMessageInputMissing": "\nErrore durante il backup automatico.\nPercorso mancante!", + "ErrorMessageSamePaths": "\nErrore durante il backup automatico.\nIl percorso iniziale e il percorso di destinazione non possono essere uguali. Scegli percorsi diversi!" + }, + "Dialogs": { + "WarningGenericTitle": "Avviso", + "SuccessfullyExportedToCsvMessage": "Backup esportati in CSV con successo!", + "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", + "ConfirmationDeletionMessage": "Sei sicuro di voler eliminare le righe selezionate?", + "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", + "ErrorMessageInvalidPath": "The selected path is invalid!", + "ErrorMessageForExportingToCsv": "Errore durante l'esportazione dei backup in CSV: ", + "ErrorGenericTitle": "Errore", + "ErrorMessageForExportingToPdf": "Errore durante l'esportazione dei backup in PDF: ", + "BackupListCorrectlyImportedTitle": "Menu Importa", + "ErrorWrongTimeInterval": "L'intervallo di tempo non è corretto", + "ErrorMessageOpenHistoryFile": "Errore durante l'apertura del file storico.", + "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", + "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", + "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!", + "ErrorMessageUnexpected": "An unexpected error has occurred!", + "ShareLinkCopiedMessage": "Link di condivisione copiato negli appunti!", + "ErrorMessageForSavingFileWithPathsEmpty": "Impossibile salvare il file. Entrambi i percorsi iniziale e di destinazione devono essere specificati e non possono essere vuoti", + "BackupListCorrectlyExportedMessage": "Lista di backup esportata correttamente sul desktop!", + "BackupSavedCorrectlyMessage": "salvato con successo!", + "SettedEveryMessage": "\nImpostato ogni", + "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", + "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?", + "ConfirmationMessageCancelAutoBackup": "Sei sicuro di voler annullare il backup automatico?", + "ErrorMessageCountingFiles": "Errore durante il calcolo dei file da eseguire il backup.", + "ErrorMessageForFolderNotExisting": "La cartella non esiste o non è valida", + "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", + "ErrorMessageForIncorrectInitialPath": "Errore durante il backup: il percorso iniziale è errato!", + "ErrorMessageInputMissingGeneric": "Input Mancanti!", + "BackupNameInput": "Nome del backup", + "CsvNameMessageInput": "Inserisci il nome del file CSV.", + "ConfirmationDeletionTitle": "Conferma Eliminazione", + "ErrorMessageForWrongFileExtensionMessage": "Errore: Selezionare un file JSON valido.", + "ErrorMessageZippingGeneric": "Errore durante la compressione dei file.", + "ErrorMessageNotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email.", + "ErrorMessageZippingIO": "Errore durante la compressione dei file: errore di I/O.", + "SuccessfullyExportedToPdfMessage": "Backup esportati in PDF con successo!", + "DuplicatedFileNameMessage": "Il file esiste già. Sovrascrivere?", + "AutoBackupActivatedMessage": "Backup Automatico attivato", + "DaysMessage": " giorni", + "ExceptionMessageReportButton": "Riporta il problema", + "ErrorMessageInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore.", + "ExceptionMessageClipboardMessage": "Il testo dell'errore è stato copiato negli appunti.", + "SuccessGenericTitle": "Successo", + "ConfirmationMessageForClear": "Sei sicuro di voler pulire i campi?", + "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", + "BackupListCorrectlyImportedMessage": "Lista di backup importata correttamente!", + "DuplicatedBackupNameMessage": "Esiste già un backup con lo stesso nome, vuoi sovrascriverlo?", + "ExceptionMessageClipboardButton": "Copia negli appunti", + "ErrorMessageZippingSecurity": "Errore durante la compressione dei file: Errore di sicurezza.", + "InterruptBackupProcessMessage": "Sei sicuro di voler interrompere questo backup?", + "BackupListCorrectlyExportedTitle": "Menu Esporta", + "ConfirmationMessageForUnsavedChanges": "Ci sono modifiche non salvate, vuoi salvarle prima di passare a un altro backup?", + "ExceptionMessageTitle": "Errore...", + "PdfNameMessageInput": "Inserisci il nome del file PDF.", + "ErrorMessageNotSupportedEmail": "Il tuo sistema non supporta l'invio di email direttamente da questa applicazione.", + "ErrorMessageForSavingFile": "Errore nel salvataggio del file", + "ErrorMessageUnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi.", + "ConfirmationMessageBeforeDeleteBackup": "Sei sicuro di voler eliminare questo elemento? Nota: questa azione non può essere annullata.", + "ErrorMessageOpeningWebsite": "Impossibile aprire la pagina web. Riprova.", + "ErrorSavingBackupMessage": "Errore durante il salvataggio del backup", + "ConfirmationRequiredTitle": "Conferma richiesta", + "BackupNameAlreadyUsedMessage": "Nome del backup già utilizzato!", + "ErrorMessageForWrongFileExtensionTitle": "File non valido", + "BackupSavedCorrectlyTitle": "Backup salvato" + }, + "TimePickerDialog": { + "TimeIntervalTitle": "Intervallo di tempo per backup automatico", + "Days": "Giorni", + "Hours": "Ore", + "SpinnerTooltip": "Usa la rotellina per regolare il valore", + "Description": "Seleziona la frequenza del backup automatico \nscegliendo la frequenza in giorni, ore e minuti.", + "Minutes": "Minuti" + }, + "Subscription": { + "ExpiredTitle": "Abbonamento Backup Manager scaduto", + "ExpiringMessage": "Il tuo abbonamento a Backup Manager sta per scadere.\nI backup automatici continueranno a funzionare fino alla data di scadenza.\nContatta l'assistenza per rinnovarlo.", + "ExpiringTitle": "Abbonamento Backup Manager in scadenza", + "ExpiredMessage": "Il tuo abbonamento a Backup Manager è scaduto.\nI backup automatici non verranno più eseguiti.\nContatta l'assistenza per riattivarlo.", + "ActiveLabel": "Attivo", + "ExpiringLabel": "In scadenza", + "ExpiredLabel": "Scaduto", + "Status": "Stato abbonamento", + "ValidFrom": "Valido dal", + "ValidTo": "Valido fino al", + "ContactUs": "Contattaci", + "ToExtend": "per estendere il periodo di abbonamento." + }, + "ProgressBackupFrame": { + "StatusLoading": "Caricamento...", + "ProgressBackupTitle": "Backup in corso", + "StatusCompleted": "Backup completato!" + }, + "Menu": { + "Import": "Importa lista di backup", + "Save": "Salva", + "About": "Informazioni", + "File": "File", + "Support": "Supporto", + "BugReport": "Segnala un problema", + "Quit": "Esci", + "Options": "Opzioni", + "New": "Nuovo", + "Clear": "Pulisci", + "Share": "Condividi", + "Preferences": "Preferenze", + "Website": "Sito web", + "SaveWithName": "Salva con nome", + "Export": "Esporta lista di backup", + "Help": "Aiuto", + "InfoPage": "Info", + "History": "Cronologia", + "SubmenuMain": "PRINCIPALE", + "SubmenuOther": "ALTRO", + "Donate": "Supporta il progetto", + "Backups": "Elenco backup", + "CreateBackup": "Crea nuovo backup", + "ImportBackup": "Importa backup da CSV", + "ExportBackup": "Esporta backup in CSV", + "Dashboard": "Dashboard", + "Github": "Pagina GitHub", + "Paypal": "PayPal", + "BuyMeACoffe": "Offrimi un caffè", + "ContactUs": "Contattaci", + "Subscription": "Abbonamento" + }, + "BackupList": { + "NotesDetail": "Note", + "BackupNameDetail": "NomeBackup", + "ExportAsPdfTooltip": "Esporta come PDF", + "EditPopup": "Modifica", + "ResearchBarTooltip": "Barra di ricerca", + "InitialPathDetail": "PercorsoIniziale", + "ExportAs": "Esporta come: ", + "RenameBackupPopup": "Rinomina backup", + "NextBackupDateDetail": "ProssimoBackup", + "AutoBackupPopup": "Backup automatico", + "BackupNameColumn": "Nome del Backup", + "BackupPopup": "Backup", + "BackupCountDetail": "ConteggioBackup", + "ExportAsCsvTooltip": "Esporta come CSV", + "InitialPathColumn": "Percorso Iniziale", + "MaxBackupsToKeepDetail": "MassimoNumeroBackupDaMantenere", + "DeletePopup": "Elimina", + "OpenDestinationFolderPopup": "Apri percorso di destinazione", + "LastBackupColumn": "Ultimo Backup", + "SingleBackupPopup": "Esegui backup singolo", + "AddBackupTooltip": "Aggiungi nuovo backup", + "CopyTextPopup": "Copia testo", + "DestinationPathDetail": "PercorsoDestinazione", + "CopyDestinationPathPopup": "Copia percorso di destinazione", + "LastBackupDetail": "UltimoBackup", + "OpenInitialFolderPopup": "Apri percorso iniziale", + "ResearchBarPlaceholder": "Cerca...", + "InterruptPopup": "Interrompi", + "DuplicatePopup": "Duplica", + "NextBackupDateColumn": "Data del Prossimo Backup", + "DestinationPathColumn": "Percorso di Destinazione", + "AutomaticBackupColumn": "Backup Automatico", + "LastUpdateDateDetail": "DataUltimoAggiornamento", + "TimeIntervalDetail": "IntervalloDiTempo", + "CopyBackupNamePopup": "Copia nome backup", + "CreationDateDetail": "DataCreazione", + "CopyInitialPathPopup": "Copia percorso iniziale", + "BackupListTitle": "Elenco backup", + "BackupListDescription": "Gestisci e monitora le configurazioni di backup, inclusa la creazione, modifica, pianificazione ed esecuzione.", + "TimeIntervalColumn": "Intervallo (gg.HH:mm)", + "MaxBackupsColumn": "Numero massimo di backup da mantenere" + }, + "TabbedFrames": { + "BackupEntry": "VoceBackup", + "BackupList": "ListaBackup" + }, + "UserDialog": { + "ErrorMessageForMissingData": "Per favore, compila tutti i campi richiesti.", + "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager", + "EmailConfirmationSubject": "Grazie per aver scelto Backup Manager!", + "Surname": "Cognome", + "ErrorMessageForWrongEmail": "L'indirizzo email fornito non è valido. Inseriscine uno corretto.", + "Name": "Nome", + "Email": "Email", + "UserTitle": "Inserisci i tuoi dati", + "UserDescription": "Inserisci i tuoi dati per accedere al sistema", + "UserNamePlaceholder": "Inserisci il tuo nome", + "UserSurnamePlaceholder": "Inserisci il tuo cognome", + "UserEmailPlaceholder": "Inserisci la tua email" + }, + "BackupEntry": { + "TimePickerTooltip": "Selettore orario", + "MaxBackupsToKeepTooltip": "Numero massimo di backup da conservare prima di eliminare i più vecchi.", + "BackupName": "Nome del backup", + "AutoBackupButton": "Backup Automatico", + "InitialPathTooltip": "(Obbligatorio) Percorso iniziale", + "CurrentFile": "File corrente", + "InitialFileChooserTooltip": "Apri esplora file", + "LastBackup": "Ultimo backup", + "SingleBackupButton": "Backup Singolo", + "AutoBackupButtonON": "Backup Automatico (ON)", + "AutoBackupButtonOFF": "Backup Automatico (OFF)", + "DestinationPathTooltip": "(Obbligatorio) Percorso di destinazione", + "MaxBackupsToKeep": "Massimo numero di backup da mantenere", + "SingleBackupTooltip": "Esegui il backup", + "AutoBackupTooltip": "Attiva/Disattiva backup automatico", + "NotesTooltip": "(Opzionale) Descrizione del backup", + "DestinationFileChooserTooltip": "Apri esplora file", + "BackupNameTooltip": "(Obbligatorio) Nome del backup", + "PageTitle": "Voce di Backup", + "Notes": "Note", + "PageSubtitleCreate": "Crea backup", + "PageSubtitleEdit": "Modifica backup", + "PageSubtitleInfo": "Informazioni backup", + "PageSubtitleSettings": "Impostazioni backup", + "Paths": "Percorsi", + "BackupNamePlaceholder": "Nome backup (univoco)", + "InitialPathPlaceholder": "Percorso di origine es. C:\\Users\\Admin\\Documents", + "DestinationPathPlaceholder": "Cartella di destinazione es. D:\\Backup" + }, + "HistoryLogs": { + "HistoryLogsTitle": "Registro cronologia", + "HistoryLogsDescription": "Qui puoi trovare i log dell'applicazione, utili per la risoluzione dei problemi e per comprendere il comportamento dell'applicazione nel tempo." + }, + "About": { + "AboutSystemInformation": "Informazioni di sistema", + "AboutMessageBody": "<html><b>Backup Manager</b> è un'applicazione semplice e potente progettata per automatizzare il backup di cartelle e sottocartelle.<br><br> Gli utenti possono pianificare backup automatici oppure eseguire backup manuali in qualsiasi momento.<br><br> La cronologia dei backup viene salvata in modo sicuro, consentendo il pieno controllo sui dati archiviati.<br><p>Visita il <a href=[PROJECT_WEBSITE]>sito del progetto</a> per maggiori informazioni.</p></html>" + }, + "Dashboard": { + "DashboardTitle": "Dashboard analisi backup", + "DashboardCardTotalConfigurations": "Configurazioni backup totali", + "DashboardCardTotalExecutions": "Esecuzioni backup totali", + "DashboardCardSuccessRate": "Tasso di successo", + "DashboardCardAvgDuration": "Durata media backup", + "DashboardCardCompressionRate": "Tasso di compressione", + "DashboardChartExecutions": "Esecuzioni backup", + "DashboardChartAvgDuration": "Durata media backup (min)" + }, + "Settings": { + "SettingsLayoutTab": "Layout", + "SettingsStyleTab": "Stile", + "SettingsWindowsLayout": "Layout finestra", + "SettingsWindowsRight": "Da destra a sinistra", + "SettingsWindowsFull": "Contenuto finestra completo", + "SettingsDrawerLayout": "Layout pannello", + "SettingsDrawerLeft": "Sinistra", + "SettingsDrawerLeading": "Inizio", + "SettingsDrawerTrailing": "Fine", + "SettingsDrawerRight": "Destra", + "SettingsDrawerTop": "Alto", + "SettingsDrawerBottom": "Basso", + "SettingsModalOption": "Opzione modale predefinita", + "SettingsModalAnimation": "Abilita animazione", + "SettingsModalClose": "Chiudi premendo ESC", + "SettingsLanguagesLayout": "Lingua", + "SettingsAccentLayout": "Colore accento", + "SettingsColorPickerLayout": "Selettore colore", + "SettingsDrawerLineLayout": "Stile linea pannello", + "SettingsDrawerLineCurved": "Linea curva", + "SettingsDrawerDotLine": "Linea tratteggiata", + "SettingsLineStyleLayout": "Opzione stile linea", + "SettingsLineStyleRettangle": "Rettangolo", + "SettingsLineStyleEllipse": "Ellisse", + "SettingsLineStyleLine": "Linea", + "SettingsLineStyleCurved": "Curva", + "SettingsColorOptionLayout": "Opzione colore", + "SettingsColorOptionPainted": "Colora la linea selezionata" + }, + "SearchBar": { + "SearcTitle": "Cerca...", + "SearchNoRecent": "Nessuna ricerca recente", + "SearchNoResult": "Nessun risultato per", + "SearchFavorite": "Preferiti", + "SearchRecent": "Recenti" + } } From c16f8c6a87de15c7d827b579db01a1a139f87521 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Fri, 24 Apr 2026 11:45:48 +0200 Subject: [PATCH 12/17] backup observer update --- pom.xml | 15 +++++--------- .../java/backupmanager/BackupOperations.java | 2 +- .../Entities/BackupExecutionContext.java | 2 +- src/main/java/backupmanager/MainApp.java | 2 +- .../Managers/LanguageManager.java | 2 +- .../Services/RunningBackupService.java | 2 +- .../backupmanager/Utils/AppPreferences.java | 2 +- .../java/backupmanager/Utils/FolderUtils.java | 2 +- .../java/backupmanager/Utils/SystemForm.java | 2 +- .../java/backupmanager/Utils/UndoRedo.java | 2 +- .../table/CheckBoxTableHeaderRenderer.java | 2 +- .../Utils/table/CheckboxCellRenderer.java | 2 +- .../Utils/table/ProgressBarRenderer.java | 2 +- .../Utils/table/TableHeaderAlignment.java | 2 +- .../gui/component/FormSearchPanel.java | 4 ++-- .../gui/forms/FormBackupDashboard.java | 2 +- .../gui/forms/FormBackupTable.java | 14 ++++++++++++- .../backupmanager/gui/forms/FormHistory.java | 2 +- .../backupmanager/gui/forms/FormSetting.java | 4 ++-- .../backupmanager/gui/system/FormManager.java | 20 +++++++++++++++++-- .../backupmanager/gui/system/FormSearch.java | 2 +- .../backupmanager/gui/themes/PanelThemes.java | 2 +- 22 files changed, 57 insertions(+), 34 deletions(-) diff --git a/pom.xml b/pom.xml index 4cf9d4ed..01985c02 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <!-- <groupId>backupmanager</groupId> --> - <parent> - <groupId>io.github.dj-raven</groupId> - <artifactId>swing-modal-dialog</artifactId> - <version>2.6.0</version> - </parent> + <groupId>io.github.dj-raven</groupId> <artifactId>backupmanager</artifactId> - <!-- <version>1.0-SNAPSHOT</version> - <packaging>jar</packaging> --> + <version>2.6.0</version> + <packaging>jar</packaging> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> @@ -26,9 +21,9 @@ <dependencies> <dependency> - <groupId>${parent.groupId}</groupId> + <groupId>io.github.dj-raven</groupId> <artifactId>modal-dialog</artifactId> - <version>${version}</version> + <version>2.6.0</version> </dependency> <dependency> diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index cc08836d..256ebda8 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -31,7 +31,7 @@ import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.utils.FolderUtils; +import backupmanager.Utils.FolderUtils; public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); diff --git a/src/main/java/backupmanager/Entities/BackupExecutionContext.java b/src/main/java/backupmanager/Entities/BackupExecutionContext.java index 9d1322a7..e6e20754 100644 --- a/src/main/java/backupmanager/Entities/BackupExecutionContext.java +++ b/src/main/java/backupmanager/Entities/BackupExecutionContext.java @@ -1,6 +1,6 @@ package backupmanager.Entities; -import backupmanager.utils.FolderUtils; +import backupmanager.Utils.FolderUtils; public record BackupExecutionContext ( ConfigurationBackup backup, diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 27b95a74..4d9415db 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -21,7 +21,7 @@ import backupmanager.database.ProductionDatabaseInitializer; import backupmanager.gui.Controllers.AppController; import backupmanager.gui.frames.BackupManager; -import backupmanager.utils.AppPreferences; +import backupmanager.Utils.AppPreferences; public class MainApp { private static final String CONFIG = "src/main/resources/res/config/config.json"; diff --git a/src/main/java/backupmanager/Managers/LanguageManager.java b/src/main/java/backupmanager/Managers/LanguageManager.java index 8ea1dc9c..e082681a 100644 --- a/src/main/java/backupmanager/Managers/LanguageManager.java +++ b/src/main/java/backupmanager/Managers/LanguageManager.java @@ -14,7 +14,7 @@ import backupmanager.Enums.LanguagesEnum; import backupmanager.Enums.Translations; import backupmanager.interfaces.ITranslatable; -import backupmanager.utils.AppPreferences; +import backupmanager.Utils.AppPreferences; // Observer class -> every time the language is changed, it notify all the components registered public class LanguageManager { diff --git a/src/main/java/backupmanager/Services/RunningBackupService.java b/src/main/java/backupmanager/Services/RunningBackupService.java index 518eed79..5daffcc4 100644 --- a/src/main/java/backupmanager/Services/RunningBackupService.java +++ b/src/main/java/backupmanager/Services/RunningBackupService.java @@ -11,7 +11,7 @@ import backupmanager.Helpers.SqlHelper; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.utils.FolderUtils; +import backupmanager.Utils.FolderUtils; public class RunningBackupService { diff --git a/src/main/java/backupmanager/Utils/AppPreferences.java b/src/main/java/backupmanager/Utils/AppPreferences.java index 2d975f90..632b54e1 100644 --- a/src/main/java/backupmanager/Utils/AppPreferences.java +++ b/src/main/java/backupmanager/Utils/AppPreferences.java @@ -1,4 +1,4 @@ -package backupmanager.utils; +package backupmanager.Utils; import java.awt.Color; import java.util.ArrayList; diff --git a/src/main/java/backupmanager/Utils/FolderUtils.java b/src/main/java/backupmanager/Utils/FolderUtils.java index 75beeaac..75faab37 100644 --- a/src/main/java/backupmanager/Utils/FolderUtils.java +++ b/src/main/java/backupmanager/Utils/FolderUtils.java @@ -1,4 +1,4 @@ -package backupmanager.utils; +package backupmanager.Utils; import java.io.File; import java.io.IOException; diff --git a/src/main/java/backupmanager/Utils/SystemForm.java b/src/main/java/backupmanager/Utils/SystemForm.java index 873d77f7..c382c341 100644 --- a/src/main/java/backupmanager/Utils/SystemForm.java +++ b/src/main/java/backupmanager/Utils/SystemForm.java @@ -1,4 +1,4 @@ -package backupmanager.utils; +package backupmanager.Utils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/backupmanager/Utils/UndoRedo.java b/src/main/java/backupmanager/Utils/UndoRedo.java index d5942f2f..4deaaf8c 100644 --- a/src/main/java/backupmanager/Utils/UndoRedo.java +++ b/src/main/java/backupmanager/Utils/UndoRedo.java @@ -1,4 +1,4 @@ -package backupmanager.utils; +package backupmanager.Utils; import java.util.Iterator; import java.util.Stack; diff --git a/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java b/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java index dd86b00d..13ef3142 100644 --- a/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java +++ b/src/main/java/backupmanager/Utils/table/CheckBoxTableHeaderRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.utils.table; +package backupmanager.Utils.table; import com.formdev.flatlaf.FlatClientProperties; diff --git a/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java b/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java index 64c5540b..898e3254 100644 --- a/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java +++ b/src/main/java/backupmanager/Utils/table/CheckboxCellRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.utils.table; +package backupmanager.Utils.table; import java.awt.Color; import java.awt.Component; diff --git a/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java index d6f80a31..825e977b 100644 --- a/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java +++ b/src/main/java/backupmanager/Utils/table/ProgressBarRenderer.java @@ -1,4 +1,4 @@ -package backupmanager.utils.table; +package backupmanager.Utils.table; import java.awt.Component; diff --git a/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java b/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java index c3325299..1e4dc981 100644 --- a/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java +++ b/src/main/java/backupmanager/Utils/table/TableHeaderAlignment.java @@ -1,4 +1,4 @@ -package backupmanager.utils.table; +package backupmanager.Utils.table; import javax.swing.*; import javax.swing.table.TableCellRenderer; diff --git a/src/main/java/backupmanager/gui/component/FormSearchPanel.java b/src/main/java/backupmanager/gui/component/FormSearchPanel.java index 2f77051a..afed312b 100644 --- a/src/main/java/backupmanager/gui/component/FormSearchPanel.java +++ b/src/main/java/backupmanager/gui/component/FormSearchPanel.java @@ -41,8 +41,8 @@ import backupmanager.gui.svg.SVGIconUIColor; import backupmanager.gui.system.Form; import backupmanager.gui.system.FormSearch; -import backupmanager.utils.AppPreferences; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.AppPreferences; +import backupmanager.Utils.SystemForm; import net.miginfocom.swing.MigLayout; import raven.modal.Drawer; import raven.modal.ModalDialog; diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index f797f469..8dcf456a 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -33,7 +33,7 @@ import backupmanager.gui.component.chart.themes.ColorThemes; import backupmanager.gui.component.chart.themes.DefaultChartTheme; import backupmanager.gui.component.dashboard.CardBox; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.SystemForm; import net.miginfocom.swing.MigLayout; @SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index 7a11348f..d5506409 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -34,13 +34,14 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; +import backupmanager.Services.BackupObserver; import backupmanager.Services.BackupService; import backupmanager.gui.Table.BackupTable; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.Controllers.BackupManagerController; import backupmanager.gui.frames.Controllers.BackupPopupController; import backupmanager.gui.svg.SVGButton; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.SystemForm; import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; @@ -51,6 +52,7 @@ public class FormBackupTable extends CustomForm { private BackupManagerController managerController; private BackupTableDataService tableService; + private BackupObserver backupObserver; private final int COL_LAST_RUN = 3; private final int COL_AUTOMATIC = 4; private final int COL_NEXT_RUN = 5; @@ -83,6 +85,9 @@ public void formInit() { tableService = new BackupTableDataService(backupTable, formatter); managerController = new BackupManagerController(new BackupService(), tableService); + backupObserver = new BackupObserver(tableService, 2000); + backupObserver.start(); + formRefresh(); } @@ -541,4 +546,11 @@ public void setTranslations() { private JMenuItem itemCopyBackupName; private JMenuItem itemCopyTargetPath; private JMenuItem itemCopyDestinationPath; + + public void formClose() { + if (backupObserver != null) { + backupObserver.stop(); + backupObserver = null; + } + } } diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java index a00915eb..f72f4e96 100644 --- a/src/main/java/backupmanager/gui/forms/FormHistory.java +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -13,7 +13,7 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.SystemForm; import net.miginfocom.swing.MigLayout; @SystemForm(name = "History", description = "application history log") diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index 63653025..a187ddcb 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -39,8 +39,8 @@ import backupmanager.gui.component.AccentColorIcon; import backupmanager.gui.system.FormManager; import backupmanager.gui.themes.PanelThemes; -import backupmanager.utils.AppPreferences; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.AppPreferences; +import backupmanager.Utils.SystemForm; import net.miginfocom.swing.MigLayout; import raven.color.ColorPicker; import raven.modal.Drawer; diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index 8ce612cf..22d03be3 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -12,7 +12,7 @@ import backupmanager.gui.component.Subscription; import backupmanager.gui.forms.FormBackupTable; import backupmanager.gui.forms.FormLogin; -import backupmanager.utils.UndoRedo; +import backupmanager.Utils.UndoRedo; import raven.modal.Drawer; import raven.modal.ModalDialog; import raven.modal.component.SimpleModalBorder; @@ -42,7 +42,11 @@ private static void install() { } public static void showForm(Form form) { - if (form != FORMS.getCurrent()) { + Form current = FORMS.getCurrent(); + if (current instanceof FormBackupTable) { + ((FormBackupTable) current).formClose(); + } + if (form != current) { FORMS.add(form); form.formCheck(); form.formOpen(); @@ -53,6 +57,10 @@ public static void showForm(Form form) { public static void undo() { if (FORMS.isUndoAble()) { + Form current = FORMS.getCurrent(); + if (current instanceof FormBackupTable) { + ((FormBackupTable) current).formClose(); + } Form form = FORMS.undo(); form.formCheck(); form.formOpen(); @@ -63,6 +71,10 @@ public static void undo() { public static void redo() { if (FORMS.isRedoAble()) { + Form current = FORMS.getCurrent(); + if (current instanceof FormBackupTable) { + ((FormBackupTable) current).formClose(); + } Form form = FORMS.redo(); form.formCheck(); form.formOpen(); @@ -90,6 +102,10 @@ public static void login() { public static void logout() { Drawer.setVisible(false); + Form current = FORMS.getCurrent(); + if (current instanceof FormBackupTable) { + ((FormBackupTable) current).formClose(); + } frame.getContentPane().removeAll(); Form login = getLogin(); login.formCheck(); diff --git a/src/main/java/backupmanager/gui/system/FormSearch.java b/src/main/java/backupmanager/gui/system/FormSearch.java index 27a7b378..03626766 100644 --- a/src/main/java/backupmanager/gui/system/FormSearch.java +++ b/src/main/java/backupmanager/gui/system/FormSearch.java @@ -15,7 +15,7 @@ import backupmanager.gui.component.EmptyModalBorder; import backupmanager.gui.component.FormSearchPanel; import backupmanager.gui.menu.DrawerManager; -import backupmanager.utils.SystemForm; +import backupmanager.Utils.SystemForm; import raven.modal.ModalDialog; import raven.modal.drawer.item.Item; import raven.modal.drawer.item.MenuItem; diff --git a/src/main/java/backupmanager/gui/themes/PanelThemes.java b/src/main/java/backupmanager/gui/themes/PanelThemes.java index e0e462c1..6d300f18 100644 --- a/src/main/java/backupmanager/gui/themes/PanelThemes.java +++ b/src/main/java/backupmanager/gui/themes/PanelThemes.java @@ -4,7 +4,7 @@ import com.formdev.flatlaf.extras.FlatAnimatedLafChange; import com.formdev.flatlaf.util.LoggingFacade; import net.miginfocom.swing.MigLayout; -import backupmanager.utils.AppPreferences; +import backupmanager.Utils.AppPreferences; import javax.swing.*; import javax.swing.border.Border; From eab1c105e3602d3b38dedcd2dfd11e918d1915df Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Sat, 25 Apr 2026 15:29:22 +0200 Subject: [PATCH 13/17] create and edit fixed --- code_documentation.md | 4 ++ .../java/backupmanager/BackupOperations.java | 36 ++++++------ .../java/backupmanager/Email/EmailSender.java | 6 +- .../backupmanager/Helpers/BackupHelper.java | 57 +++++-------------- .../Helpers/SubscriptionHelper.java | 8 +-- .../Helpers/SubscriptionNotifier.java | 12 ++-- .../Managers/ExceptionManager.java | 10 ++-- .../backupmanager/Managers/ExportManager.java | 12 ++-- .../Managers/RunningBackupManager.java | 38 ------------- .../Managers/WebsiteManager.java | 10 ++-- .../Services/BackupObserver.java | 43 ++++++++++++++ .../backupmanager/Services/BackupService.java | 24 ++++---- .../Controllers/BackupEntryController.java | 55 ++++++++++++++---- .../gui/Controllers/EntryUserController.java | 6 +- .../gui/Controllers/TimePickerController.java | 6 +- .../gui/Controllers/TrayController.java | 6 +- .../gui/component/Subscription.java | 12 ++-- .../gui/forms/FormBackupDashboard.java | 4 +- .../gui/forms/FormBackupTable.java | 13 +---- .../gui/frames/BackupProgressGUI.java | 14 ++--- .../Controllers/BackupManagerController.java | 43 +++++++++++--- .../Controllers/BackupPopupController.java | 12 ++-- .../Controllers/BackupProgressController.java | 4 +- .../gui/simple/BackupEntryDialog.java | 23 ++++---- .../backupmanager/gui/system/FormManager.java | 17 +----- .../backupmanager/gui/themes/PanelThemes.java | 46 +++++++-------- .../backupmanager/gui/themes/ThemesInfo.java | 33 ++++------- src/test/java/test/LoginServiceTest.java | 5 ++ .../BackupRequestRepositoryTest.java | 2 +- 29 files changed, 283 insertions(+), 278 deletions(-) delete mode 100644 src/main/java/backupmanager/Managers/RunningBackupManager.java diff --git a/code_documentation.md b/code_documentation.md index 752615ad..f7267df5 100644 --- a/code_documentation.md +++ b/code_documentation.md @@ -157,3 +157,7 @@ Backup Manager is intentionally designed to be: * Minimal in external dependencies The goal is to avoid enterprise-level complexity while maintaining production-grade stability. + +## Build + +To build the project: `mvn clean install` diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 256ebda8..ce0056c4 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -23,15 +23,15 @@ import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; import backupmanager.Enums.ErrorType; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.dateForfolderNameFormatter; import backupmanager.Managers.ExceptionManager; import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; -import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.Utils.FolderUtils; +import backupmanager.database.Repositories.BackupRequestRepository; public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); @@ -42,7 +42,7 @@ public static void requestSingleBackup(ZippingContext context, BackupTriggerType if (!BackupRequestRepository.isAnyBackupRunning()) singleBackup(context, triggeredBy); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), Translations.get(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); } case SCHEDULER -> singleBackup(context, triggeredBy); } @@ -136,7 +136,7 @@ private static void updateAfterBackup(String path1, String path2, ZippingContext logger.info("Backup :\"" + context.execution().backup().getName() + "\" updated after the backup"); if (context.ui().trayIcon() != null) - context.ui().trayIcon().displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + context.execution().backup().getName() + TCategory.TRAY_ICON.getTranslation(TKey.SUCCESS_MESSAGE) + "\n" + TCategory.GENERAL.getTranslation(TKey.FROM) + ": " + path1 + "\n" + TCategory.GENERAL.getTranslation(TKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); + context.ui().trayIcon().displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + context.execution().backup().getName() + Translations.get(TKey.SUCCESS_MESSAGE) + "\n" + Translations.get(TKey.FROM) + ": " + path1 + "\n" + Translations.get(TKey.TO) + ": " + path2, TrayIcon.MessageType.INFO); } catch (IllegalArgumentException ex) { logger.error("An error occurred: " + ex.getMessage(), ex); ExceptionManager.openExceptionMessage(ex.getMessage(), Arrays.toString(ex.getStackTrace())); @@ -338,51 +338,51 @@ public static void setError(ErrorType error, TrayIcon trayIcon, String backupNam case InputMissing -> { logger.warn("Input Missing!"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_INPUT_MISSING), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_INPUT_MISSING), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INPUT_MISSING_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_INPUT_MISSING_GENERIC), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case InputError -> { logger.warn("Input Error! One or both paths do not exist."); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_FILES_NOT_EXISTING), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_FILES_NOT_EXISTING), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_PATH_NOT_EXISTING), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_PATH_NOT_EXISTING), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case SamePaths -> { logger.warn("The initial path and destination path cannot be the same. Please choose different paths"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_SAME_PATHS), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_SAME_PATHS), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_SAME_PATHS_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_SAME_PATHS_GENERIC), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ErrorCountingFiles -> { logger.warn("Error during counting files in directory"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_COUNTING_FILES), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_COUNTING_FILES), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_COUNTING_FILES), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_COUNTING_FILES), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingGenericError -> { logger.warn("Error during zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_ZIPPING_GENERIC), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingIOError -> { logger.warn("I/O error occurred while zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_IO), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_ZIPPING_IO), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_IO), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_ZIPPING_IO), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } case ZippingSecurityError -> { logger.warn("Security exception while zipping directory"); if (trayIcon != null) - trayIcon.displayMessage(TCategory.GENERAL.getTranslation(TKey.APP_NAME), TCategory.GENERAL.getTranslation(TKey.BACKUP) + ": " + backupName + TCategory.TRAY_ICON.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), TrayIcon.MessageType.ERROR); + trayIcon.displayMessage(Translations.get(TKey.APP_NAME), Translations.get(TKey.BACKUP) + ": " + backupName + Translations.get(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), TrayIcon.MessageType.ERROR); else - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_ZIPPING_SECURITY), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } default -> throw new IllegalArgumentException("Error type not recognized: " + error); } diff --git a/src/main/java/backupmanager/Email/EmailSender.java b/src/main/java/backupmanager/Email/EmailSender.java index 3851ee5d..2e905f6a 100644 --- a/src/main/java/backupmanager/Email/EmailSender.java +++ b/src/main/java/backupmanager/Email/EmailSender.java @@ -16,7 +16,7 @@ import backupmanager.Entities.User; import backupmanager.Enums.ConfigKey; import backupmanager.Enums.EmailType; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.EmailRepository; @@ -113,8 +113,8 @@ public static void sendUserCreationEmail(User user) { public static void sendConfirmEmailToUser(User user) { if (user == null) throw new IllegalArgumentException("User object cannot be null"); - String subject = TCategory.USER_DIALOG.getTranslation(TKey.EMAIL_CONFIRMATION_SUBJECT); - String body = TCategory.USER_DIALOG.getTranslation(TKey.EMAIL_CONFIRMATION_BODY); + String subject = Translations.get(TKey.EMAIL_CONFIRMATION_SUBJECT); + String body = Translations.get(TKey.EMAIL_CONFIRMATION_BODY); body = body.replace("[UserName]", user.getUserCompleteName()); body = body.replace("[SupportEmail]", ConfigKey.EMAIL.getValue()); diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 9f5ea965..23adb517 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -13,12 +13,10 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.BackupStatus; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; -import backupmanager.gui.Table.BackupTable; -import backupmanager.gui.frames.BackupProgressGUI; import backupmanager.gui.simple.TimePickerDialog; public class BackupHelper { @@ -27,29 +25,9 @@ public class BackupHelper { public static final DateTimeFormatter dateForfolderNameFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy'T'HH-mm-ss"); public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); - public static void newBackup(BackupProgressGUI progressBar) { - logger.info("Event --> new backup"); - } - public static void newBackup(ConfigurationBackup backup) { logger.info("Event --> new backup"); BackupConfigurationRepository.insertBackup(backup); - - updateBackupTable(); - } - - public static void deleteBackup(int selectedRow, BackupTable backupTable, boolean isConfermationRequired) { - logger.info("Event --> deleting backup"); - - if (isConfermationRequired) { - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (response != JOptionPane.YES_OPTION) { - return; - } - } - - String backupName = (String) backupTable.getValueAt(selectedRow, 0); - deleteBackup(backupName); } public static void deleteBackup(String backupName) { @@ -61,13 +39,12 @@ public static void deleteBackup(String backupName) { public static void deleteBackup(ConfigurationBackup backup) { logger.info("Event --> deleting backup" + backup.getName()); BackupConfigurationRepository.deleteBackup(backup.getId()); - updateBackupTable(); } public static void deleteBackupWithConfirmition(ConfigurationBackup backup) { logger.info("Event --> deleting backup request with confirmation for backup: " + backup.getName()); - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { BackupHelper.deleteBackup(backup); } @@ -82,28 +59,28 @@ public static void updateBackup(ConfigurationBackup updatedBackup) { logger.info("Updating backup: " + updatedBackup.getName()); BackupConfigurationRepository.updateBackup(updatedBackup); } - - updateBackupTable(); } public static List<ConfigurationBackup> getBackupList() { List<ConfigurationBackup> backups = BackupConfigurationRepository.getBackupList(); - // BackupManagerGUI.backups = backups; // i have to keep update also the backup list in the main panel return backups; } - public static TimeInterval openTimePicker(TimeInterval time) { - TimePickerDialog picker = new TimePickerDialog(time); + public static TimeInterval openTimePicker() { + return openTimePicker(new TimePickerDialog(null)); + } + + public static TimeInterval openTimePicker(TimePickerDialog picker) { picker.setVisible(true); return picker.getResult(); } public static void showMessageActivationAutoBackup(TimeInterval timeInterval, String startPath, String destinationPath) { - String from = TCategory.GENERAL.getTranslation(TKey.FROM); - String to = TCategory.GENERAL.getTranslation(TKey.TO); - String activated = TCategory.DIALOGS.getTranslation(TKey.AUTO_BACKUP_ACTIVATED_MESSAGE); - String setted = TCategory.DIALOGS.getTranslation(TKey.SETTED_EVERY_MESSAGE); - String days = TCategory.DIALOGS.getTranslation(TKey.DAYS_MESSAGE); + String from = Translations.get(TKey.FROM); + String to = Translations.get(TKey.TO); + String activated = Translations.get(TKey.AUTO_BACKUP_ACTIVATED_MESSAGE); + String setted = Translations.get(TKey.SETTED_EVERY_MESSAGE); + String days = Translations.get(TKey.DAYS_MESSAGE); JOptionPane.showMessageDialog(null, activated + "\n\t" + from + ": " + startPath + "\n\t" + to + ": " @@ -120,19 +97,13 @@ public static LocalDateTime getNexDateBackup(TimeInterval timeInterval) { public static void forceBackupTermination(int requestId) { BackupRequestRepository.updateRequestStatusByRequestId(requestId, BackupStatus.TERMINATED); - updateBackupTable(); - } - - private static void updateBackupTable() { - // if (BackupManagerGUI.model != null) - // TableDataManager.updateTableWithNewBackupList(getBackupList()); } public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup backup) { logger.info("Event --> automatic backup"); if (backup.isAutomatic()) { - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response != JOptionPane.YES_OPTION) { return null; } @@ -157,7 +128,7 @@ public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup back if (backup.getName() == null || backup.getName().isEmpty()) return null; // message - TimeInterval timeInterval = openTimePicker(null); + TimeInterval timeInterval = openTimePicker(); if (timeInterval == null) return null; //set date for next backup diff --git a/src/main/java/backupmanager/Helpers/SubscriptionHelper.java b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java index f0def76b..7ab31e54 100644 --- a/src/main/java/backupmanager/Helpers/SubscriptionHelper.java +++ b/src/main/java/backupmanager/Helpers/SubscriptionHelper.java @@ -5,7 +5,7 @@ import backupmanager.Entities.Configurations; import backupmanager.Entities.Subscription; import backupmanager.Enums.SubscriptionStatus; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Json.JsonConfig; import backupmanager.database.Repositories.SubscriptionRepository; @@ -29,9 +29,9 @@ public static SubscriptionStatus getSubscriptionStatus() { public static String getSubscriptionStatusTranslated(SubscriptionStatus status) { String statusTranslation; switch (status) { - case EXPIRED -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED); - case ACTIVE -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_ACTIVE); - case EXPIRATION -> statusTranslation = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING); + case EXPIRED -> statusTranslation = Translations.get(TKey.SUBSCRIPTION_EXPIRED); + case ACTIVE -> statusTranslation = Translations.get(TKey.SUBSCRIPTION_ACTIVE); + case EXPIRATION -> statusTranslation = Translations.get(TKey.SUBSCRIPTION_EXPIRING); default -> statusTranslation = ""; } return statusTranslation; diff --git a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java index c72679cb..f97c541e 100644 --- a/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java +++ b/src/main/java/backupmanager/Helpers/SubscriptionNotifier.java @@ -4,21 +4,21 @@ import javax.swing.JOptionPane; -import backupmanager.gui.Controllers.TrayController; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.gui.Controllers.TrayController; public class SubscriptionNotifier { public static void showExpiringWarning(TrayController trayController) { - String title = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING_TITLE); - String message = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRING_MESSAGE); + String title = Translations.get(TKey.SUBSCRIPTION_EXPIRING_TITLE); + String message = Translations.get(TKey.SUBSCRIPTION_EXPIRING_MESSAGE); showMessage(trayController, title, message, TrayIcon.MessageType.WARNING); } public static void showExpiredAlert(TrayController trayController) { - String title = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED_TITLE); - String message = TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_EXPIRED_MESSAGE); + String title = Translations.get(TKey.SUBSCRIPTION_EXPIRED_TITLE); + String message = Translations.get(TKey.SUBSCRIPTION_EXPIRED_MESSAGE); showMessage(trayController, title, message, TrayIcon.MessageType.ERROR); } diff --git a/src/main/java/backupmanager/Managers/ExceptionManager.java b/src/main/java/backupmanager/Managers/ExceptionManager.java index 0c7e498a..ba25d259 100644 --- a/src/main/java/backupmanager/Managers/ExceptionManager.java +++ b/src/main/java/backupmanager/Managers/ExceptionManager.java @@ -13,14 +13,14 @@ import backupmanager.Email.EmailSender; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; public class ExceptionManager { private static final Logger logger = LoggerFactory.getLogger(ExceptionManager.class); public static void openExceptionMessage(String errorMessage, String stackTrace) { - Object[] options = {TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON), TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_CLIPBOARD_BUTTON), TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_REPORT_BUTTON)}; + Object[] options = {Translations.get(TKey.CLOSE_BUTTON), Translations.get(TKey.EXCEPTION_MESSAGE_CLIPBOARD_BUTTON), Translations.get(TKey.EXCEPTION_MESSAGE_REPORT_BUTTON)}; if (errorMessage == null) { errorMessage = ""; @@ -30,7 +30,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) EmailSender.sendErrorEmail("Critical Error Report", stackTrace, errorMessage); - String stackTraceMessage = TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_REPORT_MESSAGE) + "\n" + stackTrace; + String stackTraceMessage = Translations.get(TKey.EXCEPTION_MESSAGE_REPORT_MESSAGE) + "\n" + stackTrace; int choice; @@ -59,7 +59,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) scrollPane.setPreferredSize(new Dimension(MAX_WIDTH, 300)); // Display the option dialog with the JScrollPane - String error = TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE); + String error = Translations.get(TKey.ERROR_GENERIC_TITLE); choice = JOptionPane.showOptionDialog( null, scrollPane, // The JScrollPane containing the error message @@ -75,7 +75,7 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) StringSelection selection = new StringSelection(stackTrace); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); logger.info("Error text has been copied to the clipboard"); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE)); + JOptionPane.showMessageDialog(null, Translations.get(TKey.EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE)); } else if (choice == 2) { WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue()); } diff --git a/src/main/java/backupmanager/Managers/ExportManager.java b/src/main/java/backupmanager/Managers/ExportManager.java index 9f0a47f1..1fba3441 100644 --- a/src/main/java/backupmanager/Managers/ExportManager.java +++ b/src/main/java/backupmanager/Managers/ExportManager.java @@ -14,7 +14,7 @@ import backupmanager.BackupOperations; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; public class ExportManager { @@ -31,7 +31,7 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) return; } - String filename = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.CSV_NAME_MESSAGE_INPUT)); + String filename = JOptionPane.showInputDialog(null, Translations.get(TKey.CSV_NAME_MESSAGE_INPUT)); if (filename == null || filename.isEmpty()) { logger.info("Exporting backups to CSV cancelled"); return; @@ -39,7 +39,7 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) // Validate filename if (!filename.matches("[a-zA-Z0-9-_ ]+")) { - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_INVALID_FILENAME), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_INVALID_FILENAME), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); logger.info("Exporting backups to CSV cancelled due to invalid file name"); return; } @@ -50,7 +50,7 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) // Check if the file exists File file = new File(fullPath); if (file.exists()) { - int overwrite = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_FILE_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); + int overwrite = JOptionPane.showConfirmDialog(null, Translations.get(TKey.DUPLICATED_FILE_NAME_MESSAGE), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION); if (overwrite != JOptionPane.YES_OPTION) { logger.info("Exporting backups to CSV cancelled by user (file exists)"); return; @@ -70,10 +70,10 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) } } - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE), Translations.get(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); } catch (IOException ex) { logger.error("Error exporting backups to CSV: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_CSV) + ex.getMessage(), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_CSV) + ex.getMessage(), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } finally { logger.info("Exporting backups to CSV finished"); } diff --git a/src/main/java/backupmanager/Managers/RunningBackupManager.java b/src/main/java/backupmanager/Managers/RunningBackupManager.java deleted file mode 100644 index 6c10c164..00000000 --- a/src/main/java/backupmanager/Managers/RunningBackupManager.java +++ /dev/null @@ -1,38 +0,0 @@ -package backupmanager.Managers; - -public final class RunningBackupManager { - - private static volatile RunningBackupManager instance; - private volatile boolean running = false; - - private RunningBackupManager() {} - - public static RunningBackupManager getInstance() { - RunningBackupManager result = instance; - - if (result != null) - return result; - - synchronized (RunningBackupManager.class) { - if (instance == null) - instance = new RunningBackupManager(); - return instance; - } - } - - public synchronized boolean startBackup() { - if (running) { - return false; - } - running = true; - return true; - } - - public synchronized void finishBackup() { - running = false; - } - - public boolean isRunning() { - return running; - } -} diff --git a/src/main/java/backupmanager/Managers/WebsiteManager.java b/src/main/java/backupmanager/Managers/WebsiteManager.java index 412dcbb3..94d21eb1 100644 --- a/src/main/java/backupmanager/Managers/WebsiteManager.java +++ b/src/main/java/backupmanager/Managers/WebsiteManager.java @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; public class WebsiteManager { @@ -27,7 +27,7 @@ public static void openWebSite(String reportUrl) { } } catch (IOException | URISyntaxException e) { logger.error("Failed to open the web page: " + e.getMessage(), e); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_OPENING_WEBSITE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_OPENING_WEBSITE), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } @@ -44,15 +44,15 @@ public static void sendEmail() { desktop.mail(uri); } catch (IOException | URISyntaxException ex) { logger.error("Failed to send email: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { logger.warn("Mail action is unsupported in your system's desktop environment."); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { logger.warn("Desktop integration is unsupported on this system."); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } diff --git a/src/main/java/backupmanager/Services/BackupObserver.java b/src/main/java/backupmanager/Services/BackupObserver.java index 6eb6891e..d8fcf284 100644 --- a/src/main/java/backupmanager/Services/BackupObserver.java +++ b/src/main/java/backupmanager/Services/BackupObserver.java @@ -1,7 +1,11 @@ package backupmanager.Services; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -29,6 +33,12 @@ public class BackupObserver { private final BackupTableDataService tableService; private final long millisecondsToWait; + // track last-seen timestamps for backups that were running + private final ConcurrentMap<Integer, Long> lastSeenRunning = new ConcurrentHashMap<>(); + + // grace period to wait before removing progress indicator (milliseconds) + private final long graceMillis = 3000L; + public BackupObserver(BackupTableDataService tableService, int millisecondsToWait) { this.tableService = tableService; this.millisecondsToWait = millisecondsToWait; @@ -44,6 +54,11 @@ public void start() { Map<Integer, ConfigurationBackup> configs = BackupConfigurationRepository.getBackupMap(); + // Collect running configuration ids + Set<Integer> runningConfigIds = new HashSet<>(); + for (BackupRequest r : running) runningConfigIds.add(r.backupConfigurationId()); + + // Update progress for running backups for (BackupRequest request : running) { ConfigurationBackup config = configs.get(request.backupConfigurationId()); @@ -63,6 +78,34 @@ public void start() { }); } + long now = System.currentTimeMillis(); + + // Update last seen timestamps for running backups + for (BackupRequest r : running) { + lastSeenRunning.put(r.backupConfigurationId(), now); + } + + // Cleanup any progress indicators for backups that are not currently running, + // but only if we saw them running before and the grace period has elapsed. + for (ConfigurationBackup config : configs.values()) { + int id = config.getId(); + if (!runningConfigIds.contains(id)) { + Long lastSeen = lastSeenRunning.get(id); + if (lastSeen != null) { + if (now - lastSeen >= graceMillis) { + lastSeenRunning.remove(id); + SwingUtilities.invokeLater(() -> { + try { + tableService.removeProgress(config); + } catch (Exception e) { + logger.debug("Error while cleaning obsolete progress for {}: {}", config.getName(), e.getMessage()); + } + }); + } + } + } + } + } catch (Exception ex) { logger.error("Observer error", ex); } diff --git a/src/main/java/backupmanager/Services/BackupService.java b/src/main/java/backupmanager/Services/BackupService.java index c3a48b7f..b6e34746 100644 --- a/src/main/java/backupmanager/Services/BackupService.java +++ b/src/main/java/backupmanager/Services/BackupService.java @@ -4,7 +4,7 @@ import java.util.List; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -45,17 +45,17 @@ public ConfigurationBackup getBackupByName(String name) { } public String buildDetails(ConfigurationBackup backup) { - String backupNameStr = TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_NAME_DETAIL); - String initialPathStr = TCategory.BACKUP_LIST.getTranslation(TKey.INITIAL_PATH_DETAIL); - String destinationPathStr = TCategory.BACKUP_LIST.getTranslation(TKey.DESTINATION_PATH_DETAIL); - String lastBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.LAST_BACKUP_DETAIL); - String nextBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.NEXT_BACKUP_DATE_DETAIL); - String timeIntervalBackupStr = TCategory.BACKUP_LIST.getTranslation(TKey.TIME_INTERVAL_DETAIL); - String creationDateStr = TCategory.BACKUP_LIST.getTranslation(TKey.CREATION_DATE_DETAIL); - String lastUpdateDateStr = TCategory.BACKUP_LIST.getTranslation(TKey.LAST_UPDATE_DATE_DETAIL); - String backupCountStr = TCategory.BACKUP_LIST.getTranslation(TKey.BACKUP_COUNT_DETAIL); - String notesStr = TCategory.BACKUP_LIST.getTranslation(TKey.NOTES_DETAIL); - String maxBackupsToKeepStr = TCategory.BACKUP_LIST.getTranslation(TKey.MAX_BACKUPS_TO_KEEP_DETAIL); + String backupNameStr = Translations.get(TKey.BACKUP_NAME_DETAIL); + String initialPathStr = Translations.get(TKey.INITIAL_PATH_DETAIL); + String destinationPathStr = Translations.get(TKey.DESTINATION_PATH_DETAIL); + String lastBackupStr = Translations.get(TKey.LAST_BACKUP_DETAIL); + String nextBackupStr = Translations.get(TKey.NEXT_BACKUP_DATE_DETAIL); + String timeIntervalBackupStr = Translations.get(TKey.TIME_INTERVAL_DETAIL); + String creationDateStr = Translations.get(TKey.CREATION_DATE_DETAIL); + String lastUpdateDateStr = Translations.get(TKey.LAST_UPDATE_DATE_DETAIL); + String backupCountStr = Translations.get(TKey.BACKUP_COUNT_DETAIL); + String notesStr = Translations.get(TKey.NOTES_DETAIL); + String maxBackupsToKeepStr = Translations.get(TKey.MAX_BACKUPS_TO_KEEP_DETAIL); return """ <html> diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index 9a4793f2..b502c0e2 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -16,7 +16,7 @@ import backupmanager.Entities.TimeInterval; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; import backupmanager.Exceptions.InvalidTimeInterval; @@ -24,6 +24,7 @@ import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; +import backupmanager.gui.simple.TimePickerDialog; public class BackupEntryController { private static final Logger logger = LoggerFactory.getLogger(BackupEntryController.class); @@ -62,8 +63,8 @@ public ConfigurationBackup getBackup(String name, String initialPath, String des } } - public TimeInterval handleTimePickerAction(String target, String destination) throws InvalidTimeInterval { - TimeInterval time = BackupHelper.openTimePicker(currentBackup.getTimeIntervalBackup()); + public TimeInterval handleTimePickerAction(TimePickerDialog picker, String target, String destination) throws InvalidTimeInterval { + TimeInterval time = BackupHelper.openTimePicker(picker); if (time == null) throw new InvalidTimeInterval(); LocalDateTime nextDateBackup = BackupHelper.getNexDateBackup(time); @@ -80,11 +81,11 @@ public boolean canDisposeAfterOk(String name, String initialPath, String destina if (name.isBlank() || destinationPath.isBlank() || initialPath.isBlank()) return false; - currentBackup = getBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); + updateCurrentBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); if (create) { if (ConfigurationBackup.getBackupByName(currentBackup.getName()) != null) { - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { BackupHelper.deleteBackup(currentBackup.getName()); } else { @@ -107,7 +108,8 @@ public void openFileChooser(JTextField filed, boolean allowFiles) { } public boolean toggleAutomaticBackup(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { - currentBackup = getBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); + updateCurrentBackup(name, initialPath, destinationPath,notes, autoBackup, maxBackupsToKeep); + currentBackup.setAutomatic(!currentBackup.isAutomatic()); ConfigurationBackup backup = BackupHelper.toggleAutomaticBackup(currentBackup); @@ -128,13 +130,24 @@ public boolean toggleAutomaticBackup(String name, String initialPath, String des public void handleSingleBackupRequest(BackupTableDataService backupTable, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) throws BackupAlreadyRunningException { if (BackupRequestRepository.isAnyBackupRunning()) { - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); + JOptionPane.showMessageDialog(null, + Translations.get(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), + Translations.get(TKey.WARNING_GENERIC_TITLE), + JOptionPane.WARNING_MESSAGE); throw new BackupAlreadyRunningException(); } - // update currentBackup - if (currentBackup == null) { - currentBackup = getBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); + currentBackup = getBackup( + name, + initialPath, + destinationPath, + notes, + autoBackup, + maxBackupsToKeep + ); + + if (ConfigurationBackup.getBackupByName(currentBackup.getName()) == null) { + BackupHelper.newBackup(currentBackup); } singleBackup(initialPath, destinationPath, backupTable); @@ -193,4 +206,26 @@ public ConfigurationBackup getCurrentBackup() { public void setCurrentBackup(ConfigurationBackup currentBackup) { this.currentBackup = currentBackup; } + + private void updateCurrentBackup(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { + if (currentBackup == null) { + currentBackup = getBackup( + name, + initialPath, + destinationPath, + notes, + autoBackup, + maxBackupsToKeep + ); + return; + } + + currentBackup.setName(name); + currentBackup.setTargetPath(initialPath); + currentBackup.setDestinationPath(destinationPath); + currentBackup.setNotes(notes); + currentBackup.setAutomatic(autoBackup); + currentBackup.setMaxToKeep(maxBackupsToKeep); + currentBackup.setLastUpdateDate(LocalDateTime.now()); + } } diff --git a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java index 4c8d0317..4c5a814a 100644 --- a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java +++ b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java @@ -3,16 +3,16 @@ import javax.swing.JOptionPane; import backupmanager.Email.EmailValidator; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; public class EntryUserController { public boolean isInputOkAndShowErrorIfNecessary(javax.swing.JDialog dialog, String name, String surname, String email) { if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) { - JOptionPane.showMessageDialog(dialog, TCategory.USER_DIALOG.getTranslation(TKey.ERROR_MESSAGE_FOR_MISSING_DATA), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_MESSAGE_FOR_MISSING_DATA), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); return false; } else if (!EmailValidator.isValidEmail(email)) { - JOptionPane.showMessageDialog(dialog, TCategory.USER_DIALOG.getTranslation(TKey.ERROR_MESSAGE_FOR_WRONG_EMAIL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_MESSAGE_FOR_WRONG_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); return false; } return true; diff --git a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java index 234dbd84..2fd56a38 100644 --- a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java +++ b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java @@ -3,7 +3,7 @@ import javax.swing.JOptionPane; import backupmanager.Entities.TimeInterval; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.gui.simple.TimePickerDialog; @@ -33,11 +33,11 @@ private boolean isShortTimeCorrect(int days, int hours) { } private void showErrorMessageForLongTime(javax.swing.JDialog dialog) { - JOptionPane.showMessageDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.ERROR_WRONG_TIME_INTERVAL), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_WRONG_TIME_INTERVAL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } private boolean showWarningMessageForShortTimeAndGetIfItOkayResponse(javax.swing.JDialog dialog) { - int response = JOptionPane.showConfirmDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.WARNING_SHORT_TIME_INTERVAL_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.WARNING_GENERIC_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(dialog, Translations.get(TKey.WARNING_SHORT_TIME_INTERVAL_MESSAGE), Translations.get(TKey.WARNING_GENERIC_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); return response == JOptionPane.YES_OPTION; } } diff --git a/src/main/java/backupmanager/gui/Controllers/TrayController.java b/src/main/java/backupmanager/gui/Controllers/TrayController.java index 3cb6b827..4f8d3c87 100644 --- a/src/main/java/backupmanager/gui/Controllers/TrayController.java +++ b/src/main/java/backupmanager/gui/Controllers/TrayController.java @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; public class TrayController { @@ -71,8 +71,8 @@ public void mouseClicked(MouseEvent e) { private PopupMenu setupAndGetPopupMenu() { PopupMenu popup = new PopupMenu(); - MenuItem openItem = new MenuItem(TCategory.TRAY_ICON.getTranslation(TKey.OPEN_ACTION)); - MenuItem exitItem = new MenuItem(TCategory.TRAY_ICON.getTranslation(TKey.EXIT_ACTION)); + MenuItem openItem = new MenuItem(Translations.get(TKey.OPEN_ACTION)); + MenuItem exitItem = new MenuItem(Translations.get(TKey.EXIT_ACTION)); popup.add(openItem); popup.addSeparator(); diff --git a/src/main/java/backupmanager/gui/component/Subscription.java b/src/main/java/backupmanager/gui/component/Subscription.java index 00a49703..c2b9b667 100644 --- a/src/main/java/backupmanager/gui/component/Subscription.java +++ b/src/main/java/backupmanager/gui/component/Subscription.java @@ -11,7 +11,7 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.SubscriptionStatus; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Managers.WebsiteManager; @@ -93,17 +93,17 @@ private String buildHtml(SubscriptionStatus status, String statusText, String va </div> </html> """.formatted( - TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_STATUS), + Translations.get(TKey.SUBSCRIPTION_STATUS), statusColor, statusText, - TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_VALID_FROM), + Translations.get(TKey.SUBSCRIPTION_VALID_FROM), validFrom, - TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_VALID_TO), + Translations.get(TKey.SUBSCRIPTION_VALID_TO), validTo, ConfigKey.EMAIL.getValue(), subject, - TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_CONTACT_US), - TCategory.SUBSCRIPTION.getTranslation(TKey.SUBSCRIPTION_TO_EXTEND) + Translations.get(TKey.SUBSCRIPTION_CONTACT_US), + Translations.get(TKey.SUBSCRIPTION_TO_EXTEND) ); } diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index 8dcf456a..50ff4b31 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -25,6 +25,7 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Services.BackupAnalyticsService; +import backupmanager.Utils.SystemForm; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.component.ToolBarSelection; @@ -33,7 +34,6 @@ import backupmanager.gui.component.chart.themes.ColorThemes; import backupmanager.gui.component.chart.themes.DefaultChartTheme; import backupmanager.gui.component.dashboard.CardBox; -import backupmanager.Utils.SystemForm; import net.miginfocom.swing.MigLayout; @SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") @@ -43,7 +43,7 @@ public class FormBackupDashboard extends CustomForm { private static final int CARD_SUCCESS_RATE = 1; private static final int CARD_DURATION = 2; private static final int CARD_COMPRESSION = 3; - private static final int CARD_DISK_USAGE = 4; + // private static final int CARD_DISK_USAGE = 4; public FormBackupDashboard() { build(); diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index d5506409..fe13fb3b 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -36,13 +36,13 @@ import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Services.BackupObserver; import backupmanager.Services.BackupService; +import backupmanager.Utils.SystemForm; +import backupmanager.Utils.table.TableHeaderAlignment; import backupmanager.gui.Table.BackupTable; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.Controllers.BackupManagerController; import backupmanager.gui.frames.Controllers.BackupPopupController; import backupmanager.gui.svg.SVGButton; -import backupmanager.Utils.SystemForm; -import backupmanager.utils.table.TableHeaderAlignment; import net.miginfocom.swing.MigLayout; @SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) @@ -469,12 +469,10 @@ private void update() { public void showCreateModal() { managerController.showCreateModal(this); - loadData(); } private void showEditModal(ConfigurationBackup backup) { managerController.showEditModal(this, backup); - loadData(); } private void showEditModal() { @@ -546,11 +544,4 @@ public void setTranslations() { private JMenuItem itemCopyBackupName; private JMenuItem itemCopyTargetPath; private JMenuItem itemCopyDestinationPath; - - public void formClose() { - if (backupObserver != null) { - backupObserver.stop(); - backupObserver = null; - } - } } diff --git a/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java index cc7f473e..ec18a2ed 100644 --- a/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java +++ b/src/main/java/backupmanager/gui/frames/BackupProgressGUI.java @@ -1,7 +1,7 @@ package backupmanager.gui.frames; import backupmanager.gui.Controllers.GuiController; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.gui.frames.Controllers.BackupProgressController; @@ -34,10 +34,10 @@ public void updateProgressBar(int value, String fileProcessed, int filesCopiedSo fileZippedLabel.setText(fileProcessed); // edit the title with counts - setTitle(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.PROGRESS_BACKUP_TITLE) + " - " + filesCopiedSoFar + "/" + totalFilesCount); + setTitle(Translations.get(TKey.PROGRESS_BACKUP_TITLE) + " - " + filesCopiedSoFar + "/" + totalFilesCount); if (value == 100) { - loadingMessageLabel.setText(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.STATUS_COMPLETED)); + loadingMessageLabel.setText(Translations.get(TKey.STATUS_COMPLETED)); closeButton.setEnabled(true); CancelButton.setEnabled(false); fileZippedLabel.setText(""); @@ -155,10 +155,10 @@ private void CancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN- }//GEN-LAST:event_CancelButtonActionPerformed private void setTranslations() { - setTitle(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.PROGRESS_BACKUP_TITLE)); - CancelButton.setText(TCategory.GENERAL.getTranslation(TKey.CANCEL_BUTTON)); - closeButton.setText(TCategory.GENERAL.getTranslation(TKey.CLOSE_BUTTON)); - loadingMessageLabel.setText(TCategory.PROGRESS_BACKUP_FRAME.getTranslation(TKey.STATUS_LOADING)); + setTitle(Translations.get(TKey.PROGRESS_BACKUP_TITLE)); + CancelButton.setText(Translations.get(TKey.CANCEL_BUTTON)); + closeButton.setText(Translations.get(TKey.CLOSE_BUTTON)); + loadingMessageLabel.setText(Translations.get(TKey.STATUS_LOADING)); } // Variables declaration - do not modify//GEN-BEGIN:variables diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index 6dffa90e..51949582 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -1,6 +1,5 @@ package backupmanager.gui.frames.Controllers; -import java.awt.Component; import java.awt.Dimension; import java.awt.Toolkit; import java.util.ArrayList; @@ -8,7 +7,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Enums.ConfigKey; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Services.BackupService; import backupmanager.gui.Table.BackupTableDataService; @@ -29,6 +28,8 @@ public BackupManagerController(BackupService backupService, BackupTableDataServi this.backupTable = backupTable; } + + // TODO: enable it public int[] getScreenSize() { Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); int width = Math.min((int) size.getWidth(), Integer.parseInt(ConfigKey.GUI_WIDTH.getValue())); @@ -54,21 +55,37 @@ public List<ConfigurationBackup> researchInTableAndGet(List<ConfigurationBackup> return tempBackups; } - public void showCreateModal(Component parent) { + public void showCreateModal(CustomForm form) { Option option = ModalDialog.createOption(); option.getLayoutOption() .setSize(-1, 1f) .setLocation(Location.TRAILING, Location.TOP) .setAnimateDistance(0.7f, 0); - ModalDialog.showModal(parent, + BackupEntryDialog dialog = new BackupEntryDialog(backupTable); + + ModalDialog.showModal( + form, new SimpleModalBorder( - new BackupEntryDialog(backupTable), - TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_SUBTITLE_CREATE), + dialog, + Translations.get(TKey.PAGE_SUBTITLE_CREATE), SimpleModalBorder.OK_CANCEL_OPTION, - (controller, action) -> {} + (controller, action) -> { + if (action == SimpleModalBorder.OK_OPTION) { + if (!dialog.canDispose()) { + return; + } + + ConfigurationBackup backup = dialog.getResult(); + backupService.updateBackup(backup); + + form.formRefresh(); + controller.close(); + } + } ), - option); + option + ); } public void showEditModal(CustomForm form, ConfigurationBackup backup) { @@ -84,13 +101,21 @@ public void showEditModal(CustomForm form, ConfigurationBackup backup) { form, new SimpleModalBorder( dialog, - TCategory.BACKUP_ENTRY.getTranslation(TKey.PAGE_SUBTITLE_EDIT), + Translations.get(TKey.PAGE_SUBTITLE_EDIT), SimpleModalBorder.OK_CANCEL_OPTION, (controller, action) -> { + if (action == SimpleModalBorder.OK_OPTION) { + + if (!dialog.canDispose()) { + return; + } + ConfigurationBackup editedBackup = dialog.getResult(); backupService.updateBackup(editedBackup); + form.formRefresh(); + controller.close(); } } ), diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index 58571c83..c40c85d7 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -24,7 +24,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.ZippingContext; import backupmanager.Enums.BackupTriggerType; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; @@ -37,7 +37,7 @@ public class BackupPopupController { private static final Logger logger = LoggerFactory.getLogger(BackupPopupController.class); public static void popupItemInterrupt() { - + // TODO: add it } public static void popupItemRenameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { @@ -138,7 +138,7 @@ private static void renameBackup(List<ConfigurationBackup> backups, Configuratio private static String getBackupNameFromInputDialog(List<ConfigurationBackup> backups, String oldName, boolean canOverwrite) { while (true) { - String backupName = JOptionPane.showInputDialog(null, TCategory.DIALOGS.getTranslation(TKey.BACKUP_NAME_INPUT), oldName); + String backupName = JOptionPane.showInputDialog(null, Translations.get(TKey.BACKUP_NAME_INPUT), oldName); // If the user cancels the operation if (backupName == null || backupName.trim().isEmpty()) { @@ -151,7 +151,7 @@ private static String getBackupNameFromInputDialog(List<ConfigurationBackup> bac if (existingBackup.isPresent()) { if (canOverwrite) { - int response = JOptionPane.showConfirmDialog(null, TCategory.DIALOGS.getTranslation(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); + int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { backups.remove(existingBackup.get()); @@ -159,7 +159,7 @@ private static String getBackupNameFromInputDialog(List<ConfigurationBackup> bac } } else { logger.warn("Backup name '{}' is already in use", backupName); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.BACKUP_NAME_ALREADY_USED_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.BACKUP_NAME_ALREADY_USED_MESSAGE), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } else { return backupName; // Return valid name @@ -191,7 +191,7 @@ private static void openFolder(String path) { } } else { logger.warn("The folder does not exist or is invalid"); - JOptionPane.showMessageDialog(null, TCategory.DIALOGS.getTranslation(TKey.ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING), TCategory.DIALOGS.getTranslation(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); } } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java index b2fa952a..b1bd5d96 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupProgressController.java @@ -2,14 +2,14 @@ import javax.swing.JOptionPane; -import backupmanager.Enums.Translations.TCategory; +import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Services.ZippingThread; public class BackupProgressController { public void handleCancelButtonRequest(javax.swing.JDialog dialog) { - int response = JOptionPane.showConfirmDialog(dialog, TCategory.DIALOGS.getTranslation(TKey.INTERRUPT_BACKUP_PROCESS_MESSAGE), TCategory.DIALOGS.getTranslation(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + int response = JOptionPane.showConfirmDialog(dialog, Translations.get(TKey.INTERRUPT_BACKUP_PROCESS_MESSAGE), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (response == JOptionPane.YES_OPTION) { cancelBackup(); dialog.dispose(); diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index fe563503..b00d8c06 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -24,6 +24,7 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; +import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; import backupmanager.gui.Controllers.BackupEntryController; import backupmanager.gui.Table.BackupTableDataService; @@ -127,7 +128,7 @@ protected void init() { styleSpinner(maxToKeeSpinner); configureSpinner(maxToKeeSpinner, 1, 100); - txtNotes.addKeyListener(new KeyAdapter() { + textAreaNotes.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { if (e.isControlDown() && e.getKeyChar() == 10) { @@ -146,14 +147,6 @@ public ConfigurationBackup getResult() { } private void openTimeInterval() { - // try { - // TimeInterval time = entryController.handleTimePickerAction(this, txtTargetPath.getText(), txtDestinationPath.getText()); - // timeIntervalBtn.setToolTipText(time.toString()); - // openBackupActivationMessage(time); - // } catch (InvalidTimeInterval e) { - // // no actions - // } - TimePickerDialog timePicker = new TimePickerDialog(entryController.getCurrentBackup().getTimeIntervalBackup()); Option option = ModalDialog.createOption(); @@ -167,10 +160,14 @@ private void openTimeInterval() { (controller, action) -> { if (action == SimpleModalBorder.YES_OPTION) { - TimeInterval time = timePicker.getResult(); + try { + TimeInterval time = entryController.handleTimePickerAction(timePicker, txtTargetPath.getText(), txtDestinationPath.getText()); + timeIntervalBtn.setToolTipText(time.toString()); + openBackupActivationMessage(time); - timeIntervalBtn.setToolTipText(time.toString()); - openBackupActivationMessage(time); + } catch (InvalidTimeInterval e) { + // no actions + } controller.close(); } @@ -254,7 +251,7 @@ private void enableTimePickerButton(ConfigurationBackup backup) { } } - private boolean canDispose() { + public boolean canDispose() { return entryController.canDisposeAfterOk(txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), textAreaNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue(), create); } diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index 22d03be3..f7ab595a 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -8,11 +8,11 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.UndoRedo; import backupmanager.gui.component.About; import backupmanager.gui.component.Subscription; import backupmanager.gui.forms.FormBackupTable; import backupmanager.gui.forms.FormLogin; -import backupmanager.Utils.UndoRedo; import raven.modal.Drawer; import raven.modal.ModalDialog; import raven.modal.component.SimpleModalBorder; @@ -43,9 +43,6 @@ private static void install() { public static void showForm(Form form) { Form current = FORMS.getCurrent(); - if (current instanceof FormBackupTable) { - ((FormBackupTable) current).formClose(); - } if (form != current) { FORMS.add(form); form.formCheck(); @@ -57,10 +54,6 @@ public static void showForm(Form form) { public static void undo() { if (FORMS.isUndoAble()) { - Form current = FORMS.getCurrent(); - if (current instanceof FormBackupTable) { - ((FormBackupTable) current).formClose(); - } Form form = FORMS.undo(); form.formCheck(); form.formOpen(); @@ -71,10 +64,6 @@ public static void undo() { public static void redo() { if (FORMS.isRedoAble()) { - Form current = FORMS.getCurrent(); - if (current instanceof FormBackupTable) { - ((FormBackupTable) current).formClose(); - } Form form = FORMS.redo(); form.formCheck(); form.formOpen(); @@ -102,10 +91,6 @@ public static void login() { public static void logout() { Drawer.setVisible(false); - Form current = FORMS.getCurrent(); - if (current instanceof FormBackupTable) { - ((FormBackupTable) current).formClose(); - } frame.getContentPane().removeAll(); Form login = getLogin(); login.formCheck(); diff --git a/src/main/java/backupmanager/gui/themes/PanelThemes.java b/src/main/java/backupmanager/gui/themes/PanelThemes.java index 6d300f18..58737fc6 100644 --- a/src/main/java/backupmanager/gui/themes/PanelThemes.java +++ b/src/main/java/backupmanager/gui/themes/PanelThemes.java @@ -41,7 +41,7 @@ public Component getListCellRendererComponent(JList<?> list, Object value, int i this.titleHeight = 0; String title = categories.get(index); - String name = ((ThemesInfo) value).name; + String name = ((ThemesInfo) value).name(); int sep = name.indexOf('/'); if (sep >= 0) { name = name.substring(sep + 1).trim(); @@ -58,12 +58,12 @@ public Component getListCellRendererComponent(JList<?> list, Object value, int i } private String buildToolTip(ThemesInfo th) { - if (th.resourceName == null) { - return th.name; + if (th.resourceName() == null) { + return th.name(); } - return "Name :" + th.name - + "\nLicense: " + th.license - + "\nSource Code: " + th.sourceCodeUrl; + return "Name :" + th.name() + + "\nLicense: " + th.license() + + "\nSource Code: " + th.sourceCodeUrl(); } }); themesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -87,8 +87,8 @@ public void updateThemesList(int option) { // add core themes categories.put(themes.size(), "Core Themes"); for (ThemesInfo th : themesManager.coreThemes) { - boolean show = (showLight && !th.dark) || (showDark && th.dark); - if (show && !th.name.contains("/")) { + boolean show = (showLight && !th.dark()) || (showDark && th.dark()); + if (show && !th.name().contains("/")) { themes.add(th); } } @@ -96,8 +96,8 @@ public void updateThemesList(int option) { // add uncategorized bundled themes categories.put(themes.size(), "IntelliJ Themes"); for (ThemesInfo th : themesManager.bundledThemes) { - boolean show = (showLight && !th.dark) || (showDark && th.dark); - if (show && !th.name.contains("/")) { + boolean show = (showLight && !th.dark()) || (showDark && th.dark()); + if (show && !th.name().contains("/")) { themes.add(th); } } @@ -105,12 +105,12 @@ public void updateThemesList(int option) { // add categorized bundled themes String lastCategory = null; for (ThemesInfo th : themesManager.bundledThemes) { - boolean show = (showLight && !th.dark) || (showDark && th.dark); - int sep = th.name.indexOf('/'); + boolean show = (showLight && !th.dark()) || (showDark && th.dark()); + int sep = th.name().indexOf('/'); if (!show || sep < 0) { continue; } - String category = th.name.substring(0, sep).trim(); + String category = th.name().substring(0, sep).trim(); if (!Objects.equals(lastCategory, category)) { lastCategory = category; categories.put(themes.size(), category); @@ -148,11 +148,11 @@ private void selectedCurrentLookAndFeel() { String lafClassName = lookAndFeel.getClass().getName(); for (int i = 0; i < themes.size(); i++) { ThemesInfo ti = themes.get(i); - if (theme == null && ti.lafClassName != null && lafClassName.equals(ti.lafClassName)) { + if (theme == null && ti.lafClassName() != null && lafClassName.equals(ti.lafClassName())) { themesList.setSelectedIndex(i); break; } - if (theme != null && ti.resourceName != null && theme.substring(AppPreferences.RESOURCE_PREFIX.length()).equals(ti.resourceName)) { + if (theme != null && ti.resourceName() != null && theme.substring(AppPreferences.RESOURCE_PREFIX.length()).equals(ti.resourceName())) { themesList.setSelectedIndex(i); break; } @@ -161,7 +161,7 @@ private void selectedCurrentLookAndFeel() { private void themesListValueChanged(ListSelectionEvent e) { ThemesInfo themesInfo = themesList.getSelectedValue(); - boolean bundledTheme = (themesInfo != null && themesInfo.resourceName != null); + boolean bundledTheme = (themesInfo != null && themesInfo.resourceName() != null); if (e.getValueIsAdjusting()) { return; } @@ -174,25 +174,25 @@ private void setThemes(ThemesInfo themesInfo) { } // change look and feel - if (themesInfo.lafClassName != null) { - if (themesInfo.lafClassName.equals(UIManager.getLookAndFeel().getClass().getName())) { + if (themesInfo.lafClassName() != null) { + if (themesInfo.lafClassName().equals(UIManager.getLookAndFeel().getClass().getName())) { return; } FlatAnimatedLafChange.showSnapshot(); try { - UIManager.setLookAndFeel(themesInfo.lafClassName); + UIManager.setLookAndFeel(themesInfo.lafClassName()); } catch (Exception e) { LoggingFacade.INSTANCE.logSevere(null, e); - showInformationDialog("Failed to create '" + themesInfo.lafClassName + "'.", e); + showInformationDialog("Failed to create '" + themesInfo.lafClassName() + "'.", e); } } else { String theme = UIManager.getLookAndFeelDefaults().getString(AppPreferences.THEME_UI_KEY); - if (theme != null && themesInfo.resourceName.equals(theme.substring(AppPreferences.RESOURCE_PREFIX.length()))) { + if (theme != null && themesInfo.resourceName().equals(theme.substring(AppPreferences.RESOURCE_PREFIX.length()))) { return; } FlatAnimatedLafChange.showSnapshot(); - IntelliJTheme.setup(getClass().getResourceAsStream(THEMES_PACKAGE + themesInfo.resourceName)); - AppPreferences.getState().put(AppPreferences.KEY_LAF_THEME, AppPreferences.RESOURCE_PREFIX + themesInfo.resourceName); + IntelliJTheme.setup(getClass().getResourceAsStream(THEMES_PACKAGE + themesInfo.resourceName())); + AppPreferences.getState().put(AppPreferences.KEY_LAF_THEME, AppPreferences.RESOURCE_PREFIX + themesInfo.resourceName()); } FlatLaf.updateUI(); FlatAnimatedLafChange.hideSnapshotWithAnimation(); diff --git a/src/main/java/backupmanager/gui/themes/ThemesInfo.java b/src/main/java/backupmanager/gui/themes/ThemesInfo.java index 915a220c..728b8049 100644 --- a/src/main/java/backupmanager/gui/themes/ThemesInfo.java +++ b/src/main/java/backupmanager/gui/themes/ThemesInfo.java @@ -1,25 +1,12 @@ package backupmanager.gui.themes; -public class ThemesInfo { - - final String name; - final String resourceName; - final boolean dark; - final String license; - final String licenseFile; - final String sourceCodeUrl; - final String sourceCodePath; - final String lafClassName; - - - public ThemesInfo(String name, String resourceName, boolean dark, String license, String licenseFile, String sourceCodeUrl, String sourceCodePath, String lafClassName) { - this.name = name; - this.resourceName = resourceName; - this.dark = dark; - this.license = license; - this.licenseFile = licenseFile; - this.sourceCodeUrl = sourceCodeUrl; - this.sourceCodePath = sourceCodePath; - this.lafClassName = lafClassName; - } -} +public record ThemesInfo ( + String name, + String resourceName, + boolean dark, + String license, + String licenseFile, + String sourceCodeUrl, + String sourceCodePath, + String lafClassName +) { } diff --git a/src/test/java/test/LoginServiceTest.java b/src/test/java/test/LoginServiceTest.java index 61566158..60cf9c54 100644 --- a/src/test/java/test/LoginServiceTest.java +++ b/src/test/java/test/LoginServiceTest.java @@ -8,7 +8,9 @@ import org.junit.jupiter.api.Test; import backupmanager.Entities.User; +import backupmanager.Managers.LanguageManager; import backupmanager.Services.LoginService; +import backupmanager.Utils.AppPreferences; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.TestDatabaseInitializer; @@ -18,6 +20,9 @@ public class LoginServiceTest { protected void setup() throws Exception { Database.init(DatabasePaths.getTestDatabasePath()); TestDatabaseInitializer.init(); + + AppPreferences.init(); + LanguageManager.loadPreferredLanguage(); } @AfterEach diff --git a/src/test/java/test/repositories/BackupRequestRepositoryTest.java b/src/test/java/test/repositories/BackupRequestRepositoryTest.java index 5dfaa363..d6ae4ec6 100644 --- a/src/test/java/test/repositories/BackupRequestRepositoryTest.java +++ b/src/test/java/test/repositories/BackupRequestRepositoryTest.java @@ -88,7 +88,7 @@ private void setupBackupRequestList() { BackupRequestRepository.insertBackupRequest(request); } - + requests = BackupRequestRepository.getRequestBackups(); } private void createBackupConfigurations() { From b766a6cd3f3ba994128761103b56b9cb9b801bdf Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Sun, 26 Apr 2026 12:11:42 +0200 Subject: [PATCH 14/17] Toast messages for actions feedback --- pom.xml | 4 +- .../backupmanager/Enums/Translations.java | 25 ++++++-- .../Exceptions/BackupDeletionException.java | 19 ++++++ .../backupmanager/Helpers/BackupHelper.java | 28 +++++---- src/main/java/backupmanager/MainApp.java | 18 ++++-- .../backupmanager/Services/BackupService.java | 26 -------- .../java/backupmanager/Utils/ToastUtils.java | 63 +++++++++++++++++++ .../BackupConfigurationRepository.java | 7 ++- .../Controllers/BackupEntryController.java | 10 ++- .../gui/Controllers/EntryUserController.java | 10 +-- .../gui/component/Subscription.java | 2 +- .../gui/forms/FormBackupTable.java | 20 +++++- .../backupmanager/gui/forms/FormLogin.java | 5 +- .../backupmanager/gui/forms/FormSetting.java | 11 +++- .../Controllers/BackupManagerController.java | 7 ++- .../gui/simple/BackupEntryDialog.java | 11 +++- .../backupmanager/gui/system/FormManager.java | 19 ++++++ src/main/resources/res/languages/deu.json | 20 ++++-- src/main/resources/res/languages/eng.json | 20 ++++-- src/main/resources/res/languages/esp.json | 20 ++++-- src/main/resources/res/languages/fra.json | 20 ++++-- src/main/resources/res/languages/ita.json | 20 ++++-- .../BackupConfigurationRepositoryTest.java | 3 +- 23 files changed, 295 insertions(+), 93 deletions(-) create mode 100644 src/main/java/backupmanager/Exceptions/BackupDeletionException.java create mode 100644 src/main/java/backupmanager/Utils/ToastUtils.java diff --git a/pom.xml b/pom.xml index 01985c02..6df80f17 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>io.github.dj-raven</groupId> <artifactId>backupmanager</artifactId> - <version>2.6.0</version> + <version>2.6.1</version> <packaging>jar</packaging> <properties> <maven.compiler.source>21</maven.compiler.source> @@ -23,7 +23,7 @@ <dependency> <groupId>io.github.dj-raven</groupId> <artifactId>modal-dialog</artifactId> - <version>2.6.0</version> + <version>2.6.1</version> </dependency> <dependency> diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index 638e0bc4..592d940a 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -37,7 +37,9 @@ public enum TCategory { ABOUT("About"), DASHBOARD("Dashboard"), SETTINGS("Settings"), - SEARCH_BAR("SearchBar"); + SEARCH_BAR("SearchBar"), + TOAST("Toast"), + ; private final String categoryName; private final Map<TKey, String> translations = new HashMap<>(); @@ -79,6 +81,7 @@ public enum TKey { CREATE_BUTTON(TCategory.GENERAL, "CreateButton", "Create"), EDIT_BUTTON(TCategory.GENERAL, "EditButton", "Edit"), DELETE_BUTTON(TCategory.GENERAL, "DeleteButton", "Delete"), + CONTACT_US(TCategory.GENERAL, "ContactUs", "Contact us"), QUICK_SEARCH(TCategory.GENERAL, "QuickSearch", "Quick Search..."), // Menu @@ -111,10 +114,8 @@ public enum TKey { GITHUB_PAGE(TCategory.MENU, "Github", "Github page"), PAYPAL(TCategory.MENU, "Paypal", "Paypal"), BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), - CONTACT_US(TCategory.MENU, "ContactUs", "Contact us"), SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), - // TabbedFrames BACKUP_ENTRY(TCategory.TABBED_FRAMES, "BackupEntry", "Backup Entry"), BACKUP_LIST(TCategory.TABBED_FRAMES, "BackupList", "Backup List"), @@ -209,8 +210,6 @@ public enum TKey { USER_NAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserNamePlaceholder", "Enter your name"), USER_SURNAME_PLACEHOLDER(TCategory.USER_DIALOG, "UserSurnamePlaceholder", "Enter your surname"), USER_EMAIL_PLACEHOLDER(TCategory.USER_DIALOG, "UserEmailPlaceholder", "Enter your email"), - ERROR_MESSAGE_FOR_MISSING_DATA(TCategory.USER_DIALOG, "ErrorMessageForMissingData", "Please fill in all the required fields."), - ERROR_MESSAGE_FOR_WRONG_EMAIL(TCategory.USER_DIALOG, "ErrorMessageForWrongEmail", "The provided email address is invalid. Please provide a correct one."), EMAIL_CONFIRMATION_SUBJECT(TCategory.USER_DIALOG, "EmailConfirmationSubject", "Thank you for choosing Backup Manager!"), EMAIL_CONFIRMATION_BODY(TCategory.USER_DIALOG, "EmailConfirmationBody", "Hi [UserName],\n\nThank you for downloading and registering **Backup Manager**, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at **[SupportEmail]**.\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team"), @@ -307,7 +306,6 @@ public enum TKey { SUBSCRIPTION_STATUS(TCategory.SUBSCRIPTION, "Status", "Subscription status"), SUBSCRIPTION_VALID_FROM(TCategory.SUBSCRIPTION, "ValidFrom", "Valid from"), SUBSCRIPTION_VALID_TO(TCategory.SUBSCRIPTION, "ValidTo", "Valid to"), - SUBSCRIPTION_CONTACT_US(TCategory.SUBSCRIPTION, "ContactUs", "Contact us"), SUBSCRIPTION_TO_EXTEND(TCategory.SUBSCRIPTION, "ToExtend", "to extend the subscription period."), // DASHBOARD @@ -364,6 +362,21 @@ public enum TKey { SEARCH_NO_RESULT(TCategory.SEARCH_BAR, "SearchNoResult", "No result for"), SEARCH_FAVORITE(TCategory.SEARCH_BAR, "SearchFavorite", "Favorite"), SEARCH_RECENT(TCategory.SEARCH_BAR, "SearchRecent", "Recents"), + + // TOAST MESSAGES + TOAST_BACKUP_EDITED(TCategory.TOAST, "BackupEditedOk", "Backup updated successfully"), + TOAST_BACKUP_CREATED(TCategory.TOAST, "BackupCreatedOk", "Backup created successfully"), + TOAST_BACKUP_DELETED(TCategory.TOAST, "BackupDeletedOk", "Backup deleted successfully"), + TOAST_BACKUP_REPLACED(TCategory.TOAST, "BackupReplacedOk", "Existing backup replaced successfully"), + TOAST_BACKUP_DELETED_ERROR(TCategory.TOAST, "BackupDeletedError", "Failed to delete the backup"), + TOAST_BACKUP_REPLACED_ERROR(TCategory.TOAST, "BackupReplacedError", "Failed to replace the backup"), + TOAST_INVALID_TIME(TCategory.TOAST, "InvalidTime", "Invalid time interval"), + TOAST_SUBSCRIPTION_EXPIRING(TCategory.TOAST, "SubscriptionExpiring", "Your Backup Manager subscription will expire soon. Please contact us to renew it"), + TOAST_SUBSCRIPTION_EXPIRED(TCategory.TOAST, "SubscriptionExpired", "Your Backup Manager subscription has expired. Please contact us to renew it"), + TOAST_LANGUAGE_CHANGE(TCategory.TOAST, "LanguageChange", "Some changes will take effect after restarting the application"), + TOAST_MISSING_DATA_LOGIN_ERROR(TCategory.TOAST, "MissingDataLoginError", "Please fill in all the required fields"), + TOAST_WRONG_EMAIL_LOGIN_ERROR(TCategory.TOAST, "WrongEmailLoginError", "The provided email address is invalid. Please provide a correct one"), + TOAST_LOGIN(TCategory.TOAST, "LoginOk", "Welcome! You have successfully signed in"), ; diff --git a/src/main/java/backupmanager/Exceptions/BackupDeletionException.java b/src/main/java/backupmanager/Exceptions/BackupDeletionException.java new file mode 100644 index 00000000..7e9b02b5 --- /dev/null +++ b/src/main/java/backupmanager/Exceptions/BackupDeletionException.java @@ -0,0 +1,19 @@ +package backupmanager.Exceptions; + +public class BackupDeletionException extends Exception { + public BackupDeletionException() { + super(); + } + + public BackupDeletionException(String message) { + super(message); + } + + public BackupDeletionException(String message, Throwable cause) { + super(message, cause); + } + + public BackupDeletionException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index 23adb517..cc0177b2 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -1,5 +1,6 @@ package backupmanager.Helpers; +import java.sql.SQLException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -15,6 +16,7 @@ import backupmanager.Enums.BackupStatus; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.simple.TimePickerDialog; @@ -30,24 +32,26 @@ public static void newBackup(ConfigurationBackup backup) { BackupConfigurationRepository.insertBackup(backup); } - public static void deleteBackup(String backupName) { + public static boolean deleteBackupWithConfirmition(ConfigurationBackup backup) throws BackupDeletionException { + logger.info("Event --> deleting backup request with confirmation for backup: " + backup.getName()); + + int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (response == JOptionPane.YES_OPTION) { + return BackupHelper.deleteBackup(backup); + } + return false; + } + + public static boolean deleteBackup(String backupName) throws BackupDeletionException { logger.info("Event --> deleting backup"); ConfigurationBackup backup = ConfigurationBackup.getBackupByName(backupName); - deleteBackup(backup); + return deleteBackup(backup); } - public static void deleteBackup(ConfigurationBackup backup) { + public static boolean deleteBackup(ConfigurationBackup backup) throws BackupDeletionException { logger.info("Event --> deleting backup" + backup.getName()); BackupConfigurationRepository.deleteBackup(backup.getId()); - } - - public static void deleteBackupWithConfirmition(ConfigurationBackup backup) { - logger.info("Event --> deleting backup request with confirmation for backup: " + backup.getName()); - - int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); - if (response == JOptionPane.YES_OPTION) { - BackupHelper.deleteBackup(backup); - } + return true; } public static void updateBackup(ConfigurationBackup updatedBackup) { diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index 4d9415db..d05f1dc0 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -13,6 +13,7 @@ import com.formdev.flatlaf.fonts.roboto.FlatRobotoFont; import com.formdev.flatlaf.util.FontUtils; +import backupmanager.Entities.Configurations; import backupmanager.Enums.ConfigKey; import backupmanager.Managers.ExceptionManager; import backupmanager.Managers.LanguageManager; @@ -28,12 +29,7 @@ public class MainApp { private static final Logger logger = LoggerFactory.getLogger(MainApp.class); public static void main(String[] args) { - ConfigKey.loadFromJson(CONFIG); - - databaseInitialization(); - - AppPreferences.init(); - LanguageManager.loadPreferredLanguage(); + dataInit(); boolean isBackgroundMode = isBackgroundMode(args); @@ -48,6 +44,16 @@ else if (!isBackgroundMode) { } } + private static void dataInit() { + ConfigKey.loadFromJson(CONFIG); + + databaseInitialization(); + + AppPreferences.init(); + LanguageManager.loadPreferredLanguage(); + Configurations.loadAllConfigurations(); + } + private static void databaseInitialization() { try { Database.init(DatabasePaths.getProductionDatabasePath()); diff --git a/src/main/java/backupmanager/Services/BackupService.java b/src/main/java/backupmanager/Services/BackupService.java index b6e34746..4419ffb1 100644 --- a/src/main/java/backupmanager/Services/BackupService.java +++ b/src/main/java/backupmanager/Services/BackupService.java @@ -14,36 +14,10 @@ public List<ConfigurationBackup> getAllBackups() { return BackupConfigurationRepository.getBackupList(); } - public boolean isRunning(String name) { - return RunningBackupService.getRunningBackupByName(name).isPresent(); - } - - public void deleteBackup(int id) { - BackupConfigurationRepository.deleteBackup(id); - } - - public void deleteBackups(List<String> names) { - names.forEach(name -> { - ConfigurationBackup backup = getBackupByName(name); - if (backup != null) { - BackupConfigurationRepository.deleteBackup(backup.getId()); - } - }); - } - - public String getBackupDetails(String name) { - ConfigurationBackup backup = getBackupByName(name); - return buildDetails(backup); - } - public void updateBackup(ConfigurationBackup backup) { BackupConfigurationRepository.updateBackup(backup); } - public ConfigurationBackup getBackupByName(String name) { - return BackupConfigurationRepository.getBackupByName(name); - } - public String buildDetails(ConfigurationBackup backup) { String backupNameStr = Translations.get(TKey.BACKUP_NAME_DETAIL); String initialPathStr = Translations.get(TKey.INITIAL_PATH_DETAIL); diff --git a/src/main/java/backupmanager/Utils/ToastUtils.java b/src/main/java/backupmanager/Utils/ToastUtils.java new file mode 100644 index 00000000..621395c4 --- /dev/null +++ b/src/main/java/backupmanager/Utils/ToastUtils.java @@ -0,0 +1,63 @@ +package backupmanager.Utils; + +import java.awt.Component; +import raven.modal.Toast; +import raven.modal.option.Location; +import raven.modal.toast.option.ToastBorderStyle; +import raven.modal.toast.option.ToastLocation; +import raven.modal.toast.option.ToastOption; +import raven.modal.toast.option.ToastStyle; + +public class ToastUtils { + public static void showDefault(Component owner, String text) { + showToast(owner, Toast.Type.DEFAULT, text); + } + + public static void showInfo(Component owner, String text) { + showToast(owner, Toast.Type.INFO, text); + } + + public static void showSuccess(Component owner, String text) { + showToast(owner, Toast.Type.SUCCESS, text); + } + + public static void showWarning(Component owner, String text) { + showToast(owner, Toast.Type.WARNING, text); + } + + public static void showError(Component owner, String text) { + showToast(owner, Toast.Type.ERROR, text); + } + + private static void showToast(Component owner, Toast.Type type, String text) { + Toast.show(owner, type, text, getSelectedOption()); + } + + private static ToastOption getSelectedOption() { + ToastOption option = Toast.createOption(); + Location h = Location.CENTER; + Location v = Location.TOP; + ToastStyle.BackgroundType backgroundType = ToastStyle.BackgroundType.DEFAULT; + ToastBorderStyle.BorderType borderType = ToastBorderStyle.BorderType.LEADING_LINE; + option.setAnimationEnabled(true) + .setPauseDelayOnHover(true) + .setAutoClose(true) + .setCloseOnClick(true) + .setHeavyWeight(false); + + option.getLayoutOption() + .setLocation(ToastLocation.from(h, v)) + .setRelativeToOwner(false); + option.getStyle().setBackgroundType(backgroundType) + .setShowIcon(true) + .setShowLabel(false) + .setIconSeparateLine(false) + .setShowCloseButton(true) + .setPaintTextColor(false) + .setPromiseLabel("Saving...") + .getBorderStyle() + .setBorderType(borderType) + ; + return option; + } +} diff --git a/src/main/java/backupmanager/database/Repositories/BackupConfigurationRepository.java b/src/main/java/backupmanager/database/Repositories/BackupConfigurationRepository.java index c4863267..486e7218 100644 --- a/src/main/java/backupmanager/database/Repositories/BackupConfigurationRepository.java +++ b/src/main/java/backupmanager/database/Repositories/BackupConfigurationRepository.java @@ -16,6 +16,7 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.Helpers.SqlHelper; import backupmanager.Managers.ExceptionManager; import backupmanager.database.Database; @@ -91,7 +92,7 @@ public static void updateBackup(ConfigurationBackup backup) { } } - public static void deleteBackup(int backupId) { + public static void deleteBackup(int backupId) throws BackupDeletionException { String sql = "DELETE FROM BackupConfigurations WHERE BackupId = ?"; try (Connection conn = Database.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -102,8 +103,10 @@ public static void deleteBackup(int backupId) { logger.info("Backup deleted succesfully"); } catch (SQLException e) { - logger.error("Backup configuration deleting error: " + e.getMessage()); + String error = "Backup configuration deleting error: " + e.getMessage(); + logger.error(error); ExceptionManager.openExceptionMessage(e.getMessage(), Arrays.toString(e.getStackTrace())); + throw new BackupDeletionException(error, e); } } diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index b502c0e2..aaeec2af 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -1,5 +1,6 @@ package backupmanager.gui.Controllers; +import java.awt.Component; import java.io.File; import java.time.LocalDateTime; @@ -19,8 +20,10 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; +import backupmanager.Utils.ToastUtils; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; @@ -77,7 +80,7 @@ public TimeInterval handleTimePickerAction(TimePickerDialog picker, String targe return time; } - public boolean canDisposeAfterOk(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep, boolean create) { + public boolean canDisposeAfterOk(Component owner, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep, boolean create) throws BackupDeletionException { if (name.isBlank() || destinationPath.isBlank() || initialPath.isBlank()) return false; @@ -88,9 +91,10 @@ public boolean canDisposeAfterOk(String name, String initialPath, String destina int response = JOptionPane.showConfirmDialog(null, Translations.get(TKey.DUPLICATED_BACKUP_NAME_MESSAGE), Translations.get(TKey.CONFIRMATION_REQUIRED_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (response == JOptionPane.YES_OPTION) { BackupHelper.deleteBackup(currentBackup.getName()); - } else { - return false; + ToastUtils.showInfo(owner, Translations.get(TKey.TOAST_BACKUP_EDITED)); } + else + return false; } BackupHelper.newBackup(currentBackup); } else { diff --git a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java index 4c5a814a..535f3b06 100644 --- a/src/main/java/backupmanager/gui/Controllers/EntryUserController.java +++ b/src/main/java/backupmanager/gui/Controllers/EntryUserController.java @@ -1,20 +1,22 @@ package backupmanager.gui.Controllers; -import javax.swing.JOptionPane; +import javax.swing.JComponent; import backupmanager.Email.EmailValidator; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.ToastUtils; public class EntryUserController { - public boolean isInputOkAndShowErrorIfNecessary(javax.swing.JDialog dialog, String name, String surname, String email) { + public boolean isInputOkAndShowErrorIfNecessary(JComponent component, String name, String surname, String email) { if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) { - JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_MESSAGE_FOR_MISSING_DATA), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(component, Translations.get(TKey.TOAST_MISSING_DATA_LOGIN_ERROR)); return false; } else if (!EmailValidator.isValidEmail(email)) { - JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_MESSAGE_FOR_WRONG_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(component, Translations.get(TKey.TOAST_WRONG_EMAIL_LOGIN_ERROR)); return false; } + ToastUtils.showSuccess(component, Translations.get(TKey.TOAST_LOGIN)); return true; } } diff --git a/src/main/java/backupmanager/gui/component/Subscription.java b/src/main/java/backupmanager/gui/component/Subscription.java index c2b9b667..05229470 100644 --- a/src/main/java/backupmanager/gui/component/Subscription.java +++ b/src/main/java/backupmanager/gui/component/Subscription.java @@ -102,7 +102,7 @@ private String buildHtml(SubscriptionStatus status, String statusText, String va validTo, ConfigKey.EMAIL.getValue(), subject, - Translations.get(TKey.SUBSCRIPTION_CONTACT_US), + Translations.get(TKey.CONTACT_US), Translations.get(TKey.SUBSCRIPTION_TO_EXTEND) ); } diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index fe13fb3b..27488b43 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -32,11 +32,13 @@ import backupmanager.Entities.TimeInterval; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.Helpers.BackupHelper; import static backupmanager.Helpers.BackupHelper.formatter; import backupmanager.Services.BackupObserver; import backupmanager.Services.BackupService; import backupmanager.Utils.SystemForm; +import backupmanager.Utils.ToastUtils; import backupmanager.Utils.table.TableHeaderAlignment; import backupmanager.gui.Table.BackupTable; import backupmanager.gui.Table.BackupTableDataService; @@ -371,7 +373,15 @@ private void handleAction(String action, JMenuItem interruptBackupPopupItem, JMe ConfigurationBackup backup = getBackupFromTableRow(selectedRow); switch (action) { case "EDIT" -> showEditModal(backup); - case "DELETE" -> BackupHelper.deleteBackup(backup); + case "DELETE" -> { + try { + boolean deleted = BackupHelper.deleteBackupWithConfirmition(backup); + if (deleted) + ToastUtils.showSuccess(this, Translations.get(TKey.TOAST_BACKUP_DELETED)); + } catch (BackupDeletionException ex) { + ToastUtils.showError(this, Translations.get(TKey.TOAST_BACKUP_DELETED_ERROR)); + } + } case "DUPLICATE" -> BackupPopupController.popupItemDuplicateBackup(backup); case "RENAME" -> BackupPopupController.popupItemRenameBackup(backups, backup); case "OPEN_TARGET" -> BackupPopupController.popupItemOpenInitialPath(backup); @@ -491,7 +501,13 @@ private void showDeleteConfirmation() { return; ConfigurationBackup backup = getBackupFromTableRow(selectedRow); - BackupHelper.deleteBackupWithConfirmition(backup); + try { + boolean deleted = BackupHelper.deleteBackupWithConfirmition(backup); + if (deleted) + ToastUtils.showSuccess(this, Translations.get(TKey.TOAST_BACKUP_DELETED)); + } catch (BackupDeletionException e) { + ToastUtils.showError(this, Translations.get(TKey.TOAST_BACKUP_DELETED_ERROR)); + } formRefresh(); } diff --git a/src/main/java/backupmanager/gui/forms/FormLogin.java b/src/main/java/backupmanager/gui/forms/FormLogin.java index 71d56935..0395c9b7 100644 --- a/src/main/java/backupmanager/gui/forms/FormLogin.java +++ b/src/main/java/backupmanager/gui/forms/FormLogin.java @@ -12,6 +12,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.LimitDocument; import backupmanager.Services.LoginService; +import backupmanager.gui.Controllers.EntryUserController; import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; import net.miginfocom.swing.MigLayout; @@ -19,6 +20,7 @@ public class FormLogin extends CustomForm { private final LoginService loginService = new LoginService(); + private final EntryUserController userController = new EntryUserController(); public FormLogin() { build(); @@ -39,7 +41,6 @@ protected void init() { @Override protected void loadData() { - JPanel panelLogin = new JPanel(new MigLayout()); JPanel loginContent = new JPanel( new MigLayout("fillx,wrap,insets 35 35 25 35", "[fill,300]") @@ -102,7 +103,7 @@ protected void loadData() { String surname = txtSurname.getText().trim(); String email = txtEmail.getText().trim(); - if (name.isEmpty() || surname.isEmpty() || email.isEmpty()) + if (!userController.isInputOkAndShowErrorIfNecessary(this, name, surname, email)) return; User user = new User(name, surname, email); diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index a187ddcb..a8428ce2 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -41,6 +41,7 @@ import backupmanager.gui.themes.PanelThemes; import backupmanager.Utils.AppPreferences; import backupmanager.Utils.SystemForm; +import backupmanager.Utils.ToastUtils; import net.miginfocom.swing.MigLayout; import raven.color.ColorPicker; import raven.modal.Drawer; @@ -59,6 +60,7 @@ public class FormSetting extends CustomForm { private static final Logger logger = LoggerFactory.getLogger(FormSetting.class); + private boolean languageInitializing = true; public FormSetting() { build(); @@ -217,13 +219,18 @@ private Component createLanguageOption() { initComboItem(languageCombo); languageCombo.addActionListener(e -> { - Object selected = languageCombo.getSelectedItem(); - String languageName = selected.toString(); + if (languageInitializing) + return; + + String languageName = languageCombo.getSelectedItem().toString(); + LanguageManager.setLanguage(languageName); + ToastUtils.showDefault(this, Translations.get(TKey.TOAST_LANGUAGE_CHANGE)); }); languageCombo.setSelectedItem(LanguageManager.getLanguage().getLanguageName()); panel.add(languageCombo); + languageInitializing = false; return panel; } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index 51949582..d62fd58a 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -10,6 +10,7 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Services.BackupService; +import backupmanager.Utils.ToastUtils; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.forms.CustomForm; import backupmanager.gui.simple.BackupEntryDialog; @@ -79,6 +80,8 @@ public void showCreateModal(CustomForm form) { ConfigurationBackup backup = dialog.getResult(); backupService.updateBackup(backup); + ToastUtils.showSuccess(form, Translations.get(TKey.TOAST_BACKUP_CREATED)); + form.formRefresh(); controller.close(); } @@ -104,9 +107,7 @@ public void showEditModal(CustomForm form, ConfigurationBackup backup) { Translations.get(TKey.PAGE_SUBTITLE_EDIT), SimpleModalBorder.OK_CANCEL_OPTION, (controller, action) -> { - if (action == SimpleModalBorder.OK_OPTION) { - if (!dialog.canDispose()) { return; } @@ -114,6 +115,8 @@ public void showEditModal(CustomForm form, ConfigurationBackup backup) { ConfigurationBackup editedBackup = dialog.getResult(); backupService.updateBackup(editedBackup); + ToastUtils.showSuccess(form, Translations.get(TKey.TOAST_BACKUP_EDITED)); + form.formRefresh(); controller.close(); } diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index b00d8c06..2f3e6084 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -24,8 +24,10 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupAlreadyRunningException; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.Exceptions.InvalidTimeInterval; import backupmanager.Helpers.BackupHelper; +import backupmanager.Utils.ToastUtils; import backupmanager.gui.Controllers.BackupEntryController; import backupmanager.gui.Table.BackupTableDataService; import net.miginfocom.swing.MigLayout; @@ -164,8 +166,8 @@ private void openTimeInterval() { TimeInterval time = entryController.handleTimePickerAction(timePicker, txtTargetPath.getText(), txtDestinationPath.getText()); timeIntervalBtn.setToolTipText(time.toString()); openBackupActivationMessage(time); - } catch (InvalidTimeInterval e) { + ToastUtils.showError(this, Translations.get(TKey.TOAST_INVALID_TIME)); // no actions } @@ -252,7 +254,12 @@ private void enableTimePickerButton(ConfigurationBackup backup) { } public boolean canDispose() { - return entryController.canDisposeAfterOk(txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), textAreaNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue(), create); + try { + return entryController.canDisposeAfterOk(this, txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), textAreaNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue(), create); + } catch (BackupDeletionException e) { + ToastUtils.showError(this, Translations.get(TKey.TOAST_BACKUP_REPLACED_ERROR)); + return false; + } } private void disableAutoBackup(ConfigurationBackup backup) { diff --git a/src/main/java/backupmanager/gui/system/FormManager.java b/src/main/java/backupmanager/gui/system/FormManager.java index f7ab595a..129985a6 100644 --- a/src/main/java/backupmanager/gui/system/FormManager.java +++ b/src/main/java/backupmanager/gui/system/FormManager.java @@ -6,8 +6,12 @@ import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.util.ColorFunctions; +import backupmanager.Enums.SubscriptionStatus; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Helpers.SubscriptionHelper; +import backupmanager.Helpers.SubscriptionNotifier; +import backupmanager.Utils.ToastUtils; import backupmanager.Utils.UndoRedo; import backupmanager.gui.component.About; import backupmanager.gui.component.Subscription; @@ -85,6 +89,7 @@ public static void login() { frame.getContentPane().add(getMainForm()); Drawer.setSelectedItemClass(FormBackupTable.class); + showSubscriptionAlertIfNeeded(); frame.repaint(); frame.revalidate(); } @@ -129,4 +134,18 @@ public static void showSubscription() { ModalDialog.createOption().setAnimationEnabled(false) ); } + + private static void showSubscriptionAlertIfNeeded() { + SubscriptionStatus status = SubscriptionHelper.getSubscriptionStatus(); + + switch (status) { + case SubscriptionStatus.EXPIRATION -> { + ToastUtils.showWarning(frame, Translations.get(TKey.TOAST_SUBSCRIPTION_EXPIRING)); + } + case SubscriptionStatus.EXPIRED -> { + ToastUtils.showError(frame, Translations.get(TKey.TOAST_SUBSCRIPTION_EXPIRED)); + } + case ACTIVE, NONE -> { } + } + } } diff --git a/src/main/resources/res/languages/deu.json b/src/main/resources/res/languages/deu.json index 5c5fca08..1c3ee4d7 100644 --- a/src/main/resources/res/languages/deu.json +++ b/src/main/resources/res/languages/deu.json @@ -13,6 +13,7 @@ "EditButton": "Bearbeiten", "DeleteButton": "Löschen", "QuickSearch": "Schnellsuche", + "ContactUs": "Kontaktieren Sie uns", "To": "Nach" }, "TrayIcon": { @@ -110,7 +111,6 @@ "Status": "Abonnementstatus", "ValidFrom": "Gültig ab", "ValidTo": "Gültig bis", - "ContactUs": "Kontaktieren Sie uns", "ToExtend": "um den Abonnementzeitraum zu verlängern." }, "ProgressBackupFrame": { @@ -148,7 +148,6 @@ "Github": "GitHub-Seite", "Paypal": "PayPal", "BuyMeACoffe": "Spendiere mir einen Kaffee", - "ContactUs": "Kontaktieren Sie uns", "Subscription": "Abonnement" }, "BackupList": { @@ -199,11 +198,9 @@ "BackupList": "Backup-Liste" }, "UserDialog": { - "ErrorMessageForMissingData": "Bitte füllen Sie alle erforderlichen Felder aus.", "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team", "EmailConfirmationSubject": "Vielen Dank, dass Sie sich für Backup Manager entschieden haben!", "Surname": "Nachname", - "ErrorMessageForWrongEmail": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte Adresse an.", "Name": "Vorname", "Email": "E-Mail", "UserTitle": "Geben Sie Ihre Daten ein", @@ -296,5 +293,20 @@ "SearchNoResult": "Kein Ergebnis für", "SearchFavorite": "Favorit", "SearchRecent": "Letzte" + }, + "Toast": { + "BackupEditedOk": "Backup erfolgreich aktualisiert", + "BackupCreatedOk": "Backup erfolgreich erstellt", + "BackupDeletedOk": "Backup erfolgreich gelöscht", + "BackupReplacedOk": "Vorhandenes Backup erfolgreich ersetzt", + "BackupDeletedError": "Backup konnte nicht gelöscht werden", + "BackupReplacedError": "Backup konnte nicht ersetzt werden", + "InvalidTime": "Ungültiges Zeitintervall", + "SubscriptionExpiring": "Ihr Backup Manager-Abonnement läuft bald ab. Bitte kontaktieren Sie uns zur Verlängerung", + "SubscriptionExpired": "Ihr Backup Manager-Abonnement ist abgelaufen. Bitte kontaktieren Sie uns zur Verlängerung", + "LanguageChange": "Einige Änderungen werden nach einem Neustart der Anwendung wirksam", + "MissingDataLoginError": "Bitte füllen Sie alle erforderlichen Felder aus", + "WrongEmailLoginError": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte ein", + "LoginOk": "Willkommen! Sie haben sich erfolgreich angemeldet" } } diff --git a/src/main/resources/res/languages/eng.json b/src/main/resources/res/languages/eng.json index b6026ebb..36317904 100644 --- a/src/main/resources/res/languages/eng.json +++ b/src/main/resources/res/languages/eng.json @@ -13,6 +13,7 @@ "EditButton": "Edit", "DeleteButton": "Delete", "QuickSearch": "Quick search", + "ContactUs": "Contact us", "To": "A" }, "TrayIcon": { @@ -110,7 +111,6 @@ "Status": "Subscription status", "ValidFrom": "Valid from", "ValidTo": "Valid to", - "ContactUs": "Contact us", "ToExtend": "to extend the subscription period." }, "ProgressBackupFrame": { @@ -148,7 +148,6 @@ "Github": "Github page", "Paypal": "Paypal", "BuyMeACoffe": "Buy me a coffe", - "ContactUs": "Contact us", "Subscription": "Subscription" }, "BackupList": { @@ -199,11 +198,9 @@ "BackupList": "BackupList" }, "UserDialog": { - "ErrorMessageForMissingData": "Please fill in all the required fields.", "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team", "EmailConfirmationSubject": "Thank you for choosing Backup Manager!", "Surname": "Surname", - "ErrorMessageForWrongEmail": "The provided email address is invalid. Please provide a correct one.", "Name": "Name", "Email": "Email", "UserTitle": "Insert your data", @@ -296,5 +293,20 @@ "SearchNoResult": "No result for", "SearchFavorite": "Favorite", "SearchRecent": "Recents" + }, + "Toast": { + "BackupEditedOk": "Backup updated successfully", + "BackupCreatedOk": "Backup created successfully", + "BackupDeletedOk": "Backup deleted successfully", + "BackupReplacedOk": "Existing backup replaced successfully", + "BackupDeletedError": "Failed to delete the backup", + "BackupReplacedError": "Failed to replace the backup", + "InvalidTime": "Invalid time interval", + "SubscriptionExpiring": "Your Backup Manager subscription will expire soon. Please contact us to renew it", + "SubscriptionExpired": "Your Backup Manager subscription has expired. Please contact us to renew it", + "LanguageChange": "Some changes will take effect after restarting the application", + "MissingDataLoginError": "Please fill in all the required fields", + "WrongEmailLoginError": "The provided email address is invalid. Please provide a correct one", + "LoginOk": "Welcome! You have successfully signed in" } } diff --git a/src/main/resources/res/languages/esp.json b/src/main/resources/res/languages/esp.json index d36dc476..93f8c21f 100644 --- a/src/main/resources/res/languages/esp.json +++ b/src/main/resources/res/languages/esp.json @@ -13,6 +13,7 @@ "EditButton": "Editar", "DeleteButton": "Eliminar", "QuickSearch": "Búsqueda rápida", + "ContactUs": "Contáctanos", "To": "A" }, "TrayIcon": { @@ -110,7 +111,6 @@ "Status": "Estado de la suscripción", "ValidFrom": "Válido desde", "ValidTo": "Válido hasta", - "ContactUs": "Contáctanos", "ToExtend": "para extender el período de suscripción." }, "ProgressBackupFrame": { @@ -148,7 +148,6 @@ "Github": "Página de GitHub", "Paypal": "PayPal", "BuyMeACoffe": "Invítame a un café", - "ContactUs": "Contáctanos", "Subscription": "Suscripción" }, "BackupList": { @@ -199,11 +198,9 @@ "BackupList": "Lista de Copias de Seguridad" }, "UserDialog": { - "ErrorMessageForMissingData": "Por favor, completa todos los campos requeridos.", "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager", "EmailConfirmationSubject": "¡Gracias por elegir Backup Manager!", "Surname": "Apellido", - "ErrorMessageForWrongEmail": "La dirección de correo electrónico proporcionada no es válida. Por favor, proporciona una correcta.", "Name": "Nombre", "Email": "Correo electrónico", "UserTitle": "Inserta tus datos", @@ -296,5 +293,20 @@ "SearchNoResult": "Sin resultados para", "SearchFavorite": "Favorito", "SearchRecent": "Recientes" + }, + "Toast": { + "BackupEditedOk": "Copia de seguridad actualizada correctamente", + "BackupCreatedOk": "Copia de seguridad creada correctamente", + "BackupDeletedOk": "Copia de seguridad eliminada correctamente", + "BackupReplacedOk": "Copia de seguridad existente reemplazada correctamente", + "BackupDeletedError": "No se pudo eliminar la copia de seguridad", + "BackupReplacedError": "No se pudo reemplazar la copia de seguridad", + "InvalidTime": "Intervalo de tiempo no válido", + "SubscriptionExpiring": "Tu suscripción a Backup Manager expirará pronto. Contáctanos para renovarla", + "SubscriptionExpired": "Tu suscripción a Backup Manager ha expirado. Contáctanos para renovarla", + "LanguageChange": "Algunos cambios tendrán efecto después de reiniciar la aplicación", + "MissingDataLoginError": "Por favor completa todos los campos obligatorios", + "WrongEmailLoginError": "La dirección de correo electrónico proporcionada no es válida. Introduce una correcta", + "LoginOk": "¡Bienvenido! Has iniciado sesión correctamente" } } diff --git a/src/main/resources/res/languages/fra.json b/src/main/resources/res/languages/fra.json index 1b1cc832..6dc1443f 100644 --- a/src/main/resources/res/languages/fra.json +++ b/src/main/resources/res/languages/fra.json @@ -13,6 +13,7 @@ "EditButton": "Modifier", "DeleteButton": "Supprimer", "QuickSearch": "Recherche rapide", + "ContactUs": "Nous contacter", "To": "À" }, "TrayIcon": { @@ -110,7 +111,6 @@ "Status": "Statut de l'abonnement", "ValidFrom": "Valide à partir du", "ValidTo": "Valide jusqu'au", - "ContactUs": "Nous contacter", "ToExtend": "pour prolonger la période d'abonnement." }, "ProgressBackupFrame": { @@ -148,7 +148,6 @@ "Github": "Page GitHub", "Paypal": "PayPal", "BuyMeACoffe": "Offrez-moi un café", - "ContactUs": "Nous contacter", "Subscription": "Abonnement" }, "BackupList": { @@ -199,11 +198,9 @@ "BackupList": "Liste de Sauvegardes" }, "UserDialog": { - "ErrorMessageForMissingData": "Veuillez remplir tous les champs requis.", "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager", "EmailConfirmationSubject": "Merci d'avoir choisi Backup Manager !", "Surname": "Nom de famille", - "ErrorMessageForWrongEmail": "L'adresse e-mail fournie n'est pas valide. Veuillez en fournir une correcte.", "Name": "Prénom", "Email": "E-Mail", "UserTitle": "Entrez vos informations", @@ -296,5 +293,20 @@ "SearchNoResult": "Aucun résultat pour", "SearchFavorite": "Favori", "SearchRecent": "Récents" + }, + "Toast": { + "BackupEditedOk": "Sauvegarde mise à jour avec succès", + "BackupCreatedOk": "Sauvegarde créée avec succès", + "BackupDeletedOk": "Sauvegarde supprimée avec succès", + "BackupReplacedOk": "Sauvegarde existante remplacée avec succès", + "BackupDeletedError": "Échec de la suppression de la sauvegarde", + "BackupReplacedError": "Échec du remplacement de la sauvegarde", + "InvalidTime": "Intervalle de temps invalide", + "SubscriptionExpiring": "Votre abonnement Backup Manager expirera bientôt. Veuillez nous contacter pour le renouveler", + "SubscriptionExpired": "Votre abonnement Backup Manager a expiré. Veuillez nous contacter pour le renouveler", + "LanguageChange": "Certaines modifications prendront effet après le redémarrage de l'application", + "MissingDataLoginError": "Veuillez remplir tous les champs obligatoires", + "WrongEmailLoginError": "L'adresse e-mail fournie est invalide. Veuillez en saisir une correcte", + "LoginOk": "Bienvenue ! Connexion réussie" } } diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 1859c716..04fe9f8e 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -13,6 +13,7 @@ "EditButton": "Modifica", "DeleteButton": "Elimina", "QuickSearch": "Ricerca rapida", + "ContactUs": "Contattaci", "To": "A" }, "TrayIcon": { @@ -110,7 +111,6 @@ "Status": "Stato abbonamento", "ValidFrom": "Valido dal", "ValidTo": "Valido fino al", - "ContactUs": "Contattaci", "ToExtend": "per estendere il periodo di abbonamento." }, "ProgressBackupFrame": { @@ -148,7 +148,6 @@ "Github": "Pagina GitHub", "Paypal": "PayPal", "BuyMeACoffe": "Offrimi un caffè", - "ContactUs": "Contattaci", "Subscription": "Abbonamento" }, "BackupList": { @@ -199,11 +198,9 @@ "BackupList": "ListaBackup" }, "UserDialog": { - "ErrorMessageForMissingData": "Per favore, compila tutti i campi richiesti.", "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager", "EmailConfirmationSubject": "Grazie per aver scelto Backup Manager!", "Surname": "Cognome", - "ErrorMessageForWrongEmail": "L'indirizzo email fornito non è valido. Inseriscine uno corretto.", "Name": "Nome", "Email": "Email", "UserTitle": "Inserisci i tuoi dati", @@ -296,5 +293,20 @@ "SearchNoResult": "Nessun risultato per", "SearchFavorite": "Preferiti", "SearchRecent": "Recenti" + }, + "Toast": { + "BackupEditedOk": "Backup aggiornato con successo", + "BackupCreatedOk": "Backup creato con successo", + "BackupDeletedOk": "Backup eliminato con successo", + "BackupReplacedOk": "Backup esistente sostituito con successo", + "BackupDeletedError": "Impossibile eliminare il backup", + "BackupReplacedError": "Impossibile sostituire il backup", + "InvalidTime": "Intervallo di tempo non valido", + "SubscriptionExpiring": "Il tuo abbonamento Backup Manager scadrà presto. Contattaci per rinnovarlo", + "SubscriptionExpired": "Il tuo abbonamento Backup Manager è scaduto. Contattaci per rinnovarlo", + "LanguageChange": "Alcune modifiche avranno effetto dopo il riavvio dell'applicazione", + "MissingDataLoginError": "Compila tutti i campi obbligatori", + "WrongEmailLoginError": "L'indirizzo email fornito non è valido. Inseriscine uno corretto", + "LoginOk": "Benvenuto! Accesso effettuato con successo" } } diff --git a/src/test/java/test/repositories/BackupConfigurationRepositoryTest.java b/src/test/java/test/repositories/BackupConfigurationRepositoryTest.java index fe5766d5..e658ff93 100644 --- a/src/test/java/test/repositories/BackupConfigurationRepositoryTest.java +++ b/src/test/java/test/repositories/BackupConfigurationRepositoryTest.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import backupmanager.Entities.ConfigurationBackup; +import backupmanager.Exceptions.BackupDeletionException; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.Repositories.BackupConfigurationRepository; @@ -41,7 +42,7 @@ protected void insertBackup_shuldBeEquals_fetchFromBackupName() { } @Test - protected void deleteBackup_shuldBeTrue_afterDelete() { + protected void deleteBackup_shuldBeTrue_afterDelete() throws BackupDeletionException { ConfigurationBackup backup = BackupConfigurationRepository.getBackupByName(backups.get(1).getName()); BackupConfigurationRepository.deleteBackup(backup.getId()); ConfigurationBackup backupDeleted = BackupConfigurationRepository.getBackupById(backup.getId()); From 376f4af22b2ecd29d4f8a0bb22ed7c51baa05bfe Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Tue, 28 Apr 2026 23:19:07 +0200 Subject: [PATCH 15/17] Toasts, Modals and translations --- .../java/backupmanager/BackupOperations.java | 5 +- .../java/backupmanager/Enums/ConfigKey.java | 5 +- .../java/backupmanager/Enums/MenuItems.java | 2 - .../backupmanager/Enums/Translations.java | 81 ++------ .../backupmanager/Helpers/BackupHelper.java | 19 +- .../Managers/ExceptionManager.java | 9 +- .../backupmanager/Managers/ExportManager.java | 10 +- .../Managers/WebsiteManager.java | 15 +- .../java/backupmanager/Utils/ModalUtils.java | 60 ++++++ .../Utils/SimpleMessageModal.java | 173 ++++++++++++++++++ .../Controllers/BackupEntryController.java | 21 +-- .../gui/Controllers/TimePickerController.java | 7 +- .../gui/Controllers/TrayController.java | 2 +- .../backupmanager/gui/component/About.java | 3 +- .../gui/component/Subscription.java | 3 +- .../gui/component/dashboard/CardBox.java | 4 +- .../gui/component/dashboard/CardItem.java | 11 +- .../gui/forms/FormBackupDashboard.java | 9 +- .../gui/forms/FormBackupTable.java | 10 +- .../backupmanager/gui/forms/FormSetting.java | 13 +- .../gui/frames/BackupManager.java | 6 +- .../Controllers/BackupManagerController.java | 12 -- .../Controllers/BackupPopupController.java | 30 +-- .../backupmanager/gui/menu/DrawerManager.java | 24 ++- .../gui/menu/MyDrawerBuilder.java | 20 +- .../gui/simple/BackupEntryDialog.java | 6 +- src/main/resources/res/config/config.json | 14 +- src/main/resources/res/languages/deu.json | 85 ++------- src/main/resources/res/languages/eng.json | 85 ++------- src/main/resources/res/languages/esp.json | 85 ++------- src/main/resources/res/languages/fra.json | 85 ++------- src/main/resources/res/languages/ita.json | 86 ++------- 32 files changed, 485 insertions(+), 515 deletions(-) create mode 100644 src/main/java/backupmanager/Utils/ModalUtils.java create mode 100644 src/main/java/backupmanager/Utils/SimpleMessageModal.java diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index ce0056c4..791f7e3a 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -31,7 +31,10 @@ import backupmanager.Services.RunningBackupService; import backupmanager.Services.ZippingThread; import backupmanager.Utils.FolderUtils; +import backupmanager.Utils.ModalUtils; import backupmanager.database.Repositories.BackupRequestRepository; +import backupmanager.gui.menu.DrawerManager; +import raven.modal.component.SimpleModalBorder; public class BackupOperations { private static final Logger logger = LoggerFactory.getLogger(BackupOperations.class); @@ -42,7 +45,7 @@ public static void requestSingleBackup(ZippingContext context, BackupTriggerType if (!BackupRequestRepository.isAnyBackupRunning()) singleBackup(context, triggeredBy); else - JOptionPane.showMessageDialog(null, Translations.get(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), Translations.get(TKey.WARNING_GENERIC_TITLE), JOptionPane.WARNING_MESSAGE); + ModalUtils.showWarning(DrawerManager.getInstance().getParent(), Translations.get(TKey.WARNING_GENERIC_TITLE), Translations.get(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), SimpleModalBorder.CLOSE_OPTION); } case SCHEDULER -> singleBackup(context, triggeredBy); } diff --git a/src/main/java/backupmanager/Enums/ConfigKey.java b/src/main/java/backupmanager/Enums/ConfigKey.java index 1eaf542a..1edcb24b 100644 --- a/src/main/java/backupmanager/Enums/ConfigKey.java +++ b/src/main/java/backupmanager/Enums/ConfigKey.java @@ -29,7 +29,10 @@ public enum ConfigKey { SHARE_LINK, VERSION, GUI_WIDTH, - GUI_HEIGHT; + GUI_HEIGHT, + GUI_MIN_WIDTH, + GUI_MIN_HEIGHT, + ; private static final Map<ConfigKey, String> configValues = new EnumMap<>(ConfigKey.class); private static final Logger logger = LoggerFactory.getLogger(ConfigKey.class); diff --git a/src/main/java/backupmanager/Enums/MenuItems.java b/src/main/java/backupmanager/Enums/MenuItems.java index 60c97d2d..e735c2be 100644 --- a/src/main/java/backupmanager/Enums/MenuItems.java +++ b/src/main/java/backupmanager/Enums/MenuItems.java @@ -8,10 +8,8 @@ public enum MenuItems { BuymeacoffeeDonate, History, InfoPage, - New, //TODO: Used?? Import, Export, - Share, //TODO: Used?? Support, ContactUs, Website, diff --git a/src/main/java/backupmanager/Enums/Translations.java b/src/main/java/backupmanager/Enums/Translations.java index 592d940a..fcc62077 100644 --- a/src/main/java/backupmanager/Enums/Translations.java +++ b/src/main/java/backupmanager/Enums/Translations.java @@ -24,7 +24,6 @@ public class Translations { public enum TCategory { GENERAL("General"), MENU("Menu"), - TABBED_FRAMES("TabbedFrames"), BACKUP_ENTRY("BackupEntry"), BACKUP_LIST("BackupList"), TIME_PICKER_DIALOG("TimePickerDialog"), @@ -92,22 +91,10 @@ public enum TKey { ABOUT(TCategory.MENU, "About", "About"), HELP(TCategory.MENU, "Help", "Help"), BUG_REPORT(TCategory.MENU, "BugReport", "Report a bug"), - CLEAR(TCategory.MENU, "Clear", "Clear"), DONATE(TCategory.MENU, "Donate", "Support the project"), HISTORY(TCategory.MENU, "History", "History"), - INFO_PAGE(TCategory.MENU, "InfoPage", "Info"), NEW(TCategory.MENU, "New", "New"), - QUIT(TCategory.MENU, "Quit", "Quit"), - SAVE(TCategory.MENU, "Save", "Save"), - PREFERENCES(TCategory.MENU, "Preferences", "Preferences"), - IMPORT(TCategory.MENU, "Import", "Import"), - EXPORT(TCategory.MENU, "Export", "Export"), - SAVE_WITH_NAME(TCategory.MENU, "SaveWithName", "Save with name"), - SHARE(TCategory.MENU, "Share", "Share"), - SUPPORT(TCategory.MENU, "Support", "Support"), - WEBSITE(TCategory.MENU, "Website", "Website"), BACKUP_TABLE(TCategory.MENU, "Backups", "Backup List"), - CREATE_BACKUP(TCategory.MENU, "CreateBackup", "Create new backup"), IMPORT_BACKUP(TCategory.MENU, "ImportBackup", "Import backups from Csv"), EXPORT_BACKUP(TCategory.MENU, "ExportBackup", "Export backups to Csv"), DASHBOARD(TCategory.MENU, "Dashboard", "Dashboard"), @@ -116,22 +103,15 @@ public enum TKey { BUYMEACOFFE(TCategory.MENU, "BuyMeACoffe", "Buy me a coffe"), SUBSCRIPTION(TCategory.MENU, "Subscription", "Subscription"), - // TabbedFrames - BACKUP_ENTRY(TCategory.TABBED_FRAMES, "BackupEntry", "Backup Entry"), - BACKUP_LIST(TCategory.TABBED_FRAMES, "BackupList", "Backup List"), - // BackupEntry - PAGE_TITLE(TCategory.BACKUP_ENTRY, "PageTitle", "Backup Entry"), PAGE_SUBTITLE_CREATE(TCategory.BACKUP_ENTRY, "PageSubtitleCreate", "Create Backup"), PAGE_SUBTITLE_EDIT(TCategory.BACKUP_ENTRY, "PageSubtitleEdit", "Edit Backup"), PAGE_SUBTITLE_INFO(TCategory.BACKUP_ENTRY, "PageSubtitleInfo", "Backup Information"), PAGE_SUBTITLE_SETTINGS(TCategory.BACKUP_ENTRY, "PageSubtitleSettings", "Backups Settings"), - CURRENT_FILE(TCategory.BACKUP_ENTRY, "CurrentFile", "Current file"), NOTES(TCategory.BACKUP_ENTRY, "Notes", "Notes"), PATHS(TCategory.BACKUP_ENTRY, "Paths", "Paths"), LAST_BACKUP(TCategory.BACKUP_ENTRY, "LastBackup", "Last backup"), SINGLE_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "SingleBackupButton", "Single Backup"), - AUTO_BACKUP_BUTTON(TCategory.BACKUP_ENTRY, "AutoBackupButton", "Auto Backup"), AUTO_BACKUP_BUTTON_ON(TCategory.BACKUP_ENTRY, "AutoBackupButtonON", "Auto Backup (ON)"), AUTO_BACKUP_BUTTON_OFF(TCategory.BACKUP_ENTRY, "AutoBackupButtonOFF", "Auto Backup (OFF)"), BACKUP_NAME_PLACEHOLDER(TCategory.BACKUP_ENTRY, "BackupNamePlaceholder", "Backup name (unique)"), @@ -172,10 +152,6 @@ public enum TKey { BACKUP_COUNT_DETAIL(TCategory.BACKUP_LIST, "BackupCountDetail", "BackupCount"), NOTES_DETAIL(TCategory.BACKUP_LIST, "NotesDetail", "Notes"), MAX_BACKUPS_TO_KEEP_DETAIL(TCategory.BACKUP_LIST, "MaxBackupsToKeepDetail", "MaxBackupsToKeep"), - ADD_BACKUP_TOOLTIP(TCategory.BACKUP_LIST, "AddBackupTooltip", "Add new backup"), - EXPORT_AS(TCategory.BACKUP_LIST, "ExportAs", "Export as: "), - EXPORT_AS_PDF_TOOLTIP(TCategory.BACKUP_LIST, "ExportAsPdfTooltip", "Export as PDF"), - EXPORT_AS_CSV_TOOLTIP(TCategory.BACKUP_LIST, "ExportAsCsvTooltip", "Export as CSV"), RESEARCH_BAR_TOOLTIP(TCategory.BACKUP_LIST, "ResearchBarTooltip", "Research bar"), RESEARCH_BAR_PLACEHOLDER(TCategory.BACKUP_LIST, "ResearchBarPlaceholder", "Search..."), EDIT_POPUP(TCategory.BACKUP_LIST, "EditPopup", "Edit"), @@ -233,67 +209,29 @@ public enum TKey { WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE(TCategory.DIALOGS, "WarningBackupAlreadyInProgressMessage", "There is already a backup in progress. It is not possible to perform parallel backups"), WARNING_SHORT_TIME_INTERVAL_MESSAGE(TCategory.DIALOGS, "WarningShortTimeIntervalMessage", "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?"), - ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING(TCategory.DIALOGS, "ErrorMessageForFolderNotExisting", "The folder does not exist or is invalid"), - ERROR_MESSAGE_FOR_SAVING_FILE_WITH_PATHS_EMPTY(TCategory.DIALOGS, "ErrorMessageForSavingFileWithPathsEmpty", "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty"), - BACKUP_SAVED_CORRECTLY_TITLE(TCategory.DIALOGS, "BackupSavedCorrectlyTitle", "Backup saved"), - BACKUP_SAVED_CORRECTLY_MESSAGE(TCategory.DIALOGS, "BackupSavedCorrectlyMessage", "saved successfully!"), - ERROR_SAVING_BACKUP_MESSAGE(TCategory.DIALOGS, "ErrorSavingBackupMessage", "Error saving backup"), BACKUP_NAME_INPUT(TCategory.DIALOGS, "BackupNameInput", "Name of the backup"), CONFIRMATION_REQUIRED_TITLE(TCategory.DIALOGS, "ConfirmationRequiredTitle", "Confirmation required"), DUPLICATED_BACKUP_NAME_MESSAGE(TCategory.DIALOGS, "DuplicatedBackupNameMessage", "A backup with the same name already exists, do you want to overwrite it?"), - BACKUP_LIST_CORRECTLY_EXPORTED_TITLE(TCategory.DIALOGS, "BackupListCorrectlyExportedTitle", "Menu Export"), - BACKUP_LIST_CORRECTLY_EXPORTED_MESSAGE(TCategory.DIALOGS, "BackupListCorrectlyExportedMessage", "Backup list successfully exported to the Desktop!"), - BACKUP_LIST_CORRECTLY_IMPORTED_TITLE(TCategory.DIALOGS, "BackupListCorrectlyImportedTitle", "Menu Import"), - BACKUP_LIST_CORRECTLY_IMPORTED_MESSAGE(TCategory.DIALOGS, "BackupListCorrectlyImportedMessage", "Backup list successfully imported!"), - BACKUP_NAME_ALREADY_USED_MESSAGE(TCategory.DIALOGS, "BackupNameAlreadyUsedMessage", "Backup name already used!"), - ERROR_MESSAGE_FOR_INCORRECT_INITIAL_PATH(TCategory.DIALOGS, "ErrorMessageForIncorrectInitialPath", "Error during the backup operation: the initial path is incorrect!"), - EXCEPTION_MESSAGE_TITLE(TCategory.DIALOGS, "ExceptionMessageTitle", "Error..."), - EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE(TCategory.DIALOGS, "ExceptionMessageClipboardMessage", "Error text has been copied to the clipboard."), EXCEPTION_MESSAGE_CLIPBOARD_BUTTON(TCategory.DIALOGS, "ExceptionMessageClipboardButton", "Copy to clipboard"), EXCEPTION_MESSAGE_REPORT_BUTTON(TCategory.DIALOGS, "ExceptionMessageReportButton", "Report the Problem"), EXCEPTION_MESSAGE_REPORT_MESSAGE(TCategory.DIALOGS, "ExceptionMessageReportMessage", "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):"), - ERROR_MESSAGE_OPENING_WEBSITE(TCategory.DIALOGS, "ErrorMessageOpeningWebsite", "Failed to open the web page. Please try again."), - CONFIRMATION_MESSAGE_FOR_CLEAR(TCategory.DIALOGS, "ConfirmationMessageForClear", "Are you sure you want to clean the fields?"), - CONFIRMATION_MESSAGE_FOR_UNSAVED_CHANGES(TCategory.DIALOGS, "ConfirmationMessageForUnsavedChanges", "There are unsaved changes, do you want to save them before moving to another backup?"), - ERROR_MESSAGE_OPEN_HISTORY_FILE(TCategory.DIALOGS, "ErrorMessageOpenHistoryFile", "Error opening history file."), CONFIRMATION_MESSAGE_BEFORE_DELETE_BACKUP(TCategory.DIALOGS, "ConfirmationMessageBeforeDeleteBackup", "Are you sure you want to delete this item? Please note, this action cannot be undone"), - SHARE_LINK_COPIED_MESSAGE(TCategory.DIALOGS, "ShareLinkCopiedMessage", "Share link copied to clipboard!"), CONFIRMATION_MESSAGE_CANCEL_AUTO_BACKUP(TCategory.DIALOGS, "ConfirmationMessageCancelAutoBackup", "Are you sure you want to cancel automatic backups for this entry?"), - CONFIRMATION_MESSAGE_CANCEL_SINGLE_BACKUP(TCategory.DIALOGS, "ConfirmationMessageCancelSingleBackup", "Are you sure you want to cancel this backup?"), - CONFIRMATION_MESSAGE_BEFORE_EXIT(TCategory.DIALOGS, "ConfirmationMessageBeforeExit", "Are you sure you want to exit?"), - ERROR_MESSAGE_UNEXPECTED(TCategory.DIALOGS, "ErrorMessageUnexpected", "An unexpected error has occurred!"), - ERROR_MESSAGE_PATHS_CANNOT_BE_SAME(TCategory.DIALOGS, "ErrorMessagePathsCannotBeSame", "The initial path and destination path cannot be the same!"), - ERROR_MESSAGE_PATHS_ARE_EMPTY(TCategory.DIALOGS, "ErrorMessagePathsAreEmpty", "The initial path and destination path cannot be empty!"), - ERROR_MESSAGE_INVALID_PATH(TCategory.DIALOGS, "ErrorMessageInvalidPath", "The selected path is invalid!"), - ERROR_MESSAGE_NOT_SUPPORTED_EMAIL(TCategory.DIALOGS, "ErrorMessageNotSupportedEmail", "Your system does not support sending emails directly from this application."), - ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC(TCategory.DIALOGS, "ErrorMessageNotSupportedEmailGeneric", "Your system does not support sending emails."), - ERROR_WRONG_TIME_INTERVAL(TCategory.DIALOGS, "ErrorWrongTimeInterval", "The time interval is not correct"), AUTO_BACKUP_ACTIVATED_MESSAGE(TCategory.DIALOGS, "AutoBackupActivatedMessage", "Auto Backup has been activated"), + AUTO_BACKUP_MESSAGE(TCategory.DIALOGS, "AutoBackup", "Auto Backup"), SETTED_EVERY_MESSAGE(TCategory.DIALOGS, "SettedEveryMessage", "\nIs setted every"), DAYS_MESSAGE(TCategory.DIALOGS, "DaysMessage", " days"), - ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL(TCategory.DIALOGS, "ErrorMessageUnableToSendEmail", "Unable to send email. Please try again later."), INTERRUPT_BACKUP_PROCESS_MESSAGE(TCategory.DIALOGS, "InterruptBackupProcessMessage", "Are you sure you want to stop this backup?"), ERROR_MESSAGE_INPUT_MISSING_GENERIC(TCategory.DIALOGS, "ErrorMessageInputMissingGeneric", "Input Missing!"), - ERROR_MESSAGE_SAVING_FILE(TCategory.DIALOGS, "ErrorMessageForSavingFile", "Error saving file"), ERROR_MESSAGE_PATH_NOT_EXISTING(TCategory.DIALOGS, "ErrorMessageForPathNotExisting", "One or both paths do not exist!"), ERROR_MESSAGE_SAME_PATHS_GENERIC(TCategory.DIALOGS, "ErrorMessageForSamePaths", "The initial path and destination path cannot be the same. Please choose different paths!"), - ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_TITLE(TCategory.DIALOGS, "ErrorMessageForWrongFileExtensionTitle", "Invalid File"), - ERROR_MESSAGE_FOR_WRONG_FILE_EXTENSION_MESSAGE(TCategory.DIALOGS, "ErrorMessageForWrongFileExtensionMessage", "Error: Please select a valid JSON file."), ERROR_MESSAGE_COUNTING_FILES(TCategory.DIALOGS, "ErrorMessageCountingFiles", "Error occurred while calculating files to back up."), ERROR_MESSAGE_ZIPPING_GENERIC(TCategory.DIALOGS, "ErrorMessageZippingGeneric", "Error occurred while zipping files."), ERROR_MESSAGE_ZIPPING_IO(TCategory.DIALOGS, "ErrorMessageZippingIO", "Error occurred while zipping files: I/O error."), ERROR_MESSAGE_ZIPPING_SECURITY(TCategory.DIALOGS, "ErrorMessageZippingSecurity", "Error occurred while zipping files: Security error."), SUCCESS_GENERIC_TITLE(TCategory.DIALOGS, "SuccessGenericTitle", "Success"), - SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE(TCategory.DIALOGS, "SuccessfullyExportedToCsvMessage", "Backups exported to CSV successfully!"), - SUCCESSFULLY_EXPORTED_TO_PDF_MESSAGE(TCategory.DIALOGS, "SuccessfullyExportedToPdfMessage", "Backups exported to PDF successfully!"), - ERROR_MESSAGE_FOR_EXPORTING_TO_CSV(TCategory.DIALOGS, "ErrorMessageForExportingToCsv", "Error exporting backups to CSV: "), - ERROR_MESSAGE_FOR_EXPORTING_TO_PDF(TCategory.DIALOGS, "ErrorMessageForExportingToPdf", "Error exporting backups to PDF: "), CSV_NAME_MESSAGE_INPUT(TCategory.DIALOGS, "CsvNameMessageInput", "Enter the name of the CSV file."), - PDF_NAME_MESSAGE_INPUT(TCategory.DIALOGS, "PdfNameMessageInput", "Enter the name of the PDF file."), DUPLICATED_FILE_NAME_MESSAGE(TCategory.DIALOGS, "DuplicatedFileNameMessage", "File already exists. Overwrite?"), - ERROR_MESSAGE_INVALID_FILENAME(TCategory.DIALOGS, "ErrorMessageInvalidFilename", "Invalid file name. Use only alphanumeric characters, dashes, and underscores."), - CONFIRMATION_DELETION_TITLE(TCategory.DIALOGS, "ConfirmationDeletionTitle", "Confirm Deletion"), - CONFIRMATION_DELETION_MESSAGE(TCategory.DIALOGS, "ConfirmationDeletionMessage", "Are you sure you want to delete the selected rows?"), // Subscription SUBSCRIPTION_EXPIRING_TITLE(TCategory.SUBSCRIPTION, "ExpiringTitle", "Backup Manager subscription expiring soon"), @@ -355,6 +293,10 @@ public enum TKey { SETTINGS_LINE_STYLE_CURVED(TCategory.SETTINGS, "SettingsLineStyleCurved", "Curved"), SETTINGS_COLOR_OPTION_LAYOUT(TCategory.SETTINGS, "SettingsColorOptionLayout", "Color option"), SETTINGS_COLOR_OPTION_PAINTED(TCategory.SETTINGS, "SettingsColorOptionPainted", "Paint selected line color"), + SETTINGS_THEMES(TCategory.SETTINGS, "SettingsThemes", "Themes"), + SETTINGS_THEME_ALL(TCategory.SETTINGS, "SettingsThemeAll", "All"), + SETTINGS_THEME_LIGHT(TCategory.SETTINGS, "SettingsThemeLight", "Light"), + SETTINGS_THEME_DARK(TCategory.SETTINGS, "SettingsThemeDark", "Dark"), // SEARCH BAR SEARCH_TITLE(TCategory.SEARCH_BAR, "SearcTitle", "Search..."), @@ -377,9 +319,20 @@ public enum TKey { TOAST_MISSING_DATA_LOGIN_ERROR(TCategory.TOAST, "MissingDataLoginError", "Please fill in all the required fields"), TOAST_WRONG_EMAIL_LOGIN_ERROR(TCategory.TOAST, "WrongEmailLoginError", "The provided email address is invalid. Please provide a correct one"), TOAST_LOGIN(TCategory.TOAST, "LoginOk", "Welcome! You have successfully signed in"), + TOAST_ERROR_TEXT_CLIPBOARD(TCategory.TOAST, "ErrorTextClipboard", "Error text has been copied to the clipboard"), + TOAST_CSV_EXPORT(TCategory.TOAST, "CsvExport", "Backups exported to CSV successfully!"), + TOAST_CSV_EXPORT_ERROR(TCategory.TOAST, "CsvExportError", "Error exporting backups to CSV"), + TOAST_CSV_EXPORT_INVALID_FILENAME(TCategory.TOAST, "CsvExportInvalidFilename", "Invalid file name. Use only alphanumeric characters, dashes, and underscores"), + TOAST_NOT_SUPPORTED_EMAIL(TCategory.TOAST, "NotSupportedEmail", "Your system does not support sending emails directly from this application"), + TOAST_UNABLE_TO_SEND_EMAIL(TCategory.TOAST, "UnableToSendEmail", "Unable to send email. Please try again later"), + TOAST_NOT_SUPPORTED_EMAIL_GENERIC(TCategory.TOAST, "NotSupportedEmailGeneric", "Your system does not support sending emails"), + TOAST_OPENING_WEBSITE_ERROR(TCategory.TOAST, "OpeningWebsiteError", "Failed to open the web page. Please try again"), + TOAST_FOLDER_NOT_EXISTING(TCategory.TOAST, "FolderNotExisting", "The folder does not exist or is invalid"), + TOAST_BACKUP_NAME_ALREADY_USED(TCategory.TOAST, "BackupNameAlreadyUsed", "Backup name already used!"), + TOAST_BACKUP_ALREADY_IN_PROGRESS(TCategory.TOAST, "BackupAlreadyInProgress", "There is already a backup in progress. It is not possible to perform parallel backups"), + TOAST_BACKUP_PATHS_EMPTY_ERROR(TCategory.TOAST, "BackupPathsEmptyError", "The initial path and destination path cannot be empty!"), ; - private final TCategory category; private final String keyName; private final String defaultValue; diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index cc0177b2..d0f0b4ce 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -1,6 +1,6 @@ package backupmanager.Helpers; -import java.sql.SQLException; +import java.awt.Component; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -17,9 +17,11 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Exceptions.BackupDeletionException; +import backupmanager.Utils.ModalUtils; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.database.Repositories.BackupRequestRepository; import backupmanager.gui.simple.TimePickerDialog; +import raven.modal.component.SimpleModalBorder; public class BackupHelper { @@ -79,17 +81,18 @@ public static TimeInterval openTimePicker(TimePickerDialog picker) { return picker.getResult(); } - public static void showMessageActivationAutoBackup(TimeInterval timeInterval, String startPath, String destinationPath) { + public static void showMessageActivationAutoBackup(Component parent, TimeInterval timeInterval, String startPath, String destinationPath) { String from = Translations.get(TKey.FROM); String to = Translations.get(TKey.TO); String activated = Translations.get(TKey.AUTO_BACKUP_ACTIVATED_MESSAGE); String setted = Translations.get(TKey.SETTED_EVERY_MESSAGE); String days = Translations.get(TKey.DAYS_MESSAGE); - JOptionPane.showMessageDialog(null, - activated + "\n\t" + from + ": " + startPath + "\n\t" + to + ": " - + destinationPath + setted + " " + timeInterval.toString() + days, - "AutoBackup", 1); + String message = + activated + "\n\n" + from + ": " + startPath + "\n" + to + ": " + + destinationPath + "\n" + setted + " " + timeInterval.toString() + days; + + ModalUtils.showInfo(parent, Translations.get(TKey.AUTO_BACKUP_MESSAGE), message, SimpleModalBorder.CLOSE_OPTION); } public static LocalDateTime getNexDateBackup(TimeInterval timeInterval) { @@ -103,7 +106,7 @@ public static void forceBackupTermination(int requestId) { BackupRequestRepository.updateRequestStatusByRequestId(requestId, BackupStatus.TERMINATED); } - public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup backup) { + public static ConfigurationBackup toggleAutomaticBackup(Component parent, ConfigurationBackup backup) { logger.info("Event --> automatic backup"); if (backup.isAutomatic()) { @@ -145,7 +148,7 @@ public static ConfigurationBackup toggleAutomaticBackup(ConfigurationBackup back logger.info("Automatic backup turned On and next date backup setted to {}", nextDateBackup); - showMessageActivationAutoBackup(timeInterval, backup.getTargetPath(), backup.getDestinationPath()); + showMessageActivationAutoBackup(parent, timeInterval, backup.getTargetPath(), backup.getDestinationPath()); updateBackup(backup); diff --git a/src/main/java/backupmanager/Managers/ExceptionManager.java b/src/main/java/backupmanager/Managers/ExceptionManager.java index ba25d259..6f76aed4 100644 --- a/src/main/java/backupmanager/Managers/ExceptionManager.java +++ b/src/main/java/backupmanager/Managers/ExceptionManager.java @@ -15,6 +15,8 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.ToastUtils; +import backupmanager.gui.menu.DrawerManager; public class ExceptionManager { private static final Logger logger = LoggerFactory.getLogger(ExceptionManager.class); @@ -22,9 +24,8 @@ public class ExceptionManager { public static void openExceptionMessage(String errorMessage, String stackTrace) { Object[] options = {Translations.get(TKey.CLOSE_BUTTON), Translations.get(TKey.EXCEPTION_MESSAGE_CLIPBOARD_BUTTON), Translations.get(TKey.EXCEPTION_MESSAGE_REPORT_BUTTON)}; - if (errorMessage == null) { + if (errorMessage == null) errorMessage = ""; - } stackTrace = !errorMessage.isEmpty() ? errorMessage + "\n" + stackTrace : errorMessage + stackTrace; @@ -75,9 +76,9 @@ public static void openExceptionMessage(String errorMessage, String stackTrace) StringSelection selection = new StringSelection(stackTrace); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); logger.info("Error text has been copied to the clipboard"); - JOptionPane.showMessageDialog(null, Translations.get(TKey.EXCEPTION_MESSAGE_CLIPBOARD_MESSAGE)); + ToastUtils.showInfo(DrawerManager.getInstance().getParent(), Translations.get(TKey.TOAST_ERROR_TEXT_CLIPBOARD)); } else if (choice == 2) { - WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue()); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), ConfigKey.ISSUE_PAGE_LINK.getValue()); } } while (choice == 1 || choice == 2); } diff --git a/src/main/java/backupmanager/Managers/ExportManager.java b/src/main/java/backupmanager/Managers/ExportManager.java index 1fba3441..55bcc417 100644 --- a/src/main/java/backupmanager/Managers/ExportManager.java +++ b/src/main/java/backupmanager/Managers/ExportManager.java @@ -7,6 +7,7 @@ import java.nio.file.Paths; import java.util.List; +import javax.swing.JFrame; import javax.swing.JOptionPane; import org.slf4j.Logger; @@ -16,12 +17,13 @@ import backupmanager.Entities.ConfigurationBackup; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.ToastUtils; public class ExportManager { private static final Logger logger = LoggerFactory.getLogger(ExportManager.class); - public static void exportAsCSV(List<ConfigurationBackup> backups, String header) { + public static void exportAsCSV(JFrame component, List<ConfigurationBackup> backups, String header) { logger.info("Exporting backups to CSV"); String path = BackupOperations.pathSearchWithFileChooser(false); @@ -39,7 +41,7 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) // Validate filename if (!filename.matches("[a-zA-Z0-9-_ ]+")) { - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_INVALID_FILENAME), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(component, Translations.get(TKey.TOAST_CSV_EXPORT_INVALID_FILENAME)); logger.info("Exporting backups to CSV cancelled due to invalid file name"); return; } @@ -70,10 +72,10 @@ public static void exportAsCSV(List<ConfigurationBackup> backups, String header) } } - JOptionPane.showMessageDialog(null, Translations.get(TKey.SUCCESSFULLY_EXPORTED_TO_CSV_MESSAGE), Translations.get(TKey.SUCCESS_GENERIC_TITLE), JOptionPane.INFORMATION_MESSAGE); + ToastUtils.showSuccess(component, Translations.get(TKey.TOAST_CSV_EXPORT)); } catch (IOException ex) { logger.error("Error exporting backups to CSV: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_FOR_EXPORTING_TO_CSV) + ex.getMessage(), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(component, Translations.get(TKey.TOAST_CSV_EXPORT_ERROR) + ": " + ex.getMessage()); } finally { logger.info("Exporting backups to CSV finished"); } diff --git a/src/main/java/backupmanager/Managers/WebsiteManager.java b/src/main/java/backupmanager/Managers/WebsiteManager.java index 94d21eb1..f9dbf597 100644 --- a/src/main/java/backupmanager/Managers/WebsiteManager.java +++ b/src/main/java/backupmanager/Managers/WebsiteManager.java @@ -5,7 +5,7 @@ import java.net.URI; import java.net.URISyntaxException; -import javax.swing.JOptionPane; +import javax.swing.JFrame; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,11 +13,12 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.ToastUtils; public class WebsiteManager { private static final Logger logger = LoggerFactory.getLogger(WebsiteManager.class); - public static void openWebSite(String reportUrl) { + public static void openWebSite(JFrame parent, String reportUrl) { try { if (Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); @@ -27,11 +28,11 @@ public static void openWebSite(String reportUrl) { } } catch (IOException | URISyntaxException e) { logger.error("Failed to open the web page: " + e.getMessage(), e); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_OPENING_WEBSITE), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_OPENING_WEBSITE_ERROR)); } } - public static void sendEmail() { + public static void sendEmail(JFrame parent) { if (Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); @@ -44,15 +45,15 @@ public static void sendEmail() { desktop.mail(uri); } catch (IOException | URISyntaxException ex) { logger.error("Failed to send email: " + ex.getMessage(), ex); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_UNABLE_TO_SEND_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_UNABLE_TO_SEND_EMAIL)); } } else { logger.warn("Mail action is unsupported in your system's desktop environment."); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_NOT_SUPPORTED_EMAIL)); } } else { logger.warn("Desktop integration is unsupported on this system."); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_NOT_SUPPORTED_EMAIL_GENERIC), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_NOT_SUPPORTED_EMAIL_GENERIC)); } } diff --git a/src/main/java/backupmanager/Utils/ModalUtils.java b/src/main/java/backupmanager/Utils/ModalUtils.java new file mode 100644 index 00000000..8649dc41 --- /dev/null +++ b/src/main/java/backupmanager/Utils/ModalUtils.java @@ -0,0 +1,60 @@ +package backupmanager.Utils; + +import java.awt.Component; + +import raven.modal.ModalDialog; +import raven.modal.option.BorderOption; +import raven.modal.option.Location; +import raven.modal.option.Option; + +public class ModalUtils { + + public static void showDefault(Component owner, String title, String message, int options) { + showCustomModal(owner, SimpleMessageModal.Type.DEFAULT, title, message, options); + } + + public static void showInfo(Component owner, String title, String message, int options) { + showCustomModal(owner, SimpleMessageModal.Type.INFO, title, message, options); + } + + public static void showSuccess(Component owner, String title, String message, int options) { + showCustomModal(owner, SimpleMessageModal.Type.SUCCESS, title, message, options); + } + + public static void showWarning(Component owner, String title, String message, int options) { + showCustomModal(owner, SimpleMessageModal.Type.WARNING, title, message, options); + } + + public static void showError(Component owner, String title, String message, int options) { + showCustomModal(owner, SimpleMessageModal.Type.ERROR, title, message, options); + } + + private static void showCustomModal(Component owner, SimpleMessageModal.Type type, String title, String message, int options) { + SimpleMessageModal modal = new SimpleMessageModal(type, message, title, options, null); + ModalDialog.showModal(owner, modal, getSelectedOption()); + } + + private static Option getSelectedOption() { + Option option = ModalDialog.createOption(); + float scale = 0.1f; + Location h = Location.CENTER; + Location v = Location.CENTER; + Option.BackgroundClickType backgroundClickType = Option.BackgroundClickType.BLOCK; + option.setAnimationEnabled(true) + .setCloseOnPressedEscape(true) + .setBackgroundClickType(backgroundClickType) + .setOpacity(0.5f) + .setHeavyWeight(false); + option.getBorderOption() + .setBorderWidth(0) + .setShadow(BorderOption.Shadow.NONE); + option.getLayoutOption().setLocation(h, v) + .setRelativeToOwner(false) + .setMovable(false); + if (scale != 0) { + option.getLayoutOption().setAnimateDistance(0, 0) + .setAnimateScale(scale); + } + return option; + } +} diff --git a/src/main/java/backupmanager/Utils/SimpleMessageModal.java b/src/main/java/backupmanager/Utils/SimpleMessageModal.java new file mode 100644 index 00000000..d972d358 --- /dev/null +++ b/src/main/java/backupmanager/Utils/SimpleMessageModal.java @@ -0,0 +1,173 @@ +package backupmanager.Utils; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.text.DefaultCaret; + +import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.extras.FlatSVGIcon; + +import net.miginfocom.swing.MigLayout; +import raven.modal.Toast; +import raven.modal.component.SimpleModalBorder; +import raven.modal.listener.ModalCallback; +import raven.modal.toast.ToastPanel; + +public class SimpleMessageModal extends SimpleModalBorder { + + private final Type type; + private Component titleComponent; + + public SimpleMessageModal(Type type, String message, String title, int optionType, ModalCallback callback) { + this(type, createMessage(type, message), title, optionType, callback); + } + + public SimpleMessageModal(Type type, Component messageComponent, String title, int optionType, ModalCallback callback) { + super(messageComponent, title, optionType, callback); + this.type = type; + } + + public SimpleMessageModal(Type type, String message, Component titleComponent, int optionType, ModalCallback callback) { + this(type, createMessage(type, message), titleComponent, optionType, callback); + } + + public SimpleMessageModal(Type type, Component messageComponent, Component titleComponent, int optionType, ModalCallback callback) { + super(messageComponent, null, optionType, callback); + this.titleComponent = titleComponent; + this.type = type; + } + + private static Component createMessage(Type type, String message) { + JTextArea text = new JTextArea(message); + text.setWrapStyleWord(true); + text.setEditable(false); + text.setCaret(new DefaultCaret() { + @Override + public void paint(Graphics g) { + } + }); + String gap = type == Type.DEFAULT ? "30" : "62"; + text.putClientProperty(FlatClientProperties.STYLE, "" + + "border:0," + gap + ",10,30;" + + "[light]foreground:lighten($Label.foreground,20%);" + + "[dark]foreground:darken($Label.foreground,20%);"); + return text; + } + + @Override + protected JComponent createTitleComponent(String title) { + if (titleComponent != null && titleComponent instanceof JComponent) { + return (JComponent) titleComponent; + } + if (type == Type.DEFAULT) { + return super.createTitleComponent(title); + } + Icon icon = createIcon(type); + JLabel label = (JLabel) super.createTitleComponent(title); + label.setIconTextGap(10); + label.setIcon(icon); + return label; + } + + @Override + protected JComponent createOptionButton(Option[] optionsType) { + JPanel panel = (JPanel) super.createOptionButton(optionsType); + if (panel == null) return null; + + // modify layout option + if (panel.getLayout() instanceof MigLayout) { + MigLayout layout = (MigLayout) panel.getLayout(); + layout.setColumnConstraints("[]12[]"); + } + return panel; + } + + @Override + protected JButton createButtonOption(Option option) { + JButton button; + if (option.getType() != 0) { + button = super.createButtonOption(option); + } else { + button = new JButton(option.getText()); + button.addActionListener(e -> doAction(option.getType())); + } + String[] colors = getColorKey(type); + if (option.getType() == 0) { + button.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:999;" + + "margin:3,33,3,33;" + + "borderWidth:0;" + + "focusWidth:0;" + + "innerFocusWidth:0;" + + "default.borderWidth:0;" + + "foreground:$Button.default.foreground;" + + "[light]background:" + colors[0] + ";" + + "[dark]background:" + colors[1] + ";"); + } else { + button.putClientProperty(FlatClientProperties.STYLE, "" + + "arc:999;" + + "margin:3,33,3,33;" + + "borderWidth:1;" + + "focusWidth:0;" + + "innerFocusWidth:1;" + + "background:null;" + + "[light]borderColor:" + colors[0] + ";" + + "[dark]borderColor:" + colors[1] + ";" + + "[light]focusedBorderColor:" + colors[0] + ";" + + "[dark]focusedBorderColor:" + colors[1] + ";" + + "[light]focusColor:" + colors[0] + ";" + + "[dark]focusColor:" + colors[1] + ";" + + "[light]hoverBorderColor:" + colors[0] + ";" + + "[dark]hoverBorderColor:" + colors[1] + ";" + + "[light]foreground:" + colors[0] + ";" + + "[dark]foreground:" + colors[1] + ";"); + } + return button; + } + + protected Icon createIcon(Type type) { + ToastPanel.ThemesData data = Toast.getThemesData().get(asToastType(type)); + FlatSVGIcon icon = new FlatSVGIcon(data.getIcon(), 0.45f); + FlatSVGIcon.ColorFilter colorFilter = new FlatSVGIcon.ColorFilter(); + colorFilter.add(Color.decode("#969696"), Color.decode(data.getColors()[0]), Color.decode(data.getColors()[1])); + icon.setColorFilter(colorFilter); + return icon; + } + + protected String[] getColorKey(Type type) { + if (type == Type.DEFAULT) { + // use accent color as default type + return new String[]{"$Component.accentColor", "$Component.accentColor"}; + } + ToastPanel.ThemesData data = Toast.getThemesData().get(asToastType(type)); + return data.getColors(); + } + + private Toast.Type asToastType(Type type) { + switch (type) { + case DEFAULT: + return Toast.Type.DEFAULT; + case SUCCESS: + return Toast.Type.SUCCESS; + case INFO: + return Toast.Type.INFO; + case WARNING: + return Toast.Type.WARNING; + default: + return Toast.Type.ERROR; + } + } + + public enum Type { + DEFAULT, SUCCESS, INFO, WARNING, ERROR + } +} + diff --git a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java index aaeec2af..a2d6e7db 100644 --- a/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java +++ b/src/main/java/backupmanager/gui/Controllers/BackupEntryController.java @@ -81,8 +81,10 @@ public TimeInterval handleTimePickerAction(TimePickerDialog picker, String targe } public boolean canDisposeAfterOk(Component owner, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep, boolean create) throws BackupDeletionException { - if (name.isBlank() || destinationPath.isBlank() || initialPath.isBlank()) + if (name.isBlank() || destinationPath.isBlank() || initialPath.isBlank()) { + ToastUtils.showError(owner, Translations.get(TKey.TOAST_BACKUP_PATHS_EMPTY_ERROR)); return false; + } updateCurrentBackup(name, initialPath, destinationPath, notes, autoBackup, maxBackupsToKeep); @@ -96,6 +98,7 @@ public boolean canDisposeAfterOk(Component owner, String name, String initialPat else return false; } + BackupHelper.newBackup(currentBackup); } else { BackupHelper.updateBackup(currentBackup); @@ -111,12 +114,12 @@ public void openFileChooser(JTextField filed, boolean allowFiles) { } } - public boolean toggleAutomaticBackup(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { + public boolean toggleAutomaticBackup(Component parent, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { updateCurrentBackup(name, initialPath, destinationPath,notes, autoBackup, maxBackupsToKeep); currentBackup.setAutomatic(!currentBackup.isAutomatic()); - ConfigurationBackup backup = BackupHelper.toggleAutomaticBackup(currentBackup); + ConfigurationBackup backup = BackupHelper.toggleAutomaticBackup(parent, currentBackup); if (backup == null) return false; @@ -134,10 +137,6 @@ public boolean toggleAutomaticBackup(String name, String initialPath, String des public void handleSingleBackupRequest(BackupTableDataService backupTable, String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) throws BackupAlreadyRunningException { if (BackupRequestRepository.isAnyBackupRunning()) { - JOptionPane.showMessageDialog(null, - Translations.get(TKey.WARNING_BACKUP_ALREADY_IN_PROGRESS_MESSAGE), - Translations.get(TKey.WARNING_GENERIC_TITLE), - JOptionPane.WARNING_MESSAGE); throw new BackupAlreadyRunningException(); } @@ -198,19 +197,15 @@ private void singleBackup(String target, String destination, BackupTableDataServ } } - public void handleOpenBackupActivationMessage(TimeInterval newtimeInterval, String target, String destination) { + public void handleOpenBackupActivationMessage(Component parent, TimeInterval newtimeInterval, String target, String destination) { currentBackup.setTimeIntervalBackup(newtimeInterval); - BackupHelper.showMessageActivationAutoBackup(newtimeInterval, target, destination); + BackupHelper.showMessageActivationAutoBackup(parent, newtimeInterval, target, destination); } public ConfigurationBackup getCurrentBackup() { return currentBackup; } - public void setCurrentBackup(ConfigurationBackup currentBackup) { - this.currentBackup = currentBackup; - } - private void updateCurrentBackup(String name, String initialPath, String destinationPath, String notes, boolean autoBackup, int maxBackupsToKeep) { if (currentBackup == null) { currentBackup = getBackup( diff --git a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java index 2fd56a38..ff401ded 100644 --- a/src/main/java/backupmanager/gui/Controllers/TimePickerController.java +++ b/src/main/java/backupmanager/gui/Controllers/TimePickerController.java @@ -5,6 +5,7 @@ import backupmanager.Entities.TimeInterval; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; +import backupmanager.Utils.ToastUtils; import backupmanager.gui.simple.TimePickerDialog; public class TimePickerController { @@ -19,7 +20,7 @@ public TimeInterval getTimeIntervalIfPossible(TimePickerDialog dialog, int days, return new TimeInterval(days, hours, minutes); } else - showErrorMessageForLongTime(null); + ToastUtils.showError(dialog, Translations.get(TKey.TOAST_INVALID_TIME)); return null; } @@ -32,10 +33,6 @@ private boolean isShortTimeCorrect(int days, int hours) { return days == 0 && hours == 0; } - private void showErrorMessageForLongTime(javax.swing.JDialog dialog) { - JOptionPane.showMessageDialog(dialog, Translations.get(TKey.ERROR_WRONG_TIME_INTERVAL), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); - } - private boolean showWarningMessageForShortTimeAndGetIfItOkayResponse(javax.swing.JDialog dialog) { int response = JOptionPane.showConfirmDialog(dialog, Translations.get(TKey.WARNING_SHORT_TIME_INTERVAL_MESSAGE), Translations.get(TKey.WARNING_GENERIC_TITLE), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); return response == JOptionPane.YES_OPTION; diff --git a/src/main/java/backupmanager/gui/Controllers/TrayController.java b/src/main/java/backupmanager/gui/Controllers/TrayController.java index 4f8d3c87..a90a1018 100644 --- a/src/main/java/backupmanager/gui/Controllers/TrayController.java +++ b/src/main/java/backupmanager/gui/Controllers/TrayController.java @@ -46,7 +46,7 @@ private void createHiddenIcon() { PopupMenu popup = setupAndGetPopupMenu(); - trayIcon = new TrayIcon(image, "Backup Service", popup); + trayIcon = new TrayIcon(image, Translations.get(TKey.TRAY_TOOLTIP), popup); trayIcon.setImageAutoSize(true); try { diff --git a/src/main/java/backupmanager/gui/component/About.java b/src/main/java/backupmanager/gui/component/About.java index 2db9270a..757d971a 100644 --- a/src/main/java/backupmanager/gui/component/About.java +++ b/src/main/java/backupmanager/gui/component/About.java @@ -16,6 +16,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.Json.JsonConfig; import backupmanager.Managers.WebsiteManager; +import backupmanager.gui.menu.DrawerManager; import net.miginfocom.swing.MigLayout; public class About extends JPanel { @@ -37,7 +38,7 @@ private void init() { description.addHyperlinkListener(e -> { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) - WebsiteManager.openWebSite(e.getURL().toString()); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), e.getURL().toString()); }); add(title); diff --git a/src/main/java/backupmanager/gui/component/Subscription.java b/src/main/java/backupmanager/gui/component/Subscription.java index 05229470..397fda34 100644 --- a/src/main/java/backupmanager/gui/component/Subscription.java +++ b/src/main/java/backupmanager/gui/component/Subscription.java @@ -15,6 +15,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Managers.WebsiteManager; +import backupmanager.gui.menu.DrawerManager; import net.miginfocom.swing.MigLayout; public class Subscription extends JPanel { @@ -55,7 +56,7 @@ private JTextPane createHtmlPane(String html) { pane.addHyperlinkListener(e -> { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - WebsiteManager.openWebSite(e.getURL().toString()); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), e.getURL().toString()); } }); diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java index 752db2b3..96ee1058 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardBox.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardBox.java @@ -38,8 +38,8 @@ public void addCardItem(Icon icon, String title) { add(cardItem, "width 100%"); } - public void setValueAt(int index, String value, String description, String tags, boolean up) { - cardItems.get(index).setValue(value, description, tags, up); + public void setValueAt(int index, String value, String tags, boolean up) { + cardItems.get(index).setValue(value, tags, up); } public void setCardIconColor(int index, Color color) { diff --git a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java index 4b5c150e..46d68dff 100644 --- a/src/main/java/backupmanager/gui/component/dashboard/CardItem.java +++ b/src/main/java/backupmanager/gui/component/dashboard/CardItem.java @@ -43,12 +43,15 @@ private void init(Icon icon, String title) { add(lbTags); } - public void setValue(String value, String description, String tags, boolean up) { + public void setValue(String value, String tags, boolean up) { lbValue.setText(value); - lbDescription.setText(description); + lbTags.setText(tags); - lbTags.putClientProperty(FlatClientProperties.STYLE_CLASS, (up ? "greenBadge" : "redBadge") + " small"); - lbDescription.setVisible(description != null); + lbTags.putClientProperty( + FlatClientProperties.STYLE_CLASS, + (up ? "greenBadge" : "redBadge") + " small" + ); + lbTags.setVisible(tags != null); } diff --git a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java index 50ff4b31..9196aefb 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupDashboard.java @@ -36,7 +36,7 @@ import backupmanager.gui.component.dashboard.CardBox; import net.miginfocom.swing.MigLayout; -@SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard") +@SystemForm(name = "Backup Dashboard", description = "Backup analytics dashboard", tags = {"backups", "dashboard"}) public class FormBackupDashboard extends CustomForm { private static final int CARD_TOTAL_CONFIG = 0; @@ -69,25 +69,21 @@ protected void loadData() { cardBox.setValueAt(CARD_TOTAL_CONFIG, String.valueOf(configurations.size()), "", - "", true); cardBox.setValueAt(CARD_SUCCESS_RATE, String.valueOf(snapshot.totalRequests()), - "Success rate", String.format("%.2f%%", snapshot.successRate()), true); cardBox.setValueAt(CARD_DURATION, String.format("%.2f min", BackupAnalyticsService.convertAvgDurationinMinutes(snapshot)), "", - "", true); cardBox.setValueAt(CARD_COMPRESSION, String.format("%.1f%%", snapshot.avgCompressionRate() * 100), "", - "", true); durationChart.setDataset(BackupAnalyticsService.buildDurationTrendDataset(snapshot.durationTrend(), Translations.get(TKey.DASHBOARD_CHART_AVG_DURATION))); @@ -201,7 +197,10 @@ public void setTranslations() { cardBox.setTitleTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_TOTAL_EXECUTIONS)); cardBox.setTitleTextAt(CARD_DURATION, Translations.get(TKey.DASHBOARD_CARD_AVG_DURATION)); cardBox.setTitleTextAt(CARD_COMPRESSION, Translations.get(TKey.DASHBOARD_CARD_COMPRESSION_RATE)); + cardBox.setDescriptionTextAt(CARD_TOTAL_CONFIG, ""); cardBox.setDescriptionTextAt(CARD_SUCCESS_RATE, Translations.get(TKey.DASHBOARD_CARD_SUCCESS_RATE)); + cardBox.setDescriptionTextAt(CARD_DURATION, ""); + cardBox.setDescriptionTextAt(CARD_COMPRESSION, ""); } private JLabel title; diff --git a/src/main/java/backupmanager/gui/forms/FormBackupTable.java b/src/main/java/backupmanager/gui/forms/FormBackupTable.java index 27488b43..1047cf72 100644 --- a/src/main/java/backupmanager/gui/forms/FormBackupTable.java +++ b/src/main/java/backupmanager/gui/forms/FormBackupTable.java @@ -47,7 +47,7 @@ import backupmanager.gui.svg.SVGButton; import net.miginfocom.swing.MigLayout; -@SystemForm(name = "Table", description = "table is a user interface component", tags = {"list"}) +@SystemForm(name = "Backup Configurations", description = "Backup configurations and management", tags = {"list", "backups"}) public class FormBackupTable extends CustomForm { private static final Logger logger = LoggerFactory.getLogger(FormBackupTable.class); @@ -383,9 +383,9 @@ private void handleAction(String action, JMenuItem interruptBackupPopupItem, JMe } } case "DUPLICATE" -> BackupPopupController.popupItemDuplicateBackup(backup); - case "RENAME" -> BackupPopupController.popupItemRenameBackup(backups, backup); - case "OPEN_TARGET" -> BackupPopupController.popupItemOpenInitialPath(backup); - case "OPEN_DEST" -> BackupPopupController.popupItemOpenDestinationPath(backup); + case "RENAME" -> BackupPopupController.popupItemRenameBackup(this, backups, backup); + case "OPEN_TARGET" -> BackupPopupController.popupItemOpenInitialPath(this, backup); + case "OPEN_DEST" -> BackupPopupController.popupItemOpenDestinationPath(this, backup); case "RUN_SINGLE" -> BackupPopupController.popupItemRunBackup(backup, tableService, interruptBackupPopupItem, RunBackupPopupItem); case "COPY_NAME" -> BackupPopupController.popupItemCopyBackupName(backup); case "COPY_TARGET" -> BackupPopupController.popupItemCopyInitialPath(backup); @@ -401,7 +401,7 @@ private void handleToggle() { return; ConfigurationBackup backup = getBackupFromTableRow(selectedRow); - BackupPopupController.popupItemAutoBackup(backup); + BackupPopupController.popupItemAutoBackup(this, backup); formRefresh(); } diff --git a/src/main/java/backupmanager/gui/forms/FormSetting.java b/src/main/java/backupmanager/gui/forms/FormSetting.java index a8428ce2..696de8fb 100644 --- a/src/main/java/backupmanager/gui/forms/FormSetting.java +++ b/src/main/java/backupmanager/gui/forms/FormSetting.java @@ -36,12 +36,12 @@ import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Managers.LanguageManager; -import backupmanager.gui.component.AccentColorIcon; -import backupmanager.gui.system.FormManager; -import backupmanager.gui.themes.PanelThemes; import backupmanager.Utils.AppPreferences; import backupmanager.Utils.SystemForm; import backupmanager.Utils.ToastUtils; +import backupmanager.gui.component.AccentColorIcon; +import backupmanager.gui.system.FormManager; +import backupmanager.gui.themes.PanelThemes; import net.miginfocom.swing.MigLayout; import raven.color.ColorPicker; import raven.modal.Drawer; @@ -478,8 +478,9 @@ private JPanel createThemes() { JPanel panel = new JPanel(new MigLayout("wrap,fill,insets 0", "[fill]", "[grow 0,fill]0[fill]")); final PanelThemes panelThemes = new PanelThemes(); JPanel panelHeader = new JPanel(new MigLayout("fillx,insets 3", "[grow 0]push[]")); - panelHeader.add(new JLabel("Themes")); - JComboBox<Object> combo = new JComboBox<>(new Object[]{"All", "Light", "Dark"}); + themeLabel = new JLabel("Themes"); + panelHeader.add(themeLabel); + JComboBox<Object> combo = new JComboBox<>(new Object[] {Translations.get(TKey.SETTINGS_THEME_ALL), Translations.get(TKey.SETTINGS_THEME_LIGHT), Translations.get(TKey.SETTINGS_THEME_DARK)} ); combo.addActionListener(e -> { panelThemes.updateThemesList(combo.getSelectedIndex()); }); @@ -515,6 +516,7 @@ public void setTranslations() { jrStyleOption1.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_RETTANGLE)); jrStyleOption2.setText(Translations.get(TKey.SETTINGS_LINE_STYLE_ELLIPSE)); chPaintLineColor.setText(Translations.get(TKey.SETTINGS_COLOR_OPTION_PAINTED)); + themeLabel.setText(Translations.get(TKey.SETTINGS_THEMES)); } private JTabbedPane tabbedPane; @@ -543,4 +545,5 @@ public void setTranslations() { private JRadioButton jrStyleOption1; private JRadioButton jrStyleOption2; private JCheckBox chPaintLineColor; + private JLabel themeLabel; } diff --git a/src/main/java/backupmanager/gui/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java index 231ee802..d6f2ddb5 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -7,10 +7,11 @@ import com.formdev.flatlaf.FlatClientProperties; +import backupmanager.Enums.ConfigKey; import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; -public class BackupManager extends JFrame{ +public class BackupManager extends JFrame { private static BackupManager instance; @@ -30,7 +31,8 @@ private void init() { getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); DrawerManager.getInstance().install(this); FormManager.install(this); - setSize(new Dimension(1366, 768)); + setSize(new Dimension(Integer.parseInt(ConfigKey.GUI_WIDTH.getValue()), Integer.parseInt(ConfigKey.GUI_HEIGHT.getValue()))); + setMinimumSize(new Dimension(Integer.parseInt(ConfigKey.GUI_MIN_WIDTH.getValue()), Integer.parseInt(ConfigKey.GUI_MIN_HEIGHT.getValue()))); setLocationRelativeTo(null); } } diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java index d62fd58a..bfeb31f2 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupManagerController.java @@ -1,12 +1,9 @@ package backupmanager.gui.frames.Controllers; -import java.awt.Dimension; -import java.awt.Toolkit; import java.util.ArrayList; import java.util.List; import backupmanager.Entities.ConfigurationBackup; -import backupmanager.Enums.ConfigKey; import backupmanager.Enums.Translations; import backupmanager.Enums.Translations.TKey; import backupmanager.Services.BackupService; @@ -29,15 +26,6 @@ public BackupManagerController(BackupService backupService, BackupTableDataServi this.backupTable = backupTable; } - - // TODO: enable it - public int[] getScreenSize() { - Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); - int width = Math.min((int) size.getWidth(), Integer.parseInt(ConfigKey.GUI_WIDTH.getValue())); - int height = Math.min((int) size.getHeight(), Integer.parseInt(ConfigKey.GUI_HEIGHT.getValue())); - return new int[]{width, height}; - } - public List<ConfigurationBackup> researchInTableAndGet(List<ConfigurationBackup> backups, String research) { List<ConfigurationBackup> tempBackups = new ArrayList<>(); research = research.toLowerCase(); diff --git a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java index c40c85d7..373d3dc6 100644 --- a/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java +++ b/src/main/java/backupmanager/gui/frames/Controllers/BackupPopupController.java @@ -12,6 +12,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JOptionPane; @@ -28,6 +29,7 @@ import backupmanager.Enums.Translations.TKey; import backupmanager.Helpers.BackupHelper; import backupmanager.Managers.ExceptionManager; +import backupmanager.Utils.ToastUtils; import backupmanager.database.Repositories.BackupConfigurationRepository; import backupmanager.gui.Table.BackupTableDataService; import backupmanager.gui.frames.BackupProgressGUI; @@ -40,20 +42,20 @@ public static void popupItemInterrupt() { // TODO: add it } - public static void popupItemRenameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { - renameBackup(backups, backup); + public static void popupItemRenameBackup(JComponent parent, List<ConfigurationBackup> backups, ConfigurationBackup backup) { + renameBackup(parent, backups, backup); } - public static void popupItemOpenDestinationPath(ConfigurationBackup backup) { - openFolder(backup.getDestinationPath()); + public static void popupItemOpenDestinationPath(JComponent parent, ConfigurationBackup backup) { + openFolder(parent, backup.getDestinationPath()); } - public static void popupItemOpenInitialPath(ConfigurationBackup backup) { - openFolder(backup.getTargetPath()); + public static void popupItemOpenInitialPath(JComponent parent, ConfigurationBackup backup) { + openFolder(parent, backup.getTargetPath()); } - public static void popupItemAutoBackup(ConfigurationBackup backup) { - BackupHelper.toggleAutomaticBackup(backup); + public static void popupItemAutoBackup(JComponent parent, ConfigurationBackup backup) { + BackupHelper.toggleAutomaticBackup(parent, backup); } public static void popupItemRunBackup(ConfigurationBackup backup, BackupTableDataService backupTable, JMenuItem interruptBackupPopupItem, JMenuItem RunBackupPopupItem) { @@ -125,10 +127,10 @@ private static int getIncrementalBackupNameValue(String backupName) { return max + 1; } - private static void renameBackup(List<ConfigurationBackup> backups, ConfigurationBackup backup) { + private static void renameBackup(JComponent parent, List<ConfigurationBackup> backups, ConfigurationBackup backup) { logger.info("Event --> backup renaming"); - String backupName = getBackupNameFromInputDialog(backups, backup.getName(), false); + String backupName = getBackupNameFromInputDialog(parent, backups, backup.getName(), false); if (backupName == null || backupName.isEmpty()) return; backup.setName(backupName); @@ -136,7 +138,7 @@ private static void renameBackup(List<ConfigurationBackup> backups, Configuratio BackupHelper.updateBackup(backup); } - private static String getBackupNameFromInputDialog(List<ConfigurationBackup> backups, String oldName, boolean canOverwrite) { + private static String getBackupNameFromInputDialog(JComponent parent, List<ConfigurationBackup> backups, String oldName, boolean canOverwrite) { while (true) { String backupName = JOptionPane.showInputDialog(null, Translations.get(TKey.BACKUP_NAME_INPUT), oldName); @@ -159,7 +161,7 @@ private static String getBackupNameFromInputDialog(List<ConfigurationBackup> bac } } else { logger.warn("Backup name '{}' is already in use", backupName); - JOptionPane.showMessageDialog(null, Translations.get(TKey.BACKUP_NAME_ALREADY_USED_MESSAGE), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_BACKUP_NAME_ALREADY_USED)); } } else { return backupName; // Return valid name @@ -167,7 +169,7 @@ private static String getBackupNameFromInputDialog(List<ConfigurationBackup> bac } } - private static void openFolder(String path) { + private static void openFolder(JComponent parent, String path) { logger.info("Event --> opening folder"); File folder = new File(path); @@ -191,7 +193,7 @@ private static void openFolder(String path) { } } else { logger.warn("The folder does not exist or is invalid"); - JOptionPane.showMessageDialog(null, Translations.get(TKey.ERROR_MESSAGE_FOR_FOLDER_NOT_EXISTING), Translations.get(TKey.ERROR_GENERIC_TITLE), JOptionPane.ERROR_MESSAGE); + ToastUtils.showError(parent, Translations.get(TKey.TOAST_FOLDER_NOT_EXISTING)); } } } diff --git a/src/main/java/backupmanager/gui/menu/DrawerManager.java b/src/main/java/backupmanager/gui/menu/DrawerManager.java index b43878f6..631844a1 100644 --- a/src/main/java/backupmanager/gui/menu/DrawerManager.java +++ b/src/main/java/backupmanager/gui/menu/DrawerManager.java @@ -12,6 +12,7 @@ public class DrawerManager implements ITranslatable { private DrawerPanel drawerPanel; private MenuItem[] menuItems; + private JFrame parent; private static DrawerManager instance; @@ -22,21 +23,14 @@ public static DrawerManager getInstance() { return instance; } - public DrawerPanel getDrawer() { - return drawerPanel; - } - - public MenuItem[] getMenuItems() { - return menuItems; - } - public void install(JFrame frame) { + parent = frame; MyDrawerBuilder builder = new MyDrawerBuilder(); drawerPanel = builder.createDrawer(); menuItems = builder.getSimpleMenuOption().getMenus(); - Drawer.installDrawer(frame, builder); + Drawer.installDrawer(parent, builder); } private DrawerManager() { @@ -49,6 +43,18 @@ private void rebuildDrawer() { drawerPanel = builder.createDrawer(); } + public DrawerPanel getDrawer() { + return drawerPanel; + } + + public MenuItem[] getMenuItems() { + return menuItems; + } + + public JFrame getParent() { + return parent; + } + // it's not the best rebuild the menu every time the language is changed but right now there is no a method to update // the MenuItem title textin the raven library @Override diff --git a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java index 05d33624..11c8e6f4 100644 --- a/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java +++ b/src/main/java/backupmanager/gui/menu/MyDrawerBuilder.java @@ -104,7 +104,7 @@ private static void initMenuActions() { FormManager.showForm(AllForms.getForm(FormBackupDashboard.class))); menuActionMap.put(MenuItems.Export, () -> - ExportManager.exportAsCSV(BackupConfigurationRepository.getBackupList(), ConfigurationBackup.getCSVHeader())); + ExportManager.exportAsCSV(DrawerManager.getInstance().getParent(), BackupConfigurationRepository.getBackupList(), ConfigurationBackup.getCSVHeader())); menuActionMap.put(MenuItems.Settings, () -> FormManager.showForm(AllForms.getForm(FormSetting.class))); @@ -113,19 +113,19 @@ private static void initMenuActions() { FormManager.showForm(AllForms.getForm(FormHistory.class))); menuActionMap.put(MenuItems.InfoPage, () -> - WebsiteManager.openWebSite(ConfigKey.INFO_PAGE_LINK.getValue())); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), ConfigKey.INFO_PAGE_LINK.getValue())); menuActionMap.put(MenuItems.BugReport, () -> - WebsiteManager.openWebSite(ConfigKey.ISSUE_PAGE_LINK.getValue())); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), ConfigKey.ISSUE_PAGE_LINK.getValue())); menuActionMap.put(MenuItems.ContactUs, () -> - WebsiteManager.sendEmail()); + WebsiteManager.sendEmail(DrawerManager.getInstance().getParent())); menuActionMap.put(MenuItems.PaypalDonate, () -> - WebsiteManager.openWebSite(ConfigKey.DONATE_PAYPAL_LINK.getValue())); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), ConfigKey.DONATE_PAYPAL_LINK.getValue())); menuActionMap.put(MenuItems.BuymeacoffeeDonate, () -> - WebsiteManager.openWebSite(ConfigKey.DONATE_BUYMEACOFFE_LINK.getValue())); + WebsiteManager.openWebSite(DrawerManager.getInstance().getParent(), ConfigKey.DONATE_BUYMEACOFFE_LINK.getValue())); menuActionMap.put(MenuItems.Subscription, () -> FormManager.showSubscription()); @@ -248,11 +248,15 @@ private static List<MenuItem> buildMenuItems() { // Backup menu Item backupItem = createMenuItem(Translations.get(TKey.BACKUP_TABLE), "forms.svg", MenuItems.BackupList, FormBackupTable.class); - if (config.isMenuItemEnabled(MenuItems.Import.name())) + if (config.isMenuItemEnabled(MenuItems.Import.name())) { backupItem.subMenu(Translations.get(TKey.IMPORT_BACKUP)); + bindSubMenu(Translations.get(TKey.IMPORT_BACKUP), MenuItems.Import); + } - if (config.isMenuItemEnabled(MenuItems.Export.name())) + if (config.isMenuItemEnabled(MenuItems.Export.name())) { backupItem.subMenu(Translations.get(TKey.EXPORT_BACKUP)); + bindSubMenu(Translations.get(TKey.EXPORT_BACKUP), MenuItems.Export); + } itemList.add(backupItem); diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index 2f3e6084..8c78711a 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -207,12 +207,12 @@ private void executeBackup() { (int) maxToKeeSpinner.getValue() ); } catch (BackupAlreadyRunningException e) { - // no handle + ToastUtils.showWarning(this, Translations.get(TKey.TOAST_BACKUP_ALREADY_IN_PROGRESS)); } } private void toggleAutomaticBackup() { - if (entryController.toggleAutomaticBackup(txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), txtNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue())) { + if (entryController.toggleAutomaticBackup(this, txtBackupName.getText(), txtTargetPath.getText(), txtDestinationPath.getText(), txtNotes.getText(), automaticBackupBtn.isSelected(), (int) maxToKeeSpinner.getValue())) { setAutoBackupOn(entryController.getCurrentBackup()); automaticBackupBtn.setSelected(true); timeIntervalBtn.setToolTipText(entryController.getCurrentBackup().getTimeIntervalBackup().toString()); @@ -272,7 +272,7 @@ private void disableAutoBackup(ConfigurationBackup backup) { } private void openBackupActivationMessage(TimeInterval newtimeInterval) { - entryController.handleOpenBackupActivationMessage(newtimeInterval, txtTargetPath.getText(), txtDestinationPath.getText()); + entryController.handleOpenBackupActivationMessage(this, newtimeInterval, txtTargetPath.getText(), txtDestinationPath.getText()); } private void setAutoBackupPreference(boolean option) { diff --git a/src/main/resources/res/config/config.json b/src/main/resources/res/config/config.json index e4f650f5..60c69c57 100644 --- a/src/main/resources/res/config/config.json +++ b/src/main/resources/res/config/config.json @@ -13,28 +13,26 @@ "SHARD_WEBSITE": "https://www.shardpc.it/", "LOGO_IMG": "/res/img/logo.png", "VERSION": "3.0.0", - "GUI_WIDTH": "982", - "GUI_HEIGHT": "715", + "GUI_WIDTH": "1366", + "GUI_HEIGHT": "768", + "GUI_MIN_WIDTH": "800", + "GUI_MIN_HEIGHT": "600", "MenuItems": { "BugReport": true, - "Preferences": true, + "Settings": true, "Donate": true, "PaypalDonate": true, "BuymeacoffeeDonate": true, "History": true, "InfoPage": true, - "New": true, - "Quit": true, "Import": false, "Export": true, - "Share": true, "Support": true, - "Website": true, "ContactUs": true, + "Website": true, "BackupList": true, "Dashboard": true, - "Settings": true, "About": true }, "BackupService": { diff --git a/src/main/resources/res/languages/deu.json b/src/main/resources/res/languages/deu.json index 1c3ee4d7..07534e18 100644 --- a/src/main/resources/res/languages/deu.json +++ b/src/main/resources/res/languages/deu.json @@ -27,70 +27,32 @@ }, "Dialogs": { "WarningGenericTitle": "Warnung", - "SuccessfullyExportedToCsvMessage": "Backups erfolgreich als CSV exportiert!", "ErrorMessageForPathNotExisting": "Ein oder beide Pfade existieren nicht!", - "ConfirmationDeletionMessage": "Sind Sie sicher, dass Sie die ausgewählten Zeilen löschen möchten?", "ExceptionMessageReportMessage": "Bitte melden Sie diesen Fehler, entweder mit einem Screenshot oder indem Sie den folgenden Fehlertext kopieren (es ist hilfreich, eine Beschreibung der durchgeführten Aktionen vor dem Fehler anzugeben):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Fehler beim Exportieren der Backups in CSV: ", "ErrorGenericTitle": "Fehler", - "ErrorMessageForExportingToPdf": "Fehler beim Exportieren der Backups in PDF: ", - "BackupListCorrectlyImportedTitle": "Menü Importieren", - "ErrorWrongTimeInterval": "Das Zeitintervall ist nicht korrekt", - "ErrorMessageOpenHistoryFile": "Fehler beim Öffnen der Verlaufsdatei.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", "ErrorMessageForSamePaths": "Der Anfangspfad und der Zielpfad dürfen nicht gleich sein. Bitte wählen Sie unterschiedliche Pfade!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Freigabelink wurde in die Zwischenablage kopiert!", - "ErrorMessageForSavingFileWithPathsEmpty": "Die Datei konnte nicht gespeichert werden. Sowohl der Anfangs- als auch der Zielpfad müssen angegeben werden und dürfen nicht leer sein", - "BackupListCorrectlyExportedMessage": "Backup-Liste erfolgreich auf den Desktop exportiert!", - "BackupSavedCorrectlyMessage": "erfolgreich gespeichert!", "SettedEveryMessage": "\nWird eingestellt auf alle", "WarningBackupAlreadyInProgressMessage": "Es läuft bereits eine Sicherung. Es ist nicht möglich, parallele Sicherungen durchzuführen.", "WarningShortTimeIntervalMessage": "Das ausgewählte Zeitintervall ist sehr kurz. Für eine optimale Leistung empfehlen wir, es auf mindestens eine Stunde einzustellen. Möchten Sie dennoch fortfahren?", "ConfirmationMessageCancelAutoBackup": "Sind Sie sicher, dass Sie automatische Backups für diesen Eintrag deaktivieren möchten?", "ErrorMessageCountingFiles": "Fehler beim Zählen der zu sichernden Dateien.", - "ErrorMessageForFolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Fehler beim Backup-Vorgang: Der Anfangspfad ist falsch!", "ErrorMessageInputMissingGeneric": "Eingabe fehlt!", "BackupNameInput": "Name des Backups", "CsvNameMessageInput": "Geben Sie den Namen der CSV-Datei ein.", - "ConfirmationDeletionTitle": "Löschen bestätigen", - "ErrorMessageForWrongFileExtensionMessage": "Fehler: Bitte wählen Sie eine gültige JSON-Datei aus.", "ErrorMessageZippingGeneric": "Fehler beim Komprimieren der Dateien.", - "ErrorMessageNotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht.", "ErrorMessageZippingIO": "Fehler beim Komprimieren der Dateien: E/A-Fehler.", - "SuccessfullyExportedToPdfMessage": "Backups erfolgreich als PDF exportiert!", "DuplicatedFileNameMessage": "Datei existiert bereits. Überschreiben?", "AutoBackupActivatedMessage": "Auto-Backup wurde aktiviert", + "AutoBackup": "Automatische Sicherung", "DaysMessage": " Tage", "ExceptionMessageReportButton": "Problem melden", - "ErrorMessageInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche.", - "ExceptionMessageClipboardMessage": "Fehlertext wurde in die Zwischenablage kopiert.", "SuccessGenericTitle": "Erfolg", - "ConfirmationMessageForClear": "Sind Sie sicher, dass Sie die Felder bereinigen möchten?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Backup-Liste erfolgreich importiert!", "DuplicatedBackupNameMessage": "Ein Backup mit demselben Namen existiert bereits. Möchten Sie es überschreiben?", "ExceptionMessageClipboardButton": "In Zwischenablage kopieren", "ErrorMessageZippingSecurity": "Fehler beim Komprimieren der Dateien: Sicherheitsfehler.", "InterruptBackupProcessMessage": "Sind Sie sicher, dass Sie dieses Backup abbrechen möchten?", - "BackupListCorrectlyExportedTitle": "Menü Exportieren", - "ConfirmationMessageForUnsavedChanges": "Es gibt nicht gespeicherte Änderungen. Möchten Sie sie speichern, bevor Sie zu einem anderen Backup wechseln?", - "ExceptionMessageTitle": "Fehler...", - "PdfNameMessageInput": "Geben Sie den Namen der PDF-Datei ein.", - "ErrorMessageNotSupportedEmail": "Ihr System unterstützt das Senden von E-Mails direkt aus dieser Anwendung nicht.", - "ErrorMessageForSavingFile": "Fehler beim Speichern der Datei", - "ErrorMessageUnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.", "ConfirmationMessageBeforeDeleteBackup": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", - "ErrorMessageOpeningWebsite": "Die Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut.", - "ErrorSavingBackupMessage": "Fehler beim Speichern des Backups", - "ConfirmationRequiredTitle": "Bestätigung erforderlich", - "BackupNameAlreadyUsedMessage": "Backup-Name bereits verwendet!", - "ErrorMessageForWrongFileExtensionTitle": "Ungültige Datei", - "BackupSavedCorrectlyTitle": "Backup gespeichert" + "ConfirmationRequiredTitle": "Bestätigung erforderlich" }, "TimePickerDialog": { "TimeIntervalTitle": "Zeitintervall für Auto-Backup", @@ -119,29 +81,17 @@ "StatusCompleted": "Backup abgeschlossen!" }, "Menu": { - "Import": "Backup-Liste importieren", - "Save": "Speichern", "About": "Über", "File": "Datei", - "Support": "Support", "BugReport": "Fehler melden", - "Quit": "Beenden", "Options": "Optionen", "New": "Neu", - "Clear": "Löschen", - "Share": "Teilen", - "Preferences": "Einstellungen", - "Website": "Webseite", - "SaveWithName": "Speichern unter", - "Export": "Backup-Liste exportieren", "Help": "Hilfe", - "InfoPage": "Info", "History": "Verlauf", "SubmenuMain": "HAUPT", "SubmenuOther": "ANDERE", "Donate": "Projekt unterstützen", "Backups": "Backup-Liste", - "CreateBackup": "Neues Backup erstellen", "ImportBackup": "Backups aus CSV importieren", "ExportBackup": "Backups in CSV exportieren", "Dashboard": "Dashboard", @@ -153,25 +103,21 @@ "BackupList": { "NotesDetail": "Notizen", "BackupNameDetail": "Backup-Name", - "ExportAsPdfTooltip": "Exportieren als PDF", "EditPopup": "Bearbeiten", "ResearchBarTooltip": "Suchleiste", "InitialPathDetail": "Anfangspfad", - "ExportAs": "Exportieren als: ", "RenameBackupPopup": "Backup umbenennen", "NextBackupDateDetail": "NächstesBackup", "AutoBackupPopup": "Auto-Backup", "BackupNameColumn": "Backup-Name", "BackupPopup": "Backup", "BackupCountDetail": "Backup-Anzahl", - "ExportAsCsvTooltip": "Exportieren als CSV", "InitialPathColumn": "Anfangspfad", "MaxBackupsToKeepDetail": "MaximaleSicherungenBehalten", "DeletePopup": "Löschen", "OpenDestinationFolderPopup": "Zielpfad öffnen", "LastBackupColumn": "Letztes Backup", "SingleBackupPopup": "Einzel-Backup ausführen", - "AddBackupTooltip": "Neues Backup hinzufügen", "CopyTextPopup": "Text kopieren", "DestinationPathDetail": "Zielpfad", "CopyDestinationPathPopup": "Zielpfad kopieren", @@ -193,10 +139,6 @@ "TimeIntervalColumn": "Intervall (tt.HH:mm)", "MaxBackupsColumn": "Maximale Anzahl aufzubewahrender Backups" }, - "TabbedFrames": { - "BackupEntry": "Backup-Eintrag", - "BackupList": "Backup-Liste" - }, "UserDialog": { "EmailConfirmationBody": "Hallo [UserName],\n\nVielen Dank, dass Sie Backup Manager heruntergeladen und registriert haben - Ihr neues Tool für eine sichere und effiziente Verwaltung Ihrer Backups!\n\nDies ist eine automatisierte E-Mail, die zur Bestätigung Ihrer Registrierung gesendet wurde. Wir werden Sie nur per E-Mail kontaktieren, um Sie über neue Versionen oder wichtige Updates der Anwendung zu informieren.\n\nFalls Sie Fragen haben, Unterstützung benötigen oder Vorschläge machen möchten, stehen wir Ihnen jederzeit gerne zur Verfügung. Sie können uns unter [SupportEmail] erreichen.\n\nVielen Dank nochmals, dass Sie sich für Backup Manager entschieden haben, und viel Erfolg bei der Verwaltung Ihrer Backups!\n\nMit freundlichen Grüßen,\nDas Backup Manager-Team", "EmailConfirmationSubject": "Vielen Dank, dass Sie sich für Backup Manager entschieden haben!", @@ -213,9 +155,7 @@ "TimePickerTooltip": "Zeitwähler", "MaxBackupsToKeepTooltip": "Maximale Anzahl an Sicherungen, bevor die ältesten entfernt werden.", "BackupName": "Sicherungsname", - "AutoBackupButton": "Auto-Backup", "InitialPathTooltip": "(Erforderlich) Anfangspfad", - "CurrentFile": "Aktuelle Datei", "InitialFileChooserTooltip": "Dateiexplorer öffnen", "LastBackup": "Letztes Backup", "SingleBackupButton": "Einzel-Backup", @@ -228,7 +168,6 @@ "NotesTooltip": "(Optional) Backup-Beschreibung", "DestinationFileChooserTooltip": "Dateiexplorer öffnen", "BackupNameTooltip": "(Erforderlich) Sicherungsname", - "PageTitle": "Backup-Eintrag", "Notes": "Notizen", "PageSubtitleCreate": "Backup erstellen", "PageSubtitleEdit": "Backup bearbeiten", @@ -285,7 +224,11 @@ "SettingsLineStyleLine": "Linie", "SettingsLineStyleCurved": "Gebogen", "SettingsColorOptionLayout": "Farboption", - "SettingsColorOptionPainted": "Ausgewählte Linie einfärben" + "SettingsColorOptionPainted": "Ausgewählte Linie einfärben", + "SettingsThemes": "Designs", + "SettingsThemeAll": "Alle", + "SettingsThemeLight": "Hell", + "SettingsThemeDark": "Dunkel" }, "SearchBar": { "SearcTitle": "Suchen...", @@ -307,6 +250,18 @@ "LanguageChange": "Einige Änderungen werden nach einem Neustart der Anwendung wirksam", "MissingDataLoginError": "Bitte füllen Sie alle erforderlichen Felder aus", "WrongEmailLoginError": "Die angegebene E-Mail-Adresse ist ungültig. Bitte geben Sie eine korrekte ein", - "LoginOk": "Willkommen! Sie haben sich erfolgreich angemeldet" + "LoginOk": "Willkommen! Sie haben sich erfolgreich angemeldet", + "ErrorTextClipboard": "Der Fehlertext wurde in die Zwischenablage kopiert", + "CsvExport": "Backups erfolgreich als CSV exportiert!", + "CsvExportError": "Fehler beim Exportieren der Backups als CSV", + "CsvExportInvalidFilename": "Ungültiger Dateiname. Verwenden Sie nur alphanumerische Zeichen, Bindestriche und Unterstriche", + "NotSupportedEmail": "Ihr System unterstützt das direkte Senden von E-Mails aus dieser Anwendung nicht", + "UnableToSendEmail": "E-Mail konnte nicht gesendet werden. Bitte versuchen Sie es später erneut", + "NotSupportedEmailGeneric": "Ihr System unterstützt das Senden von E-Mails nicht", + "OpeningWebsiteError": "Webseite konnte nicht geöffnet werden. Bitte versuchen Sie es erneut", + "FolderNotExisting": "Der Ordner existiert nicht oder ist ungültig", + "BackupNameAlreadyUsed": "Backup-Name bereits verwendet!", + "BackupAlreadyInProgress": "Es läuft bereits ein Backup. Parallele Backups sind nicht möglich", + "BackupPathsEmptyError": "The initial path and destination path cannot be empty!" } } diff --git a/src/main/resources/res/languages/eng.json b/src/main/resources/res/languages/eng.json index 36317904..b456d698 100644 --- a/src/main/resources/res/languages/eng.json +++ b/src/main/resources/res/languages/eng.json @@ -27,70 +27,32 @@ }, "Dialogs": { "WarningGenericTitle": "Warning", - "SuccessfullyExportedToCsvMessage": "Backups exported to CSV successfully!", "ErrorMessageForPathNotExisting": "One or both paths do not exist!", - "ConfirmationDeletionMessage": "Are you sure you want to delete the selected rows?", "ExceptionMessageReportMessage": "Please report this error, either with an image of the screen or by copying the following error text (it is appreciable to provide a description of the operations performed before the error):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Error exporting backups to CSV: ", "ErrorGenericTitle": "Error", - "ErrorMessageForExportingToPdf": "Error exporting backups to PDF: ", - "BackupListCorrectlyImportedTitle": "Menu Import", - "ErrorWrongTimeInterval": "The time interval is not correct", - "ErrorMessageOpenHistoryFile": "Error opening history file.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", "ErrorMessageForSamePaths": "The initial path and destination path cannot be the same. Please choose different paths!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Share link copied to clipboard!", - "ErrorMessageForSavingFileWithPathsEmpty": "Unable to save the file. Both the initial and destination paths must be specified and cannot be empty", - "BackupListCorrectlyExportedMessage": "Backup list successfully exported to the Desktop!", - "BackupSavedCorrectlyMessage": "saved successfully!", "SettedEveryMessage": "\nIs setted every", "WarningBackupAlreadyInProgressMessage": "There is already a backup in progress. It is not possible to perform parallel backups", "WarningShortTimeIntervalMessage": "The selected time interval is very short. For optimal performance, we recommend setting it to at least one hour. Do you still want to proceed?", "ConfirmationMessageCancelAutoBackup": "Are you sure you want to cancel automatic backups for this entry?", "ErrorMessageCountingFiles": "Error occurred while calculating files to back up.", - "ErrorMessageForFolderNotExisting": "The folder does not exist or is invalid", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Error during the backup operation: the initial path is incorrect!", "ErrorMessageInputMissingGeneric": "Input Missing!", "BackupNameInput": "Name of the backup", "CsvNameMessageInput": "Enter the name of the CSV file.", - "ConfirmationDeletionTitle": "Confirm Deletion", - "ErrorMessageForWrongFileExtensionMessage": "Error: Please select a valid JSON file.", "ErrorMessageZippingGeneric": "Error occurred while zipping files.", - "ErrorMessageNotSupportedEmailGeneric": "Your system does not support sending emails.", "ErrorMessageZippingIO": "Error occurred while zipping files: I/O error.", - "SuccessfullyExportedToPdfMessage": "Backups exported to PDF successfully!", "DuplicatedFileNameMessage": "File already exists. Overwrite?", "AutoBackupActivatedMessage": "Auto Backup has been activated", + "AutoBackup": "Auto Backup", "DaysMessage": " days", "ExceptionMessageReportButton": "Report the Problem", - "ErrorMessageInvalidFilename": "Invalid file name. Use only alphanumeric characters, dashes, and underscores.", - "ExceptionMessageClipboardMessage": "Error text has been copied to the clipboard.", "SuccessGenericTitle": "Success", - "ConfirmationMessageForClear": "Are you sure you want to clean the fields?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Backup list successfully imported!", "DuplicatedBackupNameMessage": "A backup with the same name already exists, do you want to overwrite it?", "ExceptionMessageClipboardButton": "Copy to clipboard", "ErrorMessageZippingSecurity": "Error occurred while zipping files: Security error.", "InterruptBackupProcessMessage": "Are you sure you want to stop this backup?", - "BackupListCorrectlyExportedTitle": "Menu Export", - "ConfirmationMessageForUnsavedChanges": "There are unsaved changes, do you want to save them before moving to another backup?", - "ExceptionMessageTitle": "Error...", - "PdfNameMessageInput": "Enter the name of the PDF file.", - "ErrorMessageNotSupportedEmail": "Your system does not support sending emails directly from this application.", - "ErrorMessageForSavingFile": "Error saving file", - "ErrorMessageUnableToSendEmail": "Unable to send email. Please try again later.", "ConfirmationMessageBeforeDeleteBackup": "Are you sure you want to delete this item? Please note, this action cannot be undone", - "ErrorMessageOpeningWebsite": "Failed to open the web page. Please try again.", - "ErrorSavingBackupMessage": "Error saving backup", - "ConfirmationRequiredTitle": "Confirmation required", - "BackupNameAlreadyUsedMessage": "Backup name already used!", - "ErrorMessageForWrongFileExtensionTitle": "Invalid File", - "BackupSavedCorrectlyTitle": "Backup saved" + "ConfirmationRequiredTitle": "Confirmation required" }, "TimePickerDialog": { "TimeIntervalTitle": "Time interval for auto backup", @@ -119,29 +81,17 @@ "StatusCompleted": "Backup completed!" }, "Menu": { - "Import": "Import backup list", - "Save": "Save", "About": "About", "File": "File", - "Support": "Support", "BugReport": "Report a bug", - "Quit": "Quit", "Options": "Options", "New": "New", - "Clear": "Clear", - "Share": "Share", - "Preferences": "Preferences", - "Website": "Website", - "SaveWithName": "Save with name", - "Export": "Export backup list", "Help": "Help", - "InfoPage": "Info", "History": "History", "SubmenuMain": "MAIN", "SubmenuOther": "OTHER", "Donate": "Support the project", "Backups": "Backup List", - "CreateBackup": "Create new backup", "ImportBackup": "Import backups from Csv", "ExportBackup": "Export backups to Csv", "Dashboard": "Dashboard", @@ -153,25 +103,21 @@ "BackupList": { "NotesDetail": "Notes", "BackupNameDetail": "BackupName", - "ExportAsPdfTooltip": "Export as PDF", "EditPopup": "Edit", "ResearchBarTooltip": "Research bar", "InitialPathDetail": "InitialPath", - "ExportAs": "Export as: ", "RenameBackupPopup": "Rename backup", "NextBackupDateDetail": "NextBackup", "AutoBackupPopup": "Auto backup", "BackupNameColumn": "Backup Name", "BackupPopup": "Backup", "BackupCountDetail": "BackupCount", - "ExportAsCsvTooltip": "Export as CSV", "InitialPathColumn": "Initial Path", "MaxBackupsToKeepDetail": "MaxBackupsToKeep", "DeletePopup": "Delete", "OpenDestinationFolderPopup": "Open destination path", "LastBackupColumn": "Last Backup", "SingleBackupPopup": "Run single backup", - "AddBackupTooltip": "Add new backup", "CopyTextPopup": "Copy text", "DestinationPathDetail": "DestinationPath", "CopyDestinationPathPopup": "Copy destination path", @@ -193,10 +139,6 @@ "TimeIntervalColumn": "Interval (gg.HH:mm)", "MaxBackupsColumn": "Max Backups To Keep" }, - "TabbedFrames": { - "BackupEntry": "BackupEntry", - "BackupList": "BackupList" - }, "UserDialog": { "EmailConfirmationBody": "Hi [UserName],\n\nThank you for downloading and registering Backup Manager, your new tool for secure and efficient backup management!\n\nThis is an automated email sent to confirm your registration. We will contact you by email only to inform you about new releases or important updates of the application.\n\nIn the meantime, if you have any questions, need assistance, or have suggestions, we are always here for you. You can reach us at [SupportEmail].\n\nThank you again for choosing Backup Manager, and enjoy managing your backups!\n\nBest regards,\nThe Backup Manager Team", "EmailConfirmationSubject": "Thank you for choosing Backup Manager!", @@ -213,9 +155,7 @@ "TimePickerTooltip": "Time picker", "MaxBackupsToKeepTooltip": "Maximum number of backups before removing the oldest.", "BackupName": "Backup name", - "AutoBackupButton": "Auto Backup", "InitialPathTooltip": "(Required) Initial path", - "CurrentFile": "Current file", "InitialFileChooserTooltip": "Open file explorer", "LastBackup": "Last backup", "SingleBackupButton": "Single Backup", @@ -228,7 +168,6 @@ "NotesTooltip": "(Optional) Backup description", "DestinationFileChooserTooltip": "Open file explorer", "BackupNameTooltip": "(Required) Backup name", - "PageTitle": "Backup Entry", "Notes": "Notes", "PageSubtitleCreate": "Create Backup", "PageSubtitleEdit": "Edit Backup", @@ -285,7 +224,11 @@ "SettingsLineStyleLine": "Line", "SettingsLineStyleCurved": "Curved", "SettingsColorOptionLayout": "Color option", - "SettingsColorOptionPainted": "Paint selected line color" + "SettingsColorOptionPainted": "Paint selected line color", + "SettingsThemes": "Themes", + "SettingsThemeAll": "All", + "SettingsThemeLight": "Light", + "SettingsThemeDark": "Dark" }, "SearchBar": { "SearcTitle": "Search...", @@ -307,6 +250,18 @@ "LanguageChange": "Some changes will take effect after restarting the application", "MissingDataLoginError": "Please fill in all the required fields", "WrongEmailLoginError": "The provided email address is invalid. Please provide a correct one", - "LoginOk": "Welcome! You have successfully signed in" + "LoginOk": "Welcome! You have successfully signed in", + "ErrorTextClipboard": "Error text has been copied to the clipboard", + "CsvExport": "Backups exported to CSV successfully!", + "CsvExportError": "Error exporting backups to CSV", + "CsvExportInvalidFilename": "Invalid file name. Use only alphanumeric characters, dashes, and underscores", + "NotSupportedEmail": "Your system does not support sending emails directly from this application", + "UnableToSendEmail": "Unable to send email. Please try again later", + "NotSupportedEmailGeneric": "Your system does not support sending emails", + "OpeningWebsiteError": "Failed to open the web page. Please try again", + "FolderNotExisting": "The folder does not exist or is invalid", + "BackupNameAlreadyUsed": "Backup name already used!", + "BackupAlreadyInProgress": "There is already a backup in progress. It is not possible to perform parallel backups", + "BackupPathsEmptyError": "The initial path and destination path cannot be empty!" } } diff --git a/src/main/resources/res/languages/esp.json b/src/main/resources/res/languages/esp.json index 93f8c21f..4e6e4a2d 100644 --- a/src/main/resources/res/languages/esp.json +++ b/src/main/resources/res/languages/esp.json @@ -27,70 +27,32 @@ }, "Dialogs": { "WarningGenericTitle": "Advertencia", - "SuccessfullyExportedToCsvMessage": "¡Copias de seguridad exportadas a CSV con éxito!", "ErrorMessageForPathNotExisting": "¡Una o ambas rutas no existen!", - "ConfirmationDeletionMessage": "¿Está seguro de que desea eliminar las filas seleccionadas?", "ExceptionMessageReportMessage": "Por favor, informe de este error, ya sea con una captura de pantalla o copiando el siguiente texto del error (se agradece proporcionar una descripción de las acciones realizadas antes del error):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Error al exportar copias de seguridad a CSV: ", "ErrorGenericTitle": "Error", - "ErrorMessageForExportingToPdf": "Error al exportar copias de seguridad a PDF: ", - "BackupListCorrectlyImportedTitle": "Menú Importar", - "ErrorWrongTimeInterval": "El intervalo de tiempo no es correcto", - "ErrorMessageOpenHistoryFile": "Error al abrir el archivo de historial.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", "ErrorMessageForSamePaths": "La ruta inicial y la de destino no pueden ser iguales. ¡Elija rutas diferentes!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "¡Enlace de compartir copiado al portapapeles!", - "ErrorMessageForSavingFileWithPathsEmpty": "No se puede guardar el archivo. Tanto la ruta inicial como la de destino deben especificarse y no pueden estar vacías", - "BackupListCorrectlyExportedMessage": "¡Lista de copias de seguridad exportada correctamente al escritorio!", - "BackupSavedCorrectlyMessage": "guardada con éxito.", "SettedEveryMessage": "\nSe establece cada", "WarningBackupAlreadyInProgressMessage": "Ya hay una copia de seguridad en progreso. No es posible realizar copias de seguridad en paralelo.", "WarningShortTimeIntervalMessage": "El intervalo de tiempo seleccionado es muy corto. Para un funcionamiento óptimo, recomendamos configurarlo en al menos una hora. ¿Quieres continuar de todos modos?", "ConfirmationMessageCancelAutoBackup": "¿Está seguro de que desea cancelar las copias automáticas para esta entrada?", "ErrorMessageCountingFiles": "Error al calcular los archivos para la copia de seguridad.", - "ErrorMessageForFolderNotExisting": "La carpeta no existe o no es válida", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Error en la operación de copia: ¡la ruta inicial es incorrecta!", "ErrorMessageInputMissingGeneric": "¡Faltan datos de entrada!", "BackupNameInput": "Nombre de la copia", "CsvNameMessageInput": "Introduce el nombre del archivo CSV.", - "ConfirmationDeletionTitle": "Confirmar Eliminación", - "ErrorMessageForWrongFileExtensionMessage": "Error: Seleccione un archivo JSON válido.", "ErrorMessageZippingGeneric": "Error al comprimir los archivos.", - "ErrorMessageNotSupportedEmailGeneric": "Su sistema no admite el envío de correos electrónicos.", "ErrorMessageZippingIO": "Error al comprimir los archivos: error de E/S.", - "SuccessfullyExportedToPdfMessage": "¡Copias de seguridad exportadas a PDF con éxito!", "DuplicatedFileNameMessage": "El archivo ya existe. ¿Sobrescribir?", "AutoBackupActivatedMessage": "La copia automática se ha activado", + "AutoBackup": "Copia de seguridad automática", "DaysMessage": " días", "ExceptionMessageReportButton": "Informar del problema", - "ErrorMessageInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos.", - "ExceptionMessageClipboardMessage": "El texto del error se ha copiado al portapapeles.", "SuccessGenericTitle": "Éxito", - "ConfirmationMessageForClear": "¿Está seguro de que desea limpiar los campos?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "¡Lista de copias de seguridad importada correctamente!", "DuplicatedBackupNameMessage": "Ya existe una copia con el mismo nombre. ¿Desea sobrescribirla?", "ExceptionMessageClipboardButton": "Copiar al portapapeles", "ErrorMessageZippingSecurity": "Error al comprimir los archivos: Error de seguridad.", "InterruptBackupProcessMessage": "¿Está seguro de que desea detener esta copia?", - "BackupListCorrectlyExportedTitle": "Menú Exportar", - "ConfirmationMessageForUnsavedChanges": "Hay cambios no guardados. ¿Desea guardarlos antes de pasar a otra copia de seguridad?", - "ExceptionMessageTitle": "Error...", - "PdfNameMessageInput": "Introduce el nombre del archivo PDF.", - "ErrorMessageNotSupportedEmail": "Su sistema no admite el envío de correos electrónicos directamente desde esta aplicación.", - "ErrorMessageForSavingFile": "Error al guardar el archivo", - "ErrorMessageUnableToSendEmail": "No se pudo enviar el correo electrónico. Por favor, intente de nuevo más tarde.", "ConfirmationMessageBeforeDeleteBackup": "¿Está seguro de que desea eliminar este elemento? Tenga en cuenta que esta acción no se puede deshacer.", - "ErrorMessageOpeningWebsite": "No se pudo abrir la página web. Por favor, intente de nuevo.", - "ErrorSavingBackupMessage": "Error al guardar la copia de seguridad", - "ConfirmationRequiredTitle": "Confirmación requerida", - "BackupNameAlreadyUsedMessage": "¡Nombre de copia ya en uso!", - "ErrorMessageForWrongFileExtensionTitle": "Archivo no válido", - "BackupSavedCorrectlyTitle": "Copia Guardada" + "ConfirmationRequiredTitle": "Confirmación requerida" }, "TimePickerDialog": { "TimeIntervalTitle": "Intervalo de tiempo para copias automáticas", @@ -119,29 +81,17 @@ "StatusCompleted": "¡Copia completada!" }, "Menu": { - "Import": "Importar lista de copias de seguridad", - "Save": "Guardar", "About": "Acerca de", "File": "Archivo", - "Support": "Soporte", "BugReport": "Reportar un error", - "Quit": "Salir", "Options": "Opciones", "New": "Nuevo", - "Clear": "Limpiar", - "Share": "Compartir", - "Preferences": "Preferencias", - "Website": "Sitio web", - "SaveWithName": "Guardar con nombre", - "Export": "Exportar lista de copias de seguridad", "Help": "Ayuda", - "InfoPage": "Información", "History": "Historial", "SubmenuMain": "PRINCIPAL", "SubmenuOther": "OTROS", "Donate": "Apoya el proyecto", "Backups": "Lista de copias de seguridad", - "CreateBackup": "Crear nueva copia de seguridad", "ImportBackup": "Importar copias de seguridad desde CSV", "ExportBackup": "Exportar copias de seguridad a CSV", "Dashboard": "Panel", @@ -153,25 +103,21 @@ "BackupList": { "NotesDetail": "Notas", "BackupNameDetail": "NombreCopia", - "ExportAsPdfTooltip": "Exportar como PDF", "EditPopup": "Editar", "ResearchBarTooltip": "Barra de búsqueda", "InitialPathDetail": "RutaInicial", - "ExportAs": "Exportar como: ", "RenameBackupPopup": "Renombrar copia de seguridad", "NextBackupDateDetail": "PróximaFecha", "AutoBackupPopup": "Copia automática", "BackupNameColumn": "Nombre de la Copia de Seguridad", "BackupPopup": "Copia de seguridad", "BackupCountDetail": "NúmeroCopias", - "ExportAsCsvTooltip": "Exportar como CSV", "InitialPathColumn": "Ruta Inicial", "MaxBackupsToKeepDetail": "MaximoCopiasDeSeguridadMantener", "DeletePopup": "Eliminar", "OpenDestinationFolderPopup": "Abrir ruta de destino", "LastBackupColumn": "Última Copia de Seguridad", "SingleBackupPopup": "Ejecutar copia única", - "AddBackupTooltip": "Agregar nueva copia de seguridad", "CopyTextPopup": "Copiar texto", "DestinationPathDetail": "RutaDestino", "CopyDestinationPathPopup": "Copiar ruta de destino", @@ -193,10 +139,6 @@ "TimeIntervalColumn": "Intervalo (dd.HH:mm)", "MaxBackupsColumn": "Número máximo de copias a conservar" }, - "TabbedFrames": { - "BackupEntry": "Entrada de Copia de Seguridad", - "BackupList": "Lista de Copias de Seguridad" - }, "UserDialog": { "EmailConfirmationBody": "Hola [UserName],\n\n¡Gracias por descargar y registrar Backup Manager, tu nueva herramienta para una gestión segura y eficiente de tus copias de seguridad!\n\nEste es un correo automático enviado para confirmar tu registro. Nos pondremos en contacto contigo por correo solo para informarte sobre nuevas versiones o actualizaciones importantes de la aplicación.\n\nMientras tanto, si tienes preguntas, necesitas ayuda o tienes sugerencias, estamos siempre a tu disposición. Puedes contactarnos en [SupportEmail].\n\n¡Gracias nuevamente por elegir Backup Manager y disfruta gestionando tus copias de seguridad!\n\nSaludos cordiales,\nEl equipo de Backup Manager", "EmailConfirmationSubject": "¡Gracias por elegir Backup Manager!", @@ -213,9 +155,7 @@ "TimePickerTooltip": "Selector de tiempo", "MaxBackupsToKeepTooltip": "Número máximo de copias de seguridad antes de eliminar las más antiguas.", "BackupName": "Nombre de la copia de seguridad", - "AutoBackupButton": "Copia de Seguridad Automática", "InitialPathTooltip": "(Requerido) Ruta inicial", - "CurrentFile": "Archivo actual", "InitialFileChooserTooltip": "Abrir explorador de archivos", "LastBackup": "Última copia de seguridad", "SingleBackupButton": "Copia de Seguridad Única", @@ -228,7 +168,6 @@ "NotesTooltip": "(Opcional) Descripción de la copia de seguridad", "DestinationFileChooserTooltip": "Abrir explorador de archivos", "BackupNameTooltip": "(Requerido) Nombre de la copia de seguridad", - "PageTitle": "Entrada de Copia de Seguridad", "Notes": "Notas", "PageSubtitleCreate": "Crear copia de seguridad", "PageSubtitleEdit": "Editar copia de seguridad", @@ -285,7 +224,11 @@ "SettingsLineStyleLine": "Línea", "SettingsLineStyleCurved": "Curva", "SettingsColorOptionLayout": "Opción de color", - "SettingsColorOptionPainted": "Colorear la línea seleccionada" + "SettingsColorOptionPainted": "Colorear la línea seleccionada", + "SettingsThemes": "Temas", + "SettingsThemeAll": "Todos", + "SettingsThemeLight": "Claro", + "SettingsThemeDark": "Oscuro" }, "SearchBar": { "SearcTitle": "Buscar...", @@ -307,6 +250,18 @@ "LanguageChange": "Algunos cambios tendrán efecto después de reiniciar la aplicación", "MissingDataLoginError": "Por favor completa todos los campos obligatorios", "WrongEmailLoginError": "La dirección de correo electrónico proporcionada no es válida. Introduce una correcta", - "LoginOk": "¡Bienvenido! Has iniciado sesión correctamente" + "LoginOk": "¡Bienvenido! Has iniciado sesión correctamente", + "ErrorTextClipboard": "El texto de error se ha copiado al portapapeles", + "CsvExport": "¡Copias de seguridad exportadas a CSV con éxito!", + "CsvExportError": "Error al exportar las copias de seguridad a CSV", + "CsvExportInvalidFilename": "Nombre de archivo no válido. Usa solo caracteres alfanuméricos, guiones y guiones bajos", + "NotSupportedEmail": "Tu sistema no admite el envío directo de correos electrónicos desde esta aplicación", + "UnableToSendEmail": "No se pudo enviar el correo. Inténtalo más tarde", + "NotSupportedEmailGeneric": "Tu sistema no admite el envío de correos electrónicos", + "OpeningWebsiteError": "No se pudo abrir la página web. Inténtalo de nuevo", + "FolderNotExisting": "La carpeta no existe o no es válida", + "BackupNameAlreadyUsed": "¡Nombre de copia de seguridad ya utilizado!", + "BackupAlreadyInProgress": "Ya hay una copia de seguridad en progreso. No es posible ejecutar copias en paralelo", + "BackupPathsEmptyError": "The initial path and destination path cannot be empty!" } } diff --git a/src/main/resources/res/languages/fra.json b/src/main/resources/res/languages/fra.json index 6dc1443f..53061f8d 100644 --- a/src/main/resources/res/languages/fra.json +++ b/src/main/resources/res/languages/fra.json @@ -27,70 +27,32 @@ }, "Dialogs": { "WarningGenericTitle": "Avertissement", - "SuccessfullyExportedToCsvMessage": "Sauvegardes exportées en CSV avec succès !", "ErrorMessageForPathNotExisting": "Un ou les deux chemins n'existent pas !", - "ConfirmationDeletionMessage": "Êtes-vous sûr de vouloir supprimer les lignes sélectionnées ?", "ExceptionMessageReportMessage": "Veuillez signaler cette erreur, soit avec une capture d'écran, soit en copiant le texte d'erreur suivant (il est recommandé de fournir une description des actions effectuées avant l'erreur) :", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Erreur lors de l'exportation des sauvegardes en CSV : ", "ErrorGenericTitle": "Erreur", - "ErrorMessageForExportingToPdf": "Erreur lors de l'exportation des sauvegardes en PDF : ", - "BackupListCorrectlyImportedTitle": "Menu Importer", - "ErrorWrongTimeInterval": "L'intervalle de temps est incorrect", - "ErrorMessageOpenHistoryFile": "Erreur lors de l'ouverture du fichier d'historique.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", "ErrorMessageForSamePaths": "Le chemin initial et le chemin de destination ne peuvent pas être identiques. Veuillez choisir des chemins différents !", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Lien de partage copié dans le presse-papiers !", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossible d'enregistrer le fichier. Les chemins initial et de destination doivent être spécifiés et ne peuvent pas être vides", - "BackupListCorrectlyExportedMessage": "Liste de sauvegarde exportée avec succès sur le bureau !", - "BackupSavedCorrectlyMessage": "enregistrée avec succès.", "SettedEveryMessage": "\nEst défini tous les", "WarningBackupAlreadyInProgressMessage": "Une sauvegarde est déjà en cours. Il n'est pas possible d'effectuer des sauvegardes en parallèle.", "WarningShortTimeIntervalMessage": "L'intervalle de temps sélectionné est très court. Pour un fonctionnement optimal, nous recommandons de le régler à au moins une heure. Voulez-vous quand même continuer ?", "ConfirmationMessageCancelAutoBackup": "Êtes-vous sûr de vouloir annuler les sauvegardes automatiques pour cette entrée ?", "ErrorMessageCountingFiles": "Erreur lors du calcul des fichiers à sauvegarder.", - "ErrorMessageForFolderNotExisting": "Le dossier n'existe pas ou est invalide", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Erreur lors de l'opération de sauvegarde : le chemin initial est incorrect !", "ErrorMessageInputMissingGeneric": "Entrée manquante !", "BackupNameInput": "Nom de la sauvegarde", "CsvNameMessageInput": "Entrez le nom du fichier CSV.", - "ConfirmationDeletionTitle": "Confirmer la suppression", - "ErrorMessageForWrongFileExtensionMessage": "Erreur : Veuillez sélectionner un fichier JSON valide.", "ErrorMessageZippingGeneric": "Erreur lors de la compression des fichiers.", - "ErrorMessageNotSupportedEmailGeneric": "Votre système ne prend pas en charge l'envoi d'e-mails.", "ErrorMessageZippingIO": "Erreur lors de la compression des fichiers : erreur d'E/S.", - "SuccessfullyExportedToPdfMessage": "Sauvegardes exportées en PDF avec succès !", "DuplicatedFileNameMessage": "Le fichier existe déjà. Écraser?", "AutoBackupActivatedMessage": "La sauvegarde automatique a été activée", + "AutoBackup": "Sauvegarde automatique", "DaysMessage": " jours", "ExceptionMessageReportButton": "Signaler le problème", - "ErrorMessageInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores.", - "ExceptionMessageClipboardMessage": "Le texte de l'erreur a été copié dans le presse-papiers.", "SuccessGenericTitle": "Succès", - "ConfirmationMessageForClear": "Êtes-vous sûr de vouloir effacer les champs ?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Liste de sauvegarde importée avec succès !", "DuplicatedBackupNameMessage": "Une sauvegarde avec le même nom existe déjà. Voulez-vous l'écraser ?", "ExceptionMessageClipboardButton": "Copier dans le presse-papiers", "ErrorMessageZippingSecurity": "Erreur lors de la compression des fichiers : Erreur de sécurité.", "InterruptBackupProcessMessage": "Êtes-vous sûr de vouloir arrêter cette sauvegarde ?", - "BackupListCorrectlyExportedTitle": "Menu Exporter", - "ConfirmationMessageForUnsavedChanges": "Des modifications non enregistrées existent. Voulez-vous les enregistrer avant de passer à une autre sauvegarde ?", - "ExceptionMessageTitle": "Erreur...", - "PdfNameMessageInput": "Entrez le nom du fichier PDF.", - "ErrorMessageNotSupportedEmail": "Votre système ne prend pas en charge l'envoi d'e-mails directement depuis cette application.", - "ErrorMessageForSavingFile": "Erreur lors de l'enregistrement du fichier", - "ErrorMessageUnableToSendEmail": "Impossible d'envoyer l'e-mail. Veuillez réessayer plus tard.", "ConfirmationMessageBeforeDeleteBackup": "Êtes-vous sûr de vouloir supprimer cet élément ? Cette action est irréversible.", - "ErrorMessageOpeningWebsite": "Échec de l'ouverture de la page web. Veuillez réessayer.", - "ErrorSavingBackupMessage": "Erreur lors de l'enregistrement de la sauvegarde", - "ConfirmationRequiredTitle": "Confirmation requise", - "BackupNameAlreadyUsedMessage": "Nom de sauvegarde déjà utilisé !", - "ErrorMessageForWrongFileExtensionTitle": "Fichier invalide", - "BackupSavedCorrectlyTitle": "Sauvegarde Enregistrée" + "ConfirmationRequiredTitle": "Confirmation requise" }, "TimePickerDialog": { "TimeIntervalTitle": "Intervalle de temps pour les sauvegardes automatiques", @@ -119,29 +81,17 @@ "StatusCompleted": "Sauvegarde terminée !" }, "Menu": { - "Import": "Importer la liste de sauvegarde", - "Save": "Enregistrer", "About": "À propos", "File": "Fichier", - "Support": "Assistance", "BugReport": "Signaler un bug", - "Quit": "Quitter", "Options": "Options", "New": "Nouveau", - "Clear": "Effacer", - "Share": "Partager", - "Preferences": "Préférences", - "Website": "Site web", - "SaveWithName": "Enregistrer sous", - "Export": "Exporter la liste de sauvegarde", "Help": "Aide", - "InfoPage": "Info", "History": "Historique", "SubmenuMain": "PRINCIPAL", "SubmenuOther": "AUTRE", "Donate": "Soutenir le projet", "Backups": "Liste des sauvegardes", - "CreateBackup": "Créer une nouvelle sauvegarde", "ImportBackup": "Importer des sauvegardes depuis CSV", "ExportBackup": "Exporter les sauvegardes vers CSV", "Dashboard": "Tableau de bord", @@ -153,25 +103,21 @@ "BackupList": { "NotesDetail": "Notes", "BackupNameDetail": "NomSauvegarde", - "ExportAsPdfTooltip": "Exporter en tant que PDF", "EditPopup": "Modifier", "ResearchBarTooltip": "Barre de recherche", "InitialPathDetail": "CheminInitial", - "ExportAs": "Exporter en tant que : ", "RenameBackupPopup": "Renommer la sauvegarde", "NextBackupDateDetail": "ProchaineSauvegarde", "AutoBackupPopup": "Sauvegarde automatique", "BackupNameColumn": "Nom de la Sauvegarde", "BackupPopup": "Sauvegarde", "BackupCountDetail": "NombreSauvegardes", - "ExportAsCsvTooltip": "Exporter en tant que CSV", "InitialPathColumn": "Chemin Initial", "MaxBackupsToKeepDetail": "NombreMaxSauvegardesConserver", "DeletePopup": "Supprimer", "OpenDestinationFolderPopup": "Ouvrir le chemin de destination", "LastBackupColumn": "Dernière Sauvegarde", "SingleBackupPopup": "Effectuer une sauvegarde unique", - "AddBackupTooltip": "Ajouter une nouvelle sauvegarde", "CopyTextPopup": "Copier le texte", "DestinationPathDetail": "CheminDestination", "CopyDestinationPathPopup": "Copier le chemin de destination", @@ -193,10 +139,6 @@ "TimeIntervalColumn": "Intervalle (jj.HH:mm)", "MaxBackupsColumn": "Nombre maximal de sauvegardes à conserver" }, - "TabbedFrames": { - "BackupEntry": "Entrée de Sauvegarde", - "BackupList": "Liste de Sauvegardes" - }, "UserDialog": { "EmailConfirmationBody": "Bonjour [UserName],\n\nMerci d'avoir téléchargé et enregistré Backup Manager, votre nouvel outil pour une gestion sécurisée et efficace des sauvegardes !\n\nCeci est un email automatique envoyé pour confirmer votre inscription. Nous vous contacterons uniquement par email pour vous informer des nouvelles versions ou des mises à jour importantes de l'application.\n\nEn attendant, si vous avez des questions, besoin d'aide ou des suggestions, nous sommes toujours à votre disposition. Vous pouvez nous contacter à [SupportEmail].\n\nMerci encore d'avoir choisi Backup Manager et bonne gestion de vos sauvegardes !\n\nCordialement,\nL'équipe de Backup Manager", "EmailConfirmationSubject": "Merci d'avoir choisi Backup Manager !", @@ -213,9 +155,7 @@ "TimePickerTooltip": "Sélecteur de temps", "MaxBackupsToKeepTooltip": "Nombre maximum de sauvegardes avant de supprimer les plus anciennes.", "BackupName": "Nom de la sauvegarde", - "AutoBackupButton": "Sauvegarde Automatique", "InitialPathTooltip": "(Requis) Chemin initial", - "CurrentFile": "Fichier actuel", "InitialFileChooserTooltip": "Ouvrir l'explorateur de fichiers", "LastBackup": "Dernière sauvegarde", "SingleBackupButton": "Sauvegarde Unique", @@ -228,7 +168,6 @@ "NotesTooltip": "(Optionnel) Description de la sauvegarde", "DestinationFileChooserTooltip": "Ouvrir l'explorateur de fichiers", "BackupNameTooltip": "(Requis) Nom de la sauvegarde", - "PageTitle": "Entrée de Sauvegarde", "Notes": "Notes", "PageSubtitleCreate": "Créer une sauvegarde", "PageSubtitleEdit": "Modifier la sauvegarde", @@ -285,7 +224,11 @@ "SettingsLineStyleLine": "Ligne", "SettingsLineStyleCurved": "Courbe", "SettingsColorOptionLayout": "Option de couleur", - "SettingsColorOptionPainted": "Colorer la ligne sélectionnée" + "SettingsColorOptionPainted": "Colorer la ligne sélectionnée", + "SettingsThemes": "Thèmes", + "SettingsThemeAll": "Tous", + "SettingsThemeLight": "Clair", + "SettingsThemeDark": "Sombre" }, "SearchBar": { "SearcTitle": "Rechercher...", @@ -307,6 +250,18 @@ "LanguageChange": "Certaines modifications prendront effet après le redémarrage de l'application", "MissingDataLoginError": "Veuillez remplir tous les champs obligatoires", "WrongEmailLoginError": "L'adresse e-mail fournie est invalide. Veuillez en saisir une correcte", - "LoginOk": "Bienvenue ! Connexion réussie" + "LoginOk": "Bienvenue ! Connexion réussie", + "ErrorTextClipboard": "Le texte d’erreur a été copié dans le presse-papiers", + "CsvExport": "Sauvegardes exportées en CSV avec succès !", + "CsvExportError": "Erreur lors de l’exportation des sauvegardes en CSV", + "CsvExportInvalidFilename": "Nom de fichier invalide. Utilisez uniquement des caractères alphanumériques, des tirets et des underscores", + "NotSupportedEmail": "Votre système ne prend pas en charge l’envoi d’e-mails directement depuis cette application", + "UnableToSendEmail": "Impossible d’envoyer l’e-mail. Veuillez réessayer plus tard", + "NotSupportedEmailGeneric": "Votre système ne prend pas en charge l’envoi d’e-mails", + "OpeningWebsiteError": "Échec de l’ouverture de la page web. Veuillez réessayer", + "FolderNotExisting": "Le dossier n’existe pas ou est invalide", + "BackupNameAlreadyUsed": "Nom de sauvegarde déjà utilisé !", + "BackupAlreadyInProgress": "Une sauvegarde est déjà en cours. Il n’est pas possible d’exécuter des sauvegardes en parallèle", + "BackupPathsEmptyError": "The initial path and destination path cannot be empty!" } } diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 04fe9f8e..9ae3e842 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -27,70 +27,31 @@ }, "Dialogs": { "WarningGenericTitle": "Avviso", - "SuccessfullyExportedToCsvMessage": "Backup esportati in CSV con successo!", "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", - "ConfirmationDeletionMessage": "Sei sicuro di voler eliminare le righe selezionate?", "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", - "ErrorMessageInvalidPath": "The selected path is invalid!", - "ErrorMessageForExportingToCsv": "Errore durante l'esportazione dei backup in CSV: ", "ErrorGenericTitle": "Errore", - "ErrorMessageForExportingToPdf": "Errore durante l'esportazione dei backup in PDF: ", - "BackupListCorrectlyImportedTitle": "Menu Importa", - "ErrorWrongTimeInterval": "L'intervallo di tempo non è corretto", - "ErrorMessageOpenHistoryFile": "Errore durante l'apertura del file storico.", - "ErrorMessagePathsAreEmpty": "The initial path and destination path cannot be empty!", - "ConfirmationMessageBeforeExit": "Are you sure you want to exit?", - "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!", - "ErrorMessageUnexpected": "An unexpected error has occurred!", - "ShareLinkCopiedMessage": "Link di condivisione copiato negli appunti!", - "ErrorMessageForSavingFileWithPathsEmpty": "Impossibile salvare il file. Entrambi i percorsi iniziale e di destinazione devono essere specificati e non possono essere vuoti", - "BackupListCorrectlyExportedMessage": "Lista di backup esportata correttamente sul desktop!", - "BackupSavedCorrectlyMessage": "salvato con successo!", "SettedEveryMessage": "\nImpostato ogni", "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?", "ConfirmationMessageCancelAutoBackup": "Sei sicuro di voler annullare il backup automatico?", "ErrorMessageCountingFiles": "Errore durante il calcolo dei file da eseguire il backup.", - "ErrorMessageForFolderNotExisting": "La cartella non esiste o non è valida", - "ConfirmationMessageCancelSingleBackup": "Are you sure you want to cancel this backup?", - "ErrorMessageForIncorrectInitialPath": "Errore durante il backup: il percorso iniziale è errato!", "ErrorMessageInputMissingGeneric": "Input Mancanti!", "BackupNameInput": "Nome del backup", "CsvNameMessageInput": "Inserisci il nome del file CSV.", - "ConfirmationDeletionTitle": "Conferma Eliminazione", - "ErrorMessageForWrongFileExtensionMessage": "Errore: Selezionare un file JSON valido.", "ErrorMessageZippingGeneric": "Errore durante la compressione dei file.", - "ErrorMessageNotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email.", "ErrorMessageZippingIO": "Errore durante la compressione dei file: errore di I/O.", - "SuccessfullyExportedToPdfMessage": "Backup esportati in PDF con successo!", "DuplicatedFileNameMessage": "Il file esiste già. Sovrascrivere?", "AutoBackupActivatedMessage": "Backup Automatico attivato", + "AutoBackup": "Backup automatico", "DaysMessage": " giorni", "ExceptionMessageReportButton": "Riporta il problema", - "ErrorMessageInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore.", - "ExceptionMessageClipboardMessage": "Il testo dell'errore è stato copiato negli appunti.", "SuccessGenericTitle": "Successo", - "ConfirmationMessageForClear": "Sei sicuro di voler pulire i campi?", - "ErrorMessagePathsCannotBeSame": "The initial path and destination path cannot be the same!", - "BackupListCorrectlyImportedMessage": "Lista di backup importata correttamente!", "DuplicatedBackupNameMessage": "Esiste già un backup con lo stesso nome, vuoi sovrascriverlo?", "ExceptionMessageClipboardButton": "Copia negli appunti", "ErrorMessageZippingSecurity": "Errore durante la compressione dei file: Errore di sicurezza.", "InterruptBackupProcessMessage": "Sei sicuro di voler interrompere questo backup?", - "BackupListCorrectlyExportedTitle": "Menu Esporta", - "ConfirmationMessageForUnsavedChanges": "Ci sono modifiche non salvate, vuoi salvarle prima di passare a un altro backup?", - "ExceptionMessageTitle": "Errore...", - "PdfNameMessageInput": "Inserisci il nome del file PDF.", - "ErrorMessageNotSupportedEmail": "Il tuo sistema non supporta l'invio di email direttamente da questa applicazione.", - "ErrorMessageForSavingFile": "Errore nel salvataggio del file", - "ErrorMessageUnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi.", "ConfirmationMessageBeforeDeleteBackup": "Sei sicuro di voler eliminare questo elemento? Nota: questa azione non può essere annullata.", - "ErrorMessageOpeningWebsite": "Impossibile aprire la pagina web. Riprova.", - "ErrorSavingBackupMessage": "Errore durante il salvataggio del backup", - "ConfirmationRequiredTitle": "Conferma richiesta", - "BackupNameAlreadyUsedMessage": "Nome del backup già utilizzato!", - "ErrorMessageForWrongFileExtensionTitle": "File non valido", - "BackupSavedCorrectlyTitle": "Backup salvato" + "ConfirmationRequiredTitle": "Conferma richiesta" }, "TimePickerDialog": { "TimeIntervalTitle": "Intervallo di tempo per backup automatico", @@ -119,29 +80,17 @@ "StatusCompleted": "Backup completato!" }, "Menu": { - "Import": "Importa lista di backup", - "Save": "Salva", "About": "Informazioni", "File": "File", - "Support": "Supporto", "BugReport": "Segnala un problema", - "Quit": "Esci", "Options": "Opzioni", "New": "Nuovo", - "Clear": "Pulisci", - "Share": "Condividi", - "Preferences": "Preferenze", - "Website": "Sito web", - "SaveWithName": "Salva con nome", - "Export": "Esporta lista di backup", "Help": "Aiuto", - "InfoPage": "Info", "History": "Cronologia", "SubmenuMain": "PRINCIPALE", "SubmenuOther": "ALTRO", "Donate": "Supporta il progetto", "Backups": "Elenco backup", - "CreateBackup": "Crea nuovo backup", "ImportBackup": "Importa backup da CSV", "ExportBackup": "Esporta backup in CSV", "Dashboard": "Dashboard", @@ -153,25 +102,21 @@ "BackupList": { "NotesDetail": "Note", "BackupNameDetail": "NomeBackup", - "ExportAsPdfTooltip": "Esporta come PDF", "EditPopup": "Modifica", "ResearchBarTooltip": "Barra di ricerca", "InitialPathDetail": "PercorsoIniziale", - "ExportAs": "Esporta come: ", "RenameBackupPopup": "Rinomina backup", "NextBackupDateDetail": "ProssimoBackup", "AutoBackupPopup": "Backup automatico", "BackupNameColumn": "Nome del Backup", "BackupPopup": "Backup", "BackupCountDetail": "ConteggioBackup", - "ExportAsCsvTooltip": "Esporta come CSV", "InitialPathColumn": "Percorso Iniziale", "MaxBackupsToKeepDetail": "MassimoNumeroBackupDaMantenere", "DeletePopup": "Elimina", "OpenDestinationFolderPopup": "Apri percorso di destinazione", "LastBackupColumn": "Ultimo Backup", "SingleBackupPopup": "Esegui backup singolo", - "AddBackupTooltip": "Aggiungi nuovo backup", "CopyTextPopup": "Copia testo", "DestinationPathDetail": "PercorsoDestinazione", "CopyDestinationPathPopup": "Copia percorso di destinazione", @@ -193,10 +138,6 @@ "TimeIntervalColumn": "Intervallo (gg.HH:mm)", "MaxBackupsColumn": "Numero massimo di backup da mantenere" }, - "TabbedFrames": { - "BackupEntry": "VoceBackup", - "BackupList": "ListaBackup" - }, "UserDialog": { "EmailConfirmationBody": "Ciao [UserName],\n\nGrazie per aver scaricato e registrato Backup Manager, il tuo nuovo strumento per una gestione sicura ed efficiente dei backup!\n\nQuesta è un’email automatica, inviata per confermare la tua registrazione. Ti contatteremo via email esclusivamente per comunicarti il rilascio di nuove versioni o aggiornamenti importanti dell’applicazione.\n\nNel frattempo, se hai domande, necessiti assistenza o hai suggerimenti, siamo sempre a tua disposizione. Puoi contattarci scrivendo a [SupportEmail].\n\nGrazie ancora per aver scelto Backup Manager e buon lavoro con i tuoi backup!\n\nA presto,\nIl Team di Backup Manager", "EmailConfirmationSubject": "Grazie per aver scelto Backup Manager!", @@ -213,9 +154,7 @@ "TimePickerTooltip": "Selettore orario", "MaxBackupsToKeepTooltip": "Numero massimo di backup da conservare prima di eliminare i più vecchi.", "BackupName": "Nome del backup", - "AutoBackupButton": "Backup Automatico", "InitialPathTooltip": "(Obbligatorio) Percorso iniziale", - "CurrentFile": "File corrente", "InitialFileChooserTooltip": "Apri esplora file", "LastBackup": "Ultimo backup", "SingleBackupButton": "Backup Singolo", @@ -228,7 +167,6 @@ "NotesTooltip": "(Opzionale) Descrizione del backup", "DestinationFileChooserTooltip": "Apri esplora file", "BackupNameTooltip": "(Obbligatorio) Nome del backup", - "PageTitle": "Voce di Backup", "Notes": "Note", "PageSubtitleCreate": "Crea backup", "PageSubtitleEdit": "Modifica backup", @@ -285,7 +223,11 @@ "SettingsLineStyleLine": "Linea", "SettingsLineStyleCurved": "Curva", "SettingsColorOptionLayout": "Opzione colore", - "SettingsColorOptionPainted": "Colora la linea selezionata" + "SettingsColorOptionPainted": "Colora la linea selezionata", + "SettingsThemes": "Temi", + "SettingsThemeAll": "Tutti", + "SettingsThemeLight": "Chiaro", + "SettingsThemeDark": "Scuro" }, "SearchBar": { "SearcTitle": "Cerca...", @@ -307,6 +249,18 @@ "LanguageChange": "Alcune modifiche avranno effetto dopo il riavvio dell'applicazione", "MissingDataLoginError": "Compila tutti i campi obbligatori", "WrongEmailLoginError": "L'indirizzo email fornito non è valido. Inseriscine uno corretto", - "LoginOk": "Benvenuto! Accesso effettuato con successo" + "LoginOk": "Benvenuto! Accesso effettuato con successo", + "ErrorTextClipboard": "Il testo di errore è stato copiato negli appunti", + "CsvExport": "Backup esportati in CSV con successo!", + "CsvExportError": "Errore durante l'esportazione dei backup in CSV", + "CsvExportInvalidFilename": "Nome file non valido. Usa solo caratteri alfanumerici, trattini e underscore", + "NotSupportedEmail": "Il tuo sistema non supporta l'invio diretto di email da questa applicazione", + "UnableToSendEmail": "Impossibile inviare l'email. Riprova più tardi", + "NotSupportedEmailGeneric": "Il tuo sistema non supporta l'invio di email", + "OpeningWebsiteError": "Impossibile aprire la pagina web. Riprova", + "FolderNotExisting": "La cartella non esiste o non è valida", + "BackupNameAlreadyUsed": "Nome backup già utilizzato!", + "BackupAlreadyInProgress": "È già in corso un backup. Non è possibile eseguire backup in parallelo", + "BackupPathsEmptyError": "Il percorso iniziale e il percorso di destinazione non possono essere uguali. Si prega di scegliere percorsi diversi!" } } From 7e8997e63139f52069568cc6f23bfdf633b42795 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Wed, 29 Apr 2026 19:24:49 +0200 Subject: [PATCH 16/17] tray icon gui start fix --- src/main/java/backupmanager/MainApp.java | 21 ++++++++++++------- .../gui/Controllers/AppController.java | 20 +++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index d05f1dc0..d8f71ac6 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -17,12 +17,12 @@ import backupmanager.Enums.ConfigKey; import backupmanager.Managers.ExceptionManager; import backupmanager.Managers.LanguageManager; +import backupmanager.Utils.AppPreferences; import backupmanager.database.Database; import backupmanager.database.DatabasePaths; import backupmanager.database.ProductionDatabaseInitializer; import backupmanager.gui.Controllers.AppController; import backupmanager.gui.frames.BackupManager; -import backupmanager.Utils.AppPreferences; public class MainApp { private static final String CONFIG = "src/main/resources/res/config/config.json"; @@ -86,13 +86,18 @@ private static void runBackgroundProcess() { private static void runGui() { java.awt.EventQueue.invokeLater(() -> { - FlatRobotoFont.install(); - FlatLaf.registerCustomDefaultsSource(".themes"); - UIManager.put("defaultFont", FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13)); - AppPreferences.setupLaf(); - - BackupManager frame = BackupManager.getInstance(); - frame.setVisible(true); + initLaf(); + BackupManager.getInstance().setVisible(true); }); } + + public static void initLaf() { + FlatRobotoFont.install(); + FlatLaf.registerCustomDefaultsSource(".themes"); + UIManager.put( + "defaultFont", + FontUtils.getCompositeFont(FlatRobotoFont.FAMILY, Font.PLAIN, 13) + ); + AppPreferences.setupLaf(); + } } diff --git a/src/main/java/backupmanager/gui/Controllers/AppController.java b/src/main/java/backupmanager/gui/Controllers/AppController.java index 57897046..17871852 100644 --- a/src/main/java/backupmanager/gui/Controllers/AppController.java +++ b/src/main/java/backupmanager/gui/Controllers/AppController.java @@ -10,6 +10,7 @@ import backupmanager.Enums.SubscriptionStatus; import backupmanager.Helpers.SubscriptionHelper; import backupmanager.Helpers.SubscriptionNotifier; +import backupmanager.MainApp; import backupmanager.Services.BackgroundService; import backupmanager.gui.frames.BackupManager; @@ -65,15 +66,20 @@ private void showSubscriptionNotificationIfNeeded(SubscriptionStatus status) { private void openGui() { logger.info("Opening main GUI"); - BackupManager frame = BackupManager.getInstance(); - frame.setVisible(true); - frame.toFront(); - frame.requestFocus(); + java.awt.EventQueue.invokeLater(() -> { + MainApp.initLaf(); - if (frame.getState() == Frame.ICONIFIED) { - frame.setState(Frame.NORMAL); - } + BackupManager frame = BackupManager.getInstance(); + + frame.setVisible(true); + frame.toFront(); + frame.requestFocus(); + + if (frame.getState() == Frame.ICONIFIED) { + frame.setState(Frame.NORMAL); + } + }); } private void exitApp() { From 0c36d4190b8d196abd9b7fdb2287cdede82e48c0 Mon Sep 17 00:00:00 2001 From: DennisTurco <dennisturco@gmail.com> Date: Tue, 5 May 2026 11:13:53 +0200 Subject: [PATCH 17/17] small updates and bug fixes --- BackupManager_convert_to_exe_launch4j.xml | 4 +- BackupManager_installer_inno_setup.iss | 1 - pom.xml | 14 ------ .../java/backupmanager/BackupOperations.java | 41 +---------------- .../java/backupmanager/Email/EmailSender.java | 43 ++++++++++-------- .../backupmanager/Helpers/BackupHelper.java | 42 ++++++++++++++++- src/main/java/backupmanager/MainApp.java | 14 ++++++ .../Managers/WebsiteManager.java | 2 + .../Services/RunningBackupService.java | 2 +- .../gui/Controllers/TrayController.java | 1 - .../backupmanager/gui/forms/FormHistory.java | 27 +++++++---- .../gui/frames/BackupManager.java | 28 ++++++++++-- .../gui/simple/BackupEntryDialog.java | 1 + src/main/resources/drawer/logo.png | Bin 55372 -> 0 bytes src/main/resources/drawer/logo.svg | 24 ++++------ src/main/resources/logback.xml | 9 +--- src/main/resources/res/img/logo.ico | Bin 67646 -> 30154 bytes src/main/resources/res/img/logo.png | Bin 9601 -> 59614 bytes src/main/resources/res/img/logo_old.ico | Bin 0 -> 67646 bytes src/main/resources/res/img/logo_old.png | Bin 0 -> 9601 bytes src/main/resources/res/languages/ita.json | 1 + 21 files changed, 138 insertions(+), 116 deletions(-) delete mode 100644 src/main/resources/drawer/logo.png create mode 100644 src/main/resources/res/img/logo_old.ico create mode 100644 src/main/resources/res/img/logo_old.png diff --git a/BackupManager_convert_to_exe_launch4j.xml b/BackupManager_convert_to_exe_launch4j.xml index 21610e81..5e66f66f 100644 --- a/BackupManager_convert_to_exe_launch4j.xml +++ b/BackupManager_convert_to_exe_launch4j.xml @@ -2,7 +2,7 @@ <launch4jConfig> <dontWrapJar>false</dontWrapJar> <headerType>gui</headerType> - <jar>C:\Users\Dennis\Documents\Programmazione\BackupManager\target\BackupManager-1.0-SNAPSHOT-jar-with-dependencies.jar</jar> + <jar>C:\Users\Dennis\Documents\Programmazione\BackupManager\target\backupmanager-2.6.1-jar-with-dependencies.jar</jar> <outfile>C:\Users\Dennis\Documents\Programmazione\BackupManager\BackupManager.exe</outfile> <errTitle></errTitle> <cmdLine></cmdLine> @@ -38,4 +38,4 @@ <trademarks></trademarks> <language>ENGLISH_US</language> </versionInfo> -</launch4jConfig> +</launch4jConfig> \ No newline at end of file diff --git a/BackupManager_installer_inno_setup.iss b/BackupManager_installer_inno_setup.iss index c5d24a4b..1c033de4 100644 --- a/BackupManager_installer_inno_setup.iss +++ b/BackupManager_installer_inno_setup.iss @@ -37,7 +37,6 @@ Source: "config.enc"; DestDir: "{app}" Source: "jre\*"; DestDir: "{app}\jre"; Flags: recursesubdirs Source: "src\main\resources\*"; DestDir: "{app}\src\main\resources"; Flags: recursesubdirs -Source: "docs\*"; DestDir: "{app}\docs"; Flags: recursesubdirs ; ========================================= ; AVVIO AUTOMATICO (PER-UTENTE) diff --git a/pom.xml b/pom.xml index 6df80f17..4f292359 100644 --- a/pom.xml +++ b/pom.xml @@ -108,20 +108,6 @@ <version>RELEASE220</version> </dependency> - <!-- for export as PDF --> - <dependency> - <groupId>com.itextpdf</groupId> - <artifactId>kernel</artifactId> - <version>7.2.5</version> - <type>jar</type> - </dependency> - <dependency> - <groupId>com.itextpdf</groupId> - <artifactId>layout</artifactId> - <version>7.2.5</version> - <type>jar</type> - </dependency> - <!-- Timing Framework --> <dependency> <groupId>com.kenai.nbpwr</groupId> diff --git a/src/main/java/backupmanager/BackupOperations.java b/src/main/java/backupmanager/BackupOperations.java index 791f7e3a..8de33e2c 100644 --- a/src/main/java/backupmanager/BackupOperations.java +++ b/src/main/java/backupmanager/BackupOperations.java @@ -293,49 +293,12 @@ public static void deletePotentiallyIncompletedBackupsFromLastExecution() { List<BackupRequest> requests = BackupRequestRepository.getRunningBackups(); if (requests != null) { for (BackupRequest request : requests) { - deletePartialBackup(request.outputPath()); - BackupHelper.forceBackupTermination(request.backupRequestId()); + ZippingThread.stopExecutorService(1); + BackupHelper.forceBackupTermination(request); } } } - private static boolean deletePartialBackup(String filePath) { - logger.info("Attempting to delete partial backup: " + filePath); - - ZippingThread.stopExecutorService(1); - - if (filePath == null || filePath.isEmpty()) { - logger.warn("The file path is null or empty."); - return false; - } - - File file = new File(filePath); - - // Check if the file exists and is a valid file - if (file.exists()) { - if (file.isFile()) { - try { - if (file.delete()) { - logger.info("Partial backup deleted successfully: " + file.getName()); - return true; - } else { - logger.warn("Failed to delete partial backup (delete failed): " + file.getName()); - } - } catch (SecurityException e) { - logger.error("Security exception occurred while attempting to delete: " + file.getName(), e); - } catch (Exception e) { - logger.error("Unexpected error while attempting to delete: " + file.getName(), e); - } - } else { - logger.warn("The path points to a directory, not a file: " + filePath); - } - } else { - logger.warn("The file does not exist: " + filePath); - } - - return false; - } - public static void setError(ErrorType error, TrayIcon trayIcon, String backupName) { switch (error) { case InputMissing -> { diff --git a/src/main/java/backupmanager/Email/EmailSender.java b/src/main/java/backupmanager/Email/EmailSender.java index 2e905f6a..966cab95 100644 --- a/src/main/java/backupmanager/Email/EmailSender.java +++ b/src/main/java/backupmanager/Email/EmailSender.java @@ -1,13 +1,14 @@ package backupmanager.Email; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.LocalDateTime; -import java.util.LinkedList; -import java.util.List; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -167,29 +168,33 @@ private static boolean hasWaitedSufficientTime(int minWaitDays, LocalDateTime no } private static String getTextFromLogFile(int rows) { - File file = new File(ConfigKey.LOG_DIRECTORY_STRING.getValue() + ConfigKey.LOG_FILE_STRING.getValue()); + Path file = Paths.get( + ConfigKey.LOG_DIRECTORY_STRING.getValue(), + ConfigKey.LOG_FILE_STRING.getValue() + ); - if (!file.exists() || !file.isFile() || file.length() == 0) { + if (!Files.exists(file) || !Files.isRegularFile(file)) { return "Log file does not exist or is empty."; } - List<String> lastLines = new LinkedList<>(); - - try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { - String line; + try { + Deque<String> lastLines = new ArrayDeque<>(rows); - while ((line = reader.readLine()) != null) { - if (lastLines.size() == rows) { - lastLines.remove(0); // remove the older - } - lastLines.add(line); + try (Stream<String> stream = Files.lines(file, StandardCharsets.UTF_8)) { + stream.forEach(line -> { + if (lastLines.size() == rows) { + lastLines.removeFirst(); + } + lastLines.addLast(line); + }); } + + return String.join("\n", lastLines); + } catch (IOException e) { - logger.error("An error occurred during reading the log file for getting the last rows: " + e.getMessage(), e); + logger.error("Error reading log file: " + e.getMessage(), e); return "Error reading the log file."; } - - return String.join("\n", lastLines); } private static User getCurrentUser() { diff --git a/src/main/java/backupmanager/Helpers/BackupHelper.java b/src/main/java/backupmanager/Helpers/BackupHelper.java index d0f0b4ce..0e84dd98 100644 --- a/src/main/java/backupmanager/Helpers/BackupHelper.java +++ b/src/main/java/backupmanager/Helpers/BackupHelper.java @@ -1,6 +1,7 @@ package backupmanager.Helpers; import java.awt.Component; +import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; @@ -11,6 +12,7 @@ import org.slf4j.LoggerFactory; import backupmanager.BackupOperations; +import backupmanager.Entities.BackupRequest; import backupmanager.Entities.ConfigurationBackup; import backupmanager.Entities.TimeInterval; import backupmanager.Enums.BackupStatus; @@ -102,8 +104,9 @@ public static LocalDateTime getNexDateBackup(TimeInterval timeInterval) { .plusMinutes(timeInterval.minutes()); } - public static void forceBackupTermination(int requestId) { - BackupRequestRepository.updateRequestStatusByRequestId(requestId, BackupStatus.TERMINATED); + public static void forceBackupTermination(BackupRequest request) { + BackupRequestRepository.updateRequestStatusByRequestId(request.backupRequestId(), BackupStatus.TERMINATED); + deletePartialBackup(request.outputPath()); } public static ConfigurationBackup toggleAutomaticBackup(Component parent, ConfigurationBackup backup) { @@ -157,4 +160,39 @@ public static ConfigurationBackup toggleAutomaticBackup(Component parent, Config return null; } + + private static boolean deletePartialBackup(String filePath) { + logger.info("Attempting to delete partial backup: " + filePath); + + if (filePath == null || filePath.isEmpty()) { + logger.warn("The file path is null or empty."); + return false; + } + + File file = new File(filePath); + + // Check if the file exists and is a valid file + if (file.exists()) { + if (file.isFile()) { + try { + if (file.delete()) { + logger.info("Partial backup deleted successfully: " + file.getName()); + return true; + } else { + logger.warn("Failed to delete partial backup (delete failed): " + file.getName()); + } + } catch (SecurityException e) { + logger.error("Security exception occurred while attempting to delete: " + file.getName(), e); + } catch (Exception e) { + logger.error("Unexpected error while attempting to delete: " + file.getName(), e); + } + } else { + logger.warn("The path points to a directory, not a file: " + filePath); + } + } else { + logger.warn("The file does not exist: " + filePath); + } + + return false; + } } diff --git a/src/main/java/backupmanager/MainApp.java b/src/main/java/backupmanager/MainApp.java index d8f71ac6..aa1dcf8d 100644 --- a/src/main/java/backupmanager/MainApp.java +++ b/src/main/java/backupmanager/MainApp.java @@ -45,6 +45,7 @@ else if (!isBackgroundMode) { } private static void dataInit() { + ensureLogDirectory(); ConfigKey.loadFromJson(CONFIG); databaseInitialization(); @@ -75,6 +76,19 @@ private static boolean isBackgroundMode(String[] args) { return isBackgroundMode; } + private static void ensureLogDirectory() { + try { + String logDir = System.getProperty("user.home") + "/.backupmanager/logs"; + java.nio.file.Path path = java.nio.file.Paths.get(logDir); + java.nio.file.Files.createDirectories(path); + + logger.info("Log directory ensured at: {}", path.toAbsolutePath()); + } catch (IOException e) { + logger.error("Failed to create log directory", e); + throw new RuntimeException("Cannot initialize log directory", e); + } + } + private static void runBackgroundProcess() { try { AppController.startBackgroundProcess(); diff --git a/src/main/java/backupmanager/Managers/WebsiteManager.java b/src/main/java/backupmanager/Managers/WebsiteManager.java index f9dbf597..35899ad0 100644 --- a/src/main/java/backupmanager/Managers/WebsiteManager.java +++ b/src/main/java/backupmanager/Managers/WebsiteManager.java @@ -20,6 +20,7 @@ public class WebsiteManager { public static void openWebSite(JFrame parent, String reportUrl) { try { + logger.info("Opening website: {}", reportUrl); if (Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); if (desktop.isSupported(Desktop.Action.BROWSE)) { @@ -33,6 +34,7 @@ public static void openWebSite(JFrame parent, String reportUrl) { } public static void sendEmail(JFrame parent) { + logger.info("Attempting to open default mail client (support request)"); if (Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); diff --git a/src/main/java/backupmanager/Services/RunningBackupService.java b/src/main/java/backupmanager/Services/RunningBackupService.java index 5daffcc4..19fa081c 100644 --- a/src/main/java/backupmanager/Services/RunningBackupService.java +++ b/src/main/java/backupmanager/Services/RunningBackupService.java @@ -33,7 +33,7 @@ public static void updateBackupZippedFolderSizeById(int requestId, String pathFo public static void updateBackupStatusAfterForceTerminationByBackupConfigurationId(int backupConfigurationId) { BackupRequest request = BackupRequestRepository.getLastBackupInProgressByConfigurationId(backupConfigurationId); - BackupHelper.forceBackupTermination(request.backupRequestId()); + BackupHelper.forceBackupTermination(request); } public static void updateBackupStatusAfterCompletitionByBackupConfigurationId(int backupConfigurationId) { diff --git a/src/main/java/backupmanager/gui/Controllers/TrayController.java b/src/main/java/backupmanager/gui/Controllers/TrayController.java index a90a1018..bf6dc174 100644 --- a/src/main/java/backupmanager/gui/Controllers/TrayController.java +++ b/src/main/java/backupmanager/gui/Controllers/TrayController.java @@ -76,7 +76,6 @@ private PopupMenu setupAndGetPopupMenu() { popup.add(openItem); popup.addSeparator(); - popup.addSeparator(); popup.add(exitItem); openItem.addActionListener(e -> onOpen.run()); diff --git a/src/main/java/backupmanager/gui/forms/FormHistory.java b/src/main/java/backupmanager/gui/forms/FormHistory.java index f72f4e96..5b8bde64 100644 --- a/src/main/java/backupmanager/gui/forms/FormHistory.java +++ b/src/main/java/backupmanager/gui/forms/FormHistory.java @@ -2,7 +2,9 @@ import java.awt.Component; import java.io.IOException; -import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -32,17 +34,22 @@ protected void init() { @Override protected void loadData() { try { - try (InputStream is = getClass().getClassLoader().getResourceAsStream("res/logs/" + ConfigKey.LOG_FILE_STRING.getValue())) { - if (is == null) { - logsPane.setText("Log file not found in resources"); - return; - } + Path logFile = Paths.get( + System.getProperty("user.home"), + ".backupmanager", + "logs", + ConfigKey.LOG_FILE_STRING.getValue() + ); + + if (!Files.exists(logFile)) { + logsPane.setText("Log file not found:\n" + logFile); + return; + } - String content = new String(is.readAllBytes()); + String content = Files.readString(logFile); - logsPane.setText(content); - logsPane.setCaretPosition(0); - } + logsPane.setText(content); + logsPane.setCaretPosition(0); } catch (IOException ex) { logsPane.setText("Failed to load logs:\n" + ex.getMessage()); diff --git a/src/main/java/backupmanager/gui/frames/BackupManager.java b/src/main/java/backupmanager/gui/frames/BackupManager.java index d6f2ddb5..affd8890 100644 --- a/src/main/java/backupmanager/gui/frames/BackupManager.java +++ b/src/main/java/backupmanager/gui/frames/BackupManager.java @@ -1,13 +1,15 @@ package backupmanager.gui.frames; - import java.awt.Dimension; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import javax.swing.JFrame; import com.formdev.flatlaf.FlatClientProperties; import backupmanager.Enums.ConfigKey; +import backupmanager.gui.Controllers.GuiController; import backupmanager.gui.menu.DrawerManager; import backupmanager.gui.system.FormManager; @@ -27,12 +29,30 @@ public static synchronized BackupManager getInstance() { } private void init() { - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + setVisible(false); + } + }); + + setTitle("Backup Manager"); + this.setIconImage(GuiController.getIcon(this.getClass())); getRootPane().putClientProperty(FlatClientProperties.FULL_WINDOW_CONTENT, true); DrawerManager.getInstance().install(this); FormManager.install(this); - setSize(new Dimension(Integer.parseInt(ConfigKey.GUI_WIDTH.getValue()), Integer.parseInt(ConfigKey.GUI_HEIGHT.getValue()))); - setMinimumSize(new Dimension(Integer.parseInt(ConfigKey.GUI_MIN_WIDTH.getValue()), Integer.parseInt(ConfigKey.GUI_MIN_HEIGHT.getValue()))); + + setSize(new Dimension( + Integer.parseInt(ConfigKey.GUI_WIDTH.getValue()), + Integer.parseInt(ConfigKey.GUI_HEIGHT.getValue()) + )); + setMinimumSize(new Dimension( + Integer.parseInt(ConfigKey.GUI_MIN_WIDTH.getValue()), + Integer.parseInt(ConfigKey.GUI_MIN_HEIGHT.getValue()) + )); + setLocationRelativeTo(null); } } diff --git a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java index 8c78711a..17ec2bb1 100644 --- a/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java +++ b/src/main/java/backupmanager/gui/simple/BackupEntryDialog.java @@ -55,6 +55,7 @@ public BackupEntryDialog(BackupTableDataService backupTable) { build(); setAutoBackupOff(); + executeBackupBtn.setEnabled(false); } public BackupEntryDialog(BackupTableDataService backupTable, ConfigurationBackup currentBackup) { diff --git a/src/main/resources/drawer/logo.png b/src/main/resources/drawer/logo.png deleted file mode 100644 index 864b897b881a5be7dfd80b5e2ce27b7e9b0d72b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55372 zcmW(+1yEbx(@k)9cZcHc#l1kGxE3#kBEg|ZaDuxOw-ycV4uJ*;_A6Q_ZpBLRqF?^s zoq5U3o5_8fyZ7ulyLTT%PgfluhXw}#0N`tCsJsIJfQ`tj4hsW$r`P;q0{MmQsbS^~ z0Qd_2?*jf3@tOhvmYy|L6b%CkPM$ut@{Rj4#D&NR&vaep9|ScxX~SgSv0-_lXo8tE zL|^G=sw7QiThv!qx(o2Nt+ulX1g<Q4_2f9VsRoX__~w(SwrQmCfk>Dmm;eO1ScX6* zJphi;b<O(Ydf`Ujt;5(&N*tU@Som#`Ou^^rzQ0Dl{DoN$UiB}34r_|crN;jM@dE?2 z%$fTCqmh;k<&6e%<Z8{HjC#nQcx&W-A_DJzkn7i&k+-!=W4ys;^TE)WZ8E6z>-8)s zk}v;vpM2r_mYpO#iLyL>HZ(nZ#pDFl_h41-oqrxkK6W+wV9lp@S^6ya2WT~!{7gi) z=rh0D!HQ3V-K714M|X^8A^!p0Du=B5Nr2md?|ku#wMI<A#`0_G8=ZDyn_)Rt;^pVE z)4ek#wenB$bu2XP#iq4E5(h$`dcN-X{yN|~_W7m0<Y;SV`tfX^;cTCNW$NjpxgT9o z*pi1E)kg^{i&crmN|4wmMr`^ttWW+_ZC>#qFgL!ocHtP8CmcYYh>kAxRm0~sJh`<! zodY^|5q_U)00+JL77YRJR$s<_19sCM`=iGgViMjvf7xX3!~R43SV|?M@{1q~NjU!Z zs`?@EcU~63AqN7Ov#5foHpGLPfXnHPg6G18A0qb+gnI9DKD6Uie4DzQy6%1Be0D+Q z1!$?p(Uml;5^z#~5o5NT2DAgog8I=K_cH$gsH)!c`>eEEACx}-LTSXdvC64OT?RBB zdHi|mMq756`%VKLv8=+z*FdBAF6gg{cuQcKKCzWCZ3f5z7GQd+y%eE);*=NwbgMq{ z0^o{=ip1|Q^awWo)6$TXlRU>TyaN2>-XV!cTM#IZV&PVHm;?x*b`ov^!K>4M+yMS1 z@R>(yCH4bCXO5OsI7t(_2HMv~+Ks-zx$Kg<uL%GK%yTaAcYr18Avc*BpOTq~2;kcM zW@W4OCc;6NoeuM)t7s`n^}izAzJbGo9dhA8R8PmbdSEB^`6R#{5I{V5dH*Nbx*w>T z>mKsMp&I?WmgF?rB0JS-dlc2g7isiOZtxOnfEaiQ<%*0foDVFBsh3bWFLXm^=H_vg z0q5E3wDDjWyw4^h6F;+v#=r`xiaC`6yp>#tL)&2{`}hlZCk1$qe$M{nPd|v0`S9N- zHfc*l6MM_fa$1+BnaWUa6w93fpa|{U7~p|<?gVB)^?cV?Wrc1lnpjNpriYo>P)W)E zq;k5!V{(dAIeC1RVijj_1MM6SJcDv4npxS7vNxPK3LoD18cAnH$BE+F(uin?A~7gH zJ$C|Z;BQ(1`X~lJlII`Ubm}MS-qhUFJ6@|eVPpH^Rj;Z;?^ne)?=3UeSZw*#fSYoI z@6pab1LT04-heZu(YY^nAHFBUlTpLPh;trDh%!63rvUwogZ{uP7r+}PoMoovLUm)S z`ljVFhY)tQM9K$;=(e-3D<t+W#dr5Et*J)f0i4ZpKq%_q2LNw5|DB{C#fm<AqxeF} z!=N|dE$W>dKpt&&6;N=YS<7SFhF*U&cmI==WDe=J#FptZn`l*&YHx&|l7T}z-uXAc zqc9k`k|>#pVN_}OGAq?EeYQq^iY2<twrg8}Jl-s6|L55~0UBM?dKEF8k0(7|<*Py@ zB*}O;UC9huay=`GiOXNG_|%;61Tg^egsKiNB!exDDr4sm-sQVPoA=l!O^nZ3A*)MO z0b4n8vutor$7*lDI>si_P$mV+@<rp!*5QSpcD6#Pw?Dddm6V3j%A--uRq5OwHZhbE z((*oJ2@Ng*A7#N|;eIT&;=w1eVS9Rzlj!&NgZ(ETjvTM(Av}@qh@Za!o`}E+C8@b^ z3SX9bj%EnAr1>HvI%JcCgn(h>=mrWpU3TvZRP{1;=pfjxx!3OzT%ZA<0DbTgVfce7 zD0C!wX<MHcbE_o?^Wit~)$EN}puCM#m^J?9Ta-IZFhf1DiK81nYdyxTO?@ZNxQ}JS zh)ZVNDHud`>tA!6RSl3>xA!tR>g^Z{X(cmzhiiWd?V0`P5Ls)4D#r#`8oYA_yl{cT z&^L=w?qa;JxYBe)AUgEn=rHuVioLNeev~_XKp1g}2QY7XR5`9Tt5s?EXS8JG@QR9D zY^-MgZF#}!{Gxnpyza(&s*z-C6%C_m<R1CifSR+8gHvsdblRMl;EN783=j`{7Bk!@ zT;ef0_4!iYRLv@gS-Rk;y+;k$K;LXYxhiD(^4Hjd4a#v_^2VIceI#ZjlC??`8qA6P z;8Q#n(xYCZ6?0!t5@Y9oTYVFP*}^ciKtRkahC}HcEQP7+%R&n1BSyTKfaTFP2T;$2 zs>n?Ae-@~@eyYJjH1&U}gMJ4*vV))Tunr!LwWB&|>g#PQjKWI&)D0m~H-tUCIjkOj zsWZHFS!%0n$zSPpj9!ajoa}XpZ@h`0CwIZs(bnI>SXlJ9)@wG{C8k!E*c_ltTta>H z20RlR#t=$~8=E<(Sqkx~h?Ao&GR=FAyHX5303MeBAv|%y{zf<u7cNj+>LNU<jgfqv zD$c#8vvm<c|M7Z@$M>J#?PW|G+6`^W_;uOWU*e~=?WczGW;pgz3s6kzCuTjp;(6@) zaD=p}BYE0CyOcLJk49i5+8Gtb|8$hZ@zP<sx!Bp0zO^B)#cf2D6b8Ejo|`><yUvC= zOiCY6U~t5*vB%cFs&YeT<;6m7VJT;yPHLSLkL7-s;t#9Ik6E`l{WR-6F>QUmWwq_8 ztAg)n-6BUuO#9P_X|?9ayUhmqOWV@(mSP}8d9JXfU-4+0r#6qGgD-f4@=;n$YZ`Wg z4i@&LZ)}w7_#@KC251Lg2uf}PjB^h7>9RVz(-*nw_mcD3N-lL=tm^HRSr@(B)=oAV z)}3RBsz;N)k#hfxaQw{C2C1JUV6dXqn6nVou7ppg+d5E~J2nY_XxDY%Y!kbo0K9wv zFnUO8+6>n>L0B6p0()J3`4q>uUwDY1T`HCL>@xXA{6l9jG3k%`$0v87Lry^y>BXv~ z9MPXG@#(6W1>N1(1b_wRodX-nCG2YVsjdf?>n2ieU9A9$luffTB5dg){@3u)vC5 zHK#0`Cfu}@YaW)C21AU^p1}Z|%@-N)3$;tI@Dh!POo1s$eid;jZ5_V|Nt4+P7kGeh zb^|j_*?KoM=lOEKbbqAT#Y#2Q_X1~$a3b!geG5t?BJBSl@tevIy5BE-+tF^dOWplP z$y6!r78Om*$KpgbX6+5m3@9b6{<iuz19sGsGFjJ6+L_hJb(=WZ*R8(QnLn;MOXgv( zXm6<H3G$?qiFhUccv<9y$Uoi|LxESfrMyk9dKK)ODS)>Mt++o*?d^CQnfaB=a|3C? z1Guxdi&inykqNIiYY?x|j>h+ip_G}Y_r$tuEsP>DmSH}ijt>SrpyKMk9jXh~Lq6a9 zx4<-|`svP+6y77k%}cKB{_ibrIzF~97CenU)+Tk7{LoTI8#%N3e*Demt0rtKGYvXZ zMnR39Z_?vgC*)04YiJ4Bkw?7)0m3M`SNm2nWJ>m7qoW+dD6Y%`KVS$zFXQ0b$ioNn z!0OROm-seGqK}GSuI+XgW=Xv2>q?iuQ>@P0!>`pTd(i2u<~(3u4(y9s^ccBgv%;SZ zXr;o2pH+#|RexOK-z;1$wTJ8oMLz7D{^-lzGzT77AAa7cV>EYd`fN3Dmi;3cTD<<0 zb`~Ad#JU>wwq}&&lJ_C#m#Ld;jVTMTj1h4D10aXyxYoHY@Mkl_E{R?`T<q%9%VUif zTnso21I*>GEx<!O+hW&sE&FR5A)OK1TqOGcsIpq^vO3<cYnU>d8BI;3R{hlAR?^kK zU_OrdRF{u(ah|+YWb!m~*0XhIr3qDYAL=st_Kvg7Pbop3)<Zna3^P&hy<zQ18hluK z&TOVudEr6=<%$sah&I33KZusGT-r^9dLcY;w!vcfeRlv@CbGaSmSfwP|3yZP)Z{c8 zg76Pwi+x_>z@wLgIfqy492<Qt1nrj@lk0g@0Ydi}sspM+W%I>`e5X2N2~)Pt22L69 z-fvhNM}kLpaBF&A%0}EJWlG>7LaPDp>Jge&Dnp_9R>{--%E@k)9iV9Y7=qi#0*Hd{ zp}UDcUyS>x!tPeE+>pCK<X=KSKg#9{z70M8yN6`TKpRO>6xZ<@+vjY+BR=?9I*tNS z6G}vKJyGkTzr4{|E11hTp$TaXrX%{8q++V$u%K;djbhU93~f^J7wu-Gm6*j$PFwI~ z+4Z!z^-}h7WoyT(PEv5z)olEu;?$#VB7Cgju->7}@X;v0i+S3G79(yN{I>&G6sx7| zNEVN$>VO9(kn#Rr+DRk>>(xcMTh+@pDd@!tt=!2Qt6Ka!&Qn!^P+v1Qi(aMk$LnHj z8)Y6}Ca?MSd+b%;ozWoy-Y@Ww+_l%t)Iq?X<<Fyt7JnYEJI5j!0p65PGTi1_kS!tA z%k~9dY~LiPe^a;NEt1AEOONNgWsVL=KoUWi7dO9!j#EuO9O8V(v+8fr2-%lQi;<qY zG+nTaw=R?>oDIX?^iW<HAHp=GznWcx*mE`xUU+%LNabVzo@v3)!g2eEGMc~z^OfiK zveSFIoZB?`e7EJsw^8H{>i-EJ`!m;3G+)GVYrW%_io27>BO;BneHpuc@2ic{1x47h zQ9_qgKHV^@Xz;O#cBJFN@ygT996)5hS6dDh9NK)>IOpa=VD&NfY{nt--hd}Zzze?N zFTD94ae5j^Qq~~Ne@b1uzBuu{&)!5+%d|(Igte;_4{dzWRT=*KZm)0uHCxrTa!-^4 zVQBJSyiq8#?eEcvJ>pa&Uo8|j@(@+MrWbCzip^dn63El2D$}WP!GrDtN}?^>2Z5@O z8$(_OFNWDPtA99LD!XO|*TQz(E0P^3?xBxwlI@WThQ)VVYCdoIXSBZQYj>;h!8G8i zcqBd7?C-4Rr%z~$g2_3PyPSYWK0I~-|8?#1FCJ~(=D(tUD>ofpG<BjY9E#V-MNd~m z--p^o?S)FXqtmnUc;{l^)<}nD=F&JEEgVw>KPvOA#`xA=#=b7*BJN%~&d@AaE4Yj^ z`{W+zTsibQ$96H#U|q}U%(tVHiZ-qPZM5x%Q)fXzbwJ^7dCZ3b8jTYV#owfe_*gAF zd*()FApwPI{1*(h=3a!b;pOS>f{)+t5k0DRz|RS5d7TN3HFPo=+4YS)|HYe`F1$*8 z!G!VE#0Mzl*lFOM%dh_oLy6LUiFeuf{9JoOzcTRL;5YNS3>RA7odU9JS<@c~GwS*$ zN}pr@OL~=1*n5!>!W+_{EVyg&OAPYQ*DVV6OadOjHu`{>)%<$7Q{!Y1yN>;JW?oTv z`oxQ`p-=<Ig_6&DihuO27gZp=zYh~%N+)=0G5dFM<<hBw`5Balr`Cz@&XL(Rkd3T< z^YnsCs)?PVm<`cuu<x+CW%U@i66EszJmr+M=#9_T=RfYWtl~MMsBiexoTu9s>szc+ z^ST)RB$eARPWR--)uG0unf6)N(fINCy5K^lfLBX^M@qdApI<pEGI^lX<?x9^nTps6 z;A1C8Ltu+JYhbhlb@zW)1Y6pi+b)WeudDck$?-np@-?L4pQ-PiDa;o5PiQxt`)gfa zvJy;S>!A_JfPCxB(f_ay;-@ed@Vy}-1n?`SGBGHe17I>ly{OwLF!UoUdo@gh<aY?= zc-2fhVEX;d8des1tz1G7FXSZIrkK3Ob*|a@11;Yelh|n>Kb6h){nLRB<G$RN4>pU~ z<A^VF=ez&Nzvqc0_NClgMlMzM>+THON5p*ABag*L6Vp-YpD!x#fXS8|892%G=W4ya zevk5q11`oVI144heAXLVAmnA^|3UTwPzWPb`7q^DIG(T>769p$F5>}p3G}h^r?dj* z+5af2{x&yX=+_1%wiXZ~LItOT&RTrs*HY`0%7h=<*o^r0P@c9qF1*;i3<&z|*!WX( zY8ATT`Tu-KD}?EWVr%bTR)W{G{poddflZ(`cZGnuiP{HKYP}QreEo4m$PIa3*9=*S z^GkEde>y7SXrVi!0`AO03ohYL#&gjyWXSXf_G50EY%Tp2w4nE)ctB-sWPVi!*rW$M z6EbywGQO<5fb`x_mXnwyw4vXD>T6^l!SmjRmG`XFTISP2+R@8erb5la1G|Mf91^KH zlki>XxXDcpZLLv>N7)OnuJBLc!WP4Rp{f}c8lR4=qbv3;K5mIzVe7bw7a$Bro!fE8 z6Ws!xvyzk?&dPAU7sCA%?U)uceyiAd%E-v0><?gWii2l{7f*}G{ZiTdn9vr}9Y-uT zjQ}rvaqBk5wHcHVR)@dUE-oy5<VZmH@8`oism|A;&O}_jh=qM{IKJ5mWwyefzdWg6 zPs)AXMv0=w>cUUAfZs94`76kRRy=O7{~p69j1W1letZ*p@v3AiuaNr%?5UyiOa2{C zS=KDeq8V*owRG}=cnm_8vOMsuQf=ZD2;BX-$@-u8<pPMKImWd?1HntHWOpWqxb@)R zgCJ9mFwDXCz;l){ZMz3u8~Wx?yuiB+l&dE>Tl6f;Bt%TAiT&#k%W+pa7<v@>yK7Cs zO^8TDZv0zwU|`-a&|UCpZ}mLdpAz{1^t`pGmm!Al@soqrJzj62&Iwa;k?#=p-)^xg z4uRTC-?dtzzTWPKWLWlb8-aN8(v+PPS`y~ig#$24peosx2|yN!%5HjoeX-PBWpktQ zIp3$BR$&5zFXZ5t*M<|-rsyE1Ggt_FBXjvH+$vlBXEFtOGtEBuM>CTw!zctvU=ldy z_)N>P{%^gFj6lC_9p8r>0uZr5UvJR<NIkD-xHgnUV2SCDdGMXUub$Lx=g7$SOc}?y ztDywiQl{S#6}if3g%M=93^H9aDxieR>DF{=-dB14-=?^aPpm&NH5)^vp(=}mC*;)l ztU@hCE4qBR;*>UzZ2w!nw*}=IbmvL(RaCiwJ5~^OKrp6Z=ty7-j;aLiIB%MX#0e{? z8oN(W%`$B{TV3~AeT%Llyfx~*kP^6b+c?is*%-=`V^bllyUbV7{|%>XP0<<}p;<z9 z$u;_lY=s3LD2@iGi{sFBB9<su%s1L+r}yhWjyq%}Ua49}hjd+|E-+NIEFE<rlwXk! zU~Sr;_c-TK?DzMO+-3~Y8UKAoL|dYscPMlkyKSata{l!6kHx4CW)VnuV&m^nrQ_sp zDd-!EX+f4th=VE|wYDqJ$>0;dyI$l<a`Se$2L&VqL`QO@Z+7=+F1+ZUlobA>i$6h| zI6(t{!pfueXX4EhGS5sL<3_FN9-M4t?54cUfG7Wu5o3W6hp%)M@0!A<p_#%>=BG4N zVYGvn0PBdXk;N5{VucpUfsl<G^Z^bQuAEZv(fN&k3CR^sNzb-mnhC8rRIJ;X3MA#| z`QF9A9qG;k-Acoj^hi02U9{hQA9xvAa%#=#JIi^#s+eb>x{0MRw-|6WvpeQ>Q8b07 z^fJ5R<rzU0b(|KqZOk*Q9ZRDtU_-JiA<M7PkE2G#=)T?7ZJGc2u)D7Lhr08&xbR@a z@3j;5`3vdb25KX|V@RHHmC6az=fO}j8|F!>$;s0{*B|f#M^$MxcEw1#O|7oc7wpQI zp-N1N9%5bC`SpC?RS(-nup%T<DQn@o7+|GZ^b`YCVV%Zov$10$FVrU%<yRDf@M(qk z!0W`<liC@1mk6tfi+bU^vr)bH4eq<fYhY99G{%qGITly3g=8u}d&p3>3_eB={@Tnh z*a))Hm9v$?yuW@JA{5>d{P*X1+s`fWm8s&UUaUc+67yjC8)dFzx>U+J;%9ME@$zkx z$j|KZBb{$2+lHZ~$6Lb$*Z=&jjMK7;Zg}}^i}6<u9Nq~SMeSAa*Sr%=`xV2?w5yCD zuvW5`Q{Z5X&;uu{j)HtjoK=<1#l5=XEukm?Eg_TFpRW;SeXLmP^iTb{5-lXRR9+YB z`rrcpX;xvceVsx@D5H&kQg3+*|B~jALmhv6OM4(fj|Q_JW3vNLw~YRf^2E`|G*0UT zd|YC>x4qma@Tcdva2exbbf5+08Tn~idh)z#DQbb$R<%-<TaIW4()L|IwMH{Fjy1r8 zO#Do{7(sg>SMi_Wd)#bO3hxG72CJ5at`vW~NDgpF*sD5N+t($*;r%V1w+F^Gwvqhr zPWa>rt~HfeCA}J4u)*c{jt6s9swn_2z+>{W@78_5ZB$=3kySS6CGq;20sL%gl<yn= znf;>1-k)zg9vPw0Xxz3FtICM_(bhkg>YPkcItaqYgPVXQ+U~=wcoAqm-$2_yCkz3_ z|L_1YNW5NLF0R7%>BB=(T+UPn{VBh2yw#3q*!a#(V9)c@sZ`x}8^6I}CxSH!E=ICB z3M=#|h*VSJHHa1A10X{QBkH;9IXeB>$nuY*^%N>o(m*S~`I4f~Tpb0c`)$y0XHJVQ zOG8ZA`6(zcMvaLd4OY1>alAhF+0SAsi)(~V4`9h8z{r$S%fukbJsh=zwha_!004yl z5t((xv&cj%hPL02VFPjpQw0};$Ug(WKzM0bahH^n;3q`}pAcoZkl)*a3>eG4S4S~t zJ|W%&h!=G53~DC?Rge8_H2Uz1CUkWcHV{fuB2QR`ATe+b>Ea8tdbL>Bu%l(&Z*)X1 zQRf;s=JwWk8wXt|rp^Vbq@QLW+8;fuQ)Oq7x27`C86673?6Nf3&61|#1n3!=L$u{o ztf62%rFf}a*_@)!O+oL&j|$zr_?oSk#pEzLAe6y`Sn;xSvE3ZTWQ(JUQs|Qi?$9|g zz!ET+-*k!{YPGT1N0RI6OMSrtc#CoshBCKT<0Y2m!Z{vnx-v!(k~w#-r2nbw#}~JF z@4zJBYa0hgkkm{>RdlBhyRu)gc4DvPkT!@kFpyEnJv@~c8pYA6(vCAkIK<RgHP-kG zHSM=o3*V;bT7g=a&MvVj4gTqvz1e|E_EOIT>}#KqaMfA|BdRB18}0f(w6bNs@VrQB z|5De$DTOWqd585ur(+&9>$9^I+CZ`3*GY_qHoglnY5vk9wA;d~F)(yigy3X;+#7HJ zX5Ti!eWXRrh>!=TD~~N27Yk``QldobVL73uh|%?I9^7&%APH{c7jxz^D(+gB_A42S zOPIE$(azVmGr40T5Ks${!{+2sKkwl=i5^rso=jcI2i|)q`ut~@DZXTjxc^Te^Nsob zt!Y~n4b+vbcJgCj_pxzPxHPC*4F0|&ytCO0A1UC1_Fg-9k!qq8H=0xpDd9SJp+=FU z?+<3;GYel&o3r2^lcB|{mYf-*C5kx>e04}#*i54uoy@Nj9Ed*W%4GRVZq+V8{@q=L z?#v!vvyN~xWkiS4C?EXRL1UM^4{Dy~8@c@1r4E{_F%mo&t}tR2yOzt{H8aA61GG~! zmQ&z2@ySlqBhF84^d}<BL>sWIjsa6U9DK5m7Xe65syo__*PJzmTv(OHK67wy0k49Y zE*hIr=QZj|t4hzYN`BuL22~nmIs0#&YF6gmw*dOFHkoDFsW(r9(Gp$4yR>({CI~k* zNC2-kS}oqk6sWcJCyNBG44)b2<PoCLfRnuQuGgNe7J3zsVKB@SmAc_~zs0ytu4k$q zRMj!fjXA@Wi7ZvrL~O85HKt^4uXo42Hm}Fj{wvh09rqjUz<9Z-fNw9yjI`_Wv_Ln? zrhn-LTQ{uDRq?w!HX<pcYjNp4>f8e&lHH2<K59?)Dt>d#ijnfdCE(u<N9|LG*rMwB zLiBDODXfc*+*ZR<Ne+pa@ZMm+5GtF5?eN_YlPL=-n^x0d7lnF!3XlLICU(EpAneT4 zttv*haQ~{{`q+h5WXZ9Od(xq;PP-YssyB`?`e?29Kr)xAKR!&WF=dWj5D<W>AWz56 z?LlfmLGaZpcvivEy6N=Ngzv&K21`!E1dFt(yB8yZkJYb;oN$)0H!*L>*I}>bY$Pky zGdGo}H`*G6*&-tV-W9KVltW<IYs8)0ZsX+0dU^ASKj|1f(Zp7iON+CK)9o5r{ta3e zu~GPgdCt){D;DMFi>l5nc*~1u-}k^6Oo{Yduo9Y{Nc(N?7?O0OXvoMycj405orx>m z*u5M$F6(cLVGj{1;fbS6%b8?aUuj6t(O9M=ViWF)Ss8kBNG<{7*H25z@t9kT4yCq{ z#q;|qiZSKC1siigAw*VWSv*$sYZ_uKY}*#fjB`!=hzALQheMPvd3<W!ka7~!03z=a zb>RJTjWgSE?Uou0_bV0tOtcH>`A@N;Nx_p=>ZWcwFIRs`C12#W6zsT^4H~t=S|6Un z)8?cjC-NvaSsOW-mYCyl2Xfi+H6S|RKCcauvS*!>^0wo|_MeVZ3oH!fCqsI-N7NEw zk9qD$8=fNpMaiE8g}ZBrMp|%n2L7O)t8{uv9M`s;$CV^3kn2;&7@Jg#!uNNgp4vw8 zm#?*xr40Cyq&NB+Yh8O{IO-Lu@;QY{Q)Hc`dLEx`Y9Ua==mTt*aHnh`J}V-v^78fX zj}odR!1Yf1sIBU%<RmR(svts^A43#x0e9|z_tEutiEPsCIch#lgDhXu)vdn)9|3U} zSU)iGt98no8dzOJe7lS-OwFtowD>OWzMAH^`YN_**9wyz;#&_PHXR+j#L`TL$wpw` z&usn9Q7*Rm5fa4SJY)4jTU_LB{AzDEw>rtLhwrQDeyar{aLP-#>W*1uZD<#yz=Qxv z)Qu#`X0b(Nc!HF>Mgs>efI&SyO8E0Wx<c-<TFdN#(cH>_+4}F?_?gF(4Wr$3X=;`K zy=%T(DWsg^VXTle877X1O4T|PYPm#->#+G1f97vjaTi)nxXV`UMj9YGHY%Dd3K1(U zwPp<Ze8^XoAVm;0I85J@xPp5Ai>aWlo`${tE}CBFxb_tLo-gZ2w~KouSFe0u_{7FH zoN1TC$<JD$p%a~1hjBP6svMp5DtD#p%+x;X6g{WJWzm!}TwSM<s(L4x7vkGF9p||3 z9qv7V^$ieAXh_AgOU@MJeN1Jf#wffi1=z6FtaTF+p)|Kr-`a|yT6{cbztGRJJw-(c zE>)wf#XThN5EKbe^~KR?zKgO446WiX=I&_ut62dygYD<H--&XoO0s?J01?**sSERd zMGK|3esi^IN@zUUk2?6zCNiXLaJaDBm`ZW)b#Ruy`?`u=z;1m*429W<n0f7FCg$#5 z7v2-$6m86FJ732=94G(({V~k#)2AL-RMabQ+nDuDy^w&oQ{AhL^0||DK61PE)ReSy zI0@&-`b;0rL=qKigD%CiHE$!`AS2NBRexvbTDcn%r6?8`UdCSXsipkNu6bS5;!$gA zVduj4qdHh!;k*3WlGDiefTVa483ByN+z0%&F0zqN41YUwtxe^_S=H^Bf7S{57h*|p zodpn*<wSu5{ST6ySq1$$&swEsUjaNCC?w>n^cfag<(B(Wb3po?fB6m8_l14TQz~va zcct$mb9_JSR`6?U=%vDUYud-ohPcUDQBuCVX;QX4F@?ye)y-7@4mI>YanrLzfoax} zTUo}P&8;!5+5`IdErp!K#Yxs$<&+6OvkA;BoymFLjwr4<!kx^Scua&uI!k3(Ioa5v z1YcGE??LYcOSII{?~;@TJq#DO?SPQLE8lrdgra@ldNAQ5`xOl!h1LBlejCj1SP2c> z5$^f5pG&uB<X7yEJHeiTs&$@qrp2o7J;+9Z*B&h&xk}XrI`b<2Vq+^SaHZuCBHfS8 z#yBhrE$3GSOo5u>I#CyC654lD@E~FfzuH6NJxI5(D%FBptX~<jj1lbHN(cv`2x5x) zXe~Nh`F5auBXir`+X%Zbj!Vs<aE<!QU(+tmCm)MY01yhwbS7JaC}ELLy`51}tV!BX z9$)van5s8(hGR*fCJqoL3P}t8d`(qMx%FG|g)FqHt5+UCLE2BV5Ct_~<}U*ofId0P zSJV0UXc7xEpQr*?DDr}I&swl9sA-yFR#E2vLS4d&CiFs7l-zCn&V`%3My0Hkl$1=k z!oTD1j#@oENwu3?^OZ64FNzxC(m33x=fY@?Z{gQ!9>6|gZUPFCY(K~FF4{3aKlH)2 zJC5redcIp*%8Taq41tW;%QD(ptpu?GN88>>BWhu+K+xOKp4~CI_ZCXSe<NtH@5P-k z8V29ueUfE7lSLsBxm<L8$6TX_M*>`$agyi%sO?-cqGR6!Stz3ekq@%+@raSEa6JvK zH4RuNnBOEVJE`MajZcay{Dkw|Faw$batZ~It`D~c@m!y3X)JRRv0YrqIr%}A;W|_e z`n)-1g(em?j0PJ`Ep<9hVJdu0YIs{%!`OfZTnZnSG$;$f?eqb_8dYyY_He9i<ac{B z<hwh3a^)*WhQHq?zO-iCXpe?F54C3?AzGJwPG)MV9&^U=A)&RR^}0Ga<t2%2?5k?P zDTRJn%`}@I+Am1YJ}_dNL>7NQ-gGsaJmwnJ+idocA8=*p_NOP5=*M}Z-y3UpMgjF8 z3j-Q2nB&%1&!Uh)Z&|GITMZ3KF06Gm4^P~_Asztbkp&)%UI%BC2GZsiz@TRL18lYU zXKwPV3DDGY@z4R%HafWW(QDBvf<BSgv@f)PH7}%`ClLKfjXN*6{6D=vu{z(hAibUC zJ68ES-Q7`9_V^Jh4ZSIOLzCRoCtaj&AzJGlbj(dh`Rn))9sAe6m;O$H1aCWrcK{UE zt*fP7<U%OkDMSx4n8RjaS;%EapBG#W?=A2x6aH8ApPreGl6#p+Z$v0a=K2q^!3K>h zXB*$a%Pj3});~1~ZESPTER9c+F`npKD28?$wvRiu^Hfv-SqvL%i8slT1o6Gv>tcfj zG%mm0IZS35*A$A_F0ip<afVh83Ei1_a%_)e<-KE-?e0DN+4d1ZlG|ClMlT3uP{OOj z-R4yt@q2aP7Z3T;Y-NKTMAQ}lUVF4tIWBKV6Oo#Q)I4k2uS!WsIM86|_oI9cd#HD8 zPW>P6LPpg%n$E$>xExqyg^Ag1))-TY<G-iA6j}z3+=oYpy#_<Oc*KgA^=`h{h^A3) zwr8tx2rw}Ta8e6^>ns=fM>69im~GQbG)~&vVU06v6xEd9;$fCPb+^^+QGjk7F%2)$ zm-$%=RNP4{?qA<W?~bmgj!=?6D$pV&>Jw)J`Bj1iq!(!y*&>O>9J(&O1P%n~(w*(c zExS1zfgQ7B<zh<FRaC3@5vLs2evdhP^ycK~;s9XD&^+^MtP_s=zaM`k%7$f!PZS;! zO#T-R>rJG0=4`sBK3Q#}<1bqRQODc=tP^-kHe1P<D<h9tNde(?=gz0_`5ed_2(+d+ zN`t>S97*$|<+#w&$fYp#FQX-uVIpyLR(Z=A<B73F_vzhxG#+&;h?R_Re$q71RFu%o zL2n1aT%P~$EF<UD%|l?4uJx<^tW-g_b9=i76}!e{i5HtWk>w886&WsRsh|63Rmq4C zbME1R&hg=+&Ln_o#mXmVsWkYNiapu|%1v4?1KT7Ivu!-F38#2!E`M!Ct{H=OfY^*K zx%4H<6ku#*X{v9@2nhlLg|Ndw?Wtc3X}!JK$ny?ldyR~=g`MJu4-)0-mD*9}0_Gbk zPT#&2xy5>ui|Enq@YAfIB%E|~|M*toko>pJTnJsF=NcU{;Mt}-p^<H(Gz?N|VOL9H za|(-;cHG6g$uMmgo32i10y8Llv+#Qt`Nl>_dB#y5IGS8IU9s9cqSCRLv|WgiA^*j8 zgk3=Ee=`Uvtdab<j#0PMQEtlpU7lHQZ_?{Yk)2~+FJKeEuj0=cGMEUAvC;p;nGs|V z?sRb>(8nV1mcJoq-rS(9vY3*+I#_9#I98xZUBM2eT{<o;cclaZi(v?n=igG84JB`L z{8ri-!xGk(^yNN$bpHV`jVJ$0Q$2rAj8`?&dK=w3oLk6)HL(7F?vuo5WNXLRAVEEm z4D<UL<QKu56XSTCYuQf&oJ){|2x{+nY`V=arb#^>110Agi({HKl{+pIGE8jtdv+bg zz0}?7;jaHn8RIY?0_c(5kQq42c1qB4HM@m|Rj$Qda(1Ca1-lIE7Zm!=9kyUxgjID+ z>fq0J#kUHjxS)>QR9gB#;Fs~qkfFp0I3oXJz%Ki45K99x&R}iRXJuL->nlP_%BTTs zPMQ-sQqd*GMTR7+-*!l{H!W=Hv-OuO<7Q>7T&&8UU<A}7jKM}^lg;|90!F&iPuJtF zs3MX1A9?>b88RZ&0C_sVHSP+mkhUA*d=AI$^}8WoW`bV^7I?gPMN<ILrOuhsv31)r zNUu_hT&~|k5b!6r^0i$TH*0aP_#ERJ+TWW{YZYV{Ri+|5Husgbvhm<CD@D4g1!I%# z1WZ$%Q<q6$Pw50Rn$^rk=QDv$&nv~%!h|?+mL{mPx?^nbntj@u(tyTJAUhiy7g8ua zZ(@MM#bG{6G!nnVhcrT9blP9Y7h@I;%IUZ(4LhBj^?=TdguDesn5p=$pDS3sF8+4r zSd*|flw`1=W#-yriXipYKTqDA^wd;AdgMul6GjJs->7g%t+Vtvbxh7K=oua_O;#W> zr4K=mwa@%?MIP=em$ldoDzRVV;r{7aS1UM)u?!vV78^yq@^@E&C!uzwM@2>bRx{>D z<~0~it@!U7nDJUCj`7RNk~I@zPrqP#aX~>6G~>}!&`<jZL?p{9q>or20oI>Y=z6GK z!MQ9oEDa<`yBxfpnE2V4nYZFfOym@d*CRioSrF$|rvwM~wYA1Tq`z`8?3Yb5qqt~f zQGpf~2|g`W)eMpGDKzb5vc`Q5yd7t+kFzX7v}zKMGHG1Juli*!A`OdD6AaqxyRVuq z%!GV>#2Otrf&;#In4JlQ8t}t@#vh(2;rWB8sP?5r_k-59%O)!0YGnCyFq^mKg7zX& zJ7bVDlw)m^JZ&eRvKsRVvT^hZ`(^+8E0_c05MvO(0EuvU6t76`G80-E#evhsyZzh$ zq7h0EZoBVT2kr64D=S;?aR=>LY1jWNN=^0TVp^a3z2_&2-8G?-94F0Ui_d!0_?dq7 zUrM|rr-~F?q&&Abj~a7h-nqt)*!E6Jx(Kh{$@Qw5DwGB!>AhbAf}~<Db&+)KLUh4= zADKTjp)$xUWP+HZAoLa#GG5kT;)4E;;zu>7L9_`Jp(P$ki?S29z13+Q%5sj>CVZIJ zmttnNC4}#OX@)WRYFqJ;+LsZ0CHUL8<-6&?92DU_Vl3$Vj`^LSfFKHwE}C=?LB6mX zOobNEIWIN+PJIu#8hxw+CaL7N3wU`{J&@Qg1||sxCu4E8{j;X}>EZx2RI;V0`$T8D zy5l$micwI?Sin!_@*U;cE`nHCA3_ueBY9%WK?;s#6OF5hhi<}Z(hs-7zIcDBbozMa zcCr|{*;kN0LCm^@(~DQ1j`~vFXGV6Kv<a~ac(6)DE+BEhc0wA5>C)=wtgmh%n`q@< zY7ncF!~^1bPc>xoiG5~~PR+($>xP*9N=I^2MsrIAbE)*lMr}IW<RZ|QvBe8LFl`q} z_}G}J%#39N-Uc6zoFTCb>Cd|HJfMx6mDs)fG<zF<#8@&e=)YN?kftjhe5mpuvzX!J zuTGN&OXG0lu2PfyLmOReyxJUv7Of3s7H}_*A41iYMf_o;$XQI;)+#WK5^7ZQnW{?* z|H?!fl7F1~3-k0YwQvGPe>CW4oP9{y;rneQY6sUXZKeR7a8a>ObrvCYZbLpVk2*m) zI7;KZFRLIG(1>?bh{A=5F$vDq$NzfzSxcPn9=wL(VU4Ns*3F;}wUPo;pTH9ox0LT` z3f~j%EKp1<L{S85N)(t~6WX(Y*$r!6Ve~*%#GxwSWiyP9aCah}d;pPf$aXuM+%FBA z(u{P$Gz!x?P2x^W3(MG(ZUSn6S#gU#+x{M^!>co2StjJ;fO`oFP&FT-Hw_rOfYv)S zE(3gJ^8hj~@$g`Vd3pSGGmPuE)nj^H*bdU9x?T7V(muTYWFSXxFdf+6SfNi90Py&Q z<{?n)o8SwNZ2-tj#?N*lZvhF7=R>&+9yBGJnfTKvZ}tA8GevO!hvM_ZzLjpYr*$>9 zk<>-fCBC93hz%DCuG13d@l%ZHOGNi$`s!+s>Xmy;luSt7{|Z_!^y4J~=&%>$Mb990 z_<{c(uvhZ@F|KP;K^zmbEe6Ob8RY~Japj{on=NctuFc(3Nr)<VQ|k3uY-hesJjDFb zT}Jd;58Bf$sF3g$n9wbZqI`HA@KD4eH!(=)5iapQVyzo^A`8e_RcL~0@NdBt&&7lG z=uhHme|~w_p6wD$x;(ELGtXU%XzJwV;T@EeboKb|X%$z0vPEK=AsW@Yp?Osvl!dce zrf(Gs-jOzZ68Ud1Qyq6=l#@xfkC@E~`Kj5J_(9+&2A<T2Wq_iWeNTQ25m6{Z{RZZD zr)BYGZR*;OUwWz%*aB`yJnxg>C2>g|?x>MQ@YB`J`qj;;9I`ETI%;IWHSixm)epm3 z`_o$}jEVz>b6NZ6$YoU=VrC;`;5IBR>`&z0+C@-{N9)uXG3yJ5PmmY7v5qo-rCGt_ zk_R^3kvVT6cODAt(TM37XlHzxKKm!`{@VL}y0YO--=#3i^04~>Y)fL?#_54Sref?_ zV9FZJX(lp{c_Mbx3zy4(&_aTFsVLI&DV?P=syfDtT~6Uu_TW5f-cs2@mUDeWH_zNJ zDUOI18X{7V3Vt0wzsi#%Yq7>9kC#?E+#7G?c>Ju|f4&$*pT4#jNI)8>!&((a19wU* z48on2udL&n00%3Qrc_yuF_=kFn7npOvH!R*W*5<7jcJHBwlLtJF#$Cs&SsY|GjkxX zh7`qDHlfW}fhC_f6rKfl?<JT`2GF`5c>|7}Ixe+xC9&3(8ak5-*IDVUAW9aF7(zU} zW7ZkN&S+qfd8@cN(qv*&B9$+qqJdJexDs_j)g5*gpf0F#7Q6JGQ~`&c2R0zJ@PT0V zBrxuWhcTm<cmq5Rs{~J6C*_O<XgkjBNo_25`K8U#D&E@cAY;#Fu`@Q<4z<M#&uDjy z{GZSpJ+VJTG;hGdaik65l7G23(+I;Q8p?yu1L1o!ksa>7@-tBo1&*b(tXi%X(i-r| zgCdjF!mFIp|GcB1D*he>`Z_ot#73@XVi`@#<H{jLx7Dnda%J5W?!)-5o@u|<AE_5? zL6m0ch_{%{)I`@z)4lYfpRrd~`~jKBig`x%tZ{ZzCmG0W*h{7nmE%t(zmLX*sYBx= z_>5vn5N(l?7B{JN�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 diff --git a/src/main/resources/drawer/logo.svg b/src/main/resources/drawer/logo.svg index 790829af..c8dbafc2 100644 --- a/src/main/resources/drawer/logo.svg +++ b/src/main/resources/drawer/logo.svg @@ -1,16 +1,8 @@ -<?xml version="1.0" encoding="UTF-8"?> -<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="432" height="428"> -<path d="M0 0 C1.98913045 -0.00082235 3.97826094 -0.00165165 5.96739113 -0.00294948 C13.67000595 -0.00794301 21.3725952 -0.00366115 29.07520676 0.00383759 C32.69281387 0.0073441 36.31042044 0.00960352 39.92802876 0.01141632 C107.17614796 0.04513284 107.17614796 0.04513284 138.63013077 0.48069 C140.61442481 0.50740714 142.59876273 0.53079829 144.58309937 0.55413818 C179.48169041 0.98893937 179.48169041 0.98893937 195.14453125 5.4453125 C196.33405396 5.77450684 196.33405396 5.77450684 197.54760742 6.11035156 C211.45811386 10.09006874 224.46492402 17.38203438 235.20703125 27.1328125 C235.20703125 27.7928125 235.20703125 28.4528125 235.20703125 29.1328125 C235.78839844 29.38160156 236.36976563 29.63039063 236.96875 29.88671875 C249.66209395 36.95334479 256.83631387 54.96636573 261.20703125 68.1328125 C265.21547738 83.09263599 266.46159051 98.81119546 266.82373047 114.2409668 C266.86163627 115.6666899 266.89961704 117.09241101 266.93766594 118.5181303 C267.35065149 134.94742451 267.35405404 151.38007771 267.34135675 167.81350541 C267.33875526 172.26480551 267.34119176 176.71610396 267.34274292 181.16740417 C267.34454316 188.63414895 267.3422162 196.10088371 267.33740234 203.56762695 C267.33196999 212.08987 267.33372376 220.61208906 267.33922875 229.13433152 C267.34382932 236.56564877 267.34437699 243.99695898 267.34179008 251.42827719 C267.340264 255.81640906 267.3399282 260.20452984 267.34332657 264.5926609 C267.35509498 282.30672037 267.24362507 300.00892951 266.77685547 317.7175293 C266.7493387 318.82708904 266.7493387 318.82708904 266.72126603 319.95906413 C266.08118315 345.05949319 263.45794602 368.75644034 249.20703125 390.1328125 C248.54703125 390.1328125 247.88703125 390.1328125 247.20703125 390.1328125 C247.20703125 391.1228125 247.20703125 392.1128125 247.20703125 393.1328125 C246.07958044 394.32859367 244.93740257 395.51309709 243.734375 396.6328125 C242.07364295 398.26381277 240.67018575 400.01052539 239.20703125 401.8203125 C235.99260595 405.53035404 232.43653398 408.55871156 228.58203125 411.5703125 C228.03160156 412.00754639 227.48117187 412.44478027 226.9140625 412.89526367 C223.73362335 415.31555871 221.20572766 416.72402383 217.20703125 417.1328125 C217.20703125 417.7928125 217.20703125 418.4528125 217.20703125 419.1328125 C208.30481969 423.93498657 201.2218676 426.01154821 191.13876343 425.8812561 C189.88993366 425.88920492 188.64110389 425.89715373 187.35443079 425.90534341 C183.92682334 425.92113714 180.50125336 425.91040593 177.07381582 425.88784242 C173.36278127 425.87002307 169.65187544 425.8870839 165.94084167 425.89956665 C158.68699554 425.91846772 151.43354952 425.90541005 144.17973326 425.88104077 C135.7219779 425.85369323 127.26431603 425.85708584 118.80652769 425.86130743 C103.71110595 425.86767606 88.61587344 425.84441472 73.52050781 425.80517578 C58.89121745 425.76715867 44.26214873 425.75300558 29.6328125 425.76416016 C13.67795379 425.77628954 -2.27680767 425.77589741 -18.23165834 425.75318849 C-19.93444148 425.75077406 -21.63722463 425.74836209 -23.34000778 425.74595261 C-24.17767874 425.74474553 -25.0153497 425.74353845 -25.87840462 425.7422948 C-31.76645767 425.73455355 -37.65447439 425.73531704 -43.54253006 425.7391243 C-50.72173985 425.74337871 -57.9007561 425.73284067 -65.07991149 425.70389719 C-68.73908188 425.68946066 -72.39797115 425.68121068 -76.05716515 425.69038391 C-80.03118679 425.70010374 -84.00470216 425.67861325 -87.97866821 425.65420532 C-89.12449863 425.66176397 -90.27032905 425.66932262 -91.4508816 425.67711031 C-100.79523644 425.57584288 -108.99691189 423.44593592 -116.79296875 418.1328125 C-116.79296875 417.4728125 -116.79296875 416.8128125 -116.79296875 416.1328125 C-117.51871094 416.03871094 -118.24445313 415.94460938 -118.9921875 415.84765625 C-122.22361425 415.02289879 -124.00928618 413.76623093 -126.60546875 411.6953125 C-127.39566406 411.07785156 -128.18585938 410.46039063 -129 409.82421875 C-129.88751953 408.98697266 -129.88751953 408.98697266 -130.79296875 408.1328125 C-130.79296875 407.4728125 -130.79296875 406.8128125 -130.79296875 406.1328125 C-131.34984375 405.9265625 -131.90671875 405.7203125 -132.48046875 405.5078125 C-135.05483857 403.97710612 -136.6242993 402.12075753 -138.44921875 399.765625 C-139.81108512 398.04651651 -139.81108512 398.04651651 -141.64453125 396.3515625 C-159.39781763 378.01725947 -163.53575264 350.56804613 -164.10498047 326.02148438 C-164.12547077 325.23477859 -164.14596107 324.44807281 -164.1670723 323.63752747 C-164.23340213 320.95897616 -164.29150512 318.28034595 -164.34765625 315.6015625 C-164.37906015 314.15703957 -164.37906015 314.15703957 -164.41109848 312.68333435 C-165.08906179 280.36184103 -164.94328447 248.02687825 -164.92153811 215.70007706 C-164.91720179 208.50022981 -164.92029723 201.30040013 -164.92516625 194.10055399 C-164.92975479 187.0206578 -164.93032128 179.940769 -164.92772758 172.86087179 C-164.92620021 168.68886499 -164.92587047 164.51686986 -164.92926407 160.34486389 C-164.97018603 101.95359844 -164.97018603 101.95359844 -161.79296875 82.1328125 C-161.58800781 80.83601563 -161.38304687 79.53921875 -161.171875 78.203125 C-157.16157397 55.68199946 -144.78528122 36.16681623 -126.79296875 22.1328125 C-125.13635114 21.11625169 -123.47185316 20.11216174 -121.79296875 19.1328125 C-121.20515625 18.47152344 -121.20515625 18.47152344 -120.60546875 17.796875 C-92.87977129 -7.65818344 -34.811611 -0.03056362 0 0 Z " fill="#171B2B" transform="translate(164.79296875,2.8671875)"/> -<path d="M0 0 C1.98913045 -0.00082235 3.97826094 -0.00165165 5.96739113 -0.00294948 C13.67000595 -0.00794301 21.3725952 -0.00366115 29.07520676 0.00383759 C32.69281387 0.0073441 36.31042044 0.00960352 39.92802876 0.01141632 C107.17614796 0.04513284 107.17614796 0.04513284 138.63013077 0.48069 C140.61442481 0.50740714 142.59876273 0.53079829 144.58309937 0.55413818 C179.48169041 0.98893937 179.48169041 0.98893937 195.14453125 5.4453125 C196.33405396 5.77450684 196.33405396 5.77450684 197.54760742 6.11035156 C211.45811386 10.09006874 224.46492402 17.38203438 235.20703125 27.1328125 C235.20703125 27.7928125 235.20703125 28.4528125 235.20703125 29.1328125 C235.78839844 29.38160156 236.36976563 29.63039063 236.96875 29.88671875 C249.66209395 36.95334479 256.83631387 54.96636573 261.20703125 68.1328125 C265.21547738 83.09263599 266.46159051 98.81119546 266.82373047 114.2409668 C266.86163627 115.6666899 266.89961704 117.09241101 266.93766594 118.5181303 C267.35065149 134.94742451 267.35405404 151.38007771 267.34135675 167.81350541 C267.33875526 172.26480551 267.34119176 176.71610396 267.34274292 181.16740417 C267.34454316 188.63414895 267.3422162 196.10088371 267.33740234 203.56762695 C267.33196999 212.08987 267.33372376 220.61208906 267.33922875 229.13433152 C267.34382932 236.56564877 267.34437699 243.99695898 267.34179008 251.42827719 C267.340264 255.81640906 267.3399282 260.20452984 267.34332657 264.5926609 C267.35509498 282.30672037 267.24362507 300.00892951 266.77685547 317.7175293 C266.7493387 318.82708904 266.7493387 318.82708904 266.72126603 319.95906413 C266.08118315 345.05949319 263.45794602 368.75644034 249.20703125 390.1328125 C248.54703125 390.1328125 247.88703125 390.1328125 247.20703125 390.1328125 C247.20703125 391.1228125 247.20703125 392.1128125 247.20703125 393.1328125 C246.07958044 394.32859367 244.93740257 395.51309709 243.734375 396.6328125 C242.07364295 398.26381277 240.67018575 400.01052539 239.20703125 401.8203125 C235.99260595 405.53035404 232.43653398 408.55871156 228.58203125 411.5703125 C228.03160156 412.00754639 227.48117187 412.44478027 226.9140625 412.89526367 C223.73362335 415.31555871 221.20572766 416.72402383 217.20703125 417.1328125 C217.20703125 417.7928125 217.20703125 418.4528125 217.20703125 419.1328125 C208.30481969 423.93498657 201.2218676 426.01154821 191.13876343 425.8812561 C189.88993366 425.88920492 188.64110389 425.89715373 187.35443079 425.90534341 C183.92682334 425.92113714 180.50125336 425.91040593 177.07381582 425.88784242 C173.36278127 425.87002307 169.65187544 425.8870839 165.94084167 425.89956665 C158.68699554 425.91846772 151.43354952 425.90541005 144.17973326 425.88104077 C135.7219779 425.85369323 127.26431603 425.85708584 118.80652769 425.86130743 C103.71110595 425.86767606 88.61587344 425.84441472 73.52050781 425.80517578 C58.89121745 425.76715867 44.26214873 425.75300558 29.6328125 425.76416016 C13.67795379 425.77628954 -2.27680767 425.77589741 -18.23165834 425.75318849 C-19.93444148 425.75077406 -21.63722463 425.74836209 -23.34000778 425.74595261 C-24.17767874 425.74474553 -25.0153497 425.74353845 -25.87840462 425.7422948 C-31.76645767 425.73455355 -37.65447439 425.73531704 -43.54253006 425.7391243 C-50.72173985 425.74337871 -57.9007561 425.73284067 -65.07991149 425.70389719 C-68.73908188 425.68946066 -72.39797115 425.68121068 -76.05716515 425.69038391 C-80.03118679 425.70010374 -84.00470216 425.67861325 -87.97866821 425.65420532 C-89.12449863 425.66176397 -90.27032905 425.66932262 -91.4508816 425.67711031 C-100.79523644 425.57584288 -108.99691189 423.44593592 -116.79296875 418.1328125 C-116.79296875 417.4728125 -116.79296875 416.8128125 -116.79296875 416.1328125 C-117.51871094 416.03871094 -118.24445313 415.94460938 -118.9921875 415.84765625 C-122.22361425 415.02289879 -124.00928618 413.76623093 -126.60546875 411.6953125 C-127.39566406 411.07785156 -128.18585938 410.46039063 -129 409.82421875 C-129.88751953 408.98697266 -129.88751953 408.98697266 -130.79296875 408.1328125 C-130.79296875 407.4728125 -130.79296875 406.8128125 -130.79296875 406.1328125 C-131.34984375 405.9265625 -131.90671875 405.7203125 -132.48046875 405.5078125 C-135.05483857 403.97710612 -136.6242993 402.12075753 -138.44921875 399.765625 C-139.81108512 398.04651651 -139.81108512 398.04651651 -141.64453125 396.3515625 C-159.39781763 378.01725947 -163.53575264 350.56804613 -164.10498047 326.02148438 C-164.12547077 325.23477859 -164.14596107 324.44807281 -164.1670723 323.63752747 C-164.23340213 320.95897616 -164.29150512 318.28034595 -164.34765625 315.6015625 C-164.37906015 314.15703957 -164.37906015 314.15703957 -164.41109848 312.68333435 C-165.08906179 280.36184103 -164.94328447 248.02687825 -164.92153811 215.70007706 C-164.91720179 208.50022981 -164.92029723 201.30040013 -164.92516625 194.10055399 C-164.92975479 187.0206578 -164.93032128 179.940769 -164.92772758 172.86087179 C-164.92620021 168.68886499 -164.92587047 164.51686986 -164.92926407 160.34486389 C-164.97018603 101.95359844 -164.97018603 101.95359844 -161.79296875 82.1328125 C-161.58800781 80.83601563 -161.38304687 79.53921875 -161.171875 78.203125 C-157.16157397 55.68199946 -144.78528122 36.16681623 -126.79296875 22.1328125 C-125.13635114 21.11625169 -123.47185316 20.11216174 -121.79296875 19.1328125 C-121.20515625 18.47152344 -121.20515625 18.47152344 -120.60546875 17.796875 C-92.87977129 -7.65818344 -34.811611 -0.03056362 0 0 Z M-53.79296875 71.1328125 C-55.02660156 72.05900391 -55.02660156 72.05900391 -56.28515625 73.00390625 C-88.72202937 98.2928362 -110.87496543 133.06970113 -119.79296875 173.1328125 C-119.99535156 173.91269531 -120.19773438 174.69257813 -120.40625 175.49609375 C-128.04157003 205.51280989 -124.83399133 241.36048229 -113.79296875 270.1328125 C-113.43589844 271.06738281 -113.07882813 272.00195312 -112.7109375 272.96484375 C-103.92772359 294.90819088 -92.32996198 314.69014483 -75.51806641 331.40478516 C-73.97753277 332.94793504 -72.47808751 334.51779679 -70.9921875 336.11328125 C-55.39446288 352.51574431 -36.30408648 363.96748786 -15.83398438 373.2109375 C-14.23635884 373.93254444 -12.64600187 374.67024841 -11.05859375 375.4140625 C27.37551676 392.31416783 74.35387821 388.91936152 112.89453125 374.8203125 C115.00943053 373.95011957 117.1127543 373.05152833 119.20703125 372.1328125 C119.89796875 371.83568359 120.58890625 371.53855469 121.30078125 371.23242188 C139.21181259 363.45768481 156.4160843 353.08720771 170.20703125 339.1328125 C171.16113514 338.1988576 172.11691355 337.26661078 173.07421875 336.3359375 C178.9593243 330.5777371 184.32952212 324.78600468 189.20703125 318.1328125 C189.89023437 317.2665625 190.5734375 316.4003125 191.27734375 315.5078125 C202.15074246 301.56561486 209.80694623 286.55982612 216.20703125 270.1328125 C216.65175781 269.02292969 217.09648437 267.91304687 217.5546875 266.76953125 C219.58986711 261.49557118 220.68635662 256.75609848 221.20703125 251.1328125 C221.53703125 250.8028125 221.86703125 250.4728125 222.20703125 250.1328125 C222.60893154 248.24652137 222.95360478 246.34789194 223.26953125 244.4453125 C223.44613281 243.41019531 223.62273437 242.37507812 223.8046875 241.30859375 C223.93746094 240.26058594 224.07023437 239.21257812 224.20703125 238.1328125 C224.40941406 237.14539063 224.61179687 236.15796875 224.8203125 235.140625 C224.07952122 231.50744485 223.31388744 231.09482405 220.40234375 228.94921875 C218.72554356 227.84412726 217.03449585 226.76044176 215.33203125 225.6953125 C213.588509 224.55473743 211.84501914 223.41411285 210.1015625 222.2734375 C209.25803223 221.73267578 208.41450195 221.19191406 207.54541016 220.63476562 C204.66764036 218.78635888 201.83616285 216.88556813 199.01953125 214.9453125 C198.26285156 214.43613281 197.50617188 213.92695313 196.7265625 213.40234375 C196.22511719 212.98339844 195.72367187 212.56445312 195.20703125 212.1328125 C195.20703125 211.4728125 195.20703125 210.8128125 195.20703125 210.1328125 C197.56940915 208.35875464 199.93600988 206.70181089 202.39453125 205.0703125 C203.85695355 204.08360409 205.31919024 203.09662052 206.78125 202.109375 C207.52036621 201.6134082 208.25948242 201.11744141 209.02099609 200.60644531 C212.27047945 198.41592917 215.48939266 196.18222469 218.70703125 193.9453125 C219.76921875 193.20925781 220.83140625 192.47320312 221.92578125 191.71484375 C222.67859375 191.19277344 223.43140625 190.67070312 224.20703125 190.1328125 C223.27566019 147.71309428 196.67977096 108.42783819 167.33984375 79.9609375 C156.50117137 69.73885156 144.38191775 62.05120555 131.21386719 55.18310547 C129.45303997 54.26156305 127.7075622 53.31081441 125.96484375 52.35546875 C70.26673141 22.69509185 -4.98132088 33.3219987 -53.79296875 71.1328125 Z " fill="#091B46" transform="translate(164.79296875,2.8671875)"/> -<path d="M0 0 C1.90458984 0.01305176 1.90458984 0.01305176 3.84765625 0.02636719 C12.7884173 0.16635344 20.94005994 0.89294591 29.5625 3.375 C30.62339844 3.63023438 31.68429688 3.88546875 32.77734375 4.1484375 C47.49093855 8.03639346 60.00958192 16.74756826 71.5625 26.375 C72.51898438 27.15488281 73.47546875 27.93476563 74.4609375 28.73828125 C94.21387912 45.64170889 105.36446219 70.93437752 108.5625 96.375 C109.38546087 107.13452429 108.95174314 117.8378251 106.5625 128.375 C106.40265625 129.261875 106.2428125 130.14875 106.078125 131.0625 C102.2370253 149.74893097 91.62186282 168.62410269 78.5625 182.375 C77.9025 182.375 77.2425 182.375 76.5625 182.375 C76.5625 183.035 76.5625 183.695 76.5625 184.375 C65.6830733 194.8769772 52.79519093 202.51245971 38.5625 207.375 C37.29803144 207.87332853 36.03371842 208.37205182 34.76953125 208.87109375 C17.31905008 215.42849546 -1.30694156 215.9465464 -19.4375 212.375 C-20.24574219 212.230625 -21.05398437 212.08625 -21.88671875 211.9375 C-27.71488344 210.79831249 -32.9989993 208.72456375 -38.4375 206.375 C-39.29085937 206.02695312 -40.14421875 205.67890625 -41.0234375 205.3203125 C-58.74263599 197.78575596 -75.80467466 184.62948658 -86.4375 168.375 C-85.4375 165.375 -85.4375 165.375 -82.70703125 163.875 C-81.56621094 163.38 -80.42539063 162.885 -79.25 162.375 C-75.83374499 160.87575209 -72.45919506 159.38609144 -69.13671875 157.6875 C-66.4375 156.375 -66.4375 156.375 -64.4375 156.375 C-60.4375 160.84558824 -60.4375 160.84558824 -60.4375 163.375 C-59.4475 163.705 -58.4575 164.035 -57.4375 164.375 C-55.20092161 166.54767615 -53.02202538 168.76581722 -50.86328125 171.015625 C-35.94067619 185.38793877 -13.72731111 189.72514197 6.125 189.5234375 C16.97422432 189.29741199 25.77863076 187.06128641 35.5625 182.375 C36.62597656 181.8696875 37.68945312 181.364375 38.78515625 180.84375 C46.17749332 177.10793246 57.67761462 171.14477077 61.5625 163.375 C62.2225 163.375 62.8825 163.375 63.5625 163.375 C63.95695312 162.52228516 63.95695312 162.52228516 64.359375 161.65234375 C65.64827679 159.21263678 67.15925518 157.14554995 68.8125 154.9375 C72.93970299 149.11642412 75.79782362 142.93267752 78.5625 136.375 C78.8821875 135.61960938 79.201875 134.86421875 79.53125 134.0859375 C86.70461437 116.08079293 85.39317594 93.81883651 78.15307617 76.02905273 C69.28923486 55.50011828 53.66572095 40.03913343 33.5625 30.375 C30.67254483 29.38577329 27.85082677 28.58919818 24.8671875 27.9453125 C22.5625 27.375 22.5625 27.375 20.81298828 26.35595703 C17.85867076 25.06821047 15.46901834 25.11587244 12.25 25.109375 C10.38794922 25.10550781 10.38794922 25.10550781 8.48828125 25.1015625 C7.19277344 25.10929687 5.89726562 25.11703125 4.5625 25.125 C2.66564453 25.11339844 2.66564453 25.11339844 0.73046875 25.1015625 C-17.2711003 25.13864091 -31.54697089 29.14542729 -46.4375 39.375 C-47.55125 40.07625 -48.665 40.7775 -49.8125 41.5 C-61.71416025 50.0011859 -72.69596649 64.9744153 -76.15234375 79.34765625 C-76.29349609 80.35119141 -76.29349609 80.35119141 -76.4375 81.375 C-75.80328125 81.51035156 -75.1690625 81.64570313 -74.515625 81.78515625 C-64.21343834 84.16148936 -54.36770344 87.78450953 -44.4375 91.375 C-46.74723188 94.83959783 -47.6640086 94.97721375 -51.4375 96.375 C-53.57384461 97.49954024 -55.69741037 98.6484747 -57.8125 99.8125 C-65.59260879 104.04282156 -73.56614022 107.83972032 -81.57958984 111.60522461 C-82.52527832 112.0513208 -83.4709668 112.49741699 -84.4453125 112.95703125 C-85.28723145 113.35124268 -86.12915039 113.7454541 -86.99658203 114.15161133 C-89.56189001 115.4373443 -91.98262809 116.89130584 -94.4375 118.375 C-97.40909217 119.67960144 -100.28969424 120.61633223 -103.4375 121.375 C-111.23313024 107.46112427 -118.43633987 93.37315794 -125.21972656 78.94091797 C-127.86297035 73.37145282 -130.64637185 67.87152198 -133.4375 62.375 C-125.48445135 64.12191242 -117.91518786 66.50435353 -110.31469727 69.39306641 C-106.56678135 70.78131301 -103.44995683 71.72834747 -99.4375 71.375 C-99.25316406 70.7665625 -99.06882813 70.158125 -98.87890625 69.53125 C-94.3234342 55.76360112 -86.7372713 42.65530103 -76.4375 32.375 C-75.7775 32.375 -75.1175 32.375 -74.4375 32.375 C-74.4375 31.715 -74.4375 31.055 -74.4375 30.375 C-56.03916533 10.30914255 -26.61300316 -0.30565148 0 0 Z " fill="#2258D4" transform="translate(214.4375,112.625)"/> -<path d="M0 0 C0 29.7 0 59.4 0 90 C-12.44775918 82.0786987 -12.44775918 82.0786987 -17.9375 77.9375 C-26.32569792 71.7017826 -34.89955192 65.74116181 -43.47363281 59.76513672 C-47.57565475 56.8991125 -51.66337683 54.01292092 -55.75 51.125 C-56.48331543 50.60687744 -57.21663086 50.08875488 -57.97216797 49.55493164 C-59.64835267 48.37029492 -61.32421659 47.18520433 -63 46 C-61.59722694 41.79168083 -58.55583951 40.40105878 -55 38.0625 C-53.52532315 37.07138074 -52.05138785 36.0791574 -50.578125 35.0859375 C-49.81693359 34.57514648 -49.05574219 34.06435547 -48.27148438 33.53808594 C-44.72812729 31.13877923 -41.23884366 28.66512058 -37.75 26.1875 C-33.09722226 22.89049136 -28.43818961 19.60319409 -23.76171875 16.33984375 C-22.87581055 15.72149658 -21.98990234 15.10314941 -21.07714844 14.46606445 C-19.36895721 13.27465953 -17.66002139 12.08432117 -15.95019531 10.89526367 C-11.39525428 7.7171275 -6.91727557 4.46782162 -2.51586914 1.07958984 C-1 0 -1 0 0 0 Z " fill="#235BDB" transform="translate(422,169)"/> -<path d="M0 0 C0.79148438 0.10699219 1.58296875 0.21398437 2.3984375 0.32421875 C3.21570313 0.44410156 4.03296875 0.56398438 4.875 0.6875 C17.6506458 2.50067544 17.6506458 2.50067544 29.875 -0.3125 C30.865 0.3475 31.855 1.0075 32.875 1.6875 C19.26334162 4.98016898 5.53247263 4.34773815 -8.125 1.6875 C-5.14933369 -0.05386526 -3.40145502 -0.52088702 0 0 Z " fill="#1C44A0" transform="translate(203.125,323.3125)"/> -<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C1.5373877 1.44166504 1.07477539 1.88333008 0.59814453 2.33837891 C-9.03439396 11.59279539 -17.74714282 20.75539833 -25 32 C-25 24.35323911 -15.98817815 15.38139039 -11 10 C-10.34 10 -9.68 10 -9 10 C-9 9.34 -9 8.68 -9 8 C-7.28515625 6.30859375 -7.28515625 6.30859375 -5.0625 4.4375 C-4.33160156 3.81746094 -3.60070312 3.19742188 -2.84765625 2.55859375 C-2.23792969 2.04425781 -1.62820312 1.52992188 -1 1 C-0.67 0.67 -0.34 0.34 0 0 Z " fill="#1C45A4" transform="translate(149,135)"/> -<path d="M0 0 C1.48463745 0.08640747 1.48463745 0.08640747 2.99926758 0.17456055 C4.07241211 0.2293457 5.14555664 0.28413086 6.2512207 0.34057617 C7.93054688 0.44982422 7.93054688 0.44982422 9.64379883 0.5612793 C11.34052734 0.653125 11.34052734 0.653125 13.0715332 0.74682617 C15.86755186 0.90019428 18.66193646 1.06805884 21.45629883 1.2487793 C21.45629883 1.5787793 21.45629883 1.9087793 21.45629883 2.2487793 C8.09129883 2.7437793 8.09129883 2.7437793 -5.54370117 3.2487793 C-2.54370117 0.2487793 -2.54370117 0.2487793 0 0 Z " fill="#1C3E90" transform="translate(205.543701171875,134.751220703125)"/> -<path d="M0 0 C5.94 0 11.88 0 18 0 C14.05050415 2.63299723 10.51142372 3.43466949 5.765625 2.61328125 C3.82951859 2.12925465 1.91244799 1.57037923 0 1 C0 0.67 0 0.34 0 0 Z " fill="#1F50C1" transform="translate(207,303)"/> -<path d="M0 0 C0.99 0.33 1.98 0.66 3 1 C2.55269531 1.36351562 2.10539063 1.72703125 1.64453125 2.1015625 C-2.38793652 5.4547873 -5.76004454 8.87642033 -9 13 C-9.33 12.34 -9.66 11.68 -10 11 C-8.89718873 9.72799029 -7.79269231 8.45744147 -6.6875 7.1875 C-6.07261719 6.47980469 -5.45773438 5.77210938 -4.82421875 5.04296875 C-3.26665665 3.29863261 -1.67341125 1.63221959 0 0 Z " fill="#1E4CB6" transform="translate(158,157)"/> -<path d="M0 0 C-3.7559214 3.42931954 -8.23466745 4.56429083 -13 6 C-9.41603189 2.00249711 -5.50726745 -1.83575582 0 0 Z " fill="#1D4CB7" transform="translate(263,315)"/> -<path d="M0 0 C-3.82288811 2.20551237 -7.78749935 3.48595996 -12.1875 2.51953125 C-12.785625 2.34808594 -13.38375 2.17664063 -14 2 C-12.42085985 1.46658816 -10.83645613 0.94874237 -9.25 0.4375 C-8.36828125 0.14746094 -7.4865625 -0.14257812 -6.578125 -0.44140625 C-3.92165684 -1.01697435 -2.52968894 -0.89248371 0 0 Z " fill="#1C439E" transform="translate(194,116)"/> -<path d="M0 0 C0 0.33 0 0.66 0 1 C-9.41002278 3.48063781 -9.41002278 3.48063781 -14 2 C-14 1.67 -14 1.34 -14 1 C-9.23692563 0.04738513 -4.8333472 -0.08333357 0 0 Z " fill="#1C4198" transform="translate(208,113)"/> -<path d="M0 0 C0.66 0.33 1.32 0.66 2 1 C0.515 4.465 0.515 4.465 -1 8 C-1.66 7.67 -2.32 7.34 -3 7 C-1.125 1.125 -1.125 1.125 0 0 Z " fill="#1E4FBD" transform="translate(312,261)"/> -</svg> +<svg xmlns="http://www.w3.org/2000/svg" width="432" height="432" viewBox="0 0 432 432"> +<g> +<path d="M 336.49 415.96 C323.41,417.20 107.96,417.16 95.01,415.91 C71.13,413.61 54.60,406.33 38.64,391.06 C25.79,378.77 17.59,364.61 14.02,348.50 C10.51,332.62 9.79,303.24 10.28,194.27 C10.75,86.62 10.97,81.73 15.92,66.00 C23.32,42.46 47.46,18.32 71.00,10.92 C87.72,5.66 90.34,5.56 216.50,5.56 C342.61,5.56 345.19,5.66 362.01,10.90 C385.02,18.08 408.72,41.90 416.34,65.50 C421.34,81.02 421.29,79.78 421.74,201.00 C422.17,319.90 421.78,335.61 417.95,350.81 C414.88,363.03 408.17,375.49 399.20,385.65 C390.01,396.05 382.81,401.52 370.50,407.46 C360.16,412.45 352.01,414.48 336.49,415.96 ZM 215.00 171.05 C229.18,172.23 267.83,171.30 280.00,169.48 C302.56,166.11 323.62,159.90 339.33,151.98 C355.23,143.97 366.17,134.01 370.52,123.61 C373.02,117.63 372.64,108.07 369.63,101.51 C366.77,95.25 357.29,85.10 350.00,80.48 C324.02,63.99 284.37,54.72 240.41,54.84 C222.00,54.89 192.88,57.56 185.46,59.88 C183.83,60.39 179.12,61.57 175.00,62.50 C159.07,66.09 138.29,74.95 127.90,82.57 C121.01,87.63 114.10,95.64 111.12,102.00 C104.40,116.40 111.73,133.42 129.77,145.31 C135.61,149.15 137.39,149.81 143.21,150.25 C150.23,150.79 155.41,153.12 163.31,159.28 C169.12,163.82 192.73,169.20 215.00,171.05 ZM 136.50 366.94 C145.00,367.65 147.96,367.50 157.50,365.90 C200.00,358.77 230.27,318.22 225.22,275.20 C224.06,265.31 219.22,249.38 217.46,249.64 C216.93,249.71 212.08,250.56 206.67,251.52 C198.51,252.97 196.92,253.54 197.34,254.88 C197.62,255.77 199.08,259.88 200.57,264.00 C203.87,273.08 205.01,288.85 203.03,297.85 C199.24,315.01 186.59,331.18 170.97,338.84 C158.91,344.75 151.62,346.37 140.00,345.73 C119.47,344.59 102.25,334.64 90.99,317.39 C83.91,306.54 81.49,298.02 81.53,284.00 C81.56,273.94 81.91,271.51 84.40,264.59 C85.96,260.24 88.83,254.17 90.79,251.12 C100.79,235.48 119.53,223.97 135.50,223.64 L 142.50 223.50 L 142.78 234.75 C142.94,241.05 143.49,246.00 144.02,246.00 C144.55,246.00 154.98,238.46 167.21,229.25 C179.43,220.04 189.90,212.18 190.46,211.79 C190.71,211.62 190.98,211.55 191.05,211.39 C191.33,210.80 188.88,208.96 172.12,196.38 C170.44,195.11 168.61,193.74 166.63,192.25 C154.74,183.31 144.57,176.00 144.03,176.00 C143.47,176.00 142.94,181.17 142.78,188.23 L 142.50 200.46 L 137.50 201.20 C123.13,203.35 118.74,204.38 111.46,207.32 C80.01,220.03 60.03,250.05 60.01,284.59 C59.99,312.72 73.37,337.58 97.11,353.50 C108.66,361.25 121.74,365.72 136.50,366.94 ZM 234.50 231.48 C236.23,232.89 238.67,233.05 252.59,232.60 C271.84,231.98 285.66,230.09 304.61,225.46 C323.66,220.80 344.41,212.16 353.26,205.20 C359.26,200.47 366.83,191.73 369.49,186.44 L 372.49 180.50 L 372.84 159.25 C373.22,136.74 372.71,134.38 369.19,142.25 C368.15,144.59 364.83,149.05 361.82,152.16 C345.89,168.63 316.18,180.34 275.50,186.18 C264.67,187.74 215.49,187.86 206.25,186.35 C202.81,185.79 200.00,185.65 200.00,186.05 C200.00,186.44 201.69,188.05 203.75,189.61 C214.77,198.00 218.67,209.52 213.98,219.86 C212.89,222.26 212.00,224.39 212.00,224.61 C212.00,224.82 214.49,225.00 217.53,225.00 C223.04,225.00 229.69,227.54 234.50,231.48 ZM 225.00 354.49 C225.00,356.07 268.94,354.20 282.44,352.05 C328.61,344.68 358.81,329.54 369.52,308.38 L 372.49 302.50 L 372.86 280.75 C373.12,264.91 372.91,259.00 372.08,259.00 C371.45,259.00 370.69,259.79 370.39,260.75 C362.63,285.56 315.19,306.16 257.78,309.65 L 247.05 310.30 L 244.45 318.40 C241.49,327.64 234.62,340.93 228.90,348.52 C226.76,351.36 225.00,354.04 225.00,354.49 ZM 250.00 284.10 L 250.00 294.04 L 259.75 293.42 C269.59,292.80 292.46,289.65 298.97,288.03 C300.91,287.54 306.10,286.25 310.50,285.15 C320.52,282.64 336.02,276.34 345.90,270.77 C355.52,265.34 365.57,255.41 369.44,247.50 C372.35,241.55 372.37,241.32 372.81,220.25 C373.28,197.43 372.83,195.49 368.89,203.22 C363.71,213.37 354.14,221.56 338.08,229.59 C316.89,240.18 288.57,246.85 257.25,248.61 C250.51,248.99 245.00,249.70 245.00,250.19 C245.00,250.69 245.64,253.88 246.41,257.29 C249.51,270.89 250.00,274.54 250.00,284.10 ZM 109.35 180.82 C109.82,181.54 110.87,181.88 111.69,181.56 C112.51,181.25 114.24,180.73 115.53,180.40 C117.54,179.90 117.98,178.99 118.55,174.16 C119.25,168.20 121.99,160.74 123.91,159.56 C124.63,159.11 123.00,156.63 119.39,152.68 C116.28,149.28 112.89,144.61 111.84,142.29 C110.80,139.98 109.47,137.79 108.88,137.43 C107.59,136.63 108.04,178.79 109.35,180.82 Z" fill="rgb(10,29,73)" data-index="0" style="opacity: 1;"></path> +<path d="M 215.00 171.05 C192.73,169.20 169.12,163.82 163.31,159.28 C155.41,153.12 150.23,150.79 143.21,150.25 C137.39,149.81 135.61,149.15 129.77,145.31 C111.73,133.42 104.40,116.40 111.12,102.00 C114.10,95.64 121.01,87.63 127.90,82.57 C138.29,74.95 159.07,66.09 175.00,62.50 C179.12,61.57 183.83,60.39 185.46,59.88 C192.88,57.56 222.00,54.89 240.41,54.84 C284.37,54.72 324.02,63.99 350.00,80.48 C357.29,85.10 366.77,95.25 369.63,101.51 C372.64,108.07 373.02,117.63 370.52,123.61 C366.17,134.01 355.23,143.97 339.33,151.98 C323.62,159.90 302.56,166.11 280.00,169.48 C267.83,171.30 229.18,172.23 215.00,171.05 ZM 214.50 165.92 C220.52,167.44 267.85,166.48 276.50,164.67 C280.35,163.87 287.55,162.68 292.50,162.02 C297.45,161.37 306.70,159.07 313.06,156.92 C319.43,154.76 325.33,153.00 326.18,153.00 C327.03,153.00 328.80,152.16 330.12,151.14 C331.43,150.11 333.55,148.94 334.84,148.54 C343.07,145.96 362.00,131.24 362.00,127.42 C362.00,126.77 363.13,123.69 364.52,120.55 C366.90,115.16 366.96,114.61 365.63,110.12 C363.37,102.51 358.39,93.52 354.81,90.60 C344.77,82.38 323.43,71.92 311.50,69.38 C308.20,68.68 302.35,67.24 298.50,66.18 C280.95,61.36 273.00,60.57 242.02,60.53 C210.43,60.50 202.14,61.23 184.00,65.67 C178.23,67.09 171.58,68.63 169.23,69.10 C166.88,69.57 161.70,71.34 157.73,73.05 C153.75,74.76 148.93,76.83 147.00,77.65 C128.05,85.76 115.06,99.72 115.02,112.04 C115.00,118.32 117.69,126.95 120.72,130.27 C121.83,131.49 123.27,133.51 123.91,134.75 C124.55,135.99 125.48,137.00 125.98,137.00 C126.48,137.00 129.53,138.55 132.76,140.44 C135.99,142.33 140.85,144.40 143.56,145.03 C146.28,145.66 153.45,148.58 159.50,151.51 C165.55,154.44 172.30,157.57 174.49,158.47 C180.19,160.80 205.82,166.08 208.36,165.45 C209.54,165.16 212.30,165.37 214.50,165.92 ZM 136.50 366.94 C121.74,365.72 108.66,361.25 97.11,353.50 C73.37,337.58 59.99,312.72 60.01,284.59 C60.03,250.05 80.01,220.03 111.46,207.32 C118.74,204.38 123.13,203.35 137.50,201.20 L 142.50 200.46 L 142.78 188.23 C142.94,181.17 143.47,176.00 144.03,176.00 C144.57,176.00 154.74,183.31 166.63,192.25 C168.61,193.74 170.44,195.11 172.12,196.38 C188.88,208.96 191.33,210.80 191.05,211.39 C190.98,211.55 190.71,211.62 190.46,211.79 C189.90,212.18 179.43,220.04 167.21,229.25 C154.98,238.46 144.55,246.00 144.02,246.00 C143.49,246.00 142.94,241.05 142.78,234.75 L 142.50 223.50 L 135.50 223.64 C119.53,223.97 100.79,235.48 90.79,251.12 C88.83,254.17 85.96,260.24 84.40,264.59 C81.91,271.51 81.56,273.94 81.53,284.00 C81.49,298.02 83.91,306.54 90.99,317.39 C102.25,334.64 119.47,344.59 140.00,345.73 C151.62,346.37 158.91,344.75 170.97,338.84 C186.59,331.18 199.24,315.01 203.03,297.85 C205.01,288.85 203.87,273.08 200.57,264.00 C199.08,259.88 197.62,255.77 197.34,254.88 C196.92,253.54 198.51,252.97 206.67,251.52 C212.08,250.56 216.93,249.71 217.46,249.64 C219.22,249.38 224.06,265.31 225.22,275.20 C230.27,318.22 200.00,358.77 157.50,365.90 C147.96,367.50 145.00,367.65 136.50,366.94 ZM 234.50 231.48 C229.69,227.54 223.04,225.00 217.53,225.00 C214.49,225.00 212.00,224.82 212.00,224.61 C212.00,224.39 212.89,222.26 213.98,219.86 C218.67,209.52 214.77,198.00 203.75,189.61 C201.69,188.05 200.00,186.44 200.00,186.05 C200.00,185.65 202.81,185.79 206.25,186.35 C215.49,187.86 264.67,187.74 275.50,186.18 C316.18,180.34 345.89,168.63 361.82,152.16 C364.83,149.05 368.15,144.59 369.19,142.25 C372.71,134.38 373.22,136.74 372.84,159.25 L 372.49 180.50 L 369.49 186.44 C366.83,191.73 359.26,200.47 353.26,205.20 C344.41,212.16 323.66,220.80 304.61,225.46 C285.66,230.09 271.84,231.98 252.59,232.60 C238.67,233.05 236.23,232.89 234.50,231.48 ZM 225.00 354.49 C225.00,354.04 226.76,351.36 228.90,348.52 C234.62,340.93 241.49,327.64 244.45,318.40 L 247.05 310.30 L 257.78 309.65 C315.19,306.16 362.63,285.56 370.39,260.75 C370.69,259.79 371.45,259.00 372.08,259.00 C372.91,259.00 373.12,264.91 372.86,280.75 L 372.49 302.50 L 369.52 308.38 C358.81,329.54 328.61,344.68 282.44,352.05 C268.94,354.20 225.00,356.07 225.00,354.49 ZM 250.00 284.10 C250.00,274.54 249.51,270.89 246.41,257.29 C245.64,253.88 245.00,250.69 245.00,250.19 C245.00,249.70 250.51,248.99 257.25,248.61 C288.57,246.85 316.89,240.18 338.08,229.59 C354.14,221.56 363.71,213.37 368.89,203.22 C372.83,195.49 373.28,197.43 372.81,220.25 C372.37,241.32 372.35,241.55 369.44,247.50 C365.57,255.41 355.52,265.34 345.90,270.77 C336.02,276.34 320.52,282.64 310.50,285.15 C306.10,286.25 300.91,287.54 298.97,288.03 C292.46,289.65 269.59,292.80 259.75,293.42 L 250.00 294.04 ZM 239.93 227.08 C243.34,229.21 287.50,225.09 300.00,221.48 C303.02,220.60 307.86,219.46 310.74,218.93 C315.17,218.12 337.20,209.47 340.57,207.22 C341.16,206.82 343.86,205.07 346.57,203.31 C356.28,197.04 358.60,194.65 361.92,187.58 C363.70,183.78 365.63,177.99 366.20,174.73 C367.14,169.43 367.04,168.53 365.27,166.15 C361.84,161.53 357.00,162.37 345.50,169.58 C339.00,173.65 326.32,179.69 322.50,180.52 C320.85,180.89 316.12,182.22 312.00,183.49 C304.91,185.67 297.19,187.38 287.00,189.03 C284.52,189.44 280.23,190.24 277.45,190.82 C274.67,191.40 263.87,192.14 253.45,192.47 C223.73,193.41 222.69,193.56 220.55,197.18 C218.29,201.02 218.89,208.90 221.89,214.79 C224.07,219.05 231.56,224.87 235.89,225.66 C237.25,225.90 239.07,226.55 239.93,227.08 ZM 129.00 360.09 C131.48,360.52 137.77,360.61 143.00,360.31 C148.23,360.00 154.75,359.65 157.50,359.54 C164.09,359.26 169.39,357.34 179.07,351.73 C189.08,345.93 191.90,343.88 192.51,341.98 C192.83,340.95 193.94,339.84 194.97,339.51 C197.72,338.64 207.93,326.27 210.22,321.03 C211.32,318.54 213.13,314.72 214.26,312.54 C217.34,306.57 219.15,294.87 219.03,281.63 C218.90,267.25 217.70,263.48 213.02,262.79 C208.74,262.16 206.61,264.31 207.44,268.41 C210.06,281.37 209.95,295.76 207.16,303.08 C203.68,312.23 196.48,325.10 192.57,329.17 C186.70,335.28 176.65,342.30 171.00,344.23 C168.52,345.08 162.53,347.18 157.67,348.89 C148.26,352.21 139.55,352.95 134.32,350.89 C132.77,350.28 128.80,349.15 125.50,348.38 C116.34,346.25 106.07,340.59 97.50,332.94 C90.24,326.46 88.30,323.95 84.74,316.50 C83.95,314.85 82.57,312.88 81.65,312.13 C80.70,311.34 80.00,309.30 80.00,307.33 C80.00,305.44 79.25,302.46 78.33,300.70 C74.39,293.13 76.13,265.11 80.97,258.07 C81.95,256.66 83.89,253.48 85.30,251.00 C88.68,245.03 99.95,232.85 105.64,229.01 C114.27,223.19 120.22,220.75 127.97,219.89 C137.53,218.81 142.44,220.02 144.47,223.94 C145.28,225.52 147.46,227.74 149.29,228.87 C152.11,230.61 153.22,230.78 156.29,229.93 C160.20,228.85 163.71,226.31 171.25,219.11 C175.21,215.33 176.00,213.97 176.00,210.94 C176.00,207.81 175.19,206.53 170.21,201.78 C167.02,198.74 162.44,195.25 160.04,194.02 C154.09,190.98 149.46,192.27 146.30,197.84 C144.21,201.52 139.07,205.00 135.72,205.00 C134.91,205.00 133.68,205.68 133.00,206.50 C132.32,207.32 130.42,208.00 128.78,208.00 C127.15,208.00 124.61,208.65 123.15,209.44 C121.69,210.23 116.85,212.05 112.39,213.48 C107.60,215.01 103.34,217.07 101.98,218.52 C100.72,219.86 98.76,221.26 97.64,221.62 C95.14,222.41 79.90,238.22 78.49,241.47 C77.93,242.76 76.72,244.65 75.81,245.66 C72.06,249.80 67.14,266.95 66.01,279.79 C65.17,289.36 65.28,290.60 67.55,297.57 C68.90,301.69 70.00,306.89 70.00,309.13 C70.00,312.31 70.62,313.78 72.84,315.85 C74.45,317.36 75.98,320.09 76.37,322.15 C76.81,324.51 78.72,327.60 81.78,330.89 C84.37,333.69 87.80,337.59 89.40,339.55 C90.99,341.51 93.91,343.99 95.90,345.07 C97.88,346.14 101.70,348.60 104.38,350.53 C107.06,352.45 110.44,354.28 111.88,354.59 C113.32,354.90 115.40,355.56 116.50,356.06 C121.39,358.26 124.81,359.37 129.00,360.09 ZM 247.26 349.30 C251.95,350.20 286.14,346.82 297.50,344.33 C310.88,341.40 324.51,337.13 329.00,334.47 C330.92,333.33 334.08,331.93 336.00,331.36 C340.62,329.99 348.87,323.96 356.62,316.29 C361.93,311.04 363.21,309.05 364.95,303.43 C367.51,295.12 367.53,291.38 365.01,287.25 C362.37,282.92 360.19,283.08 351.58,288.27 C334.33,298.67 308.58,308.32 293.50,310.03 C289.10,310.53 283.07,311.60 280.11,312.41 C277.14,313.23 273.49,313.65 271.98,313.35 C270.44,313.04 266.70,313.68 263.38,314.83 C260.14,315.95 256.38,317.11 255.00,317.41 C252.32,318.00 249.19,322.13 244.75,330.97 C240.36,339.71 241.52,348.19 247.26,349.30 ZM 263.16 286.99 C265.55,287.99 271.58,287.66 280.50,286.03 C283.25,285.53 290.00,284.35 295.50,283.42 C301.00,282.49 307.88,280.88 310.79,279.86 C313.70,278.84 316.76,278.00 317.60,278.00 C319.05,278.00 323.67,276.04 336.00,270.19 C351.11,263.02 362.36,252.61 364.69,243.61 C366.33,237.28 366.40,226.80 364.80,225.20 C362.56,222.96 357.81,223.98 350.14,228.35 C345.94,230.74 341.15,233.40 339.50,234.25 C323.52,242.50 297.22,249.55 273.50,251.95 C262.51,253.06 260.08,253.93 256.66,257.99 C254.34,260.75 254.00,262.04 254.00,268.11 C254.00,277.60 257.37,284.54 263.16,286.99 ZM 109.35 180.82 C108.04,178.79 107.59,136.63 108.88,137.43 C109.47,137.79 110.80,139.98 111.84,142.29 C112.89,144.61 116.28,149.28 119.39,152.68 C123.00,156.63 124.63,159.11 123.91,159.56 C121.99,160.74 119.25,168.20 118.55,174.16 C117.98,178.99 117.54,179.90 115.53,180.40 C114.24,180.73 112.51,181.25 111.69,181.56 C110.87,181.88 109.82,181.54 109.35,180.82 Z" fill="rgb(35,81,188)" data-index="1" style="opacity: 1;"></path> +<path d="M 214.50 165.92 C212.30,165.37 209.54,165.16 208.36,165.45 C205.82,166.08 180.19,160.80 174.49,158.47 C172.30,157.57 165.55,154.44 159.50,151.51 C153.45,148.58 146.28,145.66 143.56,145.03 C140.85,144.40 135.99,142.33 132.76,140.44 C129.53,138.55 126.48,137.00 125.98,137.00 C125.48,137.00 124.55,135.99 123.91,134.75 C123.27,133.51 121.83,131.49 120.72,130.27 C117.69,126.95 115.00,118.32 115.02,112.04 C115.06,99.72 128.05,85.76 147.00,77.65 C148.93,76.83 153.75,74.76 157.73,73.05 C161.70,71.34 166.88,69.57 169.23,69.10 C171.58,68.63 178.23,67.09 184.00,65.67 C202.14,61.23 210.43,60.50 242.02,60.53 C273.00,60.57 280.95,61.36 298.50,66.18 C302.35,67.24 308.20,68.68 311.50,69.38 C323.43,71.92 344.77,82.38 354.81,90.60 C358.39,93.52 363.37,102.51 365.63,110.12 C366.96,114.61 366.90,115.16 364.52,120.55 C363.13,123.69 362.00,126.77 362.00,127.42 C362.00,131.24 343.07,145.96 334.84,148.54 C333.55,148.94 331.43,150.11 330.12,151.14 C328.80,152.16 327.03,153.00 326.18,153.00 C325.33,153.00 319.43,154.76 313.06,156.92 C306.70,159.07 297.45,161.37 292.50,162.02 C287.55,162.68 280.35,163.87 276.50,164.67 C267.85,166.48 220.52,167.44 214.50,165.92 ZM 239.93 227.08 C239.07,226.55 237.25,225.90 235.89,225.66 C231.56,224.87 224.07,219.05 221.89,214.79 C218.89,208.90 218.29,201.02 220.55,197.18 C222.69,193.56 223.73,193.41 253.45,192.47 C263.87,192.14 274.67,191.40 277.45,190.82 C280.23,190.24 284.52,189.44 287.00,189.03 C297.19,187.38 304.91,185.67 312.00,183.49 C316.12,182.22 320.85,180.89 322.50,180.52 C326.32,179.69 339.00,173.65 345.50,169.58 C357.00,162.37 361.84,161.53 365.27,166.15 C367.04,168.53 367.14,169.43 366.20,174.73 C365.63,177.99 363.70,183.78 361.92,187.58 C358.60,194.65 356.28,197.04 346.57,203.31 C343.86,205.07 341.16,206.82 340.57,207.22 C337.20,209.47 315.17,218.12 310.74,218.93 C307.86,219.46 303.02,220.60 300.00,221.48 C287.50,225.09 243.34,229.21 239.93,227.08 ZM 129.00 360.09 C124.81,359.37 121.39,358.26 116.50,356.06 C115.40,355.56 113.32,354.90 111.88,354.59 C110.44,354.28 107.06,352.45 104.38,350.53 C101.70,348.60 97.88,346.14 95.90,345.07 C93.91,343.99 90.99,341.51 89.40,339.55 C87.80,337.59 84.37,333.69 81.78,330.89 C78.72,327.60 76.81,324.51 76.37,322.15 C75.98,320.09 74.45,317.36 72.84,315.85 C70.62,313.78 70.00,312.31 70.00,309.13 C70.00,306.89 68.90,301.69 67.55,297.57 C65.28,290.60 65.17,289.36 66.01,279.79 C67.14,266.95 72.06,249.80 75.81,245.66 C76.72,244.65 77.93,242.76 78.49,241.47 C79.90,238.22 95.14,222.41 97.64,221.62 C98.76,221.26 100.72,219.86 101.98,218.52 C103.34,217.07 107.60,215.01 112.39,213.48 C116.85,212.05 121.69,210.23 123.15,209.44 C124.61,208.65 127.15,208.00 128.78,208.00 C130.42,208.00 132.32,207.32 133.00,206.50 C133.68,205.68 134.91,205.00 135.72,205.00 C139.07,205.00 144.21,201.52 146.30,197.84 C149.46,192.27 154.09,190.98 160.04,194.02 C162.44,195.25 167.02,198.74 170.21,201.78 C175.19,206.53 176.00,207.81 176.00,210.94 C176.00,213.97 175.21,215.33 171.25,219.11 C163.71,226.31 160.20,228.85 156.29,229.93 C153.22,230.78 152.11,230.61 149.29,228.87 C147.46,227.74 145.28,225.52 144.47,223.94 C142.44,220.02 137.53,218.81 127.97,219.89 C120.22,220.75 114.27,223.19 105.64,229.01 C99.95,232.85 88.68,245.03 85.30,251.00 C83.89,253.48 81.95,256.66 80.97,258.07 C76.13,265.11 74.39,293.13 78.33,300.70 C79.25,302.46 80.00,305.44 80.00,307.33 C80.00,309.30 80.70,311.34 81.65,312.13 C82.57,312.88 83.95,314.85 84.74,316.50 C88.30,323.95 90.24,326.46 97.50,332.94 C106.07,340.59 116.34,346.25 125.50,348.38 C128.80,349.15 132.77,350.28 134.32,350.89 C139.55,352.95 148.26,352.21 157.67,348.89 C162.53,347.18 168.52,345.08 171.00,344.23 C176.65,342.30 186.70,335.28 192.57,329.17 C196.48,325.10 203.68,312.23 207.16,303.08 C209.95,295.76 210.06,281.37 207.44,268.41 C206.61,264.31 208.74,262.16 213.02,262.79 C217.70,263.48 218.90,267.25 219.03,281.63 C219.15,294.87 217.34,306.57 214.26,312.54 C213.13,314.72 211.32,318.54 210.22,321.03 C207.93,326.27 197.72,338.64 194.97,339.51 C193.94,339.84 192.83,340.95 192.51,341.98 C191.90,343.88 189.08,345.93 179.07,351.73 C169.39,357.34 164.09,359.26 157.50,359.54 C154.75,359.65 148.23,360.00 143.00,360.31 C137.77,360.61 131.48,360.52 129.00,360.09 ZM 247.26 349.30 C241.52,348.19 240.36,339.71 244.75,330.97 C249.19,322.13 252.32,318.00 255.00,317.41 C256.38,317.11 260.14,315.95 263.38,314.83 C266.70,313.68 270.44,313.04 271.98,313.35 C273.49,313.65 277.14,313.23 280.11,312.41 C283.07,311.60 289.10,310.53 293.50,310.03 C308.58,308.32 334.33,298.67 351.58,288.27 C360.19,283.08 362.37,282.92 365.01,287.25 C367.53,291.38 367.51,295.12 364.95,303.43 C363.21,309.05 361.93,311.04 356.62,316.29 C348.87,323.96 340.62,329.99 336.00,331.36 C334.08,331.93 330.92,333.33 329.00,334.47 C324.51,337.13 310.88,341.40 297.50,344.33 C286.14,346.82 251.95,350.20 247.26,349.30 ZM 263.16 286.99 C257.37,284.54 254.00,277.60 254.00,268.11 C254.00,262.04 254.34,260.75 256.66,257.99 C260.08,253.93 262.51,253.06 273.50,251.95 C297.22,249.55 323.52,242.50 339.50,234.25 C341.15,233.40 345.94,230.74 350.14,228.35 C357.81,223.98 362.56,222.96 364.80,225.20 C366.40,226.80 366.33,237.28 364.69,243.61 C362.36,252.61 351.11,263.02 336.00,270.19 C323.67,276.04 319.05,278.00 317.60,278.00 C316.76,278.00 313.70,278.84 310.79,279.86 C307.88,280.88 301.00,282.49 295.50,283.42 C290.00,284.35 283.25,285.53 280.50,286.03 C271.58,287.66 265.55,287.99 263.16,286.99 Z" fill="rgb(38,98,236)" data-index="2" style="opacity: 1;"></path> + +</g> +</svg> \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 4cdf96a3..f4e0e4b8 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -15,7 +15,7 @@ <property name="EMAIL_ERROR_SUBJECT" value="⚠ BackupManager - Critical error in application ⚠" /> <property name="EMAIL_INFO_SUBJECT" value="🆕 BackupManager - New user registered!! 🆕" /> <property name="EMAIL_CONFIRMATION_SUBJECT" value="BackupManager - Welcome!" /> - <property name="LOG_DIR" value="./src/main/resources/res/logs" /> + <property name="LOG_DIR" value="${user.home}/.backupmanager/logs" /> <property name="MAX_HISTORY" value="7" /> <property name="TOTAL_SIZE" value="100MB" /> @@ -38,11 +38,6 @@ </encoder> </appender> - <!-- async appender --> - <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> - <appender-ref ref="ROLLING_FILE" /> - </appender> - <!--Appernder only for the error logs--> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_DIR}/error.log</file> @@ -129,7 +124,7 @@ <!-- Root logger --> <root level="debug"> <appender-ref ref="CONSOLE" /> - <appender-ref ref="ASYNC" /> + <appender-ref ref="ROLLING_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </configuration> diff --git a/src/main/resources/res/img/logo.ico b/src/main/resources/res/img/logo.ico index 2d48e9d7c64b1e89bb199cbf2ae3fdfb66509aca..9a830e9cc032b27b775e61dd483feccda0663042 100644 GIT binary patch literal 30154 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 literal 67646 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 diff --git a/src/main/resources/res/img/logo.png b/src/main/resources/res/img/logo.png index 830c0f66ea1a3e866dbceb35ca3f413554addc66..9ae1fbcb6dd51f248c9a72838ee57d6e47bbd6cf 100644 GIT binary patch literal 59614 zcmX6^by!s2(_gxgPHAaamJ+0wF6qvtK|;D?0cntsMx+s?JB3{umJ*O|1d$HudiVQ# z@AK?G``mNx<Y(s0%!$*|P$Iyk!36*S1S-n%IsgDl-G4u9Oyobq8XztJU@urj9<1m8 z`S3?TBF{+vI%R;toOy_4?fqrv?#2q%n`?N^W=fw;7UEaUTneL+)GN~#Ta{==wNobA z7Qk}Y0u%-9H3aR7A=)5~6%qZTJJaYyb!i8vQk{ulYJZg_c(H4~T6<DcBPdAMGn_aU zJ3L%fb-aJPzkeguk0@#Q-q;|pP1<AmutjO&bct44n$AQ(`P`)``*)=@G_0&XBXo_u zFx_<N4f*2w+~irXo_5c(aj<31^c8phC;ZCLgRaR<0jGr+r*v|RN1hK`(RlYXX;la* zH2#7jl{NM+UQ6qr=sp=ZmFK^oyG(tQOh2<&blz%5SY(BSF8QT|%!B`Y_6cdIF`f%} zdv{Xc`_xBq*12<Q*3XlnVLXaTTixJxC;^&|64(=7<TrjnA^RZ57PQgW9fOouNP{!; zeNcl<|D?M3*QTC~E9wTX5wXc8uWQy?#7E70!pI5nBbqYvf~IRiF}}t1+|u>1DK9oD zZl}qlo2rp7YKuYVpChNSOL^+HIVaJWgN$se-+f3EX|9@tzepGg&WuZlN}$U`1uB@m zG#hP8??pY(H{(+OwylHUIK3kSCV6pOdfj#Iizg}(*V3&d>!*j-_CA^^K0KP^t8to5 z|LMcUlaR)YF96@U+{&gpQ8|uWeLd2p(aFW#qN?cPRrHl4kU8Xkt;-R+fl}hm$sZ*Z ztI=kGjTa()D3Tpw(&iQ&Hi_T57!wm>&L;R^##(k^Ai@)18RJkLsV2SR>)|ALzqw%1 zb{KOnw#i@Zj<bWE)#uK@lh<~LcjAym??vcziCK-ca3@p6r#Gdp!3iD77>bh1YM>?N z3ZoDcY~$G!h(UO_eF$;EBh9oU>l?#Ph}+66_Ppid%<t~11Kiy8lE^54TWB*~&3xS# zA(>jQwE@y599jw@jx=Lr3z*97L}+$7Hv$DBu~#urXEca6X_le;Ei<s^1EK)FGr4(Q zyM6B9ar>$5K>}#myW?Z_WoSpR&1}ey^#T8e2TIl4P~kJ^o6XJ1nO|c>DS43lwZ`Ay zAWQr7uvhQ_6<E7@j$sC4z7fOya%~CU;cg42&ob<@q3oL!?CnwD6=_+sGEei)nZpYB z=97bxx$}wQzDfqbQ&(>95rB^&!VTR=uHxG||Ko9MN48Q2Xc*E4u<a>D&=#yU?eFU9 z<f_9P-<scUgoHbUkz;HG;$&UB-*VyZ$63O52+Y2KQSebRXQvOC0;J!Vb7e<$Bmxlo z%68Sf0E(4FcT%ai2-*|N!5zNsb4vGH(zdQB)NI~DfX?fV-y-R0X*%B>*F|Q@FE}d1 zVV-D}N0izd`6$kAtP)?Y=C$7^8~Mc{)C{l9*q}Qol-cJ~BoTRdE5U%Oo(NPtJQH=k z;=*Qhk;3qF<f8$s@G8_maR<+%ddta#pb^+s6vB(eWxx;|ACdC#1D)_g0f0+NUdnOI z2Cn<#V`bZeIEOn<cM?KO2d1N=BPI@BMfaHyCKD=!-=mpR%=gVRX_eB|AOLL3(y6(6 z(4D{QsEsd8q>?wz`8Li)+;7#|x*{VoiD-RmN=r+<Eh0LwC8UY4Q9+T>uf1IX!!Uu! z?nW?U-f)KtNneBAZWA^Td(fXc>zOJm42lSgMs#wtak8Dq80hHaV#D2O>jn!a?v&ad zT*?0BEp8q-Ey@z*!a7{j0BlcM*0@weA9^EHo-DV{uRU&CwISig4uR#W$cLSD)>zB$ z(UhYC0kEAcX#?7TqqU})^RVBd!lq#ZAHPDMDkrzjEjP|9Ij|>;<VpX@adC2*tAgn; zi|(c#`LS(DDI>yCQ8GndK53ghNt!(o;=-EUcOAa}$5jUj_==&Ju^rWvy*&J|cb^%R zhu0<jF}$z<Pj-_jjR=OJ9p&s0l=~kg4nW+7o_NH%0+_Av&8{%9Ew(wt`H&ekQPtij zoY5K!6aDGB?comx)ra8Jp08<XY14HPVkewtPr7DLN@VDvWWDpYDD>FoGO^}OpuqY8 zMqfRPfgPlIGZbzNAx2yN6SL6Y2r;6;WN^Chzx!t@44l};l{iXFTgm}}k^K`?u#rS! z@(qI)QHEg99DBuo?+>E-gB6ROakAbQF3@yNF<n!lKq6(g;p_LU?ziu?K~tJ`V_Hb{ ztK#l!PzheGZ=5p}6&|D8fnBP=uO|Ss=_+V48121J;-ytUR#w)st_U12I89^+>AsfO zdLedwee2*LF~oSI_z#}fIn2zIsN(FkjzQLc+<_v*E?miE@b{-x+bk)HhJiz>+n9CW z%`-;qm}kx2hWP)?HvJ=ag<KKvad}e=A3RO`9+b%2KZpZydV{}66hLM5WLW|s89C4S zlK<b1wz&H`*->cwbCi7{4W&fNM$&#sc(Cb>fh#}w0&p)ln1M5^=)V^^FnoB&<S=~> z=e3AJ3o?J874e5Jhyr*jd+_F%<ev<+ro{a3Bs8>CR8;<^VGm9|8FEy3Ee6Ni;M8AW zW>=M@@J`iZxBp-)8C2a`{ZF^5C37UW`zt+(5zse3Gelpz^<SVHnHUfR7H#x`rTB(z zL__HRCva~S3?HESIi9XYJrU_w$2W%A`IIos%K*42Mw@>a#y1GTe^R{A!oe`0njO$$ z|BB&LhY<V~08q~~j!Y8zfOje4aVw$)LMcP?kv4scy$`8KDAK5jxo==YNa*GZ!)%MP zdAcASE^S)RH1S?Ooc}%f1%<$9u^d_Tf7whU=jz^>0Mef9J>BZf^ZC?>|0$y_P8NUP zaf#jcm`J?xuVTYpmkg0B_M<tj#edvwb5`Ur)g9N;uRb+bXz*2s%Dh1}mJqTE_%4Qr z$HOa0W*(D(+88Y>Yw_z(*?(rW+dE~w`RXc8JHLa~cL`}G^ti*12o9*>-}FfZq*PU$ z<gFRm2*br1r|uf}(bN!J&nURNWjSw3F8;GN9hx80m~RTWhBZhbk>&E?rK!(SxQZc2 zh)236D9T{rV$(jC-1i-kI4`uUEV?-C1Q+-W1ZsYnUN`)2xftyuFPXX*hL~<_J|+y) zZV~E!+Y=p8b^aeSs+6WD#f&>-vq#3aBua$!+Gy&#_qOISAcE0?-W|;n-K!)d0w9GJ zSLpUUW5qv3jp_Zl98X()3NMfjWBZ3*|Nm_0ckJ%lVBVEX1vX|O>+B>FNaWoQNXLE; zJuu@xyJb_;YJDakn?L(1g@JHtCmIOlqbhuOzjeMF`~2m9y1UsUAbV8~jf=HgJRqiF z8dk)pYjUAI^skjh2Bxn_DV7R_7C%~@2rPf4$PmRX#HTs&p~JXs+#hNEAAN7L?X|W3 zSQ4~qj%WDO0OhZ2;fW6yZ4x3|JthBJ-HnVz(lHUoEFQbW-$^46O92Sn6)5YCTo_JL zO56WLVZ*nt8vJQ7)7XmLy05}4R=|I}gi+<B7yBvte>4t|w2?fc0uwf`hRx&HQL-Iz z_hoM}<A=~W{-+A5A5n;TVriK1?}>WwYh21=m$7+avnK_!L(<Uh|KA`Z3V{sd)$VyF zRxH#T!Di4h1^QkRwu*<wHVKchNhG=>@%?28W|k!wWo&vdnoh06ohklF9Wc*VO#J!t z|2B!DL}t8KJdKiR-Jrhj>omc7=eBzcwx9mVq8uC`L-U%4rm~8YXBpGm)>Jn!<<`;R zI_F^yp%mlKa67`R0Nc#nRQ=D_<I|`Dhy?#2V&fh1_3u2}o&&Aim0$GDFNzkLeI{#U z;rFM`1{e%6$-)T4jig$)z6rk6*ESg`%BN`U&ok-{(GTXUHCwoa>Jv&cyroWx?z!Cs zkHYEc(Uf#p543G<n_@vF@VhBSS_+7DbbyhLKm2Z{?cr@HCNH-+33z~^&k<M7nxfAd zhd7&PqbdMh=#tj0$b(e1R;y>R`9MV6_c3tjdyU`W{;y1ZV^`x}znDWA1h~0rqEn)a z%8dP;GP8spayC7|$*LaoS#KJIRO{%E`S`ZB-QjBf($U7JZ^+McsN4)U9(>Q*o6pt{ zw=ND$nlHQcJC}z3Y@w3zq7IM)m}JNz-3bf7QX61|t>aGP)1tlj4lp~pSqpf^SEZCg zv^@|4TeZhX7|s}Jajl0isFsP9@X%czSWBz;%2bG26hXwIY9sFDXlmcOpe|6|Bd+Kf zValv8`km2WcXH@#!@&U|CQ9@`ZZt3J>UWEQ!3nO{zL^C0Xry7$c4CdMpSmJ8)yWfO zSK(#KPlURswDlQ!ESYI;SR2@?Z+KYCr$RRQ-6gsvakC(<sgnd*@OAHq9%*yv1=AmW z3!&=KG^@<ojfRNzZW3rCDx>+@Amh=d-HXV&_ZgWI)^yg~KRG7eQEjx%j=WLIq(jM% zP7;HDZa4(Ck8;#?hoWUk#lMfWS*PklCOVgw__$TIOCFkvDu2gH!^J1@%>uPXXrhRW z$Uks<xfJm*xzAfUmttj-v=oA}-GuRGl8?k^3chXl;D2?_#xry`|M8(n=sooSHLc+m zAxbh1h%;@HHB3n`E&*t=Ki0ukb)XJ6&<GymkFUWiU>9eT&{%QEKJ(#M1N1NhCi~Y( zyZM6O<8_Dmm=Db>_08if{%R(03Gh924rvrRq3$=2gWDUPDq+gZz|B?S!N~KUC`PMV z39H!?4zQpYN?T0p!9jorYN3ifxsPzU7`@Y>2};GKHpQ6|`GA5Bl%iY-jLKn8j`x~8 z68D-sB&SI}+KO)v=OsYn`2^GXMs^m&4c>7UAR?I)CZ=HfRzZx%PH4eYL6Sn8{6^(x zfaoYJmMI#>t2~(zRDj*)Ppgp9w6T=QUlrf~9c@_O0N`#?<Z<viI&7xK{KO<fpYrZE z{)!fCj`4}~wGev*!_Y~=$#{|X()W#qNW^=lHB@l}yj@Z4*oM7SVY4;q2^<d2{BoF& zy?!mT!yQgf)4%Vg=Fh5?zK@hwr=DNC?j{U8&g_1G?yKUs4c+g5)sYLwR43Vudm9Kh z5*cZgLb%`Z5hc78tiZ6^mWD(vzmA6uy6P@q9K<d=2n@vzO!&LbvWjMHbPi9NCUmo} zQkr4)e{ufzqwheWmahHd1Xqh{7Jr+4)lwk2X+4I+aX@3!&AGkZ<&Wz9=Gx;~x8RVT zz)Q8G>0UM9gT+D`i<@>?+)S27J9SL~2MIqPY<l;$7{ONvIc-RrZpY?QbvHz*a(8AR z|6_SN%KiR&6!E@UHKF#HoYHcMbK(c|ML>h2{`~%eKz1)hulmN;_fJo3N%Gr^R{T3q z5-V~rC!S(3*h#@gy6>fj)l;{<K$wUH14W=jqD=)W%8-SK`y$22W%R+v>N+H$phzoW zJHvVBs2qov4o?^*Z8z<7D;uI5wOzk}d_6z?><cb8{V*!PVXtoQRVnph@sli}5)1Vt zkGm+@E~P^ESYQ7;UG0C=5hDffC`53Bf*HrUK$lcp6H?n5yVA>p8EY0!JJr?a8Ge^r zy=oKyL{07d`1EwsNA58yttL>W9;(hQ%0Mq84&$5vA{sv&kM}M%elRN>`kRNB9y)T8 zgXNC8VuVOSp<QKz3*1~c9Eg0M7@N`XIMXLJMVL6<j#p1_abpeKQg-_HVD)qwTi`~c zXR~57C$QWUK|T&Uy(-}2X%)`If>_6vWWUBOR}Ek~VbIf|(V+R8#q`J<O=o1Bq5pv! z)Y@=<W##k8ZTidE^1ig`yybXIa(lxdn2x6s7Byh-F%d8Mn{ogiGo4OXMz7vJ2}c|t z-eM-3<VfU$k6h@3N<8pMlEq`f=?}XsXtl$pJIF0zf{X^&TO3EYZV9H-cpy@_(9wdZ zJo$UtX;>TT)Y<eeuo86fL8(fxlm0b&VxhF=jxLoUym0qx-=!`_HqScKV`K^qx;j|+ zSnaPK`sRl35e*lxYtO)7{r#_+5ZBIzs{w))ToI(*4e(5iZ{Rxf?se8<9E@bbxPEwM z$f=^TQ>cGSiM33qW|9L$kW&y7%D&o)GmAN&jOP{Dec6AJh^k^+mjQKy`cOU-Y%bAm zp4jz+N_q^@h3`5Q)lfTxXL1UF>Z@E7lop{{#`n{TmrI6z+?6NI30{-3vC`D93xzV@ zZJZP0R`D+eTeZu)Rws%{Oi2MgUouI_sc`CU(0mB3+FH4=*cla<G@}+)Q>#4^oeC3v z^d~k`^S7g43Ga8c?WZs}#7s23`depR1UJ=bDo*0EAqKw8#z#?38~uU-W)7AF7FY5C z2XW#b1W6&=%p@~KW1*8&gV8TNY2FW%!IMl}b@0YF!Zf>4tUm#WSF2>gmPl484eY9| zG^IB;Fem!X&+|GLbeuB{*Y@8CYJxLKJSRDllAv^6n|I%gAPt}OU7jfuA8EuWGtx-N z_2g6F%<gznf4Aem8kz(L^fWyzp*cJ?8Wf#5QlkP3E#+CZW4F4wx)~I_vCiw#48Qyn z{y7SfT`vO)y|Wq_danX6xnK?Lq1p##YDZ1IxLLs-R8NI!(d_d@y+i~0R)mwesQB5U z3-jIv#RHWI9Xk{-wdCAm^-Z10Xj(_7DC23tc>@Dkxi?Syl;!jUcN2#ur>*G|YA`}~ zmeGw2ZnZ}h9?;E><)Fgip6ac_p!gyjX5@gmc$tT7$dy036ttXC$*=+hcDW!k0pZo} zz_SpehKkl#b=D$<Rqqv1E3x&O(prdKaMEkxh^mCYl95H_F8!-MQdcNn@$##!iQ`$o zaAD?Uep<n#3=l;WR|Jn0p4qf;1IrTSc;)mB=Q?8V9sX((Bp(?D(i0MDvwvOIt_{vT z)(Bp^tk<k#-;MVMr~EeI$j^Y%y)T5aT1!(|XMP*KYqsrHp1VV3D92sXqERK{-yM_K z)Hhg{{BC~Vel)s7c}#Bq1(-e2D^%k$*9LN#2vlvXF>0k-XJ?7hx799#s(#Qz)lyfO z&^y(kQXud8qLW4h&dq)!XD>-$EGlBx?G+nO7J+)IQ}gy=^{=p9b?c2^9Peu}sEKv8 zO4#<Rss31Pr&p!gM$Mz#1P;dvjY#(iYNlOZsNw1P)k52fJ(fP9H(3H@Wu)#GK$fMH zv#hJ0^v&oQcGWY0Ur`+;{~8+fKzXaLuk)iO-Hb0ySsR-~t4%Y?7<Whnp>rvHTuxRh zX5sj4%#?-p9``m{1LV%=Tsh4}_2m3$?40;i*aY6IBiAgzfjvjWMo$4Dc=7tT3_IFO ztk%<e4=r2X7W@j8>ZVsy)7644{I;Zi-YIeo4YrvS<wxI!@{t&4>K@j619lhu4xV)= zR`M`b;@I3o*u~M!M`5FSYP|5PQO#0F(IvWTj`!m8FB>pi9*D|n$#4II3o`oUZU3k6 z9Ky>>VKQRS>=+eCPGw{65h7fyZzrGO$~=&Uug5v3OQNB2>an5hv3RENDZR-iS@%4& z8{*2kF%<eoXc%W*7n&zAkdgD^UDZ85siSex1KSHhcc*3YY)|{an(Fy@yn^P_$BxL8 z<&T2*O}I~@=HZu@>TN(pW@anNqU{eq<v5EAFUG0PZ8O-6UeRK<v?!Bp9yUInpVJ2I z{PL6Xu`lo}^xT*svy<VDSG6&jmyOCm&m1G*Fwy6qod(<4%BVthxzf>PVv{uqH_*e> zXst$YI%d5xz<fAj-P$04n?POax&zT{_IfrN&3cMTNBRXEA2)g~UGO@%2&wGlvYq*B z{$aDUrO}$S`3yz2z2^viD~&cuJ)x~Spu7o8`=Os-!@!BTEVUb1wb1VI+*$2gYA)z8 zGz;7TmbygLi^W#*^grRMJV(I|czF8p%RL3Ors*o!!v}ibF;<+*#D5#cl2O4V*71Cx z{c#VPw6vSh7QGIy`y{LJA+JA>c8ZVP<lUl7WAA!FJ<K?pdL&3|x0vv5#1Rc)f>wzy zcj)HxSV_B6CbH!Fm>aR+cu9uKQ_mw(tmgzEeN5hpf%n^1vWwZ3#Sx9&m|BGXu>5h= zygEHynP>0wqqq-0UeW69mlh{zS;kUsW;;3tByKK4>&@t$ufLX^3>T?}C&vh5nhDAA zkHfwgzO<gyTg<1G8LKRmcJvi=We*4fn)}7Aj5U1F^eC8(jAiBZ2St*TFB%a^uQ)7( zagzc%&ti2=0=*i2k1F4~Cr4JA{q66FK&Z9>(G0nU&y65kbOeF;-wtWB4EhIjj1`a; zXpUbi=Ww<7wz6Rw^!eK@Bcq%f_$<UEqHLfYGjdMI*-phgg1AneTP1Nr(Ar3am+MVM z10VyZd|M{Ar}wL##<|0EVHV1pU7B4qQ?Xg2%ACeRU0$IapTRW;#jPjx3&OhXv54-5 z#W$6N+XzVAKT}?ZD{2N$PcAxBP|Pa=?3eB{=QQXbt|a4#NfKtIbb^o(`374(gA~E* zDnpaNokAqE({+Y67BBx|1yZ}^IxA9Tl_?Y!O<9}%tj`#@OmKfR#CQ21RjG$~hplCY z#?>x<JDh|q#4>}uA%gA62}KaJkX4M_&sw+E^T^1%<*Pv0{^b-o2IuyR%g|bbr#EkV z$36|5XH2eh^LEXCP&Uo3r)7Sb5lvF}n{8_-Qrk|7$QVF+V$tzoVVZVG>}?rd1!UKn z%KK1V8s`?f6~X;J<lTIY{_PX*o2mtankt+8#-`$r0c)f2Tmo!F5Kx^^kq#L|7+cRc z#kywv7P=?ltpgi@v6;A_&bAF=nvdSiqT{`#<N>|QPqlfK^ol7>*fPu*UtpC7xU8F; zVZ|7_Ga3{WHOR|gmH@W=uA>1qAQW?By6#K=g*8~?JXQ}1yxUco(EGcEZO`XxUvTl= zu%}Dc>}Dm}_;bDn7Uedktx26rPiB__j|}(tC=H3xtyL7J1TYIli(UVtvk=d0?>bSO z)+fN*rm5m><AcN_@e)wNN@6j0-r84^>tdcC`hYIs#s(ce-ky4G^JiRnU?5RWWxCn{ zM|L%Jzf@sP8cJewn=J;(&BW(Jvy4G=bTix2vmm8`_CGCS=jZ8ehLH2Je{+t-f@X*F zTih`VJA&v<(Etil&fUli)L(nTRRftHmSni!;tJu6Ep!Xw3ev2OMyM-`Qy4pnErZmk z9WVo^j_xA#f0%gfS}c64>ROHVb{eIjtq34{bw$_DpwV{~9?pmsL-R|dC<2RUuv2Dw z28qQlkeI9*Ehsp|KDGlNP$X%<dA~D9LC{aM5{cs&7fz|pFsA|~57SlA78571`etY1 zlo&<vxGUGIz9+bSY*<vwv#2?!IDMY<rcqQ}H#=mBlefFe)a<4T53P9QOjQ6yw<h}F z@NMcXgb+3Rkke8Q7edNDJ*l59J2C6fg-oV>;}iJSSvS&*j5!F*#^e~v-cQR@G7<DE zM;a^)F&(YUD`E4LolO%NnB+#WHBWfL5)TAFwNd4SpKX11bb(<tPxOOlh>A>2Lbo-X zZky3M&#-Z#btsHwQ(kVXwtcw=kME;>BF~2DBt7TKfDh^#?vAaTZZEOyJ*;paZgC)S ziy)E>fe$fX>RZ|fuY|4($Amc=8_4Q_G0_;gV`8)<_Mi33@vgnI-&$@n4YpIXcU!ta zpK|q$E0O7Ir=i7dFoI*e;BQ73AcBDiU|;0@^a^QnY{!wPXGM4{qQ+(X#J~NydAkp{ zD7I?!&6oR@)j%qYl<7qY)nh{L18%b2!g(0HIYI8&jvhL&T$dg9H@MfMb)fBP0b<*F z3<^`*hI(5SzhX>!95l9d9^hh9p7_p=$hFn9!H&*vbO3ksfzu2AR!j8kv6JD!9V8&; zZ=h!kD%=z`IUit~n&odj;N{r|loe4=s0wgI`XtmTVAlMrH!+JZuac@RDlDt2LR9KP zjeEIlqFu2G8eR=2?zKY%gA1`;>X+$ykC}$ir2R~h7<iX5t{8{CfSNec&>eh5U>pM3 zqHR9y%Qy;xV;C3~h2mvUSgawBSpkKn845KRBd>;c?%KTvg@@a572H`%u-{Nxp_hG< zRa3!9@B76OMICRIImAU8DW$_Bkn<-3p;&^EB-fYog&Wv~9$A=NwVz7^pp$QFqy+!g z+=5S{fv(=3V(FdN;_<lKKpfk5GFnB(_TLdmLw;u0^vl@%G_PPS!GGhfa@R_-L1Oj+ z*F?3%&;pV8R}f*9F+%GwLKJ*v3`XrwnGhy4Jo=`@U@+g^W}F>3+w+?!GJFgq&y7Ic zPM^bK>kh}rR%P-5N0|m!G~7jH3SJ+|{m?4V{J3Z>Ua-w?sx4aYcKV_UP%~SIKd~gM z;$F5^j{F$=Pk=~?JHD$9Q;vS7Qz}ksqPM5s&+6e1YZo<vUT^u-$1^CTuIsw~z0+)U zcJK}v)Bx@01>V@=tLk9}^zD5kg8?fTW2^#h$JyL=BxkFaWCe6o>pJ@D&CEoD1;)%1 z=x=PFDc-x*Z~JIrHj%ZZD}L(&DoXK4^9_^0>3>2(<lIMQARKHy`l7$l84a>Sx0o=E zDLuI?WKm%DIY&0eki)zIpX|l~vExO~{K!)|1%V;HuGyvokruce@lL=tTSTIHB+Uh2 z@K|=uF3p{MI^;ip45_H%E%W-7Vz4;HSVA=O#u*>SW>`MUI%TA!>Gzn_597~4MIks$ zm@YZE^IrOxJTP0Bb(n#u@9PsR?);u%#?f;DuG+3<=U+88{>GI;%5qnLqG7*D6Z42p zP<1l*O#;V{umsB;zY0^;zY^Xij5c3=t?v4=|8S>{M+UJ~XY~GaXhD_@gqY%dAA-$s z!3<QoMYe{7#@jIY*zg0baTH%|^h`q^0%ZEV{5s*{ZC%$wlc@F_l*3c?->Tr#ql9*p zoUs{FKij4F*TtuSuZM+>1L9WV|Fv6H^S^ECUf@tnd+9$=Wwuyb6Kb{IVdj+-j)7T4 z%uSLLo*_o4-a^12CfP8fC#FxnFQVmimSNtPlRt+a6cv*N=M8AZDsVLXkcFBxZOhF! zOoYaVE5PFac6yKWe$Xh(+FC}aer?#lp0%-|0U4x^039g6IGD2PLAk0PqcK{}04hVE zQ=bhmt!iKY_n2hQ`%DTNYgrw73l|I7IlJM$gw_sg_ae}Dt#CD`b0I2Qvsr8J6U0iq z^*WV#1Lt@DWaX6<A;aqZB{z@b_7b&ec1(Z`FNn(tgBf)!i;iqmb~H-#O5jk8YrMzb zQNQM}9_`a7UP&4{a!taVT>*6y^9FbS^sXJGEdjfxWip4Vv#pan;9BZ?lEx-yPi^l5 zf7Mv7^G<}d5X3>*A95mY*^Kdc8c9a>(Mav07;1!SdPPE<{x-Mh8?VOXS*TahN)rWQ z^GOC0D9jnP{9^T?<Z|bH+cg~_IlU4eTR&)28wC`iup_de{oM3^$)e?ZWy+hOU)6D6 z@IUx45mw1J+`;-M1Y>{lF_+boC2sKXb+5F3cfMc1H~omlzZJtw@6QRtn@e@{L1O0I zep(0Kt>Bx+ld4khmNcO;oz?h6;mj?a#6JUw2&2!=daceE6aPLf`yS;=!)NLNHVgHG za;6XgI96<V{&cqTj4SriY5wBZR_{(RhEA+SZ1()W7bF|3Mi$dk{yRWD9o&qe>e#kH zh7+CHEya=vwl;fR9UNmx+4s273ol!Gud0^r7DIO$NlX9m#T*!8l55kzZ1Wu;tr|R6 zd<|pg%v@s<0fjkbQNz68zD?sWu}{$SNXl(`bw6t6|Mdh7-c<2_Q{%_spUWS<O7$Rx zMMDJD5(pf2N6Ova+M1XEjE*62aE|DLr80^OPZ?8Z$6D(s0?R|l3ZI)sKbGnmqq(kO zlhuQot5QuxC8eKc`|4{a*_&7uREZlPU~Zs-rLrk_Z%pdw%U?rDS`^rVMmH&z$CT^o zTP|o)4sMd^@ON`bSal;gFL5j!KQ}S>B=BMCcQj?%iY%^~s%pJ0aKN8sQ-_Rc5RPZ| zpdLnzCty;w6@e_l@B5O!U1-E6CoTP*70!L8;IoP0F@pP$_U>+qpX9>|MLE5s0G^<3 z{XxpffptlKTG9Cu|3=w=eb9Eyb~5eISf&;f)gk)^H$~E%f%cNE6+FpdS21t|Uje!O z$YoU7j;#92*mOLPD)iHa`b)da%-rF^#p<NJ-DE8FQCUCEu71#MIwa|)_RCqTo=%73 zs4r)^oO-a7lc$S^@1DQx+i`b_{J-oiV_U(ecedEw*h#+gAJV2k#>_`d$%bMIU@WA+ z+?ucQOtK1CAE>x9_0#u7k0zYTD#O)O{+7~UT8}WR@enDo3Vi#9zsxH{|CjozAO9x? zbmz-2FDgd3C`dmcDf0B~3*+xey?S`m_TD9pb=FwSLys1{k-aiJPgdkz>FJ6k5=3$w z`!R$GFD=yN$S^_{HoM6?&xQ#{Or)tJ5_E=xP8p%~!dW!^5^NFP+P+8c3}?p6{PE*> z$kU@8BYryasLq}!Fno~+`LESDG{YGClgsG*konE9d~$*t#h>x5&*ES=`Ap#)axD|G zVm6mPkM?gB-W|P}lns^hgj*ga9*D#a2I~VgwfgStnE&xcmO)tWXztGXE*8^w7);T} zBT6V(WkXRu^#R+6Wza$S9*&uWW|Q1FL#QXF=0pYYv23jr9LdXR?((O!n!Y1pp!q<C zWKBcOGuG*+7nPgWB#<N6O*@)AS~*L0nTOAw)uu-|qHa_|<+W!g5&X42#R8SGAg*#N z?IR=sgM#)mn75wifIGqPzxHWK7QVH2hO1We{TsvLDZjU1$~ly1^Mcp7Nh;I}>S&mD z<iuEi!GIF1KCHKik(bPfI%loG;9T}Kx}vyDa)Mc+yY4Nlh)2Bt_WH_xcDqHJq4GY5 zqWhIpAL;^QT_xz(udx-B;B6iN)z0`-2&8H+F9Xj*z0?N!*Px8jWk6evW}7Y1#3}VY z#r3#8#IpE*tXT?Vg><p3CUN-d3_q(&w;J!uLFdtx57E^0*Fb@VoALzL-4)Z-4Yj6a zVp!bFKiP};MI=?}<yy%b)6*w3HydgE5^!FWI_4-8T?ws7Nh!{}o#YUj*s9Vrwc7J8 zomJ(jEKv{s)Bqcl>Dcto)#zlo%@gzFE^X&5etY&uA<Y%p=AQXf#n0J6OicPab$35m z5}0fM+Pm?0{+3EX2BTfkFdJuoI)?90`9>El53{tJ@i&~x$oB8cGGTdzg`fKQrP+Ca zdSL%b2&BRO%{|KZD_S(Cj=!I9PTDG1UyHKrgcLhRg!z>ILC!e{sX8S(XeSidp;*HY zC$3%@L3k1bVlYbyZ0+YmN-+hJQ=3$%4~mcj9XfGt17p$qQK>w!a_=BJ9!%38$%QjU zsfMouJ5DYaCI9#Wkyh%!^hA9fVtJRAEMX?y_DQDBU6r6%NG<U&3p}DU`c<gqe6=Q; zDLRZ1>)5t~QlaPlXf2jVNZ_{m<8Nd${cGFA$DExZ@wPwG3xDo^s05~b(&nun&eHH= z8g=}Nq$V8HCDd;Malsq=%6QQiWcomz(Oh0C)MCxc{kL(SrKW!Lm_^1bb^Syg71pcq z1Rtf1s<?v<$^$@VEs?x?wU;?_*;=*~G>c>msIL~0Emko}etJ^Q^gWsj7q4WF3zwLJ z&7eogT0c=70NNbBK=$ypP}Lo82b}QLwDw`l2h_&0M^qr*SFdi>06QZ)WI@XcwNn-Z zr;{ds6DMEHaxLsZ?XU-tJI>Kg|8r56o({}NOHwF!ntUxE!pFhE6KWP**WwP=K1*2+ z-ShPgbG(0?GY-v}nL;yrqBJAa{<lN?JAlmeZ##+s7#Cv0(kS{TZ{jmG3?ZX!(a)jQ z99@iak|HV`Ns+WRZ#3T!k)1ZZ$OY%$fBMRZC&|Oq6-;Q2(uO@OVRTaR^i#67@d8Z{ zx)zm*4>`mb!2nTMBll#+-T>jv<m#LM8bS9J%eR0lPoPC}EG(c*cvSF;x5!J4S|W!K zn$p^jjs1Fzl!C0*TWC!zn7TGZlT9o`&<zazo>{8>PF2>BE!sIu;4{$~zy?flt9gNn zZx-8=h&ap%!6+lUO?jc+TIu{ksPB9Ksdc%g)$WVpZ;qlO*1foL%3yVszfG*sJuwuN zt4wV#(2TVG`7@371kl}yp;jsFM=_oGtI}NU`A3aUI$w*BgGDGnEDl;oJ1<Dt<AL2H zN2>CDVLDKm(nd^hm}A!ET>uk>MJlbf%}VU=eC4KIRJSs_b1foW9h#)+K{qDg0+%~O z!=<-l08>j(!qM~or}@r%>6wmBX@QVdF9XXxq2>g{y3!(mg_Qt;xpfbF*Y-f71E8h- zYrj}Gs&<`fuV*8|jK*UxcFxFajSrl_m5bm`HCBF&oMnj4#JA`svKk&@ze#N-ALNS# z({k+HGv9YTtxinDTpq)RKAqPc7kWd(k!a|If_JFNh*4Lc@}e5}`)|}>b;&|f1zsj) z6!oZ8RQ;zKj#y64Bn?mhGpp04+601W$o&t0gc?c6!pY%N#cyBV$@7H#a$5jt2~pfR zDn1Vgxm7B1E87MA8ID;IWOaP7=%2a}b9{Am8((5`Z_0n>@r{$tBK!dKnxlAFF72(H zeaKI(K$LeD&l=9#;*G11(z~QD&0J7mPYz~R#0(+X?krqTb1gW*(IugJr)OEM@ecE} zk%+a6-5KM|t@`+ugd7?Z+bR+d`bRVu(_sir?*qm;n(Zn+%{{D(z=gY|eFn)JgOLOJ z&jXa=+}e1F7)JJF%=s9p<_+MNNb6%~KszKdE>jqHx2K0_{VEP0m6fJ+h+^u0u4O7^ zM7lB;!3(}QAF664n{KKsrea{f6wcbISWsT|`1{F!d4A0smvPz#3`V=6AYuq|g4OHR zV*P_tzfY$Dq#D%1m*~gJjH!cbjf^e2xd@QaZd4XScmhM+OC>tY<;RlywV$i&Tu|26 zR--oI;GY0U@x2yi9VW0RA_78K#-Fw>E!*+e$iQyL%_S=8SngF7%bII8A-^TmDEKpK zPFh+8PbJ>j&uCjLyxO|;1loJ@PQnoTF2k3wpGt1dUY*UitBO6%OrH_*63LJN<Tx%} z6vq*onimr)?1>Clz>_2YQ^ZP)kha$Y$p4Wo^cy6vEUNRSP%KF*5fjAHpLo4xmGObf z{4D2M!2!mb(q^ghcbYR90V1%lz~-BCrBb52>qgFYl_#G6csiPxyS86m4y*90k@H{k ziNqmnuZyt#7)FV)4VTA5YM+E6+c88N`nCv$stXWTa*8JCp<?c#^V+_1qYYRMnfYGR z(P7NP#siY)t+fp^BC*C#ECnj;VsN&Sjim!48>(y2yV|~eq-wQt3>W~@xRfXPGZ?>M zCXqfqdOq_$*p&8@U~>5)<nBSYY5CyT&j##?k4Su(69V~f(wVw#tj=ijoD0`em-ov~ ztCsZ#rs6z1>+k?7qtKr5o+{Jf8gnINp`4y7YA~YQ7_^krG%PfZeuq&lIR1+IP&*yY zqK=v92*74&9?rIl>lP$5__m@Wqnj&4;OtZXh9Kk>-onNvIr|<4Ra9g~WJTY^#2|d~ zsrA<4!fmTcXmv0-n7DoQ$IR`L!`FL`;UD+>K$&feHt!r+$RH|yJe&-p&VvLq!D=4# z{$(&9L-;_31bUBwiGTgcSa)5~a)#ZO8~*CYSI&~=(KB`OL?r>F)X;%rCEg{%=Xi-> z7RoVChQd;zXqxGL=7U+gY%HXLc=yHd=W+V)h<t)iPCq?6f9t&e1b7$WT^WDsm3%iI zRPwy<P$>jW#V6#TpFJGm0pWsh`H*C2VS^e)HGBWq@q$vU=2II_ty@L!$I9(94TQ$= zd28#Q6J-*@sjRE({Q*Mb)x(OQQ5lMwf&0>fk#QyqJsmNJvhW1Ax((cGR<me+SifVR z{FI`j53wO+;d#f$_XO#VKiaQ${qS=Vh`u<Lh$b*Hw>Mst{IrZ{{KbgJ<H?Tncd~H2 zpWh36XScu1ug)~O>Gb7*2cjmM^K;l=`;@fBleqOSged<ZCFKSEq*247mh~uD;DDAx z%7uV5=rxd><^42ch<aOaQ|ZPzD)-m{{hF)v!()3%&~v{kUJllLk-qT&AkQ<IKG=E6 zWpEov3~^Bb;IA=ax@F611H}En4B3&Y34GTp7Bh~LH<FTPP=0~WPI?xhl};jVE(eO7 zAF`RkjU@8CT*7}op4n!rq+t9>|3IyJNLruy!XDRF^$+WaR|djdbYY*wi+&oKwBV}D z+aMJu_HX5i{b+pM($V$74SrO5?Fuewp7QViRoxKefB)R9<ViiDDF8)jFQcI90;3!< zV@H+-UPe+pxQ%Ou^_>mRZgLW#W!V&ka~ZC^X`)S*=%-i+#OIfcpf;Y5gy_;cl9@0M zYjvf=6RQYq@(K3Ebs2TVxRX%Ls(vZR{WgxpxU4-nd$d&XPu)>cx(;)|Ftwymnav}n ze?MQt<gojV#c2I`@!3S|wprA0rx~Vm4_n}nUoZgBlKyfFE^!hQRBFo*o|n9dJ?li+ zWm0H)JPjCF?S3OLZ>>EivMDAaJuL<*zO&<Jw5j5+(Zqk6Y?`nIefj~sXG6;5Ync^% z;83#-52I483C3(#L<F3a9gD2bS86qV(cL_|b|*J;bcp|eiPUrevI70@>F49_6T@@K zc|G5Ne=G(SAc0^@3($!?BR&U`wziBO#XNWU-TW1)mN?;0@EXnus?p633`W!)Am9&H zR_m1UydVnOU<M<JRw_IoFA8U<B;Od>aQcLW@szd5G)!S=zPPkAB|^QagT$$1A+)y6 zV(@gowe`fN(z&JiEU2BmqN?i0x<`2dhj&ZZl+?}dXYc4v3Y)K80JZ-Jq1Dx|4V?n2 zOYZ(N&Fc)3z70z15@~bGsAs)e{DLgdm0Rg&DInC|B`LsPl3B>=S)<y=dhrorG!_kJ zbH;Mg8M0tf8pojwUi-v>X<b6)%AXUuhYna;{rp>fo?9uSar&WqzmbjG7ggt;%~7;1 z<KjK3l~&?|I)FSq{hF>BFZ+V}uC-0JHsYX|R<Dm;F&u*=y{8wdh}!je{(fsMJ7}U8 zLjy(j$29G@KA4Mg%6}g<<#h9n%LCmN9Bs$9U_rS(DW}!nEE;5`+@CtQ1y#;=UF2P? zt}-}x&yU}(3h9~Ld^Y_Yw#oEV6*X~neLvpWW49Z8(e}<3pbLI!Q+__W{9|jyi(=*9 zYGiV5OhIU)#F&{(uCctY9RWF|#X{rv`4;}t8m7TfRcK<AKDs!jk$SpyuR!E8YdFKA z)$LgaYR>eCP-(M*!48}nZcov+*TK`-g*YUwsv)vCs+Kl#d>`66``*>MWhcsT!}wcu znCm(Z-!Dk8qr;z=EDVV4D-jH;*j-6I0IiJzK<;#`?V3>fskv-oO;eC1-L~Po^KedR zv8#yN@t%PzC*jrxn9am`MaH3&y@>osiKBCphhdnkKRsGE)KTGCZS{k!9@8Qp7Ug@V zGscn-Q*DMU0DtSHdr9L(<hO~d7z&s(m6IZh6#~IKA^D=Qflt==%fvB*IR$iwX1K~M z)`pD&QaEv(Y+8-A&P5n>5y^N-_QW&qCeh(Q@rO{j&xCX+!s+F#wKF%4Anr?ZR6$~+ zD*bYqa&Hm>&lmBuoO2Ydya`-msFbum*{`*7*8g0Zwyqw#eY@OPo;j*MA&_Qb4fTa= zc)C6!UYvS$0Xku%q<}*aK1rA!!$P(yI@Mf$$^EnU+1`_f(z^2vxGh3et@fqSuS)S; z`zj2lQv7uFQ*Lz6x)9sA5T_7(l1d%|+cc@Bo3Nxu+_;lcp%F1UM~poBo^^-&A388r z4_daBD60=lFAuJhkG=iE1bn=oTh)e~{3E<OF_SdSC?#HyMpVPPanKe-+;6dW07%Rt zu<d3{1K?kp(sov3^(@2Y7V4!)JzvQ(Yjt`cyzIZM2!GCTn%H7q+?P9jRZKe2q-zZN zWa=f5z_aAHm3V_|PiaYQL#Kqw3twUu&vEyEEsBP`E+MFFTsM5Dblqx7yPm&){H%8o zI9_D_tQYldNZW4b2rHM`$g)rk6IAm;9mB(nXaqok;l1*7Dd7E^-Y`XToTrwt)3@1q zzUE}pVC^MC8`!G6$Tp1H#z}=&WYg%(izUTr?0{n}Yu`Zr?BK9m4@rewK={D%>1~#7 z5&6p<M1*ifUJCUPIRoOK#|R8#NCm1ee?K0yy!u$6AB4qV{G(4(+eD*Jf|>r)fFa>& z!{~7V2+B#h^-2{Ngzk1Z5qUMoFwyk7$N3AoHDj6(m8p&IkqLrhLfEHKg0HpvMoIwK zB*hDmN0sLsMz1_HB2G~5e$$_z>QKB5;5Pab&f4|G-l@JeZfa}g1AjjWoEpbnn|pi* zw&UY>a!q)mCoV3{<98hz$m|}@C}|dOo5a!?N;>UTRaa|n4-H=c5MtEv>(jtwSKmDR z%R}TJy=eX<#lg7cP!Q7bUW9&eo`C%URycin?~2zqF#q=J*S{?ZLN=_>bZYfw*<E_l zqF03Jnc+MwQv&xxqA5)2gn}ND?K$ZYOMjiHjN-(x0NhK`025ntf%1gLmMvE2@DZ~p zIKP`?GX|Kpd%pdsr4`Ypy}0KRqWR)H^NV_ixkB0P<Q1-Nva7SA6*%}&08QN(LMe|1 zz=Q<2yWc-sK<C!tFm&6f2-SZ=y;sgzUWkSgg|sT!M`8X|L~*jmkqr!SX%DdXsBzXe zAcT1)3}&+qW<}k5%Mk+EaCPYSz~%|qWxYHSVfJ{O75QZXG8uOb1xAxEcy=Y_z<96f zTabyowV;b~34xq@>U9T0`%$&0!#$Sn(Gf#7(GtB78<trzN<>P4!=0do)7j2j8Xpc- z6!pvSq#u|l__NZ1b$&=sI6!JY>F}i2dj}r5;=o0EXAKfgD7PsY^H!FL-Bb@M7o2b8 zGR&oNoY`vP<*7(xv{_9RNGbBCHnxKYlGuAo(*T4PTY~5LU*yW{RHes6kuMITQmpc! z7<l6|q!WV^yoj08U0v(7ymFM~;UsvLL%$b}51-oB#M@kDuKa7Wq~S0|@^a}&RXjdd z+NeSACH^cT2v^RDf-XHYJaAkb3mM|SE{Wg#=Fw2&+EYVTa;N^TG&;`{j-77_p;$57 zWxQgMQbnP*MiqZWeHM_-l#bK1(f2CI3(9Nb*8#-c`)I{m+N}_5g>9rzJHo@W>mN5j ze(l5rpT^Dfbi|o6_`X9fH#XD;BN+6o4QQPe@3Ofhl#=q0reuF`A=?+Rf}MXcHmPu+ z6AK4Kjwa-%8n+Yibs*1)2O2$>b+Zru=D*%mCw;yp(bR+oD`?W^Wc)%a(XxyMXZpR% zlAu2OR3pzhH7tusdh7fbLfa(0j<Bf?=lMjpfw0`+xfU||IiEc_9M54}T_w+mi9y2f z8Eile@u-0fFIl4~g;UV~Nev(NTRr|VBYuO8DpVDEu?9U5>SFu}H8+<-4y)Af2$P#y zY+YgthXZK$WV`k(^*GJB%LPMP8}8`EkdC5D)DiS$e=C!-1yjv;+x{DJ?6k$$Vr@us zPi(r2$N<2X3_v)@?CPf6ded=zBC_wMIAp=K+P$F2;rn}yyGHwdNKM+mc7yqHV4k#K zkNpapA?3-0o{kdAzVAB{=!IK2OxQlaD$V8;z*<$gcf$jT8d9Rr!{?ir-U6_9V1MII zX>QqjO)2*5apG9Aw0d81J24l-7EM*G5~{@MK14|)@;H5-N86!qvhhBmG~r)^>See& zh`=gb+e*!)WM%0_1Fe-z^Qh|_mtH-)VC+k1muYU$G0OCjzMJn}Q@4PJj1O7vq$2C| z6F(bEsA7|KQxcKQw}Ja<v!q^;w8Jz8@v*f={RR^CYwTTs-j-4OjxdoyuP;p<sv}$~ zd3mTEYs6Hde>|I~4KJXg-hlc;AH&MYahTo-UGU+*WO_%7p15I|-?}OG3&S}fgUECx z%uy^UfY@Tiuk6o9wh2^3M%;FBn06W5gg@Z|M7i+sV*%y^(K|Ok209Tmm<q-t+K*jx z{~OoZ#!j8=Q0pNsKFzo}Lr4yF7rEm6Hw;BCYBm<^_OIXU6yeducId*=IQ-HmBq%OA z+^Pwd5NST`k(uNaxa#>+qt0kRG#}yZllL`5titx*whlL04S-N`c{VttDP2xfpwR!9 zhyX==o@ynh-@YU~0Y4k`ToTD0JVh)oO`QgIJA0{vd+G)6(+|cZ?x);o@U*uR+X-`b zv9A5mcbP6YG(KXYy1dMdQDs5iW5^fCwCLld4e9=IQFMI0{3lRqBM@#-^wBg)_vvsZ z#y-H#QX~O^GK@dQAz9%~m5_*Lj9%m)NZF8VVk9*l%g{kiOhfVxURg>whK;I%`Y|kj zeFCb_raqxO?M3C{ar=$YMp|%nR5gY?q+lT#qqtDj#28*cbk{~z$&FLJps;uSG35`v z>(#Qj#`EcfL`xgKqkDcwCE+?L$ZQ}dpy6cf$Bj&@s%=UG8;TK#X2K>{lV_M+F#y<B z&Qpi?l~h9c%h$iYzkUhnGDPmDRW#l!RjjXlPv5l^!kZ~*kIXCN?!S}8^q|CKETN#b ziHfR`C$|4!`P)AW%LKbYy&|e^S{_ga<MmgQnrHq~oNn>Z<vIW!BDg@<Gvwiv-xU6{ zu`S$MV_CMZ;)d^Ug%9)qJ22n)Z2}xq3khn&mR(QMgJKHwI<OcfFRz$#LC;-g>?GYW za5Wu^&~4cF^@TZ~`*(+IlpimN&xEG9DP&xjQ8Ew00npok7X}@$^T*B~yI%=hE{!an z_qrydKP@kUm0oo3{4=FhLryWbcz5#4tuSwB{0lfx8-wo&GF_I|Ci)xxt7|BcNnqN& zcPT9b2orgXgKBlldI=Q5ENh6ob<}`wzg~(GqO=w<V=l5NaPSW)gUR2kS&>%<jXA`^ zWFs=7v{r`5O&ne}MTB%O|5%j{)H)JA$+(MYHM~EHNx_D;wJXu!7BCH)Y>!DDyt&|$ z=;v)?8Lyi!8Kf#EU<iB>@UyyziMRXwG^7|(9;aQCM-C|8@WDZ1s=0XJX=mA|T@?xP zO)OH((3V!|+0|osqTq!*a-M(vQusY9Ad~xFjJ9*aTdJx75NY_JiIr8Q!NvCFunrn4 zEPCtuXNM4RZZb0rBS@E?vtUtLuVCJT;V0A4Vbz&YY&fZ3)oJT1;%HB;`sIH!+dlM; zRy%JEY@zXRR*fJMB?im`i}y&6*t5bB$$Z)Rc#7M7BIM7eEVN18>4Chb<7xlpctJP$ zFni*#*rgoHz=Vp4Rwz0+qHd!F&0q5+?0n@PSM0XH@BzI`b8C-uq-oxt5U!l_Rl@z_ zM|81Cghj2BaR&Ethw<k@%|L8^-0G<x!*3Uv<=H*MU-H8ou|v>>F>O7lLj9$1>X`aF z7y@8Lj?ypQ?D`xd;)kYKG<zifCO}6?$5LrR_5}GHIx&g8)N}2~t44VQFy((UQrDq# zBTkrV*TbPjytRH1E47}D7WxS*|G$qVNsV5_xYK8kE}2>NJD1)wIicq$-Ilc%PIE#N z=eY7=!hDWZ0dD>U_wbO=mP0zqZ&G!L!ik9&`+ss$ZHz9Hk4x+wmVvYH-)<Vbo8*{i zA?Td@#aLA5223LgT9x&1{4r>3{~Lv}3TVPqw>>yr6kU9+@Nj<XDvu^%3q|9vJ^O53 z-SVocIB-v>n8ZtoZt$S%vY)36s8|qC*<T#L#tw~NX6WPStW^V0bYVtb7Tr8{y4AXN z93?d@FCzW&+8$EKZ*=>I)16XwZ3<Z}ar4-0%qtg6ss?Tc3faDD=w6U0&}(YdXPJst z?uU`!4Ju#)hvGcTQ{Rj16@mespUnywHw|_lEiR6?I2x=9%7R-H#5$#?#e$^kg;pCt zuoUgj7yMIVidNqvv)+z#@SM$M)un$VCiUV_l|9aJr3r@j5%3q#y4@l#=*mzYQ53wb zfA2b*k$R{-;fd2wd;ehH8()0iKZ!uqzxje*l>#vjUzqa$5d3-z<i9@Tc8xTWIOn<r zWJdM7<#psHv$jQ_7oqS_N&{_jA#$?4(;y&#L1b_a7MhY2lz1|3ba;tl786*9kM{W$ z{ZK-U+4}zhDM8l0MupK&uyBj?UT6Em*<jl8i`Nzj(qHw7YZ3Y>`t5BETapFMunQPy zHN7z98CJzLoav5k*D=`YGf1--c*RM8eI`mV2{j&I@tkH%(1*m*(ioum;_Do=G+`lv zkdgF>8*v|)FW=z%xY(66mm_Pbr9mn@P6jHbKP;JSmwAt9ad{nVP3;G6!S<HZZ#`V| zy3m((gzuF$Hp1HH<{p&EuHQn1>Bo+&^2(uEoSR3w(TIU%Al?FF?n_1*qSt~E3F^=S zclqR1)wwDx<wBfb;fL95Z+~b()__S6R8DR<jSKYWX}YQFW=Y5JvUICl!BF$Cb=K9) zqN`h#o@SLAx=}*9SwONJiaxU)K@F4wwxF^=(LO)X?9~u&S@BjJZ^(#1MBFjrXB=Mx z>Vb@C!`uUqx&A8I`%*x~x)`Ofa!~-K)|T6Ants0pleJoIXAh<+nep5faSM9H(i$^` zq49QY-L<D4+p{aHFe5{W1|#uS7u^>(ve>>*Ke|@oV1M6_-R6Eccpr)P;>3&GISOEt zDiYbZQ^YOQ6!*}@7g%WECUWBn4CZEWbh8Wtw_#wY2jkuhyV2DP^{!?V`WSkirdbOb zj4#veU@DggZYT^63D#m4p#*MZT@Emf-9{922qh~RW_y@aRYNJ`x6cff7yG)z$s}a} zqc3~>YiXb&yS%9Hin#f_-UH9;>CpJX_pjr&cPy{7JB`;hh!i}n^i87@B&o)YDP)$H zVE(Q{d+O0m4H?&vE&?lU1_tseDE{4j5~u+!r{EHL@qI@xdBm?v1u!B7un86PdG(w3 zoJ-a!LqQ6l1Js4#0@_EUcN<q07KT?DW|%e$)4t(M^)<8H$IGr!m^sp|QWqb*##v+B zlD3Go6u8C9D1d}jY6k^lh5520a{P8-Xmz%H`PNVX3--weH#2}@){|lPtfl6E?74m2 z>+L(x(889L>EqIGZ!fs5hX}j|WK34n|0O%e@e<61XV-4{&dk&M*z(%JjiYtEvm(#& zNv$r{`qCu#3cX&8r4>cThQmkvwEOEqYtR>h*L^jRc|zs&k9K>_dyNSkp`TMeQwR4+ zD<7clwzoCyz%n%m3DYe{Hwrb)EJA2AnNb>BY<;}m2Nxu6Np~^y=DEJJgu77y976H2 zl7J3jN2CCJ^4CT$(bboHP4IUj9!|lw6i@=pJeYc<d$55)RlrcP{9Y4VR{P)zyc$6Y z1`UT3Mh1h3yBEr9VAyqJ)o*x(#?7YT%ry<Wwy43*V@(%C3-cRke$>E$YaT%>8U`|v zV$J;2FF9x+^ToM~lsNhQBddROPjKg4azO>@c1WR$V$(DoH<%p*x1tSAw+k?ZJBAx> zKo@=8EP5Ig-FESQ5AVlI+r<EEz?@A6J`O>N#-t%6gIx5dBp~w>kW1Sh`b(#83zkj# z_^_m<1&|@ZHT^EEZ{uS4E|iXeA*7N1Dz^<eH#OXFn1}vLdhMa_3ZhIex?mu(Ray0L zX_@84L&KR_z`&~VW4iRnO+9$#ao%+Z*FS)j7vDpSwH0G-Vj)JA%Ar9P*u-C%NtM@c z8y78f{#*sFM@X@iOtS^g0O_>AfU^Kt;40E#Y1p`6z*!{>wIYEVf<89G4q&Gn9zMXw zZ^bo{?%_&nE^3K~QIL`QD2hTe<b7+$|6+*mqhbaGdtrz<n-h@c4D`J%T(s~tT#LFI z29p8z6^4*b0M&0nUlj&adm17G!J49QVjBsQB<SBFeXOMg->%;{vKx<V;oo}=|BmGZ zu-Jk3vZ;X>>x+m67e!Ta@Y|7n>^MXpMQ-N_R<4_#_JK|IrMQHGO6I)JVWI?Sh@e3L zg|C|fzy&4P0l^_=Ay{I_5lc?T#VGiiVfmUK6kHR+5`)e3kwJn>JhF`;h~MNfbD#NP zgc=#a8oDd}*s>#l(n2snng$3@)1r**zN`DNKYks**2nPa2E~IVh7wp#P~0voX9$SE zy$1mn*y8d8>-BOd49vlhfo$=1{g!LjuOC>|)m@m7l>^tHx$Gif8BDl*ICz=<EkrGJ zKQOi;pLE#gMWbiA8hawE?>t}1r!K@a+D+t@x!B6nFd;4;1+a4xaj$iIktM~9D+@a= z*nz5I$9-5~MxT}pEYaBXS;1dKvRQ;<*C0Ix#m7hc!l*z=)(dh(BKYER2`nzFdeygU z>ungvrCF7(ZaOQtq0xvlzvP*ORdg5XF}M<$Hv$VxqX~+``ysnq&>wWbMN^)NPE$b~ z6DZ&9L2Fq7>|7OBD6)EGqN<t+Q;@505to&|Ra$6c%d4e3GjqCb6%&Ap+`i&|-aX8@ z3+b!|QA0%1?@7mZ<%%-p-#Q`6^`!?@a+7m8CPv;s=Xc=tImPK9$RN#Ggj=Mfg{q`< zp@ku#TU9c4t1$H_DlWX#D=ogZQ(k_<w5J}cn5CsT=<|g9n;I!53erHCWE(+_!?48r zAz!BvZL<-GT-qqcs*Wt{;G|;EiPdm4H4NDZx0&_JA@W5hE(7GG%Xj-4$wZxf)Ty2W zJZM(+s#jWi^}v~V%7wrNC8diaEljNIAl<GayLJr~=br0VuDrWjz4o=+_`bI6+WJ%k zD;7{X1(cZsTt*amsIe)d7;8-gm&Un^2gv7Lzriqs{$;7p$@d&&fn6G$UljR%PeXox zz)`1v4?}0aVb`j~`4=`z3!m%jX7SYjrjpVq1YQHkPgq`lvs1hN^_}Ya&)qF9zHD06 zHAp?S%2`!f3@N&eLa+@6mmvig*N++55OiH8pIKx2Dw*LA1Z1Qi^wI!~NcEFkG?9p~ zYzqv=FhRuIshtC~{M;Uia?^Bsu`vDkc4h60P26fdwLht(bX);I%+eyvzvY*&e7;v* z|H^)O`Hf9y`iX*7y*h;(V$09~gNV`Cs<HwYg>FL>ZBtm~0hi8$i?g^G7ox^mT**xI zplH6e#8b@%<hU?|;j#cMav>@!>jEhpd$W^$lz$%pbE+p2jvE5$W@+B8UE3`#eC_}$ z3Eirk>Yr9p$`ZhcB+{+lK+eolerfrQUU~WT!_vYlo27-9EW2^5W|kJ_AhlTR3XJ== zG!|mIV1aR#n88?SGH@B$;1VSR7oUC$A;#K@qD|@S)Lwtj>m5!Cz_JEHMuCTe`oJdT zVoZ!6p{d(Fu(_S!I)Fi>-GeQ+y}jMp{iCV=(Y@2Pu9FYjid9~zmKI;#bY`A(A(Kup zU{^`$^h7d(VPKK@;+$DjXgq=~t!Lfh;w#O{>U$g2^$)bt(rZ=_jIl6Jyc4>~1Y}qV zi1F5z$yir37$X1{ADqFu3iE(V6H2To#h1?yKxb2Q#db$LBQ<10Iqyk3N2n~rD!O3I zKoX^kAdgHRu@?i(cklJv4|m%;ANHE}|LM%Z$N&3u^V4q@dd<7%a(=innJ>=2xLLmP zcGE1*oy$2WT?T1kxcdq!%s%awRzBaX-}r^i+V!u0Qoize12>||MsaRl(;YqlLxdJ} z6KW<0EwLb-c|C+!T5v`>xM+Ze%ylS8GH+U(F(nxD=L;5?*FQ4Ha%!35iM=qXd=f{{ zDUG>2w~a8c%wF@(L2KjN{r!*rkEMf;f34Bl_>R@x`zd~HE1U@O{DdonMXR#5UtN2D z2V9EE5>q;<NW$F8Dk@Ar;g^@+=-00Qe6x1r8@tuDFM?ZeqcHVI*(fc{VGC<b<E*Ix z(4uZbLwr4HZBaKNaUBAnbaO~>#e>wP6|Uea40kev$)f|q;za=*kRb(IUzUHG!)MM` zxr{0`oytWMzFB+wA8T%Vr|NdW8ap`eGm;8F+)KC_3{$9Y8&<W}+4+%)!F%4Pr8MRu z$&sxJA~OWwrEXWr3<3Ba>cK1l8q5@6S7vmxY{mnEX?6>Qf&s*gr9*=vxa=ju%*P~K z2Kzah@HuP-6vVwRTtS)$s&Id>(Acf`yCSic7Pg{{D)G|i`o3c8%kA#%xUH?Jp)dIO zDp$%O6@_>K1^r;X!`ovEFIGljYPz`a*^T1-i*0B@lSr>|mlli@QaS~J%MC*c;A9jU z$aG*p{4yDf1=dw@{&}~!_;RbX^xA%T<*kj%m3MV)VcGb#vQ@ucx5`U11_{2P784tM zA?}N}MfV`m<9@Bc5(8_>O0l+N{ac1Gj!Uq(XvU4m;!5^;vBB|l$pvT-c^U4*xVWv& zV!!p^tbWCDkv0fms&7nb1k#oZ(~oaeR^QvU>o-F<jIY-kDlpxylk5z%(?9O}NQof3 z^r9}ZuySW2`JlkqE3nAzD!2h3_poK&FD|~)F0a13U%mFVjoOW0yj{EYb6R=jZNr&; zrf4^A)=bie#lpyv154xo76lS}4DTK^7-)$uLX&|OlQlz63(PR&;6fs_Qp5&q1S{eH zbX*s;z`Pi4g8LA8F6y^7?FkCt0Od+?$O23TEA^x*4Jj>p(1o&sTh7zv<u^7;ORx5I zt90HcnkuODuvOq!*WN#@UHjT0wg!66%u}A-xJ9f5GFKs3k_u#jph7nb__{P0Yw9z; zGykFsA?%g)FE=rucIr2OX`{OS)%)e;x9%4gUND`RCmgG~Uc<zi*UjRpCbOJ42(cCx z+qWe|4jKa3m}LMW#?ndzWaz_NThY`wBDhfex)75P1QfD~l0~bE1y>xb!5`z2W8CiE zw%gvBL~q(a14>z7N~2TMQk6t;K_vlGi&)ipXZp#l^2%FnsOZK~kWIeVysPz^cZ#@j zJ;K(4Rh)lOFD-ly^5gAQuDp9#UHj4zE^TZv^h%2_yT$nzJZI)<A6MK6tW2xAMh4G< z)dB7@jZAnM&I2~iBEbn3iSP?T)2GRpst8O|kCCxe_<DGy<=6X_)h~1~l#Z%bzj9bv z`{I6i<@0cjwo8kzXr=iVz?!n0smIIsdQ2I`*?FTdwMYgIW9t^?)gt+E$pB(GtfWg# z?iP%KHrN5jnvrg4g@G7+K!!ZsQ&3fAxd}xVVY=nT!7JS3M}1n+d~AaC<slj}FSrnI zcXobQhskR%qYSHli@n-<N@?KoE4Zi;s!HU6hKkv$tdNS1+&W>@R?x4YN87Y;Em+;I zn8i6$w`+U&odvIdq>*Xl$3<jZT?_SF4|U}CinwAtz~;h37_g0D<QRpzjVpG`EY6x% zdC9<)yN;JySa4;(v}=`?dRA%C#jm+|>EV4oSatZC`DO_pQ(EvLAu|3P&2*-S^#TKH z!MY>hfPyP|!ww3*WI<2^OsEJ;Qu&v{67D0%R7rln;4{Kyp&ov2o%A~eeNR*5HzczP z!?{&geY>*eL5@qiMwWf6w%)U=S9@0NY8S6}?Am$@L(IWNE7Y!QcKtg3vnwY4416u- zY@;|+z@J^j-=k=dbHwLnIw5?hWfP8@qHa3DU6<TXGhNVq40H&{*!P8hL)V$(>3c#r z>GVF-LXfc@1C4=}h7#5m8wirxx0TjF@#V|NCCHW#Rs>XTWNiiOae;_Kx4rGQ9{f%j z`o$+mhq(MIF7Tz83xX0Ci_OF_=1oJT@st+#8R)drJM18*dE&&Da_Hi*Dr@!fm3Qx9 z%f%(xJH{;B-gn!mwecPO@b>>xYj1yl*6ST^N}Uo>U_zBldWbEShE=&zwd*%Zg{em! zXZC3uTWHuId&wv*y=IhG-qg#h@9CARA85Fs%GEF9^1=Pe+LtyeYhT#HuWjS&g3CKQ zr7Q33mR3H$i!GZyY_;x}mft)mExmq-t)FIb>9uBY@wF!Y%qDD?mftum<JZf}_&EF? zcrX5(9r9WDGx29{SMldp*WTZ(Uj5)fb^R;%s_S1yxM-Et_whNs1LubM`Wl?8jV(_H z`lDb>75*Mot9GqnVoMCh_XSxj?68RaQfU@dydJp$HTJd86)4S$5nAzrg~j$|aW2Ei zHOK~H$lQan#JU-MTcNcj3(Cm2lcHr{eT4xUer!>(0UDyK&<|aO6k-%=_<r1IKm3D+ zJ2_*&h7su_n?%2<(pgHU5n3^@;udp81qA&N260s_7Z+c~mG}EiY~Y;tw~@;2?4ahI z-z>B?eh)*exjFnNgcTsQQke)iorH^*!Ae@hR^B`&#|*aArm<x+jjf_7ySmn}Ygchu zuVWzA3XNOULgSH2q48+hnR=}3Oh1m{@>m(R3-wzSyKxhP^LmZE2fx><UYR1Fg+CLM zY1$|*&KSk{Ic$9`VC!rNEIBe;G;z&QaM;3<4TxaiWbl1fgV&P*8At-EIGBR%!(t-_ z4Joj=$ic`;lV`}t1+CP+EdgAaEXxBgUkWb{VxEhT05xoV72Dh2(=XpF*+fw)DJ7-j zi9Y<1^~Jh|co(8Pj5(;>a3#KC*RE|>uDrKXoO{kAmD@Op3>0mn_RbHr!%x3C*FE^S ziTB>g^<K7LFq(#g_SNuK3-kpMD3D^O7qbu)Z1qJY+L!S@rXZb`;9QbG62X<+hf<H> zEiEpKg`^~5C0JK%h`S6};Wd_8Svn7|p`gX}ZLwM7Q97{v<OZ89KG-3F7ZM>Bx(S(J z4c7X?@Y?F^{IKG?W8`{22Mx-FsHBvx$nuIUFxqLZNi48rh%>Wp7FVsxl__Wb#jWDP z=lak;G;V@?laTGCb^lvsa4BM_We>cGBG@GERo#?V?3xS)DaBw)=CQV9pYXd#EusOR zOM@w-Za;cngFwYb2(qw}SyzlpkltfapBUR%l3Q8inA3n(bc2W+zL1O;rNE1h`|_AR zE-!+YFIZq0VxQpqu!akNCYNyhqRNG+bUbMqVmr;XsD*1Vl?gv4D2AJvtn9*d46eD- z(yMoi3oo@G@{L6AjhTAQJE(d0Hw*2p@6EW~{VnN1Oa;o#3#G@frxpbdF>5%8jj}{D z3^SIK&Q2>L{9ZCZiIB<!6%!qp1}lkm#k&LrU`4?kDOMKOr!@j-Q6<tmNkZjieO}Q3 zWmn=lbOmsceING@K52A!e{6aEBmR~{K?Lb#5*x%Jld{f~QltqM7#n3~#FY{|0F>Jo zyA>nrEw&ieoS7##Dp$X<2~%Q>BgDWOYu^3GX8Ykkp9+FI1aa`vxogFKXWlxIK||;_ z(#*^vhf|vSj(I(@<mAU820U6SpoGj8x)E7aQc&fARb*X>F2M|7Wms7ew9p8kMQ#Kn zz$>!5czB^?1`R&KP*{2{=Q<?oc%R#A7LM-!b`2U)$$Jj2*9FLQlTW$WBm8noN_nOE zh|Rpff{W{O5TX&O6(+Sl;z}4-;#a{MD=)vfQCfVpN2X^PLv^;lueTrm!L;x7nSnU8 zBBZVUxc>z)6|<~wP!q0AK0xIFoSrNR*QLy2(navZmUI$O?6Iu7GzC<w8*m`3#6Bz| z9avnrJ8xx412KjPC}btIx_E1gx(4Hfyb>!bf)*_M64znS_vN|mJIzmjeX*Z1(*)_? z9ODE5Ln@b7a8M==<yusdN((FI7+5-EZL#^|#8f;+RJkENw$$X&XeKh8sd;Di>CMve z>#cDd`0VvtsJ;Dtqy6yrVeXiqS**padJWO=>lj|VMJndp6Txwkk^-m%h$xKWLBBx} z!DL82PXrVbyrZ+<D+5#mVU^a0H4L!Wwz>eVVU@7De7V&naT$tHh6GTa3|?+9t3(Jg zY~|l;J^0pQclXEg*(UHcA<?W>?HFI^;%d-1FlCu3rHCEq$@fJBgGS@ZC?0^yRCG~5 zLugq<)<~R9fw~lRtFr9OJXyq+*e+zcfbmx2BwV?m*5*GmyZawCaP2~x$ARaHA4kN_ z)XRa4g*^v?G}m9=G5~5)u*Gi9B$$$LN&<?9R5qy6f-N%KfoU$mVStsW)$$HTM%jH` zk=2#%GGqZ22Wz-Iya>3c<%YL=%{%R_@6WV0zH6jSm84_qD;%iq=DlynTtm4Km8GRL zq%_Xzq7@FCy&&0<PWyp$uEJ*f#>5D<sGkgj%&M+7s#m{sr#SmeAF@dVE${J=*K4Aq zd;hrXc6N&V<3vHvSGX~_M9?!ZVEPJ2R}7)(9E^C^o(QHSoRR?*GDsQGy^{E>gk_a& zU7ZfFL`kZ{$m-(Vg}w-`2t1<IMf<z@Tt*GwkF6{gUSvO5W^VVB_Rf!Hu!V2KP>wWd zkowD+ado>oDF90o_k)S4r0AU>7L`g$DH2pfkb<aCzo9|a_PGBmh85cn7F9$-6j&Vn z6@W+!?@emGXmlPW^4{=uO|Kcn`TIs;O82_^w$~e<DP<rX7Op@nqcFXx8%~8<qgoW4 zz`9w5pcnCa>1;)>ji?A#P68;@O4OLbfJhfCA?}!9J@~%j-=y<pi7*mAgR`u{&l6yE zI>3s4!HiwvfMg~m6f+Se-`7R=aZ!Xw1RUGX6~WFU%a7Wd-(75NeA|J+^jUU>Ra%5x zm2FM8yw2{AE_x^;q>}nONS;cHO(!)FU}4ip0ZfSze#msItU=OR$f<{fmUvVdBc=jF zOeKlvgo42>NB)m0EEFusABvA7+q5FnjVcD`18jxiRm*nUTUtDS(m4wHD!O~RS)9{M zXU2}KEGDppUnXIn1WD4}cB~?G-H9NQfh!G6NkB!GW-6p|fGQWPMg^?shs)R{fQzik z#&xj~hs>a4#P@r#));Sz(bxI}UY^_jr1ju;mfM@Z5BInH92wb-TVA2@i0$={x}BXL zUi47Jytxn+z?1~h;0#Ff1ao20R7Z9VTNjWBwnmb{TIDPKLj9%=iDQF)EU{wNxOKkb z(xTTXm=IH8#Xv)h6~+LMtm<`qE(ODx-NdcVg73C<aa_!K2;4l7tJo?p-_s1c%DKs+ zJ|%i>Sa@mF!W1h1L7<TrO&2YzVXZWN9}kd05R@H%3{V{lR(XI$eyIVw27!y#tB9F7 znH`ucx)a5IFMjM-^g4!@+u6N`Ew6>v#_w5P&Lq9g%#&TKykho`?&G)o<f4yeRsd7V z5Op8vHquG@p#sTZ4oO<A;zBrm#*$}M*Ic`{-nZ-5y0~@M!lhZL-)Pyjn|S@!wppC5 z>vWnB5neQlH&=n_Ooc>&gkp<~tIR0M5dWG0G~KYv_`$t{%^n!oX_0;c|9rav;M*8M zw)X-*-ifuYXs}DTUjPG}5nby38&KpGy9fuFwKm8qlYNE&PZ5a61=Yx4#cD`!2@_4m z;0j%XDel6An@|XuXMx6!2XnjocUzm^nQz_yZ7Xk%LkQk*rk`vXR;AE8{M7Fqd~6JR zRmM!Yv(cO3O4^;Ef;%QmWa<qATU9W{Mq$dsrHA+R^}<xgv}<krH1u($x~4%Vj1JBC z&;^F{VvwdL>c^Rm$1*XY^bxdZOB^8-0SZOiAmVl*#{~DIhM1dC3!Z6>EH!eN@8hK( zzSj$m^MmfORl9!cj$UYVS{vV)>TG{_Cf71ey`%f6cW}F8Hy#mQ(c&co0uEl$wW6>X zm2!So#@|f7l6Gvy-^z6wqx{~Wlxi&DCJffI!4+CuWF=m*Fbe`ZBz;|pfDUf`kKN8r zsd@LemO48>($b<0lhROO>QN6jCXal#TO|+6<&g#!7+GP8vcM)t5ONOlc7cx^+zigl zQ?0`E<41P=#*tOMimjI=(<si`7-U7=bgCL|&e7a@y3Q}@^cc}{AiQD=BH8jrQ7b*J z&?&saiajJ*v<$x!3qxjK4O?MF)0y5j3U$-#>>B5nr5nT8DB?E3V;^JKH`r)Bjj_CF zKbOW>H*wTvwv{uKJ2AKGkaz2e|D={vUP~%rFy%Vv#J@xKt~_qS6pM><7X}ts+~?&A zz+$Z~8g<Bp(f_Qo{f7(9JHIiFttV}m`xY9crG=Ldj6!1uybo^sq1QY3L?8UlvDQ#7 zL|7_-O^D!z1{a`RUGwewO|LNZNVm|q*|cldi&k~bGz!xN!>$!|yHX0Qu`q)<mlcB! zsHKC9*}<x^fDP7d$ij=crP3<l2vCTDm=>8~u^TaZB|L^Xo~iTj_bKAm=%#Mh4CM8# zV6LRIcO*a~z$`C$Fu5Z;a90z7&*hP1T&{?L&k6tK0?$Aw4FHprJ!b(aQF);itfyOB zLR6s?Tx>s=kh72mZ$N}0VqJ&4CFZub9<(?A*+P5syAH{)F@*aYT>r({=UlsSV-Md$ zl>lH{mkwa!oux~P0@%0;S9UNfFHHT8Eh=Q!)?H`nu~uQ`$sL#*$}BBlXf+%%b-TvG z$wYLJDmx1<KRKltRk~5%Of*P2FT7&wDm-DtRbmmuSU00a`NeKA5c9+RK64Cre8qHT zEPPFNeXr+0-omq<@fof$ZYbcNX2}c^<<e`W@c@gKg6oh5xJW@VY(Xb0{5L!98&6Tk zfEHQx`PZc3@I1VDaJjsT5V28($#phh!}B^HyRA*Pz47gt*5<cOZ|F%WHRz{C<>faX z;zrv7IeoVa)2v{CeXIb66u`z<S_BTTwhB{^yT$nz_ltASZeoZv3}@EXO{c<TTVSlJ zC_}uaGuuqKH?tnQyV!_15nl9sg6D=0r)F&?(fXp_1x4HG5M%Zs=Iy@ERh1fZe`5>N zu`8<&@cB4ydq;;U*w0FE4fH|{8(`NoV&SsE87K>28h>4*>vOELIE69}8q}vHZpfgO z<|Y)u#RO+W)1ELeLULFof;3Q=G79w}QuTIk|7X*OpZ<EIyZ2)adBX=C!3^fk^y7WI zdKJh=@<#;#+qx_OBOQ<`3h@F8tSxM1d8PRmTF%@v2N+rhMqz3eL#dvCmc+`6Tvj@} z&1Xwstt>7z6pAZcE_yD6lNJSTut5khw79B}16eFt9~bv}wB|?F7+Fuad*IYeY>iEC z!q_aY*R+Rk=^kIWlF{t?txup|5e0!T%=L}K(~|(B*NOqyg@w(ibW&(BuctvPt$&MJ zU977x0WM~ca_BbX$9DzW{iBVoqr1Pg(AxM;!R_ou_vx^*>(^0f;iXM{e=kK}^Kt=< zXwY~86@@q^f{=HsvP{Nb8O6DdQJiUGXwBftuN1+CX}!m3olpcAK42nVkD;SQ?+xlr zCYT@!#)$Wc@QN!<(ZZtk6GCjoO5x*^P{Z)0pi?6Hn~2%1kxD82JAQE9J_%3s$hifd zL)%`_hv7@(EMQCMj%MIGW9NreT!!i>KaL=Z4AM*a$Th+p2om^{%1p!OOTs5<h3cUg zRLuA1KT)!8Qk7BdBwpg-l<gj*N3hUhj-}QXUFQZV$F9RTaDzr1%EIRPJ~0fIjqcu0 z>)rjI&h!p$qmy_8tjZPS%sh3Vna;woKV(A00!+bb3`GVJJF^&E%dS;jYg(1nJzTM` z;>x>>I2V*)9fcKr6yX;6;#fByLP?-eaFM?r!%Bn~_nEBwPu}_$;T7-o<If;r^%PDh ztT@Eu83_9*IyVThk1Q9BLXms_3QUk`6dD`4S(@n`erohvn<y*4+(|_~+w1Fzh9hB7 zP%&y+T(QR6^jd`9Ibs(*cG5t09AE|Osa96BkH5Z2D-4BD65WO@oPvFPW{os(v3+75 z@;bZsd(FGm?%q%4I=esC{C;bQ>yi{C5h%_*e}o|hu7vT*YCT%zxygMBF!;qTy4g87 zB5pei8CDh9^&4(s`iVWmnQmfJ0>D+{pvAeN7)vYB55)&bh<#JBb%s*jCwZ^9Klw%S zeA&>lqQ`Tl7=JE4v(27MqLNGhyBJ>crNx);^qcpphj;(K8eZl&$KwgCFdqO$>czMc z)HJC0!trmgPow>KOa)5577bXDSz}Ze4Q2z{$$5cAHnX4=T4Wi}3U;w$x`bSYh=#{g z3yc92@-x8Yl723?y;E*)e>m59_y@))vc$lU7!0o-h8H9NT{-awUVLd3U};amMSD6X zLXftpF!dR?F!jiuZq+)PQCQM+bDH*}@Rk>25v4+lx0HBmiv<@-ygy;}46Eb3XHrDJ zdlbPI_S4Y<m0~bLJ6NYq(u5U;h*cVVruaw3*(y63tP+Q$0*mg;39U41i-OA+qYWjn zir~guT$o%RdEEo2z4iUY_SW|-=yM%K@DOw%*!AmvVd~LsEsQ#xaOLp``WHQC)<g+g z2M|%{VXLc&Ev}Ys*XG&rLrIsR04@<)BCrs<5ci3oqn~y9-`h{Di4~bIt*H3@@=p|n zTf!UdhkvjLE=1fyKK4215scHpzmFfe2WdYT<1Q54hzTpq7uo<mv%&o~=LNDH^^Eio zai;(*8LSffSzs~8=7ttRY=B16+G1UZuEY}aS#Tk@wXxON`SF$R!N(?Kj~`{g5Uvxz z`r3j1+>w3g#h0c4HU@%<4#sp?)%9Ls>aiW&t^&BKvP|U>#JB(oJsnzHeJ64EooapO z2lYrC^Dg;=Y^8&uN+#L=MUPDajSGDyl@RlJvF<=$L}%xRb7v960pFSAi6k}`I0N|> zJaHqEKn{;U$&Y>=YK76)!>^)=SB`O=o(jnFG=TFQJ^)l?BWZc@uknD&1{Mo8FFr06 zS_rY(G&pNZYI*tGw(qrX_nY^t{i6q^-r?<;?*7N4?N`OmcA<U~!|S;{qtKWh-3L#G zw9@JauoOe*p;%#o^{cr2LtGZm=QJl%U{yg9UAuP8b7r32HA?e`7*cEL*)EdV8sw3M z;pcOeSW1Pai7RPV82xNvtd}P414%;YC1=35wwV@kO8<hG2$kfE^ZLSiFIta2-fchV zHSbkg8{aA8N+#vk7_DF^gI&9UDr@iG)9uQVM$>U6ChH7`IneQUoQoK)7#j)7UdNQ* z_B0wKnJi0tQ35KmBgL|kzBVMV*oI96FA-W{BZu1-tt~OPAsf8m1#@+|tvg=t!08@* zQtABv?7iueB*&E}<{oDrl}FuGU47ypNPr{=lHf6%A?Ms(&FoyVwf!ITS*AZhUt}hI z*GHLTlFVe1nM@|hb|ssgncdMWNst7GAOVmB&<DD^j;_1%jzj)D!abgQcw}aES5{YL z<^!VR$jGdWNDu${@4er9e(;e$ID1%hgPz+M+k#14AzAsr_7^ko(iN8CO8cjzcl7I^ z_2lobJ2Ba2%U`qQ0pa-w1bnbTx+J*@sWJ)xOf-3s^95@sUFHBbL30@Es;s>WbqNoN z<!te3NepvGDXzktPy<ZJ<qKHWI5rf>tUQQOvy9$cfukUPmg|`Cs296uA|i8YB9tJS zh$OOYK9^DoV8V42_IA4azgX_<z3)VWGh%x1Ct;RThB#(a+5D~rNvF7mq$q-%m6$S> z>B0jPOS^w#g4qk%u=^Ab*(1XlRpILeU?tBV0a&>Kkm@eXn3e`wm{Fna;&vE42)g@~ z{_!Wx-oY<jP_h;Q%8MAZ6R-I?sc!xLj^$QWx1l05$!y91>_278eKH1s!5ef2HV3c? z8g)vfyz;fstG{x_SXaxk{Tp}`E-SthliE-NuCYNYMqBxZEyvarz%qf;bAVXUcgiuZ zT&ICR3-*H~Sv1z!SD>)3ptF0_+52e|@@^9Fx;$eeW>Q{%x92zC@L^6h!N3&olG_-( z5aUkopOW&B8w}uqhJKgF1v7K!Ydm_MnBx$jN?slTR@(k>z!Jw$%0i5Cx=f&zV_RCq z4YbeN&(r?NgT~<WemQ7uQ&1H~L95_?yqG~dDXn}pDy_Zy%<&s*>hCD7RTY`=Bhv+7 z=VD;#FpsYt!!if33o^I5Mf~NrdW>})S#E7zPIPFh?4GhO=}s~N*QibkKm6*ke8+=6 z4}ZSm!ou-3^YkghJ6!$iiP!`l4aY;kOHBNSz$*nV31m@!yMOXoqj&JDa{u%}Zlrn{ z=2SLF?e;%AWI(n^cqyqAuu!EGqA)PQx{+8%sV@mOB&9)`wm<UT7rr?9A`6+8#?Xce zGKPSa@;8P77Fd+NRZBcp*w`r0!tO<OPSAcbXzf(P-m?+|EiY*A&;Vvfp5T0QyhPOU zm&4M^+xvEDX^rHbaRkoN&7ORpIe_IE*c`wv$P%H(78_Z;-D2Q%z;n6;aADRZK#ZVr zL&JbeZ`)`?1ufKx4|RE{Bl^)*IY!L^HVkA99gO12$x8Gn;AL<h3J?ps`v(lXYTdo} z-Ehp=Y$suU^L0|Y^QTYP{kXxeKV@G=1||nFmiGH=_f})HG{<iB-|jhVB*V(T9v1=` z{=8!FDh4a@J`-SNfTos$(wIB~t~_YPlEn!3VjOmWZS95Kqtc-Dq&zr%-~{cbWc>NL zp3~UxZ2L<Lyxt5-tKZzQ+zQY0$`4Xwf?b`s%Kel9*w`+_VE{IFA-=Ft4orUYjkvu2 z?x9^?T}nDE2X$CjWvEZBA%Nm}fJ=R-`4Qu&SA12~H6=Sp?9M_{qu`EqxI5AOg-sQ| zl^Ja#QClu`8%i)k;KkY4fNc&Q2i?8ORhZBciuab+z8O~5zn#sDE7%rhU@4GgX%Z<D zBRB>s?xTov>J?PdZTNQ-8_U^nmX%k6j&c=_U|2-7O%3phz)E9XD#OwN3xgF=2SUR@ z3)&VW9!KG$uzyk-oIR`!P9Bs7r}r6|f3d0=V;lReL4{n|{N9P%c;&#N?%iy_qMe(% z4|8R!pmf=60T`#9a{!yLK`@^ISK!rOJF!bkOGz-_PCGCZ>q^(EyO3tUU~0@@@?poo zl|96KWl;GQb~<>pkM^<pF|Udo24aK^`QpSRUsmT%5{yezA7b!golpPh<K@oY&niJ@ zmlrjCnda5+lFG(+kDba!BRfh{@JfGs*#>Y7tVngLDiNvn+sJLcdI9!@Z5;3_VDfp8 zO5bl#8Ro%C-Osma9k6tkW!l%>gYwve0hho(tpsPE`>gHo*cKZMkxQF?hyA|g)vrb6 zwQoJM{lzvL{M?m;Rw4!^G`)1}E{%z(w<zMlQSW%#01RA+09tbw;>$3Y>V%ENyR1Vy zrV#J4u)9d@v`nmv7LCHd5bMe(X$~8aA`s)pwT?yuFBMF!1X!RGwbDs2b+#aK)QoN~ z@s;u4SJhv6@QM_`lH!eY9rliPyZaw3^^bn-a7EY^u*&A|gyq$5?BYt{-1t4hAZBF) z8AXklJc@YAnoJ35slSl|XoMK=_qaAnJxGYb-KU+O`-~VJE&?j?$zym9Vmvs@voVEb ziH;uaGnTR)bPviwXQv#t_uQ~|NI~Ee4NfQTCe+L+uR=(n?=61e&?&91tHlXSu6O+! zg)%pZ2S?q*X#=qPWP}Uxx?kv(HK(%9!0X+Pzxc*8)@j|6l4(4hiIVF@Wyd9BPGCkC zLb727E=?+Ibfbt5inGsy2Ovx)eq<xi9mZlD1`w2;PuW4rUUTB0YqQsZF2-*4X3J~5 zwrqPf`EA5^js;VQ3u0j6yCQc^<`%(n`MhW&InG9#VgFHZ_Nc*_*J^kFm-I5{`Wj{q z6kcAq+0KC%7{AUSS;5a7*=eq_&3^`-!W_kNFcZT`u>dl-Ka`L(!C9s?#Ay|Ne2@8u zW&2dS|0WfxIrb$!%yf>*rs5bM*TD=Q6W?GQu+lvSMSumSu`K>UpdJK9)!qyHXI|7l z@`LuyTG-jQ7=T@#i%>H@YkFk^D$2J?%U|7LzxONo3OlCQ7g##;0u12bJ#f9odZ4pm za{#+&V_;<a4N~3w-7~j-?~skCZ{-qJ#YkJa4l@D7IPSvXz$KUr8i8smD-KpXXDWEa zMxA!dz$xq=mc!nGANG%7cG&f>cfw`D;Ev<qpee8X<uChAX^GDUEvH2xB;8Jcn14?R zWcV^v*H4^&C&gum;$6w{AOo*K=gHvo!BS`EN7X^=@x}diFJhdP`lP(^t*%?y0L^}i z?79Lny;lHKP-Th(mRJ&?f`LtpT!;yfi9rDzhx>qHVlgiHmA0Q)|BK^T9-vfQQ$eeU zZ7J;(?W^xA0xM!*S47@N$v))Z2Mvb6oeb?K44y<$-)8Kp6ts8CVRvsOXg#Gbo1sJa z01EQ@OJ592tMBYNrR5FQ7rY_|vXXsa6|XQ+@R^dNl1u7-6%|{gh<s{ntY$k0unRWm z)E934(TP*tJY}QsTVzPGYA)YLPGh23Zq%BO8)aaCTyfGhhh^#UeKz{64_e!mp!MWt z(B4k{{yh~`a_*kY_{5lmWFSMbYU;7@-KTJtY-s`x`I+W9l(HNz)qR*s<R5o;-&^k< zeBgwaI637gv+NS<nBI!K+MQ$e8w2}#%<iefM)@uST|VCnyKfP-z0h*X0YjoqQ8J2G zqvvZ|f|vv^EtMip0ZWX;`MrZxj7IE3V~@HOF)Z!192jMsAO}$LvoJ`d=O}SA6SFEf zj@Gdf<4uk+M*f(!>uT5m$@|kywoQ{lx~UN8C8Sd5<~3g<<@IkLxRrI*H~4q50j*l= zG-F_y;rm0==ZvpoG3X&vhF{~^sQJKV@RmHkxeK=#^ZMS2Q@eG<8su#iyfXHsf|n`Q z#o-~@6yPNo72h2(t%El810-P%&K}noSe3%=v%8>HN)j=<&sVoi1esH?;e9HRp=Oc+ zSytaKvwR|^;yP4ZhN=261+>BG=SQ7qKif(YlTXk8y(ekOZyfgyKVAxZ$E-g++9Ii1 zQgpaN-~#bO#$02|YlL=bsc)B-fwe8^>`MW&m}bX`HU}ENpGc`1Cx0#bZrXRvJ?Syp zinT*=?i|%MC=TfiOM9<mN`zz}mJMd|u5Aohd5;4dyzGSCgEG6%9s?~e?Cx)gff%?3 zCtmCF9Ol(uA^zeUU8i>Y#4WF{5zAUqW<ggNm<T1PMhUuq!ua4N@o>b`*;S8}(P99b z$~$mb=GN|z%KE!!DtHN{AqKA^!2==LPmpy4W_Md2q;eDK4Njl12fGq<_hCf30SYVj zz+cRR&C!ac9dndCN0#{376LN-V5Oj3$i9ZT4BJoo$G-)6e6xS@*^7Vw2^ho<*>|%c zPM9#mksc7%JW7u3)*Z{MSDeavz?Pm<UO!`?wk~IzmP|~z5tB9(E3$e@lH$MF7+n!^ zjQT2tid2m~Dd%hK44oI}&e2{sNaGb(AU~82&H|p7wI2>Tb{uwG)~=nXcfy!$YmMD! zF2D$TN91KBs}D8yyFeVAG4jxBzJB7>?k=)pCAC<AUhgy=7>a>NYMD1AO4>pkXKjFA zcR{TA`rD|MJF*)6SboUaHp&|68mVr4_e2M;e0@7Ck=9*?2)uOGrMn9yTTwwEV&D`H z&bZ2I(0a5sIJxf(T2ChMaL4j*0^k5Z3lK}cIY}PD{76y(Wkq!uB9~zXU}5iYm$91F z?%q#dYGU#zgIHnE-Z|fU7y}O2LOQ#I0kmWLO~-4zTJ{>Roj9eHw&m3}NSY^xQ!HYX zsvy|AD&!bbQa@64h#6I!9WgO1?YIhj2&gct67PxAGGHOEWw{eYffWZWmoX|g>>dKU zVyyOv0_)~rb%hOb03puhRySgQ>CH2*`NlK$%-_ocmT86w(I1H4G98Y<J)&Y@5E1Dw z6t`I7*VF;nNE-!Uvv)_%X_mJ@s#`xebrx<O7lW7HSgF=ng~jRrQQ9~S>`Ey1Qr|wW zj18TC*n8AJebDS5e7N2}`IIIKt4WSOa2`sKxW4#cFeai(M{*swV^!Ti8SBEr4e2s` z5OfYI?d^Y9>mPq|rCdUjGU%K;dq1-n)2y>md#$wk^#jJpPFb6|Bjxmlyf9_W4NL6J zmU#$^*;gKzl4JSG8!`Ssw66h`_WMKJDjKxB7=UVU>PB5IB3c8m0&f9t!-{M1Y8zCK zX9JM9yz=&mTe}MauDgU!i)0O9U24L5KA0u$8)8~W-78(mGul<pMI0|+Acw8q_`R-E z**N2BL*zD8inCgokJ}7h2A5$jzJ+5%XdMO*ddI(6?j3%3D`0Hvvi$$N6HCEUj1?6j zM=XF$e;=0lV<wro3>hFkWS#h0ckgFqs696e;3W;1H2V!1>!e}l*^<BbrG?V!JCE4S zyd@QhC}})KZYSN1l(02I@|~L=yE4C)2PS+>FtR-RQUEL(K&*8;3R+&&J7NHJ>N3Ex z8N0Hg{>c^tF#WeD(v^Ho3>X&xm%sSNsa;xa+kSn6T5baaR^hyU8oSniEB^b4)CrQ@ z?*z0+0(S9i0++D43-LM4_Ue#?82HUM_Nnb{@mhDHf##HlCb7nFm*H^W;v$C!{gclY z`^Ue%H8^{0K?RD-jRQdBIL*tlu){;lJZz<Xng-G`mZ!zO($5OI&-NI2-RK{E>;#=> zmv}u_%K)?od_dCn7%SN-Eq^7M6H1Ad;1PQk_Y!ec<@V=(2ckXQ8X(ET26rL<2?t5} zqNvBnau3|sN1(clqn<^;1sDx122OS~IJ4OC4Tur)!Bvpv8j5LeuCdvW^H;kad9~Xe zHi&K6<rSESww@DdDYZ`ZBz42QZ`@)z2$XVpemG;<Na{7SHfLbZVSKKqr8mRU%GdUY z#lVZ|YQq-+uc!dL!i4d#2R~$;eE;<RM(^N5e{lBs%ee{s`zGHj86d_)B;Cs3GIC)_ z|9!faYDwt=?LG?Hy9>R850<<8A6Qo<G5KX1cPs9`$3URJSX%k&kyBo6O45T^edbak zL=nzN05dSBIJy5ag#op{IEwbzzQvBGJ!r(nB@CFtt`iN~HUlMC0GuGn-HL-wGhzST zM0v^c2VZVUuSpnGiwDmOV_LCSztd%`>wr3yE!OU<Ilz)2rOz^vXJ6a|tCs$Zgw66} zG3q}Q^(i5<>OKSm0L#2Ta~o}}v3boe#Qx%&$J8lr^0Giy23Y$1UBXX&Uhq<D0Kz^L ztnEK!fYo3f+G_vkH!tP#GXwWf8nYrWK#bG4a;BD2JWzyWQfZ`?1lGky?~ezk_m|q+ z|5y!LPhZBxTzTv6{}YYFPP4rB?lJ2?`-H?oV^307b0SkzRG#osl=_+YIjNU71tZ3i z61LR05-{pRMH9BLb=Y4`tFlPfUII$JmtJe~a*ax`u}jPF><3=`-if=gb;6F{WyiO4 zR;5gdV+1T*c}Jh`E#p?Pq*!QxBHYFX_B@LQ05%7&aSf_QSJp{s>8%sHw75jIf~=Wf zLmP1wfLCsYSXq5ND_kqpd4txI&hAfc^bS9A!rtM_x&x}E-;F|fBwj)@PAf$u)dF&{ zXmOos{40s~bf0u~e!RgtX!~WA_PH+BKe<nW&R)ZEDg>AAA|h6b;-_i}F<b|e$oC{G zA*hLS$HeCe&X$+YPrm`<a_e_uzkaXdRBxQI{T{XbThwxwge!L>a1jh*!d+-(z@-70 z;1pCA6i5lz&|9znzO!L-7veb#TnCkn@18l;%@b;QjjSMR23)$@a3px;T!w;i1*v;2 zxX(JS#qREVHwNvee9D9?WiT@5*Bxi>L(#G3^Z{yVE5;^|d;7oGWY<S8d&cDJXxx3= zJG$~x^6PG(CWIU=ci~pxRyRAg-|SkY<yDK<P1691GbjUaNnoQyWnY;92MfEjZ$M;z zmoAw%8hUZY=*V0~Kc>N`x3u)-(5-_!7D_}3riCUYq`1)*s11$am7h9<yKREj<KEFH zYu#r*uMA*j;j3yqeiD<+eVCU5$1?sB1Do9GpWbiu4nAyL)x_kv&5*HY)^e)E_Umkr zS_kkNxYZkNp4Vx)ci3}WQvph4Uc-P(*%$6Y1M?D!-HZt*1K*^|hGwzOj0y5^Oce_o zi9hog*cb-j<TYN2%d21CgUa%mXd`XB)~KXJuwme(C-Dv#(>i<7JNVT~d*?s;SB`a= zOt8qD`jEevtU$FEc6WP+A1-%xf8v0IdTw)j8J5h5?edwjVcH{iVQb*k?w&deH%{0= zh;4xkzEKEPC^l9MTzZ^~qSpyg;^W+~>X0BXD(IeL?D`ern_!yy@{wpfDh4)HaQ?hz zmscUrqfOoN21PDIsW>EBLmnd77!F>E3y}fV)ArN<u+cmG*n;|Q*UE~e{Ex%_Nx8lA z!?pg=uhV$n+~#)q1}b7_;U<)M>NDUvv;D;`vHTlsAi6}UBU2SrH(~aC%UITVfeWoO z;4041GaZv<r83%;_&yTB&a?s8NSnvNMj9oh<+u87?am1Ut7<lzE>asRqcALM2Gi7r zBfu+c@9ed=|8b*t{96h+Gt*%)W0M~(cMpD9f_a#(d;ZVdu9v|;6LPkk@@mWmqXAoY zY_HLyZgtgit4ow%04e}3WuGE1%P`;~TAWJ*uG~bg>0799C8C$)09ZR&6!=bsQIOVf zQTOdS*c`xy8Ebr{r7y=`^9zU6E^j7v>F{jI>P*U}O!~xdHTf6`HV}BRb<lcz(Aj-& zrF-xzdgY3fKBtAf!-M|OZ`K%_bb{{eCMM7A@(kkDmRC>mwfsh8`%8iCH~Vbeu}e#< z>;yLhAM7I3<5WdVjH<5@?Y&^<0WM(%sbPK=by3h;^dm&7O<5mu91YGI7koQkmW>6( za{wzc)|hO6k(AfIacH><>w1otnwW@P1ca#UOUv_$jLhq^u)DX=-u{oPy~B@Q+9hA4 zv1@x8^^Z%PXYVh<jLG_hm~)%kMVN?XiFg)cR>Y~UhfaC3>y($<c6ntLDweY6(_^5u zPy|}W*cQR(V%1>2dd66;LIt>rU5Ao=$q75t*Ae@Q6$X}icEX;0l?z@^!saT(0)tVU z-*`2$yjqVctV;*4;c>5+=t;_nt59(pcAr(x9{$@^nCEI5ERF`-gR@8V_Vy2}L3{gU zf1SBaqp|1ChN>8Bkm*#`BB!#^bt`Kf+h1<6hxayt${2)Hvm7jVlL54ZoE>{n3fPuf zQ72(q=L4>EopT);;$DX7XtGIZaepG1SPV0$JJIw2*jW0Dc`d{O1NK!~`Px2r9V*Gm zS-zKOpeEUPrZ_az{6n(&u(P|@di?imgR{qJIiKroQPA1#K6`()ckrPd&U3rwc0mRO zbM4Y1JeM(B0=D$oevdJ)0A{(SR%w$uUWEbHa^8Jc09u$$q4!XtGc%QK5hag{Xn$)o zaG@ctYKp5c1FvjWYJ%RGx)ama8F=jm-Tjp*iZ7qbg*dZ7WirUV_ZqJbEWgpoREHMH z*EBP^4t2$0R+u*{3zg(~g`H=+o!y_TKv}432VVJ6<Ksc=alLo=(Q5zr(|Ka@+@8Z2 z0N6gvR-M!wbjrl?8j<Za25diI$6?)P>osaupn7(dA~#<SL=3Dde~chdD`H!w_!e59 z6SxR^RgZWndvfN6Y@HQ7KyfhkKA4?4Xg#&a^w`)83{EV%@R@WWdiA@p*LeMeHQ3vn zZCPp_NcJFL*CAMb)P$>S;HvA8=hfZ+`Fi)@m)5ldFEGJI39zpAZqVLoGUio<az}HU zTai&Ago3!0?aBBR7hbpv5p^q(<<<koZ}d1=l@?bib$s?FKVv=uXb3R@jmok}0g&mR zB|wW1Bikx;6B1$st{iCPV_sMsFOMM?VPa9veHd$V1i}(@c1yE3d&As?s2R0924Jsr z?9$@0WL&s*8O09_A-RUB#l3Qkju)@(Jn9|(YOS~bEBjhX<>8^W-I(1EDEzwJJNR&= zv-@LbUdwQX4Y|=N?(D@823(v~dBChHvE?;G_Kfuz!|L0dS=G7>&~Bw8BS^3;4F?V# zut)|@0(cDZDT;t^G-!#>MC(z)3<bEx82?MbhN?OiRc+Kl-x=e|+=tMo1W&@=;U>%k zFl$zn$UJkv#f>#mf9cKGuB^9M<5%jQ2_RD>wen#|O{uhz)%;UM8^QgcwcYIOzUPO7 zlb3Uy7hr7E9Ebgrv%$eHS37&}+q3xa%q=ml1hmTxm`c2kATJr{R3h800iz1opfF;f z71(~gM=d{KCv8%TzsaFMy+~S5&;VBa^9`;$6(liWq4SLFOam=+zOlIOIBY9<a{=4Z zI*4?gL>WYEOZV1b*P$xbP|rz<e>3nZNBxtT^p+Jdu$jCWz`p#&FPzz4eO2Un;rJJR zU`(?O<M@{z_mZNG?z3v^@!vIJ%Jl1MaBYLr2Nnb3TG&4#bMTrTOI&<TDo;<{ecaV2 z+<kyMUd(_frnb+3$&VOV1(sb78FGb|WyfqCvGbQ1OSjnCNj}3(K3@U=?1=%O39R&I zjpl%b=77dZKU(`-Jx_BOIBMYZd~9oY_o;wadL+y3LVbr0VkDcT9mT>Pbq_z>8qBEL zcK|Fw)4rYumKr>m?!ryT)^D+GS-ewCR5l4WQZu>^)9F#t_*c|Be%gKZi?zYoHJ8hC zpZo0j+n>ioj3=3ec)|)Xkd~(jyvZ~-YK$je0X^-YIaqlBNHMS_*8bTx10m`#Fml*F z11SI}c8u*WvUAwDuEb9BEC4%Zp4y@7E_Q7j{xyJ!8DtDB2r;bWqlcZJXJ9$^pb@l) z_Fl113V>_Wm_IhJrAts>MzOCXkCY9BtNr6ot!o*IxB#205K{x0ZfWVwHrv?9*q3<N zHTI=9R+H*b7S|LN<_DyA_|X!~dv)2@Hn#~I)u+@*YGhKU#LE{^ryMi3YE63J!i!GV zUyc05H%|FvmDGyav6!`A20(0`#G*pBznTad61N>^T9%Xc)YMYTp{dM+;tpGalD@Bj zRC<rpbJI_fajWcj4n&miVq#bZwkAKDydY?)=bo2sC8oO$(ZsHr>oC^V>TKC*4yl++ zZ_wIh{mIHKD}K?hX|!>EovAR(s}Z+;@5r)!a&Gp8b2UhQv;^Bhd-tG!{D~h8u32&3 zxeYhg9-#VzyKsy6%{Sum>NkRgn}5)*-TU*S`n|u{t8V?lc4gzcJO1Jqdz1buFi2vc z899~phFiT+b1R#5r@Yp1O3RD3-(0f&#*zi~|I%0$NY(*llR_1#+t5xJBc-lnkHVcO zs$C?DgI;$DRra%UY3{si;h%%tm3ZOIPVDn@Y`L9GWrSRvV!)72aYW>~mLUwFKHE6H zoxLue@l3QOhOQ*Hm=B>}MXrm%D>j0c(ubf>gaEauf9!YnKY-b^C)cR1w#9P>Mv{pt z-Rf3IZ7;x}H7@(gSKCmNmq`@f@9h5g&Y<-<nekhFjSWhRH4-HqSJTxa+p7hZQw>0% z0Lir6y+$2}Z95c{49FN0J9Zat`jD_b$u<!74q=*_Jpig<*TO*n#0-`uPEyzmjz^{M z!=E9A3k57odvBgqiSq=r!h=H-yF$mczn71Pnf?wTBbiqExl})<KlepL`#8U&)-xn# z@FF^hWhLiuC_=EWuy?fAJN#&QhQSMr3IKzj!}L_mL~jf80kP5ZF?*12<5`u>?2BZL zSGo?<T|>3`DC|ENoZesRpL|B+uz&d%H@ESP{mxdZqJu{R*f?c_8^}*$AQduDU|>}a zsZ$=X{YAETgvWnI8_eqKFTPP8oP9pl`Oj-%cb^2Eor>qaVm?7h$j8-Xu<YC<dLYCE zV0fPvxZM)7vco2z;+x}vmEsaKeZOI@LFIMzJ<knV!gAoHsSWwI3A{w!SJrQ+)q8Sc zu!n==-){Dfey!&3rmpNMsm3vu_ivgqx1Z105AIYqL%Y1P#H((QEbl8fA~pC36yXMm zWkZYw&F>vn+S~tFpDXpQgHhtvZjti(x4M;$Z?`I&-#cE|`u&~i=J%}f>N~FA{6g7X zxV7MvRu;MX`#hg)`|FJ5x4>1ccAyt)F!^cF-YJ>CL<(BKzJ#mJ%G`6hJ5RU@ljEu@ zahTgrb^VD2y8s>exkPghX0AYoNSB_K#iWP11o^<z!si)WgBYZRdywL18+l&{+8i6a zvQR=!DsPH;Nzqqma2<vkaPeag1}FCyyN4gS`h>8QGivvoE#}JFSmX%<*v!Pcuz_2M zbx1v8NLW_bm%(*d%)aDuf6(5p4o)9V%cy9&jdd7Kc{N~ceBZ0TQiWI&08jB7(vHw_ z)J3)*?QFXgc(uDAL7&(qzyzp4XAk^?k4@iIoeD^-Gq5R&OXa{R2T&;xQ4CT7h{R{= zK;@`lW!ZVK5|hRXz_PSBmIN#m%PJUvihTiCFAQ2>SPhR{hWR|Nd<Bg>c%gMF{=zC| zSi(WeX9MC|(0ZExF8uyZl;B@En|TK2R&T_1dAW<L%oDv74VZ9gphj(~xPjRh1FmLw z?`P*5s?Y7ETL1K+12r9opOwZ&Wk;TM+@jMJuj4kvz;M;8-|cXr&;$)wSlB-<D}&e+ z806Z>#L)M*FvwuBgbvmipmdPR7*#Q=QkhjTSgEdkg<+k8Wf_GS`k`^n$huUvmGudk zz)KEvqfi4aSO*5zVLDTL(0kl|{I~0jVH>{=42-BdTiwmf1DI32(YL(j5^n6W@yoTP ziZ@K`D_8drl6@a8u+H$ZFKKQsU@)@n?f-It<QR-FnvQf)r!3fpWs}V1=18CrAeQDD zo`MKuk`{$sa3hv8)}OU$8T+yfld=k6NtsmM&6hDE2~IhH(m={`@<64ts*zxoftCtZ z=K(CtvNX^-ujN=*Y-U{sm!YmQ<Ny^3@WM<?jfKTQ`$>2Az4hMFuPLZaa|ZES240W{ zIAu1}l{muyMtQYs$6xAk+Kt=|R9O?Xkqgio8v6p-{LcR1?2)6_IGfwmHs~DK??!|6 z_JVN|PC2n@1^_ApL*S*j$WjoqOMzWpi8;%e)HQ^Cb`6h9Y`vHKQA!l=f*6!Zpg?X! z2|%_2P%1bfcV7;mOdwSRRAx6|9;`6C(p`ej0a!WEGO)73n~OKJSeNVz3S5ST02QXp zOXQ3O==>lp{Qbd7XYZ#@kq`{TN4DRD>1M8BW$>Xkbcivt00s}3Q{4iU*&u2BLwx7S zJJ7phbzGoj%)`<<{J2p>_2+iAEed<2`|O|UQPAF3UO=J%>F%<!4whPoRVc1H3xn8} zTixv0UUOpKAF6&Z0H!cQul(Lx8_x<;<GJq8wc(tq0&p?{lo6z|`=f(Y-VHb$tR@3k zxjSe+6yuGQb?Lx`2C{nImLBg)YolszL;+qw!py?n!`|VCOP$^KykQB;AP#aW8v~en zW^@ag9-G-xQlQpx7jA$odN~X3>JI@{A7&Pl%uNuB#*1AA(codwdVG6co&9<mjB@*@ z4`^xi9UCgNNYmn^NoTLM72Z^!ZCE@=FFRO^jj$t&w-46Qv@O3;VkkMG3y7e^5KMCD z*<^{x4qmb^>`tT_cx3}PH4ZGU(PT(~o;!!;w~e2JjwzKthI~%ZpE=))$O?c<*}$&C z^f)SFpvJn?enHLY(vy_49Is@`C4SCl-Myb}G3MokBW7B*OH0J9Y<8lsJ5!Z@W*oq7 zpHaKq#3R0;?n7?4mKhp#rAq{z-|;8QVeg0-ABeeK17py`M!P}lN!9l1J6wrKP3$F8 zs}ht)#5*w>^ZgioCS}KBwy;t1l3iMk$nhr=^MC*g3wy_|Q@Ozik(ah_q5>9D=H(}A z&8{o4q5{?hz%mBdnSpA^XAU`Z&J#2{2XOICQ(hNV93n-smY}TXcV$crF*QErQukqZ z_a|GOXYYIC6#jPnWpGW`qtk~oRqAIp0Sp43;4dyMx2a_}DRx1ObRUwe?V=HuY=A-M zX?On@o3mV>Y+9^$__6Dh*UC<HlkaCW0?~}zB%f*lK$Xa(lTII+1|>EJFT$4y;uQl1 zjQ%7mDMbBa7l4eA3dyx`3A#wUWCCT(z=kp?Y{ft}Z0~ZwO4u;a!aGI4mBzsNCMpjV zi>eyBJ25nXnC@Ckm=|MPPrCd6wAtBt&y5DBV_l8q)dDsscczGejrGIMB!Gd8*{y7Z z)OJH6A6(5ZZgd|Ct&uL?5I*q`8lNAWJtjt_(A=(-fr2#X?4|&Q+JkH>1#E>gFgP<x zH<NZF&W+h(yS0H+TJ{I6ClmYr;o!`Qli~Z4J{Y4^xD_$5sWB(@9HP8G+s*eg!$GRx z*jU}w^MVz&a{?E370P|&8sx|F?m}hT$htD{!s=z#e?0B(y}#Dp{jn2|8G-=u1_Q7G z`@9kx5KW7&VJ!Dm5r9p#?~6JWJ_2Sv$t^q+YtDUWGF`lZMq4nYLD1f=%)x7VEa)CM ztW(&bZgoBBKeGXjoT5GjFwt&gj5Ix)gBSeE1qxB(0dN=kw!auN0K14@*=XPeU?Xxz z1+DBFvi43Az!D%PUFnEfrtcjMPQ#85f9aejox=*cvz(hS*)9ey1LI29s=F{Y$GUQ? zOWFREwY5t7$=_{osr<NQ=h*M!H{S@I(rSy1SZeG8C)S>>q5*)xFEa+0pB{s1+?K!C zrxq<`52)_*CkD@9)|g{76}V6!M*Y*hp!IlVE}fqq3%mO?=<HQow@UPw5MJ;!9-@w4 zEYD1tI|DDl!YseB#0F82s5LP!2qZMeflwlfFGCG$^EF}cQfdzt*|~a+%jjY*!cCa% z8-Ys^Y!m@j0qX)ljCe(k&w2;HT<>iEuo`st$$4s~T6URu^?NO9dmBmEVyd}E&Sukv z$QYPYUT!n;U8hohOEM5s;$Ol<!^m8Qs{0Up*C&4e^dXJO9Kfc<g6<xOIm&MRt~{nF z50UUk0k=4D8KzwyZ!0u)A9A*1`w)X=gDA(DRGBe|pTwq^NUk_<b}h>Fq~|I%K*z?N zmo)|+7qpV?(QZOrutA2zxndQZgu;;MAeMn#*gJaKJNVUB_u!XaaL!qkp$(N*z6y0* zLAX($!GSZMPGwk2n@T1I)y1h(*~lJP;RYfoP;V&StF(QrLX4pZ9gNofsDE;aS2DLr z8+QwL_5fraQ_I~-jvCmA5<`vUDr0C?(s9Yk5f;QP`j%I(guO%5iC*A$XKj*=%AKU0 zp7YJ}8NkPK8&|+axd-)~0=A{>n^c8jpsE8`rb3QMo_(2%^hS($^&W)XgGz7z7i*op z_wDB?_U)8c8GyaLPhk>XZl;0vblc1^Fi65=9Y>S~FErnN(T}e_B$@g!V`9;u^)%@0 zZcOf*&+TOyW7ce>d{DORO~b323r@tzAQVB9Jg}Iv{5WZHaTaT$mKQ-nGk5jHlZl%~ zAmo6FE-jC@SN`?R4VLtZf!Aa}E7>goE}X24fvdo6m=kWu`2s^wDnITYf40)u`$?70 z4`-_QGQ#*AhPB(F<y2-l;7(@_z#uWzaux!T)XpKguV0!m{uOHtJ_9l6;KJU~Rd~AR zcEJ`WwFN6M;-!50FH;9f)BjT;2ba=oF{LzC77ONYxl3#?#ZbkXoRA~zS}~M_aVkkw z{aCO=SZf{m$Zv6l+=9lJhJhA(y$H1Uj=@c+09R4GO9(e&={C#@H&{Q>+1+bz|L{gK zKVav%zzYm!052_n6)MGSJ=X_JiOn2<@mY1fTAy_!C8aWTE;w&8Q-C*V=nTC4XhgRB z+^(lVaon)I>ygFqG5e{vIFlLCms26)NMlGK4&&ezr|LdfY!8x}LrN`YQla;F&?Z6u z)VE5@0$NF~9aFqb(6%WK{F1~Mg)gb#G@5B8XQ`ky%uOiHMKLa-1DA=3sq=V+-TmGE z@h6S`$tSCW(?>6qX5%j0WZ?CcQwwI|opBolV3T^TUYc2MJ%*f#Vnvy5v9e0Gar}$$ zMjG}{audwWZ8{88VT{CpE6&zCOta~6Fp{*jSO_JEU|=wy>Jw^DPKg+_Nz^~_Oy4<? z_m5+f2CnA;tYo{8WyxbBK#Ok|v8@cmh%6g73b@cVfzZO~li=+0dhhVptNr8O(ibf+ z#|Av4y!uX?ffxkaYa`z<y~ck$2k*>+u~AE8Io0KS6$Mqmfek$-`S@4bpc(tJ=lNe# zYJ7llM8ZMag9<KG0kKh!j;iP*l7X>*8Gu>t!XT-<J{g0;#8J?4bk{dGiA(5u6jx#b zaTg0+=K(AIV62{^vn=J9$vv2N6{^yBt(IZRx-tfa07}HgjIl2$!rOhs2DE<f@T1l4 zv-jPkwA_msOv6)JeLH|DdB}{r4oNyZbkGjAxZwD;H)?w^wPD)rtYa|=IP#CJ7@1}J zajHO!24~xBnF~0k*y2=W>QvT9G8GE$>_x{oO3+KuULvXI?rf(_$fVrQAkmKkntWd( z+i>OvjftohA}o1LyPk8Cuo1vM23X}z8-ryfhem+bIBW~K42@tV!A)gc47u)yy~9fH z;G+!&UM>_uxd?#{v^lSKCoV0$wLg58@Y%8=*TvKU7dLR**j^+n45Y4{@h;aq>W0x) z&^;{8E6-24ah84P^T4}ZA~JTQ#EpodDW$AF&h`@_N4jm70!XZ%*w^M$bPOz&dFjBb zykpcS67_<|`4XcJjtN$|;|9Qz=OB<Gh-oRHMTm@T8QB&Fwa~!CVhmg)Xz%X#j(&Y} zaB|-P#t!Pzi!!hA8Y!>8d&t_$tbo^87*te=L)cWg7GcsiYL`Pbj|aJT2s&&ueN>k$ zQSY#Hp6vO#T}O*a*gv(B-#P1Wsqqm>jwM2lbejt}2y!Ee0hAp<LTVzhpFkpNGRIW< zHTBNF*mal(JBoS=!!;N76lQ=lgO%a@LWYITH88G>Wu@m8f)*i}C?kFEFgKxaFUDd< zWj^=XVbI#H49*@d_fH;>=gH|RG}bQs#_OcK`t@VmUtGGl55D%sf5}ma%u{7h1JSbm zkV?6;4qgSfLV3Q_2MVSFkAjv>=JBuTw!~Eo0JhJzm!b2bY3GhriNm8t6zMu-``cop zYM7yU{Q7t2`r7Htzrux>LaKsQRDg;m>JZmQlETC?zfJuMroU=_br?`7uj^ip0*0k+ z=UsyWkl<AlXo+)mwq=+K1v4+0?j-0uiGwr0fA)E`fB5lY|Mb3geJ^gTT@bH!n^bOm z??3@BQ|xd0Y}5rRF|c7LO^pEqBbF28FN+pzi^ttyLV*a6LfEq=RSsxwSHpNzWN-!} zZcAqb8G{+-%+O4*8issqP!&_k0E|q4#e-Wc8qlmWj}zb(jDyDNIt8kvdVYF6&~fu( zzvhlJcjR0^rJj>xSMvN}fR&!FfEEU_A)tjBmBy~*`>}LB8ay1dcNYdHpRV<ee(eOE zJu(UA3<_1Revedd{o%F+Rf7~m-+RUls}QFi(y3E|U}*ZQ>#S>BtX4FM$$rqa;^$0A zoZEG=IH{Ns_HC7^oa3><Qu>(495L1|Bi3O}aB|B51y2F7Vhx01DcF!}g3n1i$<)1R z7TpiIeq$W#9AKsG4+ktb7AeOPF$U`Bb<oPOEmbOy_hZJmdMBSX2FIV=8nm`~5;Bi_ zPXa{1ZMgJSTwee7p6xDdsC|>@e*`0r)cZAKhE<4D#kNewMl$RfbebamFm&1!gJl)y z(L+MwN31d5nlrE|H*g`c4lBt8;|KH;(nV`i)NrbNAz52?Ibe-*V!2>(IACAD|AfZg z4V8gFfnn&VLDUqHta6A@>m}bW0W?t#3o8?gn3j6MMxc`K9|x?`_vQc#vk=U(#J<kP zMuC=KTj`qT|7{;JmQ@N`Pd0=04h_1`Cdz!EL{w?%t*Er}wSBw1w3hFhQ)V2aq4W$7 zo@XkNnOa^<p=^(E-{d-*A<4`p|27;B13MXs&2A%_854qtWQWLhq4qB$)bx5Bi9)Xx zXEPsPyg_~<12LO8&_<o4)270e2-^_POKLXqNjUK(@FK*h@`%wRIK*W*2Ft?J6v=b7 zc$mtt#CG~_g>|9)E)3QRfZ^JPDbq?no4bjF_8!bE!2qi~IDOcJiA^Utb9Q1BX2>A^ z;+KNb+BbGBci~2IG!I}RiYb087074GW-A7k$>#Afl*D`v6Ax`1_3X(v?zvqR<1sLO zFr$N*(Td})6enR#!lEFN(nm38T8RK9;&HEJTO?P9k}>KyT=)gYRuGziDt(U;tk3{K zZl|B3xe3iI3xSl8X{DeAu2ll}CwLt8j>`R$&uYQh<JJDzLp~G1r5Lq&W%=sX_fNgX zYx|TsceDDe+PS=`Me?pDKiAX(*r=y!){6O3w7iHZqe(iYjc0BKOLD5jPE5!PU=NiY z)MP#QyrT|#j_A;-jPZm-C=1~nP7dRAhGu~mBvn<$aenY;P{S31lyVHY3UgpZ(6)$S z!LbN^h7MRn0++e6h6Y;lSoC1f-eZ9Es5&@(umyq)kif?Y;GgRrzlfDqzQW^Oj=$Wd zmYaeXn%+sk6dEYWTATbHWYa-SnT>q7W(&a1$+GgM-%tJ^OczU?+i(jRfW^^hSD|8s zfw1&tYjkzs7cri%6j&rpxP=Z+sOl(ZWY8UknO-C9D0vqN;3hgpe$B#zYI+v*<Csxp z`#B&J%nEI*?2Bl?B-VK#)2BUAgd9AMc}9ZH!BN=REeGAb+hON`MuQVRWzJ;}MD5ZN z1Fv`b{^A!7o$}hcm{2z>Z>P2iN(GZ7eBdOq3={FqR07yYo2?j_sEUy8W&gf_#WBf0 z%yXMo<CQJgGOXR4w}K$0_BcMs7ciRka+yRt5pN_j218g_d}9t`GRl`Q0ht3Pl9|(2 z@j3@m%0UcN$$k;DN;XaIL!6wfy9Lqt3d>4jTG61j&j8AcdWU}4J9NVCK{M#=l9yF{ z8-yB8^#<{qZ*<BlU)v^@bC1u`EhpC1X2s@~sSkNlJWSaF@8K~_{JTtzjrBmx8Uri% znO~&yh^@k0vAIp5;X%+vedL507@u|Ai6nT%N$e-K(#|jiWRV`g0RVt2RVT(mbTU$b zSOijFh}{eYE0Skdh_w|07XK58>yIr@qp)v>-Dj1svj<{~<)HN>VOcNHC@<bzCH~Tz zVR`MFySCrlB-Fl_02hc`)!E^5>O--`2Ow!ER;WmcMJ784x{@|yB96jX!;1jW)CN%J zW-!VVdN0~tlPhm=p_X_a(he?-eeuNP7`Y8e=IG?tm#Pz!_)7X2F+pc#oIr8=$xAYT zly)2gmuXVTeEmWVurhbygJ{rk!@*H0Xzx~n_VzkkEGVR_6xw~s=GE_#^7^-roQ17p z%ku9ebLu95#b+cCzY$G=3>m&Z+3y7REg58tUDEaX*f^c?jEOk7t45d%fxa03nv<{D zGRuz7A21)@Na84*tPqILJ!Z$kN61I;z*a1R+=i*No+efD6I7$At1%t_=i*=4brpkD zdY%cWa?DCSR~2#~0E_x0>>cliy@OKNKX$|J{_UuD#OK-qMKDZ2dT9#6G0bJaRa$zh z<1E}fbxW&j#D-i{3w{&MyfAnrQ#$3_mq;#GT2kuoO;RAH3%$rWT!=5)hhHC)zL4id zoxBv9T5brgfw|3!S=li71uYf=hd7upfWc#KWhK<2FwI@28Hf=f3a~iq5*?|0WK4Q) zyp2FDA!K6!m2@!{fED3YZrIoD?1ueQFAf;X>K(gL@6d?`XUpN>G>vhcUBQtg<aMn8 zctw8mjT5#$WI%N<0W6Jm$z!SdP%z%qVtKEG>oAE}#<BqPMX9T=l#TU+&Qvj9HgcmE zlW>f3cx*r5)x_sE6Gkm7d$0=pHf8~b{yZY~5(QQ~K>&k}l8yFp5Gr-61q{S6^RiQS zR0>>lSjSidV9CB(eWL)V_@OwFtS8gHz_JsE11FApR@gi8892F7|70cTJhRxEBojkk zHL%KzS{|{y1p?&@{pM?JZ}H7N%ku6J%hFgE2Cfv)Qt-;|c{)}S?p$J#Ol5<F8}etG zs;+#Tiy0XMn<|ULF6HW-WRJ)PiY6`)F~Q$B_fE{|L@g&wro*6Da1=PVi#BTcQBg9y z(Q>$z2eM?klSwi#9^_Z&krEXstn@osNvSCAj*?7ZoKd|7P=sh~Om{0H5R1Y`j79lT z_n;gF9VhG^cwu*cov|#aJxD^%tgcUm8(uxWevgz^-ahf_uN*M2y2}pU!3x7X>%#1d z8t!>=uQG8>lD8*ND-q#L9s3&BW-A63hn#^Ov6j#@pAZ*xMrp&6BOcU<D7^(PL}-|C zJg<j1&BlLcJ|QaKBhTMy=<c=9$|K#ySr%h4d=IGM35+X(*HcA@h{ME!O_~{)5|P0G zCHMKDkpWSAj{%Ys1uZWcv~0$zzy-+Gty{cw6U3~zGBQY#h~YW9M#3mi7JBtpV}J3B zXLe<+WxKTvYP)p|SW;<-<?>>ii0%BnPIB@RF^zQf=H`~0N*jwwoF@<L+S3bO@+pp} z7%^s3r;lf{;iyNUV~cq*&D^HfVA>O>3=AwCr5L2;=&;draYLHh#H+<m(uHWde7>>b z@0C{O03ag*ORaRwhC0>EtHp&B$pUp20eJd{42T#=b?i9k+E8+fgH_OiIn-8@^V0XQ z=a4Vgya2XcS|;wot=OyIX*-qmwq0IZWq?#uT!X@GDBXwYF$Kg-j5lGb8CVJBC1PJh zIvFqZx-ZEtBo9n{2;9AEU+^!2hb9iGIszsnzn>YNRX00=8;%9jVN7m5r@37bgK1By ztZ#B6bBxO{dM+CC#j(=dSf_7Iaza!FUg&RYGYgA&6|Nzem0LQ9qtHr@g%*$10XT)Q z^sFfCQ;1bDiUrTsBDnn+U=g7PoJO<U1->}t)!41w?t0DF525fb1EgvWup~%f;f9HQ zW$weQzY)DmD%ilYcuV&6vhKpf4IYNEMs(x45aGkFk2AW!VDv|Lz?|<Hx*-8%L%<); z>68XeSpyCyIiJ(qu7UvrD?vhVoVAbhs=xAc$tC5D7)zNTfH6R!VR!!!>NxWG%wWx* zgMjkqAqps;^pXwkAVwU=1DG9}Exjc!OXq;pO&HWQib-9JQR)7({U-66UkLoht0&IF z)(HclyX>{H&Z@FFR}o;@NG^|azNq+%gtrwrQpT){8Y*v09g+bP7zJSbqQ?cWso<!| z0~nxdQH30ZhFmAfeij3RhG_eXJ!<=NE&UW5<pY&!Ol@x|87y1+38W}i#Qzn(XC+p| zna`6r=-rs4JFS0mp9HP##RPm*$-DC13IrEZT<v_MvEQ2~AiMQfyWYahQ_HJ$*|A%U z)h^|M%Mj-(bQfBZapfy-B!g%wrh%a;qz%O>T?slHHe(_VNO0`g>ddW#<Uq_w!b}|$ zG%TAqr6vH_!sNdB++Myh0P`0^YL^_nQuxTJcuW%WI62N3juVBQNqy<$FESPtPN_?8 z5(a8Qr?N)e>Q?A3Y;`QZ*<*v$Wy>y=hy*5@x(ewC;G#LPh6-Fld8m8?BJ;b>t8ShO zsv)g7wv*BdYsd*xXE4f*f|iqwM9FiES~Ry2YlN2D=t6*hZqsa#n7Z1d@H@+#n|yDP zkB@2jP|9&AHrsNqQq_oAHOs9Lci|@Sm%kiWHoiNkZ2rzk<;L$m_7=am;8t!dTTT_G zbm#HJWC7)IvL>*d9#6_c*$KFD(T2m%!vKd}j6@EC1jr%xv;aE2P01RNd<Vr;XOD%A zssO3^7OuaA{S!OxLlx?*LlNa|@XWF}S|J+|MWby|uMF0Xzh$`#v*}}*5d&9}?Jso` zK?KU#!K3HGCn@im<5CHTHwA6?x~uW7qrvY8gHFq<6R+_KDXo4jsNVd8!}{Gnd&t1c z^&79(+3UCY?~Oo90FruK2QF<t1uYf0viKJUE{X&lBP~`79>>Y-0@KC8M%cIvY-(-_ z8}U-c!j9x9Mpm~@Emz0QWO$sN__P7QAPawP(`b|j@L7k}S4YIUm_sv#aG`jsSVseB zkItBatGQi}LE!<ncAJzL)2iS5i=+CzzkFQT{GQ`3+*+iTe<xv8m}RNVE6=!`0^qV! z;If7QmjJI}Vjz8Bt>)atNJ*;3_@~OWm`}1;=zxCmR2rB04q9%JODEeFMwL~vEk;Sg z$Ias-J)4c@<~DssZ5M<(aZ(UhpR0*x?EzU{LXVEfN)%9(_Q1=k!^~|G20-IfH%V#b zYjO3)_k;Q?e|cKn`lG$l%2!>tcBg5%wJnQ*8nEmf+u~rAfEJNpmVi|TTy_q)R0X1{ zO4Q?F8Iv!LsS4$EzGud9{1D=u(*R)Ocm=s(_{XrWf5248=)t1?X&MI;l?3n%u7nK7 zZZuLt$|3mAt*o`}^2!9i`P^QXS$2uom9+uu#BPX3#w6e{#=m0Zn#PQu3plz5P~(rx zE4W;!0mE|2tHf)(MoOz+k198Q(68S3-P6j(clOE~-*UX>7iw;GW7YN=d~hP!7GhSC zVdeJiWScrh;1Y~0aTksNuG|+lCH5fJp7vKVZ!aweCiD5C;5ZtbO#^_LWc0@C(p`vH zhlh_(MItj;6ttmwwW9<-757eR3XY**suBmwMp;&Aan&xZ%zL6KH0o4{TfKFbjLHkt zV>GvcOm+FBNy1za%NQ7$WEGBSHxbJMFoMb$9JE&6j>_xbX)oOT!@b(=Kd~5ad0z94 znp0j|12IMdL;|eb45*MOBV%8xTQCu1IC9(UDjWy6sJ3LLq+H;Ebw=S4pU;xg8Kbw8 zV#n9sbO0-i>)>RTQM7G3+=}dnjs`7DbRwbwj1qEiqv}r9X91|W>;YQp^O4uwrphd@ z#sF;Nly_c8eL7z3LKkpEL`<W?22)Nl704XG#<!IDGGJlP8*!>PiNEv~V_Co3so(vx z!}=?K{cz#dA6uoBuT~gqUZs|=u_+gUR;DCO7?sMv2nHz*Wa;ZPVP1K0#+bmBa~&E? ziN&Xdmx*9q=_H+)f$@ZR)}H#O6aNKXf)xgvF@Q}6K#gH>(4p+d-pPa<T3}Sq5mO8+ z(6XEl*tgw<0X$ThdvR{p-FQw=c{OCe>77h@it@mOD^Y*8l<;blxq?#05Am<4f8xy* zh~pWk1$i86`O9(j=I;+`cmKRqf90<ZYIpv{Ze{a#cKpRJm7Rr~i<VQmO)VU|(m~5H zgO;toF5HBU1T7igvap*_6=;}&OO2Tq4tex`C^?AYUwU69216pAdgStIL(^xvcthU4 zaf<m)9k9WuH|*?Hpr#pI9>u`LZ;dGwh-x6>YV;xw!rh3?uDfun7IgRL2sR}K>O-e; zqr-mR1u<e)t0$0LZxo~Ul&`>%uzo&WNdI_F*EO@83V~cZE^&9OT;0pSE42N^zGeFX z+wWW6!YZ{ZfIw9$UAb2JJD{rWQt^k}zUgsAt`7Uk=TPl)#B-kh=K7joVGm|iS;`IC zlP3EUX{s7pGAyO=LdLj~CCO=JgQ3H)yTAB+cl}D*Fzk`Xi&Tih#IG_nF)*;Oe?kVW zCuP|%WMZHQh^A(`E3!@EAb!%|ZMVMe)?aDQ<@D2IPI)bMtDC2MvO%GFGP)0O_*ksf zC6;Xs+}Ni_Y{Wc!105Iu*d7NbJ~$|^K}Gj87whX$dHvl%W#c=Y^2T>ht2e&CSKa!3 ztFrN3mocoW+jzC%R5n&2=~!_C=Hgi{(Ord7LCBewt%@^Jrj@t|)!0_TwoGn9Te2-9 z;}UF3GOm2g3kNHtGF7+;5zETJEOQ;=eOQ9dUKyN9Q(}NMVybD~lw-ErcwHRKdg5%c z({+U{|B@{?C-#*u(V%cDdx$Kr`TAqlQ0tVS#zxcfFmiN;qh0!#N$sro4Ycfo{^^4S z9^07P^)}WC`AcsG9_u)1sy>tMBV}p~llu^lmcw+U9ExO9GC2D@?jL>ZLV2^PG2uo- zE;a!q76T4m#l5u30Bb8|pcOID8u&|ZwaRO6AD5QDa_BeTXu7rAwo}=3o$`uryR|9| z2B`oMA(%k>LhVPTIxszE8P1dYmfR<3KVfQi+85Yae*niVJ7a4_Quv&ka8!(xs3Hxq zM)56PH7HI*;IU{&=@RoL;Pa^U4nMN`C!bD(0uk&HTmBEWv`HpZz>8#Ovo`|d7L%}d zNa8`uOX3qKnOPbA<l|sijfinyxG5MghF<+%03*z~U2g*^HtR^cS&U<}`w&}B7=$Fx zQUAyVPr$TTQmLd#-0BVD)n5T&MO<G07F4(B*6#k<S^d?2b=Y|QU+>jl`KzZ3H~;u) zW%GCJ(#qSu+qm1X{Ka+4sobI_=7gA)%CK;3%QdhpC)-zITDHoxupon<W2Nss2iqzH zE<%VA#8P>fmfT**z6fStA!1(%kcIu@1J<8SYhOMzgf(t4U-*!ziZ(-m)1b3cq2OnS z1}r)kXo#Z<`=&&UPd1kp?TiJBv&&ol;+OXON55X4v#{%JUgPzEv4s;pNuh8~NC|z) zzO>E>$G=qfVb%$;cCiz757(}&`Vcy7U`*f&PiCP`fg)Y=2{OHy+J3~A*s}eQI+cKJ zhnD3;40PD*?h<>S!_ITKOWjJsec1qzko-W9pg5B@IMmP8UHB*tMsw$iSQsJt#F5HW zCb=?BT5L;xFQS~6k8lkqLUC?ObuY)rRY(lmDv-sGdxsxy1g$4S|K>F_z#2UUW(2Zv zMI7M&n3@<E7^uGnXP=jy%H}R{N^41iVA42XmS#R%Gl>mtQHVK23SIhu#3JzPz{vfS z4KJ62?m_B0n%i|V5D|FwyJys?Ze?yG+-VX++tA`)c%n$87z<iY7lQ7yiMx9sFDffC zE!<xj5Q{46Nre&aUSrMLWq{;zaAMm~9*r%09kSzaY>@#Idn;RP&q`}Ux@w26ck&3Z zByi}Ul)iH$SP{g4ivUXlH|_6?2U<jXFOlyz1J|gZQ}gDC!Heec5pnof12Yx0Qa2*& z!@}NCxqtjy8|G}AA_J_^J5J*bE&{NrQy%6l8i0_)DurD~G!y_X8MmMUprV9j>Gv;v zAjNE{LS7vw>$_g_^)@ISSx0s0mo~Q-Ghi8R{T`H$>QG`eGnbL-HZoTVPgmG|7-KMt zxPU0^A5ZcM7|v8kdF5+;r?TEj+y!>b5GP9GKp`7wtrE);0tTA06j}@*_|tVzBHF$N zQpt{XF3kYI04Oxabh5umrQ$IEUDdytu{o+A8ymFLbI;4R5<^Ujuhejb&aM*IVHWdB zCK(MCHWnvLey_X#!SZC=($%&i5r+_Oh{b2ipRncim-96zVZ4+NWbOx+-}Dt9cPgu; zsKcV#!%DT!%GL>UbNuGhu)ALi+S?N!Y0vFq4Qg7JH@<V~l-HIM_nDm+Z_r`x!;E!_ zkuw@KKkn}Rw7~{96Z`%gfETt%Y2~ZOPHB13au-ULQz=<)xop{fnO#$r%G!UfB7h*8 z@F5dD2%!U-8U&xC>|5xwto-LGH3P+K_`Ed!B*hh2Xkqc=>U+iCPX`%Dl#xBtR2_sM zR$VB_K&({^Sx9a%AIeMzFO_+j+=MvB6`MpGd<j8$7`C4LT`8Wvx!!)wmj9b%6*)`- z4t_gFsSw2iy6`_{%Nv(?5tBF8NSxB5ZTm~RmhCOb=}}~(HmDKx1}YAvqbe#JENw(# zr^5iuox%n-)drn^W#iksmQ!kww0LV)G6)MgEYu!iGE#jQ4j}Ft^$vekf|6#FGVcCt zyc&87H>+9sE72hf3Av`+(~@;nV9<an2T&%E!b(4j6akg28JGhrRVk>d1vS9pDnSfL znPM;jJDUAw=K(E>SvF3cK%07h5a)MfMW3<33p1_+w0Oa1SYR838@+>r?*1<tLF@4} z4v>GvmVZOW0I>6jIHod~(>waD<5X@`lceT|wa2WsSkl17y`t&y6tVa^-4~4>8YRle z_iA@eg612g&hC$=RUtHW263a(%G)h!mu@M5r89D)>nP19L>-henvQx;2d58LCkb9) zz>?g`W`|Hmoh6*dt3=0dCje}TQ~+I7hh2Q8I1hFWCi}F&?Uw%W>~PKnsk<4m$HU-) zq}u(*Zo$Iuq<>caI;eh3|BXjMMGm|)wISag242EtDB4S)xen9UqVVzH<o;U!__xyt zUSI<<<{U#s3=G!7jR<RK`9EOGmnZOLU#dle7Gq({HXC(Y*=RDk4GIfWGV_!7%GOiB z!r16{fm+^C|M)lMdC8!wZ@@x$UA?t;pR(<y%q7y;mz;nQbsx$+O-d{y>zKmcNx!rE z-a^zr%0}Z8Fxy`w<+X1dS+<|%a?rFyQlc)*6`{4T>kv!H(bOP1+cF#zOp4GvQ^N1d zSe0OACT1mG*V&cHC5YUD7Gh5(mSsvbejcW!TdZ%GrSju3uO#jzgc_WIN!MZUC}=;e zb@qN%3A_6j(q68h{hTd-LlQOObGZ<449rfK@3ZCYOT36nGlpK&t=`zBx*HL>6Oy|l zjuAO})Z*J|JI@lE**`~dJg7w7eQOS4SKFNOI>e36oXYCD+OZpAU*d-sZo_mPr@)0> zK%!#`TTkN7-cKf8{}3u$l$O2(Wy$M_8x0k+r4CWzT&UKcbS>)cJ^>~QIAI{mAH%N0 z3<Ncx8V9UoOiXtP4go9#P`YanKie-1TEfNxuPCcyI1IcZ1Y+ozI35SBZtL;CTV_oD z#Tv=gHUVJZ^?U9Zm|$T7%zlq8U%$kQxI_a*aLuwyD{dCt6>cNp(!jYSDu^kJEEWt5 zHemikr{<KFcKgSl&Z~1=aVxKXYrs1GXO`tP&d<J35^9`0cQ^na_73)Y2Ori3?I#m; z`#_l`zwt`I24u~Q;h<z*E&ioetiZ7<+Xzh3rCJD3W{{G?3<apfIpcv9(PCIKB|rx( zsve_8(89o`9U9k00hin!1zwq}FqW(<lyNV?z~Z3$wDsig*88Uq=#;1D{Fp6&ONLkB z7)!(<01S@*4qM*6ylc2Ti=!SWvK+5|?{oHGG-Oo`Ioh_6yB@Ky)TsPdkC(;CgJZeR z*k}1+XK%Vn2ThGp;@0kx^4iyrY=3dXRJtf*Ur4xNVqc=Ni#Cc5&K|{`-S_-S3woRi zW0A{WIR)im#;DwkeNh2inm7dknGR4HFqr`@4^SCM;Q?VGP>lpD4X|{VAkBl=aF(Tk z)_HBHP@~9Ym;qQW=4E1DQIG;#BH)Pot)1@v&sVzpzp!UO6!t^5{5{D)OE9sRj0s?R z3=HhsZ28V*UBRVV5|5x>{Z7Derb5sICtXG(S(u1_kp#H19auG;(&E#we^LuOyY$Ld zV3<*ZDR8P=-#>8{woWX|ZQ?RS8T+z{9s>o-u`ex$Cz<BG`=o#L>t_GtlZlIY82gGD zi##N@R}-vD#J?nXNw**dYCRrha`B<X%C=R2QbB4sP>lgrId)ax5=4MSm2PYtmStdN zMfTjROD_pk;5G#ICBQ4)jtA}S-tk9Eo!y_f@pL6D7ui3g3s@2RIyVFR23vmjvaaCr zjXw~+Q(oPrRzWN*JuSz=<fhuWNg(4dSq=8jN!U9qMN?D*@46dI-%(n6Gc2#Yvuj!2 z9iqg9hRNo^4D1WXy-4B)3R;gy@9;zRyH7UZ6uz^+_@#gWaYL!Nj+vKaU^o}cAsH*m z;$5nXFB4Fx;thkKq6nmvc$F?`$Dc6<yE+fU8VOjs#X}B_0<Ph#E35c})P{=NP=HsO zz#R6Tuz}BFckg{4>bK5_{ViL5ly09Jz%Ynm0DFfdLXPJl-B;e?c)(|B_8PDCs8g=+ z2bj>|EG#D*)>Mco14~#~%m&2^JFMgI*l66n)Qg*2p}F<D1d7X2+rO2sTCP=`$G>-6 z5c|T3%V}rS+y8~rKl<33H1l>BZZHP+ddDs;dszWojZG@kgJ{5|xC)DaNe37WTna%- zXH*41H3F<A16ab&0bG33B#&2Fmw9l5CG!TiVI;R7^$$N<Va%(PRP-@?zv(l$NDy%R zB;D499OnWsEa-TPEx%8uxPpBg_QCVwI(~ES`B+%CFR%;OQ)I6<ZFd0}US)9lU~2E< zbuqUJ3CrI(aro5pxRV#t#TN@V%<M}`K4l9O?(IJNc`byxm=iX}z9N6|i-(p|T2N*a zmLQh74hhZNiUf%-R0cH+nDPKcb0AazcEf;bEU>yLz!LT%z?BYS_@;q%ks|PlQjikh zg(UM);&x`NYrXsIeLv{T40wUTP5XaHqC^xoR|haT62t%oCR&Q_MucNfh2xJ&789F7 zV~uohX0r}0w)}>Vee*=HG5KIwM2Uqdn|fzQGT^hEx>H`>hIm-in`}PN+=|Wi8l=4O zt&YF+#b-R{ix#;KHQ@%B&b}1)P*N=+VPJ#DtOHu@AAe#^da8H$-sRPII!<+KF;g05 zlQmKl2h&xRREu%xN<Rgd6mT*DR53`M8>pTOtj^;gdQsp)3@aDwidEnu*kza{Dywm@ zB<2<Mj&~VrUtw*>4SSOx_*^lA91|!b^-H1xSV$1_66<kg^ss%8E&q%yQ|<nODhaG} zbDYZh4twZp6hC<U4B=KJc@~zwt_V8hI+pKe9STV6_rl&`H7Ue7Go!03VK7%)dF|^1 zfBCI_%P!y2JNQwqLn2gWv2YowlKEsrJs@FkKWIJq`|8AFp6olbK^O6tzjA8(^|H<; zaiVg<yb!a@0ayC?4|5gfz)U@_2&B#pR2K?X!iIqs-WdnDRB5~pW@=4C6L>{Qg0cc$ zC%e6aUoN$Gf9ym->p8CP+8anLVKU3#kPN_d6{3-W=`BmJF9B$`+483(sf#$}7F(7J zd0|W5a2A&B4|OFHiXOEj9!8x?m3?lDjj+6Ve%R$1bxM%r8<p3;wP$&?8%B}C5Y-`A zj5;xytgL_->xlZiBU&7sJ+vmBvqK%9lvmy!c(psH)G61f;yOeiCf$ZAcx5p!R6}qS zdlJuu;Xflly)aNc4_IM4CvfRAB^!Zjgu77Aq8w$FHDXPyfdw3cvz^Y)4_7<;|76c_ zx(xu>|IU`r$cP*;uyIs~Vta!vf6kWD<y^<hGmxrUw(ofLyPvZMwILs1g4P(!QZ^<{ z_IXspHW3d?A9Js^Lv7a!+q?6Ezn5e_Ywy(?zqf1o%?+xF8!&hgMRl0|g4}hebyP&u zs8cJ3GwKz$pZ-HB4kq6bfs&G46XdY1W#SDx=Q_+6B-R*L3R=bNX$UZl0;n7Yjdjus z+W4R)|BYkyq*1_y+=W<Sn9K80+DL%*dCcok|M(M!wU@=g>3w<`ZRQFa6bk)c+44Cd zIT6PT0a%kQf6bQ4<y^;0G(KV)uz_vEvHZnt05K|NTplUt$lyxG$g~GZ02%8*8@Aur zW`D>JyU&~%9(7%c`HOErmH7j^vaw33CM(xFb_KjpN0$?B=%a0L10C&k_kYnGoISe8 z-q>gWSl+^ID1o(4ZLfjZmzs!;MI1VKW#D6U^GOy)Mlabn8~4Nuwo#yk4~%jX>Tbaz z;0j6RF2p%r0?Y!EPg+i0hW+DR)>c-#`#&$S_Cn_6<_P#r{};Aw6Ey~g0gQ<CIMb;J z5Ua7}U$I54a6P34j3^qMd0urZpmw=pb|n&`voYD}$#pExB4|s#OznnKS%=X^CF~#D zGm@xysRo_7SAT_6H@|n_RBtXRuC<|YFVxW%2{%MXg{Hdb9sbJg?tkD+TDSqm3-_*n zv*namSE%J_>`P4xFu4u|^HK}iYG9?_%`unDw1OD&*s-l4ZbIQE9tm72I1$9Qh|0{d z%P@-ikHgMx$lA$LXYVHt7j(@H?36A416v^Jtw@15lz|noFaWN<A_)_l0ZS&lwA=-k zF|j=g6@R5Gkz{~Hid>1peVF+W<tq~W#A&qYR5u?n)>maM+J<?f=5{d#ow{4QO%`tb z$u1ixtf=5+NoHaeZKUi=xDN3M+vGarRTEk}o!y@{g7)^MT%WgalL6RwcEDX=W?z<E z=hzob^;sCa6#RMFz*J=802V%=gBE%%$F}lr!sPu0z(q0_q2ewSfR!o@v)I>wOXP#o zM~(K=e`xfMKBh1`{M@8L&cOf77EnIv3PhcS6#|$5G3-JF*Wte)Ni1wO42rOJpTSfs zvE|i$90$wA!i=uOVIU^<<vC5KytGa2k{5J#9LPzV+Y1`I$>27u-TI>)+h1CTsYGxx zuPQ0mMH?A-$qr4Z4rNdtcVN7udenaUj~o5t-&{(uRS=~3%`XOC<KChaZQz2vCiW## z1VlT=-UHN5uMSuVI}giBwugh3*w@*X23$y-F%EF$+=WV|jezwh2m77fAFZ}`f9yw4 zS!243@;;9}V$1)=7ErdGOT;18MJ`02Br^Kjmv9kR#=s54Zi;97i-**yl(8BMEK?<> zrT`WOF(Oe*Iv;fqqi)?UHFtpZMT0YUu1<V@gV<1c<?X1v{>?+Xvc9TUqA=w{so*6{ z@8HBN%c634vJWny!SSby-MydsFvr|wm|wp~N^9TTx4c?anHoi2tBHMywwom@(<=#H z=Ve*Rb|GlVW5Y!m{C5>>OA+g!Bz0l}E+o#7K$cb3i26sny@L;(?z8u6gOg7wZzJ;@ zP?Ldt9%S?oOTiNQu|yn2iOjHu{I4Id<@L+Cj;mu~|CIF!W!J54JY^kSU1eYx#0uhJ z7{s(~6~rh9F{iTj*m5e2h4s8>a5``3=QP`I5`X!vsJ!;>GwwD-g=|gWWmFtW*CFc2 zqEL2ds_T$3_4d=Kz5V~y13t-MLFapc2G>$r{d&)DzE)S%l&Mr+xJBh!=5gg?UoW41 z4Y^+<082SyjA@B845glw+1Is%WP+vf>F+)kaH-%G4o<fRC!f*Y!7rP={STa={d6wW z7;ccj42jGj%YH5y{V*2xJu-!}GLN(*+0b%I%P|}6mMC&1W(m$<T0HFBASPa-O{iyQ zmsSp<!AUt8t<7BxqYOH30I%};yS>W#x3?|7ego%tWfJ*u!OI-?Qf1OfxhU2-E%gpR zqL%?)@V(vY4N_YE>Iu8vGG<@d)F^3uFD(tFxDie43t#@k0qi0HOB~4yG4f0cgI4;Q z!nR<I)Pr(n0l~mbZo`1&-G{7AgzYC`@2KBD_;9JS^P^J#<UXCJ%8X?&7uf$xl8PKd z>mY*Z00xGE3yz6u9AMuhQ#vQuD1)qc)=`zb`m0Y_CsE5>F$M830Zh7@4OR3|K@7PU zY4Tpnt&~|uwZmR32c6xCC&XTNa~E!s>a9OKW~}6ZG1yybNu~2LFA=mylKChDuB3uO zlEc$G{BW7EFZa@zGW+h8jqh~5`n@AQ`KDxE`8mFfd0!|7Hd*)K2rxIk{5*gqj$xLi zvZ*|1>9MT@ywv!XDM{VLxI)Rec-vrfym!Fb#M6IY>mU8bVqiAr{hAxI|3J)hfei&P zVMQ!V02mbX{r*J1{*|?umzj#gPSstwvCT%eH4HoiF);<ik`+<L2u2VS@5|U5XLn9% z!SR=NoyrCq!F9cGF1O7YwS7`v{aRGn`0gPCv17|F-BBiRd~Wb6o_tVjLs91L-p?wR z_Pem}?vz(y_UJ>#zBY^5mpumi64(AB05%5W90^onKUR;930Mlt!ZEF!iV%aA7TY2r z`n|>Po4F57;tU<QqTV6o|9PF=_Zt1<-#Q_<ljrXL^BDYS|AsB^k?aRHxe>*=3V;oB zA&TWSw)_E^>Vm#QEKw!0zM@jHL0WG)3t|*&6m_Hw>|7uw-%BN+S+>7mmzSSdZk_c< zuE#)>&Xb-=lEhoP3rW0#^6J;0*}ST}+cZw#Xk~GsVc;b@K&>;w`Cc;a)!jen?EJXd zKYd6p#dS!MI*!1;-1?nki@N7zU)0RL@NIZrE4+Nu3vvFH99W$Pu=IGACVkI=7IqPe z*p`898ERbWuEMnH%2EI16Nq!wdPg7HkU}wkKA+1VpWy!_NgPaPUq%MzjQBvk^+9Q5 z(05ODLEm8p0ocyoPhB?ZZ+c5#g3KWjL!S^sAK>tg4f+VUak6WK&O;s$oklD{EKX;r zfxTF`BEgLzj#=#AnBMgo_lR3ve{4TnTJ7$C;6agCV1+Yh5bI(cx!ZU(Dy_V8<W@J9 z7+`IP$r!WFTT9+0St5E7c!`ctFT@(7L}d<MFxuXGf4P5ppIn-2mly5bApX*qVM?Cc zoLy;?ZW0ZovNgV5Y-z)c3tkqd47FmA%Do!L#&guy)O9>m22Ui*i6#3=Uz5M3%Ekz; z7*E7t1-~xhUMdU6Q=vpb=Seg;Ed{OZO7H07CS&4ramI@qV9UOCOaUz2hA}YA#Beh6 zU$CWok>C2d86RE6q2u_A$CkH%L>-cQsWGkOfPsmj_aK*}!sbw{jsFMJu~ct7@@lu) zk4;Nq_rQv0ED_SFZjj22-)&VlzWbCh;Z3FPpOq=`o)5gT&ME^ht#(~-w%t4YaH+HV zo^zS^-YzYY((+fj?!wkGc8}&O&ReN;9=S^uP$Q}bK$?zV!;lM!ByumA-Gj>K<rtUF zuF!xxx39YeRhE@7CmhqlOiLVBt7s(hc<eq**;bTeUJn^?bq7bkS!{3ru-19@{@hh~ zaRzxq{}V~dNDYgB8Dn77u;|ImkPG&gY-vpVQm(jxGB6IhC2!&84&kmu0Yq9Xj1&PE zCFt6LnAp%k%u4$i1X|*~8K5<xn!QtA*>M+cMV8}xQSX>eJCH#+C{P%d*S>z@F5En^ zZ2wNKl6<<x3{1MuhJqIwapz{qRVpihXk(?b^CR!F-9&!#3vqev8_%d+s$~KdTsF#* z;3X5YQDqSvuf^*Ow$whN*ef^;poXz34Ty|jrS(-gm!J+<2_R*I4-8Uq<{p%a!Xe@e zSv5l|zRM?@>^~ly+_$>BKU?nZzE|s=eCk9nN$Ffzb&&>DIR1OKz#LPi_!o|WanoIh zqnrhx0|knA>jJ*)wKf2Y{>i7L^|ZWJfvGwC<|+YKUzE5Mxoa>ME<)U8qTspoD<<2N zzY!j4OM;qoDbl24g-$4rl6~82Zdz^)q~kkwdA$*wJ@kXl9u3+%FXNU>#8_ud+=Ux# zxixTWx6ho)<|4H{pMld-E)hA$y2OCOBI=+4EKdK=1RK1QjI<HBa2rX*;m0f8y`TA) zon-EmR*2Vltz|joO@e`mTIej~HZ)qCohO+M9rDnR5!Wkp3C7xKx_c167az-*jM*g! z7Nwh*q3JHeNPb@4g&3&=7V-PbT8Bb)Iv_#o>2c6~R$|Z8jj;2KvTZV#!%xii*rJaL zpWB6KT7Zo~F4$`q`K_<5u|^)Wx52Mhw!OvywfzcaQ${8xT!mr>yA<(jsLT&8Ae^y0 z!Nx2$052~%l?}&T*xI(ex<xI|6~yg2`sEowF;H%BwVl8C#h|?U^&@}j%>~<EEL(Oq zT}V<oDHALxq6L!9bCm-x5jRo6D_4{?9eGQ+d>lRuT8|s;C;xtd4H7Q<h5+*%_>I?& zSR1P;?n4CHDwu0R4UK$d78^W=dY_>)th`4jR+y8CMVXiuavPf1l?0*)fs*PfR6s`& zYY4Td`kU%gonTo!uc#7qN!UNy3p%?&|LE6t@9<X@#<(4rSs*DYFlSs7wD;Naf02YJ zg=B<;9&DHk(F9&#@1)DzQVUv77`E6Yx3a#&Iu_(k#30819Rgx#Td%1oPtib4u9Hs7 zvb~yJUiJLu>kpjDdc-<-k3Fb7Mwaimu8{F-NC3%ZjMA;&C8gDOT9u7&@B7U!99lfi zg$vZ`9k`8LXsGgpK0uJ*6{n;7q?0tx;e^2}8axU+yS1~=|E77_>=v%it!@B-9a~;~ zEtAPx>Gk7k8@k$%WNt)p8`66~<@WGJ1tv3ik&)n41XN>!l@^=CEJ;Z;S7h&MOiQ2M zBy$bI@7o7f1)_`&xCwg)AJ)6O@0EIoAK78=kW9O4{tDSYu;p(^mc;x*0ER(KBs0UC z;SJv>#eRyJw6MDm1zam`?G{Xp(ijiK2*Kyc1L$}W8%y@H+IASo*wVDTYMBA*w%2^E z?N+y7(nE)RLCUB2xMHJ7%VVHby9EM;L3#Z<$EBq=4;cV2+wKCydYbz5Cqgn#WXCOH zU3el&3|<-QB19Ey<kg0Fs&~P>o-|sY|GOpwF>=|#ddsblg{>dJbSW+NBi5)CZs^Pl z2{ni%8G44{=st`IUPFPY0H7pD6}?|!V?)3SN#L^rx+cKFuVIkFF)i_0AanRq3H-yb zdk_qcq14j{&F<b$7kWn@xj|=#&ZN5c<r_%oe?)KtG0W(s(aNp=1n=vN%SqR6y1@3I zvgN-avoNvcISq1jO3QDDY_z;XZU2U>0Ff%=vd$Kdm{Z`<Ts?|OcNgr42OCMf@wW8u zBlj4{z6VD;QSSs|H*V10Ed|{@8|EMaG0LSrtn7M$Wm*0ru}e#lQ(hTZ-eTYO7TCJ6 z1ox1N8G=QptW0$xrT`g|*EH5W%)1Sh_*V*EgX2$^+B-j5fXN0g+aN*PU;JWR-TJ{c zwcJfYh0@Z^iB^gys}xL3#L5bNiHQx9xswE*%;%!>4RZJ_W|gjWafcXO$n6MymZ9)2 zj$cV&BwE|or$LDresxLM**{{>KsoF_bA#@_1@TNS%9`hHy-b5n=szb3h`|VZkS-=R zCP$Gv0~2dqMvv8qAff-4WDa0C<3Zofp2col+4gGttOKg5Obo@s(k*2>=Tej}<=B|$ zu*IlbY-g^;tb-SEGRtq2*ht=U7q+0T<AGgTjTqB`SQBLc0twoXG|gjBSuPbhAM4<q zsmmAkM>xB3{3Z4`R)|x*LHx#Rq1Sx9UuwS5Vx9AmSHIU}ulcsuD8u<U5m#{);y9Nj z+-51;5V0=Py;C|y-0=wz(-dbMyarsbF*v=?!0U$$ygncQ`Y+JD+HF!^`{prYqN{SC zle$Xr?~K7aOHQT;qcpH6VqiIUp@DtH88~Fe5u3`zv2<2d;09FKRmz&8EM{dYqN~QT zA|hRb$W<s}T8uphY!DKITd;rhn=)IR{^@-h^^XakL^i&DvEnH+nE&g)V+;7`p+T6E zn8jM!s8Uj5jivOk{m<F5GO<g!&c+@D_Mlj-123^od7pAhfqT_B?Qk;7yA+j;>?B<E zqX5XV4n1=-QrXdo&fg`rS1&u2H3nuk+1I>By~Zn#-Gv*0U0#js(lT*MOMGFV2XlF{ z!G9S*FkM&pwq=(RkYd|VMAt5@5VyQa7%0YWbu(r_88N`>`%7<~mR7!e>@R(J!E3x~ zvEvT=>=JulO^<B}CLtu^g2Bq%CInnE1}I~P`fri^ler8rYgOg)IM0i9dQXD0N6psb zzgxJhi79o1dt6@oM%Sy|NoGJ-UBpEGwkR&D63`5^)OZ)k07zeQu0jk<dH0_JP@1qp zy8ng%mEs=6U?tpvGKQ6{aefww)f5L3!oZ_T;z9c<V^kf+um-{DebPVvq}JR2Wx2cm zPcDctcwB3)B)n?&m@WS!NoHggjY)u*-Y})<E<{t@%SjjEMuZ%&|C%IogUzkL_%!y- zFOcfSclWK*(sI%fYg5=0ORg1{B2Ky`q_AV={~Bt*sUzWGt`^Kn((}>^AiRSR?~kI# zaoBZON0DgwlTWbXbemmM$Ure-%q?UH#}-JlNUy^Zoh=3)lB@|q*Nj}LS|tfAN|1SS zBiw~#2<yV5{3I@ttWyAc)Ia|15(BSl$VTURH`(P_W%GMMW$hb#tgUSF1N=T^iP}U| z*SCfwZKHT;@)_94B22r!BDbItN6a=dm!CMMyBPI$svegvaJij+ju>okQ{CrMfTOnU zhitG>3%f^t(0&54cPpt>(@8#Gg67<=sQp`#xDX)+47(4-A^=TkWWxYVfEa+505LCJ z{}*ie&n9^_(_j$8fC}m5wQrudwcAImgSnkeVUH!y`c&`Z$91xfS{%@T%oI1^r-~Q@ z1~mK`@(-YWd@OghXobIX)`8;c{h6C91Fz&HOFJ$uTmvrjCyVb#K3=G@tt`0DRfl2s zX?O4a)$YLuP6$b=FU2VFo3BH?!$Zb|7x8goVE{u4Og#rq-Zy;D($6Tg7;TxsDi2QD z`-(scw@LjqQ~N{hHb(henW%#u585Q`9UU<?<%gX;Kj<D<AajSA%psN)0mGUD)^)P~ zmM#Ak$-t{91~wAFbQUH63=28_HCz5mGQ%|y&tqQg4)K@2-0>Q(9a6h=BkP1LV<#ls zL-|g~&~c+qM(ub;1;L3n`ZaYp=(ERUA_{X%4xoJ<nAB~G8G`cqIKYL!XA$dCm1-ha zh)?z2-Z|_Yeq8S!{>qN}r(<2~Bn)DO)ti5G;#M~5YLYU^*cO`GEYH3u$qfR)M&(fq z5pRsFMJTq5KuT#(`fmisqI44vs*?9a;efHIGpKrV5Dm_}c+hgf!HLV*6$7j`1p!9b z|Nq-N(-_H;qmD=2eNFeAJC|mc^)9=xX4&x?0mgu|5+H*R0ts_S*c`Tz`2Yrq4+w$5 zAR#0!%aR4c28<07*kFVNwm<?Qe2LTYv0h_r_88C3Zck6oPEU7NSJy?v%a`@?<%`Uh zudc4Dj-M3s?yIWj{VO6PBVUSyOPE{e%lk#Nzk+N6$H)R^hY)H0D%u~Tou1q?Ef&!b z80Q{8sIA|>qx<vdO5$cEiOkqd?5}CRkg0zyuPmBnLOQ+|&ES~KMI0nA%aXay5-~HB zl@%=j9bU{luldZWTj9cmW|%3ddzXX^<MyuZ;q~s`jT6mVpI&d>e#&~@=~Ah0P`g)q z>Fi^hxOh3U!=N40Rjk;oc^H^=NU(wg$vSI$R-m%`rA5#9TO5d7Ix^6Y+OOh1S-tkn zez#Snuywb2tASot6}_u+w{-`kU)DlG)t!C<?NguxixV(5mEI*_WEDuagVhW>kM?e~ zM<@48iw8B#FP(X`yL9gHt#W<!d}_R^GWSBtc1&f3jKvVL!J;d~!-anU_!pYJ-gOUr zy9+N^db{au`Ocl(Lt41f;lixf6$@AtLZs#P^}{<)pWJ`p<Ex$hP0#aAgA)G0d91&B z`HAO&6R*K;NhW5t@`x3VH9li!1Ui~=ec$%qgId?m{HQU1ECHlM%2?JvE(Yi5cdw%u zQ5t&#y<QsYuHHSoQ|%tyE_V;MRBF43)p3A?>rH6%pU@XROr_8hwtz+JAe+cxhY=}- z{%xQ_i9!J=V~>W!SUP)kufFlJZ75Z*l#KR_bVY@vZEWDJutEk9{<1gM880HoODy}c za$)#fnP2;y7m)QNL!ZUn=GQY`kY*n*W7>;{MClydIN18+xr19zR#mdb(YBW~PCo*x z7hip|OeynuI0xrUC&)y*=F~>yL&K&=Zmvr&vjSx-5Bh#liKh1MCVEfHy~EvFud`pl z<qCxXyK%df+3{F;Vjb}Ry$K7%`_X;{R9`Stz(}}E53COb1π*)SrB+UwBX3i)G- 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 literal 9601 zcmXwfdpy(s_qc1(+~rd4m*~c*G;%4Ta+}^M)R;SknY&>wG51i(T@*D@u4S0HG?O%! zMD8}5<{C4SJHJ<--{bp-hx0t=d2Y`=U+4Ke=XsKELvQjQJ9&(ajg8;*mhl}nHg?Lt zKb|81MytrH1Ng@tc;}`OTUDR*0x;n6G_*8iW2?;o?KyJ;V_v^o_XFA31b~SG9qe7m zcW!KKr^HQ-4ez4h%TxMwzih%^(L5$*zDi~Ux#3=CogU9u`+EDk^9F}o4+nl;<6)1u z+fOK#FK_v~PpORBwXGHIly@KY3>Eti!-|sJrd&uCsgTF(blvve-6^@4ko}41gY~uk zt8IsjN5u!LhX?)C;p)dX2=SZiT}<i$$D0lC;l}zo8sF9P)oq06?N4p$)l^EG9%FFs z>b~;i)7({}>Cw_1Gi`5O5nulVRbKEUOTJ3~rdC#jzqos&5z5(LwT2!}sXPt3U@Z#A z1}fEY!20UtQ#A*L;0d9pJ}FKVktIQz9U|}qrBBy|ToO(Zf~+2v$&>i5qZZP?*9p1c z0SrwMXGDG*nPifS<po<___@X*jD18jzi3hKCQrO{6IE{JbLJN2QS9oSPW46?bACk& zEuy(57IyL!@%Sl%+bLYo4NNx_)lIayz&WaTzFqkgLD?1WejDrbTm%mHz`HAAo!}~j zLX}1fix?eCR9MQg+g1wO`Xw4eXi#mEVW5%73l8N~*Xupr<3J?eiv@v2ovK9Ot%{9L z3)$AiXpFRJqY#cBDcZ}zX(N9QJssN17t=<;9C~MHFP}^s1uC|;B{B6dp^Dx?(f06I z5JGbwYK&5K`WElYv&w}Db&3T|iA+D{WyWwxBq+3Vicmf`W`M4*u?=6tgbHEpC5sk9 zg@_Y6pW_#DqjXgXG9N4X1~2S~SfF&>zH!@dOe)ZppPZ3sB>oM&iD?kU+E23&B2_UB zoB+g&rGDW>c33pEn_{U6yvS0ErfVEn>JeU~D*(CBCnRZzq%#s<NyCBH4;l|l-Ah1j z$Cti!F*auK!?&L$H(Y>`uQ_9>-!Kh|*6O#PMYLs&dQ}+*J^tphT)ScaIJrRsnBZ#B z8qbR~#M;05Z)I29?R}Hb0QX7prRQZuAYlrJvBbRbhyUmWd6lzIhT}%|?O#Dwc0H5{ zdAR|P`<j>q$(6Hw#uAd(J~#qsc|vlsx<;-YnFLXwS>6hIY?zERA{HdgbHs^KjrHs| zN;c&G{JNk4veIk2g>rQI#;w9go|72$GB6E#oZN|@OP<TbY;p~7qeQ)5Z5f!Nik0zf zj>1$`UZ$wA#PnHKWbCRefC^1!8WJO7<~gJlTaWTEIZHsR$61lrP)s3*7;Dm|0IkI^ zh#juj3gKajm4KdNw%`f`31bQQGprVQf-ZoG-&)~engy}fiBPjv)r}=y@vs~$<lqC` zr@eNzpfUjP&Sh~6D)kTW8sCDR0Dy<Y7p#}+gc8vA7j~(Kdj||@k3!T8zqmt8$ezaF zknTOy2;^_#W*htWq;gzgTWU1=U~7NE@rFlg9`OKAh>AF@OKEHm`Y>3ajf~pfD=R+u zdoZGDDGVQAn0PAgl+V&@W!L*D5K-?38fbK)VD)@JdufeL+QTzPUDf(tXau1X1ppk) z#;J_Q`<Nj8{I@rke9(#f)$>qGr*n;>?uN4;1j#zP>pL47Hw2hgWH~wN;g|L*6B{R~ z0Hv=kJ;K+aE!Pu9KIwiUe^2pAB;~5|Nl<CTbNCM7aZ~%`TA`08&bD5Uw+%{^|3qlw zspYw7fllPDo)1CD!Co_@u+BEu^?bDMp!6+%3R<gyHTkW)lgdMrR`WxeohnX&go~h? ztYrg2$;74Fx2J`RZj7*&uK*+E+PA!ldIVqQ=3iO(X7EHUx2?~kbRNO-iSmXCfVsd4 z|5mq|2jq8%e}c@RiA`0IvK4q%HSnp(P+;VRn@&-?KE|z`<X4QxQzu+eQzL5Tet8LJ z8etBWEe`P`pI|ShFQ~Dv+<~x`RR|@pC(}f;nC8LG>_978eP?0LG?AZUGEE&zQ73wj z_nP_9<k?q@)>#O^)Pl5WqAABD0XwtXwb7s5`FXh=>aBY^THFBzFNF9XoOe8^Bhb+$ zY6DLm-ptGwV20m2Vf*5ZFo=}M_}Q5m{T;udm$LOG>{L5q+Zk~yFds0tv(SDJ2L7(= zZIhXg{?^1u9uW^biM2o7{%7KiN*P5(6>gi}og46)!`sd}A9l2$03}n?ouq!Zt{}>? zb;HCwKm2<|XQc<Mr&`qEosWbgtbaqRo-sLO^YPK9yMMs$nEk^i^_9H6e9D!U1nks0 zF78|ZtdVQTV0Cvxkq54@fR`C=GVdAE|7g}spb?5L_)f|W6s6VkuZs-CLz9rfmKH6Q z76e>wzp%snlPw$D$i+%oIIO2ESv|-M{XRv3*_F>iIMcP3i`EO~joL^J6&0|)dmY~6 z`fAW2E2bkyiwL!%0--ZfgtBbz|JcdLdB2~rwH@)T;_`+KsQS_7@qu{FPJ!(52Etiu zWY%ic;p-^$yiN%yu8v`EnN{8}O!7|M4wJ>kJ{}9G!fp-0^Z;wDlHWTCT7Ta1(aEtz zFIH-@@<WPUQ%|TklbiBOtdRr2PO+-(<P)uB*qiXhbsF3){X_B|k4#td$jwxnTT4Oc zNc>Otpt{A^A(-VewYle_)sG~mwXKn1CRmCh4e-aM;Dn0DVx`LN5X=kk&!GSE#cN?> zWt86lk)zO~xhegFH8MGQ<kPO+L{H4}Y<L~C<^Dg%dx41#V_3@MKanxA?gil=fqe&J zqkhu?>|~$6pS?VL{+|%#nQnl~2QrrrtPR@}`R$>CUD{bTxa-sMa{vrDn#V3_@uM8l zMOs?qi+QH}GCULbBth-3o?Et*tHK9;PmURAa>rN$!){=^TAtnRMZdZ_{8RI7;XjFw z?%Pg0cY|E$>bUpDx%tE^HPaBKOw35gEpc3dv&-;Lj~2b-Z}U-z#9z*f@nOK^KWn%D z1fU$U+}#)BdGFhHWxLRW?`7wdOQ_TpF|VqFm%N(c4|4SFe;QbWdpb1+TXTjPIYe*W zH<H$1i}w8Stih$V_oCJ3K3<?l?3Hd28;92?!~?HFty*yTyR09>N51RD^(aFVN3w5; z;R>Cv4G;Rhe30pwhjMtCB|$~P`ZS(ypmCj(W!AdS01l;A4W5CQgXODLM^h2c!buWW zRPi0C@aFB`B=3SI16DjVdF3`;AMMpVW)nyS$VzxU8Op}1+TKY4R=fa72MDAOT8~e$ z^y;V39`yR&Nm*8WLgkzX*XGPxT~xX%Ww2wS7nk3q(E9zALlAck#40{iKgQ=`gE{)O zLWyn`K<$SDzruuS>+So6(PZ-eWD{u@{SeT%o{QT$P;S3H9NyPdOtwtyXqL6W<&n37 z_t(YziCxLaop_^=0m~DU$CudMrN@1v{-<Ac5bP#CXH)1(&M7b59bs&2Zh{xC5jF)I z3J_WiXP}?7X?K$2KK}uo_%L@jy^>+a8Fe3ih}xCv&^}JtXs_$FhZa8cC%%1x)YvyN zYM#gPt;{y`NAD~kmqz%0&Hr1LnhnhE@;=fuFN$X3ze3{vz2;su|HWjp4A9imu}i?^ zPl&6+7t2Fkcj8qYxF93eWF)KyJLTJ{>Az{f3}t2X%e6)ClGT<TiyuAVj<$p1i)T`` zQktqNApRv?LHpg^eLlm1zrXi8s?)P!D}k){^ADn%|G4wF%{rq%YJ2GA_mIlz2>Esk z;6B&n8b0*X)E3no6gooxYV&coIja&0Pfx5=@7yI!9oBRA`q6XY*Kr83uJ>6Ry&ZRF zl3W*O)l(t}FYT<pN(;gql-l_iW_~a!6?(~d$K6aJO6xE5=wd#ZEmdRce#V$hoYac1 z`GulpnuDO-)wYSGSXf^Uj;$be;f8dn{|9wP+}@Lv`ShjP3^DzD)D#tA(YfWTUgpG( z)mic%TYJ&$?-*+DKk3lY@BO~%IzI_wf5Z;+h^UUQ6y*0mz*p-6SFR!TR=I3j5WBc@ zMT)C>dJBULFY!(dy94QHRxey(UOo>UXNX;8!XkF2>{DylGN-}|H)`q$wVisk5u(+* z%`ZnS+tf&*A5BJ&TU_?H=&Y?rk9E8tqpJ*@+~0=@rG0_e7sSTr=2UiBSy``8UOVPO zH|dRgv+U<+GGEnO>zTWhx{Ou#BsC+w<6Ye$J?EpB!@jlP<u4`{6#pd1j@D}5p-c&m z8o2P^w*pz)pZ<L{k346x?xCOEaI1c;>Dw9UEB%$7Q@gW~ttTl063YVwX-W;OCfxqR zUj=bmvOVVVlj9RD+F#@h#?BM|H%$zV4013fHlLU_S`0WT;G!3E?iqh+!r%PFIs68S z$FZfKIDvgb<8S8nePPl&9OP;I_blyY+-it?7z)C91m$|xH|eYFU%%$Da6K;C@-^J5 z?{K{TpWUqy7ilgXg=djtQ|nXVdYE0>OE*Jbhrg={Q?WV|iecxzN;y-SgopK?MW(H$ zGvB{V^QLX6jMyp@3X(a|<Wh~q6*^k}RfD$r8oqhT$5Qr7POt-N;_f9vL2CS%O9b_q zHZ|bvI2aQ+qmLNr4W#7j@zD_WS*t6{;$f-5;hv?-Es{BaWOBrBC8Z`Wooj#Fw%`5C zc=h`p>7w`#Psb7v=hj)m(hE`BpR2QGF(#7J+&Q91A2oc%tQ0K9@-tf*dOPhdE<Y^) zjO6qq&7oF(=`(~vk7EgLdr_2R9<XU{?1}HyOU0+qc?pumtIvnh&e&lVETyZXqYvwH zbHitH_6pEPRS9lN#GupZz1lZsQ2RIcQe$y)7kB@Q>H5pyyfh3M^Ssoc06j=-lW(?N zs>M-l);|k<nts2oPH)%~R`_u0fSD}#z(4BA%<}b1e`y;FrxE1`3-{jIV@4IP^Hc4A zKM?JnUe^Bj(;~QbwA18E`0HBgbDWx`uRpp4p~fr)<&kc)JVlYstxX)o1D%%$nqry6 zslcB6TPkvx2E?Eb<m!ysllWXueL_vW%Jbya%Y8!;?O8%dt~_&pXeXz+V6;}L)-#KL zc?TtvD-UGl@D)&LjM8;lwEPWy3L);*Yacm`2-{~eHkd5%l|O&N+$@zvY0H@MhF6@< zls}U5i1!;O4x_{Nm(~_pE^fQb$i{kfH(&shw%^gV?|J@`fnKQtmU2X2cs^TC?A1nF z?MOZC?fMN=_b+pljMK8HW6F}Y0k{}UG*^*B$K1H;LQm@VOXKCLgJsl@LYbyqBS&4n z*8%>3>E^;qn!^*~>z5f{%3dX_SMH>){=JFru2`Qxrrs+`F-QjMbaJ}37>^8Jm=dN$ zC4;*wyR5;R!3ed%Q?%uRt>+e=upV6YLiXxkD|FcXn*;Ur7Mqw*K05h*cw@D<cb)<d zQ=61yJ?x6z*u~?rPb93So1k?{8IJ&o9l{h=5;#%IV0oDG;NTO1et6`T8LCUURr=@R zCs?F+*go~ZJ*<<|5}m9GW|@nO|5;iyV?|iK2g5&kw`h1To6c%`SnZ!|yEDw3U?ATv zvTTj-B(LHY4%Mp<>j`%XzxIONhHAOq)UUTrfI81<io5uVP%d|d*xcGK%8lAC`TQvK z-Hdfo(@oUGaa!6@-8<kXSVzjGOUqDy3pmCOqpC_k?9`k$y6RbI#)S$<!-RfwkQDx6 z-OCS?c*Eo71;qT4rEX8JybE}RgMu8}y`-Ujvg4+eW!EN#ch5gB#FduzUVZ$<x+kwv zyD+twE7yo_m;Mz+aZ(@#?(|&_%%tG*N2(ht%O?eiq-5XSFcJUS`n~^l2pO&xeu8%W z+oS0?F@1ShuZ*2UMdQi7Ffjr(>N0!ggFT;}=(gx?CI7x^3F^dg9}d~1)ex67WcqGP z4p*);ozwZiH7u-ZnwLXQg|=+o-JdSz6p%DyhAs{|)vh?Hxrv(i^HZPF68yP>_4%m( z^X8*^NsYM=4&H@Pbc^73{ne&TG+8_R(lYlY$|pA?sx{d&e;l9!&cCdIfh^iyve-`| z@H#Btla1q3D%nn63l^?}ccjgWLZUwJM1>#2ivl4qk;c!jRs_RvH!6`?gFnKUl4jRp zcBg3->6M_nX^1&<|5#?jYlO-AhkeaoqdI4XF&iZ<@xhO!Fb#qHoe-bYGM+#i>Fe~} zk}3T30W!`<t6|~0%{m2tyB^=^CRJ;=q&c`~U7Ma~d@{PYOqqZPzd|U0iVvol)Ymak zkM<`sT(RSEw#}gNoB0OWp@%Xn`<JhwCW5XIUYDMm1S%<wvC#A^FIFU-_-m~sU~tHP zYG7RB5)CnEPUDw>(^C9yG|$lTWz(N6Z=DYJ(G#Y?)Z^^;N5!c-m<CI5V(8$4>F}h> z&rGRjGVZCG>1i&5$k<i57o8(8^vP&=6`<?Id4h9H=I;w6!zZEYF1kA|56-uqAT~G1 zjMQ`b?5J4)Gl1y)8G1nz&gb`Eu+6iF?gC7#S4?Mv9t~lgy_)b@VzJ!C=QCQA8HQVE zxQ&AR<oIoO)*Rqd1Nb-zp=w_W)JydSB0}%tw!{HmGk{kb;QhPmtKvvvn&@1T@JOAp zyzg@F455I|9)iwNARx|)Q+j`~w=(|A-K+zri+5h9>ziNRw*lB6#>jYX^CAmU5<`{R z%k)JTJ`z;Blv9yPv?=A5ca1mE>%jMO55&9?vta-HOl|Gz5td906-v72?^4a$l!DIx zM^cQ{8Ao18IjmZSM7!@ZAIz+bfPi|&WH7MrEUa+1)5NKY5@O!yK&nw*uVce%)C0J< zweL6wTG+XZPyh7gQ{py@B}Q^Tc9zu$RbHqs;xvZ2wMNT7LbiUZx15iTs@wa#K^$<$ zkvXGvD*Hubi3If%CP&%Vc+}J^wJxzD_P6N%Nk`pMULJ5aLKxn9iF>f(_v;GfO=D&h zk^|V8ayRVwaBPmrH9~_aldmfjCZeqi2g!1{LPua1rHHB;5L_g8c^r9bGjsC+gK-;$ z7h3_MJmGGwAZyKbP@~@D-HnOxzA$zqd7GPP9=yu~(#&!%IDW1>VB&1w7()=&*PqH9 zDQoEWI@n`H6qCn_P;n_}74M~C*T87T%6e(HT3_2Jbia(TAqN}+*$B1!eU`=RhlLIO zv_lcfbiruhV%z!GCbmY>q+IDJ2l(N^f%`}uX{7z{CE9XK`Q|GrO@UZGW<aMcBwdsO z%?)?-Q~hfABF}Pd!%e@-qNR%mscO-Zr<W8U<Z;7wvC<r+>#|rmeJvNvtEGQSmbziD zxnZC}kkQ--nKphGXJ>sqUCnd9kx9N1bpmdywk$$rzi0n}jv9~Nc?ybY$_*#)2Gp+q zJo@khGB$(5+giG(A8PGKM%ME9R5O@4au4Wn=3m5@On(R-8_1YJ2i$W^*kbU0QWfuw zOq!|R^VXCtNZmFAPMq%QJ9&3V7Y)2O)isNZ7E{BDY3P`LXU@^u)<l0~{ijh6gJ2~1 zfa-wRa&CY0;756=eid-)$`c|*sG6i$38uSgB6do3XbUN?)=;yyyGUpjhr_rdg&g~| z^_Srl+O?TB0Mly)>dqozSA~E(hF5IW>-a=g>&C+Q!+LB25C4~)=sAruvWKj}p*&1U z`kQ1yXN~<kDUWg@d>~W>g6kO8k;S;Pi17I0+N_g->}!G1g6n^PS&WktJ$f$xANBqp zszt4JW%qd;!UOfVa`y5*{$M-BPI77!ahFy;aQWiRw|jYT-7=tjw&kPWQ2uM^VL@Ig zJ=dac%4hD+Wgyg0b8Nqz>r`vS6@_IXJhWF9eFs}s$5r0V$&t?)JTVAHBlI~lws^XO zs#Y#f@l_*Y@fV^FE!zs~WWl(+t{k7{yd@wms*O~|EriP2HsGOky*(d)7XoEEepnf0 z2-X*YrLs;%)Eq0)+U{HEO{LrfjJ|XK^_K5oH5=y}|MikhUzBRwgGV<WZyjM*ciX4n zZOzDfPFc??XQ9UlJR;M3_-5Nh7w0Sf=<xQlz&*vhyE2;Hqd)GR-(>G@Gwl{?Y7Sxi zT5ZmpW3swg_3G2!sl=0V2_umd+}#wU(DVVSBu`wDqCV=ia=UXY;a_Uvmq}ZO-RxLO zd8?T<BlRyIVULpVUYmmHB@zLoB|j<D_1F>A=Bju`<!qnUajGBUn7DI{3{9r}f62zV z1}@{Rl#MoOf5#6_xe^v3aiWKow~Ftds7Ye3?SyUDWi5nSVWQ{eoEJ=_%M6TXbP}VU zPy3-p4*MtP5R<+;hz&(k%;WMgFWK8iSyPiKHA0*n;g<=x0^QnFr1@qfbL^&F(qjtg z%I@lcFX}8n*cD-pta)v;GL<rn&VRXPE)adEuyU3+NP8b1c)~?9fgoVzr#EMTLO?>_ ztuJh5muiZ;p^Ex<3|7ihUzBw;w+aKowZ8s|=&zTgf&vIB#=zx<b&;S^RHlBX_5Vto z=_#FF+e+dW*S2b)W%nx;l8OgYC5>x5#3bZ5)m5&(ZCtDNqbTd97p_~I1?+uT>d7E` z|I(T*XZ{>p?ORRzP<{&q;n@3Ls^N$~=|y{cv7D&8Q!BdE0UqhwhC$;Y+QJ{to}{F) zsRo`KdfQfQ1?FkcrUFN2m)?Hl+TDweB#su@Yf4m3bp`rKP%q>CpH7QC=JwaC4|~qh zHiFh)RdLy2*jvSx=z~`Sg0B#2wZYLSe3%m~1`NC?urZ<gU$ZJb1jZBz=tswjGG(fo z3iE%d1nxxq@j|_Pk~#Lj7~eikk90h!>Ch6CD`Jf<FbB+O!58|}D2I$-9d3|6Z0|x~ z#r+I@xs!nkoi-oKLjzzj4I^vSJ~taWGJA)Pcfl9Rw#}1*m;c!8k!firq1zopWQkoN zaI_fBzwv8PhxvbbBy$)`wo386uCO@0?*k|;^*5`jErLpxP$?%anQ2!lFv-hG($x${ zUd`_XIbe(Dn9%*J#4Nun){{A@GajjagzV^3q%c%_3SG}G81}8w!x{|M5pvNqP-Vmy zuA_Ze6fC|?mmYV|wDoE?Q7UzIEwLQ(a-r-6S*Xi<d&I^5KY}V*u5aBRQ0w@dnUVN@ zoY8fyDk;u@gAlEDxbxsiL(b_o^DqU^MMjljLtf_ck-~%en2`@upSCi{OH^s*-h+nw zm0iCoLAhg5`>(0}Wc}{p0>g|9w>hgHt4CDb0`5gYn0&IvI#0wsXbjn|u1L$ok)Z;h zRQR0JSwO#2he2G_lQaJj?9slevVWi~h?M-}Ri<K%EEOO%z14u(H+7nfMVgLG+tY%z z3IR522+{uH7jZlFCi!OY_D;bmf^$1E3PIORa(Lp+HL6HHCE>bxit699J!2g8OTJS3 zX|%nd&lT|jnANW>&38ZMZb{$FSb<&b2%|Khn9ui;dd{X-J}W8M*<DwGp+?SmU42-b zy(Qp=2_%J4^yPB>9kZDkg5T#ew$>_!ic>rmm#u$1^3})E|8}3QvuIWbMZJ6)mc-Qd zDpWFUD6njqGSlx(!}S&-AoAV->3%Yk?QD?}meQ0L!X#f_P7s0hLcgL(IUfToTC`-v zXQT&LBJVRJJq4@FA<b&7S9$#!yc`NuUWUJ1n$lRN<OGlNzm=`JurVvfN-KVQX+Pwh zOU-j=%elGm33`j^ei$2Y#)$D&pQE|p7HrWvXS;^H$xW8sr9cg{Y=|Uf&zqyr8d(t> zrCc2D@DOsYOWAW=|Fs>k%}tV#o@a~Y%8UI3)IUu;M?b%~Tx1(S=ZAg?Amx+>ShnB_ zCxn=W>HT4l<su+Chkl{sw!-O6f?8mUOskf@O%=D~sz;kzZ&l&2K86WepEddT+uV({ za!3;L-u;s13(KvqP$*kmA$bBgqYx9ft&u|1vvnpwtrdS3DD)fw%Cxv5DckMIuOa>_ zttO20m4|J}hWSrYUY3N<L$I5L8n~hfoAt@08Kv#)Kq0DPok^(HxOgDCEd|>A`T=T$ zvttO>e|ai5N@}pZ>6trOCkbhDzqDCaR0*M(NFVflr^5o2IP5Oi#!TFaSnS0qDYo8! zk+kv<*2B_2XRw@50hl3Tjf~u7OGR>?)P|56<N*GY{;4{fo4>K+qDYMmoGZ-7vJLSr z(wD?~%7_A7P_8@b<^b~1RK2TDfVR&5E^}nBdZ-WaV)D2*Q>8-Y4ig#f{3vMo{J0DB zck@=)Tn4pm|1H+;SI$+J&t+=<PkmDl%kpiLFM7Gr$gS;(h7#M-xWEfb+uEzUEa&ak zeL|Fj2?*9J1?xkMt2kNvaW4@cL@cJc%J1$UZnXVe8Ht=Q`QBd=VTL}e02U%|^>SUi zA649$UHyH0>W?3+`i;H%EjVfO%@C(yslJ66E;U<(+SZ&u<|V-C=$>{~;MZ>$obXbr zax&!%&a$EEsBjFb#&cNf^_I@%)hqUqsbPb6QE^)#k6_ZVJf7dbhy924%0*eOwm{3a zHrI+~iBj1RYMjl!2+iW;6|fh#bM`{0ZemK&)i}rLOBa_DY&vw8#8w_woR=^4`b~Iu z!SFYS^o>-%`gQkJt%h&4;Z~V6e!A`pgdwlu9tUfGN}Z^Gv~j0N0ngBqqSQzW;R?Hy zh~R&$tu@OW(&0&d7w6ii2ly=!n#c!&%=x38zyE@63(+}G>Va{awL7~bewc#h=|ZQ- z%BcTP=7ayFXU&S2*=U8sFV%l{FxGy-$2zbRu$1X9f!VIp_{oMY<S+wEu)W&DKZJ)N zpPNPKEf@c5)w{8ouLlpM#uYJB<pzF<uVaBCQ&jL5*Aelia@Wb#uz^3H{Cne0Q-Azq zK?bgj@3BTqfOQJ!bbnF?s@)2}%;Vk`tR^s~YZ*QIISLd$nO_P0smuGh;hrT}t;UN! zYRG(w3+0bE+#2i&5C!@`gGg@Cs+=ilPltF&5}0@D*cx2Ie?_?!OEofXKupC$&L7QE z^EA^DnQQTKUT~{j_ImugH=M8MQ@u1^7aPuBU7lm3FSR%nZs*s)e6|32W*XPLKyO`v z=KzZUni8GnQgH+|PZ};<CK#ffc^uS>>Macz3BDNxRUJ~<asB)7`Qn95`OlGq8=d1k zG_4JyHrLjX&--Sfo57oSL8OqQWS{u7MlgnZQ_8`r**OWwqvD+{A>2$CDXz`Xshbc! zQQ#;0u(C;POel)&{0mgvrEY+#%Oz$tj4_>m&p1#=rlijPQ+~z4#Lma+gvU64$3I^! zyya-O^+^)1`CWDkUzbQF@Bzx4{7=v=*W?{}f8Ti6FQtxm9tD=uc(q2%ueVaZ&B$r+ z<ORI*pVj<u_MJ;T9d~npCvcrN8)geR3^7|M+aHDD-@s#4y5~G#CHMTd8dy&OtwV|= z@e0&&6JjAuk#A+QEKtNd=l`QW{Bdc3-};{^*Q~KAzPaiI=g2tgLq0jS{yaeQx78~8 zf5Fz^Ycke1AQn<Uh-16Af7vytqerxQi(^RGd!<J1DeLRdzjeMn^a)V4@IO^a3#C$+ z&{M5!_mfx5P@yCVdC&k*kOI*`PhZT#g(&9$4DUdT5ejfOCMRiypBRz2#Zjn;2d?*= zbl_Ec0C?3F1ZdZdH`!?Vx6te>B2#ggO<sVOW+!;{Ja|BV{{Ny%yz2p@E$U#7ks+5e z;z&8*ZZEWGQq4tKjas()XL;^$dnn^o^H7d%JKQSDc(4Ym^&w<GAN}}5(n=}@!p`DW zQNZ)^AQkB#K4_4rFa<-BkbE((@{3l%H+;0}fIx_{gUjMM!o@!CBf=s@rh+gKB`ZBn znTre$m-jkKu>Nah#t<c1uLb&XUy`o}5f-z>A(OPx?Wry?EsHb+SWmitQ@e#axB6Y| zyU>6dkkfGEiXI;seNaUr!Jbumz7nzaH6c#00g?<$U_i&gk<suY$7<>)c|?)dee^kj zo&zaG0)O2GpRzJOO_*{cFY2*`iQj%~9MBRmW6{>mi=718B#r`*A$C%L2YC~KWU!M^ z9;7D#aa;R(Vj8Tmj!w?smY=SM^!x!0USI&77)PCU;vey>u>EV;aFi@UFbsK^PF#`+ zuHR&ytO}ULypD*~Jx!A-+->`QYT_HGD1|pS>a`Shl9z{R%q5wBsu5T$US5q$*ZowL z38JD-?{4_1st|ZIUMMy4fZIf!zTWWLHbQMDuwB0pONL-_3b-VpqV~Qba9I&2Z41A! zTPPii7@Epwh6auCh=b3ON8L&k4xIp=yfr?K0-sh+8SlR;bY9FtwlXH?4Oi~xTwl|F zO)-}*y}UT}JZ<&)OP)dAK{XQ|u*p~oR@24YpJ*<IbvpYo>t8!fu%d+m7M3qhxMYGc zeJD3BkGo`udNgc;i4$5+D$kzf1y5=42gZ&55Tm^{$I{BjjIiEBvJeLK(1?5+lcT`v zKZ41A%?q|}6rsz1*>JdVnMdt9&=Pd&OK0-=^B9Q`Uy?S@n*Rugl(D!t5p;BtHy~H@ zTLb+hm-vbh95oPknz<n~A7MBpYOBo<jNXz<!<y9<**ysY+NRh{O`yhAMozK+5AD=^ A6#xJL diff --git a/src/main/resources/res/img/logo_old.ico b/src/main/resources/res/img/logo_old.ico new file mode 100644 index 0000000000000000000000000000000000000000..2d48e9d7c64b1e89bb199cbf2ae3fdfb66509aca GIT binary patch literal 67646 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/res/img/logo_old.png b/src/main/resources/res/img/logo_old.png new file mode 100644 index 0000000000000000000000000000000000000000..830c0f66ea1a3e866dbceb35ca3f413554addc66 GIT binary patch literal 9601 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 A6#xJL literal 0 HcmV?d00001 diff --git a/src/main/resources/res/languages/ita.json b/src/main/resources/res/languages/ita.json index 9ae3e842..7238200a 100644 --- a/src/main/resources/res/languages/ita.json +++ b/src/main/resources/res/languages/ita.json @@ -30,6 +30,7 @@ "ErrorMessageForPathNotExisting": "Uno o entrambi i percorsi non esistono!", "ExceptionMessageReportMessage": "Si prega di segnalare questo errore, allegando un'immagine dello schermo o copiando il seguente testo dell'errore (è apprezzato fornire una descrizione delle operazioni eseguite prima dell'errore):", "ErrorGenericTitle": "Errore", + "ErrorMessageForSamePaths": "Il percorso iniziale e il percorso di destinazione non possono essere identici. Scegli percorsi diversi!", "SettedEveryMessage": "\nImpostato ogni", "WarningBackupAlreadyInProgressMessage": "È già in corso un backup. Non è possibile eseguire backup in parallelo.", "WarningShortTimeIntervalMessage": "L'intervallo di tempo selezionato è molto breve. Per un funzionamento ottimale, consigliamo di impostarlo ad almeno un'ora. Vuoi comunque procedere?",