From dc26f6c131cbf77e3c4bd1db7aa10b3f618f7017 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Sun, 8 Mar 2015 16:34:28 -0400 Subject: [PATCH] TextField updates, and bug fixes from some recent commits. - Added modified copy of slick.gui.TextField: added CTRL+BACKSPACE for word deletion, and disabled left/right arrows to move cursor and CTRL+Z to undo. - Fixed musicEnded() incorrectly setting the "trackEnded" upon swapping tracks. This has caused some nasty loops since #38. - Fixed a null pointer exception in OsuDB from 0b03912. - Fixed the song menu search bar transition being incorrectly reset when hitting backspace on a 0-length query. Signed-off-by: Jeffrey Han --- pom.xml | 1 + src/itdelatrisu/opsu/OsuGroupList.java | 1 + .../opsu/audio/MusicController.java | 5 +- src/itdelatrisu/opsu/db/OsuDB.java | 12 +- src/itdelatrisu/opsu/states/SongMenu.java | 14 +- src/org/newdawn/slick/gui/TextField.java | 537 ++++++++++++++++++ 6 files changed, 559 insertions(+), 11 deletions(-) create mode 100644 src/org/newdawn/slick/gui/TextField.java diff --git a/pom.xml b/pom.xml index ca5097f9..241dc9d1 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,7 @@ org/newdawn/slick/GameContainer.* org/newdawn/slick/Image.* org/newdawn/slick/Music.* + org/newdawn/slick/gui/TextField.* org/newdawn/slick/openal/AudioInputStream* org/newdawn/slick/openal/OpenALStreamPlayer* org/newdawn/slick/openal/SoundStore* diff --git a/src/itdelatrisu/opsu/OsuGroupList.java b/src/itdelatrisu/opsu/OsuGroupList.java index d6bbf363..bbfe4a3f 100644 --- a/src/itdelatrisu/opsu/OsuGroupList.java +++ b/src/itdelatrisu/opsu/OsuGroupList.java @@ -254,6 +254,7 @@ public class OsuGroupList { /** * Returns the OsuGroupNode at an index, disregarding expansions. + * @param index the node index */ public OsuGroupNode getBaseNode(int index) { if (index < 0 || index >= size()) diff --git a/src/itdelatrisu/opsu/audio/MusicController.java b/src/itdelatrisu/opsu/audio/MusicController.java index a38f0316..6c84e7ad 100644 --- a/src/itdelatrisu/opsu/audio/MusicController.java +++ b/src/itdelatrisu/opsu/audio/MusicController.java @@ -111,7 +111,10 @@ public class MusicController { player = new Music(file.getPath(), true); player.addListener(new MusicListener() { @Override - public void musicEnded(Music music) { trackEnded = true; } + public void musicEnded(Music music) { + if (music == player) // don't fire if music swapped + trackEnded = true; + } @Override public void musicSwapped(Music music, Music newMusic) {} diff --git a/src/itdelatrisu/opsu/db/OsuDB.java b/src/itdelatrisu/opsu/db/OsuDB.java index d73c946c..52466eee 100644 --- a/src/itdelatrisu/opsu/db/OsuDB.java +++ b/src/itdelatrisu/opsu/db/OsuDB.java @@ -76,12 +76,6 @@ public class OsuDB { // create the database createDatabase(); - // retrieve the cache size - getCacheSize(); - - // check the database version - checkVersion(); - // prepare sql statements try { insertStmt = connection.prepareStatement( @@ -96,6 +90,12 @@ public class OsuDB { } catch (SQLException e) { ErrorHandler.error("Failed to prepare beatmap statements.", e, true); } + + // retrieve the cache size + getCacheSize(); + + // check the database version + checkVersion(); } /** diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 8a337c55..c83c25c3 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -199,6 +199,9 @@ public class SongMenu extends BasicGameState { /** Time, in milliseconds, for fading the search bar. */ private int searchTransitionTimer = SEARCH_TRANSITION_TIME; + /** The text length of the last string in the search TextField. */ + private int lastSearchTextLength = -1; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -831,11 +834,14 @@ public class SongMenu extends BasicGameState { if ((c > 31 && c < 127) || key == Input.KEY_BACK) { searchTimer = 0; int textLength = search.getText().length(); - if (key == Input.KEY_BACK) { - if (textLength == 0) + if (lastSearchTextLength != textLength) { + if (key == Input.KEY_BACK) { + if (textLength == 0) + searchTransitionTimer = 0; + } else if (textLength == 1) searchTransitionTimer = 0; - } else if (textLength == 1) - searchTransitionTimer = 0; + lastSearchTextLength = textLength; + } } break; } diff --git a/src/org/newdawn/slick/gui/TextField.java b/src/org/newdawn/slick/gui/TextField.java new file mode 100644 index 00000000..6696ff9f --- /dev/null +++ b/src/org/newdawn/slick/gui/TextField.java @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2013, Slick2D + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Slick2D nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.newdawn.slick.gui; + +import org.lwjgl.Sys; +import org.newdawn.slick.Color; +import org.newdawn.slick.Font; +import org.newdawn.slick.Graphics; +import org.newdawn.slick.Input; +import org.newdawn.slick.geom.Rectangle; + +/** + * A single text field supporting text entry + * + * @author kevin + */ +@SuppressWarnings("unused") +public class TextField extends AbstractComponent { + /** The key repeat interval */ + private static final int INITIAL_KEY_REPEAT_INTERVAL = 400; + /** The key repeat interval */ + private static final int KEY_REPEAT_INTERVAL = 50; + + /** The width of the field */ + private int width; + + /** The height of the field */ + private int height; + + /** The location in the X coordinate */ + protected int x; + + /** The location in the Y coordinate */ + protected int y; + + /** The maximum number of characters allowed to be input */ + private int maxCharacter = 10000; + + /** The value stored in the text field */ + private String value = ""; + + /** The font used to render text in the field */ + private Font font; + + /** The border color - null if no border */ + private Color border = Color.white; + + /** The text color */ + private Color text = Color.white; + + /** The background color - null if no background */ + private Color background = new Color(0, 0, 0, 0.5f); + + /** The current cursor position */ + private int cursorPos; + + /** True if the cursor should be visible */ + private boolean visibleCursor = true; + + /** The last key pressed */ + private int lastKey = -1; + + /** The last character pressed */ + private char lastChar = 0; + + /** The time since last key repeat */ + private long repeatTimer; + + /** The text before the paste in */ + private String oldText; + + /** The cursor position before the paste */ + private int oldCursorPos; + + /** True if events should be consumed by the field */ + private boolean consume = true; + + /** + * Create a new text field + * + * @param container + * The container rendering this field + * @param font + * The font to use in the text field + * @param x + * The x coordinate of the top left corner of the text field + * @param y + * The y coordinate of the top left corner of the text field + * @param width + * The width of the text field + * @param height + * The height of the text field + * @param listener + * The listener to add to the text field + */ + public TextField(GUIContext container, Font font, int x, int y, int width, + int height, ComponentListener listener) { + this(container,font,x,y,width,height); + addListener(listener); + } + + /** + * Create a new text field + * + * @param container + * The container rendering this field + * @param font + * The font to use in the text field + * @param x + * The x coordinate of the top left corner of the text field + * @param y + * The y coordinate of the top left corner of the text field + * @param width + * The width of the text field + * @param height + * The height of the text field + */ + public TextField(GUIContext container, Font font, int x, int y, int width, + int height) { + super(container); + + this.font = font; + + setLocation(x, y); + this.width = width; + this.height = height; + } + + /** + * Indicate if the input events should be consumed by this field + * + * @param consume True if events should be consumed by this field + */ + public void setConsumeEvents(boolean consume) { + this.consume = consume; + } + + /** + * Deactivate the key input handling for this field + */ + public void deactivate() { + setFocus(false); + } + + /** + * Moves the component. + * + * @param x + * X coordinate + * @param y + * Y coordinate + */ + @Override + public void setLocation(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Returns the position in the X coordinate + * + * @return x + */ + @Override + public int getX() { + return x; + } + + /** + * Returns the position in the Y coordinate + * + * @return y + */ + @Override + public int getY() { + return y; + } + + /** + * Get the width of the component + * + * @return The width of the component + */ + @Override + public int getWidth() { + return width; + } + + /** + * Get the height of the component + * + * @return The height of the component + */ + @Override + public int getHeight() { + return height; + } + + /** + * Set the background color. Set to null to disable the background + * + * @param color + * The color to use for the background + */ + public void setBackgroundColor(Color color) { + background = color; + } + + /** + * Set the border color. Set to null to disable the border + * + * @param color + * The color to use for the border + */ + public void setBorderColor(Color color) { + border = color; + } + + /** + * Set the text color. + * + * @param color + * The color to use for the text + */ + public void setTextColor(Color color) { + text = color; + } + + /** + * @see org.newdawn.slick.gui.AbstractComponent#render(org.newdawn.slick.gui.GUIContext, + * org.newdawn.slick.Graphics) + */ + @Override + public void render(GUIContext container, Graphics g) { + if (lastKey != -1) { + if (input.isKeyDown(lastKey)) { + if (repeatTimer < System.currentTimeMillis()) { + repeatTimer = System.currentTimeMillis() + KEY_REPEAT_INTERVAL; + keyPressed(lastKey, lastChar); + } + } else { + lastKey = -1; + } + } + Rectangle oldClip = g.getClip(); + g.setWorldClip(x,y,width, height); + + // Someone could have set a color for me to blend... + Color clr = g.getColor(); + + if (background != null) { + g.setColor(background.multiply(clr)); + g.fillRect(x, y, width, height); + } + g.setColor(text.multiply(clr)); + Font temp = g.getFont(); + + int cpos = font.getWidth(value.substring(0, cursorPos)); + int tx = 0; + if (cpos > width) { + tx = width - cpos - font.getWidth("_"); + } + + g.translate(tx + 2, 0); + g.setFont(font); + g.drawString(value, x + 1, y + 1); + + if (hasFocus() && visibleCursor) { + g.drawString("_", x + 1 + cpos + 2, y + 1); + } + + g.translate(-tx - 2, 0); + + if (border != null) { + g.setColor(border.multiply(clr)); + g.drawRect(x, y, width, height); + } + g.setColor(clr); + g.setFont(temp); + g.clearWorldClip(); + g.setClip(oldClip); + } + + /** + * Get the value in the text field + * + * @return The value in the text field + */ + public String getText() { + return value; + } + + /** + * Set the value to be displayed in the text field + * + * @param value + * The value to be displayed in the text field + */ + public void setText(String value) { + this.value = value; + if (cursorPos > value.length()) { + cursorPos = value.length(); + } + } + + /** + * Set the position of the cursor + * + * @param pos + * The new position of the cursor + */ + public void setCursorPos(int pos) { + cursorPos = pos; + if (cursorPos > value.length()) { + cursorPos = value.length(); + } + } + + /** + * Indicate whether the mouse cursor should be visible or not + * + * @param visibleCursor + * True if the mouse cursor should be visible + */ + public void setCursorVisible(boolean visibleCursor) { + this.visibleCursor = visibleCursor; + } + + /** + * Set the length of the allowed input + * + * @param length + * The length of the allowed input + */ + public void setMaxLength(int length) { + maxCharacter = length; + if (value.length() > maxCharacter) { + value = value.substring(0, maxCharacter); + } + } + + /** + * Do the paste into the field, overrideable for custom behaviour + * + * @param text The text to be pasted in + */ + protected void doPaste(String text) { + recordOldPosition(); + + for (int i=0;i 0) { + cursorPos--; + } + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + */ } else if (key == Input.KEY_RIGHT) { /* + if (cursorPos < value.length()) { + cursorPos++; + } + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + */ } else if (key == Input.KEY_BACK) { + if ((cursorPos > 0) && (value.length() > 0)) { + if (input.isKeyDown(Input.KEY_LCONTROL) || input.isKeyDown(Input.KEY_RCONTROL)) { + int sp = 0; + boolean startSpace = Character.isWhitespace(value.charAt(cursorPos - 1)); + boolean charSeen = false; + for (int i = cursorPos - 1; i >= 0; i--) { + boolean isSpace = Character.isWhitespace(value.charAt(i)); + if (!startSpace && isSpace) { + sp = i; + break; + } else if (startSpace) { + if (charSeen && isSpace) { + sp = i + 1; + break; + } else if (!charSeen && !isSpace) + charSeen = true; + } + } + if (cursorPos < value.length()) + value = value.substring(0, sp) + value.substring(cursorPos); + else + value = value.substring(0, sp); + cursorPos = sp; + } else { + if (cursorPos < value.length()) { + value = value.substring(0, cursorPos - 1) + + value.substring(cursorPos); + } else { + value = value.substring(0, cursorPos - 1); + } + cursorPos--; + } + } + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + } else if (key == Input.KEY_DELETE) { + if (value.length() > cursorPos) { + value = value.substring(0,cursorPos) + value.substring(cursorPos+1); + } + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + } else if ((c < 127) && (c > 31) && (value.length() < maxCharacter)) { + if (cursorPos < value.length()) { + value = value.substring(0, cursorPos) + c + + value.substring(cursorPos); + } else { + value = value.substring(0, cursorPos) + c; + } + cursorPos++; + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + } else if (key == Input.KEY_RETURN) { + notifyListeners(); + // Nobody more will be notified + if (consume) { + container.getInput().consumeEvent(); + } + } + + } + } + + /** + * @see org.newdawn.slick.gui.AbstractComponent#setFocus(boolean) + */ + @Override + public void setFocus(boolean focus) { + lastKey = -1; + + super.setFocus(focus); + } +}