From a0fcae837d7346b788cb225ad318e82145b59033 Mon Sep 17 00:00:00 2001
From: Jeffrey Han <itdelatrisu@gmail.com>
Date: Sun, 8 Jan 2017 17:44:33 -0500
Subject: [PATCH] Support Unicode input in TextFields. Only tested in Windows
 for Microsoft Chinese/Japanese keyboards.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
---
 .../opsu/states/DownloadsMenu.java            | 12 ++++++++-
 src/itdelatrisu/opsu/states/SongMenu.java     | 25 ++++++++++++++-----
 src/itdelatrisu/opsu/ui/Fonts.java            | 14 +++++++++++
 src/org/newdawn/slick/Input.java              | 16 +++++++++---
 src/org/newdawn/slick/gui/TextField.java      |  2 +-
 5 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java
index d25f56a6..15ed264a 100644
--- a/src/itdelatrisu/opsu/states/DownloadsMenu.java
+++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java
@@ -56,6 +56,7 @@
 import org.newdawn.slick.Image;
 import org.newdawn.slick.Input;
 import org.newdawn.slick.SlickException;
+import org.newdawn.slick.UnicodeFont;
 import org.newdawn.slick.gui.TextField;
 import org.newdawn.slick.state.BasicGameState;
 import org.newdawn.slick.state.StateBasedGame;
@@ -125,6 +126,9 @@ private enum Page { RESET, CURRENT, PREVIOUS, NEXT };
 
 	/** The search textfield. */
 	private TextField search;
+	
+	/** The search font. */
+	private UnicodeFont searchFont;
 
 	/**
 	 * Delay timer, in milliseconds, before running another search.
@@ -314,8 +318,9 @@ public void init(GameContainer container, StateBasedGame game)
 		// search
 		searchTimer = SEARCH_DELAY;
 		searchResultString = "Loading data from server...";
+		searchFont = Fonts.DEFAULT;
 		search = new TextField(
-				container, Fonts.DEFAULT, (int) baseX, (int) searchY,
+				container, searchFont, (int) baseX, (int) searchY,
 				(int) searchWidth, Fonts.MEDIUM.getLineHeight()
 		);
 		search.setBackgroundColor(Colors.BLACK_BG_NORMAL);
@@ -929,6 +934,11 @@ public void keyPressed(int key, char c) {
 		default:
 			// wait for user to finish typing
 			if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) {
+				// load glyphs
+				if (c > 255)
+					Fonts.loadGlyphs(searchFont, c);
+
+				// reset search timer
 				searchTimer = 0;
 				pageDir = Page.RESET;
 			}
diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java
index 8298b6b6..7621db26 100644
--- a/src/itdelatrisu/opsu/states/SongMenu.java
+++ b/src/itdelatrisu/opsu/states/SongMenu.java
@@ -73,6 +73,7 @@
 import org.newdawn.slick.Input;
 import org.newdawn.slick.SlickException;
 import org.newdawn.slick.SpriteSheet;
+import org.newdawn.slick.UnicodeFont;
 import org.newdawn.slick.gui.TextField;
 import org.newdawn.slick.state.BasicGameState;
 import org.newdawn.slick.state.StateBasedGame;
@@ -172,6 +173,9 @@ public SongNode(BeatmapSetNode node, int index) {
 	/** The search textfield. */
 	private TextField search;
 
+	/** The search font. */
+	private UnicodeFont searchFont;
+
 	/**
 	 * Delay timer, in milliseconds, before running another search.
 	 * This is overridden by character entry (reset) and 'esc' (immediate search).
@@ -272,7 +276,7 @@ private void reloadBeatmaps() {
 	private int searchTransitionTimer = SEARCH_TRANSITION_TIME;
 
 	/** The text length of the last string in the search TextField. */
-	private int lastSearchTextLength = -1;
+	private int lastSearchTextLength = 0;
 
 	/** Whether the song folder changed (notified via the watch service). */
 	private boolean songFolderChanged = false;
