diff options
3 files changed, 53 insertions, 24 deletions
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java index 92d1bd7f..377f63c6 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/EditorPanel.java | |||
| @@ -1,11 +1,13 @@ | |||
| 1 | package cuchaz.enigma.gui.panels; | 1 | package cuchaz.enigma.gui.panels; |
| 2 | 2 | ||
| 3 | import java.awt.AWTKeyStroke; | ||
| 3 | import java.awt.Color; | 4 | import java.awt.Color; |
| 4 | import java.awt.Component; | 5 | import java.awt.Component; |
| 5 | import java.awt.Font; | 6 | import java.awt.Font; |
| 6 | import java.awt.GridBagConstraints; | 7 | import java.awt.GridBagConstraints; |
| 7 | import java.awt.GridBagLayout; | 8 | import java.awt.GridBagLayout; |
| 8 | import java.awt.GridLayout; | 9 | import java.awt.GridLayout; |
| 10 | import java.awt.KeyboardFocusManager; | ||
| 9 | import java.awt.Rectangle; | 11 | import java.awt.Rectangle; |
| 10 | import java.awt.event.ActionEvent; | 12 | import java.awt.event.ActionEvent; |
| 11 | import java.awt.event.ActionListener; | 13 | import java.awt.event.ActionListener; |
| @@ -16,8 +18,11 @@ import java.awt.event.MouseEvent; | |||
| 16 | import java.util.ArrayList; | 18 | import java.util.ArrayList; |
| 17 | import java.util.Collection; | 19 | import java.util.Collection; |
| 18 | import java.util.Comparator; | 20 | import java.util.Comparator; |
| 21 | import java.util.HashSet; | ||
| 19 | import java.util.List; | 22 | import java.util.List; |
| 20 | import java.util.Map; | 23 | import java.util.Map; |
| 24 | import java.util.NavigableSet; | ||
| 25 | import java.util.Set; | ||
| 21 | 26 | ||
| 22 | import javax.swing.JButton; | 27 | import javax.swing.JButton; |
| 23 | import javax.swing.JComponent; | 28 | import javax.swing.JComponent; |
| @@ -120,6 +125,11 @@ public class EditorPanel { | |||
| 120 | customizeEditor(this.editor); | 125 | customizeEditor(this.editor); |
| 121 | this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); | 126 | this.editor.addCaretListener(event -> onCaretMove(event.getDot(), this.mouseIsPressed)); |
| 122 | 127 | ||
| 128 | // Remove the tab key from focus traversal keys (we give it a different meaning) | ||
| 129 | Set<AWTKeyStroke> focusTraversalKeys = new HashSet<>(this.editor.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)); | ||
| 130 | focusTraversalKeys.removeIf(key -> key.getKeyCode() == KeyEvent.VK_TAB); | ||
| 131 | this.editor.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, focusTraversalKeys); | ||
| 132 | |||
| 123 | // set unit increment to height of one line, the amount scrolled per | 133 | // set unit increment to height of one line, the amount scrolled per |
| 124 | // mouse wheel rotation is then controlled by OS settings | 134 | // mouse wheel rotation is then controlled by OS settings |
| 125 | this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight()); | 135 | this.editorScrollPane.getVerticalScrollBar().setUnitIncrement(this.editor.getFontMetrics(this.editor.getFont()).getHeight()); |
| @@ -199,6 +209,9 @@ public class EditorPanel { | |||
| 199 | EditorPanel.this.shouldNavigateOnClick = true; // CTRL | 209 | EditorPanel.this.shouldNavigateOnClick = true; // CTRL |
| 200 | break; | 210 | break; |
| 201 | } | 211 | } |
| 212 | } else if (event.getKeyCode() == KeyEvent.VK_TAB) { | ||
| 213 | EditorPanel.this.navigateToNextObfuscatedToken(); | ||
| 214 | event.consume(); | ||
| 202 | } | 215 | } |
| 203 | } | 216 | } |
| 204 | 217 | ||
| @@ -668,6 +681,36 @@ public class EditorPanel { | |||
| 668 | } | 681 | } |
| 669 | } | 682 | } |
| 670 | 683 | ||
| 684 | /** | ||
| 685 | * Navigate to the next obfuscated token that can be renamed. | ||
| 686 | * | ||
| 687 | * <p>If the tokens are damaged, then this method should not be called | ||
| 688 | * synchronously. Instead, the call should be wrapped in a | ||
| 689 | * {@link SwingUtilities#invokeLater(Runnable)}. Failing to do so | ||
| 690 | * will induce invalid token highlighting regions. | ||
| 691 | */ | ||
| 692 | public void navigateToNextObfuscatedToken() { | ||
| 693 | int caretPos = this.getEditor().getCaretPosition(); | ||
| 694 | Token token = this.getToken(this.getEditor().getCaretPosition()); | ||
| 695 | NavigableSet<Token> obfuscatedTokens = this.getSource().getTokenStore().getByType().get(RenamableTokenType.OBFUSCATED); | ||
| 696 | Token next; | ||
| 697 | |||
| 698 | if (token == null) { | ||
| 699 | next = obfuscatedTokens.higher(new Token(caretPos, caretPos, null)); | ||
| 700 | } else { | ||
| 701 | next = obfuscatedTokens.higher(token); | ||
| 702 | } | ||
| 703 | |||
| 704 | if (next == null) { | ||
| 705 | // Wrap to start of document | ||
| 706 | next = obfuscatedTokens.pollFirst(); | ||
| 707 | } | ||
| 708 | |||
| 709 | if (next != null) { | ||
| 710 | this.navigateToToken(next); | ||
| 711 | } | ||
| 712 | } | ||
| 713 | |||
| 671 | public void navigateToToken(Token token) { | 714 | public void navigateToToken(Token token) { |
| 672 | if (token == null) { | 715 | if (token == null) { |
| 673 | throw new IllegalArgumentException("Token cannot be null!"); | 716 | throw new IllegalArgumentException("Token cannot be null!"); |
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java index 6a8d7347..cf336e00 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java | |||
| @@ -23,8 +23,6 @@ import cuchaz.enigma.gui.events.ConvertingTextFieldListener; | |||
| 23 | import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; | 23 | import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; |
| 24 | import cuchaz.enigma.gui.util.GuiUtil; | 24 | import cuchaz.enigma.gui.util.GuiUtil; |
| 25 | import cuchaz.enigma.gui.util.ScaleUtil; | 25 | import cuchaz.enigma.gui.util.ScaleUtil; |
| 26 | import cuchaz.enigma.source.RenamableTokenType; | ||
| 27 | import cuchaz.enigma.source.Token; | ||
| 28 | import cuchaz.enigma.translation.mapping.AccessModifier; | 26 | import cuchaz.enigma.translation.mapping.AccessModifier; |
| 29 | import cuchaz.enigma.translation.mapping.EntryChange; | 27 | import cuchaz.enigma.translation.mapping.EntryChange; |
| 30 | import cuchaz.enigma.translation.mapping.EntryMapping; | 28 | import cuchaz.enigma.translation.mapping.EntryMapping; |
| @@ -197,36 +195,19 @@ public class IdentifierPanel { | |||
| 197 | 195 | ||
| 198 | @Override | 196 | @Override |
| 199 | public void onStopEditing(ConvertingTextField field, StopEditingCause cause) { | 197 | public void onStopEditing(ConvertingTextField field, StopEditingCause cause) { |
| 198 | EditorPanel e = gui.getActiveEditor(); | ||
| 199 | |||
| 200 | if (cause != StopEditingCause.ABORT) { | 200 | if (cause != StopEditingCause.ABORT) { |
| 201 | vc.reset(); | 201 | vc.reset(); |
| 202 | vc.setActiveElement(field); | 202 | vc.setActiveElement(field); |
| 203 | doRename(field.getText()); | 203 | doRename(field.getText()); |
| 204 | 204 | ||
| 205 | if (cause == StopEditingCause.TAB) { | 205 | if (cause == StopEditingCause.TAB && e != null) { |
| 206 | EditorPanel editor = gui.getActiveEditor(); | 206 | // invokeLater as per the method's javadocs |
| 207 | 207 | SwingUtilities.invokeLater(e::navigateToNextObfuscatedToken); | |
| 208 | if (editor == null) { | ||
| 209 | return; | ||
| 210 | } | ||
| 211 | |||
| 212 | Token token = editor.getToken(editor.getEditor().getCaretPosition()); | ||
| 213 | |||
| 214 | SwingUtilities.invokeLater(() -> { | ||
| 215 | Token next = editor.getSource().getTokenStore().getByType().get(RenamableTokenType.OBFUSCATED).higher(token); | ||
| 216 | |||
| 217 | if (next == null) { | ||
| 218 | editor.getEditor().requestFocusInWindow(); | ||
| 219 | } else { | ||
| 220 | editor.navigateToToken(next); | ||
| 221 | } | ||
| 222 | }); | ||
| 223 | |||
| 224 | return; | ||
| 225 | } | 208 | } |
| 226 | } | 209 | } |
| 227 | 210 | ||
| 228 | EditorPanel e = gui.getActiveEditor(); | ||
| 229 | |||
| 230 | if (e != null) { | 211 | if (e != null) { |
| 231 | e.getEditor().requestFocusInWindow(); | 212 | e.getEditor().requestFocusInWindow(); |
| 232 | } | 213 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java index 4648decc..3c8c2383 100644 --- a/enigma/src/main/java/cuchaz/enigma/utils/I18n.java +++ b/enigma/src/main/java/cuchaz/enigma/utils/I18n.java | |||
| @@ -101,6 +101,11 @@ public class I18n { | |||
| 101 | List<String> availableTranslations; | 101 | List<String> availableTranslations; |
| 102 | 102 | ||
| 103 | try (InputStream is = cl.getResourceAsStream("lang/index.txt")) { | 103 | try (InputStream is = cl.getResourceAsStream("lang/index.txt")) { |
| 104 | if (is == null) { | ||
| 105 | // This scenario should only really happen when launching from an IDE that does not run the necessary gradle tasks | ||
| 106 | throw new IOException("Resource 'lang/index.txt' not found"); | ||
| 107 | } | ||
| 108 | |||
| 104 | availableTranslations = Arrays.asList( | 109 | availableTranslations = Arrays.asList( |
| 105 | new String(is.readAllBytes(), StandardCharsets.UTF_8) | 110 | new String(is.readAllBytes(), StandardCharsets.UTF_8) |
| 106 | .split("\n") | 111 | .split("\n") |