added database dirty state monitor
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
2669fb00f1
commit
2e4e0d3f77
@ -84,6 +84,8 @@ public class KeepassApp extends JFrame {
|
|||||||
private static final int MAX_RECENT_FILES = 5;
|
private static final int MAX_RECENT_FILES = 5;
|
||||||
private File currentFile;
|
private File currentFile;
|
||||||
private String currentPassword;
|
private String currentPassword;
|
||||||
|
private boolean hasUnsavedChanges;
|
||||||
|
private final List<String> changedItems = new ArrayList<>();
|
||||||
|
|
||||||
// Menu items that need state management
|
// Menu items that need state management
|
||||||
private JMenuItem menuSave;
|
private JMenuItem menuSave;
|
||||||
@ -117,9 +119,7 @@ public class KeepassApp extends JFrame {
|
|||||||
addWindowListener(new WindowAdapter() {
|
addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
saveWindowState();
|
requestCloseApplication();
|
||||||
saveComponentState();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ public class KeepassApp extends JFrame {
|
|||||||
menuChangeMasterPassword.addActionListener(e -> changeMasterPassword());
|
menuChangeMasterPassword.addActionListener(e -> changeMasterPassword());
|
||||||
minimizeItem.addActionListener(e -> minimizeToCustomTray());
|
minimizeItem.addActionListener(e -> minimizeToCustomTray());
|
||||||
menuLock.addActionListener(e -> lockDatabase());
|
menuLock.addActionListener(e -> lockDatabase());
|
||||||
exitItem.addActionListener(e -> System.exit(0));
|
exitItem.addActionListener(e -> requestCloseApplication());
|
||||||
|
|
||||||
fileMenu.add(openItem);
|
fileMenu.add(openItem);
|
||||||
fileMenu.add(recentMenu);
|
fileMenu.add(recentMenu);
|
||||||
@ -464,7 +464,7 @@ public class KeepassApp extends JFrame {
|
|||||||
boolean hasEntrySelected = entryTable != null && entryTable.getSelectedRow() >= 0;
|
boolean hasEntrySelected = entryTable != null && entryTable.getSelectedRow() >= 0;
|
||||||
|
|
||||||
// File menu items
|
// File menu items
|
||||||
menuSave.setEnabled(dbIsLoaded);
|
menuSave.setEnabled(dbIsLoaded && hasUnsavedChanges);
|
||||||
menuChangeMasterPassword.setEnabled(dbIsLoaded);
|
menuChangeMasterPassword.setEnabled(dbIsLoaded);
|
||||||
menuLock.setEnabled(dbIsLoaded);
|
menuLock.setEnabled(dbIsLoaded);
|
||||||
|
|
||||||
@ -554,6 +554,7 @@ public class KeepassApp extends JFrame {
|
|||||||
this.database = CustomDatabaseFormat.convertJkpToDatabase(selectedFile, password);
|
this.database = CustomDatabaseFormat.convertJkpToDatabase(selectedFile, password);
|
||||||
this.currentFile = selectedFile;
|
this.currentFile = selectedFile;
|
||||||
this.currentPassword = password;
|
this.currentPassword = password;
|
||||||
|
setUnsavedChanges(false);
|
||||||
updateTree(this.database.getRootGroup());
|
updateTree(this.database.getRootGroup());
|
||||||
saveRecentFile(selectedFile.getAbsolutePath());
|
saveRecentFile(selectedFile.getAbsolutePath());
|
||||||
statusLabel.setText(" Database loaded: " + selectedFile.getName());
|
statusLabel.setText(" Database loaded: " + selectedFile.getName());
|
||||||
@ -584,21 +585,102 @@ public class KeepassApp extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveDatabase() {
|
private boolean saveDatabase() {
|
||||||
if (database == null || currentFile == null || currentPassword == null) {
|
if (database == null || currentFile == null || currentPassword == null) {
|
||||||
JOptionPane.showMessageDialog(this, "No database open to save.", "Warning", JOptionPane.WARNING_MESSAGE);
|
JOptionPane.showMessageDialog(this, "No database open to save.", "Warning", JOptionPane.WARNING_MESSAGE);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Save as custom JKP format
|
// Save as custom JKP format
|
||||||
CustomDatabaseFormat.saveDatabase(currentFile, database, currentPassword);
|
CustomDatabaseFormat.saveDatabase(currentFile, database, currentPassword);
|
||||||
|
setUnsavedChanges(false);
|
||||||
statusLabel.setText(" Database saved successfully.");
|
statusLabel.setText(" Database saved successfully.");
|
||||||
|
return true;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
JOptionPane.showMessageDialog(this, "Error saving database: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(this, "Error saving database: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void requestCloseApplication() {
|
||||||
|
if (!confirmSaveIfNeeded()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveWindowState();
|
||||||
|
saveComponentState();
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean confirmSaveIfNeeded() {
|
||||||
|
if (!hasUnsavedChanges) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
JTextArea changedItemsArea = new JTextArea(buildChangedItemsText());
|
||||||
|
changedItemsArea.setEditable(false);
|
||||||
|
changedItemsArea.setLineWrap(false);
|
||||||
|
changedItemsArea.setRows(8);
|
||||||
|
|
||||||
|
JScrollPane changedItemsScroll = new JScrollPane(changedItemsArea);
|
||||||
|
changedItemsScroll.setPreferredSize(new Dimension(420, 160));
|
||||||
|
changedItemsScroll.setBorder(BorderFactory.createTitledBorder("Changed items"));
|
||||||
|
|
||||||
|
JPanel panel = new JPanel(new BorderLayout(0, 8));
|
||||||
|
panel.add(new JLabel("Database has unsaved changes. Save before closing?"), BorderLayout.NORTH);
|
||||||
|
panel.add(changedItemsScroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
int choice = JOptionPane.showConfirmDialog(
|
||||||
|
this,
|
||||||
|
panel,
|
||||||
|
"Unsaved Changes",
|
||||||
|
JOptionPane.YES_NO_CANCEL_OPTION,
|
||||||
|
JOptionPane.WARNING_MESSAGE
|
||||||
|
);
|
||||||
|
|
||||||
|
if (choice == JOptionPane.YES_OPTION) {
|
||||||
|
return saveDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (choice == JOptionPane.NO_OPTION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUnsavedChanges(boolean hasUnsavedChanges) {
|
||||||
|
this.hasUnsavedChanges = hasUnsavedChanges;
|
||||||
|
if (!hasUnsavedChanges) {
|
||||||
|
changedItems.clear();
|
||||||
|
}
|
||||||
|
updateMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markDataChanged(String description) {
|
||||||
|
this.hasUnsavedChanges = true;
|
||||||
|
changedItems.add(description);
|
||||||
|
updateMenuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildChangedItemsText() {
|
||||||
|
if (changedItems.isEmpty()) {
|
||||||
|
return "No item-level details available.";
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int maxItems = 25;
|
||||||
|
int limit = Math.min(changedItems.size(), maxItems);
|
||||||
|
for (int i = 0; i < limit; i++) {
|
||||||
|
sb.append(i + 1).append(". ").append(changedItems.get(i)).append('\n');
|
||||||
|
}
|
||||||
|
if (changedItems.size() > maxItems) {
|
||||||
|
sb.append("... and ").append(changedItems.size() - maxItems).append(" more change(s)");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private void changeMasterPassword() {
|
private void changeMasterPassword() {
|
||||||
if (database == null || currentFile == null || currentPassword == null) {
|
if (database == null || currentFile == null || currentPassword == null) {
|
||||||
JOptionPane.showMessageDialog(this, "Database must be unlocked to change master password.", "Warning", JOptionPane.WARNING_MESSAGE);
|
JOptionPane.showMessageDialog(this, "Database must be unlocked to change master password.", "Warning", JOptionPane.WARNING_MESSAGE);
|
||||||
@ -656,6 +738,7 @@ public class KeepassApp extends JFrame {
|
|||||||
try {
|
try {
|
||||||
CustomDatabaseFormat.saveDatabase(currentFile, database, newPassword);
|
CustomDatabaseFormat.saveDatabase(currentFile, database, newPassword);
|
||||||
currentPassword = newPassword;
|
currentPassword = newPassword;
|
||||||
|
setUnsavedChanges(false);
|
||||||
statusLabel.setText(" Master password changed successfully.");
|
statusLabel.setText(" Master password changed successfully.");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
JOptionPane.showMessageDialog(this, "Error changing master password: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(this, "Error changing master password: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
@ -697,6 +780,7 @@ public class KeepassApp extends JFrame {
|
|||||||
Entry entry = currentGroup.addEntry(database.newEntry());
|
Entry entry = currentGroup.addEntry(database.newEntry());
|
||||||
updateEntryFromDialog(entry, dialog);
|
updateEntryFromDialog(entry, dialog);
|
||||||
displayEntriesInGroup(currentGroup);
|
displayEntriesInGroup(currentGroup);
|
||||||
|
markDataChanged("Entry added: " + safeItemName(entry.getTitle()));
|
||||||
statusLabel.setText(" Entry added. Remember to Save.");
|
statusLabel.setText(" Entry added. Remember to Save.");
|
||||||
updateMenuState();
|
updateMenuState();
|
||||||
}
|
}
|
||||||
@ -709,8 +793,10 @@ public class KeepassApp extends JFrame {
|
|||||||
Entry entry = currentEntries.get(modelRow);
|
Entry entry = currentEntries.get(modelRow);
|
||||||
EntryDialog dialog = new EntryDialog(this, "Edit Entry", entry);
|
EntryDialog dialog = new EntryDialog(this, "Edit Entry", entry);
|
||||||
if (dialog.showDialog()) {
|
if (dialog.showDialog()) {
|
||||||
|
String oldTitle = entry.getTitle();
|
||||||
updateEntryFromDialog(entry, dialog);
|
updateEntryFromDialog(entry, dialog);
|
||||||
displayEntriesInGroup(currentGroup);
|
displayEntriesInGroup(currentGroup);
|
||||||
|
markDataChanged("Entry edited: " + safeItemName(oldTitle) + " -> " + safeItemName(entry.getTitle()));
|
||||||
statusLabel.setText(" Entry updated. Remember to Save.");
|
statusLabel.setText(" Entry updated. Remember to Save.");
|
||||||
updateMenuState();
|
updateMenuState();
|
||||||
}
|
}
|
||||||
@ -726,8 +812,10 @@ public class KeepassApp extends JFrame {
|
|||||||
if (confirm == JOptionPane.YES_OPTION) {
|
if (confirm == JOptionPane.YES_OPTION) {
|
||||||
int modelRow = entryTable.convertRowIndexToModel(selectedRow);
|
int modelRow = entryTable.convertRowIndexToModel(selectedRow);
|
||||||
Entry entry = currentEntries.get(modelRow);
|
Entry entry = currentEntries.get(modelRow);
|
||||||
|
String entryTitle = entry.getTitle();
|
||||||
currentGroup.removeEntry(entry);
|
currentGroup.removeEntry(entry);
|
||||||
displayEntriesInGroup(currentGroup);
|
displayEntriesInGroup(currentGroup);
|
||||||
|
markDataChanged("Entry deleted: " + safeItemName(entryTitle));
|
||||||
statusLabel.setText(" Entry deleted. Remember to Save.");
|
statusLabel.setText(" Entry deleted. Remember to Save.");
|
||||||
updateMenuState();
|
updateMenuState();
|
||||||
}
|
}
|
||||||
@ -749,6 +837,7 @@ public class KeepassApp extends JFrame {
|
|||||||
if (name != null && !name.trim().isEmpty()) {
|
if (name != null && !name.trim().isEmpty()) {
|
||||||
parentGroup.addGroup(database.newGroup(name.trim()));
|
parentGroup.addGroup(database.newGroup(name.trim()));
|
||||||
updateTree(database.getRootGroup());
|
updateTree(database.getRootGroup());
|
||||||
|
markDataChanged("Category added: " + safeItemName(name.trim()));
|
||||||
statusLabel.setText(" Category added. Remember to Save.");
|
statusLabel.setText(" Category added. Remember to Save.");
|
||||||
updateMenuState();
|
updateMenuState();
|
||||||
}
|
}
|
||||||
@ -785,11 +874,19 @@ public class KeepassApp extends JFrame {
|
|||||||
Group parentGroup = ((GroupWrapper) parentNode.getUserObject()).getGroup();
|
Group parentGroup = ((GroupWrapper) parentNode.getUserObject()).getGroup();
|
||||||
parentGroup.removeGroup(group);
|
parentGroup.removeGroup(group);
|
||||||
updateTree(database.getRootGroup());
|
updateTree(database.getRootGroup());
|
||||||
|
markDataChanged("Category deleted: " + safeItemName(group.getName()));
|
||||||
statusLabel.setText(" Category deleted. Remember to Save.");
|
statusLabel.setText(" Category deleted. Remember to Save.");
|
||||||
updateMenuState();
|
updateMenuState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String safeItemName(String value) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
return "(unnamed)";
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
private void displayEntriesInGroup(Group group) {
|
private void displayEntriesInGroup(Group group) {
|
||||||
this.currentGroup = group;
|
this.currentGroup = group;
|
||||||
tableModel.setRowCount(0);
|
tableModel.setRowCount(0);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user