diff options
| author | 2020-06-03 20:16:10 +0200 | |
|---|---|---|
| committer | 2020-06-03 19:16:10 +0100 | |
| commit | 5a286d58e740f1aa5944488c602f5abc1318f6ca (patch) | |
| tree | dfde9eff0c744906b3571390af0f6a6e3be92a91 | |
| parent | Refactor MenuBar (#251) (diff) | |
| download | enigma-fork-5a286d58e740f1aa5944488c602f5abc1318f6ca.tar.gz enigma-fork-5a286d58e740f1aa5944488c602f5abc1318f6ca.tar.xz enigma-fork-5a286d58e740f1aa5944488c602f5abc1318f6ca.zip | |
Editor tabs (#238)
* Split into modules
* Add validation utils from patch-1 branch
* Tabs, iteration 1
* Delete RefreshMode
* Load initial code asynchronously
* Formatting
* Don't do anything when close() gets called multiple times
* Add scroll pane to editor
* Fix getActiveEditor()
* Rename components to more descriptive editorScrollPanes
* Move ClassHandle and related types out of gui package
* Fix tab title bar and other files not updating when changing mappings
* Fix compilation errors
* Start adding renaming functionality to new panel
* Scale validation error marker
* Make most user input validation use ValidationContext
* Fix line numbers not displaying
* Move CodeReader.navigateToToken into PanelEditor
* Add close button on tabs
* Remove TODO, it's fast enough
* Remove JS script action for 2 seconds faster startup
* Add comment on why the action is removed
* ClassHandle/ClassHandleProvider documentation
* Fix language file formatting
* Bulk tab closing operations
* Fix crash when renaming class and not connected to server
* Fix caret jumping to the end of the file when opening
* Increase identifier panel size
* Make popup menu text translatable
* Fix formatting
* Fix compilation issues
* CovertTextField -> ConvertingTextField
* Retain formatting using spaces
* Add de_de.json
* Better decompilation error handling
* Fix some caret related NPEs
* Localization
* Close editor on classhandle delete & fix onInvalidate not running on the Swing thread
* Fix crash when trying to close a tab from onDeleted class handle listener
Co-authored-by: Runemoro <runemoro1@gmail.com>
63 files changed, 3546 insertions, 1109 deletions
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java index 720744b..1b0191b 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java | |||
| @@ -5,19 +5,20 @@ import cuchaz.enigma.translation.mapping.EntryMapping; | |||
| 5 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 5 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 6 | import cuchaz.enigma.network.packet.Packet; | 6 | import cuchaz.enigma.network.packet.Packet; |
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | 7 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 8 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 8 | 9 | ||
| 9 | import java.util.List; | 10 | import java.util.List; |
| 10 | 11 | ||
| 11 | public interface ClientPacketHandler { | 12 | public interface ClientPacketHandler { |
| 12 | void openMappings(EntryTree<EntryMapping> mappings); | 13 | void openMappings(EntryTree<EntryMapping> mappings); |
| 13 | 14 | ||
| 14 | void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference); | 15 | void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree); |
| 15 | 16 | ||
| 16 | void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference); | 17 | void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference); |
| 17 | 18 | ||
| 18 | void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference); | 19 | void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs); |
| 19 | 20 | ||
| 20 | void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference); | 21 | void markAsDeobfuscated(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference); |
| 21 | 22 | ||
| 22 | void disconnectIfConnected(String reason); | 23 | void disconnectIfConnected(String reason); |
| 23 | 24 | ||
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java index 1b52cf1..23ffe99 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java | |||
| @@ -1,15 +1,17 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | 7 | import cuchaz.enigma.translation.mapping.EntryMapping; |
| 4 | import cuchaz.enigma.network.EnigmaServer; | 8 | import cuchaz.enigma.network.EnigmaServer; |
| 5 | import cuchaz.enigma.network.Message; | 9 | import cuchaz.enigma.network.Message; |
| 6 | import cuchaz.enigma.network.ServerPacketHandler; | 10 | import cuchaz.enigma.network.ServerPacketHandler; |
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | 11 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 8 | import cuchaz.enigma.utils.Utils; | 12 | import cuchaz.enigma.utils.Utils; |
| 9 | 13 | import cuchaz.enigma.utils.validation.PrintValidatable; | |
| 10 | import java.io.DataInput; | 14 | import cuchaz.enigma.utils.validation.ValidationContext; |
| 11 | import java.io.DataOutput; | ||
| 12 | import java.io.IOException; | ||
| 13 | 15 | ||
| 14 | public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { | 16 | public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { |
| 15 | private Entry<?> entry; | 17 | private Entry<?> entry; |
| @@ -37,6 +39,9 @@ public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { | |||
| 37 | 39 | ||
| 38 | @Override | 40 | @Override |
| 39 | public void handle(ServerPacketHandler handler) { | 41 | public void handle(ServerPacketHandler handler) { |
| 42 | ValidationContext vc = new ValidationContext(); | ||
| 43 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 44 | |||
| 40 | EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry); | 45 | EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry); |
| 41 | 46 | ||
| 42 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | 47 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); |
| @@ -49,11 +54,12 @@ public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> { | |||
| 49 | if (mapping == null) { | 54 | if (mapping == null) { |
| 50 | mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()); | 55 | mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()); |
| 51 | } | 56 | } |
| 52 | handler.getServer().getMappings().mapFromObf(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); | 57 | handler.getServer().getMappings().mapFromObf(vc, entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs)); |
| 58 | |||
| 59 | if (!vc.canProceed()) return; | ||
| 53 | 60 | ||
| 54 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | 61 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); |
| 55 | handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs)); | 62 | handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs)); |
| 56 | handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry)); | 63 | handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry)); |
| 57 | } | 64 | } |
| 58 | |||
| 59 | } | 65 | } |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java index 12a3025..78fa4fa 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 8 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 9 | import java.io.IOException; | 5 | import java.io.IOException; |
| 10 | 6 | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 11 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 12 | |||
| 11 | public class ChangeDocsS2CPacket implements Packet<ClientPacketHandler> { | 13 | public class ChangeDocsS2CPacket implements Packet<ClientPacketHandler> { |
| 12 | private int syncId; | 14 | private int syncId; |
| 13 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| @@ -38,7 +40,12 @@ public class ChangeDocsS2CPacket implements Packet<ClientPacketHandler> { | |||
| 38 | 40 | ||
| 39 | @Override | 41 | @Override |
| 40 | public void handle(ClientPacketHandler controller) { | 42 | public void handle(ClientPacketHandler controller) { |
| 41 | controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false); | 43 | ValidationContext vc = new ValidationContext(); |
| 44 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 45 | |||
| 46 | controller.changeDocs(vc, new EntryReference<>(entry, entry.getName()), newDocs); | ||
| 47 | |||
| 48 | if (!vc.canProceed()) return; | ||
| 42 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | 49 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); |
| 43 | } | 50 | } |
| 44 | } | 51 | } |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java index a41c620..732c744 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java | |||
| @@ -1,14 +1,16 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.network.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 9 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 10 | import java.io.IOException; | 5 | import java.io.IOException; |
| 11 | 6 | ||
| 7 | import cuchaz.enigma.network.Message; | ||
| 8 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 11 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 12 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 13 | |||
| 12 | public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> { | 14 | public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> { |
| 13 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| 14 | 16 | ||
| @@ -31,18 +33,24 @@ public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> { | |||
| 31 | 33 | ||
| 32 | @Override | 34 | @Override |
| 33 | public void handle(ServerPacketHandler handler) { | 35 | public void handle(ServerPacketHandler handler) { |
| 36 | ValidationContext vc = new ValidationContext(); | ||
| 37 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 38 | |||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | 39 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); |
| 40 | |||
| 35 | if (!valid) { | 41 | if (!valid) { |
| 36 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); | 42 | handler.getServer().sendCorrectMapping(handler.getClient(), entry, true); |
| 37 | return; | 43 | return; |
| 38 | } | 44 | } |
| 39 | 45 | ||
| 40 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); | 46 | handler.getServer().getMappings().mapFromObf(vc, entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName())); |
| 47 | |||
| 48 | if (!vc.canProceed()) return; | ||
| 49 | |||
| 41 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated"); | 50 | handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated"); |
| 42 | 51 | ||
| 43 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); | 52 | int syncId = handler.getServer().lockEntry(handler.getClient(), entry); |
| 44 | handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry)); | 53 | handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry)); |
| 45 | handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry)); | 54 | handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry)); |
| 46 | |||
| 47 | } | 55 | } |
| 48 | } | 56 | } |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java index 7504430..969d13c 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 8 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 9 | import java.io.IOException; | 5 | import java.io.IOException; |
| 10 | 6 | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 11 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 12 | |||
| 11 | public class MarkDeobfuscatedS2CPacket implements Packet<ClientPacketHandler> { | 13 | public class MarkDeobfuscatedS2CPacket implements Packet<ClientPacketHandler> { |
| 12 | private int syncId; | 14 | private int syncId; |
| 13 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| @@ -34,7 +36,12 @@ public class MarkDeobfuscatedS2CPacket implements Packet<ClientPacketHandler> { | |||
| 34 | 36 | ||
| 35 | @Override | 37 | @Override |
| 36 | public void handle(ClientPacketHandler controller) { | 38 | public void handle(ClientPacketHandler controller) { |
| 37 | controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false); | 39 | ValidationContext vc = new ValidationContext(); |
| 40 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 41 | |||
| 42 | controller.markAsDeobfuscated(vc, new EntryReference<>(entry, entry.getName())); | ||
| 43 | |||
| 44 | if (!vc.canProceed()) return; | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | 45 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); |
| 39 | } | 46 | } |
| 40 | } | 47 | } |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java index 3f85228..298e674 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java | |||
| @@ -1,14 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 4 | import cuchaz.enigma.translation.mapping.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | import cuchaz.enigma.network.Message; | ||
| 7 | |||
| 8 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 9 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 10 | import java.io.IOException; | 5 | import java.io.IOException; |
| 11 | 6 | ||
| 7 | import cuchaz.enigma.network.Message; | ||
| 8 | import cuchaz.enigma.network.ServerPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 11 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 12 | |||
| 12 | public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> { | 13 | public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> { |
| 13 | private Entry<?> entry; | 14 | private Entry<?> entry; |
| 14 | 15 | ||
| @@ -31,14 +32,14 @@ public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> { | |||
| 31 | 32 | ||
| 32 | @Override | 33 | @Override |
| 33 | public void handle(ServerPacketHandler handler) { | 34 | public void handle(ServerPacketHandler handler) { |
| 35 | ValidationContext vc = new ValidationContext(); | ||
| 36 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 37 | |||
| 34 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | 38 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); |
| 35 | 39 | ||
| 36 | if (valid) { | 40 | if (valid) { |
| 37 | try { | 41 | handler.getServer().getMappings().removeByObf(vc, entry); |
| 38 | handler.getServer().getMappings().removeByObf(entry); | 42 | valid = vc.canProceed(); |
| 39 | } catch (IllegalNameException e) { | ||
| 40 | valid = false; | ||
| 41 | } | ||
| 42 | } | 43 | } |
| 43 | 44 | ||
| 44 | if (!valid) { | 45 | if (!valid) { |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java index 70d803c..e336c7b 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 8 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 9 | import java.io.IOException; | 5 | import java.io.IOException; |
| 10 | 6 | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 11 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 12 | |||
| 11 | public class RemoveMappingS2CPacket implements Packet<ClientPacketHandler> { | 13 | public class RemoveMappingS2CPacket implements Packet<ClientPacketHandler> { |
| 12 | private int syncId; | 14 | private int syncId; |
| 13 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| @@ -34,7 +36,12 @@ public class RemoveMappingS2CPacket implements Packet<ClientPacketHandler> { | |||
| 34 | 36 | ||
| 35 | @Override | 37 | @Override |
| 36 | public void handle(ClientPacketHandler controller) { | 38 | public void handle(ClientPacketHandler controller) { |
| 37 | controller.removeMapping(new EntryReference<>(entry, entry.getName()), false); | 39 | ValidationContext vc = new ValidationContext(); |
| 40 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 41 | |||
| 42 | controller.removeMapping(vc, new EntryReference<>(entry, entry.getName())); | ||
| 43 | |||
| 44 | if (!vc.canProceed()) return; | ||
| 38 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | 45 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); |
| 39 | } | 46 | } |
| 40 | } | 47 | } |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java index e3e7e37..6a7d2fd 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java | |||
| @@ -1,14 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import java.io.DataInput; | ||
| 4 | import java.io.DataOutput; | ||
| 5 | import java.io.IOException; | ||
| 6 | |||
| 3 | import cuchaz.enigma.network.ServerPacketHandler; | 7 | import cuchaz.enigma.network.ServerPacketHandler; |
| 4 | import cuchaz.enigma.translation.mapping.IllegalNameException; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | 8 | import cuchaz.enigma.translation.mapping.EntryMapping; |
| 6 | import cuchaz.enigma.translation.representation.entry.Entry; | 9 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 7 | import cuchaz.enigma.network.Message; | 10 | import cuchaz.enigma.network.Message; |
| 8 | 11 | import cuchaz.enigma.utils.validation.PrintValidatable; | |
| 9 | import java.io.DataInput; | 12 | import cuchaz.enigma.utils.validation.ValidationContext; |
| 10 | import java.io.DataOutput; | ||
| 11 | import java.io.IOException; | ||
| 12 | 13 | ||
| 13 | public class RenameC2SPacket implements Packet<ServerPacketHandler> { | 14 | public class RenameC2SPacket implements Packet<ServerPacketHandler> { |
| 14 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| @@ -40,14 +41,14 @@ public class RenameC2SPacket implements Packet<ServerPacketHandler> { | |||
| 40 | 41 | ||
| 41 | @Override | 42 | @Override |
| 42 | public void handle(ServerPacketHandler handler) { | 43 | public void handle(ServerPacketHandler handler) { |
| 44 | ValidationContext vc = new ValidationContext(); | ||
| 45 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 46 | |||
| 43 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); | 47 | boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry); |
| 44 | 48 | ||
| 45 | if (valid) { | 49 | if (valid) { |
| 46 | try { | 50 | handler.getServer().getMappings().mapFromObf(vc, entry, new EntryMapping(newName)); |
| 47 | handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName)); | 51 | valid = vc.canProceed(); |
| 48 | } catch (IllegalNameException e) { | ||
| 49 | valid = false; | ||
| 50 | } | ||
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | if (!valid) { | 54 | if (!valid) { |
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java index 787e89e..fdf0654 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | package cuchaz.enigma.network.packet; | 1 | package cuchaz.enigma.network.packet; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 5 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 6 | |||
| 7 | import java.io.DataInput; | 3 | import java.io.DataInput; |
| 8 | import java.io.DataOutput; | 4 | import java.io.DataOutput; |
| 9 | import java.io.IOException; | 5 | import java.io.IOException; |
| 10 | 6 | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.network.ClientPacketHandler; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 10 | import cuchaz.enigma.utils.validation.PrintValidatable; | ||
| 11 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 12 | |||
| 11 | public class RenameS2CPacket implements Packet<ClientPacketHandler> { | 13 | public class RenameS2CPacket implements Packet<ClientPacketHandler> { |
| 12 | private int syncId; | 14 | private int syncId; |
| 13 | private Entry<?> entry; | 15 | private Entry<?> entry; |
| @@ -42,7 +44,12 @@ public class RenameS2CPacket implements Packet<ClientPacketHandler> { | |||
| 42 | 44 | ||
| 43 | @Override | 45 | @Override |
| 44 | public void handle(ClientPacketHandler controller) { | 46 | public void handle(ClientPacketHandler controller) { |
| 45 | controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false); | 47 | ValidationContext vc = new ValidationContext(); |
| 48 | vc.setActiveElement(PrintValidatable.INSTANCE); | ||
| 49 | |||
| 50 | controller.rename(vc, new EntryReference<>(entry, entry.getName()), newName, refreshClassTree); | ||
| 51 | |||
| 52 | if (!vc.canProceed()) return; | ||
| 46 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); | 53 | controller.sendPacket(new ConfirmChangeC2SPacket(syncId)); |
| 47 | } | 54 | } |
| 48 | } | 55 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java index 3d0e04c..488d04e 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/ClassSelector.java | |||
| @@ -16,7 +16,6 @@ import java.awt.event.MouseEvent; | |||
| 16 | import java.util.*; | 16 | import java.util.*; |
| 17 | 17 | ||
| 18 | import javax.annotation.Nullable; | 18 | import javax.annotation.Nullable; |
| 19 | import javax.swing.JOptionPane; | ||
| 20 | import javax.swing.JTree; | 19 | import javax.swing.JTree; |
| 21 | import javax.swing.event.CellEditorListener; | 20 | import javax.swing.event.CellEditorListener; |
| 22 | import javax.swing.event.ChangeEvent; | 21 | import javax.swing.event.ChangeEvent; |
| @@ -28,9 +27,9 @@ import com.google.common.collect.Maps; | |||
| 28 | import com.google.common.collect.Multimap; | 27 | import com.google.common.collect.Multimap; |
| 29 | import cuchaz.enigma.gui.node.ClassSelectorClassNode; | 28 | import cuchaz.enigma.gui.node.ClassSelectorClassNode; |
| 30 | import cuchaz.enigma.gui.node.ClassSelectorPackageNode; | 29 | import cuchaz.enigma.gui.node.ClassSelectorPackageNode; |
| 31 | import cuchaz.enigma.translation.mapping.IllegalNameException; | ||
| 32 | import cuchaz.enigma.translation.Translator; | 30 | import cuchaz.enigma.translation.Translator; |
| 33 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 31 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 32 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 34 | 33 | ||
| 35 | public class ClassSelector extends JTree { | 34 | public class ClassSelector extends JTree { |
| 36 | 35 | ||
| @@ -103,18 +102,18 @@ public class ClassSelector extends JTree { | |||
| 103 | if (allowEdit && renameSelectionListener != null) { | 102 | if (allowEdit && renameSelectionListener != null) { |
| 104 | Object prevData = node.getUserObject(); | 103 | Object prevData = node.getUserObject(); |
| 105 | Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; | 104 | Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data; |
| 106 | try { | 105 | gui.validateImmediateAction(vc -> { |
| 107 | renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node); | 106 | renameSelectionListener.onSelectionRename(vc, node.getUserObject(), objectData, node); |
| 108 | node.setUserObject(objectData); // Make sure that it's modified | 107 | if (vc.canProceed()) { |
| 109 | } catch (IllegalNameException ex) { | 108 | node.setUserObject(objectData); // Make sure that it's modified |
| 110 | JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION, | 109 | } else { |
| 111 | JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK"); | 110 | editor.cancelCellEditing(); |
| 112 | editor.cancelCellEditing(); | 111 | } |
| 113 | } | 112 | }); |
| 114 | } else | 113 | } else { |
| 115 | editor.cancelCellEditing(); | 114 | editor.cancelCellEditing(); |
| 115 | } | ||
| 116 | } | 116 | } |
| 117 | |||
| 118 | } | 117 | } |
| 119 | 118 | ||
| 120 | @Override | 119 | @Override |
| @@ -527,6 +526,6 @@ public class ClassSelector extends JTree { | |||
| 527 | } | 526 | } |
| 528 | 527 | ||
| 529 | public interface RenameSelectionListener { | 528 | public interface RenameSelectionListener { |
| 530 | void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node); | 529 | void onSelectionRename(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node); |
| 531 | } | 530 | } |
| 532 | } | 531 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java deleted file mode 100644 index 356656b..0000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ /dev/null | |||
| @@ -1,73 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.gui; | ||
| 13 | |||
| 14 | import cuchaz.enigma.source.Token; | ||
| 15 | |||
| 16 | import javax.swing.*; | ||
| 17 | import javax.swing.text.BadLocationException; | ||
| 18 | import javax.swing.text.Document; | ||
| 19 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 20 | import java.awt.*; | ||
| 21 | import java.awt.event.ActionEvent; | ||
| 22 | import java.awt.event.ActionListener; | ||
| 23 | |||
| 24 | public class CodeReader extends JEditorPane { | ||
| 25 | private static final long serialVersionUID = 3673180950485748810L; | ||
| 26 | |||
| 27 | // HACKHACK: someday we can update the main GUI to use this code reader | ||
| 28 | public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) { | ||
| 29 | |||
| 30 | // set the caret position to the token | ||
| 31 | Document document = editor.getDocument(); | ||
| 32 | int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); | ||
| 33 | |||
| 34 | editor.setCaretPosition(clampedPosition); | ||
| 35 | editor.grabFocus(); | ||
| 36 | |||
| 37 | try { | ||
| 38 | // make sure the token is visible in the scroll window | ||
| 39 | Rectangle start = editor.modelToView(token.start); | ||
| 40 | Rectangle end = editor.modelToView(token.end); | ||
| 41 | final Rectangle show = start.union(end); | ||
| 42 | show.grow(start.width * 10, start.height * 6); | ||
| 43 | SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show)); | ||
| 44 | } catch (BadLocationException ex) { | ||
| 45 | throw new Error(ex); | ||
| 46 | } | ||
| 47 | |||
| 48 | // highlight the token momentarily | ||
| 49 | final Timer timer = new Timer(200, new ActionListener() { | ||
| 50 | private int counter = 0; | ||
| 51 | private Object highlight = null; | ||
| 52 | |||
| 53 | @Override | ||
| 54 | public void actionPerformed(ActionEvent event) { | ||
| 55 | if (counter % 2 == 0) { | ||
| 56 | try { | ||
| 57 | highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); | ||
| 58 | } catch (BadLocationException ex) { | ||
| 59 | // don't care | ||
| 60 | } | ||
| 61 | } else if (highlight != null) { | ||
| 62 | editor.getHighlighter().removeHighlight(highlight); | ||
| 63 | } | ||
| 64 | |||
| 65 | if (counter++ > 6) { | ||
| 66 | Timer timer = (Timer) event.getSource(); | ||
| 67 | timer.stop(); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | }); | ||
| 71 | timer.start(); | ||
| 72 | } | ||
| 73 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java index 2f08a26..d4a71f5 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java | |||
| @@ -1,22 +1,24 @@ | |||
| 1 | package cuchaz.enigma.gui; | 1 | package cuchaz.enigma.gui; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.gui.config.Config; | 3 | import cuchaz.enigma.gui.config.Config; |
| 4 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 4 | import de.sciss.syntaxpane.components.LineNumbersRuler; | 5 | import de.sciss.syntaxpane.components.LineNumbersRuler; |
| 5 | import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; | 6 | import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit; |
| 6 | import de.sciss.syntaxpane.util.Configuration; | 7 | import de.sciss.syntaxpane.util.Configuration; |
| 7 | 8 | ||
| 8 | public class EnigmaSyntaxKit extends JavaSyntaxKit { | 9 | public class EnigmaSyntaxKit extends JavaSyntaxKit { |
| 10 | |||
| 9 | private static Configuration configuration = null; | 11 | private static Configuration configuration = null; |
| 10 | 12 | ||
| 11 | @Override | 13 | @Override |
| 12 | public Configuration getConfig() { | 14 | public Configuration getConfig() { |
| 13 | if(configuration == null){ | 15 | if (configuration == null) { |
| 14 | initConfig(super.getConfig(JavaSyntaxKit.class)); | 16 | initConfig(DefaultSyntaxKit.getConfig(JavaSyntaxKit.class)); |
| 15 | } | 17 | } |
| 16 | return configuration; | 18 | return configuration; |
| 17 | } | 19 | } |
| 18 | 20 | ||
| 19 | public void initConfig(Configuration baseConfig){ | 21 | public void initConfig(Configuration baseConfig) { |
| 20 | configuration = baseConfig; | 22 | configuration = baseConfig; |
| 21 | //See de.sciss.syntaxpane.TokenType | 23 | //See de.sciss.syntaxpane.TokenType |
| 22 | configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); | 24 | configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0"); |
| @@ -36,9 +38,15 @@ public class EnigmaSyntaxKit extends JavaSyntaxKit { | |||
| 36 | configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config | 38 | configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config |
| 37 | 39 | ||
| 38 | configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); | 40 | configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F"); |
| 41 | |||
| 42 | // This is an action written in javascript that is useless for enigma's | ||
| 43 | // use case, and removing it causes the editor to load way faster the | ||
| 44 | // first time | ||
| 45 | configuration.remove("Action.insert-date"); | ||
| 39 | } | 46 | } |
| 40 | 47 | ||
| 41 | public static void invalidate(){ | 48 | public static void invalidate() { |
| 42 | configuration = null; | 49 | configuration = null; |
| 43 | } | 50 | } |
| 51 | |||
| 44 | } | 52 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java index c67fc5b..0a5d3f7 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java | |||
| @@ -11,64 +11,64 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.gui; | 12 | package cuchaz.enigma.gui; |
| 13 | 13 | ||
| 14 | import java.awt.*; | 14 | import java.awt.BorderLayout; |
| 15 | import java.awt.Container; | ||
| 16 | import java.awt.FileDialog; | ||
| 15 | import java.awt.event.*; | 17 | import java.awt.event.*; |
| 16 | import java.nio.file.Path; | 18 | import java.nio.file.Path; |
| 17 | import java.util.List; | ||
| 18 | import java.util.*; | 19 | import java.util.*; |
| 20 | import java.util.function.Consumer; | ||
| 19 | import java.util.function.Function; | 21 | import java.util.function.Function; |
| 20 | 22 | ||
| 23 | import javax.annotation.Nullable; | ||
| 21 | import javax.swing.*; | 24 | import javax.swing.*; |
| 22 | import javax.swing.text.BadLocationException; | ||
| 23 | import javax.swing.text.Highlighter; | ||
| 24 | import javax.swing.tree.*; | 25 | import javax.swing.tree.*; |
| 25 | 26 | ||
| 26 | import com.google.common.base.Strings; | 27 | import com.google.common.collect.HashBiMap; |
| 27 | import com.google.common.collect.Lists; | 28 | import com.google.common.collect.Lists; |
| 28 | import cuchaz.enigma.Enigma; | 29 | import cuchaz.enigma.Enigma; |
| 29 | import cuchaz.enigma.EnigmaProfile; | 30 | import cuchaz.enigma.EnigmaProfile; |
| 30 | import cuchaz.enigma.analysis.*; | 31 | import cuchaz.enigma.analysis.*; |
| 32 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 31 | import cuchaz.enigma.gui.config.Config; | 33 | import cuchaz.enigma.gui.config.Config; |
| 32 | import cuchaz.enigma.gui.config.Themes; | 34 | import cuchaz.enigma.gui.config.Themes; |
| 33 | import cuchaz.enigma.gui.dialog.CrashDialog; | 35 | import cuchaz.enigma.gui.dialog.CrashDialog; |
| 34 | import cuchaz.enigma.gui.dialog.JavadocDialog; | 36 | import cuchaz.enigma.gui.dialog.JavadocDialog; |
| 35 | import cuchaz.enigma.gui.dialog.SearchDialog; | 37 | import cuchaz.enigma.gui.dialog.SearchDialog; |
| 36 | import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; | 38 | import cuchaz.enigma.gui.elements.CollapsibleTabbedPane; |
| 39 | import cuchaz.enigma.gui.elements.EditorTabPopupMenu; | ||
| 37 | import cuchaz.enigma.gui.elements.MenuBar; | 40 | import cuchaz.enigma.gui.elements.MenuBar; |
| 38 | import cuchaz.enigma.gui.elements.PopupMenuBar; | 41 | import cuchaz.enigma.gui.elements.ValidatableUi; |
| 42 | import cuchaz.enigma.gui.events.EditorActionListener; | ||
| 39 | import cuchaz.enigma.gui.filechooser.FileChooserAny; | 43 | import cuchaz.enigma.gui.filechooser.FileChooserAny; |
| 40 | import cuchaz.enigma.gui.filechooser.FileChooserFolder; | 44 | import cuchaz.enigma.gui.filechooser.FileChooserFolder; |
| 41 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | 45 | import cuchaz.enigma.gui.panels.*; |
| 42 | import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; | ||
| 43 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 44 | import cuchaz.enigma.gui.panels.PanelDeobf; | ||
| 45 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 46 | import cuchaz.enigma.gui.panels.PanelIdentifier; | ||
| 47 | import cuchaz.enigma.gui.panels.PanelObf; | ||
| 48 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 49 | import cuchaz.enigma.gui.util.History; | 46 | import cuchaz.enigma.gui.util.History; |
| 50 | import cuchaz.enigma.network.packet.*; | ||
| 51 | import cuchaz.enigma.source.Token; | ||
| 52 | import cuchaz.enigma.translation.mapping.IllegalNameException; | ||
| 53 | import cuchaz.enigma.translation.mapping.*; | ||
| 54 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 55 | import cuchaz.enigma.network.Message; | ||
| 56 | import cuchaz.enigma.gui.util.ScaleUtil; | 47 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 48 | import cuchaz.enigma.network.Message; | ||
| 49 | import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket; | ||
| 50 | import cuchaz.enigma.network.packet.MessageC2SPacket; | ||
| 51 | import cuchaz.enigma.network.packet.RemoveMappingC2SPacket; | ||
| 52 | import cuchaz.enigma.network.packet.RenameC2SPacket; | ||
| 53 | import cuchaz.enigma.source.Token; | ||
| 54 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 55 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 56 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 57 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | ||
| 58 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 57 | import cuchaz.enigma.utils.I18n; | 59 | import cuchaz.enigma.utils.I18n; |
| 58 | import de.sciss.syntaxpane.DefaultSyntaxKit; | 60 | import cuchaz.enigma.utils.validation.ParameterizedMessage; |
| 61 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 59 | 62 | ||
| 60 | public class Gui { | 63 | public class Gui { |
| 61 | 64 | ||
| 62 | public final PopupMenuBar popupMenu; | ||
| 63 | private final PanelObf obfPanel; | 65 | private final PanelObf obfPanel; |
| 64 | private final PanelDeobf deobfPanel; | 66 | private final PanelDeobf deobfPanel; |
| 65 | 67 | ||
| 66 | private final MenuBar menuBar; | 68 | private final MenuBar menuBar; |
| 69 | |||
| 67 | // state | 70 | // state |
| 68 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; | 71 | public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory; |
| 69 | public EntryReference<Entry<?>, Entry<?>> renamingReference; | ||
| 70 | public EntryReference<Entry<?>, Entry<?>> cursorReference; | ||
| 71 | private boolean shouldNavigateOnClick; | ||
| 72 | private ConnectionState connectionState; | 72 | private ConnectionState connectionState; |
| 73 | private boolean isJarOpen; | 73 | private boolean isJarOpen; |
| 74 | 74 | ||
| @@ -80,14 +80,9 @@ public class Gui { | |||
| 80 | public FileDialog exportJarFileChooser; | 80 | public FileDialog exportJarFileChooser; |
| 81 | private GuiController controller; | 81 | private GuiController controller; |
| 82 | private JFrame frame; | 82 | private JFrame frame; |
| 83 | public Config.LookAndFeel editorFeel; | ||
| 84 | public PanelEditor editor; | ||
| 85 | public JScrollPane sourceScroller; | ||
| 86 | private JPanel classesPanel; | 83 | private JPanel classesPanel; |
| 87 | private JSplitPane splitClasses; | 84 | private JSplitPane splitClasses; |
| 88 | private PanelIdentifier infoPanel; | 85 | private PanelIdentifier infoPanel; |
| 89 | public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters; | ||
| 90 | private SelectionHighlightPainter selectionHighlightPainter; | ||
| 91 | private JTree inheritanceTree; | 86 | private JTree inheritanceTree; |
| 92 | private JTree implementationsTree; | 87 | private JTree implementationsTree; |
| 93 | private JTree callsTree; | 88 | private JTree callsTree; |
| @@ -108,20 +103,9 @@ public class Gui { | |||
| 108 | private JLabel connectionStatusLabel; | 103 | private JLabel connectionStatusLabel; |
| 109 | private JLabel statusLabel; | 104 | private JLabel statusLabel; |
| 110 | 105 | ||
| 111 | public JTextField renameTextField; | 106 | private final EditorTabPopupMenu editorTabPopupMenu; |
| 112 | public JTextArea javadocTextArea; | 107 | private final JTabbedPane openFiles; |
| 113 | 108 | private final HashBiMap<ClassEntry, PanelEditor> editors = HashBiMap.create(); | |
| 114 | public void setEditorTheme(Config.LookAndFeel feel) { | ||
| 115 | if (editor != null && (editorFeel == null || editorFeel != feel)) { | ||
| 116 | editor.updateUI(); | ||
| 117 | editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 118 | if (editorFeel != null) { | ||
| 119 | getController().refreshCurrentClass(); | ||
| 120 | } | ||
| 121 | |||
| 122 | editorFeel = feel; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | 109 | ||
| 126 | public Gui(EnigmaProfile profile) { | 110 | public Gui(EnigmaProfile profile) { |
| 127 | Config.getInstance().lookAndFeel.setGlobalLAF(); | 111 | Config.getInstance().lookAndFeel.setGlobalLAF(); |
| @@ -144,7 +128,9 @@ public class Gui { | |||
| 144 | 128 | ||
| 145 | this.controller = new GuiController(this, profile); | 129 | this.controller = new GuiController(this, profile); |
| 146 | 130 | ||
| 147 | Themes.updateTheme(this); | 131 | Themes.addListener((lookAndFeel, boxHighlightPainters) -> SwingUtilities.updateComponentTreeUI(getFrame())); |
| 132 | |||
| 133 | Themes.updateTheme(); | ||
| 148 | 134 | ||
| 149 | // init file choosers | 135 | // init file choosers |
| 150 | this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); | 136 | this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD); |
| @@ -166,20 +152,6 @@ public class Gui { | |||
| 166 | 152 | ||
| 167 | // init info panel | 153 | // init info panel |
| 168 | infoPanel = new PanelIdentifier(this); | 154 | infoPanel = new PanelIdentifier(this); |
| 169 | infoPanel.clearReference(); | ||
| 170 | |||
| 171 | // init editor | ||
| 172 | selectionHighlightPainter = new SelectionHighlightPainter(); | ||
| 173 | this.editor = new PanelEditor(this); | ||
| 174 | this.sourceScroller = new JScrollPane(this.editor); | ||
| 175 | this.editor.setContentType("text/enigma-sources"); | ||
| 176 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 177 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); | ||
| 178 | kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 179 | |||
| 180 | // init editor popup menu | ||
| 181 | this.popupMenu = new PopupMenuBar(this); | ||
| 182 | this.editor.setComponentPopupMenu(this.popupMenu); | ||
| 183 | 155 | ||
| 184 | // init inheritance panel | 156 | // init inheritance panel |
| 185 | inheritanceTree = new JTree(); | 157 | inheritanceTree = new JTree(); |
| @@ -269,7 +241,7 @@ public class Gui { | |||
| 269 | } | 241 | } |
| 270 | }); | 242 | }); |
| 271 | tokens = new JList<>(); | 243 | tokens = new JList<>(); |
| 272 | tokens.setCellRenderer(new TokenListCellRenderer(this.controller)); | 244 | tokens.setCellRenderer(new TokenListCellRenderer(controller)); |
| 273 | tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); | 245 | tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| 274 | tokens.setLayoutOrientation(JList.VERTICAL); | 246 | tokens.setLayoutOrientation(JList.VERTICAL); |
| 275 | tokens.addMouseListener(new MouseAdapter() { | 247 | tokens.addMouseListener(new MouseAdapter() { |
| @@ -278,7 +250,7 @@ public class Gui { | |||
| 278 | if (event.getClickCount() == 2) { | 250 | if (event.getClickCount() == 2) { |
| 279 | Token selected = tokens.getSelectedValue(); | 251 | Token selected = tokens.getSelectedValue(); |
| 280 | if (selected != null) { | 252 | if (selected != null) { |
| 281 | showToken(selected); | 253 | openClass(controller.getTokenHandle().getRef()).navigateToToken(selected); |
| 282 | } | 254 | } |
| 283 | } | 255 | } |
| 284 | } | 256 | } |
| @@ -294,11 +266,25 @@ public class Gui { | |||
| 294 | callPanel.setResizeWeight(1); // let the top side take all the slack | 266 | callPanel.setResizeWeight(1); // let the top side take all the slack |
| 295 | callPanel.resetToPreferredSizes(); | 267 | callPanel.resetToPreferredSizes(); |
| 296 | 268 | ||
| 269 | editorTabPopupMenu = new EditorTabPopupMenu(this); | ||
| 270 | openFiles = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); | ||
| 271 | openFiles.addMouseListener(new MouseAdapter() { | ||
| 272 | @Override | ||
| 273 | public void mousePressed(MouseEvent e) { | ||
| 274 | if (SwingUtilities.isRightMouseButton(e)) { | ||
| 275 | int i = openFiles.getUI().tabForCoordinate(openFiles, e.getX(), e.getY()); | ||
| 276 | if (i != -1) { | ||
| 277 | editorTabPopupMenu.show(openFiles, e.getX(), e.getY(), PanelEditor.byUi(openFiles.getComponentAt(i))); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | }); | ||
| 282 | |||
| 297 | // layout controls | 283 | // layout controls |
| 298 | JPanel centerPanel = new JPanel(); | 284 | JPanel centerPanel = new JPanel(); |
| 299 | centerPanel.setLayout(new BorderLayout()); | 285 | centerPanel.setLayout(new BorderLayout()); |
| 300 | centerPanel.add(infoPanel, BorderLayout.NORTH); | 286 | centerPanel.add(infoPanel.getUi(), BorderLayout.NORTH); |
| 301 | centerPanel.add(sourceScroller, BorderLayout.CENTER); | 287 | centerPanel.add(openFiles, BorderLayout.CENTER); |
| 302 | tabs = new JTabbedPane(); | 288 | tabs = new JTabbedPane(); |
| 303 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); | 289 | tabs.setPreferredSize(ScaleUtil.getDimension(250, 0)); |
| 304 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); | 290 | tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel); |
| @@ -389,7 +375,7 @@ public class Gui { | |||
| 389 | this.frame.setTitle(Enigma.NAME + " - " + jarName); | 375 | this.frame.setTitle(Enigma.NAME + " - " + jarName); |
| 390 | this.classesPanel.removeAll(); | 376 | this.classesPanel.removeAll(); |
| 391 | this.classesPanel.add(splitClasses); | 377 | this.classesPanel.add(splitClasses); |
| 392 | setEditorText(null); | 378 | closeAllEditorTabs(); |
| 393 | 379 | ||
| 394 | // update menu | 380 | // update menu |
| 395 | isJarOpen = true; | 381 | isJarOpen = true; |
| @@ -404,7 +390,7 @@ public class Gui { | |||
| 404 | this.frame.setTitle(Enigma.NAME); | 390 | this.frame.setTitle(Enigma.NAME); |
| 405 | setObfClasses(null); | 391 | setObfClasses(null); |
| 406 | setDeobfClasses(null); | 392 | setDeobfClasses(null); |
| 407 | setEditorText(null); | 393 | closeAllEditorTabs(); |
| 408 | this.classesPanel.removeAll(); | 394 | this.classesPanel.removeAll(); |
| 409 | 395 | ||
| 410 | // update menu | 396 | // update menu |
| @@ -415,6 +401,54 @@ public class Gui { | |||
| 415 | redraw(); | 401 | redraw(); |
| 416 | } | 402 | } |
| 417 | 403 | ||
| 404 | public PanelEditor openClass(ClassEntry entry) { | ||
| 405 | PanelEditor panelEditor = editors.computeIfAbsent(entry, e -> { | ||
| 406 | ClassHandle ch = controller.getClassHandleProvider().openClass(entry); | ||
| 407 | if (ch == null) return null; | ||
| 408 | PanelEditor ed = new PanelEditor(this); | ||
| 409 | ed.setup(); | ||
| 410 | ed.setClassHandle(ch); | ||
| 411 | openFiles.addTab(ed.getFileName(), ed.getUi()); | ||
| 412 | |||
| 413 | ClosableTabTitlePane titlePane = new ClosableTabTitlePane(ed.getFileName(), () -> closeEditor(ed)); | ||
| 414 | openFiles.setTabComponentAt(openFiles.indexOfComponent(ed.getUi()), titlePane.getUi()); | ||
| 415 | titlePane.setTabbedPane(openFiles); | ||
| 416 | |||
| 417 | ed.addListener(new EditorActionListener() { | ||
| 418 | @Override | ||
| 419 | public void onCursorReferenceChanged(PanelEditor editor, EntryReference<Entry<?>, Entry<?>> ref) { | ||
| 420 | updateSelectedReference(editor, ref); | ||
| 421 | } | ||
| 422 | |||
| 423 | @Override | ||
| 424 | public void onClassHandleChanged(PanelEditor editor, ClassEntry old, ClassHandle ch) { | ||
| 425 | editors.remove(old); | ||
| 426 | editors.put(ch.getRef(), editor); | ||
| 427 | } | ||
| 428 | |||
| 429 | @Override | ||
| 430 | public void onTitleChanged(PanelEditor editor, String title) { | ||
| 431 | titlePane.setText(editor.getFileName()); | ||
| 432 | } | ||
| 433 | }); | ||
| 434 | |||
| 435 | ed.getEditor().addKeyListener(new KeyAdapter() { | ||
| 436 | @Override | ||
| 437 | public void keyPressed(KeyEvent e) { | ||
| 438 | if (e.getKeyCode() == KeyEvent.VK_4 && (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0) { | ||
| 439 | closeEditor(ed); | ||
| 440 | } | ||
| 441 | } | ||
| 442 | }); | ||
| 443 | |||
| 444 | return ed; | ||
| 445 | }); | ||
| 446 | if (panelEditor != null) { | ||
| 447 | openFiles.setSelectedComponent(editors.get(entry).getUi()); | ||
| 448 | } | ||
| 449 | return panelEditor; | ||
| 450 | } | ||
| 451 | |||
| 418 | public void setObfClasses(Collection<ClassEntry> obfClasses) { | 452 | public void setObfClasses(Collection<ClassEntry> obfClasses) { |
| 419 | this.obfPanel.obfClasses.setClasses(obfClasses); | 453 | this.obfPanel.obfClasses.setClasses(obfClasses); |
| 420 | } | 454 | } |
| @@ -428,29 +462,49 @@ public class Gui { | |||
| 428 | updateUiState(); | 462 | updateUiState(); |
| 429 | } | 463 | } |
| 430 | 464 | ||
| 431 | public void setEditorText(String source) { | 465 | public void closeEditor(PanelEditor ed) { |
| 432 | this.editor.getHighlighter().removeAllHighlights(); | 466 | openFiles.remove(ed.getUi()); |
| 433 | this.editor.setText(source); | 467 | editors.inverse().remove(ed); |
| 468 | ed.destroy(); | ||
| 434 | } | 469 | } |
| 435 | 470 | ||
| 436 | public void setSource(DecompiledClassSource source) { | 471 | public void closeAllEditorTabs() { |
| 437 | editor.setText(source.toString()); | 472 | for (Iterator<PanelEditor> iter = editors.values().iterator(); iter.hasNext(); ) { |
| 438 | setHighlightedTokens(source.getHighlightedTokens()); | 473 | PanelEditor e = iter.next(); |
| 474 | openFiles.remove(e.getUi()); | ||
| 475 | e.destroy(); | ||
| 476 | iter.remove(); | ||
| 477 | } | ||
| 439 | } | 478 | } |
| 440 | 479 | ||
| 441 | public void showToken(final Token token) { | 480 | public void closeTabsLeftOf(PanelEditor ed) { |
| 442 | if (token == null) { | 481 | int index = openFiles.indexOfComponent(ed.getUi()); |
| 443 | throw new IllegalArgumentException("Token cannot be null!"); | 482 | for (int i = index - 1; i >= 0; i--) { |
| 483 | closeEditor(PanelEditor.byUi(openFiles.getComponentAt(i))); | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | public void closeTabsRightOf(PanelEditor ed) { | ||
| 488 | int index = openFiles.indexOfComponent(ed.getUi()); | ||
| 489 | for (int i = openFiles.getTabCount() - 1; i > index; i--) { | ||
| 490 | closeEditor(PanelEditor.byUi(openFiles.getComponentAt(i))); | ||
| 491 | } | ||
| 492 | } | ||
| 493 | |||
| 494 | public void closeTabsExcept(PanelEditor ed) { | ||
| 495 | int index = openFiles.indexOfComponent(ed.getUi()); | ||
| 496 | for (int i = openFiles.getTabCount() - 1; i >= 0; i--) { | ||
| 497 | if (i == index) continue; | ||
| 498 | closeEditor(PanelEditor.byUi(openFiles.getComponentAt(i))); | ||
| 444 | } | 499 | } |
| 445 | CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter); | ||
| 446 | redraw(); | ||
| 447 | } | 500 | } |
| 448 | 501 | ||
| 449 | public void showTokens(Collection<Token> tokens) { | 502 | public void showTokens(PanelEditor editor, Collection<Token> tokens) { |
| 450 | Vector<Token> sortedTokens = new Vector<>(tokens); | 503 | Vector<Token> sortedTokens = new Vector<>(tokens); |
| 451 | Collections.sort(sortedTokens); | 504 | Collections.sort(sortedTokens); |
| 452 | if (sortedTokens.size() > 1) { | 505 | if (sortedTokens.size() > 1) { |
| 453 | // sort the tokens and update the tokens panel | 506 | // sort the tokens and update the tokens panel |
| 507 | this.controller.setTokenHandle(editor.getClassHandle().copy()); | ||
| 454 | this.tokens.setListData(sortedTokens); | 508 | this.tokens.setListData(sortedTokens); |
| 455 | this.tokens.setSelectedIndex(0); | 509 | this.tokens.setSelectedIndex(0); |
| 456 | } else { | 510 | } else { |
| @@ -458,311 +512,51 @@ public class Gui { | |||
| 458 | } | 512 | } |
| 459 | 513 | ||
| 460 | // show the first token | 514 | // show the first token |
| 461 | showToken(sortedTokens.get(0)); | 515 | editor.navigateToToken(sortedTokens.get(0)); |
| 462 | } | 516 | } |
| 463 | 517 | ||
| 464 | public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) { | 518 | private void updateSelectedReference(PanelEditor editor, EntryReference<Entry<?>, Entry<?>> ref) { |
| 465 | // remove any old highlighters | 519 | if (editor != getActiveEditor()) return; |
| 466 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 467 | 520 | ||
| 468 | if (boxHighlightPainters != null) { | 521 | showCursorReference(ref); |
| 469 | for (TokenHighlightType type : tokens.keySet()) { | ||
| 470 | BoxHighlightPainter painter = boxHighlightPainters.get(type); | ||
| 471 | if (painter != null) { | ||
| 472 | setHighlightedTokens(tokens.get(type), painter); | ||
| 473 | } | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | redraw(); | ||
| 478 | } | ||
| 479 | |||
| 480 | private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { | ||
| 481 | for (Token token : tokens) { | ||
| 482 | try { | ||
| 483 | this.editor.getHighlighter().addHighlight(token.start, token.end, painter); | ||
| 484 | } catch (BadLocationException ex) { | ||
| 485 | throw new IllegalArgumentException(ex); | ||
| 486 | } | ||
| 487 | } | ||
| 488 | } | 522 | } |
| 489 | 523 | ||
| 490 | private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { | 524 | private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) { |
| 491 | if (reference == null) { | 525 | infoPanel.setReference(reference == null ? null : reference.entry); |
| 492 | infoPanel.clearReference(); | ||
| 493 | return; | ||
| 494 | } | ||
| 495 | |||
| 496 | this.cursorReference = reference; | ||
| 497 | |||
| 498 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference); | ||
| 499 | |||
| 500 | infoPanel.removeAll(); | ||
| 501 | if (translatedReference.entry instanceof ClassEntry) { | ||
| 502 | showClassEntry((ClassEntry) translatedReference.entry); | ||
| 503 | } else if (translatedReference.entry instanceof FieldEntry) { | ||
| 504 | showFieldEntry((FieldEntry) translatedReference.entry); | ||
| 505 | } else if (translatedReference.entry instanceof MethodEntry) { | ||
| 506 | showMethodEntry((MethodEntry) translatedReference.entry); | ||
| 507 | } else if (translatedReference.entry instanceof LocalVariableEntry) { | ||
| 508 | showLocalVariableEntry((LocalVariableEntry) translatedReference.entry); | ||
| 509 | } else { | ||
| 510 | throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName()); | ||
| 511 | } | ||
| 512 | |||
| 513 | redraw(); | ||
| 514 | } | ||
| 515 | |||
| 516 | private void showLocalVariableEntry(LocalVariableEntry entry) { | ||
| 517 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName()); | ||
| 518 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName()); | ||
| 519 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName()); | ||
| 520 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex())); | ||
| 521 | } | ||
| 522 | |||
| 523 | private void showClassEntry(ClassEntry entry) { | ||
| 524 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName()); | ||
| 525 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 526 | } | ||
| 527 | |||
| 528 | private void showFieldEntry(FieldEntry entry) { | ||
| 529 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName()); | ||
| 530 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 531 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString()); | ||
| 532 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 533 | } | ||
| 534 | |||
| 535 | private void showMethodEntry(MethodEntry entry) { | ||
| 536 | if (entry.isConstructor()) { | ||
| 537 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName()); | ||
| 538 | } else { | ||
| 539 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName()); | ||
| 540 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName()); | ||
| 541 | } | ||
| 542 | addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString()); | ||
| 543 | addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry); | ||
| 544 | } | 526 | } |
| 545 | 527 | ||
| 546 | private void addNameValue(JPanel container, String name, String value) { | 528 | @Nullable |
| 547 | JPanel panel = new JPanel(); | 529 | public PanelEditor getActiveEditor() { |
| 548 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | 530 | return PanelEditor.byUi(openFiles.getSelectedComponent()); |
| 549 | |||
| 550 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 551 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 552 | panel.add(label); | ||
| 553 | |||
| 554 | panel.add(GuiUtil.unboldLabel(new JLabel(value, JLabel.LEFT))); | ||
| 555 | |||
| 556 | container.add(panel); | ||
| 557 | } | 531 | } |
| 558 | 532 | ||
| 559 | private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) { | 533 | @Nullable |
| 560 | if (!getController().project.isRenamable(entry)) | 534 | public EntryReference<Entry<?>, Entry<?>> getCursorReference() { |
| 561 | return null; | 535 | PanelEditor activeEditor = getActiveEditor(); |
| 562 | JPanel panel = new JPanel(); | 536 | return activeEditor == null ? null : activeEditor.getCursorReference(); |
| 563 | panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0)); | ||
| 564 | JLabel label = new JLabel(name + ":", JLabel.RIGHT); | ||
| 565 | label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 566 | panel.add(label); | ||
| 567 | JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); | ||
| 568 | ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT); | ||
| 569 | combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height))); | ||
| 570 | |||
| 571 | EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry); | ||
| 572 | if (mapping != null) { | ||
| 573 | combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); | ||
| 574 | } else { | ||
| 575 | combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); | ||
| 576 | } | ||
| 577 | |||
| 578 | combo.addItemListener(controller::modifierChange); | ||
| 579 | |||
| 580 | panel.add(combo); | ||
| 581 | |||
| 582 | container.add(panel); | ||
| 583 | |||
| 584 | return combo; | ||
| 585 | } | 537 | } |
| 586 | 538 | ||
| 587 | public void onCaretMove(int pos, boolean fromClick) { | 539 | public void startDocChange(PanelEditor editor) { |
| 588 | if (controller.project == null) | 540 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 589 | return; | 541 | if (cursorReference == null) return; |
| 590 | EntryRemapper mapper = controller.project.getMapper(); | 542 | JavadocDialog.show(frame, getController(), cursorReference); |
| 591 | Token token = this.controller.getToken(pos); | ||
| 592 | boolean isToken = token != null; | ||
| 593 | |||
| 594 | cursorReference = this.controller.getReference(token); | ||
| 595 | Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null; | ||
| 596 | |||
| 597 | if (referenceEntry != null && shouldNavigateOnClick && fromClick) { | ||
| 598 | shouldNavigateOnClick = false; | ||
| 599 | Entry<?> navigationEntry = referenceEntry; | ||
| 600 | if (cursorReference.context == null) { | ||
| 601 | EntryResolver resolver = mapper.getObfResolver(); | ||
| 602 | navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 603 | } | ||
| 604 | controller.navigateTo(navigationEntry); | ||
| 605 | return; | ||
| 606 | } | ||
| 607 | |||
| 608 | boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry; | ||
| 609 | boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry; | ||
| 610 | boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); | ||
| 611 | boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); | ||
| 612 | boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference); | ||
| 613 | |||
| 614 | if (!isRenaming()) { | ||
| 615 | if (isToken) { | ||
| 616 | showCursorReference(cursorReference); | ||
| 617 | } else { | ||
| 618 | infoPanel.clearReference(); | ||
| 619 | } | ||
| 620 | } | ||
| 621 | |||
| 622 | this.popupMenu.renameMenu.setEnabled(isRenamable); | ||
| 623 | this.popupMenu.editJavadocMenu.setEnabled(isRenamable); | ||
| 624 | this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); | ||
| 625 | this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); | ||
| 626 | this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); | ||
| 627 | this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); | ||
| 628 | this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); | ||
| 629 | this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); | ||
| 630 | this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); | ||
| 631 | this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); | ||
| 632 | |||
| 633 | if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) { | ||
| 634 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); | ||
| 635 | } else { | ||
| 636 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); | ||
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | public void startDocChange() { | ||
| 641 | EntryReference<Entry<?>, Entry<?>> curReference = cursorReference; | ||
| 642 | if (isRenaming()) { | ||
| 643 | finishRename(false); | ||
| 644 | } | ||
| 645 | renamingReference = curReference; | ||
| 646 | |||
| 647 | // init the text box | ||
| 648 | javadocTextArea = new JTextArea(10, 40); | ||
| 649 | |||
| 650 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 651 | javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs())); | ||
| 652 | |||
| 653 | JavadocDialog.init(frame, javadocTextArea, this::finishDocChange); | ||
| 654 | javadocTextArea.grabFocus(); | ||
| 655 | |||
| 656 | redraw(); | ||
| 657 | } | ||
| 658 | |||
| 659 | private void finishDocChange(JFrame ui, boolean saveName) { | ||
| 660 | String newName = javadocTextArea.getText(); | ||
| 661 | if (saveName) { | ||
| 662 | try { | ||
| 663 | this.controller.changeDocs(renamingReference, newName); | ||
| 664 | this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName)); | ||
| 665 | } catch (IllegalNameException ex) { | ||
| 666 | javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 667 | javadocTextArea.setToolTipText(ex.getReason()); | ||
| 668 | GuiUtil.showToolTipNow(javadocTextArea); | ||
| 669 | return; | ||
| 670 | } | ||
| 671 | |||
| 672 | ui.setVisible(false); | ||
| 673 | showCursorReference(cursorReference); | ||
| 674 | return; | ||
| 675 | } | ||
| 676 | |||
| 677 | // abort the jd change | ||
| 678 | javadocTextArea = null; | ||
| 679 | ui.setVisible(false); | ||
| 680 | showCursorReference(cursorReference); | ||
| 681 | |||
| 682 | this.editor.grabFocus(); | ||
| 683 | |||
| 684 | redraw(); | ||
| 685 | } | 543 | } |
| 686 | 544 | ||
| 687 | public void startRename() { | 545 | public void startRename(PanelEditor editor, String text) { |
| 546 | if (editor != getActiveEditor()) return; | ||
| 688 | 547 | ||
| 689 | // init the text box | 548 | infoPanel.startRenaming(text); |
| 690 | renameTextField = new JTextField(); | ||
| 691 | |||
| 692 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference); | ||
| 693 | renameTextField.setText(translatedReference.getNameableName()); | ||
| 694 | |||
| 695 | renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height))); | ||
| 696 | renameTextField.addKeyListener(new KeyAdapter() { | ||
| 697 | @Override | ||
| 698 | public void keyPressed(KeyEvent event) { | ||
| 699 | switch (event.getKeyCode()) { | ||
| 700 | case KeyEvent.VK_ENTER: | ||
| 701 | finishRename(true); | ||
| 702 | break; | ||
| 703 | |||
| 704 | case KeyEvent.VK_ESCAPE: | ||
| 705 | finishRename(false); | ||
| 706 | break; | ||
| 707 | default: | ||
| 708 | break; | ||
| 709 | } | ||
| 710 | } | ||
| 711 | }); | ||
| 712 | |||
| 713 | // find the label with the name and replace it with the text box | ||
| 714 | JPanel panel = (JPanel) infoPanel.getComponent(0); | ||
| 715 | panel.remove(panel.getComponentCount() - 1); | ||
| 716 | panel.add(renameTextField); | ||
| 717 | renameTextField.grabFocus(); | ||
| 718 | |||
| 719 | int offset = renameTextField.getText().lastIndexOf('/') + 1; | ||
| 720 | // If it's a class and isn't in the default package, assume that it's deobfuscated. | ||
| 721 | if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0) | ||
| 722 | renameTextField.select(offset, renameTextField.getText().length()); | ||
| 723 | else | ||
| 724 | renameTextField.selectAll(); | ||
| 725 | |||
| 726 | renamingReference = cursorReference; | ||
| 727 | |||
| 728 | redraw(); | ||
| 729 | } | 549 | } |
| 730 | 550 | ||
| 731 | private void finishRename(boolean saveName) { | 551 | public void startRename(PanelEditor editor) { |
| 732 | String newName = renameTextField.getText(); | 552 | if (editor != getActiveEditor()) return; |
| 733 | |||
| 734 | if (saveName && newName != null && !newName.isEmpty()) { | ||
| 735 | try { | ||
| 736 | this.controller.rename(renamingReference, newName, true); | ||
| 737 | this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true)); | ||
| 738 | renameTextField = null; | ||
| 739 | } catch (IllegalNameException ex) { | ||
| 740 | renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1)); | ||
| 741 | renameTextField.setToolTipText(ex.getReason()); | ||
| 742 | GuiUtil.showToolTipNow(renameTextField); | ||
| 743 | } | ||
| 744 | return; | ||
| 745 | } | ||
| 746 | |||
| 747 | renameTextField = null; | ||
| 748 | 553 | ||
| 749 | // abort the rename | 554 | infoPanel.startRenaming(); |
| 750 | showCursorReference(cursorReference); | ||
| 751 | |||
| 752 | this.editor.grabFocus(); | ||
| 753 | |||
| 754 | redraw(); | ||
| 755 | } | 555 | } |
| 756 | 556 | ||
| 757 | private boolean isRenaming() { | 557 | public void showInheritance(PanelEditor editor) { |
| 758 | return renameTextField != null; | 558 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 759 | } | 559 | if (cursorReference == null) return; |
| 760 | |||
| 761 | public void showInheritance() { | ||
| 762 | |||
| 763 | if (cursorReference == null) { | ||
| 764 | return; | ||
| 765 | } | ||
| 766 | 560 | ||
| 767 | inheritanceTree.setModel(null); | 561 | inheritanceTree.setModel(null); |
| 768 | 562 | ||
| @@ -791,11 +585,9 @@ public class Gui { | |||
| 791 | redraw(); | 585 | redraw(); |
| 792 | } | 586 | } |
| 793 | 587 | ||
| 794 | public void showImplementations() { | 588 | public void showImplementations(PanelEditor editor) { |
| 795 | 589 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | |
| 796 | if (cursorReference == null) { | 590 | if (cursorReference == null) return; |
| 797 | return; | ||
| 798 | } | ||
| 799 | 591 | ||
| 800 | implementationsTree.setModel(null); | 592 | implementationsTree.setModel(null); |
| 801 | 593 | ||
| @@ -821,10 +613,9 @@ public class Gui { | |||
| 821 | redraw(); | 613 | redraw(); |
| 822 | } | 614 | } |
| 823 | 615 | ||
| 824 | public void showCalls(boolean recurse) { | 616 | public void showCalls(PanelEditor editor, boolean recurse) { |
| 825 | if (cursorReference == null) { | 617 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); |
| 826 | return; | 618 | if (cursorReference == null) return; |
| 827 | } | ||
| 828 | 619 | ||
| 829 | if (cursorReference.entry instanceof ClassEntry) { | 620 | if (cursorReference.entry instanceof ClassEntry) { |
| 830 | ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); | 621 | ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry); |
| @@ -842,15 +633,18 @@ public class Gui { | |||
| 842 | redraw(); | 633 | redraw(); |
| 843 | } | 634 | } |
| 844 | 635 | ||
| 845 | public void toggleMapping() { | 636 | public void toggleMapping(PanelEditor editor) { |
| 637 | EntryReference<Entry<?>, Entry<?>> cursorReference = editor.getCursorReference(); | ||
| 638 | if (cursorReference == null) return; | ||
| 639 | |||
| 846 | Entry<?> obfEntry = cursorReference.entry; | 640 | Entry<?> obfEntry = cursorReference.entry; |
| 847 | Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); | 641 | Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry); |
| 848 | 642 | ||
| 849 | if (!Objects.equals(obfEntry, deobfEntry)) { | 643 | if (!Objects.equals(obfEntry, deobfEntry)) { |
| 850 | this.controller.removeMapping(cursorReference); | 644 | if (!validateImmediateAction(vc -> this.controller.removeMapping(vc, cursorReference))) return; |
| 851 | this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); | 645 | this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry())); |
| 852 | } else { | 646 | } else { |
| 853 | this.controller.markAsDeobfuscated(cursorReference); | 647 | if (!validateImmediateAction(vc -> this.controller.markAsDeobfuscated(vc, cursorReference))) return; |
| 854 | this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); | 648 | this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry())); |
| 855 | } | 649 | } |
| 856 | } | 650 | } |
| @@ -909,37 +703,51 @@ public class Gui { | |||
| 909 | this.frame.repaint(); | 703 | this.frame.repaint(); |
| 910 | } | 704 | } |
| 911 | 705 | ||
| 912 | public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException { | 706 | public void onPanelRename(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { |
| 913 | // package rename | ||
| 914 | if (data instanceof String) { | 707 | if (data instanceof String) { |
| 708 | // package rename | ||
| 915 | for (int i = 0; i < node.getChildCount(); i++) { | 709 | for (int i = 0; i < node.getChildCount(); i++) { |
| 916 | DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); | 710 | DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); |
| 917 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); | 711 | ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); |
| 918 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); | 712 | ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); |
| 919 | this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); | 713 | this.controller.rename(vc, new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false); |
| 714 | if (!vc.canProceed()) return; | ||
| 920 | this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); | 715 | this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false)); |
| 921 | childNode.setUserObject(dataChild); | 716 | childNode.setUserObject(dataChild); |
| 922 | } | 717 | } |
| 923 | node.setUserObject(data); | 718 | node.setUserObject(data); |
| 924 | // Ob package will never be modified, just reload deob view | 719 | // Ob package will never be modified, just reload deob view |
| 925 | this.deobfPanel.deobfClasses.reload(); | 720 | this.deobfPanel.deobfClasses.reload(); |
| 926 | } | 721 | } else if (data instanceof ClassEntry) { |
| 927 | // class rename | 722 | // class rename |
| 928 | else if (data instanceof ClassEntry) { | 723 | |
| 929 | this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false); | 724 | // assume this is deobf since the obf tree doesn't allow renaming in |
| 930 | this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false)); | 725 | // the first place |
| 726 | // TODO optimize reverse class lookup, although it looks like it's | ||
| 727 | // fast enough for now | ||
| 728 | EntryRemapper mapper = this.controller.project.getMapper(); | ||
| 729 | ClassEntry deobf = (ClassEntry) prevData; | ||
| 730 | ClassEntry obf = mapper.getObfToDeobf().getAllEntries() | ||
| 731 | .filter(e -> e instanceof ClassEntry) | ||
| 732 | .map(e -> (ClassEntry) e) | ||
| 733 | .filter(e -> mapper.deobfuscate(e).equals(deobf)) | ||
| 734 | .findAny().get(); | ||
| 735 | |||
| 736 | this.controller.rename(vc, new EntryReference<>(obf, obf.getFullName()), ((ClassEntry) data).getFullName(), false); | ||
| 737 | if (!vc.canProceed()) return; | ||
| 738 | this.controller.sendPacket(new RenameC2SPacket(obf, ((ClassEntry) data).getFullName(), false)); | ||
| 931 | } | 739 | } |
| 932 | } | 740 | } |
| 933 | 741 | ||
| 934 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) { | 742 | public void moveClassTree(Entry<?> obfEntry, String newName) { |
| 935 | String oldEntry = obfReference.entry.getContainingClass().getPackageName(); | 743 | String oldEntry = obfEntry.getContainingClass().getPackageName(); |
| 936 | String newEntry = new ClassEntry(newName).getPackageName(); | 744 | String newEntry = new ClassEntry(newName).getPackageName(); |
| 937 | moveClassTree(obfReference, oldEntry == null, newEntry == null); | 745 | moveClassTree(obfEntry, oldEntry == null, newEntry == null); |
| 938 | } | 746 | } |
| 939 | 747 | ||
| 940 | // TODO: getExpansionState will *not* actually update itself based on name changes! | 748 | // TODO: getExpansionState will *not* actually update itself based on name changes! |
| 941 | public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) { | 749 | public void moveClassTree(Entry<?> obfEntry, boolean isOldOb, boolean isNewOb) { |
| 942 | ClassEntry classEntry = obfReference.entry.getContainingClass(); | 750 | ClassEntry classEntry = obfEntry.getContainingClass(); |
| 943 | 751 | ||
| 944 | List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); | 752 | List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses); |
| 945 | List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); | 753 | List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses); |
| @@ -979,10 +787,6 @@ public class Gui { | |||
| 979 | return deobfPanel; | 787 | return deobfPanel; |
| 980 | } | 788 | } |
| 981 | 789 | ||
| 982 | public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) { | ||
| 983 | this.shouldNavigateOnClick = shouldNavigateOnClick; | ||
| 984 | } | ||
| 985 | |||
| 986 | public SearchDialog getSearchDialog() { | 790 | public SearchDialog getSearchDialog() { |
| 987 | if (searchDialog == null) { | 791 | if (searchDialog == null) { |
| 988 | searchDialog = new SearchDialog(this); | 792 | searchDialog = new SearchDialog(this); |
| @@ -1052,4 +856,19 @@ public class Gui { | |||
| 1052 | return this.connectionState; | 856 | return this.connectionState; |
| 1053 | } | 857 | } |
| 1054 | 858 | ||
| 859 | public PanelIdentifier getInfoPanel() { | ||
| 860 | return infoPanel; | ||
| 861 | } | ||
| 862 | |||
| 863 | public boolean validateImmediateAction(Consumer<ValidationContext> op) { | ||
| 864 | ValidationContext vc = new ValidationContext(); | ||
| 865 | op.accept(vc); | ||
| 866 | if (!vc.canProceed()) { | ||
| 867 | List<ParameterizedMessage> messages = vc.getMessages(); | ||
| 868 | String text = ValidatableUi.formatMessages(messages); | ||
| 869 | JOptionPane.showMessageDialog(this.getFrame(), text, String.format("%d message(s)", messages.size()), JOptionPane.ERROR_MESSAGE); | ||
| 870 | } | ||
| 871 | return vc.canProceed(); | ||
| 872 | } | ||
| 873 | |||
| 1055 | } | 874 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 94979e7..10f36b8 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java | |||
| @@ -11,27 +11,46 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.gui; | 12 | package cuchaz.enigma.gui; |
| 13 | 13 | ||
| 14 | import java.awt.Desktop; | ||
| 15 | import java.io.File; | ||
| 16 | import java.io.FileWriter; | ||
| 17 | import java.io.IOException; | ||
| 18 | import java.nio.file.Path; | ||
| 19 | import java.util.Collection; | ||
| 20 | import java.util.List; | ||
| 21 | import java.util.Set; | ||
| 22 | import java.util.concurrent.CompletableFuture; | ||
| 23 | import java.util.concurrent.ExecutionException; | ||
| 24 | import java.util.stream.Collectors; | ||
| 25 | import java.util.stream.Stream; | ||
| 26 | |||
| 27 | import javax.swing.JOptionPane; | ||
| 28 | import javax.swing.SwingUtilities; | ||
| 29 | |||
| 14 | import com.google.common.collect.Lists; | 30 | import com.google.common.collect.Lists; |
| 15 | import com.google.common.util.concurrent.ThreadFactoryBuilder; | ||
| 16 | import cuchaz.enigma.Enigma; | 31 | import cuchaz.enigma.Enigma; |
| 17 | import cuchaz.enigma.EnigmaProfile; | 32 | import cuchaz.enigma.EnigmaProfile; |
| 18 | import cuchaz.enigma.EnigmaProject; | 33 | import cuchaz.enigma.EnigmaProject; |
| 19 | import cuchaz.enigma.analysis.*; | 34 | import cuchaz.enigma.analysis.*; |
| 20 | import cuchaz.enigma.api.service.ObfuscationTestService; | 35 | import cuchaz.enigma.api.service.ObfuscationTestService; |
| 36 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 37 | import cuchaz.enigma.classhandle.ClassHandleProvider; | ||
| 21 | import cuchaz.enigma.gui.config.Config; | 38 | import cuchaz.enigma.gui.config.Config; |
| 22 | import cuchaz.enigma.gui.dialog.ProgressDialog; | 39 | import cuchaz.enigma.gui.dialog.ProgressDialog; |
| 23 | import cuchaz.enigma.gui.stats.StatsGenerator; | 40 | import cuchaz.enigma.gui.stats.StatsGenerator; |
| 24 | import cuchaz.enigma.gui.stats.StatsMember; | 41 | import cuchaz.enigma.gui.stats.StatsMember; |
| 25 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 26 | import cuchaz.enigma.gui.util.History; | 42 | import cuchaz.enigma.gui.util.History; |
| 27 | import cuchaz.enigma.network.*; | 43 | import cuchaz.enigma.network.*; |
| 28 | import cuchaz.enigma.network.packet.LoginC2SPacket; | 44 | import cuchaz.enigma.network.packet.LoginC2SPacket; |
| 29 | import cuchaz.enigma.network.packet.Packet; | 45 | import cuchaz.enigma.network.packet.Packet; |
| 30 | import cuchaz.enigma.source.*; | 46 | import cuchaz.enigma.source.DecompiledClassSource; |
| 31 | import cuchaz.enigma.translation.mapping.serde.MappingParseException; | 47 | import cuchaz.enigma.source.DecompilerService; |
| 48 | import cuchaz.enigma.source.SourceIndex; | ||
| 49 | import cuchaz.enigma.source.Token; | ||
| 32 | import cuchaz.enigma.translation.Translator; | 50 | import cuchaz.enigma.translation.Translator; |
| 33 | import cuchaz.enigma.translation.mapping.*; | 51 | import cuchaz.enigma.translation.mapping.*; |
| 34 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 52 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; |
| 53 | import cuchaz.enigma.translation.mapping.serde.MappingParseException; | ||
| 35 | import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; | 54 | import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; |
| 36 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 55 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 37 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | 56 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; |
| @@ -41,44 +60,21 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry; | |||
| 41 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 60 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 42 | import cuchaz.enigma.utils.I18n; | 61 | import cuchaz.enigma.utils.I18n; |
| 43 | import cuchaz.enigma.utils.Utils; | 62 | import cuchaz.enigma.utils.Utils; |
| 44 | 63 | import cuchaz.enigma.utils.validation.ValidationContext; | |
| 45 | import javax.annotation.Nullable; | ||
| 46 | import javax.swing.JOptionPane; | ||
| 47 | import javax.swing.SwingUtilities; | ||
| 48 | import java.awt.*; | ||
| 49 | import java.awt.event.ItemEvent; | ||
| 50 | import java.io.*; | ||
| 51 | import java.nio.file.Path; | ||
| 52 | import java.util.Collection; | ||
| 53 | import java.util.List; | ||
| 54 | import java.util.Set; | ||
| 55 | import java.util.concurrent.CompletableFuture; | ||
| 56 | import java.util.concurrent.ExecutorService; | ||
| 57 | import java.util.concurrent.Executors; | ||
| 58 | import java.util.stream.Collectors; | ||
| 59 | import java.util.stream.Stream; | ||
| 60 | 64 | ||
| 61 | public class GuiController implements ClientPacketHandler { | 65 | public class GuiController implements ClientPacketHandler { |
| 62 | private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor( | ||
| 63 | new ThreadFactoryBuilder() | ||
| 64 | .setDaemon(true) | ||
| 65 | .setNameFormat("decompiler-thread") | ||
| 66 | .build() | ||
| 67 | ); | ||
| 68 | |||
| 69 | private final Gui gui; | 66 | private final Gui gui; |
| 70 | public final Enigma enigma; | 67 | public final Enigma enigma; |
| 71 | 68 | ||
| 72 | public EnigmaProject project; | 69 | public EnigmaProject project; |
| 73 | private DecompilerService decompilerService; | ||
| 74 | private Decompiler decompiler; | ||
| 75 | private IndexTreeBuilder indexTreeBuilder; | 70 | private IndexTreeBuilder indexTreeBuilder; |
| 76 | 71 | ||
| 77 | private Path loadedMappingPath; | 72 | private Path loadedMappingPath; |
| 78 | private MappingFormat loadedMappingFormat; | 73 | private MappingFormat loadedMappingFormat; |
| 79 | 74 | ||
| 80 | private DecompiledClassSource currentSource; | 75 | private ClassHandleProvider chp; |
| 81 | private Source uncommentedSource; | 76 | |
| 77 | private ClassHandle tokenHandle; | ||
| 82 | 78 | ||
| 83 | private EnigmaClient client; | 79 | private EnigmaClient client; |
| 84 | private EnigmaServer server; | 80 | private EnigmaServer server; |
| @@ -88,8 +84,6 @@ public class GuiController implements ClientPacketHandler { | |||
| 88 | this.enigma = Enigma.builder() | 84 | this.enigma = Enigma.builder() |
| 89 | .setProfile(profile) | 85 | .setProfile(profile) |
| 90 | .build(); | 86 | .build(); |
| 91 | |||
| 92 | decompilerService = Config.getInstance().decompiler.service; | ||
| 93 | } | 87 | } |
| 94 | 88 | ||
| 95 | public boolean isDirty() { | 89 | public boolean isDirty() { |
| @@ -102,13 +96,15 @@ public class GuiController implements ClientPacketHandler { | |||
| 102 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { | 96 | return ProgressDialog.runOffThread(gui.getFrame(), progress -> { |
| 103 | project = enigma.openJar(jarPath, progress); | 97 | project = enigma.openJar(jarPath, progress); |
| 104 | indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); | 98 | indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); |
| 105 | decompiler = project.createDecompiler(decompilerService); | 99 | chp = new ClassHandleProvider(project, Config.getInstance().decompiler.service); |
| 106 | gui.onFinishOpenJar(jarPath.getFileName().toString()); | 100 | gui.onFinishOpenJar(jarPath.getFileName().toString()); |
| 107 | refreshClasses(); | 101 | refreshClasses(); |
| 108 | }); | 102 | }); |
| 109 | } | 103 | } |
| 110 | 104 | ||
| 111 | public void closeJar() { | 105 | public void closeJar() { |
| 106 | this.chp.destroy(); | ||
| 107 | this.chp = null; | ||
| 112 | this.project = null; | 108 | this.project = null; |
| 113 | this.gui.onCloseJar(); | 109 | this.gui.onCloseJar(); |
| 114 | } | 110 | } |
| @@ -129,7 +125,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 129 | loadedMappingPath = path; | 125 | loadedMappingPath = path; |
| 130 | 126 | ||
| 131 | refreshClasses(); | 127 | refreshClasses(); |
| 132 | refreshCurrentClass(); | 128 | chp.invalidateMapped(); |
| 133 | } catch (MappingParseException e) { | 129 | } catch (MappingParseException e) { |
| 134 | JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); | 130 | JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage()); |
| 135 | } | 131 | } |
| @@ -142,7 +138,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 142 | 138 | ||
| 143 | project.setMappings(mappings); | 139 | project.setMappings(mappings); |
| 144 | refreshClasses(); | 140 | refreshClasses(); |
| 145 | refreshCurrentClass(); | 141 | chp.invalidateMapped(); |
| 146 | } | 142 | } |
| 147 | 143 | ||
| 148 | public CompletableFuture<Void> saveMappings(Path path) { | 144 | public CompletableFuture<Void> saveMappings(Path path) { |
| @@ -177,7 +173,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 177 | 173 | ||
| 178 | this.gui.setMappingsFile(null); | 174 | this.gui.setMappingsFile(null); |
| 179 | refreshClasses(); | 175 | refreshClasses(); |
| 180 | refreshCurrentClass(); | 176 | chp.invalidateMapped(); |
| 181 | } | 177 | } |
| 182 | 178 | ||
| 183 | public CompletableFuture<Void> dropMappings() { | 179 | public CompletableFuture<Void> dropMappings() { |
| @@ -191,7 +187,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 191 | 187 | ||
| 192 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { | 188 | return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { |
| 193 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); | 189 | EnigmaProject.JarExport jar = project.exportRemappedJar(progress); |
| 194 | EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); | 190 | EnigmaProject.SourceExport source = jar.decompile(progress, chp.getDecompilerService()); |
| 195 | 191 | ||
| 196 | source.write(path, progress); | 192 | source.write(path, progress); |
| 197 | }); | 193 | }); |
| @@ -206,32 +202,34 @@ public class GuiController implements ClientPacketHandler { | |||
| 206 | }); | 202 | }); |
| 207 | } | 203 | } |
| 208 | 204 | ||
| 209 | public Token getToken(int pos) { | 205 | public void setTokenHandle(ClassHandle handle) { |
| 210 | if (this.currentSource == null) { | 206 | if (tokenHandle != null) { |
| 211 | return null; | 207 | tokenHandle.close(); |
| 212 | } | 208 | } |
| 213 | return this.currentSource.getIndex().getReferenceToken(pos); | 209 | |
| 210 | tokenHandle = handle; | ||
| 214 | } | 211 | } |
| 215 | 212 | ||
| 216 | @Nullable | 213 | public ClassHandle getTokenHandle() { |
| 217 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | 214 | return tokenHandle; |
| 218 | if (this.currentSource == null) { | ||
| 219 | return null; | ||
| 220 | } | ||
| 221 | return this.currentSource.getIndex().getReference(token); | ||
| 222 | } | 215 | } |
| 223 | 216 | ||
| 224 | public ReadableToken getReadableToken(Token token) { | 217 | public ReadableToken getReadableToken(Token token) { |
| 225 | if (this.currentSource == null) { | 218 | if (tokenHandle == null) { |
| 226 | return null; | 219 | return null; |
| 227 | } | 220 | } |
| 228 | 221 | ||
| 229 | SourceIndex index = this.currentSource.getIndex(); | 222 | try { |
| 230 | return new ReadableToken( | 223 | return tokenHandle.getSource().get() |
| 231 | index.getLineNumber(token.start), | 224 | .map(DecompiledClassSource::getIndex) |
| 232 | index.getColumnNumber(token.start), | 225 | .map(index -> new ReadableToken( |
| 233 | index.getColumnNumber(token.end) | 226 | index.getLineNumber(token.start), |
| 234 | ); | 227 | index.getColumnNumber(token.start), |
| 228 | index.getColumnNumber(token.end))) | ||
| 229 | .unwrapOr(null); | ||
| 230 | } catch (InterruptedException | ExecutionException e) { | ||
| 231 | throw new RuntimeException(e); | ||
| 232 | } | ||
| 235 | } | 233 | } |
| 236 | 234 | ||
| 237 | /** | 235 | /** |
| @@ -271,39 +269,13 @@ public class GuiController implements ClientPacketHandler { | |||
| 271 | * @param reference the reference | 269 | * @param reference the reference |
| 272 | */ | 270 | */ |
| 273 | private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { | 271 | private void setReference(EntryReference<Entry<?>, Entry<?>> reference) { |
| 274 | // get the reference target class | 272 | gui.openClass(reference.getLocationClassEntry()).showReference(reference); |
| 275 | ClassEntry classEntry = reference.getLocationClassEntry(); | ||
| 276 | if (!project.isRenamable(classEntry)) { | ||
| 277 | throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!"); | ||
| 278 | } | ||
| 279 | |||
| 280 | if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) { | ||
| 281 | // deobfuscate the class, then navigate to the reference | ||
| 282 | loadClass(classEntry, () -> showReference(reference)); | ||
| 283 | } else { | ||
| 284 | showReference(reference); | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | /** | ||
| 289 | * Navigates to the reference without modifying history. Assumes the class is loaded. | ||
| 290 | * | ||
| 291 | * @param reference | ||
| 292 | */ | ||
| 293 | private void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 294 | Collection<Token> tokens = getTokensForReference(reference); | ||
| 295 | if (tokens.isEmpty()) { | ||
| 296 | // DEBUG | ||
| 297 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry())); | ||
| 298 | } else { | ||
| 299 | this.gui.showTokens(tokens); | ||
| 300 | } | ||
| 301 | } | 273 | } |
| 302 | 274 | ||
| 303 | public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) { | 275 | public Collection<Token> getTokensForReference(DecompiledClassSource source, EntryReference<Entry<?>, Entry<?>> reference) { |
| 304 | EntryRemapper mapper = this.project.getMapper(); | 276 | EntryRemapper mapper = this.project.getMapper(); |
| 305 | 277 | ||
| 306 | SourceIndex index = this.currentSource.getIndex(); | 278 | SourceIndex index = source.getIndex(); |
| 307 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) | 279 | return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST) |
| 308 | .stream() | 280 | .stream() |
| 309 | .flatMap(r -> index.getReferenceTokens(r).stream()) | 281 | .flatMap(r -> index.getReferenceTokens(r).stream()) |
| @@ -380,131 +352,17 @@ public class GuiController implements ClientPacketHandler { | |||
| 380 | }); | 352 | }); |
| 381 | } | 353 | } |
| 382 | 354 | ||
| 383 | public void refreshCurrentClass() { | 355 | public void onModifierChanged(ValidationContext vc, Entry<?> entry, AccessModifier modifier) { |
| 384 | refreshCurrentClass(null); | 356 | EntryRemapper mapper = project.getMapper(); |
| 385 | } | ||
| 386 | |||
| 387 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 388 | refreshCurrentClass(reference, RefreshMode.MINIMAL); | ||
| 389 | } | ||
| 390 | |||
| 391 | private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) { | ||
| 392 | if (currentSource != null) { | ||
| 393 | if (reference == null) { | ||
| 394 | int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart()); | ||
| 395 | int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd()); | ||
| 396 | |||
| 397 | Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect(); | ||
| 398 | // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set | ||
| 399 | int anchorModelPos = gui.editor.getSelectionStart(); | ||
| 400 | Rectangle anchorViewPos = GuiUtil.safeModelToView(gui.editor, anchorModelPos); | ||
| 401 | if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) { | ||
| 402 | anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y)); | ||
| 403 | anchorViewPos = GuiUtil.safeModelToView(gui.editor, anchorModelPos); | ||
| 404 | } | ||
| 405 | int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos); | ||
| 406 | Rectangle anchorViewPos_f = anchorViewPos; | ||
| 407 | int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue(); | ||
| 408 | |||
| 409 | loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> { | ||
| 410 | int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos); | ||
| 411 | Rectangle newAnchorViewPos = GuiUtil.safeModelToView(gui.editor, newAnchorModelPos); | ||
| 412 | int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y); | ||
| 413 | |||
| 414 | gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd)); | ||
| 415 | // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so | ||
| 416 | // we need to wrap our change to the scroll position inside another invokeLater so it happens after | ||
| 417 | // the caret's own scrolling. | ||
| 418 | SwingUtilities.invokeLater(() -> { | ||
| 419 | gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum())); | ||
| 420 | gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum())); | ||
| 421 | }); | ||
| 422 | }), mode); | ||
| 423 | } else { | ||
| 424 | loadClass(currentSource.getEntry(), () -> showReference(reference), mode); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | private void loadClass(ClassEntry classEntry, Runnable callback) { | ||
| 430 | loadClass(classEntry, callback, RefreshMode.MINIMAL); | ||
| 431 | } | ||
| 432 | |||
| 433 | private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { | ||
| 434 | ClassEntry targetClass = classEntry.getOutermostClass(); | ||
| 435 | |||
| 436 | boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); | ||
| 437 | if (requiresDecompile) { | ||
| 438 | currentSource = null; // Or the GUI may try to find a nonexistent token | ||
| 439 | gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); | ||
| 440 | } | ||
| 441 | |||
| 442 | DECOMPILER_SERVICE.submit(() -> { | ||
| 443 | try { | ||
| 444 | if (requiresDecompile || mode == RefreshMode.JAVADOCS) { | ||
| 445 | currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); | ||
| 446 | } | ||
| 447 | |||
| 448 | remapSource(project.getMapper().getDeobfuscator()); | ||
| 449 | callback.run(); | ||
| 450 | } catch (Throwable t) { | ||
| 451 | System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName()); | ||
| 452 | t.printStackTrace(System.err); | ||
| 453 | } | ||
| 454 | }); | ||
| 455 | } | ||
| 456 | |||
| 457 | private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { | ||
| 458 | try { | ||
| 459 | if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { | ||
| 460 | uncommentedSource = decompiler.getSource(targetClass.getFullName()); | ||
| 461 | } | ||
| 462 | |||
| 463 | Source source = uncommentedSource.addJavadocs(project.getMapper()); | ||
| 464 | |||
| 465 | if (source == null) { | ||
| 466 | gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); | ||
| 467 | return DecompiledClassSource.text(targetClass, "Unable to find class"); | ||
| 468 | } | ||
| 469 | |||
| 470 | SourceIndex index = source.index(); | ||
| 471 | index.resolveReferences(project.getMapper().getObfResolver()); | ||
| 472 | |||
| 473 | return new DecompiledClassSource(targetClass, index); | ||
| 474 | } catch (Throwable t) { | ||
| 475 | StringWriter traceWriter = new StringWriter(); | ||
| 476 | t.printStackTrace(new PrintWriter(traceWriter)); | ||
| 477 | |||
| 478 | return DecompiledClassSource.text(targetClass, traceWriter.toString()); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | 357 | ||
| 482 | private void remapSource(Translator translator) { | 358 | EntryMapping mapping = mapper.getDeobfMapping(entry); |
| 483 | if (currentSource == null) { | 359 | if (mapping != null) { |
| 484 | return; | 360 | mapper.mapFromObf(vc, entry, new EntryMapping(mapping.getTargetName(), modifier)); |
| 361 | } else { | ||
| 362 | mapper.mapFromObf(vc, entry, new EntryMapping(entry.getName(), modifier)); | ||
| 485 | } | 363 | } |
| 486 | 364 | ||
| 487 | currentSource.remapSource(project, translator); | 365 | chp.invalidateMapped(); |
| 488 | |||
| 489 | gui.setEditorTheme(Config.getInstance().lookAndFeel); | ||
| 490 | gui.setSource(currentSource); | ||
| 491 | } | ||
| 492 | |||
| 493 | public void modifierChange(ItemEvent event) { | ||
| 494 | if (event.getStateChange() == ItemEvent.SELECTED) { | ||
| 495 | EntryRemapper mapper = project.getMapper(); | ||
| 496 | Entry<?> entry = gui.cursorReference.entry; | ||
| 497 | AccessModifier modifier = (AccessModifier) event.getItem(); | ||
| 498 | |||
| 499 | EntryMapping mapping = mapper.getDeobfMapping(entry); | ||
| 500 | if (mapping != null) { | ||
| 501 | mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier)); | ||
| 502 | } else { | ||
| 503 | mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier)); | ||
| 504 | } | ||
| 505 | |||
| 506 | refreshCurrentClass(); | ||
| 507 | } | ||
| 508 | } | 366 | } |
| 509 | 367 | ||
| 510 | public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { | 368 | public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) { |
| @@ -557,72 +415,71 @@ public class GuiController implements ClientPacketHandler { | |||
| 557 | return rootNode; | 415 | return rootNode; |
| 558 | } | 416 | } |
| 559 | 417 | ||
| 560 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { | 418 | @Override |
| 561 | rename(reference, newName, refreshClassTree, true); | 419 | public void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { |
| 420 | rename(vc, reference, newName, refreshClassTree, false); | ||
| 562 | } | 421 | } |
| 563 | 422 | ||
| 564 | @Override | 423 | public void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean validateOnly) { |
| 565 | public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) { | ||
| 566 | Entry<?> entry = reference.getNameableEntry(); | 424 | Entry<?> entry = reference.getNameableEntry(); |
| 567 | project.getMapper().mapFromObf(entry, new EntryMapping(newName)); | 425 | project.getMapper().mapFromObf(vc, entry, new EntryMapping(newName), true, validateOnly); |
| 568 | 426 | ||
| 569 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | 427 | if (validateOnly || !vc.canProceed()) return; |
| 570 | this.gui.moveClassTree(reference, newName); | ||
| 571 | 428 | ||
| 572 | refreshCurrentClass(jumpToReference ? reference : null); | 429 | if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) |
| 573 | } | 430 | this.gui.moveClassTree(reference.entry, newName); |
| 574 | 431 | ||
| 575 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) { | 432 | chp.invalidateMapped(); |
| 576 | removeMapping(reference, true); | ||
| 577 | } | 433 | } |
| 578 | 434 | ||
| 579 | @Override | 435 | @Override |
| 580 | public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | 436 | public void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) { |
| 581 | project.getMapper().removeByObf(reference.getNameableEntry()); | 437 | project.getMapper().removeByObf(vc, reference.getNameableEntry()); |
| 438 | |||
| 439 | if (!vc.canProceed()) return; | ||
| 582 | 440 | ||
| 583 | if (reference.entry instanceof ClassEntry) | 441 | if (reference.entry instanceof ClassEntry) |
| 584 | this.gui.moveClassTree(reference, false, true); | 442 | this.gui.moveClassTree(reference.entry, false, true); |
| 585 | refreshCurrentClass(jumpToReference ? reference : null); | ||
| 586 | } | ||
| 587 | 443 | ||
| 588 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { | 444 | chp.invalidateMapped(); |
| 589 | changeDocs(reference, updatedDocs, true); | ||
| 590 | } | 445 | } |
| 591 | 446 | ||
| 592 | @Override | 447 | @Override |
| 593 | public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) { | 448 | public void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { |
| 594 | changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs); | 449 | changeDocs(vc, reference, updatedDocs, false); |
| 595 | |||
| 596 | refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS); | ||
| 597 | } | 450 | } |
| 598 | 451 | ||
| 599 | private void changeDoc(Entry<?> obfEntry, String newDoc) { | 452 | public void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean validateOnly) { |
| 600 | EntryRemapper mapper = project.getMapper(); | 453 | changeDoc(vc, reference.entry, updatedDocs, validateOnly); |
| 601 | if (mapper.getDeobfMapping(obfEntry) == null) { | 454 | |
| 602 | markAsDeobfuscated(obfEntry, false); // NPE | 455 | if (validateOnly || !vc.canProceed()) return; |
| 603 | } | 456 | |
| 604 | mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false); | 457 | chp.invalidateJavadoc(reference.getLocationClassEntry()); |
| 605 | } | 458 | } |
| 606 | 459 | ||
| 607 | private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) { | 460 | private void changeDoc(ValidationContext vc, Entry<?> obfEntry, String newDoc, boolean validateOnly) { |
| 608 | EntryRemapper mapper = project.getMapper(); | 461 | EntryRemapper mapper = project.getMapper(); |
| 609 | mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming); | ||
| 610 | } | ||
| 611 | 462 | ||
| 612 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) { | 463 | EntryMapping deobfMapping = mapper.getDeobfMapping(obfEntry); |
| 613 | markAsDeobfuscated(reference, true); | 464 | if (deobfMapping == null) { |
| 465 | deobfMapping = new EntryMapping(mapper.deobfuscate(obfEntry).getName()); | ||
| 466 | } | ||
| 467 | |||
| 468 | mapper.mapFromObf(vc, obfEntry, deobfMapping.withDocs(newDoc), false, validateOnly); | ||
| 614 | } | 469 | } |
| 615 | 470 | ||
| 616 | @Override | 471 | @Override |
| 617 | public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) { | 472 | public void markAsDeobfuscated(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) { |
| 618 | EntryRemapper mapper = project.getMapper(); | 473 | EntryRemapper mapper = project.getMapper(); |
| 619 | Entry<?> entry = reference.getNameableEntry(); | 474 | Entry<?> entry = reference.getNameableEntry(); |
| 620 | mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName())); | 475 | mapper.mapFromObf(vc, entry, new EntryMapping(mapper.deobfuscate(entry).getName())); |
| 476 | |||
| 477 | if (!vc.canProceed()) return; | ||
| 621 | 478 | ||
| 622 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) | 479 | if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) |
| 623 | this.gui.moveClassTree(reference, true, false); | 480 | this.gui.moveClassTree(reference.entry, true, false); |
| 624 | 481 | ||
| 625 | refreshCurrentClass(jumpToReference ? reference : null); | 482 | chp.invalidateMapped(); |
| 626 | } | 483 | } |
| 627 | 484 | ||
| 628 | public void openStats(Set<StatsMember> includedMembers) { | 485 | public void openStats(Set<StatsMember> includedMembers) { |
| @@ -635,7 +492,7 @@ public class GuiController implements ClientPacketHandler { | |||
| 635 | try (FileWriter w = new FileWriter(statsFile)) { | 492 | try (FileWriter w = new FileWriter(statsFile)) { |
| 636 | w.write( | 493 | w.write( |
| 637 | Utils.readResourceToString("/stats.html") | 494 | Utils.readResourceToString("/stats.html") |
| 638 | .replace("/*data*/", data) | 495 | .replace("/*data*/", data) |
| 639 | ); | 496 | ); |
| 640 | } | 497 | } |
| 641 | 498 | ||
| @@ -647,10 +504,11 @@ public class GuiController implements ClientPacketHandler { | |||
| 647 | } | 504 | } |
| 648 | 505 | ||
| 649 | public void setDecompiler(DecompilerService service) { | 506 | public void setDecompiler(DecompilerService service) { |
| 650 | uncommentedSource = null; | 507 | chp.setDecompilerService(service); |
| 651 | decompilerService = service; | 508 | } |
| 652 | decompiler = project.createDecompiler(decompilerService); | 509 | |
| 653 | refreshCurrentClass(null, RefreshMode.FULL); | 510 | public ClassHandleProvider getClassHandleProvider() { |
| 511 | return chp; | ||
| 654 | } | 512 | } |
| 655 | 513 | ||
| 656 | public EnigmaClient getClient() { | 514 | public EnigmaClient getClient() { |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java deleted file mode 100644 index 87cb83b..0000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/RefreshMode.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | public enum RefreshMode { | ||
| 4 | MINIMAL, | ||
| 5 | JAVADOCS, | ||
| 6 | FULL | ||
| 7 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java index 10c418c..4ef0442 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java | |||
| @@ -32,4 +32,5 @@ public class TokenListCellRenderer implements ListCellRenderer<Token> { | |||
| 32 | label.setText(this.controller.getReadableToken(token).toString()); | 32 | label.setText(this.controller.getReadableToken(token).toString()); |
| 33 | return label; | 33 | return label; |
| 34 | } | 34 | } |
| 35 | |||
| 35 | } | 36 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java index 035b238..fd40cb7 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/config/Themes.java | |||
| @@ -1,25 +1,27 @@ | |||
| 1 | package cuchaz.enigma.gui.config; | 1 | package cuchaz.enigma.gui.config; |
| 2 | 2 | ||
| 3 | import java.io.IOException; | 3 | import java.io.IOException; |
| 4 | 4 | import java.util.HashSet; | |
| 5 | import javax.swing.SwingUtilities; | 5 | import java.util.Set; |
| 6 | 6 | ||
| 7 | import com.google.common.collect.ImmutableMap; | 7 | import com.google.common.collect.ImmutableMap; |
| 8 | import cuchaz.enigma.gui.EnigmaSyntaxKit; | 8 | import cuchaz.enigma.gui.EnigmaSyntaxKit; |
| 9 | import cuchaz.enigma.gui.Gui; | 9 | import cuchaz.enigma.gui.events.ThemeChangeListener; |
| 10 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | 10 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; |
| 11 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 12 | import cuchaz.enigma.gui.util.ScaleUtil; | 11 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 12 | import cuchaz.enigma.source.RenamableTokenType; | ||
| 13 | import de.sciss.syntaxpane.DefaultSyntaxKit; | 13 | import de.sciss.syntaxpane.DefaultSyntaxKit; |
| 14 | 14 | ||
| 15 | public class Themes { | 15 | public class Themes { |
| 16 | 16 | ||
| 17 | public static void setLookAndFeel(Gui gui, Config.LookAndFeel lookAndFeel) { | 17 | private static final Set<ThemeChangeListener> listeners = new HashSet<>(); |
| 18 | |||
| 19 | public static void setLookAndFeel(Config.LookAndFeel lookAndFeel) { | ||
| 18 | Config.getInstance().lookAndFeel = lookAndFeel; | 20 | Config.getInstance().lookAndFeel = lookAndFeel; |
| 19 | updateTheme(gui); | 21 | updateTheme(); |
| 20 | } | 22 | } |
| 21 | 23 | ||
| 22 | public static void updateTheme(Gui gui) { | 24 | public static void updateTheme() { |
| 23 | Config config = Config.getInstance(); | 25 | Config config = Config.getInstance(); |
| 24 | config.lookAndFeel.setGlobalLAF(); | 26 | config.lookAndFeel.setGlobalLAF(); |
| 25 | config.lookAndFeel.apply(config); | 27 | config.lookAndFeel.apply(config); |
| @@ -31,15 +33,26 @@ public class Themes { | |||
| 31 | EnigmaSyntaxKit.invalidate(); | 33 | EnigmaSyntaxKit.invalidate(); |
| 32 | DefaultSyntaxKit.initKit(); | 34 | DefaultSyntaxKit.initKit(); |
| 33 | DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); | 35 | DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName()); |
| 34 | gui.boxHighlightPainters = ImmutableMap.of( | 36 | ImmutableMap<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters = getBoxHighlightPainters(); |
| 35 | TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), | 37 | listeners.forEach(l -> l.onThemeChanged(config.lookAndFeel, boxHighlightPainters)); |
| 36 | TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), | ||
| 37 | TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) | ||
| 38 | ); | ||
| 39 | gui.setEditorTheme(config.lookAndFeel); | ||
| 40 | SwingUtilities.updateComponentTreeUI(gui.getFrame()); | ||
| 41 | ScaleUtil.applyScaling(); | 38 | ScaleUtil.applyScaling(); |
| 42 | } | 39 | } |
| 43 | 40 | ||
| 41 | public static ImmutableMap<RenamableTokenType, BoxHighlightPainter> getBoxHighlightPainters() { | ||
| 42 | Config config = Config.getInstance(); | ||
| 43 | return ImmutableMap.of( | ||
| 44 | RenamableTokenType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline), | ||
| 45 | RenamableTokenType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline), | ||
| 46 | RenamableTokenType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline) | ||
| 47 | ); | ||
| 48 | } | ||
| 49 | |||
| 50 | public static void addListener(ThemeChangeListener listener) { | ||
| 51 | listeners.add(listener); | ||
| 52 | } | ||
| 53 | |||
| 54 | public static void removeListener(ThemeChangeListener listener) { | ||
| 55 | listeners.remove(listener); | ||
| 56 | } | ||
| 44 | 57 | ||
| 45 | } | 58 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java index d81460a..9fbe65a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java | |||
| @@ -19,34 +19,64 @@ import javax.swing.*; | |||
| 19 | import javax.swing.text.html.HTML; | 19 | import javax.swing.text.html.HTML; |
| 20 | 20 | ||
| 21 | import java.awt.*; | 21 | import java.awt.*; |
| 22 | import java.awt.BorderLayout; | ||
| 23 | import java.awt.Container; | ||
| 24 | import java.awt.FlowLayout; | ||
| 22 | import java.awt.event.KeyAdapter; | 25 | import java.awt.event.KeyAdapter; |
| 23 | import java.awt.event.KeyEvent; | 26 | import java.awt.event.KeyEvent; |
| 24 | 27 | ||
| 28 | import javax.swing.*; | ||
| 29 | |||
| 30 | import com.google.common.base.Strings; | ||
| 31 | import cuchaz.enigma.analysis.EntryReference; | ||
| 32 | import cuchaz.enigma.gui.GuiController; | ||
| 33 | import cuchaz.enigma.gui.elements.ValidatableTextArea; | ||
| 34 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 35 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 36 | import cuchaz.enigma.network.packet.ChangeDocsC2SPacket; | ||
| 37 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 38 | import cuchaz.enigma.utils.I18n; | ||
| 39 | import cuchaz.enigma.utils.validation.Message; | ||
| 40 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 41 | |||
| 25 | public class JavadocDialog { | 42 | public class JavadocDialog { |
| 26 | 43 | ||
| 27 | private static JavadocDialog instance = null; | 44 | private final JDialog ui; |
| 45 | private final GuiController controller; | ||
| 46 | private final EntryReference<Entry<?>, Entry<?>> entry; | ||
| 28 | 47 | ||
| 29 | private JFrame frame; | 48 | private final ValidatableTextArea text; |
| 30 | 49 | ||
| 31 | private JavadocDialog(JFrame parent, JTextArea text, Callback callback) { | 50 | private final ValidationContext vc = new ValidationContext(); |
| 32 | // init frame | 51 | |
| 33 | frame = new JFrame(I18n.translate("javadocs.edit")); | 52 | private JavadocDialog(JFrame parent, GuiController controller, EntryReference<Entry<?>, Entry<?>> entry, String preset) { |
| 34 | final Container pane = frame.getContentPane(); | 53 | this.ui = new JDialog(parent, I18n.translate("javadocs.edit")); |
| 35 | pane.setLayout(new BorderLayout()); | 54 | this.controller = controller; |
| 55 | this.entry = entry; | ||
| 56 | this.text = new ValidatableTextArea(10, 40); | ||
| 57 | |||
| 58 | // set up dialog | ||
| 59 | Container contentPane = ui.getContentPane(); | ||
| 60 | contentPane.setLayout(new BorderLayout()); | ||
| 36 | 61 | ||
| 37 | // editor panel | 62 | // editor panel |
| 38 | text.setTabSize(2); | 63 | this.text.setText(preset); |
| 39 | pane.add(new JScrollPane(text), BorderLayout.CENTER); | 64 | this.text.setTabSize(2); |
| 40 | text.addKeyListener(new KeyAdapter() { | 65 | contentPane.add(new JScrollPane(this.text), BorderLayout.CENTER); |
| 66 | this.text.addKeyListener(new KeyAdapter() { | ||
| 41 | @Override | 67 | @Override |
| 42 | public void keyPressed(KeyEvent event) { | 68 | public void keyPressed(KeyEvent event) { |
| 43 | switch (event.getKeyCode()) { | 69 | switch (event.getKeyCode()) { |
| 44 | case KeyEvent.VK_ENTER: | 70 | case KeyEvent.VK_ENTER: |
| 45 | if (event.isControlDown()) | 71 | if (event.isControlDown()) { |
| 46 | callback.closeUi(frame, true); | 72 | doSave(); |
| 73 | if (vc.canProceed()) { | ||
| 74 | close(); | ||
| 75 | } | ||
| 76 | } | ||
| 47 | break; | 77 | break; |
| 48 | case KeyEvent.VK_ESCAPE: | 78 | case KeyEvent.VK_ESCAPE: |
| 49 | callback.closeUi(frame, false); | 79 | close(); |
| 50 | break; | 80 | break; |
| 51 | default: | 81 | default: |
| 52 | break; | 82 | break; |
| @@ -56,23 +86,15 @@ public class JavadocDialog { | |||
| 56 | 86 | ||
| 57 | // buttons panel | 87 | // buttons panel |
| 58 | JPanel buttonsPanel = new JPanel(); | 88 | JPanel buttonsPanel = new JPanel(); |
| 59 | FlowLayout buttonsLayout = new FlowLayout(); | 89 | buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); |
| 60 | buttonsLayout.setAlignment(FlowLayout.RIGHT); | ||
| 61 | buttonsPanel.setLayout(buttonsLayout); | ||
| 62 | buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); | 90 | buttonsPanel.add(GuiUtil.unboldLabel(new JLabel(I18n.translate("javadocs.instruction")))); |
| 63 | JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); | 91 | JButton cancelButton = new JButton(I18n.translate("javadocs.cancel")); |
| 64 | cancelButton.addActionListener(event -> { | 92 | cancelButton.addActionListener(event -> close()); |
| 65 | // close (hide) the dialog | ||
| 66 | callback.closeUi(frame, false); | ||
| 67 | }); | ||
| 68 | buttonsPanel.add(cancelButton); | 93 | buttonsPanel.add(cancelButton); |
| 69 | JButton saveButton = new JButton(I18n.translate("javadocs.save")); | 94 | JButton saveButton = new JButton(I18n.translate("javadocs.save")); |
| 70 | saveButton.addActionListener(event -> { | 95 | saveButton.addActionListener(event -> doSave()); |
| 71 | // exit enigma | ||
| 72 | callback.closeUi(frame, true); | ||
| 73 | }); | ||
| 74 | buttonsPanel.add(saveButton); | 96 | buttonsPanel.add(saveButton); |
| 75 | pane.add(buttonsPanel, BorderLayout.SOUTH); | 97 | contentPane.add(buttonsPanel, BorderLayout.SOUTH); |
| 76 | 98 | ||
| 77 | // tags panel | 99 | // tags panel |
| 78 | JMenuBar tagsMenu = new JMenuBar(); | 100 | JMenuBar tagsMenu = new JMenuBar(); |
| @@ -116,22 +138,56 @@ public class JavadocDialog { | |||
| 116 | }); | 138 | }); |
| 117 | tagsMenu.add(htmlList); | 139 | tagsMenu.add(htmlList); |
| 118 | 140 | ||
| 119 | pane.add(tagsMenu, BorderLayout.NORTH); | 141 | contentPane.add(tagsMenu, BorderLayout.NORTH); |
| 120 | 142 | ||
| 121 | // show the frame | 143 | // show the frame |
| 122 | frame.setSize(ScaleUtil.getDimension(600, 400)); | 144 | this.ui.setSize(ScaleUtil.getDimension(600, 400)); |
| 123 | frame.setLocationRelativeTo(parent); | 145 | this.ui.setLocationRelativeTo(parent); |
| 124 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); | 146 | this.ui.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); |
| 125 | } | 147 | } |
| 126 | 148 | ||
| 127 | public static void init(JFrame parent, JTextArea area, Callback callback) { | 149 | // Called when the "Save" button gets clicked. |
| 128 | instance = new JavadocDialog(parent, area, callback); | 150 | public void doSave() { |
| 129 | instance.frame.doLayout(); | 151 | vc.reset(); |
| 130 | instance.frame.setVisible(true); | 152 | validate(); |
| 153 | if (!vc.canProceed()) return; | ||
| 154 | save(); | ||
| 155 | if (!vc.canProceed()) return; | ||
| 156 | close(); | ||
| 131 | } | 157 | } |
| 132 | 158 | ||
| 133 | public interface Callback { | 159 | public void close() { |
| 134 | void closeUi(JFrame frame, boolean save); | 160 | this.ui.setVisible(false); |
| 161 | this.ui.dispose(); | ||
| 162 | } | ||
| 163 | |||
| 164 | public void validate() { | ||
| 165 | vc.setActiveElement(text); | ||
| 166 | |||
| 167 | if (text.getText().contains("*/")) { | ||
| 168 | vc.raise(Message.ILLEGAL_DOC_COMMENT_END); | ||
| 169 | } | ||
| 170 | |||
| 171 | controller.changeDocs(vc, entry, text.getText(), true); | ||
| 172 | } | ||
| 173 | |||
| 174 | public void save() { | ||
| 175 | vc.setActiveElement(text); | ||
| 176 | controller.changeDocs(vc, entry, text.getText()); | ||
| 177 | |||
| 178 | if (!vc.canProceed()) return; | ||
| 179 | |||
| 180 | controller.sendPacket(new ChangeDocsC2SPacket(entry.getNameableEntry(), text.getText())); | ||
| 181 | } | ||
| 182 | |||
| 183 | public static void show(JFrame parent, GuiController controller, EntryReference<Entry<?>, Entry<?>> entry) { | ||
| 184 | EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(entry); | ||
| 185 | String text = Strings.nullToEmpty(translatedReference.entry.getJavadocs()); | ||
| 186 | |||
| 187 | JavadocDialog dialog = new JavadocDialog(parent, controller, entry, text); | ||
| 188 | dialog.ui.doLayout(); | ||
| 189 | dialog.ui.setVisible(true); | ||
| 190 | dialog.text.grabFocus(); | ||
| 135 | } | 191 | } |
| 136 | 192 | ||
| 137 | private enum JavadocTag { | 193 | private enum JavadocTag { |
| @@ -156,4 +212,5 @@ public class JavadocDialog { | |||
| 156 | return this.inline; | 212 | return this.inline; |
| 157 | } | 213 | } |
| 158 | } | 214 | } |
| 215 | |||
| 159 | } | 216 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ConvertingTextField.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ConvertingTextField.java new file mode 100644 index 0000000..6f28949 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ConvertingTextField.java | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.GridLayout; | ||
| 4 | import java.awt.event.*; | ||
| 5 | import java.util.HashSet; | ||
| 6 | import java.util.Set; | ||
| 7 | |||
| 8 | import javax.swing.BorderFactory; | ||
| 9 | import javax.swing.JLabel; | ||
| 10 | import javax.swing.JPanel; | ||
| 11 | import javax.swing.text.Document; | ||
| 12 | |||
| 13 | import cuchaz.enigma.gui.events.ConvertingTextFieldListener; | ||
| 14 | import cuchaz.enigma.gui.util.GuiUtil; | ||
| 15 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | ||
| 16 | import cuchaz.enigma.utils.validation.Validatable; | ||
| 17 | |||
| 18 | /** | ||
| 19 | * A label that converts into an editable text field when you click it. | ||
| 20 | */ | ||
| 21 | public class ConvertingTextField implements Validatable { | ||
| 22 | |||
| 23 | private final JPanel ui; | ||
| 24 | private final ValidatableTextField textField; | ||
| 25 | private final JLabel label; | ||
| 26 | private boolean isEditing = false; | ||
| 27 | |||
| 28 | private final Set<ConvertingTextFieldListener> listeners = new HashSet<>(); | ||
| 29 | |||
| 30 | public ConvertingTextField(String text) { | ||
| 31 | this.ui = new JPanel(); | ||
| 32 | this.ui.setLayout(new GridLayout(1, 1, 0, 0)); | ||
| 33 | this.textField = new ValidatableTextField(text); | ||
| 34 | this.label = GuiUtil.unboldLabel(new JLabel(text)); | ||
| 35 | this.label.setBorder(BorderFactory.createLoweredBevelBorder()); | ||
| 36 | |||
| 37 | this.label.addMouseListener(new MouseAdapter() { | ||
| 38 | @Override | ||
| 39 | public void mouseClicked(MouseEvent e) { | ||
| 40 | startEditing(); | ||
| 41 | } | ||
| 42 | }); | ||
| 43 | |||
| 44 | this.textField.addFocusListener(new FocusAdapter() { | ||
| 45 | @Override | ||
| 46 | public void focusLost(FocusEvent e) { | ||
| 47 | if (!hasChanges()) { | ||
| 48 | stopEditing(true); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | }); | ||
| 52 | |||
| 53 | this.textField.addKeyListener(new KeyAdapter() { | ||
| 54 | @Override | ||
| 55 | public void keyPressed(KeyEvent e) { | ||
| 56 | if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { | ||
| 57 | stopEditing(true); | ||
| 58 | } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { | ||
| 59 | stopEditing(false); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | }); | ||
| 63 | |||
| 64 | this.ui.add(this.label); | ||
| 65 | } | ||
| 66 | |||
| 67 | public void startEditing() { | ||
| 68 | if (isEditing) return; | ||
| 69 | this.ui.removeAll(); | ||
| 70 | this.ui.add(this.textField); | ||
| 71 | this.isEditing = true; | ||
| 72 | this.ui.validate(); | ||
| 73 | this.ui.repaint(); | ||
| 74 | this.textField.requestFocusInWindow(); | ||
| 75 | this.textField.selectAll(); | ||
| 76 | this.listeners.forEach(l -> l.onStartEditing(this)); | ||
| 77 | } | ||
| 78 | |||
| 79 | public void stopEditing(boolean abort) { | ||
| 80 | if (!isEditing) return; | ||
| 81 | |||
| 82 | if (!listeners.stream().allMatch(l -> l.tryStopEditing(this, abort))) return; | ||
| 83 | |||
| 84 | if (abort) { | ||
| 85 | this.textField.setText(this.label.getText()); | ||
| 86 | } else { | ||
| 87 | this.label.setText(this.textField.getText()); | ||
| 88 | } | ||
| 89 | |||
| 90 | this.ui.removeAll(); | ||
| 91 | this.ui.add(this.label); | ||
| 92 | this.isEditing = false; | ||
| 93 | this.ui.validate(); | ||
| 94 | this.ui.repaint(); | ||
| 95 | this.listeners.forEach(l -> l.onStopEditing(this, abort)); | ||
| 96 | } | ||
| 97 | |||
| 98 | public void setText(String text) { | ||
| 99 | stopEditing(true); | ||
| 100 | this.label.setText(text); | ||
| 101 | this.textField.setText(text); | ||
| 102 | } | ||
| 103 | |||
| 104 | public void setEditText(String text) { | ||
| 105 | if (!isEditing) return; | ||
| 106 | |||
| 107 | this.textField.setText(text); | ||
| 108 | } | ||
| 109 | |||
| 110 | public void selectAll() { | ||
| 111 | if (!isEditing) return; | ||
| 112 | |||
| 113 | this.textField.selectAll(); | ||
| 114 | } | ||
| 115 | |||
| 116 | public void selectSubstring(int startIndex) { | ||
| 117 | if (!isEditing) return; | ||
| 118 | |||
| 119 | Document doc = this.textField.getDocument(); | ||
| 120 | if (doc != null) { | ||
| 121 | this.selectSubstring(startIndex, doc.getLength()); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | public void selectSubstring(int startIndex, int endIndex) { | ||
| 126 | if (!isEditing) return; | ||
| 127 | |||
| 128 | this.textField.select(startIndex, endIndex); | ||
| 129 | } | ||
| 130 | |||
| 131 | public String getText() { | ||
| 132 | if (isEditing) { | ||
| 133 | return this.textField.getText(); | ||
| 134 | } else { | ||
| 135 | return this.label.getText(); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | |||
| 139 | public String getPersistentText() { | ||
| 140 | return this.label.getText(); | ||
| 141 | } | ||
| 142 | |||
| 143 | public boolean hasChanges() { | ||
| 144 | if (!isEditing) return false; | ||
| 145 | return !this.textField.getText().equals(this.label.getText()); | ||
| 146 | } | ||
| 147 | |||
| 148 | @Override | ||
| 149 | public void addMessage(ParameterizedMessage message) { | ||
| 150 | textField.addMessage(message); | ||
| 151 | } | ||
| 152 | |||
| 153 | @Override | ||
| 154 | public void clearMessages() { | ||
| 155 | textField.clearMessages(); | ||
| 156 | } | ||
| 157 | |||
| 158 | public void addListener(ConvertingTextFieldListener listener) { | ||
| 159 | this.listeners.add(listener); | ||
| 160 | } | ||
| 161 | |||
| 162 | public void removeListener(ConvertingTextFieldListener listener) { | ||
| 163 | this.listeners.remove(listener); | ||
| 164 | } | ||
| 165 | |||
| 166 | public JPanel getUi() { | ||
| 167 | return ui; | ||
| 168 | } | ||
| 169 | |||
| 170 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java new file mode 100644 index 0000000..e92e677 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/EditorTabPopupMenu.java | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | import java.awt.event.KeyEvent; | ||
| 5 | |||
| 6 | import javax.swing.JMenuItem; | ||
| 7 | import javax.swing.JPopupMenu; | ||
| 8 | import javax.swing.KeyStroke; | ||
| 9 | |||
| 10 | import cuchaz.enigma.gui.Gui; | ||
| 11 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 12 | import cuchaz.enigma.utils.I18n; | ||
| 13 | |||
| 14 | public class EditorTabPopupMenu { | ||
| 15 | |||
| 16 | private final JPopupMenu ui; | ||
| 17 | private final JMenuItem close; | ||
| 18 | private final JMenuItem closeAll; | ||
| 19 | private final JMenuItem closeOthers; | ||
| 20 | private final JMenuItem closeLeft; | ||
| 21 | private final JMenuItem closeRight; | ||
| 22 | |||
| 23 | private final Gui gui; | ||
| 24 | private PanelEditor editor; | ||
| 25 | |||
| 26 | public EditorTabPopupMenu(Gui gui) { | ||
| 27 | this.gui = gui; | ||
| 28 | |||
| 29 | this.ui = new JPopupMenu(); | ||
| 30 | |||
| 31 | this.close = new JMenuItem(I18n.translate("popup_menu.editor_tab.close")); | ||
| 32 | this.close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_4, KeyEvent.CTRL_DOWN_MASK)); | ||
| 33 | this.close.addActionListener(a -> gui.closeEditor(editor)); | ||
| 34 | this.ui.add(this.close); | ||
| 35 | |||
| 36 | this.closeAll = new JMenuItem(I18n.translate("popup_menu.editor_tab.close_all")); | ||
| 37 | this.closeAll.addActionListener(a -> gui.closeAllEditorTabs()); | ||
| 38 | this.ui.add(this.closeAll); | ||
| 39 | |||
| 40 | this.closeOthers = new JMenuItem(I18n.translate("popup_menu.editor_tab.close_others")); | ||
| 41 | this.closeOthers.addActionListener(a -> gui.closeTabsExcept(editor)); | ||
| 42 | this.ui.add(this.closeOthers); | ||
| 43 | |||
| 44 | this.closeLeft = new JMenuItem(I18n.translate("popup_menu.editor_tab.close_left")); | ||
| 45 | this.closeLeft.addActionListener(a -> gui.closeTabsLeftOf(editor)); | ||
| 46 | this.ui.add(this.closeLeft); | ||
| 47 | |||
| 48 | this.closeRight = new JMenuItem(I18n.translate("popup_menu.editor_tab.close_right")); | ||
| 49 | this.closeRight.addActionListener(a -> gui.closeTabsRightOf(editor)); | ||
| 50 | this.ui.add(this.closeRight); | ||
| 51 | } | ||
| 52 | |||
| 53 | public void show(Component invoker, int x, int y, PanelEditor panelEditor) { | ||
| 54 | this.editor = panelEditor; | ||
| 55 | ui.show(invoker, x, y); | ||
| 56 | } | ||
| 57 | |||
| 58 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java new file mode 100644 index 0000000..533d1b3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/JMultiLineToolTip.java | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Dimension; | ||
| 4 | import java.awt.Font; | ||
| 5 | import java.awt.Graphics; | ||
| 6 | |||
| 7 | import javax.swing.CellRendererPane; | ||
| 8 | import javax.swing.JComponent; | ||
| 9 | import javax.swing.JTextArea; | ||
| 10 | import javax.swing.JToolTip; | ||
| 11 | import javax.swing.plaf.ComponentUI; | ||
| 12 | import javax.swing.plaf.basic.BasicToolTipUI; | ||
| 13 | |||
| 14 | /** | ||
| 15 | * Implements a multi line tooltip for GUI components | ||
| 16 | * Copied from http://www.codeguru.com/java/articles/122.shtml | ||
| 17 | * | ||
| 18 | * @author Zafir Anjum | ||
| 19 | */ | ||
| 20 | public class JMultiLineToolTip extends JToolTip { | ||
| 21 | |||
| 22 | private static final long serialVersionUID = 7813662474312183098L; | ||
| 23 | |||
| 24 | public JMultiLineToolTip() { | ||
| 25 | updateUI(); | ||
| 26 | } | ||
| 27 | |||
| 28 | public void updateUI() { | ||
| 29 | setUI(MultiLineToolTipUI.createUI(this)); | ||
| 30 | } | ||
| 31 | |||
| 32 | public void setColumns(int columns) { | ||
| 33 | this.columns = columns; | ||
| 34 | this.fixedwidth = 0; | ||
| 35 | } | ||
| 36 | |||
| 37 | public int getColumns() { | ||
| 38 | return columns; | ||
| 39 | } | ||
| 40 | |||
| 41 | public void setFixedWidth(int width) { | ||
| 42 | this.fixedwidth = width; | ||
| 43 | this.columns = 0; | ||
| 44 | } | ||
| 45 | |||
| 46 | public int getFixedWidth() { | ||
| 47 | return fixedwidth; | ||
| 48 | } | ||
| 49 | |||
| 50 | protected int columns = 0; | ||
| 51 | protected int fixedwidth = 0; | ||
| 52 | } | ||
| 53 | |||
| 54 | /** | ||
| 55 | * UI for multi line tool tip | ||
| 56 | */ | ||
| 57 | class MultiLineToolTipUI extends BasicToolTipUI { | ||
| 58 | |||
| 59 | static MultiLineToolTipUI sharedInstance = new MultiLineToolTipUI(); | ||
| 60 | Font smallFont; | ||
| 61 | static JToolTip tip; | ||
| 62 | protected CellRendererPane rendererPane; | ||
| 63 | |||
| 64 | private static JTextArea textArea; | ||
| 65 | |||
| 66 | public static ComponentUI createUI(JComponent c) { | ||
| 67 | return sharedInstance; | ||
| 68 | } | ||
| 69 | |||
| 70 | public MultiLineToolTipUI() { | ||
| 71 | super(); | ||
| 72 | } | ||
| 73 | |||
| 74 | public void installUI(JComponent c) { | ||
| 75 | super.installUI(c); | ||
| 76 | tip = (JToolTip) c; | ||
| 77 | rendererPane = new CellRendererPane(); | ||
| 78 | c.add(rendererPane); | ||
| 79 | } | ||
| 80 | |||
| 81 | public void uninstallUI(JComponent c) { | ||
| 82 | super.uninstallUI(c); | ||
| 83 | |||
| 84 | c.remove(rendererPane); | ||
| 85 | rendererPane = null; | ||
| 86 | } | ||
| 87 | |||
| 88 | public void paint(Graphics g, JComponent c) { | ||
| 89 | Dimension size = c.getSize(); | ||
| 90 | textArea.setBackground(c.getBackground()); | ||
| 91 | rendererPane.paintComponent(g, textArea, c, 1, 1, size.width - 1, size.height - 1, true); | ||
| 92 | } | ||
| 93 | |||
| 94 | public Dimension getPreferredSize(JComponent c) { | ||
| 95 | String tipText = ((JToolTip) c).getTipText(); | ||
| 96 | if (tipText == null) return new Dimension(0, 0); | ||
| 97 | textArea = new JTextArea(tipText); | ||
| 98 | rendererPane.removeAll(); | ||
| 99 | rendererPane.add(textArea); | ||
| 100 | textArea.setWrapStyleWord(true); | ||
| 101 | int width = ((JMultiLineToolTip) c).getFixedWidth(); | ||
| 102 | int columns = ((JMultiLineToolTip) c).getColumns(); | ||
| 103 | |||
| 104 | if (columns > 0) { | ||
| 105 | textArea.setColumns(columns); | ||
| 106 | textArea.setSize(0, 0); | ||
| 107 | textArea.setLineWrap(true); | ||
| 108 | textArea.setSize(textArea.getPreferredSize()); | ||
| 109 | } else if (width > 0) { | ||
| 110 | textArea.setLineWrap(true); | ||
| 111 | Dimension d = textArea.getPreferredSize(); | ||
| 112 | d.width = width; | ||
| 113 | d.height++; | ||
| 114 | textArea.setSize(d); | ||
| 115 | } else | ||
| 116 | textArea.setLineWrap(false); | ||
| 117 | |||
| 118 | Dimension dim = textArea.getPreferredSize(); | ||
| 119 | |||
| 120 | dim.height += 1; | ||
| 121 | dim.width += 1; | ||
| 122 | return dim; | ||
| 123 | } | ||
| 124 | |||
| 125 | public Dimension getMinimumSize(JComponent c) { | ||
| 126 | return getPreferredSize(c); | ||
| 127 | } | ||
| 128 | |||
| 129 | public Dimension getMaximumSize(JComponent c) { | ||
| 130 | return getPreferredSize(c); | ||
| 131 | } | ||
| 132 | } \ No newline at end of file | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 948798a..9b06d26 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java | |||
| @@ -321,7 +321,7 @@ public class MenuBar { | |||
| 321 | if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { | 321 | if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) { |
| 322 | themeButton.setSelected(true); | 322 | themeButton.setSelected(true); |
| 323 | } | 323 | } |
| 324 | themeButton.addActionListener(_e -> Themes.setLookAndFeel(gui, lookAndFeel)); | 324 | themeButton.addActionListener(_e -> Themes.setLookAndFeel(lookAndFeel)); |
| 325 | themesMenu.add(themeButton); | 325 | themesMenu.add(themeButton); |
| 326 | } | 326 | } |
| 327 | } | 327 | } |
| @@ -335,7 +335,12 @@ public class MenuBar { | |||
| 335 | languageButton.setSelected(true); | 335 | languageButton.setSelected(true); |
| 336 | } | 336 | } |
| 337 | languageButton.addActionListener(event -> { | 337 | languageButton.addActionListener(event -> { |
| 338 | I18n.setLanguage(lang); | 338 | Config.getInstance().language = lang; |
| 339 | try { | ||
| 340 | Config.getInstance().saveConfig(); | ||
| 341 | } catch (IOException e) { | ||
| 342 | e.printStackTrace(); | ||
| 343 | } | ||
| 339 | ChangeDialog.show(gui); | 344 | ChangeDialog.show(gui); |
| 340 | }); | 345 | }); |
| 341 | languagesMenu.add(languageButton); | 346 | languagesMenu.add(languageButton); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java index b92041c..2310cf3 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | 1 | package cuchaz.enigma.gui.elements; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.gui.Gui; | 3 | import cuchaz.enigma.gui.Gui; |
| 4 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 4 | import cuchaz.enigma.utils.I18n; | 5 | import cuchaz.enigma.utils.I18n; |
| 5 | 6 | ||
| 6 | import javax.swing.*; | 7 | import javax.swing.*; |
| @@ -20,10 +21,10 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 20 | public final JMenuItem openNextMenu; | 21 | public final JMenuItem openNextMenu; |
| 21 | public final JMenuItem toggleMappingMenu; | 22 | public final JMenuItem toggleMappingMenu; |
| 22 | 23 | ||
| 23 | public PopupMenuBar(Gui gui) { | 24 | public PopupMenuBar(PanelEditor editor, Gui gui) { |
| 24 | { | 25 | { |
| 25 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename")); | 26 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename")); |
| 26 | menu.addActionListener(event -> gui.startRename()); | 27 | menu.addActionListener(event -> gui.startRename(editor)); |
| 27 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); | 28 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); |
| 28 | menu.setEnabled(false); | 29 | menu.setEnabled(false); |
| 29 | this.add(menu); | 30 | this.add(menu); |
| @@ -31,7 +32,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 31 | } | 32 | } |
| 32 | { | 33 | { |
| 33 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc")); | 34 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc")); |
| 34 | menu.addActionListener(event -> gui.startDocChange()); | 35 | menu.addActionListener(event -> gui.startDocChange(editor)); |
| 35 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); | 36 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK)); |
| 36 | menu.setEnabled(false); | 37 | menu.setEnabled(false); |
| 37 | this.add(menu); | 38 | this.add(menu); |
| @@ -39,7 +40,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 39 | } | 40 | } |
| 40 | { | 41 | { |
| 41 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance")); | 42 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance")); |
| 42 | menu.addActionListener(event -> gui.showInheritance()); | 43 | menu.addActionListener(event -> gui.showInheritance(editor)); |
| 43 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK)); | 44 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK)); |
| 44 | menu.setEnabled(false); | 45 | menu.setEnabled(false); |
| 45 | this.add(menu); | 46 | this.add(menu); |
| @@ -47,7 +48,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 47 | } | 48 | } |
| 48 | { | 49 | { |
| 49 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations")); | 50 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations")); |
| 50 | menu.addActionListener(event -> gui.showImplementations()); | 51 | menu.addActionListener(event -> gui.showImplementations(editor)); |
| 51 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK)); | 52 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK)); |
| 52 | menu.setEnabled(false); | 53 | menu.setEnabled(false); |
| 53 | this.add(menu); | 54 | this.add(menu); |
| @@ -55,7 +56,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 55 | } | 56 | } |
| 56 | { | 57 | { |
| 57 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls")); | 58 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls")); |
| 58 | menu.addActionListener(event -> gui.showCalls(true)); | 59 | menu.addActionListener(event -> gui.showCalls(editor, true)); |
| 59 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); | 60 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK)); |
| 60 | menu.setEnabled(false); | 61 | menu.setEnabled(false); |
| 61 | this.add(menu); | 62 | this.add(menu); |
| @@ -63,7 +64,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 63 | } | 64 | } |
| 64 | { | 65 | { |
| 65 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific")); | 66 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific")); |
| 66 | menu.addActionListener(event -> gui.showCalls(false)); | 67 | menu.addActionListener(event -> gui.showCalls(editor, false)); |
| 67 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK)); | 68 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK)); |
| 68 | menu.setEnabled(false); | 69 | menu.setEnabled(false); |
| 69 | this.add(menu); | 70 | this.add(menu); |
| @@ -71,7 +72,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 71 | } | 72 | } |
| 72 | { | 73 | { |
| 73 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration")); | 74 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration")); |
| 74 | menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry)); | 75 | menu.addActionListener(event -> gui.getController().navigateTo(editor.getCursorReference().entry)); |
| 75 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); | 76 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK)); |
| 76 | menu.setEnabled(false); | 77 | menu.setEnabled(false); |
| 77 | this.add(menu); | 78 | this.add(menu); |
| @@ -95,7 +96,7 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 95 | } | 96 | } |
| 96 | { | 97 | { |
| 97 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated")); | 98 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated")); |
| 98 | menu.addActionListener(event -> gui.toggleMapping()); | 99 | menu.addActionListener(event -> gui.toggleMapping(editor)); |
| 99 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); | 100 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK)); |
| 100 | menu.setEnabled(false); | 101 | menu.setEnabled(false); |
| 101 | this.add(menu); | 102 | this.add(menu); |
| @@ -106,19 +107,19 @@ public class PopupMenuBar extends JPopupMenu { | |||
| 106 | } | 107 | } |
| 107 | { | 108 | { |
| 108 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in")); | 109 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in")); |
| 109 | menu.addActionListener(event -> gui.editor.offsetEditorZoom(2)); | 110 | menu.addActionListener(event -> editor.offsetEditorZoom(2)); |
| 110 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK)); | 111 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK)); |
| 111 | this.add(menu); | 112 | this.add(menu); |
| 112 | } | 113 | } |
| 113 | { | 114 | { |
| 114 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out")); | 115 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out")); |
| 115 | menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2)); | 116 | menu.addActionListener(event -> editor.offsetEditorZoom(-2)); |
| 116 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); | 117 | menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK)); |
| 117 | this.add(menu); | 118 | this.add(menu); |
| 118 | } | 119 | } |
| 119 | { | 120 | { |
| 120 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset")); | 121 | JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset")); |
| 121 | menu.addActionListener(event -> gui.editor.resetEditorZoom()); | 122 | menu.addActionListener(event -> editor.resetEditorZoom()); |
| 122 | this.add(menu); | 123 | this.add(menu); |
| 123 | } | 124 | } |
| 124 | } | 125 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatablePasswordField.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatablePasswordField.java new file mode 100644 index 0000000..02e1bc3 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatablePasswordField.java | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Graphics; | ||
| 4 | import java.util.ArrayList; | ||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | import javax.swing.JPasswordField; | ||
| 8 | import javax.swing.JToolTip; | ||
| 9 | import javax.swing.event.DocumentEvent; | ||
| 10 | import javax.swing.event.DocumentListener; | ||
| 11 | import javax.swing.text.Document; | ||
| 12 | |||
| 13 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | ||
| 14 | import cuchaz.enigma.utils.validation.Validatable; | ||
| 15 | |||
| 16 | public class ValidatablePasswordField extends JPasswordField implements Validatable { | ||
| 17 | |||
| 18 | private List<ParameterizedMessage> messages = new ArrayList<>(); | ||
| 19 | private String tooltipText = null; | ||
| 20 | |||
| 21 | public ValidatablePasswordField() { | ||
| 22 | } | ||
| 23 | |||
| 24 | public ValidatablePasswordField(String text) { | ||
| 25 | super(text); | ||
| 26 | } | ||
| 27 | |||
| 28 | public ValidatablePasswordField(int columns) { | ||
| 29 | super(columns); | ||
| 30 | } | ||
| 31 | |||
| 32 | public ValidatablePasswordField(String text, int columns) { | ||
| 33 | super(text, columns); | ||
| 34 | } | ||
| 35 | |||
| 36 | public ValidatablePasswordField(Document doc, String txt, int columns) { | ||
| 37 | super(doc, txt, columns); | ||
| 38 | } | ||
| 39 | |||
| 40 | { | ||
| 41 | getDocument().addDocumentListener(new DocumentListener() { | ||
| 42 | @Override | ||
| 43 | public void insertUpdate(DocumentEvent e) { | ||
| 44 | clearMessages(); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public void removeUpdate(DocumentEvent e) { | ||
| 49 | clearMessages(); | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public void changedUpdate(DocumentEvent e) { | ||
| 54 | clearMessages(); | ||
| 55 | } | ||
| 56 | }); | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public JToolTip createToolTip() { | ||
| 61 | JMultiLineToolTip tooltip = new JMultiLineToolTip(); | ||
| 62 | tooltip.setComponent(this); | ||
| 63 | return tooltip; | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public void setToolTipText(String text) { | ||
| 68 | tooltipText = text; | ||
| 69 | setToolTipText0(); | ||
| 70 | } | ||
| 71 | |||
| 72 | private void setToolTipText0() { | ||
| 73 | super.setToolTipText(ValidatableUi.getTooltipText(tooltipText, messages)); | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public void clearMessages() { | ||
| 78 | messages.clear(); | ||
| 79 | setToolTipText0(); | ||
| 80 | repaint(); | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public void addMessage(ParameterizedMessage message) { | ||
| 85 | messages.add(message); | ||
| 86 | setToolTipText0(); | ||
| 87 | repaint(); | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | public void paint(Graphics g) { | ||
| 92 | super.paint(g); | ||
| 93 | ValidatableUi.drawMarker(this, g, messages); | ||
| 94 | } | ||
| 95 | |||
| 96 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextArea.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextArea.java new file mode 100644 index 0000000..7d1f866 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextArea.java | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Graphics; | ||
| 4 | import java.util.ArrayList; | ||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | import javax.swing.JTextArea; | ||
| 8 | import javax.swing.JToolTip; | ||
| 9 | import javax.swing.event.DocumentEvent; | ||
| 10 | import javax.swing.event.DocumentListener; | ||
| 11 | import javax.swing.text.Document; | ||
| 12 | |||
| 13 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | ||
| 14 | import cuchaz.enigma.utils.validation.Validatable; | ||
| 15 | |||
| 16 | public class ValidatableTextArea extends JTextArea implements Validatable { | ||
| 17 | |||
| 18 | private List<ParameterizedMessage> messages = new ArrayList<>(); | ||
| 19 | private String tooltipText = null; | ||
| 20 | |||
| 21 | public ValidatableTextArea() { | ||
| 22 | } | ||
| 23 | |||
| 24 | public ValidatableTextArea(String text) { | ||
| 25 | super(text); | ||
| 26 | } | ||
| 27 | |||
| 28 | public ValidatableTextArea(int rows, int columns) { | ||
| 29 | super(rows, columns); | ||
| 30 | } | ||
| 31 | |||
| 32 | public ValidatableTextArea(String text, int rows, int columns) { | ||
| 33 | super(text, rows, columns); | ||
| 34 | } | ||
| 35 | |||
| 36 | public ValidatableTextArea(Document doc) { | ||
| 37 | super(doc); | ||
| 38 | } | ||
| 39 | |||
| 40 | public ValidatableTextArea(Document doc, String text, int rows, int columns) { | ||
| 41 | super(doc, text, rows, columns); | ||
| 42 | } | ||
| 43 | |||
| 44 | { | ||
| 45 | getDocument().addDocumentListener(new DocumentListener() { | ||
| 46 | @Override | ||
| 47 | public void insertUpdate(DocumentEvent e) { | ||
| 48 | clearMessages(); | ||
| 49 | } | ||
| 50 | |||
| 51 | @Override | ||
| 52 | public void removeUpdate(DocumentEvent e) { | ||
| 53 | clearMessages(); | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public void changedUpdate(DocumentEvent e) { | ||
| 58 | clearMessages(); | ||
| 59 | } | ||
| 60 | }); | ||
| 61 | } | ||
| 62 | |||
| 63 | @Override | ||
| 64 | public JToolTip createToolTip() { | ||
| 65 | JMultiLineToolTip tooltip = new JMultiLineToolTip(); | ||
| 66 | tooltip.setComponent(this); | ||
| 67 | return tooltip; | ||
| 68 | } | ||
| 69 | |||
| 70 | @Override | ||
| 71 | public void setToolTipText(String text) { | ||
| 72 | tooltipText = text; | ||
| 73 | setToolTipText0(); | ||
| 74 | } | ||
| 75 | |||
| 76 | private void setToolTipText0() { | ||
| 77 | super.setToolTipText(ValidatableUi.getTooltipText(tooltipText, messages)); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public void clearMessages() { | ||
| 82 | messages.clear(); | ||
| 83 | setToolTipText0(); | ||
| 84 | repaint(); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public void addMessage(ParameterizedMessage message) { | ||
| 89 | messages.add(message); | ||
| 90 | setToolTipText0(); | ||
| 91 | repaint(); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public void paint(Graphics g) { | ||
| 96 | super.paint(g); | ||
| 97 | ValidatableUi.drawMarker(this, g, messages); | ||
| 98 | } | ||
| 99 | |||
| 100 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextField.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextField.java new file mode 100644 index 0000000..c114dc1 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableTextField.java | |||
| @@ -0,0 +1,96 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Graphics; | ||
| 4 | import java.util.ArrayList; | ||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | import javax.swing.JTextField; | ||
| 8 | import javax.swing.JToolTip; | ||
| 9 | import javax.swing.event.DocumentEvent; | ||
| 10 | import javax.swing.event.DocumentListener; | ||
| 11 | import javax.swing.text.Document; | ||
| 12 | |||
| 13 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | ||
| 14 | import cuchaz.enigma.utils.validation.Validatable; | ||
| 15 | |||
| 16 | public class ValidatableTextField extends JTextField implements Validatable { | ||
| 17 | |||
| 18 | private List<ParameterizedMessage> messages = new ArrayList<>(); | ||
| 19 | private String tooltipText = null; | ||
| 20 | |||
| 21 | public ValidatableTextField() { | ||
| 22 | } | ||
| 23 | |||
| 24 | public ValidatableTextField(String text) { | ||
| 25 | super(text); | ||
| 26 | } | ||
| 27 | |||
| 28 | public ValidatableTextField(int columns) { | ||
| 29 | super(columns); | ||
| 30 | } | ||
| 31 | |||
| 32 | public ValidatableTextField(String text, int columns) { | ||
| 33 | super(text, columns); | ||
| 34 | } | ||
| 35 | |||
| 36 | public ValidatableTextField(Document doc, String text, int columns) { | ||
| 37 | super(doc, text, columns); | ||
| 38 | } | ||
| 39 | |||
| 40 | { | ||
| 41 | getDocument().addDocumentListener(new DocumentListener() { | ||
| 42 | @Override | ||
| 43 | public void insertUpdate(DocumentEvent e) { | ||
| 44 | clearMessages(); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | public void removeUpdate(DocumentEvent e) { | ||
| 49 | clearMessages(); | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public void changedUpdate(DocumentEvent e) { | ||
| 54 | clearMessages(); | ||
| 55 | } | ||
| 56 | }); | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public JToolTip createToolTip() { | ||
| 61 | JMultiLineToolTip tooltip = new JMultiLineToolTip(); | ||
| 62 | tooltip.setComponent(this); | ||
| 63 | return tooltip; | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public void setToolTipText(String text) { | ||
| 68 | tooltipText = text; | ||
| 69 | setToolTipText0(); | ||
| 70 | } | ||
| 71 | |||
| 72 | private void setToolTipText0() { | ||
| 73 | super.setToolTipText(ValidatableUi.getTooltipText(tooltipText, messages)); | ||
| 74 | } | ||
| 75 | |||
| 76 | @Override | ||
| 77 | public void clearMessages() { | ||
| 78 | messages.clear(); | ||
| 79 | setToolTipText0(); | ||
| 80 | repaint(); | ||
| 81 | } | ||
| 82 | |||
| 83 | @Override | ||
| 84 | public void addMessage(ParameterizedMessage message) { | ||
| 85 | messages.add(message); | ||
| 86 | setToolTipText0(); | ||
| 87 | repaint(); | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | public void paint(Graphics g) { | ||
| 92 | super.paint(g); | ||
| 93 | ValidatableUi.drawMarker(this, g, messages); | ||
| 94 | } | ||
| 95 | |||
| 96 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableUi.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableUi.java new file mode 100644 index 0000000..5df6348 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/ValidatableUi.java | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | package cuchaz.enigma.gui.elements; | ||
| 2 | |||
| 3 | import java.awt.Color; | ||
| 4 | import java.awt.Component; | ||
| 5 | import java.awt.Graphics; | ||
| 6 | import java.util.ArrayList; | ||
| 7 | import java.util.Arrays; | ||
| 8 | import java.util.List; | ||
| 9 | |||
| 10 | import javax.annotation.Nullable; | ||
| 11 | |||
| 12 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 13 | import cuchaz.enigma.utils.validation.ParameterizedMessage; | ||
| 14 | |||
| 15 | public final class ValidatableUi { | ||
| 16 | |||
| 17 | private ValidatableUi() { | ||
| 18 | } | ||
| 19 | |||
| 20 | public static String getTooltipText(String tooltipText, List<ParameterizedMessage> messages) { | ||
| 21 | List<String> strings = new ArrayList<>(); | ||
| 22 | if (tooltipText != null) { | ||
| 23 | strings.add(tooltipText); | ||
| 24 | } | ||
| 25 | if (!messages.isEmpty()) { | ||
| 26 | strings.add("Error(s): "); | ||
| 27 | |||
| 28 | messages.forEach(msg -> { | ||
| 29 | strings.add(String.format(" - %s", msg.getText())); | ||
| 30 | String longDesc = msg.getLongText(); | ||
| 31 | if (!longDesc.isEmpty()) { | ||
| 32 | Arrays.stream(longDesc.split("\n")).map(s -> String.format(" %s", s)).forEach(strings::add); | ||
| 33 | } | ||
| 34 | }); | ||
| 35 | } | ||
| 36 | if (strings.isEmpty()) { | ||
| 37 | return null; | ||
| 38 | } else { | ||
| 39 | return String.join("\n", strings); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | public static String formatMessages(List<ParameterizedMessage> messages) { | ||
| 44 | List<String> strings = new ArrayList<>(); | ||
| 45 | |||
| 46 | if (!messages.isEmpty()) { | ||
| 47 | strings.add("Error(s): "); | ||
| 48 | |||
| 49 | messages.forEach(msg -> { | ||
| 50 | strings.add(String.format(" - %s", msg.getText())); | ||
| 51 | String longDesc = msg.getLongText(); | ||
| 52 | if (!longDesc.isEmpty()) { | ||
| 53 | Arrays.stream(longDesc.split("\n")).map(s -> String.format(" %s", s)).forEach(strings::add); | ||
| 54 | } | ||
| 55 | }); | ||
| 56 | } | ||
| 57 | if (strings.isEmpty()) { | ||
| 58 | return null; | ||
| 59 | } else { | ||
| 60 | return String.join("\n", strings); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | public static void drawMarker(Component self, Graphics g, List<ParameterizedMessage> messages) { | ||
| 65 | Color color = ValidatableUi.getMarkerColor(messages); | ||
| 66 | if (color != null) { | ||
| 67 | g.setColor(color); | ||
| 68 | int x1 = self.getWidth() - ScaleUtil.scale(8) - 1; | ||
| 69 | int x2 = self.getWidth() - ScaleUtil.scale(1) - 1; | ||
| 70 | int y1 = ScaleUtil.scale(1); | ||
| 71 | int y2 = ScaleUtil.scale(8); | ||
| 72 | g.fillPolygon(new int[]{x1, x2, x2}, new int[]{y1, y1, y2}, 3); | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | @Nullable | ||
| 77 | public static Color getMarkerColor(List<ParameterizedMessage> messages) { | ||
| 78 | int level = messages.stream() | ||
| 79 | .mapToInt(ValidatableUi::getMessageLevel) | ||
| 80 | .max().orElse(0); | ||
| 81 | |||
| 82 | switch (level) { | ||
| 83 | case 0: | ||
| 84 | return null; | ||
| 85 | case 1: | ||
| 86 | return Color.BLUE; | ||
| 87 | case 2: | ||
| 88 | return Color.ORANGE; | ||
| 89 | case 3: | ||
| 90 | return Color.RED; | ||
| 91 | } | ||
| 92 | throw new IllegalStateException("unreachable"); | ||
| 93 | } | ||
| 94 | |||
| 95 | private static int getMessageLevel(ParameterizedMessage message) { | ||
| 96 | switch (message.message.type) { | ||
| 97 | case INFO: | ||
| 98 | return 1; | ||
| 99 | case WARNING: | ||
| 100 | return 2; | ||
| 101 | case ERROR: | ||
| 102 | return 3; | ||
| 103 | } | ||
| 104 | throw new IllegalStateException("unreachable"); | ||
| 105 | } | ||
| 106 | |||
| 107 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ConvertingTextFieldListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ConvertingTextFieldListener.java new file mode 100644 index 0000000..6e17fec --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ConvertingTextFieldListener.java | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | package cuchaz.enigma.gui.events; | ||
| 2 | |||
| 3 | import cuchaz.enigma.gui.elements.ConvertingTextField; | ||
| 4 | |||
| 5 | public interface ConvertingTextFieldListener { | ||
| 6 | |||
| 7 | default void onStartEditing(ConvertingTextField field) { | ||
| 8 | } | ||
| 9 | |||
| 10 | default boolean tryStopEditing(ConvertingTextField field, boolean abort) { | ||
| 11 | return true; | ||
| 12 | } | ||
| 13 | |||
| 14 | default void onStopEditing(ConvertingTextField field, boolean abort) { | ||
| 15 | } | ||
| 16 | |||
| 17 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/events/EditorActionListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/EditorActionListener.java new file mode 100644 index 0000000..8880731 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/EditorActionListener.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package cuchaz.enigma.gui.events; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.EntryReference; | ||
| 4 | import cuchaz.enigma.gui.panels.PanelEditor; | ||
| 5 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 6 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 8 | |||
| 9 | public interface EditorActionListener { | ||
| 10 | |||
| 11 | default void onCursorReferenceChanged(PanelEditor editor, EntryReference<Entry<?>, Entry<?>> ref) { | ||
| 12 | } | ||
| 13 | |||
| 14 | default void onClassHandleChanged(PanelEditor editor, ClassEntry old, ClassHandle ch) { | ||
| 15 | } | ||
| 16 | |||
| 17 | default void onTitleChanged(PanelEditor editor, String title) { | ||
| 18 | } | ||
| 19 | |||
| 20 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java new file mode 100644 index 0000000..d4962f7 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/events/ThemeChangeListener.java | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | package cuchaz.enigma.gui.events; | ||
| 2 | |||
| 3 | import java.util.Map; | ||
| 4 | |||
| 5 | import cuchaz.enigma.gui.config.Config.LookAndFeel; | ||
| 6 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | ||
| 7 | import cuchaz.enigma.source.RenamableTokenType; | ||
| 8 | |||
| 9 | public interface ThemeChangeListener { | ||
| 10 | |||
| 11 | void onThemeChanged(LookAndFeel lookAndFeel, Map<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters); | ||
| 12 | |||
| 13 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java index 2e4e462..c899e68 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java | |||
| @@ -19,6 +19,8 @@ import java.awt.*; | |||
| 19 | 19 | ||
| 20 | public class SelectionHighlightPainter implements Highlighter.HighlightPainter { | 20 | public class SelectionHighlightPainter implements Highlighter.HighlightPainter { |
| 21 | 21 | ||
| 22 | public static final SelectionHighlightPainter INSTANCE = new SelectionHighlightPainter(); | ||
| 23 | |||
| 22 | @Override | 24 | @Override |
| 23 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { | 25 | public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) { |
| 24 | // draw a thick border | 26 | // draw a thick border |
| @@ -28,4 +30,5 @@ public class SelectionHighlightPainter implements Highlighter.HighlightPainter { | |||
| 28 | g2d.setStroke(new BasicStroke(2.0f)); | 30 | g2d.setStroke(new BasicStroke(2.0f)); |
| 29 | g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); | 31 | g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4); |
| 30 | } | 32 | } |
| 33 | |||
| 31 | } | 34 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java deleted file mode 100644 index ae23f32..0000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java +++ /dev/null | |||
| @@ -1,7 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui.highlight; | ||
| 2 | |||
| 3 | public enum TokenHighlightType { | ||
| 4 | OBFUSCATED, | ||
| 5 | DEOBFUSCATED, | ||
| 6 | PROPOSED | ||
| 7 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ClosableTabTitlePane.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ClosableTabTitlePane.java new file mode 100644 index 0000000..fe5c857 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ClosableTabTitlePane.java | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | ||
| 2 | |||
| 3 | import java.awt.Component; | ||
| 4 | import java.awt.Dimension; | ||
| 5 | import java.awt.FlowLayout; | ||
| 6 | import java.awt.Point; | ||
| 7 | import java.awt.event.MouseAdapter; | ||
| 8 | import java.awt.event.MouseEvent; | ||
| 9 | |||
| 10 | import javax.accessibility.AccessibleContext; | ||
| 11 | import javax.annotation.Nullable; | ||
| 12 | import javax.swing.*; | ||
| 13 | import javax.swing.border.EmptyBorder; | ||
| 14 | import javax.swing.event.ChangeListener; | ||
| 15 | |||
| 16 | public class ClosableTabTitlePane { | ||
| 17 | |||
| 18 | private final JPanel ui; | ||
| 19 | private final JButton closeButton; | ||
| 20 | private final JLabel label; | ||
| 21 | |||
| 22 | private ChangeListener cachedChangeListener; | ||
| 23 | private JTabbedPane parent; | ||
| 24 | |||
| 25 | public ClosableTabTitlePane(String text, Runnable onClose) { | ||
| 26 | this.ui = new JPanel(new FlowLayout(FlowLayout.CENTER, 2, 2)); | ||
| 27 | this.ui.setOpaque(false); | ||
| 28 | this.label = new JLabel(text); | ||
| 29 | this.ui.add(this.label); | ||
| 30 | |||
| 31 | // Adapted from javax.swing.plaf.metal.MetalTitlePane | ||
| 32 | this.closeButton = new JButton(); | ||
| 33 | this.closeButton.setFocusPainted(false); | ||
| 34 | this.closeButton.setFocusable(false); | ||
| 35 | this.closeButton.setOpaque(true); | ||
| 36 | this.closeButton.setIcon(UIManager.getIcon("InternalFrame.closeIcon")); | ||
| 37 | this.closeButton.putClientProperty("paintActive", Boolean.TRUE); | ||
| 38 | this.closeButton.setBorder(new EmptyBorder(0, 0, 0, 0)); | ||
| 39 | this.closeButton.putClientProperty(AccessibleContext.ACCESSIBLE_NAME_PROPERTY, "Close"); | ||
| 40 | this.closeButton.setMaximumSize(new Dimension(this.closeButton.getIcon().getIconWidth(), this.closeButton.getIcon().getIconHeight())); | ||
| 41 | this.ui.add(this.closeButton); | ||
| 42 | |||
| 43 | // Use mouse listener here so that it also works for disabled buttons | ||
| 44 | closeButton.addMouseListener(new MouseAdapter() { | ||
| 45 | @Override | ||
| 46 | public void mouseClicked(MouseEvent e) { | ||
| 47 | if (SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e)) { | ||
| 48 | onClose.run(); | ||
| 49 | } | ||
| 50 | } | ||
| 51 | }); | ||
| 52 | |||
| 53 | this.ui.addMouseListener(new MouseAdapter() { | ||
| 54 | @Override | ||
| 55 | public void mouseClicked(MouseEvent e) { | ||
| 56 | if (SwingUtilities.isMiddleMouseButton(e)) { | ||
| 57 | onClose.run(); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public void mousePressed(MouseEvent e) { | ||
| 63 | // for some reason registering a mouse listener on this makes | ||
| 64 | // events never go to the tabbed pane, so we have to redirect | ||
| 65 | // the event for tab selection and context menu to work | ||
| 66 | if (parent != null) { | ||
| 67 | Point pt = new Point(e.getXOnScreen(), e.getYOnScreen()); | ||
| 68 | SwingUtilities.convertPointFromScreen(pt, parent); | ||
| 69 | MouseEvent e1 = new MouseEvent( | ||
| 70 | parent, | ||
| 71 | e.getID(), | ||
| 72 | e.getWhen(), | ||
| 73 | e.getModifiersEx(), | ||
| 74 | (int) pt.getX(), | ||
| 75 | (int) pt.getY(), | ||
| 76 | e.getXOnScreen(), | ||
| 77 | e.getYOnScreen(), | ||
| 78 | e.getClickCount(), | ||
| 79 | e.isPopupTrigger(), | ||
| 80 | e.getButton() | ||
| 81 | ); | ||
| 82 | parent.dispatchEvent(e1); | ||
| 83 | } | ||
| 84 | } | ||
| 85 | }); | ||
| 86 | |||
| 87 | this.ui.putClientProperty(ClosableTabTitlePane.class, this); | ||
| 88 | } | ||
| 89 | |||
| 90 | public void setTabbedPane(JTabbedPane pane) { | ||
| 91 | if (this.parent != null) { | ||
| 92 | pane.removeChangeListener(cachedChangeListener); | ||
| 93 | } | ||
| 94 | if (pane != null) { | ||
| 95 | updateState(pane); | ||
| 96 | cachedChangeListener = e -> updateState(pane); | ||
| 97 | pane.addChangeListener(cachedChangeListener); | ||
| 98 | } | ||
| 99 | this.parent = pane; | ||
| 100 | } | ||
| 101 | |||
| 102 | public void setText(String text) { | ||
| 103 | this.label.setText(text); | ||
| 104 | } | ||
| 105 | |||
| 106 | public String getText() { | ||
| 107 | return this.label.getText(); | ||
| 108 | } | ||
| 109 | |||
| 110 | private void updateState(JTabbedPane pane) { | ||
| 111 | int selectedIndex = pane.getSelectedIndex(); | ||
| 112 | boolean isActive = selectedIndex != -1 && pane.getTabComponentAt(selectedIndex) == this.ui; | ||
| 113 | this.closeButton.setEnabled(isActive); | ||
| 114 | this.closeButton.putClientProperty("paintActive", isActive); | ||
| 115 | this.ui.repaint(); | ||
| 116 | } | ||
| 117 | |||
| 118 | public JPanel getUi() { | ||
| 119 | return ui; | ||
| 120 | } | ||
| 121 | |||
| 122 | @Nullable | ||
| 123 | public static ClosableTabTitlePane byUi(Component c) { | ||
| 124 | if (c instanceof JComponent) { | ||
| 125 | Object prop = ((JComponent) c).getClientProperty(ClosableTabTitlePane.class); | ||
| 126 | if (prop instanceof ClosableTabTitlePane) { | ||
| 127 | return (ClosableTabTitlePane) prop; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | return null; | ||
| 131 | } | ||
| 132 | |||
| 133 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java index 346d665..dd9971a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java | |||
| @@ -1,43 +1,124 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | 1 | package cuchaz.enigma.gui.panels; |
| 2 | 2 | ||
| 3 | import java.awt.*; | ||
| 4 | import java.awt.event.*; | ||
| 5 | import java.util.ArrayList; | ||
| 6 | import java.util.Collection; | ||
| 7 | import java.util.List; | ||
| 8 | import java.util.Map; | ||
| 9 | |||
| 10 | import javax.annotation.Nullable; | ||
| 11 | import javax.swing.*; | ||
| 12 | import javax.swing.text.BadLocationException; | ||
| 13 | import javax.swing.text.Document; | ||
| 14 | import javax.swing.text.Highlighter; | ||
| 15 | import javax.swing.text.Highlighter.HighlightPainter; | ||
| 16 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | 17 | import cuchaz.enigma.EnigmaProject; |
| 4 | import cuchaz.enigma.analysis.EntryReference; | 18 | import cuchaz.enigma.analysis.EntryReference; |
| 5 | import cuchaz.enigma.gui.config.Config; | 19 | import cuchaz.enigma.classhandle.ClassHandle; |
| 20 | import cuchaz.enigma.classhandle.ClassHandleError; | ||
| 21 | import cuchaz.enigma.events.ClassHandleListener; | ||
| 6 | import cuchaz.enigma.gui.BrowserCaret; | 22 | import cuchaz.enigma.gui.BrowserCaret; |
| 7 | import cuchaz.enigma.gui.Gui; | 23 | import cuchaz.enigma.gui.Gui; |
| 24 | import cuchaz.enigma.gui.GuiController; | ||
| 25 | import cuchaz.enigma.gui.config.Config; | ||
| 26 | import cuchaz.enigma.gui.config.Themes; | ||
| 27 | import cuchaz.enigma.gui.elements.PopupMenuBar; | ||
| 28 | import cuchaz.enigma.gui.events.EditorActionListener; | ||
| 29 | import cuchaz.enigma.gui.events.ThemeChangeListener; | ||
| 30 | import cuchaz.enigma.gui.highlight.BoxHighlightPainter; | ||
| 31 | import cuchaz.enigma.gui.highlight.SelectionHighlightPainter; | ||
| 32 | import cuchaz.enigma.gui.util.ScaleUtil; | ||
| 33 | import cuchaz.enigma.source.DecompiledClassSource; | ||
| 34 | import cuchaz.enigma.source.RenamableTokenType; | ||
| 35 | import cuchaz.enigma.source.Token; | ||
| 36 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 37 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 38 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 39 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 9 | import cuchaz.enigma.translation.representation.entry.Entry; | 40 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 10 | import cuchaz.enigma.gui.util.ScaleUtil; | 41 | import cuchaz.enigma.translation.representation.entry.FieldEntry; |
| 42 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 43 | import cuchaz.enigma.utils.I18n; | ||
| 44 | import cuchaz.enigma.utils.Result; | ||
| 45 | import de.sciss.syntaxpane.DefaultSyntaxKit; | ||
| 11 | 46 | ||
| 12 | import javax.swing.*; | 47 | public class PanelEditor { |
| 13 | import java.awt.*; | ||
| 14 | import java.awt.event.KeyAdapter; | ||
| 15 | import java.awt.event.KeyEvent; | ||
| 16 | import java.awt.event.MouseAdapter; | ||
| 17 | import java.awt.event.MouseEvent; | ||
| 18 | 48 | ||
| 19 | public class PanelEditor extends JEditorPane { | 49 | private final JPanel ui = new JPanel(); |
| 50 | private final JEditorPane editor = new JEditorPane(); | ||
| 51 | private final JScrollPane editorScrollPane = new JScrollPane(this.editor); | ||
| 52 | private final PopupMenuBar popupMenu; | ||
| 53 | |||
| 54 | // progress UI | ||
| 55 | private final JLabel decompilingLabel = new JLabel(I18n.translate("editor.decompiling"), JLabel.CENTER); | ||
| 56 | private final JProgressBar decompilingProgressBar = new JProgressBar(0, 100); | ||
| 57 | |||
| 58 | // error display UI | ||
| 59 | private final JLabel errorLabel = new JLabel(I18n.translate("editor.decompile_error")); | ||
| 60 | private final JTextArea errorTextArea = new JTextArea(); | ||
| 61 | private final JScrollPane errorScrollPane = new JScrollPane(this.errorTextArea); | ||
| 62 | private final JButton retryButton = new JButton(I18n.translate("general.retry")); | ||
| 63 | |||
| 64 | private DisplayMode mode = DisplayMode.INACTIVE; | ||
| 65 | |||
| 66 | private final GuiController controller; | ||
| 67 | private final Gui gui; | ||
| 68 | |||
| 69 | private EntryReference<Entry<?>, Entry<?>> cursorReference; | ||
| 20 | private boolean mouseIsPressed = false; | 70 | private boolean mouseIsPressed = false; |
| 21 | public int fontSize = 12; | 71 | private boolean shouldNavigateOnClick; |
| 72 | |||
| 73 | public Config.LookAndFeel editorLaf; | ||
| 74 | private int fontSize = 12; | ||
| 75 | private Map<RenamableTokenType, BoxHighlightPainter> boxHighlightPainters; | ||
| 76 | |||
| 77 | private final List<EditorActionListener> listeners = new ArrayList<>(); | ||
| 78 | |||
| 79 | private final ThemeChangeListener themeChangeListener; | ||
| 80 | |||
| 81 | private ClassHandle classHandle; | ||
| 82 | private DecompiledClassSource source; | ||
| 83 | private boolean settingSource; | ||
| 22 | 84 | ||
| 23 | public PanelEditor(Gui gui) { | 85 | public PanelEditor(Gui gui) { |
| 24 | this.setEditable(false); | 86 | this.gui = gui; |
| 25 | this.setSelectionColor(new Color(31, 46, 90)); | 87 | this.controller = gui.getController(); |
| 26 | this.setCaret(new BrowserCaret()); | 88 | |
| 27 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | 89 | this.editor.setEditable(false); |
| 28 | this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed)); | 90 | this.editor.setSelectionColor(new Color(31, 46, 90)); |
| 29 | final PanelEditor self = this; | 91 | this.editor.setCaret(new BrowserCaret()); |
| 30 | this.addMouseListener(new MouseAdapter() { | 92 | this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); |
| 93 | this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); | ||
| 94 | this.editor.setCaretColor(new Color(Config.getInstance().caretColor)); | ||
| 95 | this.editor.setContentType("text/enigma-sources"); | ||
| 96 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 97 | DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit(); | ||
| 98 | kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker"); | ||
| 99 | |||
| 100 | // init editor popup menu | ||
| 101 | this.popupMenu = new PopupMenuBar(this, gui); | ||
| 102 | this.editor.setComponentPopupMenu(this.popupMenu); | ||
| 103 | |||
| 104 | this.decompilingLabel.setFont(ScaleUtil.getFont(this.decompilingLabel.getFont().getFontName(), Font.BOLD, 26)); | ||
| 105 | this.decompilingProgressBar.setIndeterminate(true); | ||
| 106 | this.errorTextArea.setEditable(false); | ||
| 107 | this.errorTextArea.setFont(ScaleUtil.getFont(Font.MONOSPACED, Font.PLAIN, 10)); | ||
| 108 | |||
| 109 | this.boxHighlightPainters = Themes.getBoxHighlightPainters(); | ||
| 110 | |||
| 111 | this.editor.addMouseListener(new MouseAdapter() { | ||
| 31 | @Override | 112 | @Override |
| 32 | public void mousePressed(MouseEvent mouseEvent) { | 113 | public void mousePressed(MouseEvent mouseEvent) { |
| 33 | mouseIsPressed = true; | 114 | PanelEditor.this.mouseIsPressed = true; |
| 34 | } | 115 | } |
| 35 | 116 | ||
| 36 | @Override | 117 | @Override |
| 37 | public void mouseReleased(MouseEvent e) { | 118 | public void mouseReleased(MouseEvent e) { |
| 38 | switch (e.getButton()) { | 119 | switch (e.getButton()) { |
| 39 | case MouseEvent.BUTTON3: // Right click | 120 | case MouseEvent.BUTTON3: // Right click |
| 40 | self.setCaretPosition(self.viewToModel(e.getPoint())); | 121 | PanelEditor.this.editor.setCaretPosition(PanelEditor.this.editor.viewToModel(e.getPoint())); |
| 41 | break; | 122 | break; |
| 42 | 123 | ||
| 43 | case 4: // Back navigation | 124 | case 4: // Back navigation |
| @@ -48,57 +129,59 @@ public class PanelEditor extends JEditorPane { | |||
| 48 | gui.getController().openNextReference(); | 129 | gui.getController().openNextReference(); |
| 49 | break; | 130 | break; |
| 50 | } | 131 | } |
| 51 | mouseIsPressed = false; | 132 | PanelEditor.this.mouseIsPressed = false; |
| 52 | } | 133 | } |
| 53 | }); | 134 | }); |
| 54 | this.addKeyListener(new KeyAdapter() { | 135 | this.editor.addKeyListener(new KeyAdapter() { |
| 55 | @Override | 136 | @Override |
| 56 | public void keyPressed(KeyEvent event) { | 137 | public void keyPressed(KeyEvent event) { |
| 57 | if (event.isControlDown()) { | 138 | if (event.isControlDown()) { |
| 58 | gui.setShouldNavigateOnClick(false); | 139 | PanelEditor.this.shouldNavigateOnClick = false; |
| 59 | switch (event.getKeyCode()) { | 140 | switch (event.getKeyCode()) { |
| 60 | case KeyEvent.VK_I: | 141 | case KeyEvent.VK_I: |
| 61 | gui.popupMenu.showInheritanceMenu.doClick(); | 142 | PanelEditor.this.popupMenu.showInheritanceMenu.doClick(); |
| 62 | break; | 143 | break; |
| 63 | 144 | ||
| 64 | case KeyEvent.VK_M: | 145 | case KeyEvent.VK_M: |
| 65 | gui.popupMenu.showImplementationsMenu.doClick(); | 146 | PanelEditor.this.popupMenu.showImplementationsMenu.doClick(); |
| 66 | break; | 147 | break; |
| 67 | 148 | ||
| 68 | case KeyEvent.VK_N: | 149 | case KeyEvent.VK_N: |
| 69 | gui.popupMenu.openEntryMenu.doClick(); | 150 | PanelEditor.this.popupMenu.openEntryMenu.doClick(); |
| 70 | break; | 151 | break; |
| 71 | 152 | ||
| 72 | case KeyEvent.VK_P: | 153 | case KeyEvent.VK_P: |
| 73 | gui.popupMenu.openPreviousMenu.doClick(); | 154 | PanelEditor.this.popupMenu.openPreviousMenu.doClick(); |
| 74 | break; | 155 | break; |
| 75 | 156 | ||
| 76 | case KeyEvent.VK_E: | 157 | case KeyEvent.VK_E: |
| 77 | gui.popupMenu.openNextMenu.doClick(); | 158 | PanelEditor.this.popupMenu.openNextMenu.doClick(); |
| 78 | break; | 159 | break; |
| 79 | 160 | ||
| 80 | case KeyEvent.VK_C: | 161 | case KeyEvent.VK_C: |
| 81 | if (event.isShiftDown()) { | 162 | if (event.isShiftDown()) { |
| 82 | gui.popupMenu.showCallsSpecificMenu.doClick(); | 163 | PanelEditor.this.popupMenu.showCallsSpecificMenu.doClick(); |
| 83 | } else { | 164 | } else { |
| 84 | gui.popupMenu.showCallsMenu.doClick(); | 165 | PanelEditor.this.popupMenu.showCallsMenu.doClick(); |
| 85 | } | 166 | } |
| 86 | break; | 167 | break; |
| 87 | 168 | ||
| 88 | case KeyEvent.VK_O: | 169 | case KeyEvent.VK_O: |
| 89 | gui.popupMenu.toggleMappingMenu.doClick(); | 170 | PanelEditor.this.popupMenu.toggleMappingMenu.doClick(); |
| 90 | break; | 171 | break; |
| 91 | 172 | ||
| 92 | case KeyEvent.VK_R: | 173 | case KeyEvent.VK_R: |
| 93 | gui.popupMenu.renameMenu.doClick(); | 174 | PanelEditor.this.popupMenu.renameMenu.doClick(); |
| 94 | break; | 175 | break; |
| 95 | 176 | ||
| 96 | case KeyEvent.VK_D: | 177 | case KeyEvent.VK_D: |
| 97 | gui.popupMenu.editJavadocMenu.doClick(); | 178 | PanelEditor.this.popupMenu.editJavadocMenu.doClick(); |
| 98 | break; | 179 | break; |
| 99 | 180 | ||
| 100 | case KeyEvent.VK_F5: | 181 | case KeyEvent.VK_F5: |
| 101 | gui.getController().refreshCurrentClass(); | 182 | if (PanelEditor.this.classHandle != null) { |
| 183 | PanelEditor.this.classHandle.invalidateMapped(); | ||
| 184 | } | ||
| 102 | break; | 185 | break; |
| 103 | 186 | ||
| 104 | case KeyEvent.VK_F: | 187 | case KeyEvent.VK_F: |
| @@ -108,15 +191,15 @@ public class PanelEditor extends JEditorPane { | |||
| 108 | case KeyEvent.VK_ADD: | 191 | case KeyEvent.VK_ADD: |
| 109 | case KeyEvent.VK_EQUALS: | 192 | case KeyEvent.VK_EQUALS: |
| 110 | case KeyEvent.VK_PLUS: | 193 | case KeyEvent.VK_PLUS: |
| 111 | self.offsetEditorZoom(2); | 194 | offsetEditorZoom(2); |
| 112 | break; | 195 | break; |
| 113 | case KeyEvent.VK_SUBTRACT: | 196 | case KeyEvent.VK_SUBTRACT: |
| 114 | case KeyEvent.VK_MINUS: | 197 | case KeyEvent.VK_MINUS: |
| 115 | self.offsetEditorZoom(-2); | 198 | offsetEditorZoom(-2); |
| 116 | break; | 199 | break; |
| 117 | 200 | ||
| 118 | default: | 201 | default: |
| 119 | gui.setShouldNavigateOnClick(true); // CTRL | 202 | PanelEditor.this.shouldNavigateOnClick = true; // CTRL |
| 120 | break; | 203 | break; |
| 121 | } | 204 | } |
| 122 | } | 205 | } |
| @@ -124,11 +207,11 @@ public class PanelEditor extends JEditorPane { | |||
| 124 | 207 | ||
| 125 | @Override | 208 | @Override |
| 126 | public void keyTyped(KeyEvent event) { | 209 | public void keyTyped(KeyEvent event) { |
| 127 | if (!gui.popupMenu.renameMenu.isEnabled()) return; | 210 | if (!PanelEditor.this.popupMenu.renameMenu.isEnabled()) return; |
| 128 | 211 | ||
| 129 | if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) { | 212 | if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) { |
| 130 | EnigmaProject project = gui.getController().project; | 213 | EnigmaProject project = gui.getController().project; |
| 131 | EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference); | 214 | EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(PanelEditor.this.cursorReference); |
| 132 | Entry<?> entry = reference.getNameableEntry(); | 215 | Entry<?> entry = reference.getNameableEntry(); |
| 133 | 216 | ||
| 134 | String name = String.valueOf(event.getKeyChar()); | 217 | String name = String.valueOf(event.getKeyChar()); |
| @@ -139,33 +222,429 @@ public class PanelEditor extends JEditorPane { | |||
| 139 | } | 222 | } |
| 140 | } | 223 | } |
| 141 | 224 | ||
| 142 | gui.popupMenu.renameMenu.doClick(); | 225 | gui.startRename(PanelEditor.this, name); |
| 143 | gui.renameTextField.setText(name); | ||
| 144 | } | 226 | } |
| 145 | } | 227 | } |
| 146 | 228 | ||
| 147 | @Override | 229 | @Override |
| 148 | public void keyReleased(KeyEvent event) { | 230 | public void keyReleased(KeyEvent event) { |
| 149 | gui.setShouldNavigateOnClick(event.isControlDown()); | 231 | PanelEditor.this.shouldNavigateOnClick = event.isControlDown(); |
| 232 | } | ||
| 233 | }); | ||
| 234 | |||
| 235 | this.retryButton.addActionListener(_e -> redecompileClass()); | ||
| 236 | |||
| 237 | this.themeChangeListener = (laf, boxHighlightPainters) -> { | ||
| 238 | if ((this.editorLaf == null || this.editorLaf != laf)) { | ||
| 239 | this.editor.updateUI(); | ||
| 240 | this.editor.setBackground(new Color(Config.getInstance().editorBackground)); | ||
| 241 | if (this.editorLaf != null) { | ||
| 242 | this.classHandle.invalidateMapped(); | ||
| 243 | } | ||
| 244 | |||
| 245 | this.editorLaf = laf; | ||
| 246 | } | ||
| 247 | this.boxHighlightPainters = boxHighlightPainters; | ||
| 248 | }; | ||
| 249 | |||
| 250 | this.ui.putClientProperty(PanelEditor.class, this); | ||
| 251 | } | ||
| 252 | |||
| 253 | @Nullable | ||
| 254 | public static PanelEditor byUi(Component ui) { | ||
| 255 | if (ui instanceof JComponent) { | ||
| 256 | Object prop = ((JComponent) ui).getClientProperty(PanelEditor.class); | ||
| 257 | if (prop instanceof PanelEditor) { | ||
| 258 | return (PanelEditor) prop; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | return null; | ||
| 262 | } | ||
| 263 | |||
| 264 | public void setClassHandle(ClassHandle handle) { | ||
| 265 | ClassEntry old = null; | ||
| 266 | if (this.classHandle != null) { | ||
| 267 | old = this.classHandle.getRef(); | ||
| 268 | this.classHandle.close(); | ||
| 269 | } | ||
| 270 | setClassHandle0(old, handle); | ||
| 271 | } | ||
| 272 | |||
| 273 | private void setClassHandle0(ClassEntry old, ClassHandle handle) { | ||
| 274 | this.setDisplayMode(DisplayMode.IN_PROGRESS); | ||
| 275 | setCursorReference(null); | ||
| 276 | |||
| 277 | handle.addListener(new ClassHandleListener() { | ||
| 278 | @Override | ||
| 279 | public void onDeobfRefChanged(ClassHandle h, ClassEntry deobfRef) { | ||
| 280 | SwingUtilities.invokeLater(() -> { | ||
| 281 | PanelEditor.this.listeners.forEach(l -> l.onTitleChanged(PanelEditor.this, getFileName())); | ||
| 282 | }); | ||
| 283 | } | ||
| 284 | |||
| 285 | @Override | ||
| 286 | public void onMappedSourceChanged(ClassHandle h, Result<DecompiledClassSource, ClassHandleError> res) { | ||
| 287 | handleDecompilerResult(res); | ||
| 288 | } | ||
| 289 | |||
| 290 | @Override | ||
| 291 | public void onInvalidate(ClassHandle h, InvalidationType t) { | ||
| 292 | SwingUtilities.invokeLater(() -> { | ||
| 293 | if (t == InvalidationType.FULL) { | ||
| 294 | PanelEditor.this.setDisplayMode(DisplayMode.IN_PROGRESS); | ||
| 295 | } | ||
| 296 | }); | ||
| 297 | } | ||
| 298 | |||
| 299 | @Override | ||
| 300 | public void onDeleted(ClassHandle h) { | ||
| 301 | SwingUtilities.invokeLater(() -> PanelEditor.this.gui.closeEditor(PanelEditor.this)); | ||
| 150 | } | 302 | } |
| 151 | }); | 303 | }); |
| 304 | |||
| 305 | handle.getSource().thenAcceptAsync(this::handleDecompilerResult, SwingUtilities::invokeLater); | ||
| 306 | |||
| 307 | this.classHandle = handle; | ||
| 308 | this.listeners.forEach(l -> l.onClassHandleChanged(this, old, handle)); | ||
| 309 | } | ||
| 310 | |||
| 311 | public void setup() { | ||
| 312 | Themes.addListener(this.themeChangeListener); | ||
| 313 | } | ||
| 314 | |||
| 315 | public void destroy() { | ||
| 316 | Themes.removeListener(this.themeChangeListener); | ||
| 317 | this.classHandle.close(); | ||
| 318 | } | ||
| 319 | |||
| 320 | private void redecompileClass() { | ||
| 321 | if (this.classHandle != null) { | ||
| 322 | this.classHandle.invalidate(); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | private void handleDecompilerResult(Result<DecompiledClassSource, ClassHandleError> res) { | ||
| 327 | SwingUtilities.invokeLater(() -> { | ||
| 328 | if (res.isOk()) { | ||
| 329 | this.setSource(res.unwrap()); | ||
| 330 | } else { | ||
| 331 | this.displayError(res.unwrapErr()); | ||
| 332 | } | ||
| 333 | }); | ||
| 334 | } | ||
| 335 | |||
| 336 | public void displayError(ClassHandleError t) { | ||
| 337 | this.setDisplayMode(DisplayMode.ERRORED); | ||
| 338 | this.errorTextArea.setText(t.getStackTrace()); | ||
| 339 | this.errorTextArea.setCaretPosition(0); | ||
| 340 | } | ||
| 341 | |||
| 342 | public void setDisplayMode(DisplayMode mode) { | ||
| 343 | if (this.mode == mode) return; | ||
| 344 | this.ui.removeAll(); | ||
| 345 | switch (mode) { | ||
| 346 | case INACTIVE: | ||
| 347 | break; | ||
| 348 | case IN_PROGRESS: { | ||
| 349 | // make progress bar start from the left every time | ||
| 350 | this.decompilingProgressBar.setIndeterminate(false); | ||
| 351 | this.decompilingProgressBar.setIndeterminate(true); | ||
| 352 | |||
| 353 | this.ui.setLayout(new GridBagLayout()); | ||
| 354 | GridBagConstraints c = new GridBagConstraints(); | ||
| 355 | c.gridx = 0; | ||
| 356 | c.gridy = 0; | ||
| 357 | c.insets = ScaleUtil.getInsets(2, 2, 2, 2); | ||
| 358 | c.anchor = GridBagConstraints.SOUTH; | ||
| 359 | this.ui.add(this.decompilingLabel, c); | ||
| 360 | c.gridy = 1; | ||
| 361 | c.anchor = GridBagConstraints.NORTH; | ||
| 362 | this.ui.add(this.decompilingProgressBar, c); | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | case SUCCESS: { | ||
| 366 | this.ui.setLayout(new GridLayout(1, 1, 0, 0)); | ||
| 367 | this.ui.add(this.editorScrollPane); | ||
| 368 | break; | ||
| 369 | } | ||
| 370 | case ERRORED: { | ||
| 371 | this.ui.setLayout(new GridBagLayout()); | ||
| 372 | GridBagConstraints c = new GridBagConstraints(); | ||
| 373 | c.insets = ScaleUtil.getInsets(2, 2, 2, 2); | ||
| 374 | c.gridx = 0; | ||
| 375 | c.gridy = 0; | ||
| 376 | c.weightx = 1.0; | ||
| 377 | c.anchor = GridBagConstraints.WEST; | ||
| 378 | this.ui.add(this.errorLabel, c); | ||
| 379 | c.gridy = 1; | ||
| 380 | c.fill = GridBagConstraints.HORIZONTAL; | ||
| 381 | this.ui.add(new JSeparator(JSeparator.HORIZONTAL), c); | ||
| 382 | c.gridy = 2; | ||
| 383 | c.fill = GridBagConstraints.BOTH; | ||
| 384 | c.weighty = 1.0; | ||
| 385 | this.ui.add(this.errorScrollPane, c); | ||
| 386 | c.gridy = 3; | ||
| 387 | c.fill = GridBagConstraints.NONE; | ||
| 388 | c.anchor = GridBagConstraints.EAST; | ||
| 389 | c.weightx = 0.0; | ||
| 390 | c.weighty = 0.0; | ||
| 391 | this.ui.add(this.retryButton, c); | ||
| 392 | break; | ||
| 393 | } | ||
| 394 | } | ||
| 395 | this.ui.validate(); | ||
| 396 | this.ui.repaint(); | ||
| 397 | this.mode = mode; | ||
| 152 | } | 398 | } |
| 153 | 399 | ||
| 154 | public void offsetEditorZoom(int zoomAmount) { | 400 | public void offsetEditorZoom(int zoomAmount) { |
| 155 | int newResult = this.fontSize + zoomAmount; | 401 | int newResult = this.fontSize + zoomAmount; |
| 156 | if (newResult > 8 && newResult < 72) { | 402 | if (newResult > 8 && newResult < 72) { |
| 157 | this.fontSize = newResult; | 403 | this.fontSize = newResult; |
| 158 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | 404 | this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); |
| 159 | } | 405 | } |
| 160 | } | 406 | } |
| 161 | 407 | ||
| 162 | public void resetEditorZoom() { | 408 | public void resetEditorZoom() { |
| 163 | this.fontSize = 12; | 409 | this.fontSize = 12; |
| 164 | this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize)); | 410 | this.editor.setFont(ScaleUtil.getFont(this.editor.getFont().getFontName(), Font.PLAIN, this.fontSize)); |
| 165 | } | 411 | } |
| 166 | 412 | ||
| 167 | @Override | 413 | public void onCaretMove(int pos, boolean fromClick) { |
| 168 | public Color getCaretColor() { | 414 | if (this.controller.project == null) return; |
| 169 | return new Color(Config.getInstance().caretColor); | 415 | |
| 416 | EntryRemapper mapper = this.controller.project.getMapper(); | ||
| 417 | Token token = getToken(pos); | ||
| 418 | |||
| 419 | if (this.settingSource) { | ||
| 420 | EntryReference<Entry<?>, Entry<?>> ref = getCursorReference(); | ||
| 421 | EntryReference<Entry<?>, Entry<?>> refAtCursor = getReference(token); | ||
| 422 | if (this.editor.getDocument().getLength() != 0 && ref != null && !ref.equals(refAtCursor)) { | ||
| 423 | showReference0(ref); | ||
| 424 | } | ||
| 425 | return; | ||
| 426 | } else { | ||
| 427 | setCursorReference(getReference(token)); | ||
| 428 | } | ||
| 429 | |||
| 430 | Entry<?> referenceEntry = this.cursorReference != null ? this.cursorReference.entry : null; | ||
| 431 | |||
| 432 | if (referenceEntry != null && this.shouldNavigateOnClick && fromClick) { | ||
| 433 | this.shouldNavigateOnClick = false; | ||
| 434 | Entry<?> navigationEntry = referenceEntry; | ||
| 435 | if (this.cursorReference.context == null) { | ||
| 436 | EntryResolver resolver = mapper.getObfResolver(); | ||
| 437 | navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 438 | } | ||
| 439 | this.controller.navigateTo(navigationEntry); | ||
| 440 | } | ||
| 441 | } | ||
| 442 | |||
| 443 | private void setCursorReference(EntryReference<Entry<?>, Entry<?>> ref) { | ||
| 444 | this.cursorReference = ref; | ||
| 445 | |||
| 446 | Entry<?> referenceEntry = ref == null ? null : ref.entry; | ||
| 447 | |||
| 448 | boolean isClassEntry = referenceEntry instanceof ClassEntry; | ||
| 449 | boolean isFieldEntry = referenceEntry instanceof FieldEntry; | ||
| 450 | boolean isMethodEntry = referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor(); | ||
| 451 | boolean isConstructorEntry = referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor(); | ||
| 452 | boolean isRenamable = ref != null && this.controller.project.isRenamable(ref); | ||
| 453 | |||
| 454 | this.popupMenu.renameMenu.setEnabled(isRenamable); | ||
| 455 | this.popupMenu.editJavadocMenu.setEnabled(isRenamable); | ||
| 456 | this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry); | ||
| 457 | this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry); | ||
| 458 | this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry); | ||
| 459 | this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry); | ||
| 460 | this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry)); | ||
| 461 | this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference()); | ||
| 462 | this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference()); | ||
| 463 | this.popupMenu.toggleMappingMenu.setEnabled(isRenamable); | ||
| 464 | |||
| 465 | if (referenceEntry != null && referenceEntry.equals(this.controller.project.getMapper().deobfuscate(referenceEntry))) { | ||
| 466 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated")); | ||
| 467 | } else { | ||
| 468 | this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated")); | ||
| 469 | } | ||
| 470 | |||
| 471 | this.listeners.forEach(l -> l.onCursorReferenceChanged(this, ref)); | ||
| 472 | } | ||
| 473 | |||
| 474 | public Token getToken(int pos) { | ||
| 475 | if (this.source == null) { | ||
| 476 | return null; | ||
| 477 | } | ||
| 478 | return this.source.getIndex().getReferenceToken(pos); | ||
| 479 | } | ||
| 480 | |||
| 481 | @Nullable | ||
| 482 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | ||
| 483 | if (this.source == null) { | ||
| 484 | return null; | ||
| 485 | } | ||
| 486 | return this.source.getIndex().getReference(token); | ||
| 170 | } | 487 | } |
| 488 | |||
| 489 | public void setSource(DecompiledClassSource source) { | ||
| 490 | this.setDisplayMode(DisplayMode.SUCCESS); | ||
| 491 | if (source == null) return; | ||
| 492 | try { | ||
| 493 | this.settingSource = true; | ||
| 494 | this.source = source; | ||
| 495 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 496 | this.editor.setText(source.toString()); | ||
| 497 | setHighlightedTokens(source.getHighlightedTokens()); | ||
| 498 | } finally { | ||
| 499 | this.settingSource = false; | ||
| 500 | } | ||
| 501 | showReference0(getCursorReference()); | ||
| 502 | } | ||
| 503 | |||
| 504 | public void setHighlightedTokens(Map<RenamableTokenType, Collection<Token>> tokens) { | ||
| 505 | // remove any old highlighters | ||
| 506 | this.editor.getHighlighter().removeAllHighlights(); | ||
| 507 | |||
| 508 | if (this.boxHighlightPainters != null) { | ||
| 509 | for (RenamableTokenType type : tokens.keySet()) { | ||
| 510 | BoxHighlightPainter painter = this.boxHighlightPainters.get(type); | ||
| 511 | if (painter != null) { | ||
| 512 | setHighlightedTokens(tokens.get(type), painter); | ||
| 513 | } | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | this.editor.validate(); | ||
| 518 | this.editor.repaint(); | ||
| 519 | } | ||
| 520 | |||
| 521 | private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) { | ||
| 522 | for (Token token : tokens) { | ||
| 523 | try { | ||
| 524 | this.editor.getHighlighter().addHighlight(token.start, token.end, painter); | ||
| 525 | } catch (BadLocationException ex) { | ||
| 526 | throw new IllegalArgumentException(ex); | ||
| 527 | } | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | public EntryReference<Entry<?>, Entry<?>> getCursorReference() { | ||
| 532 | return this.cursorReference; | ||
| 533 | } | ||
| 534 | |||
| 535 | public void showReference(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 536 | setCursorReference(reference); | ||
| 537 | showReference0(reference); | ||
| 538 | } | ||
| 539 | |||
| 540 | /** | ||
| 541 | * Navigates to the reference without modifying history. Assumes the class is loaded. | ||
| 542 | * | ||
| 543 | * @param reference | ||
| 544 | */ | ||
| 545 | private void showReference0(EntryReference<Entry<?>, Entry<?>> reference) { | ||
| 546 | if (this.source == null) return; | ||
| 547 | if (reference == null) return; | ||
| 548 | |||
| 549 | Collection<Token> tokens = this.controller.getTokensForReference(this.source, reference); | ||
| 550 | if (tokens.isEmpty()) { | ||
| 551 | // DEBUG | ||
| 552 | System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.classHandle.getRef())); | ||
| 553 | } else { | ||
| 554 | this.gui.showTokens(this, tokens); | ||
| 555 | } | ||
| 556 | } | ||
| 557 | |||
| 558 | public void navigateToToken(Token token) { | ||
| 559 | if (token == null) { | ||
| 560 | throw new IllegalArgumentException("Token cannot be null!"); | ||
| 561 | } | ||
| 562 | navigateToToken(token, SelectionHighlightPainter.INSTANCE); | ||
| 563 | } | ||
| 564 | |||
| 565 | private void navigateToToken(Token token, HighlightPainter highlightPainter) { | ||
| 566 | // set the caret position to the token | ||
| 567 | Document document = this.editor.getDocument(); | ||
| 568 | int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength()); | ||
| 569 | |||
| 570 | this.editor.setCaretPosition(clampedPosition); | ||
| 571 | this.editor.grabFocus(); | ||
| 572 | |||
| 573 | try { | ||
| 574 | // make sure the token is visible in the scroll window | ||
| 575 | Rectangle start = this.editor.modelToView(token.start); | ||
| 576 | Rectangle end = this.editor.modelToView(token.end); | ||
| 577 | Rectangle show = start.union(end); | ||
| 578 | show.grow(start.width * 10, start.height * 6); | ||
| 579 | SwingUtilities.invokeLater(() -> this.editor.scrollRectToVisible(show)); | ||
| 580 | } catch (BadLocationException ex) { | ||
| 581 | if (!this.settingSource) { | ||
| 582 | throw new RuntimeException(ex); | ||
| 583 | } else { | ||
| 584 | return; | ||
| 585 | } | ||
| 586 | } | ||
| 587 | |||
| 588 | // highlight the token momentarily | ||
| 589 | Timer timer = new Timer(200, new ActionListener() { | ||
| 590 | private int counter = 0; | ||
| 591 | private Object highlight = null; | ||
| 592 | |||
| 593 | @Override | ||
| 594 | public void actionPerformed(ActionEvent event) { | ||
| 595 | if (this.counter % 2 == 0) { | ||
| 596 | try { | ||
| 597 | this.highlight = PanelEditor.this.editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter); | ||
| 598 | } catch (BadLocationException ex) { | ||
| 599 | // don't care | ||
| 600 | } | ||
| 601 | } else if (this.highlight != null) { | ||
| 602 | PanelEditor.this.editor.getHighlighter().removeHighlight(this.highlight); | ||
| 603 | } | ||
| 604 | |||
| 605 | if (this.counter++ > 6) { | ||
| 606 | Timer timer = (Timer) event.getSource(); | ||
| 607 | timer.stop(); | ||
| 608 | } | ||
| 609 | } | ||
| 610 | }); | ||
| 611 | timer.start(); | ||
| 612 | } | ||
| 613 | |||
| 614 | public void addListener(EditorActionListener listener) { | ||
| 615 | this.listeners.add(listener); | ||
| 616 | } | ||
| 617 | |||
| 618 | public void removeListener(EditorActionListener listener) { | ||
| 619 | this.listeners.remove(listener); | ||
| 620 | } | ||
| 621 | |||
| 622 | public JPanel getUi() { | ||
| 623 | return this.ui; | ||
| 624 | } | ||
| 625 | |||
| 626 | public JEditorPane getEditor() { | ||
| 627 | return this.editor; | ||
| 628 | } | ||
| 629 | |||
| 630 | public DecompiledClassSource getSource() { | ||
| 631 | return this.source; | ||
| 632 | } | ||
| 633 | |||
| 634 | public ClassHandle getClassHandle() { | ||
| 635 | return this.classHandle; | ||
| 636 | } | ||
| 637 | |||
| 638 | public String getFileName() { | ||
| 639 | ClassEntry classEntry = this.classHandle.getDeobfRef() != null ? this.classHandle.getDeobfRef() : this.classHandle.getRef(); | ||
| 640 | return classEntry.getSimpleName(); | ||
| 641 | } | ||
| 642 | |||
| 643 | private enum DisplayMode { | ||
| 644 | INACTIVE, | ||
| 645 | IN_PROGRESS, | ||
| 646 | SUCCESS, | ||
| 647 | ERRORED, | ||
| 648 | } | ||
| 649 | |||
| 171 | } | 650 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java index 8c19efb..bfba845 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java | |||
| @@ -1,32 +1,255 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | 1 | package cuchaz.enigma.gui.panels; |
| 2 | 2 | ||
| 3 | import java.awt.*; | ||
| 4 | import java.awt.event.ItemEvent; | ||
| 5 | import java.util.function.Consumer; | ||
| 6 | |||
| 7 | import javax.swing.BorderFactory; | ||
| 8 | import javax.swing.JComboBox; | ||
| 9 | import javax.swing.JLabel; | ||
| 10 | import javax.swing.JPanel; | ||
| 11 | |||
| 12 | import cuchaz.enigma.EnigmaProject; | ||
| 13 | import cuchaz.enigma.analysis.EntryReference; | ||
| 3 | import cuchaz.enigma.gui.Gui; | 14 | import cuchaz.enigma.gui.Gui; |
| 15 | import cuchaz.enigma.gui.elements.ConvertingTextField; | ||
| 16 | import cuchaz.enigma.gui.events.ConvertingTextFieldListener; | ||
| 4 | import cuchaz.enigma.gui.util.GuiUtil; | 17 | import cuchaz.enigma.gui.util.GuiUtil; |
| 5 | import cuchaz.enigma.utils.I18n; | ||
| 6 | import cuchaz.enigma.gui.util.ScaleUtil; | 18 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 19 | import cuchaz.enigma.network.packet.RenameC2SPacket; | ||
| 20 | import cuchaz.enigma.translation.mapping.AccessModifier; | ||
| 21 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 23 | import cuchaz.enigma.utils.I18n; | ||
| 24 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 7 | 25 | ||
| 8 | import javax.swing.*; | 26 | public class PanelIdentifier { |
| 9 | import java.awt.*; | ||
| 10 | |||
| 11 | public class PanelIdentifier extends JPanel { | ||
| 12 | 27 | ||
| 13 | private final Gui gui; | 28 | private final Gui gui; |
| 14 | 29 | ||
| 30 | private final JPanel ui; | ||
| 31 | |||
| 32 | private Entry<?> entry; | ||
| 33 | private Entry<?> deobfEntry; | ||
| 34 | |||
| 35 | private ConvertingTextField nameField; | ||
| 36 | |||
| 37 | private final ValidationContext vc = new ValidationContext(); | ||
| 38 | |||
| 15 | public PanelIdentifier(Gui gui) { | 39 | public PanelIdentifier(Gui gui) { |
| 16 | this.gui = gui; | 40 | this.gui = gui; |
| 17 | 41 | ||
| 18 | this.setLayout(new GridLayout(4, 1, 0, 0)); | 42 | this.ui = new JPanel(); |
| 19 | this.setPreferredSize(ScaleUtil.getDimension(0, 100)); | 43 | this.ui.setLayout(new GridBagLayout()); |
| 20 | this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); | 44 | this.ui.setPreferredSize(ScaleUtil.getDimension(0, 120)); |
| 45 | this.ui.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier"))); | ||
| 46 | this.ui.setEnabled(false); | ||
| 47 | } | ||
| 48 | |||
| 49 | public void setReference(Entry<?> entry) { | ||
| 50 | this.entry = entry; | ||
| 51 | refreshReference(); | ||
| 21 | } | 52 | } |
| 22 | 53 | ||
| 23 | public void clearReference() { | 54 | public boolean startRenaming() { |
| 24 | this.removeAll(); | 55 | if (this.nameField == null) return false; |
| 25 | JLabel label = new JLabel(I18n.translate("info_panel.identifier.none")); | ||
| 26 | GuiUtil.unboldLabel(label); | ||
| 27 | label.setHorizontalAlignment(JLabel.CENTER); | ||
| 28 | this.add(label); | ||
| 29 | 56 | ||
| 30 | gui.redraw(); | 57 | this.nameField.startEditing(); |
| 58 | |||
| 59 | return true; | ||
| 60 | } | ||
| 61 | |||
| 62 | public boolean startRenaming(String text) { | ||
| 63 | if (this.nameField == null) return false; | ||
| 64 | |||
| 65 | this.nameField.startEditing(); | ||
| 66 | this.nameField.setEditText(text); | ||
| 67 | |||
| 68 | return true; | ||
| 69 | } | ||
| 70 | |||
| 71 | private void onModifierChanged(AccessModifier modifier) { | ||
| 72 | gui.validateImmediateAction(vc -> this.gui.getController().onModifierChanged(vc, entry, modifier)); | ||
| 31 | } | 73 | } |
| 74 | |||
| 75 | public void refreshReference() { | ||
| 76 | this.deobfEntry = entry == null ? null : gui.getController().project.getMapper().deobfuscate(this.entry); | ||
| 77 | |||
| 78 | this.nameField = null; | ||
| 79 | |||
| 80 | TableHelper th = new TableHelper(this.ui, this.entry, this.gui.getController().project); | ||
| 81 | th.begin(); | ||
| 82 | if (this.entry == null) { | ||
| 83 | this.ui.setEnabled(false); | ||
| 84 | } else { | ||
| 85 | this.ui.setEnabled(true); | ||
| 86 | |||
| 87 | if (deobfEntry instanceof ClassEntry) { | ||
| 88 | ClassEntry ce = (ClassEntry) deobfEntry; | ||
| 89 | this.nameField = th.addRenameTextField(I18n.translate("info_panel.identifier.class"), ce.getFullName()); | ||
| 90 | th.addModifierRow(I18n.translate("info_panel.identifier.modifier"), this::onModifierChanged); | ||
| 91 | } else if (deobfEntry instanceof FieldEntry) { | ||
| 92 | FieldEntry fe = (FieldEntry) deobfEntry; | ||
| 93 | this.nameField = th.addRenameTextField(I18n.translate("info_panel.identifier.field"), fe.getName()); | ||
| 94 | th.addStringRow(I18n.translate("info_panel.identifier.class"), fe.getParent().getFullName()); | ||
| 95 | th.addStringRow(I18n.translate("info_panel.identifier.type_descriptor"), fe.getDesc().toString()); | ||
| 96 | th.addModifierRow(I18n.translate("info_panel.identifier.modifier"), this::onModifierChanged); | ||
| 97 | } else if (deobfEntry instanceof MethodEntry) { | ||
| 98 | MethodEntry me = (MethodEntry) deobfEntry; | ||
| 99 | if (me.isConstructor()) { | ||
| 100 | th.addStringRow(I18n.translate("info_panel.identifier.constructor"), me.getParent().getFullName()); | ||
| 101 | } else { | ||
| 102 | this.nameField = th.addRenameTextField(I18n.translate("info_panel.identifier.method"), me.getName()); | ||
| 103 | th.addStringRow(I18n.translate("info_panel.identifier.class"), me.getParent().getFullName()); | ||
| 104 | } | ||
| 105 | th.addStringRow(I18n.translate("info_panel.identifier.method_descriptor"), me.getDesc().toString()); | ||
| 106 | th.addModifierRow(I18n.translate("info_panel.identifier.modifier"), this::onModifierChanged); | ||
| 107 | } else if (deobfEntry instanceof LocalVariableEntry) { | ||
| 108 | LocalVariableEntry lve = (LocalVariableEntry) deobfEntry; | ||
| 109 | this.nameField = th.addRenameTextField(I18n.translate("info_panel.identifier.variable"), lve.getName()); | ||
| 110 | th.addStringRow(I18n.translate("info_panel.identifier.class"), lve.getContainingClass().getFullName()); | ||
| 111 | th.addStringRow(I18n.translate("info_panel.identifier.method"), lve.getParent().getName()); | ||
| 112 | th.addStringRow(I18n.translate("info_panel.identifier.index"), Integer.toString(lve.getIndex())); | ||
| 113 | } else { | ||
| 114 | throw new IllegalStateException("unreachable"); | ||
| 115 | } | ||
| 116 | } | ||
| 117 | th.end(); | ||
| 118 | |||
| 119 | if (this.nameField != null) { | ||
| 120 | this.nameField.addListener(new ConvertingTextFieldListener() { | ||
| 121 | @Override | ||
| 122 | public void onStartEditing(ConvertingTextField field) { | ||
| 123 | int i = field.getText().lastIndexOf('/'); | ||
| 124 | if (i != -1) { | ||
| 125 | field.selectSubstring(i + 1); | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | @Override | ||
| 130 | public boolean tryStopEditing(ConvertingTextField field, boolean abort) { | ||
| 131 | if (abort) return true; | ||
| 132 | vc.reset(); | ||
| 133 | vc.setActiveElement(field); | ||
| 134 | validateRename(field.getText()); | ||
| 135 | return vc.canProceed(); | ||
| 136 | } | ||
| 137 | |||
| 138 | @Override | ||
| 139 | public void onStopEditing(ConvertingTextField field, boolean abort) { | ||
| 140 | if (abort) return; | ||
| 141 | vc.reset(); | ||
| 142 | vc.setActiveElement(field); | ||
| 143 | doRename(field.getText()); | ||
| 144 | } | ||
| 145 | }); | ||
| 146 | } | ||
| 147 | |||
| 148 | this.ui.validate(); | ||
| 149 | this.ui.repaint(); | ||
| 150 | } | ||
| 151 | |||
| 152 | private void validateRename(String newName) { | ||
| 153 | gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true, true); | ||
| 154 | } | ||
| 155 | |||
| 156 | private void doRename(String newName) { | ||
| 157 | gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true); | ||
| 158 | if (!vc.canProceed()) return; | ||
| 159 | gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true)); | ||
| 160 | } | ||
| 161 | |||
| 162 | public JPanel getUi() { | ||
| 163 | return ui; | ||
| 164 | } | ||
| 165 | |||
| 166 | private static final class TableHelper { | ||
| 167 | |||
| 168 | private final Container c; | ||
| 169 | private final Entry<?> e; | ||
| 170 | private final EnigmaProject project; | ||
| 171 | private final GridBagConstraints col1; | ||
| 172 | private final GridBagConstraints col2; | ||
| 173 | |||
| 174 | public TableHelper(Container c, Entry<?> e, EnigmaProject project) { | ||
| 175 | this.c = c; | ||
| 176 | this.e = e; | ||
| 177 | this.project = project; | ||
| 178 | this.col1 = new GridBagConstraints(); | ||
| 179 | this.col2 = new GridBagConstraints(); | ||
| 180 | Insets insets = ScaleUtil.getInsets(2, 2, 2, 2); | ||
| 181 | this.col1.gridx = 0; | ||
| 182 | this.col1.gridy = 0; | ||
| 183 | this.col1.insets = insets; | ||
| 184 | this.col1.anchor = GridBagConstraints.WEST; | ||
| 185 | this.col2.gridx = 1; | ||
| 186 | this.col2.gridy = 0; | ||
| 187 | this.col2.weightx = 1.0; | ||
| 188 | this.col2.fill = GridBagConstraints.HORIZONTAL; | ||
| 189 | this.col2.insets = insets; | ||
| 190 | this.col2.anchor = GridBagConstraints.WEST; | ||
| 191 | } | ||
| 192 | |||
| 193 | public void begin() { | ||
| 194 | c.removeAll(); | ||
| 195 | c.setLayout(new GridBagLayout()); | ||
| 196 | } | ||
| 197 | |||
| 198 | public void addRow(Component c1, Component c2) { | ||
| 199 | c.add(c1, col1); | ||
| 200 | c.add(c2, col2); | ||
| 201 | |||
| 202 | col1.gridy += 1; | ||
| 203 | col2.gridy += 1; | ||
| 204 | } | ||
| 205 | |||
| 206 | public ConvertingTextField addCovertTextField(String c1, String c2) { | ||
| 207 | ConvertingTextField textField = new ConvertingTextField(c2); | ||
| 208 | addRow(new JLabel(c1), textField.getUi()); | ||
| 209 | return textField; | ||
| 210 | } | ||
| 211 | |||
| 212 | public ConvertingTextField addRenameTextField(String c1, String c2) { | ||
| 213 | if (project.isRenamable(e)) { | ||
| 214 | return addCovertTextField(c1, c2); | ||
| 215 | } else { | ||
| 216 | addStringRow(c1, c2); | ||
| 217 | return null; | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | public void addStringRow(String c1, String c2) { | ||
| 222 | addRow(new JLabel(c1), GuiUtil.unboldLabel(new JLabel(c2))); | ||
| 223 | } | ||
| 224 | |||
| 225 | public JComboBox<AccessModifier> addModifierRow(String c1, Consumer<AccessModifier> changeListener) { | ||
| 226 | if (!project.isRenamable(e)) | ||
| 227 | return null; | ||
| 228 | JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); | ||
| 229 | EntryMapping mapping = project.getMapper().getDeobfMapping(e); | ||
| 230 | if (mapping != null) { | ||
| 231 | combo.setSelectedIndex(mapping.getAccessModifier().ordinal()); | ||
| 232 | } else { | ||
| 233 | combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal()); | ||
| 234 | } | ||
| 235 | combo.addItemListener(event -> { | ||
| 236 | if (event.getStateChange() == ItemEvent.SELECTED) { | ||
| 237 | AccessModifier modifier = (AccessModifier) event.getItem(); | ||
| 238 | changeListener.accept(modifier); | ||
| 239 | } | ||
| 240 | }); | ||
| 241 | |||
| 242 | addRow(new JLabel(c1), combo); | ||
| 243 | |||
| 244 | return combo; | ||
| 245 | } | ||
| 246 | |||
| 247 | public void end() { | ||
| 248 | // Add an empty panel with y-weight=1 so that all the other elements get placed at the top edge | ||
| 249 | this.col1.weighty = 1.0; | ||
| 250 | c.add(new JPanel(), col1); | ||
| 251 | } | ||
| 252 | |||
| 253 | } | ||
| 254 | |||
| 32 | } | 255 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java index 70172fe..3b8df61 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java | |||
| @@ -40,17 +40,4 @@ public class GuiUtil { | |||
| 40 | manager.setInitialDelay(oldDelay); | 40 | manager.setInitialDelay(oldDelay); |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | public static Rectangle safeModelToView(JTextComponent component, int modelPos) { | ||
| 44 | if (modelPos < 0) { | ||
| 45 | modelPos = 0; | ||
| 46 | } else if (modelPos >= component.getText().length()) { | ||
| 47 | modelPos = component.getText().length(); | ||
| 48 | } | ||
| 49 | try { | ||
| 50 | return component.modelToView(modelPos); | ||
| 51 | } catch (BadLocationException e) { | ||
| 52 | throw new RuntimeException(e); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | } | 43 | } |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java index e7ee565..985615a 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java | |||
| @@ -2,6 +2,7 @@ package cuchaz.enigma.gui.util; | |||
| 2 | 2 | ||
| 3 | import java.awt.Dimension; | 3 | import java.awt.Dimension; |
| 4 | import java.awt.Font; | 4 | import java.awt.Font; |
| 5 | import java.awt.Insets; | ||
| 5 | import java.io.IOException; | 6 | import java.io.IOException; |
| 6 | import java.lang.reflect.Field; | 7 | import java.lang.reflect.Field; |
| 7 | import java.util.ArrayList; | 8 | import java.util.ArrayList; |
| @@ -51,8 +52,12 @@ public class ScaleUtil { | |||
| 51 | return new Dimension(scale(width), scale(height)); | 52 | return new Dimension(scale(width), scale(height)); |
| 52 | } | 53 | } |
| 53 | 54 | ||
| 54 | public static Font getFont(String fontName, int plain, int fontSize) { | 55 | public static Insets getInsets(int top, int left, int bottom, int right) { |
| 55 | return scaleFont(new Font(fontName, plain, fontSize)); | 56 | return new Insets(scale(top), scale(left), scale(bottom), scale(right)); |
| 57 | } | ||
| 58 | |||
| 59 | public static Font getFont(String fontName, int style, int fontSize) { | ||
| 60 | return scaleFont(new Font(fontName, style, fontSize)); | ||
| 56 | } | 61 | } |
| 57 | 62 | ||
| 58 | public static Font scaleFont(Font font) { | 63 | public static Font scaleFont(Font font) { |
| @@ -106,5 +111,4 @@ public class ScaleUtil { | |||
| 106 | } | 111 | } |
| 107 | return new BasicTweaker(dpiScaling); | 112 | return new BasicTweaker(dpiScaling); |
| 108 | } | 113 | } |
| 109 | |||
| 110 | } | 114 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java index 320f945..9d6bc4e 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/EntryReference.java | |||
| @@ -11,19 +11,19 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.analysis; | 12 | package cuchaz.enigma.analysis; |
| 13 | 13 | ||
| 14 | import java.util.Arrays; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Objects; | ||
| 17 | |||
| 14 | import cuchaz.enigma.translation.Translatable; | 18 | import cuchaz.enigma.translation.Translatable; |
| 15 | import cuchaz.enigma.translation.Translator; | 19 | import cuchaz.enigma.translation.Translator; |
| 20 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | 21 | import cuchaz.enigma.translation.mapping.EntryMapping; |
| 17 | import cuchaz.enigma.translation.mapping.EntryResolver; | 22 | import cuchaz.enigma.translation.mapping.EntryResolver; |
| 18 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 23 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 20 | import cuchaz.enigma.translation.representation.entry.Entry; | 24 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 21 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 25 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 22 | 26 | ||
| 23 | import java.util.Arrays; | ||
| 24 | import java.util.List; | ||
| 25 | import java.util.Objects; | ||
| 26 | |||
| 27 | public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable { | 27 | public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable { |
| 28 | 28 | ||
| 29 | private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); | 29 | private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); |
| @@ -100,6 +100,8 @@ public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements T | |||
| 100 | } | 100 | } |
| 101 | 101 | ||
| 102 | public boolean equals(EntryReference<?, ?> other) { | 102 | public boolean equals(EntryReference<?, ?> other) { |
| 103 | if (other == null) return false; | ||
| 104 | |||
| 103 | // check entry first | 105 | // check entry first |
| 104 | boolean isEntrySame = entry.equals(other.entry); | 106 | boolean isEntrySame = entry.equals(other.entry); |
| 105 | if (!isEntrySame) { | 107 | if (!isEntrySame) { |
diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandle.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandle.java new file mode 100644 index 0000000..326197d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandle.java | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | package cuchaz.enigma.classhandle; | ||
| 2 | |||
| 3 | import java.util.concurrent.CompletableFuture; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | import cuchaz.enigma.events.ClassHandleListener; | ||
| 8 | import cuchaz.enigma.source.DecompiledClassSource; | ||
| 9 | import cuchaz.enigma.source.Source; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 11 | import cuchaz.enigma.utils.Result; | ||
| 12 | |||
| 13 | /** | ||
| 14 | * A handle to a class file. Can be treated similarly to a handle to a file. | ||
| 15 | * This type allows for accessing decompiled classes and being notified when | ||
| 16 | * mappings for the class it belongs to changes. | ||
| 17 | * | ||
| 18 | * @see ClassHandleProvider | ||
| 19 | */ | ||
| 20 | public interface ClassHandle extends AutoCloseable { | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Gets the reference to this class. This is always obfuscated, for example | ||
| 24 | * {@code net/minecraft/class_1000}. | ||
| 25 | * | ||
| 26 | * @return the obfuscated class reference | ||
| 27 | * @throws IllegalStateException if the class handle is closed | ||
| 28 | */ | ||
| 29 | ClassEntry getRef(); | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Gets the deobfuscated reference to this class, if any. | ||
| 33 | * | ||
| 34 | * @return the deobfuscated reference, or {@code null} if the class is not | ||
| 35 | * mapped | ||
| 36 | * @throws IllegalStateException if the class handle is closed | ||
| 37 | */ | ||
| 38 | @Nullable | ||
| 39 | ClassEntry getDeobfRef(); | ||
| 40 | |||
| 41 | /** | ||
| 42 | * Gets the class source, or the error generated while decompiling the | ||
| 43 | * class, asynchronously. If this class has already finished decompiling, | ||
| 44 | * this will return an already completed future. | ||
| 45 | * | ||
| 46 | * @return the class source | ||
| 47 | * @throws IllegalStateException if the class handle is closed | ||
| 48 | */ | ||
| 49 | CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> getSource(); | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Gets the class source without any decoration, or the error generated | ||
| 53 | * while decompiling the class, asynchronously. This is the raw source from | ||
| 54 | * the decompiler and will not be deobfuscated, and does not contain any | ||
| 55 | * Javadoc comments added via mappings. If this class has already finished | ||
| 56 | * decompiling, this will return an already completed future. | ||
| 57 | * | ||
| 58 | * @return the uncommented class source | ||
| 59 | * @throws IllegalStateException if the class handle is closed | ||
| 60 | * @see ClassHandle#getSource() | ||
| 61 | */ | ||
| 62 | CompletableFuture<Result<Source, ClassHandleError>> getUncommentedSource(); | ||
| 63 | |||
| 64 | void invalidate(); | ||
| 65 | |||
| 66 | void invalidateMapped(); | ||
| 67 | |||
| 68 | void invalidateJavadoc(); | ||
| 69 | |||
| 70 | /** | ||
| 71 | * Adds a listener for this class handle. | ||
| 72 | * | ||
| 73 | * @param listener the listener to add | ||
| 74 | * @see ClassHandleListener | ||
| 75 | */ | ||
| 76 | void addListener(ClassHandleListener listener); | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Removes a previously added listener (with | ||
| 80 | * {@link ClassHandle#addListener(ClassHandleListener)}) from this class | ||
| 81 | * handle. | ||
| 82 | * | ||
| 83 | * @param listener the listener to remove | ||
| 84 | */ | ||
| 85 | void removeListener(ClassHandleListener listener); | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Copies this class handle. The new class handle points to the same class, | ||
| 89 | * but is independent from this class handle in every other aspect. | ||
| 90 | * Specifically, any listeners will not be copied to the new class handle. | ||
| 91 | * | ||
| 92 | * @return a copy of this class handle | ||
| 93 | * @throws IllegalStateException if the class handle is closed | ||
| 94 | */ | ||
| 95 | ClassHandle copy(); | ||
| 96 | |||
| 97 | /** | ||
| 98 | * {@inheritDoc} | ||
| 99 | * | ||
| 100 | * <p>Specifically, for class handles, this means that most methods on the | ||
| 101 | * handle will throw an exception if called, that the handle will no longer | ||
| 102 | * receive any events over any added listeners, and the handle will no | ||
| 103 | * longer be able to be copied. | ||
| 104 | */ | ||
| 105 | @Override | ||
| 106 | void close(); | ||
| 107 | |||
| 108 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleError.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleError.java new file mode 100644 index 0000000..a11b9dc --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleError.java | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | package cuchaz.enigma.classhandle; | ||
| 2 | |||
| 3 | import java.io.ByteArrayOutputStream; | ||
| 4 | import java.io.PrintStream; | ||
| 5 | |||
| 6 | import javax.annotation.Nullable; | ||
| 7 | |||
| 8 | public final class ClassHandleError { | ||
| 9 | |||
| 10 | public final Type type; | ||
| 11 | public final Throwable cause; | ||
| 12 | |||
| 13 | private ClassHandleError(Type type, Throwable cause) { | ||
| 14 | this.type = type; | ||
| 15 | this.cause = cause; | ||
| 16 | } | ||
| 17 | |||
| 18 | @Nullable | ||
| 19 | public String getStackTrace() { | ||
| 20 | if (cause == null) return null; | ||
| 21 | ByteArrayOutputStream os = new ByteArrayOutputStream(); | ||
| 22 | PrintStream ps = new PrintStream(os); | ||
| 23 | cause.printStackTrace(ps); | ||
| 24 | return os.toString(); | ||
| 25 | } | ||
| 26 | |||
| 27 | public static ClassHandleError decompile(Throwable cause) { | ||
| 28 | return new ClassHandleError(Type.DECOMPILE, cause); | ||
| 29 | } | ||
| 30 | |||
| 31 | public enum Type { | ||
| 32 | DECOMPILE, | ||
| 33 | } | ||
| 34 | |||
| 35 | } \ No newline at end of file | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java new file mode 100644 index 0000000..2d9b52d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java | |||
| @@ -0,0 +1,445 @@ | |||
| 1 | package cuchaz.enigma.classhandle; | ||
| 2 | |||
| 3 | import java.util.*; | ||
| 4 | import java.util.concurrent.CompletableFuture; | ||
| 5 | import java.util.concurrent.ExecutorService; | ||
| 6 | import java.util.concurrent.Executors; | ||
| 7 | import java.util.concurrent.TimeUnit; | ||
| 8 | import java.util.concurrent.atomic.AtomicInteger; | ||
| 9 | import java.util.concurrent.locks.ReadWriteLock; | ||
| 10 | import java.util.concurrent.locks.ReentrantReadWriteLock; | ||
| 11 | |||
| 12 | import javax.annotation.Nullable; | ||
| 13 | |||
| 14 | import cuchaz.enigma.Enigma; | ||
| 15 | import cuchaz.enigma.EnigmaProject; | ||
| 16 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 17 | import cuchaz.enigma.events.ClassHandleListener; | ||
| 18 | import cuchaz.enigma.events.ClassHandleListener.InvalidationType; | ||
| 19 | import cuchaz.enigma.source.*; | ||
| 20 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 21 | import cuchaz.enigma.utils.Result; | ||
| 22 | import org.objectweb.asm.tree.ClassNode; | ||
| 23 | |||
| 24 | import static cuchaz.enigma.utils.Utils.withLock; | ||
| 25 | |||
| 26 | public final class ClassHandleProvider { | ||
| 27 | |||
| 28 | private final EnigmaProject project; | ||
| 29 | |||
| 30 | private final ExecutorService pool = Executors.newWorkStealingPool(); | ||
| 31 | private DecompilerService ds; | ||
| 32 | private Decompiler decompiler; | ||
| 33 | |||
| 34 | private final Map<ClassEntry, Entry> handles = new HashMap<>(); | ||
| 35 | |||
| 36 | private final ReadWriteLock lock = new ReentrantReadWriteLock(); | ||
| 37 | |||
| 38 | public ClassHandleProvider(EnigmaProject project, DecompilerService ds) { | ||
| 39 | this.project = project; | ||
| 40 | this.ds = ds; | ||
| 41 | this.decompiler = createDecompiler(); | ||
| 42 | } | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Open a class by entry. Schedules decompilation immediately if this is the | ||
| 46 | * only handle to the class. | ||
| 47 | * | ||
| 48 | * @param entry the entry of the class to open | ||
| 49 | * @return a handle to the class, {@code null} if a class by that name does | ||
| 50 | * not exist | ||
| 51 | */ | ||
| 52 | @Nullable | ||
| 53 | public ClassHandle openClass(ClassEntry entry) { | ||
| 54 | if (!project.getJarIndex().getEntryIndex().hasClass(entry)) return null; | ||
| 55 | |||
| 56 | return withLock(lock.writeLock(), () -> { | ||
| 57 | Entry e = handles.computeIfAbsent(entry, entry1 -> new Entry(this, entry1)); | ||
| 58 | return e.createHandle(); | ||
| 59 | }); | ||
| 60 | } | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Set the decompiler service to use when decompiling classes. Invalidates | ||
| 64 | * all currently open classes. | ||
| 65 | * | ||
| 66 | * <p>If the current decompiler service equals the old one, no classes will | ||
| 67 | * be invalidated. | ||
| 68 | * | ||
| 69 | * @param ds the decompiler service to use | ||
| 70 | */ | ||
| 71 | public void setDecompilerService(DecompilerService ds) { | ||
| 72 | if (this.ds.equals(ds)) return; | ||
| 73 | |||
| 74 | this.ds = ds; | ||
| 75 | this.decompiler = createDecompiler(); | ||
| 76 | withLock(lock.readLock(), () -> { | ||
| 77 | handles.values().forEach(Entry::invalidate); | ||
| 78 | }); | ||
| 79 | } | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Gets the current decompiler service in use. | ||
| 83 | * | ||
| 84 | * @return the current decompiler service | ||
| 85 | */ | ||
| 86 | public DecompilerService getDecompilerService() { | ||
| 87 | return ds; | ||
| 88 | } | ||
| 89 | |||
| 90 | private Decompiler createDecompiler() { | ||
| 91 | return ds.create(name -> { | ||
| 92 | ClassNode node = project.getClassCache().getClassNode(name); | ||
| 93 | |||
| 94 | if (node == null) { | ||
| 95 | return null; | ||
| 96 | } | ||
| 97 | |||
| 98 | ClassNode fixedNode = new ClassNode(); | ||
| 99 | node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, project.getJarIndex())); | ||
| 100 | return fixedNode; | ||
| 101 | }, new SourceSettings(true, true)); | ||
| 102 | } | ||
| 103 | |||
| 104 | /** | ||
| 105 | * Invalidates all mappings. This causes all open class handles to be | ||
| 106 | * re-remapped. | ||
| 107 | */ | ||
| 108 | public void invalidateMapped() { | ||
| 109 | withLock(lock.readLock(), () -> { | ||
| 110 | handles.values().forEach(Entry::invalidateMapped); | ||
| 111 | }); | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * Invalidates mappings for a single class. Note that this does not | ||
| 116 | * invalidate any mappings of other classes where this class is used, so | ||
| 117 | * this should not be used to notify that the mapped name for this class has | ||
| 118 | * changed. | ||
| 119 | * | ||
| 120 | * @param entry the class entry to invalidate | ||
| 121 | */ | ||
| 122 | public void invalidateMapped(ClassEntry entry) { | ||
| 123 | withLock(lock.readLock(), () -> { | ||
| 124 | Entry e = handles.get(entry); | ||
| 125 | if (e != null) { | ||
| 126 | e.invalidateMapped(); | ||
| 127 | } | ||
| 128 | }); | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * Invalidates javadoc for a single class. This also causes the class to be | ||
| 133 | * remapped again. | ||
| 134 | * | ||
| 135 | * @param entry the class entry to invalidate | ||
| 136 | */ | ||
| 137 | public void invalidateJavadoc(ClassEntry entry) { | ||
| 138 | withLock(lock.readLock(), () -> { | ||
| 139 | Entry e = handles.get(entry); | ||
| 140 | if (e != null) { | ||
| 141 | e.invalidateJavadoc(); | ||
| 142 | } | ||
| 143 | }); | ||
| 144 | } | ||
| 145 | |||
| 146 | private void deleteEntry(Entry entry) { | ||
| 147 | withLock(lock.writeLock(), () -> { | ||
| 148 | handles.remove(entry.entry); | ||
| 149 | }); | ||
| 150 | } | ||
| 151 | |||
| 152 | /** | ||
| 153 | * Destroy this class handle provider. The decompiler threads will try to | ||
| 154 | * shutdown cleanly, and then every open class handle will also be deleted. | ||
| 155 | * This causes {@link ClassHandleListener#onDeleted(ClassHandle)} to get | ||
| 156 | * called. | ||
| 157 | * | ||
| 158 | * <p>After this method is called, this class handle provider can no longer | ||
| 159 | * be used. | ||
| 160 | */ | ||
| 161 | public void destroy() { | ||
| 162 | pool.shutdown(); | ||
| 163 | try { | ||
| 164 | pool.awaitTermination(30, TimeUnit.SECONDS); | ||
| 165 | } catch (InterruptedException e) { | ||
| 166 | throw new RuntimeException(e); | ||
| 167 | } | ||
| 168 | |||
| 169 | withLock(lock.writeLock(), () -> { | ||
| 170 | handles.values().forEach(Entry::destroy); | ||
| 171 | handles.clear(); | ||
| 172 | }); | ||
| 173 | } | ||
| 174 | |||
| 175 | private static final class Entry { | ||
| 176 | |||
| 177 | private final ClassHandleProvider p; | ||
| 178 | private final ClassEntry entry; | ||
| 179 | private ClassEntry deobfRef; | ||
| 180 | private final List<ClassHandleImpl> handles = new ArrayList<>(); | ||
| 181 | private Result<Source, ClassHandleError> uncommentedSource; | ||
| 182 | private Result<DecompiledClassSource, ClassHandleError> source; | ||
| 183 | |||
| 184 | private final List<CompletableFuture<Result<Source, ClassHandleError>>> waitingUncommentedSources = Collections.synchronizedList(new ArrayList<>()); | ||
| 185 | private final List<CompletableFuture<Result<DecompiledClassSource, ClassHandleError>>> waitingSources = Collections.synchronizedList(new ArrayList<>()); | ||
| 186 | |||
| 187 | private final AtomicInteger decompileVersion = new AtomicInteger(); | ||
| 188 | private final AtomicInteger javadocVersion = new AtomicInteger(); | ||
| 189 | private final AtomicInteger indexVersion = new AtomicInteger(); | ||
| 190 | private final AtomicInteger mappedVersion = new AtomicInteger(); | ||
| 191 | |||
| 192 | private final ReadWriteLock lock = new ReentrantReadWriteLock(); | ||
| 193 | |||
| 194 | private Entry(ClassHandleProvider p, ClassEntry entry) { | ||
| 195 | this.p = p; | ||
| 196 | this.entry = entry; | ||
| 197 | this.deobfRef = p.project.getMapper().deobfuscate(entry); | ||
| 198 | invalidate(); | ||
| 199 | } | ||
| 200 | |||
| 201 | public ClassHandleImpl createHandle() { | ||
| 202 | ClassHandleImpl handle = new ClassHandleImpl(this); | ||
| 203 | withLock(lock.writeLock(), () -> { | ||
| 204 | handles.add(handle); | ||
| 205 | }); | ||
| 206 | return handle; | ||
| 207 | } | ||
| 208 | |||
| 209 | @Nullable | ||
| 210 | public ClassEntry getDeobfRef() { | ||
| 211 | return deobfRef; | ||
| 212 | } | ||
| 213 | |||
| 214 | private void checkDeobfRefForUpdate() { | ||
| 215 | ClassEntry newDeobf = p.project.getMapper().deobfuscate(entry); | ||
| 216 | if (!Objects.equals(deobfRef, newDeobf)) { | ||
| 217 | deobfRef = newDeobf; | ||
| 218 | // copy the list so we don't call event listener code with the lock active | ||
| 219 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onDeobfRefChanged(newDeobf)); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | public void invalidate() { | ||
| 224 | checkDeobfRefForUpdate(); | ||
| 225 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onInvalidate(InvalidationType.FULL)); | ||
| 226 | continueMapSource(continueIndexSource(continueInsertJavadoc(decompile()))); | ||
| 227 | } | ||
| 228 | |||
| 229 | public void invalidateJavadoc() { | ||
| 230 | checkDeobfRefForUpdate(); | ||
| 231 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onInvalidate(InvalidationType.JAVADOC)); | ||
| 232 | continueMapSource(continueIndexSource(continueInsertJavadoc(CompletableFuture.completedFuture(uncommentedSource)))); | ||
| 233 | } | ||
| 234 | |||
| 235 | public void invalidateMapped() { | ||
| 236 | checkDeobfRefForUpdate(); | ||
| 237 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onInvalidate(InvalidationType.MAPPINGS)); | ||
| 238 | continueMapSource(CompletableFuture.completedFuture(source)); | ||
| 239 | } | ||
| 240 | |||
| 241 | private CompletableFuture<Result<Source, ClassHandleError>> decompile() { | ||
| 242 | int v = decompileVersion.incrementAndGet(); | ||
| 243 | return CompletableFuture.supplyAsync(() -> { | ||
| 244 | if (decompileVersion.get() != v) return null; | ||
| 245 | |||
| 246 | Result<Source, ClassHandleError> _uncommentedSource; | ||
| 247 | try { | ||
| 248 | _uncommentedSource = Result.ok(p.decompiler.getSource(entry.getFullName())); | ||
| 249 | } catch (Throwable e) { | ||
| 250 | return Result.err(ClassHandleError.decompile(e)); | ||
| 251 | } | ||
| 252 | Result<Source, ClassHandleError> uncommentedSource = _uncommentedSource; | ||
| 253 | Entry.this.uncommentedSource = uncommentedSource; | ||
| 254 | Entry.this.waitingUncommentedSources.forEach(f -> f.complete(uncommentedSource)); | ||
| 255 | Entry.this.waitingUncommentedSources.clear(); | ||
| 256 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onUncommentedSourceChanged(uncommentedSource)); | ||
| 257 | return uncommentedSource; | ||
| 258 | }, p.pool); | ||
| 259 | } | ||
| 260 | |||
| 261 | private CompletableFuture<Result<Source, ClassHandleError>> continueInsertJavadoc(CompletableFuture<Result<Source, ClassHandleError>> f) { | ||
| 262 | int v = javadocVersion.incrementAndGet(); | ||
| 263 | return f.thenApplyAsync(res -> { | ||
| 264 | if (res == null || javadocVersion.get() != v) return null; | ||
| 265 | Result<Source, ClassHandleError> jdSource = res.map(s -> s.addJavadocs(p.project.getMapper())); | ||
| 266 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onDocsChanged(jdSource)); | ||
| 267 | return jdSource; | ||
| 268 | }, p.pool); | ||
| 269 | } | ||
| 270 | |||
| 271 | private CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> continueIndexSource(CompletableFuture<Result<Source, ClassHandleError>> f) { | ||
| 272 | int v = indexVersion.incrementAndGet(); | ||
| 273 | return f.thenApplyAsync(res -> { | ||
| 274 | if (res == null || indexVersion.get() != v) return null; | ||
| 275 | return res.andThen(jdSource -> { | ||
| 276 | SourceIndex index = jdSource.index(); | ||
| 277 | index.resolveReferences(p.project.getMapper().getObfResolver()); | ||
| 278 | DecompiledClassSource source = new DecompiledClassSource(entry, index); | ||
| 279 | return Result.ok(source); | ||
| 280 | }); | ||
| 281 | }, p.pool); | ||
| 282 | } | ||
| 283 | |||
| 284 | private void continueMapSource(CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> f) { | ||
| 285 | int v = mappedVersion.incrementAndGet(); | ||
| 286 | f.thenAcceptAsync(res -> { | ||
| 287 | if (res == null || mappedVersion.get() != v) return; | ||
| 288 | res = res.map(source -> { | ||
| 289 | source.remapSource(p.project, p.project.getMapper().getDeobfuscator()); | ||
| 290 | return source; | ||
| 291 | }); | ||
| 292 | Entry.this.source = res; | ||
| 293 | Entry.this.waitingSources.forEach(s -> s.complete(source)); | ||
| 294 | Entry.this.waitingSources.clear(); | ||
| 295 | withLock(lock.readLock(), () -> new ArrayList<>(handles)).forEach(h -> h.onMappedSourceChanged(source)); | ||
| 296 | }, p.pool); | ||
| 297 | } | ||
| 298 | |||
| 299 | public void closeHandle(ClassHandleImpl classHandle) { | ||
| 300 | classHandle.destroy(); | ||
| 301 | withLock(lock.writeLock(), () -> { | ||
| 302 | handles.remove(classHandle); | ||
| 303 | if (handles.isEmpty()) { | ||
| 304 | p.deleteEntry(this); | ||
| 305 | } | ||
| 306 | }); | ||
| 307 | } | ||
| 308 | |||
| 309 | public void destroy() { | ||
| 310 | withLock(lock.writeLock(), () -> { | ||
| 311 | handles.forEach(ClassHandleImpl::destroy); | ||
| 312 | handles.clear(); | ||
| 313 | }); | ||
| 314 | } | ||
| 315 | |||
| 316 | public CompletableFuture<Result<Source, ClassHandleError>> getUncommentedSourceAsync() { | ||
| 317 | if (uncommentedSource != null) { | ||
| 318 | return CompletableFuture.completedFuture(uncommentedSource); | ||
| 319 | } else { | ||
| 320 | CompletableFuture<Result<Source, ClassHandleError>> f = new CompletableFuture<>(); | ||
| 321 | waitingUncommentedSources.add(f); | ||
| 322 | return f; | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | public CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> getSourceAsync() { | ||
| 327 | if (source != null) { | ||
| 328 | return CompletableFuture.completedFuture(source); | ||
| 329 | } else { | ||
| 330 | CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> f = new CompletableFuture<>(); | ||
| 331 | waitingSources.add(f); | ||
| 332 | return f; | ||
| 333 | } | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | private static final class ClassHandleImpl implements ClassHandle { | ||
| 338 | |||
| 339 | private final Entry entry; | ||
| 340 | |||
| 341 | private boolean valid = true; | ||
| 342 | |||
| 343 | private final Set<ClassHandleListener> listeners = new HashSet<>(); | ||
| 344 | |||
| 345 | private ClassHandleImpl(Entry entry) { | ||
| 346 | this.entry = entry; | ||
| 347 | } | ||
| 348 | |||
| 349 | @Override | ||
| 350 | public ClassEntry getRef() { | ||
| 351 | checkValid(); | ||
| 352 | return entry.entry; | ||
| 353 | } | ||
| 354 | |||
| 355 | @Nullable | ||
| 356 | @Override | ||
| 357 | public ClassEntry getDeobfRef() { | ||
| 358 | checkValid(); | ||
| 359 | // cache this? | ||
| 360 | return entry.getDeobfRef(); | ||
| 361 | } | ||
| 362 | |||
| 363 | @Override | ||
| 364 | public CompletableFuture<Result<DecompiledClassSource, ClassHandleError>> getSource() { | ||
| 365 | checkValid(); | ||
| 366 | return entry.getSourceAsync(); | ||
| 367 | } | ||
| 368 | |||
| 369 | @Override | ||
| 370 | public CompletableFuture<Result<Source, ClassHandleError>> getUncommentedSource() { | ||
| 371 | checkValid(); | ||
| 372 | return entry.getUncommentedSourceAsync(); | ||
| 373 | } | ||
| 374 | |||
| 375 | @Override | ||
| 376 | public void invalidate() { | ||
| 377 | checkValid(); | ||
| 378 | this.entry.invalidate(); | ||
| 379 | } | ||
| 380 | |||
| 381 | @Override | ||
| 382 | public void invalidateMapped() { | ||
| 383 | checkValid(); | ||
| 384 | this.entry.invalidateMapped(); | ||
| 385 | } | ||
| 386 | |||
| 387 | @Override | ||
| 388 | public void invalidateJavadoc() { | ||
| 389 | checkValid(); | ||
| 390 | this.entry.invalidateJavadoc(); | ||
| 391 | } | ||
| 392 | |||
| 393 | public void onUncommentedSourceChanged(Result<Source, ClassHandleError> source) { | ||
| 394 | listeners.forEach(l -> l.onUncommentedSourceChanged(this, source)); | ||
| 395 | } | ||
| 396 | |||
| 397 | public void onDocsChanged(Result<Source, ClassHandleError> source) { | ||
| 398 | listeners.forEach(l -> l.onDocsChanged(this, source)); | ||
| 399 | } | ||
| 400 | |||
| 401 | public void onMappedSourceChanged(Result<DecompiledClassSource, ClassHandleError> source) { | ||
| 402 | listeners.forEach(l -> l.onMappedSourceChanged(this, source)); | ||
| 403 | } | ||
| 404 | |||
| 405 | public void onInvalidate(InvalidationType t) { | ||
| 406 | listeners.forEach(l -> l.onInvalidate(this, t)); | ||
| 407 | } | ||
| 408 | |||
| 409 | public void onDeobfRefChanged(ClassEntry newDeobf) { | ||
| 410 | listeners.forEach(l -> l.onDeobfRefChanged(this, newDeobf)); | ||
| 411 | } | ||
| 412 | |||
| 413 | @Override | ||
| 414 | public void addListener(ClassHandleListener listener) { | ||
| 415 | listeners.add(listener); | ||
| 416 | } | ||
| 417 | |||
| 418 | @Override | ||
| 419 | public void removeListener(ClassHandleListener listener) { | ||
| 420 | listeners.remove(listener); | ||
| 421 | } | ||
| 422 | |||
| 423 | @Override | ||
| 424 | public ClassHandle copy() { | ||
| 425 | checkValid(); | ||
| 426 | return entry.createHandle(); | ||
| 427 | } | ||
| 428 | |||
| 429 | @Override | ||
| 430 | public void close() { | ||
| 431 | if (valid) entry.closeHandle(this); | ||
| 432 | } | ||
| 433 | |||
| 434 | private void checkValid() { | ||
| 435 | if (!valid) throw new IllegalStateException("Class handle no longer valid"); | ||
| 436 | } | ||
| 437 | |||
| 438 | public void destroy() { | ||
| 439 | listeners.forEach(l -> l.onDeleted(this)); | ||
| 440 | valid = false; | ||
| 441 | } | ||
| 442 | |||
| 443 | } | ||
| 444 | |||
| 445 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/events/ClassHandleListener.java b/enigma/src/main/java/cuchaz/enigma/events/ClassHandleListener.java new file mode 100644 index 0000000..61fea4e --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/events/ClassHandleListener.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | package cuchaz.enigma.events; | ||
| 2 | |||
| 3 | import cuchaz.enigma.classhandle.ClassHandle; | ||
| 4 | import cuchaz.enigma.classhandle.ClassHandleError; | ||
| 5 | import cuchaz.enigma.source.DecompiledClassSource; | ||
| 6 | import cuchaz.enigma.source.Source; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 8 | import cuchaz.enigma.utils.Result; | ||
| 9 | |||
| 10 | public interface ClassHandleListener { | ||
| 11 | |||
| 12 | default void onDeobfRefChanged(ClassHandle h, ClassEntry deobfRef) { | ||
| 13 | } | ||
| 14 | |||
| 15 | default void onUncommentedSourceChanged(ClassHandle h, Result<Source, ClassHandleError> res) { | ||
| 16 | } | ||
| 17 | |||
| 18 | default void onDocsChanged(ClassHandle h, Result<Source, ClassHandleError> res) { | ||
| 19 | } | ||
| 20 | |||
| 21 | default void onMappedSourceChanged(ClassHandle h, Result<DecompiledClassSource, ClassHandleError> res) { | ||
| 22 | } | ||
| 23 | |||
| 24 | default void onInvalidate(ClassHandle h, InvalidationType t) { | ||
| 25 | } | ||
| 26 | |||
| 27 | default void onDeleted(ClassHandle h) { | ||
| 28 | } | ||
| 29 | |||
| 30 | enum InvalidationType { | ||
| 31 | FULL, | ||
| 32 | JAVADOC, | ||
| 33 | MAPPINGS, | ||
| 34 | } | ||
| 35 | |||
| 36 | } | ||
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java index aca5d72..85fba50 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java | |||
| @@ -1,13 +1,13 @@ | |||
| 1 | package cuchaz.enigma.gui; | 1 | package cuchaz.enigma.source; |
| 2 | |||
| 3 | import java.util.*; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 2 | 6 | ||
| 3 | import cuchaz.enigma.EnigmaProject; | 7 | import cuchaz.enigma.EnigmaProject; |
| 4 | import cuchaz.enigma.EnigmaServices; | 8 | import cuchaz.enigma.EnigmaServices; |
| 5 | import cuchaz.enigma.analysis.EntryReference; | 9 | import cuchaz.enigma.analysis.EntryReference; |
| 6 | import cuchaz.enigma.source.Token; | ||
| 7 | import cuchaz.enigma.api.service.NameProposalService; | 10 | import cuchaz.enigma.api.service.NameProposalService; |
| 8 | import cuchaz.enigma.gui.highlight.TokenHighlightType; | ||
| 9 | import cuchaz.enigma.source.SourceIndex; | ||
| 10 | import cuchaz.enigma.source.SourceRemapper; | ||
| 11 | import cuchaz.enigma.translation.LocalNameGenerator; | 11 | import cuchaz.enigma.translation.LocalNameGenerator; |
| 12 | import cuchaz.enigma.translation.Translator; | 12 | import cuchaz.enigma.translation.Translator; |
| 13 | import cuchaz.enigma.translation.mapping.EntryRemapper; | 13 | import cuchaz.enigma.translation.mapping.EntryRemapper; |
| @@ -17,16 +17,13 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; | |||
| 17 | import cuchaz.enigma.translation.representation.entry.Entry; | 17 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 18 | import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; | 18 | import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; |
| 19 | 19 | ||
| 20 | import javax.annotation.Nullable; | ||
| 21 | import java.util.*; | ||
| 22 | |||
| 23 | public class DecompiledClassSource { | 20 | public class DecompiledClassSource { |
| 24 | private final ClassEntry classEntry; | 21 | private final ClassEntry classEntry; |
| 25 | 22 | ||
| 26 | private final SourceIndex obfuscatedIndex; | 23 | private final SourceIndex obfuscatedIndex; |
| 27 | private SourceIndex remappedIndex; | 24 | private SourceIndex remappedIndex; |
| 28 | 25 | ||
| 29 | private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class); | 26 | private final Map<RenamableTokenType, Collection<Token>> highlightedTokens = new EnumMap<>(RenamableTokenType.class); |
| 30 | 27 | ||
| 31 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { | 28 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { |
| 32 | this.classEntry = classEntry; | 29 | this.classEntry = classEntry; |
| @@ -55,16 +52,16 @@ public class DecompiledClassSource { | |||
| 55 | 52 | ||
| 56 | if (project.isRenamable(reference)) { | 53 | if (project.isRenamable(reference)) { |
| 57 | if (isDeobfuscated(entry, translatedEntry)) { | 54 | if (isDeobfuscated(entry, translatedEntry)) { |
| 58 | highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); | 55 | highlightToken(movedToken, RenamableTokenType.DEOBFUSCATED); |
| 59 | return translatedEntry.getSourceRemapName(); | 56 | return translatedEntry.getSourceRemapName(); |
| 60 | } else { | 57 | } else { |
| 61 | Optional<String> proposedName = proposeName(project, entry); | 58 | Optional<String> proposedName = proposeName(project, entry); |
| 62 | if (proposedName.isPresent()) { | 59 | if (proposedName.isPresent()) { |
| 63 | highlightToken(movedToken, TokenHighlightType.PROPOSED); | 60 | highlightToken(movedToken, RenamableTokenType.PROPOSED); |
| 64 | return proposedName.get(); | 61 | return proposedName.get(); |
| 65 | } | 62 | } |
| 66 | 63 | ||
| 67 | highlightToken(movedToken, TokenHighlightType.OBFUSCATED); | 64 | highlightToken(movedToken, RenamableTokenType.OBFUSCATED); |
| 68 | } | 65 | } |
| 69 | } | 66 | } |
| 70 | 67 | ||
| @@ -119,11 +116,11 @@ public class DecompiledClassSource { | |||
| 119 | return remappedIndex; | 116 | return remappedIndex; |
| 120 | } | 117 | } |
| 121 | 118 | ||
| 122 | public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() { | 119 | public Map<RenamableTokenType, Collection<Token>> getHighlightedTokens() { |
| 123 | return highlightedTokens; | 120 | return highlightedTokens; |
| 124 | } | 121 | } |
| 125 | 122 | ||
| 126 | private void highlightToken(Token token, TokenHighlightType highlightType) { | 123 | private void highlightToken(Token token, RenamableTokenType highlightType) { |
| 127 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); | 124 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); |
| 128 | } | 125 | } |
| 129 | 126 | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/source/RenamableTokenType.java b/enigma/src/main/java/cuchaz/enigma/source/RenamableTokenType.java new file mode 100644 index 0000000..c63aad9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/source/RenamableTokenType.java | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | public enum RenamableTokenType { | ||
| 4 | OBFUSCATED, | ||
| 5 | DEOBFUSCATED, | ||
| 6 | PROPOSED | ||
| 7 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java index 18c966c..dec75ff 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java | |||
| @@ -1,18 +1,18 @@ | |||
| 1 | package cuchaz.enigma.translation; | 1 | package cuchaz.enigma.translation; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 4 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 5 | |||
| 6 | import java.util.Collection; | 3 | import java.util.Collection; |
| 7 | import java.util.Locale; | 4 | import java.util.Locale; |
| 8 | 5 | ||
| 6 | import cuchaz.enigma.translation.mapping.IdentifierValidation; | ||
| 7 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 8 | |||
| 9 | public class LocalNameGenerator { | 9 | public class LocalNameGenerator { |
| 10 | public static String generateArgumentName(int index, TypeDescriptor desc, Collection<TypeDescriptor> arguments) { | 10 | public static String generateArgumentName(int index, TypeDescriptor desc, Collection<TypeDescriptor> arguments) { |
| 11 | boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1; | 11 | boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1; |
| 12 | String translatedName; | 12 | String translatedName; |
| 13 | int nameIndex = index + 1; | 13 | int nameIndex = index + 1; |
| 14 | StringBuilder nameBuilder = new StringBuilder(getTypeName(desc)); | 14 | StringBuilder nameBuilder = new StringBuilder(getTypeName(desc)); |
| 15 | if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) { | 15 | if (!uniqueType || IdentifierValidation.isReservedMethodName(nameBuilder.toString())) { |
| 16 | nameBuilder.append(nameIndex); | 16 | nameBuilder.append(nameIndex); |
| 17 | } | 17 | } |
| 18 | translatedName = nameBuilder.toString(); | 18 | translatedName = nameBuilder.toString(); |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java index 1dd7eac..932b5bb 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java | |||
| @@ -1,5 +1,10 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | 1 | package cuchaz.enigma.translation.mapping; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | ||
| 4 | import java.util.stream.Stream; | ||
| 5 | |||
| 6 | import javax.annotation.Nullable; | ||
| 7 | |||
| 3 | import cuchaz.enigma.analysis.index.JarIndex; | 8 | import cuchaz.enigma.analysis.index.JarIndex; |
| 4 | import cuchaz.enigma.translation.MappingTranslator; | 9 | import cuchaz.enigma.translation.MappingTranslator; |
| 5 | import cuchaz.enigma.translation.Translatable; | 10 | import cuchaz.enigma.translation.Translatable; |
| @@ -8,10 +13,7 @@ import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; | |||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 13 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 9 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; | 14 | import cuchaz.enigma.translation.mapping.tree.HashEntryTree; |
| 10 | import cuchaz.enigma.translation.representation.entry.Entry; | 15 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 11 | 16 | import cuchaz.enigma.utils.validation.ValidationContext; | |
| 12 | import javax.annotation.Nullable; | ||
| 13 | import java.util.Collection; | ||
| 14 | import java.util.stream.Stream; | ||
| 15 | 17 | ||
| 16 | public class EntryRemapper { | 18 | public class EntryRemapper { |
| 17 | private final DeltaTrackingTree<EntryMapping> obfToDeobf; | 19 | private final DeltaTrackingTree<EntryMapping> obfToDeobf; |
| @@ -39,26 +41,32 @@ public class EntryRemapper { | |||
| 39 | return new EntryRemapper(index, new HashEntryTree<>()); | 41 | return new EntryRemapper(index, new HashEntryTree<>()); |
| 40 | } | 42 | } |
| 41 | 43 | ||
| 42 | public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { | 44 | public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { |
| 43 | mapFromObf(obfuscatedEntry, deobfMapping, true); | 45 | mapFromObf(vc, obfuscatedEntry, deobfMapping, true); |
| 44 | } | 46 | } |
| 45 | 47 | ||
| 46 | public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { | 48 | public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { |
| 49 | mapFromObf(vc, obfuscatedEntry, deobfMapping, renaming, false); | ||
| 50 | } | ||
| 51 | |||
| 52 | public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming, boolean validateOnly) { | ||
| 47 | Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); | 53 | Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); |
| 48 | 54 | ||
| 49 | if (renaming && deobfMapping != null) { | 55 | if (renaming && deobfMapping != null) { |
| 50 | for (E resolvedEntry : resolvedEntries) { | 56 | for (E resolvedEntry : resolvedEntries) { |
| 51 | validator.validateRename(resolvedEntry, deobfMapping.getTargetName()); | 57 | validator.validateRename(vc, resolvedEntry, deobfMapping.getTargetName()); |
| 52 | } | 58 | } |
| 53 | } | 59 | } |
| 54 | 60 | ||
| 61 | if (validateOnly || !vc.canProceed()) return; | ||
| 62 | |||
| 55 | for (E resolvedEntry : resolvedEntries) { | 63 | for (E resolvedEntry : resolvedEntries) { |
| 56 | obfToDeobf.insert(resolvedEntry, deobfMapping); | 64 | obfToDeobf.insert(resolvedEntry, deobfMapping); |
| 57 | } | 65 | } |
| 58 | } | 66 | } |
| 59 | 67 | ||
| 60 | public void removeByObf(Entry<?> obfuscatedEntry) { | 68 | public void removeByObf(ValidationContext vc, Entry<?> obfuscatedEntry) { |
| 61 | mapFromObf(obfuscatedEntry, null); | 69 | mapFromObf(vc, obfuscatedEntry, null); |
| 62 | } | 70 | } |
| 63 | 71 | ||
| 64 | @Nullable | 72 | @Nullable |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IdentifierValidation.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IdentifierValidation.java new file mode 100644 index 0000000..097c9e9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IdentifierValidation.java | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping; | ||
| 13 | |||
| 14 | import java.util.Arrays; | ||
| 15 | import java.util.List; | ||
| 16 | |||
| 17 | import cuchaz.enigma.utils.validation.Message; | ||
| 18 | import cuchaz.enigma.utils.validation.StandardValidation; | ||
| 19 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 20 | |||
| 21 | public final class IdentifierValidation { | ||
| 22 | |||
| 23 | private IdentifierValidation() { | ||
| 24 | } | ||
| 25 | |||
| 26 | private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList( | ||
| 27 | "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", | ||
| 28 | "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", | ||
| 29 | "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", | ||
| 30 | "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", | ||
| 31 | "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_" | ||
| 32 | ); | ||
| 33 | |||
| 34 | public static boolean validateClassName(ValidationContext vc, String name) { | ||
| 35 | if (!StandardValidation.notBlank(vc, name)) return false; | ||
| 36 | String[] parts = name.split("/"); | ||
| 37 | for (String part : parts) { | ||
| 38 | validateIdentifier(vc, part); | ||
| 39 | } | ||
| 40 | return true; | ||
| 41 | } | ||
| 42 | |||
| 43 | public static boolean validateIdentifier(ValidationContext vc, String name) { | ||
| 44 | if (!StandardValidation.notBlank(vc, name)) return false; | ||
| 45 | if (checkForReservedName(vc, name)) return false; | ||
| 46 | |||
| 47 | // Adapted from javax.lang.model.SourceVersion.isIdentifier | ||
| 48 | |||
| 49 | int cp = name.codePointAt(0); | ||
| 50 | int position = 1; | ||
| 51 | if (!Character.isJavaIdentifierStart(cp)) { | ||
| 52 | vc.raise(Message.ILLEGAL_IDENTIFIER, name, new String(Character.toChars(cp)), position); | ||
| 53 | return false; | ||
| 54 | } | ||
| 55 | for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) { | ||
| 56 | cp = name.codePointAt(i); | ||
| 57 | position += 1; | ||
| 58 | if (!Character.isJavaIdentifierPart(cp)) { | ||
| 59 | vc.raise(Message.ILLEGAL_IDENTIFIER, name, new String(Character.toChars(cp)), position); | ||
| 60 | return false; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | return true; | ||
| 65 | } | ||
| 66 | |||
| 67 | private static boolean checkForReservedName(ValidationContext vc, String name) { | ||
| 68 | if (isReservedMethodName(name)) { | ||
| 69 | vc.raise(Message.RESERVED_IDENTIFIER); | ||
| 70 | return true; | ||
| 71 | } | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | public static boolean isReservedMethodName(String name) { | ||
| 76 | return ILLEGAL_IDENTIFIERS.contains(name); | ||
| 77 | } | ||
| 78 | |||
| 79 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java deleted file mode 100644 index a7f83cd..0000000 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/IllegalNameException.java +++ /dev/null | |||
| @@ -1,39 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping; | ||
| 13 | |||
| 14 | public class IllegalNameException extends RuntimeException { | ||
| 15 | |||
| 16 | private String name; | ||
| 17 | private String reason; | ||
| 18 | |||
| 19 | public IllegalNameException(String name, String reason) { | ||
| 20 | this.name = name; | ||
| 21 | this.reason = reason; | ||
| 22 | } | ||
| 23 | |||
| 24 | public String getReason() { | ||
| 25 | return this.reason; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public String getMessage() { | ||
| 30 | StringBuilder buf = new StringBuilder(); | ||
| 31 | buf.append("Illegal name: "); | ||
| 32 | buf.append(this.name); | ||
| 33 | if (this.reason != null) { | ||
| 34 | buf.append(" because "); | ||
| 35 | buf.append(this.reason); | ||
| 36 | } | ||
| 37 | return buf.toString(); | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java index ae615da..f9f3b88 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java | |||
| @@ -1,17 +1,20 @@ | |||
| 1 | package cuchaz.enigma.translation.mapping; | 1 | package cuchaz.enigma.translation.mapping; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | ||
| 4 | import java.util.HashSet; | ||
| 5 | import java.util.stream.Collectors; | ||
| 6 | |||
| 3 | import cuchaz.enigma.analysis.index.InheritanceIndex; | 7 | import cuchaz.enigma.analysis.index.InheritanceIndex; |
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | 8 | import cuchaz.enigma.analysis.index.JarIndex; |
| 5 | import cuchaz.enigma.translation.Translator; | 9 | import cuchaz.enigma.translation.Translator; |
| 6 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 10 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 7 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 11 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| 8 | import cuchaz.enigma.translation.representation.entry.Entry; | 12 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 9 | 13 | import cuchaz.enigma.utils.validation.Message; | |
| 10 | import java.util.Collection; | 14 | import cuchaz.enigma.utils.validation.ValidationContext; |
| 11 | import java.util.HashSet; | ||
| 12 | import java.util.stream.Collectors; | ||
| 13 | 15 | ||
| 14 | public class MappingValidator { | 16 | public class MappingValidator { |
| 17 | |||
| 15 | private final EntryTree<EntryMapping> obfToDeobf; | 18 | private final EntryTree<EntryMapping> obfToDeobf; |
| 16 | private final Translator deobfuscator; | 19 | private final Translator deobfuscator; |
| 17 | private final JarIndex index; | 20 | private final JarIndex index; |
| @@ -22,15 +25,15 @@ public class MappingValidator { | |||
| 22 | this.index = index; | 25 | this.index = index; |
| 23 | } | 26 | } |
| 24 | 27 | ||
| 25 | public void validateRename(Entry<?> entry, String name) throws IllegalNameException { | 28 | public void validateRename(ValidationContext vc, Entry<?> entry, String name) { |
| 26 | Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); | 29 | Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); |
| 27 | for (Entry<?> equivalentEntry : equivalentEntries) { | 30 | for (Entry<?> equivalentEntry : equivalentEntries) { |
| 28 | equivalentEntry.validateName(name); | 31 | equivalentEntry.validateName(vc, name); |
| 29 | validateUnique(equivalentEntry, name); | 32 | validateUnique(vc, equivalentEntry, name); |
| 30 | } | 33 | } |
| 31 | } | 34 | } |
| 32 | 35 | ||
| 33 | private void validateUnique(Entry<?> entry, String name) { | 36 | private void validateUnique(ValidationContext vc, Entry<?> entry, String name) { |
| 34 | ClassEntry containingClass = entry.getContainingClass(); | 37 | ClassEntry containingClass = entry.getContainingClass(); |
| 35 | Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass); | 38 | Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass); |
| 36 | 39 | ||
| @@ -45,9 +48,9 @@ public class MappingValidator { | |||
| 45 | if (!isUnique(translatedEntry, translatedSiblings, name)) { | 48 | if (!isUnique(translatedEntry, translatedSiblings, name)) { |
| 46 | Entry<?> parent = translatedEntry.getParent(); | 49 | Entry<?> parent = translatedEntry.getParent(); |
| 47 | if (parent != null) { | 50 | if (parent != null) { |
| 48 | throw new IllegalNameException(name, "Name is not unique in " + parent + "!"); | 51 | vc.raise(Message.NONUNIQUE_NAME_CLASS, name, parent); |
| 49 | } else { | 52 | } else { |
| 50 | throw new IllegalNameException(name, "Name is not unique!"); | 53 | vc.raise(Message.NONUNIQUE_NAME, name); |
| 51 | } | 54 | } |
| 52 | } | 55 | } |
| 53 | } | 56 | } |
| @@ -72,4 +75,5 @@ public class MappingValidator { | |||
| 72 | } | 75 | } |
| 73 | return true; | 76 | return true; |
| 74 | } | 77 | } |
| 78 | |||
| 75 | } | 79 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java deleted file mode 100644 index 74ba633..0000000 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java +++ /dev/null | |||
| @@ -1,50 +0,0 @@ | |||
| 1 | /******************************************************************************* | ||
| 2 | * Copyright (c) 2015 Jeff Martin. | ||
| 3 | * All rights reserved. This program and the accompanying materials | ||
| 4 | * are made available under the terms of the GNU Lesser General Public | ||
| 5 | * License v3.0 which accompanies this distribution, and is available at | ||
| 6 | * http://www.gnu.org/licenses/lgpl.html | ||
| 7 | * <p> | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma.translation.mapping; | ||
| 13 | |||
| 14 | import java.util.Arrays; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.regex.Pattern; | ||
| 17 | |||
| 18 | public class NameValidator { | ||
| 19 | private static final Pattern IDENTIFIER_PATTERN; | ||
| 20 | private static final Pattern CLASS_PATTERN; | ||
| 21 | private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList( | ||
| 22 | "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", | ||
| 23 | "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", | ||
| 24 | "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", | ||
| 25 | "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", | ||
| 26 | "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_" | ||
| 27 | ); | ||
| 28 | |||
| 29 | static { | ||
| 30 | String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*"; | ||
| 31 | IDENTIFIER_PATTERN = Pattern.compile(identifierRegex); | ||
| 32 | CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex)); | ||
| 33 | } | ||
| 34 | |||
| 35 | public static void validateClassName(String name) { | ||
| 36 | if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { | ||
| 37 | throw new IllegalNameException(name, "This doesn't look like a legal class name"); | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | public static void validateIdentifier(String name) { | ||
| 42 | if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) { | ||
| 43 | throw new IllegalNameException(name, "This doesn't look like a legal identifier"); | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | public static boolean isReserved(String name) { | ||
| 48 | return ILLEGAL_IDENTIFIERS.contains(name); | ||
| 49 | } | ||
| 50 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java index 7d4b2ba..15b0a9b 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java | |||
| @@ -11,16 +11,17 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.translation.representation.entry; | 12 | package cuchaz.enigma.translation.representation.entry; |
| 13 | 13 | ||
| 14 | import cuchaz.enigma.translation.mapping.IllegalNameException; | 14 | import java.util.List; |
| 15 | import cuchaz.enigma.translation.Translator; | 15 | import java.util.Objects; |
| 16 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 17 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 18 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 19 | 16 | ||
| 20 | import javax.annotation.Nonnull; | 17 | import javax.annotation.Nonnull; |
| 21 | import javax.annotation.Nullable; | 18 | import javax.annotation.Nullable; |
| 22 | import java.util.List; | 19 | |
| 23 | import java.util.Objects; | 20 | import cuchaz.enigma.translation.Translator; |
| 21 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 22 | import cuchaz.enigma.translation.mapping.IdentifierValidation; | ||
| 23 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 24 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 24 | 25 | ||
| 25 | public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> { | 26 | public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> { |
| 26 | private final String fullName; | 27 | private final String fullName; |
| @@ -97,8 +98,8 @@ public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable< | |||
| 97 | } | 98 | } |
| 98 | 99 | ||
| 99 | @Override | 100 | @Override |
| 100 | public void validateName(String name) throws IllegalNameException { | 101 | public void validateName(ValidationContext vc, String name) { |
| 101 | NameValidator.validateClassName(name); | 102 | IdentifierValidation.validateClassName(vc, name); |
| 102 | } | 103 | } |
| 103 | 104 | ||
| 104 | @Override | 105 | @Override |
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java index 40bff31..ff392fe 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java | |||
| @@ -11,14 +11,15 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.translation.representation.entry; | 12 | package cuchaz.enigma.translation.representation.entry; |
| 13 | 13 | ||
| 14 | import cuchaz.enigma.translation.mapping.IllegalNameException; | ||
| 15 | import cuchaz.enigma.translation.Translatable; | ||
| 16 | import cuchaz.enigma.translation.mapping.NameValidator; | ||
| 17 | |||
| 18 | import javax.annotation.Nullable; | ||
| 19 | import java.util.ArrayList; | 14 | import java.util.ArrayList; |
| 20 | import java.util.List; | 15 | import java.util.List; |
| 21 | 16 | ||
| 17 | import javax.annotation.Nullable; | ||
| 18 | |||
| 19 | import cuchaz.enigma.translation.Translatable; | ||
| 20 | import cuchaz.enigma.translation.mapping.IdentifierValidation; | ||
| 21 | import cuchaz.enigma.utils.validation.ValidationContext; | ||
| 22 | |||
| 22 | public interface Entry<P extends Entry<?>> extends Translatable { | 23 | public interface Entry<P extends Entry<?>> extends Translatable { |
| 23 | String getName(); | 24 | String getName(); |
| 24 | 25 | ||
| @@ -92,8 +93,8 @@ public interface Entry<P extends Entry<?>> extends Translatable { | |||
| 92 | return withParent((P) parent.replaceAncestor(target, replacement)); | 93 | return withParent((P) parent.replaceAncestor(target, replacement)); |
| 93 | } | 94 | } |
| 94 | 95 | ||
| 95 | default void validateName(String name) throws IllegalNameException { | 96 | default void validateName(ValidationContext vc, String name) { |
| 96 | NameValidator.validateIdentifier(name); | 97 | IdentifierValidation.validateIdentifier(vc, name); |
| 97 | } | 98 | } |
| 98 | 99 | ||
| 99 | @SuppressWarnings("unchecked") | 100 | @SuppressWarnings("unchecked") |
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java index e18532b..cb498e0 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java | |||
| @@ -5,9 +5,8 @@ import java.io.IOException; | |||
| 5 | import java.io.InputStream; | 5 | import java.io.InputStream; |
| 6 | import java.io.InputStreamReader; | 6 | import java.io.InputStreamReader; |
| 7 | import java.nio.charset.StandardCharsets; | 7 | import java.nio.charset.StandardCharsets; |
| 8 | import java.util.ArrayList; | 8 | import java.util.*; |
| 9 | import java.util.Collections; | 9 | import java.util.stream.Collectors; |
| 10 | import java.util.Map; | ||
| 11 | import java.util.stream.Stream; | 10 | import java.util.stream.Stream; |
| 12 | 11 | ||
| 13 | import com.google.common.collect.ImmutableList; | 12 | import com.google.common.collect.ImmutableList; |
| @@ -22,12 +21,12 @@ public class I18n { | |||
| 22 | private static Map<String, String> translations = Maps.newHashMap(); | 21 | private static Map<String, String> translations = Maps.newHashMap(); |
| 23 | private static Map<String, String> defaultTranslations = Maps.newHashMap(); | 22 | private static Map<String, String> defaultTranslations = Maps.newHashMap(); |
| 24 | private static Map<String, String> languageNames = Maps.newHashMap(); | 23 | private static Map<String, String> languageNames = Maps.newHashMap(); |
| 25 | 24 | ||
| 26 | static { | 25 | static { |
| 27 | defaultTranslations = load(DEFAULT_LANGUAGE); | 26 | defaultTranslations = load(DEFAULT_LANGUAGE); |
| 28 | translations = defaultTranslations; | 27 | translations = defaultTranslations; |
| 29 | } | 28 | } |
| 30 | 29 | ||
| 31 | @SuppressWarnings("unchecked") | 30 | @SuppressWarnings("unchecked") |
| 32 | public static Map<String, String> load(String language) { | 31 | public static Map<String, String> load(String language) { |
| 33 | try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) { | 32 | try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) { |
| @@ -41,30 +40,50 @@ public class I18n { | |||
| 41 | } | 40 | } |
| 42 | return Collections.emptyMap(); | 41 | return Collections.emptyMap(); |
| 43 | } | 42 | } |
| 44 | 43 | ||
| 45 | public static String translate(String key) { | 44 | public static String translateOrNull(String key) { |
| 46 | String value = translations.get(key); | 45 | String value = translations.get(key); |
| 47 | if (value != null) { | 46 | if (value != null) return value; |
| 48 | return value; | 47 | |
| 48 | return defaultTranslations.get(key); | ||
| 49 | } | ||
| 50 | |||
| 51 | public static String translate(String key) { | ||
| 52 | String tr = translateOrNull(key); | ||
| 53 | return tr != null ? tr : key; | ||
| 54 | } | ||
| 55 | |||
| 56 | public static String translateOrEmpty(String key, Object... args) { | ||
| 57 | String text = translateOrNull(key); | ||
| 58 | if (text != null) { | ||
| 59 | return String.format(text, args); | ||
| 60 | } else { | ||
| 61 | return ""; | ||
| 49 | } | 62 | } |
| 50 | value = defaultTranslations.get(key); | 63 | } |
| 51 | if (value != null) { | 64 | |
| 52 | return value; | 65 | public static String translateFormatted(String key, Object... args) { |
| 66 | String text = translateOrNull(key); | ||
| 67 | if (text != null) { | ||
| 68 | return String.format(text, args); | ||
| 69 | } else if (args.length == 0) { | ||
| 70 | return key; | ||
| 71 | } else { | ||
| 72 | return key + Arrays.stream(args).map(Objects::toString).collect(Collectors.joining(", ", "[", "]")); | ||
| 53 | } | 73 | } |
| 54 | return key; | ||
| 55 | } | 74 | } |
| 56 | 75 | ||
| 57 | public static String getLanguageName(String language) { | 76 | public static String getLanguageName(String language) { |
| 58 | return languageNames.get(language); | 77 | return languageNames.get(language); |
| 59 | } | 78 | } |
| 60 | 79 | ||
| 61 | public static void setLanguage(String language) { | 80 | public static void setLanguage(String language) { |
| 62 | translations = load(language); | 81 | translations = load(language); |
| 63 | } | 82 | } |
| 64 | 83 | ||
| 65 | public static ArrayList<String> getAvailableLanguages() { | 84 | public static ArrayList<String> getAvailableLanguages() { |
| 66 | ArrayList<String> list = new ArrayList<String>(); | 85 | ArrayList<String> list = new ArrayList<String>(); |
| 67 | 86 | ||
| 68 | try { | 87 | try { |
| 69 | ImmutableList<ResourceInfo> resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList(); | 88 | ImmutableList<ResourceInfo> resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList(); |
| 70 | Stream<ResourceInfo> dirStream = resources.stream(); | 89 | Stream<ResourceInfo> dirStream = resources.stream(); |
| @@ -81,7 +100,7 @@ public class I18n { | |||
| 81 | } | 100 | } |
| 82 | return list; | 101 | return list; |
| 83 | } | 102 | } |
| 84 | 103 | ||
| 85 | private static void loadLanguageName(String fileName) { | 104 | private static void loadLanguageName(String fileName) { |
| 86 | try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) { | 105 | try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) { |
| 87 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { | 106 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { |
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Result.java b/enigma/src/main/java/cuchaz/enigma/utils/Result.java new file mode 100644 index 0000000..dcaabd5 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/Result.java | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import java.util.Objects; | ||
| 4 | import java.util.Optional; | ||
| 5 | import java.util.function.Function; | ||
| 6 | |||
| 7 | public final class Result<T, E> { | ||
| 8 | |||
| 9 | private final T ok; | ||
| 10 | private final E err; | ||
| 11 | |||
| 12 | private Result(T ok, E err) { | ||
| 13 | this.ok = ok; | ||
| 14 | this.err = err; | ||
| 15 | } | ||
| 16 | |||
| 17 | public static <T, E> Result<T, E> ok(T ok) { | ||
| 18 | return new Result<>(Objects.requireNonNull(ok), null); | ||
| 19 | } | ||
| 20 | |||
| 21 | public static <T, E> Result<T, E> err(E err) { | ||
| 22 | return new Result<>(null, Objects.requireNonNull(err)); | ||
| 23 | } | ||
| 24 | |||
| 25 | public boolean isOk() { | ||
| 26 | return this.ok != null; | ||
| 27 | } | ||
| 28 | |||
| 29 | public boolean isErr() { | ||
| 30 | return this.err != null; | ||
| 31 | } | ||
| 32 | |||
| 33 | public Optional<T> ok() { | ||
| 34 | return Optional.ofNullable(this.ok); | ||
| 35 | } | ||
| 36 | |||
| 37 | public Optional<E> err() { | ||
| 38 | return Optional.ofNullable(this.err); | ||
| 39 | } | ||
| 40 | |||
| 41 | public T unwrap() { | ||
| 42 | if (this.isOk()) return this.ok; | ||
| 43 | throw new IllegalStateException(String.format("Called Result.unwrap on an Err value: %s", this.err)); | ||
| 44 | } | ||
| 45 | |||
| 46 | public E unwrapErr() { | ||
| 47 | if (this.isErr()) return this.err; | ||
| 48 | throw new IllegalStateException(String.format("Called Result.unwrapErr on an Ok value: %s", this.ok)); | ||
| 49 | } | ||
| 50 | |||
| 51 | public T unwrapOr(T fallback) { | ||
| 52 | if (this.isOk()) return this.ok; | ||
| 53 | return fallback; | ||
| 54 | } | ||
| 55 | |||
| 56 | public T unwrapOrElse(Function<E, T> fn) { | ||
| 57 | if (this.isOk()) return this.ok; | ||
| 58 | return fn.apply(this.err); | ||
| 59 | } | ||
| 60 | |||
| 61 | @SuppressWarnings("unchecked") | ||
| 62 | public <U> Result<U, E> map(Function<T, U> op) { | ||
| 63 | if (!this.isOk()) return (Result<U, E>) this; | ||
| 64 | return Result.ok(op.apply(this.ok)); | ||
| 65 | } | ||
| 66 | |||
| 67 | @SuppressWarnings("unchecked") | ||
| 68 | public <F> Result<T, F> mapErr(Function<E, F> op) { | ||
| 69 | if (!this.isErr()) return (Result<T, F>) this; | ||
| 70 | return Result.err(op.apply(this.err)); | ||
| 71 | } | ||
| 72 | |||
| 73 | @SuppressWarnings("unchecked") | ||
| 74 | public <U> Result<U, E> and(Result<U, E> next) { | ||
| 75 | if (this.isErr()) return (Result<U, E>) this; | ||
| 76 | return next; | ||
| 77 | } | ||
| 78 | |||
| 79 | @SuppressWarnings("unchecked") | ||
| 80 | public <U> Result<U, E> andThen(Function<T, Result<U, E>> op) { | ||
| 81 | if (this.isErr()) return (Result<U, E>) this; | ||
| 82 | return op.apply(this.ok); | ||
| 83 | } | ||
| 84 | |||
| 85 | @Override | ||
| 86 | public boolean equals(Object o) { | ||
| 87 | if (this == o) return true; | ||
| 88 | if (o == null || getClass() != o.getClass()) return false; | ||
| 89 | Result<?, ?> result = (Result<?, ?>) o; | ||
| 90 | return Objects.equals(ok, result.ok) && | ||
| 91 | Objects.equals(err, result.err); | ||
| 92 | } | ||
| 93 | |||
| 94 | @Override | ||
| 95 | public int hashCode() { | ||
| 96 | return Objects.hash(ok, err); | ||
| 97 | } | ||
| 98 | |||
| 99 | @Override | ||
| 100 | public String toString() { | ||
| 101 | if (this.isOk()) { | ||
| 102 | return String.format("Result.Ok(%s)", this.ok); | ||
| 103 | } else { | ||
| 104 | return String.format("Result.Err(%s)", this.err); | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java index 2664099..8beaaae 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/Utils.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/Utils.java | |||
| @@ -25,6 +25,8 @@ import java.util.Collections; | |||
| 25 | import java.util.Comparator; | 25 | import java.util.Comparator; |
| 26 | import java.util.List; | 26 | import java.util.List; |
| 27 | import java.util.Locale; | 27 | import java.util.Locale; |
| 28 | import java.util.concurrent.locks.Lock; | ||
| 29 | import java.util.function.Supplier; | ||
| 28 | import java.util.stream.Collectors; | 30 | import java.util.stream.Collectors; |
| 29 | import java.util.zip.ZipEntry; | 31 | import java.util.zip.ZipEntry; |
| 30 | import java.util.zip.ZipFile; | 32 | import java.util.zip.ZipFile; |
| @@ -78,6 +80,25 @@ public class Utils { | |||
| 78 | return digest.digest(); | 80 | return digest.digest(); |
| 79 | } | 81 | } |
| 80 | 82 | ||
| 83 | public static void withLock(Lock l, Runnable op) { | ||
| 84 | try { | ||
| 85 | l.lock(); | ||
| 86 | op.run(); | ||
| 87 | } finally { | ||
| 88 | l.unlock(); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | public static <R> R withLock(Lock l, Supplier<R> op) { | ||
| 93 | try { | ||
| 94 | l.lock(); | ||
| 95 | return op.get(); | ||
| 96 | } finally { | ||
| 97 | l.unlock(); | ||
| 98 | } | ||
| 99 | } | ||
| 100 | |||
| 101 | |||
| 81 | public static boolean isBlank(String input) { | 102 | public static boolean isBlank(String input) { |
| 82 | if (input == null) { | 103 | if (input == null) { |
| 83 | return true; | 104 | return true; |
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/Message.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/Message.java new file mode 100644 index 0000000..dca74bc --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/Message.java | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.utils.I18n; | ||
| 4 | |||
| 5 | public class Message { | ||
| 6 | |||
| 7 | public static final Message EMPTY_FIELD = create(Type.ERROR, "empty_field"); | ||
| 8 | public static final Message NOT_INT = create(Type.ERROR, "not_int"); | ||
| 9 | public static final Message FIELD_OUT_OF_RANGE_INT = create(Type.ERROR, "field_out_of_range_int"); | ||
| 10 | public static final Message FIELD_LENGTH_OUT_OF_RANGE = create(Type.ERROR, "field_length_out_of_range"); | ||
| 11 | public static final Message NONUNIQUE_NAME_CLASS = create(Type.ERROR, "nonunique_name_class"); | ||
| 12 | public static final Message NONUNIQUE_NAME = create(Type.ERROR, "nonunique_name"); | ||
| 13 | public static final Message ILLEGAL_CLASS_NAME = create(Type.ERROR, "illegal_class_name"); | ||
| 14 | public static final Message ILLEGAL_IDENTIFIER = create(Type.ERROR, "illegal_identifier"); | ||
| 15 | public static final Message RESERVED_IDENTIFIER = create(Type.ERROR, "reserved_identifier"); | ||
| 16 | public static final Message ILLEGAL_DOC_COMMENT_END = create(Type.ERROR, "illegal_doc_comment_end"); | ||
| 17 | |||
| 18 | public static final Message STYLE_VIOLATION = create(Type.WARNING, "style_violation"); | ||
| 19 | |||
| 20 | public final Type type; | ||
| 21 | public final String textKey; | ||
| 22 | public final String longTextKey; | ||
| 23 | |||
| 24 | private Message(Type type, String textKey, String longTextKey) { | ||
| 25 | this.type = type; | ||
| 26 | this.textKey = textKey; | ||
| 27 | this.longTextKey = longTextKey; | ||
| 28 | } | ||
| 29 | |||
| 30 | public String format(Object[] args) { | ||
| 31 | return I18n.translateFormatted(textKey, args); | ||
| 32 | } | ||
| 33 | |||
| 34 | public String formatDetails(Object[] args) { | ||
| 35 | return I18n.translateOrEmpty(longTextKey, args); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static Message create(Type type, String name) { | ||
| 39 | return new Message(type, String.format("validation.message.%s", name), String.format("validation.message.%s.long", name)); | ||
| 40 | } | ||
| 41 | |||
| 42 | public enum Type { | ||
| 43 | INFO, | ||
| 44 | WARNING, | ||
| 45 | ERROR, | ||
| 46 | } | ||
| 47 | |||
| 48 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/ParameterizedMessage.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/ParameterizedMessage.java new file mode 100644 index 0000000..56b0ecc --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/ParameterizedMessage.java | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | import java.util.Arrays; | ||
| 4 | import java.util.Objects; | ||
| 5 | |||
| 6 | public class ParameterizedMessage { | ||
| 7 | |||
| 8 | public final Message message; | ||
| 9 | private final Object[] params; | ||
| 10 | |||
| 11 | public ParameterizedMessage(Message message, Object[] params) { | ||
| 12 | this.message = message; | ||
| 13 | this.params = params; | ||
| 14 | } | ||
| 15 | |||
| 16 | public String getText() { | ||
| 17 | return message.format(params); | ||
| 18 | } | ||
| 19 | |||
| 20 | public String getLongText() { | ||
| 21 | return message.formatDetails(params); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public boolean equals(Object o) { | ||
| 26 | if (this == o) return true; | ||
| 27 | if (o == null || getClass() != o.getClass()) return false; | ||
| 28 | ParameterizedMessage that = (ParameterizedMessage) o; | ||
| 29 | return Objects.equals(message, that.message) && | ||
| 30 | Arrays.equals(params, that.params); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public int hashCode() { | ||
| 35 | int result = Objects.hash(message); | ||
| 36 | result = 31 * result + Arrays.hashCode(params); | ||
| 37 | return result; | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public String toString() { | ||
| 42 | return String.format("ParameterizedMessage { message: %s, params: %s }", message, Arrays.toString(params)); | ||
| 43 | } | ||
| 44 | |||
| 45 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java new file mode 100644 index 0000000..fe91cc1 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | import java.util.Arrays; | ||
| 4 | |||
| 5 | public class PrintValidatable implements Validatable { | ||
| 6 | |||
| 7 | public static final PrintValidatable INSTANCE = new PrintValidatable(); | ||
| 8 | |||
| 9 | @Override | ||
| 10 | public void addMessage(ParameterizedMessage message) { | ||
| 11 | String text = message.getText(); | ||
| 12 | String longText = message.getLongText(); | ||
| 13 | String type; | ||
| 14 | switch (message.message.type) { | ||
| 15 | case INFO: | ||
| 16 | type = "info"; | ||
| 17 | break; | ||
| 18 | case WARNING: | ||
| 19 | type = "warning"; | ||
| 20 | break; | ||
| 21 | case ERROR: | ||
| 22 | type = "error"; | ||
| 23 | break; | ||
| 24 | default: | ||
| 25 | throw new IllegalStateException("unreachable"); | ||
| 26 | } | ||
| 27 | System.out.printf("%s: %s\n", type, text); | ||
| 28 | if (!longText.isEmpty()) { | ||
| 29 | Arrays.stream(longText.split("\n")).forEach(s -> System.out.printf(" %s\n", s)); | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void clearMessages() { | ||
| 35 | } | ||
| 36 | |||
| 37 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/StandardValidation.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/StandardValidation.java new file mode 100644 index 0000000..871b59d --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/StandardValidation.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | public class StandardValidation { | ||
| 4 | |||
| 5 | public static boolean notBlank(ValidationContext vc, String value) { | ||
| 6 | if (value.trim().isEmpty()) { | ||
| 7 | vc.raise(Message.EMPTY_FIELD); | ||
| 8 | return false; | ||
| 9 | } | ||
| 10 | return true; | ||
| 11 | } | ||
| 12 | |||
| 13 | public static boolean isInt(ValidationContext vc, String value) { | ||
| 14 | if (!notBlank(vc, value)) return false; | ||
| 15 | try { | ||
| 16 | Integer.parseInt(value); | ||
| 17 | return true; | ||
| 18 | } catch (NumberFormatException e) { | ||
| 19 | vc.raise(Message.NOT_INT); | ||
| 20 | return false; | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | public static boolean isIntInRange(ValidationContext vc, String value, int min, int max) { | ||
| 25 | if (!isInt(vc, value)) return false; | ||
| 26 | int intVal = Integer.parseInt(value); | ||
| 27 | if (intVal < min || intVal > max) { | ||
| 28 | vc.raise(Message.FIELD_OUT_OF_RANGE_INT, min, max); | ||
| 29 | return false; | ||
| 30 | } | ||
| 31 | return true; | ||
| 32 | } | ||
| 33 | |||
| 34 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/Validatable.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/Validatable.java new file mode 100644 index 0000000..765ee08 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/Validatable.java | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | public interface Validatable { | ||
| 4 | |||
| 5 | void addMessage(ParameterizedMessage message); | ||
| 6 | |||
| 7 | void clearMessages(); | ||
| 8 | |||
| 9 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java new file mode 100644 index 0000000..d38fc21 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | package cuchaz.enigma.utils.validation; | ||
| 2 | |||
| 3 | import java.util.*; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | import cuchaz.enigma.utils.validation.Message.Type; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * A context for user input validation. Handles collecting error messages and | ||
| 11 | * displaying the errors on the relevant input fields. UIs using validation | ||
| 12 | * often have two stages of applying changes: validating all the input fields, | ||
| 13 | * then checking if there's any errors or unconfirmed warnings, and if not, | ||
| 14 | * then actually applying the changes. This allows for easily collecting | ||
| 15 | * multiple errors and displaying them to the user at the same time. | ||
| 16 | */ | ||
| 17 | public class ValidationContext { | ||
| 18 | |||
| 19 | private Validatable activeElement = null; | ||
| 20 | private final Set<Validatable> elements = new HashSet<>(); | ||
| 21 | private final List<ParameterizedMessage> messages = new ArrayList<>(); | ||
| 22 | |||
| 23 | /** | ||
| 24 | * Sets the currently active element (such as an input field). Any messages | ||
| 25 | * raised while this is set get displayed on this element. | ||
| 26 | * | ||
| 27 | * @param v the active element to set, or {@code null} to unset | ||
| 28 | */ | ||
| 29 | public void setActiveElement(@Nullable Validatable v) { | ||
| 30 | if (v != null) { | ||
| 31 | elements.add(v); | ||
| 32 | } | ||
| 33 | activeElement = v; | ||
| 34 | } | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Raises a message. If there's a currently active element, also notifies | ||
| 38 | * that element about the message. | ||
| 39 | * | ||
| 40 | * @param message the message to raise | ||
| 41 | * @param args the arguments used when formatting the message text | ||
| 42 | */ | ||
| 43 | public void raise(Message message, Object... args) { | ||
| 44 | ParameterizedMessage pm = new ParameterizedMessage(message, args); | ||
| 45 | if (activeElement != null) { | ||
| 46 | activeElement.addMessage(pm); | ||
| 47 | } | ||
| 48 | messages.add(pm); | ||
| 49 | } | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Returns whether the validation context currently has no messages that | ||
| 53 | * block executing actions, such as errors and unconfirmed warnings. | ||
| 54 | * | ||
| 55 | * @return whether the program can proceed executing and the UI is in a | ||
| 56 | * valid state | ||
| 57 | */ | ||
| 58 | public boolean canProceed() { | ||
| 59 | // TODO on warnings, wait until user confirms | ||
| 60 | return messages.stream().noneMatch(m -> m.message.type == Type.ERROR); | ||
| 61 | } | ||
| 62 | |||
| 63 | public List<ParameterizedMessage> getMessages() { | ||
| 64 | return Collections.unmodifiableList(messages); | ||
| 65 | } | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Clears all currently pending messages. This should be called whenever the | ||
| 69 | * interface starts getting validated, to get rid of old messages. | ||
| 70 | */ | ||
| 71 | public void reset() { | ||
| 72 | activeElement = null; | ||
| 73 | elements.forEach(Validatable::clearMessages); | ||
| 74 | elements.clear(); | ||
| 75 | messages.clear(); | ||
| 76 | } | ||
| 77 | |||
| 78 | } | ||
diff --git a/enigma/src/main/resources/lang/de_de.json b/enigma/src/main/resources/lang/de_de.json new file mode 100644 index 0000000..ef41da1 --- /dev/null +++ b/enigma/src/main/resources/lang/de_de.json | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | { | ||
| 2 | "language": "German", | ||
| 3 | |||
| 4 | "general.retry": "Wiederholen", | ||
| 5 | |||
| 6 | "popup_menu.editor_tab.close": "Schließen", | ||
| 7 | "popup_menu.editor_tab.close_all": "Alle schließen", | ||
| 8 | "popup_menu.editor_tab.close_others": "Andere schließen", | ||
| 9 | "popup_menu.editor_tab.close_left": "Alle links hiervon schließen", | ||
| 10 | "popup_menu.editor_tab.close_right": "Alle rechts hiervon schließen", | ||
| 11 | |||
| 12 | "editor.decompiling": "Dekompiliere...", | ||
| 13 | "editor.decompile_error": "Ein Fehler ist während des Dekompilierens aufgetreten.", | ||
| 14 | |||
| 15 | "validation.message.empty_field": "Dieses Feld muss ausgefüllt werden.", | ||
| 16 | "validation.message.not_int": "Wert muss eine ganze Zahl sein.", | ||
| 17 | "validation.message.field_out_of_range_int": "Wert muss eine ganze Zahl zwischen %d und %d sein.", | ||
| 18 | "validation.message.field_length_out_of_range": "Wert muss kürzer als %d Zeichen sein.", | ||
| 19 | "validation.message.nonunique_name_class": "Name „%s“ ist in „%s“ nicht eindeutig.", | ||
| 20 | "validation.message.nonunique_name": "Name „%s“ ist nicht eindeutig.", | ||
| 21 | "validation.message.illegal_class_name": "„%s“ ist kein gültiger Klassenname.", | ||
| 22 | "validation.message.illegal_identifier": "„%s“ ist kein gültiger Name.", | ||
| 23 | "validation.message.illegal_identifier.long": "Ungültiges Zeichen „%2$s“ an Position %3$d.", | ||
| 24 | "validation.message.illegal_doc_comment_end": "Javadoc-Kommentar darf die Zeichenfolge „*/“ nicht enthalten.", | ||
| 25 | "validation.message.reserved_identifier": "„%s“ ist ein reservierter Name." | ||
| 26 | } \ No newline at end of file | ||
diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index dbf4b93..b8db4b0 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json | |||
| @@ -1,6 +1,8 @@ | |||
| 1 | { | 1 | { |
| 2 | "language": "English", | 2 | "language": "English", |
| 3 | 3 | ||
| 4 | "general.retry": "Retry", | ||
| 5 | |||
| 4 | "mapping_format.enigma_file": "Enigma File", | 6 | "mapping_format.enigma_file": "Enigma File", |
| 5 | "mapping_format.enigma_directory": "Enigma Directory", | 7 | "mapping_format.enigma_directory": "Enigma Directory", |
| 6 | "mapping_format.enigma_zip": "Enigma ZIP", | 8 | "mapping_format.enigma_zip": "Enigma ZIP", |
| @@ -69,6 +71,14 @@ | |||
| 69 | "popup_menu.zoom.in": "Zoom in", | 71 | "popup_menu.zoom.in": "Zoom in", |
| 70 | "popup_menu.zoom.out": "Zoom out", | 72 | "popup_menu.zoom.out": "Zoom out", |
| 71 | "popup_menu.zoom.reset": "Reset zoom", | 73 | "popup_menu.zoom.reset": "Reset zoom", |
| 74 | "popup_menu.editor_tab.close": "Close", | ||
| 75 | "popup_menu.editor_tab.close_all": "Close All", | ||
| 76 | "popup_menu.editor_tab.close_others": "Close Others", | ||
| 77 | "popup_menu.editor_tab.close_left": "Close All to the Left", | ||
| 78 | "popup_menu.editor_tab.close_right": "Close All to the Right", | ||
| 79 | |||
| 80 | "editor.decompiling": "Decompiling...", | ||
| 81 | "editor.decompile_error": "An error was encountered while decompiling.", | ||
| 72 | 82 | ||
| 73 | "info_panel.classes.obfuscated": "Obfuscated Classes", | 83 | "info_panel.classes.obfuscated": "Obfuscated Classes", |
| 74 | "info_panel.classes.deobfuscated": "De-obfuscated Classes", | 84 | "info_panel.classes.deobfuscated": "De-obfuscated Classes", |
| @@ -79,8 +89,8 @@ | |||
| 79 | "info_panel.identifier.method": "Method", | 89 | "info_panel.identifier.method": "Method", |
| 80 | "info_panel.identifier.constructor": "Constructor", | 90 | "info_panel.identifier.constructor": "Constructor", |
| 81 | "info_panel.identifier.class": "Class", | 91 | "info_panel.identifier.class": "Class", |
| 82 | "info_panel.identifier.type_descriptor": "TypeDescriptor", | 92 | "info_panel.identifier.type_descriptor": "Type Descriptor", |
| 83 | "info_panel.identifier.method_descriptor": "MethodDescriptor", | 93 | "info_panel.identifier.method_descriptor": "Method Descriptor", |
| 84 | "info_panel.identifier.modifier": "Modifier", | 94 | "info_panel.identifier.modifier": "Modifier", |
| 85 | "info_panel.identifier.index": "Index", | 95 | "info_panel.identifier.index": "Index", |
| 86 | "info_panel.editor.class.decompiling": "(decompiling...)", | 96 | "info_panel.editor.class.decompiling": "(decompiling...)", |
| @@ -149,12 +159,23 @@ | |||
| 149 | "message.mark_deobf.text": "%s marked %s as deobfuscated", | 159 | "message.mark_deobf.text": "%s marked %s as deobfuscated", |
| 150 | "message.remove_mapping.text": "%s removed mappings for %s", | 160 | "message.remove_mapping.text": "%s removed mappings for %s", |
| 151 | "message.rename.text": "%s renamed %s to %s", | 161 | "message.rename.text": "%s renamed %s to %s", |
| 152 | |||
| 153 | "status.disconnected": "Disconnected.", | 162 | "status.disconnected": "Disconnected.", |
| 154 | "status.connected": "Connected.", | 163 | "status.connected": "Connected.", |
| 155 | "status.connected_user_count": "Connected (%d users).", | 164 | "status.connected_user_count": "Connected (%d users).", |
| 156 | "status.ready": "Ready.", | 165 | "status.ready": "Ready.", |
| 157 | 166 | ||
| 167 | "validation.message.empty_field": "This field is required.", | ||
| 168 | "validation.message.not_int": "Value must be an integer.", | ||
| 169 | "validation.message.field_out_of_range_int": "Value must be an integer between %d and %d.", | ||
| 170 | "validation.message.field_length_out_of_range": "Value must be less than %d characters long.", | ||
| 171 | "validation.message.nonunique_name_class": "Name '%s' is not unique in '%s'.", | ||
| 172 | "validation.message.nonunique_name": "Name '%s' is not unique.", | ||
| 173 | "validation.message.illegal_class_name": "'%s' is not a valid class name.", | ||
| 174 | "validation.message.illegal_identifier": "'%s' is not a valid identifier.", | ||
| 175 | "validation.message.illegal_identifier.long": "Invalid character '%2$s' at position %3$d.", | ||
| 176 | "validation.message.illegal_doc_comment_end": "Javadoc comment cannot contain the character sequence '*/'.", | ||
| 177 | "validation.message.reserved_identifier": "'%s' is a reserved identifier.", | ||
| 178 | |||
| 158 | "crash.title": "%s - Crash Report", | 179 | "crash.title": "%s - Crash Report", |
| 159 | "crash.summary": "%s has crashed! =(", | 180 | "crash.summary": "%s has crashed! =(", |
| 160 | "crash.export": "Export", | 181 | "crash.export": "Export", |