diff options
| author | 2020-06-03 20:16:10 +0200 | |
|---|---|---|
| committer | 2020-06-03 19:16:10 +0100 | |
| commit | 5a286d58e740f1aa5944488c602f5abc1318f6ca (patch) | |
| tree | dfde9eff0c744906b3571390af0f6a6e3be92a91 /enigma-swing/src/main/java | |
| 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>
Diffstat (limited to 'enigma-swing/src/main/java')
29 files changed, 2195 insertions, 1043 deletions
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/DecompiledClassSource.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java deleted file mode 100644 index aca5d72..0000000 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ /dev/null | |||
| @@ -1,160 +0,0 @@ | |||
| 1 | package cuchaz.enigma.gui; | ||
| 2 | |||
| 3 | import cuchaz.enigma.EnigmaProject; | ||
| 4 | import cuchaz.enigma.EnigmaServices; | ||
| 5 | import cuchaz.enigma.analysis.EntryReference; | ||
| 6 | import cuchaz.enigma.source.Token; | ||
| 7 | 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; | ||
| 12 | import cuchaz.enigma.translation.Translator; | ||
| 13 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 14 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 15 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 16 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; | ||
| 19 | |||
| 20 | import javax.annotation.Nullable; | ||
| 21 | import java.util.*; | ||
| 22 | |||
| 23 | public class DecompiledClassSource { | ||
| 24 | private final ClassEntry classEntry; | ||
| 25 | |||
| 26 | private final SourceIndex obfuscatedIndex; | ||
| 27 | private SourceIndex remappedIndex; | ||
| 28 | |||
| 29 | private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class); | ||
| 30 | |||
| 31 | public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) { | ||
| 32 | this.classEntry = classEntry; | ||
| 33 | this.obfuscatedIndex = index; | ||
| 34 | this.remappedIndex = index; | ||
| 35 | } | ||
| 36 | |||
| 37 | public static DecompiledClassSource text(ClassEntry classEntry, String text) { | ||
| 38 | return new DecompiledClassSource(classEntry, new SourceIndex(text)); | ||
| 39 | } | ||
| 40 | |||
| 41 | public void remapSource(EnigmaProject project, Translator translator) { | ||
| 42 | highlightedTokens.clear(); | ||
| 43 | |||
| 44 | SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens()); | ||
| 45 | |||
| 46 | SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator)); | ||
| 47 | remappedIndex = obfuscatedIndex.remapTo(remapResult); | ||
| 48 | } | ||
| 49 | |||
| 50 | private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) { | ||
| 51 | EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token); | ||
| 52 | |||
| 53 | Entry<?> entry = reference.getNameableEntry(); | ||
| 54 | Entry<?> translatedEntry = translator.translate(entry); | ||
| 55 | |||
| 56 | if (project.isRenamable(reference)) { | ||
| 57 | if (isDeobfuscated(entry, translatedEntry)) { | ||
| 58 | highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED); | ||
| 59 | return translatedEntry.getSourceRemapName(); | ||
| 60 | } else { | ||
| 61 | Optional<String> proposedName = proposeName(project, entry); | ||
| 62 | if (proposedName.isPresent()) { | ||
| 63 | highlightToken(movedToken, TokenHighlightType.PROPOSED); | ||
| 64 | return proposedName.get(); | ||
| 65 | } | ||
| 66 | |||
| 67 | highlightToken(movedToken, TokenHighlightType.OBFUSCATED); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | String defaultName = generateDefaultName(translatedEntry); | ||
| 72 | if (defaultName != null) { | ||
| 73 | return defaultName; | ||
| 74 | } | ||
| 75 | |||
| 76 | return null; | ||
| 77 | } | ||
| 78 | |||
| 79 | private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) { | ||
| 80 | EnigmaServices services = project.getEnigma().getServices(); | ||
| 81 | |||
| 82 | return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { | ||
| 83 | EntryRemapper mapper = project.getMapper(); | ||
| 84 | Collection<Entry<?>> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT); | ||
| 85 | |||
| 86 | return resolved.stream() | ||
| 87 | .map(e -> nameProposalService.proposeName(e, mapper)) | ||
| 88 | .filter(Optional::isPresent) | ||
| 89 | .map(Optional::get); | ||
| 90 | }).findFirst(); | ||
| 91 | } | ||
| 92 | |||
| 93 | @Nullable | ||
| 94 | private String generateDefaultName(Entry<?> entry) { | ||
| 95 | if (entry instanceof LocalVariableDefEntry) { | ||
| 96 | LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry; | ||
| 97 | |||
| 98 | int index = localVariable.getIndex(); | ||
| 99 | if (localVariable.isArgument()) { | ||
| 100 | List<TypeDescriptor> arguments = localVariable.getParent().getDesc().getArgumentDescs(); | ||
| 101 | return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments); | ||
| 102 | } else { | ||
| 103 | return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc()); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | return null; | ||
| 108 | } | ||
| 109 | |||
| 110 | private boolean isDeobfuscated(Entry<?> entry, Entry<?> translatedEntry) { | ||
| 111 | return !entry.getName().equals(translatedEntry.getName()); | ||
| 112 | } | ||
| 113 | |||
| 114 | public ClassEntry getEntry() { | ||
| 115 | return classEntry; | ||
| 116 | } | ||
| 117 | |||
| 118 | public SourceIndex getIndex() { | ||
| 119 | return remappedIndex; | ||
| 120 | } | ||
| 121 | |||
| 122 | public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() { | ||
| 123 | return highlightedTokens; | ||
| 124 | } | ||
| 125 | |||
| 126 | private void highlightToken(Token token, TokenHighlightType highlightType) { | ||
| 127 | highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token); | ||
| 128 | } | ||
| 129 | |||
| 130 | public int getObfuscatedOffset(int deobfOffset) { | ||
| 131 | return getOffset(remappedIndex, obfuscatedIndex, deobfOffset); | ||
| 132 | } | ||
| 133 | |||
| 134 | public int getDeobfuscatedOffset(int obfOffset) { | ||
| 135 | return getOffset(obfuscatedIndex, remappedIndex, obfOffset); | ||
| 136 | } | ||
| 137 | |||
| 138 | private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) { | ||
| 139 | int relativeOffset = 0; | ||
| 140 | |||
| 141 | Iterator<Token> fromTokenItr = fromIndex.referenceTokens().iterator(); | ||
| 142 | Iterator<Token> toTokenItr = toIndex.referenceTokens().iterator(); | ||
| 143 | while (fromTokenItr.hasNext() && toTokenItr.hasNext()) { | ||
| 144 | Token fromToken = fromTokenItr.next(); | ||
| 145 | Token toToken = toTokenItr.next(); | ||
| 146 | if (fromToken.end > fromOffset) { | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | |||
| 150 | relativeOffset = toToken.end - fromToken.end; | ||
| 151 | } | ||
| 152 | |||
| 153 | return fromOffset + relativeOffset; | ||
| 154 | } | ||
| 155 | |||
| 156 | @Override | ||
| 157 | public String toString() { | ||
| 158 | return remappedIndex.getSource(); | ||
| 159 | } | ||
| 160 | } | ||
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 | } |