@@ -419,8 +423,9 @@ public boolean menuClicked(int index) {
 		// search
 		int textFieldX = (int) (width * 0.7125f + Fonts.BOLD.getWidth("Search: "));
 		int textFieldY = (int) (headerY + Fonts.BOLD.getLineHeight() / 2);
+		searchFont = Fonts.BOLD;
 		search = new TextField(
-				container, Fonts.BOLD, textFieldX, textFieldY,
+				container, searchFont, textFieldX, textFieldY,
 				(int) (width * 0.99f) - textFieldX, Fonts.BOLD.getLineHeight()
 		);
 		search.setBackgroundColor(Color.transparent);
@@ -854,9 +859,11 @@ else if (reloadThread.isFinished()) {
 					BeatmapSetList.get().init();
 					if (search.getText().isEmpty()) {  // cleared search
 						// use previous start/focus if possible
-						if (oldFocusNode != null)
+						if (oldFocusNode != null) {
 							setFocus(oldFocusNode.getNode(), oldFocusNode.getIndex(), true, true);
-						else
+							songChangeTimer.setTime(songChangeTimer.getDuration());
+							musicIconBounceTimer.setTime(musicIconBounceTimer.getDuration());
+						} else
 							setFocus(BeatmapSetList.get().getRandomNode(), -1, true, true);
 					} else {
 						int size = BeatmapSetList.get().size();
@@ -1117,6 +1124,7 @@ public void keyPressed(int key, char c) {
 				searchTimer = SEARCH_DELAY;
 				searchTransitionTimer = 0;
 				searchResultString = null;
+				lastSearchTextLength = 0;
 			} else {
 				// return to main menu
 				SoundController.playSound(SoundEffect.MENUBACK);
@@ -1238,8 +1246,12 @@ public void keyPressed(int key, char c) {
 			break;
 		default:
 			// wait for user to finish typing
-			// TODO: accept all characters (current conditions are from TextField class)
-			if ((c > 31 && c < 127) || key == Input.KEY_BACK) {
+			if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) {
+				// load glyphs
+				if (c > 255)
+					Fonts.loadGlyphs(searchFont, c);
+
+				// reset search timer
 				searchTimer = 0;
 				int textLength = search.getText().length();
 				if (lastSearchTextLength != textLength) {
@@ -1772,6 +1784,7 @@ private void reloadBeatmaps(final boolean fullReload) {
 		searchTransitionTimer = SEARCH_TRANSITION_TIME;
 		searchResultString = null;
 		lastBackgroundImage = null;
+		lastSearchTextLength = 0;
 
 		// reload songs in new thread
 		reloadThread = new BeatmapReloadThread(fullReload);
diff --git a/src/itdelatrisu/opsu/ui/Fonts.java b/src/itdelatrisu/opsu/ui/Fonts.java
index feafbeec..fc0636a3 100644
--- a/src/itdelatrisu/opsu/ui/Fonts.java
+++ b/src/itdelatrisu/opsu/ui/Fonts.java
@@ -115,6 +115,20 @@ public static void loadGlyphs(UnicodeFont font, String s) {
 		}
 	}
 
+	/**
+	 * Adds and loads glyphs for a font.
+	 * @param font the font to add the glyphs to
+	 * @param c the character to load
+	 */
+	public static void loadGlyphs(UnicodeFont font, char c) {
+		font.addGlyphs(c, c);
+		try {
+			font.loadGlyphs();
+		} catch (SlickException e) {
+			Log.warn(String.format("Failed to load glyphs for codepoint '%d'.", (int) c), e);
+		}
+	}
+
 	/**
 	 * Wraps the given string into a list of split lines based on the width.
 	 * @param font the font used to draw the string
diff --git a/src/org/newdawn/slick/Input.java b/src/org/newdawn/slick/Input.java
index 3dd677c7..533dd90a 100644
--- a/src/org/newdawn/slick/Input.java
+++ b/src/org/newdawn/slick/Input.java
@@ -1200,9 +1200,10 @@ public void poll(int width, int height) {
 		
 		while (Keyboard.next()) {
 			if (Keyboard.getEventKeyState()) {
-				int eventKey = resolveEventKey(Keyboard.getEventKey(), Keyboard.getEventCharacter());
+				char eventCh = Keyboard.getEventCharacter();
+				int eventKey = resolveEventKey(Keyboard.getEventKey(), eventCh);
 				
-				keys[eventKey] = Keyboard.getEventCharacter();
+				keys[eventKey] = eventCh;
 				pressed[eventKey] = true;
 				nextRepeat[eventKey] = System.currentTimeMillis() + keyRepeatInitial;
 				
@@ -1211,14 +1212,15 @@ public void poll(int width, int height) {
 					KeyListener listener = (KeyListener) keyListeners.get(i);
 					
 					if (listener.isAcceptingInput()) {
-						listener.keyPressed(eventKey, Keyboard.getEventCharacter());
+						listener.keyPressed(eventKey, eventCh);
 						if (consumed) {
 							break;
 						}
 					}
 				}
 			} else {
-				int eventKey = resolveEventKey(Keyboard.getEventKey(), Keyboard.getEventCharacter());
+				char eventCh = Keyboard.getEventCharacter();
+				int eventKey = resolveEventKey(Keyboard.getEventKey(), eventCh);
 				nextRepeat[eventKey] = 0;
 				
 				consumed = false;
@@ -1226,6 +1228,12 @@ public void poll(int width, int height) {
 					KeyListener listener = (KeyListener) keyListeners.get(i);
 					if (listener.isAcceptingInput()) {
 						listener.keyReleased(eventKey, keys[eventKey]);
+						// Fix Unicode input in some keyboards (Chinese, Japanese, etc.)
+						// not firing keypress events: fire them here.
+						if (eventCh != Keyboard.CHAR_NONE && keys[eventKey] != eventCh) {
+							listener.keyPressed(eventKey, eventCh);
+							listener.keyReleased(eventKey, eventCh);
+						}
 						if (consumed) {
 							break;
 						}
diff --git a/src/org/newdawn/slick/gui/TextField.java b/src/org/newdawn/slick/gui/TextField.java
index 73505127..d1bee53f 100644
--- a/src/org/newdawn/slick/gui/TextField.java
+++ b/src/org/newdawn/slick/gui/TextField.java
@@ -509,7 +509,7 @@ public void keyPressed(int key, char c) {
 				if (consume) {
 					container.getInput().consumeEvent();
 				}
-			} else if (c != Keyboard.CHAR_NONE && key != Input.KEY_TAB && value.length() < maxCharacter) {
+			} else if (c > 31 && value.length() < maxCharacter) {
 				if (cursorPos < value.length()) {
 					value = value.substring(0, cursorPos) + c
 							+ value.substring(cursorPos);